Skip to content

feat(stores): add identity-based authentication for stores - #2099

Merged
Andriy Knysh (aknysh) merged 5 commits into
mainfrom
aknysh/atmos-auth-for-stores
Feb 22, 2026
Merged

Andriy Knysh (aknysh) merged 5 commits into
mainfrom
aknysh/atmos-auth-for-stores

Conversation

@aknysh

@aknysh Andriy Knysh (aknysh) commented Feb 22, 2026 •

Copy link
Copy Markdown
Member

what

  • Stores (!store YAML function) can now reference an Atmos auth identity via a new identity field in the store configuration
  • When identity is set, the store authenticates using that identity's credentials instead of the default credential chain (environment variables, default AWS profiles, etc.)
  • Supported for all cloud-backed store types: AWS SSM Parameter Store, Azure Key Vault, and Google Secret Manager
  • Redis and Artifactory stores emit a warning if identity is set (unsupported since they don't map to cloud provider identity types)
  • Stores with identity use lazy client initialization (sync.Once) — the cloud client is created on first Get/Set access rather than at construction time
  • Fully backward compatible: stores without the identity field work exactly as before

why

  • Previously, stores always used the default credential chain, requiring separate credential management for secrets access vs. Terraform execution
  • This change lets users reuse the same atmos auth identity system for both, simplifying credential management and enabling more granular access control
  • Lazy initialization avoids circular dependency issues: stores are registered during config loading, but auth happens later during command execution

references

Configuration example

stores:
  prod/aws-ssm:
    type: aws-ssm-parameter-store
    identity: prod-admin
    options:
      region: us-east-1

Example

See examples/auth-stores/ for a complete multi-cloud configuration showing AWS SSM, Azure Key Vault, and GCP Secret Manager stores with identity-based authentication.

Files changed

Area Files Description
Schema pkg/store/config.go Added Identity field to StoreConfig
Interfaces pkg/store/identity.go AuthContextResolver, IdentityAwareStore, local auth config types
Errors pkg/store/errors.go ErrIdentityNotConfigured, ErrAuthContextNotAvailable
AWS SSM pkg/store/aws_ssm_param_store.go Lazy init, SetAuthContext, identity-based AWS config loading
Azure KV pkg/store/azure_keyvault_store.go Lazy init, SetAuthContext, identity-based credential creation
GCP GSM pkg/store/google_secret_manager_store.go Lazy init, SetAuthContext, identity-based client creation
Registry pkg/store/registry.go Pass identity to constructors, SetAuthContextResolver()
Bridge pkg/store/authbridge/resolver.go Bridges store ↔ auth packages (avoids circular deps)
Wiring internal/exec/terraform.go, terraform_shell.go Injects resolver after auth manager creation
Tests pkg/store/identity_test.go, authbridge/resolver_test.go, registry_test.go 40+ unit tests
Example examples/auth-stores/ Multi-cloud auth + stores configuration example
Docs PRD, blog post, roadmap Feature documentation

Summary by CodeRabbit

Release Notes

  • New Features

    • Stores (AWS SSM, Azure Key Vault, Google Secret Manager) can now authenticate using Atmos auth identities instead of default credential chains.
    • Added identity field in store configuration to specify which auth identity each store should use.
  • Documentation

    • Added comprehensive guide and examples for identity-based store authentication.
    • Added blog post explaining the new store identity support feature.

Stores can now reference an Atmos auth identity via a new `identity` field
in the store configuration. When set, the store authenticates using that
identity's credentials instead of the default credential chain. This works
with AWS SSM Parameter Store, Azure Key Vault, and Google Secret Manager.

Stores use lazy client initialization (sync.Once) so the cloud client is
created on first access rather than at construction time, allowing auth to
complete before store usage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aknysh
Andriy Knysh (aknysh) requested a review from a team as a code owner February 22, 2026 16:21
@aknysh Andriy Knysh (aknysh) added the minor New features that do not break anything label Feb 22, 2026
@github-actions github-actions Bot added the size/xl Extra large size PR label Feb 22, 2026
@aknysh Andriy Knysh (aknysh) self-assigned this Feb 22, 2026
@mergify

mergify Bot commented Feb 22, 2026

Copy link
Copy Markdown
Contributor

Warning

This PR exceeds the recommended limit of 1,000 lines.

Large PRs are difficult to review and may be rejected due to their size.

Please verify that this PR does not address multiple issues.
Consider refactoring it into smaller, more focused PRs to facilitate a smoother review process.

@github-actions

github-actions Bot commented Feb 22, 2026 •

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

@coderabbitai

coderabbitai Bot commented Feb 22, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds optional store-level identity and identity-aware store interfaces, a resolver bridging AuthManager to stores for lazy AWS/Azure/GCP credential resolution, wiring to inject the resolver after authentication, and docs/tests/examples for identity-based store authentication.

Changes

Cohort / File(s) Summary
Store identity abstractions
pkg/store/identity.go, pkg/store/identity_test.go, pkg/store/mock_identity.go
Adds provider auth config types, AuthContextResolver and IdentityAwareStore interfaces, mocks, and extensive tests for resolver and identity behaviors.
Auth bridge resolver
pkg/store/authbridge/resolver.go, pkg/store/authbridge/resolver_test.go
New authbridge Resolver implementing AuthContextResolver that delegates to AuthManager to authenticate identities and produce AWS/Azure/GCP auth configs with error handling and logging.
Store implementations (identity support)
pkg/store/aws_ssm_param_store.go, pkg/store/azure_keyvault_store.go, pkg/store/google_secret_manager_store.go, pkg/store/*_test.go
SSM, Azure KV, and GSM stores accept identityName, support lazy init via sync.Once, add SetAuthContext(), ensureClient paths, and update constructors/tests to new signatures.
Store registry & wiring
pkg/store/registry.go, pkg/store/registry_test.go, pkg/store/config.go, pkg/store/errors.go
Propagates identity from StoreConfig, adds SetAuthContextResolver to apply resolver to IdentityAwareStore implementations, adds identity YAML field and related errors, and tests registry handling.
Terraform exec wiring
internal/exec/terraform.go, internal/exec/terraform_shell.go
When AuthManager exists, create authbridge.Resolver and inject it into the store registry after authentication so stores can lazily resolve credentials.
Docs / examples / website
docs/prd/store-identity-support.md, website/blog/2026-02-22-store-identity-support.mdx, website/src/data/roadmap.js, examples/auth-stores/*
Adds PRD, blog post, roadmap entry, and example atmos.yaml/README demonstrating identity selection for stores.

Sequence Diagram

sequenceDiagram
    participant Client as Terraform/Client
    participant Store as Identity-Aware Store
    participant Resolver as AuthContextResolver
    participant AuthMgr as AuthManager
    participant Cloud as Cloud Provider

    Client->>Store: Get/Set(key)
    activate Store

    Store->>Store: ensureClient()
    alt Identity configured
        Store->>Resolver: Resolve<Provider>AuthContext(identity)
        activate Resolver
        Resolver->>AuthMgr: Authenticate(identity)
        activate AuthMgr
        AuthMgr->>Cloud: Request credentials for identity
        Cloud-->>AuthMgr: Temporary credentials
        AuthMgr-->>Resolver: Auth context with credentials
        deactivate AuthMgr
        Resolver-->>Store: ProviderAuthConfig (creds, region, profile, etc.)
        deactivate Resolver
        Store->>Store: Create client with resolved credentials
    else No identity
        Store->>Store: Create client with default credential chain
    end

    Store->>Cloud: Access secret store
    Cloud-->>Store: Secret/value
    Store-->>Client: Return result
    deactivate Store
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • osterman
  • mcalhoun
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main feature: identity-based authentication for stores.
Linked Issues check ✅ Passed All coding requirements from issue #2082 are met: identity field in store config, lazy initialization, cloud store support (SSM/Azure/GSM), backward compatibility, error handling, and wiring integration.
Out of Scope Changes check ✅ Passed All changes align with issue #2082 scope: store identity support, auth wiring, tests, examples, and documentation for the feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch aknysh/atmos-auth-for-stores

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 and usage tips.

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
pkg/store/authbridge/resolver_test.go (1)

248-265: TestResolver_NilStackInfo only exercises the AWS path despite the comment.

The comment on line 261 says "All resolve methods should return error when stackInfo is nil" but only ResolveAWSAuthContext is called. Consider adding calls for Azure and GCP to match the stated intent — or adjust the comment.

Also, the mock currently expects exactly one Authenticate call. If you add Azure/GCP checks, you'll need gomock.AnyTimes() or additional expectations.

Proposed expansion
 	// All resolve methods should return error when stackInfo is nil.
 	_, err := resolver.ResolveAWSAuthContext(context.Background(), "test-identity")
 	assert.Error(t, err)
 	assert.Contains(t, err.Error(), "AWS auth context not available")
+
+	mockManager.EXPECT().
+		Authenticate(gomock.Any(), "test-identity").
+		Return(&types.WhoamiInfo{}, nil)
+
+	_, err = resolver.ResolveAzureAuthContext(context.Background(), "test-identity")
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "Azure auth context not available")
+
+	mockManager.EXPECT().
+		Authenticate(gomock.Any(), "test-identity").
+		Return(&types.WhoamiInfo{}, nil)
+
+	_, err = resolver.ResolveGCPAuthContext(context.Background(), "test-identity")
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "GCP auth context not available")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/store/authbridge/resolver_test.go` around lines 248 - 265,
TestResolver_NilStackInfo only calls ResolveAWSAuthContext but its comment
claims "All resolve methods should return error when stackInfo is nil"; update
the test to either call ResolveAzureAuthContext and ResolveGCPAuthContext as
well (and assert errors containing the corresponding "auth context not
available" messages) or change the comment to match the single-path test; if you
add Azure/GCP calls also change the mock expectation on mockManager.Authenticate
in TestResolver_NilStackInfo (or use gomock.AnyTimes()) so multiple Authenticate
invocations are allowed; locate these symbols: TestResolver_NilStackInfo,
ResolveAWSAuthContext, ResolveAzureAuthContext, ResolveGCPAuthContext,
mockManager, and Authenticate to make the changes.
pkg/store/identity_test.go (1)

13-40: Manual mock — consider using mockgen instead.

The mockAuthContextResolver is hand-rolled with testify/mock. The coding guidelines state to use go.uber.org/mock/mockgen with //go:generate directives and avoid manual mocks. Since AuthContextResolver is an exported interface, mockgen can generate this cleanly.

Not a blocker, but worth aligning with the rest of the codebase (e.g., resolver_test.go already uses gomock).

As per coding guidelines: "Use go.uber.org/mock/mockgen with //go:generate directives. Never manual mocks."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/store/identity_test.go` around lines 13 - 40, The tests currently use a
hand-rolled mock type mockAuthContextResolver implementing AuthContextResolver;
replace this manual mock with a mock generated by go.uber.org/mock/mockgen: add
a //go:generate mockgen ... directive near the AuthContextResolver interface (or
in pkg/store) to emit a generated mock (e.g., into pkg/store/mocks), run mockgen
to produce the mock, update tests to import and use the generated mock type
instead of mockAuthContextResolver, and remove the manual
mockAuthContextResolver implementation and its
ResolveAWSAuthContext/ResolveAzureAuthContext/ResolveGCPAuthContext methods from
identity_test.go. Ensure the generated mock package is referenced in tests and
the go:generate comment is committed so future regenerations are
straightforward.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/store/aws_ssm_param_store.go`:
- Around line 182-199: The early-return in SSMStore.ensureClient (the if
s.client != nil) causes a data race with the write to s.client inside
s.initOnce.Do; remove that pre-check and always call s.initOnce.Do (using
s.initOnce.Do to perform s.initDefaultClient or s.initIdentityClient) so the
write happens under the Once synchronization and callers get the proper
happens-before guarantee; apply the same change to
AzureKeyVaultStore.ensureClient and GSMStore.ensureClient to eliminate the race
on their respective client fields and initOnce usage.

In `@pkg/store/azure_keyvault_store.go`:
- Around line 152-167: The early check reading s.client in
AzureKeyVaultStore.ensureClient introduces a data race; remove the top-level if
s.client != nil return nil and instead perform the s.client nil check inside the
initOnce.Do closure (alongside the existing logic that sets s.initErr via
initDefaultClient or initIdentityClient), so that initialization and the client
presence check are both performed under the sync.Once protection; keep returning
s.initErr after initOnce.Do as before.

In `@pkg/store/google_secret_manager_store.go`:
- Around line 133-182: ensureClient has an unsynchronized read of s.client (data
race) and inlines identity initialization; fix by making the nil check
thread-safe and moving identity setup into a new initIdentityClient method:
replace the top-level if s.client != nil return nil with a synchronized check
using s.initOnce (or re-check inside initOnce.Do) consistent with SSM/Azure
pattern, extract the block that handles s.identityName != "" (including
authResolver.ResolveGCPAuthContext, credential selection, clientOpts creation
and secretmanager.NewClient) into a new GSMStore.initIdentityClient() helper
that sets s.client and s.initErr, and have ensureClient call
s.initOnce.Do(func(){ if s.identityName=="" { s.initErr = s.initDefaultClient();
return } s.initErr = s.initIdentityClient() }) so identity path is no longer
inlined; keep existing cleanup/close logic and preserve initErr semantics.

In `@website/src/data/roadmap.js`:
- Line 167: Add the missing pr field to the milestone object with label
'Identity selection for stores' in the roadmap data: include pr: 2099 alongside
the existing keys (label, status, quarter, changelog, description, benefits) so
the milestone follows the same schema as other shipped entries (e.g., those with
pr: 2043, pr: 2051).
- Line 141: The roadmap entry's progress regressed from 85 to 83; update the
progress property (the object with "progress: 83") to a value ≥85 (suggest 88)
to reflect the newly shipped milestone so the initiative's progress only moves
forward.

---

Nitpick comments:
In `@pkg/store/authbridge/resolver_test.go`:
- Around line 248-265: TestResolver_NilStackInfo only calls
ResolveAWSAuthContext but its comment claims "All resolve methods should return
error when stackInfo is nil"; update the test to either call
ResolveAzureAuthContext and ResolveGCPAuthContext as well (and assert errors
containing the corresponding "auth context not available" messages) or change
the comment to match the single-path test; if you add Azure/GCP calls also
change the mock expectation on mockManager.Authenticate in
TestResolver_NilStackInfo (or use gomock.AnyTimes()) so multiple Authenticate
invocations are allowed; locate these symbols: TestResolver_NilStackInfo,
ResolveAWSAuthContext, ResolveAzureAuthContext, ResolveGCPAuthContext,
mockManager, and Authenticate to make the changes.

In `@pkg/store/identity_test.go`:
- Around line 13-40: The tests currently use a hand-rolled mock type
mockAuthContextResolver implementing AuthContextResolver; replace this manual
mock with a mock generated by go.uber.org/mock/mockgen: add a //go:generate
mockgen ... directive near the AuthContextResolver interface (or in pkg/store)
to emit a generated mock (e.g., into pkg/store/mocks), run mockgen to produce
the mock, update tests to import and use the generated mock type instead of
mockAuthContextResolver, and remove the manual mockAuthContextResolver
implementation and its
ResolveAWSAuthContext/ResolveAzureAuthContext/ResolveGCPAuthContext methods from
identity_test.go. Ensure the generated mock package is referenced in tests and
the go:generate comment is committed so future regenerations are
straightforward.

Comment thread pkg/store/aws_ssm_param_store.go
Comment thread pkg/store/azure_keyvault_store.go
Comment thread pkg/store/google_secret_manager_store.go
Comment thread website/src/data/roadmap.js Outdated
Comment thread website/src/data/roadmap.js Outdated
@codecov

codecov Bot commented Feb 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.16393% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.32%. Comparing base (a5a525c) to head (cb55c22).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pkg/store/azure_keyvault_store.go 81.13% 5 Missing and 5 partials ⚠️
pkg/store/google_secret_manager_store.go 91.93% 4 Missing and 1 partial ⚠️
pkg/store/aws_ssm_param_store.go 93.54% 2 Missing and 2 partials ⚠️
internal/exec/terraform.go 0.00% 2 Missing and 1 partial ⚠️
internal/exec/terraform_shell.go 0.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2099      +/-   ##
==========================================
+ Coverage   76.19%   76.32%   +0.12%     
==========================================
  Files         830      831       +1     
  Lines       78597    78802     +205     
==========================================
+ Hits        59888    60143     +255     
+ Misses      14964    14899      -65     
- Partials     3745     3760      +15     
Flag Coverage Δ
unittests 76.32% <90.16%> (+0.12%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
pkg/store/authbridge/resolver.go 100.00% <100.00%> (ø)
pkg/store/config.go 100.00% <ø> (ø)
pkg/store/registry.go 61.40% <100.00%> (+42.25%) ⬆️
internal/exec/terraform_shell.go 33.33% <0.00%> (-1.05%) ⬇️
internal/exec/terraform.go 63.02% <0.00%> (-0.43%) ⬇️
pkg/store/aws_ssm_param_store.go 85.85% <93.54%> (+3.54%) ⬆️
pkg/store/google_secret_manager_store.go 78.77% <91.93%> (+10.28%) ⬆️
pkg/store/azure_keyvault_store.go 76.51% <81.13%> (+16.13%) ⬆️

... and 4 files with indirect coverage changes

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

Replace hand-rolled testify/mock with gomock-generated MockAuthContextResolver
per coding guidelines. Expand TestResolver_NilStackInfo to test all three
cloud providers (AWS, Azure, GCP) instead of only the AWS path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d roadmap

Move client-nil check inside initOnce.Do in all three store implementations
(SSM, Azure, GSM) to eliminate potential data race between unsynchronized
read and write from another goroutine. Bump auth initiative progress to 88%
and add pr: 2099 to store identity milestone.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

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

🧹 Nitpick comments (2)
pkg/store/identity_test.go (1)

104-129: Minor: ensureClient error is silently discarded.

Intentional per the comment (credential files don't exist in test), and the gomock expectation validates the resolver was called. Just noting this pattern — if future refactors make ensureClient succeed unexpectedly, the test won't catch regressions. Same applies to lines 400 and 425.

Consider at minimum asserting err != nil to confirm the expected failure path is exercised.

Example
-	_ = store.ensureClient()
+	err := store.ensureClient()
+	// Expected to fail because credential files don't exist in test environment.
+	assert.Error(t, err)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/store/identity_test.go` around lines 104 - 129, The test
TestSSMStore_LazyInit_WithResolver currently discards the result of
store.ensureClient(), which hides the expected failure when AWS credential files
are missing; update the test to capture the returned error from
SSMStore.ensureClient() and add an assertion that err != nil (or use
require.Error/AssertError) so the test explicitly verifies the failure path and
that the resolver was invoked; apply the same pattern to the other tests that
call ensureClient() (the ones referenced around lines ~400 and ~425) to ensure
they also assert the expected error outcome.
pkg/store/aws_ssm_param_store_test.go (1)

627-692: TestSSMStore_BuildAuthConfigOpts covers the matrix well but only asserts slice length.

The table-driven approach with 6 scenarios is solid. Consider also asserting specific option types or values in at least the "all fields populated" case to catch regressions where the right number of options is produced but with wrong content. Not critical though — length checks already catch most config wiring bugs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/store/aws_ssm_param_store_test.go` around lines 627 - 692, Test only
checks the length of the options slice in TestSSMStore_BuildAuthConfigOpts which
can miss incorrect option contents; update the test to also assert the actual
option types/values for at least the "all fields populated" case by calling
store.buildAuthConfigOpts with the AWSAuthConfig fixture and verifying that opts
contains the expected option entries (credentials file, config file, profile and
region) in whatever ordering buildAuthConfigOpts produces — use the
AWSAuthConfig struct, the SSMStore.buildAuthConfigOpts call and inspect elements
of opts to compare against expected values/types so regressions in option
contents are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pkg/store/google_secret_manager_store.go`:
- Around line 170-185: The check of s.client in GSMStore.ensureClient is done
unsafely outside the sync.Once, causing a data race; move the nil-check into the
initOnce.Do closure so the initialization (calling initDefaultClient or
initIdentityClient) and any writes to s.client happen only inside the Do, and
ensure ensureClient returns s.initErr after Do; specifically, update
GSMStore.ensureClient to call s.initOnce.Do(func(){ if s.client == nil { if
s.identityName == "" { s.initErr = s.initDefaultClient() } else { s.initErr =
s.initIdentityClient() } } }) and then return s.initErr, referencing
ensureClient, s.client, s.initOnce.Do, initDefaultClient and initIdentityClient.

---

Nitpick comments:
In `@pkg/store/aws_ssm_param_store_test.go`:
- Around line 627-692: Test only checks the length of the options slice in
TestSSMStore_BuildAuthConfigOpts which can miss incorrect option contents;
update the test to also assert the actual option types/values for at least the
"all fields populated" case by calling store.buildAuthConfigOpts with the
AWSAuthConfig fixture and verifying that opts contains the expected option
entries (credentials file, config file, profile and region) in whatever ordering
buildAuthConfigOpts produces — use the AWSAuthConfig struct, the
SSMStore.buildAuthConfigOpts call and inspect elements of opts to compare
against expected values/types so regressions in option contents are caught.

In `@pkg/store/identity_test.go`:
- Around line 104-129: The test TestSSMStore_LazyInit_WithResolver currently
discards the result of store.ensureClient(), which hides the expected failure
when AWS credential files are missing; update the test to capture the returned
error from SSMStore.ensureClient() and add an assertion that err != nil (or use
require.Error/AssertError) so the test explicitly verifies the failure path and
that the resolver was invoked; apply the same pattern to the other tests that
call ensureClient() (the ones referenced around lines ~400 and ~425) to ensure
they also assert the expected error outcome.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 22, 2026
Add tests for eager-init constructors (no identity), ensureClient
default-client path, identity client success with temp credential files,
and Get/Set/GetKey ensureClient error guards across all three cloud stores.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Minimal example showing AWS SSM, Azure Key Vault, and GCP Secret Manager
stores configured with Atmos auth identities.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

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

🧹 Nitpick comments (5)
examples/auth-stores/atmos.yaml (2)

32-35: Consider clarifying the gcp/adc identity/provider symmetry.

Both the gcp-adc provider (Line 17) and the gcp-prod identity (Line 33) carry kind: gcp/adc. A brief comment here (e.g., # ADC resolves credentials from the environment; via links the identity to the registered provider) would clarify to readers that this isn't redundant boilerplate but a deliberate schema requirement.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/auth-stores/atmos.yaml` around lines 32 - 35, Add a short clarifying
comment near the "gcp-prod" identity that explains why both the provider
("gcp-adc") and the identity ("gcp-prod") use kind: gcp/adc and what "via" does;
specifically mention that ADC resolves credentials from the environment and that
the "via" field links the identity to the registered provider (so this
duplication is intentional schema wiring, not redundant boilerplate). Reference
the provider name "gcp-adc", the identity "gcp-prod", the kind value "gcp/adc",
and the "via" field so reviewers can locate where to add the comment.

1-57: Flag this example as intentionally minimal.

The file has no base_path, stacks, or components block — required for a runnable Atmos project. A short comment up top noting that this is a config snippet (not a complete project) would save readers from chasing a "why doesn't this work" rabbit hole.

💡 Suggested header clarification
 # Demonstrates using Atmos auth identities with stores.
 # Each store references an identity for credential resolution.
+#
+# NOTE: This is an intentionally minimal snippet focused on auth + stores.
+# A complete atmos.yaml also requires base_path, stacks, and components sections.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/auth-stores/atmos.yaml` around lines 1 - 57, Add a short top-line
comment to clarify this file is an intentionally minimal config snippet (not a
complete Atmos project) because the file contains auth:, providers:,
identities:, and stores: entries but lacks required project-level blocks like
base_path, stacks, or components; update the header to state it’s an example
snippet for demonstrating identity-based stores so readers won’t expect a
runnable project.
pkg/store/aws_ssm_param_store.go (1)

170-177: Dead code: s.region is guaranteed non-empty by the constructor.

NewSSMStore returns ErrRegionRequired when options.Region == "" (line 65-67), so s.region is always non-empty. That means region at line 171 is never "", making the fallback at lines 172-174 unreachable.

Not harmful — just defensive code that can't trigger. Up to you whether to trim it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/store/aws_ssm_param_store.go` around lines 170 - 177, Remove the dead
fallback that checks s.region for emptiness since NewSSMStore enforces a
non-empty region (ErrRegionRequired) so s.region is always set; in the AWS SSM
store initialization replace the three-line branch that assigns region :=
s.region and then overrides it with authContext.Region only-if-empty with a
single use of s.region (and still apply config.WithRegion(s.region) to cfgOpts)
and delete the unreachable authContext.Region fallback logic to simplify code
around cfgOpts and config.WithRegion.
pkg/store/identity_test.go (1)

678-785: Consider consolidating these 9 repetitive guard tests into a table-driven test.

Each SSM/Azure/GSM × Get/Set/GetKey test follows the exact same pattern: create a store with identityName but no resolver, call the operation, assert ErrIdentityNotConfigured. A single table-driven test would reduce ~100 lines to ~30 and make it trivial to add new store types later.

Not urgent — the current form is clear and correct.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/store/identity_test.go` around lines 678 - 785, Replace the nine
repetitive tests for SSMStore/AzureKeyVaultStore/GSMStore
(TestSSMStore_Get_EnsureClientError, TestSSMStore_Set_EnsureClientError,
TestSSMStore_GetKey_EnsureClientError, TestAzureKeyVaultStore_...,
TestGSMStore_...) with a single table-driven test (e.g.,
TestStore_EnsureClientError_TableDriven) that iterates cases describing store
constructor (returning initialized SSMStore/AzureKeyVaultStore/GSMStore with
identityName:"broken"), the operation to call (Get, Set, GetKey) as a
function/closure, and the expected error ErrIdentityNotConfigured; for each case
construct the store via the factory, invoke the operation closure, and assert
the returned error is non-nil and errors.Is(err, ErrIdentityNotConfigured) so
adding new stores/ops only requires another table entry.
pkg/store/azure_keyvault_store.go (1)

119-150: Consider adding a comment explaining why initIdentityClient uses only TenantID.

The struct carries CredentialsFile, SubscriptionID, UseOIDC, ClientID, and TokenFilePath to mirror schema.AzureAuthContext (per design pattern), but the store's Azure auth flow works through MSAL cache and environment variables set during Authenticate(), not through loading credential files like SSM and GSM do. A comment here would clarify that DefaultAzureCredential with the tenant hint is sufficient because it checks the cached MSAL tokens and env vars already configured upstream.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/store/azure_keyvault_store.go` around lines 119 - 150, Add a short
clarifying comment inside initIdentityClient explaining that only
authContext.TenantID is applied to azidentity.DefaultAzureCredentialOptions
because this store relies on the MSAL cache and environment variables
(configured during Authenticate and exposed via
authResolver.ResolveAzureAuthContext) rather than loading credential files or
using SubscriptionID/ClientID/TokenFilePath directly; mention that
DefaultAzureCredential will pick up cached tokens and env credentials so a
tenant hint is sufficient before creating azidentity.NewDefaultAzureCredential
and azsecrets.NewClient.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pkg/store/aws_ssm_param_store.go`:
- Around line 186-199: The race condition has already been fixed by moving the
s.client != nil check inside ensureClient's s.initOnce.Do; no further changes
required — keep the current implementation of ensureClient (with s.initOnce.Do
wrapping the s.client check) and retain usage of s.initErr, initDefaultClient
and initIdentityClient as-is.

In `@pkg/store/azure_keyvault_store.go`:
- Around line 152-168: ensureClient already moves the client-nil check inside
initOnce.Do to avoid the data race; no code changes required. Keep the current
implementation of AzureKeyVaultStore.ensureClient which uses s.initOnce.Do,
checks s.client, and sets s.initErr via s.initDefaultClient or
s.initIdentityClient as appropriate.

In `@pkg/store/google_secret_manager_store.go`:
- Around line 170-186: Previous data-race fix looks applied: ensureClient moved
the client-nil check inside initOnce.Do and identity init was extracted; now
confirm and enforce that initDefaultClient and initIdentityClient both set
s.client and s.initErr (no other goroutine reads s.client unsafely), add a unit
test exercising the eager-init path where s.client is pre-set and a test for the
identity init path to verify initErr propagation, and ensure all accesses to
s.client outside ensureClient are synchronized or only read after ensureClient
returns; reference functions: ensureClient, initDefaultClient,
initIdentityClient.

In `@website/src/data/roadmap.js`:
- Line 141: The change to the roadmap entry (the progress property set to 88 and
the milestone including `pr: 2099` in website/src/data/roadmap.js) has addressed
prior review feedback; no code changes required—approve and merge the PR as-is,
ensuring the `progress: 88` value and the `pr: 2099` milestone remain in the
`roadmap` entry.

---

Nitpick comments:
In `@examples/auth-stores/atmos.yaml`:
- Around line 32-35: Add a short clarifying comment near the "gcp-prod" identity
that explains why both the provider ("gcp-adc") and the identity ("gcp-prod")
use kind: gcp/adc and what "via" does; specifically mention that ADC resolves
credentials from the environment and that the "via" field links the identity to
the registered provider (so this duplication is intentional schema wiring, not
redundant boilerplate). Reference the provider name "gcp-adc", the identity
"gcp-prod", the kind value "gcp/adc", and the "via" field so reviewers can
locate where to add the comment.
- Around line 1-57: Add a short top-line comment to clarify this file is an
intentionally minimal config snippet (not a complete Atmos project) because the
file contains auth:, providers:, identities:, and stores: entries but lacks
required project-level blocks like base_path, stacks, or components; update the
header to state it’s an example snippet for demonstrating identity-based stores
so readers won’t expect a runnable project.

In `@pkg/store/aws_ssm_param_store.go`:
- Around line 170-177: Remove the dead fallback that checks s.region for
emptiness since NewSSMStore enforces a non-empty region (ErrRegionRequired) so
s.region is always set; in the AWS SSM store initialization replace the
three-line branch that assigns region := s.region and then overrides it with
authContext.Region only-if-empty with a single use of s.region (and still apply
config.WithRegion(s.region) to cfgOpts) and delete the unreachable
authContext.Region fallback logic to simplify code around cfgOpts and
config.WithRegion.

In `@pkg/store/azure_keyvault_store.go`:
- Around line 119-150: Add a short clarifying comment inside initIdentityClient
explaining that only authContext.TenantID is applied to
azidentity.DefaultAzureCredentialOptions because this store relies on the MSAL
cache and environment variables (configured during Authenticate and exposed via
authResolver.ResolveAzureAuthContext) rather than loading credential files or
using SubscriptionID/ClientID/TokenFilePath directly; mention that
DefaultAzureCredential will pick up cached tokens and env credentials so a
tenant hint is sufficient before creating azidentity.NewDefaultAzureCredential
and azsecrets.NewClient.

In `@pkg/store/identity_test.go`:
- Around line 678-785: Replace the nine repetitive tests for
SSMStore/AzureKeyVaultStore/GSMStore (TestSSMStore_Get_EnsureClientError,
TestSSMStore_Set_EnsureClientError, TestSSMStore_GetKey_EnsureClientError,
TestAzureKeyVaultStore_..., TestGSMStore_...) with a single table-driven test
(e.g., TestStore_EnsureClientError_TableDriven) that iterates cases describing
store constructor (returning initialized SSMStore/AzureKeyVaultStore/GSMStore
with identityName:"broken"), the operation to call (Get, Set, GetKey) as a
function/closure, and the expected error ErrIdentityNotConfigured; for each case
construct the store via the factory, invoke the operation closure, and assert
the returned error is non-nil and errors.Is(err, ErrIdentityNotConfigured) so
adding new stores/ops only requires another table entry.

@aknysh
Andriy Knysh (aknysh) merged commit 8fdf9b6 into main Feb 22, 2026
58 checks passed
@aknysh
Andriy Knysh (aknysh) deleted the aknysh/atmos-auth-for-stores branch February 22, 2026 23:23
@github-actions

Copy link
Copy Markdown

These changes were released in v1.208.0-rc.0.

@github-actions

Copy link
Copy Markdown

These changes were released in v1.208.0-test.15.

This branch was successfully deployed

1 active deployment
preview — cb55c22b Deployed Feb 22, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor New features that do not break anything size/xl Extra large size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ADD Identity Selection Support to Atmos Stores Configuration

2 participants