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 }