| /* |
| Copyright 2017 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package validate |
| |
| import ( |
| "context" |
| "fmt" |
| "strings" |
| "time" |
| |
| . "github.com/onsi/ginkgo/v2" |
| . "github.com/onsi/gomega" |
| selinux "github.com/opencontainers/selinux/go-selinux" |
| internalapi "k8s.io/cri-api/pkg/apis" |
| runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" |
| |
| "sigs.k8s.io/cri-tools/pkg/framework" |
| ) |
| |
| var _ = framework.KubeDescribe("SELinux", func() { |
| f := framework.NewDefaultCRIFramework() |
| |
| var ( |
| rc internalapi.RuntimeService |
| ic internalapi.ImageManagerService |
| ) |
| |
| if selinux.GetEnabled() { |
| BeforeEach(func() { |
| rc = f.CRIClient.CRIRuntimeClient |
| ic = f.CRIClient.CRIImageClient |
| }) |
| |
| Context("runtime should support selinux", func() { |
| var ( |
| sandboxID string |
| sandboxConfig *runtimeapi.PodSandboxConfig |
| ) |
| |
| sandboxTests := func(privileged bool) { |
| It("should work with just selinux level set", func(ctx SpecContext) { |
| options := &runtimeapi.SELinuxOption{ |
| Level: "s0", |
| } |
| containerID := createContainerWithSelinux(ctx, rc, ic, sandboxID, sandboxConfig, options, privileged, true, true) |
| checkContainerSelinux(ctx, rc, containerID, true) |
| }) |
| |
| It("should work with selinux set", func(ctx SpecContext) { |
| options := &runtimeapi.SELinuxOption{ |
| User: "system_u", |
| Role: "system_r", |
| Type: "svirt_lxc_net_t", |
| Level: "s0:c4,c5", |
| } |
| containerID := createContainerWithSelinux(ctx, rc, ic, sandboxID, sandboxConfig, options, privileged, true, true) |
| checkContainerSelinux(ctx, rc, containerID, true) |
| }) |
| |
| It("should error on create with wrong options", func(ctx SpecContext) { |
| options := &runtimeapi.SELinuxOption{ |
| User: "system_u", |
| Role: "system_r", |
| Type: "svirt_lxc_net_t", |
| // s0,c4,c5 is wrong, should have been s0:c4,c5 |
| Level: "s0,c4,c5", |
| } |
| createContainerWithSelinux(ctx, rc, ic, sandboxID, sandboxConfig, options, privileged, false, false) |
| }) |
| |
| It("mount label should have correct role and type", func(ctx SpecContext) { |
| containerID := createContainerWithSelinux(ctx, rc, ic, sandboxID, sandboxConfig, nil, privileged, true, true) |
| checkMountLabelRoleType(ctx, rc, containerID) |
| }) |
| |
| It("mount label should have category", func(ctx SpecContext) { |
| containerID := createContainerWithSelinux(ctx, rc, ic, sandboxID, sandboxConfig, nil, privileged, true, true) |
| checkMountLabelMCS(ctx, rc, containerID) |
| }) |
| |
| It("process label should have correct role and type", func(ctx SpecContext) { |
| containerID := createContainerWithSelinux(ctx, rc, ic, sandboxID, sandboxConfig, nil, privileged, true, true) |
| checkProcessLabelRoleType(ctx, rc, containerID, privileged) |
| }) |
| |
| It("process label should have category", func(ctx SpecContext) { |
| containerID := createContainerWithSelinux(ctx, rc, ic, sandboxID, sandboxConfig, nil, privileged, true, true) |
| checkProcessLabelMCS(ctx, rc, containerID, privileged) |
| }) |
| |
| It("should create containers with the same process label", func(ctx SpecContext) { |
| containerID := createContainerWithSelinux(ctx, rc, ic, sandboxID, sandboxConfig, nil, privileged, true, true) |
| containerID2 := createContainerWithSelinux(ctx, rc, ic, sandboxID, sandboxConfig, nil, privileged, true, true) |
| label1 := checkProcessLabelMCS(ctx, rc, containerID, privileged) |
| label2 := checkProcessLabelMCS(ctx, rc, containerID2, privileged) |
| Expect(label1).To(Equal(label2)) |
| }) |
| } |
| |
| Context("when single pod sandbox is not privileged", func() { |
| BeforeEach(func(ctx SpecContext) { |
| sandboxID, sandboxConfig = framework.CreatePodSandboxForContainer(ctx, rc) |
| }) |
| |
| AfterEach(func(ctx SpecContext) { |
| framework.CleanupPodSandbox(ctx, rc, sandboxID) |
| }) |
| |
| sandboxTests(false) |
| }) |
| |
| Context("when single pod sandbox is privileged", func() { |
| BeforeEach(func(ctx SpecContext) { |
| sandboxID, sandboxConfig = createPrivilegedPodSandbox(ctx, rc, true) |
| }) |
| |
| AfterEach(func(ctx SpecContext) { |
| framework.CleanupPodSandbox(ctx, rc, sandboxID) |
| }) |
| |
| sandboxTests(true) |
| }) |
| |
| Context("when multiple pod sandboxes are not privileged", func() { |
| var ( |
| sandboxID2 string |
| sandboxConfig2 *runtimeapi.PodSandboxConfig |
| ) |
| |
| BeforeEach(func(ctx SpecContext) { |
| sandboxID, sandboxConfig = framework.CreatePodSandboxForContainer(ctx, rc) |
| sandboxID2, sandboxConfig2 = framework.CreatePodSandboxForContainer(ctx, rc) |
| }) |
| |
| AfterEach(func(ctx SpecContext) { |
| framework.CleanupPodSandbox(ctx, rc, sandboxID) |
| framework.CleanupPodSandbox(ctx, rc, sandboxID2) |
| }) |
| |
| It("should create containers with different process labels", func(ctx SpecContext) { |
| containerID := createContainerWithSelinux(ctx, rc, ic, sandboxID, sandboxConfig, nil, false, true, true) |
| containerID2 := createContainerWithSelinux(ctx, rc, ic, sandboxID2, sandboxConfig2, nil, false, true, true) |
| label1 := checkProcessLabelMCS(ctx, rc, containerID, false) |
| label2 := checkProcessLabelMCS(ctx, rc, containerID2, false) |
| Expect(label1).NotTo(Equal(label2)) |
| }) |
| }) |
| }) |
| } |
| }) |
| |
| func createContainerWithSelinux(ctx context.Context, rc internalapi.RuntimeService, ic internalapi.ImageManagerService, sandboxID string, sandboxConfig *runtimeapi.PodSandboxConfig, options *runtimeapi.SELinuxOption, privileged, shouldStart, shouldCreate bool) string { |
| By("create a container with selinux") |
| |
| containerName := "selinux-test-" + framework.NewUUID() |
| containerConfig := &runtimeapi.ContainerConfig{ |
| Metadata: framework.BuildContainerMetadata(containerName, framework.DefaultAttempt), |
| Image: &runtimeapi.ImageSpec{Image: framework.TestContext.TestImageList.DefaultTestContainerImage}, |
| Command: pauseCmd, |
| Linux: &runtimeapi.LinuxContainerConfig{ |
| SecurityContext: &runtimeapi.LinuxContainerSecurityContext{ |
| SelinuxOptions: options, |
| Privileged: privileged, |
| }, |
| }, |
| } |
| |
| containerID, err := framework.CreateContainerWithError(ctx, rc, ic, containerConfig, sandboxID, sandboxConfig) |
| if !shouldCreate { |
| Expect(err).To(HaveOccurred()) |
| |
| return "" |
| } |
| |
| Expect(err).NotTo(HaveOccurred()) |
| |
| By("start container with selinux") |
| |
| err = rc.StartContainer(ctx, containerID) |
| if shouldStart { |
| Expect(err).NotTo(HaveOccurred()) |
| } else { |
| Expect(err).To(HaveOccurred()) |
| } |
| |
| // wait container running |
| Eventually(func() runtimeapi.ContainerState { |
| return getContainerStatus(ctx, rc, containerID).GetState() |
| }, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING)) |
| |
| return containerID |
| } |
| |
| func checkContainerSelinux(ctx context.Context, rc internalapi.RuntimeService, containerID string, shouldRun bool) { |
| By("get container status") |
| |
| status, err := rc.ContainerStatus(ctx, containerID, false) |
| Expect(err).NotTo(HaveOccurred()) |
| |
| if shouldRun { |
| Expect(status.GetStatus().GetExitCode()).To(Equal(int32(0))) |
| } else { |
| Expect(status.GetStatus().GetExitCode()).NotTo(Equal(int32(0))) |
| |
| return |
| } |
| |
| cmd := []string{"touch", "foo"} |
| stdout, stderr, err := rc.ExecSync(ctx, containerID, cmd, time.Duration(defaultExecSyncTimeout)*time.Second) |
| msg := fmt.Sprintf("cmd %v, stdout %q, stderr %q", cmd, stdout, stderr) |
| Expect(err).NotTo(HaveOccurred(), msg) |
| } |
| |
| func checkMountLabelRoleType(ctx context.Context, rc internalapi.RuntimeService, containerID string) { |
| // Check that the mount label policy is correct |
| cmd := []string{"cat", "/proc/1/mountinfo"} |
| stdout, stderr, err := rc.ExecSync(ctx, containerID, cmd, time.Duration(defaultExecSyncTimeout)*time.Second) |
| msg := fmt.Sprintf("cmd %v, stdout %q, stderr %q", cmd, stdout, stderr) |
| Expect(err).NotTo(HaveOccurred(), msg) |
| Expect(string(stdout)).To(ContainSubstring(":object_r:container_file_t:")) |
| } |
| |
| func checkProcessLabelRoleType(ctx context.Context, rc internalapi.RuntimeService, containerID string, privileged bool) { |
| // Check that the process label policy is correct |
| cmd := []string{"cat", "/proc/self/attr/current"} |
| stdout, stderr, err := rc.ExecSync(ctx, containerID, cmd, time.Duration(defaultExecSyncTimeout)*time.Second) |
| label := strings.Trim(string(stdout), "\x00") |
| msg := fmt.Sprintf("cmd %v, stdout %q, stderr %q", cmd, stdout, stderr) |
| Expect(err).NotTo(HaveOccurred(), msg) |
| |
| if privileged { |
| Expect(label).To(ContainSubstring(":system_r:spc_t:")) |
| } else { |
| Expect(label).To(ContainSubstring(":system_r:container_t:")) |
| } |
| } |
| |
| func checkMountLabelMCS(ctx context.Context, rc internalapi.RuntimeService, containerID string) { |
| // Check that the mount label MCS is correct |
| cmd := []string{"cat", "/proc/1/mountinfo"} |
| stdout, stderr, err := rc.ExecSync(ctx, containerID, cmd, time.Duration(defaultExecSyncTimeout)*time.Second) |
| msg := fmt.Sprintf("cmd %v, stdout %q, stderr %q", cmd, stdout, stderr) |
| Expect(err).NotTo(HaveOccurred(), msg) |
| // check that a mount exists with MCS, where level is always s0 and there are two or more categories |
| Expect(string(stdout)).To(MatchRegexp(`,context="[^"]*:s0(-s0)?:c[0-9]+(,c[0-9]+)+",`)) |
| } |
| |
| func checkProcessLabelMCS(ctx context.Context, rc internalapi.RuntimeService, containerID string, privileged bool) string { |
| // Check that the process label MCS is correct |
| cmd := []string{"cat", "/proc/self/attr/current"} |
| stdout, stderr, err := rc.ExecSync(ctx, containerID, cmd, time.Duration(defaultExecSyncTimeout)*time.Second) |
| label := strings.Trim(string(stdout), "\x00") |
| msg := fmt.Sprintf("cmd %v, stdout %q, stderr %q", cmd, stdout, stderr) |
| Expect(err).NotTo(HaveOccurred(), msg) |
| |
| if privileged { |
| // check that a process label exists with optional MCS, where level is always s0 and we permit all categories |
| Expect(label).To(MatchRegexp(`:s0(-s0)?(:c0\.c1023)?$`)) |
| } else { |
| // check that a process label exists with MCS, where level is always s0 and there are two or more categories |
| Expect(label).To(MatchRegexp(`:s0(-s0)?:c[0-9]+(,c[0-9]+)+$`)) |
| } |
| |
| return label |
| } |