feat: assume role provider credentials (#28)
* feat: assume role provider credentials This commit updates the awsutil dependency to its latest version, which supports the capability of using dynamic credentials. The storage service was refactored to make static credentials optional. The credentials pkg was refactored to support several attribute fields used for the assume role provider. The host service still enforces the use of static credentials.
This commit is contained in:
1 parent
334732967d
commit
07f015be28
32 files changed
+2351
-998
No files matched your search
@@ -20,33 +20,47 @@ type CredentialAttributes struct {
|
||||
|
||||
// DisableCredentialRotation disables the rotation of aws secrets associated with the plugin
|
||||
DisableCredentialRotation bool
|
||||
|
||||
// RoleArn is the role arn associated with the aws credentials
|
||||
RoleArn string
|
||||
|
||||
// RoleExternalId is the external id associated with the aws credentials
|
||||
RoleExternalId string
|
||||
|
||||
// RoleSessionName is the session name associated with the aws credentials
|
||||
RoleSessionName string
|
||||
|
||||
// RoleTags is the tags associated with the aws credentials
|
||||
RoleTags map[string]string
|
||||
}
|
||||
|
||||
// GetCredentialsConfig parses values out of a protobuf struct input and returns a
|
||||
// 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 of the following fields are missing from the protobuf struct input or have
|
||||
// invalid value types: access_key_id, secret_access_key. An error is returned if
|
||||
// any unrecognized fields are found in the protobuf struct input.
|
||||
func GetCredentialsConfig(in *structpb.Struct, region string) (*awsutil.CredentialsConfig, error) {
|
||||
unknownFields := values.StructFields(in)
|
||||
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 {
|
||||
secrets = &structpb.Struct{
|
||||
Fields: make(map[string]*structpb.Value),
|
||||
}
|
||||
}
|
||||
|
||||
unknownFields := values.StructFields(secrets)
|
||||
badFields := make(map[string]string)
|
||||
|
||||
accessKey, err := values.GetStringValue(in, ConstAccessKeyId, true)
|
||||
accessKey, err := values.GetStringValue(secrets, ConstAccessKeyId, required)
|
||||
if err != nil {
|
||||
badFields[fmt.Sprintf("secrets.%s", ConstAccessKeyId)] = err.Error()
|
||||
}
|
||||
delete(unknownFields, ConstAccessKeyId)
|
||||
|
||||
secretKey, err := values.GetStringValue(in, ConstSecretAccessKey, true)
|
||||
secretKey, err := values.GetStringValue(secrets, ConstSecretAccessKey, required)
|
||||
if err != nil {
|
||||
badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = err.Error()
|
||||
}
|
||||
delete(unknownFields, ConstSecretAccessKey)
|
||||
|
||||
if region == "" {
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstRegion)] = err.Error()
|
||||
}
|
||||
|
||||
// the creds_last_rotated_time field will be found in the input struct for
|
||||
// persisted secrets, this value is not needed for creating the CredentialsConfig
|
||||
delete(unknownFields, ConstCredsLastRotatedTime)
|
||||
@@ -58,11 +72,32 @@ func GetCredentialsConfig(in *structpb.Struct, region string) (*awsutil.Credenti
|
||||
return nil, errors.InvalidArgumentError("Error in the secrets provided", badFields)
|
||||
}
|
||||
|
||||
return awsutil.NewCredentialsConfig(
|
||||
awsutil.WithAccessKey(accessKey),
|
||||
awsutil.WithSecretKey(secretKey),
|
||||
awsutil.WithRegion(region),
|
||||
)
|
||||
opts := []awsutil.Option{
|
||||
awsutil.WithEnvironmentCredentials(true),
|
||||
}
|
||||
if accessKey != "" && secretKey != "" {
|
||||
opts = append(opts,
|
||||
awsutil.WithAccessKey(accessKey),
|
||||
awsutil.WithSecretKey(secretKey),
|
||||
)
|
||||
}
|
||||
if attrs.Region != "" {
|
||||
opts = append(opts, awsutil.WithRegion(attrs.Region))
|
||||
}
|
||||
if attrs.RoleArn != "" {
|
||||
opts = append(opts, awsutil.WithRoleArn(attrs.RoleArn))
|
||||
}
|
||||
if attrs.RoleExternalId != "" {
|
||||
opts = append(opts, awsutil.WithRoleExternalId(attrs.RoleExternalId))
|
||||
}
|
||||
if attrs.RoleSessionName != "" {
|
||||
opts = append(opts, awsutil.WithRoleSessionName(attrs.RoleSessionName))
|
||||
}
|
||||
if len(attrs.RoleTags) != 0 {
|
||||
opts = append(opts, awsutil.WithRoleTags(attrs.RoleTags))
|
||||
}
|
||||
|
||||
return awsutil.NewCredentialsConfig(opts...)
|
||||
}
|
||||
|
||||
// GetCredentialAttributes parses values out of a protobuf struct input and returns a
|
||||
@@ -88,6 +123,26 @@ func GetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error)
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstDisableCredentialRotation)] = err.Error()
|
||||
}
|
||||
|
||||
roleArn, err := values.GetStringValue(in, ConstRoleArn, false)
|
||||
if err != nil {
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstRoleArn)] = err.Error()
|
||||
}
|
||||
|
||||
roleExternalId, err := values.GetStringValue(in, ConstRoleExternalId, false)
|
||||
if err != nil {
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstRoleExternalId)] = err.Error()
|
||||
}
|
||||
|
||||
roleSessionName, err := values.GetStringValue(in, ConstRoleSessionName, false)
|
||||
if err != nil {
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstRoleSessionName)] = err.Error()
|
||||
}
|
||||
|
||||
roleTags, err := values.GetMapStringString(in, ConstRoleTags, false)
|
||||
if err != nil {
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstRoleTags)] = err.Error()
|
||||
}
|
||||
|
||||
if len(badFields) > 0 {
|
||||
return nil, errors.InvalidArgumentError("Error in the attributes provided", badFields)
|
||||
}
|
||||
@@ -95,5 +150,9 @@ func GetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error)
|
||||
return &CredentialAttributes{
|
||||
Region: region,
|
||||
DisableCredentialRotation: disableCredentialRotation,
|
||||
RoleArn: roleArn,
|
||||
RoleExternalId: roleExternalId,
|
||||
RoleSessionName: roleSessionName,
|
||||
RoleTags: roleTags,
|
||||
}, nil
|
||||
}
|
||||
@@ -61,6 +61,28 @@ func TestGetCredentialAttributes(t *testing.T) {
|
||||
DisableCredentialRotation: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with assume role",
|
||||
in: map[string]any{
|
||||
ConstRegion: "us-west-2",
|
||||
ConstRoleArn: "arn:aws:iam::123456789012:role/S3Access",
|
||||
ConstRoleExternalId: "1234567890",
|
||||
ConstRoleSessionName: "test-session",
|
||||
ConstRoleTags: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
expected: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
DisableCredentialRotation: false,
|
||||
RoleArn: "arn:aws:iam::123456789012:role/S3Access",
|
||||
RoleExternalId: "1234567890",
|
||||
RoleSessionName: "test-session",
|
||||
RoleTags: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -89,44 +111,91 @@ func TestGetCredentialAttributes(t *testing.T) {
|
||||
func TestGetCredentialsConfig(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in map[string]any
|
||||
region string
|
||||
secrets *structpb.Struct
|
||||
required bool
|
||||
attrs *CredentialAttributes
|
||||
expected *awsutil.CredentialsConfig
|
||||
expectedErrContains string
|
||||
}{
|
||||
{
|
||||
name: "missing access_key_id",
|
||||
in: map[string]any{},
|
||||
region: "us-west-2",
|
||||
expectedErrContains: "missing required value \"access_key_id\"",
|
||||
name: "no secrets",
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing secret_access_key",
|
||||
in: map[string]any{
|
||||
ConstAccessKeyId: "foobar",
|
||||
name: "with static credentials",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
},
|
||||
},
|
||||
region: "us-west-2",
|
||||
expectedErrContains: "missing required value \"secret_access_key\"",
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing access key",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
},
|
||||
},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
required: true,
|
||||
expectedErrContains: "secrets.access_key_id: missing required value",
|
||||
},
|
||||
{
|
||||
name: "missing secret key",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expectedErrContains: "secrets.secret_access_key: missing required value",
|
||||
},
|
||||
{
|
||||
name: "unknown fields",
|
||||
in: map[string]any{
|
||||
ConstAccessKeyId: "foobar",
|
||||
ConstSecretAccessKey: "bazqux",
|
||||
"foo": true,
|
||||
"bar": true,
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
"foo": structpb.NewBoolValue(true),
|
||||
"bar": structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
region: "us-west-2",
|
||||
expectedErrContains: "secrets.bar: unrecognized field, secrets.foo: unrecognized field",
|
||||
},
|
||||
{
|
||||
name: "valid ignore creds_last_rotated_time",
|
||||
in: map[string]any{
|
||||
ConstAccessKeyId: "foobar",
|
||||
ConstSecretAccessKey: "bazqux",
|
||||
ConstCredsLastRotatedTime: "2006-01-02T15:04:05+07:00",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
||||
},
|
||||
},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
region: "us-west-2",
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
@@ -134,16 +203,56 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "good",
|
||||
in: map[string]any{
|
||||
ConstAccessKeyId: "foobar",
|
||||
ConstSecretAccessKey: "bazqux",
|
||||
name: "with assume role",
|
||||
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",
|
||||
},
|
||||
},
|
||||
region: "us-west-2",
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
Region: "us-west-2",
|
||||
RoleARN: "arn:aws:iam::123456789012:role/S3Access",
|
||||
RoleExternalId: "1234567890",
|
||||
RoleSessionName: "test-session",
|
||||
RoleTags: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with static credential & assume role",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
},
|
||||
},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
RoleArn: "arn:aws:iam::123456789012:role/S3Access",
|
||||
RoleExternalId: "1234567890",
|
||||
RoleSessionName: "test-session",
|
||||
RoleTags: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
RoleARN: "arn:aws:iam::123456789012:role/S3Access",
|
||||
RoleExternalId: "1234567890",
|
||||
RoleSessionName: "test-session",
|
||||
RoleTags: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -153,10 +262,7 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
input, err := structpb.NewStruct(tc.in)
|
||||
require.NoError(err)
|
||||
|
||||
actual, err := GetCredentialsConfig(input, tc.region)
|
||||
actual, err := GetCredentialsConfig(tc.secrets, tc.attrs, tc.required)
|
||||
if tc.expectedErrContains != "" {
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), tc.expectedErrContains)
|
||||
@@ -168,6 +274,10 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
require.Equal(tc.expected.AccessKey, actual.AccessKey)
|
||||
require.Equal(tc.expected.SecretKey, actual.SecretKey)
|
||||
require.Equal(tc.expected.Region, actual.Region)
|
||||
require.Equal(tc.expected.RoleARN, actual.RoleARN)
|
||||
require.Equal(tc.expected.RoleExternalId, actual.RoleExternalId)
|
||||
require.Equal(tc.expected.RoleSessionName, actual.RoleSessionName)
|
||||
require.Equal(tc.expected.RoleTags, actual.RoleTags)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,16 @@ const (
|
||||
|
||||
// ConstCredsLastRotatedTime is the key for the last rotated time in the aws credentials.
|
||||
ConstCredsLastRotatedTime = "creds_last_rotated_time"
|
||||
|
||||
// ConstRoleArn is the key for assuming a IAM role.
|
||||
ConstRoleArn = "role_arn"
|
||||
|
||||
// ConstRoleExternalId is the key for the external id used for assuming a IAM role.
|
||||
ConstRoleExternalId = "role_external_id"
|
||||
|
||||
// ConstRoleSessionName is the key for the session name used for assuming a IAM role.
|
||||
ConstRoleSessionName = "role_session_name"
|
||||
|
||||
// ConstRoleTags is the key for the tags used for assuming a IAM role.
|
||||
ConstRoleTags = "role_tags"
|
||||
)
|
||||
+94
-113
@@ -6,6 +6,7 @@ package credential
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
@@ -25,19 +26,13 @@ type AwsCredentialPersistedStateOption func(s *AwsCredentialPersistedState) erro
|
||||
|
||||
// AwsCredentialPersistedState is the persisted state for the AWS credential.
|
||||
type AwsCredentialPersistedState struct {
|
||||
// AccessKeyId is the access key id for the AWS credential.
|
||||
AccessKeyId string
|
||||
// SecretAccessKey is the secret access key for the AWS credential.
|
||||
SecretAccessKey string
|
||||
// CredentialsConfig is the credential configuration for the AWS credential.
|
||||
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 []awsutil.Option
|
||||
|
||||
// Region is not a part of persisted state but if set is passed to clients
|
||||
// created with this state
|
||||
region string
|
||||
}
|
||||
|
||||
// WithStateTestOpts enables unit testing different edge cases
|
||||
@@ -50,31 +45,19 @@ func WithStateTestOpts(opts []awsutil.Option) AwsCredentialPersistedStateOption
|
||||
}
|
||||
}
|
||||
|
||||
// WithAccessKeyId sets the value for AccesskeyId in the storage persisted state.
|
||||
func WithAccessKeyId(x string) AwsCredentialPersistedStateOption {
|
||||
// WithCredentialsConfig sets the value for CredentialsConfig in the credential persisted state.
|
||||
func WithCredentialsConfig(x *awsutil.CredentialsConfig) AwsCredentialPersistedStateOption {
|
||||
return func(s *AwsCredentialPersistedState) error {
|
||||
if s.AccessKeyId != "" {
|
||||
return errors.New("access key id already set")
|
||||
if s.CredentialsConfig != nil {
|
||||
return errors.New("credentials config already set")
|
||||
}
|
||||
|
||||
s.AccessKeyId = x
|
||||
s.CredentialsConfig = x
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSecretAccessKey sets the value for SecretAccessKey in the storage persisted state.
|
||||
func WithSecretAccessKey(x string) AwsCredentialPersistedStateOption {
|
||||
return func(s *AwsCredentialPersistedState) error {
|
||||
if s.SecretAccessKey != "" {
|
||||
return errors.New("secret access key already set")
|
||||
}
|
||||
|
||||
s.SecretAccessKey = x
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithCredsLastRotatedTime sets the value for CredsLastRotatedTime in the storage persisted state.
|
||||
// WithCredsLastRotatedTime sets the value for CredsLastRotatedTime in the credential persisted state.
|
||||
func WithCredsLastRotatedTime(t time.Time) AwsCredentialPersistedStateOption {
|
||||
return func(s *AwsCredentialPersistedState) error {
|
||||
if !s.CredsLastRotatedTime.IsZero() {
|
||||
@@ -86,18 +69,6 @@ func WithCredsLastRotatedTime(t time.Time) AwsCredentialPersistedStateOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithRegion sets the value for region in the storage persisted state.
|
||||
func WithRegion(with string) AwsCredentialPersistedStateOption {
|
||||
return func(s *AwsCredentialPersistedState) error {
|
||||
if s.region != "" {
|
||||
return errors.New("region already set")
|
||||
}
|
||||
|
||||
s.region = with
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewAwsCredentialPersistedState returns a AwsCredentialPersistedState.
|
||||
// Supported options include: WithAccessKeyId, WithSecretAccessKey
|
||||
// WithCredsLastRotatedTime, & WithRegion.
|
||||
@@ -108,27 +79,19 @@ func NewAwsCredentialPersistedState(opts ...AwsCredentialPersistedStateOption) (
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ValidateCreds takes the access key and secret key from the persisted state
|
||||
// ValidateCreds takes the credentials configuration from the persisted state
|
||||
// and runs sts.GetCallerIdentity for the current credentials, which is done
|
||||
// to check that the credentials are valid.
|
||||
func (s *AwsCredentialPersistedState) ValidateCreds() error {
|
||||
c, err := awsutil.NewCredentialsConfig(append([]awsutil.Option{
|
||||
awsutil.WithAccessKey(s.AccessKeyId),
|
||||
awsutil.WithSecretKey(s.SecretAccessKey),
|
||||
awsutil.WithRegion(s.region),
|
||||
}, s.testOpts...)...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading credentials: %w", err)
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.New("missing credentials config")
|
||||
}
|
||||
|
||||
if _, err := c.GetCallerIdentity(s.testOpts...); err != nil {
|
||||
if _, err := s.CredentialsConfig.GetCallerIdentity(s.testOpts...); err != nil {
|
||||
return fmt.Errorf("error validating credentials: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -138,42 +101,27 @@ func (s *AwsCredentialPersistedState) ValidateCreds() error {
|
||||
// This ensures that any generated new secret key never leaves this function in case of an error, even though it will
|
||||
// still result in an extraneous access key existing.
|
||||
func (s *AwsCredentialPersistedState) RotateCreds() error {
|
||||
c, err := awsutil.NewCredentialsConfig(append([]awsutil.Option{
|
||||
awsutil.WithAccessKey(s.AccessKeyId),
|
||||
awsutil.WithSecretKey(s.SecretAccessKey),
|
||||
awsutil.WithRegion(s.region),
|
||||
}, s.testOpts...)...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading credentials: %w", err)
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.New("missing credentials config")
|
||||
}
|
||||
|
||||
if err := c.RotateKeys(append([]awsutil.Option{
|
||||
if err := s.CredentialsConfig.RotateKeys(append([]awsutil.Option{
|
||||
awsutil.WithValidityCheckTimeout(rotationWaitTimeout),
|
||||
}, s.testOpts...)...); err != nil {
|
||||
return fmt.Errorf("error rotating credentials: %w", err)
|
||||
}
|
||||
|
||||
s.AccessKeyId = c.AccessKey
|
||||
s.SecretAccessKey = c.SecretKey
|
||||
s.CredsLastRotatedTime = time.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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(accessKeyId, secretAccessKey string) error {
|
||||
if accessKeyId == "" {
|
||||
return errors.New("access key id cannot be empty")
|
||||
func (s *AwsCredentialPersistedState) ReplaceCreds(credentialsConfig *awsutil.CredentialsConfig) error {
|
||||
if credentialsConfig == nil {
|
||||
return errors.New("missing new credentials config")
|
||||
}
|
||||
|
||||
if secretAccessKey == "" {
|
||||
return errors.New("secret access key cannot be empty")
|
||||
}
|
||||
|
||||
if accessKeyId == s.AccessKeyId {
|
||||
return errors.New("attempting to replace access key with the same one")
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.New("missing credentials config")
|
||||
}
|
||||
|
||||
if !s.CredsLastRotatedTime.IsZero() {
|
||||
@@ -186,8 +134,7 @@ func (s *AwsCredentialPersistedState) ReplaceCreds(accessKeyId, secretAccessKey
|
||||
}
|
||||
|
||||
// Set the new attributes and clear the rotated time.
|
||||
s.AccessKeyId = accessKeyId
|
||||
s.SecretAccessKey = secretAccessKey
|
||||
s.CredentialsConfig = credentialsConfig
|
||||
s.CredsLastRotatedTime = time.Time{}
|
||||
return nil
|
||||
}
|
||||
@@ -196,23 +143,17 @@ func (s *AwsCredentialPersistedState) ReplaceCreds(accessKeyId, secretAccessKey
|
||||
// ID, secret access key, and rotation time fields are zeroed out in
|
||||
// the state just to ensure that they cannot be re-used after.
|
||||
func (s *AwsCredentialPersistedState) DeleteCreds() error {
|
||||
c, err := awsutil.NewCredentialsConfig(append([]awsutil.Option{
|
||||
awsutil.WithAccessKey(s.AccessKeyId),
|
||||
awsutil.WithSecretKey(s.SecretAccessKey),
|
||||
awsutil.WithRegion(s.region),
|
||||
}, s.testOpts...)...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading credentials: %w", err)
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.New("missing credentials config")
|
||||
}
|
||||
|
||||
if err := c.DeleteAccessKey(s.AccessKeyId, s.testOpts...); err != nil {
|
||||
if err := s.CredentialsConfig.DeleteAccessKey(s.CredentialsConfig.AccessKey, s.testOpts...); err != nil {
|
||||
// Determine if the deletion error was due to a missing
|
||||
// resource. If it was, just pass it.
|
||||
var awsErr awserr.Error
|
||||
if errors.As(err, &awsErr) {
|
||||
if awsErr.Code() == iam.ErrCodeNoSuchEntityException {
|
||||
s.AccessKeyId = ""
|
||||
s.SecretAccessKey = ""
|
||||
s.CredentialsConfig = nil
|
||||
s.CredsLastRotatedTime = time.Time{}
|
||||
return nil
|
||||
}
|
||||
@@ -222,8 +163,7 @@ func (s *AwsCredentialPersistedState) DeleteCreds() error {
|
||||
return err
|
||||
}
|
||||
|
||||
s.AccessKeyId = ""
|
||||
s.SecretAccessKey = ""
|
||||
s.CredentialsConfig = nil
|
||||
s.CredsLastRotatedTime = time.Time{}
|
||||
return nil
|
||||
}
|
||||
@@ -231,50 +171,50 @@ func (s *AwsCredentialPersistedState) DeleteCreds() error {
|
||||
// GetSession returns a configured AWS session for the credentials in
|
||||
// the state.
|
||||
func (s *AwsCredentialPersistedState) GetSession() (*session.Session, error) {
|
||||
c, err := awsutil.NewCredentialsConfig(append([]awsutil.Option{
|
||||
awsutil.WithAccessKey(s.AccessKeyId),
|
||||
awsutil.WithSecretKey(s.SecretAccessKey),
|
||||
awsutil.WithRegion(s.region),
|
||||
}, s.testOpts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.GetSession()
|
||||
return s.CredentialsConfig.GetSession(s.testOpts...)
|
||||
}
|
||||
|
||||
// ToMap returns a map of the credentials stored in the persisted state,
|
||||
// which includes the following keys: access_key_id, secret_access_key,
|
||||
// & creds_last_rotated_time
|
||||
// ToMap returns a map of the credentials stored in the persisted state.
|
||||
// ToMap will return an empty map for temporary credentials. ToMap will
|
||||
// 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) {
|
||||
return map[string]any{}
|
||||
}
|
||||
return map[string]any{
|
||||
ConstAccessKeyId: s.AccessKeyId,
|
||||
ConstSecretAccessKey: s.SecretAccessKey,
|
||||
ConstAccessKeyId: s.CredentialsConfig.AccessKey,
|
||||
ConstSecretAccessKey: s.CredentialsConfig.SecretKey,
|
||||
ConstCredsLastRotatedTime: s.CredsLastRotatedTime.Format(time.RFC3339Nano),
|
||||
}
|
||||
}
|
||||
|
||||
// AwsCredentialPersistedStateFromProto parses values out of a protobuf struct input
|
||||
// and returns a AwsCredentialPersistedState used for configuring an AWS session.
|
||||
// An error is returned if any of the following fields are missing from the protobuf
|
||||
// struct input or have invalid value types: access_key_id, secret_access_key, &
|
||||
// creds_last_rotated_time.
|
||||
func AwsCredentialPersistedStateFromProto(in *structpb.Struct, opts ...AwsCredentialPersistedStateOption) (*AwsCredentialPersistedState, error) {
|
||||
if in == nil {
|
||||
return nil, errors.New("missing persisted secrets")
|
||||
func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *CredentialAttributes, opts ...AwsCredentialPersistedStateOption) (*AwsCredentialPersistedState, error) {
|
||||
// initialize secrets if it is nil
|
||||
// secrets can be nil because static credentials are optional
|
||||
if secrets == nil {
|
||||
secrets = &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{},
|
||||
}
|
||||
}
|
||||
|
||||
accessKeyId, err := values.GetStringValue(in, ConstAccessKeyId, true)
|
||||
if attrs == nil {
|
||||
return nil, fmt.Errorf("missing credential attributes")
|
||||
}
|
||||
|
||||
accessKeyId, err := values.GetStringValue(secrets, ConstAccessKeyId, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("persisted state integrity error: %w", err)
|
||||
}
|
||||
|
||||
secretAccessKey, err := values.GetStringValue(in, ConstSecretAccessKey, true)
|
||||
secretAccessKey, err := values.GetStringValue(secrets, ConstSecretAccessKey, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("persisted state integrity error: %w", err)
|
||||
}
|
||||
|
||||
credsLastRotatedTime, err := values.GetTimeValue(in, ConstCredsLastRotatedTime)
|
||||
credsLastRotatedTime, err := values.GetTimeValue(secrets, ConstCredsLastRotatedTime)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("persisted state integrity error: %w", err)
|
||||
}
|
||||
@@ -284,9 +224,50 @@ func AwsCredentialPersistedStateFromProto(in *structpb.Struct, opts ...AwsCreden
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.AccessKeyId = accessKeyId
|
||||
s.SecretAccessKey = secretAccessKey
|
||||
awsOpts := append([]awsutil.Option{}, s.testOpts...)
|
||||
if accessKeyId != "" && secretAccessKey != "" {
|
||||
awsOpts = append(awsOpts,
|
||||
awsutil.WithAccessKey(accessKeyId),
|
||||
awsutil.WithSecretKey(secretAccessKey),
|
||||
)
|
||||
}
|
||||
if attrs.Region != "" {
|
||||
awsOpts = append(awsOpts, awsutil.WithRegion(attrs.Region))
|
||||
}
|
||||
if attrs.RoleArn != "" {
|
||||
awsOpts = append(awsOpts, awsutil.WithRoleArn(attrs.RoleArn))
|
||||
}
|
||||
if attrs.RoleExternalId != "" {
|
||||
awsOpts = append(awsOpts, awsutil.WithRoleExternalId(attrs.RoleExternalId))
|
||||
}
|
||||
if attrs.RoleSessionName != "" {
|
||||
awsOpts = append(awsOpts, awsutil.WithRoleSessionName(attrs.RoleSessionName))
|
||||
}
|
||||
if len(attrs.RoleTags) != 0 {
|
||||
awsOpts = append(awsOpts, awsutil.WithRoleTags(attrs.RoleTags))
|
||||
}
|
||||
credentialsConfig, err := awsutil.NewCredentialsConfig(awsOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.CredentialsConfig = credentialsConfig
|
||||
s.CredsLastRotatedTime = credsLastRotatedTime
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// HasDynamicCredentials returns true if the access key is a dynamic credential type.
|
||||
//
|
||||
// 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.
|
||||
func HasDynamicCredentials(accessKey string) bool {
|
||||
return strings.HasPrefix(accessKey, "ASIA")
|
||||
}
|
||||
|
||||
// HasStaticCredentials returns true if the access key is a static credential type.
|
||||
//
|
||||
// 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 HasStaticCredentials(accessKey string) bool {
|
||||
return strings.HasPrefix(accessKey, "AKIA")
|
||||
}
|
||||
+434
-193
@@ -36,21 +36,22 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
|
||||
expectedErr: testOptionErr,
|
||||
},
|
||||
{
|
||||
name: "access key id",
|
||||
name: "with credentials config",
|
||||
opts: []AwsCredentialPersistedStateOption{
|
||||
WithAccessKeyId("foobar"),
|
||||
WithCredentialsConfig(
|
||||
&awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
),
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "secret access key",
|
||||
opts: []AwsCredentialPersistedStateOption{
|
||||
WithSecretAccessKey("bazqux"),
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -63,29 +64,12 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "region key",
|
||||
name: "double set credentials config",
|
||||
opts: []AwsCredentialPersistedStateOption{
|
||||
WithRegion("foobar"),
|
||||
WithCredentialsConfig(&awsutil.CredentialsConfig{}),
|
||||
WithCredentialsConfig(&awsutil.CredentialsConfig{}),
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
region: "foobar",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "double set access key id",
|
||||
opts: []AwsCredentialPersistedStateOption{
|
||||
WithAccessKeyId("foobar"),
|
||||
WithAccessKeyId("onetwo"),
|
||||
},
|
||||
expectedErr: "access key id already set",
|
||||
},
|
||||
{
|
||||
name: "double set secret access key",
|
||||
opts: []AwsCredentialPersistedStateOption{
|
||||
WithSecretAccessKey("bazqux"),
|
||||
WithSecretAccessKey("threefour"),
|
||||
},
|
||||
expectedErr: "secret access key already set",
|
||||
expectedErr: "credentials config already set",
|
||||
},
|
||||
{
|
||||
name: "double set rotation time",
|
||||
@@ -95,14 +79,6 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
|
||||
},
|
||||
expectedErr: "last rotation time already set",
|
||||
},
|
||||
{
|
||||
name: "double set region",
|
||||
opts: []AwsCredentialPersistedStateOption{
|
||||
WithRegion("foobar"),
|
||||
WithRegion("barfoo"),
|
||||
},
|
||||
expectedErr: "region already set",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -116,7 +92,13 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
|
||||
}
|
||||
|
||||
require.NoError(err)
|
||||
require.Equal(tc.expected, actual)
|
||||
require.Equal(tc.expected.CredsLastRotatedTime, actual.CredsLastRotatedTime)
|
||||
if tc.expected.CredentialsConfig != nil {
|
||||
require.NotNil(actual.CredentialsConfig)
|
||||
require.Equal(tc.expected.CredentialsConfig.AccessKey, actual.CredentialsConfig.AccessKey)
|
||||
require.Equal(tc.expected.CredentialsConfig.SecretKey, actual.CredentialsConfig.SecretKey)
|
||||
require.Equal(tc.expected.CredentialsConfig.Region, actual.CredentialsConfig.Region)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -124,43 +106,95 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
|
||||
func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in map[string]any
|
||||
secrets *structpb.Struct
|
||||
attrs *CredentialAttributes
|
||||
opts []AwsCredentialPersistedStateOption
|
||||
expected *AwsCredentialPersistedState
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "no secrets",
|
||||
in: nil,
|
||||
expectedErr: "missing persisted secrets",
|
||||
name: "missing credential attributes",
|
||||
expectedErr: "missing credential attributes",
|
||||
},
|
||||
{
|
||||
name: "missing access key ID",
|
||||
in: map[string]any{},
|
||||
expectedErr: "persisted state integrity error: missing required value \"access_key_id\"",
|
||||
},
|
||||
{
|
||||
name: "missing secret access key",
|
||||
in: map[string]any{
|
||||
ConstAccessKeyId: "foobar",
|
||||
name: "missing secrets",
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing long-term access key id",
|
||||
secrets: &structpb.Struct{},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing long-term secret access key",
|
||||
secrets: &structpb.Struct{},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with long-term static credentials",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
},
|
||||
},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
},
|
||||
expectedErr: "persisted state integrity error: missing required value \"secret_access_key\"",
|
||||
},
|
||||
{
|
||||
name: "bad last rotated time",
|
||||
in: map[string]any{
|
||||
ConstAccessKeyId: "foobar",
|
||||
ConstSecretAccessKey: "bazqux",
|
||||
ConstCredsLastRotatedTime: "notatime",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
ConstCredsLastRotatedTime: structpb.NewStringValue("notatime"),
|
||||
},
|
||||
},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expectedErr: "persisted state integrity error: could not parse time in value \"creds_last_rotated_time\": parsing time \"notatime\" as \"2006-01-02T15:04:05.999999999Z07:00\": cannot parse \"notatime\" as \"2006\"",
|
||||
},
|
||||
{
|
||||
name: "option error",
|
||||
in: map[string]any{
|
||||
ConstAccessKeyId: "foobar",
|
||||
ConstSecretAccessKey: "bazqux",
|
||||
ConstCredsLastRotatedTime: "2006-01-02T15:04:05+07:00",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
||||
},
|
||||
},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
opts: []AwsCredentialPersistedStateOption{
|
||||
func(s *AwsCredentialPersistedState) error {
|
||||
@@ -171,14 +205,22 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "good with non-zero timestamp",
|
||||
in: map[string]any{
|
||||
ConstAccessKeyId: "foobar",
|
||||
ConstSecretAccessKey: "bazqux",
|
||||
ConstCredsLastRotatedTime: "2006-01-02T15:04:05+07:00",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
||||
},
|
||||
},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
CredsLastRotatedTime: func() time.Time {
|
||||
t, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
|
||||
if err != nil {
|
||||
@@ -190,33 +232,79 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "good with zero timestamp",
|
||||
in: map[string]any{
|
||||
ConstAccessKeyId: "foobar",
|
||||
ConstSecretAccessKey: "bazqux",
|
||||
ConstCredsLastRotatedTime: (time.Time{}).Format(time.RFC3339Nano),
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
ConstCredsLastRotatedTime: structpb.NewStringValue((time.Time{}).Format(time.RFC3339Nano)),
|
||||
},
|
||||
},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
CredsLastRotatedTime: time.Time{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "good (ignoring non-test options)",
|
||||
in: map[string]any{
|
||||
ConstAccessKeyId: "foobar",
|
||||
ConstSecretAccessKey: "bazqux",
|
||||
ConstCredsLastRotatedTime: (time.Time{}).Format(time.RFC3339Nano),
|
||||
},
|
||||
opts: []AwsCredentialPersistedStateOption{
|
||||
WithAccessKeyId("ignored"),
|
||||
WithRegion("us-west-2"),
|
||||
name: "with assume role",
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
RoleArn: "arn:aws:iam::123456789012:role/S3Access",
|
||||
RoleExternalId: "1234567890",
|
||||
RoleSessionName: "test-session",
|
||||
RoleTags: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
RoleARN: "arn:aws:iam::123456789012:role/S3Access",
|
||||
RoleExternalId: "1234567890",
|
||||
RoleSessionName: "test-session",
|
||||
RoleTags: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with assume role & static credentials",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
ConstCredsLastRotatedTime: structpb.NewStringValue((time.Time{}).Format(time.RFC3339Nano)),
|
||||
},
|
||||
},
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
RoleArn: "arn:aws:iam::123456789012:role/S3Access",
|
||||
RoleExternalId: "1234567890",
|
||||
RoleSessionName: "test-session",
|
||||
RoleTags: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
RoleARN: "arn:aws:iam::123456789012:role/S3Access",
|
||||
RoleExternalId: "1234567890",
|
||||
RoleSessionName: "test-session",
|
||||
RoleTags: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
CredsLastRotatedTime: time.Time{},
|
||||
region: "us-west-2",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -226,55 +314,67 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
var (
|
||||
input *structpb.Struct
|
||||
err error
|
||||
)
|
||||
if tc.in != nil {
|
||||
input, err = structpb.NewStruct(tc.in)
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
actual, err := AwsCredentialPersistedStateFromProto(input, tc.opts...)
|
||||
actual, err := AwsCredentialPersistedStateFromProto(tc.secrets, tc.attrs, tc.opts...)
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(err)
|
||||
require.Equal(tc.expected.AccessKeyId, actual.AccessKeyId)
|
||||
require.Equal(tc.expected.SecretAccessKey, actual.SecretAccessKey)
|
||||
require.Equal(tc.expected.region, actual.region)
|
||||
require.NotNil(actual)
|
||||
require.Equal(tc.expected.CredsLastRotatedTime, actual.CredsLastRotatedTime)
|
||||
if tc.expected.CredentialsConfig != nil {
|
||||
require.NotNil(actual.CredentialsConfig)
|
||||
require.Equal(tc.expected.CredentialsConfig.AccessKey, actual.CredentialsConfig.AccessKey)
|
||||
require.Equal(tc.expected.CredentialsConfig.SecretKey, actual.CredentialsConfig.SecretKey)
|
||||
require.Equal(tc.expected.CredentialsConfig.Region, actual.CredentialsConfig.Region)
|
||||
require.Equal(tc.expected.CredentialsConfig.RoleARN, actual.CredentialsConfig.RoleARN)
|
||||
require.Equal(tc.expected.CredentialsConfig.RoleExternalId, actual.CredentialsConfig.RoleExternalId)
|
||||
require.Equal(tc.expected.CredentialsConfig.RoleSessionName, actual.CredentialsConfig.RoleSessionName)
|
||||
require.Equal(tc.expected.CredentialsConfig.RoleTags, actual.CredentialsConfig.RoleTags)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAwsCatalogPersistedStateValidateCreds(t *testing.T) {
|
||||
func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "could not load credentials",
|
||||
in: &AwsCredentialPersistedState{
|
||||
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
|
||||
},
|
||||
expectedErr: fmt.Sprintf("error loading credentials: error reading options in NewCredentialsConfig: %s", testOptionErr),
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedErr: "missing credentials config",
|
||||
},
|
||||
{
|
||||
name: "validation error",
|
||||
in: &AwsCredentialPersistedState{
|
||||
testOpts: []awsutil.Option{awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr))))},
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(
|
||||
awsutil.NewMockSTS(
|
||||
awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
expectedErr: fmt.Sprintf("error validating credentials: %s", testGetCallerIdentityErr),
|
||||
},
|
||||
{
|
||||
name: "good",
|
||||
in: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
testOpts: []awsutil.Option{awsutil.WithSTSAPIFunc(awsutil.NewMockSTS())},
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
testOpts: []awsutil.Option{awsutil.WithSTSAPIFunc(awsutil.NewMockSTS())},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -294,27 +394,27 @@ func TestAwsCatalogPersistedStateValidateCreds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAwsCatalogPersistedStateRotateCreds(t *testing.T) {
|
||||
func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
expectedAccessKeyId string
|
||||
expectedSecretAccessKey string
|
||||
exepected *awsutil.CredentialsConfig
|
||||
expectedNonZeroRotatedTime bool
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "could not load credentials",
|
||||
in: &AwsCredentialPersistedState{
|
||||
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
|
||||
},
|
||||
expectedErr: fmt.Sprintf("error loading credentials: error reading options in NewCredentialsConfig: %s", testOptionErr),
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedErr: "missing credentials config",
|
||||
},
|
||||
{
|
||||
name: "rotation error",
|
||||
in: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
@@ -328,8 +428,11 @@ func TestAwsCatalogPersistedStateRotateCreds(t *testing.T) {
|
||||
{
|
||||
name: "good",
|
||||
in: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(
|
||||
awsutil.NewMockSTS(),
|
||||
@@ -358,8 +461,11 @@ func TestAwsCatalogPersistedStateRotateCreds(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
expectedAccessKeyId: "one",
|
||||
expectedSecretAccessKey: "two",
|
||||
exepected: &awsutil.CredentialsConfig{
|
||||
AccessKey: "one",
|
||||
SecretKey: "two",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expectedNonZeroRotatedTime: true,
|
||||
},
|
||||
}
|
||||
@@ -375,52 +481,42 @@ func TestAwsCatalogPersistedStateRotateCreds(t *testing.T) {
|
||||
}
|
||||
|
||||
require.NoError(err)
|
||||
require.Equal(tc.expectedAccessKeyId, tc.in.AccessKeyId)
|
||||
require.Equal(tc.expectedSecretAccessKey, tc.in.SecretAccessKey)
|
||||
require.Equal(tc.exepected.AccessKey, tc.in.CredentialsConfig.AccessKey)
|
||||
require.Equal(tc.exepected.SecretKey, tc.in.CredentialsConfig.SecretKey)
|
||||
require.Equal(tc.expectedNonZeroRotatedTime, !tc.in.CredsLastRotatedTime.IsZero())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAwsCatalogPersistedStateReplaceCreds(t *testing.T) {
|
||||
func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
||||
// NOTE: Not safe to run this test in parallel
|
||||
state := new(testMockIAMState)
|
||||
cases := []struct {
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
accessKeyId string
|
||||
secretAccessKey string
|
||||
expectedAccessKeyId string
|
||||
expectedSecretAccessKey string
|
||||
expectedDeleteCalled bool
|
||||
expectedErr string
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
credentialConfig *awsutil.CredentialsConfig
|
||||
expected *awsutil.CredentialsConfig
|
||||
expectedDeleteCalled bool
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "missing access key id",
|
||||
name: "missing new credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedErr: "access key id cannot be empty",
|
||||
expectedErr: "missing new credentials config",
|
||||
},
|
||||
{
|
||||
name: "missing access key id",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
accessKeyId: "one",
|
||||
expectedErr: "secret access key cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "identical access key id",
|
||||
in: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
},
|
||||
accessKeyId: "foobar",
|
||||
secretAccessKey: "bazqux",
|
||||
expectedErr: "attempting to replace access key with the same one",
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
credentialConfig: &awsutil.CredentialsConfig{},
|
||||
expectedErr: "missing credentials config",
|
||||
},
|
||||
{
|
||||
name: "deletion error",
|
||||
in: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
CredsLastRotatedTime: time.Now(),
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
@@ -430,15 +526,16 @@ func TestAwsCatalogPersistedStateReplaceCreds(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
accessKeyId: "one",
|
||||
secretAccessKey: "two",
|
||||
expectedErr: fmt.Sprintf("error deleting old access key: %s", testDeleteAccessKeyErr),
|
||||
credentialConfig: &awsutil.CredentialsConfig{},
|
||||
expectedErr: fmt.Sprintf("error deleting old access key: %s", testDeleteAccessKeyErr),
|
||||
},
|
||||
{
|
||||
name: "good with delete of old rotated",
|
||||
in: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
CredsLastRotatedTime: time.Now(),
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
@@ -446,28 +543,38 @@ func TestAwsCatalogPersistedStateReplaceCreds(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
accessKeyId: "one",
|
||||
secretAccessKey: "two",
|
||||
expectedAccessKeyId: "one",
|
||||
expectedSecretAccessKey: "two",
|
||||
expectedDeleteCalled: true,
|
||||
credentialConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "one",
|
||||
SecretKey: "two",
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
AccessKey: "one",
|
||||
SecretKey: "two",
|
||||
},
|
||||
expectedDeleteCalled: true,
|
||||
},
|
||||
{
|
||||
name: "good without delete of old rotated",
|
||||
in: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
newTestMockIAM(state),
|
||||
),
|
||||
},
|
||||
},
|
||||
accessKeyId: "one",
|
||||
secretAccessKey: "two",
|
||||
expectedAccessKeyId: "one",
|
||||
expectedSecretAccessKey: "two",
|
||||
expectedDeleteCalled: false,
|
||||
credentialConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "one",
|
||||
SecretKey: "two",
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
AccessKey: "one",
|
||||
SecretKey: "two",
|
||||
},
|
||||
expectedDeleteCalled: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -476,39 +583,39 @@ func TestAwsCatalogPersistedStateReplaceCreds(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
state.Reset()
|
||||
err := tc.in.ReplaceCreds(tc.accessKeyId, tc.secretAccessKey)
|
||||
err := tc.in.ReplaceCreds(tc.credentialConfig)
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(err)
|
||||
require.Equal(tc.expectedAccessKeyId, tc.in.AccessKeyId)
|
||||
require.Equal(tc.expectedSecretAccessKey, tc.in.SecretAccessKey)
|
||||
require.Equal(tc.expected.AccessKey, tc.in.CredentialsConfig.AccessKey)
|
||||
require.Equal(tc.expected.SecretKey, tc.in.CredentialsConfig.SecretKey)
|
||||
require.Zero(tc.in.CredsLastRotatedTime)
|
||||
require.Equal(tc.expectedDeleteCalled, state.DeleteAccessKeyCalled)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAwsCatalogPersistedStateDeleteCreds(t *testing.T) {
|
||||
func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "could not load credentials",
|
||||
in: &AwsCredentialPersistedState{
|
||||
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
|
||||
},
|
||||
expectedErr: fmt.Sprintf("error loading credentials: error reading options in NewCredentialsConfig: %s", testOptionErr),
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedErr: "missing credentials config",
|
||||
},
|
||||
{
|
||||
name: "deletion error",
|
||||
in: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
@@ -522,8 +629,10 @@ func TestAwsCatalogPersistedStateDeleteCreds(t *testing.T) {
|
||||
{
|
||||
name: "deletion error, but OK because key was just gone",
|
||||
in: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(
|
||||
@@ -538,8 +647,10 @@ func TestAwsCatalogPersistedStateDeleteCreds(t *testing.T) {
|
||||
{
|
||||
name: "good",
|
||||
in: &AwsCredentialPersistedState{
|
||||
AccessKeyId: "foobar",
|
||||
SecretAccessKey: "bazqux",
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(),
|
||||
@@ -560,19 +671,149 @@ func TestAwsCatalogPersistedStateDeleteCreds(t *testing.T) {
|
||||
}
|
||||
|
||||
require.NoError(err)
|
||||
require.Empty(tc.in.AccessKeyId)
|
||||
require.Empty(tc.in.SecretAccessKey)
|
||||
require.Nil(tc.in.CredentialsConfig)
|
||||
require.Zero(tc.in.CredsLastRotatedTime)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAwsCatalogPersistedStateGetSessionErr(t *testing.T) {
|
||||
func TestAwsCatalogPersistedState_GetSession(t *testing.T) {
|
||||
require := require.New(t)
|
||||
state := &AwsCredentialPersistedState{
|
||||
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
|
||||
cases := []struct {
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "error",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
|
||||
},
|
||||
expectedErr: fmt.Sprintf("error reading options in GetSession: %s", testOptionErr),
|
||||
},
|
||||
{
|
||||
name: "static credentials",
|
||||
in: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "AKIA_foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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{
|
||||
"foo": "bar",
|
||||
}),
|
||||
)
|
||||
require.NoError(err)
|
||||
return config
|
||||
}(),
|
||||
testOpts: []awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(
|
||||
awsutil.NewMockIAM(),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := tc.in.GetSession()
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasStaticCredentials(t *testing.T) {
|
||||
require := require.New(t)
|
||||
cases := []struct {
|
||||
name string
|
||||
accessKey string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "dynamic credential",
|
||||
accessKey: "ASIAEXAMPLE",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "static credential",
|
||||
accessKey: "AKIAEXAMPLE",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
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, 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))
|
||||
})
|
||||
}
|
||||
|
||||
_, err := state.GetSession()
|
||||
require.EqualError(err, fmt.Sprintf("error reading options in NewCredentialsConfig: %s", testOptionErr))
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/aws/aws-sdk-go/service/iam/iamiface"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -48,3 +49,39 @@ func newTestMockIAM(state *testMockIAMState, opts ...awsutil.MockIAMOption) awsu
|
||||
return m, err
|
||||
}
|
||||
}
|
||||
|
||||
// MockStaticCredentialSecrets returns a *structpb.Struct that contains two
|
||||
// key pair values: (access_key_id, AKIA_foobar) & (secret_access_key, bazqux)
|
||||
func MockStaticCredentialSecrets() *structpb.Struct {
|
||||
return &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MockAssumeRoleAttributes returns a *structpb.Struct that contains six key pair values:
|
||||
//
|
||||
// (region, region)
|
||||
// (disable_credential_rotation, disableRotate)
|
||||
// (role_arn, arn:aws:iam::123456789012:role/S3Access)
|
||||
// (role_external_id, 1234567890)
|
||||
// (role_session_name, ec2-assume-role-provider)
|
||||
// (role_tags, struct{foo:bar})
|
||||
func MockAssumeRoleAttributes(region string, disableRotate bool) *structpb.Struct {
|
||||
return &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstRegion: structpb.NewStringValue(region),
|
||||
ConstDisableCredentialRotation: structpb.NewBoolValue(disableRotate),
|
||||
ConstRoleArn: structpb.NewStringValue("arn:aws:iam::123456789012:role/S3Access"),
|
||||
ConstRoleExternalId: structpb.NewStringValue("1234567890"),
|
||||
ConstRoleSessionName: structpb.NewStringValue("ec2-assume-role-provider"),
|
||||
ConstRoleTags: structpb.NewStructValue(&structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"foo": structpb.NewStringValue("bar"),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,33 @@ func GetTimeValue(in *structpb.Struct, k string) (time.Time, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// GetMapStringString returns a map[string]string value and no error if the given key
|
||||
// is found in the provided proto struct input. An error is returned if the key
|
||||
// is not found or the value type is not map[string]string.
|
||||
func GetMapStringString(in *structpb.Struct, k string, required bool) (map[string]string, error) {
|
||||
mv := in.GetFields()
|
||||
v, ok := mv[k]
|
||||
if !ok {
|
||||
if required {
|
||||
return nil, fmt.Errorf("missing required value %q", k)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
result := map[string]string{}
|
||||
m, ok := v.AsInterface().(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type for value %q: want map[string]string, got %T", k, v.AsInterface())
|
||||
}
|
||||
for mapKey, mapValue := range m {
|
||||
e, ok := mapValue.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type for value in map[%q]: want string, got %T", mapKey, mapValue)
|
||||
}
|
||||
result[mapKey] = e
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// StructFields returns a map[string]struct{} of the
|
||||
// proto struct input.
|
||||
func StructFields(s *structpb.Struct) map[string]struct{} {
|
||||
|
||||
@@ -208,3 +208,95 @@ func TestGetTimeValue(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMapStringString(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in *structpb.Struct
|
||||
key string
|
||||
required bool
|
||||
expected map[string]string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "required missing",
|
||||
in: &structpb.Struct{},
|
||||
key: "foo",
|
||||
required: true,
|
||||
expectedErr: "missing required value \"foo\"",
|
||||
},
|
||||
{
|
||||
name: "optional missing",
|
||||
in: &structpb.Struct{},
|
||||
key: "foo",
|
||||
},
|
||||
{
|
||||
name: "non-map[string]string value",
|
||||
in: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"foo": structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
key: "foo",
|
||||
expectedErr: "unexpected type for value \"foo\": want map[string]string, got bool",
|
||||
},
|
||||
{
|
||||
name: "empty map",
|
||||
in: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"foo": structpb.NewStructValue(&structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{},
|
||||
}),
|
||||
},
|
||||
},
|
||||
key: "foo",
|
||||
expected: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "non-string value in map",
|
||||
in: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"foo": structpb.NewStructValue(&structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"bar": structpb.NewBoolValue(true),
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
key: "foo",
|
||||
expectedErr: "unexpected type for value in map[\"bar\"]: want string, got bool",
|
||||
},
|
||||
{
|
||||
name: "good",
|
||||
in: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"foo": structpb.NewStructValue(&structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"bar": structpb.NewStringValue("zoo"),
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
key: "foo",
|
||||
expected: map[string]string{
|
||||
"bar": "zoo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
actual, err := GetMapStringString(tc.in, tc.key, tc.required)
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(err)
|
||||
require.Equal(tc.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user