blob: 2d42226a27eb68f8321a860c2dfe50310004cd60 [file] [edit]
/*
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
}