mirror of
https://github.com/containers/podman.git
synced 2025-06-17 15:08:08 +08:00
Merge pull request #10221 from ashley-cui/envsec
Add support for environment variable secrets
This commit is contained in:
@ -639,12 +639,16 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
|
||||
}
|
||||
s.RestartPolicy = splitRestart[0]
|
||||
}
|
||||
|
||||
s.Secrets, s.EnvSecrets, err = parseSecrets(c.Secrets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Remove = c.Rm
|
||||
s.StopTimeout = &c.StopTimeout
|
||||
s.Timeout = c.Timeout
|
||||
s.Timezone = c.Timezone
|
||||
s.Umask = c.Umask
|
||||
s.Secrets = c.Secrets
|
||||
s.PidFile = c.PidFile
|
||||
s.Volatile = c.Rm
|
||||
|
||||
@ -773,3 +777,70 @@ func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrot
|
||||
}
|
||||
return td, nil
|
||||
}
|
||||
|
||||
func parseSecrets(secrets []string) ([]string, map[string]string, error) {
|
||||
secretParseError := errors.New("error parsing secret")
|
||||
var mount []string
|
||||
envs := make(map[string]string)
|
||||
for _, val := range secrets {
|
||||
source := ""
|
||||
secretType := ""
|
||||
target := ""
|
||||
split := strings.Split(val, ",")
|
||||
|
||||
// --secret mysecret
|
||||
if len(split) == 1 {
|
||||
source = val
|
||||
mount = append(mount, source)
|
||||
continue
|
||||
}
|
||||
// --secret mysecret,opt=opt
|
||||
if !strings.Contains(split[0], "=") {
|
||||
source = split[0]
|
||||
split = split[1:]
|
||||
}
|
||||
// TODO: implement other secret options
|
||||
for _, val := range split {
|
||||
kv := strings.SplitN(val, "=", 2)
|
||||
if len(kv) < 2 {
|
||||
return nil, nil, errors.Wrapf(secretParseError, "option %s must be in form option=value", val)
|
||||
}
|
||||
switch kv[0] {
|
||||
case "source":
|
||||
source = kv[1]
|
||||
case "type":
|
||||
if secretType != "" {
|
||||
return nil, nil, errors.Wrap(secretParseError, "cannot set more tha one secret type")
|
||||
}
|
||||
if kv[1] != "mount" && kv[1] != "env" {
|
||||
return nil, nil, errors.Wrapf(secretParseError, "type %s is invalid", kv[1])
|
||||
}
|
||||
secretType = kv[1]
|
||||
case "target":
|
||||
target = kv[1]
|
||||
default:
|
||||
return nil, nil, errors.Wrapf(secretParseError, "option %s invalid", val)
|
||||
}
|
||||
}
|
||||
|
||||
if secretType == "" {
|
||||
secretType = "mount"
|
||||
}
|
||||
if source == "" {
|
||||
return nil, nil, errors.Wrapf(secretParseError, "no source found %s", val)
|
||||
}
|
||||
if secretType == "mount" {
|
||||
if target != "" {
|
||||
return nil, nil, errors.Wrapf(secretParseError, "target option is invalid for mounted secrets")
|
||||
}
|
||||
mount = append(mount, source)
|
||||
}
|
||||
if secretType == "env" {
|
||||
if target == "" {
|
||||
target = source
|
||||
}
|
||||
envs[target] = source
|
||||
}
|
||||
}
|
||||
return mount, envs, nil
|
||||
}
|
||||
|
@ -2,15 +2,16 @@ package secrets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/completion"
|
||||
"github.com/containers/podman/v3/cmd/podman/common"
|
||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -29,6 +30,7 @@ var (
|
||||
|
||||
var (
|
||||
createOpts = entities.SecretCreateOptions{}
|
||||
env = false
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -43,6 +45,9 @@ func init() {
|
||||
driverFlagName := "driver"
|
||||
flags.StringVar(&createOpts.Driver, driverFlagName, "file", "Specify secret driver")
|
||||
_ = createCmd.RegisterFlagCompletionFunc(driverFlagName, completion.AutocompleteNone)
|
||||
|
||||
envFlagName := "env"
|
||||
flags.BoolVar(&env, envFlagName, false, "Read secret data from environment variable")
|
||||
}
|
||||
|
||||
func create(cmd *cobra.Command, args []string) error {
|
||||
@ -52,7 +57,13 @@ func create(cmd *cobra.Command, args []string) error {
|
||||
path := args[1]
|
||||
|
||||
var reader io.Reader
|
||||
if path == "-" || path == "/dev/stdin" {
|
||||
if env {
|
||||
envValue := os.Getenv(path)
|
||||
if envValue == "" {
|
||||
return errors.Errorf("cannot create store secret data: environment variable %s is not set", path)
|
||||
}
|
||||
reader = strings.NewReader(envValue)
|
||||
} else if path == "-" || path == "/dev/stdin" {
|
||||
stat, err := os.Stdin.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -840,7 +840,7 @@ Specify the policy to select the seccomp profile. If set to *image*, Podman will
|
||||
|
||||
Note that this feature is experimental and may change in the future.
|
||||
|
||||
#### **\-\-secret**=*secret*
|
||||
#### **\-\-secret**=*secret*[,opt=opt ...]
|
||||
|
||||
Give the container access to a secret. Can be specified multiple times.
|
||||
|
||||
@ -848,12 +848,17 @@ A secret is a blob of sensitive data which a container needs at runtime but
|
||||
should not be stored in the image or in source control, such as usernames and passwords,
|
||||
TLS certificates and keys, SSH keys or other important generic strings or binary content (up to 500 kb in size).
|
||||
|
||||
Secrets are copied and mounted into the container when a container is created. If a secret is deleted using
|
||||
`podman secret rm`, the container will still have access to the secret. If a secret is deleted and
|
||||
another secret is created with the same name, the secret inside the container will not change; the old
|
||||
secret value will still remain.
|
||||
When secrets are specified as type `mount`, the secrets are copied and mounted into the container when a container is created.
|
||||
When secrets are specified as type `env`, the secret will be set as an environment variable within the container.
|
||||
Secrets are written in the container at the time of container creation, and modifying the secret using `podman secret` commands
|
||||
after the container is created will not affect the secret inside the container.
|
||||
|
||||
Secrets are managed using the `podman secret` command.
|
||||
Secrets and its storage are managed using the `podman secret` command.
|
||||
|
||||
Secret Options
|
||||
|
||||
- `type=mount|env` : How the secret will be exposed to the container. Default mount.
|
||||
- `target=target` : Target of secret. Defauts to secret name.
|
||||
|
||||
#### **\-\-security-opt**=*option*
|
||||
|
||||
|
@ -892,7 +892,7 @@ Specify the policy to select the seccomp profile. If set to *image*, Podman will
|
||||
|
||||
Note that this feature is experimental and may change in the future.
|
||||
|
||||
#### **\-\-secret**=*secret*
|
||||
#### **\-\-secret**=*secret*[,opt=opt ...]
|
||||
|
||||
Give the container access to a secret. Can be specified multiple times.
|
||||
|
||||
@ -900,12 +900,17 @@ A secret is a blob of sensitive data which a container needs at runtime but
|
||||
should not be stored in the image or in source control, such as usernames and passwords,
|
||||
TLS certificates and keys, SSH keys or other important generic strings or binary content (up to 500 kb in size).
|
||||
|
||||
Secrets are copied and mounted into the container when a container is created. If a secret is deleted using
|
||||
`podman secret rm`, the container will still have access to the secret. If a secret is deleted and
|
||||
another secret is created with the same name, the secret inside the container will not change; the old
|
||||
secret value will still remain.
|
||||
When secrets are specified as type `mount`, the secrets are copied and mounted into the container when a container is created.
|
||||
When secrets are specified as type `env`, the secret will be set as an environment variable within the container.
|
||||
Secrets are written in the container at the time of container creation, and modifying the secret using `podman secret` commands
|
||||
after the container is created will not affect the secret inside the container.
|
||||
|
||||
Secrets are managed using the `podman secret` command
|
||||
Secrets and its storage are managed using the `podman secret` command.
|
||||
|
||||
Secret Options
|
||||
|
||||
- `type=mount|env` : How the secret will be exposed to the container. Default mount.
|
||||
- `target=target` : Target of secret. Defauts to secret name.
|
||||
|
||||
#### **\-\-security-opt**=*option*
|
||||
|
||||
|
@ -20,6 +20,10 @@ Secrets will not be committed to an image with `podman commit`, and will not be
|
||||
|
||||
## OPTIONS
|
||||
|
||||
#### **\-\-env**=*false*
|
||||
|
||||
Read secret data from environment variable
|
||||
|
||||
#### **\-\-driver**=*driver*
|
||||
|
||||
Specify the secret driver (default **file**, which is unencrypted).
|
||||
|
@ -373,4 +373,6 @@ type ContainerMiscConfig struct {
|
||||
PidFile string `json:"pid_file,omitempty"`
|
||||
// CDIDevices contains devices that use the CDI
|
||||
CDIDevices []string `json:"cdiDevices,omitempty"`
|
||||
// EnvSecrets are secrets that are set as environment variables
|
||||
EnvSecrets map[string]*secrets.Secret `json:"secret_env,omitempty"`
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
"github.com/containers/common/pkg/apparmor"
|
||||
"github.com/containers/common/pkg/chown"
|
||||
"github.com/containers/common/pkg/config"
|
||||
"github.com/containers/common/pkg/secrets"
|
||||
"github.com/containers/common/pkg/subscriptions"
|
||||
"github.com/containers/common/pkg/umask"
|
||||
"github.com/containers/podman/v3/libpod/define"
|
||||
@ -757,6 +758,19 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||
if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil {
|
||||
return nil, errors.Wrapf(err, "error setting up OCI Hooks")
|
||||
}
|
||||
if len(c.config.EnvSecrets) > 0 {
|
||||
manager, err := secrets.NewManager(c.runtime.GetSecretsStorageDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for name, secr := range c.config.EnvSecrets {
|
||||
_, data, err := manager.LookupSecretData(secr.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.AddProcessEnv(name, string(data))
|
||||
}
|
||||
}
|
||||
|
||||
return g.Config, nil
|
||||
}
|
||||
|
@ -1716,6 +1716,28 @@ func WithSecrets(secretNames []string) CtrCreateOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithSecrets adds environment variable secrets to the container
|
||||
func WithEnvSecrets(envSecrets map[string]string) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
ctr.config.EnvSecrets = make(map[string]*secrets.Secret)
|
||||
if ctr.valid {
|
||||
return define.ErrCtrFinalized
|
||||
}
|
||||
manager, err := secrets.NewManager(ctr.runtime.GetSecretsStorageDir())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for target, src := range envSecrets {
|
||||
secr, err := manager.Lookup(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctr.config.EnvSecrets[target] = secr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPidFile adds pidFile to the container
|
||||
func WithPidFile(pidFile string) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
|
@ -402,6 +402,11 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
|
||||
if len(s.Secrets) != 0 {
|
||||
options = append(options, libpod.WithSecrets(s.Secrets))
|
||||
}
|
||||
|
||||
if len(s.EnvSecrets) != 0 {
|
||||
options = append(options, libpod.WithEnvSecrets(s.EnvSecrets))
|
||||
}
|
||||
|
||||
if len(s.DependencyContainers) > 0 {
|
||||
deps := make([]*libpod.Container, 0, len(s.DependencyContainers))
|
||||
for _, ctr := range s.DependencyContainers {
|
||||
|
@ -180,6 +180,9 @@ type ContainerBasicConfig struct {
|
||||
// set tags as `json:"-"` for not supported remote
|
||||
// Optional.
|
||||
PidFile string `json:"-"`
|
||||
// EnvSecrets are secrets that will be set as environment variables
|
||||
// Optional.
|
||||
EnvSecrets map[string]string `json:"secret_env,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerStorageConfig contains information on the storage configuration of a
|
||||
|
@ -304,4 +304,28 @@ var _ = Describe("Podman commit", func() {
|
||||
Expect(session.ExitCode()).To(Not(Equal(0)))
|
||||
|
||||
})
|
||||
|
||||
It("podman commit should not commit env secret", func() {
|
||||
secretsString := "somesecretdata"
|
||||
secretFilePath := filepath.Join(podmanTest.TempDir, "secret")
|
||||
err := ioutil.WriteFile(secretFilePath, []byte(secretsString), 0755)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
session := podmanTest.Podman([]string{"secret", "create", "mysecret", secretFilePath})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=env", "--name", "secr", ALPINE, "printenv", "mysecret"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(session.OutputToString()).To(Equal(secretsString))
|
||||
|
||||
session = podmanTest.Podman([]string{"commit", "secr", "foobar.com/test1-image:latest"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"run", "foobar.com/test1-image:latest", "printenv", "mysecret"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.OutputToString()).To(Not(ContainSubstring(secretsString)))
|
||||
})
|
||||
})
|
||||
|
@ -1611,6 +1611,95 @@ WORKDIR /madethis`, BB)
|
||||
|
||||
})
|
||||
|
||||
It("podman run --secret source=mysecret,type=mount", func() {
|
||||
secretsString := "somesecretdata"
|
||||
secretFilePath := filepath.Join(podmanTest.TempDir, "secret")
|
||||
err := ioutil.WriteFile(secretFilePath, []byte(secretsString), 0755)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
session := podmanTest.Podman([]string{"secret", "create", "mysecret", secretFilePath})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=mount", "--name", "secr", ALPINE, "cat", "/run/secrets/mysecret"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(session.OutputToString()).To(Equal(secretsString))
|
||||
|
||||
session = podmanTest.Podman([]string{"inspect", "secr", "--format", " {{(index .Config.Secrets 0).Name}}"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(session.OutputToString()).To(ContainSubstring("mysecret"))
|
||||
|
||||
})
|
||||
|
||||
It("podman run --secret source=mysecret,type=env", func() {
|
||||
secretsString := "somesecretdata"
|
||||
secretFilePath := filepath.Join(podmanTest.TempDir, "secret")
|
||||
err := ioutil.WriteFile(secretFilePath, []byte(secretsString), 0755)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
session := podmanTest.Podman([]string{"secret", "create", "mysecret", secretFilePath})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=env", "--name", "secr", ALPINE, "printenv", "mysecret"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(session.OutputToString()).To(Equal(secretsString))
|
||||
})
|
||||
|
||||
It("podman run --secret target option", func() {
|
||||
secretsString := "somesecretdata"
|
||||
secretFilePath := filepath.Join(podmanTest.TempDir, "secret")
|
||||
err := ioutil.WriteFile(secretFilePath, []byte(secretsString), 0755)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
session := podmanTest.Podman([]string{"secret", "create", "mysecret", secretFilePath})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
// target with mount type should fail
|
||||
session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=mount,target=anotherplace", "--name", "secr", ALPINE, "cat", "/run/secrets/mysecret"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Not(Equal(0)))
|
||||
|
||||
session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=env,target=anotherplace", "--name", "secr", ALPINE, "printenv", "anotherplace"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(session.OutputToString()).To(Equal(secretsString))
|
||||
})
|
||||
|
||||
It("podman run invalid secret option", func() {
|
||||
secretsString := "somesecretdata"
|
||||
secretFilePath := filepath.Join(podmanTest.TempDir, "secret")
|
||||
err := ioutil.WriteFile(secretFilePath, []byte(secretsString), 0755)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
session := podmanTest.Podman([]string{"secret", "create", "mysecret", secretFilePath})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
|
||||
// Invalid type
|
||||
session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=other", "--name", "secr", ALPINE, "printenv", "mysecret"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Not(Equal(0)))
|
||||
|
||||
// Invalid option
|
||||
session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,invalid=invalid", "--name", "secr", ALPINE, "printenv", "mysecret"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Not(Equal(0)))
|
||||
|
||||
// Option syntax not valid
|
||||
session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type", "--name", "secr", ALPINE, "printenv", "mysecret"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Not(Equal(0)))
|
||||
|
||||
// No source given
|
||||
session = podmanTest.Podman([]string{"run", "--secret", "type=env", "--name", "secr", ALPINE, "printenv", "mysecret"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Not(Equal(0)))
|
||||
})
|
||||
|
||||
It("podman run --requires", func() {
|
||||
depName := "ctr1"
|
||||
depContainer := podmanTest.Podman([]string{"create", "--name", depName, ALPINE, "top"})
|
||||
|
@ -199,4 +199,27 @@ var _ = Describe("Podman secret", func() {
|
||||
Expect(len(session.OutputToStringArray())).To(Equal(1))
|
||||
})
|
||||
|
||||
It("podman secret creates from environment variable", func() {
|
||||
// no env variable set, should fail
|
||||
session := podmanTest.Podman([]string{"secret", "create", "--env", "a", "MYENVVAR"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
secrID := session.OutputToString()
|
||||
Expect(session.ExitCode()).To(Not(Equal(0)))
|
||||
|
||||
os.Setenv("MYENVVAR", "somedata")
|
||||
if IsRemote() {
|
||||
podmanTest.RestartRemoteService()
|
||||
}
|
||||
|
||||
session = podmanTest.Podman([]string{"secret", "create", "--env", "a", "MYENVVAR"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
secrID = session.OutputToString()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
|
||||
inspect := podmanTest.Podman([]string{"secret", "inspect", "--format", "{{.ID}}", secrID})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect.ExitCode()).To(Equal(0))
|
||||
Expect(inspect.OutputToString()).To(Equal(secrID))
|
||||
})
|
||||
|
||||
})
|
||||
|
Reference in New Issue
Block a user