diff options
-rw-r--r-- | .cirrus.yml | 23 | ||||
-rwxr-xr-x | API.md | 37 | ||||
-rw-r--r-- | RELEASE_NOTES.md | 7 | ||||
-rw-r--r-- | cmd/podman/libpodruntime/runtime.go | 2 | ||||
-rw-r--r-- | cmd/podman/varlink/io.podman.varlink | 37 | ||||
-rw-r--r-- | docs/podman-events.1.md | 8 | ||||
-rw-r--r-- | libpod/container_internal.go | 2 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 48 | ||||
-rw-r--r-- | libpod/container_internal_unsupported.go | 4 | ||||
-rw-r--r-- | libpod/networking_linux.go | 62 | ||||
-rw-r--r-- | libpod/oci_internal_linux.go | 33 | ||||
-rw-r--r-- | libpod/runtime_pod_infra_linux.go | 4 | ||||
-rw-r--r-- | pkg/adapter/pods.go | 4 | ||||
-rw-r--r-- | pkg/lookup/lookup.go | 30 | ||||
-rw-r--r-- | pkg/netns/netns_linux.go | 35 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 2 | ||||
-rw-r--r-- | pkg/spec/storage.go | 3 | ||||
-rw-r--r-- | rootless.md | 1 | ||||
-rw-r--r-- | test/e2e/mount_test.go | 120 | ||||
-rw-r--r-- | test/e2e/run_test.go | 6 | ||||
-rw-r--r-- | test/e2e/run_volume_test.go | 10 | ||||
-rw-r--r-- | test/system/030-run.bats | 21 |
22 files changed, 436 insertions, 63 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index f302d8a89..d128337c3 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -633,6 +633,28 @@ verify_test_built_images_task: <<: *standardlogs +upload_snap_task: + + # Only when PR or branch is merged into master + only_if: $CIRRUS_BRANCH == $DEST_BRANCH + + depends_on: + - "test_building_snap" + + container: + image: yakshaveinc/snapcraft:core18 + + env: + SNAPCRAFT_LOGIN: ENCRYPTED[d8e82eb31c6372fec07f405f413d57806026b1a9f8400033531ebcd54d6750a5e4a8b1f68e3ec65c98c65e0d9b2a6a75] + snapcraft_login_file: + path: /root/.snapcraft/login.cfg + variable_name: SNAPCRAFT_LOGIN + snapcraft_script: + - 'apt-get -y update' + - 'snapcraft login --with "/root/.snapcraft/login.cfg"' + - 'cd contrib/snapcraft && snapcraft && snapcraft push *.snap --release edge' + + # Post message to IRC if everything passed PR testing success_task: @@ -658,6 +680,7 @@ success_task: - "special_testing_endpoint" - "test_build_cache_images" - "test_building_snap" + - "upload_snap" - "verify_test_built_images" env: @@ -304,8 +304,43 @@ method AttachControl(name: [string](https://godoc.org/builtin#string)) </div> method BuildImage(build: [BuildInfo](#BuildInfo)) [MoreResponse](#MoreResponse)</div> BuildImage takes a [BuildInfo](#BuildInfo) structure and builds an image. At a minimum, you must provide the -'dockerfile' and 'tags' options in the BuildInfo structure. It will return a [MoreResponse](#MoreResponse) structure +contextDir tarball path, the 'dockerfiles' path, and 'output' option in the BuildInfo structure. The 'output' +options is the name of the of the resulting build. It will return a [MoreResponse](#MoreResponse) structure that contains the build logs and resulting image ID. +#### Example +~~~ +$ sudo varlink call -m unix:///run/podman/io.podman/io.podman.BuildImage '{"build":{"contextDir":"/tmp/t/context.tar","dockerfiles":["Dockerfile"], "output":"foobar"}}' +{ + "image": { + "id": "", + "logs": [ + "STEP 1: FROM alpine\n" + ] + } +} +{ + "image": { + "id": "", + "logs": [ + "STEP 2: COMMIT foobar\n" + ] + } +} +{ + "image": { + "id": "", + "logs": [ + "b7b28af77ffec6054d13378df4fdf02725830086c7444d9c278af25312aa39b9\n" + ] + } +} +{ + "image": { + "id": "b7b28af77ffec6054d13378df4fdf02725830086c7444d9c278af25312aa39b9", + "logs": [] + } +} +~~~ ### <a name="BuildImageHierarchyMap"></a>func BuildImageHierarchyMap <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5c9c06687..4fcdd68de 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,6 +8,7 @@ - The `podman volume rm` and `podman volume inspect` commands can now refer to volumes by an unambiguous partial name, in addition to full name (e.g. `podman volume rm myvol` to remove a volume named `myvolume`) ([#3891](https://github.com/containers/libpod/issues/3891)) - The `podman run` and `podman create` commands now support the `--pull` flag to allow forced re-pulling of images ([#3734](https://github.com/containers/libpod/issues/3734)) - Mounting volumes into a container using `--volume`, `--mount`, and `--tmpfs` now allows the `suid`, `dev`, and `exec` mount options (the inverse of `nosuid`, `nodev`, `noexec`) ([#3819](https://github.com/containers/libpod/issues/3819)) +- Mounting volumes into a container using `--mount` now allows the `relabel=Z` and `relabel=z` options to relabel mounts. - The `podman push` command now supports the `--digestfile` option to save a file containing the pushed digest - Pods can now have their hostname set via `podman pod create --hostname` or providing Pod YAML with a hostname set to `podman play kube` ([#3732](https://github.com/containers/libpod/issues/3732)) - The `podman image sign` command now supports the `--cert-dir` flag @@ -37,12 +38,18 @@ - Fixed a bug where `podman exec` would run as the wrong user when execing into a container was started from an image with Dockerfile `USER` (or a user specified via `podman run --user`) ([#3838](https://github.com/containers/libpod/issues/3838)) - Fixed a bug where images pulled using the `oci:` transport would be improperly named - Fixed a bug where `podman varlink` would hang when managed by systemd due to SD_NOTIFY support conflicting with Varlink ([#3572](https://github.com/containers/libpod/issues/3572)) +- Fixed a bug where mounts to the same destination would sometimes not trigger a conflict, causing a race as to which was actually mounted +- Fixed a bug where `podman exec --preserve-fds` caused Podman to hang ([#4020](https://github.com/containers/libpod/issues/4020)) +- Fixed a bug where removing an unmounted container that was unmounted might sometimes not properly clean up the container ([#4033](https://github.com/containers/libpod/issues/4033)) +- Fixed a bug where the Varlink server would freeze when run in a systemd unit file ([#4005](https://github.com/containers/libpod/issues/4005)) +- Fixed a bug where Podman would not properly set the `$HOME` environment variable when the OCI runtime did not set it ### Misc - Significant changes were made to Podman volumes in this release. If you have pre-existing volumes, it is strongly recommended to run `podman system renumber` after upgrading. - Version 0.8.1 or greater of the CNI Plugins is now required for Podman - Version 2.0.1 or greater of Conmon is strongly recommended - Updated vendored Buildah to v1.11.2 +- Updated vendored containers/storage library to v1.13.3 - Improved error messages when trying to run `podman pause` or `podman stats` on a rootless container on a system without CGroups V2 enabled - `TMPDIR` has been set to `/var/tmp` by default to better handle large temporary files - `podman wait` has been optimized to detect stopped containers more rapidly diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index a133549ea..6dafeb0b0 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -171,7 +171,7 @@ func getRuntime(ctx context.Context, c *cliconfig.PodmanCommand, renumber, migra options = append(options, libpod.WithDefaultInfraCommand(infraCommand)) } - if withFDS { + if !withFDS { options = append(options, libpod.WithEnableSDNotify()) } if c.Flags().Changed("config") { diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 4692525e3..7239f5d2e 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -765,8 +765,43 @@ method ListImages() -> (images: []Image) method GetImage(id: string) -> (image: Image) # BuildImage takes a [BuildInfo](#BuildInfo) structure and builds an image. At a minimum, you must provide the -# 'dockerfile' and 'tags' options in the BuildInfo structure. It will return a [MoreResponse](#MoreResponse) structure +# contextDir tarball path, the 'dockerfiles' path, and 'output' option in the BuildInfo structure. The 'output' +# options is the name of the of the resulting build. It will return a [MoreResponse](#MoreResponse) structure # that contains the build logs and resulting image ID. +# #### Example +# ~~~ +# $ sudo varlink call -m unix:///run/podman/io.podman/io.podman.BuildImage '{"build":{"contextDir":"/tmp/t/context.tar","dockerfiles":["Dockerfile"], "output":"foobar"}}' +# { +# "image": { +# "id": "", +# "logs": [ +# "STEP 1: FROM alpine\n" +# ] +# } +# } +# { +# "image": { +# "id": "", +# "logs": [ +# "STEP 2: COMMIT foobar\n" +# ] +# } +# } +# { +# "image": { +# "id": "", +# "logs": [ +# "b7b28af77ffec6054d13378df4fdf02725830086c7444d9c278af25312aa39b9\n" +# ] +# } +# } +# { +# "image": { +# "id": "b7b28af77ffec6054d13378df4fdf02725830086c7444d9c278af25312aa39b9", +# "logs": [] +# } +# } +# ~~~ method BuildImage(build: BuildInfo) -> (image: MoreResponse) # This function is not implemented yet. diff --git a/docs/podman-events.1.md b/docs/podman-events.1.md index a5a715098..bb1923574 100644 --- a/docs/podman-events.1.md +++ b/docs/podman-events.1.md @@ -11,7 +11,9 @@ podman\-events - Monitor Podman events Monitor and print events that occur in Podman. Each event will include a timestamp, a type, a status, name (if applicable), and image (if applicable). The default logging mechanism is *journald*. This can be changed in libpod.conf by changing the `events_logger` -value to `file`. Only `file` and `journald` are the accepted. +value to `file`. Only `file` and `journald` are accepted. A `none` logger is also +available but this logging mechanism completely disables events; nothing will be reported by +`podman events`. The *container* event type will report the follow statuses: * attach @@ -54,6 +56,10 @@ The *image* event type will report the following statuses: * tag * untag +The *system* type will report the following statuses: + * refresh + * renumber + The *volume* type will report the following statuses: * create * prune diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 8b96b3f62..7403a216b 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -622,7 +622,7 @@ func (c *Container) refresh() error { return err } - return nil + return c.refreshCNI() } // Remove conmon attach socket and terminal resize FIFO diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 355b9bea4..2636fdb6c 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -78,15 +78,21 @@ func (c *Container) prepare() (Err error) { // Set up network namespace if not already set up if c.config.CreateNetNS && c.state.NetNS == nil && !c.config.PostConfigureNetNS { netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c) + if createNetNSErr != nil { + return + } tmpStateLock.Lock() defer tmpStateLock.Unlock() // Assign NetNS attributes to container - if createNetNSErr == nil { - c.state.NetNS = netNS - c.state.NetworkStatus = networkStatus - } + c.state.NetNS = netNS + c.state.NetworkStatus = networkStatus + } + + // handle rootless network namespace setup + if c.state.NetNS != nil && c.config.NetMode == "slirp4netns" && !c.config.PostConfigureNetNS { + createNetNSErr = c.runtime.setupRootlessNetNS(c) } }() // Mount storage if not mounted @@ -181,6 +187,30 @@ func (c *Container) cleanupNetwork() error { return nil } +func (c *Container) getUserOverrides() *lookup.Overrides { + var hasPasswdFile, hasGroupFile bool + overrides := lookup.Overrides{} + for _, m := range c.config.Spec.Mounts { + if m.Destination == "/etc/passwd" { + overrides.ContainerEtcPasswdPath = m.Source + hasPasswdFile = true + } + if m.Destination == "/etc/group" { + overrides.ContainerEtcGroupPath = m.Source + hasGroupFile = true + } + if m.Destination == "/etc" { + if !hasPasswdFile { + overrides.ContainerEtcPasswdPath = filepath.Join(m.Source, "passwd") + } + if !hasGroupFile { + overrides.ContainerEtcGroupPath = filepath.Join(m.Source, "group") + } + } + } + return &overrides +} + // Generate spec for a container // Accepts a map of the container's dependencies func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { @@ -188,7 +218,8 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { span.SetTag("type", "container") defer span.Finish() - execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, nil) + overrides := c.getUserOverrides() + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, overrides) if err != nil { return nil, err } @@ -1294,3 +1325,10 @@ func (c *Container) copyOwnerAndPerms(source, dest string) error { } return nil } + +// Teardown CNI config on refresh +func (c *Container) refreshCNI() error { + // Let's try and delete any lingering network config... + podNetwork := c.runtime.getPodNetwork(c.ID(), c.config.Name, "", c.config.Networks, c.config.PortMappings, c.config.StaticIP) + return c.runtime.netPlugin.TearDownPod(podNetwork) +} diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go index 6fa19a778..05a587c59 100644 --- a/libpod/container_internal_unsupported.go +++ b/libpod/container_internal_unsupported.go @@ -40,3 +40,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti func (c *Container) copyOwnerAndPerms(source, dest string) error { return nil } + +func (c *Container) refreshCNI() error { + return define.ErrNotImplemented +} diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 67dd0150b..d854a2de6 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -90,9 +90,6 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re // Create and configure a new network namespace for a container func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result, err error) { - if rootless.IsRootless() { - return nil, nil, errors.New("cannot configure a new network namespace in rootless mode, only --network=slirp4netns is supported") - } ctrNS, err := netns.NewNS() if err != nil { return nil, nil, errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID()) @@ -110,7 +107,10 @@ func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result, logrus.Debugf("Made network namespace at %s for container %s", ctrNS.Path(), ctr.ID()) - networkStatus, err := r.configureNetNS(ctr, ctrNS) + networkStatus := []*cnitypes.Result{} + if !rootless.IsRootless() { + networkStatus, err = r.configureNetNS(ctr, ctrNS) + } return ctrNS, networkStatus, err } @@ -138,9 +138,6 @@ func checkSlirpFlags(path string) (bool, bool, bool, error) { // Configure the network namespace for a rootless container func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { - defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR) - defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW) - path := r.config.NetworkCmdPath if path == "" { @@ -164,7 +161,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { cmdArgs := []string{} if havePortMapping { - cmdArgs = append(cmdArgs, "--api-socket", apiSocket, fmt.Sprintf("%d", ctr.state.PID)) + cmdArgs = append(cmdArgs, "--api-socket", apiSocket) } dhp, mtu, sandbox, err := checkSlirpFlags(path) if err != nil { @@ -179,13 +176,32 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { if sandbox { cmdArgs = append(cmdArgs, "--enable-sandbox") } - cmdArgs = append(cmdArgs, "-c", "-e", "3", "-r", "4", fmt.Sprintf("%d", ctr.state.PID), "tap0") - cmd := exec.Command(path, cmdArgs...) + // the slirp4netns arguments being passed are describes as follows: + // from the slirp4netns documentation: https://github.com/rootless-containers/slirp4netns + // -c, --configure Brings up the tap interface + // -e, --exit-fd=FD specify the FD for terminating slirp4netns + // -r, --ready-fd=FD specify the FD to write to when the initialization steps are finished + cmdArgs = append(cmdArgs, "-c", "-e", "3", "-r", "4") + if !ctr.config.PostConfigureNetNS { + ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() + if err != nil { + return errors.Wrapf(err, "failed to create rootless network sync pipe") + } + cmdArgs = append(cmdArgs, "--netns-type=path", ctr.state.NetNS.Path(), "tap0") + } else { + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR) + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW) + cmdArgs = append(cmdArgs, fmt.Sprintf("%d", ctr.state.PID), "tap0") + } + cmd := exec.Command(path, cmdArgs...) + logrus.Debugf("slirp4netns command: %s", strings.Join(cmd.Args, " ")) cmd.SysProcAttr = &syscall.SysProcAttr{ Setpgid: true, } + + // Leak one end of the pipe in slirp4netns, the other will be sent to conmon cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncR, syncW) if err := cmd.Start(); err != nil { @@ -388,20 +404,22 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) - var requestedIP net.IP - if ctr.requestedIP != nil { - requestedIP = ctr.requestedIP - // cancel request for a specific IP in case the container is reused later - ctr.requestedIP = nil - } else { - requestedIP = ctr.config.StaticIP - } + // rootless containers do not use the CNI plugin + if !rootless.IsRootless() { + var requestedIP net.IP + if ctr.requestedIP != nil { + requestedIP = ctr.requestedIP + // cancel request for a specific IP in case the container is reused later + ctr.requestedIP = nil + } else { + requestedIP = ctr.config.StaticIP + } - podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) - // The network may have already been torn down, so don't fail here, just log - if err := r.netPlugin.TearDownPod(podNetwork); err != nil { - return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID()) + if err := r.netPlugin.TearDownPod(podNetwork); err != nil { + return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID()) + } } // First unmount the namespace diff --git a/libpod/oci_internal_linux.go b/libpod/oci_internal_linux.go index 4df1e4010..a5cce795b 100644 --- a/libpod/oci_internal_linux.go +++ b/libpod/oci_internal_linux.go @@ -131,9 +131,14 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Containe } if ctr.config.NetMode.IsSlirp4netns() { - ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() - if err != nil { - return errors.Wrapf(err, "failed to create rootless network sync pipe") + if ctr.config.PostConfigureNetNS { + ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() + if err != nil { + return errors.Wrapf(err, "failed to create rootless network sync pipe") + } + } else { + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR) + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW) } // Leak one end in conmon, the other one will be leaked into slirp4netns cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW) @@ -200,13 +205,16 @@ func prepareProcessExec(c *Container, cmd, env []string, tty bool, cwd, user, se pspec.Cwd = cwd } + + overrides := c.getUserOverrides() + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides) + if err != nil { + return nil, err + } + // If user was set, look it up in the container to get a UID to use on // the host if user != "" { - execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, nil) - if err != nil { - return nil, err - } sgids := make([]uint32, 0, len(execUser.Sgids)) for _, sgid := range execUser.Sgids { sgids = append(sgids, uint32(sgid)) @@ -220,6 +228,17 @@ func prepareProcessExec(c *Container, cmd, env []string, tty bool, cwd, user, se pspec.User = processUser } + hasHomeSet := false + for _, s := range pspec.Env { + if strings.HasPrefix(s, "HOME=") { + hasHomeSet = true + break + } + } + if !hasHomeSet { + pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home)) + } + processJSON, err := json.Marshal(pspec) if err != nil { return nil, err diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index ad6662f03..6a27c2800 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -99,7 +99,9 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID if isRootless { netmode = "slirp4netns" } - options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, isRootless, netmode, networks)) + // PostConfigureNetNS should not be set since user namespace sharing is not implemented + // and rootless networking no longer supports post configuration setup + options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, false, netmode, networks)) return r.newContainer(ctx, g.Config, options...) } diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index 70293a2c5..2905d5466 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -467,6 +467,10 @@ func (r *LocalRuntime) PlayKubeYAML(ctx context.Context, c *cliconfig.KubePlayVa return nil, errors.Wrapf(err, "unable to read %s as YAML", yamlFile) } + if podYAML.Kind != "Pod" { + return nil, errors.Errorf("Invalid YAML kind: %s. Pod is the only supported Kubernetes YAML kind", podYAML.Kind) + } + // check for name collision between pod and container podName := podYAML.ObjectMeta.Name for _, n := range podYAML.Spec.Containers { diff --git a/pkg/lookup/lookup.go b/pkg/lookup/lookup.go index 70b97144f..a249dd753 100644 --- a/pkg/lookup/lookup.go +++ b/pkg/lookup/lookup.go @@ -29,17 +29,30 @@ func GetUserGroupInfo(containerMount, containerUser string, override *Overrides) defaultExecUser *user.ExecUser err error ) - passwdPath := etcpasswd - groupPath := etcgroup if override != nil { // Check for an override /etc/passwd path if override.ContainerEtcPasswdPath != "" { - passwdPath = override.ContainerEtcPasswdPath + passwdDest = override.ContainerEtcPasswdPath } // Check for an override for /etc/group path if override.ContainerEtcGroupPath != "" { - groupPath = override.ContainerEtcGroupPath + groupDest = override.ContainerEtcGroupPath + } + } + + if passwdDest == "" { + // Make sure the /etc/passwd destination is not a symlink to something naughty + if passwdDest, err = securejoin.SecureJoin(containerMount, etcpasswd); err != nil { + logrus.Debug(err) + return nil, err + } + } + if groupDest == "" { + // Make sure the /etc/group destination is not a symlink to something naughty + if groupDest, err = securejoin.SecureJoin(containerMount, etcgroup); err != nil { + logrus.Debug(err) + return nil, err } } @@ -56,15 +69,6 @@ func GetUserGroupInfo(containerMount, containerUser string, override *Overrides) } - // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty - if passwdDest, err = securejoin.SecureJoin(containerMount, passwdPath); err != nil { - logrus.Debug(err) - return nil, err - } - if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil { - logrus.Debug(err) - return nil, err - } return user.GetExecUserPath(containerUser, defaultExecUser, passwdDest, groupDest) } diff --git a/pkg/netns/netns_linux.go b/pkg/netns/netns_linux.go index 1d6fb873c..a62296549 100644 --- a/pkg/netns/netns_linux.go +++ b/pkg/netns/netns_linux.go @@ -23,23 +23,42 @@ import ( "fmt" "os" "path" + "path/filepath" "runtime" "strings" "sync" "github.com/containernetworking/plugins/pkg/ns" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) -const nsRunDir = "/var/run/netns" +// get NSRunDir returns the dir of where to create the netNS. When running +// rootless, it needs to be at a location writable by user. +func getNSRunDir() (string, error) { + if rootless.IsRootless() { + rootlessDir, err := util.GetRuntimeDir() + if err != nil { + return "", err + } + return filepath.Join(rootlessDir, "netns"), nil + } + return "/var/run/netns", nil +} // NewNS creates a new persistent (bind-mounted) network namespace and returns // an object representing that namespace, without switching to it. func NewNS() (ns.NetNS, error) { + nsRunDir, err := getNSRunDir() + if err != nil { + return nil, err + } + b := make([]byte, 16) - _, err := rand.Reader.Read(b) + _, err = rand.Reader.Read(b) if err != nil { return nil, fmt.Errorf("failed to generate random netns name: %v", err) } @@ -127,14 +146,15 @@ func NewNS() (ns.NetNS, error) { // Put this thread back to the orig ns, since it might get reused (pre go1.10) defer func() { if err := origNS.Set(); err != nil { - logrus.Errorf("unable to set namespace: %q", err) + logrus.Warnf("unable to set namespace: %q", err) } }() // bind mount the netns from the current thread (from /proc) onto the // mount point. This causes the namespace to persist, even when there - // are no threads in the ns. - err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND, "") + // are no threads in the ns. Make this a shared mount; it needs to be + // back-propogated to the host + err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND|unix.MS_SHARED|unix.MS_REC, "") if err != nil { err = fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err) } @@ -150,6 +170,11 @@ func NewNS() (ns.NetNS, error) { // UnmountNS unmounts the NS held by the netns object func UnmountNS(ns ns.NetNS) error { + nsRunDir, err := getNSRunDir() + if err != nil { + return err + } + nsPath := ns.Path() // Only unmount if it's been bind-mounted (don't touch namespaces in /proc...) if strings.HasPrefix(nsPath, nsRunDir) { diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index c17172016..7c3195be4 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -275,7 +275,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, libpod.WithNetNSFrom(connectedCtr)) } else if !c.NetMode.IsHost() && !c.NetMode.IsNone() { hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0 - postConfigureNetNS := c.NetMode.IsSlirp4netns() || (hasUserns && !c.UsernsMode.IsHost()) + postConfigureNetNS := hasUserns && !c.UsernsMode.IsHost() options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks)) } diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index 3d59d70d8..93919dd0a 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -168,6 +168,9 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount, if _, ok := baseMounts[dest]; ok { continue } + if _, ok := baseVolumes[dest]; ok { + continue + } localOpts := options if dest == "/run" { localOpts = append(localOpts, "noexec", "size=65536k") diff --git a/rootless.md b/rootless.md index 53463dccc..8cccb86eb 100644 --- a/rootless.md +++ b/rootless.md @@ -27,7 +27,6 @@ can easily fail * Can not use overlayfs driver, but does support fuse-overlayfs * Ubuntu supports non root overlay, but no other Linux distros do. * Only other supported driver is VFS. -* No KATA Container support * No CNI Support * CNI wants to modify IPTables, plus other network manipulation that requires CAP_SYS_ADMIN. * There is potential we could probably do some sort of blacklisting of the relevant plugins, and add a new plugin for rootless networking - slirp4netns as one example and there may be others diff --git a/test/e2e/mount_test.go b/test/e2e/mount_test.go index 3197aa655..dda83ba31 100644 --- a/test/e2e/mount_test.go +++ b/test/e2e/mount_test.go @@ -156,4 +156,124 @@ var _ = Describe("Podman mount", func() { umount.WaitWithDefaultTimeout() Expect(umount.ExitCode()).To(Equal(0)) }) + + It("podman list mounted container", func() { + setup := podmanTest.Podman([]string{"create", ALPINE, "ls"}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid := setup.OutputToString() + + lmount := podmanTest.Podman([]string{"mount", "--notruncate"}) + lmount.WaitWithDefaultTimeout() + Expect(lmount.ExitCode()).To(Equal(0)) + Expect(lmount.OutputToString()).To(Equal("")) + + mount := podmanTest.Podman([]string{"mount", cid}) + mount.WaitWithDefaultTimeout() + Expect(mount.ExitCode()).To(Equal(0)) + + lmount = podmanTest.Podman([]string{"mount", "--notruncate"}) + lmount.WaitWithDefaultTimeout() + Expect(lmount.ExitCode()).To(Equal(0)) + Expect(lmount.OutputToString()).To(ContainSubstring(cid)) + + umount := podmanTest.Podman([]string{"umount", cid}) + umount.WaitWithDefaultTimeout() + Expect(umount.ExitCode()).To(Equal(0)) + }) + + It("podman list running container", func() { + SkipIfRootless() + + setup := podmanTest.Podman([]string{"run", "-dt", ALPINE, "top"}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid := setup.OutputToString() + + lmount := podmanTest.Podman([]string{"mount", "--notruncate"}) + lmount.WaitWithDefaultTimeout() + Expect(lmount.ExitCode()).To(Equal(0)) + Expect(lmount.OutputToString()).To(ContainSubstring(cid)) + + stop := podmanTest.Podman([]string{"stop", cid}) + stop.WaitWithDefaultTimeout() + Expect(stop.ExitCode()).To(Equal(0)) + + lmount = podmanTest.Podman([]string{"mount", "--notruncate"}) + lmount.WaitWithDefaultTimeout() + Expect(lmount.ExitCode()).To(Equal(0)) + Expect(lmount.OutputToString()).To(Equal("")) + }) + + It("podman list mulitple mounted containers", func() { + SkipIfRootless() + + setup := podmanTest.Podman([]string{"create", ALPINE, "ls"}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid1 := setup.OutputToString() + + setup = podmanTest.Podman([]string{"create", ALPINE, "ls"}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid2 := setup.OutputToString() + + setup = podmanTest.Podman([]string{"create", ALPINE, "ls"}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid3 := setup.OutputToString() + + lmount := podmanTest.Podman([]string{"mount", "--notruncate"}) + lmount.WaitWithDefaultTimeout() + Expect(lmount.ExitCode()).To(Equal(0)) + Expect(lmount.OutputToString()).To(Equal("")) + + mount := podmanTest.Podman([]string{"mount", cid1, cid3}) + mount.WaitWithDefaultTimeout() + Expect(mount.ExitCode()).To(Equal(0)) + + lmount = podmanTest.Podman([]string{"mount", "--notruncate"}) + lmount.WaitWithDefaultTimeout() + Expect(lmount.ExitCode()).To(Equal(0)) + Expect(lmount.OutputToString()).To(ContainSubstring(cid1)) + Expect(lmount.OutputToString()).ToNot(ContainSubstring(cid2)) + Expect(lmount.OutputToString()).To(ContainSubstring(cid3)) + + umount := podmanTest.Podman([]string{"umount", cid1, cid3}) + umount.WaitWithDefaultTimeout() + Expect(umount.ExitCode()).To(Equal(0)) + + lmount = podmanTest.Podman([]string{"mount", "--notruncate"}) + lmount.WaitWithDefaultTimeout() + Expect(lmount.ExitCode()).To(Equal(0)) + Expect(lmount.OutputToString()).To(Equal("")) + + }) + + It("podman list mounted container", func() { + SkipIfRootless() + + setup := podmanTest.Podman([]string{"create", ALPINE, "ls"}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid := setup.OutputToString() + + lmount := podmanTest.Podman([]string{"mount", "--notruncate"}) + lmount.WaitWithDefaultTimeout() + Expect(lmount.ExitCode()).To(Equal(0)) + Expect(lmount.OutputToString()).To(Equal("")) + + mount := podmanTest.Podman([]string{"mount", cid}) + mount.WaitWithDefaultTimeout() + Expect(mount.ExitCode()).To(Equal(0)) + + lmount = podmanTest.Podman([]string{"mount", "--notruncate"}) + lmount.WaitWithDefaultTimeout() + Expect(lmount.ExitCode()).To(Equal(0)) + Expect(lmount.OutputToString()).To(ContainSubstring(cid)) + + umount := podmanTest.Podman([]string{"umount", cid}) + umount.WaitWithDefaultTimeout() + Expect(umount.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 55920467c..1e6f1d97d 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -200,6 +200,12 @@ var _ = Describe("Podman run", func() { match, _ := session.GrepString("/root") Expect(match).Should(BeTrue()) + session = podmanTest.Podman([]string{"run", "--rm", "--user", "2", ALPINE, "printenv", "HOME"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ = session.GrepString("/sbin") + Expect(match).Should(BeTrue()) + session = podmanTest.Podman([]string{"run", "--rm", "--env", "HOME=/foo", ALPINE, "printenv", "HOME"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index fc1998ab2..bc3a14b66 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -270,4 +270,14 @@ var _ = Describe("Podman run with volumes", func() { Expect(separateVolumeSession.ExitCode()).To(Equal(0)) Expect(separateVolumeSession.OutputToString()).To(Equal(baselineOutput)) }) + + It("podman read-only tmpfs conflict with volume", func() { + session := podmanTest.Podman([]string{"run", "--rm", "-t", "-i", "--read-only", "-v", "tmp_volume:/run", ALPINE, "touch", "/run/a"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session2 := podmanTest.Podman([]string{"run", "--rm", "-t", "-i", "--read-only", "--tmpfs", "/run", ALPINE, "touch", "/run/a"}) + session2.WaitWithDefaultTimeout() + Expect(session2.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 65e13d559..7cbb60501 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -3,7 +3,6 @@ load helpers @test "podman run - basic tests" { - skip "Temporarily disabled during investigation into github issue 4044" rand=$(random_string 30) # 2019-09 Fedora 31 and rawhide (32) are switching from runc to crun @@ -28,6 +27,7 @@ echo $rand | 0 | $rand /etc | 126 | $err_no_exec_dir " + tests_run=0 while read cmd expected_rc expected_output; do if [ "$expected_output" = "''" ]; then expected_output=""; fi @@ -41,9 +41,24 @@ echo $rand | 0 | $rand # a way to do so. eval set "$cmd" - run_podman $expected_rc run $IMAGE "$@" - is "$output" "$expected_output" "podman run $cmd - output" + # FIXME: The </dev/null is a hack, necessary because as of 2019-09 + # podman-remote has a bug in which it silently slurps up stdin, + # including the output of parse_table (i.e. tests to be run). + run_podman $expected_rc run $IMAGE "$@" </dev/null + + # FIXME: remove conditional once podman-remote issue #4096 is fixed + if ! is_remote; then + is "$output" "$expected_output" "podman run $cmd - output" + fi + + tests_run=$(expr $tests_run + 1) done < <(parse_table "$tests") + + # Make sure we ran the expected number of tests! Until 2019-09-24 + # podman-remote was only running one test (the "true" one); all + # the rest were being silently ignored because of podman-remote + # bug #4095, in which it slurps up stdin. + is "$tests_run" "$(grep . <<<$tests | wc -l)" "Ran the full set of tests" } @test "podman run - uidmapping has no /sys/kernel mounts" { |