sigs.k8s.io/cluster-api-provider-azure@v1.14.3/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 ResourceID: "my-resource-id", 148 }, 149 }, 150 want: false, 151 }, 152 { 153 name: "service principal with secret", 154 identity: &infrav1.AzureClusterIdentity{ 155 Spec: infrav1.AzureClusterIdentitySpec{ 156 Type: infrav1.ServicePrincipal, 157 ClientSecret: corev1.SecretReference{Name: "my-client-secret"}, 158 }, 159 }, 160 want: true, 161 }, 162 { 163 name: "service principal with certificate", 164 identity: &infrav1.AzureClusterIdentity{ 165 Spec: infrav1.AzureClusterIdentitySpec{ 166 Type: infrav1.ServicePrincipalCertificate, 167 ClientSecret: corev1.SecretReference{Name: "my-client-secret"}, 168 }, 169 }, 170 want: true, 171 }, 172 { 173 name: "manual service principal", 174 identity: &infrav1.AzureClusterIdentity{ 175 Spec: infrav1.AzureClusterIdentitySpec{ 176 Type: infrav1.ManualServicePrincipal, 177 ClientSecret: corev1.SecretReference{Name: "my-client-secret"}, 178 }, 179 }, 180 want: true, 181 }, 182 } 183 for _, tt := range tests { 184 t.Run(tt.name, func(t *testing.T) { 185 p := &AzureCredentialsProvider{ 186 Identity: tt.identity, 187 } 188 if got := p.hasClientSecret(); got != tt.want { 189 t.Errorf("AzureCredentialsProvider.hasClientSecret() = %v, want %v", got, tt.want) 190 } 191 }) 192 } 193 } 194 195 func TestGetTokenCredential(t *testing.T) { 196 g := NewWithT(t) 197 198 // Test cert data was generated with this command: 199 // openssl req -x509 -noenc -days 3650 -newkey rsa:2048 --keyout - -subj /CN=localhost | base64 200 encodedCertData := "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGpyZEVyOVAwVGFVRVMKZHNwRTZjeW8yMk5VOHloUnJiWWxWOVZIMnZXdm5Qc1RoWGN4aG5kK2NVcWRORUJzd2h3Z0ZsVVFjZy9lU1Z4dwpyciszbmgrYkZUWldQY1krMUxRWXhmcEtHc3JDWFFmQjgyTERKSVpEWDRnSFlyV2YzWjI3MmpYTjFYZUZBS3RpCndES2dEWFh1UEg3cjVsSDd2QzNSWGVBZmZxTHdRSmhaZitOb0hOdHY5TUg5SWRVa1FmbURGWnRJL0NRekNyYjYKK3ZPUzZFbVVEL1EyRk5IQnpneENndUdxZ055QmNRYnhKOVFuZytaaklGdWhHWVhKbHN5UlV0ZXh5elRSNS92MApWTks4VXNaZ1JCRmhYcXJCdi9Sb0NDRyt4VkpZdG1kMFFzcnZOekRxRzZRbmpVQjIxelZYcXpLRWtXMmdSdGpYCmN3NHZZUWVoQWdNQkFBRUNnZ0VBUzZ4dGpnMG5Bb2trMGpTK1pPcEtsa01aQUZhemEzWnZ5SGlwa0hEejRQTXQKdGw3UmI1b1FaR3ZXVDJyYkVPcnhleTdCQmk3TEhHaEl1OEV4UXAvaFJHUG9CQUVUUDdYbHlDZ2hXUGtQdEV0RQpkVS9tWHhMb04wTnN6SHVmLzJzaTdwbUg4WXFHWjZRQjB0Z3IyMnV0NjBtYksrQUpGc0VFZjRhU3BCVXNwZXBKCjI4MDBzUUhzcVBFNkw2a1lrZloyR1JSWTFWOXZVcllFT0RLWnBXek1oTjNVQTluQUtIOVBCNnh2UDJPZHlNTmgKaEtnbVVVTU5JRnR3cjhwWmxKbjYwY2YwVXJXcmM1Q3ZxUUx1YUdZbHpEZ1VRR1Y0SkVWanFtOUY2bE1mRVBVdwplTjcwTVZlMXBjTGVMcTJyR0NWV1UzZ2FraC9IdkpxbFIvc2E1NDZIZ3dLQmdRRHlmMXZreVg0dzVzYm9pNkRKCmNsNWRNVUx0TU1ScEIxT2FNRlZPSmpJOWdaSjhtQ2RSanFYZFlvNWFTMktJcXhpZTh0R0c5K1NvaHhEQVdsNHQKbFNVdERzRTQ0ZlNtSUxxQzV6SWF3TlJRbm5rdjBYOEx3bVl1MFFkN1lBakpNbExUV3lEUnNqRDlYUnE0bnNSKwptSlZ3cnQ4NWlTcFM1VUZ5cnlFelBiRmowd0tCZ1FEd1d6cmFlTjBFY2NmMWlJWW1Rc1l5K3lNRUFsSE5SNXlpCmdQWHVBaFN5YnYySlJlUmhkVWIzOWhMci9Mdkt3MFplWGlMV1htWVVHcGJ5elB5WEltMHMrUEwzTFdsNjVHVEYKbCtjZlY1d2ZBZERrazZyQWRFUEVFMnB4Tjg1Q2h5YVBZUG9ZcjBvaG1WOTdWUWNZYzVGcVkrajF0TTZSMVJEdAovZldCU2E4aU93S0JnUUNwYTFkdFdXVERqNGdxVWRyc3d1MndtRWtVNDd4bFVJd1ZMbTE2NHU2NHovemk5WDZLCjJXbUNhV2ZoSjhmWWlnanlpOXpkT2ZYVDFFRmMwZ1g0UExvelo1cVJQalFwbUxZVjNLYkIwRFRGZW1KYWlUZ0UKcERXMXdhNURnUTNDVzFsSWR1TlAvZm1DR2ZrZ1FUUXc2ak9GL1hiUmdNWkVFZzJPclZJNXRZRm9wd0tCZ0VSOQppcWpFdGg1VkdlakNqWStMaVpUdmNVdnNLVWs0dGM2c3R1ZXFtaUU2ZFc3UGhzT3F1cDFmOW9aZWoxaTVDbTFMCm45dThMSlJmKzFHV3pnZDNIT3NxeVhsYjdHbkRlVi9BNkhCSzg4YjJLb05uL01rNG1ETGdZWDEvckh2U3JVOUEKRUNSR2x2WTZFVFpBeFhQWFFzR3hWS25uYXRHdGlGUjVBS05senMwUEFvR0FhNStYK0RVcUdoOWFFNUlEM3dydgpqa2p4UTJLTEZKQ05TcThmOUdTdXZwdmdYc3RIaDZ3S29NNnZNd0lTaGpnWHVVUkg4VWI0dWhSc1dueE1pbGRGCjdFRStRYVdVOWpuQ20ySFFZQXJmWHJBV3c2REJ1ZGlTa0JxZ0tjNkhqREh1bjVmWGxZVW84VWVzTk1RT3JnN2IKYnlkUVo1LzRWLzFvU1dQRVRrN2pTcjA9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURDVENDQWZHZ0F3SUJBZ0lVRlNudEVuK1R2NkhNMnhKUmVFQ0pwSmNDN2lVd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0ZERVNNQkFHQTFVRUF3d0piRzlqWVd4b2IzTjBNQjRYRFRJME1ERXdPREU1TlRReE5Gb1hEVE0wTURFdwpOVEU1TlRReE5Gb3dGREVTTUJBR0ExVUVBd3dKYkc5allXeG9iM04wTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGCkFBT0NBUThBTUlJQkNnS0NBUUVBNDYzUksvVDlFMmxCRW5iS1JPbk1xTnRqVlBNb1VhMjJKVmZWUjlyMXI1ejcKRTRWM01ZWjNmbkZLblRSQWJNSWNJQlpWRUhJUDNrbGNjSzYvdDU0Zm14VTJWajNHUHRTMEdNWDZTaHJLd2wwSAp3Zk5pd3lTR1ExK0lCMksxbjkyZHU5bzF6ZFYzaFFDcllzQXlvQTExN2p4KzYrWlIrN3d0MFYzZ0gzNmk4RUNZCldYL2phQnpiYi9UQi9TSFZKRUg1Z3hXYlNQd2tNd3EyK3Zyemt1aEpsQS8wTmhUUndjNE1Rb0xocW9EY2dYRUcKOFNmVUo0UG1ZeUJib1JtRnlaYk1rVkxYc2NzMDBlZjc5RlRTdkZMR1lFUVJZVjZxd2IvMGFBZ2h2c1ZTV0xabgpkRUxLN3pjdzZodWtKNDFBZHRjMVY2c3loSkZ0b0ViWTEzTU9MMkVIb1FJREFRQUJvMU13VVRBZEJnTlZIUTRFCkZnUVVmcnkvS0R0YW13TWxSUXNGUGJCaHpkdjJVNWN3SHdZRFZSMGpCQmd3Rm9BVWZyeS9LRHRhbXdNbFJRc0YKUGJCaHpkdjJVNWN3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBeVlzdApWdmV3S1JScHVZUldjNFhHNlduWXBoVWR5WkxNb0lscTBzeVoxYWo2WWJxb0s5Tk1IQVlFbkN2U292NnpJWk9hCnRyaHVVY2Y5R0Z6NWUwaUoyeklsRGMzMTJJd3N2NDF4aUMvYnMxNmtFbjhZZi9TdWpFWGFzajd2bUEzSHJGV2YKd1pUSC95Rkw1YXpvL2YrbEExUTI4WXdxRnBIbWxlMHkwTzUzVXRoNHAwdG13bG51K0NyTzlmSHAza1RsYjdmRAo2bXFmazlOcnQ4dE9DNGFIWURvcXRZVWdaaHg1OHhzSE1PVGV0S2VSbHA4SE1GOW9ST3RyaXo0blltNkloVHdvCjVrMUExM1MzQmpheGtaQ3lQWENnWHNzdVhhZ05MYXNycjVRcStWZ2RiL25EaFZlaFY4K1o0SjBZbnp5OU1ac0UKSDFOMU5mTXRzQStQRXF0UFhBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" 201 certPEM, err := base64.StdEncoding.DecodeString(encodedCertData) 202 g.Expect(err).NotTo(HaveOccurred()) 203 204 tests := []struct { 205 name string 206 cluster *infrav1.AzureCluster 207 secret *corev1.Secret 208 identity *infrav1.AzureClusterIdentity 209 ActiveDirectoryAuthorityHost string 210 }{ 211 { 212 name: "workload identity", 213 cluster: &infrav1.AzureCluster{ 214 Spec: infrav1.AzureClusterSpec{ 215 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 216 IdentityRef: &corev1.ObjectReference{ 217 Kind: infrav1.AzureClusterIdentityKind, 218 }, 219 }, 220 }, 221 }, 222 identity: &infrav1.AzureClusterIdentity{ 223 Spec: infrav1.AzureClusterIdentitySpec{ 224 Type: infrav1.WorkloadIdentity, 225 ClientID: fakeClientID, 226 TenantID: fakeTenantID, 227 }, 228 }, 229 }, 230 { 231 name: "manual service principal", 232 cluster: &infrav1.AzureCluster{ 233 Spec: infrav1.AzureClusterSpec{ 234 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 235 IdentityRef: &corev1.ObjectReference{ 236 Kind: infrav1.AzureClusterIdentityKind, 237 }, 238 }, 239 }, 240 }, 241 identity: &infrav1.AzureClusterIdentity{ 242 Spec: infrav1.AzureClusterIdentitySpec{ 243 Type: infrav1.ManualServicePrincipal, 244 TenantID: fakeTenantID, 245 ClientSecret: corev1.SecretReference{ 246 Name: "test-identity-secret", 247 }, 248 }, 249 }, 250 secret: &corev1.Secret{ 251 ObjectMeta: metav1.ObjectMeta{ 252 Name: "test-identity-secret", 253 }, 254 Data: map[string][]byte{ 255 "clientSecret": []byte("fooSecret"), 256 }, 257 }, 258 ActiveDirectoryAuthorityHost: "https://login.microsoftonline.com", 259 }, 260 { 261 name: "service principal", 262 cluster: &infrav1.AzureCluster{ 263 Spec: infrav1.AzureClusterSpec{ 264 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 265 IdentityRef: &corev1.ObjectReference{ 266 Kind: infrav1.AzureClusterIdentityKind, 267 }, 268 }, 269 }, 270 }, 271 identity: &infrav1.AzureClusterIdentity{ 272 Spec: infrav1.AzureClusterIdentitySpec{ 273 Type: infrav1.ServicePrincipal, 274 TenantID: fakeTenantID, 275 ClientSecret: corev1.SecretReference{ 276 Name: "test-identity-secret", 277 }, 278 }, 279 }, 280 secret: &corev1.Secret{ 281 ObjectMeta: metav1.ObjectMeta{ 282 Name: "test-identity-secret", 283 }, 284 Data: map[string][]byte{ 285 "clientSecret": []byte("fooSecret"), 286 }, 287 }, 288 ActiveDirectoryAuthorityHost: "https://login.microsoftonline.com", 289 }, 290 { 291 name: "service principal certificate", 292 cluster: &infrav1.AzureCluster{ 293 Spec: infrav1.AzureClusterSpec{ 294 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 295 IdentityRef: &corev1.ObjectReference{ 296 Kind: infrav1.AzureClusterIdentityKind, 297 }, 298 }, 299 }, 300 }, 301 identity: &infrav1.AzureClusterIdentity{ 302 Spec: infrav1.AzureClusterIdentitySpec{ 303 Type: infrav1.ServicePrincipalCertificate, 304 TenantID: fakeTenantID, 305 ClientSecret: corev1.SecretReference{ 306 Name: "test-identity-secret", 307 }, 308 }, 309 }, 310 secret: &corev1.Secret{ 311 ObjectMeta: metav1.ObjectMeta{ 312 Name: "test-identity-secret", 313 }, 314 Data: map[string][]byte{ 315 "clientSecret": certPEM, 316 }, 317 }, 318 }, 319 { 320 name: "user-assigned identity", 321 cluster: &infrav1.AzureCluster{ 322 Spec: infrav1.AzureClusterSpec{ 323 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 324 IdentityRef: &corev1.ObjectReference{ 325 Kind: infrav1.AzureClusterIdentityKind, 326 }, 327 }, 328 }, 329 }, 330 identity: &infrav1.AzureClusterIdentity{ 331 Spec: infrav1.AzureClusterIdentitySpec{ 332 Type: infrav1.UserAssignedMSI, 333 TenantID: fakeTenantID, 334 }, 335 }, 336 }, 337 } 338 339 scheme := runtime.NewScheme() 340 _ = infrav1.AddToScheme(scheme) 341 _ = corev1.AddToScheme(scheme) 342 for _, tt := range tests { 343 tt := tt 344 t.Run(tt.name, func(t *testing.T) { 345 t.Parallel() 346 g := NewWithT(t) 347 initObjects := []runtime.Object{tt.cluster} 348 if tt.identity != nil { 349 initObjects = append(initObjects, tt.identity) 350 } 351 if tt.secret != nil { 352 initObjects = append(initObjects, tt.secret) 353 } 354 fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build() 355 provider, err := NewAzureClusterCredentialsProvider(context.Background(), fakeClient, tt.cluster) 356 g.Expect(err).NotTo(HaveOccurred()) 357 cred, err := provider.GetTokenCredential(context.Background(), "", tt.ActiveDirectoryAuthorityHost, "") 358 g.Expect(err).NotTo(HaveOccurred()) 359 g.Expect(cred).NotTo(BeNil()) 360 }) 361 } 362 }