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 load-balanced services based on IP ranges, bot detection, and custom rules. This guide focuses on three capabilities: IP-based access control, reCAPTCHA Enterprise integration, and header injection with rate limiting.

Security policies are attached to backend services and may reference reCAPTCHA Enterprise keys for bot detection. The examples are intentionally small. Combine them with your own backend services and monitoring 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 taking precedence. The action property specifies what happens when a rule matches: “deny(403)” blocks the request, while “allow” permits it. Every policy must include a default rule with priority 2147483647 that matches all traffic ("*"). The versionedExpr property uses “SRC_IPS_V1” to match against source IP ranges defined 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 links the security policy to a reCAPTCHA Enterprise key via redirectSiteKey. This enables reCAPTCHA scoring in rule expressions, allowing you to challenge or block requests based on bot likelihood scores.

Add custom headers based on request conditions

Security policies can inject custom headers into requests that match specific conditions, enabling downstream services to make routing or logging decisions.

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.expression property uses Common Expression Language (CEL) to evaluate request attributes and reCAPTCHA scores. Here, requests to “/login.html” with low reCAPTCHA scores receive warning headers that backend services can inspect.

Apply rate limiting with IP-based enforcement

Rate limiting protects backend services from excessive requests by throttling or redirecting traffic that exceeds configured 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 conformAction specifies what happens to requests within the limit (“allow”), while exceedAction handles requests that exceed it (“redirect”). The enforceOnKeyConfigs property sets the enforcement dimension; here, “IP” means each source IP gets its own rate limit. The rateLimitThreshold property sets the limit: 10 requests per 60-second interval.

Beyond these examples

These snippets focus on specific security policy features: IP-based access control and rule prioritization, reCAPTCHA Enterprise integration, and header injection and rate limiting. They’re intentionally minimal rather than full security configurations.

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

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

  • Adaptive Protection configuration (adaptiveProtectionConfig)
  • Advanced options for JSON parsing (advancedOptionsConfig)
  • Policy attachment to backend services
  • Custom expressions beyond basic IP matching

These omissions are intentional: the goal is to illustrate how each security policy feature is wired, not provide drop-in security 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 one, a default rule with action “allow” is automatically created.
How do I block specific IP addresses?
Create a rule with action deny(403), set a priority (like 1000), and specify the IP ranges in match.config.srcIpRanges (e.g., ["9.9.9.0/24"]).
What priority should I use for my custom rules?
Use priorities lower than 2147483647 (which is reserved for the default rule). Lower numbers are evaluated first, so use 1000, 2000, etc., for your custom rules.
Policy Types & Immutability
What are the different security policy types?

There are three types:

  • CLOUD_ARMOR: Filters requests before they hit backend services
  • CLOUD_ARMOR_EDGE: Filters requests at the edge (CDN/Cloud Storage) before serving from cache
  • CLOUD_ARMOR_INTERNAL_SERVICE: Filters requests in service mesh before serving from application
What properties can't be changed after creation?
The name, type, and project properties are immutable. Changing them requires recreating the resource.
Advanced Features
How do I integrate reCAPTCHA with my security policy?
Configure recaptchaOptionsConfig with a redirectSiteKey that references your reCAPTCHA Enterprise key name.
How do I add custom headers to requests?
Use headerAction.requestHeadersToAdds in your rule to add headers. Each entry needs a headerName and headerValue.
How do I configure rate limiting?
Set the rule action to throttle and configure rateLimitOptions with conformAction, exceedAction, rateLimitThreshold (count and intervalSec), and optionally enforceOnKeyConfigs.
How do I avoid conflicts between enforceOnKey and enforceOnKeyConfigs?
Set enforceOnKey to an empty string ("") when using enforceOnKeyConfigs in rate limit rules.
Labels & Metadata
Why don't I see all labels in my configuration?
The labels field is non-authoritative and only manages labels in your configuration. Use the effectiveLabels output property to see all labels present on the resource in GCP.

Using a different cloud?

Explore security guides for other cloud providers: