AccessControl policy

This page applies to Apigee and Apigee hybrid.

View Apigee Edge documentation.

policy icon

What

The AccessControl policy lets you allow or deny access to your APIs by specific IP addresses.

This policy is a Standard policy and can be deployed to any environment type. For information on policy types and availability with each environment type, see Policy types.

Video: Watch a short video to learn more about how the to allow or deny access to your APIs by specific IP addresses.

While you can attach this policy anywhere in the API proxy flow, you'll most likely want to check IP addresses at the beginning of the flow ( Request / ProxyEndpoint / PreFlow), even before authentication or quota checking.

You should also consider using Google Cloud Armor with Apigee as alternative way to secure your APIs.

Samples

The mask values in the following IPv4 samples identify which of the four octets (8, 16, 24, 32 bits) the match rule considers when allowing or denying access. The default value is 32. See the mask attribute in the Element reference for more information.

Deny 198.51.100.1

<AccessControl name="ACL">
  <IPRules noRuleMatchAction = "ALLOW">
    <MatchRule action = "DENY">
      <SourceAddress mask="32">198.51.100.1</SourceAddress>
    </MatchRule>
  </IPRules>
</AccessControl>

Deny all requests from client address: 198.51.100.1

Allow requests from any other client address.

Deny using variables

<AccessControl name="ACL">
  <IPRules noRuleMatchAction = "ALLOW">
    <MatchRule action = "DENY">
      <SourceAddress mask="{kvm.mask.value}">{kvm.ip.value}</SourceAddress>
    </MatchRule>
    </IPRules>
</AccessControl>

Suppose you're using a key value map (KVM) to store values for masking and IPs. This is a handy approach to changing IPs and masking during runtime without having to update and redeploy your API proxy. You can use the KeyValueMapOperations policy to retrieve the variables containing the values for kvm.mask.value and kvm.ip.value (assuming that's what you named the variables in your KVM policy that contain the values of the mask and IP values from your KVM). If the values you retrieved were 24 for the mask and 198.51.100.1 for the IP address, the AccessControl policy would deny all requests from: 198.51.100.*

All other client addresses would be allowed.

Deny 198.51.100.*

<AccessControl name="ACL">
  <IPRules noRuleMatchAction = "ALLOW">
    <MatchRule action = "DENY">
      <SourceAddress mask="24">198.51.100.1</SourceAddress>
    </MatchRule>
    </IPRules>
</AccessControl>

Deny all requests from client address: 198.51.100.*

Allow requests from any other client address.

198.51.*.*

<AccessControl name="ACL">
  <IPRules noRuleMatchAction = "ALLOW">
    <MatchRule action = "DENY">
       <SourceAddress mask="16">198.51.100.1</SourceAddress>
    </MatchRule>
  </IPRules>
</AccessControl>

Deny all requests from client address: 198.51.*.*

Allow requests from any other client address.

Deny 198.51.100.*, allow 192.0.2.1

<AccessControl name="ACL">
  <IPRules noRuleMatchAction = "ALLOW">
    <MatchRule action = "ALLOW">
      <SourceAddress mask="32">192.0.2.1</SourceAddress>
    </MatchRule>
    <MatchRule action = "DENY">
      <SourceAddress mask="24">198.51.100.1</SourceAddress>
    </MatchRule>
  </IPRules>
</AccessControl>

Deny all requests from client address: 198.51.100.*, but allow 192.0.2.1.

Allow requests from any other client address.

Allow 198.51.*.*

<AccessControl name="ACL">
  <IPRules noRuleMatchAction = "DENY">
    <MatchRule action = "ALLOW">
      <SourceAddress mask="16">198.51.100.1</SourceAddress>
    </MatchRule>
  </IPRules>
</AccessControl>

Allow all requests from address: 198.51.*.*

Deny requests from any other client address.

Allow multiple IPs

<AccessControl name="ACL">
  <IPRules noRuleMatchAction = "DENY">
    <MatchRule action = "ALLOW">
      <SourceAddress mask="24">198.51.100.1</SourceAddress>
      <SourceAddress mask="24">192.0.2.1</SourceAddress>
      <SourceAddress mask="24">203.0.113.1</SourceAddress>
     </MatchRule>
  </IPRules>
</AccessControl>

Allow requests from client addresses: 198.51.100.* 192.0.2.* 203.0.113.*

Deny all other addresses.

Deny multiple IPs

<AccessControl name="ACL">
  <IPRules noRuleMatchAction = "ALLOW">
    <MatchRule action = "DENY">
      <SourceAddress mask="24">198.51.100.1</SourceAddress>
      <SourceAddress mask="24">192.0.2.1</SourceAddress>
      <SourceAddress mask="24">203.0.113.1</SourceAddress>
    </MatchRule>
  </IPRules>
