feat(auth): add Azure AKS/ACR integrations mirroring EKS/ECR @osterman (#2790)
## what- Adds
atmos azure aks token,atmos azure aks update-kubeconfig, andatmos azure acr login, mirroring the existingatmos aws eks/atmos aws ecrintegrations. - Generalizes
pkg/auth/cloud/kube.KubeconfigManagerfrom AWS-specific to a cloud-agnostic writer shared by EKS and AKS, with a regression suite locking in byte-identical AWS output. - Widens the existing
IntegrationSpec.Cluster/.Registryschema structs (renamed fromEKSCluster/ECRRegistrytoCluster/Registry) sospec.cluster/spec.registryare reused verbatim acrossaws/eks+azure/aksandaws/ecr+azure/acr— no new per-cloud config keys. - Adds AKS-scoped AAD token acquisition to all three Azure identity providers (device-code, OIDC, Azure CLI), alongside their existing Graph/KeyVault token acquisition, since Azure AAD tokens are scope-bound at issuance (unlike AWS SigV4).
- Adds docs (
website/docs/cli/commands/azure/), a changelog post, a roadmap update, two new agent skills (atmos-azure-aks,atmos-azure-acr), and a PRD documenting the design (docs/prd/azure-aks-acr-integrations.md). - Remediates 4 open Dependabot alerts found on push:
google.golang.org/grpc(xDS RBAC auth bypass / HTTP2 rapid-reset bypass), and three transitive website npm packages (fast-uri,svgo,dompurify).
why
- Atmos already lets an AWS identity configure
kubectland Docker credentials in one step viaatmos auth login. Azure had the same auth foundation (providers, identities) but no equivalent for AKS/ACR, so Azure users still needed theazCLI — and for AAD-enabled clusters, the separatekubeloginbinary — outside of Atmos entirely. - This closes that gap using the same integration pattern, with no new external tool dependency: AKS cluster description parses the exec-format kubeconfig Azure returns and points the exec plugin at
atmos azure aks tokeninstead ofkubelogin; ACR login is a plain OAuth2 token exchange, matching whataz acr logindoes under the hood.
references
- Design:
docs/prd/azure-aks-acr-integrations.md - Precedent: EKS kubeconfig PRD (
docs/prd/eks-kubeconfig.md), ECR authentication PRD (docs/prd/ecr-authentication.md)
manual testing
Exercised end-to-end against a live AAD-enabled AKS cluster (Azure CNI Overlay + Cilium, AAD + Azure RBAC, local accounts disabled) — the live path that PRD Success Metric #2 had previously left to unit tests only. This surfaced, and fixed, a registration gap.
Bug found + fixed. atmos azure aks update-kubeconfig --integration <name> failed with unknown integration kind: azure/aks. The pkg/auth/integrations/azure package self-registers azure/aks and azure/acr in its init(), but nothing blank-imported that package in pkg/auth/manager.go (unlike the aws and github integration packages), so init() never ran and the kinds never registered. The unit suites import the azure package directly, which registered the kinds incidentally and masked the missing production import. Fixed by adding the blank import alongside aws/github.
Integration mode — describe the cluster and write kubeconfig via the Go SDK (no az, no kubelogin):
$ atmos azure aks update-kubeconfig --integration dev/aks
✓ AKS kubeconfig: dev-aks → ~/.config/atmos/kube/config
$ export KUBECONFIG=~/.config/atmos/kube/config
$ kubectl config current-context
dev-aks
$ kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system cilium-8trqv 3/3 Running 0 134m
kube-system coredns-5d474ff6db-pknhn 1/1 Running 0 132m
kube-system metrics-server-5b879b45fc-5nxzs 2/2 Running 0 129m
...The kubeconfig Atmos wrote drives its exec plugin through atmos azure aks token (not kubelogin), with --server-id discovered from the cluster (here the well-known AKS AAD server app):
$ kubectl config view --raw -o jsonpath='{.users[0].user.exec.command} {.users[0].user.exec.args}'
atmos [azure aks token --cluster-name aks-dev --resource-group rg-aks-cus \
--server-id 6dae42f8-4368-4678-94ff-3960e28e3630 --subscription-id <redacted> --identity=dev]auth exec mode — Atmos injects KUBECONFIG into the child process from the integration's Environment() (works even with auto_provision: false, which only suppresses the auto-write on login, not the env composition), so no manual export is needed:
$ atmos auth exec --identity dev -- kubectl get nodes
NAME STATUS ROLES AGE VERSION
aks-system-64934532-vmss000000 Ready <none> 139m v1.35.6
aks-system-64934532-vmss000001 Ready <none> 139m v1.35.6Both paths mint bearer tokens through atmos azure aks token against the Atmos-managed identity — no az CLI and no kubelogin binary. (ACR login against a live registry remains unit-test-only.)
Summary by CodeRabbit
- New Features
- Added native Azure authentication for AKS and ACR.
- Added ACR login, AKS kubeconfig update, and AKS token commands.
- Added identity-based integrations, Docker credential provisioning, and Kubernetes ExecCredential support.
- Added shared AWS and Azure registry and cluster configuration.
- Bug Fixes
- Improved cluster selection, token handling, kubeconfig updates, and authentication errors.
- Improved Packer and Ansible component command resolution.
- Documentation
- Added Azure command guides, integration references, examples, and roadmap updates.
🚀 Enhancements
fix(secret): inherit the component's default identity for stores @jaguer0 (#2746)
## whatatmos secret(set/get/init/validate) now inherits the component's effective identity for store-backed secrets whose store declares no explicitidentity:, instead of falling back to the default AWS credential chain (→ EC2 IMDS, which fails off-EC2).
why
injectSecretStoreAuthResolver(cmd/secret/shared.go) calledatmosConfig.Stores.SetAuthContextResolver(resolver), which passes an empty identity to every store, so an identity-less store fell back to the AWS default chain → EC2 IMDS and failed off-EC2 (e.g.no EC2 IMDS role found ... dial tcp 169.254.169.254:80: connect: host is down).- The terraform paths (
cmd/terraform/utils.go,internal/exec/terraform_execute_helpers.go) already callSetAuthContextResolverWithDefaultIdentity; the secret CLI even computed the sameDefaultIdentity(intoSecretsAuth) but never applied it to the stores. - This aligns the code with documented behavior —
website/docs/cli/configuration/secrets.mdx: "When omitted and the secret is resolved within a component scope, the component's effective identity is inherited." - Stores with an explicit
identity, and an explicit--identity, are unaffected (defaultIdentityForStoreonly fills empty-identity stores).atmos terraformandatmos secret listbehavior is unchanged.
Suggested label: patch (user-visible bug fix, no new surface; no blog/roadmap required).
references
- Related: #2662 (terraform store-output hooks inherit the run's default identity — sibling fix).
- Fix write-up:
docs/fixes/2026-07-13-secret-cli-inherit-default-identity.md
Summary by CodeRabbit
- Bug Fixes
- Store-backed
atmos secretoperations now inherit the component’s effective default identity when no store identity is configured. - Prevents unintended fallback to the default AWS credential chain across store-backed actions.
- Store-backed
- Tests
- Added coverage for default identity inheritance and resolver setup.
- Documentation
- Added guidance on identity inheritance and override precedence.
fix(merge): resolve deferred YAML functions losing data on merge (#2888) @osterman (#2892)
## whatFixes #2888 — deferred YAML functions silently losing data on merge
- Every production call site of
ApplyDeferredMergespassedprocessor = nil, so deferred YAML
functions (!template,!terraform.output,!terraform.state,!store,!exec,!env) were
never actually resolved-and-merged — they silently lost data whenever a concrete value at another
config layer collided with them. On top of that,!labels/!tags/!labels.keys/!labels.values
weren't in the defer list at all, which is the literal scenario reported in the issue. - Adds a real Stage 3 resolution pass (
internal/exec/deferred_contexts.go, plus changes across
internal/exec/stack_processor_*.go,internal/exec/yaml_processor.go,
internal/exec/yaml_func_tags.go,pkg/merge/deferred.go,pkg/merge/merge_yaml_functions.go)
that resolves deferred functions per-invocation (auth- and template-context-aware) and deep-merges
the result against any concrete override at the same path — including the mirror-precedence
direction (a concrete value at a lower-precedence layer than the function), which the original
design didn't handle. - Fixes a nondeterministic parent/child collision found while field-testing:
ApplyDeferredMerges
now processes deferred paths ancestor-before-descendant, so a descendant leaf can never be
clobbered by a later wholesale replace of its ancestor map (see
docs/fixes/2026-08-07-deferred-merge-nested-function-collision.md).
Fixes a double-execution regression introduced by the Stage 3 pass
- Reviewing the Stage 3 wiring surfaced a behavior regression: with
--process-functions=true, the
document-wideProcessCustomYamlTagspass already resolves each surviving function, and Stage 3
then re-resolved every deferred path unconditionally — so each deferred function ran twice
per component. Harmless for pure/cached functions (!template,!terraform.output/state,
!labels,!tags,!env), but!exec(uncached — runs the shell again) and!store
(an extra backend read) were executed twice. Confirmed live: a non-collidingvars.foo: !exec
ran the shell 2× on this branch vs 1× onmain. - Fix (
pkg/merge/merge_yaml_functions.go): for a single-contribution (no-collision) deferred path
whose value is already resolved in the result,ApplyDeferredMergesnow reuses that value instead
of re-invoking the processor. Tightly guarded so genuine collisions (len > 1) still fully
resolve-and-merge — the #2888 fix is untouched. See
docs/fixes/2026-08-13-deferred-merge-double-execution.md.
Housekeeping
- Introduces named types
StackComponentDeferredContextsandAllStacksDeferredContextsin place
of the rawmap[string]map[string][...]ComponentDeferredContextssignatures threaded through the
stack processor (readability only; no behavior change). - Also bundled in this branch (unrelated to #2888, surfaced during field-test CI runs): transient-error
retry logic for the Aqua registry and GitHub releases/rate-limit fetches
(pkg/toolchain/registry/*), and a stack-completion fix so completion lists all project stacks
includinglocal(cmd/emulator/completions.go).
why
vars.tags: !labels(and other deferred functions) silently lost data when another config layer
set a conflicting value at the same path — a correctness bug with no error or warning, so it was
hard to detect in real stacks.- The double-execution fix prevents side-effecting/uncached functions (
!exec,!store) from
running twice, which could surprise users with duplicated side effects or extra load. - Per this repo's bug-fixing workflow, regression tests were written and confirmed failing first,
then the fixes were implemented and verified against them (including live before/after runs of a
real!execfixture and end-to-end assertions throughExecuteDescribeComponent).
references
- Closes #2888
Summary by CodeRabbit
-
New Features
- Deferred YAML functions now resolve across merged configurations, including templates, labels, and tags.
- Deep merging preserves nested maps, lists, and concrete overrides across precedence levels.
- Stack completion now lists all project stacks, including
local.
-
Bug Fixes
- Terraform backend and variable-file generation now retain deferred configuration values.
- Toolchain and Aqua registry requests retry transient network, rate-limit, and server errors.
-
Tests
- Added broad regression coverage for deferred merges, stack processing, generation, and network retries.
Fix const variable interpolation in Terraform module sources @gitbluf (#2914)
## WhatUpdates terraform-config-inspect to support static (const = true) variable
interpolation in Terraform module.source values.
Adds regression coverage for:
- Successfully describing a component with
source = "./mods/${var.org}". - Preserving real HCL syntax failures when loading Terraform components.
- Returning parsed Terraform configuration as
*tfconfig.Modulein the OpenTofu
interpolation test.
Why
Atmos previously failed while parsing valid Terraform 1.15+
configurations that interpolate a static variable in module.source:
variable "org" {
const = true
type = string
default = "myorg"
}
module "greeting" {
source = "./mods/${var.org}"
}The previous terraform-config-inspect version evaluated module.source without an HCL
evaluation context and returned Variables not allowed before Terraform or OpenTofu was
invoked.
Fixes: #2913
Summary by CodeRabbit
-
New Features
- Added support for Terraform local module sources using static constant interpolation.
- Components using these sources now load successfully, with variables and outputs parsed correctly.
-
Bug Fixes
- Preserved clear failure handling for genuine Terraform syntax errors.
-
Documentation
- Added guidance covering supported interpolation behavior and validation.
-
Tests
- Added coverage for valid interpolated module sources and malformed Terraform configurations.
- Expanded scenario validation for module outputs and component configuration.