The IaC sidecar launches the Terraform or OpenTofu engine with its
entire container environment inherited, so the credentials below are
ordinary environment variables set in services/iac/.env. This page
is the curated, per-scenario minimum for each cloud provider and state
backend; the exhaustive list of every variable each provider and
backend reads is in Complete variable
reference below.
None of the tables below carries the variable the request’s cloud
scope (scope_id) supplies: ARM_SUBSCRIPTION_ID and GOOGLE_PROJECT
arrive with each request and overwrite whatever the container sets, so
set the credential variables in each row and leave the scope to the
request. Azure and GCP are the only providers with such a variable;
AWS, OCI, and Kubernetes commands run on the container’s ambient
credentials alone. See
Cloud
credentials for the IaC engine for where these files fit in the
deployment.
Azure — provider minimum
| Scenario | Required |
|---|---|
Service principal + secret |
|
Service principal + certificate |
|
OIDC (GitHub Actions) |
|
OIDC (Azure DevOps) |
above, plus |
Managed identity (system-assigned) |
|
Managed identity (user-assigned) |
|
AKS workload identity |
|
Local development ( |
none |
No row carries ARM_SUBSCRIPTION_ID, even though azurerm v4+ makes it
mandatory: init, plan, apply, and import carry a scope_id,
and the sidecar injects it as ARM_SUBSCRIPTION_ID into the engine
subprocess for that one command, overriding whatever the container
holds.
OIDC rows leave out ARM_OIDC_REQUEST_URL/_TOKEN because the CI
runner injects them.
Import discovery (/v1/import/scope-resource-ids) reads the
credential variables through azure-identity — the subscription it
lists comes from the request’s scope_id, never from the environment.
Three exceptions: the az login row has no equivalent (discovery
needs a service principal, a managed identity, or a workload
identity); the OIDC rows need the assertion itself in ARM_OIDC_TOKEN
or ARM_OIDC_TOKEN_FILE_PATH, because the ARM_OIDC_REQUEST_URL
exchange the CI runner performs is not reimplemented here; and only
the direct ARM_CLIENT_ID/ARM_CLIENT_SECRET forms are read, not the
ARM_CLIENT_ID_FILE_PATH/ARM_CLIENT_SECRET_FILE_PATH ones the
provider also accepts (see Complete
variable reference) — a deployment using those runs every command
fine and fails discovery with no Azure credentials configured.
ARM_ADO_PIPELINE_SERVICE_CONNECTION_ID is likewise provider-only, and
ARM_ENVIRONMENT is ignored: discovery talks to the public cloud
only.
AWS — provider minimum
| Scenario | Required |
|---|---|
Static keys |
|
Temporary or STS credentials |
above, plus |
Named profile |
|
OIDC (GitHub Actions) |
|
OIDC (manual) |
|
EKS IRSA |
|
EC2 instance role |
|
ECS task role |
|
LocalStack |
|
AWS_REGION is the only universal requirement, and never an account
id — unlike Azure and GCP, nothing here is injected from scope_id,
because no environment variable redirects the provider to an account.
Every command runs against whatever account the credentials above
resolve to, so making them agree with the scope_id callers send is
the deployment’s job.
Import discovery additionally requires AWS Resource Explorer to be
enabled for the account, with an aggregator index and a default view.
Every row above works for it except LocalStack, which has no Resource
Explorer to query, since boto3’s default chain resolves the same
credentials the provider does. Two details are discovery’s alone: it
reads the region from AWS_REGION/AWS_DEFAULT_REGION only, so the
region a profile names does not satisfy it, and it does not select the
account either — it calls STS and fails if the resolved account is not
the requested scope_id.
GCP — provider minimum
| Scenario | Required |
|---|---|
Service account key |
|
Workload Identity Federation |
|
WIF plus impersonation |
above, plus |
GCE, GKE, or Cloud Run attached service account |
none |
Local development ( |
none |
Short-lived token |
|
GOOGLE_REGION/GOOGLE_ZONE are optional, but leaving them out forces
explicit region/zone on many resources.
No row carries GOOGLE_PROJECT either, for the same reason as
ARM_SUBSCRIPTION_ID above: the sidecar injects the request’s
scope_id under that name for init, plan, apply, and import.
It sets GOOGLE_PROJECT specifically, which outranks the
GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT/CLOUDSDK_CORE_PROJECT aliases
(see Complete variable reference), so
an ambient alias cannot quietly win. Set the credential variables in
each row and leave the project to the request.
Import discovery reads the credential variables through
google-auth, including GOOGLE_OAUTH_ACCESS_TOKEN and
GOOGLE_IMPERSONATE_SERVICE_ACCOUNT, and falls back to
application-default credentials exactly as the provider does — so the
gcloud auth application-default login row works for it too. The
project it lists is the request’s scope_id; the project that
application-default credentials name is deliberately discarded. It
needs cloudasset.googleapis.com and
cloudresourcemanager.googleapis.com enabled on that project.
OCI — provider minimum
| Scenario | Required |
|---|---|
API key |
|
Config file profile |
|
Instance principal |
|
Resource principal (Functions) |
|
OKE workload identity |
|
Add TF_VAR_compartment_ocid in practice — not a provider setting,
but nearly every resource needs it.
Kubernetes — provider minimum
Variable names below are the Terraform kubernetes provider’s own, as
documented in its registry page; the helm provider accepts the same
set.
| Scenario | Required |
|---|---|
Kubeconfig file |
|
Bearer token |
|
In-cluster service account |
none — the provider reads the projected service-account token and CA when the sidecar runs inside the cluster it manages |
No scope variable exists for this provider. The sidecar’s scope overlay
covers azure and gcp only, so the value the wizard collects as
scope_id is stored with the session and hashed into the state project
id, but never reaches the engine environment: every command runs
against whatever cluster the variables above select. A
mounted kubeconfig must be readable by the unprivileged user the image
runs as (kumoss, uid and gid 10001), and any paths inside it must
resolve inside the container.
Import discovery (/v1/import/scope-resource-ids) has no Kubernetes
implementation: it answers exit code 2 with no scope discovery for
provider 'kubernetes'. Importing existing objects therefore needs the
resource ids supplied by hand.
Backend minimums
azurerm
| Scenario | Required env |
|---|---|
Storage account key |
|
SAS token |
|
Service principal + Entra ID (RBAC) |
|
OIDC + Entra ID |
|
Managed identity |
|
Local development ( |
none — plus |
Non-environment HCL always needed: resource_group_name,
storage_account_name, container_name, key.
Add subscription_id too when the state storage account lives in a
different subscription than the resources being managed. The backend
resolves the account through ARM_SUBSCRIPTION_ID, and init runs
with that variable overlaid from the request’s scope_id — so a
backend that does not name its subscription explicitly looks for the
storage account in the workload’s subscription and fails. Pin it in
the backend block or in the file IAC_BACKEND_CONFIG points at. The
ARM_ACCESS_KEY and ARM_SAS_TOKEN rows are unaffected: they address
the account directly rather than resolving it through a subscription.
s3
| Scenario | Required env |
|---|---|
Static keys |
|
Named profile |
|
OIDC, IRSA, or instance role |
none |
Separate state account |
|
Region comes from the backend block’s region, not AWS_REGION — so
a minimal CI setup can need zero backend environment variables.
Non-environment HCL: bucket, key, region, and
use_lockfile = true (Terraform 1.10+) or dynamodb_table.
gcs
| Scenario | Required env |
|---|---|
Service account key |
|
Workload Identity Federation |
|
Attached service account, or |
none |
Separate state project |
|
No project or region needed — bucket names are globally unique.
Non-environment HCL: bucket, prefix.
Copy-paste: typical CI setups
These are the sets for this service. The scope variable is
deliberately absent: ARM_SUBSCRIPTION_ID and GOOGLE_PROJECT arrive
with each request’s scope_id. Add them only when the engine runs
outside this service.
export ARM_USE_OIDC=true
export ARM_USE_AZUREAD=true
export ARM_TENANT_ID=...
export ARM_CLIENT_ID=...
export AWS_REGION=eu-west-1
# aws-actions/configure-aws-credentials supplies the rest
export GOOGLE_REGION=europe-west1
# google-github-actions/auth sets GOOGLE_APPLICATION_CREDENTIALS
Three things that trip people up
Azure needs the most variables by a wide margin. AWS and GCP collapse to one or two variables in keyless CI because their SDKs auto-discover identity and the account or project is either implicit or a single value. Azure always needs tenant plus client plus subscription explicitly — though under this service the subscription arrives with the request rather than from the environment.
Backend environment variables are usually a subset of provider
ones — with ARM_ACCESS_KEY and GOOGLE_BACKEND_CREDENTIALS being
the deliberate exceptions for splitting identities. If the full
provider set is configured, init almost always works too; the
reverse is not true.
The scope this service injects reaches the azurerm backend as well,
because the provider and the backend read the same
ARM_SUBSCRIPTION_ID. Split state — a state account outside the
managed subscription — therefore needs subscription_id in the
backend config, not just provider credentials. GCP has no equivalent
problem: the gcs backend needs no project.
Complete variable reference
Every provider and backend environment variable
The tables above are a curated, per-scenario minimum. This section lists every variable each provider and backend reads, grouped by cloud, for cases the minimum sets above do not cover.
=== Azure (azurerm, azuread, azapi — all share the ARM_ prefix)
| Variable | Group | Purpose |
|---|---|---|
|
Core |
Target subscription. Mandatory in
provider v4+. Injected per request from |
|
Core |
Entra ID tenant |
|
Core |
App/service-principal or managed-identity client id |
|
Core |
Read the client id from a file.
Provider only — import discovery reads |
|
Core |
|
|
Core |
Custom metadata endpoint (Azure Stack) |
|
Core |
Comma-separated, for cross-tenant |
|
Service principal + secret |
Client secret |
|
Service principal + secret |
Read the
secret from a file. Provider only — import discovery reads
|
|
Service principal + certificate |
Base64 PKCS#12 bundle |
|
Service principal + certificate |
Path
to |
|
Service principal + certificate |
Certificate password |
|
OIDC |
Enable OIDC/workload identity federation |
|
OIDC |
ID token value |
|
OIDC |
Path to the ID token |
|
OIDC |
Token request URL (GitHub Actions or Azure DevOps) |
|
OIDC |
Bearer token for the above |
|
OIDC |
Azure DevOps service connection |
|
Managed identity |
Enable IMDS authentication |
|
Managed identity |
Override the IMDS endpoint |
|
AKS |
Workload identity in AKS pods |
|
AKS |
Path of the projected
service-account token, set by the AKS workload-identity webhook. Read
by the provider and by import discovery
( |
|
Local development |
Use the |
|
Behavior |
|
|
Behavior |
Deprecated predecessor of the above |
|
Behavior |
Entra ID instead of shared keys for the storage data plane |
|
Telemetry |
Partner attribution GUID |
|
Telemetry |
Opt out |
|
Telemetry |
Opt out |
=== AWS (aws)
| Variable | Group | Purpose |
|---|---|---|
|
Static keys |
Access key |
|
Static keys |
Secret key |
|
Static keys |
Required for temporary or STS credentials |
|
Core |
Region — mandatory unless set in the provider block |
|
Core |
Fallback |
|
Profiles |
Named profile |
|
Profiles |
Override
|
|
Profiles |
Override |
|
Profiles |
Legacy (provider v3 only) |
|
Assume role / OIDC |
Role to assume |
|
Assume role / OIDC |
Session name |
|
OIDC |
IRSA / GitHub Actions token path |
|
ECS |
Task role |
|
ECS / EKS Pod Identity |
Credential endpoint |
|
ECS / EKS |
Token for the above |
|
ECS / EKS |
Token from a file |
|
IMDS |
|
|
IMDS |
Custom IMDS endpoint |
|
IMDS |
|
|
Behavior |
Retry count |
|
Behavior |
|
|
Behavior |
Custom CA for proxies |
|
Behavior |
Global endpoint override |
|
Behavior |
Per-service override (LocalStack) |
|
Behavior |
FIPS endpoints |
|
Behavior |
IPv6 dual-stack |
|
Behavior |
|
No AWS_ACCOUNT_ID — the account is implicit in the credential.
=== GCP (google, google-beta)
| Variable | Group | Purpose |
|---|---|---|
|
Key-based |
JSON key contents or a path |
|
Key-based |
Alias |
|
Key-based |
Alias |
|
ADC / WIF |
Key file or Workload Identity Federation config |
|
Token |
Short-lived bearer token |
|
Impersonation |
Target service account to impersonate |
|
Targeting |
Default project — required unless set
in the provider block. Injected per request from |
|
Targeting |
Alias |
|
Targeting |
Alias |
|
Targeting |
Alias |
|
Targeting |
Default region |
|
Targeting |
Default zone |
|
Quota |
Project billed for API calls |
|
Quota |
Enables the above |
|
Behavior |
Audit log reason string |
|
Behavior |
Per-request timeout |
|
Behavior |
Non- |
=== OCI (oracle/oci)
An unusual quirk: this provider uses TF_VAR_* names as its own
environment-variable defaults, which is why OCI examples look
different from every other cloud.
| Variable | Group | Purpose |
|---|---|---|
|
API key |
Tenancy OCID |
|
API key |
User OCID |
|
API key |
Public key fingerprint |
|
API key |
Path to the PEM private key |
|
API key |
PEM contents inline |
|
API key |
Passphrase |
|
Core |
For example |
|
Core |
|
|
Config file |
Profile in
|
|
Config file |
Override the config path |
|
Config file |
Profile (SDK/CLI level) |
|
Resource principal |
Set by the OCI Functions runtime |
|
Resource principal |
Session token |
|
Resource principal |
Private key |
|
Resource principal |
Region |
Compartment OCID is a resource argument, not an environment
variable — commonly passed as TF_VAR_compartment_ocid, an ordinary
Terraform variable rather than a provider setting.
=== Kubernetes (kubernetes, helm)
Names as documented by the upstream kubernetes provider; no scope
variable is injected for this provider.
| Variable | Group | Purpose |
|---|---|---|
|
Kubeconfig |
Path to a kubeconfig file |
|
Kubeconfig |
Several kubeconfig paths, merged |
|
Kubeconfig |
Context to use instead of the current one |
|
Kubeconfig |
Override the context’s user |
|
Kubeconfig |
Override the context’s cluster |
|
Direct |
API server URL |
|
Direct |
Bearer token |
|
Direct |
Basic authentication |
|
Direct |
PEM client certificate and key |
|
Direct |
PEM CA bundle of the API server |
|
Direct |
Skip server certificate verification |
|
Direct |
Server name for certificate verification |
|
Behavior |
HTTP proxy for API calls |
=== Backend storage environment variables
| Variable | Backend | Purpose |
|---|---|---|
|
azurerm |
Subscription of the state storage
account. Overlaid from |
|
azurerm |
Tenant |
|
azurerm |
Service principal / managed identity |
|
azurerm |
Service principal secret |
|
azurerm |
Service principal certificate |
|
azurerm |
Certificate password |
|
azurerm |
Storage account key (simplest CI option) |
|
azurerm |
SAS token alternative |
|
azurerm |
Entra ID authentication to blob instead of keys |
|
azurerm |
Managed identity |
|
azurerm |
Override IMDS |
|
azurerm |
OIDC / Workload Identity Federation |
|
azurerm |
ID token |
|
azurerm |
ID token path |
|
azurerm |
Token request URL |
|
azurerm |
Bearer for the above |
|
azurerm |
AKS workload identity |
|
azurerm |
Cloud environment |
|
azurerm |
Azure Stack metadata |
|
azurerm |
Snapshot the blob before writing |
|
s3 |
Access key |
|
s3 |
Secret key |
|
s3 |
Temporary credentials |
|
s3 |
Bucket region |
|
s3 |
Named profile (often a different one than the provider) |
|
s3 |
Credentials file path |
|
s3 |
Config file path |
|
s3 |
Role to assume for state access |
|
s3 |
OIDC token |
|
s3 |
Custom CA |
|
s3 |
Custom S3 endpoint (MinIO, OCI Object Storage) |
|
s3 |
Custom DynamoDB endpoint (legacy locking) |
|
s3 |
Custom STS endpoint |
|
s3 |
Legacy alias |
|
s3 |
Legacy alias |
|
s3 |
Legacy IMDS override |
|
gcs |
Backend-only credentials (key JSON or path) |
|
gcs |
Falls back to the provider variable |
|
gcs |
ADC / WIF config |
|
gcs |
Alias |
|
gcs |
Alias |
|
gcs |
Bearer token |
|
gcs |
Impersonation |
|
gcs |
Customer-supplied AES-256 key for state |
|
gcs |
Cloud KMS key for state |
=== Caveats worth internalizing
Backend location is never settable through environment variables.
Only credentials are. bucket/key, storage_account_name/
container_name, bucket/prefix must be literal HCL,
-backend-config=…, or a .tfbackend file. TF_VAR_* does not
work for backends because backend blocks are evaluated before
variables exist.
terraform init -backend-config=env/prod.s3.tfbackend
gcs needs no project — the bucket namespace is global. azurerm
and s3 both need region or subscription context.
Backend and provider credentials can, and often should, differ.
GOOGLE_BACKEND_CREDENTIALS and AWS_PROFILE switching exist
precisely for this. Azure has no ARM_BACKEND_* equivalent, so
separation there means either ARM_ACCESS_KEY for the backend
(leaving the service-principal variables for the provider) or wrapper
scripts that swap the environment between init and plan.
Version drift is real. ARM_SUBSCRIPTION_ID became mandatory in
azurerm v4; ARM_SKIP_PROVIDER_REGISTRATION was superseded;
dynamodb_table gave way to use_lockfile. Treat this section as a
working reference and confirm against the registry documentation for
the provider version pinned in the deployment.
Next steps
-
Configure state backends for how these credentials fit into the backend model Kumoss chooses.
-
Environment variables and secrets for every other variable the stack reads.