Configure IP-based access control for applications running in a Service Mesh (ASM) instance. Use Istio AuthorizationPolicy resources to deny or allow traffic from specific IP addresses at the ingress gateway or the sidecar proxy level.
How it works
ASM uses Istio authorization policies to filter traffic by source IP address. Two fields control which IP addresses are evaluated:
| Field | Matches against | Use when |
|---|---|---|
ipBlocks | The source IP of the direct TCP connection (downstream_remote_address) | No Layer 7 proxy sits between the client and the ASM gateway, and externalTrafficPolicy is set to Local |
remoteIpBlocks | The original client IP derived from the X-Forwarded-For header | A Layer 7 proxy or HTTP load balancer sits in front of the ASM gateway |
Prerequisites
Before you begin, make sure that you have:
An ASM instance v1.15.3.25 or later with a cluster added. For more information, see Add a cluster to an ASM instance
Automatic sidecar proxy injection enabled. For more information, see Enable automatic sidecar proxy injection
The httpbin application deployed and accessible. For more information, see Deploy the httpbin application
Usage notes
All examples in this topic set the
externalTrafficPolicyfield of the gateway service toLocal. IfexternalTrafficPolicyis set toCluster, source IP addresses are not preserved, and IP-based access control does not work as expected.
Scenario 1: No Layer 7 proxy between the client and the ASM gateway
When no Layer 7 proxy sits between the client and the ASM gateway, the gateway receives traffic directly. Both ipBlocks and remoteIpBlocks identify the real client IP address.
To verify, access the httpbin application:
curl <gateway-ip>:80/ -IReplace <gateway-ip> with the external IP address of your ASM ingress gateway.
Verify the client IP in access logs
Gateway access logs:
{
"downstream_remote_address": "106.XX.XX.1:58656",
"x_forwarded_for": "106.11.XX.X",
"response_code": "200",
...
}Both downstream_remote_address and x_forwarded_for reflect the real client IP (106.11.XX.X in this example), so both ipBlocks and remoteIpBlocks work correctly at the gateway level.
Sidecar proxy access logs:
{
"downstream_remote_address": "106.11.XX.X:0",
"x_forwarded_for": "106.11.XX.X",
"response_code": "200",
...
}The sidecar proxy receives the real client IP in x_forwarded_for. The port in downstream_remote_address is 0 because the address is inferred from headers, not from the physical TCP connection.
Example 1: Deny an IP address at the gateway
The following AuthorizationPolicy denies traffic from a specific IP address at the ingress gateway.
Test with ipBlocks
Create a file named
gateway-test.yamlwith the following content: Replace106.11.XX.Xwith the IP address to deny.apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: gateway-test namespace: istio-system spec: action: DENY rules: - from: - source: ipBlocks: - 106.11.XX.X selector: matchLabels: istio: ingressgatewayDeploy the authorization policy:
kubectl apply -f gateway-test.yamlAccess the httpbin application: Expected output: A
403 Forbiddenresponse confirms the IP address is denied.curl <gateway-ip>:80/ -IHTTP/1.1 403 Forbidden content-length: 19 content-type: text/plain server: istio-envoy
Test with remoteIpBlocks
Create a file named
gateway-test.yamlwith the following content:apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: gateway-test namespace: istio-system spec: action: DENY rules: - from: - source: remoteIpBlocks: - 106.11.XX.X selector: matchLabels: istio: ingressgatewayDeploy the authorization policy:
kubectl apply -f gateway-test.yamlAccess the httpbin application: Expected output: Both
ipBlocksandremoteIpBlocksproduce the same result because no Layer 7 proxy is involved.curl <gateway-ip>:80/ -IHTTP/1.1 403 Forbidden content-length: 19 content-type: text/plain server: istio-envoy
Example 2: Deny an IP address at the sidecar proxy
Apply the authorization policy to the sidecar proxy instead of the gateway.
Test with ipBlocks
Create a file named
gateway-test.yamlwith the following content:apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: gateway-test namespace: default spec: action: DENY rules: - from: - source: ipBlocks: - 106.11.XX.X selector: matchLabels: app: httpbinDeploy the authorization policy:
kubectl apply -f gateway-test.yamlAccess the httpbin application: Expected output: The request succeeds because
ipBlocksmatches againstdownstream_remote_address-- the IP of the pod that established the TCP connection. At the sidecar proxy, this is always the gateway pod IP, not the original client IP. Thedownstream_remote_addressfield indicates the remote address of the downstream connection. It is not always the real physical address of the remote end -- the address may be inferred from the Proxy Protocol filter or anX-Forwarded-Forrequest header.NoteTo block traffic with
ipBlocksat the sidecar level, specify the gateway pod IP address instead of the client IP. TheipBlocksfield matches the direct TCP connection source, which at the sidecar proxy is the gateway pod.curl <gateway-ip>:80/ -IHTTP/1.1 200 OK server: istio-envoy content-type: text/html; charset=utf-8
Test with remoteIpBlocks
Create a file named
gateway-test.yamlwith the following content:apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: gateway-test namespace: default spec: action: DENY rules: - from: - source: remoteIpBlocks: - 106.11.XX.X selector: matchLabels: app: httpbinDeploy the authorization policy:
kubectl apply -f gateway-test.yamlAccess the httpbin application: Expected output:
remoteIpBlockscorrectly blocks the client IP because it reads from theX-Forwarded-Forheader, which preserves the original client address.curl <gateway-ip>:80/ -IHTTP/1.1 403 Forbidden content-length: 19 content-type: text/plain server: istio-envoy
Scenario 2: A Layer 7 proxy between the client and the ASM gateway
When a Layer 7 proxy (such as an HTTP load balancer) sits in front of the ASM gateway, the X-Forwarded-For header contains a chain of IP addresses from each proxy in the path. This scenario only tests remoteIpBlocks because ipBlocks matches the physical peer IP -- the proxy, not the original client.
Understand the X-Forwarded-For chain
Gateway access logs:
{
"downstream_remote_address": "106.11.XX.X:62232",
"x_forwarded_for": "56.5.X.X, 72.9.X.X, 98.1.X.X, 106.11.XX.X",
"response_code": "200",
...
}The X-Forwarded-For header contains four IP addresses:
56.5.X.X-- the original client IP72.9.X.X,98.1.X.X-- intermediate proxies106.11.XX.X-- added by the ASM gateway itself
Before the request reached the ASM gateway, the header value was 56.5.X.X, 72.9.X.X, 98.1.X.X. The gateway appended 106.11.XX.X (the downstream_remote_address of the direct connection).
Sidecar proxy access logs:
{
"downstream_remote_address": "106.11.XX.X:0",
"x_forwarded_for": "56.5.X.X, 72.9.X.X, 98.1.X.X, 106.11.XX.X",
...
}At the sidecar level, downstream_remote_address is not the physical peer address -- it is the last IP from the X-Forwarded-For header. The port is 0 because the address is derived from header data, not from a TCP connection.
Example 1: Deny the last IP in the X-Forwarded-For chain at the gateway
By default, numTrustedProxies is 0, so remoteIpBlocks evaluates the last IP in the X-Forwarded-For header.
Create a file named
gateway-test.yamlwith the following content:apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: gateway-test-ap-wg-gateway-test-istio-system-gateway-ingressgateway namespace: istio-system spec: action: DENY rules: - from: - source: remoteIpBlocks: - 106.11.XX.X selector: matchLabels: istio: ingressgatewayDeploy the authorization policy:
kubectl apply -f gateway-test.yamlSimulate a request with an
X-Forwarded-Forheader: Expected output: The gateway denies the last IP inX-Forwarded-For(106.11.XX.X, appended by the gateway from the direct connection).curl <gateway-ip>:80/ -H 'X-Forwarded-For: 56.5.X.X, 72.9.X.X, 98.1.X.X' -IHTTP/1.1 403 Forbidden content-length: 19 content-type: text/plain server: istio-envoy
Example 2: Change the trusted proxy count with numTrustedProxies
The numTrustedProxies setting controls how many proxies in the X-Forwarded-For chain the gateway considers trusted. This shifts which IP address remoteIpBlocks evaluates.
numTrustedProxies | Trusted proxies (from right) | IP evaluated by remoteIpBlocks |
|---|---|---|
| 0 (default) | None | Last IP in X-Forwarded-For |
| 1 | 1 rightmost proxy | Second-to-last IP |
| 2 | 2 rightmost proxies | Third-to-last IP |
Add the following annotation to the
specfield of the ingress gateway YAML file. For more information about editing the gateway configuration, see Manage the ingress gateway in the ASM console.WarningThis change restarts the gateway pods.
podAnnotations: proxy.istio.io/config: '{"gatewayTopology" : { "numTrustedProxies": 2 } }'After the gateway restarts, access the httpbin application: Expected output: The request succeeds. With
numTrustedProxiesset to2, the gateway trusts the two rightmost proxies and evaluates the third-to-last IP (72.9.X.X) againstremoteIpBlocks. Since the authorization policy denies106.11.XX.X(the last IP), the request is no longer blocked.curl <gateway-ip>:80/ -H 'X-Forwarded-For: 56.5.X.X, 72.9.X.X, 98.1.X.X' -IHTTP/1.1 200 OK server: istio-envoy content-type: text/html; charset=utf-8
Example 3: Deny the third-to-last IP in the X-Forwarded-For chain
With numTrustedProxies set to 2, create a policy that denies the IP address the gateway now evaluates.
Create a file named
gateway-test.yamlwith the following content:apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: gateway-test-ap-wg-gateway-test-istio-system-gateway-ingressgateway namespace: istio-system spec: action: DENY rules: - from: - source: remoteIpBlocks: - 72.9.X.X selector: matchLabels: istio: ingressgatewayDeploy the authorization policy:
kubectl apply -f gateway-test.yamlAccess the httpbin application: Expected output: The third-to-last IP (
72.9.X.X) is denied, confirming thatnumTrustedProxies: 2shifts the evaluation target.curl <gateway-ip>:80/ -H 'X-Forwarded-For: 56.5.X.X, 72.9.X.X, 98.1.X.X' -IHTTP/1.1 403 Forbidden content-length: 19 content-type: text/plain server: istio-envoy
Example 4: Apply the same policy to the sidecar proxy
Change the authorization policy scope from the gateway to the httpbin sidecar proxy.
Create a file named
gateway-test.yamlwith the following content:apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: gateway-test namespace: default spec: action: DENY rules: - from: - source: remoteIpBlocks: - 72.9.X.X selector: matchLabels: app: httpbinDeploy the authorization policy:
kubectl apply -f gateway-test.yamlAccess the httpbin application: Expected output: The request succeeds because the
numTrustedProxiessetting was applied only to the gateway. The sidecar proxy uses the default value (0) and evaluates the last IP in theX-Forwarded-Forheader. Since the policy denies72.9.X.Xbut the sidecar evaluates106.11.XX.X, the request is not blocked.NoteTo deny traffic at the sidecar level, set
remoteIpBlocksto the last IP in theX-Forwarded-Forheader as seen in the sidecar proxy logs. ThenumTrustedProxiessetting on the gateway does not affect sidecar proxy behavior.curl <gateway-ip>:80/ -H 'X-Forwarded-For: 56.5.X.X, 72.9.X.X, 98.1.X.X' -IHTTP/1.1 200 OK server: istio-envoy content-type: text/html; charset=utf-8
Troubleshooting
Authorization policy has no effect
If the policy does not block traffic as expected:
Confirm
externalTrafficPolicyis set toLocalon the gateway service. If set toCluster, the source IP is replaced with a node IP.Check gateway access logs to identify the actual values of
downstream_remote_addressandx_forwarded_for. Make sure the IP in your policy matches what appears in the logs.
ipBlocks does not work on the sidecar proxy
When you apply an ipBlocks-based policy to a sidecar proxy, the downstream_remote_address is the gateway pod IP, not the client IP. Use remoteIpBlocks instead, or specify the gateway pod IP in ipBlocks.
numTrustedProxies does not affect the sidecar proxy
The numTrustedProxies setting is a gateway-level configuration. Sidecar proxies always use the default value (0), evaluating the last IP in the X-Forwarded-For header. Adjust your remoteIpBlocks value accordingly when targeting sidecar proxies.
Clean up
After testing, remove the authorization policies to avoid blocking legitimate traffic:
kubectl delete authorizationpolicy gateway-test -n istio-system
kubectl delete authorizationpolicy gateway-test -n defaultIf you modified numTrustedProxies, remove the podAnnotations from the ingress gateway configuration and wait for the gateway pods to restart.
What's next
ASM security policies -- Simplify authorization policy management through the ASM console.
Manage the ingress gateway in the ASM console -- Edit gateway configurations including
numTrustedProxies.