package integration /* toolbox_test.go is under the care of the Toolbox Team. The tests are trying to stress parts of Podman that Toolbox[0] needs for its functionality. [0] https://github.com/containers/toolbox Info about test cases: - some tests rely on a certain configuration of a container that is done by executing several commands in the entry-point of a container. To make sure the initialization had enough time to be executed, WaitContainerReady() after the container is started. - in several places there's an invocation of 'podman logs' It is there mainly to ease debugging when a test goes wrong (during the initialization of a container) but sometimes it is also used in the test case itself. Maintainers (Toolbox Team): - Ondřej Míchal - Debarshi Ray Also available on Freenode IRC on #silverblue or #podman */ import ( "fmt" "os" "os/exec" "os/user" "path" "strconv" "strings" "syscall" "github.com/containers/podman/v4/pkg/rootless" . "github.com/containers/podman/v4/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" ) var _ = Describe("Toolbox-specific testing", func() { var ( tempdir string err error podmanTest *PodmanTestIntegration ) BeforeEach(func() { tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) podmanTest.Setup() podmanTest.SeedImages() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() processTestResult(f) }) It("podman run --dns=none - allows self-management of /etc/resolv.conf", func() { session := podmanTest.Podman([]string{"run", "--dns", "none", ALPINE, "sh", "-c", "rm -f /etc/resolv.conf; touch -d '1970-01-01 00:02:03' /etc/resolv.conf; stat -c %s:%Y /etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring("0:123")) }) It("podman run --no-hosts - allows self-management of /etc/hosts", func() { session := podmanTest.Podman([]string{"run", "--no-hosts", ALPINE, "sh", "-c", "rm -f /etc/hosts; touch -d '1970-01-01 00:02:03' /etc/hosts; stat -c %s:%Y /etc/hosts"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring("0:123")) }) It("podman create --ulimit host + podman exec - correctly mirrors hosts ulimits", func() { if podmanTest.RemoteTest { Skip("Ulimit check does not work with a remote client") } var session *PodmanSessionIntegration var containerHardLimit int var rlimit syscall.Rlimit var err error err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit) Expect(err).To(BeNil()) fmt.Printf("Expected value: %d", rlimit.Max) session = podmanTest.Podman([]string{"create", "--name", "test", "--ulimit", "host", ALPINE, "sleep", "1000"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"start", "test"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"exec", "test", "sh", "-c", "ulimit -H -n"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) containerHardLimit, err = strconv.Atoi(strings.Trim(session.OutputToString(), "\n")) Expect(err).To(BeNil()) Expect(containerHardLimit).To(BeNumerically(">=", rlimit.Max)) }) It("podman create --ipc=host --pid=host + podman exec - correct shared memory limit size", func() { // Comparison of the size of /dev/shm on the host being equal to the one in // a container if podmanTest.RemoteTest { Skip("Shm size check does not work with a remote client") } SkipIfRootlessCgroupsV1("Not supported for rootless + CgroupsV1") var session *PodmanSessionIntegration var cmd *exec.Cmd var hostShmSize, containerShmSize int var err error // Because Alpine uses busybox, most commands don't offer advanced options // like "--output" in df. Therefore the value of the field 'Size' (or // ('1K-blocks') needs to be extracted manually. cmd = exec.Command("df", "/dev/shm") res, err := cmd.Output() Expect(err).To(BeNil()) lines := strings.SplitN(string(res), "\n", 2) fields := strings.Fields(lines[len(lines)-1]) hostShmSize, err = strconv.Atoi(fields[1]) Expect(err).To(BeNil()) session = podmanTest.Podman([]string{"create", "--name", "test", "--ipc=host", "--pid=host", ALPINE, "sleep", "1000"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"start", "test"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"exec", "test", "df", "/dev/shm"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) lines = session.OutputToStringArray() fields = strings.Fields(lines[len(lines)-1]) containerShmSize, err = strconv.Atoi(fields[1]) Expect(err).To(BeNil()) // In some cases it may happen that the size of /dev/shm is not exactly // equal. Therefore it's fine if there's a slight tolerance between the // compared values. Expect(hostShmSize).To(BeNumerically("~", containerShmSize, 100)) }) It("podman create --userns=keep-id --user root:root - entrypoint - entrypoint is executed as root", func() { session := podmanTest.Podman([]string{"run", "--userns=keep-id", "--user", "root:root", ALPINE, "id"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring("uid=0(root) gid=0(root)")) }) It("podman create --userns=keep-id + podman exec - correct names of user and group", func() { var session *PodmanSessionIntegration var err error currentUser, err := user.Current() Expect(err).To(BeNil()) currentGroup, err := user.LookupGroupId(currentUser.Gid) Expect(err).To(BeNil()) session = podmanTest.Podman([]string{"create", "--name", "test", "--userns=keep-id", ALPINE, "sleep", "1000"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(err).To(BeNil()) session = podmanTest.Podman([]string{"start", "test"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) expectedOutput := fmt.Sprintf("uid=%s(%s) gid=%s(%s)", currentUser.Uid, currentUser.Username, currentGroup.Gid, currentGroup.Name) session = podmanTest.Podman([]string{"exec", "test", "id"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring(expectedOutput)) }) It("podman create --userns=keep-id - entrypoint - adding user with useradd and then removing their password", func() { var session *PodmanSessionIntegration var username string = "testuser" var homeDir string = "/home/testuser" var shell string = "/bin/sh" var uid string = "1001" var gid string = "1001" useradd := fmt.Sprintf("useradd --home-dir %s --shell %s --uid %s %s", homeDir, shell, uid, username) passwd := fmt.Sprintf("passwd --delete %s", username) session = podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", fmt.Sprintf("%s; %s; echo READY; sleep 1000", useradd, passwd)}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"start", "test"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(WaitContainerReady(podmanTest, "test", "READY", 5, 1)).To(BeTrue()) expectedOutput := fmt.Sprintf("%s:x:%s:%s::%s:%s", username, uid, gid, homeDir, shell) session = podmanTest.Podman([]string{"exec", "test", "cat", "/etc/passwd"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring(expectedOutput)) expectedOutput = "passwd: Note: deleting a password also unlocks the password." session = podmanTest.Podman([]string{"logs", "test"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.ErrorToString()).To(ContainSubstring(expectedOutput)) }) It("podman create --userns=keep-id + podman exec - adding group with groupadd", func() { var session *PodmanSessionIntegration var groupName string = "testgroup" var gid string = "1001" groupadd := fmt.Sprintf("groupadd --gid %s %s", gid, groupName) session = podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", fmt.Sprintf("%s; echo READY; sleep 1000", groupadd)}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"start", "test"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(WaitContainerReady(podmanTest, "test", "READY", 5, 1)).To(BeTrue()) session = podmanTest.Podman([]string{"exec", "test", "cat", "/etc/group"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring(groupName)) session = podmanTest.Podman([]string{"logs", "test"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring("READY")) }) It("podman create --userns=keep-id - entrypoint - modifying existing user with usermod - add to new group, change home/shell/uid", func() { var session *PodmanSessionIntegration var badHomeDir string = "/home/badtestuser" var badShell string = "/bin/sh" var badUID string = "1001" var username string = "testuser" var homeDir string = "/home/testuser" var shell string = "/bin/bash" var uid string = "2000" var groupName string = "testgroup" var gid string = "2000" // The use of bad* in the name of variables does not imply the invocation // of useradd should fail The user is supposed to be created successfully // but later his information (uid, home, shell,..) is changed via usermod. useradd := fmt.Sprintf("useradd --home-dir %s --shell %s --uid %s %s", badHomeDir, badShell, badUID, username) groupadd := fmt.Sprintf("groupadd --gid %s %s", gid, groupName) usermod := fmt.Sprintf("usermod --append --groups wheel --home %s --shell %s --uid %s --gid %s %s", homeDir, shell, uid, gid, username) session = podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", fmt.Sprintf("%s; %s; %s; echo READY; sleep 1000", useradd, groupadd, usermod)}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"start", "test"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(WaitContainerReady(podmanTest, "test", "READY", 5, 1)).To(BeTrue()) expectedUser := fmt.Sprintf("%s:x:%s:%s::%s:%s", username, uid, gid, homeDir, shell) session = podmanTest.Podman([]string{"exec", "test", "cat", "/etc/passwd"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring(expectedUser)) session = podmanTest.Podman([]string{"logs", "test"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring("READY")) }) It("podman run --privileged --userns=keep-id --user root:root - entrypoint - (bind)mounting", func() { var session *PodmanSessionIntegration session = podmanTest.Podman([]string{"run", "--privileged", "--userns=keep-id", "--user", "root:root", ALPINE, "mount", "-t", "tmpfs", "tmpfs", "/tmp"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"run", "--privileged", "--userns=keep-id", "--user", "root:root", ALPINE, "mount", "--rbind", "/tmp", "/var/tmp"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) }) It("podman create + start - with all needed switches for create - sleep as entry-point", func() { var session *PodmanSessionIntegration // These should be most of the switches that Toolbox uses to create a "toolbox" container // https://github.com/containers/toolbox/blob/master/src/cmd/create.go session = podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--dns", "none", "--hostname", "toolbox", "--ipc", "host", "--label", "com.github.containers.toolbox=true", "--name", "test", "--network", "host", "--no-hosts", "--pid", "host", "--privileged", "--security-opt", "label=disable", "--ulimit", "host", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", "echo READY; sleep 1000"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"start", "test"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(WaitContainerReady(podmanTest, "test", "READY", 5, 1)).To(BeTrue()) session = podmanTest.Podman([]string{"logs", "test"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring("READY")) }) It("podman run --userns=keep-id check $HOME", func() { var session *PodmanSessionIntegration currentUser, err := user.Current() Expect(err).To(BeNil()) session = podmanTest.Podman([]string{"run", "-v", fmt.Sprintf("%s:%s", currentUser.HomeDir, currentUser.HomeDir), "--userns=keep-id", fedoraToolbox, "sh", "-c", "echo $HOME"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring(currentUser.HomeDir)) if rootless.IsRootless() { location := path.Dir(currentUser.HomeDir) volumeArg := fmt.Sprintf("%s:%s", location, location) session = podmanTest.Podman([]string{"run", "--userns=keep-id", "--volume", volumeArg, fedoraToolbox, "sh", "-c", "echo $HOME"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring(currentUser.HomeDir)) } }) })