Skip to content

For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.

IP-based filtering

Page as Markdown

Block or allow traffic based on the client IP address or CIDR range.

About IP-based filtering

Not all traffic to your apps is legitimate. Malicious actors, botnets, and malicious IP address ranges can target your services with brute-force attacks, credential stuffing, or reconnaissance. Without IP-level filtering at the gateway proxy, these requests reach your app and can consume its resources.

Solo Enterprise for kgateway lets you block or allow traffic based on the client IP address by using the Coraza REMOTE_ADDR variable in a WAFPolicy. IP filtering rules run in the earliest phase (phase:1), so blocked requests are rejected before the request body is buffered or forwarded to the upstream.

In production, requests typically pass through one or more proxies, such as cloud load balancers, CDNs, or reverse proxies before they reach the gateway. Each hop appends an IP address to the X-Forwarded-For header. By default, the gateway proxy uses the socket-level peer IP address of the client to determine the source IP address. IP addresses that are sent in the X-Forwarded-For request header are ignored by default.

You can change the default behavior and instead configure the gateway proxy to derive the REMOTE_ADDR from the X-Forwarded-For header instead. To do that, you create a ListenerPolicy with the following settings:

  • useRemoteAddress: true (default setting): Append the socket-level peer IP to the X-Forwarded-For header.
  • xffNumTrustedHops: In cases where the request passes through multiple trusted proxy hops, each hop appends an IP address to the X-Forwarded-For header. The xffNumTrustedHops setting defines the number of addresses that Envoy ignores from the right to find the true client IP. For example, xffNumTrustedHops: 1 causes Envoy to ignore the rightmost entry and use the entry immediately to its left as REMOTE_ADDR.

The following table shows how the gateway proxy determines the value of REMOTE_ADDR:

ListenerPolicy fieldREMOTE_ADDR value
default.httpSettings.useRemoteAddress: true (default)Use the immediate downstream socket peer IP address. The X-Forwarded-For header is ignored.
default.httpSettings.useRemoteAddress: true, xffNumTrustedHops: NUse the Nth address from the right of the X-Forwarded-For header.

Note that you can use IP filtering rules alongside CRS and custom rules in the same WAFPolicy to create a multi-layer defense-in-depth system.

Before you begin

  1. Follow the Get started guide to install Solo Enterprise for kgateway.

  2. Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.

  3. Get the external address of the gateway and save it in an environment variable.

    export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
    echo $INGRESS_GW_ADDRESS  
    kubectl port-forward deployment/http -n kgateway-system 8080:8080

  1. Enable the WAF server for your GatewayClass.

Configure IP-based filtering

Use Coraza’s REMOTE_ADDR variable to block specific client IP addresses or CIDR ranges. For testing purposes, this guide configures a ListenerPolicy so that the client IP is derived from the X-Forwarded-For request header instead of the socket-level peer IP.

  1. Create a ListenerPolicy that configures Envoy to use the X-Forwarded-For header to determine the client IP.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: ListenerPolicy
    metadata:
      name: xff-trust
      namespace: kgateway-system
    spec:
      targetRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: http
      default:
        httpSettings:
          useRemoteAddress: true
          xffNumTrustedHops: 1
    EOF
    SettingDescription
    default.httpSettings.useRemoteAddress: trueEnvoy appends the socket-level peer IP to the X-Forwarded-For header and uses the downstream address for connection metadata. Combined with xffNumTrustedHops: 1, Envoy selects the address one hop back from the right in the X-Forwarded-For header as the trusted client IP, which is what the WAF server receives as REMOTE_ADDR.
    default.httpSettings.xffNumTrustedHops: 1Each proxy hop in the request chain appends an IP address to the X-Forwarded-For request header. Because of that, the header can hold multiple IP addresses in a comma-separated list. The gateway proxy (Envoy) uses this header to determine the number of trusted proxies that the request passed through and the value to use for the REMOTE_ADDR variable. The xffNumTrustedHops setting defines the number of IP addresses that the gateway needs to ignore to find the value for the REMOTE_ADDR variable, starting from the right. For example, setting this field to 1 means that the gateway proxy ignores the right-most entry and reads the entry immediately to its left as REMOTE_ADDR. The example in this guide assumes that the X-Forwarded-For request header contains two IP addresses: the IP address of the client that sent the request, such as your local machine, and the gateway proxy. A value of 1 means that the gateway proxy ignores the gateway proxy IP address and instead uses the client IP address for the REMOTE_ADDR variable.
  2. Create a WAFPolicy that blocks requests from the 198.51.100.0/24 CIDR range.

    kubectl apply -f- <<EOF
    apiVersion: waf.solo.io/v1alpha1
    kind: WAFPolicy
    metadata:
      name: httpbin-waf
      namespace: httpbin
    spec:
      ruleEngineSettings:
        inline: |
          SecRuleEngine On
      customDirectives:
        - inline: |
            SecRule REMOTE_ADDR "@ipMatch 198.51.100.0/24" "id:1001,phase:1,deny,status:403,msg:'blocked IP'"
    EOF
    SettingDescription
    @ipMatchCoraza operator that matches IP addresses or CIDR ranges. Accepts a comma-separated list, for example 10.0.0.0/8,172.16.0.0/12.
    phase:1Evaluates the rule during request headers processing, before the body is buffered.
  3. Create an EnterpriseKgatewayTrafficPolicy to attach the WAFPolicy to the httpbin HTTPRoute.

    kubectl apply -f- <<EOF
    apiVersion: enterprisekgateway.solo.io/v1alpha1
    kind: EnterpriseKgatewayTrafficPolicy
    metadata:
      name: httpbin-waf-ip
      namespace: httpbin
    spec:
      targetRefs:
        - group: gateway.networking.k8s.io
          kind: HTTPRoute
          name: httpbin
      entWAF:
        wafPolicyRef:
          name: httpbin-waf
    EOF
  4. Send a request with an allowed IP address in the X-Forwarded-For. Verify that you get a 200 response.

    curl -i http://$INGRESS_GW_ADDRESS:8080/status/200 -H "host: www.example.com:8080" \
      -H "X-Forwarded-For: 10.0.0.1"
    curl -i localhost:8080/status/200 -H "host: www.example.com" \
      -H "X-Forwarded-For: 10.0.0.1"

    Example output:

    HTTP/1.1 200 OK
    ...
    
  5. Send a request with a blocked IP address in the X-Forwarded-For request header. Verify that the WAF blocks the request with a 403 response.

    curl -vik http://$INGRESS_GW_ADDRESS:8080/status/200 -H "host: www.example.com:8080" \
      -H "X-Forwarded-For: 198.51.100.10"
    curl -vik localhost:8080/status/200 -H "host: www.example.com" \
      -H "X-Forwarded-For: 198.51.100.10"

    Example output:

    HTTP/1.1 403 Forbidden
    ...
    

