fix(storage): Storage Assume Role Support
This commit introduces fixes to enable operators to use AWS dynamic credentials on the storage side of this plugin, namely validation, improved credential lifecycle management to allow for storage buckets to be updated from static to dynamic credentials and vice versa, and a new way to determine credential types. It also introduces new tests for dynamic credential usage for the various functions.
This commit is contained in:
1 parent
d970294995
commit
2568eb83b8
15 files changed
+858
-210
No files matched your search
@@ -83,8 +83,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
||||
}
|
||||
|
||||
// Try to rotate static credentials
|
||||
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) {
|
||||
if cred.GetCredentialType(credState.CredentialsConfig) == cred.StaticAWS {
|
||||
if !catalogAttributes.DisableCredentialRotation {
|
||||
if err := credState.RotateCreds(ctx); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err)
|
||||
@@ -160,7 +159,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error loading persisted state: %s", err)
|
||||
}
|
||||
|
||||
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) {
|
||||
if cred.GetCredentialType(credState.CredentialsConfig) == cred.StaticAWS {
|
||||
if catalogAttributes.DisableCredentialRotation && !updateSecrets {
|
||||
// This is a validate check to make sure that we aren't disabling
|
||||
// rotation for credentials currently being managed by rotation.
|
||||
@@ -244,7 +243,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo
|
||||
}
|
||||
|
||||
// try to delete static credentials
|
||||
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) {
|
||||
if cred.GetCredentialType(credState.CredentialsConfig) == cred.StaticAWS {
|
||||
if !credState.CredsLastRotatedTime.IsZero() {
|
||||
// Delete old/existing credentials. This is done with the same
|
||||
// credentials to ensure that it has the proper permissions to do
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -76,17 +76,10 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
||||
}
|
||||
|
||||
// Try to rotate static credentials
|
||||
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) {
|
||||
if !storageAttributes.DisableCredentialRotation {
|
||||
if err := credState.RotateCreds(ctx); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err)
|
||||
}
|
||||
} else {
|
||||
// Simply validate if we aren't rotating.
|
||||
if err := credState.ValidateCreds(ctx); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error during credential validation: %s", err)
|
||||
}
|
||||
// Try to rotate AWS static credentials
|
||||
if cred.GetCredentialType(credConfig) == cred.StaticAWS && !storageAttributes.DisableCredentialRotation {
|
||||
if err := credState.RotateCreds(ctx); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,15 +149,18 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error loading persisted state: %s", err)
|
||||
}
|
||||
|
||||
// Verify the incoming credentials are valid and return any errors to the
|
||||
// user if they're not. Note this doesn't validate the credentials against
|
||||
// AWS - it only does logical validation on the fields.
|
||||
updatedCredentials, err := cred.GetCredentialsConfig(newBucket.GetSecrets(), newStorageAttributes.CredentialAttributes, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We will be updating credentials when one of two changes occur:
|
||||
// 1. the new bucket has secrets for static credentials, in which case we need to delete the old static credentials
|
||||
// 2. the new bucket has a different roleARN value for dynamic credentials
|
||||
if newBucket.GetSecrets() != nil || newStorageAttributes.RoleArn != oldStorageAttributes.RoleArn {
|
||||
updatedCredentials, err := cred.GetCredentialsConfig(newBucket.GetSecrets(), newStorageAttributes.CredentialAttributes, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Replace the existing credential state.
|
||||
// This checks the timestamp on the last rotation time as well
|
||||
// and deletes the credentials if we are managing them
|
||||
@@ -174,9 +170,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
||||
}
|
||||
}
|
||||
|
||||
credentialType := cred.GetCredentialType(credState.CredentialsConfig.AccessKey)
|
||||
switch credentialType {
|
||||
case cred.Static:
|
||||
if cred.GetCredentialType(credState.CredentialsConfig) == cred.StaticAWS {
|
||||
// This is a validate check to make sure that we aren't disabling
|
||||
// rotation for credentials currently being managed by rotation.
|
||||
// This is not allowed.
|
||||
@@ -193,16 +187,8 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err)
|
||||
}
|
||||
}
|
||||
case cred.Dynamic:
|
||||
// We cannot allow credential rotation for dynamic credentials
|
||||
if !newStorageAttributes.DisableCredentialRotation {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "cannot enable credential rotation for dynamic credential type")
|
||||
}
|
||||
case cred.Unknown:
|
||||
fallthrough
|
||||
default:
|
||||
return nil, status.Errorf(codes.InvalidArgument, "unknown credential type")
|
||||
}
|
||||
|
||||
storageState, err := newAwsStoragePersistedState(
|
||||
append([]awsStoragePersistedStateOption{
|
||||
withCredentials(credState),
|
||||
@@ -268,7 +254,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
|
||||
}
|
||||
|
||||
// try to delete static credentials
|
||||
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) {
|
||||
if cred.GetCredentialType(credState.CredentialsConfig) == cred.StaticAWS {
|
||||
if !credState.CredsLastRotatedTime.IsZero() {
|
||||
// Delete old/existing credentials. This is done with the same
|
||||
// credentials to ensure that it has the proper permissions to do
|
||||
|
||||
@@ -20,10 +20,12 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts/types"
|
||||
"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"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -104,6 +106,23 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
expectedErrContains: "not a valid region: foobar",
|
||||
expectedErrCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "dynamic credentials without disable credential rotation",
|
||||
req: &pb.OnCreateStorageBucketRequest{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Secrets: &structpb.Struct{Fields: map[string]*structpb.Value{}},
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::123456789012:role/S3Access"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrContains: "disable_credential_rotation attribute is required when providing a role_arn",
|
||||
expectedErrCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "persisted state setup error",
|
||||
req: validRequest(),
|
||||
@@ -141,21 +160,6 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
expectedErrContains: fmt.Sprintf("error during credential rotation: error rotating credentials: error calling CreateAccessKey: error calling iam.GetUser: %s", testGetUserErr),
|
||||
expectedErrCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "validation error",
|
||||
req: validRequest(),
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.WithSTSAPIFunc(
|
||||
awsutilv2.NewMockSTS(
|
||||
awsutilv2.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
|
||||
),
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: fmt.Sprintf("error during credential validation: error validating credentials: %s", testGetCallerIdentityErr),
|
||||
expectedErrCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "dryRunValidation failed putObject",
|
||||
req: validRequest(),
|
||||
@@ -324,6 +328,44 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
credential.ConstSecretAccessKey: "two",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success with dynamic credentials",
|
||||
req: &pb.OnCreateStorageBucketRequest{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Secrets: &structpb.Struct{},
|
||||
Attributes: credential.MockAssumeRoleAttributes("us-west-2", true),
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts(
|
||||
[]awsutilv2.Option{
|
||||
awsutilv2.WithSTSAPIFunc(
|
||||
awsutilv2.NewMockSTS(
|
||||
awsutilv2.WithAssumeRoleOutput(&sts.AssumeRoleOutput{
|
||||
Credentials: &types.Credentials{
|
||||
AccessKeyId: aws.String("ASIAfoobar"),
|
||||
Expiration: aws.Time(time.Now().Add(time.Hour)),
|
||||
SecretAccessKey: aws.String("secretkeyfoobar"),
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}),
|
||||
testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}),
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}),
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedPersistedData: map[string]any{}, // No credentials sent back to Boundary as they are temporary.
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -344,8 +386,10 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
require.NotNil(resp)
|
||||
require.NotNil(resp.GetPersisted())
|
||||
require.NotNil(resp.GetPersisted().GetData())
|
||||
|
||||
actualPersistedData := resp.GetPersisted().GetData().AsMap()
|
||||
if len(actualPersistedData) != 0 {
|
||||
if len(tc.expectedPersistedData) != 0 {
|
||||
require.NotEmpty(actualPersistedData)
|
||||
require.Equal(tc.expectedPersistedData[credential.ConstAccessKeyId], actualPersistedData[credential.ConstAccessKeyId])
|
||||
require.Equal(tc.expectedPersistedData[credential.ConstSecretAccessKey], actualPersistedData[credential.ConstSecretAccessKey])
|
||||
require.NotEmpty(actualPersistedData[credential.ConstCredsLastRotatedTime])
|
||||
@@ -383,12 +427,14 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
}
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
req *pb.OnUpdateStorageBucketRequest
|
||||
credOpts []credential.AwsCredentialPersistedStateOption
|
||||
storageOpts []awsStoragePersistedStateOption
|
||||
expectedErrContains string
|
||||
expectedErrCode codes.Code
|
||||
name string
|
||||
req *pb.OnUpdateStorageBucketRequest
|
||||
credOpts []credential.AwsCredentialPersistedStateOption
|
||||
storageOpts []awsStoragePersistedStateOption
|
||||
expectedPersistedData map[string]any
|
||||
expectCredsLastRotatedTime bool
|
||||
expectedErrContains string
|
||||
expectedErrCode codes.Code
|
||||
}{
|
||||
{
|
||||
name: "nil new bucket",
|
||||
@@ -552,6 +598,36 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
expectedErrContains: "cannot disable rotation for already-rotated credentials",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
},
|
||||
{
|
||||
name: "attempt to enable credential rotation with dynamic credentials",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
CurrentBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::123456789012:role/S3Access"),
|
||||
},
|
||||
},
|
||||
},
|
||||
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),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::123456789012:role/S3Access"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{
|
||||
Data: credential.MockAssumeRoleAttributes("us-west-2", true),
|
||||
},
|
||||
},
|
||||
expectedErrContains: "disable_credential_rotation attribute is required when providing a role_arn",
|
||||
expectedErrCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "updating secrets, replace error",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
@@ -796,6 +872,11 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedPersistedData: map[string]any{
|
||||
credential.ConstAccessKeyId: "AKIA_one",
|
||||
credential.ConstSecretAccessKey: "two",
|
||||
},
|
||||
expectCredsLastRotatedTime: true,
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
@@ -811,6 +892,488 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedPersistedData: map[string]any{
|
||||
credential.ConstAccessKeyId: "AKIA_foobar",
|
||||
credential.ConstSecretAccessKey: "bazqux",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dry run fail on dynamic to dynamic credentials update",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
CurrentBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::123456789012:role/S3Access"),
|
||||
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.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::098765432109:role/S3Access"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{Data: &structpb.Struct{}},
|
||||
},
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithHeadObjectError(fmt.Errorf("head object failed oops")),
|
||||
testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}),
|
||||
testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}),
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "head object failed oops",
|
||||
expectedErrCode: codes.Internal,
|
||||
},
|
||||
{
|
||||
name: "dynamic to dynamic credentials success",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
CurrentBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::123456789012:role/S3Access"),
|
||||
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.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::098765432109:role/S3Access"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{Data: &structpb.Struct{}},
|
||||
},
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}),
|
||||
testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}),
|
||||
testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}),
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedPersistedData: map[string]any{},
|
||||
},
|
||||
{
|
||||
name: "dry run fail on static non rotated to dynamic credentials update",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
CurrentBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
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.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::098765432109:role/S3Access"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{
|
||||
Data: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("secretkey"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithPutObjectError(fmt.Errorf("put object failed oops")),
|
||||
testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}),
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}),
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "put object failed oops",
|
||||
expectedErrCode: codes.Internal,
|
||||
},
|
||||
{
|
||||
name: "success static non rotated to dynamic credentials",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
CurrentBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
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.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::098765432109:role/S3Access"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{
|
||||
Data: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("secretkey"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}),
|
||||
testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}),
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}),
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedPersistedData: map[string]any{},
|
||||
},
|
||||
{
|
||||
name: "DeleteAccessKey error on static rotated to dynamic credentials update",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
CurrentBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
NewBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::098765432109:role/S3Access"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{
|
||||
Data: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar_rotated_accesskeyid"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("secretkey"),
|
||||
credential.ConstCredsLastRotatedTime: structpb.NewStringValue(time.Now().Add(-time.Hour).Format(time.RFC3339Nano)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithDeleteAccessKeyError(fmt.Errorf("delete access key fail oops")),
|
||||
),
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "delete access key fail oops",
|
||||
expectedErrCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "success static rotated to dynamic credentials",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
CurrentBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
NewBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::098765432109:role/S3Access"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{
|
||||
Data: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar_rotated_accesskeyid"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("secretkey"),
|
||||
credential.ConstCredsLastRotatedTime: structpb.NewStringValue(time.Now().Add(-time.Hour).Format(time.RFC3339Nano)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(), // DeleteAccessKey Success
|
||||
),
|
||||
}),
|
||||
},
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}),
|
||||
testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}),
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}),
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedPersistedData: map[string]any{},
|
||||
},
|
||||
{
|
||||
name: "dry run fail on dynamic to static non-rotated credentials update",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
CurrentBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::098765432109:role/S3Access"),
|
||||
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(true),
|
||||
},
|
||||
},
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("secretkey"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{
|
||||
Data: &structpb.Struct{Fields: map[string]*structpb.Value{}},
|
||||
},
|
||||
},
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithPutObjectError(fmt.Errorf("put object fail oops")),
|
||||
testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}),
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}),
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "put object fail oops",
|
||||
expectedErrCode: codes.Internal,
|
||||
},
|
||||
{
|
||||
name: "success dynamic to static non-rotated credentials",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
CurrentBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::098765432109:role/S3Access"),
|
||||
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(true),
|
||||
},
|
||||
},
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("secretkey"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{
|
||||
Data: &structpb.Struct{Fields: map[string]*structpb.Value{}},
|
||||
},
|
||||
},
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}),
|
||||
testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}),
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}),
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedPersistedData: map[string]any{
|
||||
credential.ConstAccessKeyId: "AKIA_foobar",
|
||||
credential.ConstSecretAccessKey: "secretkey",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create access key error on dynamic to static rotated credentials update",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
CurrentBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::098765432109:role/S3Access"),
|
||||
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"),
|
||||
},
|
||||
},
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("secretkey"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{
|
||||
Data: &structpb.Struct{Fields: map[string]*structpb.Value{}},
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.WithSTSAPIFunc(
|
||||
awsutilv2.NewMockSTS(),
|
||||
),
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithGetUserOutput(
|
||||
&iam.GetUserOutput{
|
||||
User: &iamTypes.User{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"),
|
||||
UserId: aws.String("AIDAJQABLZS4A3QDU576Q"),
|
||||
UserName: aws.String("JohnDoe"),
|
||||
},
|
||||
},
|
||||
),
|
||||
awsutilv2.WithCreateAccessKeyError(fmt.Errorf("create access key fail oops")),
|
||||
),
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "create access key fail oops",
|
||||
expectedErrCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "success dynamic to static rotated credentials",
|
||||
req: &pb.OnUpdateStorageBucketRequest{
|
||||
CurrentBucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::098765432109:role/S3Access"),
|
||||
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"),
|
||||
},
|
||||
},
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("secretkey"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{
|
||||
Data: &structpb.Struct{Fields: map[string]*structpb.Value{}},
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.WithSTSAPIFunc(
|
||||
awsutilv2.NewMockSTS(),
|
||||
),
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithGetUserOutput(
|
||||
&iam.GetUserOutput{
|
||||
User: &iamTypes.User{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"),
|
||||
UserId: aws.String("AIDAJQABLZS4A3QDU576Q"),
|
||||
UserName: aws.String("JohnDoe"),
|
||||
},
|
||||
},
|
||||
),
|
||||
awsutilv2.WithCreateAccessKeyOutput(
|
||||
&iam.CreateAccessKeyOutput{
|
||||
AccessKey: &iamTypes.AccessKey{
|
||||
AccessKeyId: aws.String("AKIA_one"),
|
||||
SecretAccessKey: aws.String("two"),
|
||||
UserName: aws.String("JohnDoe"),
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
}),
|
||||
},
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}),
|
||||
testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}),
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}),
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedPersistedData: map[string]any{
|
||||
credential.ConstAccessKeyId: "AKIA_one",
|
||||
credential.ConstSecretAccessKey: "two",
|
||||
},
|
||||
expectCredsLastRotatedTime: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -824,6 +1387,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
}
|
||||
resp, err := p.OnUpdateStorageBucket(context.Background(), tc.req)
|
||||
if tc.expectedErrContains != "" {
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), tc.expectedErrContains)
|
||||
require.Equal(status.Code(err).String(), tc.expectedErrCode.String())
|
||||
return
|
||||
@@ -833,18 +1397,18 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
require.NotNil(resp.GetPersisted())
|
||||
require.NotNil(resp.GetPersisted().GetData())
|
||||
|
||||
var disableCredentialRotation bool
|
||||
expectedAccessKey := tc.req.Persisted.Data.AsMap()[credential.ConstAccessKeyId]
|
||||
expectedSecretKey := tc.req.Persisted.Data.AsMap()[credential.ConstSecretAccessKey]
|
||||
if value, ok := tc.req.GetNewBucket().GetAttributes().AsMap()[credential.ConstDisableCredentialRotation]; ok {
|
||||
disableCredentialRotation = value.(bool)
|
||||
actualPersistedData := resp.GetPersisted().GetData().AsMap()
|
||||
if len(actualPersistedData) > 0 && len(tc.expectedPersistedData) == 0 {
|
||||
t.Fatalf("test case expected no persisted data, but got %#v", actualPersistedData)
|
||||
}
|
||||
if !disableCredentialRotation {
|
||||
expectedAccessKey = "AKIA_one"
|
||||
expectedSecretKey = "two"
|
||||
if len(tc.expectedPersistedData) != 0 {
|
||||
require.NotEmpty(actualPersistedData)
|
||||
require.Equal(tc.expectedPersistedData[credential.ConstAccessKeyId], actualPersistedData[credential.ConstAccessKeyId])
|
||||
require.Equal(tc.expectedPersistedData[credential.ConstSecretAccessKey], actualPersistedData[credential.ConstSecretAccessKey])
|
||||
if tc.expectCredsLastRotatedTime {
|
||||
require.NotEmpty(actualPersistedData[credential.ConstCredsLastRotatedTime])
|
||||
}
|
||||
}
|
||||
require.Equal(expectedAccessKey, resp.GetPersisted().GetData().AsMap()[credential.ConstAccessKeyId])
|
||||
require.Equal(expectedSecretKey, resp.GetPersisted().GetData().AsMap()[credential.ConstSecretAccessKey])
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -988,6 +1552,23 @@ func TestStoragePlugin_OnDeleteStorageBucket(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success with dynamic credentials",
|
||||
req: &pb.OnDeleteStorageBucketRequest{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::098765432109:role/S3Access"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &storagebuckets.StorageBucketPersisted{
|
||||
Data: &structpb.Struct{Fields: map[string]*structpb.Value{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -1171,6 +1752,37 @@ func TestStoragePlugin_HeadObject(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success with dynamic credentials",
|
||||
req: &pb.HeadObjectRequest{
|
||||
Key: "/foo/bar/key",
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Secrets: new(structpb.Struct),
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::123456789012:role/S3Access"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
credOpts: validSTSMock(),
|
||||
contentLength: 1024,
|
||||
lastModified: createTime(t, "2006-01-02T15:04:05Z"),
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{
|
||||
ContentLength: 1024,
|
||||
LastModified: aws.Time(createTime(t, "2006-01-02T15:04:05Z")),
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -1401,6 +2013,32 @@ func TestStoragePlugin_ValidatePermissions(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success with dynamic credentials",
|
||||
req: &pb.ValidatePermissionsRequest{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::123456789012:role/S3Access"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
credOpts: validSTSMock(),
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}),
|
||||
testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}),
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -1602,6 +2240,34 @@ func TestStoragePlugin_GetObject(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success with dynamic credentials",
|
||||
req: &pb.GetObjectRequest{
|
||||
Key: "/foo/bar/key",
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "foo",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::123456789012:role/S3Access"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
credOpts: validSTSMock(),
|
||||
objectData: []byte("test"),
|
||||
storageOpts: []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithGetObjectOutput(&s3.GetObjectOutput{
|
||||
Body: ioutil.NopCloser(bytes.NewReader([]byte("test"))),
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -1970,6 +2636,43 @@ func TestStoragePlugin_PutObject(t *testing.T) {
|
||||
}
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "valid file using dynamic credentials",
|
||||
request: &pb.PutObjectRequest{
|
||||
Bucket: &storagebuckets.StorageBucket{
|
||||
BucketName: "external-obj-store",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::123456789012:role/S3Access"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "mock-object",
|
||||
Path: validFilePath,
|
||||
},
|
||||
credOpts: validSTSMock(),
|
||||
expectedObject: []byte("CONTENT CHECK"),
|
||||
storageOpts: func() []awsStoragePersistedStateOption {
|
||||
hash := sha256.New()
|
||||
_, err := hash.Write([]byte("CONTENT CHECK"))
|
||||
require.NoError(t, err)
|
||||
checksum := base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
||||
return []awsStoragePersistedStateOption{
|
||||
withTestS3APIFunc(
|
||||
newTestMockS3(
|
||||
&testMockS3State{
|
||||
PutObjectBody: []byte("CONTENT CHECK"),
|
||||
},
|
||||
testMockS3WithPutObjectOutput(&s3.PutObjectOutput{
|
||||
ChecksumSHA256: aws.String(checksum),
|
||||
}),
|
||||
),
|
||||
),
|
||||
}
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"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"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
Reference in new issue
Block a user