Compare commits

..
Author SHA1 Message Date
Hugo 39f8f290fc fix(credential): Allow for static AWS credentials in environment
This commit fixes a problem where the plugin/AWS SDK would not recognise
static AWS credentials set in the machine's environment variables (by
setting AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY)

The default `WithSharedCredentials` value is `true` in awsutil (v2) -
This essentially disables the ability to read credentials from the
environment because we set the `default` AWS profile options, making the
SDK go search for a profile that may not exist and ignoring the
environment in the way, so we need to set it to `false` (see
`awsutil@v2.(*CredentialsConfig).generateAwsConfigOptions`) for extra
details.
2023-10-05 14:47:16 +01:00
Damian DebkowskiandHugo fcfe267a3f refactor: switch from awsutil to awsutilv2 (#34)
* 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.

* fix(storage): Static rotated creds being deleted on plugin err

On Storage Bucket update, when we determine if the credentials are to be
updated to new ones, we call `credState.ReplaceCreds`.

This call, if the old credentials were managed by us (rotated at create
time), will delete the old credentials and set the new ones onto
credState.

We called this function without validating if the incoming credentials
are valid. This could get the bucket in a state where where, while we'd
return an update error if the incoming credentials weren't working with
S3 (err in the dry run process that happens at the end of the update),
we'd also silently delete the old credentials the bucket used, rendering
the bucket completely broken.

---------

Co-authored-by: Hugo <hugoamvieira@users.noreply.github.com>
2023-09-22 12:26:08 -07:00
Michael Li f263eefbba doc: Fix links to READMEs (#32) 2023-09-08 14:31:18 -04:00
16 changed files with 1130 additions and 512 deletions

No files matched your search

+4 -4
View File
@@ -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](https://github.com/hashicorp/boundary-plugin-aws/plugin/service/host/README.md)
[Getting Started](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](https://github.com/hashicorp/boundary-plugin-aws/plugin/service/storage/README.md)
[Getting Started](plugin/service/storage/README.md)
+1 -3
View File
@@ -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 v0.2.2
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0
github.com/hashicorp/terraform-json v0.14.0
github.com/mitchellh/mapstructure v1.5.0
github.com/stretchr/testify v1.8.4
@@ -21,8 +21,6 @@ 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
+2 -2
View File
@@ -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/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/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/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=
+15 -16
View File
@@ -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"
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
"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) (*awsutilv2.CredentialsConfig, error) {
func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, required bool) (*awsutil.CredentialsConfig, error) {
// initialize secrets if it is nil
// secrets can be nil because static credentials are optional
if secrets == nil {
@@ -55,10 +55,6 @@ 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()
@@ -72,7 +68,7 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
badFields[fmt.Sprintf("secrets.%s", s)] = "unrecognized field"
}
opts := []awsutilv2.Option{}
opts := []awsutil.Option{}
// logic for parsing the credential type
// supported types:
// - user static credential
@@ -83,22 +79,25 @@ 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("secrets.%s", ConstRoleArn)] = "conflicts with access_key_id and secret_access_key values"
badFields[fmt.Sprintf("attributes.%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,
awsutilv2.WithAccessKey(accessKey),
awsutilv2.WithSecretKey(secretKey),
awsutil.WithAccessKey(accessKey),
awsutil.WithSecretKey(secretKey),
)
// add dynamic credentials
case attrs.RoleArn != "":
opts = append(opts, awsutilv2.WithRoleArn(attrs.RoleArn))
opts = append(opts, awsutil.WithRoleArn(attrs.RoleArn))
}
if len(badFields) > 0 {
@@ -106,19 +105,19 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
}
if attrs.Region != "" {
opts = append(opts, awsutilv2.WithRegion(attrs.Region))
opts = append(opts, awsutil.WithRegion(attrs.Region))
}
if attrs.RoleExternalId != "" {
opts = append(opts, awsutilv2.WithRoleExternalId(attrs.RoleExternalId))
opts = append(opts, awsutil.WithRoleExternalId(attrs.RoleExternalId))
}
if attrs.RoleSessionName != "" {
opts = append(opts, awsutilv2.WithRoleSessionName(attrs.RoleSessionName))
opts = append(opts, awsutil.WithRoleSessionName(attrs.RoleSessionName))
}
if len(attrs.RoleTags) != 0 {
opts = append(opts, awsutilv2.WithRoleTags(attrs.RoleTags))
opts = append(opts, awsutil.WithRoleTags(attrs.RoleTags))
}
return awsutilv2.NewCredentialsConfig(opts...)
return awsutil.NewCredentialsConfig(opts...)
}
// GetCredentialAttributes parses values out of a protobuf struct input and returns a
+22 -20
View File
@@ -6,7 +6,7 @@ package credential
import (
"testing"
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
"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 *awsutilv2.CredentialsConfig
expected *awsutil.CredentialsConfig
expectedErrContains string
}{
{
@@ -122,23 +122,10 @@ func TestGetCredentialsConfig(t *testing.T) {
attrs: &CredentialAttributes{
Region: "us-west-2",
},
expected: &awsutilv2.CredentialsConfig{
expected: &awsutil.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{
@@ -150,7 +137,7 @@ func TestGetCredentialsConfig(t *testing.T) {
attrs: &CredentialAttributes{
Region: "us-west-2",
},
expected: &awsutilv2.CredentialsConfig{
expected: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
Region: "us-west-2",
@@ -209,7 +196,7 @@ func TestGetCredentialsConfig(t *testing.T) {
attrs: &CredentialAttributes{
Region: "us-west-2",
},
expected: &awsutilv2.CredentialsConfig{
expected: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
Region: "us-west-2",
@@ -228,8 +215,9 @@ func TestGetCredentialsConfig(t *testing.T) {
RoleTags: map[string]string{
"foo": "bar",
},
DisableCredentialRotation: true,
},
expected: &awsutilv2.CredentialsConfig{
expected: &awsutil.CredentialsConfig{
Region: "us-west-2",
RoleARN: "arn:aws:iam::123456789012:role/S3Access",
RoleExternalId: "1234567890",
@@ -256,7 +244,21 @@ func TestGetCredentialsConfig(t *testing.T) {
"foo": "bar",
},
},
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",
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",
},
}
+69 -50
View File
@@ -13,16 +13,30 @@ 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"
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
"google.golang.org/protobuf/types/known/structpb"
)
type CredentialType int
const (
Unknown CredentialType = iota
Static
Dynamic
// 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
)
// rotationWaitTimeout controls the time we wait for credential rotation to
@@ -35,18 +49,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 *awsutilv2.CredentialsConfig
CredentialsConfig *awsutil.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 []awsutilv2.Option
testOpts []awsutil.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 []awsutilv2.Option) AwsCredentialPersistedStateOption {
func WithStateTestOpts(opts []awsutil.Option) AwsCredentialPersistedStateOption {
return func(s *AwsCredentialPersistedState) error {
s.testOpts = opts
return nil
@@ -54,7 +68,7 @@ func WithStateTestOpts(opts []awsutilv2.Option) AwsCredentialPersistedStateOptio
}
// WithCredentialsConfig sets the value for CredentialsConfig in the credential persisted state.
func WithCredentialsConfig(x *awsutilv2.CredentialsConfig) AwsCredentialPersistedStateOption {
func WithCredentialsConfig(x *awsutil.CredentialsConfig) AwsCredentialPersistedStateOption {
return func(s *AwsCredentialPersistedState) error {
if s.CredentialsConfig != nil {
return errors.New("credentials config already set")
@@ -112,11 +126,11 @@ func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error {
if s.CredentialsConfig == nil {
return errors.New("missing credentials config")
}
if GetCredentialType(s.CredentialsConfig.AccessKey) != Static {
if GetCredentialType(s.CredentialsConfig) != StaticAWS {
return errors.New("invalid credential type")
}
if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutilv2.Option{
awsutilv2.WithValidityCheckTimeout(rotationWaitTimeout),
if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutil.Option{
awsutil.WithValidityCheckTimeout(rotationWaitTimeout),
}, s.testOpts...)...); err != nil {
return fmt.Errorf("error rotating credentials: %w", err)
}
@@ -127,20 +141,18 @@ 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 *awsutilv2.CredentialsConfig) error {
func (s *AwsCredentialPersistedState) ReplaceCreds(ctx context.Context, credentialsConfig *awsutil.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.
if !s.CredsLastRotatedTime.IsZero() {
ct := GetCredentialType(s.CredentialsConfig)
if !s.CredsLastRotatedTime.IsZero() && ct == StaticAWS {
if err := s.DeleteCreds(ctx); err != nil {
return err
}
@@ -159,7 +171,7 @@ func (s *AwsCredentialPersistedState) DeleteCreds(ctx context.Context) error {
if s.CredentialsConfig == nil {
return errors.New("missing credentials config")
}
if GetCredentialType(s.CredentialsConfig.AccessKey) != Static {
if GetCredentialType(s.CredentialsConfig) != StaticAWS {
return errors.New("invalid credential type")
}
@@ -184,7 +196,13 @@ func (s *AwsCredentialPersistedState) DeleteCreds(ctx context.Context) error {
// GenerateCredentialChain returns a AWS configuration for the credentials in the state.
func (s *AwsCredentialPersistedState) GenerateCredentialChain(ctx context.Context) (*aws.Config, error) {
return s.CredentialsConfig.GenerateCredentialChain(ctx, s.testOpts...)
// Default `WithSharedCredentials` is `true` - This disables the ability to
// read credentials from environment variables because we set the default
// profile options, so we need to set it to false (see
// awsutil@v2.(*CredentialsConfig).generateAwsConfigOptions) for extra
// details.
opts := append(s.testOpts, awsutil.WithSharedCredentials(false))
return s.CredentialsConfig.GenerateCredentialChain(ctx, opts...)
}
// ToMap returns a map of the credentials stored in the persisted state.
@@ -192,7 +210,15 @@ 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 {
if HasDynamicCredentials(s.CredentialsConfig.AccessKey) {
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 {
return map[string]any{}
}
return map[string]any{
@@ -237,29 +263,29 @@ func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *Crede
return nil, err
}
awsOpts := append([]awsutilv2.Option{}, s.testOpts...)
awsOpts := append([]awsutil.Option{}, s.testOpts...)
if accessKeyId != "" && secretAccessKey != "" {
awsOpts = append(awsOpts,
awsutilv2.WithAccessKey(accessKeyId),
awsutilv2.WithSecretKey(secretAccessKey),
awsutil.WithAccessKey(accessKeyId),
awsutil.WithSecretKey(secretAccessKey),
)
}
if attrs.Region != "" {
awsOpts = append(awsOpts, awsutilv2.WithRegion(attrs.Region))
awsOpts = append(awsOpts, awsutil.WithRegion(attrs.Region))
}
if attrs.RoleArn != "" {
awsOpts = append(awsOpts, awsutilv2.WithRoleArn(attrs.RoleArn))
awsOpts = append(awsOpts, awsutil.WithRoleArn(attrs.RoleArn))
}
if attrs.RoleExternalId != "" {
awsOpts = append(awsOpts, awsutilv2.WithRoleExternalId(attrs.RoleExternalId))
awsOpts = append(awsOpts, awsutil.WithRoleExternalId(attrs.RoleExternalId))
}
if attrs.RoleSessionName != "" {
awsOpts = append(awsOpts, awsutilv2.WithRoleSessionName(attrs.RoleSessionName))
awsOpts = append(awsOpts, awsutil.WithRoleSessionName(attrs.RoleSessionName))
}
if len(attrs.RoleTags) != 0 {
awsOpts = append(awsOpts, awsutilv2.WithRoleTags(attrs.RoleTags))
awsOpts = append(awsOpts, awsutil.WithRoleTags(attrs.RoleTags))
}
credentialsConfig, err := awsutilv2.NewCredentialsConfig(awsOpts...)
credentialsConfig, err := awsutil.NewCredentialsConfig(awsOpts...)
if err != nil {
return nil, err
}
@@ -269,29 +295,22 @@ func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *Crede
return s, nil
}
// 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
// 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
}
if strings.HasPrefix(accessKey, "AKIA") {
return Static
if len(cc.RoleARN) > 0 || strings.HasPrefix(cc.AccessKey, "ASIA") {
return DynamicAWS
}
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")
}
+123 -168
View File
@@ -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"
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
"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(
&awsutilv2.CredentialsConfig{
&awsutil.CredentialsConfig{
AccessKey: "foobar",
SecretKey: "bazqux",
Region: "us-west-2",
@@ -48,7 +48,7 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
),
},
expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.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(&awsutilv2.CredentialsConfig{}),
WithCredentialsConfig(&awsutilv2.CredentialsConfig{}),
WithCredentialsConfig(&awsutil.CredentialsConfig{}),
WithCredentialsConfig(&awsutil.CredentialsConfig{}),
},
expectedErr: "credentials config already set",
},
@@ -123,7 +123,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
Region: "us-west-2",
},
expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
Region: "us-west-2",
},
},
@@ -135,7 +135,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
Region: "us-west-2",
},
expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
Region: "us-west-2",
},
},
@@ -147,7 +147,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
Region: "us-west-2",
},
expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
Region: "us-west-2",
},
},
@@ -164,7 +164,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
Region: "us-west-2",
},
expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.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: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.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: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "foobar",
SecretKey: "bazqux",
Region: "us-west-2",
@@ -264,7 +264,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
},
},
expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.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: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.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: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "foobar",
SecretKey: "bazqux",
Region: "us-west-2",
},
testOpts: []awsutilv2.Option{
awsutilv2.WithSTSAPIFunc(
awsutilv2.NewMockSTS(
awsutilv2.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
testOpts: []awsutil.Option{
awsutil.WithSTSAPIFunc(
awsutil.NewMockSTS(
awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
),
),
},
@@ -370,12 +370,12 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
{
name: "good",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "foobar",
SecretKey: "bazqux",
Region: "us-west-2",
},
testOpts: []awsutilv2.Option{awsutilv2.WithSTSAPIFunc(awsutilv2.NewMockSTS())},
testOpts: []awsutil.Option{awsutil.WithSTSAPIFunc(awsutil.NewMockSTS())},
},
},
}
@@ -399,7 +399,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
cases := []struct {
name string
in *AwsCredentialPersistedState
exepected *awsutilv2.CredentialsConfig
exepected *awsutil.CredentialsConfig
expectedNonZeroRotatedTime bool
expectedErr string
}{
@@ -411,7 +411,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
{
name: "cannot delete dynamic credential type",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "ASIAfoobar",
SecretKey: "bazqux",
},
@@ -422,7 +422,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
{
name: "cannot delete unknown credential type",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "DNEfoobar",
SecretKey: "bazqux",
},
@@ -433,15 +433,15 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
{
name: "rotation error",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
Region: "us-west-2",
},
testOpts: []awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(
awsutilv2.WithGetUserError(errors.New(testGetUserErr)),
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithGetUserError(errors.New(testGetUserErr)),
),
),
},
@@ -451,18 +451,18 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
{
name: "good",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
Region: "us-west-2",
},
testOpts: []awsutilv2.Option{
awsutilv2.WithSTSAPIFunc(
awsutilv2.NewMockSTS(),
testOpts: []awsutil.Option{
awsutil.WithSTSAPIFunc(
awsutil.NewMockSTS(),
),
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(
awsutilv2.WithGetUserOutput(
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.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) {
},
},
),
awsutilv2.WithCreateAccessKeyOutput(
awsutil.WithCreateAccessKeyOutput(
&iam.CreateAccessKeyOutput{
AccessKey: &iamTypes.AccessKey{
AccessKeyId: aws.String("one"),
@@ -484,7 +484,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
),
},
},
exepected: &awsutilv2.CredentialsConfig{
exepected: &awsutil.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 *awsutilv2.CredentialsConfig
expected *awsutilv2.CredentialsConfig
credentialConfig *awsutil.CredentialsConfig
expected *awsutil.CredentialsConfig
expectedDeleteCalled bool
expectedErr string
}{
@@ -530,71 +530,47 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
{
name: "missing credentials config",
in: &AwsCredentialPersistedState{},
credentialConfig: &awsutilv2.CredentialsConfig{},
credentialConfig: &awsutil.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: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
},
CredsLastRotatedTime: time.Now(),
testOpts: []awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(
awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
),
),
},
},
credentialConfig: &awsutilv2.CredentialsConfig{},
credentialConfig: &awsutil.CredentialsConfig{},
expectedErr: fmt.Sprintf("error deleting old access key: %s", testDeleteAccessKeyErr),
},
{
name: "good with delete of old rotated",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
},
CredsLastRotatedTime: time.Now(),
testOpts: []awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
newTestMockIAM(state),
),
},
},
credentialConfig: &awsutilv2.CredentialsConfig{
credentialConfig: &awsutil.CredentialsConfig{
AccessKey: "one",
SecretKey: "two",
},
expected: &awsutilv2.CredentialsConfig{
expected: &awsutil.CredentialsConfig{
AccessKey: "one",
SecretKey: "two",
},
@@ -603,21 +579,21 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
{
name: "good without delete of old rotated",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
},
testOpts: []awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
newTestMockIAM(state),
),
},
},
credentialConfig: &awsutilv2.CredentialsConfig{
credentialConfig: &awsutil.CredentialsConfig{
AccessKey: "one",
SecretKey: "two",
},
expected: &awsutilv2.CredentialsConfig{
expected: &awsutil.CredentialsConfig{
AccessKey: "one",
SecretKey: "two",
},
@@ -659,7 +635,7 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
{
name: "cannot delete dynamic credential type",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "ASIAfoobar",
SecretKey: "bazqux",
},
@@ -670,7 +646,7 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
{
name: "cannot delete unknown credential type",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "DNEfoobar",
SecretKey: "bazqux",
},
@@ -681,14 +657,14 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
{
name: "deletion error",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
},
testOpts: []awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(
awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
),
),
},
@@ -698,14 +674,14 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
{
name: "deletion error, but OK because key was just gone",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
},
testOpts: []awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(
awsutilv2.WithDeleteAccessKeyError(
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithDeleteAccessKeyError(
&iamTypes.NoSuchEntityException{},
),
),
@@ -716,13 +692,13 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
{
name: "good",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
},
testOpts: []awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(),
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(),
),
},
},
@@ -756,24 +732,24 @@ func TestAwsCatalogPersistedState_GenerateCredentialChain(t *testing.T) {
{
name: "error",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "foobar",
SecretKey: "bazqux",
},
testOpts: []awsutilv2.Option{awsutilv2.MockOptionErr(errors.New(testOptionErr))},
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
},
expectedErr: fmt.Sprintf("error reading options in GenerateCredentialChain: %s", testOptionErr),
},
{
name: "static credentials",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
CredentialsConfig: &awsutil.CredentialsConfig{
AccessKey: "AKIA_foobar",
SecretKey: "bazqux",
},
testOpts: []awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(),
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(),
),
},
},
@@ -781,22 +757,22 @@ func TestAwsCatalogPersistedState_GenerateCredentialChain(t *testing.T) {
{
name: "assume role",
in: &AwsCredentialPersistedState{
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{
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{
"foo": "bar",
}),
)
require.NoError(err)
return config
}(),
testOpts: []awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(),
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(),
),
},
},
@@ -815,74 +791,53 @@ func TestAwsCatalogPersistedState_GenerateCredentialChain(t *testing.T) {
}
}
func TestHasStaticCredentials(t *testing.T) {
require := require.New(t)
cases := []struct {
name string
accessKey string
expected bool
func TestGetCredentialType(t *testing.T) {
tests := []struct {
name string
credConfig *awsutil.CredentialsConfig
expectedCredType CredentialType
}{
{
name: "dynamic credential",
accessKey: "ASIAEXAMPLE",
expected: false,
name: "nil credential config",
credConfig: nil,
expectedCredType: Unknown,
},
{
name: "static credential",
accessKey: "AKIAEXAMPLE",
expected: true,
name: "empty credential config",
credConfig: &awsutil.CredentialsConfig{},
expectedCredType: Unknown,
},
{
name: "other credential",
accessKey: "EXAMPLE",
expected: false,
name: "static aws",
credConfig: &awsutil.CredentialsConfig{AccessKey: "AKIAfoobar"},
expectedCredType: StaticAWS,
},
{
name: "empty",
accessKey: "",
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,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
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
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
require.Equal(tc.expected, HasDynamicCredentials(tc.accessKey))
ct := GetCredentialType(tc.credConfig)
require.Equal(t, tc.expectedCredType, ct)
})
}
}
+5 -5
View File
@@ -8,7 +8,7 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
"google.golang.org/protobuf/types/known/structpb"
)
@@ -28,7 +28,7 @@ func (s *testMockIAMState) Reset() {
}
type testMockIAM struct {
awsutilv2.IAMClient
awsutil.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 ...awsutilv2.MockIAMOption) awsutilv2.IAMAPIFunc {
return func(awsConfig *aws.Config) (awsutilv2.IAMClient, error) {
func newTestMockIAM(state *testMockIAMState, opts ...awsutil.MockIAMOption) awsutil.IAMAPIFunc {
return func(awsConfig *aws.Config) (awsutil.IAMClient, error) {
m := &testMockIAM{
State: state,
}
f := awsutilv2.NewMockIAM(opts...)
f := awsutil.NewMockIAM(opts...)
var err error
m.IAMClient, err = f(awsConfig)
+3 -4
View File
@@ -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
+27 -27
View File
@@ -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"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
"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([]awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(
awsutilv2.WithGetUserError(errors.New(testGetUserErr)),
credential.WithStateTestOpts([]awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithGetUserError(errors.New(testGetUserErr)),
),
),
}),
@@ -176,10 +176,10 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
},
},
credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutilv2.Option{
awsutilv2.WithSTSAPIFunc(
awsutilv2.NewMockSTS(
awsutilv2.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
credential.WithStateTestOpts([]awsutil.Option{
awsutil.WithSTSAPIFunc(
awsutil.NewMockSTS(
awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
),
),
}),
@@ -370,10 +370,10 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
},
},
credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(
awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
credential.WithStateTestOpts([]awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
),
),
}),
@@ -403,10 +403,10 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
},
},
credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(
awsutilv2.WithGetUserError(errors.New(testGetUserErr)),
credential.WithStateTestOpts([]awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithGetUserError(errors.New(testGetUserErr)),
),
),
}),
@@ -493,10 +493,10 @@ func TestPluginOnDeleteCatalogErr(t *testing.T) {
},
},
credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(
awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
credential.WithStateTestOpts([]awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
),
),
}),
@@ -729,8 +729,8 @@ func TestPluginOnCreateSetErr(t *testing.T) {
},
},
credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutilv2.Option{
awsutilv2.MockOptionErr(errors.New(testOptionErr)),
credential.WithStateTestOpts([]awsutil.Option{
awsutil.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([]awsutilv2.Option{
awsutilv2.MockOptionErr(errors.New(testOptionErr)),
credential.WithStateTestOpts([]awsutil.Option{
awsutil.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([]awsutilv2.Option{
awsutilv2.MockOptionErr(errors.New(testOptionErr)),
credential.WithStateTestOpts([]awsutil.Option{
awsutil.MockOptionErr(errors.New(testOptionErr)),
}),
},
expectedErrContains: fmt.Sprintf("error loading persisted state: error reading options in NewCredentialsConfig: %s", testOptionErr),
+2 -2
View File
@@ -8,14 +8,14 @@ import (
"testing"
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
"github.com/stretchr/testify/require"
)
func TestAwsCatalogPersistedStateEC2Client(t *testing.T) {
require := require.New(t)
cred, err := credential.NewAwsCredentialPersistedState(
credential.WithCredentialsConfig(&awsutilv2.CredentialsConfig{
credential.WithCredentialsConfig(&awsutil.CredentialsConfig{
Region: "us-west-2",
AccessKey: "foobar",
SecretKey: "barfoo",
+34 -41
View File
@@ -21,7 +21,6 @@ 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"
@@ -40,10 +39,6 @@ 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.
@@ -81,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)
}
}
@@ -161,13 +149,38 @@ 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)
// 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...)...,
)
if err != nil {
return nil, err
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)
}
// Replace the existing credential state.
@@ -179,19 +192,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
}
}
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:
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.
@@ -208,16 +209,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),
@@ -283,7 +276,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
File diff suppressed because it is too large. Load diff
+11 -11
View File
@@ -8,34 +8,34 @@ import (
"testing"
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
"github.com/stretchr/testify/require"
)
func TestAwsStoragePersistedStateS3Client(t *testing.T) {
cases := []struct {
name string
creds *awsutilv2.CredentialsConfig
creds *awsutil.CredentialsConfig
opts []awsStoragePersistedStateOption
awsOpts []awsutilv2.Option
awsOpts []awsutil.Option
s3Opts []s3Option
expectedRegion string
expectedEndpoint string
}{
{
name: "static credentials",
creds: func() *awsutilv2.CredentialsConfig {
creds, err := awsutilv2.NewCredentialsConfig(
awsutilv2.WithRegion("us-west-2"),
awsutilv2.WithAccessKey("foobar"),
awsutilv2.WithSecretKey("barfoo"),
creds: func() *awsutil.CredentialsConfig {
creds, err := awsutil.NewCredentialsConfig(
awsutil.WithRegion("us-west-2"),
awsutil.WithAccessKey("foobar"),
awsutil.WithSecretKey("barfoo"),
)
require.NoError(t, err)
return creds
}(),
awsOpts: []awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(),
awsOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(),
),
},
opts: []awsStoragePersistedStateOption{
+5 -8
View File
@@ -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"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/structpb"
@@ -146,9 +146,6 @@ func newTestMockS3(state *testMockS3State, opts ...testMockS3Option) s3APIFunc {
if cfg.Region != "" {
m.Region = cfg.Region
}
// if cfg. != nil {
// m.Endpoint = cfg.EndpointResolverWithOptions
// }
}
return m, nil
@@ -287,10 +284,10 @@ func deepCopyPutObjectRequest(v *pb.PutObjectRequest) *pb.PutObjectRequest {
func validSTSMock() []credential.AwsCredentialPersistedStateOption {
return []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutilv2.Option{
awsutilv2.WithSTSAPIFunc(
awsutilv2.NewMockSTS(
awsutilv2.WithGetCallerIdentityOutput(&sts.GetCallerIdentityOutput{
credential.WithStateTestOpts([]awsutil.Option{
awsutil.WithSTSAPIFunc(
awsutil.NewMockSTS(
awsutil.WithGetCallerIdentityOutput(&sts.GetCallerIdentityOutput{
Account: aws.String("0123456789"),
Arn: aws.String("arn:aws:iam::0123456789:user/test"),
UserId: aws.String("test"),
+7 -7
View File
@@ -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"
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
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 := awsutilv2.NewCredentialsConfig(
awsutilv2.WithAccessKey(accessKeyId),
awsutilv2.WithSecretKey(secretAccessKey),
c, err := awsutil.NewCredentialsConfig(
awsutil.WithAccessKey(accessKeyId),
awsutil.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 := awsutilv2.NewCredentialsConfig(
awsutilv2.WithAccessKey(accessKeyId),
awsutilv2.WithSecretKey(secretAccessKey),
c, err := awsutil.NewCredentialsConfig(
awsutil.WithAccessKey(accessKeyId),
awsutil.WithSecretKey(secretAccessKey),
)
require.NoError(err)
_, err = c.GetCallerIdentity(context.Background())