Skip to content

awsutil: AssumeRole sends ExternalId="" when no external ID is configured, STS rejects with ValidationError #185

Description

@nomeelnoj

Describe the bug
In awsutil (module github.com/hashicorp/go-secure-stdlib/awsutil/v2), the
non-web-identity AssumeRole path in generateAwsConfigOptions
(awsutil/generate_credentials.go) sets the STS external ID unconditionally:

options.ExternalID = aws.String(c.RoleExternalId)

When no external ID is configured, c.RoleExternalId is "", so
aws.String("") produces a non-nil pointer to an empty string. The AWS SDK
serializes that as ExternalId="" on the sts:AssumeRole request, and STS
rejects it:

ValidationError: 1 validation error detected: Value '' at 'externalId' failed
to satisfy constraint: Member must have length greater than or equal to 2

This breaks role assumption for any caller that does not supply an external ID.
Note the same field is already guarded elsewhere in the file (the validation
block that returns "role external ID specified without role ARN" only fires
if c.RoleExternalId != ""), so the unconditional assignment in the
AssumeRole option callback is an inconsistency.

The web-identity branches (WithWebIdentityRoleCredentialOptions) do not set
ExternalID, so only the plain (non-web-identity) AssumeRole path is affected.

To Reproduce
Steps to reproduce the behavior:

  1. Build a credentials config that assumes a role without an external ID:

    c, _ := awsutil.NewCredentialsConfig(
        awsutil.WithRoleArn("arn:aws:iam::123456789012:role/example"),
    )
    _, err := c.GenerateCredentialChain(ctx)
    // resolve credentials (e.g. via the returned aws.Config)
  2. Trigger credential resolution so the SDK performs the sts:AssumeRole call.

  3. See the STS ValidationError above.

A unit-level reproduction (no network) is that the generated
AssumeRoleOptions.ExternalID is aws.String("") (non-nil) instead of nil
when no external ID is provided.

Expected behavior
When no external ID is configured, AssumeRoleOptions.ExternalID should remain
nil and no ExternalId parameter should be sent to STS, so AssumeRole
succeeds for roles whose trust policy does not require an external ID.

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions