Configure OCI Regional WAF Rate Limiting with a Custom 503 Response Using OCI CLI

Configure OCI Regional WAF Rate Limiting with a Custom 503 Response Using OCI CLI

Overview

This guide shows how to configure an Oracle Cloud Infrastructure regional Web Application Firewall policy to:

  • Monitor requests to eits-uat.fcicanada.com.
  • Apply rate limiting only to requests whose path begins with /apex/.
  • Limit each client IP address to 100 requests within 60 seconds.
  • Return a custom HTTP 503 page for 300 seconds when the threshold is exceeded.
  • Preserve all existing WAF actions and rules.

The tested regional WAF policy was:

Policy name: QA_RegionalWAF_Policy
Region: ca-toronto-1
Lifecycle state: ACTIVE
Target host: eits-uat.fcicanada.com
Target path: /apex/
Important: OCI WAF rate limiting is evaluated per client IP address. It does not calculate one combined request total for all application users. Users behind the same NAT gateway, VPN, proxy, or secure web gateway may share one visible public IP address.

Prerequisites

  • OCI CLI installed and configured.
  • An OCI CLI profile with permission to read and update the WAF policy.
  • jq installed.
  • The regional WAF policy OCID.
  • A Linux shell such as Bash.

Step 1: Create a Working Directory

mkdir -p rate_limiting_waf
cd rate_limiting_waf

Step 2: Set Environment Variables

Set the OCI profile, region, WAF policy OCID, action name, and rule name.

export OCI_PROFILE="CLOUD_ADMIN_FCIAS2"
export OCI_REGION="ca-toronto-1"

export WAF_POLICY_ID="ocid1.webappfirewallpolicy.oc1.ca-toronto-1.REPLACE_WITH_POLICY_OCID"

export ACTION_NAME="EITS-UAT-HIGH-VOLUME-503"
export RULE_NAME="EITS-UAT-APEX-RATE-LIMIT"
Security note: Do not publish a production or internal OCID in a public blog post. Replace it with a placeholder as shown above.

Step 3: Verify the WAF Policy

oci waf web-app-firewall-policy get \
  --web-app-firewall-policy-id "$WAF_POLICY_ID" \
  --profile "$OCI_PROFILE" \
  --region "$OCI_REGION" \
  --query 'data.{"Name":"display-name","State":"lifecycle-state","OCID":id}' \
  --output table

Example output:

+-----------------------+----------------------------------------------+--------+
| Name                  | OCID                                         | State  |
+-----------------------+----------------------------------------------+--------+
| QA_RegionalWAF_Policy | ocid1.webappfirewallpolicy...                | ACTIVE |
+-----------------------+----------------------------------------------+--------+

Confirm that the policy is the correct one and that its lifecycle state is ACTIVE.

Step 4: Back Up the Existing WAF Policy

The OCI CLI update command submits complete policy sections. Back up the policy before changing the actions or request-rate-limiting arrays.

oci waf web-app-firewall-policy get \
  --web-app-firewall-policy-id "$WAF_POLICY_ID" \
  --profile "$OCI_PROFILE" \
  --region "$OCI_REGION" \
  > waf-policy-before-rate-limit.json

Extract the existing actions and rate-limiting configuration:

jq '.data.actions' \
  waf-policy-before-rate-limit.json \
  > actions-before.json

jq '.data."request-rate-limiting"' \
  waf-policy-before-rate-limit.json \
  > request-rate-limiting-before.json

In this implementation, the original rate-limiting section returned:

null

That confirmed that no request rate-limiting rules were configured before this change.

Step 5: Create the Custom 503 HTML Page

Create the static response page that OCI WAF will return when a client exceeds the configured request threshold.

cat > eits-high-volume.html <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>EITS Intake – Temporarily Unavailable</title>
    <style>
        body {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #f5f6f7;
            color: #222;
            font-family: Arial, Helvetica, sans-serif;
        }

        .message {
            max-width: 700px;
            margin: 20px;
            padding: 40px;
            text-align: center;
            background: #fff;
            border-radius: 8px;
            box-shadow: 0 3px 15px rgba(0, 0, 0, 0.12);
        }

        h1 {
            margin-top: 0;
            font-size: 28px;
        }

        p {
            font-size: 18px;
            line-height: 1.6;
        }
    </style>
</head>
<body>
    <main class="message">
        <h1>503 Service Temporarily Unavailable</h1>

        <p>
            EITS Intake is currently experiencing a high volume of requests
            and is unable to process your request at this time.
        </p>

        <p>
            Please wait a few minutes and try again. We apologize for the
            inconvenience and appreciate your patience.
        </p>
    </main>
</body>
</html>
EOF
Correction: The tested shell output contained <h1>503 Service Temporarily Unavailable /h1>. The version above corrects it to a valid closing tag: </h1>.

Step 6: Build the Complete WAF Actions JSON

The following command reads the current action list, removes any older action with the same name, and appends the custom RETURN_HTTP_RESPONSE action.

