summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel J Walsh <dwalsh@redhat.com>2018-04-23 20:42:53 -0400
committerAtomic Bot <atomic-devel@projectatomic.io>2018-05-04 17:15:55 +0000
commitb51d7379987581da82902027fe91cdf298047bc0 (patch)
treef9d7fbebf3b946caea5eb5e2c626a19413c795c8
parent1f5debd43806cc3bd07f562ff00ef4c426540f98 (diff)
downloadpodman-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.go17
-rw-r--r--cmd/podman/create.go71
-rw-r--r--cmd/podman/inspect.go2
-rw-r--r--cmd/podman/libpodruntime/runtime.go37
-rw-r--r--cmd/podman/run.go16
-rw-r--r--cmd/podman/run_test.go3
-rw-r--r--cmd/podman/spec.go17
-rw-r--r--completions/bash/podman4
-rw-r--r--docs/podman-create.1.md38
-rw-r--r--docs/podman-run.1.md38
-rw-r--r--docs/podman.1.md1
-rw-r--r--libpod/container.go28
-rw-r--r--libpod/container_internal.go16
-rw-r--r--libpod/options.go12
-rw-r--r--libpod/storage.go16
-rw-r--r--pkg/secrets/secrets.go23
-rw-r--r--pkg/util/utils.go76
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
+}