8000 support for storage for rootless containers by giuseppe · Pull Request #936 · containers/podman · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

support for storage for rootless containers #936

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions cmd/podman/libpodruntime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/containers/storage"
"github.com/projectatomic/libpod/libpod"
"github.com/projectatomic/libpod/pkg/rootless"
"github.com/urfave/cli"
)

Expand All @@ -24,23 +25,22 @@ func GetRootlessStorageOpts() (storage.StoreOptions, error) {

opts.RunRoot = filepath.Join(libpod.GetRootlessRuntimeDir(), "run")

dataDir := os.Getenv("XDG_DATA_DIR")
if dataDir != "" {
opts.GraphRoot = filepath.Join(dataDir, "containers", "storage")
} else {
dataDir := os.Getenv("XDG_DATA_HOME")
if dataDir == "" {
home := os.Getenv("HOME")
if home == "" {
return opts, fmt.Errorf("HOME not specified")
return opts, fmt.Errorf("neither XDG_DATA_HOME nor HOME was set non-empty")
}
opts.GraphRoot = filepath.Join(home, ".containers", "storage")
dataDir = filepath.Join(home, ".local", "share")
}
opts.GraphRoot = filepath.Join(dataDir, "containers", "storage")
opts.GraphDriverName = "vfs"
return opts, nil
}

func GetDefaultStoreOptions() (storage.StoreOptions, error) {
storageOpts := storage.DefaultStoreOptions
if os.Getuid() != 0 {
if rootless.IsRootless() {
var err error
storageOpts, err = GetRootlessStorageOpts()
if err != nil {
Expand Down
10 changes: 10 additions & 0 deletions cmd/podman/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand Down
14 changes: 13 additions & 1 deletion docs/podman.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Print the version

**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. This file is ignored when running in rootless mode.

**storage.conf** (`/etc/containers/storage.conf`)

Expand Down Expand Up @@ -143,10 +143,22 @@ For the annotation conditions, libpod uses any annotations set in the generated

For the bind-mount conditions, only mounts explicitly requested by the caller via `--volume` are considered. Bind mounts that libpod inserts by default (e.g. `/dev/shm`) are not considered.

Hooks are not used when running in rootless mode.

**registries.conf** (`/etc/containers/registries.conf`)

registries.conf is the configuration file which specifies which container registries should be consulted when completing image names which do not include a registry or domain portion.

## Rootless mode
Podman can also be used as non-root user. When podman runs in rootless mode, an user namespace is automatically created.

Containers created by a non-root user are not visible to other users and are not seen or managed by podman running as root.

Images are pulled under `XDG_DATA_HOME` when specified, otherwise in the home directory of the user under `.local/share/containers/storage`.

Currently it is not possible to create a network device, so rootless containers need to run in the host network namespace. If a rootless container creates a network namespace,
then only the loopback device will be available.

## SEE ALSO
`oci-hooks(5)`, `registries.conf(5)`, `storage.conf(5)`, `crio(8)`

Expand Down
25 changes: 11 additions & 14 deletions libpod/container_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down< 9E12 /tool-tip> Expand Up @@ -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")
}
Expand Down Expand Up @@ -1289,7 +1286,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
}

// Look up and add groups the user belongs to, if a group wasn't directly specified
if !strings.Contains(c.config.User, ":") {
if !rootless.IsRootless() && !strings.Contains(c.config.User, ":") {
groups, err := chrootuser.GetAdditionalGroupsForUser(c.state.Mountpoint, uint64(g.Spec().Process.User.UID))
if err != nil && errors.Cause(err) != chrootuser.ErrNoSuchUser {
return nil, err
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion libpod/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string) (er
// 0, 1 and 2 are stdin, stdout and stderr
cmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3))
cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_STARTPIPE=%d", 4))
cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", os.Getenv("XDG_RUNTIME_DIR")))
cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", GetRootlessRuntimeDir()))
if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok {
cmd.Env = append(cmd.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify))
}
Expand Down
15 changes: 7 additions & 8 deletions libpod/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -176,10 +177,8 @@ var (

// GetRootlessRuntimeDir returns the runtime directory when running as non root
func GetRootlessRuntimeDir() string {
hasNoEnv := false
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
if runtimeDir == "" {
hasNoEnv = true
tmpDir := filepath.Join(os.TempDir(), "user", fmt.Sprintf("%d", os.Getuid()))
os.MkdirAll(tmpDir, 0700)
st, err := os.Stat(tmpDir)
Expand All @@ -190,14 +189,11 @@ func GetRootlessRuntimeDir() string {
if runtimeDir == "" {
runtimeDir = filepath.Join(os.Getenv("HOME"), "rundir")
}
if hasNoEnv {
os.Setenv("XDG_RUNTIME_DIR", runtimeDir)
}
return runtimeDir
}

func getDefaultTmpDir() string {
if os.Getuid() == 0 {
if !rootless.IsRootless() {
return "/var/run/libpod"
}

Expand All @@ -216,8 +212,11 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {

configPath := ConfigPath
foundConfig := true
if os.Getuid() != 0 {
foundConfig = false
if rootless.IsRootless() {
configPath = filepath.Join(os.Getenv("HOME"), ".config/containers/libpod.conf")
if _, err := os.Stat(configPath); err != nil {
foundConfig = false
}
} else if _, err := os.Stat(OverrideConfigPath); err == nil {
// Use the override configuration path
configPath = OverrideConfigPath
Expand Down
145 changes: 145 additions & 0 deletions pkg/rootless/rootless.go
Original file line number Diff line number Diff line change
@@ -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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be unlocked somewhere? I'm not sure of a way around it, but a number of google posts to slow performance when this lock is used. Something to watch for.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should not really matter here as the program is supposed to exit after the function call. I am going to add an Unlock though as the fu A377 nction doesn't change the state of the thread, I've added it just to play safe

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
}
Loading
0