diff --git a/pkg/securitypolicy/opts.go b/pkg/securitypolicy/opts.go new file mode 100644 index 0000000000..32885ce666 --- /dev/null +++ b/pkg/securitypolicy/opts.go @@ -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 + } +} diff --git a/test/cri-containerd/layer_integrity_test.go b/test/cri-containerd/layer_integrity_test.go index b4ae92ea59..b9b8f94457 100644 --- a/test/cri-containerd/layer_integrity_test.go +++ b/test/cri-containerd/layer_integrity_test.go @@ -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()) @@ -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) } diff --git a/test/cri-containerd/policy_test.go b/test/cri-containerd/policy_test.go index 2f8d5e8d8b..334ab6012e 100644 --- a/test/cri-containerd/policy_test.go +++ b/test/cri-containerd/policy_test.go @@ -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 { @@ -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", @@ -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 { @@ -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, @@ -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) @@ -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) @@ -148,8 +146,8 @@ 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", @@ -157,7 +155,7 @@ func syncContainerRequests( 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", }) @@ -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", @@ -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" @@ -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) + } + }) + } +} diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/opts.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/opts.go new file mode 100644 index 0000000000..32885ce666 --- /dev/null +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/opts.go @@ -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 + } +}