Skip to content

Commit

Permalink
Merge pull request #38050 from AkihiroSuda/rootless
Browse files Browse the repository at this point in the history
Allow running dockerd as a non-root user (Rootless mode)
  • Loading branch information
thaJeztah committed Feb 3, 2019
2 parents 50e63ad + ec87479 commit 93d994e
Show file tree
Hide file tree
Showing 36 changed files with 638 additions and 68 deletions.
8 changes: 7 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,12 @@ ENV INSTALL_BINARY_NAME=tini
COPY hack/dockerfile/install/$INSTALL_BINARY_NAME.installer ./
RUN PREFIX=/build ./install.sh $INSTALL_BINARY_NAME


FROM base AS rootlesskit
ENV INSTALL_BINARY_NAME=rootlesskit
COPY hack/dockerfile/install/install.sh ./install.sh
COPY hack/dockerfile/install/$INSTALL_BINARY_NAME.installer ./
RUN PREFIX=/build/ ./install.sh $INSTALL_BINARY_NAME
COPY ./contrib/dockerd-rootless.sh /build

# TODO: Some of this is only really needed for testing, it would be nice to split this up
FROM runtime-dev AS dev
Expand Down Expand Up @@ -233,6 +238,7 @@ RUN cd /docker-py \
&& pip install paramiko==2.4.2 \
&& pip install yamllint==1.5.0 \
&& pip install -r test-requirements.txt
COPY --from=rootlesskit /build/ /usr/local/bin/

ENV PATH=/usr/local/cli:$PATH
ENV DOCKER_BUILDTAGS apparmor seccomp selinux
Expand Down
2 changes: 2 additions & 0 deletions cli/config/configdir.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ var (
)

// Dir returns the path to the configuration directory as specified by the DOCKER_CONFIG environment variable.
// If DOCKER_CONFIG is unset, Dir returns ~/.docker .
// Dir ignores XDG_CONFIG_HOME (same as the docker client).
// TODO: this was copied from cli/config/configfile and should be removed once cmd/dockerd moves
func Dir() string {
return configDir
Expand Down
15 changes: 14 additions & 1 deletion cmd/dockerd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,20 @@ const (
)

// installCommonConfigFlags adds flags to the pflag.FlagSet to configure the daemon
func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) error {
var maxConcurrentDownloads, maxConcurrentUploads int
defaultPidFile, err := getDefaultPidFile()
if err != nil {
return err
}
defaultDataRoot, err := getDefaultDataRoot()
if err != nil {
return err
}
defaultExecRoot, err := getDefaultExecRoot()
if err != nil {
return err
}

installRegistryServiceFlags(&conf.ServiceOptions, flags)

Expand Down Expand Up @@ -80,6 +92,7 @@ func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) {

conf.MaxConcurrentDownloads = &maxConcurrentDownloads
conf.MaxConcurrentUploads = &maxConcurrentUploads
return nil
}

func installRegistryServiceFlags(options *registry.ServiceOptions, flags *pflag.FlagSet) {
Expand Down
41 changes: 36 additions & 5 deletions cmd/dockerd/config_common_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,48 @@
package main

import (
"path/filepath"

"github.com/docker/docker/api/types"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/homedir"
"github.com/docker/docker/rootless"
"github.com/spf13/pflag"
)

var (
defaultPidFile = "/var/run/docker.pid"
defaultDataRoot = "/var/lib/docker"
defaultExecRoot = "/var/run/docker"
)
func getDefaultPidFile() (string, error) {
if !rootless.RunningWithNonRootUsername() {
return "/var/run/docker.pid", nil
}
runtimeDir, err := homedir.GetRuntimeDir()
if err != nil {
return "", err
}
return filepath.Join(runtimeDir, "docker.pid"), nil
}

func getDefaultDataRoot() (string, error) {
if !rootless.RunningWithNonRootUsername() {
return "/var/lib/docker", nil
}
dataHome, err := homedir.GetDataHome()
if err != nil {
return "", err
}
return filepath.Join(dataHome, "docker"), nil
}

func getDefaultExecRoot() (string, error) {
if !rootless.RunningWithNonRootUsername() {
return "/var/run/docker", nil
}
runtimeDir, err := homedir.GetRuntimeDir()
if err != nil {
return "", err
}
return filepath.Join(runtimeDir, "docker"), nil
}

// installUnixConfigFlags adds command-line options to the top-level flag parser for
// the current process that are common across Unix platforms.
Expand Down
11 changes: 8 additions & 3 deletions cmd/dockerd/config_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ package main
import (
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/opts"
"github.com/docker/docker/rootless"
"github.com/docker/go-units"
"github.com/spf13/pflag"
)

// installConfigFlags adds flags to the pflag.FlagSet to configure the daemon
func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) error {
// First handle install flags which are consistent cross-platform
installCommonConfigFlags(conf, flags)
if err := installCommonConfigFlags(conf, flags); err != nil {
return err
}

// Then install flags common to unix platforms
installUnixConfigFlags(conf, flags)
Expand Down Expand Up @@ -46,5 +49,7 @@ func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
flags.BoolVar(&conf.NoNewPrivileges, "no-new-privileges", false, "Set no-new-privileges by default for new containers")
flags.StringVar(&conf.IpcMode, "default-ipc-mode", config.DefaultIpcMode, `Default mode for containers ipc ("shareable" | "private")`)
flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "Default address pools for node specific local networks")

