> ## 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

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

## Overview

Use **Address Groups** in Google Cloud Armor Enterprise to manage a centralized IP deny list, reusable across multiple security policies. This guide covers integrating the OneFirewall WCF Agent with Cloud Armor Enterprise Address Groups for automated dynamic updates to the deny list.

## Prerequisites

<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>

## 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
}
```

## Step 5: Update the Deny List Without Terraform

To add or remove IPs dynamically, without going through Terraform, 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
```

## Step 6: Reuse the Address Group Across Multiple Security Policies

The same address group can be referenced by multiple security policies simultaneously:

```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)"
      }
    }
  }
  # ...
}
```

## Step 7: Handle a 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)"
      }
    }
  }
  # ...
}
```

## Notes

| 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 |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.