github.com/Axway/agent-sdk@v1.1.101/pkg/agent/handler/credential_test.go (about) 1 package handler 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/rand" 7 "crypto/rsa" 8 "crypto/sha256" 9 "crypto/x509" 10 "encoding/base64" 11 "encoding/json" 12 "encoding/pem" 13 "fmt" 14 "net/http" 15 "os" 16 "strings" 17 "testing" 18 "time" 19 20 apiv1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1" 21 management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1" 22 defs "github.com/Axway/agent-sdk/pkg/apic/definitions" 23 prov "github.com/Axway/agent-sdk/pkg/apic/provisioning" 24 "github.com/Axway/agent-sdk/pkg/apic/provisioning/mock" 25 "github.com/Axway/agent-sdk/pkg/authz/oauth" 26 "github.com/Axway/agent-sdk/pkg/config" 27 "github.com/Axway/agent-sdk/pkg/util" 28 "github.com/Axway/agent-sdk/pkg/util/log" 29 "github.com/Axway/agent-sdk/pkg/watchmanager/proto" 30 "github.com/stretchr/testify/assert" 31 ) 32 33 func TestCredentialHandler(t *testing.T) { 34 crdRI, _ := crd.AsInstance() 35 36 tests := []struct { 37 action proto.Event_Type 38 expectedProvType string 39 getAppErr error 40 getCrdErr error 41 hasError bool 42 isRenew bool 43 inboundStatus string 44 inboundState prov.CredentialAction 45 name string 46 outboundStatus string 47 subError error 48 appStatus string 49 }{ 50 { 51 action: proto.Event_CREATED, 52 expectedProvType: provision, 53 inboundStatus: prov.Pending.String(), 54 name: "should handle a create event for a Credential when status is pending", 55 outboundStatus: prov.Success.String(), 56 }, 57 { 58 action: proto.Event_UPDATED, 59 expectedProvType: provision, 60 inboundStatus: prov.Pending.String(), 61 name: "should handle an update event for a Credential when status is pending", 62 outboundStatus: prov.Success.String(), 63 }, 64 { 65 action: proto.Event_CREATED, 66 inboundStatus: prov.Pending.String(), 67 name: "should return nil with the appStatus is not success", 68 outboundStatus: prov.Error.String(), 69 appStatus: prov.Error.String(), 70 }, 71 { 72 action: proto.Event_SUBRESOURCEUPDATED, 73 name: "should return nil when the event is for subresources", 74 }, 75 { 76 action: proto.Event_UPDATED, 77 inboundStatus: prov.Error.String(), 78 name: "should return nil and not process anything when the Credential status is set to Error", 79 }, 80 { 81 action: proto.Event_UPDATED, 82 inboundStatus: prov.Success.String(), 83 name: "should return nil and not process anything when the Credential status is set to Success", 84 }, 85 { 86 action: proto.Event_CREATED, 87 getAppErr: fmt.Errorf("error getting managed app"), 88 inboundStatus: prov.Pending.String(), 89 name: "should handle an error when retrieving the managed app, and set a failed status", 90 outboundStatus: prov.Error.String(), 91 }, 92 { 93 action: proto.Event_CREATED, 94 getCrdErr: fmt.Errorf("error getting credential request definition"), 95 inboundStatus: prov.Pending.String(), 96 name: "should handle an error when retrieving the credential request definition, and set a failed status", 97 outboundStatus: prov.Error.String(), 98 }, 99 { 100 action: proto.Event_CREATED, 101 expectedProvType: provision, 102 hasError: true, 103 inboundStatus: prov.Pending.String(), 104 name: "should handle an error when updating the Credential subresources", 105 outboundStatus: prov.Success.String(), 106 subError: fmt.Errorf("error updating subresources"), 107 }, 108 { 109 action: proto.Event_CREATED, 110 name: "should return nil and not process anything when the status field is empty", 111 }, 112 } 113 114 for _, tc := range tests { 115 t.Run(tc.name, func(t *testing.T) { 116 credApp.SubResources["status"].(map[string]interface{})["level"] = prov.Success.String() 117 if tc.appStatus != "" { 118 credApp.SubResources["status"].(map[string]interface{})["level"] = tc.appStatus 119 } 120 121 cred := credential 122 cred.Status.Level = tc.inboundStatus 123 cred.Spec.State.Name = tc.inboundState.String() 124 if tc.inboundState.String() == "" { 125 cred.Spec.State.Name = apiv1.Active 126 } 127 128 p := &mockCredProv{ 129 t: t, 130 expectedStatus: mock.MockRequestStatus{ 131 Status: prov.Success, 132 Msg: "msg", 133 Properties: map[string]string{ 134 "status_key": "status_val", 135 }, 136 }, 137 expectedAppDetails: util.GetAgentDetails(credApp), 138 expectedCredDetails: util.GetAgentDetails(&cred), 139 expectedManagedApp: credAppRefName, 140 expectedCredType: cred.Spec.CredentialRequestDefinition, 141 } 142 143 c := &credClient{ 144 t: t, 145 expectedStatus: tc.outboundStatus, 146 managedApp: credApp, 147 crd: crdRI, 148 getAppErr: tc.getAppErr, 149 getCrdErr: tc.getCrdErr, 150 subError: tc.subError, 151 } 152 153 handler := NewCredentialHandler(p, c, nil) 154 v := handler.(*credentials) 155 v.encryptSchema = func(_, _ map[string]interface{}, _, _, _ string) (map[string]interface{}, error) { 156 return map[string]interface{}{}, nil 157 } 158 159 ri, _ := cred.AsInstance() 160 err := handler.Handle(NewEventContext(tc.action, nil, ri.Kind, ri.Name), nil, ri) 161 assert.Equal(t, tc.expectedProvType, p.expectedProvType) 162 163 if tc.hasError { 164 assert.Error(t, err) 165 } else { 166 assert.Nil(t, err) 167 } 168 }) 169 } 170 } 171 172 func TestCredentialHandler_deleting(t *testing.T) { 173 crdRI, _ := crd.AsInstance() 174 175 tests := []struct { 176 name string 177 outboundStatus prov.Status 178 resourceState string 179 provStatus string 180 expectedProvType string 181 specState string 182 specStateReason string 183 skipFinalizers bool 184 }{ 185 { 186 name: "should deprovision with no error", 187 outboundStatus: prov.Success, 188 expectedProvType: deprovision, 189 resourceState: apiv1.ResourceDeleting, 190 provStatus: prov.Success.String(), 191 }, 192 { 193 name: "should deprovision expired with no error and not Deleting", 194 expectedProvType: deprovision, 195 outboundStatus: prov.Success, 196 provStatus: prov.Pending.String(), 197 specState: apiv1.Inactive, 198 specStateReason: prov.CredExpDetail, 199 }, 200 { 201 name: "should not deprovision when error and not Deleting", 202 outboundStatus: prov.Success, 203 provStatus: prov.Error.String(), 204 }, 205 { 206 name: "should deprovision when and Deleting", 207 outboundStatus: prov.Success, 208 provStatus: prov.Error.String(), 209 resourceState: apiv1.ResourceDeleting, 210 expectedProvType: deprovision, 211 }, 212 { 213 name: "should fail to deprovision and set the status to error", 214 outboundStatus: prov.Error, 215 expectedProvType: deprovision, 216 resourceState: apiv1.ResourceDeleting, 217 provStatus: prov.Success.String(), 218 }, 219 { 220 name: "should not deprovision with no agent finalizers", 221 resourceState: apiv1.ResourceDeleting, 222 provStatus: prov.Success.String(), 223 outboundStatus: prov.Success, 224 skipFinalizers: true, 225 }, 226 } 227 228 for _, tc := range tests { 229 t.Run(tc.name, func(t *testing.T) { 230 cred := credential 231 cred.Spec.State.Name = tc.specState 232 cred.Spec.State.Reason = tc.specStateReason 233 cred.Status.Level = tc.provStatus 234 cred.Status.Reasons = []apiv1.ResourceStatusReason{ 235 { 236 Type: tc.provStatus, 237 Detail: tc.specStateReason, 238 }, 239 } 240 cred.Metadata.State = tc.resourceState 241 if !tc.skipFinalizers { 242 cred.Finalizers = []apiv1.Finalizer{{Name: crFinalizer}} 243 } 244 p := &mockCredProv{ 245 t: t, 246 expectedStatus: mock.MockRequestStatus{ 247 Status: tc.outboundStatus, 248 Msg: "msg", 249 Properties: map[string]string{ 250 "status_key": "status_val", 251 }, 252 }, 253 expectedAppDetails: util.GetAgentDetails(mApp), 254 expectedCredDetails: util.GetAgentDetails(&cred), 255 expectedManagedApp: credAppRefName, 256 expectedCredType: cred.Spec.CredentialRequestDefinition, 257 } 258 259 c := &credClient{ 260 crd: crdRI, 261 expectedStatus: tc.outboundStatus.String(), 262 isDeleting: true, 263 managedApp: credApp, 264 t: t, 265 } 266 267 handler := NewCredentialHandler(p, c, nil) 268 v := handler.(*credentials) 269 v.encryptSchema = func(_, _ map[string]interface{}, _, _, _ string) (map[string]interface{}, error) { 270 return map[string]interface{}{}, nil 271 } 272 273 ri, _ := cred.AsInstance() 274 err := handler.Handle(NewEventContext(proto.Event_UPDATED, nil, ri.Kind, ri.Name), nil, ri) 275 assert.Nil(t, err) 276 assert.Equal(t, tc.expectedProvType, p.expectedProvType) 277 278 if tc.outboundStatus.String() == prov.Success.String() && tc.specStateReason != prov.CredExpDetail { 279 assert.False(t, c.createSubCalled) 280 } else { 281 assert.True(t, c.createSubCalled) 282 } 283 }) 284 } 285 } 286 287 func TestCredentialHandler_update(t *testing.T) { 288 crdRI, _ := crd.AsInstance() 289 290 tests := []struct { 291 name string 292 isRotating bool 293 inboundStatus string 294 inboundSpecState string 295 inboundState string 296 outboundState string 297 expectedProvType string 298 outboundStatus prov.Status 299 }{ 300 { 301 name: "should update credential on rotate", 302 isRotating: true, 303 inboundState: apiv1.Active, 304 expectedProvType: update, 305 }, 306 { 307 name: "should update credential on suspend", 308 inboundSpecState: apiv1.Active, 309 inboundState: apiv1.Inactive, 310 outboundState: apiv1.Active, 311 expectedProvType: update, 312 }, 313 { 314 name: "should update credential on enable", 315 inboundSpecState: apiv1.Inactive, 316 inboundState: apiv1.Active, 317 outboundState: apiv1.Inactive, 318 expectedProvType: update, 319 }, 320 { 321 name: "should update credential on suspend and rotate", 322 inboundSpecState: apiv1.Active, 323 inboundState: apiv1.Inactive, 324 outboundState: apiv1.Active, 325 expectedProvType: update, 326 isRotating: true, 327 }, 328 { 329 name: "should update credential on rotate and enable", 330 inboundSpecState: apiv1.Inactive, 331 inboundState: apiv1.Active, 332 outboundState: apiv1.Inactive, 333 expectedProvType: update, 334 isRotating: true, 335 }, 336 } 337 for _, tc := range tests { 338 t.Run(tc.name, func(t *testing.T) { 339 cred := credential 340 cred.Status.Level = tc.inboundStatus 341 if tc.inboundStatus == "" { 342 cred.Status.Level = prov.Pending.String() 343 } 344 cred.Metadata.State = tc.inboundStatus 345 cred.State.Name = tc.inboundState 346 cred.Spec.State.Name = tc.inboundSpecState 347 cred.Spec.State.Rotate = tc.isRotating 348 cred.Finalizers = []apiv1.Finalizer{{Name: crFinalizer}} 349 350 if tc.outboundStatus.String() == "" { 351 tc.outboundStatus = prov.Success 352 } 353 354 p := &mockCredProv{ 355 t: t, 356 expectedProvType: tc.expectedProvType, 357 expectedStatus: mock.MockRequestStatus{ 358 Status: tc.outboundStatus, 359 Msg: "msg", 360 }, 361 expectedAppDetails: util.GetAgentDetails(credApp), 362 expectedCredDetails: util.GetAgentDetails(&cred), 363 expectedManagedApp: credAppRefName, 364 expectedCredType: cred.Spec.CredentialRequestDefinition, 365 } 366 367 c := &credClient{ 368 crd: crdRI, 369 expectedStatus: tc.outboundStatus.String(), 370 managedApp: credApp, 371 t: t, 372 } 373 374 handler := NewCredentialHandler(p, c, nil) 375 v := handler.(*credentials) 376 v.encryptSchema = func(_, _ map[string]interface{}, _, _, _ string) (map[string]interface{}, error) { 377 return map[string]interface{}{}, nil 378 } 379 380 ri, _ := cred.AsInstance() 381 err := handler.Handle(NewEventContext(proto.Event_UPDATED, nil, ri.Kind, ri.Name), nil, ri) 382 assert.Nil(t, err) 383 assert.Equal(t, tc.expectedProvType, p.expectedProvType) 384 }) 385 } 386 } 387 388 func TestCredentialHandler_wrong_kind(t *testing.T) { 389 c := &mockClient{} 390 p := &mockCredProv{} 391 handler := NewCredentialHandler(p, c, nil) 392 ri := &apiv1.ResourceInstance{ 393 ResourceMeta: apiv1.ResourceMeta{ 394 GroupVersionKind: management.EnvironmentGVK(), 395 }, 396 } 397 err := handler.Handle(NewEventContext(proto.Event_CREATED, nil, ri.Kind, ri.Name), nil, ri) 398 assert.Nil(t, err) 399 } 400 401 func Test_creds(t *testing.T) { 402 c := provCreds{ 403 managedApp: "app-name", 404 credType: "api-key", 405 credDetails: map[string]interface{}{ 406 "abc": "123", 407 }, 408 appDetails: map[string]interface{}{ 409 "def": "456", 410 }, 411 credData: map[string]interface{}{ 412 "def": "789", 413 }, 414 id: "cred-id", 415 credSchema: map[string]interface{}{ 416 "properties": "test", 417 }, 418 credProvSchema: map[string]interface{}{ 419 "properties": "test", 420 }, 421 } 422 423 assert.Equal(t, c.managedApp, c.GetApplicationName()) 424 assert.Equal(t, c.credType, c.GetCredentialType()) 425 assert.Equal(t, c.id, c.GetID()) 426 assert.Equal(t, c.credData, c.GetCredentialData()) 427 assert.Equal(t, c.credDetails["abc"], c.GetCredentialDetailsValue("abc")) 428 assert.Equal(t, c.appDetails["def"], c.GetApplicationDetailsValue("def")) 429 assert.Equal(t, c.credSchema, c.GetCredentialSchema()) 430 assert.Equal(t, c.credProvSchema, c.GetCredentialProvisionSchema()) 431 assert.Empty(t, c.GetCredentialSchemaDetailsValue("prop")) 432 433 c.credSchemaDetails = map[string]interface{}{ 434 "detail": "test", 435 } 436 assert.Equal(t, c.credSchemaDetails["prop"], c.GetCredentialSchemaDetailsValue("prop")) 437 438 c.credDetails = nil 439 c.appDetails = nil 440 assert.Empty(t, c.GetApplicationDetailsValue("app_details_key")) 441 assert.Empty(t, c.GetCredentialDetailsValue("access_details_key")) 442 assert.Empty(t, c.GetCredentialSchemaDetailsValue("invalid_key")) 443 } 444 445 func TestIDPCredentialProvisioning(t *testing.T) { 446 crdRI, _ := crd.AsInstance() 447 s := oauth.NewMockIDPServer() 448 defer s.Close() 449 450 publicKey, err := os.ReadFile("../../authz/oauth/testdata/publickey") 451 assert.Nil(t, err) 452 453 certificate, err := os.ReadFile("../../authz/oauth/testdata/client_cert.pem") 454 assert.Nil(t, err) 455 tests := []struct { 456 name string 457 metadataURL string 458 tokenURL string 459 expectedProvType string 460 outboundStatus prov.Status 461 registrationStatus int 462 handlerInvoked bool 463 hasError bool 464 authMethod string 465 jwks []byte 466 }{ 467 { 468 name: "should provision IDP credential with no error", 469 metadataURL: s.GetMetadataURL(), 470 tokenURL: s.GetTokenURL(), 471 expectedProvType: provision, 472 outboundStatus: prov.Success, 473 registrationStatus: http.StatusCreated, 474 authMethod: config.ClientSecretBasic, 475 }, 476 { 477 name: "should fail to provision and set the status to error", 478 metadataURL: s.GetMetadataURL(), 479 tokenURL: "test", 480 outboundStatus: prov.Error, 481 authMethod: config.ClientSecretBasic, 482 }, 483 { 484 name: "should fail to provision with no jwks for private_kwy_jwt", 485 metadataURL: s.GetMetadataURL(), 486 tokenURL: s.GetTokenURL(), 487 outboundStatus: prov.Error, 488 authMethod: config.PrivateKeyJWT, 489 }, 490 { 491 name: "should fail to provision with no jwks for tls_client_auth", 492 metadataURL: s.GetMetadataURL(), 493 tokenURL: s.GetTokenURL(), 494 outboundStatus: prov.Error, 495 authMethod: config.TLSClientAuth, 496 }, 497 { 498 name: "should fail to provision with no jwks for self_signed_tls_client_auth", 499 metadataURL: s.GetMetadataURL(), 500 tokenURL: s.GetTokenURL(), 501 outboundStatus: prov.Error, 502 authMethod: config.SelfSignedTLSClientAuth, 503 }, 504 { 505 name: "should fail to provision with invalid jwks for private_kwy_jwt", 506 metadataURL: s.GetMetadataURL(), 507 tokenURL: s.GetTokenURL(), 508 outboundStatus: prov.Error, 509 authMethod: config.PrivateKeyJWT, 510 jwks: []byte("invalid-private-key"), 511 }, 512 { 513 name: "should fail to provision with invalid jwks for tls_client_auth", 514 metadataURL: s.GetMetadataURL(), 515 tokenURL: s.GetTokenURL(), 516 outboundStatus: prov.Error, 517 authMethod: config.TLSClientAuth, 518 jwks: []byte("invalid-certificate"), 519 }, 520 { 521 name: "should fail to provision with invalid jwks for private_kwy_jwt", 522 metadataURL: s.GetMetadataURL(), 523 tokenURL: s.GetTokenURL(), 524 outboundStatus: prov.Success, 525 expectedProvType: provision, 526 authMethod: config.PrivateKeyJWT, 527 jwks: publicKey, 528 registrationStatus: http.StatusCreated, 529 }, 530 { 531 name: "should fail to provision with invalid jwks for tls_client_auth", 532 metadataURL: s.GetMetadataURL(), 533 tokenURL: s.GetTokenURL(), 534 outboundStatus: prov.Success, 535 expectedProvType: provision, 536 authMethod: config.TLSClientAuth, 537 jwks: certificate, 538 registrationStatus: http.StatusCreated, 539 }, 540 } 541 for _, tc := range tests { 542 t.Run(tc.name, func(t *testing.T) { 543 idpConfig := createIDPConfig(s) 544 idpProviderRegistry := oauth.NewIdpRegistry() 545 idpProviderRegistry.RegisterProvider(context.Background(), idpConfig, config.NewTLSConfig(), "", 30*time.Second) 546 547 cred := credential 548 cred.Status.Level = prov.Pending.String() 549 550 cred.Spec.Data = map[string]interface{}{ 551 prov.IDPTokenURL: tc.tokenURL, 552 prov.OauthGrantType: "client_credentials", 553 prov.OauthTokenAuthMethod: tc.authMethod, 554 prov.OauthJwks: string(tc.jwks), 555 prov.OauthCertificate: string(tc.jwks), 556 } 557 558 p := &mockCredProv{ 559 t: t, 560 expectedStatus: mock.MockRequestStatus{ 561 Status: prov.Success, 562 Msg: "msg", 563 Properties: map[string]string{ 564 "status_key": "status_val", 565 }, 566 }, 567 expectedAppDetails: util.GetAgentDetails(credApp), 568 expectedCredDetails: util.GetAgentDetails(&cred), 569 expectedManagedApp: credAppRefName, 570 expectedCredType: cred.Spec.CredentialRequestDefinition, 571 } 572 573 c := &credClient{ 574 crd: crdRI, 575 expectedStatus: tc.outboundStatus.String(), 576 managedApp: credApp, 577 t: t, 578 } 579 580 handler := NewCredentialHandler(p, c, idpProviderRegistry) 581 v := handler.(*credentials) 582 v.encryptSchema = func(_, _ map[string]interface{}, _, _, _ string) (map[string]interface{}, error) { 583 return map[string]interface{}{}, nil 584 } 585 586 ri, _ := cred.AsInstance() 587 s.SetRegistrationResponseCode(tc.registrationStatus) 588 err := handler.Handle(NewEventContext(proto.Event_UPDATED, nil, ri.Kind, ri.Name), nil, ri) 589 assert.Nil(t, err) 590 if !tc.hasError { 591 assert.Equal(t, tc.expectedProvType, p.expectedProvType) 592 } else { 593 assert.NotNil(t, err) 594 } 595 }) 596 } 597 } 598 599 func TestIDPCredentialDeprovisioning(t *testing.T) { 600 crdRI, _ := crd.AsInstance() 601 s := oauth.NewMockIDPServer() 602 defer s.Close() 603 604 tests := []struct { 605 name string 606 metadataURL string 607 tokenURL string 608 outboundStatus prov.Status 609 registrationStatus int 610 handlerInvoked bool 611 }{ 612 { 613 name: "should deprovision IDP credential with no error", 614 metadataURL: s.GetMetadataURL(), 615 tokenURL: s.GetTokenURL(), 616 outboundStatus: prov.Success, 617 registrationStatus: http.StatusNoContent, 618 handlerInvoked: true, 619 }, 620 { 621 name: "should fail to deprovision and set the status to error", 622 metadataURL: s.GetMetadataURL(), 623 tokenURL: "test", 624 outboundStatus: prov.Error, 625 handlerInvoked: false, 626 }, 627 } 628 for _, tc := range tests { 629 t.Run(tc.name, func(t *testing.T) { 630 idpConfig := createIDPConfig(s) 631 idpProviderRegistry := oauth.NewIdpRegistry() 632 idpProviderRegistry.RegisterProvider(context.Background(), idpConfig, config.NewTLSConfig(), "", 30*time.Second) 633 634 cred := credential 635 cred.Status.Level = prov.Success.String() 636 cred.Metadata.State = apiv1.ResourceDeleting 637 cred.Finalizers = []apiv1.Finalizer{{Name: crFinalizer}} 638 cred.Spec.Data = map[string]interface{}{ 639 "idpTokenURL": tc.tokenURL, 640 } 641 642 p := &mockCredProv{ 643 t: t, 644 expectedStatus: mock.MockRequestStatus{ 645 Status: tc.outboundStatus, 646 Msg: "msg", 647 Properties: map[string]string{ 648 "status_key": "status_val", 649 }, 650 }, 651 expectedAppDetails: util.GetAgentDetails(mApp), 652 expectedCredDetails: util.GetAgentDetails(&cred), 653 expectedManagedApp: credAppRefName, 654 expectedCredType: cred.Spec.CredentialRequestDefinition, 655 } 656 657 c := &credClient{ 658 crd: crdRI, 659 expectedStatus: tc.outboundStatus.String(), 660 managedApp: credApp, 661 isDeleting: true, 662 t: t, 663 } 664 665 handler := NewCredentialHandler(p, c, idpProviderRegistry) 666 v := handler.(*credentials) 667 v.encryptSchema = func(_, _ map[string]interface{}, _, _, _ string) (map[string]interface{}, error) { 668 return map[string]interface{}{}, nil 669 } 670 671 ri, _ := cred.AsInstance() 672 s.SetRegistrationResponseCode(tc.registrationStatus) 673 err := handler.Handle(NewEventContext(proto.Event_UPDATED, nil, ri.Kind, ri.Name), nil, ri) 674 assert.Nil(t, err) 675 if tc.handlerInvoked { 676 assert.Equal(t, deprovision, p.expectedProvType) 677 if tc.outboundStatus.String() == prov.Success.String() { 678 assert.False(t, c.createSubCalled) 679 } else { 680 assert.True(t, c.createSubCalled) 681 } 682 } else { 683 assert.False(t, c.createSubCalled) 684 } 685 }) 686 } 687 688 } 689 690 type mockCredProv struct { 691 expectedAppDetails map[string]interface{} 692 expectedCredDetails map[string]interface{} 693 expectedCredType string 694 expectedManagedApp string 695 expectedProvType string 696 expectedStatus mock.MockRequestStatus 697 t *testing.T 698 } 699 700 func (m *mockCredProv) CredentialProvision(cr prov.CredentialRequest) (status prov.RequestStatus, credentails prov.Credential) { 701 m.expectedProvType = provision 702 v := cr.(*provCreds) 703 assert.Equal(m.t, m.expectedAppDetails, v.appDetails) 704 assert.Equal(m.t, m.expectedCredDetails, v.credDetails) 705 assert.Equal(m.t, m.expectedManagedApp, v.managedApp) 706 assert.Equal(m.t, m.expectedCredType, v.credType) 707 return m.expectedStatus, &mockProvCredential{} 708 } 709 710 func (m *mockCredProv) CredentialDeprovision(cr prov.CredentialRequest) (status prov.RequestStatus) { 711 m.expectedProvType = deprovision 712 v := cr.(*provCreds) 713 assert.Equal(m.t, m.expectedAppDetails, v.appDetails) 714 assert.Equal(m.t, m.expectedCredDetails, v.credDetails) 715 assert.Equal(m.t, m.expectedManagedApp, v.managedApp) 716 assert.Equal(m.t, m.expectedCredType, v.credType) 717 return m.expectedStatus 718 } 719 720 func (m *mockCredProv) CredentialUpdate(cr prov.CredentialRequest) (status prov.RequestStatus, credentails prov.Credential) { 721 m.expectedProvType = update 722 v := cr.(*provCreds) 723 assert.Equal(m.t, m.expectedAppDetails, v.appDetails) 724 assert.Equal(m.t, m.expectedCredDetails, v.credDetails) 725 assert.Equal(m.t, m.expectedManagedApp, v.managedApp) 726 assert.Equal(m.t, m.expectedCredType, v.credType) 727 return m.expectedStatus, &mockProvCredential{} 728 } 729 730 type mockProvCredential struct{} 731 732 func (m *mockProvCredential) GetData() map[string]interface{} { 733 return map[string]interface{}{} 734 } 735 736 func (m *mockProvCredential) GetExpirationTime() time.Time { 737 return time.Now() 738 } 739 740 func decrypt(pk *rsa.PrivateKey, alg string, data map[string]interface{}) map[string]interface{} { 741 enc := func(v string) ([]byte, error) { 742 switch alg { 743 case "RSA-OAEP": 744 bts, _ := base64.StdEncoding.DecodeString(v) 745 return rsa.DecryptOAEP(sha256.New(), rand.Reader, pk, bts, nil) 746 case "PKCS": 747 bts, _ := base64.StdEncoding.DecodeString(v) 748 return rsa.DecryptPKCS1v15(rand.Reader, pk, bts) 749 default: 750 return nil, fmt.Errorf("unexpected algorithm") 751 } 752 } 753 754 for key, value := range data { 755 v, ok := value.(string) 756 if !ok { 757 continue 758 } 759 760 bts, err := enc(v) 761 if err != nil { 762 log.Errorf("Failed to decrypt: %s\n", err) 763 continue 764 } 765 data[key] = string(bts) 766 } 767 768 return data 769 } 770 771 func Test_encrypt(t *testing.T) { 772 var crdSchema = `{ 773 "type": "object", 774 "$schema": "http://json-schema.org/draft-07/schema#", 775 "required": [ 776 "abc" 777 ], 778 "properties": { 779 "one": { 780 "type": "string", 781 "description": "abc.", 782 "x-axway-encrypted": true 783 }, 784 "two": { 785 "type": "string", 786 "description": "def." 787 }, 788 "three": { 789 "type": "string", 790 "description": "ghi.", 791 "x-axway-encrypted": true 792 } 793 }, 794 "description": "sample." 795 }` 796 797 crd := map[string]interface{}{} 798 err := json.Unmarshal([]byte(crdSchema), &crd) 799 assert.Nil(t, err) 800 801 pub, priv, err := newKeyPair() 802 assert.Nil(t, err) 803 804 tests := []struct { 805 alg string 806 hasErr bool 807 hasEncryptErr bool 808 hash string 809 name string 810 publicKey string 811 privateKey string 812 }{ 813 { 814 name: "should encrypt when the algorithm is PKCS", 815 alg: "PKCS", 816 hash: "SHA256", 817 publicKey: pub, 818 privateKey: priv, 819 }, 820 { 821 name: "should encrypt when the algorithm is RSA-OAEP", 822 alg: "RSA-OAEP", 823 hash: "SHA256", 824 publicKey: pub, 825 privateKey: priv, 826 }, 827 { 828 name: "should return an error when the algorithm is unknown", 829 hasErr: true, 830 alg: "fake", 831 hash: "SHA256", 832 publicKey: pub, 833 privateKey: priv, 834 }, 835 { 836 name: "should return an error when the hash is unknown", 837 hasErr: true, 838 alg: "RSA-OAEP", 839 hash: "fake", 840 publicKey: pub, 841 privateKey: priv, 842 }, 843 { 844 name: "should return an error when the public key cannot be parsed", 845 hasErr: true, 846 alg: "RSA-OAEP", 847 hash: "SHA256", 848 publicKey: "fake", 849 privateKey: priv, 850 }, 851 } 852 853 for _, tc := range tests { 854 t.Run(tc.name, func(t *testing.T) { 855 schemaData := map[string]interface{}{ 856 "one": "abc", 857 "two": "def", 858 "three": "ghi", 859 } 860 861 encrypted, err := encryptSchema(crd, schemaData, tc.publicKey, tc.alg, tc.hash) 862 if tc.hasErr { 863 assert.Error(t, err) 864 } else { 865 assert.NotEqual(t, "abc", schemaData["one"]) 866 assert.Equal(t, "def", schemaData["two"]) 867 assert.NotEqual(t, "ghi", schemaData["three"]) 868 869 decrypted := decrypt(parsePrivateKey(tc.privateKey), tc.alg, encrypted) 870 assert.Equal(t, "abc", decrypted["one"]) 871 assert.Equal(t, "def", decrypted["two"]) 872 assert.Equal(t, "ghi", decrypted["three"]) 873 } 874 }) 875 } 876 877 } 878 879 type credClient struct { 880 managedApp *apiv1.ResourceInstance 881 crd *apiv1.ResourceInstance 882 getAppErr error 883 getCrdErr error 884 createSubCalled bool 885 subError error 886 expectedStatus string 887 t *testing.T 888 isDeleting bool 889 } 890 891 func (m *credClient) GetResource(url string) (*apiv1.ResourceInstance, error) { 892 if strings.Contains(url, "/managedapplications") { 893 return m.managedApp, m.getAppErr 894 } 895 if strings.Contains(url, "/credentialrequestdefinitions") { 896 return m.crd, m.getCrdErr 897 } 898 899 return nil, fmt.Errorf("mock client - resource not found") 900 } 901 902 func (m *credClient) CreateSubResource(_ apiv1.ResourceMeta, subs map[string]interface{}) error { 903 if statusI, ok := subs["status"]; ok { 904 status := statusI.(*apiv1.ResourceStatus) 905 assert.Equal(m.t, m.expectedStatus, status.Level, status.Reasons) 906 } 907 m.createSubCalled = true 908 return m.subError 909 } 910 911 func (m *credClient) UpdateResourceFinalizer(ri *apiv1.ResourceInstance, _, _ string, addAction bool) (*apiv1.ResourceInstance, error) { 912 if m.isDeleting { 913 assert.False(m.t, addAction, "addAction should be false when the resource is deleting") 914 } else { 915 assert.True(m.t, addAction, "addAction should be true when the resource is not deleting") 916 } 917 918 return nil, nil 919 } 920 921 func (m *credClient) UpdateResourceInstance(ri apiv1.Interface) (*apiv1.ResourceInstance, error) { 922 return nil, nil 923 } 924 925 func parsePrivateKey(priv string) *rsa.PrivateKey { 926 block, _ := pem.Decode([]byte(priv)) 927 if block == nil { 928 panic("failed to parse PEM block containing the public key") 929 } 930 931 pk, err := x509.ParsePKCS1PrivateKey(block.Bytes) 932 if err != nil { 933 panic("failed to parse private key: " + err.Error()) 934 } 935 936 return pk 937 } 938 939 func newKeyPair() (public string, private string, err error) { 940 priv, err := rsa.GenerateKey(rand.Reader, 2048) 941 if err != nil { 942 return "", "", err 943 } 944 945 pkBts := x509.MarshalPKCS1PrivateKey(priv) 946 fmt.Println(pkBts) 947 pvBlock := &pem.Block{ 948 Type: "RSA PRIVATE KEY", 949 Bytes: pkBts, 950 } 951 952 privBuff := bytes.NewBuffer([]byte{}) 953 err = pem.Encode(privBuff, pvBlock) 954 if err != nil { 955 return "", "", err 956 } 957 958 pubKeyBts, err := x509.MarshalPKIXPublicKey(&priv.PublicKey) 959 if err != nil { 960 return "", "", err 961 } 962 963 pubKeyBlock := &pem.Block{ 964 Type: "PUBLIC KEY", 965 Bytes: pubKeyBts, 966 } 967 968 pubKeyBuff := bytes.NewBuffer([]byte{}) 969 err = pem.Encode(pubKeyBuff, pubKeyBlock) 970 if err != nil { 971 return "", "", err 972 } 973 974 return pubKeyBuff.String(), privBuff.String(), nil 975 } 976 977 const credAppRefName = "managed-app-name" 978 979 var credApp = &apiv1.ResourceInstance{ 980 ResourceMeta: apiv1.ResourceMeta{ 981 Name: credAppRefName, 982 SubResources: map[string]interface{}{ 983 defs.XAgentDetails: map[string]interface{}{ 984 "sub_managed_app_key": "sub_managed_app_val", 985 }, 986 "status": map[string]interface{}{ 987 "level": prov.Success.String(), 988 }, 989 }, 990 }, 991 } 992 993 var crd = &management.CredentialRequestDefinition{ 994 ResourceMeta: apiv1.ResourceMeta{ 995 Name: credAppRefName, 996 SubResources: map[string]interface{}{ 997 defs.XAgentDetails: map[string]interface{}{ 998 "sub_crd_key": "sub_crd_val", 999 }, 1000 }, 1001 }, 1002 Owner: nil, 1003 References: management.CredentialRequestDefinitionReferences{}, 1004 Spec: management.CredentialRequestDefinitionSpec{ 1005 Schema: nil, 1006 Provision: &management.CredentialRequestDefinitionSpecProvision{ 1007 Schema: map[string]interface{}{ 1008 "properties": map[string]interface{}{}, 1009 }, 1010 }, 1011 Webhooks: nil, 1012 }, 1013 } 1014 1015 var credential = management.Credential{ 1016 ResourceMeta: apiv1.ResourceMeta{ 1017 Metadata: apiv1.Metadata{ 1018 ID: "11", 1019 Scope: apiv1.MetadataScope{ 1020 Kind: management.EnvironmentGVK().Kind, 1021 Name: "env-1", 1022 }, 1023 }, 1024 SubResources: map[string]interface{}{ 1025 defs.XAgentDetails: map[string]interface{}{ 1026 "sub_credential_key": "sub_credential_val", 1027 }, 1028 }, 1029 }, 1030 Spec: management.CredentialSpec{ 1031 CredentialRequestDefinition: "api-key", 1032 ManagedApplication: credAppRefName, 1033 Data: nil, 1034 State: management.CredentialSpecState{ 1035 Name: apiv1.Active, 1036 }, 1037 }, 1038 Status: &apiv1.ResourceStatus{ 1039 Level: "", 1040 }, 1041 } 1042 1043 func createIDPConfig(s oauth.MockIDPServer) *config.IDPConfiguration { 1044 return &config.IDPConfiguration{ 1045 Name: "test", 1046 Type: "okta", 1047 MetadataURL: s.GetMetadataURL(), 1048 AuthConfig: &config.IDPAuthConfiguration{ 1049 Type: "client", 1050 ClientID: "test", 1051 ClientSecret: "test", 1052 }, 1053 GrantType: oauth.GrantTypeClientCredentials, 1054 ClientScopes: "read,write", 1055 AuthMethod: config.ClientSecretBasic, 1056 AuthResponseType: oauth.AuthResponseToken, 1057 ExtraProperties: config.ExtraProperties{"key": "value"}, 1058 } 1059 }