hashicorp / hashicorp/go-secure-stdlib
`awsutil`: `AssumeRole` sends `ExternalId=""` when no external ID is configured, STS rejects with `ValidationError`
- Dominant language
- Go
- Stars
- 74
- Forks
- 28
- PR merge metrics
- No merged PRs in 30d
Description
**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:
```go
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:
```go
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**
- Affected version: `awsutil/v2 v2.1.2` (latest at time of writing); the
unconditional assignment is present on `main`.
- Surfaced downstream via `terraform-provider-vault`
(hashicorp/terraform-provider-vault#2922). The provider hits this whenever it
routes a non-web-identity role assumption through `WithRoleArn`. The provider
fix in hashicorp/terraform-provider-vault#2923 only stops *exercising* this
code path; the underlying library defect remains and affects other consumers.
Contributor guide
Assessment
This issue has not been assessed yet.