Skip to content

Add AWS STS token exchange for OIDC-to-AWS credential conversion#3659

Open
jhrozek wants to merge 1 commit intomainfrom
aws_sts-pr-3-tokenexchange
Open

Add AWS STS token exchange for OIDC-to-AWS credential conversion#3659
jhrozek wants to merge 1 commit intomainfrom
aws_sts-pr-3-tokenexchange

Conversation

@jhrozek
Copy link
Contributor

@jhrozek jhrozek commented Feb 6, 2026

This implements the Exchanger component that converts OIDC identity tokens into temporary AWS credentials using STS AssumeRoleWithWebIdentity. The exchanger uses regional STS endpoints for lower latency and maps STS API errors to package-level sentinel errors for consistent error handling.

Related: #3567

@github-actions github-actions bot added the size/S Small PR: 100-299 lines changed label Feb 6, 2026
dmjb
dmjb previously approved these changes Feb 6, 2026
@codecov
Copy link

codecov bot commented Feb 6, 2026

Codecov Report

❌ Patch coverage is 53.33333% with 21 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@65c7aaa). Learn more about missing BASE report.

Files with missing lines Patch % Lines
pkg/auth/awssts/exchange.go 53.33% 18 Missing and 3 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3659   +/-   ##
=======================================
  Coverage        ?   65.96%           
=======================================
  Files           ?      417           
  Lines           ?    41196           
  Branches        ?        0           
=======================================
  Hits            ?    27173           
  Misses          ?    11926           
  Partials        ?     2097           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jhrozek jhrozek force-pushed the aws_sts-pr-3-tokenexchange branch from 88f2721 to 483bd2f Compare February 6, 2026 13:18
@github-actions github-actions bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels Feb 6, 2026
@jhrozek jhrozek force-pushed the aws_sts-pr-3-tokenexchange branch from 483bd2f to 4f58f04 Compare February 6, 2026 13:34
@github-actions github-actions bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels Feb 6, 2026
dmjb
dmjb previously approved these changes Feb 6, 2026
@jhrozek jhrozek force-pushed the aws_sts-pr-3-tokenexchange branch from 4f58f04 to ff62fab Compare February 6, 2026 17:42
@github-actions github-actions bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels Feb 6, 2026
This implements the Exchanger component that converts OIDC identity tokens
into temporary AWS credentials using STS AssumeRoleWithWebIdentity. The
exchanger uses regional STS endpoints for lower latency and maps STS API
errors to package-level sentinel errors for consistent error handling.

Related: #3567
@jhrozek jhrozek force-pushed the aws_sts-pr-3-tokenexchange branch from ff62fab to 799c85c Compare February 6, 2026 18:20
@github-actions github-actions bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels Feb 6, 2026
Copy link
Collaborator

@ChrisJBurns ChrisJBurns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, couple of nits, had to ask Claude to check a couple things like the SSRF etc - but I think we should be ok.

// Using regional endpoints (https://sts.{region}.amazonaws.com) provides lower latency
// compared to the global endpoint.
func newRegionalSTSClient(ctx context.Context, region string) (STSClient, error) {
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to call config.LoadDefaultConfig here for AssumeRoleWithWebIdentity?

AssumeRoleWithWebIdentity is designed to work out pre-existing AWS creds, the web-id token is the auth. Calling LoadDefaultConfig does a call to the internal metadata server (IMDS/IMDSv2) reads .aws/credentials, then reads env vars that aren't relevant. You should be able to use an anonymous creds here.

Something like

cfg, err := config.LoadDefaultConfig(ctx,
      config.WithRegion(region),
      config.WithCredentialsProvider(aws.AnonymousCredentials{}),
  )


input := &sts.AssumeRoleWithWebIdentityInput{
RoleArn: aws.String(roleArn),
RoleSessionName: aws.String(sessionName),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could we validate the session names? I believe they have to be a certain length and characters

return nil, fmt.Errorf("failed to load AWS config: %w", err)
}

regionalEndpoint := fmt.Sprintf("https://sts.%s.amazonaws.com", region)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probs a nit: is there a SSRF risk here? an unvalidated region value like evil.com/foo?x= as region could produce https://sts.evil.com/foo?x=.amazonaws.com which the HTTP client could follow. There will likely be TLS cert validation so this is probably a nothing burger. I'm not too deep on the details of the caller, so i doubt a user is specifying this directly which makes it even more of a nothing burger. Alternatively, we could use the SDK's built-in endpoint resolution by removing the BaseEndpoint override entirely. the SDK will use regional endpoints when config.WithRegion is set.

// validateInputs validates the exchange inputs.
func validateInputs(token, roleArn string, durationSeconds int32) error {
if token == "" {
return fmt.Errorf("token is required")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: a couple of bare errors here, and they can't be matched with errors.Is(). the rest of the package uses sentinel errors, wondering if its worth doing so in this function too?

)

// mockSTSClient implements STSClient for testing.
type mockSTSClient struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: i assume this was intended, but any reason we didn't use mockgen here? i assume simplicity and overkill


output, err := e.client.AssumeRoleWithWebIdentity(ctx, input)
if err != nil {
return nil, fmt.Errorf("STS AssumeRoleWithWebIdentity failed: %w", err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah so you'd probably not want to wrap errors here. STS errors can contain token fragments and role ARN details. I'd consider mapping to generic errors and logging the error itself at DEBUG.

return nil, fmt.Errorf("STS AssumeRoleWithWebIdentity failed: %w", err)
}

if output.Credentials == nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: although typically the SDK doesn't reutnr nil output on success, should we check output for nil here just in case?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/S Small PR: 100-299 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants