diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index a9fc2aebbf..866c2ec3e2 100644 --- a/cmd/dockerd/daemon.go +++ b/cmd/dockerd/daemon.go @@ -6,6 +6,7 @@ import ( "io" "os" "path/filepath" + "runtime" "strings" "time" @@ -67,10 +68,15 @@ func NewDaemonCli() *DaemonCli { return &DaemonCli{} } -func migrateKey() (err error) { +func migrateKey(config *daemon.Config) (err error) { + // No migration necessary on Windows + if runtime.GOOS == "windows" { + return nil + } + // Migrate trust key if exists at ~/.docker/key.json and owned by current user oldPath := filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) - newPath := filepath.Join(getDaemonConfDir(), cliflags.DefaultTrustKeyFile) + newPath := filepath.Join(getDaemonConfDir(config.Root), cliflags.DefaultTrustKeyFile) if _, statErr := os.Stat(newPath); os.IsNotExist(statErr) && currentUserIsOwner(oldPath) { defer func() { // Ensure old path is removed if no error occurred @@ -82,7 +88,7 @@ func migrateKey() (err error) { } }() - if err := system.MkdirAll(getDaemonConfDir(), os.FileMode(0644)); err != nil { + if err := system.MkdirAll(getDaemonConfDir(config.Root), os.FileMode(0644)); err != nil { return fmt.Errorf("Unable to create daemon configuration directory: %s", err) } @@ -117,17 +123,18 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) { opts.common.SetDefaultOptions(opts.flags) - if opts.common.TrustKey == "" { - opts.common.TrustKey = filepath.Join( - getDaemonConfDir(), - cliflags.DefaultTrustKeyFile) - } if cli.Config, err = loadDaemonCliConfig(opts); err != nil { return err } cli.configFile = &opts.configFile cli.flags = opts.flags + if opts.common.TrustKey == "" { + opts.common.TrustKey = filepath.Join( + getDaemonConfDir(cli.Config.Root), + cliflags.DefaultTrustKeyFile) + } + if cli.Config.Debug { utils.EnableDebug() } @@ -151,6 +158,12 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) { } } + // Create the daemon root before we create ANY other files (PID, or migrate keys) + // to ensure the appropriate ACL is set (particularly relevant on Windows) + if err := daemon.CreateDaemonRoot(cli.Config); err != nil { + return err + } + if cli.Pidfile != "" { pf, err := pidfile.New(cli.Pidfile) if err != nil { @@ -230,9 +243,10 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) { api.Accept(addr, ls...) } - if err := migrateKey(); err != nil { + if err := migrateKey(cli.Config); err != nil { return err } + // FIXME: why is this down here instead of with the other TrustKey logic above? cli.TrustKeyPath = opts.common.TrustKey diff --git a/cmd/dockerd/daemon_solaris.go b/cmd/dockerd/daemon_solaris.go index 74dac5dd29..974ba16345 100644 --- a/cmd/dockerd/daemon_solaris.go +++ b/cmd/dockerd/daemon_solaris.go @@ -38,7 +38,7 @@ func setDefaultUmask() error { return nil } -func getDaemonConfDir() string { +func getDaemonConfDir(_ string) string { return "/etc/docker" } diff --git a/cmd/dockerd/daemon_unix.go b/cmd/dockerd/daemon_unix.go index 83156dae16..bdce98bd26 100644 --- a/cmd/dockerd/daemon_unix.go +++ b/cmd/dockerd/daemon_unix.go @@ -43,7 +43,7 @@ func setDefaultUmask() error { return nil } -func getDaemonConfDir() string { +func getDaemonConfDir(_ string) string { return "/etc/docker" } diff --git a/cmd/dockerd/daemon_windows.go b/cmd/dockerd/daemon_windows.go index 39d5a87889..4cccd32688 100644 --- a/cmd/dockerd/daemon_windows.go +++ b/cmd/dockerd/daemon_windows.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "os" + "path/filepath" "syscall" "github.com/Sirupsen/logrus" @@ -11,7 +12,7 @@ import ( "github.com/docker/docker/pkg/system" ) -var defaultDaemonConfigFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker" + string(os.PathSeparator) + "config" + string(os.PathSeparator) + "daemon.json" +var defaultDaemonConfigFile = "" // currentUserIsOwner checks whether the current user is the owner of the given // file. @@ -24,8 +25,8 @@ func setDefaultUmask() error { return nil } -func getDaemonConfDir() string { - return os.Getenv("PROGRAMDATA") + `\docker\config` +func getDaemonConfDir(root string) string { + return filepath.Join(root, `\config`) } // notifySystem sends a message to the host when the server is ready to be used diff --git a/cmd/dockerd/docker.go b/cmd/dockerd/docker.go index 5ce495c2e8..60742ae927 100644 --- a/cmd/dockerd/docker.go +++ b/cmd/dockerd/docker.go @@ -62,9 +62,14 @@ func runDaemon(opts daemonOptions) error { daemonCli := NewDaemonCli() - // On Windows, if there's no explicit pidfile set, set to under the daemon root - if runtime.GOOS == "windows" && opts.daemonConfig.Pidfile == "" { - opts.daemonConfig.Pidfile = filepath.Join(opts.daemonConfig.Root, "docker.pid") + // Windows specific settings as these are not defaulted. + if runtime.GOOS == "windows" { + if opts.daemonConfig.Pidfile == "" { + opts.daemonConfig.Pidfile = filepath.Join(opts.daemonConfig.Root, "docker.pid") + } + if opts.configFile == "" { + opts.configFile = filepath.Join(opts.daemonConfig.Root, `config\daemon.json`) + } } // On Windows, this may be launching as a service or with an option to diff --git a/daemon/daemon.go b/daemon/daemon.go index a118d88283..44b41be38b 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -492,21 +492,6 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot return nil, err } - // get the canonical path to the Docker root directory - var realRoot string - if _, err := os.Stat(config.Root); err != nil && os.IsNotExist(err) { - realRoot = config.Root - } else { - realRoot, err = fileutils.ReadSymlinkedDirectory(config.Root) - if err != nil { - return nil, fmt.Errorf("Unable to get the full path to root (%s): %s", config.Root, err) - } - } - - if err := setupDaemonRoot(config, realRoot, rootUID, rootGID); err != nil { - return nil, err - } - if err := setupDaemonProcess(config); err != nil { return nil, err } @@ -555,7 +540,7 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot } if runtime.GOOS == "windows" { - if err := idtools.MkdirAllAs(filepath.Join(config.Root, "credentialspecs"), 0700, rootUID, rootGID); err != nil && !os.IsExist(err) { + if err := system.MkdirAll(filepath.Join(config.Root, "credentialspecs"), 0); err != nil && !os.IsExist(err) { return nil, err } } @@ -1284,3 +1269,32 @@ func (daemon *Daemon) pluginShutdown() { manager.Shutdown() } } + +// CreateDaemonRoot creates the root for the daemon +func CreateDaemonRoot(config *Config) error { + // get the canonical path to the Docker root directory + var realRoot string + if _, err := os.Stat(config.Root); err != nil && os.IsNotExist(err) { + realRoot = config.Root + } else { + realRoot, err = fileutils.ReadSymlinkedDirectory(config.Root) + if err != nil { + return fmt.Errorf("Unable to get the full path to root (%s): %s", config.Root, err) + } + } + + uidMaps, gidMaps, err := setupRemappedRoot(config) + if err != nil { + return err + } + rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps) + if err != nil { + return err + } + + if err := setupDaemonRoot(config, realRoot, rootUID, rootGID); err != nil { + return err + } + + return nil +} diff --git a/daemon/daemon_windows.go b/daemon/daemon_windows.go index 8931f28176..2620fc4b9d 100644 --- a/daemon/daemon_windows.go +++ b/daemon/daemon_windows.go @@ -432,7 +432,7 @@ func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error { config.Root = rootDir // Create the root directory if it doesn't exists - if err := system.MkdirAll(config.Root, 0700); err != nil && !os.IsExist(err) { + if err := system.MkdirAllWithACL(config.Root, 0); err != nil && !os.IsExist(err) { return err } return nil diff --git a/pkg/system/filesys.go b/pkg/system/filesys.go index c14feb8496..82bcaa67f0 100644 --- a/pkg/system/filesys.go +++ b/pkg/system/filesys.go @@ -7,6 +7,12 @@ import ( "path/filepath" ) +// MkdirAllWithACL is a wrapper for MkdirAll that creates a directory +// ACL'd for Builtin Administrators and Local System. +func MkdirAllWithACL(path string, perm os.FileMode) error { + return MkdirAll(path, perm) +} + // MkdirAll creates a directory named path along with any necessary parents, // with permission specified by attribute perm for all dir created. func MkdirAll(path string, perm os.FileMode) error { diff --git a/pkg/system/filesys_windows.go b/pkg/system/filesys_windows.go index 16823d5517..97f1c7c842 100644 --- a/pkg/system/filesys_windows.go +++ b/pkg/system/filesys_windows.go @@ -8,15 +8,31 @@ import ( "regexp" "strings" "syscall" + "unsafe" + + winio "github.com/Microsoft/go-winio" ) +// MkdirAllWithACL is a wrapper for MkdirAll that creates a directory +// ACL'd for Builtin Administrators and Local System. +func MkdirAllWithACL(path string, perm os.FileMode) error { + return mkdirall(path, true) +} + // MkdirAll implementation that is volume path aware for Windows. -func MkdirAll(path string, perm os.FileMode) error { +func MkdirAll(path string, _ os.FileMode) error { + return mkdirall(path, false) +} + +// mkdirall is a custom version of os.MkdirAll modified for use on Windows +// so that it is both volume path aware, and can create a directory with +// a DACL. +func mkdirall(path string, adminAndLocalSystem bool) error { if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) { return nil } - // The rest of this method is copied from os.MkdirAll and should be kept + // The rest of this method is largely copied from os.MkdirAll and should be kept // as-is to ensure compatibility. // Fast path: if we can tell whether path is a directory or file, stop with success or error. @@ -45,14 +61,19 @@ func MkdirAll(path string, perm os.FileMode) error { if j > 1 { // Create parent - err = MkdirAll(path[0:j-1], perm) + err = mkdirall(path[0:j-1], false) if err != nil { return err } } - // Parent now exists; invoke Mkdir and use its result. - err = os.Mkdir(path, perm) + // Parent now exists; invoke os.Mkdir or mkdirWithACL and use its result. + if adminAndLocalSystem { + err = mkdirWithACL(path) + } else { + err = os.Mkdir(path, 0) + } + if err != nil { // Handle arguments like "foo/." by // double-checking that directory doesn't exist. @@ -65,6 +86,36 @@ func MkdirAll(path string, perm os.FileMode) error { return nil } +// mkdirWithACL creates a new directory. If there is an error, it will be of +// type *PathError. . +// +// This is a modified and combined version of os.Mkdir and syscall.Mkdir +// in golang to cater for creating a directory am ACL permitting full +// access, with inheritance, to any subfolder/file for Built-in Administrators +// and Local System. +func mkdirWithACL(name string) error { + sa := syscall.SecurityAttributes{Length: 0} + sddl := "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)" + sd, err := winio.SddlToSecurityDescriptor(sddl) + if err != nil { + return &os.PathError{"mkdir", name, err} + } + sa.Length = uint32(unsafe.Sizeof(sa)) + sa.InheritHandle = 1 + sa.SecurityDescriptor = uintptr(unsafe.Pointer(&sd[0])) + + namep, err := syscall.UTF16PtrFromString(name) + if err != nil { + return &os.PathError{"mkdir", name, err} + } + + e := syscall.CreateDirectory(namep, &sa) + if e != nil { + return &os.PathError{"mkdir", name, e} + } + return nil +} + // IsAbs is a platform-specific wrapper for filepath.IsAbs. On Windows, // golang filepath.IsAbs does not consider a path \windows\system32 as absolute // as it doesn't start with a drive-letter/colon combination. However, in