Skip to content

fix(credentials): rank an EKS ownership record above ambient AWS variables - #6472

Merged
devantler merged 5 commits into
mainfrom
claude/eks-recorded-alias-precedence-6427
Aug 4, 2026
Merged

devantler merged 5 commits into
mainfrom
claude/eks-recorded-alias-precedence-6427

Conversation

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

Why

An EKS cluster created through the web UI records the AWS variable names its create actually ran through, so later delete/start/stop can resolve the same identity. That record was being ignored whenever the operator also had a plain AWS_ACCESS_KEY_ID exported — the ambient value won instead.

The practical effect is a dead end: an operator whose shell points at a different account can no longer operate a cluster through the UI, even though the correct credentials are available under the recorded name. Ownership verification then refuses the mutation, so nothing acts on the wrong account — but the cluster becomes stuck rather than manageable.

What

Credentials the operator set deliberately (secure store / Settings) still win, exactly as before. What changed is that a resolver's ambient fall-through no longer outranks the cluster's own ownership record — the record now sits between the two.

The safe direction is the default: a credential source that does not distinguish "deliberately configured" from "found in the environment" is treated as ambient, so it loses to the record rather than silently overriding it.

Fixes #6427

…ables

RecordedAWSResolver resolved a cluster's lifecycle credentials base-first,
falling back to the ownership record's captured alias only where the base
resolved nothing. In the web UI the base is credentials.Manager, whose Value
falls through to the canonical process environment whenever the secure store
holds nothing — so an exported AWS_ACCESS_KEY_ID outranked the alias the
create actually ran through.

The precedence was written for the secure store, where a stored value is
name-independent operator intent and rightly wins. Manager could not express
the difference between that and its own ambient fall-through, so the rule
applied to both halves.

Introduce ExplicitResolver, the optional half of Resolver that reports only a
deliberately-configured value, and implement it on Manager over the secure
store alone. RecordedAWSResolver now resolves explicit intent first, the
ownership record second, and the ambient environment last. A base that
declares no explicit channel is treated as ambient in full, so a resolver that
forgets to distinguish its halves loses to the record rather than silently
overriding it.

This also settles a name/value disagreement: EnvVar reported the record's
alias while Value could come from the canonical variable, and the frozen
resolution carries EnvVar onward to scrub child process environments.

Fixes #6427
The type comment still described values as base-first and the composition as
strictly additive, which the precedence change makes false. State the actual
order and name the direction the change moves a mismatch in: a refused
mutation becomes a working one, never the reverse.
@github-actions

github-actions Bot commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

✅MegaLinter analysis: Success

✅ Linters with no issues

actionlint, bash-exec, git_diff, hadolint, jscpd, jsonlint, lychee, markdown-table-formatter, markdownlint, prettier, prettier, shellcheck, shfmt, stylelint, syft, trivy-sbom, trufflehog, v8r, v8r, yamllint

Notices

📣 MegaLinter 9.5.0 is out! Discover the new features and security recommendations in the release announcement. (Skip this info by defining SECURITY_SUGGESTIONS: false)

See detailed reports in MegaLinter artifacts

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

ExplicitResolver ranks a base above the ownership record only for values it
holds deliberately, and *Manager — the sole implementer — returns only the
secure store from ExplicitValue. A Settings entry selects which variable to
read, not its contents, so its value arrives from the ambient environment and
ranks behind the record. Two doc claims said the opposite.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

@devantler I will review the changes in #6472.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Readiness record for 83834e53

Programmatically tested. go build ./... clean, go test ./pkg/svc/credentials/... passes, gofmt clean, CI 26 pass / 0 fail at head.

How the red check was resolved — and what I deliberately did NOT do. The blocking failure was 🧹 Lint - mega-linter: lychee reported [503] https://golangci-lint.run/docs/welcome/install/ in CONTRIBUTING.md, a file this PR does not touch. This repo has an established pattern for links that 5xx from GitHub Actions runner IPs while resolving fine elsewhere (starlight.astro.build, git.k8s.io, img.shields.io, …), so the tempting move was another .lycheeignore entry.

I checked first instead: the URL returned 200 on three consecutive probes locally, and re-running the job turned it green with no code change. So it was a one-off upstream blip, not the CI-IP class — and a permanent exclusion would have silently removed link coverage for a link that is fine. Re-running is the cheap diagnostic that separates the two; the exclusion is only correct once the failure is shown to recur.

