This document explains how authentication works with the !terraform.output YAML function, including identity resolution, credential management, and the execution flow through the Atmos codebase.
The !terraform.output YAML function executes the terraform binary to retrieve outputs from Terraform state. This requires:
- Executing
terraform initto initialize the backend - Executing
terraform outputto read output values - Providing AWS credentials via environment variables
- Auto-generating
backend.tf.jsonwith backend configuration
Unlike !terraform.state which uses the AWS SDK directly, !terraform.output spawns a terraform process and passes credentials through environment variables.
When you use !terraform.output in a component configuration:
components:
terraform:
my-component:
vars:
subnet_ids: !terraform.output vpc public_subnet_idsAtmos resolves authentication in this order:
- Identity specification - Uses
--identityflag if provided - Auto-detection - Finds default identity from configuration
- Interactive selection - Prompts user if no defaults (TTY mode only)
- Explicit disable - Respects
--identity=offto skip Atmos Auth
Terraform Command Execution
↓
ExecuteTerraform()
├─ Creates AuthManager from --identity flag or auto-detection
├─ Stores authenticated identity in info.Identity for hooks
└─ Calls ProcessStacks(authManager)
↓
ProcessStacks()
└─ Calls ProcessComponentConfig(authManager)
├─ Populates stackInfo.AuthContext from AuthManager
└─ Component YAML processed with AuthContext available
↓
YAML Function: !terraform.output vpc subnet_ids
↓
processTagTerraformOutputWithContext()
├─ Extracts authContext from stackInfo
└─ Calls GetOutput(authContext)
↓
execTerraformOutput()
├─ Generates backend.tf.json
├─ Converts AuthContext to environment variables:
│ • AWS_PROFILE
│ • AWS_SHARED_CREDENTIALS_FILE
│ • AWS_CONFIG_FILE
│ • AWS_REGION
│ • AWS_EC2_METADATA_DISABLED=true
├─ tf.Init() - Terraform reads credentials from files
└─ tf.Output() - Returns output values
Nested functions occur when a component's configuration contains !terraform.output (or !terraform.state) functions that reference other components, which themselves also contain !terraform.output or !terraform.state functions in their configurations.
Example:
# Component 1: api-gateway (being deployed)
components:
terraform:
api-gateway:
vars:
backends:
# Level 1: This references backend-service
- endpoint: !terraform.output backend-service alb_dns_name
# Component 2: backend-service (referenced by api-gateway)
components:
terraform:
backend-service:
vars:
# Level 2: This also has !terraform.output functions
vpc_id: !terraform.output vpc vpc_id
subnet_ids: !terraform.output vpc private_subnet_idsWhen Atmos evaluates the Level 1 function, it needs to read the backend-service component configuration to execute terraform output. When that configuration is processed, it encounters the Level 2 functions, which must also be evaluated with proper authentication.
Atmos propagates authentication through nested function evaluations using the AuthManager:
Level 1: atmos terraform apply api-gateway -s production
├─ Creates AuthManager with prod-deploy identity
├─ Stores AuthManager in configAndStacksInfo.AuthManager
├─ Populates configAndStacksInfo.AuthContext from AuthManager
└─ Evaluates component configuration
↓
Level 2: !terraform.output backend-service alb_dns_name
├─ Extracts authContext and authManager from stackInfo
├─ Calls GetTerraformOutput(authContext, authManager)
└─ ExecuteDescribeComponent(AuthManager: authManager) ✅ Passes AuthManager!
↓
Level 3: Processing backend-service component config
├─ AuthManager propagated from Level 2
├─ Populates stackInfo.AuthContext from AuthManager
└─ Evaluates nested !terraform.output functions
↓
Level 4: !terraform.output vpc vpc_id
├─ Extracts authContext from stackInfo ✅ AuthContext available!
├─ Converts AuthContext to environment variables
├─ Executes terraform output with authenticated credentials
└─ Successfully retrieves output value
Key Points:
- AuthManager is stored in
configAndStacksInfo.AuthManagerat the top level - AuthManager is passed through
GetTerraformOutput()toExecuteDescribeComponent() - AuthContext is populated at each level from the AuthManager
- All nested levels use the same authenticated session
This ensures that deeply nested component configurations can execute terraform output with proper credentials without requiring separate authentication at each level.
# API Gateway aggregates endpoints from multiple services
api-gateway:
vars:
service_endpoints:
auth: !terraform.output auth-service endpoint_url
users: !terraform.output users-service endpoint_url
orders: !terraform.output orders-service endpoint_urlEach service component may have !terraform.output functions reading VPC, database, or cache configurations.
# Application tier references platform tier
app-component:
vars:
database_url: !terraform.output database connection_string
cache_endpoint: !terraform.output redis endpoint
# Platform tier references network tier
database:
vars:
subnet_ids: !terraform.output vpc database_subnet_ids
security_group_id: !terraform.output vpc database_sg_idThe app-component evaluation triggers database evaluation, which triggers vpc evaluation.
# Global load balancer references regional backends
global-lb:
vars:
backends:
- endpoint: !terraform.output app us-east-1 lb_dns_name
- endpoint: !terraform.output app us-west-2 lb_dns_name
- endpoint: !terraform.output app eu-west-1 lb_dns_nameEach regional app component may have nested !terraform.output functions for regional resources.
All nested function evaluations inherit the same AuthManager and credentials from the top-level command execution. This means:
- Single authentication session - All nested components use the same authenticated identity
- Consistent credentials - Environment variables set once and used by all nested terraform executions
- Transitive permissions - The identity must have access to all resources across nested components
If different components require different credentials, use component-level auth configuration to specify different default identities. Note that during a single command execution, all nested evaluations will use the top-level credentials.
When using nested !terraform.output functions:
- Each
!terraform.outputspawns a terraform process (init + output) - Consider using
!terraform.statewhen possible (faster, no subprocess) - Cache results are shared across nested evaluations
- Deep nesting (Level 4+) can impact performance
- Consider flattening dependencies for complex scenarios
-
Prefer !terraform.state for Speed
- Use
!terraform.stateinstead of!terraform.outputwhen possible - Faster execution (no subprocess, no terraform init)
- Same authentication propagation behavior
- Use
-
Configure Adequate Permissions
- Ensure the identity has access to all resources needed across nested components
- Include transitive dependencies (if A calls B, and B calls C, identity needs permissions for all)
-
Test Nested Configurations
- Use
atmos describe componentto test nested function resolution - Enable debug logging to verify authentication at each level:
atmos terraform plan component -s stack --logs-level=Debug
- Use
-
Document Dependencies
- Document which components reference others via
!terraform.output - Use
settings.depends_onto declare explicit dependencies - This helps with deployment ordering and troubleshooting
- Document which components reference others via
-
Cache Optimization
- Nested function results are cached to avoid redundant terraform executions
- Cache is per-component per-stack combination
- Use
skipCache: false(default) for better performance
You can mix both function types in nested scenarios:
# Component using both function types
my-component:
vars:
# Fast state read (no subprocess)
vpc_id: !terraform.state vpc vpc_id
# Formatted output (uses terraform binary)
formatted_config: !terraform.output config-service json_configAuthentication works the same way for both function types when nested:
- Both receive
AuthManagerfrom parent context - Both populate
AuthContextfor their own nested calls - Mixed nesting works seamlessly (state can call output, output can call state)
Specify an identity explicitly with the --identity flag:
atmos terraform plan component -s stack --identity core-auto/terraformThis bypasses auto-detection and uses the specified identity directly.
When no --identity flag is provided, Atmos searches for default identities in configuration:
Global default (in atmos.yaml):
auth:
identities:
core-auto/terraform:
kind: aws/permission-set
default: true
# ... configurationStack-level default (in stack manifest):
auth:
identities:
prod-admin:
default: trueBehavior:
- Single default - Uses it automatically
- Multiple defaults - Prompts user in TTY mode, fails in CI
- No defaults - Prompts user in TTY mode, uses external auth in CI
In TTY mode with no default identities, Atmos prompts once:
$ atmos terraform plan component -s stack
? Select identity:
> core-auto/terraform
core-identity/managers-team-access
prod-deployThe selected identity is stored in info.Identity to prevent double-prompting from hooks.
Use --identity=off to disable Atmos Auth and rely on external mechanisms:
atmos terraform plan component -s stack --identity=offThis allows:
- Environment variables (
AWS_PROFILE,AWS_ACCESS_KEY_ID, etc.) - Leapp credential management
- EC2 instance roles (IMDS)
- AWS credential files in standard locations
Atmos supports defining auth configuration at three levels:
- Global (in
atmos.yaml) - Applies to all components - Stack-level (in stack YAML) - Applies to stack
- Component-level (in component section) - Highest precedence
components:
terraform:
security-component:
auth:
identities:
security-team:
kind: aws/permission-set
default: true
via:
provider: aws-sso
principal:
name: SecurityTeamAccess
vars:
# component variablesThis component always uses the security-team identity, overriding global defaults.
The GetComponentAuthConfig() function:
- Starts with global auth config from
atmos.yaml - Searches for the component in stack files
- Extracts component-specific
auth:section if present - Deep merges component config with global config
- Returns merged config for authentication
Key behaviors:
- Component identities override global identities with the same name
- Component defaults take precedence over global defaults
Different identities per environment:
# stacks/prod.yaml
components:
terraform:
app:
auth:
identities:
prod-admin:
default: true
# stacks/dev.yaml
components:
terraform:
app:
auth:
identities:
dev-user:
default: trueComponent-specific permissions:
components:
terraform:
security-component:
auth:
identities:
security-team:
default: true
app-component:
auth:
identities:
app-team:
default: trueWhen evaluating nested !terraform.output functions, Atmos checks each component's configuration for an auth: section at every nesting level:
- Component has
auth:section → Merges with global auth and creates component-specific AuthManager - Component has no
auth:section → Inherits parent's AuthManager
This enables each nesting level to optionally override authentication while defaulting to the parent's credentials.
# Global auth configuration
auth:
identities:
dev-account:
default: true
kind: aws/permission-set
via:
provider: aws-sso
account: "111111111111"
permission_set: "DevAccess"
# Component 1: Uses global auth (dev account)
components:
terraform:
api-gateway:
vars:
# Reads from backend-service which is in a different account
backend_url: !terraform.output backend-service endpoint
# Component 2: Overrides auth for prod account access
components:
terraform:
backend-service:
auth:
identities:
prod-account:
default: true
kind: aws/permission-set
via:
provider: aws-sso
account: "222222222222"
permission_set: "ProdReadOnly"
vars:
# This component's outputs are in prod account
database_url: !terraform.output database connection_stringAuthentication flow:
api-gatewayevaluated with dev-account credentials (global default)- Encounters
!terraform.output backend-service→ checks forauth:section - Finds
auth:section inbackend-service→ creates new AuthManager with prod-account credentials backend-serviceconfig evaluated with prod-account credentials- Nested
!terraform.output databaseinherits prod-account credentials from parent
File: internal/exec/terraform_nested_auth_helper.go
The resolveAuthManagerForNestedComponent() function implements this logic:
func resolveAuthManagerForNestedComponent(
atmosConfig *schema.AtmosConfiguration,
component string,
stack string,
parentAuthManager auth.AuthManager,
) (auth.AuthManager, error) {
// 1. Get component config WITHOUT processing templates/functions
// (avoids circular dependency)
componentConfig, err := ExecuteDescribeComponent(&ExecuteDescribeComponentParams{
Component: component,
Stack: stack,
ProcessTemplates: false,
ProcessYamlFunctions: false,
AuthManager: nil,
})
// 2. Check for auth section in component config
authSection, hasAuthSection := componentConfig[cfg.AuthSectionName].(map[string]any)
if !hasAuthSection || authSection == nil {
// No auth config → inherit parent
return parentAuthManager, nil
}
// 3. Merge component auth with global auth
mergedAuthConfig, err := auth.MergeComponentAuthFromConfig(
&atmosConfig.Auth,
componentConfig,
atmosConfig,
cfg.AuthSectionName,
)
// 4. Create and authenticate new AuthManager with merged config
componentAuthManager, err := auth.CreateAndAuthenticateManager(
"",
mergedAuthConfig,
cfg.IdentityFlagSelectValue,
)
return componentAuthManager, nil
}-
Minimize Auth Overrides
- Use component-level auth only when necessary
- Prefer global defaults for simplicity
- Document why specific components need different credentials
-
Test Cross-Account Access
- Verify IAM permissions for cross-account output reads
- Test nested function resolution with component-level auth
- Use
atmos describe componentto verify auth resolution
-
Cache Considerations
- Each component-specific AuthManager creates a new authentication session
- Output reads are still cached per-component to avoid redundant terraform executions
- Authentication is performed once per unique component auth configuration
-
Debug Component-Level Auth
# Enable debug logging to see auth resolution ATMOS_LOGS_LEVEL=Debug atmos describe component my-component -s stackLook for log lines like:
Component has auth config, creating component-specific AuthManager Created component-specific AuthManager identityChain=[prod-account]
- Single top-level identity - When using
--identityflag, it applies at the top level only - Nested overrides respected - Component-level auth in nested components is still respected
- No re-prompting - Interactive identity selection happens once at the top level
- Transitive permissions - Top-level identity must have permissions for initial component, nested components use their own auth
File: pkg/auth/manager_helpers.go
The CreateAndAuthenticateManager() function:
- Checks if auth is explicitly disabled (
--identity=off) - Auto-detects default identity if no identity provided
- Creates AuthManager with resolved identity
- Authenticates to populate AuthContext
- Returns AuthManager with AWS credentials
File: internal/exec/utils.go
The ProcessComponentConfig() function receives the AuthManager and populates stackInfo.AuthContext:
// Populate AuthContext from AuthManager if provided.
if authManager != nil {
managerStackInfo := authManager.GetStackInfo()
if managerStackInfo != nil && managerStackInfo.AuthContext != nil {
configAndStacksInfo.AuthContext = managerStackInfo.AuthContext
}
}This makes AuthContext available to all YAML functions during component processing.
File: internal/exec/terraform_output_utils.go
The execTerraformOutput() function converts AuthContext to environment variables:
// Add auth-based environment variables if authContext is provided.
if authContext != nil && authContext.AWS != nil {
environMap = awsCloud.PrepareEnvironment(
environMap,
authContext.AWS.Profile,
authContext.AWS.CredentialsFile,
authContext.AWS.ConfigFile,
authContext.AWS.Region,
)
}This sets:
AWS_PROFILE- Profile name for credential lookupAWS_SHARED_CREDENTIALS_FILE- Path to Atmos-managed credentialsAWS_CONFIG_FILE- Path to Atmos-managed configAWS_REGION- AWS region for API callsAWS_EC2_METADATA_DISABLED=true- Disables IMDS fallback
The terraform binary reads credentials from the files specified in environment variables:
// Set environment variables on terraform executor.
err = tf.SetEnv(environMap)
// Execute terraform init with credentials.
err = tf.Init(ctx, initOptions...)
// Execute terraform output with credentials.
outputMeta, outputErr = tf.Output(ctx)The terraform binary:
- Reads
backend.tf.jsonfor backend configuration - Uses
AWS_PROFILEto find credentials inAWS_SHARED_CREDENTIALS_FILE - Assumes the role specified in backend config
- Connects to S3 backend
- Reads state and extracts output values
| Aspect | !terraform.state | !terraform.output |
|---|---|---|
| Execution | AWS SDK Go v2 directly | Terraform binary subprocess |
| Credentials | SDK config from AuthContext | Environment variables |
| Speed | Faster (no subprocess) | Slower (process spawn) |
| Backend config | Read from component metadata | Auto-generated backend.tf.json |
| Role assumption | SDK handles it | Terraform binary handles it |
Both functions receive AuthContext through stackInfo and use it to access AWS credentials.
Common authentication errors:
-
No credentials available - Occurs when AuthContext is not provided and external auth fails
failed to execute terraform: no credentials available -
Invalid credentials - Terraform cannot authenticate to S3 backend
Error: error configuring S3 Backend: InvalidAccessKeyId -
Missing S3 permissions - IAM role lacks required permissions
Error: AccessDenied: Access Denied -
Backend initialization fails - Cannot connect to S3 backend
Error: Backend initialization required -
Output not found - Requested output doesn't exist in state
The output variable requested could not be found
Set default identities to avoid specifying --identity on every command:
# atmos.yaml
auth:
identities:
dev:
default: true
# ... configurationOverride defaults for specific components:
# stacks/catalog/security.yaml
components:
terraform:
security-scanner:
auth:
identities:
security-team:
default: trueUse --identity=off when using external credential management:
# Use Leapp or environment variables
atmos terraform plan component -s stack --identity=offIn CI, either:
- Set explicit identity:
--identity ci-deploy - Configure default identity in
atmos.yaml - Use
--identity=offwith environment variables
If you see:
dial tcp 169.254.169.254:80: i/o timeout
This means:
- No AuthContext was provided (no
--identityflag) - No default identity configured
- External auth failed (no env vars, no credential files)
- System falling back to IMDS on non-EC2 instance
Solution: Configure a default identity or use --identity flag.
Enable debug logging to see credential resolution:
atmos terraform plan component -s stack --logs-level=Debug
ATMOS_LOGS_LEVEL=Debug atmos terraform plan component -s stack