Skip to content

Commit e3ab308

Browse files
committed
Add user namespaces tests
Adding user namespaces tests for covering the `UsernamespaceMode` supported by the CRI. Fixes #1348 Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
1 parent 8463eae commit e3ab308

File tree

4 files changed

+188
-2
lines changed

4 files changed

+188
-2
lines changed

.github/workflows/containerd.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ jobs:
1212
#
1313
build-and-critest-containerd:
1414
strategy:
15+
fail-fast: false
1516
matrix:
1617
# ╔══════════════════╤═══════════╤═════════╗
1718
# ║ master / release │ ubuntu │ windows ║
@@ -312,11 +313,17 @@ jobs:
312313
# Remove possibly existing containerd configuration
313314
sudo rm -rf /etc/containerd
314315
316+
# UserNamespaces only work for 1.7 and main
317+
SKIP="${SKIP:-""}"
318+
if [[ "${{ format(matrix.version, '/') }}" == "release/1.6" ]]; then
319+
SKIP=UserNamespaces
320+
fi
321+
315322
sudo PATH=$PATH /usr/local/bin/containerd -a ${BDIR}/c.sock -root ${BDIR}/root -state ${BDIR}/state -log-level debug &> ${BDIR}/containerd-cri.log &
316323
sudo /usr/local/bin/ctr -a ${BDIR}/c.sock version
317324
sudo /usr/local/sbin/runc --version
318325
319-
sudo -E PATH=$PATH critest --runtime-endpoint=unix:///${BDIR}/c.sock --parallel=8
326+
sudo -E PATH=$PATH critest --runtime-endpoint=unix:///${BDIR}/c.sock --ginkgo.skip="${SKIP}" --parallel=8
320327
TEST_RC=$?
321328
test $TEST_RC -ne 0 && cat ${BDIR}/containerd-cri.log
322329
sudo pkill containerd

pkg/framework/util.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,13 @@ func RunPodSandbox(c internalapi.RuntimeService, config *runtimeapi.PodSandboxCo
220220
return podID
221221
}
222222

