From 3f2939c2ef6d9867862f597751c12b14c74440a3 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Mon, 21 Mar 2022 16:43:14 +0100 Subject: run, create: add --passwd-entry It allows to customize the entry that is written to the `/etc/passwd` file when --passwd is used. Closes: https://github.com/containers/podman/issues/13185 Signed-off-by: Giuseppe Scrivano --- cmd/podman/common/create.go | 4 ++++ docs/source/markdown/podman-create.1.md | 6 ++++++ docs/source/markdown/podman-run.1.md | 6 ++++++ libpod/container_config.go | 2 ++ libpod/container_internal_linux.go | 19 +++++++++++++++++++ libpod/options.go | 13 +++++++++++++ pkg/domain/entities/pods.go | 2 ++ pkg/specgen/generate/container_create.go | 3 +++ pkg/specgen/specgen.go | 2 ++ pkg/specgenutil/specgen.go | 5 +++++ test/e2e/run_passwd_test.go | 12 ++++++++++++ 11 files changed, 74 insertions(+) diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 8a3f02036..309bbda59 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -630,6 +630,10 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(chrootDirsFlagName, completion.AutocompleteDefault) + passwdEntryName := "passwd-entry" + createFlags.StringVar(&cf.PasswdEntry, passwdEntryName, "", "Entry to write to /etc/passwd") + _ = cmd.RegisterFlagCompletionFunc(passwdEntryName, completion.AutocompleteNone) + if registry.IsRemote() { _ = createFlags.MarkHidden("env-host") _ = createFlags.MarkHidden("http-proxy") diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index c4d27e321..9af7174fc 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -755,6 +755,12 @@ Tune the host's OOM preferences for containers (accepts -1000 to 1000) #### **--os**=*OS* Override the OS, defaults to hosts, of the image to be pulled. For example, `windows`. +#### **--passwd-entry**=*ENTRY* + +Customize the entry that is written to the `/etc/passwd` file within the container when `--passwd` is used. + +The variables $USERNAME, $UID, $GID, $NAME, $HOME are automatically replaced with their value at runtime. + #### **--personality**=*persona* Personality sets the execution domain via Linux personality(2). diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index e4ccd0368..ecb24ba6d 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -787,6 +787,12 @@ Override the OS, defaults to hosts, of the image to be pulled. For example, `win Allow Podman to add entries to /etc/passwd and /etc/group when used in conjunction with the --user option. This is used to override the Podman provided user setup in favor of entrypoint configurations such as libnss-extrausers. +#### **--passwd-entry**=*ENTRY* + +Customize the entry that is written to the `/etc/passwd` file within the container when `--passwd` is used. + +The variables $USERNAME, $UID, $GID, $NAME, $HOME are automatically replaced with their value at runtime. + #### **--personality**=*persona* Personality sets the execution domain via Linux personality(2). diff --git a/libpod/container_config.go b/libpod/container_config.go index ea644764c..8500c6db9 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -404,6 +404,8 @@ type ContainerMiscConfig struct { // InitContainerType specifies if the container is an initcontainer // and if so, what type: always or once are possible non-nil entries InitContainerType string `json:"init_container_type,omitempty"` + // PasswdEntry specifies arbitrary data to append to a file. + PasswdEntry string `json:"passwd_entry,omitempty"` } // InfraInherit contains the compatible options inheritable from the infra container diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 11ca169ca..9369b746c 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -2724,6 +2724,9 @@ func (c *Container) userPasswdEntry(u *user.User) (string, error) { if !hasHomeSet { c.config.Spec.Process.Env = append(c.config.Spec.Process.Env, fmt.Sprintf("HOME=%s", homeDir)) } + if c.config.PasswdEntry != "" { + return c.passwdEntry(u.Username, u.Uid, u.Gid, u.Name, homeDir), nil + } return fmt.Sprintf("%s:*:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Name, homeDir), nil } @@ -2775,9 +2778,25 @@ func (c *Container) generateUserPasswdEntry(addedUID int) (string, int, int, err gid = group.Gid } } + + if c.config.PasswdEntry != "" { + entry := c.passwdEntry(fmt.Sprintf("%d", uid), fmt.Sprintf("%d", uid), fmt.Sprintf("%d", gid), "container user", c.WorkingDir()) + return entry, int(uid), gid, nil + } + return fmt.Sprintf("%d:*:%d:%d:container user:%s:/bin/sh\n", uid, uid, gid, c.WorkingDir()), int(uid), gid, nil } +func (c *Container) passwdEntry(username string, uid, gid, name, homeDir string) string { + s := c.config.PasswdEntry + s = strings.Replace(s, "$USERNAME", username, -1) + s = strings.Replace(s, "$UID", uid, -1) + s = strings.Replace(s, "$GID", gid, -1) + s = strings.Replace(s, "$NAME", name, -1) + s = strings.Replace(s, "$HOME", homeDir, -1) + return s + "\n" +} + // generatePasswdAndGroup generates container-specific passwd and group files // iff g.config.User is a number or we are configured to make a passwd entry for // the current user or the user specified HostsUsers diff --git a/libpod/options.go b/libpod/options.go index 2e5454393..6c4b4cc42 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -2051,3 +2051,16 @@ func WithChrootDirs(dirs []string) CtrCreateOption { return nil } } + +// WithPasswdEntry sets the entry to write to the /etc/passwd file. +func WithPasswdEntry(passwdEntry string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.PasswdEntry = passwdEntry + + return nil + } +} diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index f1d445c4b..1e25e0872 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -272,6 +272,8 @@ type ContainerCreateOptions struct { Net *NetOptions `json:"net,omitempty"` CgroupConf []string + + PasswdEntry string } func NewInfraContainerCreateOptions() ContainerCreateOptions { diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 6a611e854..b85a87742 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -286,6 +286,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. if s.Volatile { options = append(options, libpod.WithVolatile()) } + if s.PasswdEntry != "" { + options = append(options, libpod.WithPasswdEntry(s.PasswdEntry)) + } useSystemd := false switch s.Systemd { diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index dfac1d457..79e20667b 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -206,6 +206,8 @@ type ContainerBasicConfig struct { UnsetEnvAll bool `json:"unsetenvall,omitempty"` // Passwd is a container run option that determines if we are validating users/groups before running the container Passwd *bool `json:"manage_password,omitempty"` + // PasswdEntry specifies arbitrary data to append to a file. + PasswdEntry string `json:"passwd_entry,omitempty"` } // ContainerStorageConfig contains information on the storage configuration of a diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 7d4fca846..42b66d909 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -832,6 +832,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions if s.Passwd == nil { s.Passwd = &t } + + if len(s.PasswdEntry) == 0 || len(c.PasswdEntry) != 0 { + s.PasswdEntry = c.PasswdEntry + } + return nil } diff --git a/test/e2e/run_passwd_test.go b/test/e2e/run_passwd_test.go index 4c97e665a..ce6c6ffda 100644 --- a/test/e2e/run_passwd_test.go +++ b/test/e2e/run_passwd_test.go @@ -137,4 +137,16 @@ USER 1000`, ALPINE) Expect(run).Should(Exit(0)) Expect(run.OutputToString()).NotTo((ContainSubstring("1234:1234"))) }) + + It("podman run --passwd-entry flag", func() { + // Test that the line we add doesn't contain anything else than what is specified + run := podmanTest.Podman([]string{"run", "--user", "1234:1234", "--passwd-entry=FOO", ALPINE, "grep", "^FOO$", "/etc/passwd"}) + run.WaitWithDefaultTimeout() + Expect(run).Should(Exit(0)) + + run = podmanTest.Podman([]string{"run", "--user", "12345:12346", "-w", "/etc", "--passwd-entry=$UID-$GID-$NAME-$HOME-$USERNAME", ALPINE, "cat", "/etc/passwd"}) + run.WaitWithDefaultTimeout() + Expect(run).Should(Exit(0)) + Expect(run.OutputToString()).To(ContainSubstring("12345-12346-container user-/etc-12345")) + }) }) -- cgit v1.2.3-54-g00ecf