Skip to content

Commit

Permalink
OCM-12971 | 'ocm gcp update wif-config' remediates all wif-config mis…
Browse files Browse the repository at this point in the history
…configurations
  • Loading branch information
renan-campos committed Dec 11, 2024
1 parent 7f8095b commit dc439d0
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 26 deletions.
107 changes: 92 additions & 15 deletions cmd/ocm/gcp/gcp-client-shim.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log"
"reflect"
"sort"
"strings"
"time"
Expand Down Expand Up @@ -90,42 +91,55 @@ func (c *shim) CreateWorkloadIdentityPool(
}
}

// Enable the pool if it exists but is disabled.
if resp.Disabled {
if err := c.gcpClient.EnableWorkloadIdentityPool(
ctx,
resp.Name,
); err != nil {
return err
}
log.Printf("Workload identity pool '%s' enabled", resp.DisplayName)
}

return nil
}

func (c *shim) CreateWorkloadIdentityProvider(
ctx context.Context,
log *log.Logger,
) error {
projectId := c.wifConfig.Gcp().ProjectId()
poolId := c.wifConfig.Gcp().WorkloadIdentityPool().PoolId()
jwks := c.wifConfig.Gcp().WorkloadIdentityPool().IdentityProvider().Jwks()
attributeMap := map[string]string{
"google.subject": "assertion.sub",
}
audiences := c.wifConfig.Gcp().WorkloadIdentityPool().IdentityProvider().AllowedAudiences()
description := fmt.Sprintf(wifDescription, c.wifConfig.DisplayName())
issuerUrl := c.wifConfig.Gcp().WorkloadIdentityPool().IdentityProvider().IssuerUrl()
jwks := c.wifConfig.Gcp().WorkloadIdentityPool().IdentityProvider().Jwks()
poolId := c.wifConfig.Gcp().WorkloadIdentityPool().PoolId()
projectId := c.wifConfig.Gcp().ProjectId()
providerId := c.wifConfig.Gcp().WorkloadIdentityPool().IdentityProvider().IdentityProviderId()
state := "ACTIVE"

parent := fmt.Sprintf("projects/%s/locations/global/workloadIdentityPools/%s", projectId, poolId)
providerResource := fmt.Sprintf("%s/providers/%s", parent, providerId)

_, err := c.gcpClient.GetWorkloadIdentityProvider(ctx, providerResource)
resp, err := c.gcpClient.GetWorkloadIdentityProvider(ctx, providerResource)
if err != nil {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 &&
strings.Contains(gerr.Message, "Requested entity was not found") {
description := fmt.Sprintf(wifDescription, c.wifConfig.DisplayName())
provider := &iamv1.WorkloadIdentityPoolProvider{
Name: providerId,
DisplayName: providerId,
Description: description,
State: "ACTIVE",
State: state,
Disabled: false,
Oidc: &iamv1.Oidc{
AllowedAudiences: audiences,
IssuerUri: issuerUrl,
JwksJson: jwks,
},
AttributeMapping: map[string]string{
"google.subject": "assertion.sub",
},
AttributeMapping: attributeMap,
}

_, err := c.gcpClient.CreateWorkloadIdentityProvider(ctx, parent, providerId, provider)
Expand All @@ -139,6 +153,38 @@ func (c *shim) CreateWorkloadIdentityProvider(
}
}

var needsUpdate bool
if resp.Description != description ||
resp.Disabled ||
resp.DisplayName != providerId ||
resp.State != state ||
resp.Oidc.IssuerUri != issuerUrl ||
resp.Oidc.JwksJson != jwks ||
!reflect.DeepEqual(resp.AttributeMapping, attributeMap) ||
!reflect.DeepEqual(resp.Oidc.AllowedAudiences, audiences) {
needsUpdate = true
}

if needsUpdate {
if err := c.gcpClient.UpdateWorkloadIdentityPoolOidcIdentityProvider(ctx,
&iamv1.WorkloadIdentityPoolProvider{
Name: providerResource,
DisplayName: providerId,
Description: description,
State: state,
Disabled: false,
Oidc: &iamv1.Oidc{
AllowedAudiences: audiences,
IssuerUri: issuerUrl,
JwksJson: jwks,
},
AttributeMapping: attributeMap,
},
); err != nil {
return err
}
log.Printf("Workload identity pool '%s' identity provider '%s' updated", poolId, providerId)
}
return nil
}

Expand All @@ -147,9 +193,20 @@ func (c *shim) CreateServiceAccounts(
log *log.Logger,
) error {
for _, serviceAccount := range c.wifConfig.Gcp().ServiceAccounts() {
if err := c.createServiceAccount(ctx, log, serviceAccount); err != nil {
sa, err := c.createServiceAccount(ctx, log, serviceAccount)
if err != nil {
return err
}
if sa.Disabled {
if err := c.gcpClient.EnableServiceAccount(
ctx,
serviceAccount.ServiceAccountId(),
c.wifConfig.Gcp().ProjectId(),
); err != nil {
return err
}
log.Printf("IAM service account %s enabled", serviceAccount.ServiceAccountId())
}
if err := c.createOrUpdateRoles(ctx, log, serviceAccount.Roles()); err != nil {
return err
}
Expand Down Expand Up @@ -177,11 +234,14 @@ func (c *shim) GrantSupportAccess(
return nil
}

// Returns the internal representation of the specified gcp service account on
// successful creation. If the service account already exists, the current
// instance of the service account is returned without error.
func (c *shim) createServiceAccount(
ctx context.Context,
log *log.Logger,
serviceAccount *cmv1.WifServiceAccount,
) error {
) (*adminpb.ServiceAccount, error) {
serviceAccountId := serviceAccount.ServiceAccountId()
serviceAccountName := c.wifConfig.DisplayName() + "-" + serviceAccountId
serviceAccountDescription := fmt.Sprintf(wifDescription, c.wifConfig.DisplayName())
Expand All @@ -193,20 +253,27 @@ func (c *shim) createServiceAccount(
Description: serviceAccountDescription,
},
}
_, err := c.gcpClient.CreateServiceAccount(ctx, request)
sa, err := c.gcpClient.CreateServiceAccount(ctx, request)
if err != nil {
pApiError, ok := err.(*apierror.APIError)
if ok {
if pApiError.GRPCStatus().Code() == codes.AlreadyExists {
return nil
return c.gcpClient.GetServiceAccount(
ctx,
&adminpb.GetServiceAccountRequest{
Name: gcp.FmtSaResourceId(
serviceAccount.ServiceAccountId(),
c.wifConfig.Gcp().ProjectId(),
)},
)
}
}
}
if err != nil {
return errors.Wrap(err, "Failed to create IAM service account")
return nil, errors.Wrap(err, "Failed to create IAM service account")
}
log.Printf("IAM service account %s created", serviceAccountId)
return nil
return sa, nil
}

func (c *shim) createOrUpdateRoles(
Expand Down Expand Up @@ -252,6 +319,16 @@ func (c *shim) createOrUpdateRoles(
log.Printf("Role %q undeleted", roleID)
}

// If role was disabled, enable role
if existingRole.Stage == adminpb.Role_DISABLED {
existingRole.Stage = adminpb.Role_GA
_, err := c.updateRole(ctx, existingRole, c.fmtRoleResourceId(role))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Failed to update %s", roleID))
}
log.Printf("Role %q enabled", roleID)
}

if addedPermissions, needsUpdate := c.missingPermissions(permissions, existingRole.IncludedPermissions); needsUpdate {
// Add missing permissions
existingRole.IncludedPermissions = append(existingRole.IncludedPermissions, addedPermissions...)
Expand Down
63 changes: 53 additions & 10 deletions pkg/gcp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,29 @@ import (
secretmanager "google.golang.org/api/secretmanager/v1"
)

//nolint:lll
type GcpClient interface {
AttachImpersonator(ctx context.Context, saId, projectId, impersonatorResourceId string) error
AttachWorkloadIdentityPool(ctx context.Context, sa *cmv1.WifServiceAccount, poolId, projectId string) error
CreateRole(context.Context, *adminpb.CreateRoleRequest) (*adminpb.Role, error)
CreateServiceAccount(ctx context.Context, request *adminpb.CreateServiceAccountRequest) (*adminpb.ServiceAccount, error) //nolint:lll
CreateWorkloadIdentityPool(ctx context.Context, parent, poolID string, pool *iamv1.WorkloadIdentityPool) (*iamv1.Operation, error) //nolint:lll
CreateWorkloadIdentityProvider(ctx context.Context, parent, providerID string, provider *iamv1.WorkloadIdentityPoolProvider) (*iamv1.Operation, error) //nolint:lll
CreateServiceAccount(ctx context.Context, request *adminpb.CreateServiceAccountRequest) (*adminpb.ServiceAccount, error)
CreateWorkloadIdentityPool(ctx context.Context, parent, poolID string, pool *iamv1.WorkloadIdentityPool) (*iamv1.Operation, error)
CreateWorkloadIdentityProvider(ctx context.Context, parent, providerID string, provider *iamv1.WorkloadIdentityPoolProvider) (*iamv1.Operation, error)
DeleteServiceAccount(ctx context.Context, saName string, project string, allowMissing bool) error
DeleteWorkloadIdentityPool(ctx context.Context, resource string) (*iamv1.Operation, error) //nolint:lll
GetProjectIamPolicy(ctx context.Context, projectName string, request *cloudresourcemanager.GetIamPolicyRequest) (*cloudresourcemanager.Policy, error) //nolint:lll
DeleteWorkloadIdentityPool(ctx context.Context, resource string) (*iamv1.Operation, error)
EnableServiceAccount(ctx context.Context, serviceAccountId string, projectId string) error
EnableWorkloadIdentityPool(ctx context.Context, poolId string) error
GetProjectIamPolicy(ctx context.Context, projectName string, request *cloudresourcemanager.GetIamPolicyRequest) (*cloudresourcemanager.Policy, error)
GetRole(context.Context, *adminpb.GetRoleRequest) (*adminpb.Role, error)
GetServiceAccount(ctx context.Context, request *adminpb.GetServiceAccountRequest) (*adminpb.ServiceAccount, error)
GetWorkloadIdentityPool(ctx context.Context, resource string) (*iamv1.WorkloadIdentityPool, error) //nolint:lll
GetWorkloadIdentityProvider(ctx context.Context, resource string) (*iamv1.WorkloadIdentityPoolProvider, error) //nolint:lll
GetWorkloadIdentityPool(ctx context.Context, resource string) (*iamv1.WorkloadIdentityPool, error)
GetWorkloadIdentityProvider(ctx context.Context, resource string) (*iamv1.WorkloadIdentityPoolProvider, error)
ProjectNumberFromId(ctx context.Context, projectId string) (int64, error)
SetProjectIamPolicy(ctx context.Context, svcAcctResource string, request *cloudresourcemanager.SetIamPolicyRequest) (*cloudresourcemanager.Policy, error) //nolint:lll
SetProjectIamPolicy(ctx context.Context, svcAcctResource string, request *cloudresourcemanager.SetIamPolicyRequest) (*cloudresourcemanager.Policy, error)
UndeleteRole(context.Context, *adminpb.UndeleteRoleRequest) (*adminpb.Role, error)
UndeleteWorkloadIdentityPool(ctx context.Context, resource string, request *iamv1.UndeleteWorkloadIdentityPoolRequest) (*iamv1.Operation, error) //nolint:lll
UndeleteWorkloadIdentityPool(ctx context.Context, resource string, request *iamv1.UndeleteWorkloadIdentityPoolRequest) (*iamv1.Operation, error)
UpdateRole(context.Context, *adminpb.UpdateRoleRequest) (*adminpb.Role, error)
UpdateWorkloadIdentityPoolOidcIdentityProvider(ctx context.Context, provider *iamv1.WorkloadIdentityPoolProvider) error
}

type gcpClient struct {
Expand Down Expand Up @@ -108,7 +112,7 @@ func (c *gcpClient) AttachWorkloadIdentityPool(
poolId string,
projectId string,
) error {
saResourceId := c.fmtSaResourceId(sa.ServiceAccountId(), projectId)
saResourceId := FmtSaResourceId(sa.ServiceAccountId(), projectId)

projectNum, err := c.ProjectNumberFromId(ctx, projectId)
if err != nil {
Expand Down Expand Up @@ -178,6 +182,34 @@ func (c *gcpClient) DeleteWorkloadIdentityPool(ctx context.Context, resource str
return c.oldIamClient.Projects.Locations.WorkloadIdentityPools.Delete(resource).Context(ctx).Do()
}

func (c *gcpClient) EnableServiceAccount(
ctx context.Context,
serviceAccountId string,
projectId string,
) error {
_, err := c.oldIamClient.Projects.ServiceAccounts.Enable(
FmtSaResourceId(serviceAccountId, projectId),
&iamv1.EnableServiceAccountRequest{},
).Do()
if err != nil {
return c.handleEnableServiceAccountError(err)
}
return nil
}

func (c *gcpClient) EnableWorkloadIdentityPool(
ctx context.Context,
poolId string,
) error {
_, err := c.oldIamClient.Projects.Locations.WorkloadIdentityPools.Patch(
poolId,
&iamv1.WorkloadIdentityPool{
Disabled: false,
},
).UpdateMask("disabled").Do()
return err
}

//nolint:lll
func (c *gcpClient) GetProjectIamPolicy(
ctx context.Context,
Expand Down Expand Up @@ -233,3 +265,14 @@ func (c *gcpClient) UndeleteWorkloadIdentityPool(ctx context.Context, resource s
func (c *gcpClient) UpdateRole(ctx context.Context, request *adminpb.UpdateRoleRequest) (*adminpb.Role, error) {
return c.iamClient.UpdateRole(ctx, request)
}

func (c *gcpClient) UpdateWorkloadIdentityPoolOidcIdentityProvider(
ctx context.Context,
provider *iamv1.WorkloadIdentityPoolProvider,
) error {
_, err := c.oldIamClient.Projects.Locations.WorkloadIdentityPools.Providers.Patch(
provider.Name,
provider,
).UpdateMask("attributeMapping,description,displayName,disabled,state,oidc").Do()
return err
}
9 changes: 9 additions & 0 deletions pkg/gcp/error_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/googleapis/gax-go/v2/apierror"
googleapi "google.golang.org/api/googleapi"
"google.golang.org/grpc/codes"
)

Expand Down Expand Up @@ -34,3 +35,11 @@ func (c *gcpClient) handleDeleteServiceAccountError(err error, allowMissing bool
}
return fmt.Errorf(pApiError.Details().String())
}

func (c *gcpClient) handleEnableServiceAccountError(err error) error {
gError, ok := err.(*googleapi.Error)
if !ok {
return fmt.Errorf("Unexpected error")
}
return fmt.Errorf(gError.Error())
}
2 changes: 1 addition & 1 deletion pkg/gcp/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import (
"fmt"
)

func (c *gcpClient) fmtSaResourceId(accountId, projectId string) string {
func FmtSaResourceId(accountId, projectId string) string {
return fmt.Sprintf("projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com", projectId, accountId, projectId)
}

0 comments on commit dc439d0

Please sign in to comment.