Compare commits

..
Author SHA1 Message Date
Hugo 7fa6b7e7a3 wip: Dynamic Creds tests 2023-08-23 12:23:36 +01:00
Damian Debkowski d970294995 refactor: switch from awsutil to awsutilv2 2023-08-08 23:14:11 -07:00
16 changed files with 512 additions and 1124 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](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)
+3 -1
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/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
+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/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=
+16 -15
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"
"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
+20 -22
View File
@@ -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",
},
}
+49 -62
View File
@@ -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
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"
"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))
})
}
}
+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"
"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)
+4 -3
View File
@@ -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
+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"
"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),
+2 -2
View File
@@ -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",
+41 -34
View File
@@ -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
+11 -11
View File
@@ -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{
+8 -5
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"
"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
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"
"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())