sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/scope/session_test.go (about)

     1  /*
     2  Copyright 2020 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  	"testing"
    22  
    23  	. "github.com/onsi/gomega"
    24  	"github.com/pkg/errors"
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/klog/v2/klogr"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    32  
    33  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    34  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/identity"
    35  	"sigs.k8s.io/cluster-api-provider-aws/util/system"
    36  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    37  )
    38  
    39  func TestIsClusterPermittedToUsePrincipal(t *testing.T) {
    40  	testCases := []struct {
    41  		name             string
    42  		clusterNamespace string
    43  		allowedNs        *infrav1.AllowedNamespaces
    44  		setup            func(*testing.T, client.Client)
    45  		expectedResult   bool
    46  		expectErr        bool
    47  	}{
    48  		{
    49  			name:             "All clusters are permitted to use identity if allowedNamespaces is empty",
    50  			clusterNamespace: "default",
    51  			allowedNs:        &infrav1.AllowedNamespaces{},
    52  			expectedResult:   true,
    53  			expectErr:        false,
    54  		},
    55  		{
    56  			name:             "No clusters are permitted to use identity if allowedNamespaces is nil",
    57  			clusterNamespace: "default",
    58  			allowedNs:        nil,
    59  			expectedResult:   false,
    60  			expectErr:        false,
    61  		},
    62  		{
    63  			name:             "A namespace is permitted if allowedNamespaces list has it",
    64  			clusterNamespace: "match",
    65  			allowedNs: &infrav1.AllowedNamespaces{
    66  				NamespaceList: []string{"match"},
    67  				Selector:      metav1.LabelSelector{},
    68  			},
    69  			setup: func(t *testing.T, c client.Client) {
    70  				t.Helper()
    71  
    72  				ns := &corev1.Namespace{
    73  					ObjectMeta: metav1.ObjectMeta{
    74  						Name: "match",
    75  					},
    76  				}
    77  				ns.SetGroupVersionKind(infrav1.GroupVersion.WithKind("Namespace"))
    78  				err := c.Create(context.Background(), ns)
    79  				if err != nil {
    80  					t.Fatal(err)
    81  				}
    82  			},
    83  			expectedResult: true,
    84  			expectErr:      false,
    85  		},
    86  		{
    87  			name:             "A namespace is not permitted if allowedNamespaces list does not have it",
    88  			clusterNamespace: "default",
    89  			allowedNs: &infrav1.AllowedNamespaces{
    90  				NamespaceList: []string{"nomatch"},
    91  				Selector:      metav1.LabelSelector{},
    92  			},
    93  			setup: func(t *testing.T, c client.Client) {
    94  				t.Helper()
    95  
    96  				ns := &corev1.Namespace{
    97  					ObjectMeta: metav1.ObjectMeta{
    98  						Name: "default",
    99  					},
   100  				}
   101  				ns.SetGroupVersionKind(infrav1.GroupVersion.WithKind("Namespace"))
   102  				err := c.Create(context.Background(), ns)
   103  				if err != nil {
   104  					t.Fatal(err)
   105  				}
   106  			},
   107  			expectedResult: false,
   108  			expectErr:      false,
   109  		},
   110  		{
   111  			name:             "A namespace is not permitted if allowedNamespaces list and selector do not have it",
   112  			clusterNamespace: "default",
   113  			allowedNs: &infrav1.AllowedNamespaces{
   114  				NamespaceList: []string{"nomatch"},
   115  				Selector: metav1.LabelSelector{
   116  					MatchLabels: map[string]string{"ns": "nomatchlabel"},
   117  				},
   118  			},
   119  			setup: func(t *testing.T, c client.Client) {
   120  				t.Helper()
   121  
   122  				ns := &corev1.Namespace{
   123  					ObjectMeta: metav1.ObjectMeta{
   124  						Name: "match",
   125  					},
   126  				}
   127  				ns.SetGroupVersionKind(infrav1.GroupVersion.WithKind("Namespace"))
   128  				err := c.Create(context.Background(), ns)
   129  				if err != nil {
   130  					t.Fatal(err)
   131  				}
   132  			},
   133  			expectedResult: false,
   134  			expectErr:      false,
   135  		},
   136  		{
   137  			name:             "A namespace is not permitted if allowedNamespaces list and selector do not have it",
   138  			clusterNamespace: "default",
   139  			allowedNs: &infrav1.AllowedNamespaces{
   140  				NamespaceList: nil,
   141  				Selector: metav1.LabelSelector{
   142  					MatchLabels: map[string]string{"ns": "nomatchlabel"},
   143  				},
   144  			},
   145  			setup: func(t *testing.T, c client.Client) {
   146  				t.Helper()
   147  
   148  				ns := &corev1.Namespace{
   149  					ObjectMeta: metav1.ObjectMeta{
   150  						Name: "default",
   151  					},
   152  				}
   153  				ns.SetGroupVersionKind(infrav1.GroupVersion.WithKind("Namespace"))
   154  				err := c.Create(context.Background(), ns)
   155  				if err != nil {
   156  					t.Fatal(err)
   157  				}
   158  			},
   159  			expectedResult: false,
   160  			expectErr:      false,
   161  		},
   162  		{
   163  			name:             "A namespace is permitted if allowedNamespaces list does not have it but selector matches its label",
   164  			clusterNamespace: "default",
   165  			allowedNs: &infrav1.AllowedNamespaces{
   166  				NamespaceList: []string{"noMatch"},
   167  				Selector: metav1.LabelSelector{
   168  					MatchLabels: map[string]string{"ns": "matchlabel"},
   169  				},
   170  			},
   171  			setup: func(t *testing.T, c client.Client) {
   172  				t.Helper()
   173  
   174  				ns := &corev1.Namespace{
   175  					ObjectMeta: metav1.ObjectMeta{
   176  						Name:   "default",
   177  						Labels: map[string]string{"ns": "matchlabel"},
   178  					},
   179  				}
   180  				ns.SetGroupVersionKind(infrav1.GroupVersion.WithKind("Namespace"))
   181  				err := c.Create(context.Background(), ns)
   182  				if err != nil {
   183  					t.Fatal(err)
   184  				}
   185  			},
   186  			expectedResult: true,
   187  			expectErr:      false,
   188  		},
   189  	}
   190  
   191  	for _, tc := range testCases {
   192  		t.Run(tc.name, func(t *testing.T) {
   193  			g := NewWithT(t)
   194  
   195  			scheme, err := setupScheme()
   196  			if err != nil {
   197  				t.Fatal(err)
   198  			}
   199  			k8sClient := fake.NewClientBuilder().WithScheme(scheme).Build()
   200  			if tc.setup != nil {
   201  				tc.setup(t, k8sClient)
   202  			}
   203  			result, err := isClusterPermittedToUsePrincipal(k8sClient, tc.allowedNs, tc.clusterNamespace)
   204  			if tc.expectErr {
   205  				g.Expect(err).ToNot(BeNil())
   206  			} else {
   207  				g.Expect(err).To(BeNil())
   208  			}
   209  
   210  			if tc.expectedResult != result {
   211  				t.Fatal("Did not get expected result")
   212  			}
   213  		})
   214  	}
   215  }
   216  
   217  func TestPrincipalParsing(t *testing.T) {
   218  	// Create the scope.
   219  	scheme := runtime.NewScheme()
   220  	_ = infrav1.AddToScheme(scheme)
   221  	cl := fake.NewClientBuilder().WithScheme(scheme).Build()
   222  	clusterScope, _ := NewClusterScope(ClusterScopeParams{
   223  		Client: cl,
   224  		Cluster: &clusterv1.Cluster{
   225  			ObjectMeta: metav1.ObjectMeta{
   226  				Name:      "test",
   227  				Namespace: "default",
   228  			},
   229  		},
   230  		AWSCluster: &infrav1.AWSCluster{},
   231  	},
   232  	)
   233  
   234  	testCases := []struct {
   235  		name        string
   236  		awsCluster  infrav1.AWSCluster
   237  		identityRef *corev1.ObjectReference
   238  		identity    runtime.Object
   239  		setup       func(*testing.T, client.Client)
   240  		expect      func([]identity.AWSPrincipalTypeProvider)
   241  		expectError bool
   242  	}{
   243  		{
   244  			name: "Default case - no Principal specified",
   245  			awsCluster: infrav1.AWSCluster{
   246  				ObjectMeta: metav1.ObjectMeta{
   247  					Name:      "cluster1",
   248  					Namespace: "default",
   249  				},
   250  				TypeMeta: metav1.TypeMeta{
   251  					APIVersion: infrav1.GroupVersion.String(),
   252  					Kind:       "AWSCluster",
   253  				},
   254  				Spec: infrav1.AWSClusterSpec{},
   255  			},
   256  			setup: func(t *testing.T, c client.Client) {
   257  				t.Helper()
   258  			},
   259  			expect: func(providers []identity.AWSPrincipalTypeProvider) {
   260  				if len(providers) != 0 {
   261  					t.Fatalf("Expected 0 providers, got %v", len(providers))
   262  				}
   263  			},
   264  		},
   265  		{
   266  			name: "Can get a session for a static Principal",
   267  			awsCluster: infrav1.AWSCluster{
   268  				ObjectMeta: metav1.ObjectMeta{
   269  					Name:      "cluster2",
   270  					Namespace: "default",
   271  				},
   272  				TypeMeta: metav1.TypeMeta{
   273  					APIVersion: infrav1.GroupVersion.String(),
   274  					Kind:       "AWSCluster",
   275  				},
   276  				Spec: infrav1.AWSClusterSpec{
   277  					IdentityRef: &infrav1.AWSIdentityReference{
   278  						Name: "static-identity",
   279  						Kind: infrav1.ClusterStaticIdentityKind,
   280  					},
   281  				},
   282  			},
   283  			setup: func(t *testing.T, c client.Client) {
   284  				t.Helper()
   285  
   286  				identity := &infrav1.AWSClusterStaticIdentity{
   287  					ObjectMeta: metav1.ObjectMeta{
   288  						Name: "static-identity",
   289  					},
   290  					Spec: infrav1.AWSClusterStaticIdentitySpec{
   291  						SecretRef: "static-credentials-secret",
   292  						AWSClusterIdentitySpec: infrav1.AWSClusterIdentitySpec{
   293  							AllowedNamespaces: &infrav1.AllowedNamespaces{},
   294  						},
   295  					},
   296  				}
   297  				identity.SetGroupVersionKind(infrav1.GroupVersion.WithKind("AWSClusterStaticIdentity"))
   298  				err := c.Create(context.Background(), identity)
   299  				if err != nil {
   300  					t.Fatal(err)
   301  				}
   302  
   303  				credentialsSecret := &corev1.Secret{
   304  					ObjectMeta: metav1.ObjectMeta{
   305  						Name:      "static-credentials-secret",
   306  						Namespace: system.GetManagerNamespace(),
   307  					},
   308  					Data: map[string][]byte{
   309  						"AccessKeyID":     []byte("1234567890"),
   310  						"SecretAccessKey": []byte("abcdefghijklmnop"),
   311  						"SessionToken":    []byte("asdfasdfasdf"),
   312  					},
   313  				}
   314  				credentialsSecret.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Kind: "Secret", Version: "v1"})
   315  				err = c.Create(context.Background(), credentialsSecret)
   316  				if err != nil {
   317  					t.Fatal(err)
   318  				}
   319  			},
   320  			expect: func(providers []identity.AWSPrincipalTypeProvider) {
   321  				if len(providers) != 1 {
   322  					t.Fatalf("Expected 1 provider, got %v", len(providers))
   323  				}
   324  				provider := providers[0]
   325  				p, ok := provider.(*identity.AWSStaticPrincipalTypeProvider)
   326  				if !ok {
   327  					t.Fatal("Expected providers to be of type AWSStaticPrincipalTypeProvider")
   328  				}
   329  				if p.AccessKeyID != "1234567890" {
   330  					t.Fatalf("Expected AccessKeyID to be '%s', got '%s'", "1234567890", p.AccessKeyID)
   331  				}
   332  				if p.SecretAccessKey != "abcdefghijklmnop" {
   333  					t.Fatalf("Expected SecretAccessKey to be '%s', got '%s'", "abcdefghijklmnop", p.SecretAccessKey)
   334  				}
   335  				if p.SessionToken != "asdfasdfasdf" {
   336  					t.Fatalf("Expected SessionToken to be '%s', got '%s'", "asdfasdfasdf", p.SessionToken)
   337  				}
   338  			},
   339  		},
   340  		{
   341  			name: "Can build a chain identity",
   342  			awsCluster: infrav1.AWSCluster{
   343  				ObjectMeta: metav1.ObjectMeta{
   344  					Name:      "cluster3",
   345  					Namespace: "default",
   346  				},
   347  				TypeMeta: metav1.TypeMeta{
   348  					APIVersion: infrav1.GroupVersion.String(),
   349  					Kind:       "AWSCluster",
   350  				},
   351  				Spec: infrav1.AWSClusterSpec{
   352  					IdentityRef: &infrav1.AWSIdentityReference{
   353  						Name: "role-identity",
   354  						Kind: infrav1.ClusterRoleIdentityKind,
   355  					},
   356  				},
   357  			},
   358  			setup: func(t *testing.T, c client.Client) {
   359  				t.Helper()
   360  
   361  				staticPrincipal := &infrav1.AWSClusterStaticIdentity{
   362  					ObjectMeta: metav1.ObjectMeta{
   363  						Name: "static-identity",
   364  					},
   365  					Spec: infrav1.AWSClusterStaticIdentitySpec{
   366  						SecretRef: "static-credentials-secret",
   367  						AWSClusterIdentitySpec: infrav1.AWSClusterIdentitySpec{
   368  							AllowedNamespaces: &infrav1.AllowedNamespaces{},
   369  						},
   370  					},
   371  				}
   372  				staticPrincipal.SetGroupVersionKind(infrav1.GroupVersion.WithKind("AWSClusterStaticIdentity"))
   373  				err := c.Create(context.Background(), staticPrincipal)
   374  				if err != nil {
   375  					t.Fatal(err)
   376  				}
   377  
   378  				credentialsSecret := &corev1.Secret{
   379  					ObjectMeta: metav1.ObjectMeta{
   380  						Name:      "static-credentials-secret",
   381  						Namespace: system.GetManagerNamespace(),
   382  					},
   383  					Data: map[string][]byte{
   384  						"AccessKeyID":     []byte("1234567890"),
   385  						"SecretAccessKey": []byte("abcdefghijklmnop"),
   386  						"SessionToken":    []byte("asdfasdfasdf"),
   387  					},
   388  				}
   389  				credentialsSecret.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Kind: "Secret", Version: "v1"})
   390  				err = c.Create(context.Background(), credentialsSecret)
   391  				if err != nil {
   392  					t.Fatal(err)
   393  				}
   394  
   395  				roleIdentity := &infrav1.AWSClusterRoleIdentity{
   396  					ObjectMeta: metav1.ObjectMeta{
   397  						Name: "role-identity",
   398  					},
   399  					Spec: infrav1.AWSClusterRoleIdentitySpec{
   400  						AWSRoleSpec: infrav1.AWSRoleSpec{
   401  							RoleArn:     "role-arn",
   402  							SessionName: "test-session",
   403  						},
   404  						SourceIdentityRef: &infrav1.AWSIdentityReference{
   405  							Name: "static-identity",
   406  							Kind: infrav1.ClusterStaticIdentityKind,
   407  						},
   408  						AWSClusterIdentitySpec: infrav1.AWSClusterIdentitySpec{
   409  							AllowedNamespaces: &infrav1.AllowedNamespaces{},
   410  						},
   411  					},
   412  				}
   413  				roleIdentity.SetGroupVersionKind(infrav1.GroupVersion.WithKind("AWSClusterRoleIdentity"))
   414  				err = c.Create(context.Background(), roleIdentity)
   415  				if err != nil {
   416  					t.Fatal(err)
   417  				}
   418  			},
   419  			expect: func(providers []identity.AWSPrincipalTypeProvider) {
   420  				if len(providers) != 1 {
   421  					t.Fatalf("Expected 1 providers, got %v", len(providers))
   422  				}
   423  			},
   424  		},
   425  		{
   426  			name: "Can get a session for a role Principal",
   427  			awsCluster: infrav1.AWSCluster{
   428  				ObjectMeta: metav1.ObjectMeta{
   429  					Name:      "cluster3",
   430  					Namespace: "default",
   431  				},
   432  				TypeMeta: metav1.TypeMeta{
   433  					APIVersion: infrav1.GroupVersion.String(),
   434  					Kind:       "AWSCluster",
   435  				},
   436  				Spec: infrav1.AWSClusterSpec{
   437  					IdentityRef: &infrav1.AWSIdentityReference{
   438  						Name: "role-identity",
   439  						Kind: infrav1.ClusterRoleIdentityKind,
   440  					},
   441  				},
   442  			},
   443  			setup: func(t *testing.T, c client.Client) {
   444  				t.Helper()
   445  
   446  				identity := &infrav1.AWSClusterRoleIdentity{
   447  					ObjectMeta: metav1.ObjectMeta{
   448  						Name: "role-identity",
   449  					},
   450  					Spec: infrav1.AWSClusterRoleIdentitySpec{
   451  						AWSClusterIdentitySpec: infrav1.AWSClusterIdentitySpec{
   452  							AllowedNamespaces: &infrav1.AllowedNamespaces{},
   453  						},
   454  						AWSRoleSpec: infrav1.AWSRoleSpec{
   455  							RoleArn: "role-arn",
   456  						},
   457  					},
   458  				}
   459  				identity.SetGroupVersionKind(infrav1.GroupVersion.WithKind("AWSClusterRoleIdentity"))
   460  				err := c.Create(context.Background(), identity)
   461  				if err != nil {
   462  					t.Fatal(err)
   463  				}
   464  			},
   465  			expect: func(providers []identity.AWSPrincipalTypeProvider) {
   466  				if len(providers) != 1 {
   467  					t.Fatalf("Expected 1 providers, got %v", len(providers))
   468  				}
   469  				provider := providers[0]
   470  				p, ok := provider.(*identity.AWSRolePrincipalTypeProvider)
   471  				if !ok {
   472  					t.Fatal("Expected providers to be of type AWSRolePrincipalTypeProvider")
   473  				}
   474  				if p.Principal.Spec.RoleArn != "role-arn" {
   475  					t.Fatal(errors.Errorf("Expected Role Provider ARN to be 'role-arn', got '%s'", p.Principal.Spec.RoleArn))
   476  				}
   477  			},
   478  		},
   479  	}
   480  
   481  	for _, tc := range testCases {
   482  		tc := tc
   483  		t.Run(tc.name, func(t *testing.T) {
   484  			scheme, err := setupScheme()
   485  			if err != nil {
   486  				t.Fatal(err)
   487  			}
   488  			k8sClient := fake.NewClientBuilder().WithScheme(scheme).Build()
   489  			tc.setup(t, k8sClient)
   490  			clusterScope.AWSCluster = &tc.awsCluster
   491  			providers, err := getProvidersForCluster(context.Background(), k8sClient, clusterScope, klogr.New())
   492  			if tc.expectError {
   493  				if err == nil {
   494  					t.Fatal("Expected an error but didn't get one")
   495  				}
   496  			} else {
   497  				if err != nil {
   498  					t.Fatal(err)
   499  				}
   500  				tc.expect(providers)
   501  			}
   502  		})
   503  	}
   504  }