diff --git a/plugins/providers/gcloudiam/provider.go b/plugins/providers/gcloudiam/provider.go index 35c55f2dd..58daebffa 100644 --- a/plugins/providers/gcloudiam/provider.go +++ b/plugins/providers/gcloudiam/provider.go @@ -98,6 +98,8 @@ func (p *Provider) GetResources(pc *domain.ProviderConfig) ([]*domain.Resource, return nil, fmt.Errorf("listing service accounts: %w", err) } + // TODO: filter + for _, sa := range serviceAccounts { resources = append(resources, &domain.Resource{ ProviderType: pc.Type, @@ -116,7 +118,7 @@ func (p *Provider) GetResources(pc *domain.ProviderConfig) ([]*domain.Resource, return resources, nil } -func (p *Provider) GrantAccess(pc *domain.ProviderConfig, a domain.Grant) error { +func (p *Provider) GrantAccess(pc *domain.ProviderConfig, g domain.Grant) error { // TODO: validate provider config and appeal var creds Credentials @@ -129,10 +131,10 @@ func (p *Provider) GrantAccess(pc *domain.ProviderConfig, a domain.Grant) error return err } - switch a.Resource.Type { + switch g.Resource.Type { case ResourceTypeProject, ResourceTypeOrganization: - for _, p := range a.Permissions { - if err := client.GrantAccess(a.AccountType, a.AccountID, p); err != nil { + for _, p := range g.Permissions { + if err := client.GrantAccess(g.AccountType, g.AccountID, p); err != nil { if !errors.Is(err, ErrPermissionAlreadyExists) { return err } @@ -141,8 +143,8 @@ func (p *Provider) GrantAccess(pc *domain.ProviderConfig, a domain.Grant) error return nil case ResourceTypeServiceAccount: - for _, p := range a.Permissions { - if err := client.GrantServiceAccountAccess(context.TODO(), a.Resource.URN, a.AccountType, a.AccountID, p); err != nil { + for _, p := range g.Permissions { + if err := client.GrantServiceAccountAccess(context.TODO(), g.Resource.URN, g.AccountType, g.AccountID, p); err != nil { if !errors.Is(err, ErrPermissionAlreadyExists) { return err } @@ -155,7 +157,7 @@ func (p *Provider) GrantAccess(pc *domain.ProviderConfig, a domain.Grant) error } } -func (p *Provider) RevokeAccess(pc *domain.ProviderConfig, a domain.Grant) error { +func (p *Provider) RevokeAccess(pc *domain.ProviderConfig, g domain.Grant) error { var creds Credentials if err := mapstructure.Decode(pc.Credentials, &creds); err != nil { return err @@ -166,10 +168,10 @@ func (p *Provider) RevokeAccess(pc *domain.ProviderConfig, a domain.Grant) error return err } - switch a.Resource.Type { + switch g.Resource.Type { case ResourceTypeProject, ResourceTypeOrganization: - for _, p := range a.Permissions { - if err := client.RevokeAccess(a.AccountType, a.AccountID, p); err != nil { + for _, p := range g.Permissions { + if err := client.RevokeAccess(g.AccountType, g.AccountID, p); err != nil { if !errors.Is(err, ErrPermissionNotFound) { return err } @@ -178,8 +180,8 @@ func (p *Provider) RevokeAccess(pc *domain.ProviderConfig, a domain.Grant) error return nil case ResourceTypeServiceAccount: - for _, p := range a.Permissions { - if err := client.RevokeServiceAccountAccess(context.TODO(), a.Resource.URN, a.AccountType, a.AccountID, p); err != nil { + for _, p := range g.Permissions { + if err := client.RevokeServiceAccountAccess(context.TODO(), g.Resource.URN, g.AccountType, g.AccountID, p); err != nil { if !errors.Is(err, ErrPermissionNotFound) { return err } diff --git a/plugins/providers/gcloudiam/provider_test.go b/plugins/providers/gcloudiam/provider_test.go index 77de4b857..d0cef8ee0 100644 --- a/plugins/providers/gcloudiam/provider_test.go +++ b/plugins/providers/gcloudiam/provider_test.go @@ -314,7 +314,7 @@ func TestGetResources(t *testing.T) { providerURN: client, } - gCloudRolesList := []*iam.Role{ + projectRoles := []*iam.Role{ { Name: "roles/bigquery.admin", Title: "BigQuery Admin", @@ -326,9 +326,29 @@ func TestGetResources(t *testing.T) { Description: "Read-only access to ApiGateway and related resources", }, } + saRoles := []*iam.Role{ + { + Name: "roles/workstations.serviceAgent", + Title: "Workstations Service Agent", + Description: "Grants the Workstations Service Account access to manage resources in consumer project.", + }, + } client.EXPECT(). GetGrantableRoles(mock.AnythingOfType("*context.emptyCtx"), gcloudiam.ResourceTypeProject). - Return(gCloudRolesList, nil).Once() + Return(projectRoles, nil).Once() + client.EXPECT(). + GetGrantableRoles(mock.AnythingOfType("*context.emptyCtx"), gcloudiam.ResourceTypeServiceAccount). + Return(saRoles, nil).Once() + + expectedServiceAccounts := []*iam.ServiceAccount{ + { + Name: "sa-name", + DisplayName: "sa-display-name", + }, + } + client.EXPECT(). + ListServiceAccounts(mock.AnythingOfType("*context.emptyCtx")). + Return(expectedServiceAccounts, nil).Once() pc := &domain.ProviderConfig{ Type: domain.ProviderTypeGCloudIAM, @@ -353,6 +373,15 @@ func TestGetResources(t *testing.T) { }, }, }, + { + Type: gcloudiam.ResourceTypeServiceAccount, + Roles: []*domain.Role{ + { + ID: "role-1", + Permissions: []interface{}{"roles/workstations.serviceAgent"}, + }, + }, + }, }, } @@ -364,6 +393,13 @@ func TestGetResources(t *testing.T) { URN: "project/test-resource-name", Name: "project/test-resource-name - GCP IAM", }, + { + ProviderType: pc.Type, + ProviderURN: pc.URN, + Type: gcloudiam.ResourceTypeServiceAccount, + URN: "sa-name", + Name: "sa-display-name", + }, } actualResources, actualError := p.GetResources(pc) @@ -562,7 +598,7 @@ func TestGrantAccess(t *testing.T) { }, URN: providerURN, } - a := domain.Grant{ + g := domain.Grant{ Resource: &domain.Resource{ Type: gcloudiam.ResourceTypeProject, URN: "test-role", @@ -575,10 +611,52 @@ func TestGrantAccess(t *testing.T) { Permissions: []string{"roles/bigquery.admin"}, } - actualError := p.GrantAccess(pc, a) + actualError := p.GrantAccess(pc, g) assert.Nil(t, actualError) }) + + t.Run("successful grant access to a service account", func(t *testing.T) { + providerURN := "test-provider-urn" + crypto := new(mocks.Encryptor) + client := new(mocks.GcloudIamClient) + p := gcloudiam.NewProvider("", crypto) + p.Clients = map[string]gcloudiam.GcloudIamClient{ + providerURN: client, + } + + pc := &domain.ProviderConfig{ + URN: providerURN, + Resources: []*domain.ResourceConfig{ + { + Type: gcloudiam.ResourceTypeServiceAccount, + Roles: []*domain.Role{ + { + ID: "test-role", + Permissions: []interface{}{"test-permission"}, + }, + }, + }, + }, + } + g := domain.Grant{ + Resource: &domain.Resource{ + Type: gcloudiam.ResourceTypeServiceAccount, + URN: "sa-urn", + }, + Role: "test-role", + AccountType: "test-account-type", + AccountID: "test-account-id", + Permissions: []string{"test-permission"}, + } + + client.EXPECT(). + GrantServiceAccountAccess(mock.AnythingOfType("*context.emptyCtx"), g.Resource.URN, g.AccountType, g.AccountID, g.Permissions[0]). + Return(nil).Once() + + err := p.GrantAccess(pc, g) + assert.NoError(t, err) + }) } func TestRevokeAccess(t *testing.T) { @@ -708,6 +786,48 @@ func TestRevokeAccess(t *testing.T) { assert.Nil(t, actualError) }) + + t.Run("successful revoke access to a service account", func(t *testing.T) { + providerURN := "test-provider-urn" + crypto := new(mocks.Encryptor) + client := new(mocks.GcloudIamClient) + p := gcloudiam.NewProvider("", crypto) + p.Clients = map[string]gcloudiam.GcloudIamClient{ + providerURN: client, + } + + pc := &domain.ProviderConfig{ + URN: providerURN, + Resources: []*domain.ResourceConfig{ + { + Type: gcloudiam.ResourceTypeServiceAccount, + Roles: []*domain.Role{ + { + ID: "test-role", + Permissions: []interface{}{"test-permission"}, + }, + }, + }, + }, + } + g := domain.Grant{ + Resource: &domain.Resource{ + Type: gcloudiam.ResourceTypeServiceAccount, + URN: "sa-urn", + }, + Role: "test-role", + AccountType: "test-account-type", + AccountID: "test-account-id", + Permissions: []string{"test-permission"}, + } + + client.EXPECT(). + RevokeServiceAccountAccess(mock.AnythingOfType("*context.emptyCtx"), g.Resource.URN, g.AccountType, g.AccountID, g.Permissions[0]). + Return(nil).Once() + + err := p.RevokeAccess(pc, g) + assert.NoError(t, err) + }) } func TestGetRoles(t *testing.T) {