</AccessControl>

Deny requests from client addresses: 198.51.100.* 192.0.2.* 203.0.113.*

Allow all other addresses.

Allow multiple IPs, deny multiple IPs

<AccessControl name="ACL">
  <IPRules noRuleMatchAction = "DENY">
    <MatchRule action = "DENY">
      <SourceAddress mask="24">198.51.100.1</SourceAddress>
      <SourceAddress mask="24">192.0.2.1</SourceAddress>
      <SourceAddress mask="24">203.0.113.1</SourceAddress>
    </MatchRule>
    <MatchRule action = "ALLOW">
      <SourceAddress mask="16">198.51.100.1</SourceAddress>
      <SourceAddress mask="16">192.0.2.1</SourceAddress>
      <SourceAddress mask="16">203.0.113.1</SourceAddress>
    </MatchRule>
  </IPRules>
</AccessControl>

Allow: 198.51.*.* 192.0.*.* 203.0.*.*

Deny a subset of the allow list: 198.51.100.* 192.0.2.* 203.0.113.*


Usage notes

In addition to protecting your APIs against malicious IPs, the AccessControl policy also gives you control over legitimate IP access. For example, if you only want computers under the control of your enterprise to access the APIs exposed in your test environment, you can allow the IP address range for your internal network. Developers working from home can access these APIs using VPN.

The configuration and execution of an AccessControl policy involves the following:

  • Define a set of match rules with one of two actions (ALLOW or DENY) associated with each.
  • For each match rule, specify the IP address (SourceAddress element).
  • Specify the order in which the rules are tested.
  • All the match rules are executed in the given order. When a rules matches, the corresponding action is executed and following match rules are skipped.
    • If the same rule is configured with both ALLOW and DENY actions, the rule that is defined first in the order is triggered and the subsequent rule (with the other action) is skipped.

How the policy chooses which IP address to evaluate

IP addresses can come from various sources in a request. For example, the True-Client-IP message header might contain an IP address, and the X-Forwarded-For header may contain one or more IP addresses. This section describes how to configure the AccessControl policy to evaluate the exact IP address(es) you want it to evaluate.

Following is the logic the AccessControl policy uses to decide which IP address to evaluate:

1. True-Client-IP header

The policy first checks for an IP address in the True-Client-IP header. If the header contains a valid IP address, the policy evaluates that address.

2. X-Forwarded-For header

If there's no True-Client-IP header, or if you've set the <IgnoreTrueClientIPHeader> element to true, the policy evaluates the IP address(es) in the X-Forwarded-For header.

Apigee automatically populates the X-Forwarded-For header with the IP address it received from the last external TCP handshake (such as the client IP or router). If there are multiple IP addresses in the header, those addresses are likely the chain of servers that processed a request. However, the list of addresses could also contain a spoofed IP address. So how does the policy know which addresses to evaluate?

X-Forwarded-For dimensions in Apigee analytics

Apigee Analytics writes the value of the X-Forwarded-For header to the x_forwarded_for_ip dimension. To determine the client IP that made the request to Apigee, use the values in the ax_resolved_client_ip dimension but exclude ax_true_client_ip, which is not supported with the AccessControl policy. See Analytics metrics, dimensions, and filters reference.

About IP masking with CIDR notation

CIDR notation (Classless Inter-Domain Routing) is a way of indicating a range of IP addresses through masking. It applies to both IPv4 and IPv6. Here's how it works. We'll use IPv4 in our examples for simplicity.

IP addresses are groups of numbers separated by periods. In binary terms, each group is a specific number of bits (8 for IPv4 and 16 for IPv6). The IPv4 address 198.51.100.1 looks like this in binary:

11000110.00110011.01100100.00000001

That's 4 groups of 8 bits, or 32 total bits. With CIDR, you can indicate a range by adding a /number (1-32) to the IP address, like this:

198.51.100.1/24

In this case, the 24 is the number you would use for the mask attribute value in this policy.

This notation means, "Keep the first 24 bits exactly as is, the remaining bits can be any value 0 through 255." For example:

Keep these exactly as is Possible values for the last group
198.51.100. 0 - 255

Notice that the mask happens at the end of group three. This makes things nice and tidy, in essence creating a mask like this: 198.51.100.*. In most cases, using multiples of 8 (IPv4) and 16 (IPv6) will give you the masking level you want:

IPv4: 8, 16, 24, 32

IPv6: 16, 32, 48, 64, 80, 96, 112, 128

