Skip to content

ROSAENG-60638 | fix: reduce STS pressure and improve retry for transient InvalidClientTokenId#3342

Draft
jerichokeyne wants to merge 1 commit into
openshift:masterfrom
jerichokeyne:ROSAENG-60638
Draft

ROSAENG-60638 | fix: reduce STS pressure and improve retry for transient InvalidClientTokenId#3342
jerichokeyne wants to merge 1 commit into
openshift:masterfrom
jerichokeyne:ROSAENG-60638

Conversation

@jerichokeyne

@jerichokeyne jerichokeyne commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

PR Summary

Fixes intermittent InvalidClientTokenId failures during AWS client initialization by eliminating redundant STS calls and extending the retry window from ~10 seconds to ~60 seconds.

Detailed Description of the Issue

ROSA CLI commands and CI e2e tests intermittently fail with InvalidClientTokenId when calling STS GetCallerIdentity. The credentials are valid — the error is transient and resolves after ~30 seconds. Two root causes were identified:

  1. Redundant STS calls: A single WithAWS() runtime initialization made 3 separate GetCallerIdentity calls (ValidateCredentialsGetCallerIdentity, then getClientDetailsGetCallerIdentity again, then GetCreatorGetCallerIdentity a third time), tripling STS pressure and increasing the likelihood of hitting transient failures.

  2. Insufficient retry window: The SDK retryer was configured with a max backoff of 1 second across 12 attempts (~10s total), but the transient issue needs ~30 seconds to resolve. The constants minRetryDelay, minThrottleDelay, and maxThrottleDelay were defined but never wired into the retryer configuration.

Related Issues and PRs

Type of Change

  • fix - resolves an incorrect behavior or bug.

Previous Behavior

  • getClientDetails() called ValidateCredentials() (which calls GetCallerIdentity) and then called GetCallerIdentity again directly — result from validation was discarded.
  • GetCreator() called GetCallerIdentity a third time independently.
  • The SDK retryer used a max backoff of 1 second with 12 attempts, giving a total retry window of ~10 seconds.
  • The constants minRetryDelay (1s), minThrottleDelay (5s), and maxThrottleDelay (5s) were defined but unused.
  • ValidateAccessKeys had fmt.Printf calls where fmt.Sprintf was intended (debug logging wrote to stdout instead of the logger).

Behavior After This Change

  • GetCallerIdentity result is cached in awsClient.callerIdentity. Build(), GetCallerIdentity(), and GetCreator() all share a single STS call.
  • getClientDetails calls GetCallerIdentity once and handles InvalidClientTokenId inline.
  • The SDK retryer now uses a custom backoff that enforces minRetryDelay (1s) as a floor for normal errors, minThrottleDelay (5s) as a floor for InvalidClientTokenId errors, and maxThrottleDelay (5s) as the ceiling. With 12 attempts at 5s floor for token errors, the retry window covers ~60 seconds.
  • ValidateAccessKeys debug logging now correctly uses fmt.Sprintf.

How to Test (Step-by-Step)

Preconditions

  • Valid AWS credentials configured in the environment.

Test Steps

  1. Build the CLI: make rosa
  2. Run unit tests: make test — verify all pass.
  3. Run any ROSA command that initializes an AWS client (e.g. rosa list clusters) and confirm it works.

Expected Results

  • All unit tests pass.
  • ROSA commands that initialize AWS clients succeed, including under transient STS pressure.

Proof of the Fix

Breaking Changes

  • No breaking changes

Developer Verification Checklist

  • Commit subject/title follows [JIRA-TICKET] | [TYPE]: <MESSAGE>.
  • PR description clearly explains both what changed and why.
  • Relevant Jira/GitHub issues and related PRs are linked.
  • make install-hooks has been run in this clone.
  • Tests were added/updated where appropriate.
  • I manually tested the change.
  • make test passes.
  • make lint passes.
  • make rosa passes.
  • Documentation or repo-local agent guidance was added/updated where appropriate.
  • Any risk, limitation, or follow-up work is documented.

Made with Cursor

Summary by CodeRabbit

  • Bug Fixes
    • Improved AWS credential validation with smarter retry handling for temporary and invalid-token errors.
    • Reduced repeated identity lookups by caching AWS caller identity, which can speed up access checks.
    • Updated error messages for invalid AWS client tokens to provide clearer guidance when configuration issues are detected.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jerichokeyne, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: c156c486-6b64-4311-bad8-da27a1efebdd

📥 Commits

Reviewing files that changed from the base of the PR and between 7ad7d67 and 32f3f61.

📒 Files selected for processing (3)
  • pkg/aws/client.go
  • pkg/aws/client_test.go
  • pkg/aws/helpers.go
📝 Walkthrough

Walkthrough