// Mostly users don't need to set this flag explicitly.
flags.BoolVar(&conf.Rootless, "rootless", rootless.RunningWithNonRootUsername(), "Enable rootless mode (experimental)")
return nil
}
3 changes: 2 additions & 1 deletion cmd/dockerd/config_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ func TestDaemonParseShmSize(t *testing.T) {
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)

conf := &config.Config{}
installConfigFlags(conf, flags)
err := installConfigFlags(conf, flags)
assert.NilError(t, err)
// By default `--default-shm-size=64M`
assert.Check(t, is.Equal(int64(64*1024*1024), conf.ShmSize.Value()))
assert.Check(t, flags.Set("default-shm-size", "128M"))
Expand Down
23 changes: 16 additions & 7 deletions cmd/dockerd/config_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,28 @@ import (
"github.com/spf13/pflag"
)

var (
defaultPidFile string
defaultDataRoot = filepath.Join(os.Getenv("programdata"), "docker")
defaultExecRoot = filepath.Join(os.Getenv("programdata"), "docker", "exec-root")
)
func getDefaultPidFile() (string, error) {
return "", nil
}

func getDefaultDataRoot() (string, error) {
return filepath.Join(os.Getenv("programdata"), "docker"), nil
}

func getDefaultExecRoot() (string, error) {
return filepath.Join(os.Getenv("programdata"), "docker", "exec-root"), nil
}

