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  }