CSRF

Apply a CSRF filter to the gateway to help prevent cross-site request forgery attacks.

About CSRF

According to OWASP, CSRF is defined as follows:

Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker’s choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.

To help prevent CSRF attacks, you can enable the CSRF filter on your gateway or a specific route. For each route that you apply the CSRF policy to, the filter checks to make sure that a request’s origin matches its destination. If the origin and destination do not match, a 403 Forbidden error code is returned.

ℹ️
Note that because CSRF attacks specifically target state-changing requests, the filter only acts on HTTP requests that have a state-changing method such as POST or PUT.
ℹ️
To learn more about CSRF, you can try out the CSRF sandbox in Envoy.

Before you begin

  1. Follow the Get started guide to install 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

Set up CSRF

Use a TrafficPolicy resource to define your CSRF rules.

  1. Create a TrafficPolicy resource to define your CSRF rules. The following example allows request from only the allowThisOne.example.com origin.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: TrafficPolicy
    metadata:
      name: csrf
      namespace: httpbin
    spec:
      csrf:
        percentageEnabled: 100
        additionalOrigins:
        - exact: allowThisOne.example.com
          ignoreCase: false
    EOF

    Review the following table to understand this configuration. For more information, see the API docs.

    Field Description
    percentageEnabled The percentage of requests for which the CSRF policy is enabled. A value of 100 means that all requests are enforced by the CSRF rules.
    additionalOrigins Additional origins that the CSRF policy allows, besides the destination origin. Possible values include exact, prefix, suffix, contains, safeRegex, and an ignoreCase boolean. At least one of exact, prefix, suffix, contains, or safeRegex must be set. The example allows requests from the allowThisOne.example.com exact origin.
  2. Create an HTTPRoute resource for the httpbin app that applies the TrafficPolicy resource that you just created.

    kubectl apply -f- <<EOF
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: httpbin-csrf
      namespace: httpbin
    spec:
      parentRefs:
      - name: http
        namespace: kgateway-system
      hostnames:
        - csrf.example
      rules:
        - filters:
            - type: ExtensionRef
              extensionRef:
                group: gateway.kgateway.dev
                kind: TrafficPolicy
                name: csrf
          backendRefs:
            - name: httpbin
              port: 8000
    EOF
  3. Send a request to the httpbin app on the csrf.example domain. Verify that you get back a 403 HTTP response code because no origin is set in your request.

    curl -vi -X POST http://$INGRESS_GW_ADDRESS:8080/post -H "host: csrf.example:8080"
    curl -vi -X POST localhost:8080/post -H "host: csrf.example"

    Example output:

    HTTP/1.1 403 Forbidden
    ...
    Invalid origin
    
  4. Send another request to the httpbin app. This time, you include the allowThisOne.example.com origin header. Verify that you get back a 200 HTTP response code, because the origin matches the origin that you specified in the TrafficPolicy resource.

    curl -vi -X POST http://$INGRESS_GW_ADDRESS:8080/post -H "host: csrf.example:8080" -H "origin: allowThisOne.example.com"
    curl -vi -X POST localhost:8080/post -H "host: csrf.example" -H "origin: allowThisOne.example.com"

    Example output:

    HTTP/1.1 200 OK
    ...
    {
      "args": {},
      "headers": {
        "Accept": [
          "*/*"
        ],
        "Content-Length": [
          "0"
        ],
        "Host": [
          "csrf.example:8080"
        ],
        "Origin": [
          "allowThisOne.example.com"
        ],
        "User-Agent": [
          "curl/7.77.0"
        ],
        "X-Envoy-Expected-Rq-Timeout-Ms": [
          "15000"
        ],
        "X-Forwarded-Proto": [
          "http"
       ],
        "X-Request-Id": [
          "b1b53950-f7b3-47e6-8b7b-45a44196f1c4"
        ]
      },
      "origin": "10.X.X.XX:33896",
      "url": "http://csrf.example:8080/post",
      "data": "",
      "files": null,
      "form": null,
      "json": null
    }
    

Monitor CSRF metrics

  1. Port-forward the gateway proxy.

    kubectl port-forward -n kgateway-system deploy/http 19000
  2. Open the /stats endpoint.

  3. Find the statistics for csrf.

    Example output:

    http.http.csrf.missing_source_origin: 1
    http.http.csrf.request_invalid: 0
    http.http.csrf.request_valid: 1
    ...
    
    • missing_source_origin: The number of requests that were sent without an origin. The number is 1 because you sent the first request without an origin.
    • request_invalid: The number of requests that were sent with an origin that did not match the destination origin. The number is 0 because you did not send an invalid request.
    • request_valid: The number of requests that were sent with an origin that matched the destination origin. The number is 1 because you sent a valid request in your second request.

Cleanup

You can remove the resources that you created in this guide.
kubectl delete TrafficPolicy csrf -n httpbin
kubectl delete httproute httpbin-csrf -n httpbin