Skip to main content

Cloud Armor Enterprise: Deny List with Address Group

This guide explains how to use Address Groups in Google Cloud Armor Enterprise to manage a centralized IP deny list, reusable across multiple security policies.

Requirements

Address Groups with purpose = CLOUD_ARMOR require the project to be enrolled in Cloud Armor Enterprise. Without it, you cannot create or modify address groups. If you downgrade, all security policies referencing address groups will be frozen (read-only).

Cloud Armor Enterprise

The GCP project must be enrolled in the Enterprise tier. On downgrade, security policies referencing address groups become read-only until those rules are removed.

Terraform Provider

The google_network_security_address_group resource requires the google-beta provider. Make sure it is configured in your Terraform setup.

Required IAM Permissions for the Service Account

The service account used by Terraform must have the following roles:
RoleDescription
roles/compute.securityAdminCreate and manage Cloud Armor security policies
roles/compute.networkAdminCreate and manage address groups
roles/iam.serviceAccountUserRequired if the SA needs to impersonate other accounts
For production environments, prefer creating a custom role with only the necessary permissions, rather than assigning broad roles like roles/editor.

Address Group Limits & Quota

The capacity of an address group cannot be changed after creation. Plan your value carefully before deploying.
purpose configurationMaximum capacity
CLOUD_ARMOR onlyUp to 10,000+ IPs (requestable via quota increase)
DEFAULT + CLOUD_ARMORMaximum 1,000 IPs
To increase quota limits, the service account needs the serviceusage.quotas.update permission, included in the Owner, Editor, and Quota Administrator roles. Requests can be submitted from the GCP Console under IAM & Admin → Quotas.

Configuration with Terraform

Step 1 — Configure the google-beta provider

terraform.tf
terraform {
  required_providers {
    google-beta = {
      source  = "hashicorp/google-beta"
      version = ">= 5.0"
    }
  }
}

provider "google-beta" {
  project = var.project_id
  region  = "global"
}

Step 2 — Create the Address Group

address_group.tf
resource "google_network_security_address_group" "denylist" {
  provider  = google-beta
  name      = "denylist-addresses"
  parent    = "projects/${var.project_id}"
  location  = "global"
  type      = "IPV4"       # "IPV4" or "IPV6"
  capacity  = 10000         # Cannot be changed after creation
  purpose   = ["CLOUD_ARMOR"]

  items = [
    "1.2.3.4/32",
    "5.6.7.8/32",
    "10.0.0.0/8",
  ]

  description = "Centrally managed IP deny list for Cloud Armor"
}
The items field can also be managed externally via gcloud or the API, without re-running Terraform every time you add or remove an IP.

Step 3 — Create the Security Policy with the deny rule

security_policy.tf
resource "google_compute_security_policy" "main" {
  provider = google-beta
  name     = "main-security-policy"

  # Rule 1: block IPs in the address group
  rule {
    action   = "deny(403)"
    priority = 1000
    description = "Block IPs listed in the deny list address group"
    match {
      expr {
        # use origin.user_ip instead of origin.ip if CDN mask
        expression = "evaluateAddressGroup('${google_network_security_address_group.denylist.id}', origin.ip)"
      }
    }
  }

  # Default rule: allow everything else
  rule {
    action      = "allow"
    priority    = 2147483647
    description = "Default allow rule"

    match {
      versioned_expr = "SRC_IPS_V1"
      config {
        src_ip_ranges = ["*"]
      }
    }
  }
}

Step 4 — Attach the policy to a backend service

backend.tf
resource "google_compute_backend_service" "app" {
  name                  = "my-backend-service"
  security_policy       = google_compute_security_policy.main.id
  load_balancing_scheme = "EXTERNAL_MANAGED"
  protocol              = "HTTP"
  # ... other backend parameters
}

Updating the Deny List Without Terraform

If you need to add or remove IPs dynamically (without going through Terraform), you can use gcloud:
# Add IPs to the address group
gcloud network-security address-groups add-items denylist-addresses \
  --location global \
  --items "9.9.9.9/32,8.8.8.8/32"

# Remove IPs from the address group
gcloud network-security address-groups remove-items denylist-addresses \
  --location global \
  --items "9.9.9.9/32"

# Inspect the current state
gcloud network-security address-groups describe denylist-addresses \
  --location global

Reusing Across Multiple Security Policies

One of the main advantages of address groups is that the same list can be referenced by multiple security policies simultaneously. This avoids duplication and ensures consistency across your infrastructure:
multi_policy.tf
# Reference the same address group in different policies
resource "google_compute_security_policy" "api" {
  name = "api-security-policy"

  rule {
    action   = "deny(403)"
    priority = 1000
    match {
      expr {
        # use origin.user_ip instead of origin.ip if CDN mask
        expression = "evaluateAddressGroup('${google_network_security_address_group.denylist.id}', origin.ip)"
      }
    }
  }
  # ...
}

CDN with masked ip x-forwarded-for

multi_policy.tf
resource "google_compute_security_policy" "main" {
  provider = google-beta
  name     = "main-security-policy"

  advanced_options_config {
    user_ip_request_headers = ["X-Forwarded-For"]
    # or: ["True-Client-IP"]
    # or both with priority order: ["True-Client-IP", "X-Forwarded-For"]
  }

  rule {
    action   = "deny(403)"
    priority = 1000
    match {
      expr {
        # use origin.user_ip instead origin.ip
        expression = "evaluateAddressGroup('${google_network_security_address_group.denylist.id}', origin.user_ip)"
      }
    }
  }
  # ...
}

Summary

AspectValue
Required tierCloud Armor Enterprise
Terraform resourcegoogle_network_security_address_group
Terraform providergoogle-beta
Required purpose fieldCLOUD_ARMOR
Max capacity (CLOUD_ARMOR only)10,000+ (with quota increase)
Max capacity (CLOUD_ARMOR + DEFAULT)1,000
Capacity editable after creationNo
Minimum SA rolessecurityAdmin + networkAdmin
Update IPs without TerraformYes, via gcloud or API