sigs.k8s.io/cluster-api-provider-azure@v1.17.0/azure/scope/identity_test.go (about) 1 /* 2 Copyright 2021 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 scope 18 19 import ( 20 "context" 21 "encoding/base64" 22 "testing" 23 24 . "github.com/onsi/gomega" 25 corev1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 29 "sigs.k8s.io/controller-runtime/pkg/client/fake" 30 ) 31 32 func TestAllowedNamespaces(t *testing.T) { 33 g := NewWithT(t) 34 scheme := runtime.NewScheme() 35 _ = infrav1.AddToScheme(scheme) 36 _ = corev1.AddToScheme(scheme) 37 38 tests := []struct { 39 name string 40 identity *infrav1.AzureClusterIdentity 41 clusterNamespace string 42 expected bool 43 }{ 44 { 45 name: "allow any cluster namespace when empty", 46 identity: &infrav1.AzureClusterIdentity{ 47 Spec: infrav1.AzureClusterIdentitySpec{ 48 AllowedNamespaces: &infrav1.AllowedNamespaces{}, 49 }, 50 }, 51 clusterNamespace: "default", 52 expected: true, 53 }, 54 { 55 name: "no namespaces allowed when list is empty", 56 identity: &infrav1.AzureClusterIdentity{ 57 Spec: infrav1.AzureClusterIdentitySpec{ 58 AllowedNamespaces: &infrav1.AllowedNamespaces{ 59 NamespaceList: []string{}, 60 }, 61 }, 62 }, 63 clusterNamespace: "default", 64 expected: false, 65 }, 66 { 67 name: "allow cluster with namespace in list", 68 identity: &infrav1.AzureClusterIdentity{ 69 Spec: infrav1.AzureClusterIdentitySpec{ 70 AllowedNamespaces: &infrav1.AllowedNamespaces{ 71 NamespaceList: []string{"namespace24", "namespace32"}, 72 }, 73 }, 74 }, 75 clusterNamespace: "namespace24", 76 expected: true, 77 }, 78 { 79 name: "don't allow cluster with namespace not in list", 80 identity: &infrav1.AzureClusterIdentity{ 81 Spec: infrav1.AzureClusterIdentitySpec{ 82 AllowedNamespaces: &infrav1.AllowedNamespaces{ 83 NamespaceList: []string{"namespace24", "namespace32"}, 84 }, 85 }, 86 }, 87 clusterNamespace: "namespace8", 88 expected: false, 89 }, 90 { 91 name: "allow cluster when namespace has selector with matching label", 92 identity: &infrav1.AzureClusterIdentity{ 93 Spec: infrav1.AzureClusterIdentitySpec{ 94 AllowedNamespaces: &infrav1.AllowedNamespaces{ 95 Selector: &metav1.LabelSelector{ 96 MatchLabels: map[string]string{"c": "d"}, 97 }, 98 }, 99 }, 100 }, 101 clusterNamespace: "namespace8", 102 expected: true, 103 }, 104 { 105 name: "don't allow cluster when namespace has selector with different label", 106 identity: &infrav1.AzureClusterIdentity{ 107 Spec: infrav1.AzureClusterIdentitySpec{ 108 AllowedNamespaces: &infrav1.AllowedNamespaces{ 109 Selector: &metav1.LabelSelector{ 110 MatchLabels: map[string]string{"x": "y"}, 111 }, 112 }, 113 }, 114 }, 115 clusterNamespace: "namespace8", 116 expected: false, 117 }, 118 } 119 for _, tc := range tests { 120 t.Run(tc.name, func(t *testing.T) { 121 fakeNamespace := &corev1.Namespace{ 122 ObjectMeta: metav1.ObjectMeta{ 123 Name: "namespace8", 124 Labels: map[string]string{"c": "d"}, 125 }, 126 } 127 initObjects := []runtime.Object{tc.identity, fakeNamespace} 128 fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build() 129 130 actual := IsClusterNamespaceAllowed(context.TODO(), fakeClient, tc.identity.Spec.AllowedNamespaces, tc.clusterNamespace) 131 g.Expect(actual).To(Equal(tc.expected)) 132 }) 133 } 134 } 135 136 func TestHasClientSecret(t *testing.T) { 137 tests := []struct { 138 name string 139 identity *infrav1.AzureClusterIdentity 140 want bool 141 }{ 142 { 143 name: "user assigned identity", 144 identity: &infrav1.AzureClusterIdentity{ 145 Spec: infrav1.AzureClusterIdentitySpec{ 146 Type: infrav1.UserAssignedMSI, 147 }, 148 }, 149 want: false, 150 }, 151 { 152 name: "service principal with secret", 153 identity: &infrav1.AzureClusterIdentity{ 154 Spec: infrav1.AzureClusterIdentitySpec{ 155 Type: infrav1.ServicePrincipal, 156 ClientSecret: corev1.SecretReference{Name: "my-client-secret"}, 157 }, 158 }, 159 want: true, 160 }, 161 { 162 name: "service principal with certificate", 163 identity: &infrav1.AzureClusterIdentity{ 164 Spec: infrav1.AzureClusterIdentitySpec{ 165 Type: infrav1.ServicePrincipalCertificate, 166 ClientSecret: corev1.SecretReference{Name: "my-client-secret"}, 167 }, 168 }, 169 want: true, 170 }, 171 { 172 name: "manual service principal", 173 identity: &infrav1.AzureClusterIdentity{ 174 Spec: infrav1.AzureClusterIdentitySpec{ 175 Type: infrav1.ManualServicePrincipal, 176 ClientSecret: corev1.SecretReference{Name: "my-client-secret"}, 177 }, 178 }, 179 want: true, 180 }, 181 } 182 for _, tt := range tests { 183 t.Run(tt.name, func(t *testing.T) { 184 p := &AzureCredentialsProvider{ 185 Identity: tt.identity, 186 } 187 if got := p.hasClientSecret(); got != tt.want { 188 t.Errorf("AzureCredentialsProvider.hasClientSecret() = %v, want %v", got, tt.want) 189 } 190 }) 191 } 192 } 193 194 func TestGetTokenCredential(t *testing.T) { 195 g := NewWithT(t) 196 197 // Test cert data was generated with this command: 198 // openssl req -x509 -noenc -days 3650 -newkey rsa:2048 --keyout - -subj /CN=localhost | base64 199 encodedCertData := "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGpyZEVyOVAwVGFVRVMKZHNwRTZjeW8yMk5VOHloUnJiWWxWOVZIMnZXdm5Qc1RoWGN4aG5kK2NVcWRORUJzd2h3Z0ZsVVFjZy9lU1Z4dwpyciszbmgrYkZUWldQY1krMUxRWXhmcEtHc3JDWFFmQjgyTERKSVpEWDRnSFlyV2YzWjI3MmpYTjFYZUZBS3RpCndES2dEWFh1UEg3cjVsSDd2QzNSWGVBZmZxTHdRSmhaZitOb0hOdHY5TUg5SWRVa1FmbURGWnRJL0NRekNyYjYKK3ZPUzZFbVVEL1EyRk5IQnpneENndUdxZ055QmNRYnhKOVFuZytaaklGdWhHWVhKbHN5UlV0ZXh5elRSNS92MApWTks4VXNaZ1JCRmhYcXJCdi9Sb0NDRyt4VkpZdG1kMFFzcnZOekRxRzZRbmpVQjIxelZYcXpLRWtXMmdSdGpYCmN3NHZZUWVoQWdNQkFBRUNnZ0VBUzZ4dGpnMG5Bb2trMGpTK1pPcEtsa01aQUZhemEzWnZ5SGlwa0hEejRQTXQKdGw3UmI1b1FaR3ZXVDJyYkVPcnhleTdCQmk3TEhHaEl1OEV4UXAvaFJHUG9CQUVUUDdYbHlDZ2hXUGtQdEV0RQpkVS9tWHhMb04wTnN6SHVmLzJzaTdwbUg4WXFHWjZRQjB0Z3IyMnV0NjBtYksrQUpGc0VFZjRhU3BCVXNwZXBKCjI4MDBzUUhzcVBFNkw2a1lrZloyR1JSWTFWOXZVcllFT0RLWnBXek1oTjNVQTluQUtIOVBCNnh2UDJPZHlNTmgKaEtnbVVVTU5JRnR3cjhwWmxKbjYwY2YwVXJXcmM1Q3ZxUUx1YUdZbHpEZ1VRR1Y0SkVWanFtOUY2bE1mRVBVdwplTjcwTVZlMXBjTGVMcTJyR0NWV1UzZ2FraC9IdkpxbFIvc2E1NDZIZ3dLQmdRRHlmMXZreVg0dzVzYm9pNkRKCmNsNWRNVUx0TU1ScEIxT2FNRlZPSmpJOWdaSjhtQ2RSanFYZFlvNWFTMktJcXhpZTh0R0c5K1NvaHhEQVdsNHQKbFNVdERzRTQ0ZlNtSUxxQzV6SWF3TlJRbm5rdjBYOEx3bVl1MFFkN1lBakpNbExUV3lEUnNqRDlYUnE0bnNSKwptSlZ3cnQ4NWlTcFM1VUZ5cnlFelBiRmowd0tCZ1FEd1d6cmFlTjBFY2NmMWlJWW1Rc1l5K3lNRUFsSE5SNXlpCmdQWHVBaFN5YnYySlJlUmhkVWIzOWhMci9Mdkt3MFplWGlMV1htWVVHcGJ5elB5WEltMHMrUEwzTFdsNjVHVEYKbCtjZlY1d2ZBZERrazZyQWRFUEVFMnB4Tjg1Q2h5YVBZUG9ZcjBvaG1WOTdWUWNZYzVGcVkrajF0TTZSMVJEdAovZldCU2E4aU93S0JnUUNwYTFkdFdXVERqNGdxVWRyc3d1MndtRWtVNDd4bFVJd1ZMbTE2NHU2NHovemk5WDZLCjJXbUNhV2ZoSjhmWWlnanlpOXpkT2ZYVDFFRmMwZ1g0UExvelo1cVJQalFwbUxZVjNLYkIwRFRGZW1KYWlUZ0UKcERXMXdhNURnUTNDVzFsSWR1TlAvZm1DR2ZrZ1FUUXc2ak9GL1hiUmdNWkVFZzJPclZJNXRZRm9wd0tCZ0VSOQppcWpFdGg1VkdlakNqWStMaVpUdmNVdnNLVWs0dGM2c3R1ZXFtaUU2ZFc3UGhzT3F1cDFmOW9aZWoxaTVDbTFMCm45dThMSlJmKzFHV3pnZDNIT3NxeVhsYjdHbkRlVi9BNkhCSzg4YjJLb05uL01rNG1ETGdZWDEvckh2U3JVOUEKRUNSR2x2WTZFVFpBeFhQWFFzR3hWS25uYXRHdGlGUjVBS05senMwUEFvR0FhNStYK0RVcUdoOWFFNUlEM3dydgpqa2p4UTJLTEZKQ05TcThmOUdTdXZwdmdYc3RIaDZ3S29NNnZNd0lTaGpnWHVVUkg4VWI0dWhSc1dueE1pbGRGCjdFRStRYVdVOWpuQ20ySFFZQXJmWHJBV3c2REJ1ZGlTa0JxZ0tjNkhqREh1bjVmWGxZVW84VWVzTk1RT3JnN2IKYnlkUVo1LzRWLzFvU1dQRVRrN2pTcjA9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURDVENDQWZHZ0F3SUJBZ0lVRlNudEVuK1R2NkhNMnhKUmVFQ0pwSmNDN2lVd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0ZERVNNQkFHQTFVRUF3d0piRzlqWVd4b2IzTjBNQjRYRFRJME1ERXdPREU1TlRReE5Gb1hEVE0wTURFdwpOVEU1TlRReE5Gb3dGREVTTUJBR0ExVUVBd3dKYkc5allXeG9iM04wTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGCkFBT0NBUThBTUlJQkNnS0NBUUVBNDYzUksvVDlFMmxCRW5iS1JPbk1xTnRqVlBNb1VhMjJKVmZWUjlyMXI1ejcKRTRWM01ZWjNmbkZLblRSQWJNSWNJQlpWRUhJUDNrbGNjSzYvdDU0Zm14VTJWajNHUHRTMEdNWDZTaHJLd2wwSAp3Zk5pd3lTR1ExK0lCMksxbjkyZHU5bzF6ZFYzaFFDcllzQXlvQTExN2p4KzYrWlIrN3d0MFYzZ0gzNmk4RUNZCldYL2phQnpiYi9UQi9TSFZKRUg1Z3hXYlNQd2tNd3EyK3Zyemt1aEpsQS8wTmhUUndjNE1Rb0xocW9EY2dYRUcKOFNmVUo0UG1ZeUJib1JtRnlaYk1rVkxYc2NzMDBlZjc5RlRTdkZMR1lFUVJZVjZxd2IvMGFBZ2h2c1ZTV0xabgpkRUxLN3pjdzZodWtKNDFBZHRjMVY2c3loSkZ0b0ViWTEzTU9MMkVIb1FJREFRQUJvMU13VVRBZEJnTlZIUTRFCkZnUVVmcnkvS0R0YW13TWxSUXNGUGJCaHpkdjJVNWN3SHdZRFZSMGpCQmd3Rm9BVWZyeS9LRHRhbXdNbFJRc0YKUGJCaHpkdjJVNWN3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBeVlzdApWdmV3S1JScHVZUldjNFhHNlduWXBoVWR5WkxNb0lscTBzeVoxYWo2WWJxb0s5Tk1IQVlFbkN2U292NnpJWk9hCnRyaHVVY2Y5R0Z6NWUwaUoyeklsRGMzMTJJd3N2NDF4aUMvYnMxNmtFbjhZZi9TdWpFWGFzajd2bUEzSHJGV2YKd1pUSC95Rkw1YXpvL2YrbEExUTI4WXdxRnBIbWxlMHkwTzUzVXRoNHAwdG13bG51K0NyTzlmSHAza1RsYjdmRAo2bXFmazlOcnQ4dE9DNGFIWURvcXRZVWdaaHg1OHhzSE1PVGV0S2VSbHA4SE1GOW9ST3RyaXo0blltNkloVHdvCjVrMUExM1MzQmpheGtaQ3lQWENnWHNzdVhhZ05MYXNycjVRcStWZ2RiL25EaFZlaFY4K1o0SjBZbnp5OU1ac0UKSDFOMU5mTXRzQStQRXF0UFhBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" 200 certPEM, err := base64.StdEncoding.DecodeString(encodedCertData) 201 g.Expect(err).NotTo(HaveOccurred()) 202 203 tests := []struct { 204 name string 205 cluster *infrav1.AzureCluster 206 secret *corev1.Secret 207 identity *infrav1.AzureClusterIdentity 208 ActiveDirectoryAuthorityHost string 209 }{ 210 { 211 name: "workload identity", 212 cluster: &infrav1.AzureCluster{ 213 Spec: infrav1.AzureClusterSpec{ 214 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 215 IdentityRef: &corev1.ObjectReference{ 216 Kind: infrav1.AzureClusterIdentityKind, 217 }, 218 }, 219 }, 220 }, 221 identity: &infrav1.AzureClusterIdentity{ 222 Spec: infrav1.AzureClusterIdentitySpec{ 223 Type: infrav1.WorkloadIdentity, 224 ClientID: fakeClientID, 225 TenantID: fakeTenantID, 226 }, 227 }, 228 }, 229 { 230 name: "manual service principal", 231 cluster: &infrav1.AzureCluster{ 232 Spec: infrav1.AzureClusterSpec{ 233 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 234 IdentityRef: &corev1.ObjectReference{ 235 Kind: infrav1.AzureClusterIdentityKind, 236 }, 237 }, 238 }, 239 }, 240 identity: &infrav1.AzureClusterIdentity{ 241 Spec: infrav1.AzureClusterIdentitySpec{ 242 Type: infrav1.ManualServicePrincipal, 243 TenantID: fakeTenantID, 244 ClientSecret: corev1.SecretReference{ 245 Name: "test-identity-secret", 246 }, 247 }, 248 }, 249 secret: &corev1.Secret{ 250 ObjectMeta: metav1.ObjectMeta{ 251 Name: "test-identity-secret", 252 }, 253 Data: map[string][]byte{ 254 "clientSecret": []byte("fooSecret"), 255 }, 256 }, 257 ActiveDirectoryAuthorityHost: "https://login.microsoftonline.com", 258 }, 259 { 260 name: "service principal", 261 cluster: &infrav1.AzureCluster{ 262 Spec: infrav1.AzureClusterSpec{ 263 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 264 IdentityRef: &corev1.ObjectReference{ 265 Kind: infrav1.AzureClusterIdentityKind, 266 }, 267 }, 268 }, 269 }, 270 identity: &infrav1.AzureClusterIdentity{ 271 Spec: infrav1.AzureClusterIdentitySpec{ 272 Type: infrav1.ServicePrincipal, 273 TenantID: fakeTenantID, 274 ClientSecret: corev1.SecretReference{ 275 Name: "test-identity-secret", 276 }, 277 }, 278 }, 279 secret: &corev1.Secret{ 280 ObjectMeta: metav1.ObjectMeta{ 281 Name: "test-identity-secret", 282 }, 283 Data: map[string][]byte{ 284 "clientSecret": []byte("fooSecret"), 285 }, 286 }, 287 ActiveDirectoryAuthorityHost: "https://login.microsoftonline.com", 288 }, 289 { 290 name: "service principal certificate", 291 cluster: &infrav1.AzureCluster{ 292 Spec: infrav1.AzureClusterSpec{ 293 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 294 IdentityRef: &corev1.ObjectReference{ 295 Kind: infrav1.AzureClusterIdentityKind, 296 }, 297 }, 298 }, 299 }, 300 identity: &infrav1.AzureClusterIdentity{ 301 Spec: infrav1.AzureClusterIdentitySpec{ 302 Type: infrav1.ServicePrincipalCertificate, 303 TenantID: fakeTenantID, 304 ClientSecret: corev1.SecretReference{ 305 Name: "test-identity-secret", 306 }, 307 }, 308 }, 309 secret: &corev1.Secret{ 310 ObjectMeta: metav1.ObjectMeta{ 311 Name: "test-identity-secret", 312 }, 313 Data: map[string][]byte{ 314 "clientSecret": certPEM, 315 }, 316 }, 317 }, 318 { 319 name: "user-assigned identity", 320 cluster: &infrav1.AzureCluster{ 321 Spec: infrav1.AzureClusterSpec{ 322 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 323 IdentityRef: &corev1.ObjectReference{ 324 Kind: infrav1.AzureClusterIdentityKind, 325 }, 326 }, 327 }, 328 }, 329 identity: &infrav1.AzureClusterIdentity{ 330 Spec: infrav1.AzureClusterIdentitySpec{ 331 Type: infrav1.UserAssignedMSI, 332 TenantID: fakeTenantID, 333 }, 334 }, 335 }, 336 } 337 338 scheme := runtime.NewScheme() 339 _ = infrav1.AddToScheme(scheme) 340 _ = corev1.AddToScheme(scheme) 341 for _, tt := range tests { 342 tt := tt 343 t.Run(tt.name, func(t *testing.T) { 344 t.Parallel() 345 g := NewWithT(t) 346 initObjects := []runtime.Object{tt.cluster} 347 if tt.identity != nil { 348 initObjects = append(initObjects, tt.identity) 349 } 350 if tt.secret != nil { 351 initObjects = append(initObjects, tt.secret) 352 } 353 fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build() 354 provider, err := NewAzureCredentialsProvider(context.Background(), fakeClient, tt.cluster.Spec.IdentityRef, "") 355 g.Expect(err).NotTo(HaveOccurred()) 356 cred, err := provider.GetTokenCredential(context.Background(), "", tt.ActiveDirectoryAuthorityHost, "") 357 g.Expect(err).NotTo(HaveOccurred()) 358 g.Expect(cred).NotTo(BeNil()) 359 }) 360 } 361 }