allow running dockerd
in an unprivileged user namespace (rootless mode)
Please refer to `docs/rootless.md`. TLDR: * Make sure `/etc/subuid` and `/etc/subgid` contain the entry for you * `dockerd-rootless.sh --experimental` * `docker -H unix://$XDG_RUNTIME_DIR/docker.sock run ...` Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
This commit is contained in:
parent
50e63adf30
commit
ec87479b7e
36 changed files with 638 additions and 68 deletions
|
@ -161,7 +161,12 @@ ENV INSTALL_BINARY_NAME=tini
|
||||||
COPY hack/dockerfile/install/$INSTALL_BINARY_NAME.installer ./
|
COPY hack/dockerfile/install/$INSTALL_BINARY_NAME.installer ./
|
||||||
RUN PREFIX=/build ./install.sh $INSTALL_BINARY_NAME
|
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
|
# TODO: Some of this is only really needed for testing, it would be nice to split this up
|
||||||
FROM runtime-dev AS dev
|
FROM runtime-dev AS dev
|
||||||
|
@ -233,6 +238,7 @@ RUN cd /docker-py \
|
||||||
&& pip install paramiko==2.4.2 \
|
&& pip install paramiko==2.4.2 \
|
||||||
&& pip install yamllint==1.5.0 \
|
&& pip install yamllint==1.5.0 \
|
||||||
&& pip install -r test-requirements.txt
|
&& pip install -r test-requirements.txt
|
||||||
|
COPY --from=rootlesskit /build/ /usr/local/bin/
|
||||||
|
|
||||||
ENV PATH=/usr/local/cli:$PATH
|
ENV PATH=/usr/local/cli:$PATH
|
||||||
ENV DOCKER_BUILDTAGS apparmor seccomp selinux
|
ENV DOCKER_BUILDTAGS apparmor seccomp selinux
|
||||||
|
|
|
@ -13,6 +13,8 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dir returns the path to the configuration directory as specified by the DOCKER_CONFIG environment variable.
|
// 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
|
// TODO: this was copied from cli/config/configfile and should be removed once cmd/dockerd moves
|
||||||
func Dir() string {
|
func Dir() string {
|
||||||
return configDir
|
return configDir
|
||||||
|
|
|
@ -17,8 +17,20 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// installCommonConfigFlags adds flags to the pflag.FlagSet to configure the daemon
|
// 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
|
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)
|
installRegistryServiceFlags(&conf.ServiceOptions, flags)
|
||||||
|
|
||||||
|
@ -80,6 +92,7 @@ func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
|
||||||
|
|
||||||
conf.MaxConcurrentDownloads = &maxConcurrentDownloads
|
conf.MaxConcurrentDownloads = &maxConcurrentDownloads
|
||||||
conf.MaxConcurrentUploads = &maxConcurrentUploads
|
conf.MaxConcurrentUploads = &maxConcurrentUploads
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func installRegistryServiceFlags(options *registry.ServiceOptions, flags *pflag.FlagSet) {
|
func installRegistryServiceFlags(options *registry.ServiceOptions, flags *pflag.FlagSet) {
|
||||||
|
|
|
@ -3,17 +3,48 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/daemon/config"
|
"github.com/docker/docker/daemon/config"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
|
"github.com/docker/docker/rootless"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func getDefaultPidFile() (string, error) {
|
||||||
defaultPidFile = "/var/run/docker.pid"
|
if !rootless.RunningWithNonRootUsername() {
|
||||||
defaultDataRoot = "/var/lib/docker"
|
return "/var/run/docker.pid", nil
|
||||||
defaultExecRoot = "/var/run/docker"
|
}
|
||||||
)
|
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
|
// installUnixConfigFlags adds command-line options to the top-level flag parser for
|
||||||
// the current process that are common across Unix platforms.
|
// the current process that are common across Unix platforms.
|
||||||
|
|
|
@ -5,14 +5,17 @@ package main
|
||||||
import (
|
import (
|
||||||
"github.com/docker/docker/daemon/config"
|
"github.com/docker/docker/daemon/config"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
|
"github.com/docker/docker/rootless"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// installConfigFlags adds flags to the pflag.FlagSet to configure the daemon
|
// 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
|
// 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
|
// Then install flags common to unix platforms
|
||||||
installUnixConfigFlags(conf, flags)
|
installUnixConfigFlags(conf, flags)
|
||||||
|
@ -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.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.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")
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@ func TestDaemonParseShmSize(t *testing.T) {
|
||||||
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||||
|
|
||||||
conf := &config.Config{}
|
conf := &config.Config{}
|
||||||
installConfigFlags(conf, flags)
|
err := installConfigFlags(conf, flags)
|
||||||
|
assert.NilError(t, err)
|
||||||
// By default `--default-shm-size=64M`
|
// By default `--default-shm-size=64M`
|
||||||
assert.Check(t, is.Equal(int64(64*1024*1024), conf.ShmSize.Value()))
|
assert.Check(t, is.Equal(int64(64*1024*1024), conf.ShmSize.Value()))
|
||||||
assert.Check(t, flags.Set("default-shm-size", "128M"))
|
assert.Check(t, flags.Set("default-shm-size", "128M"))
|
||||||
|
|
|
@ -8,19 +8,28 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func getDefaultPidFile() (string, error) {
|
||||||
defaultPidFile string
|
return "", nil
|
||||||
defaultDataRoot = filepath.Join(os.Getenv("programdata"), "docker")
|
}
|
||||||
defaultExecRoot = filepath.Join(os.Getenv("programdata"), "docker", "exec-root")
|
|
||||||
)
|
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
|
// 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
|
// 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.
|
// Then platform-specific install flags.
|
||||||
flags.StringVar(&conf.BridgeConfig.FixedCIDR, "fixed-cidr", "", "IPv4 subnet for fixed IPs")
|
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.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")
|
flags.StringVarP(&conf.SocketGroup, "group", "G", "", "Users or groups that can access the named pipe")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,12 +40,14 @@ import (
|
||||||
"github.com/docker/docker/libcontainerd/supervisor"
|
"github.com/docker/docker/libcontainerd/supervisor"
|
||||||
dopts "github.com/docker/docker/opts"
|
dopts "github.com/docker/docker/opts"
|
||||||
"github.com/docker/docker/pkg/authorization"
|
"github.com/docker/docker/pkg/authorization"
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
"github.com/docker/docker/pkg/pidfile"
|
"github.com/docker/docker/pkg/pidfile"
|
||||||
"github.com/docker/docker/pkg/plugingetter"
|
"github.com/docker/docker/pkg/plugingetter"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/docker/plugin"
|
"github.com/docker/docker/plugin"
|
||||||
|
"github.com/docker/docker/rootless"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
swarmapi "github.com/docker/swarmkit/api"
|
swarmapi "github.com/docker/swarmkit/api"
|
||||||
|
@ -97,6 +99,17 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
|
||||||
|
|
||||||
if cli.Config.Experimental {
|
if cli.Config.Experimental {
|
||||||
logrus.Warn("Running experimental build")
|
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)
|
system.InitLCOW(cli.Config.Experimental)
|
||||||
|
@ -115,11 +128,14 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
potentiallyUnderRuntimeDir := []string{cli.Config.ExecRoot}
|
||||||
|
|
||||||
if cli.Pidfile != "" {
|
if cli.Pidfile != "" {
|
||||||
pf, err := pidfile.New(cli.Pidfile)
|
pf, err := pidfile.New(cli.Pidfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to start daemon")
|
return errors.Wrap(err, "failed to start daemon")
|
||||||
}
|
}
|
||||||
|
potentiallyUnderRuntimeDir = append(potentiallyUnderRuntimeDir, cli.Pidfile)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := pf.Remove(); err != nil {
|
if err := pf.Remove(); err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
|
@ -127,6 +143,12 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
serverConfig, err := newAPIServerConfig(cli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to create API server")
|
return errors.Wrap(err, "failed to create API server")
|
||||||
|
@ -140,7 +162,11 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
if cli.Config.ContainerdAddr == "" && runtime.GOOS != "windows" {
|
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()
|
opts, err := cli.getContainerdDaemonOpts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
|
@ -157,7 +183,7 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
|
||||||
// Try to wait for containerd to shutdown
|
// Try to wait for containerd to shutdown
|
||||||
defer r.WaitTimeout(10 * time.Second)
|
defer r.WaitTimeout(10 * time.Second)
|
||||||
} else {
|
} else {
|
||||||
cli.Config.ContainerdAddr = containerddefaults.DefaultAddress
|
cli.Config.ContainerdAddr = systemContainerdAddr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -403,9 +429,11 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.TrustKeyPath == "" {
|
if conf.TrustKeyPath == "" {
|
||||||
conf.TrustKeyPath = filepath.Join(
|
daemonConfDir, err := getDaemonConfDir(conf.Root)
|
||||||
getDaemonConfDir(conf.Root),
|
if err != nil {
|
||||||
defaultTrustKeyFile)
|
return nil, err
|
||||||
|
}
|
||||||
|
conf.TrustKeyPath = filepath.Join(daemonConfDir, defaultTrustKeyFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if flags.Changed("graph") && flags.Changed("data-root") {
|
if flags.Changed("graph") && flags.Changed("data-root") {
|
||||||
|
@ -585,7 +613,7 @@ func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, er
|
||||||
var hosts []string
|
var hosts []string
|
||||||
for i := 0; i < len(cli.Config.Hosts); i++ {
|
for i := 0; i < len(cli.Config.Hosts); i++ {
|
||||||
var err error
|
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])
|
return nil, errors.Wrapf(err, "error parsing -H %s", cli.Config.Hosts[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,9 +690,17 @@ func validateAuthzPlugins(requestedPlugins []string, pg plugingetter.PluginGette
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func systemContainerdRunning() bool {
|
func systemContainerdRunning(isRootless bool) (string, bool, error) {
|
||||||
_, err := os.Lstat(containerddefaults.DefaultAddress)
|
addr := containerddefaults.DefaultAddress
|
||||||
return err == nil
|
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
|
// configureDaemonLogs sets the logrus logging level and formatting
|
||||||
|
|
|
@ -11,18 +11,22 @@ import (
|
||||||
"gotest.tools/fs"
|
"gotest.tools/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaultOptions(configFile string) *daemonOptions {
|
func defaultOptions(t *testing.T, configFile string) *daemonOptions {
|
||||||
opts := newDaemonOptions(&config.Config{})
|
opts := newDaemonOptions(&config.Config{})
|
||||||
opts.flags = &pflag.FlagSet{}
|
opts.flags = &pflag.FlagSet{}
|
||||||
opts.InstallFlags(opts.flags)
|
opts.InstallFlags(opts.flags)
|
||||||
installConfigFlags(opts.daemonConfig, opts.flags)
|
if err := installConfigFlags(opts.daemonConfig, opts.flags); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defaultDaemonConfigFile, err := getDefaultDaemonConfigFile()
|
||||||
|
assert.NilError(t, err)
|
||||||
opts.flags.StringVar(&opts.configFile, "config-file", defaultDaemonConfigFile, "")
|
opts.flags.StringVar(&opts.configFile, "config-file", defaultDaemonConfigFile, "")
|
||||||
opts.configFile = configFile
|
opts.configFile = configFile
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) {
|
func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) {
|
||||||
opts := defaultOptions("")
|
opts := defaultOptions(t, "")
|
||||||
opts.Debug = true
|
opts.Debug = true
|
||||||
|
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
|
@ -34,7 +38,7 @@ func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadDaemonCliConfigWithTLS(t *testing.T) {
|
func TestLoadDaemonCliConfigWithTLS(t *testing.T) {
|
||||||
opts := defaultOptions("")
|
opts := defaultOptions(t, "")
|
||||||
opts.TLSOptions.CAFile = "/tmp/ca.pem"
|
opts.TLSOptions.CAFile = "/tmp/ca.pem"
|
||||||
opts.TLS = true
|
opts.TLS = true
|
||||||
|
|
||||||
|
@ -49,7 +53,7 @@ func TestLoadDaemonCliConfigWithConflicts(t *testing.T) {
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
configFile := tempFile.Path()
|
configFile := tempFile.Path()
|
||||||
|
|
||||||
opts := defaultOptions(configFile)
|
opts := defaultOptions(t, configFile)
|
||||||
flags := opts.flags
|
flags := opts.flags
|
||||||
|
|
||||||
assert.Check(t, flags.Set("config-file", configFile))
|
assert.Check(t, flags.Set("config-file", configFile))
|
||||||
|
@ -65,7 +69,7 @@ func TestLoadDaemonCliWithConflictingNodeGenericResources(t *testing.T) {
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
configFile := tempFile.Path()
|
configFile := tempFile.Path()
|
||||||
|
|
||||||
opts := defaultOptions(configFile)
|
opts := defaultOptions(t, configFile)
|
||||||
flags := opts.flags
|
flags := opts.flags
|
||||||
|
|
||||||
assert.Check(t, flags.Set("config-file", configFile))
|
assert.Check(t, flags.Set("config-file", configFile))
|
||||||
|
@ -77,7 +81,7 @@ func TestLoadDaemonCliWithConflictingNodeGenericResources(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadDaemonCliWithConflictingLabels(t *testing.T) {
|
func TestLoadDaemonCliWithConflictingLabels(t *testing.T) {
|
||||||
opts := defaultOptions("")
|
opts := defaultOptions(t, "")
|
||||||
flags := opts.flags
|
flags := opts.flags
|
||||||
|
|
||||||
assert.Check(t, flags.Set("label", "foo=bar"))
|
assert.Check(t, flags.Set("label", "foo=bar"))
|
||||||
|
@ -88,7 +92,7 @@ func TestLoadDaemonCliWithConflictingLabels(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadDaemonCliWithDuplicateLabels(t *testing.T) {
|
func TestLoadDaemonCliWithDuplicateLabels(t *testing.T) {
|
||||||
opts := defaultOptions("")
|
opts := defaultOptions(t, "")
|
||||||
flags := opts.flags
|
flags := opts.flags
|
||||||
|
|
||||||
assert.Check(t, flags.Set("label", "foo=the-same"))
|
assert.Check(t, flags.Set("label", "foo=the-same"))
|
||||||
|
@ -102,7 +106,7 @@ func TestLoadDaemonCliConfigWithTLSVerify(t *testing.T) {
|
||||||
tempFile := fs.NewFile(t, "config", fs.WithContent(`{"tlsverify": true}`))
|
tempFile := fs.NewFile(t, "config", fs.WithContent(`{"tlsverify": true}`))
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
|
|
||||||
opts := defaultOptions(tempFile.Path())
|
opts := defaultOptions(t, tempFile.Path())
|
||||||
opts.TLSOptions.CAFile = "/tmp/ca.pem"
|
opts.TLSOptions.CAFile = "/tmp/ca.pem"
|
||||||
|
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
|
@ -115,7 +119,7 @@ func TestLoadDaemonCliConfigWithExplicitTLSVerifyFalse(t *testing.T) {
|
||||||
tempFile := fs.NewFile(t, "config", fs.WithContent(`{"tlsverify": false}`))
|
tempFile := fs.NewFile(t, "config", fs.WithContent(`{"tlsverify": false}`))
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
|
|
||||||
opts := defaultOptions(tempFile.Path())
|
opts := defaultOptions(t, tempFile.Path())
|
||||||
opts.TLSOptions.CAFile = "/tmp/ca.pem"
|
opts.TLSOptions.CAFile = "/tmp/ca.pem"
|
||||||
|
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
|
@ -128,7 +132,7 @@ func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) {
|
||||||
tempFile := fs.NewFile(t, "config", fs.WithContent(`{}`))
|
tempFile := fs.NewFile(t, "config", fs.WithContent(`{}`))
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
|
|
||||||
opts := defaultOptions(tempFile.Path())
|
opts := defaultOptions(t, tempFile.Path())
|
||||||
opts.TLSOptions.CAFile = "/tmp/ca.pem"
|
opts.TLSOptions.CAFile = "/tmp/ca.pem"
|
||||||
|
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
|
@ -141,7 +145,7 @@ func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) {
|
||||||
tempFile := fs.NewFile(t, "config", fs.WithContent(`{"log-level": "warn"}`))
|
tempFile := fs.NewFile(t, "config", fs.WithContent(`{"log-level": "warn"}`))
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
|
|
||||||
opts := defaultOptions(tempFile.Path())
|
opts := defaultOptions(t, tempFile.Path())
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, loadedConfig != nil)
|
assert.Assert(t, loadedConfig != nil)
|
||||||
|
@ -153,7 +157,7 @@ func TestLoadDaemonConfigWithEmbeddedOptions(t *testing.T) {
|
||||||
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
|
|
||||||
opts := defaultOptions(tempFile.Path())
|
opts := defaultOptions(t, tempFile.Path())
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, loadedConfig != nil)
|
assert.Assert(t, loadedConfig != nil)
|
||||||
|
@ -170,7 +174,7 @@ func TestLoadDaemonConfigWithRegistryOptions(t *testing.T) {
|
||||||
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
|
|
||||||
opts := defaultOptions(tempFile.Path())
|
opts := defaultOptions(t, tempFile.Path())
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, loadedConfig != nil)
|
assert.Assert(t, loadedConfig != nil)
|
||||||
|
|
|
@ -15,11 +15,33 @@ import (
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/daemon/config"
|
"github.com/docker/docker/daemon/config"
|
||||||
"github.com/docker/docker/libcontainerd/supervisor"
|
"github.com/docker/docker/libcontainerd/supervisor"
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
|
"github.com/docker/docker/rootless"
|
||||||
"github.com/docker/libnetwork/portallocator"
|
"github.com/docker/libnetwork/portallocator"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultDaemonConfigFile = "/etc/docker/daemon.json"
|
func getDefaultDaemonConfigDir() (string, error) {
|
||||||
|
if !rootless.RunningWithNonRootUsername() {
|
||||||
|
return "/etc/docker", nil
|
||||||
|
}
|
||||||
|
// NOTE: CLI uses ~/.docker while the daemon uses ~/.config/docker, because
|
||||||
|
// ~/.docker was not designed to store daemon configurations.
|
||||||
|
// In future, the daemon directory may be renamed to ~/.config/moby-engine (?).
|
||||||
|
configHome, err := homedir.GetConfigHome()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return filepath.Join(configHome, "docker"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultDaemonConfigFile() (string, error) {
|
||||||
|
dir, err := getDefaultDaemonConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(dir, "daemon.json"), nil
|
||||||
|
}
|
||||||
|
|
||||||
// setDefaultUmask sets the umask to 0022 to avoid problems
|
// setDefaultUmask sets the umask to 0022 to avoid problems
|
||||||
// caused by custom umask
|
// caused by custom umask
|
||||||
|
@ -33,8 +55,8 @@ func setDefaultUmask() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDaemonConfDir(_ string) string {
|
func getDaemonConfDir(_ string) (string, error) {
|
||||||
return "/etc/docker"
|
return getDefaultDaemonConfigDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DaemonCli) getPlatformContainerdDaemonOpts() ([]supervisor.DaemonOpt, error) {
|
func (cli *DaemonCli) getPlatformContainerdDaemonOpts() ([]supervisor.DaemonOpt, error) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ func TestLoadDaemonCliConfigWithDaemonFlags(t *testing.T) {
|
||||||
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
|
|
||||||
opts := defaultOptions(tempFile.Path())
|
opts := defaultOptions(t, tempFile.Path())
|
||||||
opts.Debug = true
|
opts.Debug = true
|
||||||
opts.LogLevel = "info"
|
opts.LogLevel = "info"
|
||||||
assert.Check(t, opts.flags.Set("selinux-enabled", "true"))
|
assert.Check(t, opts.flags.Set("selinux-enabled", "true"))
|
||||||
|
@ -37,7 +37,7 @@ func TestLoadDaemonConfigWithNetwork(t *testing.T) {
|
||||||
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
|
|
||||||
opts := defaultOptions(tempFile.Path())
|
opts := defaultOptions(t, tempFile.Path())
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, loadedConfig != nil)
|
assert.Assert(t, loadedConfig != nil)
|
||||||
|
@ -54,7 +54,7 @@ func TestLoadDaemonConfigWithMapOptions(t *testing.T) {
|
||||||
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
|
|
||||||
opts := defaultOptions(tempFile.Path())
|
opts := defaultOptions(t, tempFile.Path())
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, loadedConfig != nil)
|
assert.Assert(t, loadedConfig != nil)
|
||||||
|
@ -71,7 +71,7 @@ func TestLoadDaemonConfigWithTrueDefaultValues(t *testing.T) {
|
||||||
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
|
|
||||||
opts := defaultOptions(tempFile.Path())
|
opts := defaultOptions(t, tempFile.Path())
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, loadedConfig != nil)
|
assert.Assert(t, loadedConfig != nil)
|
||||||
|
@ -90,7 +90,7 @@ func TestLoadDaemonConfigWithTrueDefaultValuesLeaveDefaults(t *testing.T) {
|
||||||
tempFile := fs.NewFile(t, "config", fs.WithContent(`{}`))
|
tempFile := fs.NewFile(t, "config", fs.WithContent(`{}`))
|
||||||
defer tempFile.Remove()
|
defer tempFile.Remove()
|
||||||
|
|
||||||
opts := defaultOptions(tempFile.Path())
|
opts := defaultOptions(t, tempFile.Path())
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, loadedConfig != nil)
|
assert.Assert(t, loadedConfig != nil)
|
||||||
|
|
|
@ -12,15 +12,17 @@ import (
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultDaemonConfigFile = ""
|
func getDefaultDaemonConfigFile() (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
// setDefaultUmask doesn't do anything on windows
|
// setDefaultUmask doesn't do anything on windows
|
||||||
func setDefaultUmask() error {
|
func setDefaultUmask() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDaemonConfDir(root string) string {
|
func getDaemonConfDir(root string) (string, error) {
|
||||||
return filepath.Join(root, `\config`)
|
return filepath.Join(root, `\config`), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// preNotifySystem sends a message to the host when the API is active, but before the daemon is
|
// preNotifySystem sends a message to the host when the API is active, but before the daemon is
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDaemonCommand() *cobra.Command {
|
func newDaemonCommand() (*cobra.Command, error) {
|
||||||
opts := newDaemonOptions(config.New())
|
opts := newDaemonOptions(config.New())
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -36,12 +36,18 @@ func newDaemonCommand() *cobra.Command {
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolP("version", "v", false, "Print version information and quit")
|
flags.BoolP("version", "v", false, "Print version information and quit")
|
||||||
|
defaultDaemonConfigFile, err := getDefaultDaemonConfigFile()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
flags.StringVar(&opts.configFile, "config-file", defaultDaemonConfigFile, "Daemon configuration file")
|
flags.StringVar(&opts.configFile, "config-file", defaultDaemonConfigFile, "Daemon configuration file")
|
||||||
opts.InstallFlags(flags)
|
opts.InstallFlags(flags)
|
||||||
installConfigFlags(opts.daemonConfig, flags)
|
if err := installConfigFlags(opts.daemonConfig, flags); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
installServiceFlags(flags)
|
installServiceFlags(flags)
|
||||||
|
|
||||||
return cmd
|
return cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -72,10 +78,17 @@ func main() {
|
||||||
logrus.SetOutput(stderr)
|
logrus.SetOutput(stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := newDaemonCommand()
|
onError := func(err error) {
|
||||||
cmd.SetOutput(stdout)
|
|
||||||
if err := cmd.Execute(); err != nil {
|
|
||||||
fmt.Fprintf(stderr, "%s\n", err)
|
fmt.Fprintf(stderr, "%s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd, err := newDaemonCommand()
|
||||||
|
if err != nil {
|
||||||
|
onError(err)
|
||||||
|
}
|
||||||
|
cmd.SetOutput(stdout)
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
onError(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,8 @@ func newDaemonOptions(config *config.Config) *daemonOptions {
|
||||||
// InstallFlags adds flags for the common options on the FlagSet
|
// InstallFlags adds flags for the common options on the FlagSet
|
||||||
func (o *daemonOptions) InstallFlags(flags *pflag.FlagSet) {
|
func (o *daemonOptions) InstallFlags(flags *pflag.FlagSet) {
|
||||||
if dockerCertPath == "" {
|
if dockerCertPath == "" {
|
||||||
|
// cliconfig.Dir returns $DOCKER_CONFIG or ~/.docker.
|
||||||
|
// cliconfig.Dir does not look up $XDG_CONFIG_HOME
|
||||||
dockerCertPath = cliconfig.Dir()
|
dockerCertPath = cliconfig.Dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
77
contrib/dockerd-rootless.sh
Executable file
77
contrib/dockerd-rootless.sh
Executable file
|
@ -0,0 +1,77 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# dockerd-rootless.sh executes dockerd in rootless mode.
|
||||||
|
#
|
||||||
|
# Usage: dockerd-rootless.sh --experimental [DOCKERD_OPTIONS]
|
||||||
|
# Currently, specifying --experimental is mandatory.
|
||||||
|
#
|
||||||
|
# External dependencies:
|
||||||
|
# * newuidmap and newgidmap needs to be installed.
|
||||||
|
# * /etc/subuid and /etc/subgid needs to be configured for the current user.
|
||||||
|
# * Either slirp4netns (v0.3+) or VPNKit needs to be installed.
|
||||||
|
#
|
||||||
|
# See the documentation for the further information.
|
||||||
|
|
||||||
|
set -e -x
|
||||||
|
if ! [ -w $XDG_RUNTIME_DIR ]; then
|
||||||
|
echo "XDG_RUNTIME_DIR needs to be set and writable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! [ -w $HOME ]; then
|
||||||
|
echo "HOME needs to be set and writable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rootlesskit=""
|
||||||
|
for f in docker-rootlesskit rootlesskit; do
|
||||||
|
if which $f >/dev/null 2>&1; then
|
||||||
|
rootlesskit=$f
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -z $rootlesskit ]; then
|
||||||
|
echo "rootlesskit needs to be installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
net=""
|
||||||
|
mtu=""
|
||||||
|
if which slirp4netns >/dev/null 2>&1; then
|
||||||
|
if slirp4netns --help | grep -- --disable-host-loopback; then
|
||||||
|
net=slirp4netns
|
||||||
|
mtu=65520
|
||||||
|
else
|
||||||
|
echo "slirp4netns does not support --disable-host-loopback. Falling back to VPNKit."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -z $net ]; then
|
||||||
|
if which vpnkit >/dev/null 2>&1; then
|
||||||
|
net=vpnkit
|
||||||
|
mtu=1500
|
||||||
|
else
|
||||||
|
echo "Either slirp4netns (v0.3+) or vpnkit needs to be installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z $_DOCKERD_ROOTLESS_CHILD ]; then
|
||||||
|
_DOCKERD_ROOTLESS_CHILD=1
|
||||||
|
export _DOCKERD_ROOTLESS_CHILD
|
||||||
|
# Re-exec the script via RootlessKit, so as to create unprivileged {user,mount,network} namespaces.
|
||||||
|
#
|
||||||
|
# --copy-up allows removing/creating files in the directories by creating tmpfs and symlinks
|
||||||
|
# * /etc: copy-up is required so as to prevent `/etc/resolv.conf` in the
|
||||||
|
# namespace from being unexpectedly unmounted when `/etc/resolv.conf` is recreated on the host
|
||||||
|
# (by either systemd-networkd or NetworkManager)
|
||||||
|
# * /run: copy-up is required so that we can create /run/docker (hardcoded for plugins) in our namespace
|
||||||
|
$rootlesskit \
|
||||||
|
--net=$net --mtu=$mtu --disable-host-loopback \
|
||||||
|
--copy-up=/etc --copy-up=/run \
|
||||||
|
$DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS \
|
||||||
|
$0 $@
|
||||||
|
else
|
||||||
|
[ $_DOCKERD_ROOTLESS_CHILD = 1 ]
|
||||||
|
# remove the symlinks for the existing files in the parent namespace if any,
|
||||||
|
# so that we can create our own files in our mount namespace.
|
||||||
|
rm -f /run/docker /run/xtables.lock
|
||||||
|
dockerd $@
|
||||||
|
fi
|
|
@ -39,6 +39,7 @@ type Config struct {
|
||||||
IpcMode string `json:"default-ipc-mode,omitempty"`
|
IpcMode string `json:"default-ipc-mode,omitempty"`
|
||||||
// ResolvConf is the path to the configuration of the host resolver
|
// ResolvConf is the path to the configuration of the host resolver
|
||||||
ResolvConf string `json:"resolv-conf,omitempty"`
|
ResolvConf string `json:"resolv-conf,omitempty"`
|
||||||
|
Rootless bool `json:"rootless,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BridgeConfig stores all the bridge driver specific
|
// BridgeConfig stores all the bridge driver specific
|
||||||
|
@ -87,3 +88,8 @@ func verifyDefaultIpcMode(mode string) error {
|
||||||
func (conf *Config) ValidatePlatformConfig() error {
|
func (conf *Config) ValidatePlatformConfig() error {
|
||||||
return verifyDefaultIpcMode(conf.IpcMode)
|
return verifyDefaultIpcMode(conf.IpcMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsRootless returns conf.Rootless
|
||||||
|
func (conf *Config) IsRootless() bool {
|
||||||
|
return conf.Rootless
|
||||||
|
}
|
||||||
|
|
|
@ -55,3 +55,8 @@ func (conf *Config) IsSwarmCompatible() error {
|
||||||
func (conf *Config) ValidatePlatformConfig() error {
|
func (conf *Config) ValidatePlatformConfig() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsRootless returns conf.Rootless on Unix but false on Windows
|
||||||
|
func (conf *Config) IsRootless() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -800,6 +800,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
|
||||||
logrus.Warnf("Failed to configure golang's threads limit: %v", err)
|
logrus.Warnf("Failed to configure golang's threads limit: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensureDefaultAppArmorProfile does nothing if apparmor is disabled
|
||||||
if err := ensureDefaultAppArmorProfile(); err != nil {
|
if err := ensureDefaultAppArmorProfile(); err != nil {
|
||||||
logrus.Errorf(err.Error())
|
logrus.Errorf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -745,9 +745,6 @@ func verifyDaemonSettings(conf *config.Config) error {
|
||||||
|
|
||||||
// checkSystem validates platform-specific requirements
|
// checkSystem validates platform-specific requirements
|
||||||
func checkSystem() error {
|
func checkSystem() error {
|
||||||
if os.Geteuid() != 0 {
|
|
||||||
return fmt.Errorf("The Docker daemon needs to be run as root")
|
|
||||||
}
|
|
||||||
return checkKernel()
|
return checkKernel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -175,6 +175,9 @@ func (daemon *Daemon) fillSecurityOptions(v *types.Info, sysInfo *sysinfo.SysInf
|
||||||
if rootIDs := daemon.idMapping.RootPair(); rootIDs.UID != 0 || rootIDs.GID != 0 {
|
if rootIDs := daemon.idMapping.RootPair(); rootIDs.UID != 0 || rootIDs.GID != 0 {
|
||||||
securityOptions = append(securityOptions, "name=userns")
|
securityOptions = append(securityOptions, "name=userns")
|
||||||
}
|
}
|
||||||
|
if daemon.configStoreRootless() {
|
||||||
|
securityOptions = append(securityOptions, "name=rootless")
|
||||||
|
}
|
||||||
v.SecurityOptions = securityOptions
|
v.SecurityOptions = securityOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -245,3 +245,7 @@ func parseRuncVersion(v string) (version string, commit string, err error) {
|
||||||
}
|
}
|
||||||
return version, commit, err
|
return version, commit, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (daemon *Daemon) configStoreRootless() bool {
|
||||||
|
return daemon.configStore.Rootless
|
||||||
|
}
|
||||||
|
|
|
@ -13,3 +13,7 @@ func (daemon *Daemon) fillPlatformVersion(v *types.Version) {}
|
||||||
|
|
||||||
func fillDriverWarnings(v *types.Info) {
|
func fillDriverWarnings(v *types.Info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (daemon *Daemon) configStoreRootless() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/coreos/go-systemd/activation"
|
"github.com/coreos/go-systemd/activation"
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -45,6 +46,10 @@ func Init(proto, addr, socketGroup string, tlsConfig *tls.Config) ([]net.Listene
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't create unix socket %s: %v", addr, err)
|
return nil, fmt.Errorf("can't create unix socket %s: %v", addr, err)
|
||||||
}
|
}
|
||||||
|
if _, err := homedir.StickRuntimeDirContents([]string{addr}); err != nil {
|
||||||
|
// StickRuntimeDirContents returns nil error if XDG_RUNTIME_DIR is just unset
|
||||||
|
logrus.WithError(err).Warnf("cannot set sticky bit on socket %s under XDG_RUNTIME_DIR", addr)
|
||||||
|
}
|
||||||
ls = append(ls, l)
|
ls = append(ls, l)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid protocol format: %q", proto)
|
return nil, fmt.Errorf("invalid protocol format: %q", proto)
|
||||||
|
|
|
@ -17,10 +17,12 @@ import (
|
||||||
"github.com/docker/docker/oci/caps"
|
"github.com/docker/docker/oci/caps"
|
||||||
"github.com/docker/docker/pkg/idtools"
|
"github.com/docker/docker/pkg/idtools"
|
||||||
"github.com/docker/docker/pkg/mount"
|
"github.com/docker/docker/pkg/mount"
|
||||||
|
"github.com/docker/docker/rootless/specconv"
|
||||||
volumemounts "github.com/docker/docker/volume/mounts"
|
volumemounts "github.com/docker/docker/volume/mounts"
|
||||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||||
"github.com/opencontainers/runc/libcontainer/devices"
|
"github.com/opencontainers/runc/libcontainer/devices"
|
||||||
|
rsystem "github.com/opencontainers/runc/libcontainer/system"
|
||||||
"github.com/opencontainers/runc/libcontainer/user"
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -89,7 +91,7 @@ func setDevices(s *specs.Spec, c *container.Container) error {
|
||||||
// Build lists of devices allowed and created within the container.
|
// Build lists of devices allowed and created within the container.
|
||||||
var devs []specs.LinuxDevice
|
var devs []specs.LinuxDevice
|
||||||
devPermissions := s.Linux.Resources.Devices
|
devPermissions := s.Linux.Resources.Devices
|
||||||
if c.HostConfig.Privileged {
|
if c.HostConfig.Privileged && !rsystem.RunningInUserNS() {
|
||||||
hostDevices, err := devices.HostDevices()
|
hostDevices, err := devices.HostDevices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -867,6 +869,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e
|
||||||
s.Linux.ReadonlyPaths = c.HostConfig.ReadonlyPaths
|
s.Linux.ReadonlyPaths = c.HostConfig.ReadonlyPaths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if daemon.configStore.Rootless {
|
||||||
|
if err := specconv.ToRootless(&s); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
92
docs/rootless.md
Normal file
92
docs/rootless.md
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
# Rootless mode (Experimental)
|
||||||
|
|
||||||
|
The rootless mode allows running `dockerd` as an unprivileged user, using `user_namespaces(7)`, `mount_namespaces(7)`, `network_namespaces(7)`.
|
||||||
|
|
||||||
|
No SETUID/SETCAP binary is required except `newuidmap` and `newgidmap`.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
* `newuidmap` and `newgidmap` need to be installed on the host. These commands are provided by the `uidmap` package on most distros.
|
||||||
|
|
||||||
|
* `/etc/subuid` and `/etc/subgid` should contain >= 65536 sub-IDs. e.g. `penguin:231072:65536`.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ id -u
|
||||||
|
1001
|
||||||
|
$ whoami
|
||||||
|
penguin
|
||||||
|
$ grep ^$(whoami): /etc/subuid
|
||||||
|
penguin:231072:65536
|
||||||
|
$ grep ^$(whoami): /etc/subgid
|
||||||
|
penguin:231072:65536
|
||||||
|
```
|
||||||
|
|
||||||
|
* Either [slirp4netns](https://github.com/rootless-containers/slirp4netns) (v0.3+) or [VPNKit](https://github.com/moby/vpnkit) needs to be installed. slirp4netns is preferred for the best performance.
|
||||||
|
|
||||||
|
### Distribution-specific hint
|
||||||
|
|
||||||
|
#### Debian (excluding Ubuntu)
|
||||||
|
* `sudo sh -c "echo 1 > /proc/sys/kernel/unprivileged_userns_clone"` is required
|
||||||
|
|
||||||
|
#### Arch Linux
|
||||||
|
* `sudo sh -c "echo 1 > /proc/sys/kernel/unprivileged_userns_clone"` is required
|
||||||
|
|
||||||
|
#### openSUSE
|
||||||
|
* `sudo modprobe ip_tables iptable_mangle iptable_nat iptable_filter` is required. (This is likely to be required on other distros as well)
|
||||||
|
|
||||||
|
#### RHEL/CentOS 7
|
||||||
|
* `sudo sh -c "echo 28633 > /proc/sys/user/max_user_namespaces"` is required
|
||||||
|
* [COPR package `vbatts/shadow-utils-newxidmap`](https://copr.fedorainfracloud.org/coprs/vbatts/shadow-utils-newxidmap/) needs to be installed
|
||||||
|
|
||||||
|
## Restrictions
|
||||||
|
|
||||||
|
* Only `vfs` graphdriver is supported. However, on [Ubuntu](http://kernel.ubuntu.com/git/ubuntu/ubuntu-artful.git/commit/fs/overlayfs?h=Ubuntu-4.13.0-25.29&id=0a414bdc3d01f3b61ed86cfe3ce8b63a9240eba7) and a few distros, `overlay2` and `overlay` are also supported.
|
||||||
|
* Following features are not supported:
|
||||||
|
* Cgroups (including `docker top`, which depends on the cgroups device controller)
|
||||||
|
* Apparmor
|
||||||
|
* Checkpoint
|
||||||
|
* Overlay network
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Daemon
|
||||||
|
|
||||||
|
You need to run `dockerd-rootless.sh` instead of `dockerd`.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ dockerd-rootless.sh --experimental"
|
||||||
|
```
|
||||||
|
As Rootless mode is experimental per se, currently you always need to run `dockerd-rootless.sh` with `--experimental`.
|
||||||
|
|
||||||
|
Remarks:
|
||||||
|
* The socket path is set to `$XDG_RUNTIME_DIR/docker.sock` by default. `$XDG_RUNTIME_DIR` is typically set to `/run/user/$UID`.
|
||||||
|
* The data dir is set to `~/.local/share/docker` by default.
|
||||||
|
* The exec dir is set to `$XDG_RUNTIME_DIR/docker` by default.
|
||||||
|
* The daemon config dir is set to `~/.config/docker` (not `~/.docker`, which is used by the client) by default.
|
||||||
|
* The `dockerd-rootless.sh` script executes `dockerd` in its own user, mount, and network namespaces. You can enter the namespaces by running `nsenter -U --preserve-credentials -n -m -t $(cat $XDG_RUNTIME_DIR/docker.pid)`.
|
||||||
|
|
||||||
|
### Client
|
||||||
|
|
||||||
|
You can just use the upstream Docker client but you need to set the socket path explicitly.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker -H unix://$XDG_RUNTIME_DIR/docker.sock run -d nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exposing ports
|
||||||
|
|
||||||
|
In addition to exposing container ports to the `dockerd` network namespace, you also need to expose the ports in the `dockerd` network namespace to the host network namespace.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker -H unix://$XDG_RUNTIME_DIR/docker.sock run -d -p 80:80 nginx
|
||||||
|
$ socat -t -- TCP-LISTEN:8080,reuseaddr,fork EXEC:"nsenter -U -n -t $(cat $XDG_RUNTIME_DIR/docker.pid) socat -t -- STDIN TCP4\:127.0.0.1\:80"
|
||||||
|
```
|
||||||
|
|
||||||
|
In future, `dockerd` will be able to expose the ports automatically.
|
||||||
|
|
||||||
|
### Routing ping packets
|
||||||
|
|
||||||
|
To route ping packets, you need to set up `net.ipv4.ping_group_range` properly as the root.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ sudo sh -c "echo 0 2147483647 > /proc/sys/net/ipv4/ping_group_range"
|
||||||
|
```
|
34
hack/dockerfile/install/rootlesskit.installer
Executable file
34
hack/dockerfile/install/rootlesskit.installer
Executable file
|
@ -0,0 +1,34 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# v0.3.0-alpha.0
|
||||||
|
ROOTLESSKIT_COMMIT=3c4582e950e3a67795c2832179c125b258b78124
|
||||||
|
|
||||||
|
install_rootlesskit() {
|
||||||
|
case "$1" in
|
||||||
|
"dynamic")
|
||||||
|
install_rootlesskit_dynamic
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
"")
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
_install_rootlesskit
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo 'Usage: $0 [dynamic]'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
install_rootlesskit_dynamic() {
|
||||||
|
export ROOTLESSKIT_LDFLAGS="-linkmode=external" install_rootlesskit
|
||||||
|
export BUILD_MODE="-buildmode=pie"
|
||||||
|
_install_rootlesskit
|
||||||
|
}
|
||||||
|
|
||||||
|
_install_rootlesskit() {
|
||||||
|
echo "Install rootlesskit version $ROOTLESSKIT_COMMIT"
|
||||||
|
git clone https://github.com/rootless-containers/rootlesskit.git "$GOPATH/src/github.com/rootless-containers/rootlesskit"
|
||||||
|
cd "$GOPATH/src/github.com/rootless-containers/rootlesskit"
|
||||||
|
git checkout -q "$ROOTLESSKIT_COMMIT"
|
||||||
|
go build $BUILD_MODE -ldflags="$ROOTLESSKIT_LDFLAGS" -o "${PREFIX}/rootlesskit" github.com/rootless-containers/rootlesskit/cmd/rootlesskit
|
||||||
|
}
|
|
@ -7,3 +7,5 @@ DOCKER_CONTAINERD_CTR_BINARY_NAME='ctr'
|
||||||
DOCKER_CONTAINERD_SHIM_BINARY_NAME='containerd-shim'
|
DOCKER_CONTAINERD_SHIM_BINARY_NAME='containerd-shim'
|
||||||
DOCKER_PROXY_BINARY_NAME='docker-proxy'
|
DOCKER_PROXY_BINARY_NAME='docker-proxy'
|
||||||
DOCKER_INIT_BINARY_NAME='docker-init'
|
DOCKER_INIT_BINARY_NAME='docker-init'
|
||||||
|
DOCKER_ROOTLESSKIT_BINARY_NAME='rootlesskit'
|
||||||
|
DOCKER_DAEMON_ROOTLESS_SH_BINARY_NAME='dockerd-rootless.sh'
|
||||||
|
|
|
@ -14,7 +14,7 @@ copy_binaries() {
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
echo "Copying nested executables into $dir"
|
echo "Copying nested executables into $dir"
|
||||||
for file in containerd containerd-shim ctr runc docker-init docker-proxy; do
|
for file in containerd containerd-shim ctr runc docker-init docker-proxy rootlesskit dockerd-rootless.sh; do
|
||||||
cp -f `which "$file"` "$dir/"
|
cp -f `which "$file"` "$dir/"
|
||||||
if [ "$hash" == "hash" ]; then
|
if [ "$hash" == "hash" ]; then
|
||||||
hash_files "$dir/$file"
|
hash_files "$dir/$file"
|
||||||
|
|
|
@ -26,4 +26,6 @@ install_binary() {
|
||||||
install_binary "${DEST}/${DOCKER_CONTAINERD_SHIM_BINARY_NAME}"
|
install_binary "${DEST}/${DOCKER_CONTAINERD_SHIM_BINARY_NAME}"
|
||||||
install_binary "${DEST}/${DOCKER_PROXY_BINARY_NAME}"
|
install_binary "${DEST}/${DOCKER_PROXY_BINARY_NAME}"
|
||||||
install_binary "${DEST}/${DOCKER_INIT_BINARY_NAME}"
|
install_binary "${DEST}/${DOCKER_INIT_BINARY_NAME}"
|
||||||
|
install_binary "${DEST}/${DOCKER_ROOTLESSKIT_BINARY_NAME}"
|
||||||
|
install_binary "${DEST}/${DOCKER_DAEMON_ROOTLESS_SH_BINARY_NAME}"
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,8 +4,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -41,12 +44,20 @@ func ValidateHost(val string) (string, error) {
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseHost and set defaults for a Daemon host string
|
// ParseHost and set defaults for a Daemon host string.
|
||||||
func ParseHost(defaultToTLS bool, val string) (string, error) {
|
// defaultToTLS is preferred over defaultToUnixRootless.
|
||||||
|
func ParseHost(defaultToTLS, defaultToUnixRootless bool, val string) (string, error) {
|
||||||
host := strings.TrimSpace(val)
|
host := strings.TrimSpace(val)
|
||||||
if host == "" {
|
if host == "" {
|
||||||
if defaultToTLS {
|
if defaultToTLS {
|
||||||
host = DefaultTLSHost
|
host = DefaultTLSHost
|
||||||
|
} else if defaultToUnixRootless {
|
||||||
|
runtimeDir, err := homedir.GetRuntimeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
socket := filepath.Join(runtimeDir, "docker.sock")
|
||||||
|
host = "unix://" + socket
|
||||||
} else {
|
} else {
|
||||||
host = DefaultHost
|
host = DefaultHost
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,13 +38,13 @@ func TestParseHost(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, value := range invalid {
|
for _, value := range invalid {
|
||||||
if _, err := ParseHost(false, value); err == nil {
|
if _, err := ParseHost(false, false, value); err == nil {
|
||||||
t.Errorf("Expected an error for %v, got [nil]", value)
|
t.Errorf("Expected an error for %v, got [nil]", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for value, expected := range valid {
|
for value, expected := range valid {
|
||||||
if actual, err := ParseHost(false, value); err != nil || actual != expected {
|
if actual, err := ParseHost(false, false, value); err != nil || actual != expected {
|
||||||
t.Errorf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err)
|
t.Errorf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package homedir // import "github.com/docker/docker/pkg/homedir"
|
package homedir // import "github.com/docker/docker/pkg/homedir"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/idtools"
|
"github.com/docker/docker/pkg/idtools"
|
||||||
)
|
)
|
||||||
|
@ -19,3 +22,88 @@ func GetStatic() (string, error) {
|
||||||
}
|
}
|
||||||
return usr.Home, nil
|
return usr.Home, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRuntimeDir returns XDG_RUNTIME_DIR.
|
||||||
|
// XDG_RUNTIME_DIR is typically configured via pam_systemd.
|
||||||
|
// GetRuntimeDir returns non-nil error if XDG_RUNTIME_DIR is not set.
|
||||||
|
//
|
||||||
|
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||||
|
func GetRuntimeDir() (string, error) {
|
||||||
|
if xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR"); xdgRuntimeDir != "" {
|
||||||
|
return xdgRuntimeDir, nil
|
||||||
|
}
|
||||||
|
return "", errors.New("could not get XDG_RUNTIME_DIR")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StickRuntimeDirContents sets the sticky bit on files that are under
|
||||||
|
// XDG_RUNTIME_DIR, so that the files won't be periodically removed by the system.
|
||||||
|
//
|
||||||
|
// StickyRuntimeDir returns slice of sticked files.
|
||||||
|
// StickyRuntimeDir returns nil error if XDG_RUNTIME_DIR is not set.
|
||||||
|
//
|
||||||
|
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||||
|
func StickRuntimeDirContents(files []string) ([]string, error) {
|
||||||
|
runtimeDir, err := GetRuntimeDir()
|
||||||
|
if err != nil {
|
||||||
|
// ignore error if runtimeDir is empty
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
runtimeDir, err = filepath.Abs(runtimeDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var sticked []string
|
||||||
|
for _, f := range files {
|
||||||
|
f, err = filepath.Abs(f)
|
||||||
|
if err != nil {
|
||||||
|
return sticked, err
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(f, runtimeDir+"/") {
|
||||||
|
if err = stick(f); err != nil {
|
||||||
|
return sticked, err
|
||||||
|
}
|
||||||
|
sticked = append(sticked, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sticked, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stick(f string) error {
|
||||||
|
st, err := os.Stat(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m := st.Mode()
|
||||||
|
m |= os.ModeSticky
|
||||||
|
return os.Chmod(f, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDataHome returns XDG_DATA_HOME.
|
||||||
|
// GetDataHome returns $HOME/.local/share and nil error if XDG_DATA_HOME is not set.
|
||||||
|
//
|
||||||
|
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||||
|
func GetDataHome() (string, error) {
|
||||||
|
if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" {
|
||||||
|
return xdgDataHome, nil
|
||||||
|
}
|
||||||
|
home := os.Getenv("HOME")
|
||||||
|
if home == "" {
|
||||||
|
return "", errors.New("could not get either XDG_DATA_HOME or HOME")
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".local", "share"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigHome returns XDG_CONFIG_HOME.
|
||||||
|
// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set.
|
||||||
|
//
|
||||||
|
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||||
|
func GetConfigHome() (string, error) {
|
||||||
|
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
|
||||||
|
return xdgConfigHome, nil
|
||||||
|
}
|
||||||
|
home := os.Getenv("HOME")
|
||||||
|
if home == "" {
|
||||||
|
return "", errors.New("could not get either XDG_CONFIG_HOME or HOME")
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".config"), nil
|
||||||
|
}
|
||||||
|
|
|
@ -11,3 +11,23 @@ import (
|
||||||
func GetStatic() (string, error) {
|
func GetStatic() (string, error) {
|
||||||
return "", errors.New("homedir.GetStatic() is not supported on this system")
|
return "", errors.New("homedir.GetStatic() is not supported on this system")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRuntimeDir is unsupported on non-linux system.
|
||||||
|
func GetRuntimeDir() (string, error) {
|
||||||
|
return "", errors.New("homedir.GetRuntimeDir() is not supported on this system")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StickRuntimeDirContents is unsupported on non-linux system.
|
||||||
|
func StickRuntimeDirContents(files []string) ([]string, error) {
|
||||||
|
return nil, errors.New("homedir.StickRuntimeDirContents() is not supported on this system")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDataHome is unsupported on non-linux system.
|
||||||
|
func GetDataHome() (string, error) {
|
||||||
|
return "", errors.New("homedir.GetDataHome() is not supported on this system")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigHome is unsupported on non-linux system.
|
||||||
|
func GetConfigHome() (string, error) {
|
||||||
|
return "", errors.New("homedir.GetConfigHome() is not supported on this system")
|
||||||
|
}
|
||||||
|
|
|
@ -51,7 +51,9 @@ func New(quiet bool) *SysInfo {
|
||||||
|
|
||||||
// Check if AppArmor is supported.
|
// Check if AppArmor is supported.
|
||||||
if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) {
|
if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) {
|
||||||
sysInfo.AppArmor = true
|
if _, err := ioutil.ReadFile("/sys/kernel/security/apparmor/profiles"); err == nil {
|
||||||
|
sysInfo.AppArmor = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if Seccomp is supported, via CONFIG_SECCOMP.
|
// Check if Seccomp is supported, via CONFIG_SECCOMP.
|
||||||
|
|
26
rootless/rootless.go
Normal file
26
rootless/rootless.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package rootless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
runningWithNonRootUsername bool
|
||||||
|
runningWithNonRootUsernameOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunningWithNonRootUsername returns true if we $USER is set to a non-root value,
|
||||||
|
// regardless to the UID/EUID value.
|
||||||
|
//
|
||||||
|
// The value of this variable is mostly used for configuring default paths.
|
||||||
|
// If the value is true, $HOME and $XDG_RUNTIME_DIR should be honored for setting up the default paths.
|
||||||
|
// If false (not only EUID==0 but also $USER==root), $HOME and $XDG_RUNTIME_DIR should be ignored
|
||||||
|
// even if we are in a user namespace.
|
||||||
|
func RunningWithNonRootUsername() bool {
|
||||||
|
runningWithNonRootUsernameOnce.Do(func() {
|
||||||
|
u := os.Getenv("USER")
|
||||||
|
runningWithNonRootUsername = u != "" && u != "root"
|
||||||
|
})
|
||||||
|
return runningWithNonRootUsername
|
||||||
|
}
|
38
rootless/specconv/specconv_linux.go
Normal file
38
rootless/specconv/specconv_linux.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package specconv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToRootless converts spec to be compatible with "rootless" runc.
|
||||||
|
// * Remove cgroups (will be supported in separate PR when delegation permission is configured)
|
||||||
|
// * Fix up OOMScoreAdj
|
||||||
|
func ToRootless(spec *specs.Spec) error {
|
||||||
|
return toRootless(spec, getCurrentOOMScoreAdj())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentOOMScoreAdj() int {
|
||||||
|
b, err := ioutil.ReadFile("/proc/self/oom_score_adj")
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i, err := strconv.Atoi(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func toRootless(spec *specs.Spec, currentOOMScoreAdj int) error {
|
||||||
|
// Remove cgroup settings.
|
||||||
|
spec.Linux.Resources = nil
|
||||||
|
spec.Linux.CgroupsPath = ""
|
||||||
|
|
||||||
|
if spec.Process.OOMScoreAdj != nil && *spec.Process.OOMScoreAdj < currentOOMScoreAdj {
|
||||||
|
*spec.Process.OOMScoreAdj = currentOOMScoreAdj
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue