Configure GCP Cloud Armor Security Policies

The gcp:compute/securityPolicy:SecurityPolicy resource, part of the Pulumi GCP provider, defines Cloud Armor security policies that filter HTTP requests to backend services based on IP ranges, bot detection, rate limits, and custom rules. This guide focuses on four capabilities: IP-based access control, reCAPTCHA Enterprise integration, request header injection, and rate limiting with enforcement keys.

Security policies are attached to backend services using the google_compute_backend_service resource. Some configurations reference reCAPTCHA Enterprise keys that must be created separately. The examples are intentionally small. Combine them with your own backend services and bot detection infrastructure.

Block traffic from specific IP ranges

Most Cloud Armor deployments start by defining IP-based rules to block malicious traffic before it reaches backend services.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const policy = new gcp.compute.SecurityPolicy("policy", {
    name: "my-policy",
    rules: [
        {
            action: "deny(403)",
            priority: 1000,
            match: {
                versionedExpr: "SRC_IPS_V1",
                config: {
                    srcIpRanges: ["9.9.9.0/24"],
                },
            },
            description: "Deny access to IPs in 9.9.9.0/24",
        },
        {
            action: "allow",
            priority: 2147483647,
            match: {
                versionedExpr: "SRC_IPS_V1",
                config: {
                    srcIpRanges: ["*"],
                },
            },
            description: "default rule",
        },
    ],
});
import pulumi
import pulumi_gcp as gcp

policy = gcp.compute.SecurityPolicy("policy",
    name="my-policy",
    rules=[
        {
            "action": "deny(403)",
            "priority": 1000,
            "match": {
                "versioned_expr": "SRC_IPS_V1",
                "config": {
                    "src_ip_ranges": ["9.9.9.0/24"],
                },
            },
            "description": "Deny access to IPs in 9.9.9.0/24",
        },
        {
            "action": "allow",
            "priority": 2147483647,
            "match": {
                "versioned_expr": "SRC_IPS_V1",
                "config": {
                    "src_ip_ranges": ["*"],
                },
            },
            "description": "default rule",
        },
    ])
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := compute.NewSecurityPolicy(ctx, "policy", &compute.SecurityPolicyArgs{
			Name: pulumi.String("my-policy"),
			Rules: compute.SecurityPolicyRuleTypeArray{
				&compute.SecurityPolicyRuleTypeArgs{
					Action:   pulumi.String("deny(403)"),
					Priority: pulumi.Int(1000),
					Match: &compute.SecurityPolicyRuleMatchArgs{
						VersionedExpr: pulumi.String("SRC_IPS_V1"),
						Config: &compute.SecurityPolicyRuleMatchConfigArgs{
							SrcIpRanges: pulumi.StringArray{
								pulumi.String("9.9.9.0/24"),
							},
						},
					},
					Description: pulumi.String("Deny access to IPs in 9.9.9.0/24"),
				},
				&compute.SecurityPolicyRuleTypeArgs{
					Action:   pulumi.String("allow"),
					Priority: pulumi.Int(2147483647),
					Match: &compute.SecurityPolicyRuleMatchArgs{
						VersionedExpr: pulumi.String("SRC_IPS_V1"),
						Config: &compute.SecurityPolicyRuleMatchConfigArgs{
							SrcIpRanges: pulumi.StringArray{
								pulumi.String("*"),
							},
						},
					},
					Description: pulumi.String("default rule"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var policy = new Gcp.Compute.SecurityPolicy("policy", new()
    {
        Name = "my-policy",
        Rules = new[]
        {
            new Gcp.Compute.Inputs.SecurityPolicyRuleArgs
            {
                Action = "deny(403)",
                Priority = 1000,
                Match = new Gcp.Compute.Inputs.SecurityPolicyRuleMatchArgs
                {
                    VersionedExpr = "SRC_IPS_V1",
                    Config = new Gcp.Compute.Inputs.SecurityPolicyRuleMatchConfigArgs
                    {
                        SrcIpRanges = new[]
                        {
                            "9.9.9.0/24",
                        },
                    },
                },
                Description = "Deny access to IPs in 9.9.9.0/24",
            },
            new Gcp.Compute.Inputs.SecurityPolicyRuleArgs
            {
                Action = "allow",
                Priority = 2147483647,
                Match = new Gcp.Compute.Inputs.SecurityPolicyRuleMatchArgs
                {
                    VersionedExpr = "SRC_IPS_V1",
                    Config = new Gcp.Compute.Inputs.SecurityPolicyRuleMatchConfigArgs
                    {
                        SrcIpRanges = new[]
                        {
                            "*",
                        },
                    },
                },
                Description = "default rule",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.SecurityPolicy;
import com.pulumi.gcp.compute.SecurityPolicyArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleMatchArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleMatchConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var policy = new SecurityPolicy("policy", SecurityPolicyArgs.builder()
            .name("my-policy")
            .rules(            
                SecurityPolicyRuleArgs.builder()
                    .action("deny(403)")
                    .priority(1000)
                    .match(SecurityPolicyRuleMatchArgs.builder()
                        .versionedExpr("SRC_IPS_V1")
                        .config(SecurityPolicyRuleMatchConfigArgs.builder()
                            .srcIpRanges("9.9.9.0/24")
                            .build())
                        .build())
                    .description("Deny access to IPs in 9.9.9.0/24")
                    .build(),
                SecurityPolicyRuleArgs.builder()
                    .action("allow")
                    .priority(2147483647)
                    .match(SecurityPolicyRuleMatchArgs.builder()
                        .versionedExpr("SRC_IPS_V1")
                        .config(SecurityPolicyRuleMatchConfigArgs.builder()
                            .srcIpRanges("*")
                            .build())
                        .build())
                    .description("default rule")
                    .build())
            .build());

    }
}
resources:
  policy:
    type: gcp:compute:SecurityPolicy
    properties:
      name: my-policy
      rules:
        - action: deny(403)
          priority: '1000'
          match:
            versionedExpr: SRC_IPS_V1
            config:
              srcIpRanges:
                - 9.9.9.0/24
          description: Deny access to IPs in 9.9.9.0/24
        - action: allow
          priority: '2147483647'
          match:
            versionedExpr: SRC_IPS_V1
            config:
              srcIpRanges:
                - '*'
          description: default rule

Rules are evaluated in priority order, with lower numbers processed first. The first rule denies traffic from 9.9.9.0/24 with a 403 response. The second rule (priority 2147483647) acts as the default, allowing all other traffic. The versionedExpr property uses SRC_IPS_V1 to match source IP ranges specified in srcIpRanges.

Integrate reCAPTCHA Enterprise for bot detection

Applications facing bot traffic can integrate reCAPTCHA Enterprise to distinguish between human users and automated clients.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const primary = new gcp.recaptcha.EnterpriseKey("primary", {
    displayName: "display-name",
    labels: {
        "label-one": "value-one",
    },
    project: "my-project-name",
    webSettings: {
        integrationType: "INVISIBLE",
        allowAllDomains: true,
        allowedDomains: ["localhost"],
    },
});
const policy = new gcp.compute.SecurityPolicy("policy", {
    name: "my-policy",
    description: "basic security policy",
    type: "CLOUD_ARMOR",
    recaptchaOptionsConfig: {
        redirectSiteKey: primary.name,
    },
});
import pulumi
import pulumi_gcp as gcp

primary = gcp.recaptcha.EnterpriseKey("primary",
    display_name="display-name",
    labels={
        "label-one": "value-one",
    },
    project="my-project-name",
    web_settings={
        "integration_type": "INVISIBLE",
        "allow_all_domains": True,
        "allowed_domains": ["localhost"],
    })
policy = gcp.compute.SecurityPolicy("policy",
    name="my-policy",
    description="basic security policy",
    type="CLOUD_ARMOR",
    recaptcha_options_config={
        "redirect_site_key": primary.name,
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/recaptcha"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		primary, err := recaptcha.NewEnterpriseKey(ctx, "primary", &recaptcha.EnterpriseKeyArgs{
			DisplayName: pulumi.String("display-name"),
			Labels: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
			Project: pulumi.String("my-project-name"),
			WebSettings: &recaptcha.EnterpriseKeyWebSettingsArgs{
				IntegrationType: pulumi.String("INVISIBLE"),
				AllowAllDomains: pulumi.Bool(true),
				AllowedDomains: pulumi.StringArray{
					pulumi.String("localhost"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = compute.NewSecurityPolicy(ctx, "policy", &compute.SecurityPolicyArgs{
			Name:        pulumi.String("my-policy"),
			Description: pulumi.String("basic security policy"),
			Type:        pulumi.String("CLOUD_ARMOR"),
			RecaptchaOptionsConfig: &compute.SecurityPolicyRecaptchaOptionsConfigArgs{
				RedirectSiteKey: primary.Name,
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var primary = new Gcp.Recaptcha.EnterpriseKey("primary", new()
    {
        DisplayName = "display-name",
        Labels = 
        {
            { "label-one", "value-one" },
        },
        Project = "my-project-name",
        WebSettings = new Gcp.Recaptcha.Inputs.EnterpriseKeyWebSettingsArgs
        {
            IntegrationType = "INVISIBLE",
            AllowAllDomains = true,
            AllowedDomains = new[]
            {
                "localhost",
            },
        },
    });

    var policy = new Gcp.Compute.SecurityPolicy("policy", new()
    {
        Name = "my-policy",
        Description = "basic security policy",
        Type = "CLOUD_ARMOR",
        RecaptchaOptionsConfig = new Gcp.Compute.Inputs.SecurityPolicyRecaptchaOptionsConfigArgs
        {
            RedirectSiteKey = primary.Name,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.recaptcha.EnterpriseKey;
import com.pulumi.gcp.recaptcha.EnterpriseKeyArgs;
import com.pulumi.gcp.recaptcha.inputs.EnterpriseKeyWebSettingsArgs;
import com.pulumi.gcp.compute.SecurityPolicy;
import com.pulumi.gcp.compute.SecurityPolicyArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRecaptchaOptionsConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var primary = new EnterpriseKey("primary", EnterpriseKeyArgs.builder()
            .displayName("display-name")
            .labels(Map.of("label-one", "value-one"))
            .project("my-project-name")
            .webSettings(EnterpriseKeyWebSettingsArgs.builder()
                .integrationType("INVISIBLE")
                .allowAllDomains(true)
                .allowedDomains("localhost")
                .build())
            .build());

        var policy = new SecurityPolicy("policy", SecurityPolicyArgs.builder()
            .name("my-policy")
            .description("basic security policy")
            .type("CLOUD_ARMOR")
            .recaptchaOptionsConfig(SecurityPolicyRecaptchaOptionsConfigArgs.builder()
                .redirectSiteKey(primary.name())
                .build())
            .build());

    }
}
resources:
  primary:
    type: gcp:recaptcha:EnterpriseKey
    properties:
      displayName: display-name
      labels:
        label-one: value-one
      project: my-project-name
      webSettings:
        integrationType: INVISIBLE
        allowAllDomains: true
        allowedDomains:
          - localhost
  policy:
    type: gcp:compute:SecurityPolicy
    properties:
      name: my-policy
      description: basic security policy
      type: CLOUD_ARMOR
      recaptchaOptionsConfig:
        redirectSiteKey: ${primary.name}

The recaptchaOptionsConfig property references a reCAPTCHA Enterprise key by name. The type property must be set to CLOUD_ARMOR for backend service protection. This configuration enables challenge flows and bot scoring that can be used in rule expressions.

Add custom headers based on request conditions

Security policies can inject custom headers into requests that match specific conditions, enabling downstream services to make decisions based on security signals.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const policy = new gcp.compute.SecurityPolicy("policy", {
    name: "my-policy",
    rules: [
        {
            action: "allow",
            priority: 2147483647,
            match: {
                versionedExpr: "SRC_IPS_V1",
                config: {
                    srcIpRanges: ["*"],
                },
            },
            description: "default rule",
        },
        {
            action: "allow",
            priority: 1000,
            match: {
                expr: {
                    expression: "request.path.matches(\"/login.html\") && token.recaptcha_session.score < 0.2",
                },
            },
            headerAction: {
                requestHeadersToAdds: [
                    {
                        headerName: "reCAPTCHA-Warning",
                        headerValue: "high",
                    },
                    {
                        headerName: "X-Resource",
                        headerValue: "test",
                    },
                ],
            },
        },
    ],
});
import pulumi
import pulumi_gcp as gcp

policy = gcp.compute.SecurityPolicy("policy",
    name="my-policy",
    rules=[
        {
            "action": "allow",
            "priority": 2147483647,
            "match": {
                "versioned_expr": "SRC_IPS_V1",
                "config": {
                    "src_ip_ranges": ["*"],
                },
            },
            "description": "default rule",
        },
        {
            "action": "allow",
            "priority": 1000,
            "match": {
                "expr": {
                    "expression": "request.path.matches(\"/login.html\") && token.recaptcha_session.score < 0.2",
                },
            },
            "header_action": {
                "request_headers_to_adds": [
                    {
                        "header_name": "reCAPTCHA-Warning",
                        "header_value": "high",
                    },
                    {
                        "header_name": "X-Resource",
                        "header_value": "test",
                    },
                ],
            },
        },
    ])
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := compute.NewSecurityPolicy(ctx, "policy", &compute.SecurityPolicyArgs{
			Name: pulumi.String("my-policy"),
			Rules: compute.SecurityPolicyRuleTypeArray{
				&compute.SecurityPolicyRuleTypeArgs{
					Action:   pulumi.String("allow"),
					Priority: pulumi.Int(2147483647),
					Match: &compute.SecurityPolicyRuleMatchArgs{
						VersionedExpr: pulumi.String("SRC_IPS_V1"),
						Config: &compute.SecurityPolicyRuleMatchConfigArgs{
							SrcIpRanges: pulumi.StringArray{
								pulumi.String("*"),
							},
						},
					},
					Description: pulumi.String("default rule"),
				},
				&compute.SecurityPolicyRuleTypeArgs{
					Action:   pulumi.String("allow"),
					Priority: pulumi.Int(1000),
					Match: &compute.SecurityPolicyRuleMatchArgs{
						Expr: &compute.SecurityPolicyRuleMatchExprArgs{
							Expression: pulumi.String("request.path.matches(\"/login.html\") && token.recaptcha_session.score < 0.2"),
						},
					},
					HeaderAction: &compute.SecurityPolicyRuleHeaderActionArgs{
						RequestHeadersToAdds: compute.SecurityPolicyRuleHeaderActionRequestHeadersToAddArray{
							&compute.SecurityPolicyRuleHeaderActionRequestHeadersToAddArgs{
								HeaderName:  pulumi.String("reCAPTCHA-Warning"),
								HeaderValue: pulumi.String("high"),
							},
							&compute.SecurityPolicyRuleHeaderActionRequestHeadersToAddArgs{
								HeaderName:  pulumi.String("X-Resource"),
								HeaderValue: pulumi.String("test"),
							},
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var policy = new Gcp.Compute.SecurityPolicy("policy", new()
    {
        Name = "my-policy",
        Rules = new[]
        {
            new Gcp.Compute.Inputs.SecurityPolicyRuleArgs
            {
                Action = "allow",
                Priority = 2147483647,
                Match = new Gcp.Compute.Inputs.SecurityPolicyRuleMatchArgs
                {
                    VersionedExpr = "SRC_IPS_V1",
                    Config = new Gcp.Compute.Inputs.SecurityPolicyRuleMatchConfigArgs
                    {
                        SrcIpRanges = new[]
                        {
                            "*",
                        },
                    },
                },
                Description = "default rule",
            },
            new Gcp.Compute.Inputs.SecurityPolicyRuleArgs
            {
                Action = "allow",
                Priority = 1000,
                Match = new Gcp.Compute.Inputs.SecurityPolicyRuleMatchArgs
                {
                    Expr = new Gcp.Compute.Inputs.SecurityPolicyRuleMatchExprArgs
                    {
                        Expression = "request.path.matches(\"/login.html\") && token.recaptcha_session.score < 0.2",
                    },
                },
                HeaderAction = new Gcp.Compute.Inputs.SecurityPolicyRuleHeaderActionArgs
                {
                    RequestHeadersToAdds = new[]
                    {
                        new Gcp.Compute.Inputs.SecurityPolicyRuleHeaderActionRequestHeadersToAddArgs
                        {
                            HeaderName = "reCAPTCHA-Warning",
                            HeaderValue = "high",
                        },
                        new Gcp.Compute.Inputs.SecurityPolicyRuleHeaderActionRequestHeadersToAddArgs
                        {
                            HeaderName = "X-Resource",
                            HeaderValue = "test",
                        },
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.SecurityPolicy;
import com.pulumi.gcp.compute.SecurityPolicyArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleMatchArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleMatchConfigArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleMatchExprArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleHeaderActionArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var policy = new SecurityPolicy("policy", SecurityPolicyArgs.builder()
            .name("my-policy")
            .rules(            
                SecurityPolicyRuleArgs.builder()
                    .action("allow")
                    .priority(2147483647)
                    .match(SecurityPolicyRuleMatchArgs.builder()
                        .versionedExpr("SRC_IPS_V1")
                        .config(SecurityPolicyRuleMatchConfigArgs.builder()
                            .srcIpRanges("*")
                            .build())
                        .build())
                    .description("default rule")
                    .build(),
                SecurityPolicyRuleArgs.builder()
                    .action("allow")
                    .priority(1000)
                    .match(SecurityPolicyRuleMatchArgs.builder()
                        .expr(SecurityPolicyRuleMatchExprArgs.builder()
                            .expression("request.path.matches(\"/login.html\") && token.recaptcha_session.score < 0.2")
                            .build())
                        .build())
                    .headerAction(SecurityPolicyRuleHeaderActionArgs.builder()
                        .requestHeadersToAdds(                        
                            SecurityPolicyRuleHeaderActionRequestHeadersToAddArgs.builder()
                                .headerName("reCAPTCHA-Warning")
                                .headerValue("high")
                                .build(),
                            SecurityPolicyRuleHeaderActionRequestHeadersToAddArgs.builder()
                                .headerName("X-Resource")
                                .headerValue("test")
                                .build())
                        .build())
                    .build())
            .build());

    }
}
resources:
  policy:
    type: gcp:compute:SecurityPolicy
    properties:
      name: my-policy
      rules:
        - action: allow
          priority: '2147483647'
          match:
            versionedExpr: SRC_IPS_V1
            config:
              srcIpRanges:
                - '*'
          description: default rule
        - action: allow
          priority: '1000'
          match:
            expr:
              expression: request.path.matches("/login.html") && token.recaptcha_session.score < 0.2
          headerAction:
            requestHeadersToAdds:
              - headerName: reCAPTCHA-Warning
                headerValue: high
              - headerName: X-Resource
                headerValue: test

The headerAction property adds custom headers to matching requests. The expr property uses a custom expression that evaluates request paths and reCAPTCHA session scores. When a request matches, the specified headers are added before forwarding to the backend. This allows backend services to see security metadata like reCAPTCHA-Warning and X-Resource headers.

Apply rate limiting with IP-based enforcement

Rate limiting protects backend services from excessive requests by throttling or redirecting traffic that exceeds defined thresholds.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const policy = new gcp.compute.SecurityPolicy("policy", {
    name: "%s",
    description: "throttle rule with enforce_on_key_configs",
    rules: [{
        action: "throttle",
        priority: 2147483647,
        match: {
            versionedExpr: "SRC_IPS_V1",
            config: {
                srcIpRanges: ["*"],
            },
        },
        description: "default rule",
        rateLimitOptions: {
            conformAction: "allow",
            exceedAction: "redirect",
            enforceOnKey: "",
            enforceOnKeyConfigs: [{
                enforceOnKeyType: "IP",
            }],
            exceedRedirectOptions: {
                type: "EXTERNAL_302",
                target: "<https://www.example.com>",
            },
            rateLimitThreshold: {
                count: 10,
                intervalSec: 60,
            },
        },
    }],
});
import pulumi
import pulumi_gcp as gcp

policy = gcp.compute.SecurityPolicy("policy",
    name="%s",
    description="throttle rule with enforce_on_key_configs",
    rules=[{
        "action": "throttle",
        "priority": 2147483647,
        "match": {
            "versioned_expr": "SRC_IPS_V1",
            "config": {
                "src_ip_ranges": ["*"],
            },
        },
        "description": "default rule",
        "rate_limit_options": {
            "conform_action": "allow",
            "exceed_action": "redirect",
            "enforce_on_key": "",
            "enforce_on_key_configs": [{
                "enforce_on_key_type": "IP",
            }],
            "exceed_redirect_options": {
                "type": "EXTERNAL_302",
                "target": "<https://www.example.com>",
            },
            "rate_limit_threshold": {
                "count": 10,
                "interval_sec": 60,
            },
        },
    }])
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := compute.NewSecurityPolicy(ctx, "policy", &compute.SecurityPolicyArgs{
			Name:        pulumi.String("%s"),
			Description: pulumi.String("throttle rule with enforce_on_key_configs"),
			Rules: compute.SecurityPolicyRuleTypeArray{
				&compute.SecurityPolicyRuleTypeArgs{
					Action:   pulumi.String("throttle"),
					Priority: pulumi.Int(2147483647),
					Match: &compute.SecurityPolicyRuleMatchArgs{
						VersionedExpr: pulumi.String("SRC_IPS_V1"),
						Config: &compute.SecurityPolicyRuleMatchConfigArgs{
							SrcIpRanges: pulumi.StringArray{
								pulumi.String("*"),
							},
						},
					},
					Description: pulumi.String("default rule"),
					RateLimitOptions: &compute.SecurityPolicyRuleRateLimitOptionsArgs{
						ConformAction: pulumi.String("allow"),
						ExceedAction:  pulumi.String("redirect"),
						EnforceOnKey:  pulumi.String(""),
						EnforceOnKeyConfigs: compute.SecurityPolicyRuleRateLimitOptionsEnforceOnKeyConfigArray{
							&compute.SecurityPolicyRuleRateLimitOptionsEnforceOnKeyConfigArgs{
								EnforceOnKeyType: pulumi.String("IP"),
							},
						},
						ExceedRedirectOptions: &compute.SecurityPolicyRuleRateLimitOptionsExceedRedirectOptionsArgs{
							Type:   pulumi.String("EXTERNAL_302"),
							Target: pulumi.String("<https://www.example.com>"),
						},
						RateLimitThreshold: &compute.SecurityPolicyRuleRateLimitOptionsRateLimitThresholdArgs{
							Count:       pulumi.Int(10),
							IntervalSec: pulumi.Int(60),
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var policy = new Gcp.Compute.SecurityPolicy("policy", new()
    {
        Name = "%s",
        Description = "throttle rule with enforce_on_key_configs",
        Rules = new[]
        {
            new Gcp.Compute.Inputs.SecurityPolicyRuleArgs
            {
                Action = "throttle",
                Priority = 2147483647,
                Match = new Gcp.Compute.Inputs.SecurityPolicyRuleMatchArgs
                {
                    VersionedExpr = "SRC_IPS_V1",
                    Config = new Gcp.Compute.Inputs.SecurityPolicyRuleMatchConfigArgs
                    {
                        SrcIpRanges = new[]
                        {
                            "*",
                        },
                    },
                },
                Description = "default rule",
                RateLimitOptions = new Gcp.Compute.Inputs.SecurityPolicyRuleRateLimitOptionsArgs
                {
                    ConformAction = "allow",
                    ExceedAction = "redirect",
                    EnforceOnKey = "",
                    EnforceOnKeyConfigs = new[]
                    {
                        new Gcp.Compute.Inputs.SecurityPolicyRuleRateLimitOptionsEnforceOnKeyConfigArgs
                        {
                            EnforceOnKeyType = "IP",
                        },
                    },
                    ExceedRedirectOptions = new Gcp.Compute.Inputs.SecurityPolicyRuleRateLimitOptionsExceedRedirectOptionsArgs
                    {
                        Type = "EXTERNAL_302",
                        Target = "<https://www.example.com>",
                    },
                    RateLimitThreshold = new Gcp.Compute.Inputs.SecurityPolicyRuleRateLimitOptionsRateLimitThresholdArgs
                    {
                        Count = 10,
                        IntervalSec = 60,
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.SecurityPolicy;
import com.pulumi.gcp.compute.SecurityPolicyArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleMatchArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleMatchConfigArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleRateLimitOptionsArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleRateLimitOptionsExceedRedirectOptionsArgs;
import com.pulumi.gcp.compute.inputs.SecurityPolicyRuleRateLimitOptionsRateLimitThresholdArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var policy = new SecurityPolicy("policy", SecurityPolicyArgs.builder()
            .name("%s")
            .description("throttle rule with enforce_on_key_configs")
            .rules(SecurityPolicyRuleArgs.builder()
                .action("throttle")
                .priority(2147483647)
                .match(SecurityPolicyRuleMatchArgs.builder()
                    .versionedExpr("SRC_IPS_V1")
                    .config(SecurityPolicyRuleMatchConfigArgs.builder()
                        .srcIpRanges("*")
                        .build())
                    .build())
                .description("default rule")
                .rateLimitOptions(SecurityPolicyRuleRateLimitOptionsArgs.builder()
                    .conformAction("allow")
                    .exceedAction("redirect")
                    .enforceOnKey("")
                    .enforceOnKeyConfigs(SecurityPolicyRuleRateLimitOptionsEnforceOnKeyConfigArgs.builder()
                        .enforceOnKeyType("IP")
                        .build())
                    .exceedRedirectOptions(SecurityPolicyRuleRateLimitOptionsExceedRedirectOptionsArgs.builder()
                        .type("EXTERNAL_302")
                        .target("<https://www.example.com>")
                        .build())
                    .rateLimitThreshold(SecurityPolicyRuleRateLimitOptionsRateLimitThresholdArgs.builder()
                        .count(10)
                        .intervalSec(60)
                        .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  policy:
    type: gcp:compute:SecurityPolicy
    properties:
      name: '%s'
      description: throttle rule with enforce_on_key_configs
      rules:
        - action: throttle
          priority: '2147483647'
          match:
            versionedExpr: SRC_IPS_V1
            config:
              srcIpRanges:
                - '*'
          description: default rule
          rateLimitOptions:
            conformAction: allow
            exceedAction: redirect
            enforceOnKey: ""
            enforceOnKeyConfigs:
              - enforceOnKeyType: IP
            exceedRedirectOptions:
              type: EXTERNAL_302
              target: <https://www.example.com>
            rateLimitThreshold:
              count: 10
              intervalSec: 60

The rateLimitOptions property defines rate limiting behavior. The rateLimitThreshold sets the maximum request count (10) within an interval (60 seconds). The enforceOnKeyConfigs property scopes enforcement to IP addresses. When the threshold is exceeded, the exceedAction redirects traffic to the URL specified in exceedRedirectOptions.

Beyond these examples

These snippets focus on specific security policy features: IP-based access control, reCAPTCHA integration and bot detection, and rate limiting and request throttling. They’re intentionally minimal rather than full application protection configurations.

The examples may reference pre-existing infrastructure such as backend services to attach policies to, and reCAPTCHA Enterprise keys for bot detection. They focus on configuring the security policy rather than provisioning the complete Cloud Armor stack.

To keep things focused, common security policy patterns are omitted, including:

  • Adaptive Protection for DDoS mitigation (adaptiveProtectionConfig)
  • Advanced options for JSON parsing (advancedOptionsConfig)
  • Policy types (CLOUD_ARMOR_EDGE, CLOUD_ARMOR_INTERNAL_SERVICE)
  • Custom expressions beyond basic IP and path matching

These omissions are intentional: the goal is to illustrate how each security policy feature is wired, not provide drop-in protection modules. See the Security Policy resource reference for all available configuration options.

Let's configure GCP Cloud Armor Security Policies

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Rules & Configuration
What's the default rule requirement for security policies?
Every security policy must have a default rule with priority 2147483647 and match “*”. If you don’t provide rules during creation, a default rule with action “allow” is automatically added.
How do I deny traffic from specific IP ranges?
Create a rule with action: "deny(403)", set a priority (lower numbers = higher priority), and specify srcIpRanges in the match config. For example, srcIpRanges: ["9.9.9.0/24"] blocks that subnet.
How do I add custom headers to requests?
Use headerAction with requestHeadersToAdds to specify header names and values. You can add multiple headers in a single rule.
Policy Types & Use Cases
What are the different security policy types?

There are three types:

  1. CLOUD_ARMOR - Filters requests before they hit backend services
  2. CLOUD_ARMOR_EDGE - Filters before serving from Google’s cache (CDN/Cloud Storage)
  3. CLOUD_ARMOR_INTERNAL_SERVICE - Filters requests in Traffic Director service mesh
When should I use CLOUD_ARMOR_EDGE vs CLOUD_ARMOR?
Use CLOUD_ARMOR_EDGE when protecting Cloud CDN-enabled services or Cloud Storage buckets, as it filters at the edge before cache. Use CLOUD_ARMOR for standard backend service protection.
Security Features
How do I integrate reCAPTCHA with my security policy?
Configure recaptchaOptionsConfig with redirectSiteKey pointing to your reCAPTCHA Enterprise key name. You can then use reCAPTCHA scores in rule expressions.
Can I use reCAPTCHA scores in rule matching?
Yes, use expressions like token.recaptcha_session.score < 0.2 in the match expr to evaluate reCAPTCHA scores and take actions based on them.
How do I configure rate limiting with multiple enforcement keys?
Set enforceOnKey to an empty string ("") and use enforceOnKeyConfigs to specify enforcement types like IP. This avoids conflicts between the two properties.
Common Issues & Gotchas
What properties can't I change after creating the policy?
The name, type, and project properties are immutable. Changing them requires replacing the resource.
Why aren't all my labels showing in the labels field?
The labels field is non-authoritative and only manages labels in your configuration. Use effectiveLabels to see all labels present on the resource in GCP, including those set by other clients.
How do I avoid conflicts between enforceOnKey and enforceOnKeyConfigs?
Set enforceOnKey to an empty string ("") when using enforceOnKeyConfigs. This prevents conflicts between the two rate limiting configuration options.

Using a different cloud?

Explore security guides for other cloud providers: