...

Source file src/k8s.io/kubernetes/test/e2e/common/storage/secrets_volume.go

Documentation: k8s.io/kubernetes/test/e2e/common/storage

     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 storage
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"path"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/util/uuid"
    28  	"k8s.io/kubernetes/test/e2e/framework"
    29  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    30  	e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
    31  	imageutils "k8s.io/kubernetes/test/utils/image"
    32  	admissionapi "k8s.io/pod-security-admission/api"
    33  
    34  	"github.com/onsi/ginkgo/v2"
    35  	"github.com/onsi/gomega"
    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 Volume, default
    45  		Description: Create a secret. Create a Pod with secret volume source configured into the container. Pod MUST be able to read the secret from the mounted volume from the container runtime and the file mode of the secret MUST be -rw-r--r-- by default.
    46  	*/
    47  	framework.ConformanceIt("should be consumable from pods in volume", f.WithNodeConformance(), func(ctx context.Context) {
    48  		doSecretE2EWithoutMapping(ctx, f, nil /* default mode */, "secret-test-"+string(uuid.NewUUID()), nil, nil)
    49  	})
    50  
    51  	/*
    52  		Release: v1.9
    53  		Testname: Secrets Volume, volume mode 0400
    54  		Description: Create a secret. Create a Pod with secret volume source configured into the container with file mode set to 0x400. Pod MUST be able to read the secret from the mounted volume from the container runtime and the file mode of the secret MUST be -r-------- by default.
    55  		This test is marked LinuxOnly since Windows does not support setting specific file permissions.
    56  	*/
    57  	framework.ConformanceIt("should be consumable from pods in volume with defaultMode set [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
    58  		defaultMode := int32(0400)
    59  		doSecretE2EWithoutMapping(ctx, f, &defaultMode, "secret-test-"+string(uuid.NewUUID()), nil, nil)
    60  	})
    61  
    62  	/*
    63  		Release: v1.9
    64  		Testname: Secrets Volume, volume mode 0440, fsGroup 1001 and uid 1000
    65  		Description: Create a secret. Create a Pod with secret volume source configured into the container with file mode set to 0x440 as a non-root user with uid 1000 and fsGroup id 1001. Pod MUST be able to read the secret from the mounted volume from the container runtime and the file mode of the secret MUST be -r--r-----by default.
    66  		This test is marked LinuxOnly since Windows does not support setting specific file permissions, or running as UID / GID.
    67  	*/
    68  	framework.ConformanceIt("should be consumable from pods in volume as non-root with defaultMode and fsGroup set [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
    69  		defaultMode := int32(0440) /* setting fsGroup sets mode to at least 440 */
    70  		fsGroup := int64(1001)
    71  		doSecretE2EWithoutMapping(ctx, f, &defaultMode, "secret-test-"+string(uuid.NewUUID()), &fsGroup, &nonRootTestUserID)
    72  	})
    73  
    74  	/*
    75  		Release: v1.9
    76  		Testname: Secrets Volume, mapping
    77  		Description: Create a secret. Create a Pod with secret volume source configured into the container with a custom path. Pod MUST be able to read the secret from the mounted volume from the specified custom path. The file mode of the secret MUST be -rw-r--r-- by default.
    78  	*/
    79  	framework.ConformanceIt("should be consumable from pods in volume with mappings", f.WithNodeConformance(), func(ctx context.Context) {
    80  		doSecretE2EWithMapping(ctx, f, nil)
    81  	})
    82  
    83  	/*
    84  		Release: v1.9
    85  		Testname: Secrets Volume, mapping, volume mode 0400
    86  		Description: Create a secret. Create a Pod with secret volume source configured into the container with a custom path and file mode set to 0x400. Pod MUST be able to read the secret from the mounted volume from the specified custom path. The file mode of the secret MUST be -r--r--r--.
    87  		This test is marked LinuxOnly since Windows does not support setting specific file permissions.
    88  	*/
    89  	framework.ConformanceIt("should be consumable from pods in volume with mappings and Item Mode set [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
    90  		mode := int32(0400)
    91  		doSecretE2EWithMapping(ctx, f, &mode)
    92  	})
    93  
    94  	/*
    95  		Release: v1.12
    96  		Testname: Secrets Volume, volume mode default, secret with same name in different namespace
    97  		Description: Create a secret with same name in two namespaces. Create a Pod with secret volume source configured into the container. Pod MUST be able to read the secrets from the mounted volume from the container runtime and only secrets which are associated with namespace where pod is created. The file mode of the secret MUST be -rw-r--r-- by default.
    98  	*/
    99  	framework.ConformanceIt("should be able to mount in a volume regardless of a different secret existing with same name in different namespace", f.WithNodeConformance(), func(ctx context.Context) {
   100  		var (
   101  			namespace2  *v1.Namespace
   102  			err         error
   103  			secret2Name = "secret-test-" + string(uuid.NewUUID())
   104  		)
   105  
   106  		if namespace2, err = f.CreateNamespace(ctx, "secret-namespace", nil); err != nil {
   107  			framework.Failf("unable to create new namespace %s: %v", namespace2.Name, err)
   108  		}
   109  
   110  		secret2 := secretForTest(namespace2.Name, secret2Name)
   111  		secret2.Data = map[string][]byte{
   112  			"this_should_not_match_content_of_other_secret": []byte("similarly_this_should_not_match_content_of_other_secret\n"),
   113  		}
   114  		if secret2, err = f.ClientSet.CoreV1().Secrets(namespace2.Name).Create(ctx, secret2, metav1.CreateOptions{}); err != nil {
   115  			framework.Failf("unable to create test secret %s: %v", secret2.Name, err)
   116  		}
   117  		doSecretE2EWithoutMapping(ctx, f, nil /* default mode */, secret2.Name, nil, nil)
   118  	})
   119  
   120  	/*
   121  		Release: v1.9
   122  		Testname: Secrets Volume, mapping multiple volume paths
   123  		Description: Create a secret. Create a Pod with two secret volume sources configured into the container in to two different custom paths. Pod MUST be able to read the secret from the both the mounted volumes from the two specified custom paths.
   124  	*/
   125  	framework.ConformanceIt("should be consumable in multiple volumes in a pod", f.WithNodeConformance(), func(ctx context.Context) {
   126  		// This test ensures that the same secret can be mounted in multiple
   127  		// volumes in the same pod.  This test case exists to prevent
   128  		// regressions that break this use-case.
   129  		var (
   130  			name             = "secret-test-" + string(uuid.NewUUID())
   131  			volumeName       = "secret-volume"
   132  			volumeMountPath  = "/etc/secret-volume"
   133  			volumeName2      = "secret-volume-2"
   134  			volumeMountPath2 = "/etc/secret-volume-2"
   135  			secret           = secretForTest(f.Namespace.Name, name)
   136  		)
   137  
   138  		ginkgo.By(fmt.Sprintf("Creating secret with name %s", secret.Name))
   139  		var err error
   140  		if secret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}); err != nil {
   141  			framework.Failf("unable to create test secret %s: %v", secret.Name, err)
   142  		}
   143  
   144  		pod := &v1.Pod{
   145  			ObjectMeta: metav1.ObjectMeta{
   146  				Name: "pod-secrets-" + string(uuid.NewUUID()),
   147  			},
   148  			Spec: v1.PodSpec{
   149  				Volumes: []v1.Volume{
   150  					{
   151  						Name: volumeName,
   152  						VolumeSource: v1.VolumeSource{
   153  							Secret: &v1.SecretVolumeSource{
   154  								SecretName: name,
   155  							},
   156  						},
   157  					},
   158  					{
   159  						Name: volumeName2,
   160  						VolumeSource: v1.VolumeSource{
   161  							Secret: &v1.SecretVolumeSource{
   162  								SecretName: name,
   163  							},
   164  						},
   165  					},
   166  				},
   167  				Containers: []v1.Container{
   168  					{
   169  						Name:  "secret-volume-test",
   170  						Image: imageutils.GetE2EImage(imageutils.Agnhost),
   171  						Args: []string{
   172  							"mounttest",
   173  							"--file_content=/etc/secret-volume/data-1",
   174  							"--file_mode=/etc/secret-volume/data-1"},
   175  						VolumeMounts: []v1.VolumeMount{
   176  							{
   177  								Name:      volumeName,
   178  								MountPath: volumeMountPath,
   179  								ReadOnly:  true,
   180  							},
   181  							{
   182  								Name:      volumeName2,
   183  								MountPath: volumeMountPath2,
   184  								ReadOnly:  true,
   185  							},
   186  						},
   187  					},
   188  				},
   189  				RestartPolicy: v1.RestartPolicyNever,
   190  			},
   191  		}
   192  
   193  		fileModeRegexp := getFileModeRegex("/etc/secret-volume/data-1", nil)
   194  		e2epodoutput.TestContainerOutputRegexp(ctx, f, "consume secrets", pod, 0, []string{
   195  			"content of file \"/etc/secret-volume/data-1\": value-1",
   196  			fileModeRegexp,
   197  		})
   198  	})
   199  
   200  	/*
   201  		Release: v1.9
   202  		Testname: Secrets Volume, create, update and delete
   203  		Description: Create a Pod with three containers with secrets volume sources namely a create, update and delete container. Create Container when started MUST not have secret, update and delete containers MUST be created with a secret value. Create a secret in the create container, the Pod MUST be able to read the secret from the create container. Update the secret in the update container, Pod MUST be able to read the updated secret value. Delete the secret in the delete container. Pod MUST fail to read the secret from the delete container.
   204  	*/
   205  	framework.ConformanceIt("optional updates should be reflected in volume", f.WithNodeConformance(), func(ctx context.Context) {
   206  		podLogTimeout := e2epod.GetPodSecretUpdateTimeout(ctx, f.ClientSet)
   207  		containerTimeoutArg := fmt.Sprintf("--retry_time=%v", int(podLogTimeout.Seconds()))
   208  		trueVal := true
   209  		volumeMountPath := "/etc/secret-volumes"
   210  
   211  		deleteName := "s-test-opt-del-" + string(uuid.NewUUID())
   212  		deleteContainerName := "dels-volume-test"
   213  		deleteVolumeName := "deletes-volume"
   214  		deleteSecret := &v1.Secret{
   215  			ObjectMeta: metav1.ObjectMeta{
   216  				Namespace: f.Namespace.Name,
   217  				Name:      deleteName,
   218  			},
   219  			Data: map[string][]byte{
   220  				"data-1": []byte("value-1"),
   221  			},
   222  		}
   223  
   224  		updateName := "s-test-opt-upd-" + string(uuid.NewUUID())
   225  		updateContainerName := "upds-volume-test"
   226  		updateVolumeName := "updates-volume"
   227  		updateSecret := &v1.Secret{
   228  			ObjectMeta: metav1.ObjectMeta{
   229  				Namespace: f.Namespace.Name,
   230  				Name:      updateName,
   231  			},
   232  			Data: map[string][]byte{
   233  				"data-1": []byte("value-1"),
   234  			},
   235  		}
   236  
   237  		createName := "s-test-opt-create-" + string(uuid.NewUUID())
   238  		createContainerName := "creates-volume-test"
   239  		createVolumeName := "creates-volume"
   240  		createSecret := &v1.Secret{
   241  			ObjectMeta: metav1.ObjectMeta{
   242  				Namespace: f.Namespace.Name,
   243  				Name:      createName,
   244  			},
   245  			Data: map[string][]byte{
   246  				"data-1": []byte("value-1"),
   247  			},
   248  		}
   249  
   250  		ginkgo.By(fmt.Sprintf("Creating secret with name %s", deleteSecret.Name))
   251  		var err error
   252  		if deleteSecret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, deleteSecret, metav1.CreateOptions{}); err != nil {
   253  			framework.Failf("unable to create test secret %s: %v", deleteSecret.Name, err)
   254  		}
   255  
   256  		ginkgo.By(fmt.Sprintf("Creating secret with name %s", updateSecret.Name))
   257  		if updateSecret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, updateSecret, metav1.CreateOptions{}); err != nil {
   258  			framework.Failf("unable to create test secret %s: %v", updateSecret.Name, err)
   259  		}
   260  
   261  		pod := &v1.Pod{
   262  			ObjectMeta: metav1.ObjectMeta{
   263  				Name: "pod-secrets-" + string(uuid.NewUUID()),
   264  			},
   265  			Spec: v1.PodSpec{
   266  				Volumes: []v1.Volume{
   267  					{
   268  						Name: deleteVolumeName,
   269  						VolumeSource: v1.VolumeSource{
   270  							Secret: &v1.SecretVolumeSource{
   271  								SecretName: deleteName,
   272  								Optional:   &trueVal,
   273  							},
   274  						},
   275  					},
   276  					{
   277  						Name: updateVolumeName,
   278  						VolumeSource: v1.VolumeSource{
   279  							Secret: &v1.SecretVolumeSource{
   280  								SecretName: updateName,
   281  								Optional:   &trueVal,
   282  							},
   283  						},
   284  					},
   285  					{
   286  						Name: createVolumeName,
   287  						VolumeSource: v1.VolumeSource{
   288  							Secret: &v1.SecretVolumeSource{
   289  								SecretName: createName,
   290  								Optional:   &trueVal,
   291  							},
   292  						},
   293  					},
   294  				},
   295  				Containers: []v1.Container{
   296  					{
   297  						Name:  deleteContainerName,
   298  						Image: imageutils.GetE2EImage(imageutils.Agnhost),
   299  						Args:  []string{"mounttest", "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/secret-volumes/delete/data-1"},
   300  						VolumeMounts: []v1.VolumeMount{
   301  							{
   302  								Name:      deleteVolumeName,
   303  								MountPath: path.Join(volumeMountPath, "delete"),
   304  								ReadOnly:  true,
   305  							},
   306  						},
   307  					},
   308  					{
   309  						Name:  updateContainerName,
   310  						Image: imageutils.GetE2EImage(imageutils.Agnhost),
   311  						Args:  []string{"mounttest", "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/secret-volumes/update/data-3"},
   312  						VolumeMounts: []v1.VolumeMount{
   313  							{
   314  								Name:      updateVolumeName,
   315  								MountPath: path.Join(volumeMountPath, "update"),
   316  								ReadOnly:  true,
   317  							},
   318  						},
   319  					},
   320  					{
   321  						Name:  createContainerName,
   322  						Image: imageutils.GetE2EImage(imageutils.Agnhost),
   323  						Args:  []string{"mounttest", "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/secret-volumes/create/data-1"},
   324  						VolumeMounts: []v1.VolumeMount{
   325  							{
   326  								Name:      createVolumeName,
   327  								MountPath: path.Join(volumeMountPath, "create"),
   328  								ReadOnly:  true,
   329  							},
   330  						},
   331  					},
   332  				},
   333  				RestartPolicy: v1.RestartPolicyNever,
   334  			},
   335  		}
   336  		ginkgo.By("Creating the pod")
   337  		e2epod.NewPodClient(f).CreateSync(ctx, pod)
   338  
   339  		pollCreateLogs := func() (string, error) {
   340  			return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, createContainerName)
   341  		}
   342  		gomega.Eventually(ctx, pollCreateLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("Error reading file /etc/secret-volumes/create/data-1"))
   343  
   344  		pollUpdateLogs := func() (string, error) {
   345  			return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, updateContainerName)
   346  		}
   347  		gomega.Eventually(ctx, pollUpdateLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("Error reading file /etc/secret-volumes/update/data-3"))
   348  
   349  		pollDeleteLogs := func() (string, error) {
   350  			return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, deleteContainerName)
   351  		}
   352  		gomega.Eventually(ctx, pollDeleteLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("value-1"))
   353  
   354  		ginkgo.By(fmt.Sprintf("Deleting secret %v", deleteSecret.Name))
   355  		err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(ctx, deleteSecret.Name, metav1.DeleteOptions{})
   356  		framework.ExpectNoError(err, "Failed to delete secret %q in namespace %q", deleteSecret.Name, f.Namespace.Name)
   357  
   358  		ginkgo.By(fmt.Sprintf("Updating secret %v", updateSecret.Name))
   359  		updateSecret.ResourceVersion = "" // to force update
   360  		delete(updateSecret.Data, "data-1")
   361  		updateSecret.Data["data-3"] = []byte("value-3")
   362  		_, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Update(ctx, updateSecret, metav1.UpdateOptions{})
   363  		framework.ExpectNoError(err, "Failed to update secret %q in namespace %q", updateSecret.Name, f.Namespace.Name)
   364  
   365  		ginkgo.By(fmt.Sprintf("Creating secret with name %s", createSecret.Name))
   366  		if createSecret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, createSecret, metav1.CreateOptions{}); err != nil {
   367  			framework.Failf("unable to create test secret %s: %v", createSecret.Name, err)
   368  		}
   369  
   370  		ginkgo.By("waiting to observe update in volume")
   371  
   372  		gomega.Eventually(ctx, pollCreateLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("value-1"))
   373  		gomega.Eventually(ctx, pollUpdateLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("value-3"))
   374  		gomega.Eventually(ctx, pollDeleteLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("Error reading file /etc/secret-volumes/delete/data-1"))
   375  	})
   376  
   377  	/*
   378  		Release: v1.21
   379  		Testname: Secrets Volume, immutability
   380  		Description: Create a secret. Update it's data field, the update MUST succeed.
   381  			Mark the secret as immutable, the update MUST succeed. Try to update its data, the update MUST fail.
   382  			Try to mark the secret back as not immutable, the update MUST fail.
   383  			Try to update the secret`s metadata (labels), the update must succeed.
   384  			Try to delete the secret, the deletion must succeed.
   385  	*/
   386  	framework.ConformanceIt("should be immutable if `immutable` field is set", func(ctx context.Context) {
   387  		name := "immutable"
   388  		secret := secretForTest(f.Namespace.Name, name)
   389  
   390  		currentSecret, err := f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{})
   391  		framework.ExpectNoError(err, "Failed to create secret %q in namespace %q", secret.Name, secret.Namespace)
   392  
   393  		currentSecret.Data["data-4"] = []byte("value-4\n")
   394  		currentSecret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Update(ctx, currentSecret, metav1.UpdateOptions{})
   395  		framework.ExpectNoError(err, "Failed to update secret %q in namespace %q", secret.Name, secret.Namespace)
   396  
   397  		// Mark secret as immutable.
   398  		trueVal := true
   399  		currentSecret.Immutable = &trueVal
   400  		currentSecret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Update(ctx, currentSecret, metav1.UpdateOptions{})
   401  		framework.ExpectNoError(err, "Failed to mark secret %q in namespace %q as immutable", secret.Name, secret.Namespace)
   402  
   403  		// Ensure data can't be changed now.
   404  		currentSecret.Data["data-5"] = []byte("value-5\n")
   405  		_, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Update(ctx, currentSecret, metav1.UpdateOptions{})
   406  		if !apierrors.IsInvalid(err) {
   407  			framework.Failf("expected 'invalid' as error, got instead: %v", err)
   408  		}
   409  
   410  		// Ensure secret can't be switched from immutable to mutable.
   411  		currentSecret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Get(ctx, name, metav1.GetOptions{})
   412  		framework.ExpectNoError(err, "Failed to get secret %q in namespace %q", secret.Name, secret.Namespace)
   413  		if !*currentSecret.Immutable {
   414  			framework.Failf("currentSecret %s can be switched from immutable to mutable", currentSecret.Name)
   415  		}
   416  
   417  		falseVal := false
   418  		currentSecret.Immutable = &falseVal
   419  		_, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Update(ctx, currentSecret, metav1.UpdateOptions{})
   420  		if !apierrors.IsInvalid(err) {
   421  			framework.Failf("expected 'invalid' as error, got instead: %v", err)
   422  		}
   423  
   424  		// Ensure that metadata can be changed.
   425  		currentSecret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Get(ctx, name, metav1.GetOptions{})
   426  		framework.ExpectNoError(err, "Failed to get secret %q in namespace %q", secret.Name, secret.Namespace)
   427  		currentSecret.Labels = map[string]string{"label1": "value1"}
   428  		_, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Update(ctx, currentSecret, metav1.UpdateOptions{})
   429  		framework.ExpectNoError(err, "Failed to update secret %q in namespace %q", secret.Name, secret.Namespace)
   430  
   431  		// Ensure that immutable secret can be deleted.
   432  		err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(ctx, name, metav1.DeleteOptions{})
   433  		framework.ExpectNoError(err, "Failed to delete secret %q in namespace %q", secret.Name, secret.Namespace)
   434  	})
   435  
   436  	// The secret is in pending during volume creation until the secret objects are available
   437  	// or until mount the secret volume times out. There is no secret object defined for the pod, so it should return timeout exception unless it is marked optional.
   438  	// Slow (~5 mins)
   439  	f.It("Should fail non-optional pod creation due to secret object does not exist", f.WithSlow(), func(ctx context.Context) {
   440  		volumeMountPath := "/etc/secret-volumes"
   441  		podName := "pod-secrets-" + string(uuid.NewUUID())
   442  		pod := createNonOptionalSecretPod(ctx, f, volumeMountPath, podName)
   443  		getPod := e2epod.Get(f.ClientSet, pod)
   444  		gomega.Consistently(ctx, getPod).WithTimeout(f.Timeouts.PodStart).Should(e2epod.BeInPhase(v1.PodPending))
   445  	})
   446  
   447  	// Secret object defined for the pod, If a key is specified which is not present in the secret,
   448  	// the volume setup will error unless it is marked optional, during the pod creation.
   449  	// Slow (~5 mins)
   450  	f.It("Should fail non-optional pod creation due to the key in the secret object does not exist", f.WithSlow(), func(ctx context.Context) {
   451  		volumeMountPath := "/etc/secret-volumes"
   452  		podName := "pod-secrets-" + string(uuid.NewUUID())
   453  		pod := createNonOptionalSecretPodWithSecret(ctx, f, volumeMountPath, podName)
   454  		getPod := e2epod.Get(f.ClientSet, pod)
   455  		gomega.Consistently(ctx, getPod).WithTimeout(f.Timeouts.PodStart).Should(e2epod.BeInPhase(v1.PodPending))
   456  	})
   457  })
   458  
   459  func secretForTest(namespace, name string) *v1.Secret {
   460  	return &v1.Secret{
   461  		ObjectMeta: metav1.ObjectMeta{
   462  			Namespace: namespace,
   463  			Name:      name,
   464  		},
   465  		Data: map[string][]byte{
   466  			"data-1": []byte("value-1\n"),
   467  			"data-2": []byte("value-2\n"),
   468  			"data-3": []byte("value-3\n"),
   469  		},
   470  	}
   471  }
   472  
   473  func doSecretE2EWithoutMapping(ctx context.Context, f *framework.Framework, defaultMode *int32, secretName string,
   474  	fsGroup *int64, uid *int64) {
   475  	var (
   476  		volumeName      = "secret-volume"
   477  		volumeMountPath = "/etc/secret-volume"
   478  		secret          = secretForTest(f.Namespace.Name, secretName)
   479  	)
   480  
   481  	ginkgo.By(fmt.Sprintf("Creating secret with name %s", secret.Name))
   482  	var err error
   483  	if secret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}); err != nil {
   484  		framework.Failf("unable to create test secret %s: %v", secret.Name, err)
   485  	}
   486  
   487  	pod := &v1.Pod{
   488  		ObjectMeta: metav1.ObjectMeta{
   489  			Name:      "pod-secrets-" + string(uuid.NewUUID()),
   490  			Namespace: f.Namespace.Name,
   491  		},
   492  		Spec: v1.PodSpec{
   493  			Volumes: []v1.Volume{
   494  				{
   495  					Name: volumeName,
   496  					VolumeSource: v1.VolumeSource{
   497  						Secret: &v1.SecretVolumeSource{
   498  							SecretName: secretName,
   499  						},
   500  					},
   501  				},
   502  			},
   503  			Containers: []v1.Container{
   504  				{
   505  					Name:  "secret-volume-test",
   506  					Image: imageutils.GetE2EImage(imageutils.Agnhost),
   507  					Args: []string{
   508  						"mounttest",
   509  						"--file_content=/etc/secret-volume/data-1",
   510  						"--file_mode=/etc/secret-volume/data-1"},
   511  					VolumeMounts: []v1.VolumeMount{
   512  						{
   513  							Name:      volumeName,
   514  							MountPath: volumeMountPath,
   515  						},
   516  					},
   517  				},
   518  			},
   519  			RestartPolicy: v1.RestartPolicyNever,
   520  		},
   521  	}
   522  
   523  	if defaultMode != nil {
   524  		pod.Spec.Volumes[0].VolumeSource.Secret.DefaultMode = defaultMode
   525  	}
   526  
   527  	if fsGroup != nil || uid != nil {
   528  		pod.Spec.SecurityContext = &v1.PodSecurityContext{
   529  			FSGroup:   fsGroup,
   530  			RunAsUser: uid,
   531  		}
   532  	}
   533  
   534  	fileModeRegexp := getFileModeRegex("/etc/secret-volume/data-1", defaultMode)
   535  	expectedOutput := []string{
   536  		"content of file \"/etc/secret-volume/data-1\": value-1",
   537  		fileModeRegexp,
   538  	}
   539  
   540  	e2epodoutput.TestContainerOutputRegexp(ctx, f, "consume secrets", pod, 0, expectedOutput)
   541  }
   542  
   543  func doSecretE2EWithMapping(ctx context.Context, f *framework.Framework, mode *int32) {
   544  	var (
   545  		name            = "secret-test-map-" + string(uuid.NewUUID())
   546  		volumeName      = "secret-volume"
   547  		volumeMountPath = "/etc/secret-volume"
   548  		secret          = secretForTest(f.Namespace.Name, name)
   549  	)
   550  
   551  	ginkgo.By(fmt.Sprintf("Creating secret with name %s", secret.Name))
   552  	var err error
   553  	if secret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}); err != nil {
   554  		framework.Failf("unable to create test secret %s: %v", secret.Name, err)
   555  	}
   556  
   557  	pod := &v1.Pod{
   558  		ObjectMeta: metav1.ObjectMeta{
   559  			Name: "pod-secrets-" + string(uuid.NewUUID()),
   560  		},
   561  		Spec: v1.PodSpec{
   562  			Volumes: []v1.Volume{
   563  				{
   564  					Name: volumeName,
   565  					VolumeSource: v1.VolumeSource{
   566  						Secret: &v1.SecretVolumeSource{
   567  							SecretName: name,
   568  							Items: []v1.KeyToPath{
   569  								{
   570  									Key:  "data-1",
   571  									Path: "new-path-data-1",
   572  								},
   573  							},
   574  						},
   575  					},
   576  				},
   577  			},
   578  			Containers: []v1.Container{
   579  				{
   580  					Name:  "secret-volume-test",
   581  					Image: imageutils.GetE2EImage(imageutils.Agnhost),
   582  					Args: []string{
   583  						"mounttest",
   584  						"--file_content=/etc/secret-volume/new-path-data-1",
   585  						"--file_mode=/etc/secret-volume/new-path-data-1"},
   586  					VolumeMounts: []v1.VolumeMount{
   587  						{
   588  							Name:      volumeName,
   589  							MountPath: volumeMountPath,
   590  						},
   591  					},
   592  				},
   593  			},
   594  			RestartPolicy: v1.RestartPolicyNever,
   595  		},
   596  	}
   597  
   598  	if mode != nil {
   599  		pod.Spec.Volumes[0].VolumeSource.Secret.Items[0].Mode = mode
   600  	}
   601  
   602  	fileModeRegexp := getFileModeRegex("/etc/secret-volume/new-path-data-1", mode)
   603  	expectedOutput := []string{
   604  		"content of file \"/etc/secret-volume/new-path-data-1\": value-1",
   605  		fileModeRegexp,
   606  	}
   607  
   608  	e2epodoutput.TestContainerOutputRegexp(ctx, f, "consume secrets", pod, 0, expectedOutput)
   609  }
   610  
   611  func createNonOptionalSecretPod(ctx context.Context, f *framework.Framework, volumeMountPath, podName string) *v1.Pod {
   612  	podLogTimeout := e2epod.GetPodSecretUpdateTimeout(ctx, f.ClientSet)
   613  	containerTimeoutArg := fmt.Sprintf("--retry_time=%v", int(podLogTimeout.Seconds()))
   614  	falseValue := false
   615  
   616  	createName := "s-test-opt-create-" + string(uuid.NewUUID())
   617  	createContainerName := "creates-volume-test"
   618  	createVolumeName := "creates-volume"
   619  
   620  	// creating a pod without secret object created, by mentioning the secret volume source reference name
   621  	pod := &v1.Pod{
   622  		ObjectMeta: metav1.ObjectMeta{
   623  			Name: podName,
   624  		},
   625  		Spec: v1.PodSpec{
   626  			Volumes: []v1.Volume{
   627  				{
   628  					Name: createVolumeName,
   629  					VolumeSource: v1.VolumeSource{
   630  						Secret: &v1.SecretVolumeSource{
   631  							SecretName: createName,
   632  							Optional:   &falseValue,
   633  						},
   634  					},
   635  				},
   636  			},
   637  			Containers: []v1.Container{
   638  				{
   639  					Name:  createContainerName,
   640  					Image: imageutils.GetE2EImage(imageutils.Agnhost),
   641  					Args:  []string{"mounttest", "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/secret-volumes/create/data-1"},
   642  					VolumeMounts: []v1.VolumeMount{
   643  						{
   644  							Name:      createVolumeName,
   645  							MountPath: path.Join(volumeMountPath, "create"),
   646  							ReadOnly:  true,
   647  						},
   648  					},
   649  				},
   650  			},
   651  			RestartPolicy: v1.RestartPolicyNever,
   652  		},
   653  	}
   654  	ginkgo.By("Creating the pod")
   655  	pod = e2epod.NewPodClient(f).Create(ctx, pod)
   656  	return pod
   657  }
   658  
   659  func createNonOptionalSecretPodWithSecret(ctx context.Context, f *framework.Framework, volumeMountPath, podName string) *v1.Pod {
   660  	podLogTimeout := e2epod.GetPodSecretUpdateTimeout(ctx, f.ClientSet)
   661  	containerTimeoutArg := fmt.Sprintf("--retry_time=%v", int(podLogTimeout.Seconds()))
   662  	falseValue := false
   663  
   664  	createName := "s-test-opt-create-" + string(uuid.NewUUID())
   665  	createContainerName := "creates-volume-test"
   666  	createVolumeName := "creates-volume"
   667  
   668  	secret := secretForTest(f.Namespace.Name, createName)
   669  
   670  	ginkgo.By(fmt.Sprintf("Creating secret with name %s", secret.Name))
   671  	var err error
   672  	if secret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}); err != nil {
   673  		framework.Failf("unable to create test secret %s: %v", secret.Name, err)
   674  	}
   675  	// creating a pod with secret object, with the key which is not present in secret object.
   676  	pod := &v1.Pod{
   677  		ObjectMeta: metav1.ObjectMeta{
   678  			Name: podName,
   679  		},
   680  		Spec: v1.PodSpec{
   681  			Volumes: []v1.Volume{
   682  				{
   683  					Name: createVolumeName,
   684  					VolumeSource: v1.VolumeSource{
   685  						Secret: &v1.SecretVolumeSource{
   686  							SecretName: createName,
   687  							Items: []v1.KeyToPath{
   688  								{
   689  									Key:  "data_4",
   690  									Path: "value-4\n",
   691  								},
   692  							},
   693  							Optional: &falseValue,
   694  						},
   695  					},
   696  				},
   697  			},
   698  			Containers: []v1.Container{
   699  				{
   700  					Name:  createContainerName,
   701  					Image: imageutils.GetE2EImage(imageutils.Agnhost),
   702  					Args:  []string{"mounttest", "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/secret-volumes/create/data-1"},
   703  					VolumeMounts: []v1.VolumeMount{
   704  						{
   705  							Name:      createVolumeName,
   706  							MountPath: path.Join(volumeMountPath, "create"),
   707  							ReadOnly:  true,
   708  						},
   709  					},
   710  				},
   711  			},
   712  			RestartPolicy: v1.RestartPolicyNever,
   713  		},
   714  	}
   715  	ginkgo.By("Creating the pod")
   716  	pod = e2epod.NewPodClient(f).Create(ctx, pod)
   717  	return pod
   718  }
   719  

View as plain text