Skip to content

Commit

Permalink
Add user namespaces tests
Browse files Browse the repository at this point in the history
Adding user namespaces tests for covering the `UsernamespaceMode`
supported by the CRI.

Fixes kubernetes-sigs#1348

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
  • Loading branch information
saschagrunert committed Feb 13, 2024
1 parent 8463eae commit f3747eb
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 3 deletions.
11 changes: 9 additions & 2 deletions .github/workflows/containerd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ jobs:
os: [ubuntu-22.04, windows-2019]
# not every command likes the slash in branch name.
# So will use format command to replace to either `_` or '/'
version: [main, 'release{0}1.6', 'release{0}1.7']
runtime: [io.containerd.runtime.v1.linux, io.containerd.runc.v1, io.containerd.runc.v2, containerd-shim-runhcs-v1]
version:
- main
# - 'release{0}1.6'
# - 'release{0}1.7'
runtime:
# - io.containerd.runtime.v1.linux
# - io.containerd.runc.v1
- io.containerd.runc.v2
# - containerd-shim-runhcs-v1
runc: [runc, crun]
exclude:
- runtime: io.containerd.runc.v1
Expand Down
7 changes: 7 additions & 0 deletions pkg/framework/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,13 @@ func RunPodSandbox(c internalapi.RuntimeService, config *runtimeapi.PodSandboxCo
return podID
}

// RunPodSandboxError runs a PodSandbox and expects an error.
func RunPodSandboxError(c internalapi.RuntimeService, config *runtimeapi.PodSandboxConfig) string {
podID, err := c.RunPodSandbox(context.TODO(), config, TestContext.RuntimeHandler)
Expect(err).To(HaveOccurred())
return podID
}

// CreatePodSandboxForContainer creates a PodSandbox for creating containers.
func CreatePodSandboxForContainer(c internalapi.RuntimeService) (string, *runtimeapi.PodSandboxConfig) {
podSandboxName := "create-PodSandbox-for-container-" + NewUUID()
Expand Down
16 changes: 16 additions & 0 deletions pkg/validate/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"

Expand Down Expand Up @@ -629,6 +630,21 @@ func verifyLogContents(podConfig *runtimeapi.PodSandboxConfig, logPath string, l
Expect(found).To(BeTrue(), "expected log %q (stream=%q) not found in logs %+v", log, stream, msgs)
}

// verifyLogContentsRe verifies the contents of container log using the provided regular expression pattern.
func verifyLogContentsRe(podConfig *runtimeapi.PodSandboxConfig, logPath string, pattern string, stream streamType) {
By("verify log contents using regex pattern")
msgs := parseLogLine(podConfig, logPath)

found := false
for _, msg := range msgs {
if matched, _ := regexp.MatchString(pattern, msg.log); matched && msg.stream == stream {
found = true
break
}
}
Expect(found).To(BeTrue(), "expected log pattern %q (stream=%q) to match logs %+v", pattern, stream, msgs)
}

// listContainerStatsForID lists container for containerID.
func listContainerStatsForID(c internalapi.RuntimeService, containerID string) *runtimeapi.ContainerStats {
By("List container stats for containerID: " + containerID)
Expand Down
158 changes: 157 additions & 1 deletion pkg/validate/security_context_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,100 @@ var _ = framework.KubeDescribe("Security Context", func() {
matchContainerOutput(podConfig, containerName, "Effective uid: 0\n")
})
})

Context("UserNamespaces", func() {
var (
podName string
defaultMapping = []*runtimeapi.IDMapping{{
ContainerId: 0,
HostId: 1000,
Length: 100000,
}}
)

BeforeEach(func() {
podName = "user-namespaces-pod-" + framework.NewUUID()
})

It("runtime should support NamespaceMode_POD", func() {
namespaceOption := &runtimeapi.NamespaceOption{
UsernsOptions: &runtimeapi.UserNamespace{
Mode: runtimeapi.NamespaceMode_POD,
Uids: defaultMapping,
Gids: defaultMapping,
},
}

hostLogPath, podLogPath := createLogTempDir(podName)
defer os.RemoveAll(hostLogPath)
podID, podConfig = createNamespacePodSandbox(rc, namespaceOption, podName, podLogPath)
containerName := runUserNamespaceContainer(rc, ic, podID, podConfig)

matchContainerOutputRe(podConfig, containerName, `\s+0\s+1000\s+100000\n`)
})

It("runtime should support NamespaceMode_NODE", func() {
namespaceOption := &runtimeapi.NamespaceOption{
UsernsOptions: &runtimeapi.UserNamespace{
Mode: runtimeapi.NamespaceMode_NODE,
},
}

hostLogPath, podLogPath := createLogTempDir(podName)
defer os.RemoveAll(hostLogPath)
podID, podConfig = createNamespacePodSandbox(rc, namespaceOption, podName, podLogPath)
containerName := runUserNamespaceContainer(rc, ic, podID, podConfig)

// 4294967295 means that the entire range is available
matchContainerOutputRe(podConfig, containerName, `\s+0\s+0\s+4294967295\n`)
})

It("runtime should fail if more than one mapping provided", func() {
wrongMapping := []*runtimeapi.IDMapping{{
ContainerId: 0,
HostId: 1000,
Length: 100000,
}, {
ContainerId: 0,
HostId: 2000,
Length: 100000,
}}
usernsOptions := &runtimeapi.UserNamespace{
Mode: runtimeapi.NamespaceMode_POD,
Uids: wrongMapping,
Gids: wrongMapping,
}

runUserNamespacePodWithError(rc, podName, usernsOptions)
})

It("runtime should fail if container ID 0 is not mapped", func() {
mapping := []*runtimeapi.IDMapping{{
ContainerId: 1,
HostId: 1000,
Length: 100000,
}}
usernsOptions := &runtimeapi.UserNamespace{
Mode: runtimeapi.NamespaceMode_POD,
Uids: mapping,
Gids: mapping,
}

runUserNamespacePodWithError(rc, podName, usernsOptions)
})

It("runtime should fail with NamespaceMode_CONTAINER", func() {
usernsOptions := &runtimeapi.UserNamespace{Mode: runtimeapi.NamespaceMode_CONTAINER}

runUserNamespacePodWithError(rc, podName, usernsOptions)
})

It("runtime should fail with NamespaceMode_TARGET", func() {
usernsOptions := &runtimeapi.UserNamespace{Mode: runtimeapi.NamespaceMode_TARGET}

runUserNamespacePodWithError(rc, podName, usernsOptions)
})
})
})

// matchContainerOutput matches log line in container logs.
Expand All @@ -850,6 +944,12 @@ func matchContainerOutput(podConfig *runtimeapi.PodSandboxConfig, name, output s
verifyLogContents(podConfig, fmt.Sprintf("%s.log", name), output, stdoutType)
}

// matchContainerOutputRe matches log line in container logs using the provided regular expression pattern.
func matchContainerOutputRe(podConfig *runtimeapi.PodSandboxConfig, name, pattern string) {
By("check container output")
verifyLogContentsRe(podConfig, fmt.Sprintf("%s.log", name), pattern, stdoutType)
}

// createRunAsUserContainer creates the container with specified RunAsUser in ContainerConfig.
func createRunAsUserContainer(rc internalapi.RuntimeService, ic internalapi.ImageManagerService, podID string, podConfig *runtimeapi.PodSandboxConfig, prefix string) (string, string) {
By("create RunAsUser container")
Expand Down Expand Up @@ -946,7 +1046,8 @@ func createNamespacePodSandbox(rc internalapi.RuntimeService, podSandboxNamespac
uid := framework.DefaultUIDPrefix + framework.NewUUID()
namespace := framework.DefaultNamespacePrefix + framework.NewUUID()
config := &runtimeapi.PodSandboxConfig{
Metadata: framework.BuildPodSandboxMetadata(podSandboxName, uid, namespace, framework.DefaultAttempt),
Metadata: framework.BuildPodSandboxMetadata(podSandboxName, uid, namespace, framework.DefaultAttempt),
DnsConfig: &runtimeapi.DNSConfig{},
Linux: &runtimeapi.LinuxPodSandboxConfig{
SecurityContext: &runtimeapi.LinuxSandboxSecurityContext{
NamespaceOptions: podSandboxNamespace,
Expand Down Expand Up @@ -1281,3 +1382,58 @@ func checkSetHostname(rc internalapi.RuntimeService, containerID string, setable
Expect(err).To(HaveOccurred(), msg)
}
}

func runUserNamespaceContainer(
rc internalapi.RuntimeService,
ic internalapi.ImageManagerService,
podID string,
podConfig *runtimeapi.PodSandboxConfig,
) string {
By("create user namespaces container")
containerName := "user-namespaces-container-" + framework.NewUUID()
containerConfig := &runtimeapi.ContainerConfig{
Metadata: framework.BuildContainerMetadata(containerName, framework.DefaultAttempt),
Image: &runtimeapi.ImageSpec{
Image: framework.TestContext.TestImageList.DefaultTestContainerImage,
UserSpecifiedImage: framework.TestContext.TestImageList.DefaultTestContainerImage,
},
Command: []string{"cat", "/proc/self/uid_map"},
LogPath: fmt.Sprintf("%s.log", containerName),
Linux: &runtimeapi.LinuxContainerConfig{
SecurityContext: &runtimeapi.LinuxContainerSecurityContext{
NamespaceOptions: podConfig.Linux.SecurityContext.NamespaceOptions,
},
},
}

containerID := createContainerWithExpectation(rc, ic, containerConfig, podID, podConfig, true)
startContainer(rc, containerID)

Eventually(func() runtimeapi.ContainerState {
return getContainerStatus(rc, containerID).State
}, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_EXITED))

return containerName
}

func runUserNamespacePodWithError(
rc internalapi.RuntimeService,
podName string,
usernsOptions *runtimeapi.UserNamespace,
) {
uid := framework.DefaultUIDPrefix + framework.NewUUID()
namespace := framework.DefaultNamespacePrefix + framework.NewUUID()
config := &runtimeapi.PodSandboxConfig{
Metadata: framework.BuildPodSandboxMetadata(podName, uid, namespace, framework.DefaultAttempt),
Linux: &runtimeapi.LinuxPodSandboxConfig{
SecurityContext: &runtimeapi.LinuxSandboxSecurityContext{
NamespaceOptions: &runtimeapi.NamespaceOption{
UsernsOptions: usernsOptions,
},
},
},
Labels: framework.DefaultPodLabels,
}

framework.RunPodSandboxError(rc, config)
}

0 comments on commit f3747eb

Please sign in to comment.