Next

Cleanup

You can optionally remove the resources that you set up as part of this guide.
kubectl delete wafpolicy -n httpbin httpbin-waf
kubectl delete enterprisekgatewaytrafficpolicy -n httpbin httpbin-waf-ip
kubectl delete listenerpolicy -n kgateway-system xff-trust

Other configurations

Review other common configurations.

IP allowlisting

Instead of blocking malicious IP addresses, you can invert the logic to allow only trusted IP addresses. The !@ipMatch negation denies any request with a source IP address that is not in the allowlist.

apiVersion: waf.solo.io/v1alpha1
kind: WAFPolicy
metadata:
  name: ip-allowlist
  namespace: httpbin
spec:
  ruleEngineSettings:
    inline: |
      SecRuleEngine On
  customDirectives:
    - inline: |
        SecRule REMOTE_ADDR "!@ipMatch 203.0.113.0/24,198.51.100.50" \
          "phase:1,deny,status:403,id:1,msg:'IP not in allowlist'"

Per-route IP allowlists with sectionName

In multi-team environments, different routes might need independent IP address restrictions. Use the sectionName in the EnterpriseKgatewayTrafficPolicy to add WAF rules to specific HTTPRoute routes. This setup allows you to make updates to your WAF rules without impacting other teams.

apiVersion: waf.solo.io/v1alpha1
kind: WAFPolicy
metadata:
  name: ip-allowlist-team-a
  namespace: httpbin
spec:
  ruleEngineSettings:
    inline: |
      SecRuleEngine On
  customDirectives:
    - inline: |
        SecRule REMOTE_ADDR "!@ipMatch 203.0.113.0/24" \
          "phase:1,deny,status:403,id:1,msg:'IP not in team-a allowlist'"
---
apiVersion: waf.solo.io/v1alpha1
kind: WAFPolicy
metadata:
  name: ip-allowlist-team-b
  namespace: httpbin
spec:
  ruleEngineSettings:
    inline: |
      SecRuleEngine On
  customDirectives:
    - inline: |
        SecRule REMOTE_ADDR "!@ipMatch 198.51.100.0/24" \
          "phase:1,deny,status:403,id:1,msg:'IP not in team-b allowlist'"

Then attach each policy to its route rule:

apiVersion: enterprisekgateway.solo.io/v1alpha1
kind: EnterpriseKgatewayTrafficPolicy
metadata:
  name: waf-team-a
  namespace: httpbin
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: httpbin-route
      sectionName: team-a
  entWAF:
    wafPolicyRef:
      name: ip-allowlist-team-a
---
apiVersion: enterprisekgateway.solo.io/v1alpha1
kind: EnterpriseKgatewayTrafficPolicy
metadata:
  name: waf-team-b
  namespace: httpbin
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: httpbin-route
      sectionName: team-b
  entWAF:
    wafPolicyRef:
      name: ip-allowlist-team-b

Combining IP filtering with attack detection

You can layer IP filtering with additional rules in the same WAFPolicy for defense in depth. For example, combine an IP allowlist with SQL injection and XSS detection so that even allowed IPs are still inspected for malicious payloads.

apiVersion: waf.solo.io/v1alpha1
kind: WAFPolicy
metadata:
  name: ip-allowlist-with-attack-detection
  namespace: httpbin
spec:
  ruleEngineSettings:
    inline: |
      SecRuleEngine On
  customDirectives:
    - inline: |
        SecRule REMOTE_ADDR "!@ipMatch 203.0.113.0/24,198.51.100.50" \
          "phase:1,deny,status:403,id:1,msg:'IP not in allowlist'"
    - inline: |
        SecRule QUERY_STRING "@rx (?i:union.*select|select.*from|drop\s+table|insert\s+into)" \
          "phase:1,deny,status:403,id:2,msg:'SQL injection detected'"
    - inline: |
        SecRule QUERY_STRING "@rx (?i:%3Cscript|<script|javascript:|onerror=|onload=)" \
          "phase:1,deny,status:403,id:3,msg:'XSS detected'"
Was this page helpful?