diff --git a/docker/daemon.go b/docker/daemon.go index 508a75bd86..0923997375 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -3,6 +3,11 @@ package main import ( + "fmt" + "io" + "os" + "path/filepath" + log "github.com/Sirupsen/logrus" "github.com/docker/docker/builder" "github.com/docker/docker/builtins" @@ -14,6 +19,7 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/registry" + "github.com/docker/docker/utils" ) const CanDaemon = true @@ -28,6 +34,46 @@ func init() { registryCfg.InstallFlags() } +func migrateKey() (err error) { + // Migrate trust key if exists at ~/.docker/key.json and owned by current user + oldPath := filepath.Join(getHomeDir(), ".docker", defaultTrustKeyFile) + newPath := filepath.Join(getDaemonConfDir(), defaultTrustKeyFile) + if _, statErr := os.Stat(newPath); os.IsNotExist(statErr) && utils.IsFileOwner(oldPath) { + defer func() { + // Ensure old path is removed if no error occurred + if err == nil { + err = os.Remove(oldPath) + } else { + log.Warnf("Key migration failed, key file not removed at %s", oldPath) + } + }() + + if err := os.MkdirAll(getDaemonConfDir(), os.FileMode(0644)); err != nil { + return fmt.Errorf("Unable to create daemon configuration directory: %s", err) + } + + newFile, err := os.OpenFile(newPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("error creating key file %q: %s", newPath, err) + } + defer newFile.Close() + + oldFile, err := os.Open(oldPath) + if err != nil { + return fmt.Errorf("error opening key file %q: %s", oldPath, err) + } + defer oldFile.Close() + + if _, err := io.Copy(newFile, oldFile); err != nil { + return fmt.Errorf("error copying key: %s", err) + } + + log.Infof("Migrated key from %s to %s", oldPath, newPath) + } + + return nil +} + func mainDaemon() { if flag.NArg() != 0 { flag.Usage() @@ -36,6 +82,9 @@ func mainDaemon() { eng := engine.New() signal.Trap(eng.Shutdown) + if err := migrateKey(); err != nil { + log.Fatal(err) + } daemonCfg.TrustKeyPath = *flTrustKey // Load builtins diff --git a/docker/docker.go b/docker/docker.go index 92f5f14603..6410171fab 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -67,6 +67,8 @@ func main() { flHosts = append(flHosts, defaultHost) } + setDefaultConfFlag(flTrustKey, defaultTrustKeyFile) + if *flDaemon { mainDaemon() return diff --git a/docker/flags.go b/docker/flags.go index 4170fb2e5d..3b54612e89 100644 --- a/docker/flags.go +++ b/docker/flags.go @@ -28,6 +28,14 @@ func getHomeDir() string { return os.Getenv("HOME") } +func getDaemonConfDir() string { + // TODO: update for Windows daemon + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("USERPROFILE"), ".docker") + } + return "/etc/docker" +} + var ( flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode") @@ -47,10 +55,20 @@ var ( flHosts []string ) +func setDefaultConfFlag(flag *string, def string) { + if *flag == "" { + if *flDaemon { + *flag = filepath.Join(getDaemonConfDir(), def) + } else { + *flag = filepath.Join(getHomeDir(), ".docker", def) + } + } +} + func init() { - // placeholder for trust key flag - trustKeyDefault := filepath.Join(dockerCertPath, defaultTrustKeyFile) - flTrustKey = &trustKeyDefault + var placeholderTrustKey string + // TODO use flag flag.String([]string{"i", "-identity"}, "", "Path to libtrust key file") + flTrustKey = &placeholderTrustKey flCa = flag.String([]string{"-tlscacert"}, filepath.Join(dockerCertPath, defaultCaFile), "Trust only remotes providing a certificate signed by the CA given here") flCert = flag.String([]string{"-tlscert"}, filepath.Join(dockerCertPath, defaultCertFile), "Path to TLS certificate file") diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index b7db552b62..95188296d8 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -8,8 +8,11 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" "strings" "testing" + + "github.com/docker/libtrust" ) func TestDaemonRestartWithRunningContainersPorts(t *testing.T) { @@ -350,3 +353,53 @@ func TestDaemonVolumesBindsRefs(t *testing.T) { logDone("daemon - bind refs in data-containers survive daemon restart") } + +func TestDaemonKeyGeneration(t *testing.T) { + // TODO: skip or update for Windows daemon + os.Remove("/etc/docker/key.json") + d := NewDaemon(t) + if err := d.Start(); err != nil { + t.Fatalf("Could not start daemon: %v", err) + } + d.Stop() + + k, err := libtrust.LoadKeyFile("/etc/docker/key.json") + if err != nil { + t.Fatalf("Error opening key file") + } + kid := k.KeyID() + // Test Key ID is a valid fingerprint (e.g. QQXN:JY5W:TBXI:MK3X:GX6P:PD5D:F56N:NHCS:LVRZ:JA46:R24J:XEFF) + if len(kid) != 59 { + t.Fatalf("Bad key ID: %s", kid) + } + + logDone("daemon - key generation") +} + +func TestDaemonKeyMigration(t *testing.T) { + // TODO: skip or update for Windows daemon + os.Remove("/etc/docker/key.json") + k1, err := libtrust.GenerateECP256PrivateKey() + if err != nil { + t.Fatalf("Error generating private key: %s", err) + } + if err := libtrust.SaveKey(filepath.Join(os.Getenv("HOME"), ".docker", "key.json"), k1); err != nil { + t.Fatalf("Error saving private key: %s", err) + } + + d := NewDaemon(t) + if err := d.Start(); err != nil { + t.Fatalf("Could not start daemon: %v", err) + } + d.Stop() + + k2, err := libtrust.LoadKeyFile("/etc/docker/key.json") + if err != nil { + t.Fatalf("Error opening key file") + } + if k1.KeyID() != k2.KeyID() { + t.Fatalf("Key not migrated") + } + + logDone("daemon - key migration") +} diff --git a/utils/utils_daemon.go b/utils/utils_daemon.go index 098e227367..9989f05e31 100644 --- a/utils/utils_daemon.go +++ b/utils/utils_daemon.go @@ -37,3 +37,13 @@ func TreeSize(dir string) (size int64, err error) { }) return } + +// IsFileOwner checks whether the current user is the owner of the given file. +func IsFileOwner(f string) bool { + if fileInfo, err := os.Stat(f); err == nil && fileInfo != nil { + if stat, ok := fileInfo.Sys().(*syscall.Stat_t); ok && int(stat.Uid) == os.Getuid() { + return true + } + } + return false +}