diff options
-rw-r--r-- | cmd/podman/common/create.go | 8 | ||||
-rw-r--r-- | docs/source/markdown/podman-create.1.md | 5 | ||||
-rw-r--r-- | docs/source/markdown/podman-run.1.md | 6 | ||||
-rw-r--r-- | libpod/container_config.go | 4 | ||||
-rw-r--r-- | libpod/container_inspect.go | 1 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 35 | ||||
-rw-r--r-- | libpod/define/container_inspect.go | 4 | ||||
-rw-r--r-- | libpod/networking_slirp4netns.go | 40 | ||||
-rw-r--r-- | libpod/options.go | 15 | ||||
-rw-r--r-- | pkg/bindings/containers/checkpoint.go | 2 | ||||
-rw-r--r-- | pkg/bindings/containers/types.go | 2 | ||||
-rw-r--r-- | pkg/bindings/containers/types_restore_options.go | 14 | ||||
-rw-r--r-- | pkg/domain/entities/pods.go | 1 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 2 | ||||
-rw-r--r-- | pkg/specgen/generate/container_create.go | 4 | ||||
-rw-r--r-- | pkg/specgen/specgen.go | 4 | ||||
-rw-r--r-- | pkg/specgenutil/specgen.go | 3 | ||||
-rw-r--r-- | test/e2e/create_test.go | 30 |
18 files changed, 156 insertions, 24 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 634b49db7..f3e2e4d6d 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -631,6 +631,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, "Write the container process ID to the file") _ = cmd.RegisterFlagCompletionFunc(pidFileFlagName, completion.AutocompleteDefault) + chrootDirsFlagName := "chrootdirs" + createFlags.StringSliceVar( + &cf.ChrootDirs, + chrootDirsFlagName, []string{}, + "Chroot directories inside the container", + ) + _ = cmd.RegisterFlagCompletionFunc(chrootDirsFlagName, completion.AutocompleteDefault) + 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 2a0f3b738..506f575fe 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -1453,6 +1453,11 @@ After the container is started, the location for the pidfile can be discovered w $ podman inspect --format '{{ .PidFile }}' $CID /run/containers/storage/${storage-driver}-containers/$CID/userdata/pidfile +#### **--chrootdirs**=*path* + +Path to a directory inside the container that should be treated as a `chroot` directory. +Any Podman managed file (e.g., /etc/resolv.conf, /etc/hosts, etc/hostname) that is mounted into the root directory will be mounted into that location as well. +Multiple directories should be separated with a comma. ## EXAMPLES diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 239cf3b83..7fa7bda30 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -1529,6 +1529,12 @@ After the container is started, the location for the pidfile can be discovered w $ podman inspect --format '{{ .PidFile }}' $CID /run/containers/storage/${storage-driver}-containers/$CID/userdata/pidfile +#### **--chrootdirs**=*path* + +Path to a directory inside the container that should be treated as a `chroot` directory. +Any Podman managed file (e.g., /etc/resolv.conf, /etc/hosts, etc/hostname) that is mounted into the root directory will be mounted into that location as well. +Multiple directories should be separated with a comma. + ## Exit Status The exit code from **podman run** gives information about why the container diff --git a/libpod/container_config.go b/libpod/container_config.go index e56f1342a..0d9cd5723 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -165,6 +165,10 @@ type ContainerRootFSConfig struct { Volatile bool `json:"volatile,omitempty"` // Passwd allows to user to override podman's passwd/group file setup Passwd *bool `json:"passwd,omitempty"` + // ChrootDirs is an additional set of directories that need to be + // treated as root directories. Standard bind mounts will be mounted + // into paths relative to these directories. + ChrootDirs []string `json:"chroot_directories,omitempty"` } // ContainerSecurityConfig is an embedded sub-config providing security configuration diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 3df6203e3..5fb32bd90 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -411,6 +411,7 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp } ctrConfig.Passwd = c.config.Passwd + ctrConfig.ChrootDirs = append(ctrConfig.ChrootDirs, c.config.ChrootDirs...) return ctrConfig } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 1517a7df7..75250b9b1 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -1811,6 +1811,17 @@ func (c *Container) getRootNetNsDepCtr() (depCtr *Container, err error) { return depCtr, nil } +// Ensure standard bind mounts are mounted into all root directories (including chroot directories) +func (c *Container) mountIntoRootDirs(mountName string, mountPath string) error { + c.state.BindMounts[mountName] = mountPath + + for _, chrootDir := range c.config.ChrootDirs { + c.state.BindMounts[filepath.Join(chrootDir, mountName)] = mountPath + } + + return nil +} + // Make standard bind mounts to include in the container func (c *Container) makeBindMounts() error { if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil { @@ -1864,7 +1875,11 @@ func (c *Container) makeBindMounts() error { // If it doesn't, don't copy them resolvPath, exists := bindMounts["/etc/resolv.conf"] if !c.config.UseImageResolvConf && exists { - c.state.BindMounts["/etc/resolv.conf"] = resolvPath + err := c.mountIntoRootDirs("/etc/resolv.conf", resolvPath) + + if err != nil { + return errors.Wrapf(err, "error assigning mounts to container %s", c.ID()) + } } // check if dependency container has an /etc/hosts file. @@ -1884,7 +1899,11 @@ func (c *Container) makeBindMounts() error { depCtr.lock.Unlock() // finally, save it in the new container - c.state.BindMounts["/etc/hosts"] = hostsPath + err := c.mountIntoRootDirs("/etc/hosts", hostsPath) + + if err != nil { + return errors.Wrapf(err, "error assigning mounts to container %s", c.ID()) + } } if !hasCurrentUserMapped(c) { @@ -1901,7 +1920,11 @@ func (c *Container) makeBindMounts() error { if err != nil { return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID()) } - c.state.BindMounts["/etc/resolv.conf"] = newResolv + err = c.mountIntoRootDirs("/etc/resolv.conf", newResolv) + + if err != nil { + return errors.Wrapf(err, "error assigning mounts to container %s", c.ID()) + } } if !c.config.UseImageHosts { @@ -2329,7 +2352,11 @@ func (c *Container) updateHosts(path string) error { if err != nil { return err } - c.state.BindMounts["/etc/hosts"] = newHosts + + if err = c.mountIntoRootDirs("/etc/hosts", newHosts); err != nil { + return err + } + return nil } diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index 804b2b143..ae2ce9724 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -75,6 +75,10 @@ type InspectContainerConfig struct { StopTimeout uint `json:"StopTimeout"` // Passwd determines whether or not podman can add entries to /etc/passwd and /etc/group Passwd *bool `json:"Passwd,omitempty"` + // ChrootDirs is an additional set of directories that need to be + // treated as root directories. Standard bind mounts will be mounted + // into paths relative to these directories. + ChrootDirs []string `json:"ChrootDirs,omitempty"` } // InspectRestartPolicy holds information about the container's restart policy. diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go index 690f0c1fa..cc44f78f7 100644 --- a/libpod/networking_slirp4netns.go +++ b/libpod/networking_slirp4netns.go @@ -13,6 +13,7 @@ import ( "path/filepath" "strconv" "strings" + "sync" "syscall" "time" @@ -302,11 +303,15 @@ func (r *Runtime) setupSlirp4netns(ctr *Container, netns ns.NetNS) error { cmd.Stdout = logFile cmd.Stderr = logFile - var slirpReadyChan (chan struct{}) - + var slirpReadyWg, netnsReadyWg *sync.WaitGroup if netOptions.enableIPv6 { - slirpReadyChan = make(chan struct{}) - defer close(slirpReadyChan) + // use two wait groups to make sure we set the sysctl before + // starting slirp and reset it only after slirp is ready + slirpReadyWg = &sync.WaitGroup{} + netnsReadyWg = &sync.WaitGroup{} + slirpReadyWg.Add(1) + netnsReadyWg.Add(1) + go func() { err := ns.WithNetNSPath(netnsPath, func(_ ns.NetNS) error { // Duplicate Address Detection slows the ipv6 setup down for 1-2 seconds. @@ -318,23 +323,37 @@ func (r *Runtime) setupSlirp4netns(ctr *Container, netns ns.NetNS) error { // is ready in case users rely on this sysctl. orgValue, err := ioutil.ReadFile(ipv6ConfDefaultAcceptDadSysctl) if err != nil { + netnsReadyWg.Done() + // on ipv6 disabled systems the sysctl does not exists + // so we should not error + if errors.Is(err, os.ErrNotExist) { + return nil + } return err } err = ioutil.WriteFile(ipv6ConfDefaultAcceptDadSysctl, []byte("0"), 0644) + netnsReadyWg.Done() if err != nil { return err } - // wait for slirp to finish setup - <-slirpReadyChan + + // wait until slirp4nets is ready before reseting this value + slirpReadyWg.Wait() return ioutil.WriteFile(ipv6ConfDefaultAcceptDadSysctl, orgValue, 0644) }) if err != nil { logrus.Warnf("failed to set net.ipv6.conf.default.accept_dad sysctl: %v", err) } }() + + // wait until we set the sysctl + netnsReadyWg.Wait() } if err := cmd.Start(); err != nil { + if netOptions.enableIPv6 { + slirpReadyWg.Done() + } return errors.Wrapf(err, "failed to start slirp4netns process") } defer func() { @@ -344,11 +363,12 @@ func (r *Runtime) setupSlirp4netns(ctr *Container, netns ns.NetNS) error { } }() - if err := waitForSync(syncR, cmd, logFile, 1*time.Second); err != nil { - return err + err = waitForSync(syncR, cmd, logFile, 1*time.Second) + if netOptions.enableIPv6 { + slirpReadyWg.Done() } - if slirpReadyChan != nil { - slirpReadyChan <- struct{}{} + if err != nil { + return err } // Set a default slirp subnet. Parsing a string with the net helper is easier than building the struct myself diff --git a/libpod/options.go b/libpod/options.go index 1ee4e7322..2e5454393 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -2036,3 +2036,18 @@ func WithVolatile() CtrCreateOption { return nil } } + +// WithChrootDirs is an additional set of directories that need to be +// treated as root directories. Standard bind mounts will be mounted +// into paths relative to these directories. +func WithChrootDirs(dirs []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.ChrootDirs = dirs + + return nil + } +} diff --git a/pkg/bindings/containers/checkpoint.go b/pkg/bindings/containers/checkpoint.go index 1d8c34b33..84590d052 100644 --- a/pkg/bindings/containers/checkpoint.go +++ b/pkg/bindings/containers/checkpoint.go @@ -79,7 +79,7 @@ func Restore(ctx context.Context, nameOrID string, options *RestoreOptions) (*en // Open the to-be-imported archive if needed. var r io.Reader - if i := options.GetImportAchive(); i != "" { + if i := options.GetImportArchive(); i != "" { params.Set("import", "true") r, err = os.Open(i) if err != nil { diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go index 66b90af9b..3c8b1eefa 100644 --- a/pkg/bindings/containers/types.go +++ b/pkg/bindings/containers/types.go @@ -64,7 +64,7 @@ type RestoreOptions struct { IgnoreVolumes *bool IgnoreStaticIP *bool IgnoreStaticMAC *bool - ImportAchive *string + ImportArchive *string Keep *bool Name *string TCPEstablished *bool diff --git a/pkg/bindings/containers/types_restore_options.go b/pkg/bindings/containers/types_restore_options.go index d2778396a..e8a0e236c 100644 --- a/pkg/bindings/containers/types_restore_options.go +++ b/pkg/bindings/containers/types_restore_options.go @@ -77,19 +77,19 @@ func (o *RestoreOptions) GetIgnoreStaticMAC() bool { return *o.IgnoreStaticMAC } -// WithImportAchive set field ImportAchive to given value -func (o *RestoreOptions) WithImportAchive(value string) *RestoreOptions { - o.ImportAchive = &value +// WithImportArchive set field ImportArchive to given value +func (o *RestoreOptions) WithImportArchive(value string) *RestoreOptions { + o.ImportArchive = &value return o } -// GetImportAchive returns value of field ImportAchive -func (o *RestoreOptions) GetImportAchive() string { - if o.ImportAchive == nil { +// GetImportArchive returns value of field ImportArchive +func (o *RestoreOptions) GetImportArchive() string { + if o.ImportArchive == nil { var z string return z } - return *o.ImportAchive + return *o.ImportArchive } // WithKeep set field Keep to given value diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 6fb3db1b5..da93d3f8b 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -263,6 +263,7 @@ type ContainerCreateOptions struct { Workdir string SeccompPolicy string PidFile string + ChrootDirs []string IsInfra bool IsClone bool diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index fe986361b..046c2509d 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -390,7 +390,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st options.WithPublishPorts(opts.PublishPorts) if opts.Import != "" { - options.WithImportAchive(opts.Import) + options.WithImportArchive(opts.Import) report, err := containers.Restore(ic.ClientCtx, "", options) return []*entities.RestoreReport{report}, err } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index c0b23953f..8ab0eae5a 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -526,6 +526,10 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. options = append(options, libpod.WithPidFile(s.PidFile)) } + if len(s.ChrootDirs) != 0 { + options = append(options, libpod.WithChrootDirs(s.ChrootDirs)) + } + options = append(options, libpod.WithSelectedPasswordManagement(s.Passwd)) return options, nil diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 7f6f79b87..27d77af9f 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -301,6 +301,10 @@ type ContainerStorageConfig struct { // Volatile specifies whether the container storage can be optimized // at the cost of not syncing all the dirty files in memory. Volatile bool `json:"volatile,omitempty"` + // ChrootDirs is an additional set of directories that need to be + // treated as root directories. Standard bind mounts will be mounted + // into paths relative to these directories. + ChrootDirs []string `json:"chroot_directories,omitempty"` } // ContainerSecurityConfig is a container's security features, including diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index b037e14cc..b87da61fb 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -819,6 +819,9 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions if !s.UnsetEnvAll { s.UnsetEnvAll = c.UnsetEnvAll } + if len(s.ChrootDirs) == 0 || len(c.ChrootDirs) != 0 { + s.ChrootDirs = c.ChrootDirs + } // Initcontainers if len(s.InitContainerType) == 0 || len(c.InitContainerType) != 0 { diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 6a4a394ef..339fa66d8 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -706,4 +706,34 @@ var _ = Describe("Podman create", func() { Expect(create.ErrorToString()).To(ContainSubstring("cannot specify a new uid/gid map when entering a pod with an infra container")) }) + + It("podman create --chrootdirs inspection test", func() { + session := podmanTest.Podman([]string{"create", "--chrootdirs", "/var/local/qwerty", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + setup := podmanTest.Podman([]string{"container", "inspect", session.OutputToString()}) + setup.WaitWithDefaultTimeout() + Expect(setup).Should(Exit(0)) + + data := setup.InspectContainerToJSON() + Expect(data).To(HaveLen(1)) + Expect(data[0].Config.ChrootDirs).To(HaveLen(1)) + Expect(data[0].Config.ChrootDirs[0]).To(Equal("/var/local/qwerty")) + }) + + It("podman create --chrootdirs functionality test", func() { + session := podmanTest.Podman([]string{"create", "-t", "--chrootdirs", "/var/local/qwerty", ALPINE, "/bin/cat"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + ctrID := session.OutputToString() + + setup := podmanTest.Podman([]string{"start", ctrID}) + setup.WaitWithDefaultTimeout() + Expect(setup).Should(Exit(0)) + + setup = podmanTest.Podman([]string{"exec", ctrID, "cmp", "/etc/resolv.conf", "/var/local/qwerty/etc/resolv.conf"}) + setup.WaitWithDefaultTimeout() + Expect(setup).Should(Exit(0)) + }) }) |