<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>CodeShare articles</title>
    <link>https://community.f5.com/t5/codeshare/tkb-p/codeshare</link>
    <description>CodeShare articles</description>
    <pubDate>Wed, 27 May 2026 20:04:57 GMT</pubDate>
    <dc:creator>codeshare</dc:creator>
    <dc:date>2026-05-27T20:04:57Z</dc:date>
    <item>
      <title>DGCat-Admin — F5 BIG-IP Datagroup &amp; URL Category Manager</title>
      <link>https://community.f5.com/t5/codeshare/dgcat-admin-f5-big-ip-datagroup-url-category-manager/ta-p/345952</link>
      <description>&lt;P&gt;Available in two versions with identical functionality:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;STRONG&gt;Bash&lt;/STRONG&gt;&amp;nbsp;(dgcat-admin.sh) — For Linux, macOS, or directly on BIG-IP/Big-IQ&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;PowerShell&lt;/STRONG&gt;&amp;nbsp;(dgcat-admin.ps1) — For Windows (PowerShell 5.1+)&lt;/LI&gt;&lt;/UL&gt;&lt;H2&gt;Why This Tool?&lt;/H2&gt;&lt;P&gt;SSL Orchestrator (SSLO) policies rely heavily on datagroups and URL categories for traffic classification. While you can add sites directly to SSLO policies, this approach has limitations:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;SSLO uses iAppLX to generate APM per-request policies under the hood&lt;/LI&gt;&lt;LI&gt;Each host or site added directly becomes an expression in the APM policy&lt;/LI&gt;&lt;LI&gt;Large lists could degrade policy performance and are not easily manageable&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;The recommended approach:&lt;/STRONG&gt;&amp;nbsp;Use datagroups or URL categories for SSLO security policy rules. They're optimized for fast lookups, keep policies clean and are operationally easier to maintain.&lt;/P&gt;&lt;P&gt;DGCat-Admin makes managing those lists very easy.&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;Need to export a few massive datagroups or custom URL categories so you can precisely replicate existing SSLO business logic at another site in just minutes?&lt;/LI&gt;&lt;LI&gt;Need to ingest a large number of subnets or hosts from an Excel spreadsheet into a datagroup for SSLO security policy use?&lt;/LI&gt;&lt;LI&gt;Want to take a custom URL category and convert it to a datagroup?&lt;/LI&gt;&lt;LI&gt;Want to take a datagroup and convert it to a custom URL category?&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;STRONG&gt;This tool was designed specifically for those purposes.&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;https://github.com/hauptem/F5-SSL-Orchestrator-Tools/tree/main/DGCat-Admin&lt;/P&gt;</description>
      <pubDate>Sun, 29 Mar 2026 00:14:49 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/dgcat-admin-f5-big-ip-datagroup-url-category-manager/ta-p/345952</guid>
      <dc:creator>hauptem</dc:creator>
      <dc:date>2026-03-29T00:14:49Z</dc:date>
    </item>
    <item>
      <title>F5 Per-App AS3 Part 2 How to see if there are manual changes!</title>
      <link>https://community.f5.com/t5/codeshare/f5-per-app-as3-part-2-how-to-see-if-there-are-manual-changes/ta-p/345109</link>
      <description>&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;Code version:&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;The code was tested on 17.1.5.3&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;AS3: 3.55&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;For more about AS3 and per-app AS3 see my previous code share Part 1 article:&lt;/P&gt;
&lt;P&gt;https://community.f5.com/kb/codeshare/f5-per-app-as3-part-1-how-share-tenant-specific-object/345072&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;First we will send Per-App AS3 declaration as shown below.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="json"&gt;{
    "id": "per-app-declarationn",
    "schemaVersion": "3.55.0",
    "controls": {
        "class": "Controls",
        "logLevel": "debug",
        "trace": true,
        "traceResponse": true
    },
         "A2": {
            "class": "Application",
            "service": {
               "class": "Service_HTTP",
               "virtualAddresses": [
                  "10.0.3.10"
               ],
               "pool": "web2_pool"
            },
            "web2_pool": {
               "class": "Pool",
               "monitors": [
                  "http"
               ],
               "members": [{
                  "servicePort": 80,
                  "serverAddresses": [
                     "192.7.21.10",
                     "192.7.21.11"
                  ]
               }]
            }
         }
      }&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Then we will change for example virtual server ip from 10.0.3.10 to 10.0.3.11 and we will send the same declaration but with &lt;U&gt;&lt;EM&gt;&lt;STRONG&gt;"dryRun"&lt;/STRONG&gt;&lt;/EM&gt;&lt;/U&gt; set to true as this will cause AS3 to validate the config but not to execute it and with&amp;nbsp;&lt;STRONG&gt; trace and traceResponse &lt;/STRONG&gt;we will get the difference 😎&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="json"&gt;{
    "id": "per-app-declarationn",
    "schemaVersion": "3.55.0",
    "controls": {
        "class": "Controls",
        "logLevel": "debug",
        "trace": true,
        "dryRun": true,
        "traceResponse": true
    },
         "A2": {
            "class": "Application",
            "service": {
               "class": "Service_HTTP",
               "virtualAddresses": [
                  "10.0.3.10"
               ],
               "pool": "web2_pool"
            },
            "web2_pool": {
               "class": "Pool",
               "monitors": [
                  "http"
               ],
               "members": [{
                  "servicePort": 80,
                  "serverAddresses": [
                     "192.7.21.10",
                     "192.7.21.11"
                  ]
               }]
            }
         }
      }&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;Now we see that the IP has been changed from 10.0.3.10 to 10.0.3.11 and here we go now we have the difference !&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;This can be added in CI/CD to always first do "dry-run" using the original declaration to see if there are changes before executing the new AS3 declaration that could be for example changing the IP address to 10.0.3.12 but using the official way. Look at the Json reply "diff" section that is seen thanks to &lt;STRONG&gt;trace and traceResponse&lt;/STRONG&gt; options and an automation can just check this section and stop the new deployment if the manual changes need to be reviewed first.&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;For AS3 basic declaration not Per-App actually the "dry-run" is a different. F5 likes changing the naming like Local Traffic policies to Endpoint Policies or naming of TLS profiles between GU/TMSH and AS3 😅&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang=""&gt;{
    "$schema": "https://raw.githubusercontent.com/F5Networks/f5-appsvcs-extension/refs/heads/main/schema/3.55.0/as3-schema.json",
    "class": "AS3",
    "action": "dry-run",
     "logLevel": "debug",
     "trace": true,
     "traceResponse": true,
    "persist": true,
    "declaration": {
        "class": "ADC",
        "schemaVersion": "3.55.0",
        "id": "BIG-IP-Example-Tenant",
        "Example": {
            "class": "Tenant",
            "Shared": {
                "class": "Application",
                "template": "shared",
                "Example_Response": {
                    "remark": "Used for F5 response",
                    "class": "iRule",
                    "iRule": {
                        "base64": "d2hlbiBIVFRQX1JFUVVFU1Qgew0KICAgSFRUUDo6cmVzcG9uZCAyMDAgY29udGVudCB7DQogICAgICA8aHRtbD4NCiAgICAgICAgIDxoZWFkPg0KICAgICAgICAgICAgPHRpdGxlPkFwb2xvZ3kgUGFnZTwvdGl0bGU+DQogICAgICAgICA8L2hlYWQ+DQogICAgICAgICA8Ym9keT4NCiAgICAgICAgICAgIFdlIGFyZSBzb3JyeSwgYnV0IHRoZSBzaXRlIHlvdSBhcmUgbG9va2luZyBmb3IgaXMgdGVtcG9yYXJpbHkgb3V0IG9mIHNlcnZpY2U8YnI+DQogICAgICAgICAgICBJZiB5b3UgZmVlbCB5b3UgaGF2ZSByZWFjaGVkIHRoaXMgcGFnZSBpbiBlcnJvciwgcGxlYXNlIHRyeSBhZ2Fpbi4NCiAgICAgICAgIDwvYm9keT4NCiAgICAgIDwvaHRtbD4NCiAgIH0NCn0="
                    }
                }
            }
        }
    }
}&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;This will not show if someone has manually added a vlan for example as only changes on the apps that were deployed with AS3 will be seen. For those you will get error&amp;nbsp; like the one below when you try to delete the partition.&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;"" 0107082a:3: All objects must be removed from a partition ""&lt;/P&gt;
&lt;P&gt;&lt;A class="lia-external-url" href="https://my.f5.com/manage/s/article/K02718312" target="_blank" rel="noopener"&gt;https://my.f5.com/manage/s/article/K02718312&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&lt;A class="lia-external-url" href="https://my.f5.com/manage/s/article/K000138638" target="_blank" rel="noopener"&gt;https://my.f5.com/manage/s/article/K000138638&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Github link:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;A class="lia-external-url" href="https://github.com/Nikoolayy1/AS3-Per-App-Manual-Changes/tree/main" target="_blank"&gt;https://github.com/Nikoolayy1/AS3-Per-App-Manual-Changes/tree/main&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Wed, 04 Mar 2026 15:53:09 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/f5-per-app-as3-part-2-how-to-see-if-there-are-manual-changes/ta-p/345109</guid>
      <dc:creator>Nikoolayy1</dc:creator>
      <dc:date>2026-03-04T15:53:09Z</dc:date>
    </item>
    <item>
      <title>F5 Per-App AS3 Part 1 How Share Tenant specific object</title>
      <link>https://community.f5.com/t5/codeshare/f5-per-app-as3-part-1-how-share-tenant-specific-object/ta-p/345072</link>
      <description>&lt;img /&gt;
&lt;P&gt;&lt;EM&gt;Code version:&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;The code was tested on 17.1.5.3&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;AS3: 3.55&lt;/EM&gt;&lt;/P&gt;
&lt;DIV class="styles_lia-table-wrapper__h6Xo9 styles_table-responsive__MW0lN"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;DIV class="styles_lia-table-wrapper__h6Xo9 styles_table-responsive__MW0lN"&gt;For testing F5 AS3 you can use combination of Postman as Visual Studio has option to post AS3 declarations but not Per-App specific as that will be needed for executing my code. Still I recommend using Visual Studio for general AS3 or Fast Templates and you can use the reference links for how to use Visual Studio F5 extensions and maybe F5 will update the extension in the future to work for Per-App AS3 with options for adding tenant and as variables 🤔 For FAST nice example is at&amp;nbsp;
&lt;P&gt;https://github.com/Nikoolayy1/fast-template-examples&lt;/P&gt;
&lt;/DIV&gt;
&lt;DIV class="styles_lia-table-wrapper__h6Xo9 styles_table-responsive__MW0lN"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;DIV class="styles_lia-table-wrapper__h6Xo9 styles_table-responsive__MW0lN"&gt;&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;/DIV&gt;
&lt;DIV class="styles_lia-table-wrapper__h6Xo9 styles_table-responsive__MW0lN"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;P&gt;&lt;EM&gt;The same way the /Shared objects can be configured under /Common can be done under a specific tenant and this is really useful if Per-App AS3 is enabled as each AS3 app will use separate declaration and all of them can use a customer iRule defined under /&amp;lt;Tenant&amp;gt;/Shared.&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;References for AS3 Declarative API:&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;A class="lia-external-url" href="https://www.youtube.com/watch?v=rrjrcnwAe1g&amp;amp;t=433s" target="_blank" rel="noopener"&gt;https://www.youtube.com/watch?v=rrjrcnwAe1g&amp;amp;t=433s&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&lt;A class="lia-internal-link lia-internal-url lia-internal-url-content-type-forum" href="https://community.f5.com/discussions/devcentralconnectsforum-board/as3-foundations-beyond-imperatives---what-the-heck-is-as3/324976" target="_blank" rel="noopener" data-lia-auto-title="AS3 Foundations: Beyond Imperatives - What the Heck is AS3? | DevCentral" data-lia-auto-title-active="0"&gt;AS3 Foundations: Beyond Imperatives - What the Heck is AS3? | DevCentral&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&lt;A class="lia-external-url" href="https://www.youtube.com/watch?v=OHSBpOtQg_0&amp;amp;list=PLrn3yqXftAPUPdSP_kbCX6BDicVprsnfI" target="_blank" rel="noopener"&gt;https://www.youtube.com/watch?v=OHSBpOtQg_0&amp;amp;list=PLrn3yqXftAPUPdSP_kbCX6BDicVprsnfI&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Per-App AS3 is enabled by default for the AS3 3.50.x and above but if needed enable it.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;POST Endpoint: &lt;STRONG&gt;/mgmt/shared/appsvcs/settings&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="json"&gt;{
"perAppDeploymentAllowed": true
}&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;POST Endpoint for Creating the Tenant and the Shared irule that is base64 endpoint:&lt;STRONG&gt; /mgmt/shared/appsvcs/declare&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="json"&gt;{
    "schemaVersion": "3.45.0",
    "Shared": {
        "class": "Application",
        "template": "shared",
        "test": {
            "class": "iRule",
            "remark": "yes",
            "iRule": {
                "base64": "d2hlbiBIVFRQX1JFUVVFU1Qgew0KICAgSFRUUDo6cmVzcG9uZCAyMDAgY29udGVudCB7DQogICAgICA8aHRtbD4NCiAgICAgICAgIDxoZWFkPg0KICAgICAgICAgICAgPHRpdGxlPkFwb2xvZ3kgUGFnZTwvdGl0bGU+DQogICAgICAgICA8L2hlYWQ+DQogICAgICAgICA8Ym9keT4NCiAgICAgICAgICAgIFdlIGFyZSBzb3JyeSwgYnV0IHRoZSBzaXRlIHlvdSBhcmUgbG9va2luZyBmb3IgaXMgdGVtcG9yYXJpbHkgb3V0IG9mIHNlcnZpY2U8YnI+DQogICAgICAgICAgICBJZiB5b3UgZmVlbCB5b3UgaGF2ZSByZWFjaGVkIHRoaXMgcGFnZSBpbiBlcnJvciwgcGxlYXNlIHRyeSBhZ2Fpbi4NCiAgICAgICAgIDwvYm9keT4NCiAgICAgIDwvaHRtbD4NCiAgIH0NCn0="
            }
        }
    }
}&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;POST Endpoint for sending Per-App AP that references the Tenant shared iRule I:&lt;STRONG&gt; /mgmt/shared/appsvcs/declare/Example/applications&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="json"&gt;{
    "schemaVersion": "3.45.0",
    "A1": {
        "class": "Application",
        "service": {
            "class": "Service_HTTP",
            "virtualAddresses": [
                "10.0.1.10"
            ],
            "pool": "web_pool",
            "iRules": [
                {
                    "use": "/Test/Shared/test"
                },
                "insert_xff"
            ]
        },
        "web_pool": {
            "class": "Pool",
            "monitors": [
                "http"
            ],
            "members": [
                {
                    "servicePort": 80,
                    "serverAddresses": [
                        "192.0.1.10",
                        "192.0.1.12"
                    ]
                }
            ]
        },
        "insert_xff": {
            "class": "iRule",
            "iRule": "when HTTP_REQUEST { HTTP::header insert X-Forwarded-For [IP::client_addr] }"
        }
    }
}&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Github link:&lt;/P&gt;
&lt;P&gt;&lt;A href="https://github.com/Nikoolayy1/AS3-Per-App-Shared-Tenant-Objects/edit/main/README.md" target="_blank" rel="noopener"&gt;Editing AS3-Per-App-Shared-Tenant-Objects/README.md at main · Nikoolayy1/AS3-Per-App-Shared-Tenant-Objects&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;Note!&lt;/P&gt;
&lt;P&gt;To see the Tenant config use GET Endpoint &lt;STRONG&gt;/mgmt/shared/appsvcs/declare/Example&lt;/STRONG&gt; or for the app &lt;STRONG&gt;/mgmt/shared/appsvcs/declare/Example/A1&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;For only the Tenant shared config use GET &lt;STRONG&gt;/mgmt/shared/appsvcs/declare/Example/applications/Shared&amp;nbsp;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;By placing the pools in the /Shared under the Tenant all Per-AS3 declarations for Tenant can use the pool like for the iRule example I showed and also the pools can share the node IP addresses like the "shareNodes" option but only for the tenant.&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&lt;A class="lia-external-url" href="https://my.f5.com/manage/s/article/K88250015" target="_blank"&gt;https://my.f5.com/manage/s/article/K88250015&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Tue, 17 Feb 2026 11:13:12 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/f5-per-app-as3-part-1-how-share-tenant-specific-object/ta-p/345072</guid>
      <dc:creator>Nikoolayy1</dc:creator>
      <dc:date>2026-02-17T11:13:12Z</dc:date>
    </item>
    <item>
      <title>Encrypted UCS Backup with REST-API</title>
      <link>https://community.f5.com/t5/codeshare/encrypted-ucs-backup-with-rest-api/ta-p/344574</link>
      <description>&lt;P&gt;Because it seems this nowhere documented: Create a encrypted F5 backup with REST-API including private keys. This script should creates the task, starts it and get's it status.&lt;/P&gt;
