Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add user namespaces tests #1354

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
Loading