diff options
author | Giuseppe Scrivano <gscrivan@redhat.com> | 2018-06-11 16:03:34 +0200 |
---|---|---|
committer | Atomic Bot <atomic-devel@projectatomic.io> | 2018-06-27 14:07:17 +0000 |
commit | 5ff90677c83b78366554493e10f7615119f7e0a1 (patch) | |
tree | 094685755b891f5813a458cfadb5374e68f732fb | |
parent | 8ee8f847340c3ac2264e183655fb7cdcbbfbb2cd (diff) | |
download | podman-5ff90677c83b78366554493e10f7615119f7e0a1.tar.gz podman-5ff90677c83b78366554493e10f7615119f7e0a1.tar.bz2 podman-5ff90677c83b78366554493e10f7615119f7e0a1.zip |
rootless: add management for the userNS
When running podman as non root user always create an userNS and let
the OCI runtime use it.
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
Closes: #936
Approved by: rhatdan
-rw-r--r-- | cmd/podman/libpodruntime/runtime.go | 3 | ||||
-rw-r--r-- | cmd/podman/main.go | 10 | ||||
-rw-r--r-- | libpod/container_internal.go | 23 | ||||
-rw-r--r-- | libpod/runtime.go | 5 | ||||
-rw-r--r-- | pkg/rootless/rootless.go | 145 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.c | 128 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 3 | ||||
-rw-r--r-- | pkg/spec/spec.go | 6 |
8 files changed, 303 insertions, 20 deletions
diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index 042ce87e5..abe0115e9 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -7,6 +7,7 @@ import ( "github.com/containers/storage" "github.com/projectatomic/libpod/libpod" + "github.com/projectatomic/libpod/pkg/rootless" "github.com/urfave/cli" ) @@ -40,7 +41,7 @@ func GetRootlessStorageOpts() (storage.StoreOptions, error) { func GetDefaultStoreOptions() (storage.StoreOptions, error) { storageOpts := storage.DefaultStoreOptions - if os.Getuid() != 0 { + if rootless.IsRootless() { var err error storageOpts, err = GetRootlessStorageOpts() if err != nil { diff --git a/cmd/podman/main.go b/cmd/podman/main.go index f936a332f..fefd8fdad 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/projectatomic/libpod/pkg/hooks" _ "github.com/projectatomic/libpod/pkg/hooks/0.1.0" + "github.com/projectatomic/libpod/pkg/rootless" "github.com/projectatomic/libpod/version" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -26,6 +27,15 @@ func main() { debug := false cpuProfile := false + became, err := rootless.BecomeRootInUserNS() + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + if became { + os.Exit(0) + } + if reexec.Init() { return } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 6531d5de0..3d94fa871 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -29,6 +29,7 @@ import ( "github.com/projectatomic/libpod/pkg/chrootuser" "github.com/projectatomic/libpod/pkg/hooks" "github.com/projectatomic/libpod/pkg/hooks/exec" + "github.com/projectatomic/libpod/pkg/rootless" "github.com/projectatomic/libpod/pkg/secrets" "github.com/projectatomic/libpod/pkg/util" "github.com/sirupsen/logrus" @@ -235,7 +236,7 @@ func (c *Container) setupStorage(ctx context.Context) error { return errors.Wrapf(err, "error creating container storage") } - if os.Getuid() == 0 && (len(c.config.IDMappings.UIDMap) != 0 || len(c.config.IDMappings.GIDMap) != 0) { + if !rootless.IsRootless() && (len(c.config.IDMappings.UIDMap) != 0 || len(c.config.IDMappings.GIDMap) != 0) { info, err := os.Stat(c.runtime.config.TmpDir) if err != nil { return errors.Wrapf(err, "cannot stat `%s`", c.runtime.config.TmpDir) @@ -531,7 +532,7 @@ func (c *Container) completeNetworkSetup() error { if !c.config.PostConfigureNetNS { return nil } - if os.Getuid() != 0 { + if rootless.IsRootless() { return nil } if err := c.syncContainer(); err != nil { @@ -734,7 +735,7 @@ func (c *Container) mountStorage() (err error) { return nil } - if os.Getuid() == 0 { + if !rootless.IsRootless() { // TODO: generalize this mount code so it will mount every mount in ctr.config.Mounts mounted, err := mount.Mounted(c.config.ShmDir) if err != nil { @@ -1004,10 +1005,8 @@ func (c *Container) postDeleteHooks(ctx context.Context) (err error) { // Make standard bind mounts to include in the container func (c *Container) makeBindMounts() error { - if os.Getuid() == 0 { - if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil { - return errors.Wrapf(err, "cannot chown run directory %s", c.state.RunDir) - } + if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil { + return errors.Wrapf(err, "cannot chown run directory %s", c.state.RunDir) } if c.state.BindMounts == nil { @@ -1084,10 +1083,8 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error) return "", errors.Wrapf(err, "unable to create %s", destFileName) } defer f.Close() - if os.Getuid() == 0 { - if err := f.Chown(c.RootUID(), c.RootGID()); err != nil { - return "", err - } + if err := f.Chown(c.RootUID(), c.RootGID()); err != nil { + return "", err } if _, err := f.WriteString(output); err != nil { @@ -1249,7 +1246,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } var err error - if os.Getuid() == 0 { + if !rootless.IsRootless() { if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, &g); err != nil { return nil, errors.Wrapf(err, "error setting up OCI Hooks") } @@ -1361,7 +1358,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { g.AddProcessEnv("container", "libpod") } - if os.Getuid() != 0 { + if rootless.IsRootless() { g.SetLinuxCgroupsPath("") } else if c.runtime.config.CgroupManager == SystemdCgroupsManager { // When runc is set to use Systemd as a cgroup manager, it diff --git a/libpod/runtime.go b/libpod/runtime.go index 21dd9d024..338a2b436 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -19,6 +19,7 @@ import ( "github.com/projectatomic/libpod/libpod/image" "github.com/projectatomic/libpod/pkg/hooks" sysreg "github.com/projectatomic/libpod/pkg/registries" + "github.com/projectatomic/libpod/pkg/rootless" "github.com/sirupsen/logrus" "github.com/ulule/deepcopier" ) @@ -197,7 +198,7 @@ func GetRootlessRuntimeDir() string { } func getDefaultTmpDir() string { - if os.Getuid() == 0 { + if !rootless.IsRootless() { return "/var/run/libpod" } @@ -216,7 +217,7 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { configPath := ConfigPath foundConfig := true - if os.Getuid() != 0 { + if rootless.IsRootless() { foundConfig = false } else if _, err := os.Stat(OverrideConfigPath); err == nil { // Use the override configuration path diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go new file mode 100644 index 000000000..737fc91c7 --- /dev/null +++ b/pkg/rootless/rootless.go @@ -0,0 +1,145 @@ +package rootless + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + gosignal "os/signal" + "runtime" + "syscall" + + "github.com/containers/storage/pkg/idtools" + "github.com/docker/docker/pkg/signal" + "github.com/pkg/errors" +) + +/* +extern int reexec_in_user_namespace(int ready); +extern int reexec_in_user_namespace_wait(int pid); +*/ +import "C" + +func runInUser() error { + os.Setenv("_LIBPOD_USERNS_CONFIGURED", "done") + return nil +} + +// IsRootless tells us if we are running in rootless mode +func IsRootless() bool { + return os.Getuid() != 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" +} + +func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) error { + path, err := exec.LookPath(tool) + if err != nil { + return err + } + + appendTriplet := func(l []string, a, b, c int) []string { + return append(l, fmt.Sprintf("%d", a), fmt.Sprintf("%d", b), fmt.Sprintf("%d", c)) + } + + args := []string{path, fmt.Sprintf("%d", pid)} + args = appendTriplet(args, 0, hostID, 1) + if mappings != nil { + for _, i := range mappings { + args = appendTriplet(args, i.ContainerID+1, i.HostID, i.Size) + } + } + cmd := exec.Cmd{ + Path: path, + Args: args, + } + return cmd.Run() +} + +// BecomeRootInUserNS re-exec podman in a new userNS +func BecomeRootInUserNS() (bool, error) { + + if os.Getuid() == 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" { + if os.Getenv("_LIBPOD_USERNS_CONFIGURED") == "init" { + return false, runInUser() + } + return false, nil + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + r, w, err := os.Pipe() + if err != nil { + return false, err + } + defer r.Close() + defer w.Close() + + pidC := C.reexec_in_user_namespace(C.int(r.Fd())) + pid := int(pidC) + if pid < 0 { + return false, errors.Errorf("cannot re-exec process") + } + + setgroups := fmt.Sprintf("/proc/%d/setgroups", pid) + err = ioutil.WriteFile(setgroups, []byte("deny\n"), 0666) + if err != nil { + return false, errors.Wrapf(err, "cannot write setgroups file") + } + + var uids, gids []idtools.IDMap + username := os.Getenv("USER") + mappings, err := idtools.NewIDMappings(username, username) + if err == nil { + uids = mappings.UIDs() + gids = mappings.GIDs() + } + + uidsMapped := false + if mappings != nil && uids != nil { + uidsMapped = tryMappingTool("newuidmap", pid, os.Getuid(), uids) == nil + } + if !uidsMapped { + uidMap := fmt.Sprintf("/proc/%d/uid_map", pid) + err = ioutil.WriteFile(uidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Getuid())), 0666) + if err != nil { + return false, errors.Wrapf(err, "cannot write uid_map") + } + } + + gidsMapped := false + if mappings != nil && gids != nil { + gidsMapped = tryMappingTool("newgidmap", pid, os.Getgid(), gids) == nil + } + if !gidsMapped { + gidMap := fmt.Sprintf("/proc/%d/gid_map", pid) + err = ioutil.WriteFile(gidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Getgid())), 0666) + if err != nil { + return false, errors.Wrapf(err, "cannot write gid_map") + } + } + + _, err = w.Write([]byte("1")) + if err != nil { + return false, errors.Wrapf(err, "write to sync pipe") + } + + c := make(chan os.Signal, 1) + + gosignal.Notify(c) + defer gosignal.Reset() + go func() { + for s := range c { + if s == signal.SIGCHLD || s == signal.SIGPIPE { + continue + } + + syscall.Kill(int(pidC), s.(syscall.Signal)) + } + }() + + if C.reexec_in_user_namespace_wait(pidC) < 0 { + return false, errors.Wrapf(err, "error waiting for the re-exec process") + } + + return true, nil +} diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c new file mode 100644 index 000000000..f107af7c7 --- /dev/null +++ b/pkg/rootless/rootless_linux.c @@ -0,0 +1,128 @@ +#define _GNU_SOURCE +#include <sched.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/syscall.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/stat.h> +#include <limits.h> +#include <sys/types.h> +#include <signal.h> +#include <fcntl.h> +#include <sys/wait.h> + +static int +syscall_clone (unsigned long flags, void *child_stack) +{ + return (int) syscall (__NR_clone, flags, child_stack); +} + +static char ** +get_cmd_line_args (pid_t pid) +{ + int fd; + char path[PATH_MAX]; + char *buffer; + size_t allocated; + size_t used = 0; + int ret; + int i, argc = 0; + char **argv; + + sprintf (path, "/proc/%d/cmdline", pid); + fd = open (path, O_RDONLY); + if (fd < 0) + return NULL; + + allocated = 512; + buffer = malloc (allocated); + if (buffer == NULL) + return NULL; + for (;;) + { + do + ret = read (fd, buffer + used, allocated - used); + while (ret < 0 && errno == EINTR); + if (ret < 0) + return NULL; + + if (ret == 0) + break; + + used += ret; + if (allocated == used) + { + allocated += 512; + buffer = realloc (buffer, allocated); + if (buffer == NULL) + return NULL; + } + } + close (fd); + + for (i = 0; i < used; i++) + if (buffer[i] == '\0') + argc++; + + argv = malloc (sizeof (char *) * (argc + 1)); + argc = 0; + + argv[argc++] = buffer; + for (i = 0; i < used - 1; i++) + if (buffer[i] == '\0') + argv[argc++] = buffer + i + 1; + + argv[argc] = NULL; + + return argv; +} + +int +reexec_in_user_namespace(int ready) +{ + int ret; + pid_t pid; + char b; + pid_t ppid = getpid (); + char **argv; + + pid = syscall_clone (CLONE_NEWUSER|SIGCHLD, NULL); + if (pid) + return pid; + + argv = get_cmd_line_args (ppid); + + setenv ("_LIBPOD_USERNS_CONFIGURED", "init", 1); + + do + ret = read (ready, &b, 1) < 0; + while (ret < 0 && errno == EINTR); + if (ret < 0) + _exit (1); + close (ready); + + execv (argv[0], argv); + + _exit (1); +} + +int +reexec_in_user_namespace_wait (int pid) +{ + pid_t p; + int status; + + do + p = waitpid (pid, &status, 0); + while (p < 0 && errno == EINTR); + + if (p < 0) + return -1; + + if (WIFEXITED (status)) + return WEXITSTATUS (status); + if (WIFSIGNALED (status)) + return 128 + WTERMSIG (status); + return -1; +} diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 205e08c57..af0a62c65 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -16,6 +16,7 @@ import ( "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/projectatomic/libpod/libpod" + "github.com/projectatomic/libpod/pkg/rootless" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) @@ -360,7 +361,7 @@ func (c *CreateConfig) GetContainerCreateOptions() ([]libpod.CtrCreateOption, er // does not have one options = append(options, libpod.WithEntrypoint(c.Entrypoint)) - if os.Getuid() != 0 { + if rootless.IsRootless() { if !c.NetMode.IsHost() && !c.NetMode.IsNone() { options = append(options, libpod.WithNetNS(portBindings, true)) } diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index b341c8a35..565c07014 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -1,7 +1,6 @@ package createconfig import ( - "os" "strings" "github.com/docker/docker/daemon/caps" @@ -12,6 +11,7 @@ import ( spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" + "github.com/projectatomic/libpod/pkg/rootless" "github.com/sirupsen/logrus" "io/ioutil" ) @@ -45,7 +45,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } g.AddMount(sysMnt) } - if os.Getuid() != 0 { + if rootless.IsRootless() { g.RemoveMount("/dev/pts") devPts := spec.Mount{ Destination: "/dev/pts", @@ -82,7 +82,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } g.AddProcessEnv("container", "podman") - canAddResources := os.Getuid() == 0 + canAddResources := !rootless.IsRootless() if canAddResources { // RESOURCES - MEMORY |