This PR modifies pkg/aws/client.go and pkg/aws/helpers.go to add a custom retry backoff function used when building AWS sessions, introduce caching of STS GetCallerIdentity results on the awsClient struct, and route GetCreator and getClientDetails through this cached call. Session retry configuration for both static-credential and profile-based builders is updated to use the new backoff function with a configured max backoff. Error handling in getClientDetails now returns a dedicated credential error with a help link when InvalidClientTokenId occurs, and debug logging in ValidateAccessKeys switches to formatted string logging.

Changes

Cohort / File(s) Summary
Retry backoff and caching (pkg/aws/client.go, pkg/aws/helpers.go) Adds newBackoffWithMinDelay, wires it into session retry configs, adds callerIdentity cache field, refactors GetCallerIdentity/GetCreator to use cache, updates getClientDetails error handling, and updates ValidateAccessKeys logging format

Sequence Diagram(s)

See caller identity retrieval flow in the hidden review stack artifact.

Estimated code review effort: High

Related issues: None found in provided data.

Related PRs: None found in provided data.

Suggested labels: aws, retry-logic, refactor

Suggested reviewers: None specified.

Poem:
A rabbit hops through AWS trees,
Caching tokens with practiced ease,
Backoffs bend but never break,
Errors now speak plain, for goodness' sake,
Hop, hop, retry — a smoother release. 🐇

🚥 Pre-merge checks | ✅ 15
✅ Passed checks (15 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed PR changes only pkg/aws/client.go and pkg/aws/helpers.go; no *_test.go files or Ginkgo titles were added or modified.
Test Structure And Quality ✅ Passed No Ginkgo test files were changed in this PR; the relevant AWS specs already use BeforeEach/AfterEach and isolated It blocks.
Microshift Test Compatibility ✅ Passed No new Ginkgo/e2e tests were added; the PR only changes pkg/aws library code and contains no It/Describe/Context/When blocks or MicroShift-sensitive APIs.
Single Node Openshift (Sno) Test Compatibility ✅ Passed Only pkg/aws client/helper code changed; no *_test.go or Ginkgo specs were added, so the SNO test-compatibility check is not applicable.
Topology-Aware Scheduling Compatibility ✅ Passed Only pkg/aws client/helper logic changed; no manifests, controllers, affinities, nodeSelectors, or topology-aware scheduling code were introduced.
Ote Binary Stdout Contract ✅ Passed PASS: The PR only changes pkg/aws library code; no main/init/TestMain/suite setup or stdout print calls were added, and the debug change uses logger.Debug with Sprintf.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PASS: The PR only changes pkg/aws client/helper code; no Ginkgo e2e tests or external-network/IP-assumption test code was added.
No-Weak-Crypto ✅ Passed Touched files only add AWS retry/backoff and STS caching; no MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or secret comparisons found.
Container-Privileges ✅ Passed PR only changes pkg/aws Go code; no container/K8s manifests or privileged flags (privileged, hostPID/Network/IPC, SYS_ADMIN, allowPrivilegeEscalation) were added.
No-Sensitive-Data-In-Logs ✅ Passed Touched AWS logs only emit generic status/errors; I found no code logging secrets, tokens, PII, or customer data.
Title check ✅ Passed The title matches the main change and follows the required ticket and type format.
Description check ✅ Passed The PR description covers the issue, changes, related links, testing, proof, and checklist items.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@openshift-ci

openshift-ci Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: jerichokeyne

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 7, 2026
@jerichokeyne

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your plan includes PR reviews subject to rate limits. More reviews will be available in 54 minutes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/aws/helpers.go (1)

162-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse awserr.IsInvalidTokenException here and wrap the error with %w.

This duplicates the existing InvalidClientTokenId handling in pkg/aws/client.go; using the shared helper keeps the check consistent, and %w preserves the error chain for callers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/aws/helpers.go` around lines 162 - 169, The AWS caller identity error
handling in awsClient.GetCallerIdentity() should reuse the shared invalid-token
check instead of string matching on InvalidClientTokenId. Update the error
branch in helpers.go to use awserr.IsInvalidTokenException like the existing
logic in client.go, and change the returned formatted error to wrap the original
error with %w so callers can inspect the chain. Keep the same credential-help
context and locate the change around GetCallerIdentity and the invalid
credentials return path.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/aws/helpers.go`:
- Around line 162-169: The AWS caller identity error handling in
awsClient.GetCallerIdentity() should reuse the shared invalid-token check
instead of string matching on InvalidClientTokenId. Update the error branch in
helpers.go to use awserr.IsInvalidTokenException like the existing logic in
client.go, and change the returned formatted error to wrap the original error
with %w so callers can inspect the chain. Keep the same credential-help context
and locate the change around GetCallerIdentity and the invalid credentials
return path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 32b37547-0418-4b08-ae6a-562b79881cbb

📥 Commits

Reviewing files that changed from the base of the PR and between b7e7f8e and 7ad7d67.

📒 Files selected for processing (2)
  • pkg/aws/client.go
  • pkg/aws/helpers.go

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.70588% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 28.40%. Comparing base (b7e7f8e) to head (32f3f61).
⚠️ Report is 10 commits behind head on master.

Files with missing lines Patch % Lines
pkg/aws/client.go 57.14% 11 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3342      +/-   ##
==========================================
+ Coverage   28.38%   28.40%   +0.02%     
==========================================
  Files         334      333       -1     
  Lines       36730    36710      -20     
==========================================
+ Hits        10424    10427       +3     
+ Misses      25549    25530      -19     
+ Partials      757      753       -4     

☔ View full report in Codecov by Harness.
📢 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.

…ent InvalidClientTokenId

Eliminate redundant GetCallerIdentity calls during AWS client initialization
and wire in the previously unused retry delay constants to extend the retry
window for transient InvalidClientTokenId errors.

- Cache GetCallerIdentity result in awsClient so Build(), GetCreator(), and
  GetCallerIdentity() share a single STS call instead of making three.
- Consolidate getClientDetails to call GetCallerIdentity once instead of
  calling ValidateCredentials (which calls it) then calling it again.
- Replace the 1-second max backoff with a custom backoff that enforces
  minRetryDelay (1s) as a floor for normal errors and minThrottleDelay (5s)
  as a floor for InvalidClientTokenId, with maxThrottleDelay (5s) as the
  ceiling. This extends the retry window from ~10s to ~60s.
- Fix fmt.Printf misuse in ValidateAccessKeys debug logging (should be
  fmt.Sprintf).

Co-authored-by: Cursor <cursoragent@cursor.com>
olucasfreitas

This comment was marked as low quality.

@amandahla

Copy link
Copy Markdown
Contributor

Out of curiosity: what if the InvalidClientTokenId is a valid error? The user will wait 55-60 seconds to find out?

Comment thread pkg/aws/client.go
retryer = retry.AddWithMaxBackoffDelay(retryer, time.Second)
retryer := retry.AddWithMaxAttempts(retry.NewStandard(func(o *retry.StandardOptions) {
o.MaxBackoff = maxThrottleDelay
o.Backoff = newBackoffWithMinDelay(minRetryDelay, minThrottleDelay, maxThrottleDelay)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

With minThrottleDelay == maxThrottleDelay == 5s and 12 attempts, a genuinely invalid token now takes ~60s to fail instead of ~12s. For an interactive CLI, that's a noticeable hang. Could we show a warning on first retry, or cap the token-specific retry window shorter than the full 12 attempts?

Comment thread pkg/aws/helpers.go
user, err := awsClient.stsClient.GetCallerIdentity(context.Background(), &sts.GetCallerIdentityInput{})
user, err := awsClient.GetCallerIdentity()
if err != nil {
if awserr.IsInvalidTokenException(err) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ValidateCredentials still uses strings.Contains for InvalidClientTokenId detection while the rest of this PR uses the typed awserr.IsInvalidTokenException helper. Worth aligning for consistency and robustness, or at minimum noting as a follow-up.

Comment thread pkg/aws/client.go
retryer := retry.AddWithMaxAttempts(retry.NewStandard(), numMaxRetries)
retryer = retry.AddWithMaxBackoffDelay(retryer, time.Second)
retryer := retry.AddWithMaxAttempts(retry.NewStandard(func(o *retry.StandardOptions) {
o.MaxBackoff = maxThrottleDelay

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: o.MaxBackoff is unused by the SDK when o.Backoff is explicitly provided. Not harmful, but the max is already enforced by the inner ExponentialJitterBackoff(maxDelay).

@olucasfreitas

Copy link
Copy Markdown
Contributor

@amandahla This is a real trade-off. With 12 attempts at a 5s floor, permanently invalid credentials now take ~60s to fail vs ~12s before. A few options to mitigate: (1) emit a reporter warning on the first InvalidClientTokenId retry so the user knows we're retrying, (2) reduce the retry count for token-specific errors, or (3) scope the extended retry to only the initialization path. The transient case genuinely needs ~30s, so some increase is necessary, but there's room to reduce the penalty for permanent failures.

@jerichokeyne

Copy link
Copy Markdown
Contributor Author

Yeah, that's fair. I'm gonna do a little bit more investigation into this issue I think. I tried re-running the tests that I ran against the master branch and wasn't able to trigger the issue with the tests I ran for this, so I'm not fully confident that this is going to be enough to improve the situation anyways. I think part of the real issue is that in the e2e tests we're basically creating a lot of AWS clients that are short lived (both as part of the test code and from running a bunch of rosa commands that will each have to make their own clients), and it sounds like we might just be hitting some sort of rate limits on the AWS side. I'm gonna take a look at the test code and both see if I can find a way to actually replicate this issue to test if what I change improves it, and see if I can try to optimize some of our AWS calls a bit more (probably only in the e2e test suite for now)

@jerichokeyne jerichokeyne marked this pull request as draft July 9, 2026 14:49
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. dco-signoff: yes do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants