k8s.io/kubernetes@v1.29.3/pkg/api/v1/persistentvolume/util_test.go (about)

     1  /*
     2  Copyright 2017 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 persistentvolume
    18  
    19  import (
    20  	"reflect"
    21  	"strings"
    22  	"testing"
    23  
    24  	corev1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  	api "k8s.io/kubernetes/pkg/apis/core"
    28  )
    29  
    30  func TestPVSecrets(t *testing.T) {
    31  	// Stub containing all possible secret references in a PV.
    32  	// The names of the referenced secrets match struct paths detected by reflection.
    33  	secretNamespace := "Spec.PersistentVolumeSource.AzureFile.SecretNamespace"
    34  	pvs := []*corev1.PersistentVolume{
    35  		{Spec: corev1.PersistentVolumeSpec{
    36  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
    37  			PersistentVolumeSource: corev1.PersistentVolumeSource{
    38  				AzureFile: &corev1.AzureFilePersistentVolumeSource{
    39  					SecretName: "Spec.PersistentVolumeSource.AzureFile.SecretName"}}}},
    40  		{Spec: corev1.PersistentVolumeSpec{
    41  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
    42  			PersistentVolumeSource: corev1.PersistentVolumeSource{
    43  				AzureFile: &corev1.AzureFilePersistentVolumeSource{
    44  					SecretName:      "Spec.PersistentVolumeSource.AzureFile.SecretName",
    45  					SecretNamespace: &secretNamespace}}}},
    46  		{Spec: corev1.PersistentVolumeSpec{
    47  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
    48  			PersistentVolumeSource: corev1.PersistentVolumeSource{
    49  				CephFS: &corev1.CephFSPersistentVolumeSource{
    50  					SecretRef: &corev1.SecretReference{
    51  						Name:      "Spec.PersistentVolumeSource.CephFS.SecretRef",
    52  						Namespace: "cephfs"}}}}},
    53  		{Spec: corev1.PersistentVolumeSpec{
    54  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
    55  			PersistentVolumeSource: corev1.PersistentVolumeSource{
    56  				CephFS: &corev1.CephFSPersistentVolumeSource{
    57  					SecretRef: &corev1.SecretReference{
    58  						Name: "Spec.PersistentVolumeSource.CephFS.SecretRef"}}}}},
    59  		{Spec: corev1.PersistentVolumeSpec{
    60  			PersistentVolumeSource: corev1.PersistentVolumeSource{
    61  				Cinder: &corev1.CinderPersistentVolumeSource{
    62  					SecretRef: &corev1.SecretReference{
    63  						Name:      "Spec.PersistentVolumeSource.Cinder.SecretRef",
    64  						Namespace: "cinder"}}}}},
    65  		{Spec: corev1.PersistentVolumeSpec{
    66  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
    67  			PersistentVolumeSource: corev1.PersistentVolumeSource{
    68  				FlexVolume: &corev1.FlexPersistentVolumeSource{
    69  					SecretRef: &corev1.SecretReference{
    70  						Name:      "Spec.PersistentVolumeSource.FlexVolume.SecretRef",
    71  						Namespace: "flexns"}}}}},
    72  		{Spec: corev1.PersistentVolumeSpec{
    73  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
    74  			PersistentVolumeSource: corev1.PersistentVolumeSource{
    75  				FlexVolume: &corev1.FlexPersistentVolumeSource{
    76  					SecretRef: &corev1.SecretReference{
    77  						Name: "Spec.PersistentVolumeSource.FlexVolume.SecretRef"}}}}},
    78  		{Spec: corev1.PersistentVolumeSpec{
    79  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
    80  			PersistentVolumeSource: corev1.PersistentVolumeSource{
    81  				RBD: &corev1.RBDPersistentVolumeSource{
    82  					SecretRef: &corev1.SecretReference{
    83  						Name: "Spec.PersistentVolumeSource.RBD.SecretRef"}}}}},
    84  		{Spec: corev1.PersistentVolumeSpec{
    85  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
    86  			PersistentVolumeSource: corev1.PersistentVolumeSource{
    87  				RBD: &corev1.RBDPersistentVolumeSource{
    88  					SecretRef: &corev1.SecretReference{
    89  						Name:      "Spec.PersistentVolumeSource.RBD.SecretRef",
    90  						Namespace: "rbdns"}}}}},
    91  		{Spec: corev1.PersistentVolumeSpec{
    92  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
    93  			PersistentVolumeSource: corev1.PersistentVolumeSource{
    94  				ScaleIO: &corev1.ScaleIOPersistentVolumeSource{
    95  					SecretRef: &corev1.SecretReference{
    96  						Name: "Spec.PersistentVolumeSource.ScaleIO.SecretRef"}}}}},
    97  		{Spec: corev1.PersistentVolumeSpec{
    98  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
    99  			PersistentVolumeSource: corev1.PersistentVolumeSource{
   100  				ScaleIO: &corev1.ScaleIOPersistentVolumeSource{
   101  					SecretRef: &corev1.SecretReference{
   102  						Name:      "Spec.PersistentVolumeSource.ScaleIO.SecretRef",
   103  						Namespace: "scaleions"}}}}},
   104  		{Spec: corev1.PersistentVolumeSpec{
   105  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
   106  			PersistentVolumeSource: corev1.PersistentVolumeSource{
   107  				ISCSI: &corev1.ISCSIPersistentVolumeSource{
   108  					SecretRef: &corev1.SecretReference{
   109  						Name:      "Spec.PersistentVolumeSource.ISCSI.SecretRef",
   110  						Namespace: "iscsi"}}}}},
   111  		{Spec: corev1.PersistentVolumeSpec{
   112  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
   113  			PersistentVolumeSource: corev1.PersistentVolumeSource{
   114  				ISCSI: &corev1.ISCSIPersistentVolumeSource{
   115  					SecretRef: &corev1.SecretReference{
   116  						Name: "Spec.PersistentVolumeSource.ISCSI.SecretRef"}}}}},
   117  		{Spec: corev1.PersistentVolumeSpec{
   118  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
   119  			PersistentVolumeSource: corev1.PersistentVolumeSource{
   120  				StorageOS: &corev1.StorageOSPersistentVolumeSource{
   121  					SecretRef: &corev1.ObjectReference{
   122  						Name:      "Spec.PersistentVolumeSource.StorageOS.SecretRef",
   123  						Namespace: "storageosns"}}}}},
   124  		{Spec: corev1.PersistentVolumeSpec{
   125  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
   126  			PersistentVolumeSource: corev1.PersistentVolumeSource{
   127  				CSI: &corev1.CSIPersistentVolumeSource{
   128  					ControllerPublishSecretRef: &corev1.SecretReference{
   129  						Name:      "Spec.PersistentVolumeSource.CSI.ControllerPublishSecretRef",
   130  						Namespace: "csi"}}}}},
   131  		{Spec: corev1.PersistentVolumeSpec{
   132  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
   133  			PersistentVolumeSource: corev1.PersistentVolumeSource{
   134  				CSI: &corev1.CSIPersistentVolumeSource{
   135  					NodePublishSecretRef: &corev1.SecretReference{
   136  						Name:      "Spec.PersistentVolumeSource.CSI.NodePublishSecretRef",
   137  						Namespace: "csi"}}}}},
   138  		{Spec: corev1.PersistentVolumeSpec{
   139  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
   140  			PersistentVolumeSource: corev1.PersistentVolumeSource{
   141  				CSI: &corev1.CSIPersistentVolumeSource{
   142  					NodeStageSecretRef: &corev1.SecretReference{
   143  						Name:      "Spec.PersistentVolumeSource.CSI.NodeStageSecretRef",
   144  						Namespace: "csi"}}}}},
   145  		{Spec: corev1.PersistentVolumeSpec{
   146  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
   147  			PersistentVolumeSource: corev1.PersistentVolumeSource{
   148  				CSI: &corev1.CSIPersistentVolumeSource{
   149  					ControllerExpandSecretRef: &corev1.SecretReference{
   150  						Name:      "Spec.PersistentVolumeSource.CSI.ControllerExpandSecretRef",
   151  						Namespace: "csi"}}}}},
   152  		{Spec: corev1.PersistentVolumeSpec{
   153  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
   154  			PersistentVolumeSource: corev1.PersistentVolumeSource{
   155  				CSI: &corev1.CSIPersistentVolumeSource{
   156  					NodeExpandSecretRef: &corev1.SecretReference{
   157  						Name:      "Spec.PersistentVolumeSource.CSI.NodeExpandSecretRef",
   158  						Namespace: "csi"}}}}},
   159  	}
   160  	extractedNames := sets.NewString()
   161  	extractedNamesWithNamespace := sets.NewString()
   162  
   163  	for _, pv := range pvs {
   164  		VisitPVSecretNames(pv, func(namespace, name string, kubeletVisible bool) bool {
   165  			extractedNames.Insert(name)
   166  			extractedNamesWithNamespace.Insert(namespace + "/" + name)
   167  			return true
   168  		})
   169  	}
   170  
   171  	// excludedSecretPaths holds struct paths to fields with "secret" in the name that are not actually references to secret API objects
   172  	excludedSecretPaths := sets.NewString(
   173  		"Spec.PersistentVolumeSource.CephFS.SecretFile",
   174  		"Spec.PersistentVolumeSource.AzureFile.SecretNamespace",
   175  	)
   176  	// expectedSecretPaths holds struct paths to fields with "secret" in the name that are references to secret API objects.
   177  	// every path here should be represented as an example in the PV stub above, with the secret name set to the path.
   178  	expectedSecretPaths := sets.NewString(
   179  		"Spec.PersistentVolumeSource.AzureFile.SecretName",
   180  		"Spec.PersistentVolumeSource.CephFS.SecretRef",
   181  		"Spec.PersistentVolumeSource.Cinder.SecretRef",
   182  		"Spec.PersistentVolumeSource.FlexVolume.SecretRef",
   183  		"Spec.PersistentVolumeSource.RBD.SecretRef",
   184  		"Spec.PersistentVolumeSource.ScaleIO.SecretRef",
   185  		"Spec.PersistentVolumeSource.ISCSI.SecretRef",
   186  		"Spec.PersistentVolumeSource.StorageOS.SecretRef",
   187  		"Spec.PersistentVolumeSource.CSI.ControllerPublishSecretRef",
   188  		"Spec.PersistentVolumeSource.CSI.NodePublishSecretRef",
   189  		"Spec.PersistentVolumeSource.CSI.NodeStageSecretRef",
   190  		"Spec.PersistentVolumeSource.CSI.ControllerExpandSecretRef",
   191  		"Spec.PersistentVolumeSource.CSI.NodeExpandSecretRef",
   192  	)
   193  	secretPaths := collectSecretPaths(t, nil, "", reflect.TypeOf(&api.PersistentVolume{}))
   194  	secretPaths = secretPaths.Difference(excludedSecretPaths)
   195  	if missingPaths := expectedSecretPaths.Difference(secretPaths); len(missingPaths) > 0 {
   196  		t.Logf("Missing expected secret paths:\n%s", strings.Join(missingPaths.List(), "\n"))
   197  		t.Error("Missing expected secret paths. Verify VisitPVSecretNames() is correctly finding the missing paths, then correct expectedSecretPaths")
   198  	}
   199  	if extraPaths := secretPaths.Difference(expectedSecretPaths); len(extraPaths) > 0 {
   200  		t.Logf("Extra secret paths:\n%s", strings.Join(extraPaths.List(), "\n"))
   201  		t.Error("Extra fields with 'secret' in the name found. Verify VisitPVSecretNames() is including these fields if appropriate, then correct expectedSecretPaths")
   202  	}
   203  
   204  	if missingNames := expectedSecretPaths.Difference(extractedNames); len(missingNames) > 0 {
   205  		t.Logf("Missing expected secret names:\n%s", strings.Join(missingNames.List(), "\n"))
   206  		t.Error("Missing expected secret names. Verify the PV stub above includes these references, then verify VisitPVSecretNames() is correctly finding the missing names")
   207  	}
   208  	if extraNames := extractedNames.Difference(expectedSecretPaths); len(extraNames) > 0 {
   209  		t.Logf("Extra secret names:\n%s", strings.Join(extraNames.List(), "\n"))
   210  		t.Error("Extra secret names extracted. Verify VisitPVSecretNames() is correctly extracting secret names")
   211  	}
   212  
   213  	expectedNamespacedNames := sets.NewString(
   214  		"claimrefns/Spec.PersistentVolumeSource.AzureFile.SecretName",
   215  		"Spec.PersistentVolumeSource.AzureFile.SecretNamespace/Spec.PersistentVolumeSource.AzureFile.SecretName",
   216  
   217  		"claimrefns/Spec.PersistentVolumeSource.CephFS.SecretRef",
   218  		"cephfs/Spec.PersistentVolumeSource.CephFS.SecretRef",
   219  
   220  		"cinder/Spec.PersistentVolumeSource.Cinder.SecretRef",
   221  
   222  		"claimrefns/Spec.PersistentVolumeSource.FlexVolume.SecretRef",
   223  		"flexns/Spec.PersistentVolumeSource.FlexVolume.SecretRef",
   224  
   225  		"claimrefns/Spec.PersistentVolumeSource.RBD.SecretRef",
   226  		"rbdns/Spec.PersistentVolumeSource.RBD.SecretRef",
   227  
   228  		"claimrefns/Spec.PersistentVolumeSource.ScaleIO.SecretRef",
   229  		"scaleions/Spec.PersistentVolumeSource.ScaleIO.SecretRef",
   230  
   231  		"claimrefns/Spec.PersistentVolumeSource.ISCSI.SecretRef",
   232  		"iscsi/Spec.PersistentVolumeSource.ISCSI.SecretRef",
   233  
   234  		"storageosns/Spec.PersistentVolumeSource.StorageOS.SecretRef",
   235  
   236  		"csi/Spec.PersistentVolumeSource.CSI.ControllerPublishSecretRef",
   237  		"csi/Spec.PersistentVolumeSource.CSI.NodePublishSecretRef",
   238  		"csi/Spec.PersistentVolumeSource.CSI.NodeStageSecretRef",
   239  		"csi/Spec.PersistentVolumeSource.CSI.ControllerExpandSecretRef",
   240  		"csi/Spec.PersistentVolumeSource.CSI.NodeExpandSecretRef",
   241  	)
   242  	if missingNames := expectedNamespacedNames.Difference(extractedNamesWithNamespace); len(missingNames) > 0 {
   243  		t.Logf("Missing expected namespaced names:\n%s", strings.Join(missingNames.List(), "\n"))
   244  		t.Error("Missing expected namespaced names. Verify the PV stub above includes these references, then verify VisitPVSecretNames() is correctly finding the missing names")
   245  	}
   246  	if extraNames := extractedNamesWithNamespace.Difference(expectedNamespacedNames); len(extraNames) > 0 {
   247  		t.Logf("Extra namespaced names:\n%s", strings.Join(extraNames.List(), "\n"))
   248  		t.Error("Extra namespaced names extracted. Verify VisitPVSecretNames() is correctly extracting secret names")
   249  	}
   250  
   251  	emptyPV := &corev1.PersistentVolume{
   252  		Spec: corev1.PersistentVolumeSpec{
   253  			ClaimRef: &corev1.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"},
   254  			PersistentVolumeSource: corev1.PersistentVolumeSource{
   255  				CephFS: &corev1.CephFSPersistentVolumeSource{
   256  					SecretRef: &corev1.SecretReference{
   257  						Name:      "",
   258  						Namespace: "cephfs"}}}}}
   259  	VisitPVSecretNames(emptyPV, func(namespace, name string, kubeletVisible bool) bool {
   260  		t.Fatalf("expected no empty names collected, got %q", name)
   261  		return false
   262  	})
   263  }
   264  
   265  // collectSecretPaths traverses the object, computing all the struct paths that lead to fields with "secret" in the name.
   266  func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect.Type) sets.String {
   267  	secretPaths := sets.NewString()
   268  
   269  	if tp.Kind() == reflect.Pointer {
   270  		secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...)
   271  		return secretPaths
   272  	}
   273  
   274  	if strings.Contains(strings.ToLower(name), "secret") {
   275  		secretPaths.Insert(path.String())
   276  	}
   277  
   278  	switch tp.Kind() {
   279  	case reflect.Pointer:
   280  		secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...)
   281  	case reflect.Struct:
   282  		// ObjectMeta should not have any field with the word "secret" in it;
   283  		// it contains cycles so it's easiest to just skip it.
   284  		if name == "ObjectMeta" {
   285  			break
   286  		}
   287  		for i := 0; i < tp.NumField(); i++ {
   288  			field := tp.Field(i)
   289  			secretPaths.Insert(collectSecretPaths(t, path.Child(field.Name), field.Name, field.Type).List()...)
   290  		}
   291  	case reflect.Interface:
   292  		t.Errorf("cannot find secret fields in interface{} field %s", path.String())
   293  	case reflect.Map:
   294  		secretPaths.Insert(collectSecretPaths(t, path.Key("*"), "", tp.Elem()).List()...)
   295  	case reflect.Slice:
   296  		secretPaths.Insert(collectSecretPaths(t, path.Key("*"), "", tp.Elem()).List()...)
   297  	default:
   298  		// all primitive types
   299  	}
   300  
   301  	return secretPaths
   302  }