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 FREEFrequently Asked Questions
Rules & Configuration
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.headerAction with requestHeadersToAdds to specify header names and values. You can add multiple headers in a single rule.Policy Types & Use Cases
There are three types:
- CLOUD_ARMOR - Filters requests before they hit backend services
- CLOUD_ARMOR_EDGE - Filters before serving from Google’s cache (CDN/Cloud Storage)
- CLOUD_ARMOR_INTERNAL_SERVICE - Filters requests in Traffic Director service mesh
Security Features
recaptchaOptionsConfig with redirectSiteKey pointing to your reCAPTCHA Enterprise key name. You can then use reCAPTCHA scores in rule expressions.token.recaptcha_session.score < 0.2 in the match expr to evaluate reCAPTCHA scores and take actions based on them.enforceOnKey to an empty string ("") and use enforceOnKeyConfigs to specify enforcement types like IP. This avoids conflicts between the two properties.Common Issues & Gotchas
name, type, and project properties are immutable. Changing them requires replacing the resource.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.enforceOnKey to an empty string ("") when using enforceOnKeyConfigs. This prevents conflicts between the two rate limiting configuration options.