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 <acui@redhat.com>
This commit is contained in:
Ashley Cui
2021-01-15 01:27:23 -05:00
parent 2aaf631586
commit 832a69b0be
58 changed files with 2962 additions and 7 deletions

View File

@ -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.

View File

@ -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

View File

@ -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))

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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.

View File

@ -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")
}

View File

@ -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 != "" {