Tried as a user / reviewed. Self-review of the diff before requesting this review found one real defect, now fixed in 83834e53: ExplicitResolver, and the RecordedAWSResolver precedence doc, both described a Settings entry as deliberate operator intent that still outranks the ownership record. It does not. *Manager is the sole implementer and its ExplicitValue returns only the secure store; a Settings entry selects which variable to read, not its contents, so its value arrives from the ambient environment and now ranks behind the record. The behaviour is right — it is the fail-closed direction — but two doc claims asserted the opposite, which would mislead the next reader about a credential-precedence rule.

Precedence exercised through the tests that pin both directions: stored value still wins, record beats ambient canonical, unrecorded key still falls through to ambient, nil base prefers the record, and name/value agree.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The credential system adds an explicit-value resolution contract. Manager now returns secure-store values without ambient environment fallback. RecordedAWSResolver resolves credentials in this order: explicit base value, recorded ownership value, then ambient base value. It also handles nil base resolvers. Tests cover precedence, fallback, zero-value behavior, resolver consistency, and manager integration.

Possibly related PRs


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Linked Issues check ❌ Error The resolver and tests address precedence and fallback, but the changes do not show the ownership mapping reaching the EKS provisioner as required by #6427. Add or reference the clusterapi and EKS provisioner changes that carry the ownership record's AWSOptions mapping through to credential resolution.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: recorded EKS ownership credentials now rank above ambient AWS variables.
Description check ✅ Passed The description explains the credential precedence change and its effect on EKS cluster management.
Out of Scope Changes check ✅ Passed The changes remain within credential resolution and related tests, with no unrelated code changes identified.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