// installConfigFlags adds flags to the pflag.FlagSet to configure the daemon
func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) error {
// First handle install flags which are consistent cross-platform
installCommonConfigFlags(conf, flags)
if err := installCommonConfigFlags(conf, flags); err != nil {
return err
}

// Then platform-specific install flags.
flags.StringVar(&conf.BridgeConfig.FixedCIDR, "fixed-cidr", "", "IPv4 subnet for fixed IPs")
flags.StringVarP(&conf.BridgeConfig.Iface, "bridge", "b", "", "Attach containers to a virtual switch")
flags.StringVarP(&conf.SocketGroup, "group", "G", "", "Users or groups that can access the named pipe")
return nil
}
54 changes: 45 additions & 9 deletions cmd/dockerd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ import (
"github.com/docker/docker/libcontainerd/supervisor"
dopts "github.com/docker/docker/opts"
"github.com/docker/docker/pkg/authorization"
"github.com/docker/docker/pkg/homedir"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/pidfile"
"github.com/docker/docker/pkg/plugingetter"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/plugin"
"github.com/docker/docker/rootless"
"github.com/docker/docker/runconfig"
"github.com/docker/go-connections/tlsconfig"
swarmapi "github.com/docker/swarmkit/api"
Expand Down Expand Up @@ -97,6 +99,17 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {

if cli.Config.Experimental {
logrus.Warn("Running experimental build")
if cli.Config.IsRootless() {
logrus.Warn("Running in rootless mode. Cgroups, AppArmor, and CRIU are disabled.")
}
} else {
if cli.Config.IsRootless() {
return fmt.Errorf("rootless mode is supported only when running in experimental mode")
}
}
// return human-friendly error before creating files
if runtime.GOOS == "linux" && os.Geteuid() != 0 {
return fmt.Errorf("dockerd needs to be started with root. To see how to run dockerd in rootless mode with unprivileged user, see the documentation")
}

system.InitLCOW(cli.Config.Experimental)
Expand All @@ -115,18 +128,27 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
return err
}

potentiallyUnderRuntimeDir := []string{cli.Config.ExecRoot}

if cli.Pidfile != "" {
pf, err := pidfile.New(cli.Pidfile)
if err != nil {
return errors.Wrap(err, "failed to start daemon")
}
potentiallyUnderRuntimeDir = append(potentiallyUnderRuntimeDir, cli.Pidfile)
defer func() {
if err := pf.Remove(); err != nil {
logrus.Error(err)
}
}()
}

// Set sticky bit if XDG_RUNTIME_DIR is set && the file is actually under XDG_RUNTIME_DIR
if _, err := homedir.StickRuntimeDirContents(potentiallyUnderRuntimeDir); err != nil {
// StickRuntimeDirContents returns nil error if XDG_RUNTIME_DIR is just unset
logrus.WithError(err).Warn("cannot set sticky bit on files under XDG_RUNTIME_DIR")
}

serverConfig, err := newAPIServerConfig(cli)
if err != nil {
return errors.Wrap(err, "failed to create API server")
Expand All @@ -140,7 +162,11 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {

ctx, cancel := context.WithCancel(context.Background())
if cli.Config.ContainerdAddr == "" && runtime.GOOS != "windows" {
if !systemContainerdRunning() {
systemContainerdAddr, ok, err := systemContainerdRunning(cli.Config.IsRootless())
if err != nil {
return errors.Wrap(err, "could not determine whether the system containerd is running")
}
if !ok {
opts, err := cli.getContainerdDaemonOpts()
if err != nil {
cancel()
Expand All @@ -157,7 +183,7 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
// Try to wait for containerd to shutdown
defer r.WaitTimeout(10 * time.Second)
} else {
cli.Config.ContainerdAddr = containerddefaults.DefaultAddress
cli.Config.ContainerdAddr = systemContainerdAddr
}
}
defer cancel()
Expand Down Expand Up @@ -403,9 +429,11 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
}

if conf.TrustKeyPath == "" {
conf.TrustKeyPath = filepath.Join(
getDaemonConfDir(conf.Root),
defaultTrustKeyFile)
daemonConfDir, err := getDaemonConfDir(conf.Root)
if err != nil {
return nil, err
}
conf.TrustKeyPath = filepath.Join(daemonConfDir, defaultTrustKeyFile)
}

if flags.Changed("graph") && flags.Changed("data-root") {
Expand Down Expand Up @@ -585,7 +613,7 @@ func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, er
var hosts []string
for i := 0; i < len(cli.Config.Hosts); i++ {
var err error
if cli.Config.Hosts[i], err = dopts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil {
if cli.Config.Hosts[i], err = dopts.ParseHost(cli.Config.TLS, rootless.RunningWithNonRootUsername(), cli.Config.Hosts[i]); err != nil {
return nil, errors.Wrapf(err, "error parsing -H %s", cli.Config.Hosts[i])
}

Expand Down Expand Up @@ -662,9 +690,17 @@ func validateAuthzPlugins(requestedPlugins []string, pg plugingetter.PluginGette
return nil
}

func systemContainerdRunning() bool {
_, err := os.Lstat(containerddefaults.DefaultAddress)
return err == nil
func systemContainerdRunning(isRootless bool) (string, bool, error) {
addr := containerddefaults.DefaultAddress
if isRootless {
runtimeDir, err := homedir.GetRuntimeDir()
if err != nil {
return "", false, err
}
addr = filepath.Join(runtimeDir, "containerd", "containerd.sock")
}
_, err := os.Lstat(addr)
return addr, err == nil, nil
}

// configureDaemonLogs sets the logrus logging level and formatting
Expand Down
Loading

0 comments on commit 93d994e

Please sign in to comment.