diff --git a/test/cri-containerd/policy_test.go b/test/cri-containerd/policy_test.go index 93327eccd0..f3ea3a23dc 100644 --- a/test/cri-containerd/policy_test.go +++ b/test/cri-containerd/policy_test.go @@ -5,18 +5,31 @@ package cri_containerd import ( "context" + "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 + +type configSideEffect func(*runtime.CreateContainerRequest) error + +func withMountConstraints(mc []securitypolicy.MountConfig) configOpt { + return func(config *securitypolicy.ContainerConfig) error { + config.Mounts = append(config.Mounts, mc...) + return nil + } +} + func securityPolicyFromContainers(containers []securitypolicy.ContainerConfig) (string, error) { pc, err := helpers.PolicyContainersFromConfigs(containers) if err != nil { @@ -39,7 +52,7 @@ func sandboxSecurityPolicy(t *testing.T) string { return policyString } -func alpineSecurityPolicy(t *testing.T) string { +func alpineSecurityPolicy(t *testing.T, opts ...configOpt) string { defaultContainers := helpers.DefaultContainerConfigs() alpineContainer := securitypolicy.NewContainerConfig( "alpine:latest", @@ -50,6 +63,12 @@ func alpineSecurityPolicy(t *testing.T) string { []securitypolicy.MountConfig{}, ) + for _, o := range opts { + if err := o(&alpineContainer); err != nil { + t.Fatalf("failed to apply configOpt: %s", err) + } + } + containers := append(defaultContainers, alpineContainer) policyString, err := securityPolicyFromContainers(containers) if err != nil { @@ -58,7 +77,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, @@ -117,3 +136,201 @@ func Test_RunSimpleAlpineContainer_WithPolicy_Allowed(t *testing.T) { startContainer(t, client, ctx, containerID) stopContainer(t, client, ctx, containerID) } + +func Test_RunContainer_WithMountConstraints_Allowed(t *testing.T) { + requireFeatures(t, featureLCOW) + pullRequiredLCOWImages(t, []string{imageLcowK8sPause, imageLcowAlpine}) + + client := newTestRuntimeClient(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + type config struct { + name string + sideEffect configSideEffect + opts []configOpt + } + + for _, testConfig := range []config{ + { + name: "DefaultMounts", + sideEffect: func(_ *runtime.CreateContainerRequest) error { + return nil + }, + opts: []configOpt{}, + }, + { + name: "SandboxMountRW", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append(req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + }) + return nil + }, + opts: []configOpt{withMountConstraints([]securitypolicy.MountConfig{ + { + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + }, + })}, + }, + { + name: "SandboxMountRO", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append(req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + Readonly: true, + }) + return nil + }, + opts: []configOpt{withMountConstraints([]securitypolicy.MountConfig{ + { + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + Readonly: true, + }, + })}, + }, + } { + 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, + ) + + if err := testConfig.sideEffect(containerRequest); err != nil { + t.Fatalf("failed to apply containerRequest side effect: %s", err) + } + + containerID := createContainer(t, client, ctx, containerRequest) + defer removeContainer(t, client, ctx, containerID) + defer stopContainer(t, client, ctx, containerID) + }) + } +} + +func Test_RunContainer_WithMountConstraints_NotAllowed(t *testing.T) { + requireFeatures(t, featureLCOW) + pullRequiredLCOWImages(t, []string{imageLcowK8sPause, imageLcowAlpine}) + + client := newTestRuntimeClient(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + type config struct { + name string + sideEffect configSideEffect + opts []configOpt + expectedError string + } + + testSandboxMountOpts := []configOpt{ + withMountConstraints([]securitypolicy.MountConfig{ + { + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + }, + }), + } + for _, testConfig := range []config{ + { + name: "InvalidSandboxMountSource", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append(req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/invalid/path", + ContainerPath: "/container/path", + }) + return nil + }, + opts: testSandboxMountOpts, + expectedError: "is not allowed by mount constraints", + }, + { + name: "InvalidSandboxMountDestination", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append(req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path/invalid", + }) + return nil + }, + opts: testSandboxMountOpts, + expectedError: "is not allowed by mount constraints", + }, + { + name: "InvalidSandboxMountFlagRO", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append(req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + Readonly: true, + }) + return nil + }, + opts: testSandboxMountOpts, + expectedError: "is not allowed by mount constraints", + }, + { + name: "InvalidSandboxMountFlagRW", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append(req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + }) + return nil + }, + opts: []configOpt{withMountConstraints([]securitypolicy.MountConfig{ + { + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + Readonly: true, + }, + })}, + expectedError: "is not allowed by mount constraints", + }, + } { + 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, + "alipne-with-policy", + "alpine:latest", + validPolicyAlpineCommand, + sandboxRequest.Config, + ) + + if err := testConfig.sideEffect(containerRequest); err != nil { + t.Fatalf("failed to apply containerRequest side effect: %s", err) + } + + 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) + } + }) + } +}