From 8b6a7c18260be740ef696a4779c0ca7fb108a4c6 Mon Sep 17 00:00:00 2001 From: Urvashi Mohnani Date: Thu, 30 Mar 2023 15:50:55 -0400 Subject: [PATCH] Use secret.items to create volume mounts if present If the kube yaml volumes has secret.items set, then use the values from that to set up the paths inside the container similar to what we do for configMap. Add tests for this as well. Signed-off-by: Urvashi Mohnani --- pkg/specgen/generate/kube/volume.go | 27 ++++-- test/e2e/play_kube_test.go | 136 ++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 8 deletions(-) diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go index b87557280d..148490f76e 100644 --- a/pkg/specgen/generate/kube/volume.go +++ b/pkg/specgen/generate/kube/volume.go @@ -147,20 +147,31 @@ func VolumeFromSecret(secretSource *v1.SecretVolumeSource, secretsManager *secre return nil, err } - data := &v1.Secret{} + secret := &v1.Secret{} - err = yaml.Unmarshal(secretByte, data) + err = yaml.Unmarshal(secretByte, secret) if err != nil { return nil, err } - // add key: value pairs to the items array - for key, entry := range data.Data { - kv.Items[key] = entry - } + // If there are Items specified in the volumeSource, that overwrites the Data from the Secret + if len(secretSource.Items) > 0 { + for _, item := range secretSource.Items { + if val, ok := secret.Data[item.Key]; ok { + kv.Items[item.Path] = val + } else if val, ok := secret.StringData[item.Key]; ok { + kv.Items[item.Path] = []byte(val) + } + } + } else { + // add key: value pairs to the items array + for key, entry := range secret.Data { + kv.Items[key] = entry + } - for key, entry := range data.StringData { - kv.Items[key] = []byte(entry) + for key, entry := range secret.StringData { + kv.Items[key] = []byte(entry) + } } return kv, nil diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index b95456ceaa..1ffd196bfb 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -492,6 +492,19 @@ data: {{ end }} ` +var secretYamlTemplate = ` +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Name }} +data: +{{ with .Data }} + {{ range $key, $value := . }} + {{ $key }}: {{ $value }} + {{ end }} +{{ end }} +` + var persistentVolumeClaimYamlTemplate = ` apiVersion: v1 kind: PersistentVolumeClaim @@ -692,6 +705,18 @@ spec: path: {{ .path }} {{- end }} {{- end }} + {{- end }} + {{- if (eq .VolumeType "Secret") }} + secret: + secretName: {{ .SecretVol.SecretName }} + optional: {{ .SecretVol.Optional }} + {{- with .SecretVol.Items }} + items: + {{- range . }} + - key: {{ .key }} + path: {{ .path }} + {{- end }} + {{- end }} {{- end }} {{ end }} {{ end }} @@ -1019,6 +1044,7 @@ var ( defaultVolName = "testVol" defaultDeploymentName = "testDeployment" defaultConfigMapName = "testConfigMap" + defaultSecretName = "testSecret" defaultPVCName = "testPVC" seccompPwdEPERM = []byte(`{"defaultAction":"SCMP_ACT_ALLOW","syscalls":[{"name":"getcwd","action":"SCMP_ACT_ERRNO"}]}`) // CPU Period in ms @@ -1041,6 +1067,8 @@ func getKubeYaml(kind string, object interface{}) (string, error) { yamlTemplate = deploymentYamlTemplate case "persistentVolumeClaim": yamlTemplate = persistentVolumeClaimYamlTemplate + case "secret": + yamlTemplate = secretYamlTemplate default: return "", fmt.Errorf("unsupported kubernetes kind") } @@ -1089,6 +1117,39 @@ func createSecret(podmanTest *PodmanTestIntegration, name string, value []byte) Expect(secret).Should(Exit(0)) } +// Secret describes the options a kube yaml can be configured at secret level +type Secret struct { + Name string + Data map[string]string +} + +func getSecret(options ...secretOption) *Secret { + secret := Secret{ + Name: defaultSecretName, + Data: map[string]string{}, + } + + for _, option := range options { + option(&secret) + } + + return &secret +} + +type secretOption func(*Secret) + +func withSecretName(name string) secretOption { + return func(secret *Secret) { + secret.Name = name + } +} + +func withSecretData(k, v string) secretOption { + return func(secret *Secret) { + secret.Data[k] = base64.StdEncoding.EncodeToString([]byte(v)) + } +} + // CM describes the options a kube yaml can be configured at configmap level type CM struct { Name string @@ -1568,6 +1629,12 @@ type ConfigMap struct { Optional bool } +type SecretVol struct { + SecretName string + Items []map[string]string + Optional bool +} + type EmptyDir struct{} type Volume struct { @@ -1577,6 +1644,7 @@ type Volume struct { PersistentVolumeClaim ConfigMap EmptyDir + SecretVol } // getHostPathVolume takes a type and a location for a HostPath @@ -1618,6 +1686,18 @@ func getConfigMapVolume(vName string, items []map[string]string, optional bool) } } +func getSecretVolume(vName string, items []map[string]string, optional bool) *Volume { + return &Volume{ + VolumeType: "Secret", + Name: defaultVolName, + SecretVol: SecretVol{ + SecretName: vName, + Items: items, + Optional: optional, + }, + } +} + func getEmptyDirVolume() *Volume { return &Volume{ VolumeType: "EmptyDir", @@ -4730,6 +4810,62 @@ ENV OPENJ9_JAVA_OPTIONS=%q deleteAndTestSecret(podmanTest, "newsecret") }) + It("podman play kube secret as volume with no items", func() { + volumeName := "secretVol" + secret := getSecret(withSecretName(volumeName), withSecretData("FOO", "testuser")) + secretYaml, err := getKubeYaml("secret", secret) + Expect(err).ToNot(HaveOccurred()) + + ctr := getCtr(withVolumeMount("/test", "", false), withImage(BB)) + pod := getPod(withVolume(getSecretVolume(volumeName, []map[string]string{}, false)), withCtr(ctr)) + podYaml, err := getKubeYaml("pod", pod) + Expect(err).ToNot(HaveOccurred()) + yamls := []string{secretYaml, podYaml} + err = generateMultiDocKubeYaml(yamls, kubeYaml) + Expect(err).ToNot(HaveOccurred()) + + kube := podmanTest.Podman([]string{"kube", "play", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + secretData := podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/test/FOO"}) + secretData.WaitWithDefaultTimeout() + Expect(secretData).Should(Exit(0)) + Expect(secretData.OutputToString()).To(Equal("testuser")) + }) + + It("podman play kube secret as volume with items", func() { + volumeName := "secretVol" + secret := getSecret(withSecretName(volumeName), withSecretData("FOO", "foobar")) + secretYaml, err := getKubeYaml("secret", secret) + Expect(err).ToNot(HaveOccurred()) + volumeContents := []map[string]string{{ + "key": "FOO", + "path": "BAR", + }} + + ctr := getCtr(withVolumeMount("/test", "", false), withImage(BB)) + pod := getPod(withVolume(getSecretVolume(volumeName, volumeContents, false)), withCtr(ctr)) + podYaml, err := getKubeYaml("pod", pod) + Expect(err).ToNot(HaveOccurred()) + yamls := []string{secretYaml, podYaml} + err = generateMultiDocKubeYaml(yamls, kubeYaml) + Expect(err).ToNot(HaveOccurred()) + + kube := podmanTest.Podman([]string{"kube", "play", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + secretData := podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/test/BAR"}) + secretData.WaitWithDefaultTimeout() + Expect(secretData).Should(Exit(0)) + Expect(secretData.OutputToString()).To(Equal("foobar")) + + secretData = podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/test/FOO"}) + secretData.WaitWithDefaultTimeout() + Expect(secretData).Should(Not(Exit(0))) + }) + It("podman play kube with disabled cgroup", func() { SkipIfRunc(podmanTest, "Test not supported with runc, see issue #17436") conffile := filepath.Join(podmanTest.TempDir, "container.conf")