Merge pull request #28274 from Microsoft/jjh/acl
Windows: create daemon root with ACL
This commit is contained in:
commit
2712bb26e3
9 changed files with 130 additions and 39 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ func setDefaultUmask() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getDaemonConfDir() string {
|
||||
func getDaemonConfDir(_ string) string {
|
||||
return "/etc/docker"
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ func setDefaultUmask() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getDaemonConfDir() string {
|
||||
func getDaemonConfDir(_ string) string {
|
||||
return "/etc/docker"
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue