k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/common/node/secrets.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package node 18 19 import ( 20 "context" 21 "encoding/base64" 22 "encoding/json" 23 "fmt" 24 25 v1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/types" 28 "k8s.io/apimachinery/pkg/util/uuid" 29 "k8s.io/kubernetes/test/e2e/feature" 30 "k8s.io/kubernetes/test/e2e/framework" 31 e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" 32 imageutils "k8s.io/kubernetes/test/utils/image" 33 admissionapi "k8s.io/pod-security-admission/api" 34 35 "github.com/onsi/ginkgo/v2" 36 "github.com/onsi/gomega" 37 ) 38 39 var _ = SIGDescribe("Secrets", func() { 40 f := framework.NewDefaultFramework("secrets") 41 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline 42 43 /* 44 Release: v1.9 45 Testname: Secrets, pod environment field 46 Description: Create a secret. Create a Pod with Container that declares a environment variable which references the secret created to extract a key value from the secret. Pod MUST have the environment variable that contains proper value for the key to the secret. 47 */ 48 framework.ConformanceIt("should be consumable from pods in env vars", f.WithNodeConformance(), func(ctx context.Context) { 49 name := "secret-test-" + string(uuid.NewUUID()) 50 secret := secretForTest(f.Namespace.Name, name) 51 52 ginkgo.By(fmt.Sprintf("Creating secret with name %s", secret.Name)) 53 var err error 54 if secret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}); err != nil { 55 framework.Failf("unable to create test secret %s: %v", secret.Name, err) 56 } 57 58 pod := &v1.Pod{ 59 ObjectMeta: metav1.ObjectMeta{ 60 Name: "pod-secrets-" + string(uuid.NewUUID()), 61 }, 62 Spec: v1.PodSpec{ 63 Containers: []v1.Container{ 64 { 65 Name: "secret-env-test", 66 Image: imageutils.GetE2EImage(imageutils.BusyBox), 67 Command: []string{"sh", "-c", "env"}, 68 Env: []v1.EnvVar{ 69 { 70 Name: "SECRET_DATA", 71 ValueFrom: &v1.EnvVarSource{ 72 SecretKeyRef: &v1.SecretKeySelector{ 73 LocalObjectReference: v1.LocalObjectReference{ 74 Name: name, 75 }, 76 Key: "data-1", 77 }, 78 }, 79 }, 80 }, 81 }, 82 }, 83 RestartPolicy: v1.RestartPolicyNever, 84 }, 85 } 86 87 e2epodoutput.TestContainerOutput(ctx, f, "consume secrets", pod, 0, []string{ 88 "SECRET_DATA=value-1", 89 }) 90 }) 91 92 /* 93 Release: v1.9 94 Testname: Secrets, pod environment from source 95 Description: Create a secret. Create a Pod with Container that declares a environment variable using 'EnvFrom' which references the secret created to extract a key value from the secret. Pod MUST have the environment variable that contains proper value for the key to the secret. 96 */ 97 framework.ConformanceIt("should be consumable via the environment", f.WithNodeConformance(), func(ctx context.Context) { 98 name := "secret-test-" + string(uuid.NewUUID()) 99 secret := secretForTest(f.Namespace.Name, name) 100 ginkgo.By(fmt.Sprintf("creating secret %v/%v", f.Namespace.Name, secret.Name)) 101 var err error 102 if secret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}); err != nil { 103 framework.Failf("unable to create test secret %s: %v", secret.Name, err) 104 } 105 106 pod := &v1.Pod{ 107 ObjectMeta: metav1.ObjectMeta{ 108 Name: "pod-configmaps-" + string(uuid.NewUUID()), 109 }, 110 Spec: v1.PodSpec{ 111 Containers: []v1.Container{ 112 { 113 Name: "env-test", 114 Image: imageutils.GetE2EImage(imageutils.BusyBox), 115 Command: []string{"sh", "-c", "env"}, 116 EnvFrom: []v1.EnvFromSource{ 117 { 118 SecretRef: &v1.SecretEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: name}}, 119 }, 120 { 121 Prefix: "p-", 122 SecretRef: &v1.SecretEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: name}}, 123 }, 124 }, 125 }, 126 }, 127 RestartPolicy: v1.RestartPolicyNever, 128 }, 129 } 130 131 e2epodoutput.TestContainerOutput(ctx, f, "consume secrets", pod, 0, []string{ 132 "data-1=value-1", "data-2=value-2", "data-3=value-3", 133 "p-data-1=value-1", "p-data-2=value-2", "p-data-3=value-3", 134 }) 135 }) 136 137 /* 138 Release: v1.15 139 Testname: Secrets, with empty-key 140 Description: Attempt to create a Secret with an empty key. The creation MUST fail. 141 */ 142 framework.ConformanceIt("should fail to create secret due to empty secret key", func(ctx context.Context) { 143 secret, err := createEmptyKeySecretForTest(ctx, f) 144 gomega.Expect(err).To(gomega.HaveOccurred(), "created secret %q with empty key in namespace %q", secret.Name, f.Namespace.Name) 145 }) 146 147 /* 148 Release: v1.18 149 Testname: Secret patching 150 Description: A Secret is created. 151 Listing all Secrets MUST return an empty list. 152 Given the patching and fetching of the Secret, the fields MUST equal the new values. 153 The Secret is deleted by it's static Label. 154 Secrets are listed finally, the list MUST NOT include the originally created Secret. 155 */ 156 framework.ConformanceIt("should patch a secret", func(ctx context.Context) { 157 ginkgo.By("creating a secret") 158 159 secretTestName := "test-secret-" + string(uuid.NewUUID()) 160 161 // create a secret in the test namespace 162 _, err := f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, &v1.Secret{ 163 ObjectMeta: metav1.ObjectMeta{ 164 Name: secretTestName, 165 Labels: map[string]string{ 166 "testsecret-constant": "true", 167 }, 168 }, 169 Data: map[string][]byte{ 170 "key": []byte("value"), 171 }, 172 Type: "Opaque", 173 }, metav1.CreateOptions{}) 174 framework.ExpectNoError(err, "failed to create secret") 175 176 ginkgo.By("listing secrets in all namespaces to ensure that there are more than zero") 177 // list all secrets in all namespaces to ensure endpoint coverage 178 secretsList, err := f.ClientSet.CoreV1().Secrets("").List(ctx, metav1.ListOptions{ 179 LabelSelector: "testsecret-constant=true", 180 }) 181 framework.ExpectNoError(err, "failed to list secrets") 182 gomega.Expect(secretsList.Items).ToNot(gomega.BeEmpty(), "no secrets found") 183 184 foundCreatedSecret := false 185 var secretCreatedName string 186 for _, val := range secretsList.Items { 187 if val.ObjectMeta.Name == secretTestName && val.ObjectMeta.Namespace == f.Namespace.Name { 188 foundCreatedSecret = true 189 secretCreatedName = val.ObjectMeta.Name 190 break 191 } 192 } 193 if !foundCreatedSecret { 194 framework.Failf("unable to find secret %s/%s by name", f.Namespace.Name, secretTestName) 195 } 196 197 ginkgo.By("patching the secret") 198 // patch the secret in the test namespace 199 secretPatchNewData := base64.StdEncoding.EncodeToString([]byte("value1")) 200 secretPatch, err := json.Marshal(map[string]interface{}{ 201 "metadata": map[string]interface{}{ 202 "labels": map[string]string{"testsecret": "true"}, 203 }, 204 "data": map[string][]byte{"key": []byte(secretPatchNewData)}, 205 }) 206 framework.ExpectNoError(err, "failed to marshal JSON") 207 _, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Patch(ctx, secretCreatedName, types.StrategicMergePatchType, []byte(secretPatch), metav1.PatchOptions{}) 208 framework.ExpectNoError(err, "failed to patch secret") 209 210 secret, err := f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Get(ctx, secretCreatedName, metav1.GetOptions{}) 211 framework.ExpectNoError(err, "failed to get secret") 212 213 secretDecodedstring, err := base64.StdEncoding.DecodeString(string(secret.Data["key"])) 214 framework.ExpectNoError(err, "failed to decode secret from Base64") 215 216 gomega.Expect(string(secretDecodedstring)).To(gomega.Equal("value1"), "found secret, but the data wasn't updated from the patch") 217 218 ginkgo.By("deleting the secret using a LabelSelector") 219 err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{ 220 LabelSelector: "testsecret=true", 221 }) 222 framework.ExpectNoError(err, "failed to delete patched secret") 223 224 ginkgo.By("listing secrets in all namespaces, searching for label name and value in patch") 225 // list all secrets in all namespaces 226 secretsList, err = f.ClientSet.CoreV1().Secrets("").List(ctx, metav1.ListOptions{ 227 LabelSelector: "testsecret-constant=true", 228 }) 229 framework.ExpectNoError(err, "failed to list secrets") 230 231 foundCreatedSecret = false 232 for _, val := range secretsList.Items { 233 if val.ObjectMeta.Name == secretTestName && val.ObjectMeta.Namespace == f.Namespace.Name { 234 foundCreatedSecret = true 235 break 236 } 237 } 238 if foundCreatedSecret { 239 framework.Failf("secret %s/%s was not deleted successfully", f.Namespace.Name, secretTestName) 240 } 241 }) 242 243 /* 244 Release: v1.30 245 Testname: Secrets, pod environment from source 246 Description: Create a secret. Create a Pod with Container that declares a environment variable using 'EnvFrom' which references the secret created to extract a key value from the secret. 247 Allows users to use envFrom to set prefix starting with a digit as environment variable names. 248 */ 249 framework.It("should be consumable as environment variable names when secret keys start with a digit", feature.RelaxedEnvironmentVariableValidation, func(ctx context.Context) { 250 name := "secret-test-" + string(uuid.NewUUID()) 251 secret := secretForTest(f.Namespace.Name, name) 252 253 ginkgo.By(fmt.Sprintf("creating secret %v/%v", f.Namespace.Name, secret.Name)) 254 var err error 255 if secret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}); err != nil { 256 framework.Failf("unable to create test secret %s: %v", secret.Name, err) 257 } 258 259 pod := &v1.Pod{ 260 ObjectMeta: metav1.ObjectMeta{ 261 Name: "pod-configmaps-" + string(uuid.NewUUID()), 262 }, 263 Spec: v1.PodSpec{ 264 Containers: []v1.Container{ 265 { 266 Name: "env-test", 267 Image: imageutils.GetE2EImage(imageutils.BusyBox), 268 Command: []string{"sh", "-c", "env"}, 269 EnvFrom: []v1.EnvFromSource{ 270 { 271 SecretRef: &v1.SecretEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: name}}, 272 }, 273 { 274 // prefix start with a digit can be consumed as environment variables. 275 Prefix: "1-", 276 SecretRef: &v1.SecretEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: name}}, 277 }, 278 }, 279 }, 280 }, 281 RestartPolicy: v1.RestartPolicyNever, 282 }, 283 } 284 285 e2epodoutput.TestContainerOutput(ctx, f, "consume secrets", pod, 0, []string{ 286 "data-1=value-1", "data-2=value-2", "data-3=value-3", 287 "1-data-1=value-1", "1-data-2=value-2", "1-data-3=value-3", 288 }) 289 }) 290 }) 291 292 func secretForTest(namespace, name string) *v1.Secret { 293 return &v1.Secret{ 294 ObjectMeta: metav1.ObjectMeta{ 295 Namespace: namespace, 296 Name: name, 297 }, 298 Data: map[string][]byte{ 299 "data-1": []byte("value-1\n"), 300 "data-2": []byte("value-2\n"), 301 "data-3": []byte("value-3\n"), 302 }, 303 } 304 } 305 306 func createEmptyKeySecretForTest(ctx context.Context, f *framework.Framework) (*v1.Secret, error) { 307 secretName := "secret-emptykey-test-" + string(uuid.NewUUID()) 308 secret := &v1.Secret{ 309 ObjectMeta: metav1.ObjectMeta{ 310 Namespace: f.Namespace.Name, 311 Name: secretName, 312 }, 313 Data: map[string][]byte{ 314 "": []byte("value-1\n"), 315 }, 316 } 317 ginkgo.By(fmt.Sprintf("Creating projection with secret that has name %s", secret.Name)) 318 return f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}) 319 }