However, you can use other numbers for finer-grained control, which involves a little binary calculation. Here's an example using a mask of 30, as in 198.51.100.1/30, where the last 1 is 00000001 in binary:

Keep these exactly as is Possible values
11000110.00110011.01100100.000000 (first 30 bits) 00000000, 00000001, 00000010, or 00000011
198.51.100. 0, 1, 2, or 3

In this example, with the configuration set to <SourceAddress mask="30">198.51.100.1</SourceAddress>, the following IPs would be allowed (or denied, depending on your rules):

  • 198.51.100.0
  • 198.51.100.1
  • 198.51.100.2
  • 198.51.100.3

Element reference

The element reference describes the elements and attributes of the AccessControl policy.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AccessControl async="false" continueOnError="false" enabled="true" name="Access-Control-1">
    <DisplayName>Access Control 1</DisplayName>
    <IPRules noRuleMatchAction = "ALLOW">
        <MatchRule action = "ALLOW">
            <SourceAddress mask="32">198.51.100.1</SourceAddress>
        </MatchRule>
        <MatchRule action = "DENY">
            <SourceAddress mask="24">198.51.100.1</SourceAddress>
        </MatchRule>
    </IPRules>
    <ValidateBasedOn>X_FORWARDED_FOR_ALL_IP</ValidateBasedOn>
</AccessControl>

<AccessControl> attributes

<AccessControl async="false" continueOnError="false" enabled="true" name="Access-Control-1">

<ClientIPVariable> element

Specifies a flow variable containing an IP address that the policy checks against the IPRules. If the flow variable does not contain a valid IP address (ipv4 or ipv6), then the policy throws an error.

Suppose the flow variable is set to the 12.31.34.52. In the following example, access is denied. If the variable is set to 10.11.12.13, access is granted.

<AccessControl name='ACL'>
   <ClientIPVariable>FLOW_VARIABLE</ClientIPVariable>
   <IPRules noRuleMatchAction = 'DENY'>
     <MatchRule action = 'ALLOW'>
       <SourceAddress mask='32'>10.11.12.13</SourceAddress>
     </MatchRule>
   </IPRules>
</AccessControl>
Default N/A
Presence Optional
Type Flow variable

<IgnoreTrueClientIPHeader> element

When you set this to true, the policy ignores the True-Client-IP header and evaluates IP addresses in the X-Forwarded-For header, following the X-Forwarded-For evaluation behavior you've configured.

<AccessControl async="false" continueOnError="false" enabled="true" name="Access-Control-1">
    <DisplayName>Access Control-1</DisplayName>
    <IgnoreTrueClientIPHeader>true</IgnoreTrueClientIPHeader>
    ...
</AccessControl>
Default false
Presence Optional
Type Boolean

<IPRules> element

The parent element containing the rules that allow or deny IP addresses. The noRuleMatchAction attribute lets you define how to handle any IP addresses that aren't covered by your matching rules.

<IPRules noRuleMatchAction = "ALLOW">
Default N/A
Presence Optional
Type N/A

Attributes

Attribute Description Type Default Presence
noRuleMatchAction
The action to take (allow or deny access) if the match rule specified is not resolved (unmatched).
Valid value: ALLOW or DENY
String ALLOW Required

<IPRules>/<MatchRule> element

The action to take (allow or deny access) if the IP address matches the SourceAddress(es) you define.

<IPRules noRuleMatchAction = "ALLOW">
    <MatchRule action = "ALLOW">
        <SourceAddress mask="32">198.51.100.1</SourceAddress>
    </MatchRule>
    <MatchRule action = "DENY">
        <SourceAddress mask="24">198.51.100.1</SourceAddress>
    </MatchRule>
</IPRules>
Default N/A
Presence Optional
Type N/A

Attributes

Attribute Description Type Default Presence
action

The action to take (allow or deny access) if the match rule specified is not resolved (unmatched).

Valid value: ALLOW or DENY

String ALLOW Required

<IPRules>/<MatchRule>/<SourceAddress> element

The IP address range of a client.

Valid value: Valid IP address (dotted decimal notation). For wildcard behavior, use the mask attribute.

<IPRules noRuleMatchAction = "ALLOW">
    <MatchRule action = "ALLOW">
        <SourceAddress mask="{variable}">198.51.100.1</SourceAddress>
    </MatchRule>
    <MatchRule action = "DENY">
        <SourceAddress mask="24">{variable}</SourceAddress>
    </MatchRule>
</IPRules>

As shown in the previous example, the SourceAddress element also supports Message templates for the mask attribute or IP address, which means you can set the values using variables that are currently available in the API proxy flow.

