From c548054560b366ba7f8f7230782bb2ebd43ac287 Mon Sep 17 00:00:00 2001 From: Paul Morie Date: Mon, 18 Jan 2016 12:20:51 -0500 Subject: [PATCH] Add ability to consume secrets in env vars --- docs/user-guide/secrets/secret-env-pod.yaml | 16 ++++++ examples/examples_test.go | 5 +- pkg/api/types.go | 14 ++++- pkg/api/v1/types.go | 14 ++++- pkg/kubelet/kubelet.go | 16 ++++++ test/e2e/secrets.go | 63 ++++++++++++++++++++- 6 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 docs/user-guide/secrets/secret-env-pod.yaml diff --git a/docs/user-guide/secrets/secret-env-pod.yaml b/docs/user-guide/secrets/secret-env-pod.yaml new file mode 100644 index 00000000000..a5d9c0ff758 --- /dev/null +++ b/docs/user-guide/secrets/secret-env-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: secret-env-pod +spec: + containers: + - name: test-container + image: gcr.io/google_containers/busybox + command: [ "/bin/sh", "-c", "env" ] + env: + - name: MY_SECRET_DATA + valueFrom: + secretKeyRef: + name: test-secret + key: data-1 + restartPolicy: Never diff --git a/examples/examples_test.go b/examples/examples_test.go index b144e6d6f88..6ec81b83f12 100644 --- a/examples/examples_test.go +++ b/examples/examples_test.go @@ -354,8 +354,9 @@ func TestExampleObjectSchemas(t *testing.T) { "rc": &api.ReplicationController{}, }, "../docs/user-guide/secrets": { - "secret-pod": &api.Pod{}, - "secret": &api.Secret{}, + "secret-pod": &api.Pod{}, + "secret": &api.Secret{}, + "secret-env-pod": &api.Pod{}, }, "../examples/spark": { "spark-master-controller": &api.ReplicationController{}, diff --git a/pkg/api/types.go b/pkg/api/types.go index 588b8ed2ce6..b937ed12da5 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -560,8 +560,8 @@ type GitRepoVolumeSource struct { // as files using the keys in the Data field as the file names. // Secret volumes support ownership management and SELinux relabeling. type SecretVolumeSource struct { - // Name of the secret in the pod's namespace to use - SecretName string `json:"secretName"` + // Name of the secret in the pod's namespace to use. + SecretName string `json:"secretName,omitempty"` } // Represents an NFS mount that lasts the lifetime of a pod. @@ -721,6 +721,8 @@ type EnvVarSource struct { FieldRef *ObjectFieldSelector `json:"fieldRef,omitempty"` // Selects a key of a ConfigMap. ConfigMapKeyRef *ConfigMapKeySelector `json:"configMapKeyRef,omitempty"` + // Selects a key of a secret in the pod's namespace. + SecretKeyRef *SecretKeySelector `json:"secretKeyRef,omitempty"` } // ObjectFieldSelector selects an APIVersioned field of an object. @@ -741,6 +743,14 @@ type ConfigMapKeySelector struct { Key string `json:"key"` } +// SecretKeySelector selects a key of a Secret. +type SecretKeySelector struct { + // The name of the secret in the pod's namespace to select from. + LocalObjectReference `json:",inline"` + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key"` +} + // HTTPGetAction describes an action based on HTTP Get requests. type HTTPGetAction struct { // Optional: Path to access on the HTTP server. diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index 9b0cfe5df09..e99db438fa4 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -724,9 +724,9 @@ type GitRepoVolumeSource struct { // as files using the keys in the Data field as the file names. // Secret volumes support ownership management and SELinux relabeling. type SecretVolumeSource struct { - // SecretName is the name of a secret in the pod's namespace. + // Name of the secret in the pod's namespace to use. // More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#secrets - SecretName string `json:"secretName"` + SecretName string `json:"secretName,omitempty"` } // Represents an NFS mount that lasts the lifetime of a pod. @@ -847,6 +847,8 @@ type EnvVarSource struct { FieldRef *ObjectFieldSelector `json:"fieldRef,omitempty"` // Selects a key of a ConfigMap. ConfigMapKeyRef *ConfigMapKeySelector `json:"configMapKeyRef,omitempty"` + // Selects a key of a secret in the pod's namespace + SecretKeyRef *SecretKeySelector `json:"secretKeyRef,omitempty"` } // ObjectFieldSelector selects an APIVersioned field of an object. @@ -865,6 +867,14 @@ type ConfigMapKeySelector struct { Key string `json:"key"` } +// SecretKeySelector selects a key of a Secret. +type SecretKeySelector struct { + // The name of the secret in the pod's namespace to select from. + LocalObjectReference `json:",inline"` + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key"` +} + // HTTPGetAction describes an action based on HTTP Get requests. type HTTPGetAction struct { // Path to access on the HTTP server. diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index d5bbf795b3d..1931f6e57d1 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -1369,6 +1369,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *api.Pod, container *api.Contain var ( tmpEnv = make(map[string]string) configMaps = make(map[string]*apiextensions.ConfigMap) + secrets = make(map[string]*api.Secret) mappingFunc = expansion.MappingFuncFor(tmpEnv, serviceEnv) ) for _, envVar := range container.Env { @@ -1405,6 +1406,21 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *api.Pod, container *api.Contain if !ok { return result, fmt.Errorf("Couldn't find key %v in ConfigMap %v/%v", key, pod.Namespace, name) } + case envVar.ValueFrom.SecretKeyRef != nil: + name := envVar.ValueFrom.SecretKeyRef.Name + key := envVar.ValueFrom.SecretKeyRef.Key + secret, ok := secrets[name] + if !ok { + secret, err = kl.kubeClient.Secrets(pod.Namespace).Get(name) + if err != nil { + return result, err + } + } + runtimeValBytes, ok := secret.Data[key] + if !ok { + return result, fmt.Errorf("Couldn't find key %v in Secret %v/%v", key, pod.Namespace, name) + } + runtimeVal = string(runtimeValBytes) } } diff --git a/test/e2e/secrets.go b/test/e2e/secrets.go index b67e172e07a..d63dbcabe5a 100644 --- a/test/e2e/secrets.go +++ b/test/e2e/secrets.go @@ -28,7 +28,7 @@ import ( var _ = Describe("Secrets", func() { f := NewFramework("secrets") - It("should be consumable from pods [Conformance]", func() { + It("should be consumable from pods in volume [Conformance]", func() { name := "secret-test-" + string(util.NewUUID()) volumeName := "secret-volume" volumeMountPath := "/etc/secret-volume" @@ -74,7 +74,7 @@ var _ = Describe("Secrets", func() { }, Containers: []api.Container{ { - Name: "secret-test", + Name: "secret-volume-test", Image: "gcr.io/google_containers/mounttest:0.2", Args: []string{ "--file_content=/etc/secret-volume/data-1", @@ -97,4 +97,63 @@ var _ = Describe("Secrets", func() { "mode of file \"/etc/secret-volume/data-1\": -r--r--r--", }, f.Namespace.Name) }) + + It("should be consumable from pods in env vars [Conformance]", func() { + name := "secret-test-" + string(util.NewUUID()) + + secret := &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Namespace: f.Namespace.Name, + Name: name, + }, + Data: map[string][]byte{ + "data-1": []byte("value-1"), + }, + } + + By(fmt.Sprintf("Creating secret with name %s", secret.Name)) + defer func() { + By("Cleaning up the secret") + if err := f.Client.Secrets(f.Namespace.Name).Delete(secret.Name); err != nil { + Failf("unable to delete secret %v: %v", secret.Name, err) + } + }() + var err error + if secret, err = f.Client.Secrets(f.Namespace.Name).Create(secret); err != nil { + Failf("unable to create test secret %s: %v", secret.Name, err) + } + + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod-secrets-" + string(util.NewUUID()), + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "secret-env-test", + Image: "gcr.io/google_containers/busybox", + Command: []string{"sh", "-c", "env"}, + Env: []api.EnvVar{ + { + Name: "SECRET_DATA", + ValueFrom: &api.EnvVarSource{ + SecretKeyRef: &api.SecretKeySelector{ + LocalObjectReference: api.LocalObjectReference{ + Name: name, + }, + Key: "data-1", + }, + }, + }, + }, + }, + }, + RestartPolicy: api.RestartPolicyNever, + }, + } + + testContainerOutput("consume secrets", f.Client, pod, 0, []string{ + "SECRET_DATA=value-1", + }, f.Namespace.Name) + }) })