Architecture¶
See also
index | iac/opentofu/explanation | iac/opentofu/how-to-guides | | index#Questions
OpenTofu is a community-driven fork of Terraform, created after HashiCorp switched the Terraform license to BSL 1.1 in August 2023. It retains the same HCL syntax, provider ecosystem, and core execution model while adding features such as native state encryption and an open registry.
Component Overview¶
graph TB
subgraph CLI["CLI Layer"]
CMD[tofu CLI]
end
subgraph CORE["OpenTofu Core"]
HCL[HCL Parser]
CFG[Config Loader]
GRAPH[Graph Builder]
EVAL[Evaluator]
PLAN[Planning Engine]
APPLY[Apply Engine]
end
subgraph PLUGINS["Plugin System"]
PV["Provider Plugins<br/>(gRPC)"]
PROV["Provisioner Plugins"]
end
subgraph STATE["State Layer"]
SF["State File<br/>(JSON / encrypted)"]
BE["Backend<br/>(S3, GCS, Azure, local, etc.)"]
ENC["Encryption Layer<br/>(AES-GCM, KMS)"]
end
subgraph REG["Registry"]
OREG["OpenTofu Registry"]
end
CMD --> HCL
HCL --> CFG
CFG --> GRAPH
GRAPH --> EVAL
EVAL --> PLAN
PLAN --> APPLY
APPLY --> PV
APPLY --> PROV
PV -->|gRPC| PV
PLAN --> SF
APPLY --> SF
SF --> ENC
ENC --> BE
CMD -->|resolve providers| OREG
Core Engine¶
The core engine is written in Go and follows the same architectural patterns established by Terraform up to version 1.5.x. Key subsystems:
HCL Parser and Config Loader¶
- Parses
.tfand.tf.jsonfiles into an intermediate configuration structure. - The config loader resolves module sources (local paths, git repositories, the OpenTofu Registry) and merges them into a single configuration tree.
- Variable definitions, output values, and provider configuration blocks are all parsed at this stage.
Graph Builder¶
- Constructs a directed acyclic graph (DAG) of resource dependencies.
- Nodes represent resources, data sources, providers, variables, and outputs.
- Edges represent explicit
depends_ondeclarations plus implicit references detected by analyzing HCL expressions. - Separate graph builders exist for plan, apply, and destroy operations:
PlanGraphBuilder-- determines what changes are needed.ApplyGraphBuilder-- executes the planned changes.DestroyGraphBuilder-- reverses the order of the apply graph.
Evaluator¶
- Walks the graph and evaluates HCL expressions.
- Handles
count,for_each, and conditional resource creation. - Resolves variable values, local values, and data source outputs.
- Produces "desired state" representations for each resource instance.
Planning Engine¶
The planning engine compares three representations:
- Configuration -- what the HCL files declare.
- Prior State -- what was recorded after the last successful apply.
- Refreshed State -- what the provider reports as the current real-world state.
From these inputs, the engine produces a plan: a list of create, update, and delete actions.
Apply Engine¶
- Takes a saved plan and walks the apply graph.
- For each node, invokes the appropriate provider gRPC call (PlanResourceChange, ApplyResourceChange).
- Updates the state file incrementally as each resource operation completes.
- If an error occurs mid-apply, partial state is preserved so the user can re-run without duplication.
Plugin Protocol¶
OpenTofu communicates with provider and provisioner plugins over gRPC (protocol version 5.x, codenamed tfplugin5).
sequenceDiagram
participant Core as OpenTofu Core
participant Plugin as Provider Plugin
Core->>Plugin: Launch process (stdout handshake)
Plugin-->>Core: gRPC address + protocol version
Core->>Plugin: Negotiate protocol version
Core->>Plugin: GetSchema
Plugin-->>Core: Resource schemas
Core->>Plugin: PrepareProviderConfig
Plugin-->>Core: Validated config
Core->>Plugin: PlanResourceChange
Plugin-->>Core: Planned change
Core->>Plugin: ApplyResourceChange
Plugin-->>Core: New state
Core->>Plugin: Graceful shutdown
Protocol compatibility
The protocol version must be negotiated during handshake. OpenTofu supports protocol v5 and v6. Providers compiled for Terraform protocol v5 are generally compatible with OpenTofu without modification.
Key protocol RPCs¶
| RPC | Purpose |
|---|---|
GetSchema |
Returns the provider's resource and data source schemas |
PrepareProviderConfig |
Validates and defaults provider configuration |
ValidateResourceTypeConfig |
Validates a resource's configuration |
PlanResourceChange |
Computes the diff between prior and proposed state |
ApplyResourceChange |
Executes the planned change against the real infrastructure |
ReadResource |
Refreshes a single resource's current state |
ImportResourceState |
Imports an existing resource into state |
Plan / Apply Lifecycle¶
sequenceDiagram
participant User
participant CLI as tofu CLI
participant Core as OpenTofu Core
participant State as State Storage
participant Prov as Provider Plugins
User->>CLI: tofu plan
CLI->>Core: Parse config
Core->>State: Load prior state
Core->>Prov: Refresh (ReadResource per instance)
Prov-->>Core: Current real-world state
Core->>Core: Compute diff (config vs refreshed state)
Core->>Core: Build execution graph
Core->>Prov: PlanResourceChange (per resource)
Prov-->>Core: Planned changes
Core-->>CLI: Plan output
CLI-->>User: Plan summary
User->>CLI: tofu apply
CLI->>Core: Load saved plan
Core->>Core: Walk apply graph
loop For each resource change
Core->>Prov: ApplyResourceChange
Prov-->>Core: New resource state
Core->>State: Write partial state
end
Core->>State: Write final state
Core-->>CLI: Apply complete
CLI-->>User: Apply summary
Key insight
The plan phase produces a deterministic, saveable plan file. The apply phase can be run separately (even on a different machine with a remote backend), guaranteeing that exactly the planned changes are executed.
State Management¶
State File Format¶
- JSON-based, containing the serialized state of every managed resource instance.
- Includes provider configuration hashes, resource dependencies, and output values.
- Sensitive values are stored in plaintext by default (unlike Pulumi's per-secret encryption), unless the encryption feature is enabled.
Backends¶
OpenTofu supports multiple backend types for remote state storage:
| Backend | State Locking | Notes |
|---|---|---|
local |
No | Default, stores terraform.tfstate on disk |
s3 |
Yes (DynamoDB or S3 object lock) | Most common for AWS deployments |
gcs |
Yes (native) | Google Cloud Storage |
azurerm |
Yes (blob lease) | Azure Blob Storage |
cos |
Yes | Tencent Cloud Object Storage |
oss |
Yes | Alibaba Cloud OSS |
pg |
Yes (PostgreSQL advisory lock) | PostgreSQL backend |
http |
Varies | Generic HTTP backend |
State Encryption¶
A feature unique to OpenTofu (not available in upstream Terraform). Encryption is configured in the terraform block:
terraform {
encryption {
key_provider "aws_kms" "my_key" {
kms_key_id = "alias/my-key"
key_spec = "AES_256"
region = "us-east-1"
}
method "aes_gcm" "default" {
keys = key_provider.aws_kms.my_key
}
state { method = method.aes_gcm.default }
plan { method = method.aes_gcm.default }
}
}
Supported key providers: PBKDF2 (passphrase), AWS KMS, GCP KMS, Azure Key Vault, OpenBao.
OpenTofu Registry¶
- The OpenTofu Registry (
registry.opentofu.org) is a community-operated index of providers and modules. - Mirrors the Terraform Registry API structure, so existing Terraform provider lookups often work unchanged.
- Providers are addressed as
registry.opentofu.org/<namespace>/<name>. - Supports GPG signature verification for module integrity.
Comparison with Terraform¶
| Aspect | OpenTofu | Terraform |
|---|---|---|
| License | MPL 2.0 | BSL 1.1 (transitioning) |
| State encryption | Native (built-in) | Not available |
| Provider protocol | gRPC v5/v6 (compatible) | gRPC v5/v6 |
| Registry | registry.opentofu.org | registry.terraform.io |
| Language server | OpenTofu LS | Terraform LS |
| Key differentiator | Community-governed, encryption-first | HashiCorp ecosystem, HCP Terraform |
Migration path
Migrating from Terraform to OpenTofu typically requires only renaming terraform blocks and installing the tofu binary. State files, provider plugins, and HCL configurations are fully compatible.
References¶
- OpenTofu GitHub repository
- Plugin protocol specification
- State encryption documentation
- Plan/apply architecture RFC
How It Works¶
HCL parsing, plan/apply execution model, provider gRPC protocol, DAG-based dependency resolution, and client-side state encryption.
Core Execution Flow¶
sequenceDiagram
participant User as User (CLI)
participant Parser as HCL Parser
participant Graph as DAG Builder
participant Plan as Plan Engine
participant Provider as Provider Plugin (gRPC)
participant State as State Backend
User->>Parser: tofu plan / tofu apply
Parser->>Parser: Parse .tf files → AST
Parser->>Graph: Extract resource references
Graph->>Graph: Build DAG (topological sort)
Graph->>Plan: Walk graph, compare desired vs actual state
Plan->>Provider: ReadResource (refresh current state)
Provider-->>Plan: Actual resource state
Plan->>Plan: Compute diff (create / update / delete / no-op)
Plan-->>User: Plan output (changes to apply)
User->>Plan: Approve apply
Plan->>Provider: ApplyResourceChange (per graph node)
Provider-->>Plan: New resource state
Plan->>State: Write updated state
Plan/Apply Execution Model¶
OpenTofu separates operations into two phases:
Plan Phase¶
- Refresh -- For each resource in state, call
ReadResourceon the provider to get the current actual state - Diff -- Compare the refreshed state against the desired configuration in
.tffiles - Graph walk -- Traverse the DAG in dependency order, computing changes for each resource
- Output -- Present the plan: which resources will be created, updated (with attribute diffs), or destroyed
Apply Phase¶
- Graph reconstruction -- Rebuild the apply graph (can differ from plan graph due to destroy dependencies)
- Parallel execution -- Walk the graph, executing independent nodes concurrently (controlled by
-parallelism, default 10) - Provider calls -- For each node, call
ApplyResourceChangeon the provider via gRPC - State write -- After each successful resource operation, write the new state to the backend
- Output -- Report results for each resource
Provider gRPC Protocol¶
Providers communicate with the OpenTofu engine via a gRPC protocol defined in the terraform-plugin-go specification:
| RPC | Purpose |
|---|---|
GetProviderSchema |
Returns resource and data source schemas |
ValidateResourceConfig |
Validates resource configuration before plan |
UpgradeResourceState |
Migrates state from older schema versions |
ReadResource |
Fetches current state of an existing resource |
PlanResourceChange |
Computes the proposed new state for an update |
ApplyResourceChange |
Creates, updates, or deletes a resource |
ConfigureProvider |
Passes provider configuration (credentials, region, and others) |
Each provider runs as a separate process, launched by the engine via exec.Command. Communication happens over a gRPC connection on a local unix socket or shared pipe.
State Encryption¶
OpenTofu adds a client-side encryption layer not present in Terraform. This layer encrypts the state blob before it is written to the backend.
sequenceDiagram
participant CLI as OpenTofu CLI
participant Enc as Encryption Layer
participant KMS as Key Provider (AWS/GCP KMS, age, PBKDF2)
participant Backend as Remote Backend (S3, GCS)
CLI->>CLI: Compute state changes
CLI->>Enc: Serialize state JSON
Enc->>KMS: Request encryption key
KMS-->>Enc: DEK (data encryption key)
Enc->>Enc: AES-GCM encrypt state
Enc->>Backend: Write encrypted state blob
Note over Enc,Backend: Attacker with backend access<br/>sees only ciphertext
Encryption Configuration¶
terraform {
encryption {
key_provider "aes_gcm" "mykey" {
keys = [
{
key = base64decode(var.encryption_key)
}
]
}
method "aes_gcm" "mymethod" {
keys = key_provider.aes_gcm.mykey
}
state {
method = method.aes_gcm.mymethod
fallback {
method = method.aes_gcm.oldmethod # For key rotation
}
}
}
}
Key Rotation¶
flowchart LR
Old["Old Key\n(fallback)"] --> Read["Decrypt with\nold key"]
Read --> Reencrypt["Re-encrypt with\nnew key"]
Reencrypt --> New["New Key\n(primary)"]
style Old fill:#c62828,color:#fff
style New fill:#2e7d32,color:#fff
OpenTofu supports a fallback key configuration for key rotation without downtime. On the next state write, the state is re-encrypted with the new primary key.
Sources¶
Benchmarks¶
Scope
Performance characteristics, scaling limits, and resource consumption for OpenTofu.
Plan/Apply Performance¶
| State Size (resources) | Plan Time | Apply Time (parallel=10) |
|---|---|---|
| 50 | < 5s | 30s-2m |
| 200 | 10-30s | 2-5m |
| 1,000 | 1-3m | 10-30m |
| 5,000 | 5-15m | 30m-2h |
Provider Performance¶
| Provider | Init Time | Resource Create | Notes |
|---|---|---|---|
| AWS | 2-5s | 5-30s per resource | API rate limits apply |
| Azure | 3-8s | 10-60s per resource | Slower API responses |
| GCP | 2-5s | 5-30s per resource | Similar to AWS |
| Kubernetes | 1-3s | 1-5s per resource | Fast for small objects |
State File Scaling¶
| Resources | State File Size | Refresh Time |
|---|---|---|
| 100 | 100KB-1MB | 10-30s |
| 1,000 | 1-10MB | 1-5m |
| 10,000 | 10-100MB | 10-30m |
Warning
Split state files at 50 MB. Larger files degrade plan performance.
Sourcing Status¶
Unsourced Performance Data
Do not plan capacity from these numbers. We estimated them from vendor documentation, community benchmarks, and engineering judgment. They do not represent controlled benchmarks with documented test conditions. Specific hardware configurations, software versions, and test methodologies were not recorded.
Use these figures as rough guidance only. For production capacity planning, run your own benchmarks against your specific workload and infrastructure.
Sources¶
Security¶
See also
index | iac/opentofu/explanation | iac/opentofu/how-to-guides | index#Questions
This note covers the security model of OpenTofu, including state encryption, provider credential handling, module verification, and backend security.
Threat Model Overview¶
| Threat Surface | Mitigation |
|---|---|
| State file exposure (at rest) | Built-in encryption (AES-GCM with KMS/PBKDF2) |
| State file tampering | Lineage + serial integrity checks |
| Provider credential leakage | Environment variables, vault integration, IAM roles |
| Supply chain (malicious modules/providers) | GPG verification, lock file checksums |
| Network interception | TLS for all remote backend and registry connections |
| Concurrent state corruption | State locking on all supported backends |
State Encryption¶
State encryption is OpenTofu's flagship security feature and the primary differentiator from Terraform OSS. It encrypts state and plan files at rest using a configurable key provider and encryption method.
Configuration¶
terraform {
encryption {
# Key provider: where the encryption key comes from
key_provider "aws_kms" "prod_key" {
kms_key_id = "alias/tofu-state-key"
key_spec = "AES_256"
region = "us-east-1"
}
# Encryption method: how data is encrypted
method "aes_gcm" "default" {
keys = key_provider.aws_kms.prod_key
}
# Apply encryption to state and plan files
state { method = method.aes_gcm.default }
plan { method = method.aes_gcm.default }
}
}
Supported Key Providers¶
| Key Provider | Use Case | Key Generation |
|---|---|---|
pbkdf2 |
Local/dev environments | Passphrase-derived (configurable iterations, SHA-256/512) |
aws_kms |
AWS deployments | AWS KMS generates data keys |
gcp_kms |
GCP deployments | Google Cloud KMS generates data keys |
azure_keyvault |
Azure deployments | Azure Key Vault key wrapping |
openbao |
Self-hosted vault | OpenBao (Vault fork) transit secrets engine |
Encryption Method: AES-GCM¶
- Algorithm: AES-256-GCM (Galois/Counter Mode).
- Provides both confidentiality and authenticity (AEAD).
- Each encryption operation uses a unique nonce.
- Encrypted data is stored as a base64-encoded envelope containing the ciphertext, nonce, and key provider metadata.
Migration from Unencrypted State¶
OpenTofu supports a fallback mechanism for migrating existing unencrypted states:
terraform {
encryption {
key_provider "aws_kms" "my_key" {
kms_key_id = "alias/my-key"
key_spec = "AES_256"
}
method "aes_gcm" "new_method" {
keys = key_provider.aws_kms.my_key
}
method "unencrypted" "legacy" {}
state {
method = method.aes_gcm.new_method
fallback = method.unencrypted.legacy
}
}
}
Key rotation
To rotate encryption keys, add a new key provider and use the fallback field to reference the old key during the transition period. OpenTofu will decrypt with the fallback key and re-encrypt with the new key on the next write.
Provider Credential Management¶
Providers (AWS, GCP, Azure, and others) require credentials to interact with cloud APIs. OpenTofu does not store provider credentials in state or configuration files.
Credential Sources (AWS Example)¶
| Priority | Source | Recommended For |
|---|---|---|
| 1 | Static credentials in provider block | Not recommended (do not use in production) |
| 2 | Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) |
CI/CD pipelines |
| 3 | Shared credentials file (~/.aws/credentials) |
Local development |
| 4 | IAM role for EC2/ECS | Compute-hosted OpenTofu |
| 5 | Assume role (STS) | Cross-account access |
Never hardcode credentials
Do not embed credentials in .tf files. Use environment variables, IAM roles, or a secrets manager. The sensitive attribute can mark variables as sensitive to prevent CLI output from displaying values.
Vault Integration¶
OpenTofu can retrieve dynamic credentials from HashiCorp Vault (or OpenBao) using the Vault provider:
provider "vault" {
address = "https://vault.YOUR_DOMAIN:8200"
}
data "vault_generic_secret" "aws_creds" {
path = "aws/creds/my-role"
}
provider "aws" {
access_key = data.vault_generic_secret.aws_creds.data["access_key"]
secret_key = data.vault_generic_secret.aws_creds.data["secret_key"]
}
Module Verification¶
Lock File Integrity¶
The .terraform.lock.hcl file pins provider versions and records SHA256 hashes of provider packages:
provider "registry.opentofu.org/hashicorp/aws" {
version = "5.45.0"
constraints = "~> 5.0"
hashes = [
"h1:abc123...",
"zh:def456...",
]
}
h1:hashes are computed from the provider zip content.zh:hashes are computed from the provider zip content using a platform-specific scheme.- Running
tofu providers lockgenerates this file. Commit the file to version control.
GPG Verification¶
The OpenTofu Registry supports GPG signature verification for modules. When enabled, OpenTofu verifies that module packages are signed by the expected author before downloading and extracting them.
Backend Security¶
Remote State Backends¶
| Backend | Encryption at Rest | Encryption in Transit | State Locking |
|---|---|---|---|
| S3 | SSE-S3, SSE-KMS, SSE-C | TLS (HTTPS) | DynamoDB or S3 object lock |
| GCS | Google-managed or CMEK | TLS | Native object versioning + generation |
| Azure Blob | SSE (Microsoft-managed or CMK) | TLS | Blob lease |
| PostgreSQL | Disk encryption (pg configuration) | TLS (required) | Advisory locks |
| Consul | Optional (base64) | TLS (recommended) | Session-based locks |
State Locking¶
State locking prevents concurrent writes that can corrupt state:
- All remote backends except
localandhttpsupport state locking. - Locks are acquired before any write operation (plan, apply, refresh).
- If a lock cannot be acquired, the operation fails with a clear error message.
tofu force-unlockis available as a last resort when locks are stuck (use with caution).
Backend Authentication¶
Each backend type has its own authentication mechanism:
# S3 backend with IAM role assumption
terraform {
backend "s3" {
bucket = "my-tofu-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "tofu-locks"
}
}
The encrypt = true option enables S3 server-side encryption (SSE-S3 by default, SSE-KMS if kms_key_id is specified). This is separate from OpenTofu's built-in state encryption and provides a defense-in-depth layer.
Sensitive Data in State¶
Even with encryption, certain best practices apply:
- Use
sensitive = trueon variables and outputs to prevent values from appearing in CLI output. - Review state files for inadvertently stored sensitive values (some providers can store passwords in plaintext attributes).
- Restrict file system permissions on local state files (
chmod 600 terraform.tfstate). - Use remote backends with encryption for all production workloads.
- Consider using
datasources to fetch secrets at plan/apply time rather than storing them in configuration.
Security Checklist¶
- Enable state encryption with a KMS-backed key provider
- Use remote backend with state locking for all team environments
- Enable backend encryption at rest (S3 SSE-KMS, GCS CMEK, and others)
- Never hardcode provider credentials in
.tffiles - Use IAM roles or Vault for dynamic credential generation
- Commit
.terraform.lock.hclto version control - Mark all sensitive variables and outputs with
sensitive = true - Restrict local state file permissions to owner-only
- Use GPG verification for modules from external sources
- Rotate encryption keys periodically using the fallback mechanism