hashicorp / hashicorp/go-secure-stdlib

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

Open
#185 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.