Procházet zdrojové kódy

Merge pull request #28274 from Microsoft/jjh/acl

Windows: create daemon root with ACL
Victor Vieux před 8 roky
rodič
revize
2712bb26e3

+ 23 - 9
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
 

+ 1 - 1
cmd/dockerd/daemon_solaris.go

@@ -38,7 +38,7 @@ func setDefaultUmask() error {
 	return nil
 }
 
-func getDaemonConfDir() string {
+func getDaemonConfDir(_ string) string {
 	return "/etc/docker"
 }
 

+ 1 - 1
cmd/dockerd/daemon_unix.go

@@ -43,7 +43,7 @@ func setDefaultUmask() error {
 	return nil
 }
 
-func getDaemonConfDir() string {
+func getDaemonConfDir(_ string) string {
 	return "/etc/docker"
 }
 

+ 4 - 3
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

+ 8 - 3
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

+ 30 - 16
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
+}

+ 1 - 1
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

+ 6 - 0
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 {

+ 56 - 5
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