@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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@pkg/svc/credentials/credentials.go`:
- Around line 217-224: Update the credential resolution flow around Value and
EnvVar to capture the recorded variable name once and use that check for both
operations: after ExplicitValue lookup, delegate to base when no captured name
exists, while keeping the nil-base path safe. Preserve the existing behavior for
recorded mappings, add a regression test covering a canonical variable plus a
Settings-selected base alias with no recorded mapping, and revise the nearby
documentation to state that Settings selects ambient input rather than
deliberate credential intent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 55087499-5bea-4981-9a01-dcfe1695274b

📥 Commits

Reviewing files that changed from the base of the PR and between 560ee42 and 83834e5.

📒 Files selected for processing (4)
  • pkg/svc/credentials/credentials.go
  • pkg/svc/credentials/manager.go
  • pkg/svc/credentials/recorded_aws_resolver_ambient_test.go
  • pkg/svc/credentials/recorded_aws_resolver_test.go
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Use Go 1.26.1 or newer, matching the version declared in go.mod.
All user-supplied file path arguments in CLI commands must be canonicalized with fsutil.EvalCanonicalPath before use; create parent directories first for new output paths.
Use fsutil.ReadFileSafe for constrained file reads instead of reimplementing path-containment checks.
Do not manually register MCP or Copilot tool handlers; runnable Cobra commands are exposed through automatic generation in pkg/toolgen.
Use a typed experimental field in ksail.yaml for configuration-gated behavior that is not an entire command; regenerate the schema and CRD.
Graduate validated experimental features by deleting the single Guard call; do not retain unnecessary experimental scaffolding.
Run formatting and linting with golangci-lint run --fix and golangci-lint run --timeout 5m; validate with go build and go test ./....

Files:

  • pkg/svc/credentials/recorded_aws_resolver_ambient_test.go
  • pkg/svc/credentials/manager.go
  • pkg/svc/credentials/recorded_aws_resolver_test.go
  • pkg/svc/credentials/credentials.go
**/*.{go,yaml,yml,md,mdx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Generated files must not be hand-edited; run make generate as the canonical regeneration command.

Files:

  • pkg/svc/credentials/recorded_aws_resolver_ambient_test.go
  • pkg/svc/credentials/manager.go
  • pkg/svc/credentials/recorded_aws_resolver_test.go
  • pkg/svc/credentials/credentials.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Add regression tests for confident bug fixes and run flaky-test candidates repeatedly with go test -run <T> -count=10 ./....

Files:

  • pkg/svc/credentials/recorded_aws_resolver_ambient_test.go
  • pkg/svc/credentials/recorded_aws_resolver_test.go
🧠 Learnings (1)
📚 Learning: 2026-08-02T19:26:41.922Z
Learnt from: devantler
Repo: devantler-tech/ksail PR: 6434
File: pkg/cli/clusterapi/eks_create_identity_test.go:0-0
Timestamp: 2026-08-02T19:26:41.922Z
Learning: In Go tests using Testify v1.11.1, do not flag require.NoError(t, err) inside an Eventually condition solely because the condition may run in another goroutine: require calls t.Errorf before FailNow, and t.Errorf marks the outer test as failed. However, prefer decomposing Eventually conditions so service errors, missing resources, and state or phase mismatches are reported separately for clearer diagnostics.

Applied to files:

  • pkg/svc/credentials/recorded_aws_resolver_ambient_test.go
  • pkg/svc/credentials/recorded_aws_resolver_test.go
🔇 Additional comments (4)
pkg/svc/credentials/credentials.go (1)

192-206: LGTM!

Also applies to: 225-237, 249-251, 261-267, 272-277

pkg/svc/credentials/manager.go (1)

140-150: LGTM!

pkg/svc/credentials/recorded_aws_resolver_test.go (1)

29-32: LGTM!

pkg/svc/credentials/recorded_aws_resolver_ambient_test.go (1)

1-11: LGTM!

Also applies to: 13-39, 41-58, 60-76, 78-88, 90-101, 103-119, 121-142

Comment thread pkg/svc/credentials/credentials.go
…e value

AWSOptionsResolver.EnvVar resolves an absent mapping to the canonical default,
so consulting the record for an UNRECORDED key fabricated an opinion it does
not hold -- the plain ambient value -- and that outranked the base own
Settings-selected alias, which had won before this PR. EnvVar had the mirror
defect: it reported the canonical name for a value the base resolved through
its alias, so the frozen resolution would scrub the wrong variable.

Both now check the captured name directly and delegate to base when there is
none. Regression test covers a canonical variable plus a base alias with no
recorded mapping; the existing unrecorded-key arm cannot see it, because its
stub configures no alias so both paths read the same variable.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Review Result

No actionable findings. The updated resolver correctly:

  • Preserves secure-store credentials as the highest-priority explicit source.
  • Prefers captured ownership-record aliases over ambient AWS variables.
  • Delegates unrecorded keys to the base resolver, preserving Settings-selected aliases.
  • Keeps Value and EnvVar aligned for frozen credential resolution.
  • Handles nil and zero-value resolver paths safely.

No code changes were necessary, so no commit or follow-up pull request was created.

Validation

  • ✅ git diff --check 560ee42ba6b3f74837b30cf49834a973c930bc8a...HEAD
  • ✅ go test -count=1 ./pkg/svc/credentials/...

View task →

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Readiness record for 231a0948

Supersedes the 83834e53 record above — the head moved for CodeRabbit's regression finding, so all
three conditions were re-established against the current code.

1. Programmatically tested. go build ./... clean, go test ./pkg/svc/credentials/... exit 0
(0 failures), gofmt clean. CI at head: 26 pass / 0 fail / 18 skipped, mergeStateStatus: CLEAN,
0 unresolved threads.

2. Reviewed. Codex, clean at this head — requested 07:03:32Z against 231a0948, acknowledged, and
returned "No actionable findings" at 07:17:25Z. The head has not moved since 06:15:49Z, i.e. it was
already current when the request was made and throughout the review, so the result is attributable to
this commit. CodeRabbit's earlier finding on 83834e53 is fixed and its thread resolved.

3. Tried and evaluated as a user. Drove the real RecordedAWSResolver through a base that models
the web UI (a Settings alias that selects which variable to read but holds no deliberate value of
its own), with AWS_ACCESS_KEY_ID exported ambiently:

case EnvVar() Value()
A — key is in the ownership record RECORDED_ACCESS RECORDED-VALUE
B — key not recorded, base has an alias SETTINGS_ALIAS SETTINGS-VALUE
C — key not recorded, nil base AWS_ACCESS_KEY_ID AMBIENT-CANONICAL

Both halves are printed deliberately: the original defect was EnvVar() and Value() naming
different variables, so asserting on the value alone would not have seen it. They agree in all three
cases.

Counterfactual against the parent commit, which is what makes case B evidence rather than
assertion — same program, credentials.go reverted to 83834e53:

BEFORE  B)  EnvVar=AWS_ACCESS_KEY_ID   Value="AMBIENT-CANONICAL"   <- the regression
AFTER   B)  EnvVar=SETTINGS_ALIAS      Value="SETTINGS-VALUE"      <- fixed

A and C are unchanged by the fix, so the change is localised to the unrecorded-key path it claims to
address. Promoting.

@devantler
devantler marked this pull request as ready for review August 4, 2026 07:22
@devantler
devantler merged commit b7fdabc into main Aug 4, 2026
18 checks passed
@devantler
devantler deleted the claude/eks-recorded-alias-precedence-6427 branch August 4, 2026 07:22
@github-project-automation github-project-automation Bot moved this from 🫴 Ready to ✅ Done in 🌊 Project Board Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

fix(clusterapi): carry the ownership record's AWS credential mapping through to the EKS provisioner

1 participant