WAF-OPS-020 โ Infrastructure as Code Enforced
Description
All cloud infrastructure MUST be defined as code using an IaC tool (Terraform, Pulumi, AWS CDK, or equivalent). Manual resource creation through cloud consoles MUST be prohibited for all production and staging environments. Infrastructure definitions MUST be version-controlled, reviewed through pull requests, and deployed exclusively through CI/CD pipelines.
Rationale
| Risk | Description |
|---|---|
Risk |
Manually managed infrastructure drifts from documentation and is hard to restore. |
Risk |
Missing IaC practices prevent versioning, peer review, and disaster recovery. |
Requirements
-
All cloud infrastructure MUST be defined as code using an IaC tool (Terraform, Pulumi, AWS CDK, or equivalent).
-
Manual resource creation through cloud consoles MUST be prohibited for all production and staging environments.
-
Infrastructure definitions MUST be version-controlled, reviewed through pull requests, and deployed exclusively through CI/CD pipelines.
Implementation Guidance
-
Document the requirement in a version-controlled policy or standard.
-
Assign ownership and establish a review cadence (e.g., quarterly).
-
Implement the required configuration as code in Terraform or equivalent IaC.
Maturity Levels
| Level | Name | Criteria |
|---|---|---|
L1 |
Ad hoc โ control not consistently applied |
* Manual evidence exists for some workloads, but coverage is unknown. |
L2 |
Standardised โ policy defined and communicated |
* A version-controlled policy, ADR, or standard describes the requirement. |
L3 |
Integrated โ automated validation in CI/CD |
* Terraform checks run automatically on pull requests and report results. |
L4 |
Auditable โ metrics and exceptions tracked |
* Control coverage, failures, and approved exceptions are reported quarterly. |
L5 |
Optimised โ continuous improvement based on data |
* Control effectiveness is measured and iteratively improved. |
Checks
waf-ops-020.tf.aws.s3-terraform-state-backend
Engine: terraform | Provider: aws | Automated: Yes
Terraform state must use S3 backend with DynamoDB locking
Terraform remote state MUST be stored in an S3 bucket with versioning enabled and a DynamoDB table for state locking. This prevents concurrent state modifications and enables state history and rollback.
Scope: terraform
Assertions:
* backend attribute exists โ Terraform must configure a remote backend (not local state).
Remediation: Configure the S3 backend in your Terraform root module’s terraform block. Create the S3 bucket with versioning enabled and a DynamoDB table for locking.
terraform {
backend "s3" {
bucket = "my-org-terraform-state"
key = "payment-service/production/terraform.tfstate"
region = "eu-central-1"
dynamodb_table = "terraform-state-lock"
encrypt = true
}
}
resource "aws_s3_bucket_versioning" "state" {
bucket = "my-org-terraform-state"
versioning_configuration {
status = "Enabled"
}
}
terraform {
# No backend configured โ local state only
# WAF-OPS-020 Violation: no remote state
}
waf-ops-020.tf.aws.s3-state-bucket-versioning
Engine: terraform | Provider: aws | Automated: Yes
Terraform state S3 bucket must have versioning enabled
The S3 bucket used for Terraform remote state MUST have versioning enabled to allow state history, audit, and rollback capabilities.
Scope: resource โ aws_s3_bucket_versioning
Assertions:
* versioning_configuration.status equals Enabled โ S3 state bucket versioning must be Enabled.
Remediation: Add an aws_s3_bucket_versioning resource with status = "Enabled" for your Terraform state bucket.
resource "aws_s3_bucket_versioning" "state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_versioning" "state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Suspended"
}
}
waf-ops-020.tf.azurerm.terraform-state-storage
Engine: terraform | Provider: azurerm | Automated: Yes
Azure Terraform state must use Azure Storage Account backend
Terraform state for Azure deployments MUST be stored in an Azure Storage Account with blob versioning enabled. This ensures state durability and collaboration.
Scope: resource โ azurerm_storage_account
Assertions:
* blob_properties attribute exists โ Storage account must have blob_properties configured.
Remediation: Configure blob versioning on the Storage Account used for Terraform state.
resource "azurerm_storage_account" "terraform_state" {
name = "myorgtfstate"
resource_group_name = azurerm_resource_group.tfstate.name
location = var.location
account_tier = "Standard"
account_replication_type = "GRS"
blob_properties {
versioning_enabled = true
}
}
resource "azurerm_storage_account" "terraform_state" {
name = "myorgtfstate"
resource_group_name = azurerm_resource_group.tfstate.name
location = var.location
account_tier = "Standard"
account_replication_type = "LRS"
# No blob versioning โ WAF-OPS-020 Violation
}
waf-ops-020.tf.google.gcs-state-backend
Engine: terraform | Provider: google | Automated: Yes
GCP Terraform state must use Cloud Storage backend with versioning
Terraform state for GCP deployments MUST be stored in a google_storage_bucket configured as a backend with uniform_bucket_level_access and versioning enabled. This prevents concurrent state modifications and provides state history.
Scope: resource โ google_storage_bucket
Assertions:
* versioning attribute exists โ GCP Terraform state bucket must have versioning enabled.
Remediation: Configure the gcs backend in the Terraform root module and enable versioning on the state bucket. Use a dedicated project for state storage with restricted IAM access.
terraform {
backend "gcs" {
bucket = "my-org-terraform-state"
prefix = "payment-service/production"
}
}
resource "google_storage_bucket" "terraform_state" {
name = "my-org-terraform-state"
location = "EU"
storage_class = "STANDARD"
versioning {
enabled = true
}
uniform_bucket_level_access = true
}
terraform {
# No backend configured โ local state only
# WAF-OPS-020 Violation: no remote state
}
waf-ops-020.tf.stackit.provider-version-constrained
Engine: terraform | Provider: stackit | Automated: Yes
STACKIT Terraform provider must have version constraint
STACKIT Terraform provider must have version constraint. This check validates the Terraform configuration uses the expected resource and attribute.
Scope: terraform
Assertions:
* required_providers.stackit.source attribute exists โ STACKIT provider source must be declared.
* required_providers.stackit.version attribute exists โ STACKIT provider version must be constrained.
Remediation: Review the Terraform configuration for the listed resource types and ensure the required attribute or tag is present. See the control narrative for provider-specific guidance.
# Example resource meets the requirement.
resource "provider" "example" {
# required attribute/tag/label present
}
# Example resource does not meet the requirement.
resource "provider" "example" {
# Missing required attribute/tag/label
}
waf-ops-020.tf.ovh.provider-version-constrained
Engine: terraform | Provider: ovh | Automated: Yes
OVHcloud Terraform provider must have version constraint
OVHcloud Terraform provider must have version constraint. This check validates the Terraform configuration uses the expected resource and attribute.
Scope: terraform
Assertions:
* required_providers.ovh.source attribute exists โ OVHcloud provider source must be declared.
* required_providers.ovh.version attribute exists โ OVHcloud provider version must be constrained.
Remediation: Review the Terraform configuration for the listed resource types and ensure the required attribute or tag is present. See the control narrative for provider-specific guidance.
# Example resource meets the requirement.
resource "provider" "example" {
# required attribute/tag/label present
}
# Example resource does not meet the requirement.
resource "provider" "example" {
# Missing required attribute/tag/label
}
waf-ops-020.tf.hcloud.provider-version-constrained
Engine: terraform | Provider: hcloud | Automated: Yes
Hetzner Cloud Terraform provider must have version constraint
Hetzner Cloud Terraform provider must have version constraint. This check validates the Terraform configuration uses the expected resource and attribute.
Scope: terraform
Assertions:
* required_providers.hcloud.source attribute exists โ Hetzner Cloud provider source must be declared.
* required_providers.hcloud.version attribute exists โ Hetzner Cloud provider version must be constrained.
Remediation: Review the Terraform configuration for the listed resource types and ensure the required attribute or tag is present. See the control narrative for provider-specific guidance.
# Example resource meets the requirement.
resource "provider" "example" {
# required attribute/tag/label present
}
# Example resource does not meet the requirement.
resource "provider" "example" {
# Missing required attribute/tag/label
}
Evidence
| Kind | Required | Description |
|---|---|---|
|
Required |
Terraform configuration implementing the control. |
|
Required |
Policy, ADR, or standard documenting the requirement. |
|
Optional |
Cloud console or API output confirming runtime state. |