k8s.io/kubernetes@v1.29.3/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  	"github.com/onsi/ginkgo/v2"
    26  	"github.com/onsi/gomega"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/apimachinery/pkg/util/uuid"
    32  	"k8s.io/kubernetes/test/e2e/framework"
    33  	e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
    34  	imageutils "k8s.io/kubernetes/test/utils/image"
    35  	admissionapi "k8s.io/pod-security-admission/api"
    36  )
    37  
    38  var _ = SIGDescribe("Secrets", func() {
    39  	f := framework.NewDefaultFramework("secrets")
    40  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    41  
    42  	/*
    43  		Release: v1.9
    44  		Testname: Secrets, pod environment field
    45  		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.
    46  	*/
    47  	framework.ConformanceIt("should be consumable from pods in env vars", f.WithNodeConformance(), func(ctx context.Context) {
    48  		name := "secret-test-" + string(uuid.NewUUID())
    49  		secret := secretForTest(f.Namespace.Name, name)
    50  
    51  		ginkgo.By(fmt.Sprintf("Creating secret with name %s", secret.Name))
    52  		var err error
    53  		if secret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}); err != nil {
    54  			framework.Failf("unable to create test secret %s: %v", secret.Name, err)
    55  		}
    56  
    57  		pod := &v1.Pod{
    58  			ObjectMeta: metav1.ObjectMeta{
    59  				Name: "pod-secrets-" + string(uuid.NewUUID()),
    60  			},
    61  			Spec: v1.PodSpec{
    62  				Containers: []v1.Container{
    63  					{
    64  						Name:    "secret-env-test",
    65  						Image:   imageutils.GetE2EImage(imageutils.BusyBox),
    66  						Command: []string{"sh", "-c", "env"},
    67  						Env: []v1.EnvVar{
    68  							{
    69  								Name: "SECRET_DATA",
    70  								ValueFrom: &v1.EnvVarSource{
    71  									SecretKeyRef: &v1.SecretKeySelector{
    72  										LocalObjectReference: v1.LocalObjectReference{
    73  											Name: name,
    74  										},
    75  										Key: "data-1",
    76  									},
    77  								},
    78  							},
    79  						},
    80  					},
    81  				},
    82  				RestartPolicy: v1.RestartPolicyNever,
    83  			},
    84  		}
    85  
    86  		e2epodoutput.TestContainerOutput(ctx, f, "consume secrets", pod, 0, []string{
    87  			"SECRET_DATA=value-1",
    88  		})
    89  	})
    90  
    91  	/*
    92  		Release: v1.9
    93  		Testname: Secrets, pod environment from source
    94  		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.
    95  	*/
    96  	framework.ConformanceIt("should be consumable via the environment", f.WithNodeConformance(), func(ctx context.Context) {
    97  		name := "secret-test-" + string(uuid.NewUUID())
    98  		secret := secretForTest(f.Namespace.Name, name)
    99  		ginkgo.By(fmt.Sprintf("creating secret %v/%v", f.Namespace.Name, secret.Name))
   100  		var err error
   101  		if secret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}); err != nil {
   102  			framework.Failf("unable to create test secret %s: %v", secret.Name, err)
   103  		}
   104  
   105  		pod := &v1.Pod{
   106  			ObjectMeta: metav1.ObjectMeta{
   107  				Name: "pod-configmaps-" + string(uuid.NewUUID()),
   108  			},
   109  			Spec: v1.PodSpec{
   110  				Containers: []v1.Container{
   111  					{
   112  						Name:    "env-test",
   113  						Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   114  						Command: []string{"sh", "-c", "env"},
   115  						EnvFrom: []v1.EnvFromSource{
   116  							{
   117  								SecretRef: &v1.SecretEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: name}},
   118  							},
   119  							{
   120  								Prefix:    "p-",
   121  								SecretRef: &v1.SecretEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: name}},
   122  							},
   123  						},
   124  					},
   125  				},
   126  				RestartPolicy: v1.RestartPolicyNever,
   127  			},
   128  		}
   129  
   130  		e2epodoutput.TestContainerOutput(ctx, f, "consume secrets", pod, 0, []string{
   131  			"data-1=value-1", "data-2=value-2", "data-3=value-3",
   132  			"p-data-1=value-1", "p-data-2=value-2", "p-data-3=value-3",
   133  		})
   134  	})
   135  
   136  	/*
   137  	   Release: v1.15
   138  	   Testname: Secrets, with empty-key
   139  	   Description: Attempt to create a Secret with an empty key. The creation MUST fail.
   140  	*/
   141  	framework.ConformanceIt("should fail to create secret due to empty secret key", func(ctx context.Context) {
   142  		secret, err := createEmptyKeySecretForTest(ctx, f)
   143  		gomega.Expect(err).To(gomega.HaveOccurred(), "created secret %q with empty key in namespace %q", secret.Name, f.Namespace.Name)
   144  	})
   145  
   146  	/*
   147  			   Release: v1.18
   148  			   Testname: Secret patching
   149  			   Description: A Secret is created.
   150  		           Listing all Secrets MUST return an empty list.
   151  		           Given the patching and fetching of the Secret, the fields MUST equal the new values.
   152  		           The Secret is deleted by it's static Label.
   153  		           Secrets are listed finally, the list MUST NOT include the originally created Secret.
   154  	*/
   155  	framework.ConformanceIt("should patch a secret", func(ctx context.Context) {
   156  		ginkgo.By("creating a secret")
   157  
   158  		secretTestName := "test-secret-" + string(uuid.NewUUID())
   159  
   160  		// create a secret in the test namespace
   161  		_, err := f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, &v1.Secret{
   162  			ObjectMeta: metav1.ObjectMeta{
   163  				Name: secretTestName,
   164  				Labels: map[string]string{
   165  					"testsecret-constant": "true",
   166  				},
   167  			},
   168  			Data: map[string][]byte{
   169  				"key": []byte("value"),
   170  			},
   171  			Type: "Opaque",
   172  		}, metav1.CreateOptions{})
   173  		framework.ExpectNoError(err, "failed to create secret")
   174  
   175  		ginkgo.By("listing secrets in all namespaces to ensure that there are more than zero")
   176  		// list all secrets in all namespaces to ensure endpoint coverage
   177  		secretsList, err := f.ClientSet.CoreV1().Secrets("").List(ctx, metav1.ListOptions{
   178  			LabelSelector: "testsecret-constant=true",
   179  		})
   180  		framework.ExpectNoError(err, "failed to list secrets")
   181  		gomega.Expect(secretsList.Items).ToNot(gomega.BeEmpty(), "no secrets found")
   182  
   183  		foundCreatedSecret := false
   184  		var secretCreatedName string
   185  		for _, val := range secretsList.Items {
   186  			if val.ObjectMeta.Name == secretTestName && val.ObjectMeta.Namespace == f.Namespace.Name {
   187  				foundCreatedSecret = true
   188  				secretCreatedName = val.ObjectMeta.Name
   189  				break
   190  			}
   191  		}
   192  		if !foundCreatedSecret {
   193  			framework.Failf("unable to find secret %s/%s by name", f.Namespace.Name, secretTestName)
   194  		}
   195  
   196  		ginkgo.By("patching the secret")
   197  		// patch the secret in the test namespace
   198  		secretPatchNewData := base64.StdEncoding.EncodeToString([]byte("value1"))
   199  		secretPatch, err := json.Marshal(map[string]interface{}{
   200  			"metadata": map[string]interface{}{
   201  				"labels": map[string]string{"testsecret": "true"},
   202  			},
   203  			"data": map[string][]byte{"key": []byte(secretPatchNewData)},
   204  		})
   205  		framework.ExpectNoError(err, "failed to marshal JSON")
   206  		_, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Patch(ctx, secretCreatedName, types.StrategicMergePatchType, []byte(secretPatch), metav1.PatchOptions{})
   207  		framework.ExpectNoError(err, "failed to patch secret")
   208  
   209  		secret, err := f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Get(ctx, secretCreatedName, metav1.GetOptions{})
   210  		framework.ExpectNoError(err, "failed to get secret")
   211  
   212  		secretDecodedstring, err := base64.StdEncoding.DecodeString(string(secret.Data["key"]))
   213  		framework.ExpectNoError(err, "failed to decode secret from Base64")
   214  
   215  		gomega.Expect(string(secretDecodedstring)).To(gomega.Equal("value1"), "found secret, but the data wasn't updated from the patch")
   216  
   217  		ginkgo.By("deleting the secret using a LabelSelector")
   218  		err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{
   219  			LabelSelector: "testsecret=true",
   220  		})
   221  		framework.ExpectNoError(err, "failed to delete patched secret")
   222  
   223  		ginkgo.By("listing secrets in all namespaces, searching for label name and value in patch")
   224  		// list all secrets in all namespaces
   225  		secretsList, err = f.ClientSet.CoreV1().Secrets("").List(ctx, metav1.ListOptions{
   226  			LabelSelector: "testsecret-constant=true",
   227  		})
   228  		framework.ExpectNoError(err, "failed to list secrets")
   229  
   230  		foundCreatedSecret = false
   231  		for _, val := range secretsList.Items {
   232  			if val.ObjectMeta.Name == secretTestName && val.ObjectMeta.Namespace == f.Namespace.Name {
   233  				foundCreatedSecret = true
   234  				break
   235  			}
   236  		}
   237  		if foundCreatedSecret {
   238  			framework.Failf("secret %s/%s was not deleted successfully", f.Namespace.Name, secretTestName)
   239  		}
   240  	})
   241  })
   242  
   243  func secretForTest(namespace, name string) *v1.Secret {
   244  	return &v1.Secret{
   245  		ObjectMeta: metav1.ObjectMeta{
   246  			Namespace: namespace,
   247  			Name:      name,
   248  		},
   249  		Data: map[string][]byte{
   250  			"data-1": []byte("value-1\n"),
   251  			"data-2": []byte("value-2\n"),
   252  			"data-3": []byte("value-3\n"),
   253  		},
   254  	}
   255  }
   256  
   257  func createEmptyKeySecretForTest(ctx context.Context, f *framework.Framework) (*v1.Secret, error) {
   258  	secretName := "secret-emptykey-test-" + string(uuid.NewUUID())
   259  	secret := &v1.Secret{
   260  		ObjectMeta: metav1.ObjectMeta{
   261  			Namespace: f.Namespace.Name,
   262  			Name:      secretName,
   263  		},
   264  		Data: map[string][]byte{
   265  			"": []byte("value-1\n"),
   266  		},
   267  	}
   268  	ginkgo.By(fmt.Sprintf("Creating projection with secret that has name %s", secret.Name))
   269  	return f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{})
   270  }