mirror of
https://github.com/containers/podman.git
synced 2025-10-24 15:03:45 +08:00
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:
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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"`
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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 != "" {
|
||||
|
||||
Reference in New Issue
Block a user