Configure security policies

Media CDN uses Google Cloud Armor to provide support for IP address allowlists and denylists, as well as filtering controls based on country and region codes (geographic location). You can:

  • Deny requests based on IPv4 and IPv6 addresses and ranges (CIDRs).
  • Allow or deny requests based on country code (geography).
  • Allow requests based on IPv4 and IPv6 addresses and ranges (CIDRs).

These capabilities let you restrict content downloads to users in specific locations where you have content licensing restrictions, only allow corporate IP addresses to access testing or staging endpoints, and deny a list of known bad client IP addresses.

Google Cloud Armor policies apply to all content served from Media CDN, including both cached content and cache misses.

IP address and geographic policies are configured per-Edge Cache service (EdgeCacheService)—all requests destined for that service's IP address (or hostnames) have the security policy enforced consistently. Different services can have different security policies applied, and you can create multiple services for different geographies, as needed.

For more fine-grained protection of content at a per-user level, we recommend using signed URLs and signed cookies in conjunction with a Google Cloud Armor policy.

Configure security policies

Use the following instructions to configure a security policy.

Before you begin

To attach a Google Cloud Armor security policy to an Edge Cache service, you should:

You also need the following Identity and Access Management permissions to authorize, create, and attach security policies to an Edge Cache service:

  • compute.securityPolicies.addAssociation
  • compute.securityPolicies.create
  • compute.securityPolicies.delete
  • compute.securityPolicies.get
  • compute.securityPolicies.list
  • compute.securityPolicies.update
  • compute.securityPolicies.use

Users that need to attach an existing certificate to an Edge Cache service only require these IAM permissions:

  • compute.securityPolicies.get
  • compute.securityPolicies.list
  • compute.securityPolicies.use

The roles/networkservices.edgeCacheUser role includes all of these permissions.

Create a security policy

Google Cloud Armor security policies are composed of several rules, with each rule defining a set of matching criteria (an expression) for a request, and an action. For example, an expression can contain matching logic for clients that are located in India, with the associated action being allow. If a request doesn't match the rule, the evaluation continues to the next rule, until all rules have been attempted.

Security policies have a default rule with an allow action. The default rule allows requests that do not match preceding rules. This can be changed to a deny rule when you want to allow only requests that match preceding rules and reject all others.

The following example shows how to create a rule that blocks all clients geolocated to Australia with a HTTP 403, and allows all other requests.

gcloud

To create a new policy of type CLOUD_ARMOR_EDGE, run the following command:

gcloud compute security-policies create block-australia \
    --type="CLOUD_ARMOR_EDGE" --project="PROJECT_ID"

This creates a policy with a default allow rule at the lowest priority (priority: 2147483647):

Created [https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/securityPolicies/block-australia].

Then you can add a rule with a higher priority:

gcloud compute security-policies rules create 1000 \
    --security-policy=block-australia --description "block AU" \
    --expression="origin.region_code == 'AU'" --action="deny-403"

The output is the following:

Updated [https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/securityPolicies/block-australia].

Terraform

resource "google_compute_security_policy" "default" {
  name        = "block-australia"
  type        = "CLOUD_ARMOR_EDGE"
  description = "block AU"

  rule {
    action      = "deny(403)"
    description = "block AU"
    priority    = "1000"
    match {
      expr {
        expression = "origin.region_code == 'AU'"
      }
    }
  }
  rule {
    action   = "allow"
    priority = "2147483647"
    match {
      versioned_expr = "SRC_IPS_V1"
      config {
        src_ip_ranges = ["*"]
      }
    }
    description = "default rule"
  }
}

If you inspect the policy, you see the two rules: the first rule blocks requests originating from Australia (origin.region_code == 'AU') and the second, lowest priority rule, allows all traffic not matching the higher priority rule (or rules).

kind: compute#securityPolicy
name: block-australia
rules:
- action: deny(403)
  description: block AU
  kind: compute#securityPolicyRule
  match:
    expr:
      expression: origin.region_code == 'AU'
  preview: false
  priority: 1000
