Revenir au blog

Follow and Subscribe

Disponible uniquement en anglais

Cette page n'est actuellement disponible qu'en anglais. Nous nous excusons pour la gêne occasionnée, merci de revenir sur cette page ultérieurement.

Testing Next-Gen WAF Rate Limiting Rule with GitHub Actions

Brooks Cunningham

Senior Security Strategist, Fastly

In this blog post, we'll show you how to configure and test a rate limiting rule. Rate limiting is a technique used to control the number of requests that can be made to a given endpoint within a specified time period. This can be useful for preventing denial-of-service attacks (DDoS) and enforcing Terms of Service. In order to get started, you’ll need the following:

Prerequisites

  • A Fastly account, if you don’t already have an account, grab a free account here

  • A GitHub account

Create a rate limiting rule

Create a rule like the following screenshot. This rule will rate-limit on the request header “api-key”. When there are 5 or more requests with the same api-key within 1 minute window, then that api-key will be blocked.

Test the Rule with a Github Action

Now that the rule has been deployed, you can test it by making a series of requests to the protected endpoint. In this example, you will use a Github action to spin up a Next-Gen WAF Agent. With the same GitHub Action, you will send requests with curl to the Agent. If you make more requests than the specified limit, you should receive a 429 Too Many Requests error.

For the following GitHub Action to work you will need to set up your Next-Gen WAF Agent Access Key ID and Secret Access Key as Github Secrets.

name: Rate Limiting Demo

on:
  workflow_dispatch:

jobs:
  ngwaf-rate-limiting-demo:
    runs-on: ubuntu-latest
    environment: production
    timeout-minutes: 2
    services:
      sigsci:    
        image: signalsciences/sigsci-agent:latest
        env:
         SIGSCI_ACCESSKEYID: ${{ secrets.NGWAF_TERRAFORM_NGWAF_SITE_ACCESSKEYID }}
         SIGSCI_SECRETACCESSKEY: ${{ secrets.NGWAF_TERRAFORM_NGWAF_SITE_SECRETACCESSKEY }}
         SIGSCI_REVPROXY_LISTENER: "app1:{listener=http://0.0.0.0:8888,upstreams=https://http-me.edgecompute.app:443/,pass-host-header=false};"
         SIGSCI_UPLOAD_INTERVAL: "5s"
        ports:
          - 8888:8888

    steps:
      - name: Wait for NGWAF Agent
        run: |
          # Wait for agent to be ready
          sleep 5    

      - name: Send requests through NGWAF
        run: |
          echo "#### Sending requests for rate limiting test domains"
          for i in {1..10}; do
            echo "Request Number: $i"
            response=$(curl -si -H 'host:api.bubbs.coffee' -X POST 'http://0.0.0.0:8888/anything/start-ai-chat' -H api-key:gh-action-rl-demo)
            echo "$response"
            # Extract the HTTP status code from the first line (e.g., "HTTP/1.1 406 Not Acceptable")
            status=$(echo "$response" | head -n 1 | awk '{print $2}')
            if [ $i -gt 4 ] && [ "$status" != "406" ]; then
              echo "Error: Unexpected status code: $status on request $i"
              exit 1
            fi
            echo
          done

      - name: Wait for logs to upload
        run: |
          # Wait for agent to upload logs
          sleep 10

Example of failed GitHub Action output

Without the rule in place, you should see the job fail at the 5th request.

#### Sending requests for rate limiting test domains
Request Number: 1
HTTP/1.1 200 OK
Content-Length: 564
Content-Type: application/json
Date: Thu, 06 Feb 2025 22:16:03 GMT
X-Served-By: cache-iad-kiad7000049-IAD

{"args":"","body":"","headers":{"accept":"*/*","accept-encoding":"gzip","api-key":"gh-action-rl-demo","content-length":"0","host":"http-me.edgecompute.app","user-agent":"curl/8.5.0","x-forwarded-for":"172.18.0.1","x-forwarded-host":"api.bubbs.coffee","x-forwarded-port":"80","x-forwarded-server":"4b89e8de722e","x-real-ip":"172.18.0.1","x-sigsci-agentresponse":"200","x-sigsci-requestid":"67a534a42c015ef72d509c9f","x-sigsci-tags":"NO-CONTENT-TYPE,SUSPECTED-BOT"},"ip":"172.214.46.82","method":"POST","url":"https://http-me.edgecompute.app/anything/start-ai-chat"}

Request Number: 2
HTTP/1.1 200 OK
Content-Length: 564
Content-Type: application/json
Date: Thu, 06 Feb 2025 22:16:03 GMT
X-Served-By: cache-iad-kiad7000057-IAD

{"args":"","body":"","headers":{"accept":"*/*","accept-encoding":"gzip","api-key":"gh-action-rl-demo","content-length":"0","host":"http-me.edgecompute.app","user-agent":"curl/8.5.0","x-forwarded-for":"172.18.0.1","x-forwarded-host":"api.bubbs.coffee","x-forwarded-port":"80","x-forwarded-server":"4b89e8de722e","x-real-ip":"172.18.0.1","x-sigsci-agentresponse":"200","x-sigsci-requestid":"67a534a42c015ef72d509ca0","x-sigsci-tags":"NO-CONTENT-TYPE,SUSPECTED-BOT"},"ip":"172.214.46.82","method":"POST","url":"https://http-me.edgecompute.app/anything/start-ai-chat"}

... truncated for brevity

Request Number: 5
HTTP/1.1 200 OK
Content-Length: 564
Content-Type: application/json
Date: Thu, 06 Feb 2025 22:16:03 GMT
X-Served-By: cache-iad-kiad7000153-IAD

