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 }