Windows: create daemon root with ACL

Signed-off-by: John Howard <jhoward@microsoft.com>
This commit is contained in:
John Howard 2016-11-04 12:42:21 -07:00
parent acd6e0d9b2
commit 46ec4c1ae2
9 changed files with 130 additions and 39 deletions

View File

@ -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

View File

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

View File

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

View File

@ -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

View File

@ -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

View File

@ -489,21 +489,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
}
@ -552,7 +537,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
}
}
@ -1281,3 +1266,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
}

View File

@ -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

View File

@ -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 {

View File

@ -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