WAF-COST-040 โ Storage & Retention Lifecycle Defined
Description
All storage resources (S3 buckets, Azure Storage accounts, GCS buckets) MUST have explicit lifecycle rules configured. All log groups (CloudWatch, Azure Log Analytics, GCP Cloud Logging) MUST have an explicit retention_in_days value that is not 0 (infinite) and not null. No storage or logging resource may be provisioned without an explicit, finite retention or lifecycle policy. Infinite retention is classified as Architectural Cost Debt.
Rationale
| Risk | Description |
|---|---|
Risk |
Unlimited storage retention lets data grow unnecessarily and increases cost and deletion risk. |
Risk |
Missing lifecycle rules prevent automatic archiving or deletion by data classification. |
Requirements
-
All storage resources (S3 buckets, Azure Storage accounts, GCS buckets) MUST have explicit lifecycle rules configured.
-
All log groups (CloudWatch, Azure Log Analytics, GCP Cloud Logging) MUST have an explicit retention_in_days value that is not 0 (infinite) and not null.
Implementation Guidance
-
Implement the required configuration as code in Terraform or equivalent IaC.
-
Add an automated check to CI/CD so deviations are caught before deployment.
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-cost-040.tf.aws.s3-lifecycle-rule-defined
Engine: terraform | Provider: aws | Automated: Yes
S3 buckets must have explicit lifecycle configuration
Every aws_s3_bucket resource must have an associated aws_s3_bucket_lifecycle_configuration resource, or a lifecycle_rule block. S3 buckets without lifecycle rules accumulate data indefinitely, driving unbounded storage costs.
Scope: resource โ aws_s3_bucket_lifecycle_configuration
Assertions:
* rule not empty โ S3 lifecycle configuration must contain at least one rule.
Remediation: Add an aws_s3_bucket_lifecycle_configuration resource for every S3 bucket. Include at minimum: transition to STANDARD_IA after 30 days, GLACIER after 90 days, and an abort_incomplete_multipart_upload rule with days_after_initiation = 3.
resource "aws_s3_bucket_lifecycle_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
id = "tiering"
status = "Enabled"
transition {
days = 30
storage_class = "STANDARD_IA"
}
transition {
days = 90
storage_class = "GLACIER_IR"
}
expiration {
days = 365
}
}
rule {
id = "abort-incomplete"
status = "Enabled"
abort_incomplete_multipart_upload {
days_after_initiation = 3
}
}
}
resource "aws_s3_bucket" "data" {
bucket = "acme-application-data"
# No lifecycle configuration โ WAF-COST-040 violation
# Data will accumulate indefinitely
}
waf-cost-040.tf.aws.cloudwatch-log-retention-not-zero
Engine: terraform | Provider: aws | Automated: Yes
CloudWatch Log Groups must not have infinite retention
All aws_cloudwatch_log_group resources must have retention_in_days set to a value greater than 0. The Terraform default (not setting retention_in_days) results in infinite retention, which is classified as Architectural Cost Debt in Wombat.
Scope: resource โ aws_cloudwatch_log_group
Assertions:
* retention_in_days attribute exists โ CloudWatch Log Group is missing retention_in_days. Default is infinite retention (cost debt).
* retention_in_days greater than or equal 1 โ retention_in_days must be >= 1. Setting 0 means infinite retention (WAF-COST-040 violation).
Remediation: Set retention_in_days on all aws_cloudwatch_log_group resources. Use the 4-tier model: operational=30, security-audit=365, compliance=2555 (7yr). Never use 0 (infinite) as this represents unconstrained cost debt.
resource "aws_cloudwatch_log_group" "application" {
name = "/app/production/payment-service"
retention_in_days = 30 # Hot-tier: operational logs
kms_key_id = aws_kms_key.logging.arn
tags = module.mandatory_tags.tags
}
resource "aws_cloudwatch_log_group" "audit" {
name = "/app/production/audit"
retention_in_days = 365 # Cold-tier: audit logs
kms_key_id = aws_kms_key.logging.arn
tags = module.mandatory_tags.tags
}
resource "aws_cloudwatch_log_group" "application" {
name = "/app/production/payment-service"
# retention_in_days not set = infinite retention = WAF-COST-040 violation
}
resource "aws_cloudwatch_log_group" "debug" {
name = "/app/production/debug"
retention_in_days = 0 # Explicit infinite โ also a violation
}
waf-cost-040.tf.azurerm.storage-lifecycle-rule
Engine: terraform | Provider: azurerm | Automated: Yes
Azure Storage accounts must have explicit lifecycle management policies
Every azurerm_storage_account used for blob storage must have an azurerm_storage_management_policy resource attached with at least one rule defining tiering or deletion. Log Analytics workspaces must have an explicit retention_in_days value.
Scope: resource โ azurerm_storage_management_policy, azurerm_log_analytics_workspace
Assertions:
* rule not empty โ Azure storage management policy must contain at least one rule.
Remediation: Add an azurerm_storage_management_policy for each storage account. Define rules to tier blobs to Cool/Archive and delete old versions. Set retention_in_days on azurerm_log_analytics_workspace resources to a finite value.
resource "azurerm_storage_management_policy" "data" {
storage_account_id = azurerm_storage_account.data.id
rule {
name = "tier-and-delete"
enabled = true
filters {
prefix_match = ["container1"]
blob_types = ["blockBlob"]
}
actions {
base_blob {
tier_to_cool_after_days_since_modification_greater_than = 30
tier_to_archive_after_days_since_modification_greater_than = 90
delete_after_days_since_modification_greater_than = 365
}
}
}
}
resource "azurerm_log_analytics_workspace" "logs" {
name = "log-workspace"
location = var.location
resource_group_name = azurerm_resource_group.main.name
retention_in_days = 30
}
resource "azurerm_storage_account" "data" {
name = "acmeappdata"
resource_group_name = azurerm_resource_group.main.name
location = var.location
account_tier = "Standard"
account_replication_type = "LRS"
# No azurerm_storage_management_policy attached
}
waf-cost-040.tf.google.gcs-lifecycle-rule
Engine: terraform | Provider: google | Automated: Yes
GCS buckets must have explicit lifecycle rules
Every google_storage_bucket resource must define a lifecycle_rules block. Cloud Logging buckets must have an explicit retention_days value to prevent infinite retention and unbounded cost.
Scope: resource โ google_storage_bucket
Assertions:
* lifecycle_rule attribute exists โ GCS bucket must define at least one lifecycle rule.
Remediation: Add lifecycle_rules to every google_storage_bucket. Include rules for transitions to Nearline/Coldline/Archive and deletion. Set retention_days on logging buckets to a finite value.
resource "google_storage_bucket" "data" {
name = "acme-app-data"
location = var.region
storage_class = "STANDARD"
lifecycle_rule {
action {
type = "SetStorageClass"
storage_class = "NEARLINE"
}
condition {
age = 30
}
}
lifecycle_rule {
action {
type = "Delete"
}
condition {
age = 365
}
}
}
resource "google_storage_bucket" "data" {
name = "acme-app-data"
location = var.region
# No lifecycle_rule defined โ data accumulates indefinitely
}
waf-cost-040.tf.stackit.object-storage-lifecycle
Engine: terraform | Provider: stackit | Automated: Yes
STACKIT object storage buckets must have a lifecycle rule
STACKIT object storage buckets must have a lifecycle rule. This check validates the Terraform configuration uses the expected resource and attribute.
Scope: resource โ stackit_object_storage_bucket
Assertions:
* lifecycle_rule attribute exists โ STACKIT object storage bucket must define lifecycle rules.
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 "stackit_object_storage_bucket" "example" {
# required attribute/tag/label present
}
# Example resource does not meet the requirement.
resource "stackit_object_storage_bucket" "example" {
# Missing required attribute/tag/label
}
waf-cost-040.tf.ovh.object-storage-lifecycle
Engine: terraform | Provider: ovh | Automated: Yes
OVHcloud object storage containers must have a lifecycle rule
OVHcloud object storage containers must have a lifecycle rule. This check validates the Terraform configuration uses the expected resource and attribute.
Scope: resource โ ovh_cloud_project_storage_object_container
Assertions:
* lifecycle_rule attribute exists โ OVHcloud object storage container must define lifecycle rules.
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 "ovh_cloud_project_storage_object_container" "example" {
# required attribute/tag/label present
}
# Example resource does not meet the requirement.
resource "ovh_cloud_project_storage_object_container" "example" {
# Missing required attribute/tag/label
}
waf-cost-040.tf.hcloud.volume-backup-policy
Engine: terraform | Provider: hcloud | Automated: Yes
Hetzner Cloud volumes must have a backup or lifecycle policy
Hetzner Cloud volumes must have a backup or lifecycle policy. This check validates the Terraform configuration uses the expected resource and attribute.
Scope: resource โ hcloud_volume
Assertions:
* labels.lifecycle-policy key exists โ Hetzner Cloud volume must declare a lifecycle-policy label.
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 "hcloud_volume" "example" {
# required attribute/tag/label present
}
# Example resource does not meet the requirement.
resource "hcloud_volume" "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. |