Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fa6b7e7a3 | ||
|
|
d970294995 |
No files matched your search
@@ -17,7 +17,7 @@ and then the given credential will be revoked. In this way, after rotation,
|
||||
only Boundary knows the client secret in use by this plugin. More information
|
||||
about AWS static credentials can be found [here](https://docs.aws.amazon.com/sdkref/latest/guide/feature-static-credentials.html).
|
||||
|
||||
Credential rotation can be turned off by setting the
|
||||
Credential rotation can be turned off by setting the
|
||||
`disable_credential_rotation` attribute to `true`.
|
||||
|
||||
### Assume Role Credentials
|
||||
@@ -48,7 +48,7 @@ The plugin fetches hosts through the
|
||||
[DescribeInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html)
|
||||
call.
|
||||
|
||||
[Getting Started](plugin/service/host/README.md)
|
||||
[Getting Started](https://github.com/hashicorp/boundary-plugin-aws/plugin/service/host/README.md)
|
||||
|
||||
## Storage Bucket
|
||||
|
||||
@@ -60,7 +60,7 @@ these storage bucket resources can in turn be associated to targets within
|
||||
Boundary.
|
||||
|
||||
At creation, update or deletion of a storage bucket of this type, configuration of the
|
||||
plugin is performed via the attribute/secret values passed to the create, update, or
|
||||
plugin is performed via the attribute/secret values passed to the create, update, or
|
||||
delete calls actions. The values passed in to the plugin here are the attributes set
|
||||
on on a storage bucket in boundary.
|
||||
|
||||
@@ -76,4 +76,4 @@ The plugin fetches metadata about the files through the
|
||||
[HeadObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html)
|
||||
call.
|
||||
|
||||
[Getting Started](plugin/service/storage/README.md)
|
||||
[Getting Started](https://github.com/hashicorp/boundary-plugin-aws/plugin/service/storage/README.md)
|
||||
@@ -13,7 +13,7 @@ require (
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/hashicorp/boundary/sdk v0.0.33
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2
|
||||
github.com/hashicorp/terraform-json v0.14.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
@@ -21,6 +21,8 @@ require (
|
||||
google.golang.org/protobuf v1.28.1
|
||||
)
|
||||
|
||||
replace github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2 => github.com/hashicorp/go-secure-stdlib/awsutilv2 v0.0.0-20230809054404-9029211590fd
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.33 // indirect
|
||||
|
||||
@@ -94,8 +94,8 @@ github.com/hashicorp/go-kms-wrapping/v2 v2.0.9/go.mod h1:NtMaPhqSlfQ72XWDD2g80o8
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-plugin v1.4.9 h1:ESiK220/qE0aGxWdzKIvRH69iLiuN/PjoLTm69RoWtU=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0 h1:ca5TSI4AgaOncPpyzLDtCGjVEtKukONpeM95vFxXCOQ=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0/go.mod h1:7CUvZtfTp2U0CYQCLzMtS2ngckjAZePSfwrE2aeDP1M=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutilv2 v0.0.0-20230809054404-9029211590fd h1:46A3lfywoivyHBOXlXioGDSDo3UJj2aW2ncnSCSJ7bs=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutilv2 v0.0.0-20230809054404-9029211590fd/go.mod h1:XnoCjGsUi+UKk0+0P5TIS2rGKdw7O9ehSJy+kIuffBY=
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
||||
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.9 h1:YZ01/aiARPneDUryzjiPGvr3pmjCSC+1WBZKI1P7I7k=
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/endpoints"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/values"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@ type CredentialAttributes struct {
|
||||
// GetCredentialsConfig parses values out of a protobuf struct secrets and returns a
|
||||
// CredentialsConfig used for configuring an AWS session. An error is returned if
|
||||
// any unrecognized fields are found in the protobuf struct input.
|
||||
func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, required bool) (*awsutil.CredentialsConfig, error) {
|
||||
func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, required bool) (*awsutilv2.CredentialsConfig, error) {
|
||||
// initialize secrets if it is nil
|
||||
// secrets can be nil because static credentials are optional
|
||||
if secrets == nil {
|
||||
@@ -55,6 +55,10 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
|
||||
}
|
||||
delete(unknownFields, ConstAccessKeyId)
|
||||
|
||||
if accessKey != "" && GetCredentialType(accessKey) != Static {
|
||||
badFields[fmt.Sprintf("secrets.%s", ConstAccessKeyId)] = "requires long-term access key beginning with \"AKIA\" prefix"
|
||||
}
|
||||
|
||||
secretKey, err := values.GetStringValue(secrets, ConstSecretAccessKey, required)
|
||||
if err != nil {
|
||||
badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = err.Error()
|
||||
@@ -68,7 +72,7 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
|
||||
badFields[fmt.Sprintf("secrets.%s", s)] = "unrecognized field"
|
||||
}
|
||||
|
||||
opts := []awsutil.Option{}
|
||||
opts := []awsutilv2.Option{}
|
||||
// logic for parsing the credential type
|
||||
// supported types:
|
||||
// - user static credential
|
||||
@@ -79,25 +83,22 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
|
||||
case accessKey != "" && secretKey != "" && attrs.RoleArn != "":
|
||||
badFields[fmt.Sprintf("secrets.%s", ConstAccessKeyId)] = "conflicts with role_arn value"
|
||||
badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = "conflicts with role_arn value"
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstRoleArn)] = "conflicts with access_key_id and secret_access_key values"
|
||||
badFields[fmt.Sprintf("secrets.%s", ConstRoleArn)] = "conflicts with access_key_id and secret_access_key values"
|
||||
// static credential is missing it's secret_access_key
|
||||
case accessKey != "" && secretKey == "":
|
||||
badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = "missing required value"
|
||||
// static credential is missing it's access_key_id
|
||||
case accessKey == "" && secretKey != "":
|
||||
badFields[fmt.Sprintf("secrets.%s", ConstAccessKeyId)] = "missing required value"
|
||||
// dynamic credentials and credential rotation is not supported
|
||||
case len(attrs.RoleArn) > 0 && !attrs.DisableCredentialRotation:
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstDisableCredentialRotation)] = "disable_credential_rotation attribute is required when providing a role_arn"
|
||||
// add static credentials
|
||||
case accessKey != "" && secretKey != "":
|
||||
opts = append(opts,
|
||||
awsutil.WithAccessKey(accessKey),
|
||||
awsutil.WithSecretKey(secretKey),
|
||||
awsutilv2.WithAccessKey(accessKey),
|
||||
awsutilv2.WithSecretKey(secretKey),
|
||||
)
|
||||
// add dynamic credentials
|
||||
case attrs.RoleArn != "":
|
||||
opts = append(opts, awsutil.WithRoleArn(attrs.RoleArn))
|
||||
opts = append(opts, awsutilv2.WithRoleArn(attrs.RoleArn))
|
||||
}
|
||||
|
||||
if len(badFields) > 0 {
|
||||
@@ -105,19 +106,19 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
|
||||
}
|
||||
|
||||
if attrs.Region != "" {
|
||||
opts = append(opts, awsutil.WithRegion(attrs.Region))
|
||||
opts = append(opts, awsutilv2.WithRegion(attrs.Region))
|
||||
}
|
||||
if attrs.RoleExternalId != "" {
|
||||
opts = append(opts, awsutil.WithRoleExternalId(attrs.RoleExternalId))
|
||||
opts = append(opts, awsutilv2.WithRoleExternalId(attrs.RoleExternalId))
|
||||
}
|
||||
if attrs.RoleSessionName != "" {
|
||||
opts = append(opts, awsutil.WithRoleSessionName(attrs.RoleSessionName))
|
||||
opts = append(opts, awsutilv2.WithRoleSessionName(attrs.RoleSessionName))
|
||||
}
|
||||
if len(attrs.RoleTags) != 0 {
|
||||
opts = append(opts, awsutil.WithRoleTags(attrs.RoleTags))
|
||||
opts = append(opts, awsutilv2.WithRoleTags(attrs.RoleTags))
|
||||
}
|
||||
|
||||
return awsutil.NewCredentialsConfig(opts...)
|
||||
return awsutilv2.NewCredentialsConfig(opts...)
|
||||
}
|
||||
|
||||
// GetCredentialAttributes parses values out of a protobuf struct input and returns a
|
||||
|
||||
@@ -6,7 +6,7 @@ package credential
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -114,7 +114,7 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
secrets *structpb.Struct
|
||||
required bool
|
||||
attrs *CredentialAttributes
|
||||
expected *awsutil.CredentialsConfig
|
||||
expected *awsutilv2.CredentialsConfig
|
||||
expectedErrContains string
|
||||
}{
|
||||
{
|
||||
@@ -122,10 +122,23 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
expected: &awsutilv2.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with invalid static credentials",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("ASIAfoobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
},
|
||||
},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expectedErrContains: `secrets.access_key_id: requires long-term access key beginning with "AKIA" prefix`,
|
||||
},
|
||||
{
|
||||
name: "with static credentials",
|
||||
secrets: &structpb.Struct{
|
||||
@@ -137,7 +150,7 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
expected: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "AKIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
@@ -196,7 +209,7 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
expected: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "AKIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
@@ -215,9 +228,8 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
RoleTags: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
DisableCredentialRotation: true,
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
expected: &awsutilv2.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
RoleARN: "arn:aws:iam::123456789012:role/S3Access",
|
||||
RoleExternalId: "1234567890",
|
||||
@@ -244,21 +256,7 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
expectedErrContains: "attributes.role_arn: conflicts with access_key_id and secret_access_key values, secrets.access_key_id: conflicts with role_arn value, secrets.secret_access_key: conflicts with role_arn value",
|
||||
},
|
||||
{
|
||||
name: "with dynamic credentials and no disable credential rotation",
|
||||
secrets: &structpb.Struct{Fields: map[string]*structpb.Value{}},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
RoleArn: "arn:aws:iam::123456789012:role/S3Access",
|
||||
RoleExternalId: "1234567890",
|
||||
RoleSessionName: "test-session",
|
||||
RoleTags: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
expectedErrContains: "attributes.disable_credential_rotation: disable_credential_rotation attribute is required when providing a role_arn",
|
||||
expectedErrContains: "secrets.access_key_id: conflicts with role_arn value, secrets.role_arn: conflicts with access_key_id and secret_access_key values, secrets.secret_access_key: conflicts with role_arn value",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -13,30 +13,16 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/values"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
type CredentialType int
|
||||
|
||||
const (
|
||||
// StaticAWS denotes an Access Key Id that begins with "AKIA". These are
|
||||
// long-term access keys, provided by AWS, for an IAM user or an AWS account
|
||||
// root user.
|
||||
// https://docs.aws.amazon.com/IAM/latest/UserGuide/security-creds.html#sec-access-keys-and-secret-access-keys
|
||||
StaticAWS CredentialType = iota
|
||||
|
||||
// DynamicAWS denotes the presence of a RoleARN, or an Access Key Id that
|
||||
// begins with "ASIA". The latter are temporary credentials access keys that
|
||||
// are created using AWS STS operations.
|
||||
DynamicAWS
|
||||
|
||||
// StaticOther denotes the presence of an Access Key Id that does not follow
|
||||
// the AKIA/ASIA convention outlined by AWS.
|
||||
StaticOther
|
||||
|
||||
// Unknown is a catch-all for everything else.
|
||||
Unknown
|
||||
Unknown CredentialType = iota
|
||||
Static
|
||||
Dynamic
|
||||
)
|
||||
|
||||
// rotationWaitTimeout controls the time we wait for credential rotation to
|
||||
@@ -49,18 +35,18 @@ type AwsCredentialPersistedStateOption func(s *AwsCredentialPersistedState) erro
|
||||
// AwsCredentialPersistedState is the persisted state for the AWS credential.
|
||||
type AwsCredentialPersistedState struct {
|
||||
// CredentialsConfig is the credential configuration for the AWS credential.
|
||||
CredentialsConfig *awsutil.CredentialsConfig
|
||||
CredentialsConfig *awsutilv2.CredentialsConfig
|
||||
// CredsLastRotatedTime is the last rotation of aws secrets for the AWS credential.
|
||||
CredsLastRotatedTime time.Time
|
||||
|
||||
// testOpts are options that should be used for testing only
|
||||
testOpts []awsutil.Option
|
||||
testOpts []awsutilv2.Option
|
||||
}
|
||||
|
||||
// WithStateTestOpts enables unit testing different edge cases
|
||||
// when using CredentialsConfig. This should never be used in
|
||||
// production code. This should only be used in unit tests.
|
||||
func WithStateTestOpts(opts []awsutil.Option) AwsCredentialPersistedStateOption {
|
||||
func WithStateTestOpts(opts []awsutilv2.Option) AwsCredentialPersistedStateOption {
|
||||
return func(s *AwsCredentialPersistedState) error {
|
||||
s.testOpts = opts
|
||||
return nil
|
||||
@@ -68,7 +54,7 @@ func WithStateTestOpts(opts []awsutil.Option) AwsCredentialPersistedStateOption
|
||||
}
|
||||
|
||||
// WithCredentialsConfig sets the value for CredentialsConfig in the credential persisted state.
|
||||
func WithCredentialsConfig(x *awsutil.CredentialsConfig) AwsCredentialPersistedStateOption {
|
||||
func WithCredentialsConfig(x *awsutilv2.CredentialsConfig) AwsCredentialPersistedStateOption {
|
||||
return func(s *AwsCredentialPersistedState) error {
|
||||
if s.CredentialsConfig != nil {
|
||||
return errors.New("credentials config already set")
|
||||
@@ -126,11 +112,11 @@ func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error {
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.New("missing credentials config")
|
||||
}
|
||||
if GetCredentialType(s.CredentialsConfig) != StaticAWS {
|
||||
if GetCredentialType(s.CredentialsConfig.AccessKey) != Static {
|
||||
return errors.New("invalid credential type")
|
||||
}
|
||||
if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutil.Option{
|
||||
awsutil.WithValidityCheckTimeout(rotationWaitTimeout),
|
||||
if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutilv2.Option{
|
||||
awsutilv2.WithValidityCheckTimeout(rotationWaitTimeout),
|
||||
}, s.testOpts...)...); err != nil {
|
||||
return fmt.Errorf("error rotating credentials: %w", err)
|
||||
}
|
||||
@@ -141,18 +127,20 @@ func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error {
|
||||
// ReplaceCreds replaces the access key in the state with a new key.
|
||||
// If the existing key was rotated at any point in time, it is
|
||||
// deleted first, otherwise it's left alone.
|
||||
func (s *AwsCredentialPersistedState) ReplaceCreds(ctx context.Context, credentialsConfig *awsutil.CredentialsConfig) error {
|
||||
func (s *AwsCredentialPersistedState) ReplaceCreds(ctx context.Context, credentialsConfig *awsutilv2.CredentialsConfig) error {
|
||||
if credentialsConfig == nil {
|
||||
return errors.New("missing new credentials config")
|
||||
}
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.New("missing credentials config")
|
||||
}
|
||||
if GetCredentialType(s.CredentialsConfig.AccessKey) != Static {
|
||||
return errors.New("invalid credential type")
|
||||
}
|
||||
|
||||
// Delete old/existing credentials. This action is only possible for static credential types.
|
||||
// This is done with the same credentials to ensure that it has the proper permissions to do it.
|
||||
ct := GetCredentialType(s.CredentialsConfig)
|
||||
if !s.CredsLastRotatedTime.IsZero() && ct == StaticAWS {
|
||||
if !s.CredsLastRotatedTime.IsZero() {
|
||||
if err := s.DeleteCreds(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -171,7 +159,7 @@ func (s *AwsCredentialPersistedState) DeleteCreds(ctx context.Context) error {
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.New("missing credentials config")
|
||||
}
|
||||
if GetCredentialType(s.CredentialsConfig) != StaticAWS {
|
||||
if GetCredentialType(s.CredentialsConfig.AccessKey) != Static {
|
||||
return errors.New("invalid credential type")
|
||||
}
|
||||
|
||||
@@ -204,15 +192,7 @@ func (s *AwsCredentialPersistedState) GenerateCredentialChain(ctx context.Contex
|
||||
// return a map for long-term credentials with following keys:
|
||||
// access_key_id, secret_access_key & creds_last_rotated_time
|
||||
func (s *AwsCredentialPersistedState) ToMap() map[string]any {
|
||||
ct := GetCredentialType(s.CredentialsConfig)
|
||||
// Dynamic AWS credentials are temporary credentials that expire within an hour.
|
||||
// ToMap() returns an empty map here so that Boundary does not store temporary
|
||||
// credentials into the database.
|
||||
//
|
||||
// Uknown credentials are not aws compatiable secrets that may have been provided
|
||||
// for a non-aws s3 compatiable service. These credentials will not be returned
|
||||
// because the plugin does not know how to manage non-aws credentials.
|
||||
if ct == DynamicAWS || ct == Unknown {
|
||||
if HasDynamicCredentials(s.CredentialsConfig.AccessKey) {
|
||||
return map[string]any{}
|
||||
}
|
||||
return map[string]any{
|
||||
@@ -257,29 +237,29 @@ func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *Crede
|
||||
return nil, err
|
||||
}
|
||||
|
||||
awsOpts := append([]awsutil.Option{}, s.testOpts...)
|
||||
awsOpts := append([]awsutilv2.Option{}, s.testOpts...)
|
||||
if accessKeyId != "" && secretAccessKey != "" {
|
||||
awsOpts = append(awsOpts,
|
||||
awsutil.WithAccessKey(accessKeyId),
|
||||
awsutil.WithSecretKey(secretAccessKey),
|
||||
awsutilv2.WithAccessKey(accessKeyId),
|
||||
awsutilv2.WithSecretKey(secretAccessKey),
|
||||
)
|
||||
}
|
||||
if attrs.Region != "" {
|
||||
awsOpts = append(awsOpts, awsutil.WithRegion(attrs.Region))
|
||||
awsOpts = append(awsOpts, awsutilv2.WithRegion(attrs.Region))
|
||||
}
|
||||
if attrs.RoleArn != "" {
|
||||
awsOpts = append(awsOpts, awsutil.WithRoleArn(attrs.RoleArn))
|
||||
awsOpts = append(awsOpts, awsutilv2.WithRoleArn(attrs.RoleArn))
|
||||
}
|
||||
if attrs.RoleExternalId != "" {
|
||||
awsOpts = append(awsOpts, awsutil.WithRoleExternalId(attrs.RoleExternalId))
|
||||
awsOpts = append(awsOpts, awsutilv2.WithRoleExternalId(attrs.RoleExternalId))
|
||||
}
|
||||
if attrs.RoleSessionName != "" {
|
||||
awsOpts = append(awsOpts, awsutil.WithRoleSessionName(attrs.RoleSessionName))
|
||||
awsOpts = append(awsOpts, awsutilv2.WithRoleSessionName(attrs.RoleSessionName))
|
||||
}
|
||||
if len(attrs.RoleTags) != 0 {
|
||||
awsOpts = append(awsOpts, awsutil.WithRoleTags(attrs.RoleTags))
|
||||
awsOpts = append(awsOpts, awsutilv2.WithRoleTags(attrs.RoleTags))
|
||||
}
|
||||
credentialsConfig, err := awsutil.NewCredentialsConfig(awsOpts...)
|
||||
credentialsConfig, err := awsutilv2.NewCredentialsConfig(awsOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -289,22 +269,29 @@ func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *Crede
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetCredentialType returns the credential type based on the given
|
||||
// AccessKey/RoleARN. See CredentialType definition for more information.
|
||||
func GetCredentialType(cc *awsutil.CredentialsConfig) CredentialType {
|
||||
if cc == nil {
|
||||
return Unknown
|
||||
// GetCredentialType returns the credential type based on the given AWS AccessKey.
|
||||
//
|
||||
// https://docs.aws.amazon.com/IAM/latest/UserGuide/security-creds.html#sec-access-keys-and-secret-access-keys
|
||||
// Access key IDs beginning with ASIA are temporary credentials access keys that you create using AWS STS operations.
|
||||
//
|
||||
// https://docs.aws.amazon.com/IAM/latest/UserGuide/security-creds.html#sec-access-keys-and-secret-access-keys
|
||||
// Access key IDs beginning with AKIA are long-term access keys for an IAM user or an AWS account root user.
|
||||
func GetCredentialType(accessKey string) CredentialType {
|
||||
if strings.HasPrefix(accessKey, "ASIA") {
|
||||
return Dynamic
|
||||
}
|
||||
|
||||
if len(cc.RoleARN) > 0 || strings.HasPrefix(cc.AccessKey, "ASIA") {
|
||||
return DynamicAWS
|
||||
if strings.HasPrefix(accessKey, "AKIA") {
|
||||
return Static
|
||||
}
|
||||
if strings.HasPrefix(cc.AccessKey, "AKIA") {
|
||||
return StaticAWS
|
||||
}
|
||||
if len(cc.AccessKey) > 0 {
|
||||
return StaticOther
|
||||
}
|
||||
|
||||
return Unknown
|
||||
}
|
||||
|
||||
// HasDynamicCredentials returns true if the access key is a dynamic credential type.
|
||||
func HasDynamicCredentials(accessKey string) bool {
|
||||
return strings.HasPrefix(accessKey, "ASIA")
|
||||
}
|
||||
|
||||
// HasStaticCredentials returns true if the access key is a static credential type.
|
||||
func HasStaticCredentials(accessKey string) bool {
|
||||
return strings.HasPrefix(accessKey, "AKIA")
|
||||
}
|
||||
+169
-124
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
@@ -40,7 +40,7 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
|
||||
name: "with credentials config",
|
||||
opts: []AwsCredentialPersistedStateOption{
|
||||
WithCredentialsConfig(
|
||||
&awsutil.CredentialsConfig{
|
||||
&awsutilv2.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
@@ -48,7 +48,7 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
|
||||
),
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
@@ -67,8 +67,8 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
|
||||
{
|
||||
name: "double set credentials config",
|
||||
opts: []AwsCredentialPersistedStateOption{
|
||||
WithCredentialsConfig(&awsutil.CredentialsConfig{}),
|
||||
WithCredentialsConfig(&awsutil.CredentialsConfig{}),
|
||||
WithCredentialsConfig(&awsutilv2.CredentialsConfig{}),
|
||||
WithCredentialsConfig(&awsutilv2.CredentialsConfig{}),
|
||||
},
|
||||
expectedErr: "credentials config already set",
|
||||
},
|
||||
@@ -123,7 +123,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
@@ -135,7 +135,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
@@ -147,7 +147,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
@@ -164,7 +164,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
@@ -217,7 +217,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
@@ -244,7 +244,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
@@ -264,7 +264,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
RoleARN: "arn:aws:iam::123456789012:role/S3Access",
|
||||
RoleExternalId: "1234567890",
|
||||
@@ -294,7 +294,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
@@ -352,15 +352,15 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
|
||||
{
|
||||
name: "validation error",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(
|
||||
awsutil.NewMockSTS(
|
||||
awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
|
||||
testOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithSTSAPIFunc(
|
||||
awsutilv2.NewMockSTS(
|
||||
awsutilv2.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
|
||||
),
|
||||
),
|
||||
},
|
||||
@@ -370,12 +370,12 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
|
||||
{
|
||||
name: "good",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
testOpts: []awsutil.Option{awsutil.WithSTSAPIFunc(awsutil.NewMockSTS())},
|
||||
testOpts: []awsutilv2.Option{awsutilv2.WithSTSAPIFunc(awsutilv2.NewMockSTS())},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -399,7 +399,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
exepected *awsutil.CredentialsConfig
|
||||
exepected *awsutilv2.CredentialsConfig
|
||||
expectedNonZeroRotatedTime bool
|
||||
expectedErr string
|
||||
}{
|
||||
@@ -411,7 +411,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
{
|
||||
name: "cannot delete dynamic credential type",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "ASIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
@@ -422,7 +422,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
{
|
||||
name: "cannot delete unknown credential type",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "DNEfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
@@ -433,15 +433,15 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
{
|
||||
name: "rotation error",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "AKIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
awsutil.WithGetUserError(errors.New(testGetUserErr)),
|
||||
testOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithGetUserError(errors.New(testGetUserErr)),
|
||||
),
|
||||
),
|
||||
},
|
||||
@@ -451,18 +451,18 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
{
|
||||
name: "good",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "AKIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(
|
||||
awsutil.NewMockSTS(),
|
||||
testOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithSTSAPIFunc(
|
||||
awsutilv2.NewMockSTS(),
|
||||
),
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
awsutil.WithGetUserOutput(
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithGetUserOutput(
|
||||
&iam.GetUserOutput{
|
||||
User: &iamTypes.User{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"),
|
||||
@@ -471,7 +471,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
},
|
||||
},
|
||||
),
|
||||
awsutil.WithCreateAccessKeyOutput(
|
||||
awsutilv2.WithCreateAccessKeyOutput(
|
||||
&iam.CreateAccessKeyOutput{
|
||||
AccessKey: &iamTypes.AccessKey{
|
||||
AccessKeyId: aws.String("one"),
|
||||
@@ -484,7 +484,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
exepected: &awsutil.CredentialsConfig{
|
||||
exepected: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "one",
|
||||
SecretKey: "two",
|
||||
Region: "us-west-2",
|
||||
@@ -517,8 +517,8 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
credentialConfig *awsutil.CredentialsConfig
|
||||
expected *awsutil.CredentialsConfig
|
||||
credentialConfig *awsutilv2.CredentialsConfig
|
||||
expected *awsutilv2.CredentialsConfig
|
||||
expectedDeleteCalled bool
|
||||
expectedErr string
|
||||
}{
|
||||
@@ -530,47 +530,71 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
||||
{
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
credentialConfig: &awsutil.CredentialsConfig{},
|
||||
credentialConfig: &awsutilv2.CredentialsConfig{},
|
||||
expectedErr: "missing credentials config",
|
||||
},
|
||||
{
|
||||
name: "cannot delete dynamic credential type",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "ASIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
CredsLastRotatedTime: time.Now(),
|
||||
},
|
||||
credentialConfig: &awsutilv2.CredentialsConfig{},
|
||||
expectedErr: "invalid credential type",
|
||||
},
|
||||
{
|
||||
name: "cannot delete unknown credential type",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "DNEfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
CredsLastRotatedTime: time.Now(),
|
||||
},
|
||||
credentialConfig: &awsutilv2.CredentialsConfig{},
|
||||
expectedErr: "invalid credential type",
|
||||
},
|
||||
{
|
||||
name: "deletion error",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "AKIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
CredsLastRotatedTime: time.Now(),
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
|
||||
testOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
credentialConfig: &awsutil.CredentialsConfig{},
|
||||
credentialConfig: &awsutilv2.CredentialsConfig{},
|
||||
expectedErr: fmt.Sprintf("error deleting old access key: %s", testDeleteAccessKeyErr),
|
||||
},
|
||||
{
|
||||
name: "good with delete of old rotated",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "AKIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
CredsLastRotatedTime: time.Now(),
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
testOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
newTestMockIAM(state),
|
||||
),
|
||||
},
|
||||
},
|
||||
credentialConfig: &awsutil.CredentialsConfig{
|
||||
credentialConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "one",
|
||||
SecretKey: "two",
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
expected: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "one",
|
||||
SecretKey: "two",
|
||||
},
|
||||
@@ -579,21 +603,21 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
||||
{
|
||||
name: "good without delete of old rotated",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "AKIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
testOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
newTestMockIAM(state),
|
||||
),
|
||||
},
|
||||
},
|
||||
credentialConfig: &awsutil.CredentialsConfig{
|
||||
credentialConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "one",
|
||||
SecretKey: "two",
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
expected: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "one",
|
||||
SecretKey: "two",
|
||||
},
|
||||
@@ -635,7 +659,7 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
{
|
||||
name: "cannot delete dynamic credential type",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "ASIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
@@ -646,7 +670,7 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
{
|
||||
name: "cannot delete unknown credential type",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "DNEfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
@@ -657,14 +681,14 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
{
|
||||
name: "deletion error",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "AKIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
|
||||
testOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
|
||||
),
|
||||
),
|
||||
},
|
||||
@@ -674,14 +698,14 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
{
|
||||
name: "deletion error, but OK because key was just gone",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "AKIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
awsutil.WithDeleteAccessKeyError(
|
||||
testOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithDeleteAccessKeyError(
|
||||
&iamTypes.NoSuchEntityException{},
|
||||
),
|
||||
),
|
||||
@@ -692,13 +716,13 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
{
|
||||
name: "good",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "AKIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(),
|
||||
testOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(),
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -732,24 +756,24 @@ func TestAwsCatalogPersistedState_GenerateCredentialChain(t *testing.T) {
|
||||
{
|
||||
name: "error",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
|
||||
testOpts: []awsutilv2.Option{awsutilv2.MockOptionErr(errors.New(testOptionErr))},
|
||||
},
|
||||
expectedErr: fmt.Sprintf("error reading options in GenerateCredentialChain: %s", testOptionErr),
|
||||
},
|
||||
{
|
||||
name: "static credentials",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
CredentialsConfig: &awsutilv2.CredentialsConfig{
|
||||
AccessKey: "AKIA_foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(),
|
||||
testOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(),
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -757,22 +781,22 @@ func TestAwsCatalogPersistedState_GenerateCredentialChain(t *testing.T) {
|
||||
{
|
||||
name: "assume role",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: func() *awsutil.CredentialsConfig {
|
||||
config, err := awsutil.NewCredentialsConfig(
|
||||
awsutil.WithRegion("us-west-2"),
|
||||
awsutil.WithRoleArn("arn:aws:iam::123456789012:role/S3Access"),
|
||||
awsutil.WithRoleExternalId("1234567890"),
|
||||
awsutil.WithRoleSessionName("assume-role"),
|
||||
awsutil.WithRoleTags(map[string]string{
|
||||
CredentialsConfig: func() *awsutilv2.CredentialsConfig {
|
||||
config, err := awsutilv2.NewCredentialsConfig(
|
||||
awsutilv2.WithRegion("us-west-2"),
|
||||
awsutilv2.WithRoleArn("arn:aws:iam::123456789012:role/S3Access"),
|
||||
awsutilv2.WithRoleExternalId("1234567890"),
|
||||
awsutilv2.WithRoleSessionName("assume-role"),
|
||||
awsutilv2.WithRoleTags(map[string]string{
|
||||
"foo": "bar",
|
||||
}),
|
||||
)
|
||||
require.NoError(err)
|
||||
return config
|
||||
}(),
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(),
|
||||
testOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(),
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -791,53 +815,74 @@ func TestAwsCatalogPersistedState_GenerateCredentialChain(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCredentialType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
credConfig *awsutil.CredentialsConfig
|
||||
expectedCredType CredentialType
|
||||
func TestHasStaticCredentials(t *testing.T) {
|
||||
require := require.New(t)
|
||||
cases := []struct {
|
||||
name string
|
||||
accessKey string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "nil credential config",
|
||||
credConfig: nil,
|
||||
expectedCredType: Unknown,
|
||||
name: "dynamic credential",
|
||||
accessKey: "ASIAEXAMPLE",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty credential config",
|
||||
credConfig: &awsutil.CredentialsConfig{},
|
||||
expectedCredType: Unknown,
|
||||
name: "static credential",
|
||||
accessKey: "AKIAEXAMPLE",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "static aws",
|
||||
credConfig: &awsutil.CredentialsConfig{AccessKey: "AKIAfoobar"},
|
||||
expectedCredType: StaticAWS,
|
||||
name: "other credential",
|
||||
accessKey: "EXAMPLE",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "static other",
|
||||
credConfig: &awsutil.CredentialsConfig{AccessKey: "cK3kNFa24foobar"},
|
||||
expectedCredType: StaticOther,
|
||||
},
|
||||
{
|
||||
name: "dynamic aws - role arn",
|
||||
credConfig: &awsutil.CredentialsConfig{RoleARN: "arn:aws:iam::123456789012:role/S3Access"},
|
||||
expectedCredType: DynamicAWS,
|
||||
},
|
||||
{
|
||||
name: "dynamic aws - access key",
|
||||
credConfig: &awsutil.CredentialsConfig{AccessKey: "ASIAfoobar"},
|
||||
expectedCredType: DynamicAWS,
|
||||
},
|
||||
{
|
||||
name: "dynamic aws - role arn and access key",
|
||||
credConfig: &awsutil.CredentialsConfig{AccessKey: "ASIAfoobar", RoleARN: "arn:aws:iam::123456789012:role/S3Access"},
|
||||
expectedCredType: DynamicAWS,
|
||||
name: "empty",
|
||||
accessKey: "",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ct := GetCredentialType(tc.credConfig)
|
||||
require.Equal(t, tc.expectedCredType, ct)
|
||||
require.Equal(tc.expected, HasStaticCredentials(tc.accessKey))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasDynamicCredentials(t *testing.T) {
|
||||
require := require.New(t)
|
||||
cases := []struct {
|
||||
name string
|
||||
accessKey string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "dynamic credential",
|
||||
accessKey: "ASIAEXAMPLE",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "static credential",
|
||||
accessKey: "AKIAEXAMPLE",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "other credential",
|
||||
accessKey: "EXAMPLE",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
accessKey: "",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.Equal(tc.expected, HasDynamicCredentials(tc.accessKey))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ func (s *testMockIAMState) Reset() {
|
||||
}
|
||||
|
||||
type testMockIAM struct {
|
||||
awsutil.IAMClient
|
||||
awsutilv2.IAMClient
|
||||
|
||||
State *testMockIAMState
|
||||
}
|
||||
@@ -38,12 +38,12 @@ func (m *testMockIAM) DeleteAccessKey(ctx context.Context, input *iam.DeleteAcce
|
||||
return m.IAMClient.DeleteAccessKey(ctx, input, opts...)
|
||||
}
|
||||
|
||||
func newTestMockIAM(state *testMockIAMState, opts ...awsutil.MockIAMOption) awsutil.IAMAPIFunc {
|
||||
return func(awsConfig *aws.Config) (awsutil.IAMClient, error) {
|
||||
func newTestMockIAM(state *testMockIAMState, opts ...awsutilv2.MockIAMOption) awsutilv2.IAMAPIFunc {
|
||||
return func(awsConfig *aws.Config) (awsutilv2.IAMClient, error) {
|
||||
m := &testMockIAM{
|
||||
State: state,
|
||||
}
|
||||
f := awsutil.NewMockIAM(opts...)
|
||||
f := awsutilv2.NewMockIAM(opts...)
|
||||
var err error
|
||||
|
||||
m.IAMClient, err = f(awsConfig)
|
||||
|
||||
@@ -83,7 +83,8 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
||||
}
|
||||
|
||||
if cred.GetCredentialType(credState.CredentialsConfig) == cred.StaticAWS {
|
||||
// Try to rotate static credentials
|
||||
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) {
|
||||
if !catalogAttributes.DisableCredentialRotation {
|
||||
if err := credState.RotateCreds(ctx); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err)
|
||||
@@ -159,7 +160,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.GetCredentialType(credState.CredentialsConfig) == cred.StaticAWS {
|
||||
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) {
|
||||
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.
|
||||
@@ -243,7 +244,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo
|
||||
}
|
||||
|
||||
// try to delete static credentials
|
||||
if cred.GetCredentialType(credState.CredentialsConfig) == cred.StaticAWS {
|
||||
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) {
|
||||
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"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -145,10 +145,10 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
awsutil.WithGetUserError(errors.New(testGetUserErr)),
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithGetUserError(errors.New(testGetUserErr)),
|
||||
),
|
||||
),
|
||||
}),
|
||||
@@ -176,10 +176,10 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(
|
||||
awsutil.NewMockSTS(
|
||||
awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.WithSTSAPIFunc(
|
||||
awsutilv2.NewMockSTS(
|
||||
awsutilv2.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
|
||||
),
|
||||
),
|
||||
}),
|
||||
@@ -370,10 +370,10 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
|
||||
),
|
||||
),
|
||||
}),
|
||||
@@ -403,10 +403,10 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
awsutil.WithGetUserError(errors.New(testGetUserErr)),
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithGetUserError(errors.New(testGetUserErr)),
|
||||
),
|
||||
),
|
||||
}),
|
||||
@@ -493,10 +493,10 @@ func TestPluginOnDeleteCatalogErr(t *testing.T) {
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(
|
||||
awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
|
||||
),
|
||||
),
|
||||
}),
|
||||
@@ -729,8 +729,8 @@ func TestPluginOnCreateSetErr(t *testing.T) {
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.MockOptionErr(errors.New(testOptionErr)),
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.MockOptionErr(errors.New(testOptionErr)),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: fmt.Sprintf("error loading persisted state: error reading options in NewCredentialsConfig: %s", testOptionErr),
|
||||
@@ -1098,8 +1098,8 @@ func TestPluginOnUpdateSetErr(t *testing.T) {
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.MockOptionErr(errors.New(testOptionErr)),
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.MockOptionErr(errors.New(testOptionErr)),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: fmt.Sprintf("error loading persisted state: error reading options in NewCredentialsConfig: %s", testOptionErr),
|
||||
@@ -1458,8 +1458,8 @@ func TestPluginListHostsErr(t *testing.T) {
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.MockOptionErr(errors.New(testOptionErr)),
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.MockOptionErr(errors.New(testOptionErr)),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: fmt.Sprintf("error loading persisted state: error reading options in NewCredentialsConfig: %s", testOptionErr),
|
||||
|
||||
@@ -8,14 +8,14 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAwsCatalogPersistedStateEC2Client(t *testing.T) {
|
||||
require := require.New(t)
|
||||
cred, err := credential.NewAwsCredentialPersistedState(
|
||||
credential.WithCredentialsConfig(&awsutil.CredentialsConfig{
|
||||
credential.WithCredentialsConfig(&awsutilv2.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "barfoo",
|
||||
|
||||
@@ -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.
|
||||
@@ -76,10 +81,17 @@ 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 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)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,38 +161,13 @@ 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 {
|
||||
// Ensure the incoming credentials are valid for interaction with the S3
|
||||
// Bucket before replacing them.
|
||||
newCredState, err := cred.NewAwsCredentialPersistedState(
|
||||
append([]cred.AwsCredentialPersistedStateOption{
|
||||
cred.WithCredentialsConfig(updatedCredentials),
|
||||
}, p.testCredStateOpts...)...,
|
||||
)
|
||||
updatedCredentials, err := cred.GetCredentialsConfig(newBucket.GetSecrets(), newStorageAttributes.CredentialAttributes, false)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up new credential persisted state: %s", err)
|
||||
}
|
||||
newStorageState, err := newAwsStoragePersistedState(
|
||||
append([]awsStoragePersistedStateOption{
|
||||
withCredentials(newCredState),
|
||||
}, p.testStorageStateOpts...)...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error loading persisted state: %s", err)
|
||||
}
|
||||
if err := dryRunValidation(ctx, newStorageState, newStorageAttributes, newBucket); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "failed to validate incoming credentials: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Replace the existing credential state.
|
||||
@@ -192,7 +179,19 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
||||
}
|
||||
}
|
||||
|
||||
if cred.GetCredentialType(credState.CredentialsConfig) == cred.StaticAWS {
|
||||
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
|
||||
// rotation for credentials currently being managed by rotation.
|
||||
// This is not allowed.
|
||||
@@ -209,8 +208,16 @@ 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),
|
||||
@@ -276,7 +283,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
|
||||
}
|
||||
|
||||
// try to delete static credentials
|
||||
if cred.GetCredentialType(credState.CredentialsConfig) == cred.StaticAWS {
|
||||
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) {
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -8,34 +8,34 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAwsStoragePersistedStateS3Client(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
creds *awsutil.CredentialsConfig
|
||||
creds *awsutilv2.CredentialsConfig
|
||||
opts []awsStoragePersistedStateOption
|
||||
awsOpts []awsutil.Option
|
||||
awsOpts []awsutilv2.Option
|
||||
s3Opts []s3Option
|
||||
expectedRegion string
|
||||
expectedEndpoint string
|
||||
}{
|
||||
{
|
||||
name: "static credentials",
|
||||
creds: func() *awsutil.CredentialsConfig {
|
||||
creds, err := awsutil.NewCredentialsConfig(
|
||||
awsutil.WithRegion("us-west-2"),
|
||||
awsutil.WithAccessKey("foobar"),
|
||||
awsutil.WithSecretKey("barfoo"),
|
||||
creds: func() *awsutilv2.CredentialsConfig {
|
||||
creds, err := awsutilv2.NewCredentialsConfig(
|
||||
awsutilv2.WithRegion("us-west-2"),
|
||||
awsutilv2.WithAccessKey("foobar"),
|
||||
awsutilv2.WithSecretKey("barfoo"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
return creds
|
||||
}(),
|
||||
awsOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(),
|
||||
awsOpts: []awsutilv2.Option{
|
||||
awsutilv2.WithIAMAPIFunc(
|
||||
awsutilv2.NewMockIAM(),
|
||||
),
|
||||
},
|
||||
opts: []awsStoragePersistedStateOption{
|
||||
|
||||
@@ -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"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
@@ -146,6 +146,9 @@ func newTestMockS3(state *testMockS3State, opts ...testMockS3Option) s3APIFunc {
|
||||
if cfg.Region != "" {
|
||||
m.Region = cfg.Region
|
||||
}
|
||||
// if cfg. != nil {
|
||||
// m.Endpoint = cfg.EndpointResolverWithOptions
|
||||
// }
|
||||
}
|
||||
|
||||
return m, nil
|
||||
@@ -284,10 +287,10 @@ func deepCopyPutObjectRequest(v *pb.PutObjectRequest) *pb.PutObjectRequest {
|
||||
|
||||
func validSTSMock() []credential.AwsCredentialPersistedStateOption {
|
||||
return []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(
|
||||
awsutil.NewMockSTS(
|
||||
awsutil.WithGetCallerIdentityOutput(&sts.GetCallerIdentityOutput{
|
||||
credential.WithStateTestOpts([]awsutilv2.Option{
|
||||
awsutilv2.WithSTSAPIFunc(
|
||||
awsutilv2.NewMockSTS(
|
||||
awsutilv2.WithGetCallerIdentityOutput(&sts.GetCallerIdentityOutput{
|
||||
Account: aws.String("0123456789"),
|
||||
Arn: aws.String("arn:aws:iam::0123456789:user/test"),
|
||||
UserId: aws.String("test"),
|
||||
|
||||
+7
-7
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/aws/smithy-go"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/values"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
tfjson "github.com/hashicorp/terraform-json"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
@@ -280,9 +280,9 @@ func requireCredentialsInvalid(t *testing.T, accessKeyId, secretAccessKey string
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
|
||||
c, err := awsutil.NewCredentialsConfig(
|
||||
awsutil.WithAccessKey(accessKeyId),
|
||||
awsutil.WithSecretKey(secretAccessKey),
|
||||
c, err := awsutilv2.NewCredentialsConfig(
|
||||
awsutilv2.WithAccessKey(accessKeyId),
|
||||
awsutilv2.WithSecretKey(secretAccessKey),
|
||||
)
|
||||
require.NoError(err)
|
||||
|
||||
@@ -317,9 +317,9 @@ func requireCredentialsValid(t *testing.T, accessKeyId, secretAccessKey string)
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
|
||||
c, err := awsutil.NewCredentialsConfig(
|
||||
awsutil.WithAccessKey(accessKeyId),
|
||||
awsutil.WithSecretKey(secretAccessKey),
|
||||
c, err := awsutilv2.NewCredentialsConfig(
|
||||
awsutilv2.WithAccessKey(accessKeyId),
|
||||
awsutilv2.WithSecretKey(secretAccessKey),
|
||||
)
|
||||
require.NoError(err)
|
||||
_, err = c.GetCallerIdentity(context.Background())
|
||||
|
||||
Reference in new issue
Block a user