Streamlining Dev Workflows: A Lightweight Self-Service Solution for Bypassing Bot Defense Safely

Introduction

When developers are building and testing new features, they need to move quickly. But the same bot protections that keep production traffic safe can slow dev/test workflows—especially when a developer’s IP address changes frequently. Tickets pile up, velocity drops, and teams are tempted to weaken controls “just for now.”

I recently helped an XC customer implement a lightweight, self-service flow that let’s approved developers temporarily bypass F5 Distributed Cloud (XC) Bot Defense—without expanding risk or creating operational toil. 

 

The business challenge

  • Legitimate need: Developers must test and iterate rapidly, including bot-like behaviors such as automated scripts or headless browsers.
  • Real-world constraint: Developer IPs often change (home broadband, DHCP, VPN).
  • Operational drag: Manual allowlist updates via tickets or console clicks are slow and error-prone.
  • Security risk: Broad, persistent exemptions or shared static bypasses weaken defenses.

Goal: Deliver a fast, self-service, and a simple way to bypass F5 Distributed Cloud Bot Defense for dev/test without sacrificing security bypass strictly scoped to dev/test—without loosening guardrails.

 

The solution (in plain language)

Automate the update of a F5 Distributed Cloud IP prefix set that’s already wired to a service policy with the “Skip Bot Defense” option set. An approved developer can then hit a simple, secret endpoint; the system detects their current public IP and updates the designated IP set with a `/32`. Bot Defense is skipped for that IP on dev/test traffic—immediately.

No tickets. No console spelunking. No risky, long-lived exemptions.

At a glance

- Self-service: Developers add their _current_ IP in seconds.
- Tight scope: Changes apply only to the dev/test services attached to that policy.

 

Get started

  1. Identify your dev/test load balancer(s) that require a temporary Bot Defense bypass
  2. Create/confirm an IP prefix set and attach it to a service policy with Skip Bot Defense for those services.

Example IP Prefix List. This can be pasted into the JSON tab within the F5XC GUI

{
  "metadata": {
    "name": "glens-ip-address"
  },
  "spec": {
    "prefix": [
      "10.10.10.10/32"
    ],
    "ipv4_prefixes": [
      {
        "ipv4_prefix": "10.10.10.10/32"
      }
    ]
  }
}

Example Service Policy. This, again, can be pasted into the JSON tab within the F5XC GUI

{
  "metadata": {
    "name": "skip-bot-defense-for-devs"
  },
  "spec": {
    "algo": "FIRST_MATCH",
    "rules": [
      {
        "kind": "service_policy_rule"
      }
    ],
    "rule_list": {
      "rules": [
        {
          "metadata": {
            "name": "dev1-rule"
          },
          "spec": {
            "action": "NEXT_POLICY",
            "ip_matcher": {
              "prefix_sets": [
                {
                  "kind": "ip_prefix_set",
                  "name": "glens-ip-address"
                }
              ]
            }, 
            "waf_action": {
              "none": {}
            }
          }
        }
      ]
    }
  }
}

3. Attach the Service Policy to the LB's namespace, or to the load balancer for the identified load balancer(s). 

4. Create an XC API token and store it in an Azure Key Vault. 

5. Stand up an automation endpoint that, is this example, we're using Azure Logic Apps:
    - This sample reads the caller’s public IP and updates the designated IP prefix set with a `/32`.

Here's a visual representation of the Azure Logic App:

 

Below is the code for the Azure Logic app using the Consumption plan. As part of the Azure Key Vault parameters, I've scrubbed the subscription ID, and resource group name from the code below. 

You'll need to adjust this to align with your environment.

