lib/adapter/aws_adapter.go (170 lines of code) (raw):

// Copyright 2020 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package adapter import ( "context" "fmt" "time" "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/aws" /* copybara-comment: aws */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/clouds" /* copybara-comment: clouds */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/ga4gh" /* copybara-comment: ga4gh */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/httputils" /* copybara-comment: httputils */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/processgc" /* copybara-comment: processgc */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/srcutil" /* copybara-comment: srcutil */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/storage" /* copybara-comment: storage */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/timeutil" /* copybara-comment: timeutil */ pb "github.com/GoogleCloudPlatform/healthcare-federated-access-services/proto/dam/v1" /* copybara-comment: go_proto */ ) const ( // AwsAdapterName is the name identifier exposed in config files. AwsAdapterName = "aws" platformName = "aws" ) const ( defaultGcFrequency = 1 * 24 * time.Hour /* 1 day */ defaultKeysPerAccount = 2 ) // AwsAdapter is the AWS IAM adapter. type AwsAdapter struct { desc map[string]*pb.ServiceDescriptor warehouse *aws.AccountWarehouse } // NewAwsAdapter creates a new AwsAdapter. func NewAwsAdapter(store storage.Store, awsClient aws.APIClient) (ServiceAdapter, error) { var msg pb.ServicesResponse path := adapterFilePath(AwsAdapterName) if err := srcutil.LoadProto(path, &msg); err != nil { return nil, fmt.Errorf("reading %q service descriptors from path %q: %v", aggregatorName, path, err) } ctx := context.Background() wh, err := aws.NewWarehouse(ctx, awsClient) if err != nil { return nil, fmt.Errorf("error creating AWS key warehouse: %v", err) } keyGC := processgc.NewKeyGC("aws_key_gc", wh, store, defaultGcFrequency, defaultKeysPerAccount, func(account *clouds.Account) bool { return true }) //Register Accounts if err := registerAccountGC(store, keyGC, wh); err != nil { return nil, fmt.Errorf("error registering AWS account key GC: %v", err) } // Update Settings ttl := defaultGcFrequency if err := keyGC.UpdateSettings(ttl, defaultKeysPerAccount, nil); err != nil { return nil, fmt.Errorf("error updating settings: %v", err) } go keyGC.Run(ctx) return &AwsAdapter{ desc: msg.Services, warehouse: wh, }, nil } // Name returns the name identifier of the adapter as used in configurations. func (a *AwsAdapter) Name() string { return AwsAdapterName } // Descriptors returns a map of ServiceDescriptor descriptor. func (a *AwsAdapter) Descriptors() map[string]*pb.ServiceDescriptor { return a.desc } // Platform returns the name identifier of the platform on which this adapter operates. func (a *AwsAdapter) Platform() string { return platformName } // IsAggregator returns true if this adapter requires TokenAction.Aggregates. func (a *AwsAdapter) IsAggregator() bool { return false } // CheckConfig validates that a new configuration is compatible with this adapter. func (a *AwsAdapter) CheckConfig(templateName string, template *pb.ServiceTemplate, resName, viewName string, view *pb.View, cfg *pb.DamConfig, adapters *ServiceAdapters) (string, error) { if view == nil { return "", nil } if len(view.Items) == 1 { vars, path, err := GetItemVariables(adapters, template.ServiceName, view.Items[0]) if err != nil { return httputils.StatusPath("resources", resName, "views", viewName, "items", "0", path), err } if template.ServiceName == aws.S3ItemFormat { if vars["bucket"] == "" { return httputils.StatusPath("resources", resName, "views", viewName, "items", "0", "vars", "bucket"), fmt.Errorf("no bucket specified") } } else if template.ServiceName == aws.RedshiftItemFormat { if vars["cluster"] == "" { return httputils.StatusPath("resources", resName, "views", viewName, "items", "0", "vars", "cluster"), fmt.Errorf("no cluster specified") } } else if template.ServiceName != aws.RedshiftConsoleItemFormat { return httputils.StatusPath("serviceTemplates", templateName, "serviceName", template.ServiceName), fmt.Errorf("invalid service name: %s", template.ServiceName) } } if len(view.Items) > 1 { return httputils.StatusPath("resources", resName, "views", viewName, "items"), fmt.Errorf("more than one item is declared for the view %q", viewName) } return "", nil } // MintToken has the adapter mint a token. func (a *AwsAdapter) MintToken(ctx context.Context, input *Action) (*MintTokenResult, error) { if a.warehouse == nil { return nil, fmt.Errorf("AWS minting token: DAM service account warehouse not configured") } userID := ga4gh.TokenUserID(input.Identity, SawMaxUserIDLength) params, err := createAwsResourceTokenCreationParams(userID, input) if err != nil { return nil, fmt.Errorf("AWS minting token: %v", err) } result, err := a.warehouse.MintTokenWithTTL(ctx, params) if err != nil { return nil, fmt.Errorf("AWS minting token: %v", err) } credentials := map[string]string{ "account": result.Account, "principal": result.PrincipalARN, } if result.AccessKeyID != nil { credentials["access_key_id"] = *result.AccessKeyID } if result.SecretAccessKey != nil { credentials["secret"] = *result.SecretAccessKey } if result.SessionToken != nil { credentials["session_token"] = *result.SessionToken } if result.UserName != nil { credentials["username"] = *result.UserName } if result.Password != nil { credentials["password"] = *result.Password } return &MintTokenResult{ Credentials: credentials, TokenFormat: result.Format, }, nil } func createAwsResourceTokenCreationParams(userID string, input *Action) (*aws.ResourceParams, error) { var roles []string var scopes []string if input.ServiceRole != nil { rolesArg := input.ServiceRole.ServiceArgs["roles"] if rolesArg != nil && rolesArg.GetValues() != nil && len(rolesArg.GetValues()) > 0 { roles = append(roles, rolesArg.GetValues()...) } scopesArg := input.ServiceRole.ServiceArgs["scopes"] if scopesArg != nil && scopesArg.GetValues() != nil && len(scopesArg.GetValues()) > 0 { scopes = append(scopes, scopesArg.GetValues()...) } } var vars map[string]string if len(input.View.Items) == 0 { vars = make(map[string]string, 0) } else if len(input.View.Items) == 1 { vars = scrubVars(input.View.Items[0].Args) } else { return nil, fmt.Errorf("too many items declared") } maxKeyTTL := timeutil.ParseDurationWithDefault(input.Config.Options.GcpManagedKeysMaxRequestedTtl, input.MaxTTL) return &aws.ResourceParams{ UserID: userID, TTL: input.TTL, MaxKeyTTL: maxKeyTTL, ManagedKeysPerAccount: int(input.Config.Options.AwsManagedKeysPerIamUser), Vars: vars, TargetRoles: roles, TargetScopes: scopes, DamResourceID: input.ResourceID, DamViewID: input.ViewID, DamRoleID: input.GrantRole, DamInterfaceID: input.Interface, ServiceTemplate: input.ServiceTemplate, }, nil } func registerAccountGC(_ storage.Store, keyGC *processgc.KeyGC, wh *aws.AccountWarehouse) error { _, err := keyGC.RegisterWork(wh.GetAwsAccount(), nil, nil) return err }