diff --git a/plugin/service/storage/plugin.go b/plugin/service/storage/plugin.go index 870bd74..c6a05b4 100644 --- a/plugin/service/storage/plugin.go +++ b/plugin/service/storage/plugin.go @@ -21,6 +21,7 @@ import ( cred "github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" pb "github.com/hashicorp/boundary/sdk/pbs/plugin" + awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" @@ -39,6 +40,10 @@ type StoragePlugin struct { // testStorageStateOpts are passed in to the stored state to control test behavior testStorageStateOpts []awsStoragePersistedStateOption + + // testAwsUtilV2Opts are passed to awsutilv2 directly (currently used for + // GenerateCredentialChain). + testAwsUtilV2Opts []awsutilv2.Option } // OnCreateStorageBucket is called when a storage bucket is created. @@ -174,7 +179,17 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd } } - credentialType := cred.GetCredentialType(credState.CredentialsConfig.AccessKey) + awsCfg, err := credState.CredentialsConfig.GenerateCredentialChain(ctx, p.testAwsUtilV2Opts...) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to generate aws credential chain: %s", err) + } + + awsCredentials, err := awsCfg.Credentials.Retrieve(ctx) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to retrieve aws credentials: %s", err) + } + + credentialType := cred.GetCredentialType(awsCredentials.AccessKeyID) switch credentialType { case cred.Static: // This is a validate check to make sure that we aren't disabling diff --git a/plugin/service/storage/plugin_test.go b/plugin/service/storage/plugin_test.go index 50add5e..c713f7b 100644 --- a/plugin/service/storage/plugin_test.go +++ b/plugin/service/storage/plugin_test.go @@ -849,6 +849,83 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { } } +func TestStoragePlugin_OnUpdateStorageBucket_DynamicCredentials(t *testing.T) { + cases := []struct { + name string + req *pb.OnUpdateStorageBucketRequest + credOpts []credential.AwsCredentialPersistedStateOption + storageOpts []awsStoragePersistedStateOption + awsutilV2Opts []awsutilv2.Option + expectedErrContains string + expectedErrCode codes.Code + }{ + { + name: "attempt to enable credential rotation", + req: &pb.OnUpdateStorageBucketRequest{ + CurrentBucket: &storagebuckets.StorageBucket{ + Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + credential.ConstRegion: structpb.NewStringValue("us-west-2"), + credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true), + }, + }, + }, + NewBucket: &storagebuckets.StorageBucket{ + BucketName: "foo", + Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + credential.ConstRegion: structpb.NewStringValue("us-west-2"), + credential.ConstDisableCredentialRotation: structpb.NewBoolValue(false), + }, + }, + }, + Persisted: &storagebuckets.StorageBucketPersisted{ + Data: credential.MockAssumeRoleAttributes("us-west-2", true), + }, + }, + awsutilV2Opts: []awsutilv2.Option{ + awsutilv2.WithCredentialsProvider( + awsutilv2.NewMockCredentialsProvider( + awsutilv2.WithCredentials( + aws.Credentials{ + AccessKeyID: "ASIA_one", + SecretAccessKey: "secret_key_123", + SessionToken: "session_token_123", + CanExpire: true, + Expires: time.Now().Add(time.Hour * 2), + }, + ), + ), + ), + }, + expectedErrContains: "cannot enable credential rotation for dynamic credential type", + expectedErrCode: codes.InvalidArgument, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + require := require.New(t) + p := &StoragePlugin{ + testCredStateOpts: tc.credOpts, + testStorageStateOpts: tc.storageOpts, + testAwsUtilV2Opts: tc.awsutilV2Opts, + } + + resp, err := p.OnUpdateStorageBucket(context.Background(), tc.req) + if tc.expectedErrContains != "" { + require.Contains(err.Error(), tc.expectedErrContains) + require.Equal(status.Code(err).String(), tc.expectedErrCode.String()) + return + } + require.NoError(err) + require.NotNil(resp) + require.NotNil(resp.GetPersisted()) + require.NotNil(resp.GetPersisted().GetData()) + }) + } +} + func TestStoragePlugin_OnDeleteStorageBucket(t *testing.T) { cases := []struct { name string