&lt;LI-CODE lang="shell"&gt;#!/usr/bin/env bash

CURL_OPTS=("--fail-with-body" "--show-error" "-s" "-k" "-u" "user:pass" "-H" "Content-Type: application/json" "-H" "Accept: application/json, */*")

# Create task and get id
TASK_ID=$(jq -n --arg name /var/local/ucs/test.ucs \
    --arg passphrase "testpw" \
    '{
        "command": "save",
        "name": $name,
        "options": [
            {
                "passphrase": $passphrase
            }
        ]
    }' \
    | curl "${CURL_OPTS[@]}" -X POST -d @- https://f5-lab/mgmt/tm/task/sys/ucs \
    | jq -r "._taskId")

# Start task
jq -n '{
        "_taskState": "VALIDATING"
    }' | curl "${CURL_OPTS[@]}" -X PUT -d @- "https://f5-lab/mgmt/tm/task/sys/ucs/$TASK_ID"

# Get task status
curl "${CURL_OPTS[@]}" --retry 5 --retry-all-errors --retry-delay 10 "https://f5-lab/mgmt/tm/task/sys/ucs/$TASK_ID" \
    | jq -r "._taskState"&lt;/LI-CODE&gt;
&lt;P&gt;Reference was &lt;A class="lia-external-url" href="https://my.f5.com/manage/s/article/K000138875" target="_blank" rel="noopener"&gt;https://my.f5.com/manage/s/article/K000138875&lt;/A&gt; and the passphrase options was found by trial and error.&lt;/P&gt;</description>
      <pubDate>Fri, 28 Nov 2025 08:01:12 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/encrypted-ucs-backup-with-rest-api/ta-p/344574</guid>
      <dc:creator>Juergen_Mang</dc:creator>
      <dc:date>2025-11-28T08:01:12Z</dc:date>
    </item>
    <item>
      <title>CIS F5 Benchmark Reporter</title>
      <link>https://community.f5.com/t5/codeshare/cis-f5-benchmark-reporter/ta-p/344262</link>
      <description>&lt;H5&gt;Code is community submitted, community supported, and recognized as ‘Use At Your Own Risk’.&lt;/H5&gt;
&lt;P&gt;The CIS_F5_Benchmark_Reporter.py is a Python script that can be run on a F5 BIG-IP. This script will check if the configuration of the F5 BIG-IP is compliant with the CIS Benchmark for F5.&lt;/P&gt;
&lt;P&gt;The script will generate a report that can be saved to a file, send by e-mail or send its output to the screen. Just use the appropriate arguments when running the script.&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;[root@bigipa:Active:Standalone] # ./CIS_F5_Benchmark_Reporter.py
Usage: CIS_F5_Benchmark_Reporter.py [OPTION]...

Mandatory arguments to long options are mandatory for short options too.
  -f, --file=FILE            output report to file.
  -m, --mail                 output report to mail.
  -s, --screen               output report to screen.

Report bugs to nvansluis@gmail.com
[root@bigipa:Active:Standalone] #&lt;/LI-CODE&gt;
&lt;P&gt;To receive a daily or weekly report from your F5 BIG-IP, you can create a cron job.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Below is a screenshot that shows what the report will look like.&lt;/P&gt;
&lt;img /&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3&gt;Settings&lt;/H3&gt;
&lt;P&gt;In the script, there is a section named 'User Options'. These options should be modified to reflect your setup.&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;#-----------------------------------------------------------------------
# User Options - Configure as desired
#-----------------------------------------------------------------------&lt;/LI-CODE&gt;
&lt;H4&gt;&amp;nbsp;&lt;/H4&gt;
&lt;H4&gt;E-mail settings&lt;/H4&gt;
&lt;P&gt;Here the e-mail setting can be configured, so the script will be able to send a report by e-mail.&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;# e-mail settings
port = 587
smtp_server = "smtp.example.com"
sender_email = "johndoe@example.com"
receiver_email = "johndoe@example.com"
login = "johndoe"
password = "mySecret"&lt;/LI-CODE&gt;
&lt;H4&gt;&amp;nbsp;&lt;/H4&gt;
&lt;H4&gt;SNMP settings&lt;/H4&gt;
&lt;P&gt;Here you can add additional SNMP clients. These are necessary to be compliant with control 6.1.&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;# list containing trusted IP addresses and networks that have access to SNMP (control 6.1)
snmp_client_allow_list = [
    "127.0.0.0/8",
]&lt;/LI-CODE&gt;
&lt;H4&gt;&amp;nbsp;&lt;/H4&gt;
&lt;H4&gt;Exceptions&lt;/H4&gt;
&lt;P&gt;Sometimes there are valid circumstances, why a specific requirement of a security control can't be met. In this case you can add an exception. See the example below.&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;# set exceptions (add your own exceptions)
exceptions = {
    '2.1' : "Exception in place, because TACACS is used instead of RADIUS.",
    '2.2' : "Exception in place, because TACACS is used and there are two TACACS-servers present."
}&lt;/LI-CODE&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3&gt;Recommendations&lt;/H3&gt;
&lt;P&gt;Store the script somewhere in the /shared partition. The data stored on this partition will still be available after an upgrade.&lt;/P&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3&gt;Feedback&lt;/H3&gt;
&lt;P&gt;This script has been tested on F5 BIG-IP version 17.x. If you have any questions, remarks or feedback, just let me know.&lt;/P&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3&gt;Download&lt;/H3&gt;
&lt;P&gt;The script can be downloaded from github.com.&lt;/P&gt;
&lt;P&gt;&lt;A class="lia-external-url" href="https://github.com/nvansluis/CIS_F5_Benchmark_Reporter" target="_blank" rel="noopener"&gt;https://github.com/nvansluis/CIS_F5_Benchmark_Reporter&lt;/A&gt;&lt;/P&gt;</description>
      <pubDate>Wed, 12 Nov 2025 13:48:36 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/cis-f5-benchmark-reporter/ta-p/344262</guid>
      <dc:creator>Niels_van_Sluis</dc:creator>
      <dc:date>2025-11-12T13:48:36Z</dc:date>
    </item>
    <item>
      <title>BIG-IP security hardening and compliance checks</title>
      <link>https://community.f5.com/t5/codeshare/big-ip-security-hardening-and-compliance-checks/ta-p/344152</link>
      <description>&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Tue, 11 Nov 2025 17:05:19 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/big-ip-security-hardening-and-compliance-checks/ta-p/344152</guid>
      <dc:creator>amit-zakay</dc:creator>
      <dc:date>2025-11-11T17:05:19Z</dc:date>
    </item>
    <item>
      <title>Less than 600 seconds lab</title>
      <link>https://community.f5.com/t5/codeshare/less-than-600-seconds-lab/ta-p/344076</link>
      <description>&lt;H5&gt;Code is community submitted, community supported, and recognized as ‘Use At Your Own Risk’.&lt;/H5&gt;
&lt;P&gt;In my &lt;A class="lia-internal-link lia-internal-url lia-internal-url-content-type-tkb" href="https://community.f5.com/kb/codeshare/less-than-60-seconds-lab-setup/343943" target="_blank" rel="noopener" data-lia-auto-title="previous post" data-lia-auto-title-active="0"&gt;previous post&lt;/A&gt; I shared with you, how you can deploy a lab environment in less than 60 seconds with &lt;A class="lia-external-url" href="https://clouddocs.f5.com/products/extensions/f5-appsvcs-extension/latest/" target="_blank" rel="noopener"&gt;AS3&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;This time let's take a look at another lab, that you can set up in less than 10 minutes.&lt;/P&gt;
&lt;H2&gt;Purpose of this lab&lt;/H2&gt;
&lt;P&gt;This lab requires a web server. And some minimal knowledge of Linux (Debian) and git.&lt;BR /&gt;In my example, I use NGINX. The web application consists of four pages in four colours (red, blue, yellow and green) that are designed to demonstrate the load balancing functionality of the F5 Local Traffic Manager (LTM).&lt;BR /&gt;You can use the app to familiarise yourself with load balancing functionalities such as:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;different load balancing methods and priority groups&lt;/LI&gt;
&lt;LI&gt;different types of persistence&lt;/LI&gt;
&lt;LI&gt;caching&lt;/LI&gt;
&lt;LI&gt;HTTP, SSL and other profiles&lt;/LI&gt;
&lt;LI&gt;SNAT&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;The web application has a couple of nice features&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;real-time server information display like
&lt;UL&gt;
&lt;LI&gt;Server hostname&lt;/LI&gt;
&lt;LI&gt;Request timestamp (ISO 8601 format)&lt;/LI&gt;
&lt;LI&gt;Request URI&lt;/LI&gt;
&lt;LI&gt;Source IP address&lt;/LI&gt;
&lt;LI&gt;X-Forwarded-For (XFF) header&lt;/LI&gt;
&lt;LI&gt;User-Agent informatio&lt;/LI&gt;
&lt;/UL&gt;
&lt;/LI&gt;
&lt;LI&gt;modern, responsive UI&lt;/LI&gt;
&lt;LI&gt;picture gallery&amp;nbsp;&lt;/LI&gt;
&lt;/UL&gt;
&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2&gt;Prerequisites&lt;/H2&gt;
&lt;P&gt;First you need to set up and configure the web server.&lt;/P&gt;
&lt;H3&gt;Add multiple IPs to the web server (Debian 11+).&lt;/H3&gt;
&lt;P&gt;Edit &lt;EM&gt;/etc/network/interfaces&lt;/EM&gt;:&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;sudo nano /etc/network/interfaces&lt;/LI-CODE&gt;
&lt;P&gt;Add the following:&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;allow-hotplug eth0
iface eth0 inet static
address 192.168.1.10/24
gateway 192.168.1.1

auto eth0:1
allow-hotplug eth0:1
iface eth0:1 inet static
address 192.168.1.11/24

auto eth0:2
allow-hotplug eth0:2
iface eth0:2 inet static
address 192.168.1.12/24

auto eth0:3
allow-hotplug eth0:3
iface eth0:3 inet static
address 192.168.1.13/24&lt;/LI-CODE&gt;
&lt;P&gt;Restart networking:&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;sudo systemctl restart networking&lt;/LI-CODE&gt;
&lt;P&gt;&lt;STRONG&gt;Note:&lt;/STRONG&gt;&amp;nbsp;Replace &lt;EM&gt;eth0&lt;/EM&gt; with your actual interface name.&lt;/P&gt;
&lt;H3&gt;Generate SSL Certificate&lt;/H3&gt;
&lt;P&gt;Create a self-signed SSL certificate with RSA 2048-bit key (no password):&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout nginx-selfsigned.key -out nginx-selfsigned.crt \
  -subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"&lt;/LI-CODE&gt;