- action: allow
  description: default rule
  kind: compute#securityPolicyRule
  match:
    config:
      srcIpRanges:
      - '*'
    versionedExpr: SRC_IPS_V1
  preview: false
  priority: 2147483647
  ruleNumber: '1'
type: CLOUD_ARMOR_EDGE

To attach this policy to your Media CDN, see the next section.

Attach a policy to a service

To attach an existing Google Cloud Armor policy named us-only-delivery-policy to an Edge Cache service called prod-media-service:

gcloud

gcloud edge-cache services update prod-media-service \
    --edge-security-policy=us-only-delivery-policy

View a policy attachment

To review what policy is attached to an existing service, inspect (describe) that service:

gcloud

gcloud edge-cache services describe MY_SERVICE

The edgeSecurityPolicy field of the service describes the attached policy:

name: "MY_SERVICE"
edgeSecurityPolicy: "SECURITY_POLICY

Remove a policy

To remove an existing policy, update the associated service and pass an empty string as the policy:

gcloud

gcloud edge-cache services update MY_SERVICE \
  --edge-security-policy=""

The edgeSecurityPolicy field is now omitted from the output of the gcloud edge-cache services describe MY_SERVICE command.

Identify blocked requests

You must have logging enabled for the given Edge Cache service for blocked requests to be logged.

Requests allowed or denied by a filtering policy are logged to Logging. To filter for rejected requests, the following Logging query for the prod-video-service configuration would look like:

resource.type="edge_cache_service"
jsonPayload.statusDetails="denied_by_security_policy"

Customize response codes

Google Cloud Armor rules can be configured to return a specific status code as the action associated with a given rule. In most cases, it's best to return an HTTP 403 (deny-403) code to clearly signal that the client was blocked by the rule.

The supported status codes are:

  • HTTP 403 (Forbidden)
  • HTTP 404 (Not Found)
  • HTTP 502 (Bad Gateway)

The following example demonstrates how to configure the returned status code:

gcloud

To specify one of [allow | deny-403 | deny-404 | deny-502] as the action associated with the rule, run the following command. This example configures the rule to return an HTTP 502.

gcloud compute security-policies rules create 1000 \
    --security-policy=block-australia --description "block AU" \
    --expression="origin.region_code == 'AU'" --action="deny-502"

Each rule in a security policy can define a different status code response.

Examples

Consider the following detailed example use cases.

Example: Deny clients outside of a country, except for allowed IP addresses

A common case in media serving is denying connections from clients that are outside of the region for which you have content licenses or payment mechanisms.

For example, you might wish to only allow clients located in India, as well as any allowlisted IP addresses (content partners and your own employees) within the range 192.0.2.0/24, and reject all others.

Using the Google Cloud Armor custom rules language, the following expression achieves this:

origin.region_code == "IN" || inIpRange(origin.ip, '192.0.2.0/24')

This expression is configured as an allow rule, with a default deny rule configured to match all other clients. Security policies always have a default rule. You typically configure this to default deny traffic that you don't explicitly allow. In other cases, you might choose to block some traffic and default allow all other traffic.

In the security policy output, note the following:

  • The highest priority (priority: 0) rule allows traffic from India OR from the defined list of IP addresses.
  • The lowest priority rule represents a default deny. The rules engine denies all clients that higher priority rules don't evaluate to true.
  • You can chain multiple rules by using boolean operators.

The following policy allows traffic from clients in India, allows clients from a defined IP range, and denies all other traffic:

gcloud

Run the following security-policies describe command:

gcloud compute security-policies describe allow-india-only

This outputs a Google Cloud Armor policy that resembles the following:

kind: compute#securityPolicy
name: allow-india-only
type: "CLOUD_ARMOR_EDGE"
rules:
- action: allow
  description: ''
  kind: compute#securityPolicyRule
  match:
    expr:
      expression: origin.region_code == "IN" || inIpRange(origin.ip, '192.0.2.0/24')
  preview: false
  priority: 0
- action: deny(403)
  description: Default rule, higher priority overrides it
  kind: compute#securityPolicyRule
  match:
    config:
      srcIpRanges:
      - '*'
    versionedExpr: SRC_IPS_V1
  preview: false
  priority: 2147483647