For example, you can store an IP address in a key value map (KVM) and use the KeyValueMapOperations policy to retrieve the IP address and assign it to a variable (such as kvm.ip.value). You can then use that variable for the IP address:

<SourceAddress mask="24">{kvm.ip.value}</SourceAddress>

Setting mask and/or IP address with a variable gives you the flexibility to change values at runtime without having to modify and re-deploy your API proxy.

Default N/A
Presence Optional
Type String (single IP address only)

Attributes

Attribute Description Type Default Presence
mask

The mask attribute is a way to indicate the range of IP addresses to allow or deny. Mask is the equivalent of using CIDR notation (Classless Inter-Domain Routing). For example:

<SourceAddress mask="24">198.51.100.1</SourceAddress>

is equivalent to the following CIDR notation:

198.51.100.1/24

Valid values:

IPv4: 1-32

IPv6: 1-128

A value of zero (0) is valid only for IP 0.0.0.0, hence impractical.

Set the mask with a variable

The mask attribute also supports Message templates, which means you can set the value with a variable that's currently available in the API proxy flow. For example, you can store a mask value in a KVM and use the KeyValueMapOperations policy to retrieve the mask and assign it to a variable. To set the IP mask with the variable, use the following format, assuming the variable is named kvm.mask.value:

mask="{kvm.mask.value}"

Integer N/A Required

<ValidateBasedOn> element

When the X-Forwarded-For HTTP header contains multiple IP addresses, use this ValidateBasedOn element to control which IP addresses are evaluated.

Use this approach to evaluating a IP addresses only if you're certain about the validity of the IP addresses you want to evaluate. For example, if you choose to evaluate all IP addresses in the X-Forwarded-For header, you have to be able to trust the validity of those addresses, and/or set up comprehensive DENY or ALLOW rules to let only trusted IPs call your API proxy.

The leftmost IP address in the header belongs to the client, and the rightmost is the server which forwarded the request to the current service. The rightmost, or last IP address, is the address Apigee received from the last external TCP handshake.

The value you enter in this element lets you determine whether to check all IP addresses in the header (default), only the first IP address, or only the last IP address.

<AccessControl async="false" continueOnError="false" enabled="true" name="Access-Control-1">
    <DisplayName>Access Control 1</DisplayName>
    <IPRules noRuleMatchAction = "ALLOW">
        <MatchRule action = "DENY">
            <SourceAddress mask="32">198.51.100.1</SourceAddress>
        </MatchRule>
    </IPRules>
    <ValidateBasedOn>X_FORWARDED_FOR_ALL_IP</ValidateBasedOn>
</AccessControl>
Default X_FORWARDED_FOR_ALL_IP
Presence Optional
Valid values

X_FORWARDED_FOR_ALL_IP (default)

X_FORWARDED_FOR_FIRST_IP

X_FORWARDED_FOR_LAST_IP

Schemas

Each policy type is defined by an XML schema (.xsd). For reference, policy schemas are available on GitHub.

Error reference

This section describes the fault codes and error messages that are returned and fault variables that are set by Apigee when this policy triggers an error. This information is important to know if you are developing fault rules to handle faults. To learn more, see What you need to know about policy errors and Handling faults.

Runtime errors

These errors can occur when the policy executes.

Fault code HTTP status Cause Fix
accesscontrol.IPDeniedAccess 403 The client IP address, or an IP address passed in the API request, matches an IP address specified in the <SourceAddress> element within the <MatchRule> element of the Access Control Policy, and the action attribute of the <MatchRule> element is set to DENY.
accesscontrol.InvalidIPAddressInVariable 500 The flow variable in <ClientIPVariable> contains an invalid IP address.

Fault variables

These variables are set when a runtime error occurs. For more information, see Variables specific to policy errors.

Variables Where Example
fault.name="fault_name" fault_name is the name of the fault, as listed in the Runtime errors table above. The fault name is the last part of the fault code. fault.name Matches "IPDeniedAccess"
acl.policy_name.failed policy_name is the user-specified name of the policy that threw the fault. acl.AC-AllowAccess.failed = true

Example fault response

{
   "fault":{
     "faultstring":"Access Denied for client ip : 52.211.243.3"
      "detail":{
         "errorcode":"steps.accesscontrol.IPDeniedAccess"
      }
   }
}

Example fault rule

<FaultRule name="IPDeniedAccess">
    <Step>
        <Name>AM-IPDeniedAccess</Name>
        <Condition>(fault.name Matches "IPDeniedAccess") </Condition>
    </Step>
    <Condition>(acl.failed = true) </Condition>
</FaultRule>