&lt;H3&gt;Installing the web application&lt;/H3&gt;
&lt;P&gt;Example for NGINX&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;1. Clone the repository&lt;/STRONG&gt;&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;git clone https://github.com/webserverdude/ltm-demo-html.git
cd webpages&lt;/LI-CODE&gt;
&lt;P&gt;&lt;STRONG&gt;2. Deploy to your web server&lt;/STRONG&gt;&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;sudo cp -r * /var/www/ltm-demo-html&lt;/LI-CODE&gt;
&lt;P&gt;&lt;STRONG&gt;3. Configure your web server&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;see below&lt;/P&gt;
&lt;H3&gt;NGINX Configuration&lt;/H3&gt;
&lt;P&gt;The configuration includes HTTP as well as HTTPS listeners.&lt;/P&gt;
&lt;P&gt;Add this configuration to your NGINX server block:&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;server {
        listen 192.168.1.10:8000 default_server;
        root /var/www/ltm-demo-html;

        index index_red.html;

        server_name _;

        add_header X-Backend-Server 1;
        add_header Set-Cookie "X-Backend-Server=1; Max-Age=10";

        location / {
                try_files $uri $uri/ =404;
        }

        # Enable the substitution filter
        sub_filter_once off;  # Allow multiple substitutions

        # Replace template variables with actual NGINX variables
        sub_filter '{{server_name}}' '$hostname';
        sub_filter '{{time_iso8601}}' '$time_iso8601';
        sub_filter '{{request_uri}}' '$request_uri';
        sub_filter '{{remote_addr}}' '$remote_addr';
        sub_filter '{{http_x_forwarded_for}}' '$http_x_forwarded_for';
        sub_filter '{{http_user_agent}}' '$http_user_agent';
}
server {
        listen 10.0.2.71:443 ssl default_server;
        ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
        ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;

        # SSL configuration
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
        ssl_prefer_server_ciphers off;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;

        root /var/www/ltm-demo-html;
        index index_red.html;
        server_name _;
        add_header X-Backend-Server 1;
        add_header Set-Cookie "X-Backend-Server=$request_id; Max-Age=10; Secure; SameSite=Strict";
        location / {
                try_files $uri $uri/ =404;
        }
        # Enable the substitution filter
        sub_filter_once off;  # Allow multiple substitutions

        # Replace template variables with actual NGINX variables
        sub_filter '{{server_name}}' '$hostname';
        sub_filter '{{time_iso8601}}' '$time_iso8601';
        sub_filter '{{request_uri}}' '$request_uri';
        sub_filter '{{remote_addr}}' '$remote_addr';
        sub_filter '{{http_x_forwarded_for}}' '$http_x_forwarded_for';
        sub_filter '{{http_user_agent}}' '$http_user_agent';
}&lt;/LI-CODE&gt;
&lt;P&gt;&lt;STRONG&gt;Note:&lt;/STRONG&gt;&amp;nbsp;This is just a snippet for one HTTP and one HTTPS virtual. The full config for all four pages is available at &lt;A class="lia-external-url" href="https://github.com/webserverdude/f5_ltm_demo" target="_blank" rel="noopener"&gt;my Git repository&lt;/A&gt; in the &lt;EM&gt;nginx_config&lt;/EM&gt; folder.&lt;/P&gt;
&lt;P&gt;Once this is done, check the web pages from your browser. Make sure they work as expected.&lt;/P&gt;
&lt;H2&gt;Configure your BIG-IP&lt;/H2&gt;
&lt;P&gt;After the web server is running and serving all 4 pages with HTTP and HTTPS, you can configure your BIG-IP. My AS3 declaration includes an HTTP and an HTTPS virtual server, two pools and some http and persistence profiles. Here is a snippet:&lt;/P&gt;
&lt;LI-CODE lang="json"&gt;{
    "$schema": "https://raw.githubusercontent.com/F5Networks/f5-appsvcs-extension/main/schema/latest/as3-schema.json",
    "class": "AS3",
    "action": "deploy",
    "persist": true,
    "declaration": {
        "class": "ADC",
        "schemaVersion": "3.0.0",
        "LTM_Demo": {
            "class": "Tenant",
            "LTM_Demo": {
                "class": "Application",
                "vs_http": {
                    "class": "Service_HTTP",
                    "virtualAddresses": [
                        "192.168.3.80"
                    ],
                    "persistenceMethods": [],
                    "profileHTTP": {
                        "use": "pr_http_xff"
                    },
                    "pool": "pl_ltm-demo_http",
                    "snat": {
                        "use": "pl_SNAT_addresses"
                    }
                }, ...&lt;/LI-CODE&gt;
&lt;P&gt;The complete AS3 configuration can be found in&amp;nbsp;&lt;A class="lia-external-url" href="https://raw.githubusercontent.com/webserverdude/f5_ltm_demo/refs/heads/main/as3/ltm_demo.json" target="_blank" rel="noopener"&gt;my Git repository&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;The repository also contains an &lt;A class="lia-external-url" href="https://raw.githubusercontent.com/webserverdude/f5_ltm_demo/refs/heads/main/as3/config_options_snippets.json" target="_blank" rel="noopener"&gt;additional AS3 declaration&lt;/A&gt; with further configuration options.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Note:&lt;/STRONG&gt; You should not deploy the second declaration with the optional configurations; instead, merge the snippets you want to use into&amp;nbsp;&lt;EM&gt;ltm_demo.json&lt;/EM&gt;.&lt;/P&gt;
&lt;H2&gt;Deployment&lt;/H2&gt;
&lt;P&gt;The deployment of the AS3 declaration works similar like I described in &lt;A class="lia-internal-link lia-internal-url lia-internal-url-content-type-tkb" href="https://community.f5.com/kb/codeshare/less-than-60-seconds-lab-setup/343943" target="_blank" rel="noopener" data-lia-auto-title="my previous post" data-lia-auto-title-active="0"&gt;my previous post&lt;/A&gt;.&lt;/P&gt;
&lt;H2&gt;What's next?&lt;/H2&gt;
&lt;P&gt;You can try differnet load balancing algorithms, persistence methods, caching, SSL configurations.&lt;BR /&gt;Once you set up the web app and the LTM config, play around - the sky is the limit. Have fun!&lt;/P&gt;</description>
      <pubDate>Tue, 11 Nov 2025 17:08:31 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/less-than-600-seconds-lab/ta-p/344076</guid>
      <dc:creator>Daniel_Wolf</dc:creator>
      <dc:date>2025-11-11T17:08:31Z</dc:date>
    </item>
    <item>
      <title>Less than 60 seconds lab setup</title>
      <link>https://community.f5.com/t5/codeshare/less-than-60-seconds-lab-setup/ta-p/343943</link>
      <description>&lt;H5&gt;Code is community submitted, community supported, and recognized as ‘Use At Your Own Risk’.&lt;/H5&gt;
&lt;P&gt;Today I'll share with you my less than 60 seconds lab setup which I use for testing basic stuff. It's an AS3 declaration that will setup two virtuals, the first virtual that accepts any http traffic on port 80 and forwards it to a second virtual that will respond &lt;EM&gt;200 OK&lt;/EM&gt; to any HTTP request.&lt;/P&gt;
&lt;P&gt;The lab can easily be extended to add a https virtual.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2&gt;Purpose of this setup&lt;/H2&gt;
&lt;P&gt;I use this configuration for many scenarios. With this setup I can test different profiles, TLS configurations (requires small adjustments), AWAF rules and iRules attached to the first virtual server without the requirement to setup any backend application.&lt;/P&gt;
&lt;P&gt;Deploying this AS3 declaration takes less than 20 seconds and I have a basic lab environment ready.&lt;/P&gt;
&lt;H2&gt;Prerequisites&lt;/H2&gt;
&lt;P&gt;In order to use this config, you must have AS3 installed on your BIG-IP.&lt;/P&gt;
&lt;P&gt;If you have not worked with AS3 yet and you are new to automation, I recommend you to start with &lt;A class="lia-external-url" href="https://code.visualstudio.com/" target="_blank" rel="noopener"&gt;Visual Studio Code&lt;/A&gt; and install &lt;A class="lia-external-url" href="https://marketplace.visualstudio.com/items?itemName=F5DevCentral.vscode-f5" target="_blank" rel="noopener"&gt;The F5 Extension&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;From &lt;STRONG&gt;The F5 Extension&lt;/STRONG&gt; you can connect your BIG-IP and install the AS3 extension and deploy the declaration.&lt;/P&gt;
&lt;P&gt;Furthermore: if you have not with AS3 yet - you're damn late to the party!&lt;/P&gt;
&lt;H2&gt;My AS3 declaration&lt;/H2&gt;
&lt;P&gt;The full declaration is available on &lt;A class="lia-external-url" href="https://raw.githubusercontent.com/webserverdude/f5_as3_declarations/refs/heads/main/simple_testing/simple_testing_AS3.json" target="_blank" rel="noopener"&gt;GitHub&lt;/A&gt;, let's just look at the iRules. The iRules are the important part of this lab config.&lt;/P&gt;
&lt;P&gt;Don't get confused that you won't see the iRule code in the AS3 declaration. It's there, but it's base64 encoded.&lt;/P&gt;
&lt;H3&gt;Forwarding iRule&lt;/H3&gt;
&lt;P&gt;The iRule attached to the first virtual just forwards to the second virtual.&lt;/P&gt;
&lt;P&gt;Don't get confused by the path &lt;EM&gt;/simple_testing/responder_service/&lt;/EM&gt;. AS3 works with Partitions, so called tenants. Therefore I must reference the second virtual with the name of its partition and application.&lt;/P&gt;
&lt;LI-CODE lang="tcl"&gt;when HTTP_REQUEST {
  virtual /simple_testing/responder_service/service_http_200
}&lt;/LI-CODE&gt;
&lt;H3&gt;HTTP Responder iRule&lt;/H3&gt;
&lt;P&gt;The second iRule is attached to the second virtual server. It will just return a HTML page that says &lt;EM&gt;200 OK&lt;/EM&gt; to any request.&lt;/P&gt;
&lt;LI-CODE lang="tcl"&gt;when HTTP_REQUEST {
   HTTP::respond 200 content {
      &amp;lt;html&amp;gt;
         &amp;lt;head&amp;gt;
            &amp;lt;title&amp;gt;BIG-IP&amp;lt;/title&amp;gt;
         &amp;lt;/head&amp;gt;
         &amp;lt;body&amp;gt;
            200 OK
         &amp;lt;/body&amp;gt;
      &amp;lt;/html&amp;gt;
   }
}&lt;/LI-CODE&gt;
&lt;H2&gt;Deployment&lt;/H2&gt;
&lt;P&gt;As said above, for starting with this you don't need anything but a BIG-IP and Visual Studio Code. After installing the F5 Extension you can connect (using the &lt;STRONG&gt;+&lt;/STRONG&gt; symbol) to your BIG-IP from VS Code.&lt;/P&gt;
&lt;img /&gt;
&lt;P&gt;After connecting you can install the AS3 extension on your BIG-IP.&lt;/P&gt;
&lt;img /&gt;
&lt;P&gt;And then you are ready to deploy the AS3 declaration linked above. The deployment will take less than 60 seconds.&lt;/P&gt;
&lt;img /&gt;
&lt;P&gt;Once the deployment is done, you will have a Partition called on your BIG-IP. There you will find the two virtual servers.&lt;/P&gt;
&lt;img /&gt;
&lt;P&gt;The website is nothing special...&lt;/P&gt;
&lt;img /&gt;
&lt;H2&gt;What's next?&lt;/H2&gt;
&lt;P&gt;In the next couple of days, I will share with you a simple website I made with the help of ChatGPT. It can run on any webserver, NGINX, Apache, IIS...&lt;/P&gt;
&lt;P&gt;The website has 4 flavors (red, blue, green and yellow) and I use it for testing LTM use-cases like persistence, priority groups, http profiles, SNAT, etc.&lt;/P&gt;
&lt;P&gt;This will be my less than 600 seconds lab.&lt;/P&gt;</description>
      <pubDate>Tue, 11 Nov 2025 17:08:32 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/less-than-60-seconds-lab-setup/ta-p/343943</guid>
      <dc:creator>Daniel_Wolf</dc:creator>
      <dc:date>2025-11-11T17:08:32Z</dc:date>
    </item>
    <item>
      <title>F5 BIG-IP Multi-Site Dashboard</title>
      <link>https://community.f5.com/t5/codeshare/f5-big-ip-multi-site-dashboard/ta-p/343635</link>
      <description>&lt;H5&gt;Code is community submitted, community supported, and recognized as ‘Use At Your Own Risk’.&lt;/H5&gt;
&lt;P&gt;A comprehensive real-time monitoring dashboard for F5 BIG-IP Application Delivery Controllers featuring multi-site support, DNS hostname resolution, member state tracking, and advanced filtering capabilities.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;A 170KB modular JavaScript application runs entirely in your browser, served directly from the F5's high-speed operational dataplane. One or more sites operate as Dashboard Front-Ends serving the dashboard interface (HTML, JavaScript, CSS) via iFiles, while other sites operate as API Hosts providing pool data through optimized JSON-based dashboard API calls. This provides unified visibility across multiple sites from a single interface without requiring even a read-only account on any of the BIG-IPs, allowing you to switch between locations and see consistent pool, member, and health status data with almost no latency and very little overhead.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Think of it as an extension of the F5 GUI: near real-time state tracking, DNS hostname resolution (if configured), advanced search/filtering, and the ability to see exactly what changed and when. It gives application teams and operations teams direct visibility into application pool state without needing to wait for answers from F5 engineers, eliminating the organizational bottleneck that slows down troubleshooting when every minute counts.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;A class="lia-external-url" href="https://github.com/hauptem/F5-Multisite-Dashboard" target="_blank" rel="noopener"&gt;https://github.com/hauptem/F5-Multisite-Dashboard&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Tue, 11 Nov 2025 17:11:00 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/f5-big-ip-multi-site-dashboard/ta-p/343635</guid>
      <dc:creator>hauptem</dc:creator>
      <dc:date>2025-11-11T17:11:00Z</dc:date>
    </item>
    <item>
      <title>F5 DNS/GTM External Monitor(EAV) with SNI support and response code check</title>
      <link>https://community.f5.com/t5/codeshare/f5-dns-gtm-external-monitor-eav-with-sni-support-and-response/ta-p/343173</link>
      <description>&lt;H5&gt;Code is community submitted, community supported, and recognized as ‘Use At Your Own Risk’.&lt;/H5&gt;
&lt;P&gt;The example DNS/GTM health monitor is for versions before 16.1 as BIG-IP supports SNI for default DNS/GTM HTTPS monitor in the latest version but if you have still not upgraded then this is for you!&lt;/P&gt;
&lt;P&gt;I have used this monitor for XC Distributed Cloud as the HTTP LB share by default the same tenant IP address and SNI support is needed. You can order dedicated public IP addresses for each HTTP LB and enable "&lt;EM&gt;Default Load Balancer&lt;/EM&gt;" ( &lt;A class="lia-external-url" href="https://my.f5.com/manage/s/article/K000152902" target="_blank" rel="noopener"&gt;https://my.f5.com/manage/s/article/K000152902&lt;/A&gt;&amp;nbsp; ) option but it will cost you extra 😉&lt;/P&gt;
&lt;P&gt;The script is a modified version of&amp;nbsp;&lt;A href="https://my.f5.com/manage/s/article/K05526527" target="_blank" rel="noopener"&gt;External https health monitor for SNI-enabled pool&lt;/A&gt; as to handle response codes and to set the SNI globally for the entire pool and it's members.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;If you are uploading from Windows machine see &lt;A href="https://my.f5.com/manage/s/article/K41251003" target="_blank" rel="noopener"&gt;External monitor fails to run&lt;/A&gt; as you could hit the bug.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;This could be needed for F5 DNS/GTM below 16.1 that do not support SNI in HTTPS monitors.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;The only mandatory variable is "SNI" that should be set in the external monitor config that references this uploaded bash script. The "URI" variable by default is set to "/" and "$2" variable by default is empty or 443, the default expected response code 200.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;#!/bin/sh
# External monitoring script for checking HTTP status code
# $1 = IP (::ffff:nnn.nnn.nnn.nnn notation or hostname)
# $2 = port (optional; defaults to 443 if not provided)

# Default SNI to IP if not explicitly provided
node_ip=$(echo "$1" | sed 's/::ffff://') # Remove IPv6 compatibility prefix
SNI=${SNI:-"$node_ip"}                   # Assign sanitized IP to SNI

# Default variables
MON_NAME=${MON_NAME:-"MyExtMon$$"}
pidfile="/var/run/$MON_NAME.$1..$2.pid"  # PID file path
DEBUG=${DEBUG:-0}                        # Enable debugging if set to 1
EXPECTED_STATUS=${EXPECTED_STATUS:-200}  # Default HTTP status code to 200
URI=${URI:-"/"}                          # Default URI
DEFAULT_PORT=443                         # Default port (used if $2 is unset)

# Set port to default if $2 is not provided
if [ -z "${2}" ]; then
    PORT=${DEFAULT_PORT}
else
    PORT=${2}
fi

# Kill old process if pidfile exists
if [ -f "$pidfile" ]; then
    kill -9 -$(cat "$pidfile") &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
fi
echo "$$" &amp;gt; "$pidfile"

# Perform the HTTP(S) request via single curl (fetch status code only)
status_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 --resolve "${SNI}:${PORT}:${node_ip}" "https://${SNI}:${PORT}${URI}")

# Cleanup
rm -f "$pidfile" &amp;gt; /dev/null 2&amp;gt;&amp;amp;1

# Output server status based on HTTP status code match
if [ "$status_code" -eq "$EXPECTED_STATUS" ]; then
    echo "up"
else
    echo "down"
fi

# Debugging
if [ "$DEBUG" -eq 1 ]; then
    echo "Debugging on..."
    echo "SNI=${SNI}"
    echo "URI=${URI}"
    echo "IP=${node_ip}"
    echo "PORT=${PORT}"
    echo "MON_NAME=${MON_NAME}"
    echo "STATUS_CODE=${status_code}"
    echo "EXPECTED_STATUS=${EXPECTED_STATUS}"
    echo "curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 --resolve ${SNI}:${PORT}:${node_ip} https://${SNI}:${PORT}${URI}"
fi&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Tue, 11 Nov 2025 17:10:59 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/f5-dns-gtm-external-monitor-eav-with-sni-support-and-response/ta-p/343173</guid>
      <dc:creator>Nikoolayy1</dc:creator>
      <dc:date>2025-11-11T17:10:59Z</dc:date>
    </item>
    <item>
      <title>Remove alerts showing r-series LCD/Dashboard.</title>
      <link>https://community.f5.com/t5/codeshare/remove-alerts-showing-r-series-lcd-dashboard/ta-p/343228</link>
      <description>&lt;H5&gt;Code is community submitted, community supported, and recognized as ‘Use At Your Own Risk’.&lt;/H5&gt;
&lt;P&gt;Especially R5+ with 1.8.0+, may you will see unexpected "interface down" alerts.&lt;BR /&gt;You could use this one-liner in rseries Bash. Hope it helps :p&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;[root@appliance-1 ~]# date;docker exec system_manager /confd/scripts/f5_confd_run_cmd 'show system alarms alarm state | csv'
Mon Sep  1 14:32:24 JST 2025
# show system alarms alarm state | csv
ID,RESOURCE,SEVERITY,TEXT,TIME CREATED
263169,interface-1.0,WARNING,Interface down,2025-06-11 02:24:43.512626724 UTC
263169,interface-10.0,WARNING,Interface down,2025-06-11 02:24:43.514048068 UTC
263169,interface-2.0,WARNING,Interface down,2025-06-11 02:24:43.517935094 UTC
263169,interface-5.0,WARNING,Interface down,2025-06-11 02:24:43.526179871 UTC
263169,interface-6.0,WARNING,Interface down,2025-06-11 02:24:43.528668180 UTC
263169,interface-7.0,WARNING,Interface down,2025-06-11 02:24:43.530864483 UTC
263169,interface-8.0,WARNING,Interface down,2025-06-11 02:24:43.533197062 UTC
263169,interface-9.0,WARNING,Interface down,2025-06-11 02:24:43.535438297 UTC

[root@appliance-1 ~]# date;docker exec system_manager /confd/scripts/f5_confd_run_cmd 'show system alarms alarm state | csv'|grep 263169|cut -f 2 -d ,|xargs -I{} docker exec alert-service /confd/test/sendAlert -i 263169 -r clear-all -s {}

Mon Sep  1 14:33:27 JST 2025
Alert Sent
Alert Sent
Alert Sent
Alert Sent
Alert Sent
Alert Sent
Alert Sent
Alert Sent
[root@appliance-1 ~]# date;docker exec system_manager /confd/scripts/f5_confd_run_cmd 'show system alarms alarm state | csv'
Mon Sep  1 14:33:54 JST 2025
# show system alarms alarm state | csv
% No entries found.                           &amp;lt;----------  REMOVED.
[root@appliance-1 ~]#&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;BR /&gt;Ideas came from:&lt;BR /&gt;https://cdn.f5.com/product/bugtracker/ID1644293.html&lt;BR /&gt;https://my.f5.com/manage/s/article/K000150155&lt;/P&gt;</description>
      <pubDate>Tue, 11 Nov 2025 17:10:58 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/remove-alerts-showing-r-series-lcd-dashboard/ta-p/343228</guid>
      <dc:creator>Kalo</dc:creator>
      <dc:date>2025-11-11T17:10:58Z</dc:date>
    </item>
    <item>
      <title>The WAF Dilemma</title>
      <link>https://community.f5.com/t5/codeshare/the-waf-dilemma/ta-p/342976</link>
      <description>&lt;H5&gt;Code is community submitted, community supported, and recognized as ‘Use At Your Own Risk’.&lt;/H5&gt;
&lt;P&gt;How I lowered false positives with NGINX App Protect without compromising security.&lt;/P&gt;
&lt;P&gt;We are always facing the dilemma "Security vs Usability" in the world of security.&lt;/P&gt;
&lt;P&gt;This becomes painfully obvious once you start implementing a WAF. I have now implemented a wide range of WAF security policies, both BigIP AWAF and NAP, and two application functions/features always stand out: file upload and wiki editors.&lt;/P&gt;
&lt;P&gt;The core problem with the two scenarios is that they are about handling unstructured data. No matter how hard you try to tune the policy you will have an endless amount of false positives interrupting the end users. If we don't handle this problem correctly we will be forced (aka being demanded by the business) to&amp;nbsp; disable the WAF policy. And that is a loose-loose situation.&lt;/P&gt;
&lt;P&gt;What I have constructed is a way to minimize this problem by differentiate between authenticated and unauthenticated end users. In most situations we can have a higher level of trust in traffic that is authenticated and thus tune down on the security. My design is very binary, if you are authenticated the WAF is turned off, if not it is on. This might not be good enough for you but this is only an example on how to go about the core problem. You can fine-tune the solution to be more granular based on the information available like switching the security policy or other mitigating actions. Just remember that having a simple WAF is always better than not having any at all.&lt;/P&gt;
&lt;P&gt;You can find the details, configuration and code here: &lt;A href="https://wiki.bitw.io/howto/nginx_nap_authentication_steering" target="_blank" rel="noopener"&gt;NGINX App Protect with Authentication | Wiki&lt;/A&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;As always feedback is much appreciated!&lt;/P&gt;</description>
      <pubDate>Tue, 11 Nov 2025 17:10:57 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/the-waf-dilemma/ta-p/342976</guid>
      <dc:creator>lnxgeek</dc:creator>
      <dc:date>2025-11-11T17:10:57Z</dc:date>
    </item>
    <item>
      <title>NGINX Plus Request body Rate Limit with the NJS module and javascript</title>
      <link>https://community.f5.com/t5/codeshare/nginx-plus-request-body-rate-limit-with-the-njs-module-and/ta-p/342764</link>
      <description>&lt;img /&gt;
&lt;P&gt;The nginx njs module allows javascript to process the code or as they call it on the backend nodejs. The module is dynamic for Nginx Plus&amp;nbsp;&lt;A href="https://docs.nginx.com/nginx/admin-guide/dynamic-modules/dynamic-modules/" target="_blank" rel="noopener"&gt;https://docs.nginx.com/nginx/admin-guide/dynamic-modules/dynamic-modules/&lt;/A&gt; while for the community nginx it needs to be compiled.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The code and nginx configuration are also present at:&lt;/P&gt;
&lt;P&gt;&lt;A class="lia-external-url" href="https://github.com/Nikoolayy1/nginx_njs_request_body_limit/tree/main" target="_blank" rel="noopener"&gt;https://github.com/Nikoolayy1/nginx_njs_request_body_limit/tree/main&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;I have used the example rate limiter from &lt;A href="https://github.com/nginx/njs-examples" target="_blank" rel="noopener"&gt;https://github.com/nginx/njs-examples&lt;/A&gt; and &lt;A class="lia-external-url" href="https://clouddocs.f5.com/training/community/nginx/html/class3/class3.html" target="_blank" rel="noopener"&gt;https://clouddocs.f5.com/training/community/nginx/html/class3/class3.html&lt;/A&gt; and modified rate limit example to be based on the request body.&lt;/P&gt;
&lt;P&gt;It works as expected. The "r.internalRedirect('@app-backend');" internal redirect is needed as nginx by default does not populate or save the request body and this is why the request needs to pass 2 times in nginx proxy for the body variable to be properly populated!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The nginx plus rootless container is a great option for F5 XC RE where root containers are not accepted and for Nginx on XC RE I have made another article at &lt;A class="lia-internal-link lia-internal-url lia-internal-url-content-type-tkb" href="https://community.f5.com/kb/codeshare/f5-xc-vk8s-open-source-nginx-deployment-on-re/328275" target="_blank" rel="noopener" data-lia-auto-title="F5 XC vk8s open source nginx deployment on RE | DevCentral" data-lia-auto-title-active="0"&gt;F5 XC vk8s open source nginx deployment on RE | DevCentral&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;NJS "main" file code:&lt;/P&gt;
&lt;LI-CODE lang=""&gt;const defaultResponse = "0";
const user = 'username';
const pass = 'username';

function ratelimit(r) {


      switch (r.method) {


      case 'POST':

         var body  = r.requestText;

          r.log(`body: ${body}`);

     if (r.headersIn['Content-Type'] != 'application/x-www-form-urlencoded' || !body.length)

          {

              r.internalRedirect('@app-backend');

              return;

          }

           var result_user = body.includes(user);
           var result_pass = body.includes(pass);

                      if (!result_user) {

                              r.internalRedirect('@app-backend');

                             return;

                      }

    const zone = r.variables['rl_zone_name'];

    const kv = zone &amp;amp;&amp;amp; ngx.shared &amp;amp;&amp;amp; ngx.shared[zone];

    if (!kv) {

        r.log(`ratelimit: ${zone} js_shared_dict_zone not found`);

        r.internalRedirect('@app-backend');

        return;

    }

    const key = r.variables['rl_key'] || r.variables['remote_addr'];

    const window = Number(r.variables['rl_windows_ms']) || 60000;

    const limit = Number(r.variables['rl_limit']) || 10;

    const now = Date.now();


    let requestData = kv.get(key);

    if (requestData === undefined || requestData.length === 0) {

        requestData = { timestamp: now, count: 1 }

        kv.set(key, JSON.stringify(requestData));

        r.internalRedirect('@app-backend');

        return;

    }

    try {

        requestData = JSON.parse(requestData);

    } catch (e) {

        requestData = { timestamp: now, count: 1 }

        kv.set(key, JSON.stringify(requestData));

        r.internalRedirect('@app-backend');

        return;

    }

    if (!requestData) {

        requestData = { timestamp: now, count: 1 }

        kv.set(key, JSON.stringify(requestData));

        r.internalRedirect('@app-backend');

        return;

    }

    if (now - requestData.timestamp &amp;gt;= window) {

        requestData.timestamp = now;

        requestData.count = 1;

    } else {

        requestData.count++;

    }

    const elapsed = now - requestData.timestamp;

    r.log(`limit: ${limit} window: ${window} elapsed: ${elapsed}  count: ${requestData.count} timestamp: ${requestData.timestamp}`)

    let retryAfter = 0;

    if (requestData.count &amp;gt; limit) {

        retryAfter = 1;

    }
    kv.set(key, JSON.stringify(requestData));

   if (retryAfter) {

            r.return(401, "Unauthorized\n");

           return;

   }

      default:

           r.internalRedirect('@app-backend');

           return;

      }

  }

export default {sub, header, ratelimit, parseRequestBody, log};&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Nginx nginx.conf file:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang=""&gt; server {
    listen       80 default_server;
    server_name  localhost;
   access_log  /var/log/nginx/host.access.log  main;

   js_var $rl_zone_name kv;          # shared dict zone name; requred variable
    js_var $rl_windows_ms 30000;      # optional window in miliseconds; default 1 minute window if not set
    js_var $rl_limit 3;              # optional limit for the window; default 10 requests if not set
    js_var $rl_key $remote_addr;      # rate limit key; default remote_addr if not set
    js_set $rl_result main.ratelimit; # call ratelimit function that returns retry-after value if limit is exceeded

    root /var/www/html;

    index  index.html;
    include       /etc/nginx/mime.types;

error_log  /var/log/nginx/host.error_log  debug;

if ($target) {
    return 401;
}

    location / {
       js_content main.ratelimit;

    }

     location @app-backend {
          internal;
          proxy_pass http://backend;
      }

location /backend {
   internal;
   proxy_set_header Host httpforever.com;
   proxy_pass http://backend/;

            }&lt;/LI-CODE&gt;
&lt;P&gt;&lt;STRONG&gt;&lt;EM&gt;Summary:&lt;/EM&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;There is another example how to populate the internal request body variable using that is needed by the njs module using the " mirror " option, shown at &lt;A class="lia-external-url" href="https://www.f5.com/company/blog/nginx/deploying-nginx-plus-as-an-api-gateway-part-2-protecting-backend-services" target="_blank" rel="noopener"&gt;https://www.f5.com/company/blog/nginx/deploying-nginx-plus-as-an-api-gateway-part-2-protecting-backend-services&lt;/A&gt;&amp;nbsp; but it did not work for me, so I used the " internal " option with "r.internalRedirect(uri)" &lt;A class="lia-external-url" href="https://nginx.org/en/docs/njs/reference.html" target="_blank" rel="noopener"&gt;https://nginx.org/en/docs/njs/reference.html&lt;/A&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;Nginx njs feature r.subrequest can be used to populate response headers and body but mainly it is for logging and not for rate limiting and I think making a real http subrequest using javascript is not optimal and will not scale well, so I will not recommend this option as rate limiters are best left to be request based. Also I saw strange bug that the subrequest changes the content type header of the response and I had use "js_header_filter" to again change the response header.Nginx App Protect has the BD process from F5 BIG-IP AWAF/ASM that has DOS protections that can monitor the Server's response latency dynamically and make auto thresholds!&lt;/EM&gt;&lt;/P&gt;</description>
      <pubDate>Wed, 06 Aug 2025 08:45:50 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/nginx-plus-request-body-rate-limit-with-the-njs-module-and/ta-p/342764</guid>
      <dc:creator>Nikoolayy1</dc:creator>
      <dc:date>2025-08-06T08:45:50Z</dc:date>
    </item>
    <item>
      <title>F5 BIG-IP and ENTRUST nShield HSM SSL key/cert auto synchronization between HA peers with iCall</title>
      <link>https://community.f5.com/t5/codeshare/f5-big-ip-and-entrust-nshield-hsm-ssl-key-cert-auto/ta-p/342381</link>
      <description>&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;Code version:&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;The code was tested on 15.1.8.&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;U&gt;&lt;EM&gt;&lt;STRONG&gt;Main Article:&lt;/STRONG&gt;&lt;/EM&gt;&lt;/U&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;For more information about RFS and Client agent I suggest seeing the vendors article.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="https://nshielddocs.entrust.com/security-world-docs/v13.3/connect-ug-nix/intro.html" target="_blank" rel="noopener"&gt;https://nshielddocs.entrust.com/security-world-docs/v13.3/connect-ug-nix/intro.html&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Useful F5 links for F5 and nShield Integration for GTM and LTM:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="https://my.f5.com/manage/s/article/K000135349" target="_blank" rel="noopener"&gt;https://my.f5.com/manage/s/article/K000135349&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://techdocs.f5.com/en-us/bigip-15-1-0/big-ip-system-and-nshield-hsm-implementation/setting-up-the-nshield-hsm.html" target="_blank" rel="noopener"&gt;https://techdocs.f5.com/en-us/bigip-15-1-0/big-ip-system-and-nshield-hsm-implementation/setting-up-t...&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The nShield architecture includes a component called the Remote File System (RFS) that stores and manages the encrypted key files. The RFS can be installed on the BIG-IP system or on another server on your network.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Basically the HSM agent/client is installed on the F5 devices hos Linux host system and the F5 devices are also the RFS servers.&lt;/P&gt;
&lt;P&gt;The RFS commands are bellow as when installed on the BIG-IP the HSM agent and RFS they are available for use:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="https://nshielddocs.entrust.com/security-world-docs/utilities/rfs-sync.html" target="_blank" rel="noopener"&gt;https://nshielddocs.entrust.com/security-world-docs/utilities/rfs-sync.html&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The issue I solved with iCall script is that when when you create a new HSM key for BIG-IP HA, you must run command ‘rfs-sync --update’ on all standby BIG-IP devices (the devices where the cert/key were not created or changed) to update the local Thales encrypted file object cache. Without this action, SSL traffic using this key will fail when BIG-IP fails over to one of the unsynced standby devices.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;When you create the the key and cert on the active F5 device "rfs-sync -commit" and "rfs-sync -update" run automatically on it but not the "rfs-sync -update" on the standby devices and the icall script basically is triggered on the standby devices when you run the normal config sync.&lt;/P&gt;
&lt;P&gt;The iCall script matched an event called "HA_EVENT" that is configured in the custom alarms section and triggers the full command with the path "/opt/nfast/bin/rfs-sync --update" to check if there was an update in the rfs.&lt;/P&gt;
&lt;P&gt;I suggest reading the links below that explain the iCall (one is from &lt;a href="javascript:void(0)" data-lia-user-mentions="" data-lia-user-uid="51154" data-lia-user-login="JRahm" class="lia-mention lia-mention-user"&gt;JRahm​&lt;/a&gt; ) and the HA logs and the last one is mine that is from the time before I learned proper article formatting 😅 and it also shows how to run scripts not only with iCall but also during HA events and so on.&amp;nbsp;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="https://community.f5.com/kb/codeshare/run-tcpdump-on-event/273647" target="_blank" rel="noopener" data-lia-auto-title="Run tcpdump on event | DevCentral" data-lia-auto-title-active="0"&gt;Run tcpdump on event | DevCentral&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://community.f5.com/kb/technicalarticles/what-is-icall/288206" target="_blank" rel="noopener" data-lia-auto-title="What is iCall? | DevCentral" data-lia-auto-title-active="0"&gt;What is iCall? | DevCentral&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://my.f5.com/manage/s/article/K34291400" target="_blank" rel="noopener"&gt;https://my.f5.com/manage/s/article/K34291400&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://my.f5.com/manage/s/article/K3727" target="_blank" rel="noopener"&gt;https://my.f5.com/manage/s/article/K3727&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://my.f5.com/manage/s/article/K11127" target="_blank" rel="noopener"&gt;https://my.f5.com/manage/s/article/K11127&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://community.f5.com/kb/codeshare/knowledge-sharing-ways-to-trigger-and-schedule-scripts-on-the-f5-big-ip-devices-/291469" target="_blank" rel="noopener" data-lia-auto-title="Knowledge sharing: Ways to trigger and schedule scripts on the F5 BIG-IP devices. | DevCentral" data-lia-auto-title-active="0"&gt;Knowledge sharing: Ways to trigger and schedule scripts on the F5 BIG-IP devices. | DevCentral&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang=""&gt;tmsh list sys icall
sys icall handler triggered ha-handler {
    script ha-script
    subscriptions {
        ha-subscription {
            event-name HA_EVENT
        }
    }
}
sys icall script ha-script {
    app-service none
    definition {
        exec /bin/bash -c "logger -p local0.notice 'yes'"
        exec /bin/bash -c "/opt/nfast/bin/rfs-sync --update"
    }
    description none
    events none
}


cat /config/user_alert.conf
alert HA_EVENT "(.*)Sync of device group(.*)" {
 snmptrap OID=".1.3.6.1.4.1.3375.2.4.0.500"
}&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;You can use tmsh::log "yes" to log to /var/log/ltm as shown in:&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;A href="https://my.f5.com/s/article/K000139091" target="_blank" rel="noopener"&gt;iCall script to validate Virtual Server and node with same IP addresses&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;&lt;U&gt;&lt;STRONG&gt;Testing:&lt;/STRONG&gt;&lt;/U&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This can be tested even without HSM as I don't have one at my home using&amp;nbsp; "logger -p" to inject the logs. I have added "yes" in the logs as a test 😎&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;logger -p local0.notice "010714a0:5: Sync of device group /Common/Failover"&lt;/P&gt;
&lt;P&gt;cat /var/log/ltm&lt;/P&gt;
&lt;P&gt;............&lt;/P&gt;
&lt;P&gt;Jul &amp;nbsp;1 08:34:07 bigip1.com notice root[23762]: yes&lt;BR /&gt;Jul &amp;nbsp;1 08:34:53 bigip1.com notice root[23940]: 010714a0:5: Sync of device group /Common/Failover&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;&lt;U&gt;&lt;STRONG&gt;Using Linux bash script:&lt;/STRONG&gt;&lt;/U&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In some versions you can trigger from icall script a bash or sh script with advanced logic inside, for example&amp;nbsp;&lt;STRONG&gt; /bin/sh -c "/var/tmp/ha_script"&lt;/STRONG&gt; but I saw issues on 17.1.x triggering from from icall a bash script that in previous versions I solved by adding "HOME=/root&amp;nbsp; &amp;lt;linux bash command&amp;gt;" .&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In the "What is&amp;nbsp; iCall" it is shown that you can do "if else" or "for" loops inside the iCall script but I find it easy to use bash for advanced logic. &lt;U&gt;&lt;EM&gt;Good thing like everything with F5 usually there is more than one way to do things and this case in the user_alert.conf you can actually trigger a bash script from the log messages!&lt;/EM&gt;&lt;/U&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="https://community.f5.com/kb/technicalarticles/what-is-icall/288206" target="_blank" rel="noopener"&gt;What is iCall? | DevCentral&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://community.f5.com/discussions/technicalforum/icall-script-triggers-error-need-home-to-run/308286" target="_blank" rel="noopener"&gt;iCall script triggers error need ${HOME} to run | DevCentral&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://my.f5.com/manage/s/article/K14397" target="_blank" rel="noopener"&gt;Running a command or custom script based on a syslog message&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang=""&gt;cat /var/tmp/ha_script
#!/bin/bash


logger -p local0.notice "yes"
/opt/nfast/bin/rfs-sync --update

cat /config/user_alert.conf
alert HA_EVENT "(.*)Sync of device group(.*)" {
  exec command="/var/tmp/ha_script"
}&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;&lt;EM&gt;&lt;U&gt;Extra Notes:&lt;/U&gt;&lt;/EM&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Using /opt/nfast/bin/rfs-sync --update or rfs-sync --update depends in some cases on the versions in the iCall script.&lt;/LI&gt;
&lt;LI&gt;In the release notes I saw a new bug &lt;A href="https://cdn.f5.com/product/bugtracker/ID1429897.html" target="_blank" rel="noopener"&gt;https://cdn.f5.com/product/bugtracker/ID1429897.html&lt;/A&gt; that is solved in the latest 17.1.x versions where if the RFS is on the BIG-IP after a key/cert are created 'rfs-sync -c' needs to be run on the F5 Device that created them as well. The 'rfs-sync -c' can also be automated the way I have shown and my iCall script will work as well for BIG-IP that use external RFS and after the key/cert are created and committed from an F5 device (usually the active one) then an HA config sync needs to be started that will trigger 'rfs-sync -u' on the other F5 devices.&lt;/LI&gt;
&lt;LI&gt;Another nice way if you are using something like Ansible for example is to make to trigger the RFS update command on all F5 devices in a cluster as F5 supports bash commands even through API not only CLI.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;Example:&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;curl -sku admin:XXX https://XXXX/mgmt/tm/util/bash -H "Content-Type: application/json" -X POST -d '{"command":"run", "utilCmdArgs":"-c \"/opt/nfast/bin/rfs-sync --update\""}'&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="https://clouddocs.f5.com/products/orchestration/ansible/devel/f5_bigip/modules_2_0/bigip_command_module.html" target="_blank" rel="noopener"&gt;https://clouddocs.f5.com/products/orchestration/ansible/devel/f5_bigip/modules_2_0/bigip_command_mod...&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;</description>
      <pubDate>Wed, 06 Aug 2025 08:43:53 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/f5-big-ip-and-entrust-nshield-hsm-ssl-key-cert-auto/ta-p/342381</guid>
      <dc:creator>Nikoolayy1</dc:creator>
      <dc:date>2025-08-06T08:43:53Z</dc:date>
    </item>
    <item>
      <title>F5 ASM/AWAF Preventing unauthorized users accessing admin path​ using iRule script</title>
      <link>https://community.f5.com/t5/codeshare/f5-asm-awaf-preventing-unauthorized-users-accessing-admin-path/ta-p/342159</link>
      <description>&lt;img /&gt;
&lt;P&gt;The below code uses the new BIG-IP variables " [ASM::is_authenticated] " and " [ASM::username] " and the code is simple enough as if you are authenticated but not admin then you will not get access to the url path " /about.php "&amp;nbsp; and this is logged in the /var/log/asm logs because " log local3. ".&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;At the end of the article I have shown how with APM you can accomplish AD group limit for specific&amp;nbsp; urls but then the Authentication is moved on the APM while the AWAF iRule example the authentication is on the origin web server and the AWAF just handles the URL Authorization.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;when ASM_REQUEST_DONE {&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; if { [ASM::is_authenticated] &amp;amp;&amp;amp; [HTTP::path] equals "/about.php" } {&lt;BR /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; log local3. "This request was sent by user [ASM::username]."&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if {[ASM::username] equals "admin"} {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; log local3. "The admin has logged!"&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; } else {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;drop&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;}&amp;nbsp; &amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp;}&amp;nbsp;&amp;nbsp;&lt;BR /&gt;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Github link:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="https://github.com/Nikoolayy1/F5_AWAF-ASM-ADMIN-Access/tree/main" target="_blank" rel="noopener"&gt;Nikoolayy1/F5_AWAF-ASM-ADMIN-Access: F5 BIG-IP iRule code for limiting users by to access urls!&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The harder part is that you need to do several prerequisites that I will explain here:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Enable iRule support in the ASM policy.&lt;/LI&gt;
&lt;/UL&gt;
&lt;img /&gt;
&lt;UL&gt;
&lt;LI&gt;Configure a login page and optionally login enforcement (if " /about.php " is not blocked by the origin server to not be accessible before login this is a needed step!)&lt;/LI&gt;
&lt;/UL&gt;
&lt;img /&gt;
&lt;UL&gt;
&lt;LI&gt;Enable session tracking&amp;nbsp; by login page&lt;/LI&gt;
&lt;/UL&gt;
&lt;img /&gt;
&lt;UL&gt;
&lt;LI&gt;Attach the irule&lt;/LI&gt;
&lt;LI&gt;Test and see&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;U&gt;&lt;STRONG&gt;Example logs:&lt;/STRONG&gt;&lt;/U&gt;&lt;/P&gt;
&lt;P&gt;cat /var/log/asm&lt;/P&gt;
&lt;P&gt;.........&lt;/P&gt;
&lt;P&gt;Jun 25 03:59:33 bigip1.com info tmm2[11400]: Rule /Common/f5-asm-allow-admin &amp;lt;ASM_REQUEST_DONE&amp;gt;: This request was sent by user admin.&lt;BR /&gt;Jun 25 03:59:33 bigip1.com info tmm2[11400]: Rule /Common/f5-asm-allow-admin &amp;lt;ASM_REQUEST_DONE&amp;gt;: The admin has logged!&lt;BR /&gt;[root@bigip1:Active:Standalone] config #&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The DVWA app was used for this demo that is old but gold and there are many F5 demos how to configure login enforcement for it! Here is a youtube video for assistance:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="https://www.youtube.com/watch?v=VK4-liwHZrI&amp;amp;list=PLZmbPz-KgDtgJLfsdLmSHIXyv0TlQ-CJj&amp;amp;index=34&amp;amp;t=360s" target="_blank" rel="noopener"&gt;BIG-IP AWAF Demo 32 - Use Login Page Enforcement with F5 BIG-IP Adv WAF (formerly ASM)&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;U&gt;&lt;STRONG&gt;Extra links (there is also a new event "&lt;/STRONG&gt;&lt;/U&gt;&lt;STRONG&gt;ASM_RESPONSE_LOGIN&lt;/STRONG&gt;&lt;U&gt;&lt;STRONG&gt;"):&lt;/STRONG&gt;&lt;/U&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="https://clouddocs.f5.com/api/irules/ASM__username.html" target="_blank" rel="noopener"&gt;ASM::username&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://clouddocs.f5.com/api/irules/ASM__is_authenticated.html" target="_blank" rel="noopener"&gt;ASM::is_authenticated&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A class="lia-external-url" href="https://clouddocs.f5.com/api/irules/ASM.html" target="_blank" rel="noopener"&gt;https://clouddocs.f5.com/api/irules/ASM.html&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;U&gt;&lt;STRONG&gt;AD group url enforcement:&lt;/STRONG&gt;&lt;/U&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;If you want to control access to URLs based on AD groups I suggest seeing the F5 APM/Acess module that will take of the authentication and with Layer 7 ACL each AD group could be limited what it has access to. APM and AWAF can work together as with layered virtual server AWAF can be before the APM as by default is after it and then to get the username you need to use the login page feature and not "Use APM username and Session ID" feature in the AWAF policy.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="https://techdocs.f5.com/en-us/bigip-17-1-0/big-ip-access-policy-manager-portal-access/configure-access-control-lists.html" target="_blank" rel="noopener"&gt;Configuring Access Control Lists&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A class="lia-external-url" href="https://my.f5.com/manage/s/article/K00363504" target="_blank" rel="noopener"&gt;https://my.f5.com/manage/s/article/K00363504&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A class="lia-external-url" href="https://my.f5.com/manage/s/article/K03113285" target="_blank" rel="noopener"&gt;https://my.f5.com/manage/s/article/K03113285&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A class="lia-external-url" href="https://my.f5.com/manage/s/article/K54217479" target="_blank" rel="noopener"&gt;https://my.f5.com/manage/s/article/K54217479&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;DIV class="styles_lia-table-wrapper__h6Xo9 styles_table-responsive__MW0lN"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;P&gt;Example APM profile of type LTM+APM and the APM policy for anyone interested where the APM uses AD to authenticate the users and query for group data and the members for of the guest group have an ACL assigned that limits their access 😜&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;img /&gt;&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;U&gt;&lt;STRONG&gt;Summary:&lt;/STRONG&gt;&lt;/U&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;This probably will be seen as well in F5 NEXT with many more cool features !&lt;/EM&gt;&lt;/P&gt;</description>
      <pubDate>Thu, 14 Aug 2025 16:03:56 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/f5-asm-awaf-preventing-unauthorized-users-accessing-admin-path/ta-p/342159</guid>
      <dc:creator>Nikoolayy1</dc:creator>
      <dc:date>2025-08-14T16:03:56Z</dc:date>
    </item>
    <item>
      <title>NGINX App Protect v5 Signature Notifications</title>
      <link>https://community.f5.com/t5/codeshare/nginx-app-protect-v5-signature-notifications/ta-p/341937</link>
      <description>&lt;P&gt;When working with NAP (NGINX App Protect) you don't have an easy way of knowing when any of the signatures are updated.&lt;/P&gt;
&lt;P&gt;As an old BigIP guy I find that rather strange. Here you have build-in automatic updates and notifications.&lt;/P&gt;
&lt;P&gt;Unfortunately there isn't any API's you can probe which would have been the best way of doing it. Hopefully it will come one day.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;However, "friction" and "hard" will not keep me from finding a solution 😆&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;I have previously made a solution for NAPv4 and I have tried mentally to get me going on a NAPv5 version. The reason for the delay is in the different way NAPv4 and NAPv5 are designed. Where NAPv4 is one module loaded in NGINX, NAPv5 is completely detached from NGINX (well almost, you still need to load a small module to get the traffic from NGINX to NAP) and only works with containers.&lt;/P&gt;
&lt;P&gt;NAPv5 has moved the signature "storage" from the actual host it is running on (e.g. an installed package) to the policy. This has the consequence that finding a valid "source of truth", for the latest signature versions, is not as simple as building a new image and see which versions got installed.&lt;/P&gt;
&lt;P&gt;There are very good reasons for this design that I will come back to later.&lt;/P&gt;
&lt;P&gt;When you fire up NAPv5 you got three containers for the data plane (NGINX, waf-enforcer and waf-config-mgr) and one for the "control plane" (waf-compiler). For this solution the "control plane" is the useful one. It isn't really a control plane but it gives a nice picture of how it is detached from the actual processing of traffic.&lt;/P&gt;
&lt;P&gt;When you update your signatures you are actually doing it through the waf-compiler. The waf-compiler is a container hosting the actual signature databases and every time a new verison is released you need to rebuild this container and compile your policies into a new version and reload NGINX.&lt;/P&gt;
&lt;P&gt;And this is what I take advantage of when I look for signature updates. It has the upside that you only need the waf-compiler to get the information you need. My solution will take care of the entire process and make sure that you are always running with the latest signatures.&lt;/P&gt;
&lt;P&gt;Back to the reason why the split of functions is a very good thing. When you build a new version of the NGINX image and deploy it into production, NAP needs to compile the policies as they load. During the compilation NGINX is not moving any traffic! This becomes a annoying problem even when you have a low number of policies. I have installations where it takes 5 to 10 minutes from deployment of the new image until it starts moving traffic. That is a crazy long time when you are used to working with micro-services and expect everything to flip within seconds. If you have your NAPv4 hooked up to a NGINX Instance Manager (NIM) the problem is somewhat mitigated as NIM will compile the policies before sending them to the gateways. NIM is not a nimble piece of software so it doesn't always fit into the environment.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;And now here is my hack to the notification problem:&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;The solution consist of two bash scripts and one html template. The template is used when sending a notification mail. I wanted it to be pretty and that was easiest with html. Strictly speaking you could do with just a simple text based mail.&lt;/P&gt;
&lt;P&gt;Save all three in the same directory.&lt;/P&gt;
&lt;P&gt;The main script is called "waf_policy_auto_compile.sh"and is the one you put into crontab.&lt;/P&gt;
&lt;P&gt;The main script will build a new waf-compiler image and compile a test policy. The outcome of that is information about what versions are the newest. It will then extract versions from an old policy and simply see if any of the versions differ. For this to work you need to have an uncompiled policy (you can just use the default one) and a compiled version of it ready beforehand.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;When a diff has been identified the notification logic is executed and a second script is called: "compile_waf_policies.sh".&lt;/P&gt;
&lt;P&gt;It basically just trawls through the directory of you policies and logging profiles and compiles a new version of them all. It is not necessary to recompile the logging profiles, so this will probably change in the next version.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;As the compilation completes the main script will nudge NGINX to reload thus implement all the new versions.&lt;/P&gt;
&lt;P&gt;You can run "waf_policy_auto_compile.sh" with a verbose flag (-v) and a debug flag (-d). The verbose flag is intended to be used when you run it on a terminal and want the information displayed there. Debug is, well, for debug 😝&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The construction of the scripts are based on my own needs but they should be easy to adjust for any need.&lt;/P&gt;
&lt;P&gt;I will be happy for any feedback, so please don't hold back 😄&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;version_report_template.html:&lt;/P&gt;
&lt;LI-CODE lang="html"&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset="utf-8"&amp;gt;
  &amp;lt;title&amp;gt;WAF Policy Version Report&amp;lt;/title&amp;gt;
  &amp;lt;style&amp;gt;
    body { font-family: system-ui, sans-serif; }
    .ok { color: #28a745; font-weight: bold; }
    .warn { color: #f0ad4e; font-weight: bold; }
    .section { margin-bottom: 1.2em; }
    .label { font-weight: bold; }
  &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h2&amp;gt;WAF Policy Version Report&amp;lt;/h2&amp;gt;
  &amp;lt;div class="section"&amp;gt;
    &amp;lt;div class="label"&amp;gt;Attack Signatures:&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;Current: &amp;lt;span&amp;gt;{{ATTACK_OLD}}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;New: &amp;lt;span&amp;gt;{{ATTACK_NEW}}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;Status: &amp;lt;span class="{{ATTACK_CLASS}}"&amp;gt;{{ATTACK_STATUS}}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class="section"&amp;gt;
    &amp;lt;div class="label"&amp;gt;Bot Signatures:&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;Current: &amp;lt;span&amp;gt;{{BOT_OLD}}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;New: &amp;lt;span&amp;gt;{{BOT_NEW}}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;Status: &amp;lt;span class="{{BOT_CLASS}}"&amp;gt;{{BOT_STATUS}}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class="section"&amp;gt;
    &amp;lt;div class="label"&amp;gt;Threat Campaigns:&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;Current: &amp;lt;span&amp;gt;{{THREAT_OLD}}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;New: &amp;lt;span&amp;gt;{{THREAT_NEW}}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;Status: &amp;lt;span class="{{THREAT_CLASS}}"&amp;gt;{{THREAT_STATUS}}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;Run completed: {{RUN_DATETIME}}&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/LI-CODE&gt;
&lt;P&gt;compile_waf_policies.sh:&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;#!/bin/bash
# ==============================================================================
# Script Name: compile_waf_policies.sh
#
# Description:
#   Compiles:
#     1. WAF policy JSON files from the 'policies' directory
#     2. WAF logging JSON files from the 'logging' directory
#   using the 'waf-compiler-latest:custom' Docker image. Output goes to
#   '/opt/napv5/app_protect_etc_config' where NGINX and waf-config-mgr
#   can reach them.
#
# Requirements:
#   - Docker installed and accessible
#   - Docker image 'waf-compiler-latest:custom' present locally
#
# Usage:
#   ./compile_waf_policies.sh
# ==============================================================================

set -euo pipefail
IFS=$'\n\t'
SECONDS=0  # Track total execution time

# ========================
# CONFIGURABLE VARIABLES
# ========================

BASE_DIR="/root/napv5/waf-compiler"
OUTPUT_DIR="/opt/napv5/app_protect_etc_config"

POLICY_INPUT_DIR="$BASE_DIR/policies"
POLICY_OUTPUT_DIR="$OUTPUT_DIR"

LOGGING_INPUT_DIR="$BASE_DIR/logging"
LOGGING_OUTPUT_DIR="$OUTPUT_DIR"

GLOBAL_SETTINGS="$BASE_DIR/global_settings.json"
DOCKER_IMAGE="waf-compiler-latest:custom"

# ========================
# VALIDATION
# ========================

echo "🔧 Validating paths..."

[[ -d "$POLICY_INPUT_DIR" ]] || { echo "❌ Error: Policy input directory '$POLICY_INPUT_DIR' does not exist."; exit 1; }
[[ -f "$GLOBAL_SETTINGS" ]] || { echo "❌ Error: Global settings file '$GLOBAL_SETTINGS' not found."; exit 1; }

mkdir -p "$POLICY_OUTPUT_DIR"
mkdir -p "$LOGGING_OUTPUT_DIR"

# ========================
# POLICY COMPILATION
# ========================

echo "📦 Compiling WAF policies from: $POLICY_INPUT_DIR"
for POLICY_FILE in "$POLICY_INPUT_DIR"/*.json; do
  [[ -f "$POLICY_FILE" ]] || continue

  BASENAME=$(basename "$POLICY_FILE" .json)
  OUTPUT_FILE="$POLICY_OUTPUT_DIR/${BASENAME}.tgz"

  echo "⚙️  [Policy] Compiling $(basename "$POLICY_FILE") -&amp;gt; $(basename "$OUTPUT_FILE")"

  docker run --rm \
    -v "$POLICY_INPUT_DIR":"$POLICY_INPUT_DIR" \
    -v "$POLICY_OUTPUT_DIR":"$POLICY_OUTPUT_DIR" \
    -v "$(dirname "$GLOBAL_SETTINGS")":"$(dirname "$GLOBAL_SETTINGS")" \
    "$DOCKER_IMAGE" \
    -g "$GLOBAL_SETTINGS" \
    -p "$POLICY_FILE" \
    -o "$OUTPUT_FILE"
done

# ========================
# LOGGING COMPILATION
# ========================

echo "📝 Compiling WAF logging configs from: $LOGGING_INPUT_DIR"
if [[ -d "$LOGGING_INPUT_DIR" ]]; then
  for LOG_FILE in "$LOGGING_INPUT_DIR"/*.json; do
    [[ -f "$LOG_FILE" ]] || continue

    BASENAME=$(basename "$LOG_FILE" .json)
    OUTPUT_FILE="$LOGGING_OUTPUT_DIR/${BASENAME}.tgz"

    echo "⚙️  [Logging] Compiling $(basename "$LOG_FILE") -&amp;gt; $(basename "$OUTPUT_FILE")"

    docker run --rm \
      -v "$LOGGING_INPUT_DIR":"$LOGGING_INPUT_DIR" \
      -v "$LOGGING_OUTPUT_DIR":"$LOGGING_OUTPUT_DIR" \
      "$DOCKER_IMAGE" \
      -l "$LOG_FILE" \
      -o "$OUTPUT_FILE"
  done
else
  echo "⚠️  Skipping logging config compilation: directory '$LOGGING_INPUT_DIR' does not exist."
fi

# ========================
# COMPLETION MESSAGE
# ========================

RUNTIME=$SECONDS
printf "\n✅ Compilation complete.\n"
echo "  - Policies output: $POLICY_OUTPUT_DIR"
echo "  - Logging output: $LOGGING_OUTPUT_DIR"
echo
printf "⏱️  Total time taken: %02d minutes %02d seconds\n" $((RUNTIME / 60)) $((RUNTIME % 60))
echo

&lt;/LI-CODE&gt;
&lt;P&gt;waf_policy_auto_compile.sh:&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;#!/bin/bash

###############################################################################
# waf_policy_auto_compile.sh
#
# - Only prints colorized summary output to terminal if -v/--verbose is used
# - Mails a styled HTML report using a template, substituting version numbers/status/colors
# - Debug output (step_log) only to syslog if -d/--debug is used
# - Otherwise: completely silent except for errors
# - All main blocks are modularized in functions
###############################################################################

set -euo pipefail
IFS=$'\n\t'

# ===== CONFIGURABLE VARIABLES =====

WORKROOT="/root/napv5"
WORKDIR="$WORKROOT/waf-compiler"
DOCKERFILE="$WORKDIR/Dockerfile"
BUNDLE_DIR="$WORKDIR/test"
NEW_BUNDLE="$BUNDLE_DIR/test_new.tgz"
OLD_BUNDLE="$BUNDLE_DIR/test_old.tgz"
NEW_META="$BUNDLE_DIR/test_new_meta.json"
COMPILER_IMAGE="waf-compiler-latest:custom"
EMAIL_RECIPIENT="example@example.com"
EMAIL_SUBJECT="WAF Compiler Update Notification"
NGINX_RELOAD_CMD="docker exec nginx-plus nginx -s reload"
HTML_TEMPLATE="$WORKDIR/version_report_template.html"
HTML_REPORT="$WORKDIR/version_report.html"
VERBOSE=0
DEBUG=0

# ===== DEBUG AND ERROR LOGGING =====

exec 2&amp;gt; &amp;gt;(tee -a /tmp/waf_policy_auto_compile_error.log | /usr/bin/logger -t waf_policy_auto_compile_error)

step_log() {
  if [ "$DEBUG" -eq 1 ]; then
    echo "DEBUG: $1" | /usr/bin/logger -t waf_policy_auto_compile
  fi
}

# ===== ARGUMENT PARSING =====

while [[ $# -gt 0 ]]; do
  case "$1" in
    -v|--verbose)
      VERBOSE=1
      shift
      ;;
    -d|--debug)
      DEBUG=1
      echo "Debug log can be found in the syslog..."
      shift
      ;;
    -*)
      echo "Unknown option: $1" &amp;gt;&amp;amp;2
      exit 1
      ;;
    *)
      shift
      ;;
  esac
done

# ----- LOG INITIAL ENVIRONMENT IF DEBUG -----
step_log "waf_policy_auto_compile starting (PID $$)"
step_log "Script PATH: $PATH"
step_log "which docker: $(which docker 2&amp;gt;/dev/null)"
step_log "which jq: $(which jq 2&amp;gt;/dev/null)"

# ===== COLOR DEFINITIONS =====

color_reset="\033[0m"
color_green="\033[1;32m"
color_yellow="\033[1;33m"

# ===== LOGGING FUNCTIONS =====

log() {
  # Only log to terminal if VERBOSE is enabled
  if [ "$VERBOSE" -eq 1 ]; then
    echo "[$(date --iso-8601=seconds)] $*"
  fi
}

# ===== HTML REPORT GENERATOR =====

generate_html_report() {
  local attack_old="$1"
  local attack_new="$2"
  local attack_status="$3"
  local attack_class="$4"
  local bot_old="$5"
  local bot_new="$6"
  local bot_status="$7"
  local bot_class="$8"
  local threat_old="$9"
  local threat_new="${10}"
  local threat_status="${11}"
  local threat_class="${12}"
  local datetime
  datetime=$(date --iso-8601=seconds)
  cp "$HTML_TEMPLATE" "$HTML_REPORT"

  sed -i "s|{{ATTACK_OLD}}|$attack_old|g" "$HTML_REPORT"
  sed -i "s|{{ATTACK_NEW}}|$attack_new|g" "$HTML_REPORT"
  sed -i "s|{{ATTACK_STATUS}}|$attack_status|g" "$HTML_REPORT"
  sed -i "s|{{ATTACK_CLASS}}|$attack_class|g" "$HTML_REPORT"
  sed -i "s|{{BOT_OLD}}|$bot_old|g" "$HTML_REPORT"
  sed -i "s|{{BOT_NEW}}|$bot_new|g" "$HTML_REPORT"
  sed -i "s|{{BOT_STATUS}}|$bot_status|g" "$HTML_REPORT"
  sed -i "s|{{BOT_CLASS}}|$bot_class|g" "$HTML_REPORT"
  sed -i "s|{{THREAT_OLD}}|$threat_old|g" "$HTML_REPORT"
  sed -i "s|{{THREAT_NEW}}|$threat_new|g" "$HTML_REPORT"
  sed -i "s|{{THREAT_STATUS}}|$threat_status|g" "$HTML_REPORT"
  sed -i "s|{{THREAT_CLASS}}|$threat_class|g" "$HTML_REPORT"
  sed -i "s|{{RUN_DATETIME}}|$datetime|g" "$HTML_REPORT"
  
}

# ===== BUILD COMPILER IMAGE =====

build_compiler() {
  step_log "about to build_compiler"
  docker build --no-cache --platform linux/amd64 \
    --secret id=nginx-crt,src="$WORKROOT/nginx-repo.crt" \
    --secret id=nginx-key,src="$WORKROOT/nginx-repo.key" \
    -t "$COMPILER_IMAGE" \
    -f "$DOCKERFILE" "$WORKDIR" &amp;gt; "$WORKDIR/waf_compiler_build.log" 2&amp;gt;&amp;amp;1 || {
      echo "ERROR: docker build failed. Dumping build log:" | /usr/bin/logger -t waf_policy_auto_compile_error
      cat "$WORKDIR/waf_compiler_build.log" | /usr/bin/logger -t waf_policy_auto_compile_error
      exit 1
  }
  step_log "after build_compiler"
}

# ===== COMPILE TEST POLICY =====

compile_test_policy() {
  step_log "about to compile_test_policy"
  docker run --rm -v "$BUNDLE_DIR:/bundle" "$COMPILER_IMAGE" \
    -p /bundle/test.json -o /bundle/test_new.tgz &amp;gt; "$NEW_META"
  step_log "after compile_test_policy"
  if [ -f "$NEW_META" ]; then
    step_log "$(cat "$NEW_META")"
  else
    step_log "NEW_META does not exist"
  fi
}

# ===== CHECK OLD_BUNDLE =====

check_old_bundle() {
  step_log "about to check OLD_BUNDLE"
  if [ -f "$OLD_BUNDLE" ]; then
    step_log "$(ls -l "$OLD_BUNDLE")"
  else
    step_log "OLD_BUNDLE does not exist"
  fi
}

# ===== GET NEW VERSIONS FUNCTION =====

get_new_versions() {
  jq -r '
    {
      "attack": .attack_signatures_package.version,
      "bot": .bot_signatures_package.version,
      "threat": .threat_campaigns_package.version
    }' "$NEW_META"
}

# ===== VERSION EXTRACTION FROM OLD BUNDLE =====

extract_bundle_versions() {
  docker run --rm -v "$BUNDLE_DIR:/bundle" "$COMPILER_IMAGE" \
    -dump -bundle "/bundle/test_old.tgz"
}

extract_versions_from_dump() {
  extract_bundle_versions | awk '
    BEGIN { print "{" }
    /attack-signatures:/     { in_attack=1; next }
    /bot-signatures:/        { in_bot=1; next }
    /threat-campaigns:/      { in_threat=1; next }
    in_attack &amp;amp;&amp;amp; /version:/  {
      gsub("version: ", "")
      printf "\"attack\":\"%s\",\n", $1
      in_attack=0
    }
    in_bot &amp;amp;&amp;amp; /version:/     {
      gsub("version: ", "")
      printf "\"bot\":\"%s\",\n", $1
      in_bot=0
    }
    in_threat &amp;amp;&amp;amp; /version:/  {
      gsub("version: ", "")
      printf "\"threat\":\"%s\"\n", $1
      in_threat=0
    }
    END { print "}" }
  '
}

get_old_versions() {
  extract_versions_from_dump
}

# ===== GET &amp;amp; PRINT VERSIONS =====

get_versions() {
  step_log "about to get_new_versions"
  new_versions=$(get_new_versions)
  step_log "new_versions: $new_versions"
  step_log "after get_new_versions"

  step_log "about to get_old_versions"
  old_versions=$(get_old_versions)
  step_log "old_versions: $old_versions"
  step_log "after get_old_versions"
}

# ===== VERSION COMPARISON =====

compare_versions() {
  step_log "compare_versions start"
  attack_old=$(echo "$old_versions" | jq -r .attack)
  attack_new=$(echo "$new_versions" | jq -r .attack)
  bot_old=$(echo "$old_versions" | jq -r .bot)
  bot_new=$(echo "$new_versions" | jq -r .bot)
  threat_old=$(echo "$old_versions" | jq -r .threat)
  threat_new=$(echo "$new_versions" | jq -r .threat)

  attack_status=$([[ "$attack_old" != "$attack_new" ]] &amp;amp;&amp;amp; echo "Updated" || echo "No Change")
  bot_status=$([[ "$bot_old" != "$bot_new" ]] &amp;amp;&amp;amp; echo "Updated" || echo "No Change")
  threat_status=$([[ "$threat_old" != "$threat_new" ]] &amp;amp;&amp;amp; echo "Updated" || echo "No Change")

  attack_class=$([[ "$attack_status" == "Updated" ]] &amp;amp;&amp;amp; echo "warn" || echo "ok")
  bot_class=$([[ "$bot_status" == "Updated" ]] &amp;amp;&amp;amp; echo "warn" || echo "ok")
  threat_class=$([[ "$threat_status" == "Updated" ]] &amp;amp;&amp;amp; echo "warn" || echo "ok")

  echo "Attack:$attack_status Bot:$bot_status Threat:$threat_status" &amp;gt; "$WORKDIR/status_flags.txt"

  [[ "$attack_status" == "Updated" ]] &amp;amp;&amp;amp; attack_status_colored="${color_yellow}*** Updated ***${color_reset}" || attack_status_colored="${color_green}No Change${color_reset}"
  [[ "$bot_status" == "Updated" ]] &amp;amp;&amp;amp; bot_status_colored="${color_yellow}*** Updated ***${color_reset}" || bot_status_colored="${color_green}No Change${color_reset}"
  [[ "$threat_status" == "Updated" ]] &amp;amp;&amp;amp; threat_status_colored="${color_yellow}*** Updated ***${color_reset}" || threat_status_colored="${color_green}No Change${color_reset}"

  {
    echo -e "Version comparison for container \033[1mNAPv5\033[0m:\n"
    echo -e "Attack Signatures:"
    echo -e "  Current Version: $attack_old"
    echo -e "  New Version: $attack_new"
    echo -e "  Status: $attack_status_colored\n"
    echo -e "Threat Campaigns:"
    echo -e "  Current Version: $threat_old"
    echo -e "  New Version: $threat_new"
    echo -e "  Status: $threat_status_colored\n"
    echo -e "Bot Signatures:"
    echo -e "  Current Version: $bot_old"
    echo -e "  New Version: $bot_new"
    echo -e "  Status: $bot_status_colored"
  } &amp;gt; "$WORKDIR/version_report.ansi"

  sed 's/\x1B\[[0-9;]*[mK]//g' "$WORKDIR/version_report.ansi" &amp;gt; "$WORKDIR/version_report.txt"

  step_log "Calling log_versions_syslog"
  log_versions_syslog "$attack_old" "$attack_new" "$attack_status" "$attack_class" \
    "$bot_old" "$bot_new" "$bot_status" "$bot_class" \
    "$threat_old" "$threat_new" "$threat_status" "$threat_class"
  step_log "compare_versions finished"
}

# ===== SYSLOG VERSION LOGGING and HTML REPORT GEN =====

log_versions_syslog() {
  # Args:
  #   1-attack_old 2-attack_new 3-attack_status 4-attack_class
  #   5-bot_old    6-bot_new    7-bot_status    8-bot_class
  #   9-threat_old 10-threat_new 11-threat_status 12-threat_class

  local msg
  msg="AttackSig (current: $1, latest: $2), BotSig (current: $5, latest: $6), ThreatCamp (current: $9, latest: $10)"
  /usr/bin/logger -t waf_policy_auto_compile "$msg"
  # Also print to terminal if VERBOSE is enabled
  if [ "$VERBOSE" -eq 1 ]; then
    echo "$msg"
  fi

  # Always (re)generate HTML for the mail at this point
  generate_html_report "$@"
}

# ===== RESPONSE ACTIONS =====

compile_all_policies() {
  log "Change detected – compiling all policies..."
  if [ "$VERBOSE" -eq 1 ]; then
    "$WORKDIR/compile_waf_policies.sh"
  else
    "$WORKDIR/compile_waf_policies.sh" &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
  fi
}

reload_nginx() {
  log "Reloading NGINX..."
  eval "$NGINX_RELOAD_CMD"
}

rotate_bundles() {
  log "Archiving new test bundle as old..."
  mv "$NEW_BUNDLE" "$OLD_BUNDLE"
  rm -f "$NEW_META"
}

send_report_email() {
  local html_report="$1"
  mail -s "$EMAIL_SUBJECT" -a "Content-Type: text/html" "$EMAIL_RECIPIENT" &amp;lt; "$html_report"
}

# ===== MAIN LOGIC =====

main() {
  build_compiler
  compile_test_policy
  check_old_bundle
  get_versions
  compare_versions

  if [[ "$VERBOSE" -eq 1 ]]; then
    cat "$WORKDIR/version_report.ansi"
  fi

  if grep -q "Updated" "$WORKDIR/status_flags.txt"; then
    if [[ "$VERBOSE" -eq 1 ]]; then
      echo "Detected updates. Recompiling policies, reloading NGINX, sending report."
    fi
    compile_all_policies
    reload_nginx
    rotate_bundles
    send_report_email "$HTML_REPORT"
  else
    log "No changes detected – nothing to do."
  fi

  log "Done."
}

main "$@"

&lt;/LI-CODE&gt;
&lt;P&gt;And should be it.&lt;/P&gt;</description>
      <pubDate>Thu, 12 Jun 2025 09:17:46 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/nginx-app-protect-v5-signature-notifications/ta-p/341937</guid>
      <dc:creator>lnxgeek</dc:creator>
      <dc:date>2025-06-12T09:17:46Z</dc:date>
    </item>
    <item>
      <title>SSH Brute Force Protection with SSH Proxy</title>
      <link>https://community.f5.com/t5/codeshare/ssh-brute-force-protection-with-ssh-proxy/ta-p/341936</link>
      <description>&lt;P&gt;This script is designed to add brute force protection for SSH services on a virtual server. It makes use of the Protocol Security feature called SSH Proxy which makes it possible to look into the ssh session and see the actual logon results.&lt;/P&gt;
&lt;P&gt;Documentation for setting up SSH Proxy can be found here: &lt;A class="lia-external-url" href="https://techdocs.f5.com/en-us/bigip-14-1-0/big-ip-network-firewall-policies-and-implementations-14-1-0/ssh-proxy-security.html" target="_blank"&gt;https://techdocs.f5.com/en-us/bigip-14-1-0/big-ip-network-firewall-policies-and-implementations-14-1-0/ssh-proxy-security.html&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;The script basically looks in the ltm log for these lines:&lt;/P&gt;
&lt;P&gt;Jun &amp;nbsp;8 11:40:57 f5-01 info tmm[12656]: 23003164 "Jun 08 2025 11:40:57","ssh_serverside_auth_fail","10.1.0.2","10.10.9.64","49862","22","4092","TCP","root","Password authentication failure"&lt;/P&gt;
&lt;P&gt;and counts the number of failed attempts for each IP.&lt;/P&gt;
&lt;P&gt;When a threshold has been reached that IP goes into a AFM address-list. This address-list needs to be part of a rule and policy, and attached to the SSH VS. This way be can block access for that source. The name of the address-list is specified in a variable in the script, so you can adjust it easily for your needs.&lt;/P&gt;
&lt;P&gt;You should use iCall to run the script periodically:&lt;/P&gt;
&lt;LI-CODE lang=""&gt;sys icall script ssh_bf {
    app-service none
    definition {
        exec /shared/scripts/ban_failed_ssh.sh &amp;gt; /var/log/ssh_log.log
    }
    description none
    events none
}

sys icall handler periodic ssh_bf {
    interval 300
    script ssh_bf
}&lt;/LI-CODE&gt;
&lt;P&gt;Make sure that the location of the script matches the exec statement in the iCall script, and that the script is executable. If you are having an HA, you also need to make sure you have it on both units.&lt;/P&gt;
&lt;P&gt;You can consume the above config by running this command:&lt;/P&gt;
&lt;LI-CODE lang=""&gt;tmsh load sys config merge from-terminal&lt;/LI-CODE&gt;
&lt;P&gt;and simply paste it in and on an empty line hit CTRL-d.&lt;/P&gt;
&lt;P&gt;Next you need to have a logging profile configured with these settings:&lt;/P&gt;
&lt;img /&gt;
&lt;P&gt;It is important you have that particular publisher set as it is sending the failures to the ltm log. You should be aware of the limitations of logging locally before you make use of this solution. If it is a very busy unit the disk might not be happy with some extra writing. In a perfect world you could log these failures to a remote logging server, but then you need to have the counting logic running elsewhere and have the script use the API to ingest the IPs. Or make use of a IP Intelligence feed which can load the IPs from a webserver. This is however out of scope for this solution, but I might make a follow up article on this if time allows it.&lt;/P&gt;
&lt;P&gt;With the AFM policy on the SSH VS and the logging profile attached, you now only need to save this script to your favorite script location on the BigIP:&lt;/P&gt;
&lt;LI-CODE lang="bash"&gt;#!/bin/bash
#
# ban_failed_ssh.sh - Detect and ban IPs with repeated SSH login failures from /var/log/ltm
#
# This script uses AFM address lists and maintains a state file with banned IPs and expiry timestamps.
# Author: Thomas Domingo Dahlmann
#

# --- Configuration ---
LOG_FILE="/var/log/ltm"
STATE_FILE="/var/tmp/ssh_ban_state.csv"
AFM_ADDR_LIST="/Common/ssh_banned_ips"
DUMMY_IP="192.0.2.254"
BAN_THRESHOLD=3
BAN_WINDOW_SECONDS=300   # 5 minutes
BAN_DURATION_SECONDS=1800 # 1 hour
TMP_LOG="/var/tmp/ssh_ban_tmp.log"
DEBUG=1  # Set to 1 for verbose debug logging, 0 to disable

# --- Logging helper ---
log_debug() {
    [[ "$DEBUG" -eq 1 ]] &amp;amp;&amp;amp; echo "[DEBUG] $(date '+%F %T') - $*" &amp;gt;&amp;amp;2
}

# --- Ensure state file and dummy IP exists ---
ensure_state_file() {
    touch "$STATE_FILE"
    if ! grep -q "^$DUMMY_IP," "$STATE_FILE"; then
        echo "$DUMMY_IP,9999999999" &amp;gt;&amp;gt; "$STATE_FILE"
        log_debug "Adding dummy IP $DUMMY_IP to state file and AFM list"
        tmsh modify security firewall address-list "$AFM_ADDR_LIST" { addresses add { "$DUMMY_IP" } } 2&amp;gt;/dev/null
    else
        log_debug "Dummy IP $DUMMY_IP already present in state"
    fi
}

# --- Remove expired IPs from state and AFM ---
cleanup_expired_bans() {
    local now
    now=$(date +%s)
    local new_state=()

    log_debug "Cleaning up expired bans at epoch time $now"
    while IFS=',' read -r ip expiry; do
        [[ -z "$ip" || -z "$expiry" ]] &amp;amp;&amp;amp; continue

        if [[ "$ip" == "$DUMMY_IP" ]]; then
            new_state+=("$ip,9999999999")
            continue
        fi

        if (( expiry &amp;gt; now )); then
            log_debug "Keeping active ban for $ip (expires at $expiry)"
            new_state+=("$ip,$expiry")
        else
            log_debug "Unbanning expired IP $ip (expired at $expiry)"
            tmsh modify security firewall address-list "$AFM_ADDR_LIST" { addresses delete { "$ip" } } 2&amp;gt;/dev/null
        fi
    done &amp;lt; "$STATE_FILE"

    printf "%s\n" "${new_state[@]}" &amp;gt; "$STATE_FILE"
    log_debug "State file rewritten with ${#new_state[@]} active entries"
}

# --- Add or update ban for IP ---
ban_ip() {
    local ip=$1
    local expiry=$(( $(date +%s) + BAN_DURATION_SECONDS ))

    log_debug "Initiating ban for IP $ip until $(date -d "@$expiry") [$expiry]"

    # Remove old entry
    grep -v "^$ip," "$STATE_FILE" &amp;gt; "${STATE_FILE}.tmp" &amp;amp;&amp;amp; mv "${STATE_FILE}.tmp" "$STATE_FILE"
    log_debug "Removed previous entry for $ip (if any)"

    # Add new ban
    echo "$ip,$expiry" &amp;gt;&amp;gt; "$STATE_FILE"
    log_debug "Added $ip,$expiry to $STATE_FILE"

    # Add to AFM list
    tmsh modify security firewall address-list "$AFM_ADDR_LIST" { addresses add { "$ip" } } 2&amp;gt;/dev/null
    log_debug "Added IP $ip to AFM address list $AFM_ADDR_LIST"
}

# --- Check recent log entries for violations ---
check_for_new_violations() {
    local now start_epoch
    now=$(date +%s)
    start_epoch=$(( now - BAN_WINDOW_SECONDS ))

    log_debug "Checking for new violations between $start_epoch and $now"

    &amp;gt; "$TMP_LOG"
    awk -v threshold="$start_epoch" '
        match($0, /"([A-Z][a-z]{2} [ 0-9][0-9] [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2})","ssh_.*auth_fail","([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"/, m) {
            cmd = "date -d \"" m[1] "\" +%s"
            cmd | getline epoch
            close(cmd)
            if (epoch &amp;gt;= threshold) {
                print epoch, m[2]
            }
        }
    ' "$LOG_FILE" &amp;gt; "$TMP_LOG"

    log_debug "Parsed entries written to $TMP_LOG:"
    [[ "$DEBUG" -eq 1 ]] &amp;amp;&amp;amp; cat "$TMP_LOG" &amp;gt;&amp;amp;2

    awk '{count[$2]++} END { for (ip in count) if (count[ip] &amp;gt;= '"$BAN_THRESHOLD"') print ip }' "$TMP_LOG" | while read -r ip; do
        log_debug "Found IP with threshold violations: $ip"
        if grep -q "^$ip," "$STATE_FILE"; then
            log_debug "IP $ip already in state file, skipping ban"
        else
            ban_ip "$ip"
        fi
    done
}

# --- Main ---
log_debug "==== SSH Ban Monitor Started ===="
ensure_state_file
cleanup_expired_bans
check_for_new_violations
log_debug "==== SSH Ban Monitor Finished ===="
&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Happy hunting 😄&lt;/P&gt;</description>
      <pubDate>Thu, 12 Jun 2025 07:46:07 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/ssh-brute-force-protection-with-ssh-proxy/ta-p/341936</guid>
      <dc:creator>lnxgeek</dc:creator>
      <dc:date>2025-06-12T07:46:07Z</dc:date>
    </item>
    <item>
      <title>Automating F5 Licensing - without direct internet access</title>
      <link>https://community.f5.com/t5/codeshare/automating-f5-licensing-without-direct-internet-access/ta-p/341070</link>
      <description>&lt;P&gt;Hello DevCentral Community!&lt;/P&gt;&lt;P&gt;I'm excited to share a project I've been working on recently: **Automating F5 BIG-IP VE Licensing** without needing direct internet access!&lt;/P&gt;&lt;P&gt;The project covers:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;Retrieving a Dossier automatically via iControl REST API.&lt;/LI&gt;&lt;LI&gt;Interacting with F5 licensing servers through proxies or offline.&lt;/LI&gt;&lt;LI&gt;Re-activating licenses post-upgrade using custom scripts.&lt;/LI&gt;&lt;LI&gt;Full Python 3 support (moving away from BigSuds/Python 2 limitations).&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;✅ The idea is to help users who need to automate the licensing process, especially for secure or offline environments.&lt;/P&gt;&lt;P&gt;I'll be sharing:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;Scripts&lt;/LI&gt;&lt;LI&gt;Use cases&lt;/LI&gt;&lt;LI&gt;Lessons learned&lt;/LI&gt;&lt;LI&gt;Tips for real-world deployments&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;If you're interested in automating your BIG-IP licensing process, feel free to follow along! Feedback, ideas, or collaboration is most welcome! 🚀&lt;/P&gt;&lt;LI-CODE lang="json"&gt;import requests
import json
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class BigIPLicenseManager:
    def __init__(self, host, username, password, registration_key):
        self.host = host
        self.username = username
        self.password = password
        self.registration_key = registration_key
        self.base_url = f"https://{self.host}/mgmt/tm/sys/license"
        self.headers = {'Content-Type': 'application/json'}

    def get_dossier(self):
        payload = {
            "command": "install",
            "registrationKey": self.registration_key
        }
        response = requests.post(
            self.base_url,
            auth=(self.username, self.password),
            headers=self.headers,
            json=payload,
            verify=False
        )
        if response.status_code == 200:
            data = response.json()
            dossier = data.get('dossier')
            if dossier:
                print("[+] Dossier retrieved successfully.")
                return dossier
            else:
                print("[-] No dossier found in response.")
                return None
        else:
            print(f"[-] Failed to retrieve dossier: {response.text}")
            return None

    def install_license(self, license_text):
        payload = {
            "command": "install",
            "licenseText": license_text
        }
        response = requests.post(
            self.base_url,
            auth=(self.username, self.password),
            headers=self.headers,
            json=payload,
            verify=False
        )
        if response.status_code == 200:
            print("[+] License installed successfully.")
        else:
            print(f"[-] Failed to install license: {response.text}")

if __name__ == "__main__":
    # Define your BIG-IP credentials and registration key here
    bigip_host = "192.168.1.245"
    bigip_username = "admin"
    bigip_password = "admin"
    registration_key = "AAAAA-BBBBB-CCCCC-DDDDD-EEEEE"

    manager = BigIPLicenseManager(
        bigip_host,
        bigip_username,
        bigip_password,
        registration_key
    )

    dossier = manager.get_dossier()
    if dossier:
        # Print the dossier to manually activate it via activate.f5.com
        print("\n[!] Submit the following dossier to F5 activation server:")
        print(dossier)

        # After getting the license text (offline or from a licensing server)
        license_text = input("\nPaste the license text here:\n")

        manager.install_license(license_text.strip())&lt;/LI-CODE&gt;</description>
      <pubDate>Wed, 30 Apr 2025 18:28:07 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/automating-f5-licensing-without-direct-internet-access/ta-p/341070</guid>
      <dc:creator>ashrafelsayelshabany</dc:creator>
      <dc:date>2025-04-30T18:28:07Z</dc:date>
    </item>
    <item>
      <title>F5 MCP(Model Context Protocol) Server</title>
      <link>https://community.f5.com/t5/codeshare/f5-mcp-model-context-protocol-server/ta-p/340704</link>
      <description>&lt;P&gt;This project is a&amp;nbsp;&lt;STRONG&gt;MCP( Model Context Protocol ) server&lt;/STRONG&gt;&amp;nbsp;designed to interact with F5 devices using the&amp;nbsp;&lt;STRONG&gt;iControl REST API&lt;/STRONG&gt;. It provides a set of tools to manage F5 objects such as virtual servers (VIPs), pools, iRules, and profiles. The server is implemented using the FastMCP framework and exposes functionalities for creating, updating, listing, and deleting F5 objects.&lt;/P&gt;</description>
      <pubDate>Thu, 17 Apr 2025 11:47:34 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/f5-mcp-model-context-protocol-server/ta-p/340704</guid>
      <dc:creator>mihaic</dc:creator>
      <dc:date>2025-04-17T11:47:34Z</dc:date>
    </item>
    <item>
      <title>Trigger js challenge/Captcha for ip reputation/ip intelligence categories</title>
      <link>https://community.f5.com/t5/codeshare/trigger-js-challenge-captcha-for-ip-reputation-ip-intelligence/ta-p/339724</link>
      <description>&lt;img /&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3&gt;Problem solved by this Code Snippet&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;Because some ISP or cloud providers do not monitor their users a lot of times client ip addresses are marked as "spam sources"&amp;nbsp; or "windows exploits" and as the ip addresses are dynamic and after time a legitimate user can use this ip addresses the categories are often stopped in the IP intelligence profile or under the ASM/AWAF policy. This usually happens in Public Clouds that do not monitor what their users do and the IP gets marked as bad then another good user after a day or two has this ip address and this causes the issue.&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;For many of my clients I had to stop the ip reputation/ip intelligence category "spam sources" and in some cases "windows exploits" so having a javascript/captcha checks seems a nice compromise 😎&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;To still make use of this categories the users coming from those ip addresses can be forced to solve captcha checks or at least to be checked for javascript support!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3&gt;How to use this Code Snippet&lt;/H3&gt;
&lt;UL&gt;
&lt;LI&gt;Have AWAF/ASM and ip intelligence licensed&lt;/LI&gt;
&lt;LI&gt;Add AWAF/ASM policy with irule support option (by default not enabled under the policy) or/and Bot profile under the Virtual server&amp;nbsp;&lt;/LI&gt;
&lt;LI&gt;Optionally add IP intelligence profile or enable the Ip intelligence under the WAF policy without the categories that cause a lot of false positives,&lt;/LI&gt;
&lt;LI&gt;Add the irule and if needed modify the categories for which it triggers&lt;/LI&gt;
&lt;LI&gt;Do not forget to first create the data group, used in the code or delete that part of the code and to uncomment the Bot part of the code, if you plan to do js check and not captcha and maybe comment the captcha part !&lt;/LI&gt;
&lt;/UL&gt;
&lt;H3&gt;Code Snippet Meta Information&lt;/H3&gt;
&lt;OL&gt;
&lt;LI&gt;Version: 17.1.3&lt;/LI&gt;
&lt;LI&gt;Coding Language: TCL&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2&gt;Code&lt;/H2&gt;
&lt;P&gt;You can find the code and further documentation in my GitHub repository:&lt;/P&gt;
&lt;P&gt;&lt;A href="https://github.com/Nikoolayy1/reputation-javascript-captcha-challlenge/tree/main" target="_blank" rel="noopener"&gt;reputation-javascript-captcha-challlenge/ at main · Nikoolayy1/reputation-javascript-captcha-challlenge&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-align-left"&gt;when HTTP_REQUEST {&lt;/P&gt;
&lt;P class="lia-align-left"&gt;# Take the ip address for ip reputation/intelligence check from the XFF header if it comes from the whitelisted source ip addresses in data group "client_ip_class"&lt;/P&gt;
&lt;P class="lia-align-left"&gt;if { [HTTP::header exists "X-Forwarded-For"] &amp;amp;&amp;amp; [class match [IP::client_addr] equals "/Common/client_ip_class"] } {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; set trueIP [HTTP::header "X-Forwarded-For"]&lt;BR /&gt;} else {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; set trueIP [IP::client_addr]&lt;BR /&gt;}&lt;/P&gt;
&lt;P class="lia-align-left"&gt;# Check if IP reputation is triggered and it is containing "Spam Sources"&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&amp;nbsp; if { ([llength [IP::reputation $trueIP]] != 0) &amp;amp;&amp;amp; ([IP::reputation $trueIP] contains "Spam Sources") }{&amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;log local0. "The category is [IP::reputation $trueIP] from [IP::client_addr]"&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&lt;BR /&gt;# Set the variable 1 or bulean true as to trigger ASM captcha or bot defense javascript&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;set js_ch 1&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;} else {&lt;BR /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; set js_ch 0&lt;BR /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;}&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&lt;BR /&gt;# &amp;nbsp;Custom response page just for testing if there is no real backend origin server for testing&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&amp;nbsp; &amp;nbsp; if {!$js_ch} {&lt;BR /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;HTTP::respond 200 content {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;html&amp;gt;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;lt;head&amp;gt;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;title&amp;gt;Apology Page&amp;lt;/title&amp;gt;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;lt;/head&amp;gt;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;lt;body&amp;gt;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; We are sorry, but the site you are looking for is temporarily out of service&amp;lt;br&amp;gt;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; If you feel you have reached this page in error, please try again.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;lt;/body&amp;gt;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/html&amp;gt;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;}&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; }&lt;/P&gt;
&lt;P class="lia-align-left"&gt;}&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&lt;BR /&gt;# &amp;nbsp;when BOTDEFENSE_ACTION {&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&amp;nbsp; &amp;nbsp; # Trigger bot defense action javascript check for Spam Sources&lt;BR /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;&amp;nbsp; # &amp;nbsp; &amp;nbsp;if {$js_ch &amp;amp;&amp;amp; (not ([BOTDEFENSE::reason] starts_with "passed browser challenge")) &amp;amp;&amp;amp; ([BOTDEFENSE::action] eq "allow") }{&lt;BR /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-align-left"&gt;# &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;BOTDEFENSE::action browser_challenge&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;&amp;nbsp; # &amp;nbsp; &amp;nbsp;}&amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;# &amp;nbsp;}&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-align-left"&gt;when ASM_REQUEST_DONE {&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-align-left"&gt;# Trigger ASM captcha check only for users comming from Spam sources that have not already passed the captcha check (don't have the captcha cookie)&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&amp;nbsp; &amp;nbsp; if {$js_ch &amp;amp;&amp;amp; [ASM::captcha_status] ne "correct"} {&lt;BR /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;set res [ASM::captcha]&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;if {$res ne "ok"} {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; log local0. "Cannot send captcha_challenge: \"$res\""&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;}&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&amp;nbsp; &amp;nbsp; }&lt;BR /&gt;}&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&lt;STRONG&gt;Extra References:&amp;nbsp;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&lt;STRONG&gt;&lt;A href="https://clouddocs.f5.com/api/irules/BOTDEFENSE__action.html" target="_blank" rel="noopener"&gt;BOTDEFENSE::action&lt;/A&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&lt;STRONG&gt;&lt;A href="https://clouddocs.f5.com/api/irules/ASM__captcha.html" target="_blank" rel="noopener"&gt;ASM::captcha&lt;/A&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&lt;STRONG&gt;&lt;A href="https://clouddocs.f5.com/api/irules/ASM__captcha_status.html" target="_blank" rel="noopener"&gt;ASM::captcha_status&lt;/A&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class="lia-align-left"&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Thu, 14 Aug 2025 21:22:39 GMT</pubDate>
      <guid>https://community.f5.com/t5/codeshare/trigger-js-challenge-captcha-for-ip-reputation-ip-intelligence/ta-p/339724</guid>
      <dc:creator>Nikoolayy1</dc:creator>
      <dc:date>2025-08-14T21:22:39Z</dc:date>
    </item>
  </channel>
</rss>

