507 lines
15 KiB
Go
507 lines
15 KiB
Go
// Copyright IBM Corp. 2021, 2026
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package credential
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
)
|
|
|
|
func TestGetCredentialAttributes(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in map[string]any
|
|
expected *CredentialAttributes
|
|
expectedErrContains string
|
|
}{
|
|
{
|
|
name: "missing region",
|
|
in: map[string]any{},
|
|
expectedErrContains: "missing required value \"region\"",
|
|
},
|
|
{
|
|
name: "bad value for disable_credential_rotation",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstDisableCredentialRotation: "sure",
|
|
},
|
|
expectedErrContains: "unexpected type for value \"disable_credential_rotation\": want bool, got string",
|
|
},
|
|
{
|
|
name: "default",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
},
|
|
expected: &CredentialAttributes{
|
|
Region: "us-west-2",
|
|
DisableCredentialRotation: false,
|
|
},
|
|
},
|
|
{
|
|
name: "with disable_credential_rotation",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstDisableCredentialRotation: true,
|
|
},
|
|
expected: &CredentialAttributes{
|
|
Region: "us-west-2",
|
|
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 {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
input, err := structpb.NewStruct(tc.in)
|
|
require.NoError(err)
|
|
|
|
actual, err := GetCredentialAttributes(input)
|
|
if tc.expectedErrContains != "" {
|
|
require.Error(err)
|
|
require.Contains(err.Error(), tc.expectedErrContains)
|
|
require.Equal(status.Code(err), codes.InvalidArgument)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
require.EqualValues(tc.expected.Region, actual.Region)
|
|
require.EqualValues(tc.expected.DisableCredentialRotation, actual.DisableCredentialRotation)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetTargetCredentialAttributes(t *testing.T) {
|
|
const targetRoleArn = "arn:aws:iam::222222222222:role/Target"
|
|
|
|
cases := []struct {
|
|
name string
|
|
in map[string]any
|
|
expected *CredentialAttributes
|
|
expectedErrContains string
|
|
}{
|
|
{
|
|
name: "missing target_role_arn",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
},
|
|
expectedErrContains: "attributes.target_role_arn: missing required value \"target_role_arn\"",
|
|
},
|
|
{
|
|
name: "missing target_role_arn and region",
|
|
in: map[string]any{},
|
|
expectedErrContains: "attributes.target_role_arn: missing required value \"target_role_arn\"",
|
|
},
|
|
{
|
|
name: "empty target_role_arn",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRoleArn: "",
|
|
},
|
|
expectedErrContains: "attributes.target_role_arn: value \"target_role_arn\" cannot be empty",
|
|
},
|
|
{
|
|
name: "wrong type target_role_arn",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRoleArn: true,
|
|
},
|
|
expectedErrContains: "attributes.target_role_arn: unexpected type for value \"target_role_arn\": want string, got bool",
|
|
},
|
|
{
|
|
name: "target_region without target_role_arn",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRegion: "eu-west-1",
|
|
},
|
|
expectedErrContains: "attributes.target_role_arn: missing required value \"target_role_arn\"",
|
|
},
|
|
{
|
|
name: "other target fields without target_role_arn",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRoleExternalId: "ext",
|
|
ConstTargetRoleSessionName: "sess",
|
|
ConstTargetRoleTags: map[string]any{"k": "v"},
|
|
},
|
|
expectedErrContains: "attributes.target_role_arn: missing required value \"target_role_arn\"",
|
|
},
|
|
{
|
|
name: "target_role_arn uses region when target_region omitted",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
},
|
|
expected: &CredentialAttributes{
|
|
Region: "us-west-2",
|
|
RoleArn: targetRoleArn,
|
|
},
|
|
},
|
|
{
|
|
name: "target_role_arn without target_region or region",
|
|
in: map[string]any{
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
},
|
|
expectedErrContains: "missing required value \"region\"",
|
|
},
|
|
{
|
|
name: "target_region overrides region",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRegion: "eu-west-1",
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
},
|
|
expected: &CredentialAttributes{
|
|
Region: "eu-west-1",
|
|
RoleArn: targetRoleArn,
|
|
},
|
|
},
|
|
{
|
|
name: "empty target_region falls back to region",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRegion: "",
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
},
|
|
expected: &CredentialAttributes{
|
|
Region: "us-west-2",
|
|
RoleArn: targetRoleArn,
|
|
},
|
|
},
|
|
{
|
|
name: "all optional target fields",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstDisableCredentialRotation: true,
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
ConstTargetRegion: "eu-west-1",
|
|
ConstTargetRoleExternalId: "target-ext",
|
|
ConstTargetRoleSessionName: "target-sess",
|
|
ConstTargetRoleTags: map[string]any{"env": "target"},
|
|
},
|
|
expected: &CredentialAttributes{
|
|
Region: "eu-west-1",
|
|
DisableCredentialRotation: true,
|
|
RoleArn: targetRoleArn,
|
|
RoleExternalId: "target-ext",
|
|
RoleSessionName: "target-sess",
|
|
RoleTags: map[string]string{"env": "target"},
|
|
},
|
|
},
|
|
{
|
|
name: "optional target fields omitted do not copy principal role fields",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstRoleArn: "arn:aws:iam::111111111111:role/Principal",
|
|
ConstRoleExternalId: "principal-ext",
|
|
ConstRoleSessionName: "principal-sess",
|
|
ConstRoleTags: map[string]any{"env": "principal"},
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
},
|
|
expected: &CredentialAttributes{
|
|
Region: "us-west-2",
|
|
RoleArn: targetRoleArn,
|
|
},
|
|
},
|
|
{
|
|
name: "disable_credential_rotation false is copied",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstDisableCredentialRotation: false,
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
},
|
|
expected: &CredentialAttributes{
|
|
Region: "us-west-2",
|
|
DisableCredentialRotation: false,
|
|
RoleArn: targetRoleArn,
|
|
},
|
|
},
|
|
{
|
|
name: "wrong type target_region",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
ConstTargetRegion: true,
|
|
},
|
|
expectedErrContains: "attributes.target_region: unexpected type for value \"target_region\": want string, got bool",
|
|
},
|
|
{
|
|
name: "wrong type target_role_external_id",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
ConstTargetRoleExternalId: true,
|
|
},
|
|
expectedErrContains: "attributes.target_role_external_id: unexpected type for value \"target_role_external_id\": want string, got bool",
|
|
},
|
|
{
|
|
name: "wrong type target_role_session_name",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
ConstTargetRoleSessionName: true,
|
|
},
|
|
expectedErrContains: "attributes.target_role_session_name: unexpected type for value \"target_role_session_name\": want string, got bool",
|
|
},
|
|
{
|
|
name: "wrong type target_role_tags",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
ConstTargetRoleTags: true,
|
|
},
|
|
expectedErrContains: "attributes.target_role_tags: unexpected type for value \"target_role_tags\": want map[string]string, got bool",
|
|
},
|
|
{
|
|
name: "wrong type target_role_tag value",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
ConstTargetRoleTags: map[string]any{
|
|
"env": true,
|
|
},
|
|
},
|
|
expectedErrContains: `unexpected type for value in map["env"]: want string, got bool`,
|
|
},
|
|
{
|
|
name: "wrong type disable_credential_rotation",
|
|
in: map[string]any{
|
|
ConstRegion: "us-west-2",
|
|
ConstTargetRoleArn: targetRoleArn,
|
|
ConstDisableCredentialRotation: "sure",
|
|
},
|
|
expectedErrContains: "attributes.disable_credential_rotation: unexpected type for value \"disable_credential_rotation\": want bool, got string",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
input, err := structpb.NewStruct(tc.in)
|
|
require.NoError(err)
|
|
|
|
actual, err := GetTargetCredentialAttributes(input)
|
|
if tc.expectedErrContains != "" {
|
|
require.Error(err)
|
|
require.Nil(actual)
|
|
require.Contains(err.Error(), tc.expectedErrContains)
|
|
require.Equal(codes.InvalidArgument, status.Code(err))
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
require.Equal(tc.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetCredentialsConfig(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
secrets *structpb.Struct
|
|
attrs *CredentialAttributes
|
|
dualStack bool
|
|
expected *awsutil.CredentialsConfig
|
|
expectedErrContains string
|
|
}{
|
|
{
|
|
name: "no credentials",
|
|
attrs: &CredentialAttributes{
|
|
Region: "us-west-2",
|
|
},
|
|
expected: &awsutil.CredentialsConfig{
|
|
Region: "us-west-2",
|
|
},
|
|
},
|
|
{
|
|
name: "with static credentials",
|
|
secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
|
|
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
|
},
|
|
},
|
|
attrs: &CredentialAttributes{
|
|
Region: "us-west-2",
|
|
},
|
|
expected: &awsutil.CredentialsConfig{
|
|
AccessKey: "AKIAfoobar",
|
|
SecretKey: "bazqux",
|
|
Region: "us-west-2",
|
|
},
|
|
},
|
|
{
|
|
name: "with dualstack",
|
|
secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
|
|
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
|
},
|
|
},
|
|
dualStack: true,
|
|
attrs: &CredentialAttributes{
|
|
Region: "us-west-2",
|
|
},
|
|
expected: &awsutil.CredentialsConfig{
|
|
AccessKey: "AKIAfoobar",
|
|
SecretKey: "bazqux",
|
|
Region: "us-west-2",
|
|
},
|
|
},
|
|
{
|
|
name: "unknown fields",
|
|
secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
|
|
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
|
"foo": structpb.NewBoolValue(true),
|
|
"bar": structpb.NewBoolValue(true),
|
|
},
|
|
},
|
|
attrs: &CredentialAttributes{
|
|
Region: "us-west-2",
|
|
},
|
|
expectedErrContains: "secrets.bar: unrecognized field, secrets.foo: unrecognized field",
|
|
},
|
|
{
|
|
name: "valid ignore creds_last_rotated_time",
|
|
secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
|
|
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
|
ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
|
},
|
|
},
|
|
attrs: &CredentialAttributes{
|
|
Region: "us-west-2",
|
|
},
|
|
expected: &awsutil.CredentialsConfig{
|
|
AccessKey: "AKIAfoobar",
|
|
SecretKey: "bazqux",
|
|
Region: "us-west-2",
|
|
},
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
DisableCredentialRotation: true,
|
|
},
|
|
expected: &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 static credential & assume role",
|
|
secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
|
|
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",
|
|
},
|
|
},
|
|
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",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
actual, err := GetCredentialsConfig(tc.secrets, tc.attrs, tc.dualStack)
|
|
if tc.expectedErrContains != "" {
|
|
require.Error(err)
|
|
require.Contains(err.Error(), tc.expectedErrContains)
|
|
require.Equal(status.Code(err), codes.InvalidArgument)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
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)
|
|
if tc.dualStack {
|
|
require.NotNil(actual.IAMEndpointResolver)
|
|
require.NotNil(actual.STSEndpointResolver)
|
|
} else {
|
|
require.Nil(actual.IAMEndpointResolver)
|
|
require.Nil(actual.STSEndpointResolver)
|
|
}
|
|
})
|
|
}
|
|
}
|