From 832a69b0bee6ec289521fbd59ddd480372493ee3 Mon Sep 17 00:00:00 2001 From: Ashley Cui Date: Fri, 15 Jan 2021 01:27:23 -0500 Subject: Implement Secrets Implement podman secret create, inspect, ls, rm Implement podman run/create --secret Secrets are blobs of data that are sensitive. Currently, the only secret driver supported is filedriver, which means creating a secret stores it in base64 unencrypted in a file. After creating a secret, a user can use the --secret flag to expose the secret inside the container at /run/secrets/[secretname] This secret will not be commited to an image on a podman commit Signed-off-by: Ashley Cui --- libpod/container.go | 6 +++++ libpod/container_config.go | 5 ++++ libpod/container_inspect.go | 7 ++++++ libpod/container_internal.go | 24 +++++++++++++++++++ libpod/container_internal_linux.go | 47 +++++++++++++++++++++++++++++++++++--- libpod/define/container_inspect.go | 13 +++++++++++ libpod/options.go | 23 +++++++++++++++++++ libpod/runtime.go | 5 ++++ libpod/runtime_ctr.go | 13 ++++++++++- 9 files changed, 139 insertions(+), 4 deletions(-) (limited to 'libpod') diff --git a/libpod/container.go b/libpod/container.go index ed7535bc8..e667cd991 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -10,6 +10,7 @@ import ( "github.com/containernetworking/cni/pkg/types" cnitypes "github.com/containernetworking/cni/pkg/types/current" + "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/manifest" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/lock" @@ -1133,6 +1134,11 @@ func (c *Container) Umask() string { return c.config.Umask } +//Secrets return the secrets in the container +func (c *Container) Secrets() []*secrets.Secret { + return c.config.Secrets +} + // Networks gets all the networks this container is connected to. // Please do NOT use ctr.config.Networks, as this can be changed from those // values at runtime via network connect and disconnect. diff --git a/libpod/container_config.go b/libpod/container_config.go index 93ac8807d..5d7e65f2b 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -4,6 +4,7 @@ import ( "net" "time" + "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/manifest" "github.com/containers/podman/v2/pkg/namespaces" "github.com/containers/storage" @@ -146,6 +147,10 @@ type ContainerRootFSConfig struct { // working directory if it does not exist. Some OCI runtimes do this by // default, but others do not. CreateWorkingDir bool `json:"createWorkingDir,omitempty"` + // Secrets lists secrets to mount into the container + Secrets []*secrets.Secret `json:"secrets,omitempty"` + // SecretPath is the secrets location in storage + SecretsPath string `json:"secretsPath"` } // ContainerSecurityConfig is an embedded sub-config providing security configuration diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index ac7eae56b..f50c7dbfe 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -340,6 +340,13 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp ctrConfig.Timezone = c.config.Timezone + for _, secret := range c.config.Secrets { + newSec := define.InspectSecret{} + newSec.Name = secret.Name + newSec.ID = secret.ID + ctrConfig.Secrets = append(ctrConfig.Secrets, &newSec) + } + // Pad Umask to 4 characters if len(c.config.Umask) < 4 { pad := strings.Repeat("0", 4-len(c.config.Umask)) diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 5a61f7fe6..b280e79d1 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -13,6 +13,7 @@ import ( "strings" "time" + "github.com/containers/common/pkg/secrets" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/events" "github.com/containers/podman/v2/pkg/cgroups" @@ -29,6 +30,7 @@ import ( securejoin "github.com/cyphar/filepath-securejoin" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" + "github.com/opencontainers/selinux/go-selinux/label" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -2212,3 +2214,25 @@ func (c *Container) hasNamespace(namespace spec.LinuxNamespaceType) bool { } return false } + +// extractSecretToStorage copies a secret's data from the secrets manager to the container's static dir +func (c *Container) extractSecretToCtrStorage(name string) error { + manager, err := secrets.NewManager(c.runtime.GetSecretsStorageDir()) + if err != nil { + return err + } + secr, data, err := manager.LookupSecretData(name) + if err != nil { + return err + } + secretFile := filepath.Join(c.config.SecretsPath, secr.Name) + + err = ioutil.WriteFile(secretFile, data, 0644) + if err != nil { + return errors.Wrapf(err, "unable to create %s", secretFile) + } + if err := label.Relabel(secretFile, c.config.MountLabel, false); err != nil { + return err + } + return nil +} diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index ba85a1f47..3583f8fdd 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -25,6 +25,7 @@ import ( "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/subscriptions" + "github.com/containers/common/pkg/umask" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/events" "github.com/containers/podman/v2/pkg/annotations" @@ -1643,14 +1644,30 @@ rootless=%d c.state.BindMounts["/run/.containerenv"] = containerenvPath } - // Add Secret Mounts - secretMounts := subscriptions.MountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.Containers.DefaultMountsFile, c.state.Mountpoint, c.RootUID(), c.RootGID(), rootless.IsRootless(), false) - for _, mount := range secretMounts { + // Add Subscription Mounts + subscriptionMounts := subscriptions.MountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.Containers.DefaultMountsFile, c.state.Mountpoint, c.RootUID(), c.RootGID(), rootless.IsRootless(), false) + for _, mount := range subscriptionMounts { if _, ok := c.state.BindMounts[mount.Destination]; !ok { c.state.BindMounts[mount.Destination] = mount.Source } } + // Secrets are mounted by getting the secret data from the secrets manager, + // copying the data into the container's static dir, + // then mounting the copied dir into /run/secrets. + // The secrets mounting must come after subscription mounts, since subscription mounts + // creates the /run/secrets dir in the container where we mount as well. + if len(c.Secrets()) > 0 { + // create /run/secrets if subscriptions did not create + if err := c.createSecretMountDir(); err != nil { + return errors.Wrapf(err, "error creating secrets mount") + } + for _, secret := range c.Secrets() { + src := filepath.Join(c.config.SecretsPath, secret.Name) + dest := filepath.Join("/run/secrets", secret.Name) + c.state.BindMounts[dest] = src + } + } return nil } @@ -2368,3 +2385,27 @@ func (c *Container) checkFileExistsInRootfs(file string) (bool, error) { } return true, nil } + +// Creates and mounts an empty dir to mount secrets into, if it does not already exist +func (c *Container) createSecretMountDir() error { + src := filepath.Join(c.state.RunDir, "/run/secrets") + _, err := os.Stat(src) + if os.IsNotExist(err) { + oldUmask := umask.Set(0) + defer umask.Set(oldUmask) + + if err := os.MkdirAll(src, 0644); err != nil { + return err + } + if err := label.Relabel(src, c.config.MountLabel, false); err != nil { + return err + } + if err := os.Chown(src, c.RootUID(), c.RootGID()); err != nil { + return err + } + c.state.BindMounts["/run/secrets"] = src + return nil + } + + return err +} diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index 9a93e2ffd..2cdd53cbc 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -62,6 +62,8 @@ type InspectContainerConfig struct { SystemdMode bool `json:"SystemdMode,omitempty"` // Umask is the umask inside the container. Umask string `json:"Umask,omitempty"` + // Secrets are the secrets mounted in the container + Secrets []*InspectSecret `json:"Secrets,omitempty"` } // InspectRestartPolicy holds information about the container's restart policy. @@ -705,3 +707,14 @@ type DriverData struct { Name string `json:"Name"` Data map[string]string `json:"Data"` } + +// InspectHostPort provides information on a port on the host that a container's +// port is bound to. +type InspectSecret struct { + // IP on the host we are bound to. "" if not specified (binding to all + // IPs). + Name string `json:"Name"` + // Port on the host we are bound to. No special formatting - just an + // integer stuffed into a string. + ID string `json:"ID"` +} diff --git a/libpod/options.go b/libpod/options.go index 20f62ee37..74ee60fef 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -8,6 +8,7 @@ import ( "syscall" "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/podman/v2/libpod/define" @@ -1687,6 +1688,28 @@ func WithUmask(umask string) CtrCreateOption { } } +// WithSecrets adds secrets to the container +func WithSecrets(secretNames []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + manager, err := secrets.NewManager(ctr.runtime.GetSecretsStorageDir()) + if err != nil { + return err + } + for _, name := range secretNames { + secr, err := manager.Lookup(name) + if err != nil { + return err + } + ctr.config.Secrets = append(ctr.config.Secrets, secr) + } + + return nil + } +} + // Pod Creation Options // WithInfraImage sets the infra image for libpod. diff --git a/libpod/runtime.go b/libpod/runtime.go index 0dc220b52..1ad39fe2f 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -904,3 +904,8 @@ func (r *Runtime) getVolumePlugin(name string) (*plugin.VolumePlugin, error) { return plugin.GetVolumePlugin(name, pluginPath) } + +// GetSecretsStoreageDir returns the directory that the secrets manager should take +func (r *Runtime) GetSecretsStorageDir() string { + return filepath.Join(r.store.GraphRoot(), "secrets") +} diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index d2bcd8db3..49cf42626 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -422,6 +422,18 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai } }() + ctr.config.SecretsPath = filepath.Join(ctr.config.StaticDir, "secrets") + err = os.MkdirAll(ctr.config.SecretsPath, 0644) + if err != nil { + return nil, err + } + for _, secr := range ctr.config.Secrets { + err = ctr.extractSecretToCtrStorage(secr.Name) + if err != nil { + return nil, err + } + } + if ctr.config.ConmonPidFile == "" { ctr.config.ConmonPidFile = filepath.Join(ctr.state.RunDir, "conmon.pid") } @@ -492,7 +504,6 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai toLock.lock.Lock() defer toLock.lock.Unlock() } - // Add the container to the state // TODO: May be worth looking into recovering from name/ID collisions here if ctr.config.Pod != "" { -- cgit v1.2.3-54-g00ecf