jq \
  --rawfile html eits-high-volume.html \
  --arg actionName "$ACTION_NAME" \
  '
  (.data.actions // [])
  | map(select(.name != $actionName))
  + [
      {
        "name": $actionName,
        "type": "RETURN_HTTP_RESPONSE",
        "code": 503,
        "headers": [
          {
            "name": "Content-Type",
            "value": "text/html; charset=UTF-8"
          },
          {
            "name": "Retry-After",
            "value": "300"
          },
          {
            "name": "Cache-Control",
            "value": "no-store"
          }
        ],
        "body": {
          "type": "STATIC_TEXT",
          "text": $html
        }
      }
    ]
  ' waf-policy-before-rate-limit.json > actions.json

Review the generated JSON:

jq . actions.json

The new action should include:

{
  "name": "EITS-UAT-HIGH-VOLUME-503",
  "type": "RETURN_HTTP_RESPONSE",
  "code": 503,
  "headers": [
    {
      "name": "Content-Type",
      "value": "text/html; charset=UTF-8"
    },
    {
      "name": "Retry-After",
      "value": "300"
    },
    {
      "name": "Cache-Control",
      "value": "no-store"
    }
  ],
  "body": {
    "type": "STATIC_TEXT",
    "text": "..."
  }
}

Step 7: Add the Custom Action to the WAF Policy

oci waf web-app-firewall-policy update \
  --web-app-firewall-policy-id "$WAF_POLICY_ID" \
  --actions file://actions.json \
  --profile "$OCI_PROFILE" \
  --region "$OCI_REGION" \
  --wait-for-state SUCCEEDED \
  --force

Successful output includes:

"operation-type": "UPDATE_WAF_POLICY"
"percent-complete": 100.0
"status": "SUCCEEDED"

Step 8: Verify the Custom Action

oci waf web-app-firewall-policy get \
  --web-app-firewall-policy-id "$WAF_POLICY_ID" \
  --profile "$OCI_PROFILE" \
  --region "$OCI_REGION" \
  --query "data.actions[?name=='${ACTION_NAME}']" \
  --output json

Confirm that the returned action has:

  • type: RETURN_HTTP_RESPONSE
  • code: 503
  • Content-Type: text/html; charset=UTF-8
  • Retry-After: 300
  • Cache-Control: no-store

Step 9: Retrieve the Policy Again

Retrieve the policy after the action has been added. This refreshed JSON will be used to preserve the current configuration while adding the rate-limiting rule.

oci waf web-app-firewall-policy get \
  --web-app-firewall-policy-id "$WAF_POLICY_ID" \
  --profile "$OCI_PROFILE" \
  --region "$OCI_REGION" \
  > waf-policy-with-action.json

Step 10: Build the Rate-Limiting Rule

The rule below applies only when both of these conditions are true:

  • The HTTP host is eits-uat.fcicanada.com.
  • The request path begins with /apex/.

It does not match a specific APEX session identifier because values such as 10395221140358 change between sessions.

jq \
  --arg ruleName "$RULE_NAME" \
  --arg actionName "$ACTION_NAME" \
  '
  {
    "rules":
      (
        (.data."request-rate-limiting".rules // [])
        | map(select(.name != $ruleName))
        + [
            {
              "name": $ruleName,
              "type": "REQUEST_RATE_LIMITING",
              "conditionLanguage": "JMESPATH",
              "condition": "http.request.host == '\''eits-uat.fcicanada.com'\'' && starts_with(http.request.url.path, '\''/apex/'\'')",
              "actionName": $actionName,
              "configurations": [
                {
                  "requestsLimit": 100,
                  "periodInSeconds": 60,
                  "actionDurationInSeconds": 300
                }
              ]
            }
          ]
      )
  }
  ' waf-policy-with-action.json > request-rate-limiting.json

Review the generated rate-limiting configuration:

jq . request-rate-limiting.json

Expected output:

{
  "rules": [
    {
      "name": "EITS-UAT-APEX-RATE-LIMIT",
      "type": "REQUEST_RATE_LIMITING",
      "conditionLanguage": "JMESPATH",
      "condition": "http.request.host == 'eits-uat.fcicanada.com' && starts_with(http.request.url.path, '/apex/')",
      "actionName": "EITS-UAT-HIGH-VOLUME-503",
      "configurations": [
        {
          "requestsLimit": 100,
          "periodInSeconds": 60,
          "actionDurationInSeconds": 300
        }
      ]
    }
  ]
}

Step 11: Apply the Rate-Limiting Rule

oci waf web-app-firewall-policy update \
  --web-app-firewall-policy-id "$WAF_POLICY_ID" \
  --request-rate-limiting file://request-rate-limiting.json \
  --profile "$OCI_PROFILE" \
  --region "$OCI_REGION" \
  --wait-for-state SUCCEEDED \
  --force

Successful output includes:

"operation-type": "UPDATE_WAF_POLICY"
"percent-complete": 100.0
"status": "SUCCEEDED"

Step 12: Verify the Rate-Limiting Rule

oci waf web-app-firewall-policy get \
  --web-app-firewall-policy-id "$WAF_POLICY_ID" \
  --profile "$OCI_PROFILE" \
  --region "$OCI_REGION" \
  --query "data.\"request-rate-limiting\".rules[?name=='${RULE_NAME}']" \
  --output json

Verified output:

[
  {
    "action-name": "EITS-UAT-HIGH-VOLUME-503",
    "condition": "http.request.host == 'eits-uat.fcicanada.com' && starts_with(http.request.url.path, '/apex/')",
    "condition-language": "JMESPATH",
    "configurations": [
      {
        "action-duration-in-seconds": 300,
        "period-in-seconds": 60,
        "requests-limit": 100
      }
    ],
    "name": "EITS-UAT-APEX-RATE-LIMIT",
    "type": "REQUEST_RATE_LIMITING"
  }
]

Step 13: List All WAF Actions

oci waf web-app-firewall-policy get \
  --web-app-firewall-policy-id "$WAF_POLICY_ID" \
  --profile "$OCI_PROFILE" \
  --region "$OCI_REGION" \
  --query 'data.actions[].{Name:name,Type:type}' \
  --output table

Verified output:

+-----------------------------------------+----------------------+
| Name                                    | Type                 |
+-----------------------------------------+----------------------+
| Pre-configured Check Action             | CHECK                |
| Pre-configured Allow Action             | ALLOW                |
| Pre-configured 401 Response Code Action | RETURN_HTTP_RESPONSE |
| Unauthorized to access this web site    | RETURN_HTTP_RESPONSE |
| maintenance-503                         | RETURN_HTTP_RESPONSE |
| EITS-UAT-HIGH-VOLUME-503                | RETURN_HTTP_RESPONSE |
+-----------------------------------------+----------------------+

How the Configuration Works

Client request
      |
      v
OCI Regional WAF
      |
      |-- Host is not eits-uat.fcicanada.com
      |       '-- Rate-limit rule does not apply
      |
      |-- Path does not begin with /apex/
      |       '-- Rate-limit rule does not apply
      |
      '-- Host and path match
              |
              |-- Up to 100 requests within 60 seconds
              |       '-- Request continues to the reverse proxy
              |
              '-- Threshold exceeded for that client IP
                      '-- Return custom HTTP 503 page for 300 seconds

Configuration Values

Setting Value Meaning
requestsLimit 100 Maximum requests allowed from one client IP during the evaluation period.
periodInSeconds 60 The request-counting window.
actionDurationInSeconds 300 How long the custom response action remains active for the client IP after the threshold is exceeded.
Retry-After 300 Advises the client to retry after five minutes.

Testing

Use a non-production client and a safe URL. Avoid sending repeated requests to an APEX form submission endpoint.

seq 1 150 | xargs -n1 -P20 \
curl -sk -o /dev/null \
-w '%{http_code}\n' \
'https://eits-uat.fcicanada.com/apex/'

Expected behavior:

  • Initial requests receive the normal application response.
  • After the client IP exceeds the threshold, responses change to HTTP 503.
  • The custom HTML message is displayed.
  • The client IP remains subject to the action for 300 seconds.

To inspect response headers:

curl -skI https://eits-uat.fcicanada.com/apex/

During rate limiting, the response should include:

HTTP/1.1 503 Service Unavailable
Content-Type: text/html; charset=UTF-8
Retry-After: 300
Cache-Control: no-store

Rollback

Restore the Previous Actions

oci waf web-app-firewall-policy update \
  --web-app-firewall-policy-id "$WAF_POLICY_ID" \
  --actions file://actions-before.json \
  --profile "$OCI_PROFILE" \
  --region "$OCI_REGION" \
  --wait-for-state SUCCEEDED \
  --force

Remove the Rate-Limiting Rule

Because the original rate-limiting configuration was null, use an empty rule list to remove the rule:

cat > request-rate-limiting-empty.json <<'EOF'
{
  "rules": []
}
EOF

oci waf web-app-firewall-policy update \
  --web-app-firewall-policy-id "$WAF_POLICY_ID" \
  --request-rate-limiting file://request-rate-limiting-empty.json \
  --profile "$OCI_PROFILE" \
  --region "$OCI_REGION" \
  --wait-for-state SUCCEEDED \
  --force
Rollback warning: Always retrieve and review the current policy immediately before rollback. Another administrator may have added valid actions or rules after the original backup was created.

Operational Considerations

  • Shared public IP addresses: Users behind Skyhigh, corporate NAT, VPN, or proxy infrastructure may appear as one client IP. Choose the request threshold using observed production traffic.
  • APEX sessions: Do not include the changing APEX session number in the WAF condition.
  • HTTP status: HTTP 503 is appropriate because the restriction represents temporary unavailability. HTTP 502 should normally be reserved for an invalid upstream gateway response.
  • Policy replacement behavior: The CLI update sends the entire actions or rate-limiting section. Always merge with the existing policy instead of submitting only the new object.
  • Monitoring: Review WAF logs after activation to confirm the rule is not affecting legitimate users.

Final Result

The regional WAF policy now protects the EITS UAT APEX endpoint with a per-client-IP request limit. A client that sends more than 100 matching requests within 60 seconds receives a custom HTTP 503 response for 300 seconds. Existing WAF actions remain preserved.

No comments:

Post a Comment