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, GOOGLE_PROJECT,
TF_VAR_compartment_ocid and TF_VAR_aws_account_id 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.
Kubernetes and VMware vSphere commands run on the container’s ambient
credentials alone, and AWS commands are also refused when those
credentials belong to an account other than scope_id. 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 (or AWS_DEFAULT_REGION) is required in every row: the
account check below needs it, and it is the default region for
generated code.
Account. Do not set TF_VAR_aws_account_id; the sidecar injects
scope_id as TF_VAR_aws_account_id for init, plan, apply, and
import. The generated code passes it to the provider, which then
refuses any other account:
variable "aws_account_id" {
type = string # no default: the engine supplies it
}
provider "aws" {
allowed_account_ids = [var.aws_account_id]
}
Before those four commands the sidecar also calls STS
GetCallerIdentity with the container’s credentials, and ends the
command with exit code 1, without starting the engine, when the account
is not scope_id. One deployment therefore serves one AWS account; a
hub deployment that assumes a role into each target account is refused.
For LocalStack the scope_id is its account id, 000000000000.
Region. The region is not part of the scope. The provider takes the first of:
-
the
regionargument of theprovider "aws"block; -
AWS_REGION, thenAWS_DEFAULT_REGION, on the iac container; -
the region of the AWS profile in use.
New generated code sets no provider region, so every session that does
not name a region lands in the container’s AWS_REGION. A request that
names one gets the per-resource region argument (provider v6) or a
provider alias. An existing provider region is kept as it is, so with
AWS_REGION=us-east-1 on the container:
variable "region" {
default = "eu-west-1"
}
provider "aws" {
region = var.region # eu-west-1 wins; AWS_REGION is ignored
}
A region variable the provider block does not reference changes
nothing: the provider falls back to AWS_REGION.
Running the code outside Kumoss. Code merged to a repository declares
aws_account_id with no default, so a pipeline that runs it needs
TF_VAR_aws_account_id=<account id> (and a region) in its own
environment.
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 |
|
Do not set TF_VAR_compartment_ocid. For OCI the request’s scope_id
is the compartment OCID, and the sidecar injects it as
TF_VAR_compartment_ocid for init, plan, apply, and import,
overriding the container value. It is not a provider setting: it fills
the compartment_ocid input variable the generated code declares.
Because nothing confines the provider to a compartment, the core also
reads every OCI plan and fails validation when it creates a resource in
a compartment other than scope_id. The generator then rewrites the
code:
resource "oci_core_vcn" "main" {
compartment_id = var.compartment_ocid # accepted
# compartment_id = "ocid1.compartment.oc1..x" # rejected unless it is scope_id
}
A compartment the same plan creates is accepted, and so are resources
created in the tenancy (root compartment). Only plain creations are
checked: an update or replacement that moves an existing resource to
another compartment is not caught. Code merged to a repository declares compartment_ocid with no
default, so a pipeline that runs it outside Kumoss needs
TF_VAR_compartment_ocid.
TF_VAR_tenancy_ocid stays in the container, because it belongs to the
credential and one deployment serves one tenancy. The API key row
already requires it. Add it to the other rows too when requests touch
identity resources: the generated code reads it for resources that live
in the root compartment (dynamic groups, tenancy-level policies,
top-level compartments) and for the home-region lookup behind every
identity write (compartments, policies, dynamic groups, groups, tag
namespaces), including a compartment nested under the request’s
compartment. Other requests do not need it; availability-domain lookups
use the request’s compartment.
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', so an import round on kubernetes ends with a
report that says so and imports nothing.
VMware vSphere — provider minimum
Variable names below are the vmware/vsphere provider’s own (formerly
published as hashicorp/vsphere); the shipped prompt seeds pin
version = "~> 2.10", and the provider runs under both OpenTofu and
Terraform.
| Scenario | Required |
|---|---|
vCenter service account |
|
Lab vCenter with a self-signed certificate |
above, plus |
Leave VSPHERE_ALLOW_UNVERIFIED_SSL unset in any other environment.
Use a dedicated vCenter service account in an SSO group, holding a
custom role scoped to the folders, resource pools, datastores, and
networks Kumoss manages; never administrator@vsphere.local.
No scope variable exists for this provider. The value the wizard
collects as scope_id is the vSphere datacenter name, which the
generated code reads through a data "vsphere_datacenter" data source
argument rather than a provider setting. The sidecar’s scope overlay
covers azure and gcp only, so every command runs against whatever
vCenter the variables above point at; the deployment must make that
vCenter and the session’s datacenter agree. There is no vSphere state
backend: state stays in whichever backend the repository already
declares.
Import discovery (/v1/import/scope-resource-ids) has no vSphere
implementation: it answers exit code 2 with no scope discovery for
provider 'vmware', so an import round on vmware ends with a report
that says so and imports nothing.
A storage policy does not replace datastore placement.
storage_policy_id is applied in addition to datastore_id or
datastore_cluster_id, which the pinned provider still requires on
every virtual machine; the shipped vSphere prompts instruct the model
accordingly.
|
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
Provider versions in generated code
The shipped prompt seeds pin these majors in new workspaces:
| Provider | Constraint |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
A workspace that already pins a provider keeps its pin, and the code
generated for it uses that version’s syntax. The engine runs init
without -upgrade, so rewriting the pin would fail against the
workspace’s .terraform.lock.hcl. For example, a repository on
~> 5.0 of hashicorp/aws keeps ~> 5.0 and gets the v5 forms
(data.aws_region.current.name, not .region); moving it to v6 is an
upgrade to plan by hand. Pins can be changed in the
prompt seeds.
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: the sidecar’s account check needs it, and it is the region for code whose provider block sets none |
|
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 a provider setting —
passed as TF_VAR_compartment_ocid, an ordinary Terraform input
variable. The sidecar injects it per request from scope_id; do not set
it in the container.
=== 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 |
=== VMware vSphere (vmware/vsphere)
Names as documented by the upstream vsphere provider; no scope
variable is injected for this provider. The datacenter is a data-source
argument, not an environment variable.
| Variable | Group | Purpose |
|---|---|---|
|
Core |
vCenter Server FQDN |
|
Core |
vCenter user name |
|
Core |
vCenter password |
|
Core |
Skip certificate verification — lab self-signed certificates only; leave unset otherwise |
|
Session |
Persist vSphere sessions to disk between runs |
|
Session |
Directory for persisted VIM (SOAP) sessions |
|
Session |
Directory for persisted REST sessions |
|
Behavior |
API operation timeout, in minutes |
=== 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.