Skip to content

Commit 46ec4c1

Browse files
author
John Howard
committed
Windows: create daemon root with ACL
Signed-off-by: John Howard <[email protected]>
1 parent acd6e0d commit 46ec4c1

9 files changed

Lines changed: 130 additions & 39 deletions

File tree

cmd/dockerd/daemon.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"os"
88
"path/filepath"
9+
"runtime"
910
"strings"
1011
"time"
1112

@@ -67,10 +68,15 @@ func NewDaemonCli() *DaemonCli {
6768
return &DaemonCli{}
6869
}
6970

70-
func migrateKey() (err error) {
71+
func migrateKey(config *daemon.Config) (err error) {
72+
// No migration necessary on Windows
73+
if runtime.GOOS == "windows" {
74+
return nil
75+
}
76+
7177
// Migrate trust key if exists at ~/.docker/key.json and owned by current user
7278
oldPath := filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
73-
newPath := filepath.Join(getDaemonConfDir(), cliflags.DefaultTrustKeyFile)
79+
newPath := filepath.Join(getDaemonConfDir(config.Root), cliflags.DefaultTrustKeyFile)
7480
if _, statErr := os.Stat(newPath); os.IsNotExist(statErr) && currentUserIsOwner(oldPath) {
7581
defer func() {
7682
// Ensure old path is removed if no error occurred
@@ -82,7 +88,7 @@ func migrateKey() (err error) {
8288
}
8389
}()
8490

85-
if err := system.MkdirAll(getDaemonConfDir(), os.FileMode(0644)); err != nil {
91+
if err := system.MkdirAll(getDaemonConfDir(config.Root), os.FileMode(0644)); err != nil {
8692
return fmt.Errorf("Unable to create daemon configuration directory: %s", err)
8793
}
8894

@@ -117,17 +123,18 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
117123

118124
opts.common.SetDefaultOptions(opts.flags)
119125

120-
if opts.common.TrustKey == "" {
121-
opts.common.TrustKey = filepath.Join(
122-
getDaemonConfDir(),
123-
cliflags.DefaultTrustKeyFile)
124-
}
125126
if cli.Config, err = loadDaemonCliConfig(opts); err != nil {
126127
return err
127128
}
128129
cli.configFile = &opts.configFile
129130
cli.flags = opts.flags
130131

132+
if opts.common.TrustKey == "" {
133+
opts.common.TrustKey = filepath.Join(
134+
getDaemonConfDir(cli.Config.Root),
135+
cliflags.DefaultTrustKeyFile)
136+
}
137+
131138
if cli.Config.Debug {
132139
utils.EnableDebug()
133140
}
@@ -151,6 +158,12 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
151158
}
152159
}
153160

161+
// Create the daemon root before we create ANY other files (PID, or migrate keys)
162+
// to ensure the appropriate ACL is set (particularly relevant on Windows)
163+
if err := daemon.CreateDaemonRoot(cli.Config); err != nil {
164+
return err
165+
}
166+
154167
if cli.Pidfile != "" {
155168
pf, err := pidfile.New(cli.Pidfile)
156169
if err != nil {
@@ -230,9 +243,10 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
230243
api.Accept(addr, ls...)
231244
}
232245

233-
if err := migrateKey(); err != nil {
246+
if err := migrateKey(cli.Config); err != nil {
234247
return err
235248
}
249+
236250
// FIXME: why is this down here instead of with the other TrustKey logic above?
237251
cli.TrustKeyPath = opts.common.TrustKey
238252

cmd/dockerd/daemon_solaris.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func setDefaultUmask() error {
3838
return nil
3939
}
4040

41-
func getDaemonConfDir() string {
41+
func getDaemonConfDir(_ string) string {
4242
return "/etc/docker"
4343
}
4444

cmd/dockerd/daemon_unix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func setDefaultUmask() error {
4343
return nil
4444
}
4545

46-
func getDaemonConfDir() string {
46+
func getDaemonConfDir(_ string) string {
4747
return "/etc/docker"
4848
}
4949

cmd/dockerd/daemon_windows.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import (
44
"fmt"
55
"net"
66
"os"
7+
"path/filepath"
78
"syscall"
89

910
"github.com/Sirupsen/logrus"
1011
"github.com/docker/docker/libcontainerd"
1112
"github.com/docker/docker/pkg/system"
1213
)
1314

14-
var defaultDaemonConfigFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker" + string(os.PathSeparator) + "config" + string(os.PathSeparator) + "daemon.json"
15+
var defaultDaemonConfigFile = ""
1516

1617
// currentUserIsOwner checks whether the current user is the owner of the given
1718
// file.
@@ -24,8 +25,8 @@ func setDefaultUmask() error {
2425
return nil
2526
}
2627

27-
func getDaemonConfDir() string {
28-
return os.Getenv("PROGRAMDATA") + `\docker\config`
28+
func getDaemonConfDir(root string) string {
29+
return filepath.Join(root, `\config`)
2930
}
3031

3132
// notifySystem sends a message to the host when the server is ready to be used

cmd/dockerd/docker.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,14 @@ func runDaemon(opts daemonOptions) error {
6262

6363
daemonCli := NewDaemonCli()
6464

65-
// On Windows, if there's no explicit pidfile set, set to under the daemon root
66-
if runtime.GOOS == "windows" && opts.daemonConfig.Pidfile == "" {
67-
opts.daemonConfig.Pidfile = filepath.Join(opts.daemonConfig.Root, "docker.pid")
65+
// Windows specific settings as these are not defaulted.
66+
if runtime.GOOS == "windows" {
67+
if opts.daemonConfig.Pidfile == "" {
68+
opts.daemonConfig.Pidfile = filepath.Join(opts.daemonConfig.Root, "docker.pid")
69+
}
70+
if opts.configFile == "" {
71+
opts.configFile = filepath.Join(opts.daemonConfig.Root, `config\daemon.json`)
72+
}
6873
}
6974

7075
// On Windows, this may be launching as a service or with an option to

daemon/daemon.go

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -489,21 +489,6 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
489489
return nil, err
490490
}
491491

492-
// get the canonical path to the Docker root directory
493-
var realRoot string
494-
if _, err := os.Stat(config.Root); err != nil && os.IsNotExist(err) {
495-
realRoot = config.Root
496-
} else {
497-
realRoot, err = fileutils.ReadSymlinkedDirectory(config.Root)
498-
if err != nil {
499-
return nil, fmt.Errorf("Unable to get the full path to root (%s): %s", config.Root, err)
500-
}
501-
}
502-
503-
if err := setupDaemonRoot(config, realRoot, rootUID, rootGID); err != nil {
504-
return nil, err
505-
}
506-
507492
if err := setupDaemonProcess(config); err != nil {
508493
return nil, err
509494
}
@@ -552,7 +537,7 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
552537
}
553538

554539
if runtime.GOOS == "windows" {
555-
if err := idtools.MkdirAllAs(filepath.Join(config.Root, "credentialspecs"), 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
540+
if err := system.MkdirAll(filepath.Join(config.Root, "credentialspecs"), 0); err != nil && !os.IsExist(err) {
556541
return nil, err
557542
}
558543
}
@@ -1281,3 +1266,32 @@ func (daemon *Daemon) pluginShutdown() {
12811266
manager.Shutdown()
12821267
}
12831268
}
1269+
1270+
// CreateDaemonRoot creates the root for the daemon
1271+
func CreateDaemonRoot(config *Config) error {
1272+
// get the canonical path to the Docker root directory
1273+
var realRoot string
1274+
if _, err := os.Stat(config.Root); err != nil && os.IsNotExist(err) {
1275+
realRoot = config.Root
1276+
} else {
1277+
realRoot, err = fileutils.ReadSymlinkedDirectory(config.Root)
1278+
if err != nil {
1279+
return fmt.Errorf("Unable to get the full path to root (%s): %s", config.Root, err)
1280+
}
1281+
}
1282+
1283+
uidMaps, gidMaps, err := setupRemappedRoot(config)
1284+
if err != nil {
1285+
return err
1286+
}
1287+
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
1288+
if err != nil {
1289+
return err
1290+
}
1291+
1292+
if err := setupDaemonRoot(config, realRoot, rootUID, rootGID); err != nil {
1293+
return err
1294+
}
1295+
1296+
return nil
1297+
}

daemon/daemon_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error)
432432
func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error {
433433
config.Root = rootDir
434434
// Create the root directory if it doesn't exists
435-
if err := system.MkdirAll(config.Root, 0700); err != nil && !os.IsExist(err) {
435+
if err := system.MkdirAllWithACL(config.Root, 0); err != nil && !os.IsExist(err) {
436436
return err
437437
}
438438
return nil

pkg/system/filesys.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import (
77
"path/filepath"
88
)
99

10+
// MkdirAllWithACL is a wrapper for MkdirAll that creates a directory
11+
// ACL'd for Builtin Administrators and Local System.
12+
func MkdirAllWithACL(path string, perm os.FileMode) error {
13+
return MkdirAll(path, perm)
14+
}
15+
1016
// MkdirAll creates a directory named path along with any necessary parents,
1117
// with permission specified by attribute perm for all dir created.
1218
func MkdirAll(path string, perm os.FileMode) error {

pkg/system/filesys_windows.go

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,31 @@ import (
88
"regexp"
99
"strings"
1010
"syscall"
11+
"unsafe"
12+
13+
winio "github.com/Microsoft/go-winio"
1114
)
1215

16+
// MkdirAllWithACL is a wrapper for MkdirAll that creates a directory
17+
// ACL'd for Builtin Administrators and Local System.
18+
func MkdirAllWithACL(path string, perm os.FileMode) error {
19+
return mkdirall(path, true)
20+
}
21+
1322
// MkdirAll implementation that is volume path aware for Windows.
14-
func MkdirAll(path string, perm os.FileMode) error {
23+
func MkdirAll(path string, _ os.FileMode) error {
24+
return mkdirall(path, false)
25+
}
26+
27+
// mkdirall is a custom version of os.MkdirAll modified for use on Windows
28+
// so that it is both volume path aware, and can create a directory with
29+
// a DACL.
30+
func mkdirall(path string, adminAndLocalSystem bool) error {
1531
if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) {
1632
return nil
1733
}
1834

19-
// The rest of this method is copied from os.MkdirAll and should be kept
35+
// The rest of this method is largely copied from os.MkdirAll and should be kept
2036
// as-is to ensure compatibility.
2137

2238
// 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 {
4561

4662
if j > 1 {
4763
// Create parent
48-
err = MkdirAll(path[0:j-1], perm)
64+
err = mkdirall(path[0:j-1], false)
4965
if err != nil {
5066
return err
5167
}
5268
}
5369

54-
// Parent now exists; invoke Mkdir and use its result.
55-
err = os.Mkdir(path, perm)
70+
// Parent now exists; invoke os.Mkdir or mkdirWithACL and use its result.
71+
if adminAndLocalSystem {
72+
err = mkdirWithACL(path)
73+
} else {
74+
err = os.Mkdir(path, 0)
75+
}
76+
5677
if err != nil {
5778
// Handle arguments like "foo/." by
5879
// double-checking that directory doesn't exist.
@@ -65,6 +86,36 @@ func MkdirAll(path string, perm os.FileMode) error {
6586
return nil
6687
}
6788

89+
// mkdirWithACL creates a new directory. If there is an error, it will be of
90+
// type *PathError. .
91+
//
92+
// This is a modified and combined version of os.Mkdir and syscall.Mkdir
93+
// in golang to cater for creating a directory am ACL permitting full
94+
// access, with inheritance, to any subfolder/file for Built-in Administrators
95+
// and Local System.
96+
func mkdirWithACL(name string) error {
97+
sa := syscall.SecurityAttributes{Length: 0}
98+
sddl := "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
99+
sd, err := winio.SddlToSecurityDescriptor(sddl)
100+
if err != nil {
101+
return &os.PathError{"mkdir", name, err}
102+
}
103+
sa.Length = uint32(unsafe.Sizeof(sa))
104+
sa.InheritHandle = 1
105+
sa.SecurityDescriptor = uintptr(unsafe.Pointer(&sd[0]))
106+
107+
namep, err := syscall.UTF16PtrFromString(name)
108+
if err != nil {
109+
return &os.PathError{"mkdir", name, err}
110+
}
111+
112+
e := syscall.CreateDirectory(namep, &sa)
113+
if e != nil {
114+
return &os.PathError{"mkdir", name, e}
115+
}
116+
return nil
117+
}
118+
68119
// IsAbs is a platform-specific wrapper for filepath.IsAbs. On Windows,
69120
// golang filepath.IsAbs does not consider a path \windows\system32 as absolute
70121
// as it doesn't start with a drive-letter/colon combination. However, in

0 commit comments

Comments
 (0)