> ## Documentation Index
> Fetch the complete documentation index at: https://docs.onefirewall.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cloud Armor Enterprise: Address Group & Deny List

> How to configure a centralized IP deny list using Address Groups in Cloud Armor Enterprise with Terraform.

# 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.
This guide outlines the **essential requirements** for integrating the **OneFirewall WCF-Agent with Google Cloud Armor Enterprise Address Groups**, enabling **automated dynamic updates to the deny list**.

***

## Requirements

<Warning>
  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).
</Warning>

<CardGroup cols={2}>
  <Card title="Cloud Armor Enterprise" icon="shield-check">
    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.
  </Card>

  <Card title="Terraform Provider" icon="code">
    The `google_network_security_address_group` resource requires the **`google-beta`** provider. Make sure it is configured in your Terraform setup.
  </Card>
</CardGroup>

### Required IAM Permissions for the Service Account

The service account used by Terraform must have the following roles:

| Role                           | Description                                            |
| ------------------------------ | ------------------------------------------------------ |
| `roles/compute.securityAdmin`  | Create and manage Cloud Armor security policies        |
| `roles/compute.networkAdmin`   | Create and manage address groups                       |
| `roles/iam.serviceAccountUser` | Required if the SA needs to impersonate other accounts |

<Tip>
  For production environments, prefer creating a **custom role** with only the necessary permissions, rather than assigning broad roles like `roles/editor`.
</Tip>

***

## Address Group Limits & Quota

<Warning>
  The **capacity** of an address group **cannot be changed after creation**. Plan your value carefully before deploying.
</Warning>

| `purpose` configuration   | Maximum capacity                                       |
| ------------------------- | ------------------------------------------------------ |
| `CLOUD_ARMOR` only        | Up to **10,000+** IPs (requestable via quota increase) |
| `DEFAULT` + `CLOUD_ARMOR` | Maximum **1,000** IPs                                  |

<Note>
  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**.
</Note>

***

## Configuration with Terraform

### Step 1 — Configure the `google-beta` provider

```hcl terraform.tf theme={null}
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

```hcl address_group.tf theme={null}
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"
}
```

<Note>
  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.
</Note>

### Step 3 — Create the Security Policy with the deny rule

```hcl security_policy.tf theme={null}
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

```hcl backend.tf theme={null}
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`:

```bash theme={null}
# 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:

```hcl multi_policy.tf theme={null}
# 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

```hcl multi_policy.tf theme={null}
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

| Aspect                                | Value                                   |
| ------------------------------------- | --------------------------------------- |
| Required tier                         | **Cloud Armor Enterprise**              |
| Terraform resource                    | `google_network_security_address_group` |
| Terraform provider                    | `google-beta`                           |
| Required `purpose` field              | `CLOUD_ARMOR`                           |
| Max capacity (CLOUD\_ARMOR only)      | 10,000+ (with quota increase)           |
| Max capacity (CLOUD\_ARMOR + DEFAULT) | 1,000                                   |
| Capacity editable after creation      | No                                      |
| Minimum SA roles                      | `securityAdmin` + `networkAdmin`        |
| Update IPs without Terraform          | Yes, via `gcloud` or API                |
