Skip to content

fix(aws): use default credential chain#403

Open
olegleyz wants to merge 3 commits into
NVIDIA:mainfrom
olegleyz:fix/aws-default-credential-chain
Open

fix(aws): use default credential chain#403
olegleyz wants to merge 3 commits into
NVIDIA:mainfrom
olegleyz:fix/aws-default-credential-chain

Conversation

@olegleyz

@olegleyz olegleyz commented Jul 15, 2026

Copy link
Copy Markdown

Description

The AWS provider currently calls the EC2 instance-role credential provider whenever Topograph-specific credentials are absent. That bypasses the AWS SDK default credential chain, so EKS Pod Identity and IRSA credentials injected into the Topograph pod are ignored and the EC2 node role is used instead.

This change:

  • keeps request/config-file credentials as the highest-priority explicit override;
  • leaves the credentials provider unset otherwise, allowing the AWS SDK to resolve environment, shared config, web identity, container/Pod Identity, and EC2 IMDS credentials;
  • preserves the SDK credentials cache so temporary credentials refresh automatically;
  • documents the default chain, EKS Pod Identity setup, and the least-privilege ec2:DescribeInstanceTopology permission.

Tests cover invalid and valid explicit credentials, explicit-over-environment precedence, environment credentials, EKS container credentials with an authorization token file, and refresh after expiration.

Validation

  • make qualify
  • AWS provider race tests repeated 10 times

AWS integration test

Validated commit 4473a9e on the nkx-slinky-gb300-dev-01 EKS cluster:

  1. Deployed an isolated Topograph API using the topograph ServiceAccount and its EKS Pod Identity association with the dedicated aehiqzudem-topograph IAM role.
  2. Set AWS_EC2_METADATA_DISABLED=true to prevent fallback to the EC2 node role.
  3. Triggered a full topology generation request.

The request used the AWS SDK default credential chain, extracted topology for 35 instances, updated the test ConfigMap, and completed with HTTP 200. No authorization errors occurred.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.
  • All commits are signed off per DCO (git commit -s).

Signed-off-by: Oleg Leizerov <oleizerov@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Signed-off-by: Oleg Leizerov <oleizerov@nvidia.com>
@olegleyz
olegleyz marked this pull request as ready for review July 16, 2026 00:38
@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the AWS provider so that it uses the AWS SDK v2 default credential chain when no explicit credentials are supplied, instead of unconditionally calling the EC2 instance-role IMDS provider. This unblocks EKS Pod Identity and IRSA, whose credentials are injected into the pod environment and resolved by the SDK chain but were previously bypassed.

  • Explicit credentials (from the request or credentialsPath) are still converted to a StaticCredentialsProvider and take highest priority; when absent, credsProvider is nil and loadAWSConfig omits WithCredentialsProvider, letting the SDK resolve env vars, shared config, web identity, container/Pod Identity, and IMDS in order.
  • A pre-flight credentials.Retrieve() guard is added in generateRegionInstanceTopology that returns HTTP 401 before any EC2 API call when the chosen credential source fails to produce credentials.
  • Tests cover static creds roundtrip, explicit-over-env precedence, environment credentials, and EKS Pod Identity container credentials with expiry and automatic refresh.

Confidence Score: 5/5

Safe to merge — the change removes a custom credential-fetching loop and delegates entirely to the well-tested AWS SDK default chain, with explicit creds still taking highest priority and a pre-flight 401 guard added for early failure detection.

The logic simplification is straightforward: explicit credentials continue to work as before, and the removal of the manual EC2 IMDS loop is replaced by an already-battle-tested SDK mechanism. The pre-flight Retrieve() check is a net improvement for operator visibility. New tests cover all branching paths including credential expiry and refresh. No existing behavior for users supplying explicit credentials is changed.

No files require special attention.

Important Files Changed

