diff options
author | Daniel J Walsh <dwalsh@redhat.com> | 2018-04-23 20:42:53 -0400 |
---|---|---|
committer | Atomic Bot <atomic-devel@projectatomic.io> | 2018-05-04 17:15:55 +0000 |
commit | b51d7379987581da82902027fe91cdf298047bc0 (patch) | |
tree | f9d7fbebf3b946caea5eb5e2c626a19413c795c8 | |
parent | 1f5debd43806cc3bd07f562ff00ef4c426540f98 (diff) | |
download | podman-b51d7379987581da82902027fe91cdf298047bc0.tar.gz podman-b51d7379987581da82902027fe91cdf298047bc0.tar.bz2 podman-b51d7379987581da82902027fe91cdf298047bc0.zip |
Begin wiring in USERNS Support into podman
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
Closes: #690
Approved by: mheon
-rw-r--r-- | cmd/podman/common.go | 17 | ||||
-rw-r--r-- | cmd/podman/create.go | 71 | ||||
-rw-r--r-- | cmd/podman/inspect.go | 2 | ||||
-rw-r--r-- | cmd/podman/libpodruntime/runtime.go | 37 | ||||
-rw-r--r-- | cmd/podman/run.go | 16 | ||||
-rw-r--r-- | cmd/podman/run_test.go | 3 | ||||
-rw-r--r-- | cmd/podman/spec.go | 17 | ||||
-rw-r--r-- | completions/bash/podman | 4 | ||||
-rw-r--r-- | docs/podman-create.1.md | 38 | ||||
-rw-r--r-- | docs/podman-run.1.md | 38 | ||||
-rw-r--r-- | docs/podman.1.md | 1 | ||||
-rw-r--r-- | libpod/container.go | 28 | ||||
-rw-r--r-- | libpod/container_internal.go | 16 | ||||
-rw-r--r-- | libpod/options.go | 12 | ||||
-rw-r--r-- | libpod/storage.go | 16 | ||||
-rw-r--r-- | pkg/secrets/secrets.go | 23 | ||||
-rw-r--r-- | pkg/util/utils.go | 76 |
17 files changed, 345 insertions, 70 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 68d22b629..635869609 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -200,6 +200,10 @@ var createFlags = []cli.Flag{ Usage: "Expose a port or a range of ports (default [])", }, cli.StringSliceFlag{ + Name: "gidmap", + Usage: "GID map to use for the user namespace", + }, + cli.StringSliceFlag{ Name: "group-add", Usage: "Add additional groups to join (default [])", }, @@ -341,6 +345,15 @@ var createFlags = []cli.Flag{ Name: "storage-opt", Usage: "Storage driver options per container (default [])", }, + cli.StringFlag{ + Name: "subgidname", + Usage: "Name of range listed in /etc/subgid for use in user namespace", + }, + cli.StringFlag{ + Name: "subuidname", + Usage: "Name of range listed in /etc/subuid for use in user namespace", + }, + cli.StringSliceFlag{ Name: "sysctl", Usage: "Sysctl options (default [])", @@ -354,6 +367,10 @@ var createFlags = []cli.Flag{ Usage: "Allocate a pseudo-TTY for container", }, cli.StringSliceFlag{ + Name: "uidmap", + Usage: "UID map to use for the user namespace", + }, + cli.StringSliceFlag{ Name: "ulimit", Usage: "Ulimit options (default [])", }, diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 54a542ee5..7740da8e1 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -1,6 +1,7 @@ package main import ( + "context" "encoding/json" "fmt" "net" @@ -9,6 +10,7 @@ import ( "strings" "syscall" + "github.com/containers/storage" "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/signal" "github.com/docker/go-connections/nat" @@ -92,7 +94,8 @@ type createConfig struct { Hostname string //hostname Image string ImageID string - BuiltinImgVolumes map[string]struct{} // volumes defined in the image config + BuiltinImgVolumes map[string]struct{} // volumes defined in the image config + IDMappings *storage.IDMappingOptions ImageVolumeType string // how to handle the image volume, either bind, tmpfs, or ignore Interactive bool //interactive IpcMode container.IpcMode //ipc @@ -108,8 +111,7 @@ type createConfig struct { Network string //network NetworkAlias []string //network-alias PidMode container.PidMode //pid - NsUser string - Pod string //pod + Pod string //pod PortBindings nat.PortMap Privileged bool //privileged Publish []string //publish @@ -119,20 +121,21 @@ type createConfig struct { Resources createResourceConfig Rm bool //rm ShmDir string - StopSignal syscall.Signal // stop-signal - StopTimeout uint // stop-timeout - Sysctl map[string]string //sysctl - Tmpfs []string // tmpfs - Tty bool //tty - User string //user - UtsMode container.UTSMode //uts - Volumes []string //volume - WorkDir string //workdir - MountLabel string //SecurityOpts - ProcessLabel string //SecurityOpts - NoNewPrivs bool //SecurityOpts - ApparmorProfile string //SecurityOpts - SeccompProfilePath string //SecurityOpts + StopSignal syscall.Signal // stop-signal + StopTimeout uint // stop-timeout + Sysctl map[string]string //sysctl + Tmpfs []string // tmpfs + Tty bool //tty + UsernsMode container.UsernsMode //userns + User string //user + UtsMode container.UTSMode //uts + Volumes []string //volume + WorkDir string //workdir + MountLabel string //SecurityOpts + ProcessLabel string //SecurityOpts + NoNewPrivs bool //SecurityOpts + ApparmorProfile string //SecurityOpts + SeccompProfilePath string //SecurityOpts SecurityOpts []string } @@ -174,7 +177,15 @@ func createCmd(c *cli.Context) error { return errors.Errorf("image name or ID is required") } - runtime, err := libpodruntime.GetRuntime(c) + mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap")) + if err != nil { + return err + } + storageOpts := storage.DefaultStoreOptions + storageOpts.UIDMap = mappings.UIDMap + storageOpts.GIDMap = mappings.GIDMap + + runtime, err := libpodruntime.GetRuntimeWithStorageOpts(c, &storageOpts) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -188,7 +199,7 @@ func createCmd(c *cli.Context) error { return err } data, err := newImage.Inspect(ctx) - createConfig, err := parseCreateOpts(c, runtime, newImage.Names()[0], data) + createConfig, err := parseCreateOpts(ctx, c, runtime, newImage.Names()[0], data) if err != nil { return err } @@ -211,6 +222,7 @@ func createCmd(c *cli.Context) error { options = append(options, libpod.WithShmDir(createConfig.ShmDir)) options = append(options, libpod.WithShmSize(createConfig.Resources.ShmSize)) options = append(options, libpod.WithGroups(createConfig.GroupAdd)) + options = append(options, libpod.WithIDMappings(*createConfig.IDMappings)) ctr, err := runtime.NewContainer(ctx, runtimeSpec, options...) if err != nil { return err @@ -414,10 +426,16 @@ func getRandomPort() (int, error) { // Parses CLI options related to container creation into a config which can be // parsed into an OCI runtime spec -func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*createConfig, error) { - var inputCommand, command []string - var memoryLimit, memoryReservation, memorySwap, memoryKernel int64 - var blkioWeight uint16 +func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*createConfig, error) { + var ( + inputCommand, command []string + memoryLimit, memoryReservation, memorySwap, memoryKernel int64 + blkioWeight uint16 + ) + idmappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname")) + if err != nil { + return nil, err + } imageID := data.ID @@ -473,6 +491,11 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, return nil, errors.Errorf("--pid %q is not valid", c.String("pid")) } + usernsMode := container.UsernsMode(c.String("userns")) + if !usernsMode.Valid() { + return nil, errors.Errorf("--userns %q is not valid", c.String("userns")) + } + if c.Bool("detach") && c.Bool("rm") { return nil, errors.Errorf("--rm and --detach can not be specified together") } @@ -653,6 +676,7 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, GroupAdd: c.StringSlice("group-add"), Hostname: c.String("hostname"), HostAdd: c.StringSlice("add-host"), + IDMappings: idmappings, Image: imageName, ImageID: imageID, Interactive: c.Bool("interactive"), @@ -712,6 +736,7 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, Tmpfs: c.StringSlice("tmpfs"), Tty: tty, User: user, + UsernsMode: usernsMode, Volumes: c.StringSlice("volume"), WorkDir: workDir, } diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index f54eb6d10..0fd1760a9 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -225,7 +225,7 @@ func getCtrInspectInfo(ctr *libpod.Container, ctrInspectData *inspect.ContainerI IpcMode: string(createArtifact.IpcMode), Cgroup: cgroup, UTSMode: string(createArtifact.UtsMode), - UsernsMode: createArtifact.NsUser, + UsernsMode: string(createArtifact.UsernsMode), GroupAdd: spec.Process.User.AdditionalGids, ContainerIDFile: createArtifact.CidFile, AutoRemove: createArtifact.Rm, diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index f9c14f6e7..d1657325d 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -8,27 +8,28 @@ import ( // GetRuntime generates a new libpod runtime configured by command line options func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { + storageOpts := storage.DefaultStoreOptions + return GetRuntimeWithStorageOpts(c, &storageOpts) +} + +// GetRuntime generates a new libpod runtime configured by command line options +func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions) (*libpod.Runtime, error) { options := []libpod.RuntimeOption{} - if c.GlobalIsSet("root") || c.GlobalIsSet("runroot") || - c.GlobalIsSet("storage-opt") || c.GlobalIsSet("storage-driver") { - storageOpts := storage.DefaultStoreOptions - - if c.GlobalIsSet("root") { - storageOpts.GraphRoot = c.GlobalString("root") - } - if c.GlobalIsSet("runroot") { - storageOpts.RunRoot = c.GlobalString("runroot") - } - if c.GlobalIsSet("storage-driver") { - storageOpts.GraphDriverName = c.GlobalString("storage-driver") - } - if c.GlobalIsSet("storage-opt") { - storageOpts.GraphDriverOptions = c.GlobalStringSlice("storage-opt") - } - - options = append(options, libpod.WithStorageConfig(storageOpts)) + if c.GlobalIsSet("root") { + storageOpts.GraphRoot = c.GlobalString("root") + } + if c.GlobalIsSet("runroot") { + storageOpts.RunRoot = c.GlobalString("runroot") } + if c.GlobalIsSet("storage-driver") { + storageOpts.GraphDriverName = c.GlobalString("storage-driver") + } + if c.GlobalIsSet("storage-opt") { + storageOpts.GraphDriverOptions = c.GlobalStringSlice("storage-opt") + } + + options = append(options, libpod.WithStorageConfig(*storageOpts)) // TODO CLI flags for image config? // TODO CLI flag for signature policy? diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 5f0034e90..06bc0e9df 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -9,10 +9,12 @@ import ( "strconv" "strings" + "github.com/containers/storage" "github.com/pkg/errors" "github.com/projectatomic/libpod/cmd/podman/libpodruntime" "github.com/projectatomic/libpod/libpod" "github.com/projectatomic/libpod/libpod/image" + "github.com/projectatomic/libpod/pkg/util" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -50,7 +52,15 @@ func runCmd(c *cli.Context) error { } } - runtime, err := libpodruntime.GetRuntime(c) + storageOpts := storage.DefaultStoreOptions + mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap")) + if err != nil { + return err + } + storageOpts.UIDMap = mappings.UIDMap + storageOpts.GIDMap = mappings.GIDMap + + runtime, err := libpodruntime.GetRuntimeWithStorageOpts(c, &storageOpts) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -60,7 +70,6 @@ func runCmd(c *cli.Context) error { } ctx := getContext() - rtc := runtime.GetConfig() newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, false) if err != nil { @@ -76,7 +85,7 @@ func runCmd(c *cli.Context) error { } else { imageName = newImage.Names()[0] } - createConfig, err := parseCreateOpts(c, runtime, imageName, data) + createConfig, err := parseCreateOpts(ctx, c, runtime, imageName, data) if err != nil { return err } @@ -101,6 +110,7 @@ func runCmd(c *cli.Context) error { options = append(options, libpod.WithShmDir(createConfig.ShmDir)) options = append(options, libpod.WithShmSize(createConfig.Resources.ShmSize)) options = append(options, libpod.WithGroups(createConfig.GroupAdd)) + options = append(options, libpod.WithIDMappings(*createConfig.IDMappings)) // Default used if not overridden on command line diff --git a/cmd/podman/run_test.go b/cmd/podman/run_test.go index 3baee4615..bbcdcc60a 100644 --- a/cmd/podman/run_test.go +++ b/cmd/podman/run_test.go @@ -75,7 +75,8 @@ func getRuntimeSpec(c *cli.Context) (*spec.Spec, error) { } createConfig, err := parseCreateOpts(c, runtime, "alpine", generateAlpineImageData()) */ - createConfig, err := parseCreateOpts(c, nil, "alpine", generateAlpineImageData()) + ctx := getContext() + createConfig, err := parseCreateOpts(ctx, c, nil, "alpine", generateAlpineImageData()) if err != nil { return nil, err } diff --git a/cmd/podman/spec.go b/cmd/podman/spec.go index fc2ab267d..15dab6c4d 100644 --- a/cmd/podman/spec.go +++ b/cmd/podman/spec.go @@ -66,6 +66,13 @@ func addPidNS(config *createConfig, g *generate.Generator) error { return nil } +func addUserNS(config *createConfig, g *generate.Generator) error { + if (len(config.IDMappings.UIDMap) > 0 || len(config.IDMappings.GIDMap) > 0) && !config.UsernsMode.IsHost() { + g.AddOrReplaceLinuxNamespace(spec.UserNamespace, "") + } + return nil +} + func addNetNS(config *createConfig, g *generate.Generator) error { netMode := config.NetMode if netMode.IsHost() { @@ -257,6 +264,12 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { } } + for _, uidmap := range config.IDMappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + for _, gidmap := range config.IDMappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } // SECURITY OPTS g.SetProcessNoNewPrivileges(config.NoNewPrivs) g.SetProcessApparmorProfile(config.ApparmorProfile) @@ -300,6 +313,10 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { return nil, err } + if err := addUserNS(config, &g); err != nil { + return nil, err + } + if err := addNetNS(config, &g); err != nil { return nil, err } diff --git a/completions/bash/podman b/completions/bash/podman index cafb4d8c8..45e5c9a15 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1072,6 +1072,7 @@ _podman_container_run() { --env -e --env-file --expose + --gidmap --group-add --hostname -h --image-volume @@ -1099,7 +1100,10 @@ _podman_container_run() { --stop-signal --stop-timeout --tmpfs + --subgidname + --subuidname --sysctl + --uidmap --ulimit --user -u --userns diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 457f54edd..1291f39c6 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -209,6 +209,11 @@ inside of the container. Expose a port, or a range of ports (e.g. --expose=3300-3310) to set up port redirection on the host system. +**--gidmap**=map + GID map for the user namespace. Using this flag will run the container with user namespace enabled. It conflicts with the `--userns` and `--subgidname` flags. + + The following example maps uids 0-2000 in the container to the uids 30000-31999 on the host and gids 0-2000 in the container to the gids 30000-31999 on the host. + **--group-add**=[] Add additional groups to run as @@ -223,9 +228,9 @@ inside of the container. **--image-volume**, **builtin-volume**=*bind*|*tmpfs*|*ignore* Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind'). bind: A directory is created inside the container state directory and bind mounted into - the container for the volumes. + the container for the volumes. tmpfs: The volume is mounted onto the container as a tmpfs, which allows the users to create - content that disappears when the container is stopped. + content that disappears when the container is stopped. ignore: All volumes are just ignored and no action is taken. **-i**, **--interactive**=*true*|*false* @@ -424,6 +429,12 @@ its root filesystem mounted as read only prohibiting any writes. **--stop-timeout**=*10* Timeout (in seconds) to stop a container. Default is 10. +**--subgidname**=name + Name for GID map from the `/etc/subgid` file. Using this flag will run the container with user namespace enabled. This flag conflicts with `--userns` and `--gidmap`. + +**--subuidname**=name + Name for UID map from the `/etc/subuid` file. Using this flag will run the container with user namespace enabled. This flag conflicts with `--userns` and `--uidmap`. + **--sysctl**=SYSCTL Configure namespaced kernel parameters at runtime @@ -460,6 +471,11 @@ interactive shell. The default is false. Note: The **-t** option is incompatible with a redirection of the podman client standard input. +**--uidmap**=map + UID map for the user namespace. Using this flag will run the container with user namespace enabled. It conflicts with the `--userns` and `--subuidname` flags. + + The following example maps uids 0-2000 in the container to the uids 30000-31999 on the host and gids 0-2000 in the container to the gids 30000-31999 on the host. + **--ulimit**=[] Ulimit options @@ -472,7 +488,8 @@ standard input. Without this argument the command will be run as root in the container. **--userns**="" - Set the usernamespace mode for the container when `userns-remap` option is enabled. + Set the usernamespace mode for the container. The use of userns is disabled by default. + **host**: use the host usernamespace and enable all privileged options (e.g., `pid=host` or `--privileged`). **--uts**=*host* @@ -556,6 +573,21 @@ can override the working directory by using the **-w** option. ## EXAMPLES +### Set UID/GID mapping in a new user namespace + +If you want to run the container in a new user namespace and define the mapping of +the uid and gid from the host. + + # podman create --uidmap 0:30000:7000 --gidmap 0:30000:7000 fedora echo hello + +## FILES + +**/etc/subuid** +**/etc/subgid** + +## SEE ALSO +SUBGID(5), SUBUID(5), + ## HISTORY August 2014, updated by Sven Dowideit <SvenDowideit@home.org.au> September 2014, updated by Sven Dowideit <SvenDowideit@home.org.au> diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index a198fd9d4..df79114f6 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -214,6 +214,11 @@ inside of the container. Expose a port, or a range of ports (e.g. --expose=3300-3310) to set up port redirection on the host system. +**--gidmap**=map + GID map for the user namespace. Using this flag will run the container with user namespace enabled. It conflicts with the `--userns` and `--subgidname` flags. + + The following example maps uids 0-2000 in the container to the uids 30000-31999 on the host and gids 0-2000 in the container to the gids 30000-31999 on the host. + **--group-add**=[] Add additional groups to run as @@ -228,9 +233,9 @@ inside of the container. **--image-volume**, **builtin-volume**=*bind*|*tmpfs*|*ignore* Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind') bind: A directory is created inside the container state directory and bind mounted into - the container for the volumes. + the container for the volumes. tmpfs: The volume is mounted onto the container as a tmpfs, which allows the users to create - content that disappears when the container is stopped. + content that disappears when the container is stopped. ignore: All volumes are just ignored and no action is taken. **-i**, **--interactive**=*true*|*false* @@ -435,6 +440,12 @@ its root filesystem mounted as read only prohibiting any writes. **--stop-timeout**=*10* Timeout (in seconds) to stop a container. Default is 10. +**--subgidname**=name + Name for GID map from the `/etc/subgid` file. Using this flag will run the container with user namespace enabled. This flag conflicts with `--userns` and `--gidmap`. + +**--subuidname**=name + Name for UID map from the `/etc/subuid` file. Using this flag will run the container with user namespace enabled. This flag conflicts with `--userns` and `--uidmap`. + **--sysctl**=SYSCTL Configure namespaced kernel parameters at runtime @@ -471,6 +482,11 @@ interactive shell. The default is false. Note: The **-t** option is incompatible with a redirection of the podman client standard input. +**--uidmap**=map + UID map for the user namespace. Using this flag will run the container with user namespace enabled. It conflicts with the `--userns` and `--subuidname` flags. + + The following example maps uids 0-2000 in the container to the uids 30000-31999 on the host and gids 0-2000 in the container to the gids 30000-31999 on the host. + **--ulimit**=[] Ulimit options @@ -483,7 +499,8 @@ standard input. Without this argument the command will be run as root in the container. **--userns**="" - Set the usernamespace mode for the container when `userns-remap` option is enabled. + Set the usernamespace mode for the container. The use of userns is disabled by default. + **host**: use the host usernamespace and enable all privileged options (e.g., `pid=host` or `--privileged`). **--uts**=*host* @@ -793,6 +810,21 @@ evolves we expect to see more sysctls become namespaced. See the definition of the `--sysctl` option above for the current list of supported sysctls. +### Set UID/GID mapping in a new user namespace + +If you want to run the container in a new user namespace and define the mapping of +the uid and gid from the host. + + # podman run --uidmap 0:30000:7000 --gidmap 0:30000:7000 fedora echo hello + +## FILES + +**/etc/subuid** +**/etc/subgid** + +## SEE ALSO +SUBGID(5), SUBUID(5), + ## HISTORY April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on docker.com source material and internal work. diff --git a/docs/podman.1.md b/docs/podman.1.md index 1ea6603a7..3e0c59232 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -96,7 +96,6 @@ has the capability to debug pods/images created by crio. ## FILES - **libpod.conf** (`/etc/containers/libpod.conf`) libpod.conf is the configuration file for all tools using libpod to manage containers diff --git a/libpod/container.go b/libpod/container.go index e7fe77498..fb1f83c29 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -173,7 +173,8 @@ type ContainerConfig struct { // TODO consider breaking these subsections up into smaller structs - // Storage Config + // UID/GID mappings used by the storage + IDMappings storage.IDMappingOptions `json:"idMappingsOptions,omitempty"` // Information on the image used for the root filesystem/ RootfsImageID string `json:"rootfsImageID,omitempty"` @@ -863,3 +864,28 @@ func (c *Container) RWSize() (int64, error) { } return c.rwSize() } + +// IDMappings returns the UID/GID mapping used for the container +func (c *Container) IDMappings() (storage.IDMappingOptions, error) { + return c.config.IDMappings, nil +} + +// RootUID returns the root user mapping from container +func (c *Container) RootUID() int { + for _, uidmap := range c.config.IDMappings.UIDMap { + if uidmap.ContainerID == 0 { + return uidmap.HostID + } + } + return 0 +} + +// RootGID returns the root user mapping from container +func (c *Container) RootGID() int { + for _, gidmap := range c.config.IDMappings.GIDMap { + if gidmap.ContainerID == 0 { + return gidmap.HostID + } + } + return 0 +} diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 614c6aca0..73095316e 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -190,7 +190,8 @@ func (c *Container) setupStorage(ctx context.Context) error { return errors.Wrapf(ErrInvalidArg, "must provide image ID and image name to use an image") } - containerInfo, err := c.runtime.storageService.CreateContainerStorage(ctx, c.runtime.imageContext, c.config.RootfsImageName, c.config.RootfsImageID, c.config.Name, c.config.ID, c.config.MountLabel) + options := storage.ContainerOptions{IDMappingOptions: c.config.IDMappings} + containerInfo, err := c.runtime.storageService.CreateContainerStorage(ctx, c.runtime.imageContext, c.config.RootfsImageName, c.config.RootfsImageID, c.config.Name, c.config.ID, c.config.MountLabel, &options) if err != nil { return errors.Wrapf(err, "error creating container storage") } @@ -591,6 +592,9 @@ func (c *Container) mountStorage() (err error) { label.FormatMountLabel(shmOptions, c.config.MountLabel)); err != nil { return errors.Wrapf(err, "failed to mount shm tmpfs %q", c.config.ShmDir) } + if err := os.Chown(c.config.ShmDir, c.RootUID(), c.RootGID()); err != nil { + return err + } } mountPoint, err := c.runtime.storageService.MountContainerImage(c.ID()) @@ -755,7 +759,7 @@ func (c *Container) makeBindMounts() error { } // Add Secret Mounts - secretMounts := secrets.SecretMounts(c.config.MountLabel, c.state.RunDir, c.runtime.config.DefaultMountsFile) + secretMounts := secrets.SecretMountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.DefaultMountsFile, c.RootUID(), c.RootGID()) for _, mount := range secretMounts { if _, ok := c.state.BindMounts[mount.Destination]; !ok { c.state.BindMounts[mount.Destination] = mount.Source @@ -772,10 +776,12 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error) if err != nil { return "", errors.Wrapf(err, "unable to create %s", destFileName) } - defer f.Close() - _, err = f.WriteString(output) - if err != nil { + if err := f.Chown(c.RootUID(), c.RootGID()); err != nil { + return "", err + } + + if _, err := f.WriteString(output); err != nil { return "", errors.Wrapf(err, "unable to write %s", destFileName) } // Relabel runDirResolv for the container diff --git a/libpod/options.go b/libpod/options.go index 101ff9833..eaca70afc 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -459,6 +459,18 @@ func WithStopTimeout(timeout uint) CtrCreateOption { } } +// WithIDMappings sets the idmappsings for the container +func WithIDMappings(idmappings storage.IDMappingOptions) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + ctr.config.IDMappings = idmappings + return nil + } +} + // WithIPCNSFrom indicates the the container should join the IPC namespace of // the given container. // If the container has joined a pod, it can only join the namespaces of diff --git a/libpod/storage.go b/libpod/storage.go index 910db1970..c0391326c 100644 --- a/libpod/storage.go +++ b/libpod/storage.go @@ -59,7 +59,7 @@ func (metadata *RuntimeContainerMetadata) SetMountLabel(mountLabel string) { // CreateContainerStorage creates the storage end of things. We already have the container spec created // TO-DO We should be passing in an Image object in the future. -func (r *storageService) CreateContainerStorage(ctx context.Context, systemContext *types.SystemContext, imageName, imageID, containerName, containerID, mountLabel string) (ContainerInfo, error) { +func (r *storageService) CreateContainerStorage(ctx context.Context, systemContext *types.SystemContext, imageName, imageID, containerName, containerID, mountLabel string, options *storage.ContainerOptions) (ContainerInfo, error) { var ref types.ImageReference if imageName == "" && imageID == "" { return ContainerInfo{}, ErrEmptyID @@ -111,13 +111,15 @@ func (r *storageService) CreateContainerStorage(ctx context.Context, systemConte // Build the container. names := []string{containerName} - options := storage.ContainerOptions{ - IDMappingOptions: storage.IDMappingOptions{ - HostUIDMapping: true, - HostGIDMapping: true, - }, + if options == nil { + options = &storage.ContainerOptions{ + IDMappingOptions: storage.IDMappingOptions{ + HostUIDMapping: true, + HostGIDMapping: true, + }, + } } - container, err := r.store.CreateContainer(containerID, names, img.ID, "", string(mdata), &options) + container, err := r.store.CreateContainer(containerID, names, img.ID, "", string(mdata), options) if err != nil { logrus.Debugf("failed to create container %s(%s): %v", metadata.ContainerName, containerID, err) diff --git a/pkg/secrets/secrets.go b/pkg/secrets/secrets.go index 04890c06a..29ccd4592 100644 --- a/pkg/secrets/secrets.go +++ b/pkg/secrets/secrets.go @@ -127,7 +127,12 @@ func getMountsMap(path string) (string, string, error) { } // SecretMounts copies, adds, and mounts the secrets to the container root filesystem -func SecretMounts(mountLabel, containerWorkingDir string, mountFile string) []rspec.Mount { +func SecretMounts(mountLabel, containerWorkingDir, mountFile string) []rspec.Mount { + return SecretMountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, 0, 0) +} + +// SecretMountsWithUIDGID specifies the uid/gid of the owner +func SecretMountsWithUIDGID(mountLabel, containerWorkingDir, mountFile string, uid, gid int) []rspec.Mount { var ( secretMounts []rspec.Mount mountFiles []string @@ -141,7 +146,7 @@ func SecretMounts(mountLabel, containerWorkingDir string, mountFile string) []rs mountFiles = append(mountFiles, mountFile) } for _, file := range mountFiles { - mounts, err := addSecretsFromMountsFile(file, mountLabel, containerWorkingDir) + mounts, err := addSecretsFromMountsFile(file, mountLabel, containerWorkingDir, uid, gid) if err != nil { logrus.Warnf("error mounting secrets, skipping: %v", err) } @@ -162,9 +167,15 @@ func SecretMounts(mountLabel, containerWorkingDir string, mountFile string) []rs return secretMounts } +func rchown(chowndir string, uid, gid int) error { + return filepath.Walk(chowndir, func(filePath string, f os.FileInfo, err error) error { + return os.Lchown(filePath, uid, gid) + }) +} + // addSecretsFromMountsFile copies the contents of host directory to container directory // and returns a list of mounts -func addSecretsFromMountsFile(filePath, mountLabel, containerWorkingDir string) ([]rspec.Mount, error) { +func addSecretsFromMountsFile(filePath, mountLabel, containerWorkingDir string, uid, gid int) ([]rspec.Mount, error) { var mounts []rspec.Mount defaultMountsPaths := getMounts(filePath) for _, path := range defaultMountsPaths { @@ -186,7 +197,6 @@ func addSecretsFromMountsFile(filePath, mountLabel, containerWorkingDir string) if err = os.MkdirAll(ctrDirOnHost, 0755); err != nil { return nil, errors.Wrapf(err, "making container directory failed") } - hostDir, err = resolveSymbolicLink(hostDir) if err != nil { return nil, err @@ -206,6 +216,11 @@ func addSecretsFromMountsFile(filePath, mountLabel, containerWorkingDir string) if err != nil { return nil, errors.Wrap(err, "error applying correct labels") } + if uid != 0 || gid != 0 { + if err := rchown(ctrDirOnHost, uid, gid); err != nil { + return nil, err + } + } } else if err != nil { return nil, errors.Wrapf(err, "error getting status of %q", ctrDirOnHost) } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 1bbfe30d3..a29a1ee60 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -2,9 +2,12 @@ package util import ( "fmt" + "strconv" "strings" "github.com/containers/image/types" + "github.com/containers/storage" + "github.com/containers/storage/pkg/idtools" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "golang.org/x/crypto/ssh/terminal" @@ -120,3 +123,76 @@ func GetImageConfig(changes []string) (v1.ImageConfig, error) { StopSignal: stopSignal, }, nil } + +// ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping +func ParseIDMapping(UIDMapSlice, GIDMapSlice []string, subUIDMap, subGIDMap string) (*storage.IDMappingOptions, error) { + options := storage.IDMappingOptions{ + HostUIDMapping: true, + HostGIDMapping: true, + } + if subGIDMap == "" && subUIDMap != "" { + subGIDMap = subUIDMap + } + if subUIDMap == "" && subGIDMap != "" { + subUIDMap = subGIDMap + } + parseTriple := func(spec []string) (container, host, size int, err error) { + cid, err := strconv.ParseUint(spec[0], 10, 32) + if err != nil { + return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[0], err) + } + hid, err := strconv.ParseUint(spec[1], 10, 32) + if err != nil { + return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[1], err) + } + sz, err := strconv.ParseUint(spec[2], 10, 32) + if err != nil { + return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[2], err) + } + return int(cid), int(hid), int(sz), nil + } + parseIDMap := func(spec []string) (idmap []idtools.IDMap, err error) { + for _, uid := range spec { + splitmap := strings.SplitN(uid, ":", 3) + if len(splitmap) < 3 { + return nil, fmt.Errorf("invalid mapping requires 3 fields: %q", uid) + } + cid, hid, size, err := parseTriple(splitmap) + if err != nil { + return nil, err + } + pmap := idtools.IDMap{ + ContainerID: cid, + HostID: hid, + Size: size, + } + idmap = append(idmap, pmap) + } + return idmap, nil + } + if subUIDMap != "" && subGIDMap != "" { + mappings, err := idtools.NewIDMappings(subUIDMap, subGIDMap) + if err != nil { + return nil, err + } + options.UIDMap = mappings.UIDs() + options.GIDMap = mappings.GIDs() + } + parsedUIDMap, err := parseIDMap(UIDMapSlice) + if err != nil { + return nil, err + } + parsedGIDMap, err := parseIDMap(GIDMapSlice) + if err != nil { + return nil, err + } + options.UIDMap = append(options.UIDMap, parsedUIDMap...) + options.GIDMap = append(options.GIDMap, parsedGIDMap...) + if len(options.UIDMap) > 0 { + options.HostUIDMapping = false + } + if len(options.GIDMap) > 0 { + options.HostGIDMapping = false + } + return &options, nil +} |