WAF-COST-090 – Data Transfer & Egress Cost Management
Beschreibung
VPC endpoints MUST be deployed for S3, KMS, and other heavily-used internal cloud services to eliminate unnecessary internet egress costs. Public IP addresses on internal compute resources (that do not serve public traffic) are prohibited. CDN MUST be configured for all publicly accessible static assets. An egress cost budget alert MUST exist. Cross-region data transfer MUST be minimized and documented when necessary.
Begründung
| Risiko | Beschreibung |
|---|---|
Risk |
Internet-facing resources or open management ports become direct attack vectors. |
Risk |
Lateral movement is easier when network segmentation and access rules are missing. |
Anforderungen
-
VPC endpoints MUST be deployed for S3, KMS, and other heavily-used internal cloud services to eliminate unnecessary internet egress costs.
-
CDN MUST be configured for all publicly accessible static assets.
-
An egress cost budget alert MUST exist.
-
Cross-region data transfer MUST be minimized and documented when necessary.
Implementierungsleitfaden
-
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.
Reifegrade
| Level | Name | Kriterien |
|---|---|---|
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-090.tf.aws.vpc-endpoint-s3-exists
Engine: terraform | Provider: aws | Automated: Ja
VPC endpoint for S3 must exist in each production VPC
An aws_vpc_endpoint resource with service_name matching S3 must exist for each production VPC. Without S3 VPC endpoints, all S3 traffic from EC2 instances flows through the internet gateway, generating unnecessary egress and data processing charges.
Scope: resource — aws_vpc_endpoint
Assertions:
* service_name matches pattern .*\\.s3$ — VPC endpoint for S3 (service_name ending in .s3) must exist.
* vpc_endpoint_type in ['Gateway', 'Interface'] — VPC endpoint type must be Gateway or Interface.
Remediation: Add an aws_vpc_endpoint resource for S3 to each production VPC. For S3: use vpc_endpoint_type = "Gateway" (free of charge). For interface endpoints (KMS, ECR, STS): vpc_endpoint_type = "Interface" (hourly charge but saves on data processing and egress from NAT gateway).
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = [aws_route_table.private.id]
tags = merge(module.mandatory_tags.tags, {
Name = "vpce-s3-${var.environment}"
})
}
resource "aws_vpc_endpoint" "kms" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.kms"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoint.id]
private_dns_enabled = true
tags = merge(module.mandatory_tags.tags, {
Name = "vpce-kms-${var.environment}"
})
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
# No VPC endpoints defined – all S3/KMS traffic via internet gateway
# WAF-COST-090 violation: unnecessary egress charges
}
waf-cost-090.tf.aws.no-public-ip-internal-compute
Engine: terraform | Provider: aws | Automated: Ja
Internal compute resources must not have public IPs
EC2 instances used for internal application workloads (not serving public traffic directly) must not have associate_public_ip_address = true. Internal compute should use private IPs with NAT Gateway for outbound internet access where needed.
Scope: resource — aws_instance, aws_launch_template
Assertions:
* associate_public_ip_address is false — Internal compute must not have public IP addresses. Use private subnets with NAT Gateway.
Remediation: Set associate_public_ip_address = false on internal EC2 instances. Place instances in private subnets. Use NAT Gateway for outbound internet access. Use ALB/NLB with public IPs for load balancing inbound public traffic.
resource "aws_instance" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.medium"
subnet_id = aws_subnet.private.id
associate_public_ip_address = false # Internal compute, no public IP
tags = module.mandatory_tags.tags
}
resource "aws_instance" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.medium"
subnet_id = aws_subnet.public.id
associate_public_ip_address = true # Unnecessary public IP for internal app server
}
waf-cost-090.tf.azurerm.private-endpoint-internal-services
Engine: terraform | Provider: azurerm | Automated: Ja
Azure private endpoints must be used for internal PaaS services
azurerm_private_endpoint resources must route traffic to internal Azure PaaS services (e.g., Storage, Key Vault, SQL) over the Azure backbone. Internal compute resources must not have public IPs.
Scope: resource — azurerm_private_endpoint
Assertions:
* private_service_connection attribute exists — Azure private endpoint must define a private service connection.
Remediation: Create azurerm_private_endpoint resources for Storage accounts, Key Vaults, and SQL databases. Set public_network_access_enabled = false on those services where applicable. Place internal VMs in private subnets without public IPs.
resource "azurerm_private_endpoint" "storage" {
name = "pe-storage"
location = var.location
resource_group_name = azurerm_resource_group.main.name
subnet_id = azurerm_subnet.private.id
private_service_connection {
name = "psc-storage"
private_connection_resource_id = azurerm_storage_account.data.id
subresource_names = ["blob"]
is_manual_connection = false
}
}
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"
public_network_access_enabled = false
}
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"
public_network_access_enabled = true # Traffic via public internet
}
waf-cost-090.tf.google.private-google-access
Engine: terraform | Provider: google | Automated: Ja
GCP internal VMs must use Private Google Access and VPC-SC
GCP subnets must have private_ip_google_access enabled so internal VMs reach Google APIs without public IPs. google_access_context_manager_service_perimeter resources should restrict access to sensitive APIs.
Scope: resource — google_compute_subnetwork
Assertions:
* private_ip_google_access is true — GCP private subnet must enable Private Google Access to avoid public IP egress.
Remediation: Enable private_ip_google_access on private subnets. Configure VPC Service Controls perimeters for Cloud Storage, BigQuery, and KMS. Avoid public IPs on internal compute.
resource "google_compute_subnetwork" "private" {
name = "private-subnet"
ip_cidr_range = "10.0.1.0/24"
region = var.region
network = google_compute_network.main.id
private_ip_google_access = true
}
resource "google_compute_subnetwork" "private" {
name = "private-subnet"
ip_cidr_range = "10.0.1.0/24"
region = var.region
network = google_compute_network.main.id
private_ip_google_access = false # Internal VMs need public IPs for Google APIs
}
waf-cost-090.tf.stackit.private-network-services
Engine: terraform | Provider: stackit | Automated: Ja
STACKIT internal services must be reachable via private network
STACKIT internal services must be reachable via private network. This check validates the Terraform configuration uses the expected resource and attribute.
Scope: resource — stackit_network, stackit_object_storage_bucket, stackit_postgres_instance, stackit_mysql_instance, stackit_redis_instance, stackit_opensearch_instance
Assertions:
* network_id attribute exists — STACKIT managed service should be attached to a private network.
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_network" "example" {
# required attribute/tag/label present
}
# Example resource does not meet the requirement.
resource "stackit_network" "example" {
# Missing required attribute/tag/label
}
waf-cost-090.tf.ovh.private-network-services
Engine: terraform | Provider: ovh | Automated: Ja
OVHcloud internal services must be reachable via private network
OVHcloud internal services must be reachable via private network. This check validates the Terraform configuration uses the expected resource and attribute.
Scope: resource — ovh_cloud_project_network_private, ovh_cloud_project_instance, ovh_cloud_project_kube, ovh_cloud_project_database_postgresql, ovh_cloud_project_database_mysql
Assertions:
* network_id attribute exists — OVHcloud internal service should reference a private network.
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_network_private" "example" {
# required attribute/tag/label present
}
# Example resource does not meet the requirement.
resource "ovh_cloud_project_network_private" "example" {
# Missing required attribute/tag/label
}
waf-cost-090.tf.hcloud.private-network-services
Engine: terraform | Provider: hcloud | Automated: Ja
Hetzner Cloud internal services must be reachable via private network
Hetzner Cloud internal services must be reachable via private network. This check validates the Terraform configuration uses the expected resource and attribute.
Scope: resource — hcloud_network, hcloud_server, hcloud_load_balancer, hcloud_volume
Assertions:
* network_id attribute exists — Hetzner Cloud internal service should be attached to a private network.
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_network" "example" {
# required attribute/tag/label present
}
# Example resource does not meet the requirement.
resource "hcloud_network" "example" {
# Missing required attribute/tag/label
}
Evidenz
| Art | Erforderlich | Beschreibung |
|---|---|---|
|
Erforderlich |
Terraform configuration implementing the control. |
|
Erforderlich |
Policy, ADR, or standard documenting the requirement. |
|
Optional |
Cloud console or API output confirming runtime state. |
Verwandte Controls
-
WAF-SEC-120 == Regulatorisches Mapping
| Framework | Controls |
|---|---|
FinOps Foundation |
* Optimize Phase – Network Cost Optimization * Optimize Phase – Data Transfer Cost Reduction |