Filename Overview
pkg/providers/aws/provider.go Replaces manual credential resolution (env vars + EC2 IMDS loop) with AWS SDK default chain; getCredentialsProvider returns nil when no explicit creds are supplied, and loadAWSConfig only injects WithCredentialsProvider when a non-nil provider is provided
pkg/providers/aws/instance_topology.go Adds a pre-flight credentials.Retrieve() guard that returns HTTP 401 when credential resolution fails before the EC2 API call is attempted; guard is skipped only when client.credentials == nil (manual test construction)
pkg/providers/aws/provider_test.go Comprehensive new test suite covering invalid credentials, static provider roundtrip, explicit-over-env precedence, environment credentials via default chain, and EKS Pod Identity container credentials with expiry and refresh
docs/providers/aws.md Updates documentation to reflect the two-mode auth model, narrows the required IAM permission to ec2:DescribeInstanceTopology, and adds EKS Pod Identity setup guidance
CHANGELOG.md Adds a changelog entry describing the credential chain fix under the Fixed section
pkg/providers/aws/imds.go Removes the now-unused tokenTimeDelay constant; no logic changes

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Loader called with cfg.Creds] --> B{len creds == 0?}
    B -->|Yes| C[credsProvider = nil\nlog: SDK default chain]
    B -->|No| D[decodeCredentials]
    D -->|Error| E[HTTP 400 Bad Request]
    D -->|OK| F[NewStaticCredentialsProvider\ncredsProvider = static]

    C --> G[clientFactory per region]
    F --> G

    G --> H[loadAWSConfig\nconfig.WithRegion]
    H -->|credsProvider != nil| I[config.WithCredentialsProvider\nstatic creds]
    H -->|credsProvider == nil| J[SDK default chain\nenv → shared config → IRSA\n→ Pod Identity → IMDS]

    I --> K[ec2.NewFromConfig\nClient.credentials = awsCfg.Credentials]
    J --> K

    K --> L{client.credentials != nil?}
    L -->|Yes| M[credentials.Retrieve]
    M -->|Error| N[HTTP 401 Unauthorized]
    M -->|OK| O[DescribeInstanceTopology]
    L -->|No| O
    O -->|Error| P[HTTP 502 Bad Gateway]
    O -->|OK| Q[Return topology]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Loader called with cfg.Creds] --> B{len creds == 0?}
    B -->|Yes| C[credsProvider = nil\nlog: SDK default chain]
    B -->|No| D[decodeCredentials]
    D -->|Error| E[HTTP 400 Bad Request]
    D -->|OK| F[NewStaticCredentialsProvider\ncredsProvider = static]

    C --> G[clientFactory per region]
    F --> G

    G --> H[loadAWSConfig\nconfig.WithRegion]
    H -->|credsProvider != nil| I[config.WithCredentialsProvider\nstatic creds]
    H -->|credsProvider == nil| J[SDK default chain\nenv → shared config → IRSA\n→ Pod Identity → IMDS]

    I --> K[ec2.NewFromConfig\nClient.credentials = awsCfg.Credentials]
    J --> K

    K --> L{client.credentials != nil?}
    L -->|Yes| M[credentials.Retrieve]
    M -->|Error| N[HTTP 401 Unauthorized]
    M -->|OK| O[DescribeInstanceTopology]
    L -->|No| O
    O -->|Error| P[HTTP 502 Bad Gateway]
    O -->|OK| Q[Return topology]
Loading

Reviews (2): Last reviewed commit: "fix(aws): preserve credential error stat..." | Re-trigger Greptile

@dmitsh

dmitsh commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

/ok-to-test 4473a9e

@github-actions

Copy link
Copy Markdown
Contributor

@ArangoGutierrez ArangoGutierrez left a comment

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.

Solid refactor — moving to the SDK default credential chain is the right call, and the new tests genuinely exercise precedence and auto-refresh rather than asserting SDK tautologies. Two behavior changes worth a quick confirm below.

  1. One precedence change worth confirming is intentional: the old resolver only consulted request creds, then AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, then the EC2 instance role — it never read ~/.aws/config, ~/.aws/credentials, or AWS_PROFILE. The full default chain places shared-config/profile (and IRSA) ahead of the instance role, so a host with stray ~/.aws state or an exported AWS_PROFILE that previously fell through to instance-role creds will now silently authenticate as that profile identity. The docs cover the env / Pod Identity case; worth extending the caveat to shared-config/profile too. (pkg/providers/aws/provider.go:126)
  2. With no explicit creds, credential-resolution failure is now deferred to the API call: the old ec2rolecreds path returned 401 at load time when instance-role creds couldn't be retrieved, whereas an unresolvable default chain now surfaces as the 502 from DescribeInstanceTopology. #177 deliberately curated these status codes, so worth deciding whether a no-credentials failure should still present as 401 rather than 502. (pkg/providers/aws/provider.go:104)

SecretAccessKey: secretAccessKey,
Token: sessionToken,
}, nil
return config.LoadDefaultConfig(ctx, opts...)

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.

One precedence change worth confirming is intentional: the old resolver only consulted request creds, then AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, then the EC2 instance role — it never read ~/.aws/config, ~/.aws/credentials, or AWS_PROFILE. The full default chain places shared-config/profile (and IRSA) ahead of the instance role, so a host with stray ~/.aws state or an exported AWS_PROFILE that previously fell through to instance-role creds will now silently authenticate as that profile identity. The docs cover the env / Pod Identity case; worth extending the caveat to shared-config/profile too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, the expanded precedence is intentional because the goal is to use the standard SDK chain. I’ll make the warning explicit for AWS_PROFILE and shared config/credentials files.

func getCredentialsProvider(creds map[string]any) (aws.CredentialsProvider, *httperr.Error) {
if len(creds) == 0 {
klog.Infof("Using AWS SDK default credential chain")
return nil, nil

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 no explicit creds, credential-resolution failure is now deferred to the API call: the old ec2rolecreds path returned 401 at load time when instance-role creds couldn't be retrieved, whereas an unresolvable default chain now surfaces as the 502 from DescribeInstanceTopology. #177 deliberately curated these status codes, so worth deciding whether a no-credentials failure should still present as 401 rather than 502.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. The status-code change was not intentional. I’ll preserve the previous 401 for credential-resolution failures while keeping EC2 API authorization errors as 502.

Signed-off-by: Oleg Leizerov <oleizerov@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants