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 #1348

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
  • Loading branch information
saschagrunert committed Feb 29, 2024
1 parent 3542da5 commit 4d1e304
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 10 deletions.
17 changes: 9 additions & 8 deletions .github/workflows/containerd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -298,27 +298,28 @@ jobs:
set -o errexit
set -o nounset
set -o pipefail
set -x
BDIR="$(mktemp -d -p $PWD)"
BDIR="/var/lib/containerd-critest"
echo "containerd temp dir: ${BDIR}"
mkdir -p ${BDIR}/{root,state}
sudo mkdir -p ${BDIR}/{root,state}
cat > ${BDIR}/config.toml <<EOF
sudo bash -c 'cat > ${BDIR}/config.toml <<EOF
version = 2
[plugins]
[plugins.cri.containerd.default_runtime]
runtime_type = \"${{matrix.runtime}}\"
EOF
EOF'
# Remove possibly existing containerd configuration
sudo rm -rf /etc/containerd
sudo PATH=$PATH /usr/local/bin/containerd -a ${BDIR}/c.sock -root ${BDIR}/root -state ${BDIR}/state -log-level debug &> ${BDIR}/containerd-cri.log &
sudo PATH=$PATH bash -c "/usr/local/bin/containerd -a ${BDIR}/c.sock -root ${BDIR}/root -state ${BDIR}/state -log-level debug &> ${BDIR}/containerd-cri.log &"
sudo /usr/local/bin/ctr -a ${BDIR}/c.sock version
sudo /usr/local/sbin/runc --version
sudo mount
sudo -E PATH=$PATH critest --runtime-endpoint=unix:///${BDIR}/c.sock --parallel=8
TEST_RC=$?
TEST_RC=0
sudo -E PATH=$PATH critest --ginkgo.vv --runtime-endpoint=unix:///${BDIR}/c.sock --parallel=8 || TEST_RC=$?
test $TEST_RC -ne 0 && cat ${BDIR}/containerd-cri.log
sudo pkill containerd
echo "CONTD_CRI_DIR=$BDIR" >> $GITHUB_ENV
Expand Down
14 changes: 14 additions & 0 deletions pkg/framework/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,20 @@ func RunPodSandbox(c internalapi.RuntimeService, config *runtimeapi.PodSandboxCo
return podID
}

// RunPodSandboxWithRuntimeHandler runs a PodSandbox with a custom runtime handler.
func RunPodSandboxWithRuntimeHandler(c internalapi.RuntimeService, config *runtimeapi.PodSandboxConfig, runtimeHandler string) string {
podID, err := c.RunPodSandbox(context.TODO(), config, runtimeHandler)
ExpectNoError(err, "failed to create PodSandbox: %v", err)
return podID
}

// RunPodSandboxErrorWithRuntimeHandler runs a PodSandbox with a custom runtime handler and expects an error.
func RunPodSandboxErrorWithRuntimeHandler(c internalapi.RuntimeService, config *runtimeapi.PodSandboxConfig, runtimeHandler string) string {
podID, err := c.RunPodSandbox(context.TODO(), config, 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
197 changes: 195 additions & 2 deletions pkg/validate/security_context_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,125 @@ var _ = framework.KubeDescribe("Security Context", func() {
matchContainerOutput(podConfig, containerName, "Effective uid: 0\n")
})
})

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

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

// Find a working runtime handler if none provided
if framework.TestContext.RuntimeHandler == "" {
By("searching for runtime handler which supports user namespaces")
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
resp, err := rc.Status(ctx, false)
framework.ExpectNoError(err, "failed to get runtime config: %v", err)

for _, rh := range resp.GetRuntimeHandlers() {
if rh.GetFeatures().GetUserNamespaces() {
runtimeHandler = rh.GetName()
break
}
}
} else {
runtimeHandler = framework.TestContext.RuntimeHandler
}

if runtimeHandler == "" {
Skip("no runtime handler found which supports user namespaces")
}

By(fmt.Sprintf("using runtime handler: %s", runtimeHandler))
})

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 = createNamespacePodSandboxWithRuntimeHandler(rc, namespaceOption, podName, podLogPath, runtimeHandler)
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 = createNamespacePodSandboxWithRuntimeHandler(rc, namespaceOption, podName, podLogPath, runtimeHandler)
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, runtimeHandler)
})

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, runtimeHandler)
})

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

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

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

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

// matchContainerOutput matches log line in container logs.
Expand All @@ -850,6 +969,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 @@ -942,11 +1067,23 @@ func createInvalidRunAsGroupContainer(rc internalapi.RuntimeService, ic internal

// createNamespacePodSandbox creates a PodSandbox with different NamespaceOption config for creating containers.
func createNamespacePodSandbox(rc internalapi.RuntimeService, podSandboxNamespace *runtimeapi.NamespaceOption, podSandboxName string, podLogPath string) (string, *runtimeapi.PodSandboxConfig) {
return createNamespacePodSandboxWithRuntimeHandler(rc, podSandboxNamespace, podSandboxName, podLogPath, framework.TestContext.RuntimeHandler)
}

// createNamespacePodSandboxWithRuntimeHandler creates a PodSandbox with
// different NamespaceOption config for creating containers by using a custom
// runtime handler.
func createNamespacePodSandboxWithRuntimeHandler(
rc internalapi.RuntimeService,
podSandboxNamespace *runtimeapi.NamespaceOption,
podSandboxName, podLogPath, runtimeHandler string,
) (string, *runtimeapi.PodSandboxConfig) {
By("create NamespaceOption podSandbox")
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 All @@ -957,7 +1094,7 @@ func createNamespacePodSandbox(rc internalapi.RuntimeService, podSandboxNamespac
Labels: framework.DefaultPodLabels,
}

return framework.RunPodSandbox(rc, config), config
return framework.RunPodSandboxWithRuntimeHandler(rc, config, runtimeHandler), config
}

// createNamespaceContainer creates container with different NamespaceOption config.
Expand Down Expand Up @@ -1281,3 +1418,59 @@ 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,
runtimeHandler string,
) {
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.RunPodSandboxErrorWithRuntimeHandler(rc, config, runtimeHandler)
}

0 comments on commit 4d1e304

Please sign in to comment.