Files
podman/test/e2e/run_userns_test.go
Paul Holzinger 2ddf1c5cbd ginkgo tests: apply ginkgolinter fixes
I found the ginkgolinter[1] by accident, this looks for not optimal
matching and suggest how to do it better.

Overall these fixes seem to be all correct and they will give much
better error messages when something fails.
Check out the repo to see what the linter reports.

[1] https://github.com/nunnatsa/ginkgolinter

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2022-11-25 14:41:43 +01:00

432 lines
16 KiB
Go

package integration
import (
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
. "github.com/containers/podman/v4/test/utils"
"github.com/containers/storage"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
)
func createContainersConfFileWithCustomUserns(pTest *PodmanTestIntegration, userns string) {
configPath := filepath.Join(pTest.TempDir, "containers.conf")
containersConf := []byte(fmt.Sprintf("[containers]\nuserns = \"%s\"\n", userns))
err := os.WriteFile(configPath, containersConf, os.ModePerm)
Expect(err).ToNot(HaveOccurred())
// Set custom containers.conf file
os.Setenv("CONTAINERS_CONF", configPath)
if IsRemote() {
pTest.RestartRemoteService()
}
}
var _ = Describe("Podman UserNS support", func() {
var (
tempdir string
err error
podmanTest *PodmanTestIntegration
)
BeforeEach(func() {
if os.Getenv("SKIP_USERNS") != "" {
Skip("Skip userns tests.")
}
if _, err := os.Stat("/proc/self/uid_map"); err != nil {
Skip("User namespaces not supported.")
}
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
}
podmanTest = PodmanTestCreate(tempdir)
podmanTest.Setup()
})
AfterEach(func() {
podmanTest.Cleanup()
f := CurrentGinkgoTestDescription()
processTestResult(f)
os.Unsetenv("CONTAINERS_CONF")
})
// Note: Lot of tests for build with --userns=auto are already there in buildah
// but they are skipped in podman CI because bud tests are executed in rootfull
// environment ( where mappings for the `containers` user is not present in /etc/subuid )
// causing them to skip hence this is a redundant test for sanity to make sure
// we don't break this feature for podman-remote.
It("podman build with --userns=auto", func() {
u, err := user.Current()
Expect(err).ToNot(HaveOccurred())
name := u.Name
if name == "root" {
name = "containers"
}
content, err := os.ReadFile("/etc/subuid")
if err != nil {
Skip("cannot read /etc/subuid")
}
if !strings.Contains(string(content), name) {
Skip("cannot find mappings for the current user")
}
session := podmanTest.Podman([]string{"build", "-f", "build/Containerfile.userns-auto", "-t", "test", "--userns=auto"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
// `1024` is the default size or length of the range of user IDs
// that is mapped between the two user namespaces by --userns=auto.
Expect(session.OutputToString()).To(ContainSubstring(fmt.Sprintf("%d", storage.AutoUserNsMinSize)))
})
It("podman uidmapping and gidmapping", func() {
session := podmanTest.Podman([]string{"run", "--uidmap=0:100:5000", "--gidmap=0:200:5000", "alpine", "echo", "hello"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("hello"))
})
// It essentially repeats the test above but with the `-it` short option
// that broke execution at:
// https://github.com/containers/podman/pull/1066#issuecomment-403562116
// To avoid a potential future regression, use this as a test.
It("podman uidmapping and gidmapping with short-opts", func() {
session := podmanTest.Podman([]string{"run", "--uidmap=0:1:5000", "--gidmap=0:200:5000", "-it", "alpine", "echo", "hello"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("hello"))
})
It("podman uidmapping and gidmapping with a volume", func() {
session := podmanTest.Podman([]string{"run", "--uidmap=0:1:500", "--gidmap=0:200:5000", "-v", "my-foo-volume:/foo:Z", "alpine", "echo", "hello"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("hello"))
})
It("podman uidmapping and gidmapping with an idmapped volume", func() {
session := podmanTest.Podman([]string{"run", "--uidmap=0:1:500", "--gidmap=0:200:5000", "-v", "my-foo-volume:/foo:Z,idmap", "alpine", "echo", "hello"})
session.WaitWithDefaultTimeout()
if strings.Contains(session.ErrorToString(), "Operation not permitted") {
Skip("not sufficiently privileged")
}
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("hello"))
})
It("podman uidmapping and gidmapping --net=host", func() {
session := podmanTest.Podman([]string{"run", "--net=host", "--uidmap=0:1:5000", "--gidmap=0:200:5000", "alpine", "echo", "hello"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("hello"))
})
It("podman --userns=keep-id", func() {
session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "id", "-u"})
session.WaitWithDefaultTimeout()
if os.Geteuid() == 0 {
Expect(session).Should(Exit(125))
return
}
Expect(session).Should(Exit(0))
uid := fmt.Sprintf("%d", os.Geteuid())
Expect(session.OutputToString()).To(ContainSubstring(uid))
session = podmanTest.Podman([]string{"run", "--userns=keep-id:uid=10,gid=12", "alpine", "sh", "-c", "echo $(id -u):$(id -g)"})
session.WaitWithDefaultTimeout()
if os.Geteuid() == 0 {
Expect(session).Should(Exit(125))
return
}
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("10:12"))
})
It("podman --userns=keep-id check passwd", func() {
SkipIfNotRootless("keep-id only works in rootless mode")
session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "id", "-un"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
u, err := user.Current()
Expect(err).ToNot(HaveOccurred())
Expect(session.OutputToString()).To(ContainSubstring(u.Name))
})
It("podman --userns=keep-id root owns /usr", func() {
SkipIfNotRootless("keep-id only works in rootless mode")
session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "stat", "-c%u", "/usr"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(Equal("0"))
})
It("podman --userns=keep-id --user root:root", func() {
SkipIfNotRootless("keep-id only works in rootless mode")
session := podmanTest.Podman([]string{"run", "--userns=keep-id", "--user", "root:root", "alpine", "id", "-u"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(Equal("0"))
})
It("podman run --userns=keep-id can add users", func() {
SkipIfNotRootless("keep-id only works in rootless mode")
userName := os.Getenv("USER")
if userName == "" {
Skip("Can't complete test if no username available")
}
ctrName := "ctr-name"
session := podmanTest.Podman([]string{"run", "--userns=keep-id", "--user", "root:root", "-d", "--stop-signal", "9", "--name", ctrName, fedoraMinimal, "sleep", "600"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
exec1 := podmanTest.Podman([]string{"exec", "-t", "-i", ctrName, "cat", "/etc/passwd"})
exec1.WaitWithDefaultTimeout()
Expect(exec1).Should(Exit(0))
Expect(exec1.OutputToString()).To(ContainSubstring(userName))
exec2 := podmanTest.Podman([]string{"exec", "-t", "-i", ctrName, "useradd", "testuser"})
exec2.WaitWithDefaultTimeout()
Expect(exec2).Should(Exit(0))
})
It("podman --userns=auto", func() {
u, err := user.Current()
Expect(err).ToNot(HaveOccurred())
name := u.Name
if name == "root" {
name = "containers"
}
content, err := os.ReadFile("/etc/subuid")
if err != nil {
Skip("cannot read /etc/subuid")
}
if !strings.Contains(string(content), name) {
Skip("cannot find mappings for the current user")
}
m := make(map[string]string)
for i := 0; i < 5; i++ {
session := podmanTest.Podman([]string{"run", "--userns=auto", "alpine", "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
l := session.OutputToString()
// `1024` is the default size or length of the range of user IDs
// that is mapped between the two user namespaces by --userns=auto.
Expect(l).To(ContainSubstring("1024"))
m[l] = l
}
// check for no duplicates
Expect(m).To(HaveLen(5))
createContainersConfFileWithCustomUserns(podmanTest, "auto:size=1019")
session := podmanTest.Podman([]string{"run", "alpine", "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("1019"))
})
It("podman --userns=auto:size=%d", func() {
u, err := user.Current()
Expect(err).ToNot(HaveOccurred())
name := u.Name
if name == "root" {
name = "containers"
}
content, err := os.ReadFile("/etc/subuid")
if err != nil {
Skip("cannot read /etc/subuid")
}
if !strings.Contains(string(content), name) {
Skip("cannot find mappings for the current user")
}
session := podmanTest.Podman([]string{"run", "--userns=auto:size=500", "alpine", "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("500"))
session = podmanTest.Podman([]string{"run", "--userns=auto:size=3000", "alpine", "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("3000"))
session = podmanTest.Podman([]string{"run", "--userns=auto", "--user=2000:3000", "alpine", "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("3001"))
session = podmanTest.Podman([]string{"run", "--userns=auto", "--user=4000:1000", "alpine", "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("4001"))
})
It("podman --userns=auto:uidmapping=", func() {
u, err := user.Current()
Expect(err).ToNot(HaveOccurred())
name := u.Name
if name == "root" {
name = "containers"
}
content, err := os.ReadFile("/etc/subuid")
if err != nil {
Skip("cannot read /etc/subuid")
}
if !strings.Contains(string(content), name) {
Skip("cannot find mappings for the current user")
}
session := podmanTest.Podman([]string{"run", "--userns=auto:uidmapping=0:0:1", "alpine", "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
output := session.OutputToString()
Expect(output).To(MatchRegexp("\\s0\\s0\\s1"))
session = podmanTest.Podman([]string{"run", "--userns=auto:size=8192,uidmapping=0:0:1", "alpine", "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("8191"))
})
It("podman --userns=auto:gidmapping=", func() {
u, err := user.Current()
Expect(err).ToNot(HaveOccurred())
name := u.Name
if name == "root" {
name = "containers"
}
content, err := os.ReadFile("/etc/subuid")
if err != nil {
Skip("cannot read /etc/subuid")
}
if !strings.Contains(string(content), name) {
Skip("cannot find mappings for the current user")
}
session := podmanTest.Podman([]string{"run", "--userns=auto:gidmapping=0:0:1", "alpine", "cat", "/proc/self/gid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
output := session.OutputToString()
Expect(output).To(MatchRegexp("\\s0\\s0\\s1"))
session = podmanTest.Podman([]string{"run", "--userns=auto:size=8192,gidmapping=0:0:1", "alpine", "cat", "/proc/self/gid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("8191"))
})
It("podman --userns=container:CTR", func() {
ctrName := "userns-ctr"
session := podmanTest.Podman([]string{"run", "-d", "--uidmap=0:0:1", "--uidmap=1:1:4998", "--name", ctrName, "alpine", "top"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
// runc has an issue and we also need to join the IPC namespace.
session = podmanTest.Podman([]string{"run", "--rm", "--userns=container:" + ctrName, "--ipc=container:" + ctrName, "alpine", "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("4998"))
session = podmanTest.Podman([]string{"run", "--rm", "--userns=container:" + ctrName, "--net=container:" + ctrName, "alpine", "cat", "/proc/self/uid_map"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("4998"))
})
It("podman --user with volume", func() {
tests := []struct {
uid, gid, arg, vol string
}{
{"0", "0", "0:0", "vol-0"},
{"1000", "0", "1000", "vol-1"},
{"1000", "1000", "1000:1000", "vol-2"},
}
for _, tt := range tests {
session := podmanTest.Podman([]string{"run", "-d", "--user", tt.arg, "--mount", "type=volume,src=" + tt.vol + ",dst=/home/user", "alpine", "top"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
inspectUID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .UID }}", tt.vol})
inspectUID.WaitWithDefaultTimeout()
Expect(inspectUID).Should(Exit(0))
Expect(inspectUID.OutputToString()).To(Equal(tt.uid))
// Make sure we're defaulting to 0.
inspectGID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .GID }}", tt.vol})
inspectGID.WaitWithDefaultTimeout()
Expect(inspectGID).Should(Exit(0))
Expect(inspectGID.OutputToString()).To(Equal(tt.gid))
}
})
It("podman --userns= conflicts with ui[dg]map and sub[ug]idname", func() {
session := podmanTest.Podman([]string{"run", "--userns=host", "--uidmap=0:1:500", "alpine", "true"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(125))
Expect(session.ErrorToString()).To(ContainSubstring("--userns and --uidmap/--gidmap/--subuidname/--subgidname are mutually exclusive"))
session = podmanTest.Podman([]string{"run", "--userns=host", "--gidmap=0:200:5000", "alpine", "true"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(125))
Expect(session.ErrorToString()).To(ContainSubstring("--userns and --uidmap/--gidmap/--subuidname/--subgidname are mutually exclusive"))
// with sub[ug]idname we don't check for the error output since the error message could be different, depending on the
// system configuration since the specified user could not be defined and cause a different earlier error.
// In any case, make sure the command doesn't succeed.
session = podmanTest.Podman([]string{"run", "--userns=private", "--subuidname=containers", "alpine", "true"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Not(Exit(0)))
session = podmanTest.Podman([]string{"run", "--userns=private", "--subgidname=containers", "alpine", "true"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Not(Exit(0)))
})
It("podman PODMAN_USERNS", func() {
SkipIfNotRootless("keep-id only works in rootless mode")
podmanUserns, podmanUserusSet := os.LookupEnv("PODMAN_USERNS")
os.Setenv("PODMAN_USERNS", "keep-id")
defer func() {
if podmanUserusSet {
os.Setenv("PODMAN_USERNS", podmanUserns)
} else {
os.Unsetenv("PODMAN_USERNS")
}
}()
if IsRemote() {
podmanTest.RestartRemoteService()
}
result := podmanTest.Podman([]string{"create", ALPINE, "true"})
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
inspect := podmanTest.Podman([]string{"inspect", "--format", "{{ .HostConfig.IDMappings }}", result.OutputToString()})
inspect.WaitWithDefaultTimeout()
Expect(inspect.OutputToString()).To(Not(Equal("<nil>")))
if IsRemote() {
podmanTest.RestartRemoteService()
}
})
})