forked from pool/docker
291 lines
7.4 KiB
Diff
291 lines
7.4 KiB
Diff
From a7fbdb729255da73e47e77ca37eec0da325356c4 Mon Sep 17 00:00:00 2001
|
|
From: Aleksa Sarai <asarai@suse.de>
|
|
Date: Wed, 8 Mar 2017 11:43:29 +1100
|
|
Subject: [PATCH 2/2] SUSE: implement SUSE container secrets
|
|
|
|
This allows for us to pass in host credentials to a container, allowing
|
|
for SUSEConnect to work with containers.
|
|
|
|
THIS PATCH IS NOT TO BE UPSTREAMED, DUE TO THE FACT THAT IT IS
|
|
SUSE-SPECIFIC, AND UPSTREAM DOES NOT APPROVE OF THIS CONCEPT BECAUSE IT
|
|
MAKES BUILDS NOT ENTIRELY REPRODUCIBLE.
|
|
|
|
Signed-off-by: Aleksa Sarai <asarai@suse.de>
|
|
---
|
|
daemon/start.go | 5 +
|
|
daemon/suse_secrets.go | 246 +++++++++++++++++++++++++++++++++++++++++++++++++
|
|
2 files changed, 251 insertions(+)
|
|
create mode 100644 daemon/suse_secrets.go
|
|
|
|
diff --git a/daemon/start.go b/daemon/start.go
|
|
index 6c94fd5482d0..3c06eed778d7 100644
|
|
--- a/daemon/start.go
|
|
+++ b/daemon/start.go
|
|
@@ -146,6 +146,11 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint
|
|
return err
|
|
}
|
|
|
|
+ // SUSE:secrets -- inject the SUSE secret store
|
|
+ if err := daemon.injectSuseSecretStore(container); err != nil {
|
|
+ return err
|
|
+ }
|
|
+
|
|
spec, err := daemon.createSpec(container)
|
|
if err != nil {
|
|
return err
|
|
diff --git a/daemon/suse_secrets.go b/daemon/suse_secrets.go
|
|
new file mode 100644
|
|
index 000000000000..591abc998e67
|
|
--- /dev/null
|
|
+++ b/daemon/suse_secrets.go
|
|
@@ -0,0 +1,246 @@
|
|
+/*
|
|
+ * suse-secrets: patch for Docker to implement SUSE secrets
|
|
+ * Copyright (C) 2017 SUSE LLC.
|
|
+ *
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
+ * you may not use this file except in compliance with the License.
|
|
+ * You may obtain a copy of the License at
|
|
+ *
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
+ *
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
+ * See the License for the specific language governing permissions and
|
|
+ * limitations under the License.
|
|
+ */
|
|
+
|
|
+package daemon
|
|
+
|
|
+import (
|
|
+ "fmt"
|
|
+ "io/ioutil"
|
|
+ "os"
|
|
+ "path/filepath"
|
|
+ "syscall"
|
|
+
|
|
+ "github.com/Sirupsen/logrus"
|
|
+ "github.com/opencontainers/go-digest"
|
|
+ "github.com/docker/docker/container"
|
|
+
|
|
+ swarmtypes "github.com/docker/docker/api/types/swarm"
|
|
+ swarmexec "github.com/docker/swarmkit/agent/exec"
|
|
+ swarmapi "github.com/docker/swarmkit/api"
|
|
+)
|
|
+
|
|
+func init() {
|
|
+ // Output to tell us in logs that SUSE:secrets is enabled.
|
|
+ logrus.Infof("SUSE:secrets :: enabled")
|
|
+}
|
|
+
|
|
+// Creating a fake file.
|
|
+type SuseFakeFile struct {
|
|
+ Path string
|
|
+ Uid int
|
|
+ Gid int
|
|
+ Mode os.FileMode
|
|
+ Data []byte
|
|
+}
|
|
+
|
|
+func (s SuseFakeFile) id() string {
|
|
+ return fmt.Sprintf("suse::%s:%s", digest.FromBytes(s.Data), s.Path)
|
|
+}
|
|
+
|
|
+func (s SuseFakeFile) toSecret() *swarmapi.Secret {
|
|
+ return &swarmapi.Secret{
|
|
+ ID: s.id(),
|
|
+ Internal: true,
|
|
+ Spec: swarmapi.SecretSpec{
|
|
+ Data: s.Data,
|
|
+ },
|
|
+ }
|
|
+}
|
|
+
|
|
+func (s SuseFakeFile) toSecretReference() *swarmtypes.SecretReference {
|
|
+ return &swarmtypes.SecretReference{
|
|
+ SecretID: s.id(),
|
|
+ SecretName: s.id(),
|
|
+ File: &swarmtypes.SecretReferenceFileTarget{
|
|
+ Name: s.Path,
|
|
+ UID: fmt.Sprintf("%d", s.Uid),
|
|
+ GID: fmt.Sprintf("%d", s.Gid),
|
|
+ Mode: s.Mode,
|
|
+ },
|
|
+ }
|
|
+}
|
|
+
|
|
+// readDir will recurse into a directory prefix/dir, and return the set of secrets
|
|
+// in that directory. The Path attribute of each has the prefix stripped. Symlinks
|
|
+// are evaluated.
|
|
+func readDir(prefix, dir string) ([]*SuseFakeFile, error) {
|
|
+ var suseFiles []*SuseFakeFile
|
|
+
|
|
+ path := filepath.Join(prefix, dir)
|
|
+
|
|
+ fi, err := os.Stat(path)
|
|
+ if err != nil {
|
|
+ // Ignore dangling symlinks.
|
|
+ if os.IsNotExist(err) {
|
|
+ logrus.Warnf("SUSE:secrets :: dangling symlink: %s", path)
|
|
+ return suseFiles, nil
|
|
+ }
|
|
+ return nil, err
|
|
+ }
|
|
+
|
|
+ stat, ok := fi.Sys().(*syscall.Stat_t)
|
|
+ if !ok {
|
|
+ logrus.Warnf("SUSE:secrets :: failed to cast directory stat_t: defaulting to owned by root:root: %s", path)
|
|
+ }
|
|
+
|
|
+ suseFiles = append(suseFiles, &SuseFakeFile{
|
|
+ Path: dir,
|
|
+ Uid: int(stat.Uid),
|
|
+ Gid: int(stat.Gid),
|
|
+ Mode: fi.Mode(),
|
|
+ })
|
|
+
|
|
+ files, err := ioutil.ReadDir(path)
|
|
+ if err != nil {
|
|
+ return nil, err
|
|
+ }
|
|
+
|
|
+ for _, f := range files {
|
|
+ subpath := filepath.Join(dir, f.Name())
|
|
+
|
|
+ if f.IsDir() {
|
|
+ secrets, err := readDir(prefix, subpath)
|
|
+ if err != nil {
|
|
+ return nil, err
|
|
+ }
|
|
+ suseFiles = append(suseFiles, secrets...)
|
|
+ } else {
|
|
+ secrets, err := readFile(prefix, subpath)
|
|
+ if err != nil {
|
|
+ return nil, err
|
|
+ }
|
|
+ suseFiles = append(suseFiles, secrets...)
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return suseFiles, nil
|
|
+}
|
|
+
|
|
+// readFile returns a secret given a file under a given prefix.
|
|
+func readFile(prefix, file string) ([]*SuseFakeFile, error) {
|
|
+ var suseFiles []*SuseFakeFile
|
|
+
|
|
+ path := filepath.Join(prefix, file)
|
|
+ fi, err := os.Stat(path)
|
|
+ if err != nil {
|
|
+ // Ignore dangling symlinks.
|
|
+ if os.IsNotExist(err) {
|
|
+ logrus.Warnf("SUSE:secrets :: dangling symlink: %s", path)
|
|
+ return suseFiles, nil
|
|
+ }
|
|
+ return nil, err
|
|
+ }
|
|
+
|
|
+ stat, ok := fi.Sys().(*syscall.Stat_t)
|
|
+ if !ok {
|
|
+ logrus.Warnf("SUSE:secrets :: failed to cast file stat_t: defaulting to owned by root:root: %s", path)
|
|
+ }
|
|
+
|
|
+ if fi.IsDir() {
|
|
+ secrets, err := readDir(prefix, file)
|
|
+ if err != nil {
|
|
+ return nil, err
|
|
+ }
|
|
+ suseFiles = append(suseFiles, secrets...)
|
|
+ } else {
|
|
+ bytes, err := ioutil.ReadFile(path)
|
|
+ if err != nil {
|
|
+ return nil, err
|
|
+ }
|
|
+ suseFiles = append(suseFiles, &SuseFakeFile{
|
|
+ Path: file,
|
|
+ Uid: int(stat.Uid),
|
|
+ Gid: int(stat.Gid),
|
|
+ Mode: fi.Mode(),
|
|
+ Data: bytes,
|
|
+ })
|
|
+ }
|
|
+
|
|
+ return suseFiles, nil
|
|
+}
|
|
+
|
|
+// getHostSuseSecretData returns the list of SuseFakeFiles the need to be added
|
|
+// as SUSE secrets.
|
|
+func getHostSuseSecretData() ([]*SuseFakeFile, error) {
|
|
+ secrets := []*SuseFakeFile{}
|
|
+
|
|
+ credentials, err := readDir("/etc/zypp", "credentials.d")
|
|
+ if err != nil {
|
|
+ if os.IsNotExist(err) {
|
|
+ credentials = []*SuseFakeFile{}
|
|
+ } else {
|
|
+ logrus.Errorf("SUSE:secrets :: error while reading zypp credentials: %s", err)
|
|
+ return nil, err
|
|
+ }
|
|
+ }
|
|
+ secrets = append(secrets, credentials...)
|
|
+
|
|
+ suseConnect, err := readFile("/etc", "SUSEConnect")
|
|
+ if err != nil {
|
|
+ if os.IsNotExist(err) {
|
|
+ suseConnect = []*SuseFakeFile{}
|
|
+ } else {
|
|
+ logrus.Errorf("SUSE:secrets :: error while reading /etc/SUSEConnect: %s", err)
|
|
+ return nil, err
|
|
+ }
|
|
+ }
|
|
+ secrets = append(secrets, suseConnect...)
|
|
+
|
|
+ return secrets, nil
|
|
+}
|
|
+
|
|
+// In order to reduce the amount of code touched outside of this file, we
|
|
+// implement the swarm API for SecretGetter. This asserts that this requirement
|
|
+// will always be matched.
|
|
+var _ swarmexec.SecretGetter = &suseSecretGetter{}
|
|
+
|
|
+type suseSecretGetter struct {
|
|
+ dfl swarmexec.SecretGetter
|
|
+ secrets map[string]*swarmapi.Secret
|
|
+}
|
|
+
|
|
+func (s *suseSecretGetter) Get(id string) *swarmapi.Secret {
|
|
+ logrus.Debugf("SUSE:secrets :: id=%s requested from suseSecretGetter", id)
|
|
+
|
|
+ secret, ok := s.secrets[id]
|
|
+ if !ok {
|
|
+ // fallthrough
|
|
+ return s.dfl.Get(id)
|
|
+ }
|
|
+
|
|
+ return secret
|
|
+}
|
|
+
|
|
+func (daemon *Daemon) injectSuseSecretStore(c *container.Container) error {
|
|
+ newSecretStore := &suseSecretGetter{
|
|
+ dfl: c.SecretStore,
|
|
+ secrets: make(map[string]*swarmapi.Secret),
|
|
+ }
|
|
+
|
|
+ secrets, err := getHostSuseSecretData()
|
|
+ if err != nil {
|
|
+ return err
|
|
+ }
|
|
+
|
|
+ for _, secret := range secrets {
|
|
+ newSecretStore.secrets[secret.id()] = secret.toSecret()
|
|
+ c.SecretReferences = append(c.SecretReferences, secret.toSecretReference())
|
|
+ }
|
|
+
|
|
+ c.SecretStore = newSecretStore
|
|
+ return nil
|
|
+}
|
|
--
|
|
2.12.2
|
|
|