Begin wiring in USERNS Support into podman

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>

Closes: #690
Approved by: mheon
This commit is contained in:
Daniel J Walsh 2018-04-23 20:42:53 -04:00 committed by Atomic Bot
parent 1f5debd438
commit b51d737998
17 changed files with 345 additions and 70 deletions

View File

@ -199,6 +199,10 @@ var createFlags = []cli.Flag{
Name: "expose", Name: "expose",
Usage: "Expose a port or a range of ports (default [])", 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{ cli.StringSliceFlag{
Name: "group-add", Name: "group-add",
Usage: "Add additional groups to join (default [])", Usage: "Add additional groups to join (default [])",
@ -341,6 +345,15 @@ var createFlags = []cli.Flag{
Name: "storage-opt", Name: "storage-opt",
Usage: "Storage driver options per container (default [])", 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{ cli.StringSliceFlag{
Name: "sysctl", Name: "sysctl",
Usage: "Sysctl options (default [])", Usage: "Sysctl options (default [])",
@ -353,6 +366,10 @@ var createFlags = []cli.Flag{
Name: "tty, t", Name: "tty, t",
Usage: "Allocate a pseudo-TTY for container", Usage: "Allocate a pseudo-TTY for container",
}, },
cli.StringSliceFlag{
Name: "uidmap",
Usage: "UID map to use for the user namespace",
},
cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "ulimit", Name: "ulimit",
Usage: "Ulimit options (default [])", Usage: "Ulimit options (default [])",

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
@ -9,6 +10,7 @@ import (
"strings" "strings"
"syscall" "syscall"
"github.com/containers/storage"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/signal"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
@ -92,7 +94,8 @@ type createConfig struct {
Hostname string //hostname Hostname string //hostname
Image string Image string
ImageID 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 ImageVolumeType string // how to handle the image volume, either bind, tmpfs, or ignore
Interactive bool //interactive Interactive bool //interactive
IpcMode container.IpcMode //ipc IpcMode container.IpcMode //ipc
@ -108,8 +111,7 @@ type createConfig struct {
Network string //network Network string //network
NetworkAlias []string //network-alias NetworkAlias []string //network-alias
PidMode container.PidMode //pid PidMode container.PidMode //pid
NsUser string Pod string //pod
Pod string //pod
PortBindings nat.PortMap PortBindings nat.PortMap
Privileged bool //privileged Privileged bool //privileged
Publish []string //publish Publish []string //publish
@ -119,20 +121,21 @@ type createConfig struct {
Resources createResourceConfig Resources createResourceConfig
Rm bool //rm Rm bool //rm
ShmDir string ShmDir string
StopSignal syscall.Signal // stop-signal StopSignal syscall.Signal // stop-signal
StopTimeout uint // stop-timeout StopTimeout uint // stop-timeout
Sysctl map[string]string //sysctl Sysctl map[string]string //sysctl
Tmpfs []string // tmpfs Tmpfs []string // tmpfs
Tty bool //tty Tty bool //tty
User string //user UsernsMode container.UsernsMode //userns
UtsMode container.UTSMode //uts User string //user
Volumes []string //volume UtsMode container.UTSMode //uts
WorkDir string //workdir Volumes []string //volume
MountLabel string //SecurityOpts WorkDir string //workdir
ProcessLabel string //SecurityOpts MountLabel string //SecurityOpts
NoNewPrivs bool //SecurityOpts ProcessLabel string //SecurityOpts
ApparmorProfile string //SecurityOpts NoNewPrivs bool //SecurityOpts
SeccompProfilePath string //SecurityOpts ApparmorProfile string //SecurityOpts
SeccompProfilePath string //SecurityOpts
SecurityOpts []string SecurityOpts []string
} }
@ -174,7 +177,15 @@ func createCmd(c *cli.Context) error {
return errors.Errorf("image name or ID is required") 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 { if err != nil {
return errors.Wrapf(err, "error creating libpod runtime") return errors.Wrapf(err, "error creating libpod runtime")
} }
@ -188,7 +199,7 @@ func createCmd(c *cli.Context) error {
return err return err
} }
data, err := newImage.Inspect(ctx) 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 { if err != nil {
return err return err
} }
@ -211,6 +222,7 @@ func createCmd(c *cli.Context) error {
options = append(options, libpod.WithShmDir(createConfig.ShmDir)) options = append(options, libpod.WithShmDir(createConfig.ShmDir))
options = append(options, libpod.WithShmSize(createConfig.Resources.ShmSize)) options = append(options, libpod.WithShmSize(createConfig.Resources.ShmSize))
options = append(options, libpod.WithGroups(createConfig.GroupAdd)) options = append(options, libpod.WithGroups(createConfig.GroupAdd))
options = append(options, libpod.WithIDMappings(*createConfig.IDMappings))
ctr, err := runtime.NewContainer(ctx, runtimeSpec, options...) ctr, err := runtime.NewContainer(ctx, runtimeSpec, options...)
if err != nil { if err != nil {
return err return err
@ -414,10 +426,16 @@ func getRandomPort() (int, error) {
// Parses CLI options related to container creation into a config which can be // Parses CLI options related to container creation into a config which can be
// parsed into an OCI runtime spec // parsed into an OCI runtime spec
func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*createConfig, error) { func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*createConfig, error) {
var inputCommand, command []string var (
var memoryLimit, memoryReservation, memorySwap, memoryKernel int64 inputCommand, command []string
var blkioWeight uint16 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 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")) 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") { if c.Bool("detach") && c.Bool("rm") {
return nil, errors.Errorf("--rm and --detach can not be specified together") 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"), GroupAdd: c.StringSlice("group-add"),
Hostname: c.String("hostname"), Hostname: c.String("hostname"),
HostAdd: c.StringSlice("add-host"), HostAdd: c.StringSlice("add-host"),
IDMappings: idmappings,
Image: imageName, Image: imageName,
ImageID: imageID, ImageID: imageID,
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
@ -712,6 +736,7 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
Tmpfs: c.StringSlice("tmpfs"), Tmpfs: c.StringSlice("tmpfs"),
Tty: tty, Tty: tty,
User: user, User: user,
UsernsMode: usernsMode,
Volumes: c.StringSlice("volume"), Volumes: c.StringSlice("volume"),
WorkDir: workDir, WorkDir: workDir,
} }

View File

@ -225,7 +225,7 @@ func getCtrInspectInfo(ctr *libpod.Container, ctrInspectData *inspect.ContainerI
IpcMode: string(createArtifact.IpcMode), IpcMode: string(createArtifact.IpcMode),
Cgroup: cgroup, Cgroup: cgroup,
UTSMode: string(createArtifact.UtsMode), UTSMode: string(createArtifact.UtsMode),
UsernsMode: createArtifact.NsUser, UsernsMode: string(createArtifact.UsernsMode),
GroupAdd: spec.Process.User.AdditionalGids, GroupAdd: spec.Process.User.AdditionalGids,
ContainerIDFile: createArtifact.CidFile, ContainerIDFile: createArtifact.CidFile,
AutoRemove: createArtifact.Rm, AutoRemove: createArtifact.Rm,

View File

@ -8,27 +8,28 @@ import (
// GetRuntime generates a new libpod runtime configured by command line options // GetRuntime generates a new libpod runtime configured by command line options
func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { 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{} options := []libpod.RuntimeOption{}
if c.GlobalIsSet("root") || c.GlobalIsSet("runroot") || if c.GlobalIsSet("root") {
c.GlobalIsSet("storage-opt") || c.GlobalIsSet("storage-driver") { storageOpts.GraphRoot = c.GlobalString("root")
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("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 flags for image config?
// TODO CLI flag for signature policy? // TODO CLI flag for signature policy?

View File

@ -9,10 +9,12 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/containers/storage"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/projectatomic/libpod/cmd/podman/libpodruntime" "github.com/projectatomic/libpod/cmd/podman/libpodruntime"
"github.com/projectatomic/libpod/libpod" "github.com/projectatomic/libpod/libpod"
"github.com/projectatomic/libpod/libpod/image" "github.com/projectatomic/libpod/libpod/image"
"github.com/projectatomic/libpod/pkg/util"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "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 { if err != nil {
return errors.Wrapf(err, "error creating libpod runtime") return errors.Wrapf(err, "error creating libpod runtime")
} }
@ -60,7 +70,6 @@ func runCmd(c *cli.Context) error {
} }
ctx := getContext() ctx := getContext()
rtc := runtime.GetConfig() rtc := runtime.GetConfig()
newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, false) newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, false)
if err != nil { if err != nil {
@ -76,7 +85,7 @@ func runCmd(c *cli.Context) error {
} else { } else {
imageName = newImage.Names()[0] imageName = newImage.Names()[0]
} }
createConfig, err := parseCreateOpts(c, runtime, imageName, data) createConfig, err := parseCreateOpts(ctx, c, runtime, imageName, data)
if err != nil { if err != nil {
return err return err
} }
@ -101,6 +110,7 @@ func runCmd(c *cli.Context) error {
options = append(options, libpod.WithShmDir(createConfig.ShmDir)) options = append(options, libpod.WithShmDir(createConfig.ShmDir))
options = append(options, libpod.WithShmSize(createConfig.Resources.ShmSize)) options = append(options, libpod.WithShmSize(createConfig.Resources.ShmSize))
options = append(options, libpod.WithGroups(createConfig.GroupAdd)) options = append(options, libpod.WithGroups(createConfig.GroupAdd))
options = append(options, libpod.WithIDMappings(*createConfig.IDMappings))
// Default used if not overridden on command line // Default used if not overridden on command line

View File

@ -75,7 +75,8 @@ func getRuntimeSpec(c *cli.Context) (*spec.Spec, error) {
} }
createConfig, err := parseCreateOpts(c, runtime, "alpine", generateAlpineImageData()) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -66,6 +66,13 @@ func addPidNS(config *createConfig, g *generate.Generator) error {
return nil 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 { func addNetNS(config *createConfig, g *generate.Generator) error {
netMode := config.NetMode netMode := config.NetMode
if netMode.IsHost() { 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 // SECURITY OPTS
g.SetProcessNoNewPrivileges(config.NoNewPrivs) g.SetProcessNoNewPrivileges(config.NoNewPrivs)
g.SetProcessApparmorProfile(config.ApparmorProfile) g.SetProcessApparmorProfile(config.ApparmorProfile)
@ -300,6 +313,10 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
return nil, err return nil, err
} }
if err := addUserNS(config, &g); err != nil {
return nil, err
}
if err := addNetNS(config, &g); err != nil { if err := addNetNS(config, &g); err != nil {
return nil, err return nil, err
} }

View File

@ -1072,6 +1072,7 @@ _podman_container_run() {
--env -e --env -e
--env-file --env-file
--expose --expose
--gidmap
--group-add --group-add
--hostname -h --hostname -h
--image-volume --image-volume
@ -1099,7 +1100,10 @@ _podman_container_run() {
--stop-signal --stop-signal
--stop-timeout --stop-timeout
--tmpfs --tmpfs
--subgidname
--subuidname
--sysctl --sysctl
--uidmap
--ulimit --ulimit
--user -u --user -u
--userns --userns

View File

@ -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 Expose a port, or a range of ports (e.g. --expose=3300-3310) to set up port redirection
on the host system. 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**=[] **--group-add**=[]
Add additional groups to run as Add additional groups to run as
@ -223,9 +228,9 @@ inside of the container.
**--image-volume**, **builtin-volume**=*bind*|*tmpfs*|*ignore* **--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'). 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 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 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. ignore: All volumes are just ignored and no action is taken.
**-i**, **--interactive**=*true*|*false* **-i**, **--interactive**=*true*|*false*
@ -424,6 +429,12 @@ its root filesystem mounted as read only prohibiting any writes.
**--stop-timeout**=*10* **--stop-timeout**=*10*
Timeout (in seconds) to stop a container. Default is 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 **--sysctl**=SYSCTL
Configure namespaced kernel parameters at runtime 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 Note: The **-t** option is incompatible with a redirection of the podman client
standard input. 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**=[]
Ulimit options Ulimit options
@ -472,7 +488,8 @@ standard input.
Without this argument the command will be run as root in the container. Without this argument the command will be run as root in the container.
**--userns**="" **--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`). **host**: use the host usernamespace and enable all privileged options (e.g., `pid=host` or `--privileged`).
**--uts**=*host* **--uts**=*host*
@ -556,6 +573,21 @@ can override the working directory by using the **-w** option.
## EXAMPLES ## 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 ## HISTORY
August 2014, updated by Sven Dowideit <SvenDowideit@home.org.au> August 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
September 2014, updated by Sven Dowideit <SvenDowideit@home.org.au> September 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>

View File

@ -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 Expose a port, or a range of ports (e.g. --expose=3300-3310) to set up port redirection
on the host system. 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**=[] **--group-add**=[]
Add additional groups to run as Add additional groups to run as
@ -228,9 +233,9 @@ inside of the container.
**--image-volume**, **builtin-volume**=*bind*|*tmpfs*|*ignore* **--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') 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 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 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. ignore: All volumes are just ignored and no action is taken.
**-i**, **--interactive**=*true*|*false* **-i**, **--interactive**=*true*|*false*
@ -435,6 +440,12 @@ its root filesystem mounted as read only prohibiting any writes.
**--stop-timeout**=*10* **--stop-timeout**=*10*
Timeout (in seconds) to stop a container. Default is 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 **--sysctl**=SYSCTL
Configure namespaced kernel parameters at runtime 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 Note: The **-t** option is incompatible with a redirection of the podman client
standard input. 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**=[]
Ulimit options Ulimit options
@ -483,7 +499,8 @@ standard input.
Without this argument the command will be run as root in the container. Without this argument the command will be run as root in the container.
**--userns**="" **--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`). **host**: use the host usernamespace and enable all privileged options (e.g., `pid=host` or `--privileged`).
**--uts**=*host* **--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 See the definition of the `--sysctl` option above for the current list of
supported sysctls. 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 ## HISTORY
April 2014, Originally compiled by William Henry (whenry at redhat dot com) April 2014, Originally compiled by William Henry (whenry at redhat dot com)
based on docker.com source material and internal work. based on docker.com source material and internal work.

View File

@ -96,7 +96,6 @@ has the capability to debug pods/images created by crio.
## FILES ## FILES
**libpod.conf** (`/etc/containers/libpod.conf`) **libpod.conf** (`/etc/containers/libpod.conf`)
libpod.conf is the configuration file for all tools using libpod to manage containers libpod.conf is the configuration file for all tools using libpod to manage containers

View File

@ -173,7 +173,8 @@ type ContainerConfig struct {
// TODO consider breaking these subsections up into smaller structs // 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/ // Information on the image used for the root filesystem/
RootfsImageID string `json:"rootfsImageID,omitempty"` RootfsImageID string `json:"rootfsImageID,omitempty"`
@ -863,3 +864,28 @@ func (c *Container) RWSize() (int64, error) {
} }
return c.rwSize() 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
}

View File

@ -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") 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 { if err != nil {
return errors.Wrapf(err, "error creating container storage") 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 { label.FormatMountLabel(shmOptions, c.config.MountLabel)); err != nil {
return errors.Wrapf(err, "failed to mount shm tmpfs %q", c.config.ShmDir) 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()) mountPoint, err := c.runtime.storageService.MountContainerImage(c.ID())
@ -755,7 +759,7 @@ func (c *Container) makeBindMounts() error {
} }
// Add Secret Mounts // 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 { for _, mount := range secretMounts {
if _, ok := c.state.BindMounts[mount.Destination]; !ok { if _, ok := c.state.BindMounts[mount.Destination]; !ok {
c.state.BindMounts[mount.Destination] = mount.Source c.state.BindMounts[mount.Destination] = mount.Source
@ -772,10 +776,12 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error)
if err != nil { if err != nil {
return "", errors.Wrapf(err, "unable to create %s", destFileName) return "", errors.Wrapf(err, "unable to create %s", destFileName)
} }
defer f.Close() defer f.Close()
_, err = f.WriteString(output) if err := f.Chown(c.RootUID(), c.RootGID()); err != nil {
if err != nil { return "", err
}
if _, err := f.WriteString(output); err != nil {
return "", errors.Wrapf(err, "unable to write %s", destFileName) return "", errors.Wrapf(err, "unable to write %s", destFileName)
} }
// Relabel runDirResolv for the container // Relabel runDirResolv for the container

View File

@ -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 // WithIPCNSFrom indicates the the container should join the IPC namespace of
// the given container. // the given container.
// If the container has joined a pod, it can only join the namespaces of // If the container has joined a pod, it can only join the namespaces of

View File

@ -59,7 +59,7 @@ func (metadata *RuntimeContainerMetadata) SetMountLabel(mountLabel string) {
// CreateContainerStorage creates the storage end of things. We already have the container spec created // 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. // 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 var ref types.ImageReference
if imageName == "" && imageID == "" { if imageName == "" && imageID == "" {
return ContainerInfo{}, ErrEmptyID return ContainerInfo{}, ErrEmptyID
@ -111,13 +111,15 @@ func (r *storageService) CreateContainerStorage(ctx context.Context, systemConte
// Build the container. // Build the container.
names := []string{containerName} names := []string{containerName}
options := storage.ContainerOptions{ if options == nil {
IDMappingOptions: storage.IDMappingOptions{ options = &storage.ContainerOptions{
HostUIDMapping: true, IDMappingOptions: storage.IDMappingOptions{
HostGIDMapping: true, 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 { if err != nil {
logrus.Debugf("failed to create container %s(%s): %v", metadata.ContainerName, containerID, err) logrus.Debugf("failed to create container %s(%s): %v", metadata.ContainerName, containerID, err)

View File

@ -127,7 +127,12 @@ func getMountsMap(path string) (string, string, error) {
} }
// SecretMounts copies, adds, and mounts the secrets to the container root filesystem // 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 ( var (
secretMounts []rspec.Mount secretMounts []rspec.Mount
mountFiles []string mountFiles []string
@ -141,7 +146,7 @@ func SecretMounts(mountLabel, containerWorkingDir string, mountFile string) []rs
mountFiles = append(mountFiles, mountFile) mountFiles = append(mountFiles, mountFile)
} }
for _, file := range mountFiles { for _, file := range mountFiles {
mounts, err := addSecretsFromMountsFile(file, mountLabel, containerWorkingDir) mounts, err := addSecretsFromMountsFile(file, mountLabel, containerWorkingDir, uid, gid)
if err != nil { if err != nil {
logrus.Warnf("error mounting secrets, skipping: %v", err) logrus.Warnf("error mounting secrets, skipping: %v", err)
} }
@ -162,9 +167,15 @@ func SecretMounts(mountLabel, containerWorkingDir string, mountFile string) []rs
return secretMounts 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 // addSecretsFromMountsFile copies the contents of host directory to container directory
// and returns a list of mounts // 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 var mounts []rspec.Mount
defaultMountsPaths := getMounts(filePath) defaultMountsPaths := getMounts(filePath)
for _, path := range defaultMountsPaths { for _, path := range defaultMountsPaths {
@ -186,7 +197,6 @@ func addSecretsFromMountsFile(filePath, mountLabel, containerWorkingDir string)
if err = os.MkdirAll(ctrDirOnHost, 0755); err != nil { if err = os.MkdirAll(ctrDirOnHost, 0755); err != nil {
return nil, errors.Wrapf(err, "making container directory failed") return nil, errors.Wrapf(err, "making container directory failed")
} }
hostDir, err = resolveSymbolicLink(hostDir) hostDir, err = resolveSymbolicLink(hostDir)
if err != nil { if err != nil {
return nil, err return nil, err
@ -206,6 +216,11 @@ func addSecretsFromMountsFile(filePath, mountLabel, containerWorkingDir string)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error applying correct labels") 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 { } else if err != nil {
return nil, errors.Wrapf(err, "error getting status of %q", ctrDirOnHost) return nil, errors.Wrapf(err, "error getting status of %q", ctrDirOnHost)
} }

View File

@ -2,9 +2,12 @@ package util
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/containers/image/types" "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/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
@ -120,3 +123,76 @@ func GetImageConfig(changes []string) (v1.ImageConfig, error) {
StopSignal: stopSignal, StopSignal: stopSignal,
}, nil }, 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
}