223+
// RunPodSandboxError runs a PodSandbox and expects an error.
224+
func RunPodSandboxError(c internalapi.RuntimeService, config *runtimeapi.PodSandboxConfig) string {
225+
podID, err := c.RunPodSandbox(context.TODO(), config, TestContext.RuntimeHandler)
226+
Expect(err).To(HaveOccurred())
227+
return podID
228+
}
229+
223230
// CreatePodSandboxForContainer creates a PodSandbox for creating containers.
224231
func CreatePodSandboxForContainer(c internalapi.RuntimeService) (string, *runtimeapi.PodSandboxConfig) {
225232
podSandboxName := "create-PodSandbox-for-container-" + NewUUID()

pkg/validate/container.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"fmt"
2424
"os"
2525
"path/filepath"
26+
"regexp"
2627
"strings"
2728
"time"
2829

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

633+
// verifyLogContentsRe verifies the contents of container log using the provided regular expression pattern.
634+
func verifyLogContentsRe(podConfig *runtimeapi.PodSandboxConfig, logPath string, pattern string, stream streamType) {
635+
By("verify log contents using regex pattern")
636+
msgs := parseLogLine(podConfig, logPath)
637+
638+
found := false
639+
for _, msg := range msgs {
640+
if matched, _ := regexp.MatchString(pattern, msg.log); matched && msg.stream == stream {
641+
found = true
642+
break
643+
}
644+
}
645+
Expect(found).To(BeTrue(), "expected log pattern %q (stream=%q) to match logs %+v", pattern, stream, msgs)
646+
}
647+
632648
// listContainerStatsForID lists container for containerID.
633649
func listContainerStatsForID(c internalapi.RuntimeService, containerID string) *runtimeapi.ContainerStats {
634650
By("List container stats for containerID: " + containerID)

pkg/validate/security_context_linux.go

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,100 @@ var _ = framework.KubeDescribe("Security Context", func() {
842842
matchContainerOutput(podConfig, containerName, "Effective uid: 0\n")
843843
})
844844
})
845+
846+
Context("UserNamespaces", func() {
847+
var (
848+
podName string
849+
defaultMapping = []*runtimeapi.IDMapping{{
850+
ContainerId: 0,
851+
HostId: 1000,
852+
Length: 100000,
853+
}}
854+
)
855+
856+
BeforeEach(func() {
857+
podName = "user-namespaces-pod-" + framework.NewUUID()
858+
})
859+
860+
It("runtime should support NamespaceMode_POD", func() {
861+
namespaceOption := &runtimeapi.NamespaceOption{
862+
UsernsOptions: &runtimeapi.UserNamespace{
863+
Mode: runtimeapi.NamespaceMode_POD,
864+
Uids: defaultMapping,
865+
Gids: defaultMapping,
866+
},
867+
}
868+
869+
hostLogPath, podLogPath := createLogTempDir(podName)
870+
defer os.RemoveAll(hostLogPath)
871+
podID, podConfig = createNamespacePodSandbox(rc, namespaceOption, podName, podLogPath)
872+
containerName := runUserNamespaceContainer(rc, ic, podID, podConfig)
873+
874+
matchContainerOutputRe(podConfig, containerName, `\s+0\s+1000\s+100000\n`)
875+
})
876+
877+
It("runtime should support NamespaceMode_NODE", func() {
878+
namespaceOption := &runtimeapi.NamespaceOption{
879+
UsernsOptions: &runtimeapi.UserNamespace{
880+
Mode: runtimeapi.NamespaceMode_NODE,
881+
},
882+
}
883+
884+
hostLogPath, podLogPath := createLogTempDir(podName)
885+
defer os.RemoveAll(hostLogPath)
886+
podID, podConfig = createNamespacePodSandbox(rc, namespaceOption, podName, podLogPath)
887+
containerName := runUserNamespaceContainer(rc, ic, podID, podConfig)
888+
889+
// 4294967295 means that the entire range is available
890+
matchContainerOutputRe(podConfig, containerName, `\s+0\s+0\s+4294967295\n`)
891+
})
892+
893+
It("runtime should fail if more than one mapping provided", func() {
894+
wrongMapping := []*runtimeapi.IDMapping{{
895+
ContainerId: 0,
896+
HostId: 1000,
897+
Length: 100000,
898+
}, {
899+
ContainerId: 0,
900+
HostId: 2000,
901+
Length: 100000,
902+
}}
903+
usernsOptions := &runtimeapi.UserNamespace{
904+
Mode: runtimeapi.NamespaceMode_POD,
905+
Uids: wrongMapping,
906+
Gids: wrongMapping,
907+
}
908+
909+
runUserNamespacePodWithError(rc, podName, usernsOptions)
910+
})
911+
912+
It("runtime should fail if container ID 0 is not mapped", func() {
913+
mapping := []*runtimeapi.IDMapping{{
914+
ContainerId: 1,
915+
HostId: 1000,
916+
Length: 100000,
917+
}}
918+
usernsOptions := &runtimeapi.UserNamespace{
919+
Mode: runtimeapi.NamespaceMode_POD,
920+
Uids: mapping,
921+
Gids: mapping,
922+
}
923+
924+
runUserNamespacePodWithError(rc, podName, usernsOptions)
925+
})
926+
927+
It("runtime should fail with NamespaceMode_CONTAINER", func() {
928+
usernsOptions := &runtimeapi.UserNamespace{Mode: runtimeapi.NamespaceMode_CONTAINER}
929+
930+
runUserNamespacePodWithError(rc, podName, usernsOptions)
931+
})
932+
933+
It("runtime should fail with NamespaceMode_TARGET", func() {
934+
usernsOptions := &runtimeapi.UserNamespace{Mode: runtimeapi.NamespaceMode_TARGET}
935+
936+
runUserNamespacePodWithError(rc, podName, usernsOptions)
937+
})
938+
})
845939
})
846940

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