{
    "definition": {
        "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
        "contentVersion": "1.0.0.0",
        "triggers": {
            "HTTP_Call_to_Update_IP_Address_Prefix_List": {
                "type": "Request",
                "kind": "Http",
                "inputs": {
                    "method": "GET",
                    "schema": {}
                },
                "conditions": []
            }
        },
        "actions": {
            "Compose_Client_IP": {
                "runAfter": {},
                "type": "Compose",
                "inputs": "@split(split(triggerOutputs()?['headers']['x-forwarded-for'], ',')[0], ':')[0]\n"
            },
            "HTTP_Update_IP": {
                "runAfter": {
                    "Get_secret": [
                        "Succeeded"
                    ]
                },
                "type": "Http",
                "inputs": {
                    "uri": "https://@{parameters('tenant-name')}.console.ves.volterra.io/api/config/namespaces/@{parameters('namespace')}/ip_prefix_sets/@{parameters('ip-prefix-set')}",
                    "method": "PUT",
                    "headers": {
                        "Content-Type": "application/json",
                        "Authorization": "APIToken @{body('Get_secret')?['value']}"
                    },
                    "body": {
                        "name": "@{parameters('ip-prefix-set')}",
                        "namespace": "@{parameters('namespace')}",
                        "metadata": {
                            "name": "@{parameters('ip-prefix-set')}",
                            "namespace": "@{parameters('namespace')}",
                            "labels": {},
                            "annotations": {},
                            "description": "Last updated by Logic Apps at @{utcNow()}UTC",
                            "disable": false
                        },
                        "spec": {
                            "prefix": [
                                "@{outputs('Compose_Client_IP')}/32"
                            ],
                            "ipv6_prefix": [],
                            "ipv4_prefixes": [
                                {
                                    "ipv4_prefix": "@{outputs('Compose_Client_IP')}/32",
                                    "description": "Last updated by Logic Apps at @{utcNow()}UTC"
                                }
                            ],
                            "ipv6_prefixes": []
                        }
                    }
                }
            },
            "Response": {
                "runAfter": {
                    "HTTP_Update_IP": [
                        "Succeeded"
                    ]
                },
                "type": "Response",
                "kind": "Http",
                "inputs": {
                    "statusCode": 200,
                    "body": "Updated successfully to @{outputs('Compose_Client_IP')}/32"
                }
            },
            "Get_secret": {
                "runAfter": {
                    "Compose_Client_IP": [
                        "Succeeded"
                    ]
                },
                "type": "ApiConnection",
                "inputs": {
                    "host": {
                        "connection": {
                            "name": "@parameters('$connections')['keyvault']['connectionId']"
                        }
                    },
                    "method": "get",
                    "path": "/secrets/@{encodeURIComponent('f5xc-api-key')}/value"
                },
                "runtimeConfiguration": {
                    "secureData": {
                        "properties": [
                            "inputs",
                            "outputs"
                        ]
                    }
                }
            }
        },
        "parameters": {
            "namespace": {
                "defaultValue": "default",
                "type": "String"
            },
            "tenant-name": {
                "defaultValue": "xc-tenant-name",
                "type": "String"
            },
            "ip-prefix-set": {
                "defaultValue": "glens-ip-address",
                "type": "String"
            },
            "$connections": {
                "type": "Object",
                "defaultValue": {}
            }
        }
    },
    "parameters": {
        "$connections": {
            "type": "Object",
            "value": {
                "keyvault": {
                    "id": "/subscriptions/{subscription-guid}/Microsoft.Web/locations/westus3/managedApis/keyvault",
                    "connectionId": "/subscriptions/{subscription-guid}/resourceGroups/{rg-name}/providers/Microsoft.Web/connections/keyvault",
                    "connectionName": "keyvault",
                    "connectionProperties": {
                        "authentication": {
                            "type": "ManagedServiceIdentity"
                        }
                    }
                }
            }
        }
    }
}

 

6. Within the Logic app, the overview summary will show the Workflow URL. Capture the HTTP URL that you or your developer can use to update their IP address. 

 

7. Roll out to your developer group with simple guidance ("Click here to allow your current IP” or use curl to run the Workflow URL).

 

Caveats

  • This example relies on a secret URL. If the URL is compromised, you'll need to reset it. You can set the URL by recreating the logic app or changing the name of the HTTP Trigger function within the Logic app designer. 
  • Do not use, or prepare for unexpected results if using a proxy/SASE solution, as you could be whitelisting multiple users or your entire org. 

 

Ideas for Improvement

  • Move the IP Prefix Set and Namespace values to parameters
  • Add Authentication, or a secret parameter for additional protection.
Published Aug 19, 2025
Version 1.0
No CommentsBe the first to comment