Index: docker-1.10.0/daemon/start.go =================================================================== --- docker-1.10.0.orig/daemon/start.go +++ docker-1.10.0/daemon/start.go @@ -1,12 +1,17 @@ package daemon import ( + "fmt" + "os" + "path/filepath" "runtime" + "syscall" "github.com/Sirupsen/logrus" "github.com/docker/docker/container" derr "github.com/docker/docker/errors" "github.com/docker/docker/runconfig" + "github.com/docker/docker/vendor/src/github.com/opencontainers/runc/libcontainer/label" containertypes "github.com/docker/engine-api/types/container" ) @@ -134,6 +139,10 @@ func (daemon *Daemon) containerStart(con } } + if err := daemon.setupSecretFiles(container); err != nil { + return err + } + mounts, err := daemon.setupMounts(container) if err != nil { return err @@ -142,13 +151,96 @@ func (daemon *Daemon) containerStart(con mounts = append(mounts, container.TmpfsMounts()...) container.Command.Mounts = mounts + if err := daemon.waitForStart(container); err != nil { return err } + + // Now the container is running, unmount the secrets on the host + if err := daemon.UnmountSecrets(container, false); err != nil { + return err + } + container.HasBeenStartedBefore = true return nil } +// unmount secrets on the host. Performs a lazy unmount by default unless +// `force` is set to true. +// No unmount operation is invoked if the secrets mount point has already been +// unmounted. +func (daemon *Daemon) UnmountSecrets(container *container.Container, force bool) error { + secretsPath, err := daemon.secretsPath(container) + if err != nil { + return err + } + + logrus.WithFields(logrus.Fields{ + "container": container.ID, + "path": secretsPath, + "force": force, + }).Debug("SUSE:secrets -> unmounting container secrets") + + var stat_dot, stat_dot_dot syscall.Stat_t + if err := syscall.Stat(secretsPath, &stat_dot); err != nil { + return fmt.Errorf("Something went wrong while getting stats for dot: %v", err) + } + if err := syscall.Stat(filepath.Join(secretsPath, ".."), &stat_dot_dot); err != nil { + return fmt.Errorf("Something went wrong while getting stats for dot dot: %v", err) + } + + // Compare device IDs for //. and //.. + // If the device IDs are different then the secrets directory is actually + // mounted. Otherwise it has already been unmounted, hence there's nothing + // to do (calling unmount would return an error) + if stat_dot.Dev != stat_dot_dot.Dev { + // By default perform lazy unmount + flag := syscall.MNT_DETACH + if force { + flag = syscall.MNT_FORCE + } + if err := syscall.Unmount(secretsPath, flag); err != nil { + return err + } + } + + return nil +} + +func (daemon *Daemon) secretsPath(container *container.Container) (string, error) { + return container.GetRootResourcePath("secrets") +} + +func (daemon *Daemon) setupSecretFiles(container *container.Container) error { + secretsPath, err := daemon.secretsPath(container) + if err != nil { + return err + } + + logrus.WithFields(logrus.Fields{ + "container": container.ID, + "path": secretsPath, + }).Debug("SUSE:secrets -> setting up container secrets") + + if err := os.MkdirAll(secretsPath, 0700); err != nil { + return err + } + + if err := syscall.Mount("tmpfs", secretsPath, "tmpfs", uintptr(syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV), label.FormatMountLabel("", container.GetMountLabel())); err != nil { + return fmt.Errorf("mounting secret tmpfs: %s", err) + } + + data, err := getHostSecretData() + if err != nil { + return err + } + for _, s := range data { + s.SaveTo(secretsPath) + } + + return nil +} + func (daemon *Daemon) waitForStart(container *container.Container) error { return container.StartMonitor(daemon, container.HostConfig.RestartPolicy) } Index: docker-1.10.0/daemon/delete.go =================================================================== --- docker-1.10.0.orig/daemon/delete.go +++ docker-1.10.0/daemon/delete.go @@ -122,6 +122,17 @@ func (daemon *Daemon) cleanupContainer(c } }() + // Force unmount of the secrets tmpfs storage added by SUSE's Docker daemon. + // This is unmounted automatically at container start time, however the unmount + // is done with the 'lazy' flag. This can introduce some race conditions, for + // example when the container dies immediately (e.g. wrong entry point). In + // that case the secrets directory has not been unmounted yet, causing the + // removal of the container to fail because the file system is still reported + // as in use. See bnc#954797 + if err = daemon.UnmountSecrets(container, true); err != nil { + logrus.Errorf("SUSE:secrets -> Error unmounting secrets in cleanup: %v", err) + } + if err = os.RemoveAll(container.Root); err != nil { return derr.ErrorCodeRmFS.WithArgs(container.ID, err) } Index: docker-1.10.0/daemon/volumes_unix.go =================================================================== --- docker-1.10.0.orig/daemon/volumes_unix.go +++ docker-1.10.0/daemon/volumes_unix.go @@ -7,6 +7,7 @@ import ( "sort" "strconv" + "github.com/Sirupsen/logrus" "github.com/docker/docker/container" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/volume" @@ -18,6 +19,29 @@ import ( // calls Setup() on each. It also looks to see if is a network mount such as // /etc/resolv.conf, and if it is not, appends it to the array of mounts. func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.Mount, error) { + if _, exists := container.MountPoints["/run/secrets"]; !exists { + const ( + name = "suse:secrets" + dest = "/run/secrets" + rw = true + ) + + secretsPath, err := daemon.secretsPath(container) + if err != nil { + return nil, err + } + + logrus.WithFields(logrus.Fields{ + "name": name, + "rw": rw, + "path": secretsPath, + "dest": dest, + "container": container.ID, + }).Debug("SUSE:secrets -> adding /run/secrets to bind-mount points") + + container.AddBindMountPoint(name, secretsPath, dest, rw) + } + var mounts []execdriver.Mount for _, m := range container.MountPoints { if err := daemon.lazyInitializeVolume(container.ID, m); err != nil { Index: docker-1.10.0/daemon/secrets.go =================================================================== --- /dev/null +++ docker-1.10.0/daemon/secrets.go @@ -0,0 +1,103 @@ +package daemon + +import ( + "io/ioutil" + "os" + "path/filepath" + + log "github.com/Sirupsen/logrus" +) + +type Secret struct { + Name string + IsDir bool + HostBased bool +} + +type SecretData struct { + Name string + Data []byte +} + +func (s SecretData) SaveTo(dir string) error { + path := filepath.Join(dir, s.Name) + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil && !os.IsExist(err) { + return err + } + if err := ioutil.WriteFile(path, s.Data, 0755); err != nil { + return err + } + return nil +} + +func readAll(root, prefix string) ([]SecretData, error) { + path := filepath.Join(root, prefix) + + data := []SecretData{} + + files, err := ioutil.ReadDir(path) + if err != nil { + if os.IsNotExist(err) { + return data, nil + } + + return nil, err + } + + for _, f := range files { + fileData, err := readFile(root, filepath.Join(prefix, f.Name())) + if err != nil { + // If the file did not exist, might be a dangling symlink + // Ignore the error + if os.IsNotExist(err) { + continue + } + return nil, err + } + data = append(data, fileData...) + } + + return data, nil +} + +func readFile(root, name string) ([]SecretData, error) { + path := filepath.Join(root, name) + + s, err := os.Stat(path) + if err != nil { + return nil, err + } + + if s.IsDir() { + dirData, err := readAll(root, name) + if err != nil { + return nil, err + } + return dirData, nil + } else { + bytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + return []SecretData{{Name: name, Data: bytes}}, nil + } +} + +func getHostSecretData() ([]SecretData, error) { + credentials, err := readAll("/etc/zypp/", "credentials.d") + if err != nil { + log.Errorf("Error while reading zypp credentials: %s", err) + return credentials, err + } + + suseConnect, err := readFile("/etc", "SUSEConnect") + if err != nil { + if os.IsNotExist(err) { + suseConnect = []SecretData{} + } else { + log.Errorf("Error while reading /etc/SUSEConnect: %s", err) + return nil, err + } + } + return append(credentials, suseConnect...), nil +}