947+
// matchContainerOutputRe matches log line in container logs using the provided regular expression pattern.
948+
func matchContainerOutputRe(podConfig *runtimeapi.PodSandboxConfig, name, pattern string) {
949+
By("check container output")
950+
verifyLogContentsRe(podConfig, fmt.Sprintf("%s.log", name), pattern, stdoutType)
951+
}
952+
853953
// createRunAsUserContainer creates the container with specified RunAsUser in ContainerConfig.
854954
func createRunAsUserContainer(rc internalapi.RuntimeService, ic internalapi.ImageManagerService, podID string, podConfig *runtimeapi.PodSandboxConfig, prefix string) (string, string) {
855955
By("create RunAsUser container")
@@ -946,7 +1046,8 @@ func createNamespacePodSandbox(rc internalapi.RuntimeService, podSandboxNamespac
9461046
uid := framework.DefaultUIDPrefix + framework.NewUUID()
9471047
namespace := framework.DefaultNamespacePrefix + framework.NewUUID()
9481048
config := &runtimeapi.PodSandboxConfig{
949-
Metadata: framework.BuildPodSandboxMetadata(podSandboxName, uid, namespace, framework.DefaultAttempt),
1049+
Metadata: framework.BuildPodSandboxMetadata(podSandboxName, uid, namespace, framework.DefaultAttempt),
1050+
DnsConfig: &runtimeapi.DNSConfig{},
9501051
Linux: &runtimeapi.LinuxPodSandboxConfig{
9511052
SecurityContext: &runtimeapi.LinuxSandboxSecurityContext{
9521053
NamespaceOptions: podSandboxNamespace,
@@ -1281,3 +1382,58 @@ func checkSetHostname(rc internalapi.RuntimeService, containerID string, setable
12811382
Expect(err).To(HaveOccurred(), msg)
12821383
}
12831384
}
1385+
1386+
func runUserNamespaceContainer(
1387+
rc internalapi.RuntimeService,
1388+
ic internalapi.ImageManagerService,
1389+
podID string,
1390+
podConfig *runtimeapi.PodSandboxConfig,
1391+
) string {
1392+
By("create user namespaces container")
1393+
containerName := "user-namespaces-container-" + framework.NewUUID()
1394+
containerConfig := &runtimeapi.ContainerConfig{
1395+
Metadata: framework.BuildContainerMetadata(containerName, framework.DefaultAttempt),
1396+
Image: &runtimeapi.ImageSpec{
1397+
Image: framework.TestContext.TestImageList.DefaultTestContainerImage,
1398+
UserSpecifiedImage: framework.TestContext.TestImageList.DefaultTestContainerImage,
1399+
},
1400+
Command: []string{"cat", "/proc/self/uid_map"},
1401+
LogPath: fmt.Sprintf("%s.log", containerName),
1402+
Linux: &runtimeapi.LinuxContainerConfig{
1403+
SecurityContext: &runtimeapi.LinuxContainerSecurityContext{
1404+
NamespaceOptions: podConfig.Linux.SecurityContext.NamespaceOptions,
1405+
},
1406+
},
1407+
}
1408+
1409+
containerID := createContainerWithExpectation(rc, ic, containerConfig, podID, podConfig, true)
1410+
startContainer(rc, containerID)
1411+
1412+
Eventually(func() runtimeapi.ContainerState {
1413+
return getContainerStatus(rc, containerID).State
1414+
}, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_EXITED))
1415+
1416+
return containerName
1417+
}
1418+
1419+
func runUserNamespacePodWithError(
1420+
rc internalapi.RuntimeService,
1421+
podName string,
1422+
usernsOptions *runtimeapi.UserNamespace,
1423+
) {
1424+
uid := framework.DefaultUIDPrefix + framework.NewUUID()
1425+
namespace := framework.DefaultNamespacePrefix + framework.NewUUID()
1426+
config := &runtimeapi.PodSandboxConfig{
1427+
Metadata: framework.BuildPodSandboxMetadata(podName, uid, namespace, framework.DefaultAttempt),
1428+
Linux: &runtimeapi.LinuxPodSandboxConfig{
1429+
SecurityContext: &runtimeapi.LinuxSandboxSecurityContext{
1430+
NamespaceOptions: &runtimeapi.NamespaceOption{
1431+
UsernsOptions: usernsOptions,
1432+
},
1433+
},
1434+
},
1435+
Labels: framework.DefaultPodLabels,
1436+
}
1437+
1438+
framework.RunPodSandboxError(rc, config)
1439+
}

0 commit comments

Comments
 (0)