feat(cli) Implement parsing of target-prefixed attrs for cross account dynamic host catalogs (#84)

This commit is contained in:
Ryan Flynn authored and GitHub committed 2026-09-18 12:17:24 -04:00
1 parent 163ec69771
commit 09dd7f8a4d
5 files changed
+505 -2

No files matched your search

+60
View File
@@ -186,3 +186,63 @@ func GetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error)
RoleTags: roleTags,
}, nil
}
// GetTargetCredentialAttributes parses values out of a protobuf struct input and returns a CredentialAttributes used
// for configuring a second AWS session. target_role_arn is required when parsing target credentials.
// A status error is returned with an InvalidArgument code when target account attributes are missing
// required values or have invalid value types.
func GetTargetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error) {
badFields := make(map[string]string)
// Parse the role ARN of the target account to AssumeRole into.
targetRoleArn, err := values.GetStringValue(in, ConstTargetRoleArn, true)
if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstTargetRoleArn)] = err.Error()
}
// try parsing target_region, fallback to region, adds target_region to bad fields only when malformed since not required
targetRegion, err := values.GetStringValue(in, ConstTargetRegion, false)
if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstTargetRegion)] = err.Error()
}
if targetRegion == "" {
targetRegion, err = values.GetStringValue(in, ConstRegion, true) // region is required for processing
if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstRegion)] = err.Error()
}
}
// the remaining fields are optional and only raise errors when malformed
disableCredentialRotation, err := values.GetBoolValue(in, ConstDisableCredentialRotation, false)
if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstDisableCredentialRotation)] = err.Error()
}
targetRoleExternalId, err := values.GetStringValue(in, ConstTargetRoleExternalId, false)
if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstTargetRoleExternalId)] = err.Error()
}
targetRoleSessionName, err := values.GetStringValue(in, ConstTargetRoleSessionName, false)
if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstTargetRoleSessionName)] = err.Error()
}
targetRoleTags, err := values.GetMapStringString(in, ConstTargetRoleTags, false)
if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstTargetRoleTags)] = err.Error()
}
if len(badFields) > 0 {
return nil, errors.InvalidArgumentError("Error in the attributes provided", badFields)
}
return &CredentialAttributes{
Region: targetRegion,
DisableCredentialRotation: disableCredentialRotation,
RoleArn: targetRoleArn,
RoleExternalId: targetRoleExternalId,
RoleSessionName: targetRoleSessionName,
RoleTags: targetRoleTags,
}, nil
}
+226
View File
@@ -101,6 +101,232 @@ func TestGetCredentialAttributes(t *testing.T) {
}
}
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
+17 -2
View File
@@ -4,10 +4,10 @@
package credential
const (
// ConstAccessKey is the key for the region in the aws credentials.
// ConstRegion is the key for the region in the aws credentials.
ConstRegion = "region"
// ConstAccessKey is the key for the access key id in the aws credentials.
// ConstAccessKeyId is the key for the access key id in the aws credentials.
ConstAccessKeyId = "access_key_id"
// ConstSecretAccessKey is the key for the secret access key in the aws credentials.
@@ -30,4 +30,19 @@ const (
// ConstRoleTags is the key for the tags used for assuming a IAM role.
ConstRoleTags = "role_tags"
// ConstTargetRegion is the key for the region of the target account in the aws credentials.
ConstTargetRegion = "target_region"
// ConstTargetRoleArn is the key for assuming a IAM role for the target account.
ConstTargetRoleArn = "target_role_arn"
// ConstTargetRoleExternalId is the key for the external id used for assuming a IAM role for the target account.
ConstTargetRoleExternalId = "target_role_external_id"
// ConstTargetRoleSessionName is the key for the session name used for assuming a IAM role of the target account.
ConstTargetRoleSessionName = "target_role_session_name"
// ConstTargetRoleTags is the key for the tags used for assuming a IAM role of the target account.
ConstTargetRoleTags = "target_role_tags"
)