{"args":"","body":"","headers":{"accept":"*/*","accept-encoding":"gzip","api-key":"gh-action-rl-demo","content-length":"0","host":"http-me.edgecompute.app","user-agent":"curl/8.5.0","x-forwarded-for":"172.18.0.1","x-forwarded-host":"api.bubbs.coffee","x-forwarded-port":"80","x-forwarded-server":"4b89e8de722e","x-real-ip":"172.18.0.1","x-sigsci-agentresponse":"200","x-sigsci-requestid":"67a534a42c015ef72d509ca3","x-sigsci-tags":"NO-CONTENT-TYPE,SUSPECTED-BOT"},"ip":"172.214.46.82","method":"POST","url":"https://http-me.edgecompute.app/anything/start-ai-chat"}
Error: Unexpected status code: 200 on request 5

Example of successful GitHub Action output

Below is an example that shows a successful GitHub Action run. The block action 406 is returned on requests 5 through 10.

#### Sending requests for rate limiting test domains
Request Number: 1
HTTP/1.1 200 OK
Content-Length: 582
Content-Type: application/json
Date: Tue, 25 Feb 2025 20:59:19 GMT
X-Served-By: cache-chi-kigq8000027-CHI

{"args":"","body":"","headers":{"accept":"*/*","accept-encoding":"gzip","api-key":"gh-action-rl-demo","content-length":"0","host":"http-me.edgecompute.app","user-agent":"curl/8.5.0","x-forwarded-for":"172.18.0.1","x-forwarded-host":"api.bubbs.coffee","x-forwarded-port":"80","x-forwarded-server":"a1f860dbb73e","x-real-ip":"172.18.0.1","x-sigsci-agentresponse":"200","x-sigsci-requestid":"67be2f28d07cd4dde0e138fd","x-sigsci-tags":"NO-CONTENT-TYPE,SUSPECTED-BOT,site.api-key-rl"},"ip":"172.183.229.144","method":"POST","url":"https://http-me.edgecompute.app/anything/start-ai-chat"}

Request Number: 2
HTTP/1.1 200 OK
Content-Length: 582
Content-Type: application/json
Date: Tue, 25 Feb 2025 20:59:20 GMT
X-Served-By: cache-chi-kigq8000027-CHI

{"args":"","body":"","headers":{"accept":"*/*","accept-encoding":"gzip","api-key":"gh-action-rl-demo","content-length":"0","host":"http-me.edgecompute.app","user-agent":"curl/8.5.0","x-forwarded-for":"172.18.0.1","x-forwarded-host":"api.bubbs.coffee","x-forwarded-port":"80","x-forwarded-server":"a1f860dbb73e","x-real-ip":"172.18.0.1","x-sigsci-agentresponse":"200","x-sigsci-requestid":"67be2f28d07cd4dde0e138fe","x-sigsci-tags":"NO-CONTENT-TYPE,SUSPECTED-BOT,site.api-key-rl"},"ip":"172.183.229.144","method":"POST","url":"https://http-me.edgecompute.app/anything/start-ai-chat"}

Request Number: 3
HTTP/1.1 200 OK
Content-Length: 582
Content-Type: application/json
Date: Tue, 25 Feb 2025 20:59:20 GMT
X-Served-By: cache-chi-kigq8000027-CHI

{"args":"","body":"","headers":{"accept":"*/*","accept-encoding":"gzip","api-key":"gh-action-rl-demo","content-length":"0","host":"http-me.edgecompute.app","user-agent":"curl/8.5.0","x-forwarded-for":"172.18.0.1","x-forwarded-host":"api.bubbs.coffee","x-forwarded-port":"80","x-forwarded-server":"a1f860dbb73e","x-real-ip":"172.18.0.1","x-sigsci-agentresponse":"200","x-sigsci-requestid":"67be2f28d07cd4dde0e138ff","x-sigsci-tags":"NO-CONTENT-TYPE,SUSPECTED-BOT,site.api-key-rl"},"ip":"172.183.229.144","method":"POST","url":"https://http-me.edgecompute.app/anything/start-ai-chat"}

Request Number: 4
HTTP/1.1 200 OK
Content-Length: 582
Content-Type: application/json
Date: Tue, 25 Feb 2025 20:59:20 GMT
X-Served-By: cache-chi-kigq8000027-CHI

{"args":"","body":"","headers":{"accept":"*/*","accept-encoding":"gzip","api-key":"gh-action-rl-demo","content-length":"0","host":"http-me.edgecompute.app","user-agent":"curl/8.5.0","x-forwarded-for":"172.18.0.1","x-forwarded-host":"api.bubbs.coffee","x-forwarded-port":"80","x-forwarded-server":"a1f860dbb73e","x-real-ip":"172.18.0.1","x-sigsci-agentresponse":"200","x-sigsci-requestid":"67be2f28d07cd4dde0e13900","x-sigsci-tags":"NO-CONTENT-TYPE,SUSPECTED-BOT,site.api-key-rl"},"ip":"172.183.229.144","method":"POST","url":"https://http-me.edgecompute.app/anything/start-ai-chat"}

Request Number: 5
HTTP/1.1 406 Not Acceptable
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Tue, 25 Feb 2025 20:59:20 GMT
Content-Length: 20

406 Not Acceptable

... truncated for brevity

Request Number: 10
HTTP/1.1 406 Not Acceptable
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Tue, 25 Feb 2025 20:59:20 GMT
Content-Length: 20

406 Not Acceptable

Ensure security settings are validated

We've outlined a practical approach to testing rate-limiting rules in Fastly's Next-Gen WAF using GitHub Actions. This method allows for automated validation of these rules, crucial for defending against cyber attacks and enforcing usage policies. Watch the demo for a hands-on example: Github Action and NGWAF Rate Limiting Demo.