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  }