From 8a96b4acbc97d5c34ff0160ab1a1b585fdd5d156 Mon Sep 17 00:00:00 2001 From: umohnani8 Date: Fri, 23 Mar 2018 16:38:55 -0400 Subject: [PATCH] Add secrets patch to podman Adds support for mounting secrets especially on RHEL where the container can use the host subsription to run yum Signed-off-by: umohnani8 Closes: #544 Approved by: rhatdan --- cmd/podman/create.go | 11 ++-- libpod/container_internal.go | 19 ++++++ pkg/secrets/secrets.go | 122 +++++++++++++++++++++++++++++++++++ test/e2e/run_test.go | 28 ++++++++ 4 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 pkg/secrets/secrets.go diff --git a/cmd/podman/create.go b/cmd/podman/create.go index c1fb15bcfc..520664c8ed 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -3,6 +3,12 @@ package main import ( "encoding/json" "fmt" + "net" + "os" + "strconv" + "strings" + "syscall" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/signal" "github.com/docker/go-connections/nat" @@ -15,11 +21,6 @@ import ( "github.com/projectatomic/libpod/pkg/util" "github.com/sirupsen/logrus" "github.com/urfave/cli" - "net" - "os" - "strconv" - "strings" - "syscall" ) type mountType string diff --git a/libpod/container_internal.go b/libpod/container_internal.go index a338a1776c..4bfdfae9d8 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -22,6 +22,7 @@ import ( "github.com/pkg/errors" crioAnnotations "github.com/projectatomic/libpod/pkg/annotations" "github.com/projectatomic/libpod/pkg/chrootuser" + "github.com/projectatomic/libpod/pkg/secrets" "github.com/projectatomic/libpod/pkg/util" "github.com/sirupsen/logrus" "github.com/ulule/deepcopier" @@ -681,9 +682,27 @@ func (c *Container) makeBindMounts() error { c.state.BindMounts["/run/.containerenv"] = containerenvPath } + // Add Secret Mounts + secretMounts := c.getSecretMounts(secrets.OverrideMountsFile) + secretMounts = append(secretMounts, c.getSecretMounts(secrets.DefaultMountsFile)...) + for _, mount := range secretMounts { + if _, ok := c.state.BindMounts[mount.Destination]; !ok { + c.state.BindMounts[mount.Destination] = mount.Source + } + } + return nil } +// addSecrets mounts the secrets from the override and/or default mounts file +func (c *Container) getSecretMounts(mountFile string) (secretMounts []spec.Mount) { + secretMounts, err := secrets.SecretMounts(mountFile, c.config.MountLabel, c.state.RunDir) + if err != nil { + logrus.Warn("error mounting secrets, skipping...") + } + return secretMounts +} + // writeStringToRundir copies the provided file to the runtimedir func (c *Container) writeStringToRundir(destFile, output string) (string, error) { destFileName := filepath.Join(c.state.RunDir, destFile) diff --git a/pkg/secrets/secrets.go b/pkg/secrets/secrets.go new file mode 100644 index 0000000000..8227499e58 --- /dev/null +++ b/pkg/secrets/secrets.go @@ -0,0 +1,122 @@ +package secrets + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/storage/pkg/chrootarchive" + rspec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var ( + // DefaultMountsFile holds the default mount paths in the form + // "host_path:container_path" + DefaultMountsFile = "/usr/share/containers/mounts.conf" + // OverrideMountsFile holds the default mount paths in the form + // "host_path:container_path" overridden by the user + OverrideMountsFile = "/etc/containers/mounts.conf" +) + +func getMounts(filePath string) []string { + file, err := os.Open(filePath) + if err != nil { + logrus.Warnf("file %q not found, skipping...", filePath) + return nil + } + defer file.Close() + scanner := bufio.NewScanner(file) + if err = scanner.Err(); err != nil { + logrus.Warnf("error reading file %q, skipping...", filePath) + return nil + } + var mounts []string + for scanner.Scan() { + mounts = append(mounts, scanner.Text()) + } + return mounts +} + +// getHostAndCtrDir separates the host:container paths +func getMountsMap(path string) (string, string, error) { + arr := strings.SplitN(path, ":", 2) + if len(arr) == 2 { + return arr[0], arr[1], nil + } + return "", "", errors.Errorf("unable to get host and container dir") +} + +// SecretMounts copies the contents of host directory to container directory +// and returns a list of mounts +func SecretMounts(filePath, mountLabel, containerWorkingDir string) ([]rspec.Mount, error) { + var mounts []rspec.Mount + defaultMountsPaths := getMounts(filePath) + for _, path := range defaultMountsPaths { + hostDir, ctrDir, err := getMountsMap(path) + if err != nil { + return nil, err + } + // skip if the hostDir path doesn't exist + if _, err = os.Stat(hostDir); os.IsNotExist(err) { + logrus.Warnf("%q doesn't exist, skipping", hostDir) + continue + } + + ctrDirOnHost := filepath.Join(containerWorkingDir, ctrDir) + if err = os.RemoveAll(ctrDirOnHost); err != nil { + return nil, fmt.Errorf("remove container directory failed: %v", err) + } + + // In the event of a restart, don't want to copy secrets over again as they already would exist in ctrDirOnHost + _, err = os.Stat(ctrDirOnHost) + if os.IsNotExist(err) { + if err = os.MkdirAll(ctrDirOnHost, 0755); err != nil { + return nil, fmt.Errorf("making container directory failed: %v", err) + } + + hostDir, err = resolveSymbolicLink(hostDir) + if err != nil { + return nil, err + } + + if err = chrootarchive.NewArchiver(nil).CopyWithTar(hostDir, ctrDirOnHost); err != nil && !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "error getting host secret data") + } + + err = label.Relabel(ctrDirOnHost, mountLabel, false) + if err != nil { + return nil, errors.Wrap(err, "error applying correct labels") + } + } else if err != nil { + return nil, errors.Wrapf(err, "error getting status of %q", ctrDirOnHost) + } + + m := rspec.Mount{ + Source: ctrDirOnHost, + Destination: ctrDir, + Type: "bind", + Options: []string{"bind"}, + } + + mounts = append(mounts, m) + } + return mounts, nil +} + +// resolveSymbolicLink resolves a possbile symlink path. If the path is a symlink, returns resolved +// path; if not, returns the original path. +func resolveSymbolicLink(path string) (string, error) { + info, err := os.Lstat(path) + if err != nil { + return "", err + } + if info.Mode()&os.ModeSymlink != os.ModeSymlink { + return path, nil + } + return filepath.EvalSymlinks(path) +} diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 3abaab7836..16fae58981 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -2,6 +2,7 @@ package integration import ( "fmt" + "io/ioutil" "os" "path/filepath" @@ -216,4 +217,31 @@ var _ = Describe("Podman run", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman run with secrets", func() { + containersDir := "/usr/share/containers" + err := os.MkdirAll(containersDir, 0755) + Expect(err).To(BeNil()) + + secretsDir := filepath.Join(podmanTest.TempDir, "rhel", "secrets") + err = os.MkdirAll(secretsDir, 0755) + Expect(err).To(BeNil()) + + mountsFile := filepath.Join(containersDir, "mounts.conf") + mountString := secretsDir + ":/run/secrets" + err = ioutil.WriteFile(mountsFile, []byte(mountString), 0755) + Expect(err).To(BeNil()) + + secretsFile := filepath.Join(secretsDir, "test.txt") + secretsString := "Testing secrets mount. I am mounted!" + err = ioutil.WriteFile(secretsFile, []byte(secretsString), 0755) + Expect(err).To(BeNil()) + + session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "cat", "/run/secrets/test.txt"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(Equal(secretsString)) + + err = os.RemoveAll(containersDir) + Expect(err).To(BeNil()) + }) + })