github.com/kyma-project/kyma-environment-broker@v0.0.1/common/hyperscaler/account_pool_test.go (about)

     1  package hyperscaler
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/kyma-project/kyma-environment-broker/common/gardener"
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  	machineryv1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    13  	"k8s.io/apimachinery/pkg/runtime"
    14  	"k8s.io/apimachinery/pkg/runtime/schema"
    15  	"k8s.io/client-go/dynamic"
    16  )
    17  
    18  var (
    19  	scheme           = runtime.NewScheme()
    20  	secretBindingGVK = schema.GroupVersionKind{Group: "core.gardener.cloud", Version: "v1beta1", Kind: "SecretBinding"}
    21  	shootGVK         = schema.GroupVersionKind{Group: "core.gardener.cloud", Version: "v1beta1", Kind: "Shoot"}
    22  )
    23  
    24  const (
    25  	testNamespace = "garden-namespace"
    26  )
    27  
    28  func TestCredentialsSecretBinding(t *testing.T) {
    29  
    30  	pool := newTestAccountPool()
    31  
    32  	var testcases = []struct {
    33  		testDescription           string
    34  		tenantName                string
    35  		hyperscalerType           Type
    36  		expectedSecretBindingName string
    37  		expectedError             string
    38  	}{
    39  		{"In-use credential for tenant1, GCP returns existing secret",
    40  			"tenant1", GCP, "secretBinding1", ""},
    41  
    42  		{"In-use credential for tenant1, Azure returns existing secret",
    43  			"tenant1", Azure, "secretBinding2", ""},
    44  
    45  		{"In-use credential for tenant2, GCP returns existing secret",
    46  			"tenant2", GCP, "secretBinding3", ""},
    47  
    48  		{"Available credential for tenant3, AWS labels and returns existing secret",
    49  			"tenant3", GCP, "secretBinding4", ""},
    50  
    51  		{"Available credential for tenant4, GCP labels and returns existing secret",
    52  			"tenant4", AWS, "secretBinding5", ""},
    53  
    54  		{"There is only dirty Secret for tenant9, Azure labels and returns a new existing secret",
    55  			"tenant9", Azure, "secretBinding9", ""},
    56  
    57  		{"No Available credential for tenant5, Azure returns error",
    58  			"tenant5", Azure, "",
    59  			"failed to find unassigned secret binding for hyperscalerType: azure"},
    60  
    61  		{"No Available credential for tenant6, GCP returns error - ignore secret binding with label shared=true",
    62  			"tenant6", GCP, "",
    63  			"failed to find unassigned secret binding for hyperscalerType: gcp"},
    64  
    65  		{"Available credential for tenant7, AWS labels and returns existing secret from different namespace",
    66  			"tenant7", AWS, "secretBinding7", ""},
    67  
    68  		{"No Available credential for tenant8, AWS returns error - failed to get referenced secret",
    69  			"tenant8", AWS, "",
    70  			"failed to find unassigned secret binding for hyperscalerType: aws"},
    71  	}
    72  	for _, testcase := range testcases {
    73  
    74  		t.Run(testcase.testDescription, func(t *testing.T) {
    75  			secretBinding, err := pool.CredentialsSecretBinding(testcase.hyperscalerType, testcase.tenantName, false)
    76  			actualError := ""
    77  			if err != nil {
    78  				actualError = err.Error()
    79  				assert.Equal(t, testcase.expectedError, actualError)
    80  			} else {
    81  				assert.Equal(t, testcase.expectedSecretBindingName, secretBinding.GetName())
    82  				assert.Equal(t, string(testcase.hyperscalerType), secretBinding.GetLabels()["hyperscalerType"])
    83  				assert.Equal(t, testcase.expectedError, actualError)
    84  			}
    85  		})
    86  	}
    87  }
    88  
    89  func TestSecretsAccountPool_IsSecretBindingInternal(t *testing.T) {
    90  	for _, euAccess := range []bool{false, true} {
    91  		t.Run(fmt.Sprintf("EuAccess=%v", euAccess), func(t *testing.T) {
    92  			t.Run("should return true if internal secret binding found", func(t *testing.T) {
    93  				//given
    94  				accPool, _ := newTestAccountPoolWithSecretBindingInternal(euAccess)
    95  
    96  				//when
    97  				internal, err := accPool.IsSecretBindingInternal("azure", "tenant1", euAccess)
    98  
    99  				//then
   100  				require.NoError(t, err)
   101  				assert.True(t, internal)
   102  			})
   103  
   104  			t.Run("should return false if internal secret binding not found", func(t *testing.T) {
   105  				//given
   106  				accPool := newTestAccountPool()
   107  
   108  				//when
   109  				internal, err := accPool.IsSecretBindingInternal("azure", "tenant1", euAccess)
   110  
   111  				//then
   112  				require.NoError(t, err)
   113  				assert.False(t, internal)
   114  			})
   115  
   116  			t.Run("should return false when there is no secret binding in the pool", func(t *testing.T) {
   117  				//given
   118  				accPool := newEmptyTestAccountPool()
   119  
   120  				//when
   121  				internal, err := accPool.IsSecretBindingInternal("azure", "tenant1", euAccess)
   122  
   123  				//then
   124  				require.NoError(t, err)
   125  				assert.False(t, internal)
   126  			})
   127  		})
   128  	}
   129  }
   130  
   131  func TestSecretsAccountPool_IsSecretBindingDirty(t *testing.T) {
   132  	for _, euAccess := range []bool{false, true} {
   133  		t.Run(fmt.Sprintf("EuAccess=%v", euAccess), func(t *testing.T) {
   134  			t.Run("should return true if dirty secret binding found", func(t *testing.T) {
   135  				//given
   136  				accPool, _ := newTestAccountPoolWithSecretBindingDirty(euAccess)
   137  
   138  				//when
   139  				isdirty, err := accPool.IsSecretBindingDirty("azure", "tenant1", euAccess)
   140  
   141  				//then
   142  				require.NoError(t, err)
   143  				assert.True(t, isdirty)
   144  			})
   145  
   146  			t.Run("should return false if dirty secret binding not found", func(t *testing.T) {
   147  				//given
   148  				accPool := newTestAccountPool()
   149  
   150  				//when
   151  				isdirty, err := accPool.IsSecretBindingDirty("azure", "tenant1", euAccess)
   152  
   153  				//then
   154  				require.NoError(t, err)
   155  				assert.False(t, isdirty)
   156  			})
   157  		})
   158  	}
   159  }
   160  
   161  func TestSecretsAccountPool_IsSecretBindingUsed(t *testing.T) {
   162  	for _, euAccess := range []bool{false, true} {
   163  		t.Run(fmt.Sprintf("EuAccess=%v", euAccess), func(t *testing.T) {
   164  			t.Run("should return true when secret binding is in use", func(t *testing.T) {
   165  				//given
   166  				accPool, _ := newTestAccountPoolWithSingleShoot(euAccess)
   167  
   168  				//when
   169  				used, err := accPool.IsSecretBindingUsed("azure", "tenant1", euAccess)
   170  
   171  				//then
   172  				require.NoError(t, err)
   173  				assert.True(t, used)
   174  			})
   175  
   176  			t.Run("should return false when secret binding is not in use", func(t *testing.T) {
   177  				//given
   178  				accPool, _ := newTestAccountPoolWithoutShoots(euAccess)
   179  
   180  				//when
   181  				used, err := accPool.IsSecretBindingUsed("azure", "tenant1", euAccess)
   182  
   183  				//then
   184  				require.NoError(t, err)
   185  				assert.False(t, used)
   186  			})
   187  		})
   188  	}
   189  }
   190  
   191  func TestSecretsAccountPool_MarkSecretBindingAsDirty(t *testing.T) {
   192  	for _, euAccess := range []bool{false, true} {
   193  		t.Run(fmt.Sprintf("EuAccess=%v", euAccess), func(t *testing.T) {
   194  			t.Run("should mark secret binding as dirty", func(t *testing.T) {
   195  				//given
   196  				accPool, gardenerClient := newTestAccountPoolWithoutShoots(euAccess)
   197  
   198  				//when
   199  				err := accPool.MarkSecretBindingAsDirty("azure", "tenant1", euAccess)
   200  
   201  				//then
   202  				require.NoError(t, err)
   203  				secretBinding, err := gardenerClient.Get(context.Background(), "secretBinding1", machineryv1.GetOptions{})
   204  				require.NoError(t, err)
   205  				assert.Equal(t, secretBinding.GetLabels()["dirty"], "true")
   206  			})
   207  		})
   208  	}
   209  }
   210  
   211  func newTestAccountPool() AccountPool {
   212  	secretBinding1 := &unstructured.Unstructured{
   213  		Object: map[string]interface{}{
   214  			"metadata": map[string]interface{}{
   215  				"name":      "secretBinding1",
   216  				"namespace": testNamespace,
   217  				"labels": map[string]interface{}{
   218  					"tenantName":      "tenant1",
   219  					"hyperscalerType": "gcp",
   220  				},
   221  			},
   222  			"secretRef": map[string]interface{}{
   223  				"name":      "secret1",
   224  				"namespace": testNamespace,
   225  			},
   226  		},
   227  	}
   228  	secretBinding1.SetGroupVersionKind(secretBindingGVK)
   229  	secretBinding2 := &unstructured.Unstructured{
   230  		Object: map[string]interface{}{
   231  			"metadata": map[string]interface{}{
   232  				"name":      "secretBinding2",
   233  				"namespace": testNamespace,
   234  				"labels": map[string]interface{}{
   235  					"tenantName":      "tenant1",
   236  					"hyperscalerType": "azure",
   237  				},
   238  			},
   239  			"secretRef": map[string]interface{}{
   240  				"name":      "secret2",
   241  				"namespace": testNamespace,
   242  			},
   243  		},
   244  	}
   245  	secretBinding2.SetGroupVersionKind(secretBindingGVK)
   246  	secretBinding3 := &unstructured.Unstructured{
   247  		Object: map[string]interface{}{
   248  			"metadata": map[string]interface{}{
   249  				"name":      "secretBinding3",
   250  				"namespace": testNamespace,
   251  				"labels": map[string]interface{}{
   252  					"tenantName":      "tenant2",
   253  					"hyperscalerType": "gcp",
   254  				},
   255  			},
   256  			"secretRef": map[string]interface{}{
   257  				"name":      "secret3",
   258  				"namespace": testNamespace,
   259  			},
   260  		},
   261  	}
   262  	secretBinding3.SetGroupVersionKind(secretBindingGVK)
   263  	secretBinding4 := &unstructured.Unstructured{
   264  		Object: map[string]interface{}{
   265  			"metadata": map[string]interface{}{
   266  				"name":      "secretBinding4",
   267  				"namespace": testNamespace,
   268  				"labels": map[string]interface{}{
   269  					"hyperscalerType": "gcp",
   270  				},
   271  			},
   272  			"secretRef": map[string]interface{}{
   273  				"name":      "secret4",
   274  				"namespace": testNamespace,
   275  			},
   276  		},
   277  	}
   278  	secretBinding4.SetGroupVersionKind(secretBindingGVK)
   279  	secretBinding5 := &unstructured.Unstructured{
   280  		Object: map[string]interface{}{
   281  			"metadata": map[string]interface{}{
   282  				"name":      "secretBinding5",
   283  				"namespace": testNamespace,
   284  				"labels": map[string]interface{}{
   285  					"hyperscalerType": "aws",
   286  				},
   287  			},
   288  			"secretRef": map[string]interface{}{
   289  				"name":      "secret5",
   290  				"namespace": testNamespace,
   291  			},
   292  		},
   293  	}
   294  	secretBinding5.SetGroupVersionKind(secretBindingGVK)
   295  	secretBinding6 := &unstructured.Unstructured{
   296  		Object: map[string]interface{}{
   297  			"metadata": map[string]interface{}{
   298  				"name":      "secretBinding6",
   299  				"namespace": testNamespace,
   300  				"labels": map[string]interface{}{
   301  					"hyperscalerType": "gcp",
   302  					"shared":          "true",
   303  				},
   304  			},
   305  			"secretRef": map[string]interface{}{
   306  				"name":      "secret6",
   307  				"namespace": testNamespace,
   308  			},
   309  		},
   310  	}
   311  	secretBinding6.SetGroupVersionKind(secretBindingGVK)
   312  	secretBinding7 := &unstructured.Unstructured{
   313  		Object: map[string]interface{}{
   314  			"metadata": map[string]interface{}{
   315  				"name":      "secretBinding7",
   316  				"namespace": testNamespace,
   317  				"labels": map[string]interface{}{
   318  					"hyperscalerType": "aws",
   319  				},
   320  			},
   321  			"secretRef": map[string]interface{}{
   322  				"name":      "secret7",
   323  				"namespace": "anothernamespace",
   324  			},
   325  		},
   326  	}
   327  	secretBinding7.SetGroupVersionKind(secretBindingGVK)
   328  	secretBinding8 := &unstructured.Unstructured{
   329  		Object: map[string]interface{}{
   330  			"metadata": map[string]interface{}{
   331  				"name":      "secretBinding8",
   332  				"namespace": testNamespace,
   333  				"labels": map[string]interface{}{
   334  					"tenantName":      "tenant9",
   335  					"hyperscalerType": "azure",
   336  					"dirty":           "true",
   337  				},
   338  			},
   339  			"secretRef": map[string]interface{}{
   340  				"name":      "secret8",
   341  				"namespace": testNamespace,
   342  			},
   343  		},
   344  	}
   345  	secretBinding8.SetGroupVersionKind(secretBindingGVK)
   346  	secretBinding9 := &unstructured.Unstructured{
   347  		Object: map[string]interface{}{
   348  			"metadata": map[string]interface{}{
   349  				"name":      "secretBinding9",
   350  				"namespace": testNamespace,
   351  				"labels": map[string]interface{}{
   352  					"hyperscalerType": "azure",
   353  				},
   354  			},
   355  			"secretRef": map[string]interface{}{
   356  				"name":      "secret9",
   357  				"namespace": testNamespace,
   358  			},
   359  		},
   360  	}
   361  	secretBinding9.SetGroupVersionKind(secretBindingGVK)
   362  
   363  	gardenerFake := gardener.NewDynamicFakeClient(secretBinding1, secretBinding2, secretBinding3, secretBinding4, secretBinding5, secretBinding6, secretBinding7, secretBinding8, secretBinding9)
   364  	return NewAccountPool(gardenerFake, testNamespace)
   365  }
   366  
   367  func newTestAccountPoolWithSingleShoot(euAccess bool) (AccountPool, dynamic.ResourceInterface) {
   368  	secretBinding1 := &unstructured.Unstructured{
   369  		Object: map[string]interface{}{
   370  			"metadata": map[string]interface{}{
   371  				"name":      "secretBinding1",
   372  				"namespace": testNamespace,
   373  				"labels": map[string]interface{}{
   374  					"tenantName":      "tenant1",
   375  					"hyperscalerType": "azure",
   376  				},
   377  			},
   378  			"secretRef": map[string]interface{}{
   379  				"name":      "secret1",
   380  				"namespace": testNamespace,
   381  			},
   382  		},
   383  	}
   384  	applyEuAccess(secretBinding1, euAccess)
   385  	secretBinding1.SetGroupVersionKind(secretBindingGVK)
   386  
   387  	shoot1 := &unstructured.Unstructured{
   388  		Object: map[string]interface{}{
   389  			"metadata": map[string]interface{}{
   390  				"name":      "shoot1",
   391  				"namespace": testNamespace,
   392  			},
   393  			"spec": map[string]interface{}{
   394  				"secretBindingName": "secretBinding1",
   395  			},
   396  			"status": map[string]interface{}{
   397  				"lastOperation": map[string]interface{}{
   398  					"state": "Succeeded",
   399  					"type":  "Reconcile",
   400  				},
   401  			},
   402  		},
   403  	}
   404  	shoot1.SetGroupVersionKind(shootGVK)
   405  
   406  	gardenerFake := gardener.NewDynamicFakeClient(shoot1, secretBinding1)
   407  	return NewAccountPool(gardenerFake, testNamespace), gardenerFake.Resource(gardener.SecretBindingResource).Namespace(testNamespace)
   408  }
   409  
   410  func newEmptyTestAccountPool() AccountPool {
   411  	secretBinding1 := &unstructured.Unstructured{}
   412  	secretBinding1.SetGroupVersionKind(secretBindingGVK)
   413  	gardenerFake := gardener.NewDynamicFakeClient(secretBinding1)
   414  	return NewAccountPool(gardenerFake, testNamespace)
   415  }
   416  
   417  func applyEuAccess(obj *unstructured.Unstructured, euAccess bool) {
   418  	if euAccess {
   419  		labels := obj.GetLabels()
   420  		labels["euAccess"] = "true"
   421  		obj.SetLabels(labels)
   422  	}
   423  }
   424  
   425  func newTestAccountPoolWithSecretBindingInternal(euAccess bool) (AccountPool, dynamic.ResourceInterface) {
   426  	secretBinding1 := &unstructured.Unstructured{
   427  		Object: map[string]interface{}{
   428  			"metadata": map[string]interface{}{
   429  				"name":      "secretBinding1",
   430  				"namespace": testNamespace,
   431  				"labels": map[string]interface{}{
   432  					"tenantName":      "tenant1",
   433  					"hyperscalerType": "azure",
   434  					"internal":        "true",
   435  				},
   436  			},
   437  			"secretRef": map[string]interface{}{
   438  				"name":      "secret1",
   439  				"namespace": testNamespace,
   440  			},
   441  		},
   442  	}
   443  	applyEuAccess(secretBinding1, euAccess)
   444  	secretBinding1.SetGroupVersionKind(secretBindingGVK)
   445  
   446  	gardenerFake := gardener.NewDynamicFakeClient(secretBinding1)
   447  	return NewAccountPool(gardenerFake, testNamespace), gardenerFake.Resource(gardener.SecretBindingResource).Namespace(testNamespace)
   448  }
   449  
   450  func newTestAccountPoolWithSecretBindingDirty(euAccess bool) (AccountPool, dynamic.ResourceInterface) {
   451  	secretBinding1 := &unstructured.Unstructured{
   452  		Object: map[string]interface{}{
   453  			"metadata": map[string]interface{}{
   454  				"name":      "secretBinding1",
   455  				"namespace": testNamespace,
   456  				"labels": map[string]interface{}{
   457  					"tenantName":      "tenant1",
   458  					"hyperscalerType": "azure",
   459  					"dirty":           "true",
   460  				},
   461  			},
   462  			"secretRef": map[string]interface{}{
   463  				"name":      "secret1",
   464  				"namespace": testNamespace,
   465  			},
   466  		},
   467  	}
   468  	applyEuAccess(secretBinding1, euAccess)
   469  	secretBinding1.SetGroupVersionKind(secretBindingGVK)
   470  
   471  	shoot1 := &unstructured.Unstructured{
   472  		Object: map[string]interface{}{
   473  			"metadata": map[string]interface{}{
   474  				"name":      "shoot1",
   475  				"namespace": testNamespace,
   476  			},
   477  			"spec": map[string]interface{}{
   478  				"secretBindingName": "secretBinding1",
   479  			},
   480  			"status": map[string]interface{}{
   481  				"lastOperation": map[string]interface{}{
   482  					"state": "Succeeded",
   483  					"type":  "Reconcile",
   484  				},
   485  			},
   486  		},
   487  	}
   488  	shoot1.SetGroupVersionKind(shootGVK)
   489  
   490  	gardenerFake := gardener.NewDynamicFakeClient(shoot1, secretBinding1)
   491  	return NewAccountPool(gardenerFake, testNamespace), gardenerFake.Resource(gardener.SecretBindingResource).Namespace(testNamespace)
   492  }
   493  
   494  func newTestAccountPoolWithShootsUsingSecretBinding(euAccess bool) (AccountPool, dynamic.ResourceInterface) {
   495  	secretBinding1 := &unstructured.Unstructured{
   496  		Object: map[string]interface{}{
   497  			"metadata": map[string]interface{}{
   498  				"name":      "secretBinding1",
   499  				"namespace": testNamespace,
   500  				"labels": map[string]interface{}{
   501  					"tenantName":      "tenant1",
   502  					"hyperscalerType": "azure",
   503  				},
   504  			},
   505  			"secretRef": map[string]interface{}{
   506  				"name":      "secret1",
   507  				"namespace": testNamespace,
   508  			},
   509  		},
   510  	}
   511  	applyEuAccess(secretBinding1, euAccess)
   512  	secretBinding1.SetGroupVersionKind(secretBindingGVK)
   513  
   514  	shoot1 := &unstructured.Unstructured{
   515  		Object: map[string]interface{}{
   516  			"metadata": map[string]interface{}{
   517  				"name":      "shoot1",
   518  				"namespace": testNamespace,
   519  			},
   520  			"spec": map[string]interface{}{
   521  				"secretBindingName": "secretBinding1",
   522  			},
   523  			"status": map[string]interface{}{
   524  				"lastOperation": map[string]interface{}{
   525  					"state": "Succeeded",
   526  					"type":  "Reconcile",
   527  				},
   528  			},
   529  		},
   530  	}
   531  	shoot1.SetGroupVersionKind(shootGVK)
   532  
   533  	shoot2 := &unstructured.Unstructured{
   534  		Object: map[string]interface{}{
   535  			"metadata": map[string]interface{}{
   536  				"name":      "shoot2",
   537  				"namespace": testNamespace,
   538  			},
   539  			"spec": map[string]interface{}{
   540  				"secretBindingName": "secretBinding1",
   541  			},
   542  			"status": map[string]interface{}{
   543  				"lastOperation": map[string]interface{}{
   544  					"state": "Succeeded",
   545  					"type":  "Reconcile",
   546  				},
   547  			},
   548  		},
   549  	}
   550  	shoot2.SetGroupVersionKind(shootGVK)
   551  
   552  	gardenerFake := gardener.NewDynamicFakeClient(shoot1, shoot2, secretBinding1)
   553  	return NewAccountPool(gardenerFake, testNamespace), gardenerFake.Resource(gardener.SecretBindingResource).Namespace(testNamespace)
   554  }
   555  
   556  func newTestAccountPoolWithoutShoots(euAccess bool) (AccountPool, dynamic.ResourceInterface) {
   557  	secretBinding1 := &unstructured.Unstructured{
   558  		Object: map[string]interface{}{
   559  			"metadata": map[string]interface{}{
   560  				"name":      "secretBinding1",
   561  				"namespace": testNamespace,
   562  				"labels": map[string]interface{}{
   563  					"tenantName":      "tenant1",
   564  					"hyperscalerType": "azure",
   565  				},
   566  			},
   567  			"secretRef": map[string]interface{}{
   568  				"name":      "secret1",
   569  				"namespace": testNamespace,
   570  			},
   571  		},
   572  	}
   573  	applyEuAccess(secretBinding1, euAccess)
   574  	secretBinding1.SetGroupVersionKind(secretBindingGVK)
   575  
   576  	gardenerFake := gardener.NewDynamicFakeClient(secretBinding1)
   577  	return NewAccountPool(gardenerFake, testNamespace), gardenerFake.Resource(gardener.SecretBindingResource).Namespace(testNamespace)
   578  }