Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for security policy enforcement #1325

Merged
merged 3 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions pkg/securitypolicy/opts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package securitypolicy

type ContainerConfigOpt func(*ContainerConfig) error

// WithEnvVarRules adds environment variable constraints to container policy config.
func WithEnvVarRules(envs []EnvRuleConfig) ContainerConfigOpt {
return func(c *ContainerConfig) error {
c.EnvRules = append(c.EnvRules, envs...)
return nil
}
}

// WithExpectedMounts adds expected mounts to container policy config.
func WithExpectedMounts(em []string) ContainerConfigOpt {
return func(c *ContainerConfig) error {
c.ExpectedMounts = append(c.ExpectedMounts, em...)
return nil
}
}

// WithWorkingDir sets working directory in container policy config.
func WithWorkingDir(wd string) ContainerConfigOpt {
return func(c *ContainerConfig) error {
c.WorkingDir = wd
return nil
}
}
4 changes: 2 additions & 2 deletions test/cri-containerd/layer_integrity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

func Test_LCOW_Layer_Integrity(t *testing.T) {
requireFeatures(t, featureLCOWIntegrity, featureLCOW)
requireFeatures(t, featureLCOW, featureLCOWIntegrity)

client := newTestRuntimeClient(t)
ctx, cancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -85,7 +85,7 @@ func Test_LCOW_Layer_Integrity(t *testing.T) {

// Validate that verity target(s) present
output := shimDiagExecOutput(ctx, t, podID, []string{"ls", "-l", "/dev/mapper"})
filtered := filterStrings(strings.Split(output, "\n"), fmt.Sprintf("dm-verity-%s", scenario.layerType))
filtered := filterStrings(strings.Split(output, "\n"), fmt.Sprintf("verity-%s", scenario.layerType))
if len(filtered) == 0 {
t.Fatalf("expected verity targets for %s devices, none found.\n%s\n", scenario.layerType, output)
}
Expand Down
175 changes: 156 additions & 19 deletions test/cri-containerd/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,17 @@ import (
"strings"
"testing"

runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"

"github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers"
"github.com/Microsoft/hcsshim/pkg/annotations"
"github.com/Microsoft/hcsshim/pkg/securitypolicy"
"k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
)

var (
validPolicyAlpineCommand = []string{"ash", "-c", "echo 'Hello'"}
)

type configOpt func(*securitypolicy.ContainerConfig) error

func withExpectedMounts(em []string) configOpt {
return func(conf *securitypolicy.ContainerConfig) error {
conf.ExpectedMounts = append(conf.ExpectedMounts, em...)
return nil
}
}

func securityPolicyFromContainers(containers []securitypolicy.ContainerConfig) (string, error) {
pc, err := helpers.PolicyContainersFromConfigs(containers)
if err != nil {
Expand All @@ -50,7 +42,7 @@ func sandboxSecurityPolicy(t *testing.T) string {
return policyString
}

func alpineSecurityPolicy(t *testing.T) string {
func alpineSecurityPolicy(t *testing.T, opts ...securitypolicy.ContainerConfigOpt) string {
defaultContainers := helpers.DefaultContainerConfigs()
alpineContainer := securitypolicy.NewContainerConfig(
"alpine:latest",
Expand All @@ -61,6 +53,12 @@ func alpineSecurityPolicy(t *testing.T) string {
[]string{},
)

for _, o := range opts {
if err := o(&alpineContainer); err != nil {
t.Fatalf("failed to apply config opt: %s", err)
}
}

containers := append(defaultContainers, alpineContainer)
policyString, err := securityPolicyFromContainers(containers)
if err != nil {
Expand All @@ -69,7 +67,7 @@ func alpineSecurityPolicy(t *testing.T) string {
return policyString
}

func sandboxRequestWithPolicy(t *testing.T, policy string) *v1alpha2.RunPodSandboxRequest {
func sandboxRequestWithPolicy(t *testing.T, policy string) *runtime.RunPodSandboxRequest {
return getRunPodSandboxRequest(
t,
lcowRuntimeHandler,
Expand All @@ -82,7 +80,7 @@ func sandboxRequestWithPolicy(t *testing.T, policy string) *v1alpha2.RunPodSandb
}

func Test_RunPodSandbox_WithPolicy_Allowed(t *testing.T) {
requireFeatures(t, featureLCOW)
requireFeatures(t, featureLCOW, featureLCOWIntegrity)
pullRequiredLCOWImages(t, []string{imageLcowK8sPause})

sandboxPolicy := sandboxSecurityPolicy(t)
Expand All @@ -99,7 +97,7 @@ func Test_RunPodSandbox_WithPolicy_Allowed(t *testing.T) {
}

func Test_RunSimpleAlpineContainer_WithPolicy_Allowed(t *testing.T) {
requireFeatures(t, featureLCOW)
requireFeatures(t, featureLCOW, featureLCOWIntegrity)
pullRequiredLCOWImages(t, []string{imageLcowK8sPause, imageLcowAlpine})

alpinePolicy := alpineSecurityPolicy(t)
Expand Down Expand Up @@ -148,16 +146,16 @@ func syncContainerConfigs(writePath, waitPath string) (writer, waiter *securityp
func syncContainerRequests(
writer, waiter *securitypolicy.ContainerConfig,
podID string,
podConfig *v1alpha2.PodSandboxConfig,
) (writerReq, waiterReq *v1alpha2.CreateContainerRequest) {
podConfig *runtime.PodSandboxConfig,
) (writerReq, waiterReq *runtime.CreateContainerRequest) {
writerReq = getCreateContainerRequest(
podID,
"alpine-writer",
"alpine:latest",
writer.Command,
podConfig,
)
writerReq.Config.Mounts = append(writerReq.Config.Mounts, &v1alpha2.Mount{
writerReq.Config.Mounts = append(writerReq.Config.Mounts, &runtime.Mount{
HostPath: "sandbox://host/path",
ContainerPath: "/mnt/shared/container-A",
})
Expand All @@ -169,7 +167,7 @@ func syncContainerRequests(
waiter.Command,
podConfig,
)
waiterReq.Config.Mounts = append(waiterReq.Config.Mounts, &v1alpha2.Mount{
waiterReq.Config.Mounts = append(waiterReq.Config.Mounts, &runtime.Mount{
// The HostPath must be the same as for the "writer" container
HostPath: "sandbox://host/path",
ContainerPath: "/mnt/shared/container-B",
Expand Down Expand Up @@ -246,7 +244,7 @@ func Test_RunContainers_WithSyncHooks_InvalidWaitPath(t *testing.T) {
defer removeContainer(t, client, ctx, cidWriter)
defer stopContainer(t, client, ctx, cidWriter)

_, err = client.StartContainer(ctx, &v1alpha2.StartContainerRequest{
_, err = client.StartContainer(ctx, &runtime.StartContainerRequest{
ContainerId: cidWaiter,
})
expectedErrString := "timeout while waiting for path"
Expand All @@ -260,3 +258,142 @@ func Test_RunContainers_WithSyncHooks_InvalidWaitPath(t *testing.T) {
}
}
}

func Test_RunContainer_ValidContainerConfigs_Allowed(t *testing.T) {
type sideEffect func(*runtime.CreateContainerRequest)
type config struct {
name string
sf sideEffect
opts []securitypolicy.ContainerConfigOpt
}

requireFeatures(t, featureLCOW, featureLCOWIntegrity)
pullRequiredLCOWImages(t, []string{imageLcowK8sPause, imageLcowAlpine})

client := newTestRuntimeClient(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

for _, testConfig := range []config{
{
name: "WorkingDir",
sf: func(req *runtime.CreateContainerRequest) {
req.Config.WorkingDir = "/root"
},
opts: []securitypolicy.ContainerConfigOpt{securitypolicy.WithWorkingDir("/root")},
},
{
name: "EnvironmentVariable",
sf: func(req *runtime.CreateContainerRequest) {
req.Config.Envs = append(req.Config.Envs, &runtime.KeyValue{
Key: "KEY",
Value: "VALUE",
})
},
opts: []securitypolicy.ContainerConfigOpt{
securitypolicy.WithEnvVarRules(
[]securitypolicy.EnvRuleConfig{
{
Strategy: securitypolicy.EnvVarRuleString,
Rule: "KEY=VALUE",
},
}),
},
},
} {
t.Run(testConfig.name, func(t *testing.T) {
alpinePolicy := alpineSecurityPolicy(t, testConfig.opts...)
sandboxRequest := sandboxRequestWithPolicy(t, alpinePolicy)

podID := runPodSandbox(t, client, ctx, sandboxRequest)
defer removePodSandbox(t, client, ctx, podID)
defer stopPodSandbox(t, client, ctx, podID)

containerRequest := getCreateContainerRequest(
podID,
"alpine-with-policy",
"alpine:latest",
validPolicyAlpineCommand,
sandboxRequest.Config,
)
testConfig.sf(containerRequest)

containerID := createContainer(t, client, ctx, containerRequest)
startContainer(t, client, ctx, containerID)
defer removeContainer(t, client, ctx, containerID)
defer stopContainer(t, client, ctx, containerID)
})
}
}

func Test_RunContainer_InvalidContainerConfigs_NotAllowed(t *testing.T) {
type sideEffect func(*runtime.CreateContainerRequest)
type config struct {
name string
sf sideEffect
expectedError string
}

requireFeatures(t, featureLCOW, featureLCOWIntegrity)
pullRequiredLCOWImages(t, []string{imageLcowK8sPause, imageLcowAlpine})

client := newTestRuntimeClient(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

alpinePolicy := alpineSecurityPolicy(t)
for _, testConfig := range []config{
{
name: "InvalidWorkingDir",
sf: func(req *runtime.CreateContainerRequest) {
req.Config.WorkingDir = "/non/existent"
},
expectedError: "working_dir \"/non/existent\" unmatched by policy rule",
},
{
name: "InvalidCommand",
sf: func(req *runtime.CreateContainerRequest) {
req.Config.Command = []string{"ash", "-c", "echo 'invalid command'"}
},
expectedError: "command [ash -c echo 'invalid command'] doesn't match policy",
},
{
name: "InvalidEnvironmentVariable",
sf: func(req *runtime.CreateContainerRequest) {
req.Config.Envs = append(req.Config.Envs, &runtime.KeyValue{
Key: "KEY",
Value: "VALUE",
})
},
expectedError: "env variable KEY=VALUE unmatched by policy rule",
},
} {
t.Run(testConfig.name, func(t *testing.T) {
sandboxRequest := sandboxRequestWithPolicy(t, alpinePolicy)

podID := runPodSandbox(t, client, ctx, sandboxRequest)
defer removePodSandbox(t, client, ctx, podID)
defer stopPodSandbox(t, client, ctx, podID)

containerRequest := getCreateContainerRequest(
podID,
"alpine-with-policy",
"alpine:latest",
validPolicyAlpineCommand,
sandboxRequest.Config,
)
testConfig.sf(containerRequest)

containerID := createContainer(t, client, ctx, containerRequest)
_, err := client.StartContainer(ctx, &runtime.StartContainerRequest{
ContainerId: containerID,
})
if err == nil {
t.Fatal("expected container start failure")
}
if !strings.Contains(err.Error(), testConfig.expectedError) {
t.Fatalf("expected %q in error message, got: %q", testConfig.expectedError, err)
}
})
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.