You can also set a custom response header with the {region_code} header variable. This header can be inspected with JavaScript and reflected to the client.

Example: Block malicious clients by IP address and IP ranges

Using the Google Cloud Armor custom rules language, the following expression achieves this:

inIpRange(origin.ip, '192.0.2.2/32') || inIpRange(origin.ip, '192.0.2.170/32')

You can block IP ranges up to a /8 mask in IPv4 and a /32 in IPv6. A common case for streaming platforms is blocking the egress IP ranges of proxies or VPN providers to minimize content licensing circumvention:

inIpRange(origin.ip, '192.0.2.0/24') || inIpRange(origin.ip, '198.51.100.0/24') || inIpRange(origin.ip, '203.0.113.0/24') || inIpRange(origin.ip, '2001:DB8::B33F:2002/64')

Both IPv4 and IPv6 address ranges are supported.

Example: Only allow a fixed list of geographies

If you have a list of country codes, you can use the boolean OR operator || to chain match conditions.

Using the Google Cloud Armor custom rules language, the following expression allows users identified as coming from Australia or New Zealand:

origin.region_code == "AU" || origin.region_code == "NZ"

This can additionally be chained with origin.ip or inIpRange(origin.ip, '...') expressions to allow testers, partners, and your corporate IP ranges, even if they are not from one of the specified geographies.

There is the documented number of subexpressions for each rule with a custom expression. If you need to chain together more subexpressions, define multiple rules within a single policy.

Example: Block clients from a specific set of countries

A less common example might be to block clients from a certain set of countries, but otherwise allow requests from all other countries.

To do this, you create a policy that blocks both the country and any clients where their region cannot be determined, and then fall through to a default allow rule for all other requests.

The following example describes a policy that blocks clients from Canada, as well as any clients where the location is unknown, but allows all other traffic:

  kind: compute#securityPolicy
  name: block-canada
  type: "CLOUD_ARMOR_EDGE"
  rules:
  - action: deny(403)
    description: ''
    kind: compute#securityPolicyRule
    match:
      expr:
        expression: origin.region_code == "CA" || origin.region_code == "ZZ"
    preview: false
    priority: 0
  - action: allow
    description: Default rule, higher priority overrides it
    kind: compute#securityPolicyRule
    match:
      config:
        srcIpRanges:
        - '*'
      versionedExpr: SRC_IPS_V1
    preview: false
    priority: 2147483647

Logging enforcement actions

Each request log provides details about which security policy was applied and whether the request was allowed (ALLOW) or rejected (DENY).

To enable logging, ensure that logConfig.enable is set to true on your service. Services without logs enabled don't log security policy events.

When a client is located outside the United States and a security policy called deny-non-us-clients is in force that denies requests that originate outside the US, this is the log entry for a denied request:

enforcedSecurityPolicy:
  name: deny-non-us-clients
  outcome: DENY

Services with no Google Cloud Armor policy attached contain no_policy as the value of enforcedSecurityPolicy.name and an outcome of ALLOW. For example, a request log entry for a service without a policy attached has the following values:

enforcedSecurityPolicy:
  name: no_policy
  outcome: ALLOW

Understand GeoIP classifications

Media CDN relies on Google's internal IP classification data-sources to derive a location (region, state/province, city) from an IP address. If you are migrating from, or splitting traffic between, multiple providers, a small number of IP addresses might sometimes be associated with different locations.

  • Google Cloud Armor uses ISO 3166-1 alpha 2 region codes to associate a client to a geographic location.
  • For example, US for the United States, or AU for Australia.
  • In some cases, a region corresponds to a country, but this is not always the case. For example, the US code includes all states of the United States, one district, and six outlying areas.
  • For more information, see unicode_region_subtag in the Unicode Technical Standard.

  • For clients where the location cannot be derived, the origin.region_code is set to ZZ.

You can add geographic data to response headers to an Media CDN endpoint (with routing.routeRules[].headerActions[].responseHeadersToAdd[]) or reflect the geographic data provided to a Cloud Function to validate any differences between geoIP data sources during initial integration and testing.

Additionally, Media CDN request logs include the clientRegion and other client-specific data that you can validate against your existing data sources.

What's next