├── README.md ├── internal-layered-architecture ├── README.md ├── explicit-proxy │ ├── SSLOLIBEXP │ ├── sslo-layering-exp-rule │ └── use-case-encrypted-traffic-through-http-service │ │ └── README.md ├── images │ ├── sslo-encrypted-traffic-to-proxy.png │ ├── sslo-internal-layered-architecture-ep-back.png │ ├── sslo-internal-layered-architecture-ep-front.png │ ├── sslo-internal-layered-architecture.png │ └── sslo-tp-to-ep-layered.png └── transparent-proxy │ ├── PIFSSLOLIB │ ├── SSLOLIB │ ├── pif-explicit-proxy-rule │ ├── pif-sslo-layering-rule │ ├── sslo-layering-rule │ ├── use-case-encrypted-traffic-through-http-service │ └── README.md │ └── use-case-transparent-to-explicit-egress │ ├── README.md │ ├── client-rule.tcl │ └── server-rule.tcl ├── misc-tools └── url-to-dg-convert.sh ├── saas-tenant-isolation ├── Readme.md └── tenant-isolation-rule ├── sni-switching ├── README.md ├── archive │ ├── in-t-rule-datagroup.tcl │ ├── in-t-rule-static.tcl │ └── library-rule.tcl ├── in-t-rule-datagroup.tcl ├── in-t-rule-static.tcl └── library-rule.tcl ├── sslo-dns-over-https-detection ├── README.md ├── sslo-doh-blocking.tcl └── sslo-doh-logging.tcl ├── sslo-dns-sinkhole ├── README.md ├── create-sinkhole-internal-config.sh └── sslo-doh-logging.tcl ├── sslo-generative-ai-categories ├── Readme.md ├── ai-category-chat ├── ai-tools-urls └── sslo-create-ai-category.sh ├── sslo-nuke-delete ├── README.md └── sslo-nuke-delete.sh └── sslofix ├── README.md └── sslofix /README.md: -------------------------------------------------------------------------------- 1 | # SSL Orchestrator Script Tools 2 | 3 | This repository contains a set of script tools useful in SSL Orchestrator deployments. 4 | 5 | - **Internal Layered Architecture**: Contains an iRule implementation to create a layered SSL Orchestrator configuration, to reduce complexity by treating topologies as (semi)static functions. 6 | 7 | - **SNI Switching**: Contains a set of iRules useful in inbound topologies to dynamically select a client SSL profile based on incoming TLS Client Hello SNI value. 8 | 9 | - **SaaS Tenant Isolation**: Contains an iRule to enable tenant isolation (aka tenant restrictions) for multiple SaaS providers (Office365, Webex, Google, Dropbox, Youtube, and Slack). 10 | 11 | - **DNS over HTTPS (DoH and DoT) Detection**: Contains a set of tools for the detection and management of DoH and DoT traffic. 12 | 13 | - **Generative AI Categories**: Contains an installation script and a list of known generative AI tools. The installation script is used to install a custom URL category on the BIG-IP for use in SSL Orchestrator policy, to detect and manage generative AI traffic. 14 | 15 | - **Nuke Delete**: A small script useful in completely destroying a failed/corrupted SSL Orchestrator deployment. Note that this will erase all Guided Configuration objects, including any Access Guided Config settings. 16 | 17 | - **Misc Tools**: Miscellaneous script tools. 18 | 19 | - **SSLOFIX**: BIG-IP SSL Orchestrator control plane diagnostic, synchronization, and repair tool. 20 | 21 | -------------------------------------------------------------------------------- /internal-layered-architecture/README.md: -------------------------------------------------------------------------------- 1 | # F5 SSL Orchestrator Layered Architecture Configuration 2 | A set of iRules for creating a layered SSL Orchestrator, to reduce complexity by treating topologies as functions. 3 | 4 | ### Current version: 5 | 2.2 6 | 7 | ### Version support 8 | This utility works on BIG-IP 14.1 and above, SSL Orchestrator 5.x to 9.x. 9 | 10 | ### Description: 11 | The SSL Orchestrator Internal Layered Architecture pattern is designed to both simplify configuration and management, and expand the capabilities of an SSL Orchestrator deployment. Too often these configurations can get complex, where large security policies or multiple topologies are required to satisfy different diverse traffic pattern needs. Or, a configuration may require non-strict changes that later make management and upgrades challenging. Consider however, that across multiple security rules and multiple topologies, there are basically FOUR actions that are performed for any traffic pattern: 12 | 13 | - Allow / block 14 | - TLS intercept / bypass 15 | - Service chain assignment 16 | - Egress path 17 | 18 | The Internal Layered Architecture pattern simplifies the architecture by reducing a topology to a single "function" as a combination of these FOUR actions, and pushing policy steering to a frontend virtual server. 19 | 20 | ![SSL Orchestrator Internal Layered Architecture](images/sslo-internal-layered-architecture.png) 21 | 22 | This pattern has the following additional advantages: 23 | 24 | - The steering policy runs on a pure BIG-IP LTM virtual server, so is infinitely flexible and automatable. 25 | - Topology functions are simplified and require no or minimal customization. 26 | - Topology objects are re-usable (ex. SSL configurations, services), further reducing object counts. 27 | - Topology functions support an additional "dynamic egress" pattern, where different egress setting can be selected per topology. 28 | - Topology functions support more flexible bypass options. 29 | - Topology functions support more flexible automation and management. 30 | 31 | This repo is dedicated to a set of tooling to make this architecture pattern easier to implement and manage, including a library iRule that greatly simplifies traffic matching and steering. 32 | 33 | ### How to install 34 | While the Internal Layered Architecture can be used in any direction, it is most useful in a forward proxy scenario. The following specifically addresses layer 3 forward proxy use cases, but it is also possible to do in a layer 2 "vwire" configuration. Within the layer 3 forward proxy scenarios, there are three sub-options: 35 | 36 | - Transparent forward proxy 37 | - Explicit forward proxy (proxy in front) 38 | - Explicit forward proxy (proxy in back) 39 | 40 | -------------------------------------------------- 41 | 42 | #### Transparent Forward Proxy 43 | In a transparent forward proxy, the steering VIP and SSL Orchestrator topologies are all configured as transparent forward proxy, and the steering VIP simply forwards traffic based on a traffic match to a specific internal topology function. Under the **transparent-proxy** subfolder are two iRules. The **SSLOLIB** iRule is a library rule, and the **sslo-layering-rule** is the iRule that's applied to the steering VIP and calls the library iRule functions. 44 | 45 | - **Step 1**: Import this SSLOLIB iRule (name it "SSLOLIB") 46 | 47 | - **Step 2**: Build a set of "dummy" VLANs. A topology must be bound to a unique VLAN. But since the topologies in this architecture won't be listening on an actual client-facing VLAN, you will need to create a separate dummy VLAN for each topology you intend to create. A dummy VLAN is basically a VLAN with no interface assigned. In the BIG-IP UI, under Network -> VLANs, click Create. Give your VLAN a name and click Finished. It will ask you to confirm since you're not attaching an interface. Click OK to continue. Repeat this step by creating unique VLAN names for each topology you are planning to use. 48 | 49 | - **Step 3**: Build semi-static SSL Orchestrator topologies based on common actions (ex. allow, intercept, service chain, egress) 50 | - Minimally create a normal "intercept" topology and a separate "bypass" topology: 51 | 52 | Intercept topology: 53 | - L3 outbound topology configuration, normal topology settings, SSL config, services, service chain 54 | - No security policy rules - just a single ALL rule with TLS intercept action (and service chain) 55 | - Attach to a "dummy" VLAN 56 | 57 | Bypass topology: 58 | - L3 outbound topology configuration, skip SSL config, re-use services, service chains 59 | - No security policy rules - just a single ALL rule with TLS bypass action (and service chain) 60 | - Attached to a separate "dummy" VLAN 61 | 62 | - Create any additional topologies as required, as separate functions based on discrete actions (allow/block, intercept/bypass, service chain, egress) 63 | 64 | - **Step 4**: Import the traffic switching iRule 65 | - Set necessary static configuration values in RULE_INIT as required 66 | - Define any URL category lists in RULE_INIT as required (see example). Use the following command to get a list of URL categories: 67 | 68 | `tmsh list sys url-db url-category |grep "sys url-db url-category " |awk -F" " '{print $4}'` 69 | 70 | - **Step 5**: Create a client-facing topology switching VIP 71 | - Type: Standard 72 | - Source: 0.0.0.0/0 73 | - Destination: 0.0.0.0/0:0 74 | - Protocol: TCP 75 | - VLAN: client-facing VLAN 76 | - Address Translation: disabled 77 | - Port Translation: disabled 78 | - Default Persistence Profile: ssl 79 | - iRule: traffic switching iRule 80 | 81 | - **Step 6**: modify the traffic switching iRule with the required detection commands. See **Traffic Selector commands - Transparent Proxy** information below. 82 | 83 | -------------------------------------------------- 84 | 85 | #### Explicit Forward Proxy (proxy in front) - TLS SNI evaluation 86 | In this scenario, an explicit proxy configuration is built up front at the steering layer. A BIG-IP LTM explicit proxy consists of a DNS resolver, TCP tunnel, HTTP explicit profile, an HTTP explicit proxy virtual server, and a separate TCP tunnel virtual server. Traffic flows from the client to the HTTP explicit proxy VIP and is tunneled through the TCP tunnel VIP. **The TCP tunnel VIP parses the TLS SNI for SNI/HOST evaluation**. Therefore to configure the explicit proxy for the Internal Layered Architecture, simply apply the layering iRule to the TCP tunnel VIP, which will behave exactly the same way as the transparent proxy implementation. This will use the same iRules under the **transparent-proxy** subfolder. 87 | 88 | ![SSL Orchestrator Internal Layered Architecture](images/sslo-internal-layered-architecture-ep-front.png) 89 | 90 | *Note: to perform explicit proxy authentication, an SWG-Explicit access profile would be applied to the frontend explicit proxy configuration.* 91 | 92 | - **Step 1**: Import this SSLOLIB iRule (name "SSLOLIB") 93 | 94 | - **Step 3**: Import the "sslo-layering-rule" iRule 95 | 96 | - **Step 4**: Create the DNS Resolver for the HTTP explicit config. Under Network -> DNS Resolvers -> DNS Resolver List, click **Create** provide a unique name and click **Finished**. Now click to edit this new DNS resolver and navigate to the Forward Zones tab and click **Add**. 97 | - Name: enter "." (without quotation marks) 98 | - Address: enter the address of your preferred DNS resolver 99 | - Service Port: enter the listening port of the DNS resolver 100 | - Click **Add** and then **Finished**. 101 | 102 | - **Step 5**: Create the TCP Tunnel for the HTTP explicit config. Under Network -> Tunnels -> Tunnel List, click **Create**. 103 | - Name: provide a unique name 104 | - Profile: tcp-forward 105 | - Click **Finished** 106 | 107 | - **Step 6**: Create the HTTP explicit profile. Under Local Traffic -> Profiles -> Services -> HTTP, click **Create**. 108 | - Name: provide a unique name 109 | - Proxy Mode: select Explicit 110 | - Explicit Proxy : DNS Resolver: enable and select the previously-created DNS resolver 111 | - Explicit Proxy : Tunnel Name: enable and select the previously-create TCP tunnel 112 | - Explicit Proxy : Use Tunnel On Any Request Method: enabled (available in BIG-IP 16.0) 113 | - Explicit Proxy : Default Connect Handling: deny 114 | - Optionally add content to the Explicit Proxy failure messages 115 | - Click **Finished** 116 | 117 | - **Step 7**: Create the client-facing explicit proxy virtual server (Local Traffic :: Virtual Servers) 118 | - Type: Standard 119 | - Source: 0.0.0.0/0 120 | - Destination: client-facing explicit proxy IP address 121 | - Port: client-facing explicit proxy listening port 122 | - Protocol: TCP 123 | - HTTP Profile (Client): select the HTTP explicit profile 124 | - VLAN: client-facing VLAN 125 | - Address/Port Translation: enabled 126 | 127 | ** Note: If deploying on BIG-IP earlier than 16.0, the following must be added to the pif_explicit_proxy_rule **: 128 | 129 | ``` 130 | when HTTP_REQUEST { 131 | virtual "/Common/tcp-tunnel-vip" [HTTP::proxy addr] [HTTP::proxy port] 132 | } 133 | ``` 134 | 135 | Change "/Common/tcp-tunnel-vip" to match the name of the TCP tunnel virtual server. 136 | 137 | - **Step 8**: Create the tcp tunnel steering virtual server (Local Traffic :: Virtual Servers) 138 | - Type: Standard 139 | - Source: 0.0.0.0/0 140 | - Destination: 0.0.0.0/0 141 | - Port: 0 142 | - VLAN: select the tcp tunnel VLAN 143 | - Address/Port Translation: disabled 144 | - Default Persistence Profile: select "**ssl**" 145 | - iRule: select the sslo-layering-rule iRule 146 | 147 | - **Step 9**: Build semi-static SSL Orchestrator topologies based on common actions (ex. allow, intercept, service chain) 148 | - Minimally create a normal "intercept" topology and a separate "bypass" topology 149 | - Intercept topology: 150 | - L3 outbound topology configuration, normal topology settings, SSL config, services, service chain 151 | - No security policy rules - just a single ALL rule with TLS intercept action (and service chain) 152 | - Attach to a "dummy" VLAN 153 | - Bypass topology: 154 | - L3 outbound topology configuration, skip SSL config, re-use services, service chains 155 | - No security policy rules - just a single ALL rule with TLS bypass action (and service chain) 156 | - Attached to a separate "dummy" VLAN 157 | - Create any additional topologies as required, as separate functions based on discrete actions (allow/block, intercept/bypass, service chain) 158 | 159 | - **Step 10**: modify the traffic switching iRule with the required detection commands (below) 160 | - Set necessary static configuration values in RULE_INIT as required 161 | - Define any URL category lists in RULE_INIT as required (see example). Use the following command to get a list of URL categories: 162 | 163 | `tmsh list sys url-db url-category |grep "sys url-db url-category " |awk -F" " '{print $4}'` 164 | 165 | -------------------------------------------------- 166 | 167 | #### Explicit Forward Proxy (proxy in front) - Proxy request HOST evaluation 168 | In this scenario, an explicit proxy configuration is built up front at the steering layer. A BIG-IP LTM explicit proxy consists of a DNS resolver, TCP tunnel, HTTP explicit profile, an HTTP explicit proxy virtual server, and a separate TCP tunnel virtual server. Traffic flows from the client to the HTTP explicit proxy VIP and is tunneled through the TCP tunnel VIP. **The TCP tunnel VIP parses the proxy request for HOST evaluation**. This version has the advantage of not requiring binary TLS handshake parsing and natively works for HTTP and HTTPS requests. 169 | 170 | ![SSL Orchestrator Internal Layered Architecture](images/sslo-internal-layered-architecture-ep-front.png) 171 | 172 | *Note: to perform explicit proxy authentication, an SWG-Explicit access profile would be applied to the frontend explicit proxy configuration.* 173 | 174 | - **Step 1**: Import this SSLOLIB iRule (name "PIFSSLOLIB") 175 | 176 | - **Step 2**: Import the "pif-explicit-proxy-rule" iRule 177 | 178 | - **Step 3**: Import the "pif-sslo-layering-rule" iRule 179 | 180 | - **Step 4**: Create the DNS Resolver for the HTTP explicit config. Under Network -> DNS Resolvers -> DNS Resolver List, click **Create** provide a unique name and click **Finished**. Now click to edit this new DNS resolver and navigate to the Forward Zones tab and click **Add**. 181 | - Name: enter "." (without quotation marks) 182 | - Address: enter the address of your preferred DNS resolver 183 | - Service Port: enter the listening port of the DNS resolver 184 | - Click **Add** and then **Finished**. 185 | 186 | - **Step 5**: Create the TCP Tunnel for the HTTP explicit config. Under Network -> Tunnels -> Tunnel List, click **Create**. 187 | - Name: provide a unique name 188 | - Profile: tcp-forward 189 | - Click **Finished** 190 | 191 | - **Step 6**: Create the HTTP explicit profile. Under Local Traffic -> Profiles -> Services -> HTTP, click **Create**. 192 | - Name: provide a unique name 193 | - Proxy Mode: select Explicit 194 | - Explicit Proxy : DNS Resolver: enable and select the previously-created DNS resolver 195 | - Explicit Proxy : Tunnel Name: enable and select the previously-create TCP tunnel 196 | - Explicit Proxy : Use Tunnel On Any Request Method: enabled (available in BIG-IP 16.0) 197 | - Explicit Proxy : Default Connect Handling: deny 198 | - Optionally add content to the Explicit Proxy failure messages 199 | - Click **Finished** 200 | 201 | - **Step 7**: Create the client-facing explicit proxy virtual server (Local Traffic :: Virtual Servers) 202 | - Type: Standard 203 | - Source: 0.0.0.0/0 204 | - Destination: client-facing explicit proxy IP address 205 | - Port: client-facing explicit proxy listening port 206 | - Protocol: TCP 207 | - HTTP Profile (Client): select the HTTP explicit profile 208 | - VLAN: client-facing VLAN 209 | - Address/Port Translation: enabled 210 | - iRule: select the "pif-explicit-proxy-rule" 211 | 212 | ** Note: If deploying on BIG-IP earlier than 16.0, the following must be added to the pif_explicit_proxy_rule **: 213 | 214 | ``` 215 | when HTTP_REQUEST { 216 | virtual "/Common/tcp-tunnel-vip" [HTTP::proxy addr] [HTTP::proxy port] 217 | } 218 | ``` 219 | 220 | Change "/Common/tcp-tunnel-vip" to match the name of the TCP tunnel virtual server. 221 | 222 | - **Step 8**: Create the tcp tunnel steering virtual server (Local Traffic :: Virtual Servers) 223 | - Type: Standard 224 | - Source: 0.0.0.0/0 225 | - Destination: 0.0.0.0/0 226 | - Port: 0 227 | - VLAN: select the tcp tunnel VLAN 228 | - Address/Port Translation: disabled 229 | - iRule: select the pif-sslo-layering-rule iRule 230 | 231 | - **Step 9**: Build semi-static SSL Orchestrator topologies based on common actions (ex. allow, intercept, service chain) 232 | - Minimally create a normal "intercept" topology and a separate "bypass" topology 233 | - Intercept topology: 234 | - L3 outbound topology configuration, normal topology settings, SSL config, services, service chain 235 | - No security policy rules - just a single ALL rule with TLS intercept action (and service chain) 236 | - Attach to a "dummy" VLAN 237 | - Bypass topology: 238 | - L3 outbound topology configuration, skip SSL config, re-use services, service chains 239 | - No security policy rules - just a single ALL rule with TLS bypass action (and service chain) 240 | - Attached to a separate "dummy" VLAN 241 | - Create any additional topologies as required, as separate functions based on discrete actions (allow/block, intercept/bypass, service chain) 242 | 243 | - **Step 10**: modify the traffic switching iRule with the required detection commands (below) 244 | - Set necessary static configuration values in RULE_INIT as required 245 | - Define any URL category lists in RULE_INIT as required (see example). Use the following command to get a list of URL categories: 246 | 247 | `tmsh list sys url-db url-category |grep "sys url-db url-category " |awk -F" " '{print $4}'` 248 | 249 | -------------------------------------------------- 250 | 251 | #### Explicit Forward Proxy (proxy in back) 252 | In this scenario, an explicit proxy configuration is built at each topology instance, and the frontend steering layer is a simple forwarder. The steering policy is a bit different in this case as the frontend will be observing explicit proxy requests instead of TLS SNI traffic. This difference is captured under the **explicit-proxy** subfolder. The **SSLOLIBEXP** iRule is the library rule, and the **sslo-layering-exp-rule** is applied to the frontend forwarder. 253 | 254 | ![SSL Orchestrator Internal Layered Architecture](images/sslo-internal-layered-architecture-ep-back.png) 255 | 256 | *Note: to perform explicit proxy authentication, an SWG-Explicit access profile would be applied to each of the backend explicit proxy topologies. This pattern provides an additional advantage of adding authentication enabled/disabled actions based on steering policy.* 257 | 258 | - **Step 1**: Import this SSLOLIBEXP iRule (name it "SSLOLIBEXP") 259 | 260 | - **Step 2**: Build a set of "dummy" VLANs. A topology must be bound to a unique VLAN. But since the topologies in this architecture won't be listening on an actual client-facing VLAN, you will need to create a separate dummy VLAN for each topology you intend to create. A dummy VLAN is basically a VLAN with no interface assigned. In the BIG-IP UI, under Network -> VLANs, click Create. Give your VLAN a name and click Finished. It will ask you to confirm since you're not attaching an interface. Click OK to continue. Repeat this step by creating unique VLAN names for each topology you are planning to use. 261 | 262 | - **Step 3**: Build semi-static SSL Orchestrator explicit proxy topologies based on common actions (ex. allow, intercept, service chain, egress). 263 | - Minimally create a normal "intercept" topology and a separate "bypass" topology: 264 | 265 | Intercept topology: 266 | - L3 explicit proxy topology configuration, normal topology settings, SSL config, services, service chain 267 | - No security policy rules - just a single ALL rule with TLS intercept action (and service chain) 268 | - Attach to a "dummy" VLAN 269 | 270 | Bypass topology: 271 | - L3 explicit proxy topology configuration, skip SSL config, re-use services, service chains 272 | - No security policy rules - just a single ALL rule with TLS bypass action (and service chain) 273 | - Attached to a separate "dummy" VLAN 274 | 275 | - Create any additional explicit proxy topologies as required, as separate functions based on discrete actions (allow/block, intercept/bypass, service chain, egress) 276 | 277 | - **Step 4**: Import the traffic switching iRule 278 | - Set necessary static configuration values in RULE_INIT as required 279 | - Define any URL category lists in RULE_INIT as required (see example). Use the following command to get a list of URL categories: 280 | 281 | `tmsh list sys url-db url-category |grep "sys url-db url-category " |awk -F" " '{print $4}'` 282 | 283 | - **Step 5**: Create a client-facing topology switching VIP 284 | - Type: Standard 285 | - Source: 0.0.0.0/0 286 | - Destination: enter the client-facing explicit proxy IP (what the client will be configured to talk to) 287 | - Service Port: enter the explicit proxy listening port. This must be the same as the listening port defined in the topologies (ex. 3128, 8080). 288 | - Configuration : HTTP Profile (client): select the "sslo-default-http-explicit" profile 289 | - Configuration : VLAN and Tunnel Traffic: select **Enabled on...** and select the client-facing VLAN 290 | - Configuration : Address Translation: disabled 291 | - Configuration : Port Translation: disabled 292 | - iRule: traffic switching iRule 293 | 294 | - **Step 6**: modify the traffic switching iRule with the required detection commands. See **Traffic Selector commands - Explicit Proxy** information below. 295 | 296 | -------------------------------------------------- 297 | 298 | ### Traffic selector commands - Transparent Proxy and proxy-in-front (SNI evaluation) 299 | - Call the "target" proc with the following parameters ([topology name], ${sni}, [message]) 300 | - **[topology name]** is the base name of the defined topology 301 | - **${sni}** is static here and returns the server name indication value (SNI) for logging 302 | - **[message]** is any string message to send to the log (ex. which rule matched) 303 | - **return** is added at the end of each command to cancel any further matching 304 | - Example: 305 | `call SSLOLIB::target "bypass" ${sni} "SRCIP"` 306 | 307 | - Use the following commands to query the proc function for matches (all return true or false) 308 | The following commands run in CLIENTSSL_CLIENTHELLO to act on SSL traffic. 309 | 310 | Source IP Detection (static IP, IP subnet, data group match) 311 | SRCIP IP: 312 | SRCIP DG: (address-type data group) 313 | if { [call SSLOLIB::SRCIP IP:10.1.0.0/16] } { call SSLOLIB::target "topology name" ${sni} "SRCIP" ; return } 314 | if { [call SSLOLIB::SRCIP DG:my_sip_list] } { call SSLOLIB::target "topology name" ${sni} "SRCIP" ; return } 315 | 316 | Source Port Detection (static port, port range, data group match) 317 | SRCPORT PORT: 318 | SRCPORT DG: (integer-type data group) 319 | if { [call SSLOLIB::SRCPORT PORT:15000] } { call SSLOLIB::target "topology name" ${sni} "SRCPORT" ; return } 320 | if { [call SSLOLIB::SRCPORT PORT:1000-60000] } { call SSLOLIB::target "topology name" ${sni} "SRCPORT" ; return } 321 | if { [call SSLOLIB::SRCPORT DG:my-sport-list] } { call SSLOLIB::target "topology name" ${sni} "SRCPORT" ; return } 322 | 323 | Destination IP Detection (static IP, IP subnet, data group match) 324 | DSTIP IP: 325 | DSTIP DG: (address-type data group) 326 | if { [call SSLOLIB::DSTIP IP:93.184.216.34] } { call SSLOLIB::target "topology name" ${sni} "DSTIP" ; return } 327 | if { [call SSLOLIB::DSTIP DG:my-dip-list] } { call SSLOLIB::target "topology name" ${sni} "DSTIP" ; return } 328 | 329 | Destination Port Detection (static port, port range, data group match) 330 | DSTPORT PORT: 331 | DSTPORT DG: (integer-type data group) 332 | if { [call SSLOLIB::DSTPORT PORT:443] } { call SSLOLIB::target "topology name" ${sni} "DSTPORT" ; return } 333 | if { [call SSLOLIB::DSTPORT PORT:1-1024] } { call SSLOLIB::target "topology name" ${sni} "DSTPORT" ; return } 334 | if { [call SSLOLIB::DSTPORT DG:my-dport-list] } { call SSLOLIB::target "topology name" ${sni} "DSTPORT" ; return } 335 | 336 | SNI Detection (static URL, category match, data group match) 337 | SNI URL: 338 | SNI URLGLOB: (ends_with match) 339 | if { [call SSLOLIB::SNI URL:www.example.com] } { call SSLOLIB::target "topology name" ${sni} "SNIURL" ; return } 340 | if { [call SSLOLIB::SNI URLGLOB:.example.com] } { call SSLOLIB::target "topology name" ${sni} "SNIURLGLOB" ; return } 341 | 342 | SNI CAT: 343 | if { [call SSLOLIB::SNI CAT:/Common/Financial_Data_and_Services] } { call SSLOLIB::target "topology name" ${sni} "SNICAT" ; return } 344 | if { [call SSLOLIB::SNI CAT:$static::URLCAT_Finance_Health] } { call SSLOLIB::target "topology name" ${sni} "SNICAT" ; return } 345 | 346 | SNI DG: (string-type data group) 347 | SNI DGGLOB: (ends_with match) 348 | if { [call SSLOLIB::SNI DG:my-sni-list] } { call SSLOLIB::target "topology name" ${sni} "SNIDG" ; return } 349 | if { [call SSLOLIB::SNI DGGLOB:my-sniglob-list] } { call SSLOLIB::target "topology name" ${sni} "SNIDGGLOB" ; return } 350 | 351 | Combinations: above selectors can be used in combinations as required. Example: 352 | if { ([call SSLOLIB::SRCIP IP:10.1.0.0/16]) and ([call SSLOLIB::DSTIP IP:93.184.216.34]) } 353 | 354 |
355 | 356 | - It is also possible to add an HTTP_REQUEST event and client HTTP profile to act on the HTTP Host header of unencrypted HTTP requests. Note that by default any non-TLS traffic will flow to the specified default topology. By adding a client HTTP profile and any of the following in the HTTP_REQUEST event, you can trigger policy steering on unencrypted HTTP traffic. 357 | 358 | HOST Detection (static URL, category match, data group match) 359 | HOST URL: 360 | HOST URLGLOB: (ends_with match) 361 | if { [call SSLOLIB::HOST URL:www.example.com] } { call SSLOLIB::target "topology name" ${host} "HOSTURL" ; return } 362 | if { [call SSLOLIB::HOST URLGLOB:.example.com] } { call SSLOLIB::target "topology name" ${host} "HOSTURLGLOB" ; return } 363 | 364 | HOST CAT: 365 | if { [call SSLOLIB::HOST CAT:/Common/Financial_Data_and_Services] } { call SSLOLIB::target "topology name" ${host} "HOSTCAT" ; return } 366 | if { [call SSLOLIB::HOST CAT:$static::URLCAT_Finance_Health] } { call SSLOLIB::target "topology name" ${host} "HOSTCAT" ; return } 367 | 368 | HOST DG: (string-type data group) 369 | HOST DGGLOB: (ends_with match) 370 | if { [call SSLOLIB::HOST DG:my-sni-list] } { call SSLOLIB::target "topology name" ${host} "HOSTDG" ; return } 371 | if { [call SSLOLIB::HOST DGGLOB:my-sniglob-list] } { call SSLOLIB::target "topology name" ${host} "HOSTDGGLOB" ; return } 372 | 373 |
374 | 375 | - Likewise, adding any of the Source/destination IP and port rules to the CLIENT_ACCEPTED event will trigger policy steering at the client side TCP handshake (before TLS). 376 | 377 | -------------------------------------------------- 378 | 379 | ### Traffic selector commands - Transparent Proxy and proxy-in-front (Explicit proxy request HOST evaluation) 380 | - Call the "target" proc with the following parameters ([topology name], ${sni}, [message]) 381 | - **[topology name]** is the base name of the defined topology 382 | - **${host}** is static here and returns the server name indication value (SNI) for logging 383 | - **[message]** is any string message to send to the log (ex. which rule matched) 384 | - **return** is added at the end of each command to cancel any further matching 385 | - Example: 386 | `call PIFSSLOLIB::target "bypass" ${host} "SRCIP"` 387 | 388 | Source IP Detection (static IP, IP subnet, data group match) 389 | SRCIP IP: 390 | SRCIP DG: (address-type data group) 391 | if { [call PIFSSLOLIB::SRCIP IP:10.1.0.0/16] } { call PIFSSLOLIB::target "topology name" ${host} "SRCIP" ; return } 392 | if { [call PIFSSLOLIB::SRCIP DG:my_sip_list] } { call PIFSSLOLIB::target "topology name" ${host} "SRCIP" ; return } 393 | 394 | Source Port Detection (static port, port range, data group match) 395 | SRCPORT PORT: 396 | SRCPORT DG: (integer-type data group) 397 | if { [call PIFSSLOLIB::SRCPORT PORT:15000] } { call PIFSSLOLIB::target "topology name" ${host} "SRCPORT" ; return } 398 | if { [call PIFSSLOLIB::SRCPORT PORT:1000-60000] } { call PIFSSLOLIB::target "topology name" ${host} "SRCPORT" ; return } 399 | if { [call PIFSSLOLIB::SRCPORT DG:my-sport-list] } { call PIFSSLOLIB::target "topology name" ${host} "SRCPORT" ; return } 400 | 401 | Destination IP Detection (static IP, IP subnet, data group match) 402 | DSTIP IP: 403 | DSTIP DG: (address-type data group) 404 | if { [call PIFSSLOLIB::DSTIP IP:93.184.216.34] } { call PIFSSLOLIB::target "topology name" ${host} "DSTIP" ; return } 405 | if { [call PIFSSLOLIB::DSTIP DG:my-dip-list] } { call PIFSSLOLIB::target "topology name" ${host} "DSTIP" ; return } 406 | 407 | Destination Port Detection (static port, port range, data group match) 408 | DSTPORT PORT: 409 | DSTPORT DG: (integer-type data group) 410 | if { [call PIFSSLOLIB::DSTPORT PORT:443] } { call PIFSSLOLIB::target "topology name" ${host} "DSTPORT" ; return } 411 | if { [call PIFSSLOLIB::DSTPORT PORT:1-1024] } { call PIFSSLOLIB::target "topology name" ${host} "DSTPORT" ; return } 412 | if { [call PIFSSLOLIB::DSTPORT DG:my-dport-list] } { call PIFSSLOLIB::target "topology name" ${host} "DSTPORT" ; return } 413 | 414 | HOST Detection (static URL, category match, data group match) 415 | HOST URL: 416 | HOST URLGLOB: (ends_with match) 417 | if { [call PIFSSLOLIB::HOST URL:www.example.com] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTURL" ; return } 418 | if { [call PIFSSLOLIB::HOST URLGLOB:.example.com] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTURLGLOB" ; return } 419 | 420 | HOST CAT: 421 | if { [call PIFSSLOLIB::HOST CAT:/Common/Financial_Data_and_Services] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTCAT" ; return } 422 | if { [call PIFSSLOLIB::HOST CAT:$static::URLCAT_Finance_Health] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTCAT" ; return } 423 | 424 | HOST DG: (string-type data group) 425 | HOST DGGLOB: (ends_with match) 426 | if { [call PIFSSLOLIB::HOST DG:my-sni-list] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTDG" ; return } 427 | if { [call PIFSSLOLIB::HOST DGGLOB:my-sniglob-list] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTDGGLOB" ; return } 428 | 429 | Combinations: above selectors can be used in combinations as required. Example: 430 | if { ([call PIFSSLOLIB::SRCIP IP:10.1.0.0/16]) and ([call PIFSSLOLIB::DSTIP IP:93.184.216.34]) } 431 | 432 | -------------------------------------------------- 433 | 434 | ### Traffic selector commands - Explicit Proxy (proxy-in-back) 435 | - Call the "target" proc with the following parameters ([topology name], ${sni}, [message]) 436 | - **[topology name]** is the base name of the defined topology 437 | - **${host}** is static here and returns the server name indication value (SNI) for logging 438 | - **[message]** is any string message to send to the log (ex. which rule matched) 439 | - **return** is added at the end of each command to cancel any further matching 440 | - Example: 441 | `call SSLOLIBEXP::target "bypass" ${host} "SRCIP"` 442 | 443 | - Use the following commands to query the proc function for matches (all return true or false) 444 | All commands run in HTTP_PROXY_REQUEST to act on explicit proxy requests. 445 | 446 | Source IP Detection (static IP, IP subnet, data group match) 447 | SRCIP IP: 448 | SRCIP DG: (address-type data group) 449 | if { [call SSLOLIBEXP::SRCIP IP:10.1.0.0/16] } { call SSLOLIBEXP::target "topology name" ${host} "SRCIP" ; return } 450 | if { [call SSLOLIBEXP::SRCIP DG:my_sip_list] } { call SSLOLIBEXP::target "topology name" ${host} "SRCIP" ; return } 451 | 452 | Source Port Detection (static port, port range, data group match) 453 | SRCPORT PORT: 454 | SRCPORT DG: (integer-type data group) 455 | if { [call SSLOLIBEXP::SRCPORT PORT:15000] } { call SSLOLIBEXP::target "topology name" ${host} "SRCPORT" ; return } 456 | if { [call SSLOLIBEXP::SRCPORT PORT:1000-60000] } { call SSLOLIBEXP::target "topology name" ${host} "SRCPORT" ; return } 457 | if { [call SSLOLIBEXP::SRCPORT DG:my-sport-list] } { call SSLOLIBEXP::target "topology name" ${host} "SRCPORT" ; return } 458 | 459 | Destination IP Detection (static IP, IP subnet, data group match) 460 | DSTIP IP: 461 | DSTIP DG: (address-type data group) 462 | if { [call SSLOLIBEXP::DSTIP IP:93.184.216.34] } { call SSLOLIBEXP::target "topology name" ${host} "DSTIP" ; return } 463 | if { [call SSLOLIBEXP::DSTIP DG:my-dip-list] } { call SSLOLIBEXP::target "topology name" ${host} "DSTIP" ; return } 464 | 465 | HOST Detection (static URL, category match, data group match) 466 | HOST URL: 467 | HOST URLGLOB: (ends_with match) 468 | if { [call SSLOLIBEXP::HOST URL:www.example.com] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTURL" ; return } 469 | if { [call SSLOLIBEXP::HOST URLGLOB:.example.com] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTURLGLOB" ; return } 470 | 471 | HOST CAT: 472 | if { [call SSLOLIBEXP::HOST CAT:/Common/Financial_Data_and_Services] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTCAT" ; return } 473 | if { [call SSLOLIBEXP::HOST CAT:$static::URLCAT_Finance_Health] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTCAT" ; return } 474 | 475 | HOST DG: (string-type data group) 476 | HOST DGGLOB: (ends_with match) 477 | if { [call SSLOLIBEXP::HOST DG:my-sni-list] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTDG" ; return } 478 | if { [call SSLOLIBEXP::HOST DGGLOB:my-sniglob-list] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTDGGLOB" ; return } 479 | 480 | Combinations: above selectors can be used in combinations as required. Example: 481 | if { ([call SSLOLIBEXP::SRCIP IP:10.1.0.0/16]) and ([call SSLOLIBEXP::DSTIP IP:93.184.216.34]) } 482 | 483 | 484 | -------------------------------------------------- 485 | 486 | ### Handling Server-Initiated Protocols (transparent forward proxy) 487 | Server-initiated protocols, sometimes referred to as "server-speaks-first" (SSF) are the subset of TCP protocols that are initiated by the server. In most "client-speaks-first" (CSF) protocols, like HTTP, after the TCP three-way-handshake, the client immediately makes a request (ex. GET / HTTP/1.1...). In a server-speaks-first protocol, like those that perform StartTLS (IMAP, POP3, SMTP), after the three-way-handshake the server sends the first payload (ex. usually a "Hello" message), after which TLS can be negotiated when the server indicates support for it. Under SSL Orchestrator, SSF and CSF protocols are handled separately. Specifically, the SSL Orchestrator guided configuration for a transparent forward proxy topology makes provisions for FTP, IMAP, POP3, and SMTP. When enabled, the guided configuration will create separate port-based interception rules for each. 488 | 489 | To handle SSF protocols through the internal layered architecture, first create the port-based SSF protocol listeners through the SSL Orchestrator guided configuration. This will create separate interception rules for each. In the layered architecture iRule, specify a destination port rule (**DTSPORT**) for each, and steer to the appropriate interception rule. This will use also use a special "target_ssf" proc for steering, and will require the full name and path of the interception rule. For example (where the base topology is named "ssf"): 490 | 491 | if { [call SSLOLIB::DSTPORT PORT:21] } { call SSLOLIB::target_ssf "/Common/sslo_ssf.app/sslo_ssf-ftp-4" "FTP" ; return } 492 | if { [call SSLOLIB::DSTPORT PORT:990] } { call SSLOLIB::target_ssf "/Common/sslo_ssf.app/sslo_ssf-ftps-4" "FTPS" ; return } 493 | if { [call SSLOLIB::DSTPORT PORT:143] } { call SSLOLIB::target_ssf "/Common/sslo_ssf.app/sslo_ssf-imap-4" "IMAP" ; return } 494 | if { [call SSLOLIB::DSTPORT PORT:110] } { call SSLOLIB::target_ssf "/Common/sslo_ssf.app/sslo_ssf-pop3-4" "POP3" ; return } 495 | if { [call SSLOLIB::DSTPORT PORT:25] } { call SSLOLIB::target_ssf "/Common/sslo_ssf.app/sslo_ssf-smtp25-4" "SMTP" ; return } 496 | if { [call SSLOLIB::DSTPORT PORT:587] } { call SSLOLIB::target_ssf "/Common/sslo_ssf.app/sslo_ssf-smtp587-4" "SMTPS" ; return } 497 | -------------------------------------------------------------------------------- /internal-layered-architecture/explicit-proxy/SSLOLIBEXP: -------------------------------------------------------------------------------- 1 | ## SSL Orchestrator Use Case: Internal Layered Architecture - SSLOLIBEXP Traffic Matching Library 2 | ## Version: 2.1 3 | ## Date: 2020-12-15 4 | ## Author: Kevin Stewart, F5 Networks 5 | ## 6 | ## Configuration: 7 | ## - Step 1: Import this SSLOLIBEXP iRule (name "SSLOLIB") 8 | ## 9 | ## - Step 2: Build semi-static SSL Orchestrator topologies based on common actions (ex. allow, intercept, service chain) 10 | ## - Minimally create a normal "intercept" topology and a separate "bypass" topology 11 | ## Intercept topology: 12 | ## - L3 outbound topology configuration, normal topology settings, SSL config, services, service chain 13 | ## - No security policy rules - just a single ALL rule with TLS intercept action (and service chain) 14 | ## - Attach to a "dummy" VLAN 15 | ## Bypass topology: 16 | ## - L3 outbound topology configuration, skip SSL config, re-use services, service chains 17 | ## - No security policy rules - just a single ALL rule with TLS bypass action (and service chain) 18 | ## - Attached to a separate "dummy" VLAN 19 | ## - Create any additional topologies as required, as separate functions based on discrete actions (allow/block, intercept/bypass, service chain) 20 | ## 21 | ## - Step 3: Import the traffic switching iRule 22 | ## - Set necessary static configuration values in RULE_INIT as required 23 | ## - Define any URL category lists in RULE_INIT as required (see example). Use the following command to get a list of URL categories: 24 | ## tmsh list sys url-db url-category |grep "sys url-db url-category " |awk -F" " '{print $4}' 25 | ## 26 | ## - Step 4: Create a client-facing topology switching VIP 27 | ## Type: Standard 28 | ## Source: 0.0.0.0/0 29 | ## Destination: 0.0.0.0/0:0 30 | ## Protocol: TCP 31 | ## VLAN: client-facing VLAN 32 | ## Address/Port Translation: disabled 33 | ## Default Persistence Profile: ssl 34 | ## iRule: traffic switching iRule 35 | ## 36 | ## - Step 5: modify the traffic switching iRule with the required detection commands (below) 37 | ## 38 | ## 39 | ## Traffic selector commands (to be used in traffic switching iRule) 40 | ## - Call the "target" proc with the following parameters (, ${host}, ) 41 | ## - is the base name of the defined topology 42 | ## - ${host} is static here and returns the server name indication value (HOST) for logging 43 | ## - is any string message to send to the log (ex. which rule matched) 44 | ## - return is added at the end of each command to cancel any further matching 45 | ## - Example: 46 | ## call SSLOLIBEXP::target "bypass" ${host} "SRCIP" 47 | ## 48 | ## - Use the following commands to query the proc function for matches (all return true or false) 49 | ## All commands run in CLIENTSSL_CLIENTHELLO to act on SSL traffic 50 | ## 51 | ## Source IP Detection (static IP, IP subnet, data group match) 52 | ## SRCIP IP: 53 | ## SRCIP DG: (address-type data group) 54 | ## if { [call SSLOLIBEXP::SRCIP IP:10.1.0.0/16] } { call SSLOLIBEXP::target "topology name" ${host} "SRCIP" ; return } 55 | ## if { [call SSLOLIBEXP::SRCIP DG:my_sip_list] } { call SSLOLIBEXP::target "topology name" ${host} "SRCIP" ; return } 56 | ## 57 | ## Source Port Detection (static port, port range, data group match) 58 | ## SRCPORT PORT: 59 | ## SRCPORT DG: (integer-type data group) 60 | ## if { [call SSLOLIBEXP::SRCPORT PORT:15000] } { call SSLOLIBEXP::target "topology name" ${host} "SRCPORT" ; return } 61 | ## if { [call SSLOLIBEXP::SRCPORT PORT:1000-60000] } { call SSLOLIBEXP::target "topology name" ${host} "SRCPORT" ; return } 62 | ## if { [call SSLOLIBEXP::SRCPORT DG:my-sport-list] } { call SSLOLIBEXP::target "topology name" ${host} "SRCPORT" ; return } 63 | ## 64 | ## Destination IP Detection (static IP, IP subnet, data group match) 65 | ## DSTIP IP: 66 | ## DSTIP DG: (address-type data group) 67 | ## if { [call SSLOLIBEXP::DSTIP IP:93.184.216.34] } { call SSLOLIBEXP::target "topology name" ${host} "DSTIP" ; return } 68 | ## if { [call SSLOLIBEXP::DSTIP DG:my-dip-list] } { call SSLOLIBEXP::target "topology name" ${host} "DSTIP" ; return } 69 | ## 70 | ## HOST Detection (static URL, category match, data group match) 71 | ## HOST URL: 72 | ## HOST URLGLOB: (ends_with match) 73 | ## if { [call SSLOLIBEXP::HOST URL:www.example.com] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTURL" ; return } 74 | ## if { [call SSLOLIBEXP::HOST URLGLOB:.example.com] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTURLGLOB" ; return } 75 | ## 76 | ## HOST CAT: 77 | ## if { [call SSLOLIBEXP::HOST CAT:/Common/Financial_Data_and_Services] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTCAT" ; return } 78 | ## if { [call SSLOLIBEXP::HOST CAT:$static::URLCAT_Finance_Health] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTCAT" ; return } 79 | ## 80 | ## HOST DG: (string-type data group) 81 | ## HOST DGGLOB: (ends_with match) 82 | ## if { [call SSLOLIBEXP::HOST DG:my-host-list] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTDG" ; return } 83 | ## if { [call SSLOLIBEXP::HOST DGGLOB:my-hostglob-list] } { call SSLOLIBEXP::target "topology name" ${host} "HOSTDGGLOB" ; return } 84 | ## 85 | ## Combinations: above selectors can be used in combinations as required. Example: 86 | ## if { ([call SSLOLIBEXP::SRCIP IP:10.1.0.0/16]) and ([call SSLOLIBEXP::DSTIP IP:93.184.216.34]) } 87 | ## 88 | ## DO NOT MODIFY BELOW ## 89 | ## SSLOLIB library functions perform error detection and will return 0 (false) in any reasonable error condition (ex. incorrect data group name). 90 | 91 | proc errorlog {message} { 92 | if { $static::SSLODEBUGEXP } { 93 | log -noname local0. "SSLOLIB Error :: [IP::client_addr]:[TCP::client_port] -> [IP::local_addr]:[TCP::local_port] :: ${message}" 94 | } 95 | } 96 | proc target {topology {host ""} {message ""}} { 97 | virtual "/Common/sslo_${topology}.app/sslo_${topology}-xp-4" 98 | if { ( $static::SSLODEBUGEXP ) and ( ${host} ne "" ) and ( ${message} ne "" ) } { 99 | log -noname local0. "SSLO Switch Log :: [IP::client_addr]:[TCP::client_port] -> [IP::local_addr]:[TCP::local_port] :: (${host}) :: Match on ${message} :: Sending to ${topology}" 100 | } 101 | } 102 | proc SRCIP { arg } { 103 | set arglist [split ${arg} ":"] 104 | switch -- [lindex ${arglist} 0] { 105 | "IP" { if { [catch { set res [expr { [IP::addr [lindex ${arglist} 1] equals [IP::client_addr]] } ? 1 : 0] } err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} } } 106 | "DG" { if { [catch { set res [expr { [class match -- [IP::client_addr] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} } } 107 | } 108 | } 109 | proc SRCPORT { arg } { 110 | set arglist [split ${arg} ":"] 111 | switch -- [lindex ${arglist} 0] { 112 | "PORT" { if { [catch { if { [lindex ${arglist} 1] contains "-" } { set portlist [split [lindex ${arglist} 1] "-"] ; set res [expr { ( [TCP::client_port] > [lindex ${portlist} 0] ) and ( [TCP::client_port] < [lindex ${portlist} 1] ) } ? 1 : 0] } else {set res [expr { [lindex ${arglist} 1] == [TCP::client_port] } ? 1 : 0] }} err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} } } 113 | "DG" { if { [catch { set res [expr { [class match -- [TCP::client_port] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} } } 114 | } 115 | } 116 | proc DSTIP { arg } { 117 | set arglist [split ${arg} ":"] 118 | switch -- [lindex ${arglist} 0] { 119 | "IP" { if { [catch { set res [expr { [IP::addr [lindex ${arglist} 1] equals [IP::local_addr]] } ? 1 : 0] } err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} } } 120 | "DG" { if { [catch { set res [expr { [class match -- [IP::local_addr] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} } } 121 | } 122 | } 123 | proc DSTPORT { arg } { 124 | set arglist [split ${arg} ":"] 125 | switch -- [lindex ${arglist} 0] { 126 | "PORT" { if { [catch { if { [lindex ${arglist} 1] contains "-" } { set portlist [split [lindex ${arglist} 1] "-"] ; set res [expr { ( [TCP::local_port] > [lindex ${portlist} 0] ) and ( [TCP::local_port] < [lindex ${portlist} 1] ) } ? 1 : 0] } else {set res [expr { [lindex ${arglist} 1] == [TCP::local_port] } ? 1 : 0] }} err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} } } 127 | "DG" { if { [catch { set res [expr { [class match -- [TCP::local_port] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} } } 128 | } 129 | } 130 | proc HOST { arg } { 131 | upvar host s_host 132 | upvar cat catg 133 | set s_host "NULL" 134 | 135 | ## parse incoming request URL 136 | if { [HTTP::uri] starts_with "http://" } { 137 | set s_host [findstr [findstr [HTTP::uri] "http://" 7] "" 0 "/"] 138 | } else { 139 | set s_host [findstr [HTTP::uri] "" 0 ":"] 140 | } 141 | 142 | if { $s_host ne "NULL" } { 143 | set arglist [split ${arg} ":"] 144 | switch -- [lindex ${arglist} 0] { 145 | "URL" { return [expr { ${s_host} eq [lindex ${arglist} 1] } ? 1 : 0] } 146 | "URLGLOB" { return [expr { ${s_host} ends_with [lindex ${arglist} 1] } ? 1 : 0] } 147 | "CAT" { 148 | ## optimization 2.0: re-uses existing category lookup results 149 | ## optimization 2.1: test and don't fail if URLDB is not provisioned 150 | if { [info exists catg] } { if { [catch { set match 0 ; foreach cat ${catg} { if { [lsearch -exact [lindex ${arglist} 1] ${cat}] >= 0 } { set match 1 }} ; set res [expr { ${match} } ? 1 : 0] } err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} } } else { set URLF "CATEGORY\x3a\x3alookup" ; if { [catch "${URLF} https://${s_host}/ request_default_and_custom" catg] || ![llength ${catg}] } { return 0 } else { if { [catch { set match 0 ; foreach cat ${catg} { if { [lsearch -exact [lindex ${arglist} 1] ${cat}] >= 0 } { set match 1 } } ; set res [expr { ${match} } ? 1 : 0] } err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} }}} 151 | } 152 | "DG" { if { [catch { set res [expr { [class match -- ${s_host} equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} } } 153 | "DGGLOB" { if { [catch { set res [expr { [class match -- ${s_host} ends_with [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIBEXP::errorlog ${err} ; return 0 } else { return ${res} } } 154 | } 155 | } else { return 0 } 156 | } 157 | -------------------------------------------------------------------------------- /internal-layered-architecture/explicit-proxy/sslo-layering-exp-rule: -------------------------------------------------------------------------------- 1 | ## SSL Orchestrator Use Case: Internal Layered Architecture - Client-Facing Switching iRule 2 | ## Version: 2.1 3 | ## Date: 2024-10-18 4 | ## Author: Kevin Stewart, F5 Networks 5 | ## Configuration: see SSLOLIBEXP iRule for build instructions 6 | 7 | ## Set a default topology assuming no other matches (do not edit) 8 | when CLIENT_ACCEPTED { 9 | call SSLOLIBEXP::target $static::default_topology 10 | } 11 | 12 | 13 | ##################################################### 14 | ## EDIT BELOW THIS BLOCK ############################ 15 | ##################################################### 16 | when RULE_INIT { 17 | ## User-defined: DEBUG logging flag (1=on, 0=off) 18 | set static::SSLODEBUGEXP 0 19 | 20 | ## User-defined: Default topology if no rules match (the topology name as defined in SSLO) 21 | set static::default_topology "interceptexp" 22 | 23 | ## User-defined: URL category list (create as many lists as required) 24 | set static::URLCAT_Finance_Health { 25 | /Common/Financial_Data_and_Services 26 | /Common/Health_and_Medicine 27 | } 28 | } 29 | when HTTP_PROXY_REQUEST { 30 | ## disable proxy to create pass-through 31 | HTTP::proxy disable 32 | 33 | ## Do not edit - create host variable and set based on HTTP request (for logging) 34 | set host ""; call SSLOLIBEXP::HOST "" 35 | 36 | ## Standard certificate Pinners bypassexp rule (specify your bypassexp topology) 37 | if { [call SSLOLIBEXP::HOST CAT:/Common/sslo-urlCatPinners] } { call SSLOLIBEXP::target "bypassexp" ${host} "pinners" ; return} 38 | 39 | ################################ 40 | #### CONDITIONS GO HERE #### 41 | ################################ 42 | #if { [call SSLOLIBEXP::SRCIP IP:10.1.0.0/16] } { call SSLOLIBEXP::target "bypassexp" ${host} "SRCIP" ; return } 43 | #if { [call SSLOLIBEXP::SRCIP DG:my-srcip-dg] } { call SSLOLIBEXP::target "bypassexp" ${host} "SRCIP" ; return } 44 | 45 | #if { [call SSLOLIBEXP::SRCPORT PORT:5000] } { call SSLOLIBEXP::target "bypassexp" ${host} "SRCPORT" ; return } 46 | #if { [call SSLOLIBEXP::SRCPORT PORT:1000-60000] } { call SSLOLIBEXP::target "bypassexp" ${host} "SRCPORT" ; return } 47 | 48 | #if { [call SSLOLIBEXP::DSTIP IP:93.184.216.34] } { call SSLOLIBEXP::target "bypassexp" ${host} "DSTIP" ; return } 49 | #if { [call SSLOLIBEXP::DSTIP DG:my-destip-dg] } { call SSLOLIBEXP::target "bypassexp" ${host} "DSTIP" ; return } 50 | 51 | #if { [call SSLOLIBEXP::HOST URL:www.example.com] } { call SSLOLIBEXP::target "bypassexp" ${host} "HOSTURL" ; return } 52 | #if { [call SSLOLIBEXP::HOST URLGLOB:.example.com] } { call SSLOLIBEXP::target "bypassexp" ${host} "HOSTURLGLOB" ; return } 53 | 54 | #if { [call SSLOLIBEXP::HOST CAT:$static::URLCAT_Finance_Health] } { call SSLOLIBEXP::target "bypassexp" ${host} "HOSTCAT" ; return } 55 | #if { [call SSLOLIBEXP::HOST CAT:/Common/Financial_Data_and_Services] } { call SSLOLIBEXP::target "bypassexp" ${host} "HOSTCAT" ; return } 56 | 57 | #if { [call SSLOLIBEXP::HOST DG:my-host-dg] } { call SSLOLIBEXP::target "bypassexp" ${host} "HOSTDGGLOB" ; return } 58 | #if { [call SSLOLIBEXP::HOST DGGLOB:my-hostglob-dg] } { call SSLOLIBEXP::target "bypassexp" ${host} "HOSTDGGLOB" ; return } 59 | 60 | ## To combine these, you can use smple AND|OR logic: 61 | } 62 | -------------------------------------------------------------------------------- /internal-layered-architecture/explicit-proxy/use-case-encrypted-traffic-through-http-service/README.md: -------------------------------------------------------------------------------- 1 | # F5 SSL Orchestrator Layered Architecture Configuration 2 | # Explicit Proxy Configuration Use Cases 3 | This section defines use cases specific to an explicit forward proxy implementation using the layered architecture 4 | 5 | ## Encrypted Traffic to an HTTP Proxy Service: 6 | SSL Orchestrator employs "flow signaling" to maintain traffic context through the dynamic service chain. As a flow leaves the BIG-IP for an inline security service, its flow information is recorded (src+dst IP:port) so that when it returns from the service, context can be re-established. An inline HTTP proxy service, however, will always minimally change the source port, thus breaking the flow signal. For this reason, and to support HTTP proxy services in the dynamic service chain, SSL Orchestrator uses an HTTP header signal through this device type. This also requires that signaling through a proxy service can only happen for unencrypted/decrypted HTTP traffic. As an HTTP header cannot be injected into TLS bypassed connections, SSL Orchestrator will bypass any proxy service in the service chain for TLS bypassed traffic. 7 | 8 | The following use case describes an alternate method for passing encrypted traffic to an HTTP proxy service, by using the Internal Layered Architecture and egress proxy chaining. The layered steering VIP, per policy, will steer traffic to an internal TLS bypass SSL Orchestrator topology. That topology will be configured to proxy chain out to the HTTP proxy service (looping back into the service chain). A separate "control channel" virtual server is then established to catch the HTTPS traffic leaving the proxy service, to send direct to egress. 9 | 10 | ![SSL Orchestrator Internal Layered Architecture](../../images/sslo-encrypted-traffic-to-proxy.png) 11 | 12 | Traffic destined for TLS interception is steered to an intercept topology by the layered virtual server, and then egresses the normal routed path. Decrypted traffic to the service chain will flow through all of the defined services here, including the proxy service. Traffic destined for TLS bypass is steered to the bypass topology, which is then configured to loop back around to the proxy service inside the service chain. The SSL Orchestrator service chain would not accept this traffic, so a separate "control channel" virtual catches any HTTPS (port 443) traffic leaving the proxy service, directing that to the routed egress path. There are two methods for handling this use case, depending on the mode of the proxy security service: **explicit** or **transparent** proxy. 13 | 14 | Note that the configuration for a transparent proxy security service is considerably easier here, and recommended. Given that the proxy security service is inside the SSL Orchestrator inspection zone, there is generally little benefit to an explicit proxy configuration on that device. 15 | 16 | _____________________________________________________ 17 | 18 | ## Encrypted Traffic to an HTTP Transparent Proxy Service: 19 | First, note that a transparent proxy will usually behave exactly the same as an explicit proxy, except that instead of directing traffic at the proxy listener IP:port, traffic is *routed* through the device. Under SSL Orchestrator this also behaves more or less the same way that an inline layer 3 service works, except that a proxy (explicit or transparent) will almost always change parts of the connection tuple, thus requires proxy-based signaling. In any case, the steps to configure this use case are as follows: 20 | 21 | - Deploy an Internal Layered Architecture configuration 22 | - Create any SSL Orchestrator TLS intercept topologies as required, define services and service chains 23 | - Create a TLS bypass topology and assign the proxy service pool as its gateway 24 | - Create a service control channel virtual server 25 | 26 | ### Deploy an Internal Layered Architecture configuration 27 | - Use the explicit proxy configuration, as detailed on the main page, to establish an explicit proxy internal layered architecture. 28 | 29 | ### Create any SSL Orchestrator TLS intercept topologies as required, define services and service chains 30 | - In the SSL Orchestrator UI, create any internal TLS intercept topologies as required. Define the services and service chains here. 31 | 32 | ### Create a TLS bypass topology and configure Proxy Connect to the proxy service 33 | - In the SSL Orchestrator UI, create a TLS bypass topology. On the Egress Settings page, select the proxy service pool as the gateway. Disable SNAT as this will be done at the service control channel virtual server. 34 | 35 | ### Create a service control channel virtual server 36 | - Under Local Traffic -> Virtual Servers, create a new virtual server: 37 | - Source: 0.0.0.0/0 38 | - Destination: 0.0.0.0/0 39 | - Port: 443 40 | - VLAN: enable and select the proxy service's "from-service" VLAN as defined in the SSL Orchestrator service configuration 41 | - Address Translation: disabled 42 | - Port Translation: disabled 43 | - Pool: select an existing or create a new pool that points to the routed gateway (the same that the TLS intercept topology uses) 44 | 45 | In this scenario, traffic directed to the TLS Bypass topology will egress from that topology and loop back around (encrypted) to the proxy service in the service chain. This is routed traffic so the proxy service would be configured as a transparent proxy. Its gateway is to the same BIG-IP return VLAN for all traffic, so the service control channel virtual server collects any port 443 traffic on that VLAN and directs that to the nexthop Internet gateway. 46 | 47 | Note that the control channel virtual server listens on the proxy service's "from-service" VLAN. SSL Orchestrator defines a wildcard (0.0.0.0/0:0) virtual server on this same VLAN, so the control channel listens on a **more specific** traffic flow (port 443). Any traffic leaving the HTTP proxy service inside the service chain of a TLS intercept topology would normally be HTTP port 80. You don't have to use a specific port here though. In fact, if your proxy device is capable of enabling and disabling source address translation (i.e. SNAT, or "client IP reflection") based on local policy, you could configure that proxy device to SNAT (use its own IP for HTTPS traffic), and not SNAT (enable client IP reflection) for HTTP traffic. In this case, the control channel virtual server could be configured to instead listen on the proxy service's self-IP as the source of traffic. This would also be useful for allowing the HTTP proxy service to reach out to Internet sites (from its own IP address), for example to reach a licensing or subscription update service. 48 | 49 | _____________________________________________________ 50 | 51 | ## Encrypted Traffic to an HTTP Explicit Proxy Service: 52 | The steps to configure this are as follows: 53 | 54 | - Deploy an Internal Layered Architecture "Proxy in Back" configuration 55 | - Create any SSL Orchestrator TLS intercept topologies as required, define services and service chains 56 | - Create a new proxy service pool 57 | - Create a TLS bypass topology and configure Proxy Connect to the proxy service 58 | - Create a service control channel virtual server 59 | 60 | ### Deploy an Internal Layered Architecture "Proxy in Back" configuration 61 | - Use the explicit proxy "Proxy in Back" configuration, as detailed on the main page, to establish an explicit proxy internal layered architecture. 62 | 63 | ### Create any SSL Orchestrator TLS intercept topologies as required, define services and service chains 64 | - In the SSL Orchestrator UI, create any internal TLS intercept topologies as required. Define the services and service chains here. 65 | 66 | ### Create a new proxy service pool 67 | - Under Local Traffic -> Pools, create a new pool that points to the explicit proxy service listener IP:port. This will be the same IP and port defined for the proxy service in the SSL Orchestrator service configuration. 68 | 69 | ### Create a TLS bypass topology and configure Proxy Connect to the proxy service 70 | - In the SSL Orchestrator UI, create a TLS bypass topology. On the Security Policy page, enable **Proxy Connect** and select the new proxy service pool. 71 | 72 | ### Create a service control channel virtual server 73 | - Under Local Traffic -> Virtual Servers, create a new virtual server: 74 | - Source: 0.0.0.0/0 75 | - Destination: 0.0.0.0/0 76 | - Port: 443 77 | - VLAN: enable and select the proxy service's "from-service" VLAN as defined in the SSL Orchestrator service configuration 78 | - Address Translation: disabled 79 | - Port Translation: disabled 80 | - Pool: select an existing or create a new pool that points to the routed gateway (the same that the TLS intercept topology uses) 81 | 82 | In this scenario, traffic directed to the TLS Bypass topology will egress from that topology and loop back around (encrypted) to the proxy service in the service chain. As the HTTP service is an explicit proxy, the TLS Bypass topology must be configure to proxy chain. The proxy service's gateway is to the same BIG-IP return VLAN for all traffic, so the service control channel virtual server collects any port 443 traffic on that VLAN and directs that to the nexthop Internet gateway. 83 | 84 | Note that the control channel virtual server listens on the proxy service's "from-service" VLAN. SSL Orchestrator defines a wildcard (0.0.0.0/0:0) virtual server on this same VLAN, so the control channel listens on a **more specific** traffic flow (port 443). Any traffic leaving the HTTP proxy service inside the service chain of a TLS intercept topology would normally be HTTP port 80. You don't have to use a specific port here though. In fact, if your proxy device is capable of enabling and disabling source address translation (i.e. SNAT, or "client IP reflection") based on local policy, you could configure that proxy device to SNAT (use its own IP for HTTPS traffic), and not SNAT (enable client IP reflection) for HTTP traffic. In this case, the control channel virtual server could be configured to instead listen on the proxy service's self-IP as the source of traffic. This would also be useful for allowing the HTTP proxy service to reach out to Internet sites (from its own IP address), for example to reach a licensing or subscription update service. 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /internal-layered-architecture/images/sslo-encrypted-traffic-to-proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f5devcentral/sslo-script-tools/2183ef3731a943145347bade1a15d0394f03e80f/internal-layered-architecture/images/sslo-encrypted-traffic-to-proxy.png -------------------------------------------------------------------------------- /internal-layered-architecture/images/sslo-internal-layered-architecture-ep-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f5devcentral/sslo-script-tools/2183ef3731a943145347bade1a15d0394f03e80f/internal-layered-architecture/images/sslo-internal-layered-architecture-ep-back.png -------------------------------------------------------------------------------- /internal-layered-architecture/images/sslo-internal-layered-architecture-ep-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f5devcentral/sslo-script-tools/2183ef3731a943145347bade1a15d0394f03e80f/internal-layered-architecture/images/sslo-internal-layered-architecture-ep-front.png -------------------------------------------------------------------------------- /internal-layered-architecture/images/sslo-internal-layered-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f5devcentral/sslo-script-tools/2183ef3731a943145347bade1a15d0394f03e80f/internal-layered-architecture/images/sslo-internal-layered-architecture.png -------------------------------------------------------------------------------- /internal-layered-architecture/images/sslo-tp-to-ep-layered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f5devcentral/sslo-script-tools/2183ef3731a943145347bade1a15d0394f03e80f/internal-layered-architecture/images/sslo-tp-to-ep-layered.png -------------------------------------------------------------------------------- /internal-layered-architecture/transparent-proxy/PIFSSLOLIB: -------------------------------------------------------------------------------- 1 | ## SSL Orchestrator Use Case: Internal Layered Architecture - PIFSSLOLIB Traffic Matching Library (proxy-in-front using HTTP proxy URL) 2 | ## Version: 1.0 3 | ## Date: 2021-08-16 4 | ## Author: Kevin Stewart, F5 Networks 5 | ## 6 | ## Description: A variation of the proxy-in-front layering use case, where a manually defined LTM explicit proxy ise configured in front of the 7 | ## internal SSLO instances, and the steering virtual uses the HTTP proxy request URL for steering rules (vs. the TLS SNI). 8 | ## 9 | ## Configuration: 10 | ## - Step 1: Import this SSLOLIB iRule (name "PIFSSLOLIB") 11 | ## 12 | ## - Step 2: Import the "pif_explicit_proxy_rule" iRule 13 | ## 14 | ## - Step 3: Import the "pif_sslo_layering_rule" iRule 15 | ## 16 | ## - Step 4: Create the client-facing explicit proxy TCP tunnel (Network :: Tunnels) 17 | ## Profile: tcp-forward 18 | ## 19 | ## - Step 5: Create the client-facing explicit proxy DNS resolver (Network :: DNS Resolvers) 20 | ## Forward Zones: 21 | ## Name: "." (without quotes) 22 | ## Nameservers: add the address and port of the DNS resolvers 23 | ## 24 | ## - Step 6: Create the client-facing explicit proxy HTTP profile (Local Traffic :: Profiles :: HTTP) 25 | ## Proxy Mode: Explicit 26 | ## Explicit Proxy :: DNS Resolver: select the DNS resolver 27 | ## Explicit Proxy :: Tunnel Mame: select the TCP tunnel 28 | ## Explicit Proxy :: Use Tunnel On Any Request Method: enabled (available in BIG-IP 16.0) 29 | ## Explicit Proxy :: Default Connect Handling: deny 30 | ## 31 | ## - Step 7: Create the client-facing explicit proxy virtual server (Local Traffic :: Virtual Servers) 32 | ## Type: Standard 33 | ## Source: 0.0.0.0/0 34 | ## Destination: client-facing explicit proxy IP address 35 | ## Port: client-facing explicit proxy listening port 36 | ## Protocol: TCP 37 | ## HTTP Profile (Client): select the HTTP explicit profile 38 | ## VLAN: client-facing VLAN 39 | ## Address/Port Translation: enabled 40 | ## iRule: select the "pif_explicit_proxy_rule" 41 | ## ** Note: If deploying on BIG-IP earlier than 16.0, the following must be added to the pif_explicit_proxy_rule: 42 | ## 43 | ## when HTTP_REQUEST { 44 | ## virtual "/Common/tcp-tunnel-vip" [HTTP::proxy addr] [HTTP::proxy port] 45 | ## } 46 | ## 47 | ## Change "/Common/tcp-tunnel-vip" to match the name of the TCP tunnel virtual server. 48 | ## 49 | ## - Step 8: Create the tcp tunnel steering virtual server (Local Traffic :: Virtual Servers) 50 | ## Type: Standard 51 | ## Source: 0.0.0.0/0 52 | ## Destination: 0.0.0.0/0 53 | ## Port: 0 54 | ## VLAN: select the tcp tunnel VLAN 55 | ## Address/Port Translation: disabled 56 | ## iRule: select the pif_sslo_layering_rule iRule 57 | ## 58 | ## - Step 9: Build semi-static SSL Orchestrator topologies based on common actions (ex. allow, intercept, service chain) 59 | ## - Minimally create a normal "intercept" topology and a separate "bypass" topology 60 | ## Intercept topology: 61 | ## - L3 outbound topology configuration, normal topology settings, SSL config, services, service chain 62 | ## - No security policy rules - just a single ALL rule with TLS intercept action (and service chain) 63 | ## - Attach to a "dummy" VLAN 64 | ## Bypass topology: 65 | ## - L3 outbound topology configuration, skip SSL config, re-use services, service chains 66 | ## - No security policy rules - just a single ALL rule with TLS bypass action (and service chain) 67 | ## - Attached to a separate "dummy" VLAN 68 | ## - Create any additional topologies as required, as separate functions based on discrete actions (allow/block, intercept/bypass, service chain) 69 | ## 70 | ## - Step 10: modify the traffic switching iRule with the required detection commands (below) 71 | ## 72 | ## 73 | ## Traffic selector commands (to be used in traffic switching iRule) 74 | ## - Call the "target" proc with the following parameters (, ${host}, ) 75 | ## - is the base name of the defined topology 76 | ## - ${host} is static here and returns the server name indication value (HOST) for logging 77 | ## - is any string message to send to the log (ex. which rule matched) 78 | ## - return is added at the end of each command to cancel any further matching 79 | ## - Example: 80 | ## call PIFSSLOLIB::target "bypass" ${host} "SRCIP" 81 | ## 82 | ## Source IP Detection (static IP, IP subnet, data group match) 83 | ## SRCIP IP: 84 | ## SRCIP DG: (address-type data group) 85 | ## if { [call PIFSSLOLIB::SRCIP IP:10.1.0.0/16] } { call PIFSSLOLIB::target "topology name" ${host} "SRCIP" ; return } 86 | ## if { [call PIFSSLOLIB::SRCIP DG:my_sip_list] } { call PIFSSLOLIB::target "topology name" ${host} "SRCIP" ; return } 87 | ## 88 | ## Source Port Detection (static port, port range, data group match) 89 | ## SRCPORT PORT: 90 | ## SRCPORT DG: (integer-type data group) 91 | ## if { [call PIFSSLOLIB::SRCPORT PORT:15000] } { call PIFSSLOLIB::target "topology name" ${host} "SRCPORT" ; return } 92 | ## if { [call PIFSSLOLIB::SRCPORT PORT:1000-60000] } { call PIFSSLOLIB::target "topology name" ${host} "SRCPORT" ; return } 93 | ## if { [call PIFSSLOLIB::SRCPORT DG:my-sport-list] } { call PIFSSLOLIB::target "topology name" ${host} "SRCPORT" ; return } 94 | ## 95 | ## Destination IP Detection (static IP, IP subnet, data group match) 96 | ## DSTIP IP: 97 | ## DSTIP DG: (address-type data group) 98 | ## if { [call PIFSSLOLIB::DSTIP IP:93.184.216.34] } { call PIFSSLOLIB::target "topology name" ${host} "DSTIP" ; return } 99 | ## if { [call PIFSSLOLIB::DSTIP DG:my-dip-list] } { call PIFSSLOLIB::target "topology name" ${host} "DSTIP" ; return } 100 | ## 101 | ## Destination Port Detection (static port, port range, data group match) 102 | ## DSTPORT PORT: 103 | ## DSTPORT DG: (integer-type data group) 104 | ## if { [call PIFSSLOLIB::DSTPORT PORT:443] } { call PIFSSLOLIB::target "topology name" ${host} "DSTPORT" ; return } 105 | ## if { [call PIFSSLOLIB::DSTPORT PORT:1-1024] } { call PIFSSLOLIB::target "topology name" ${host} "DSTPORT" ; return } 106 | ## if { [call PIFSSLOLIB::DSTPORT DG:my-dport-list] } { call PIFSSLOLIB::target "topology name" ${host} "DSTPORT" ; return } 107 | ## 108 | ## HOST Detection (static URL, category match, data group match) 109 | ## HOST URL: 110 | ## HOST URLGLOB: (ends_with match) 111 | ## if { [call PIFSSLOLIB::HOST URL:www.example.com] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTURL" ; return } 112 | ## if { [call PIFSSLOLIB::HOST URLGLOB:.example.com] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTURLGLOB" ; return } 113 | ## 114 | ## HOST CAT: 115 | ## if { [call PIFSSLOLIB::HOST CAT:/Common/Financial_Data_and_Services] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTCAT" ; return } 116 | ## if { [call PIFSSLOLIB::HOST CAT:$static::URLCAT_Finance_Health] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTCAT" ; return } 117 | ## 118 | ## HOST DG: (string-type data group) 119 | ## HOST DGGLOB: (ends_with match) 120 | ## if { [call PIFSSLOLIB::HOST DG:my-sni-list] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTDG" ; return } 121 | ## if { [call PIFSSLOLIB::HOST DGGLOB:my-sniglob-list] } { call PIFSSLOLIB::target "topology name" ${host} "HOSTDGGLOB" ; return } 122 | ## 123 | ## Combinations: above selectors can be used in combinations as required. Example: 124 | ## if { ([call PIFSSLOLIB::SRCIP IP:10.1.0.0/16]) and ([call PIFSSLOLIB::DSTIP IP:93.184.216.34]) } 125 | ## 126 | ## DO NOT MODIFY BELOW ## 127 | ## PIFSSLOLIB library functions perform error detection and will return 0 (false) in any reasonable error condition (ex. incorrect data group name). 128 | 129 | proc errorlog {message} { 130 | if { $static::SSLODEBUG } { 131 | log -noname local0. "PIFSSLOLIB Error :: [IP::client_addr]:[TCP::client_port] -> [IP::local_addr]:[TCP::local_port] :: ${message}" 132 | } 133 | } 134 | proc target {topology {host ""} {message ""}} { 135 | sharedvar HOST 136 | set HOST ${host} 137 | virtual "/Common/sslo_${topology}.app/sslo_${topology}-in-t-4" 138 | if { ( $static::SSLODEBUG ) and ( ${host} ne "" ) and ( ${message} ne "" ) } { 139 | log -noname local0. "SSLO Switch Log :: [IP::client_addr]:[TCP::client_port] -> [IP::local_addr]:[TCP::local_port] :: (${host}) :: Match on ${message} :: Sending to ${topology}" 140 | } 141 | } 142 | proc target_ssf {topology {message ""}} { 143 | virtual ${topology} 144 | if { ( $static::SSLODEBUG ) and ( ${message} ne "" ) } { 145 | log -noname local0. "SSLO Switch Log :: [IP::client_addr]:[TCP::client_port] -> [IP::local_addr]:[TCP::local_port] :: Match on ${message} :: Sending to ${topology}" 146 | } 147 | } 148 | proc SRCIP { arg } { 149 | set arglist [split ${arg} ":"] 150 | switch -- [lindex ${arglist} 0] { 151 | "IP" { if { [catch { set res [expr { [IP::addr [lindex ${arglist} 1] equals [IP::client_addr]] } ? 1 : 0] } err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 152 | "DG" { if { [catch { set res [expr { [class match -- [IP::client_addr] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 153 | } 154 | } 155 | proc SRCPORT { arg } { 156 | set arglist [split ${arg} ":"] 157 | switch -- [lindex ${arglist} 0] { 158 | "PORT" { if { [catch { if { [lindex ${arglist} 1] contains "-" } { set portlist [split [lindex ${arglist} 1] "-"] ; set res [expr { ( [TCP::client_port] > [lindex ${portlist} 0] ) and ( [TCP::client_port] < [lindex ${portlist} 1] ) } ? 1 : 0] } else {set res [expr { [lindex ${arglist} 1] == [TCP::client_port] } ? 1 : 0] }} err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 159 | "DG" { if { [catch { set res [expr { [class match -- [TCP::client_port] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 160 | } 161 | } 162 | proc DSTIP { arg } { 163 | set arglist [split ${arg} ":"] 164 | switch -- [lindex ${arglist} 0] { 165 | "IP" { if { [catch { set res [expr { [IP::addr [lindex ${arglist} 1] equals [IP::local_addr]] } ? 1 : 0] } err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 166 | "DG" { if { [catch { set res [expr { [class match -- [IP::local_addr] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 167 | } 168 | } 169 | proc DSTPORT { arg } { 170 | set arglist [split ${arg} ":"] 171 | switch -- [lindex ${arglist} 0] { 172 | "PORT" { if { [catch { if { [lindex ${arglist} 1] contains "-" } { set portlist [split [lindex ${arglist} 1] "-"] ; set res [expr { ( [TCP::local_port] > [lindex ${portlist} 0] ) and ( [TCP::local_port] < [lindex ${portlist} 1] ) } ? 1 : 0] } else {set res [expr { [lindex ${arglist} 1] == [TCP::local_port] } ? 1 : 0] }} err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 173 | "DG" { if { [catch { set res [expr { [class match -- [TCP::local_port] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 174 | } 175 | } 176 | proc HOST { arg } { 177 | sharedvar exphost ; sharedvar expproto 178 | upvar exphost host 179 | upvar cat catg 180 | if { ${exphost} != "" } { 181 | set arglist [split ${arg} ":"] 182 | switch -- [lindex ${arglist} 0] { 183 | "URL" { return [expr { ${exphost} eq [lindex ${arglist} 1] } ? 1 : 0] } 184 | "URLGLOB" { return [expr { ${exphost} ends_with [lindex ${arglist} 1] } ? 1 : 0] } 185 | "CAT" { if { [info exists catg] } { if { [catch { set match 0 ; foreach cat ${catg} { if { [lsearch -exact [lindex ${arglist} 1] ${cat}] >= 0 } { set match 1 }} ; set res [expr { ${match} } ? 1 : 0] } err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } else { set URLF "CATEGORY\x3a\x3alookup" ; set test [catch "${URLF} ${expproto}://${exphost}/ request_default_and_custom" catg] ; if { ${catg} contains "Categorization engine returned an error" } { set test [catch "${URLF} ${expproto}://${exphost}/ custom" catg] } ; if { ![llength ${catg}] } { return 0 } else { if { [catch { set match 0 ; foreach cat ${catg} { if { [lsearch -exact [lindex ${arglist} 1] ${cat}] >= 0 } { set match 1 } } ; set res [expr { ${match} } ? 1 : 0] } err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} }}}} 186 | "DG" { if { [catch { set res [expr { [class match -- ${exphost} equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 187 | "DGGLOB" { if { [catch { set res [expr { [class match -- ${exphost} ends_with [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call PIFSSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 188 | } 189 | } else { return 0 } 190 | } 191 | -------------------------------------------------------------------------------- /internal-layered-architecture/transparent-proxy/SSLOLIB: -------------------------------------------------------------------------------- 1 | ## SSL Orchestrator Use Case: Internal Layered Architecture - SSLOLIB Traffic Matching Library 2 | ## Version: 2.2 3 | ## Date: 2021-07-19 4 | ## Author: Kevin Stewart, F5 Networks 5 | ## 6 | ## Updates: 7 | ## 2.0: Updated SNI:CAT re-uses existing category lookup results 8 | ## 2.1: Updated SNI:CAT test and don't fail if URLDB is not provisioned 9 | ## 2.2: Updated SNI:CAT update to support custom categories only when URLDB not provisioned 10 | ## 2.2: Added HOST proc 11 | ## 2.2: Added target_ssf proc 12 | ## 13 | ## Configuration: 14 | ## - Step 1: Import this SSLOLIB iRule (name "SSLOLIB") 15 | ## 16 | ## - Step 2: Build semi-static SSL Orchestrator topologies based on common actions (ex. allow, intercept, service chain) 17 | ## - Minimally create a normal "intercept" topology and a separate "bypass" topology 18 | ## Intercept topology: 19 | ## - L3 outbound topology configuration, normal topology settings, SSL config, services, service chain 20 | ## - No security policy rules - just a single ALL rule with TLS intercept action (and service chain) 21 | ## - Attach to a "dummy" VLAN 22 | ## Bypass topology: 23 | ## - L3 outbound topology configuration, skip SSL config, re-use services, service chains 24 | ## - No security policy rules - just a single ALL rule with TLS bypass action (and service chain) 25 | ## - Attached to a separate "dummy" VLAN 26 | ## - Create any additional topologies as required, as separate functions based on discrete actions (allow/block, intercept/bypass, service chain) 27 | ## 28 | ## - Step 3: Import the traffic switching iRule 29 | ## - Set necessary static configuration values in RULE_INIT as required 30 | ## - Define any URL category lists in RULE_INIT as required (see example). Use the following command to get a list of URL categories: 31 | ## tmsh list sys url-db url-category |grep "sys url-db url-category " |awk -F" " '{print $4}' 32 | ## 33 | ## - Step 4: Create a client-facing topology switching VIP 34 | ## Type: Standard 35 | ## Source: 0.0.0.0/0 36 | ## Destination: 0.0.0.0/0:0 37 | ## Protocol: TCP 38 | ## VLAN: client-facing VLAN 39 | ## Address/Port Translation: disabled 40 | ## Default Persistence Profile: ssl 41 | ## iRule: traffic switching iRule 42 | ## 43 | ## - Step 5: modify the traffic switching iRule with the required detection commands (below) 44 | ## 45 | ## 46 | ## Traffic selector commands (to be used in traffic switching iRule) 47 | ## - Call the "target" proc with the following parameters (, ${sni}, ) 48 | ## - is the base name of the defined topology 49 | ## - ${sni} is static here and returns the server name indication value (SNI) for logging 50 | ## - is any string message to send to the log (ex. which rule matched) 51 | ## - return is added at the end of each command to cancel any further matching 52 | ## - Example: 53 | ## call SSLOLIB::target "bypass" ${sni} "SRCIP" 54 | ## 55 | ## - Use the following commands to query the proc function for matches (all return true or false) 56 | ## The following commands run in CLIENTSSL_CLIENTHELLO to act on SSL traffic 57 | ## 58 | ## Source IP Detection (static IP, IP subnet, data group match) 59 | ## SRCIP IP: 60 | ## SRCIP DG: (address-type data group) 61 | ## if { [call SSLOLIB::SRCIP IP:10.1.0.0/16] } { call SSLOLIB::target "topology name" ${sni} "SRCIP" ; return } 62 | ## if { [call SSLOLIB::SRCIP DG:my_sip_list] } { call SSLOLIB::target "topology name" ${sni} "SRCIP" ; return } 63 | ## 64 | ## Source Port Detection (static port, port range, data group match) 65 | ## SRCPORT PORT: 66 | ## SRCPORT DG: (integer-type data group) 67 | ## if { [call SSLOLIB::SRCPORT PORT:15000] } { call SSLOLIB::target "topology name" ${sni} "SRCPORT" ; return } 68 | ## if { [call SSLOLIB::SRCPORT PORT:1000-60000] } { call SSLOLIB::target "topology name" ${sni} "SRCPORT" ; return } 69 | ## if { [call SSLOLIB::SRCPORT DG:my-sport-list] } { call SSLOLIB::target "topology name" ${sni} "SRCPORT" ; return } 70 | ## 71 | ## Destination IP Detection (static IP, IP subnet, data group match) 72 | ## DSTIP IP: 73 | ## DSTIP DG: (address-type data group) 74 | ## if { [call SSLOLIB::DSTIP IP:93.184.216.34] } { call SSLOLIB::target "topology name" ${sni} "DSTIP" ; return } 75 | ## if { [call SSLOLIB::DSTIP DG:my-dip-list] } { call SSLOLIB::target "topology name" ${sni} "DSTIP" ; return } 76 | ## 77 | ## Destination Port Detection (static port, port range, data group match) 78 | ## DSTPORT PORT: 79 | ## DSTPORT DG: (integer-type data group) 80 | ## if { [call SSLOLIB::DSTPORT PORT:443] } { call SSLOLIB::target "topology name" ${sni} "DSTPORT" ; return } 81 | ## if { [call SSLOLIB::DSTPORT PORT:1-1024] } { call SSLOLIB::target "topology name" ${sni} "DSTPORT" ; return } 82 | ## if { [call SSLOLIB::DSTPORT DG:my-dport-list] } { call SSLOLIB::target "topology name" ${sni} "DSTPORT" ; return } 83 | ## 84 | ## SNI Detection (static URL, category match, data group match) 85 | ## SNI URL: 86 | ## SNI URLGLOB: (ends_with match) 87 | ## if { [call SSLOLIB::SNI URL:www.example.com] } { call SSLOLIB::target "topology name" ${sni} "SNIURL" ; return } 88 | ## if { [call SSLOLIB::SNI URLGLOB:.example.com] } { call SSLOLIB::target "topology name" ${sni} "SNIURLGLOB" ; return } 89 | ## 90 | ## SNI CAT: 91 | ## if { [call SSLOLIB::SNI CAT:/Common/Financial_Data_and_Services] } { call SSLOLIB::target "topology name" ${sni} "SNICAT" ; return } 92 | ## if { [call SSLOLIB::SNI CAT:$static::URLCAT_Finance_Health] } { call SSLOLIB::target "topology name" ${sni} "SNICAT" ; return } 93 | ## 94 | ## SNI DG: (string-type data group) 95 | ## SNI DGGLOB: (ends_with match) 96 | ## if { [call SSLOLIB::SNI DG:my-sni-list] } { call SSLOLIB::target "topology name" ${sni} "SNIDG" ; return } 97 | ## if { [call SSLOLIB::SNI DGGLOB:my-sniglob-list] } { call SSLOLIB::target "topology name" ${sni} "SNIDGGLOB" ; return } 98 | ## 99 | ## Combinations: above selectors can be used in combinations as required. Example: 100 | ## if { ([call SSLOLIB::SRCIP IP:10.1.0.0/16]) and ([call SSLOLIB::DSTIP IP:93.184.216.34]) } 101 | ## 102 | ## 103 | ## - It is also possible to add an HTTP_REQUEST event and (client) HTTP profile to act on the HTTP Host header of unencrypted HTTP requests 104 | ## 105 | ## HOST Detection (static URL, category match, data group match) 106 | ## HOST URL: 107 | ## HOST URLGLOB: (ends_with match) 108 | ## if { [call SSLOLIB::HOST URL:www.example.com] } { call SSLOLIB::target "topology name" ${host} "HOSTURL" ; return } 109 | ## if { [call SSLOLIB::HOST URLGLOB:.example.com] } { call SSLOLIB::target "topology name" ${host} "HOSTURLGLOB" ; return } 110 | ## 111 | ## HOST CAT: 112 | ## if { [call SSLOLIB::HOST CAT:/Common/Financial_Data_and_Services] } { call SSLOLIB::target "topology name" ${host} "HOSTCAT" ; return } 113 | ## if { [call SSLOLIB::HOST CAT:$static::URLCAT_Finance_Health] } { call SSLOLIB::target "topology name" ${host} "HOSTCAT" ; return } 114 | ## 115 | ## HOST DG: (string-type data group) 116 | ## HOST DGGLOB: (ends_with match) 117 | ## if { [call SSLOLIB::HOST DG:my-sni-list] } { call SSLOLIB::target "topology name" ${host} "HOSTDG" ; return } 118 | ## if { [call SSLOLIB::HOST DGGLOB:my-sniglob-list] } { call SSLOLIB::target "topology name" ${host} "HOSTDGGLOB" ; return } 119 | 120 | ## DO NOT MODIFY BELOW ## 121 | ## SSLOLIB library functions perform error detection and will return 0 (false) in any reasonable error condition (ex. incorrect data group name). 122 | 123 | proc errorlog {message} { 124 | if { $static::SSLODEBUG } { 125 | log -noname local0. "SSLOLIB Error :: [IP::client_addr]:[TCP::client_port] -> [IP::local_addr]:[TCP::local_port] :: ${message}" 126 | } 127 | } 128 | proc target {topology {sni ""} {message ""}} { 129 | sharedvar SNI 130 | set SNI ${sni} 131 | virtual "/Common/sslo_${topology}.app/sslo_${topology}-in-t-4" 132 | if { ( $static::SSLODEBUG ) and ( ${sni} ne "" ) and ( ${message} ne "" ) } { 133 | log -noname local0. "SSLO Switch Log :: [IP::client_addr]:[TCP::client_port] -> [IP::local_addr]:[TCP::local_port] :: (${sni}) :: Match on ${message} :: Sending to ${topology}" 134 | } 135 | } 136 | proc target_ssf {topology {message ""}} { 137 | virtual ${topology} 138 | if { ( $static::SSLODEBUG ) and ( ${message} ne "" ) } { 139 | log -noname local0. "SSLO Switch Log :: [IP::client_addr]:[TCP::client_port] -> [IP::local_addr]:[TCP::local_port] :: Match on ${message} :: Sending to ${topology}" 140 | } 141 | } 142 | proc SRCIP { arg } { 143 | set arglist [split ${arg} ":"] 144 | switch -- [lindex ${arglist} 0] { 145 | "IP" { if { [catch { set res [expr { [IP::addr [lindex ${arglist} 1] equals [IP::client_addr]] } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 146 | "DG" { if { [catch { set res [expr { [class match -- [IP::client_addr] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 147 | } 148 | } 149 | proc SRCPORT { arg } { 150 | set arglist [split ${arg} ":"] 151 | switch -- [lindex ${arglist} 0] { 152 | "PORT" { if { [catch { if { [lindex ${arglist} 1] contains "-" } { set portlist [split [lindex ${arglist} 1] "-"] ; set res [expr { ( [TCP::client_port] > [lindex ${portlist} 0] ) and ( [TCP::client_port] < [lindex ${portlist} 1] ) } ? 1 : 0] } else {set res [expr { [lindex ${arglist} 1] == [TCP::client_port] } ? 1 : 0] }} err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 153 | "DG" { if { [catch { set res [expr { [class match -- [TCP::client_port] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 154 | } 155 | } 156 | proc DSTIP { arg } { 157 | set arglist [split ${arg} ":"] 158 | switch -- [lindex ${arglist} 0] { 159 | "IP" { if { [catch { set res [expr { [IP::addr [lindex ${arglist} 1] equals [IP::local_addr]] } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 160 | "DG" { if { [catch { set res [expr { [class match -- [IP::local_addr] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 161 | } 162 | } 163 | proc DSTPORT { arg } { 164 | set arglist [split ${arg} ":"] 165 | switch -- [lindex ${arglist} 0] { 166 | "PORT" { if { [catch { if { [lindex ${arglist} 1] contains "-" } { set portlist [split [lindex ${arglist} 1] "-"] ; set res [expr { ( [TCP::local_port] > [lindex ${portlist} 0] ) and ( [TCP::local_port] < [lindex ${portlist} 1] ) } ? 1 : 0] } else {set res [expr { [lindex ${arglist} 1] == [TCP::local_port] } ? 1 : 0] }} err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 167 | "DG" { if { [catch { set res [expr { [class match -- [TCP::local_port] equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 168 | } 169 | } 170 | proc HOST { arg } { 171 | upvar host s_host 172 | upvar cat catg 173 | set s_host [HTTP::host] 174 | if { ${s_host} != "" } { 175 | set arglist [split ${arg} ":"] 176 | switch -- [lindex ${arglist} 0] { 177 | "URL" { return [expr { ${s_host} eq [lindex ${arglist} 1] } ? 1 : 0] } 178 | "URLGLOB" { return [expr { ${s_host} ends_with [lindex ${arglist} 1] } ? 1 : 0] } 179 | "CAT" { if { [info exists catg] } { if { [catch { set match 0 ; foreach cat ${catg} { if { [lsearch -exact [lindex ${arglist} 1] ${cat}] >= 0 } { set match 1 }} ; set res [expr { ${match} } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } else { set URLF "CATEGORY\x3a\x3alookup" ; set test [catch "${URLF} https://${s_host}/ request_default_and_custom" catg] ; if { ${catg} contains "Categorization engine returned an error" } { set test [catch "${URLF} https://${s_host}/ custom" catg] } ; if { ![llength ${catg}] } { return 0 } else { if { [catch { set match 0 ; foreach cat ${catg} { if { [lsearch -exact [lindex ${arglist} 1] ${cat}] >= 0 } { set match 1 } } ; set res [expr { ${match} } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} }}}} 180 | "DG" { if { [catch { set res [expr { [class match -- ${s_host} equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 181 | "DGGLOB" { if { [catch { set res [expr { [class match -- ${s_host} ends_with [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 182 | } 183 | } else { return 0 } 184 | } 185 | proc SNI { arg } { 186 | upvar sni s_sni 187 | upvar cat catg 188 | set s_sni "NULL" 189 | set sni_exists [SSL::extensions exists -type 0] 190 | if { $sni_exists } { 191 | binary scan [SSL::extensions -type 0] @9a* s_sni 192 | set arglist [split ${arg} ":"] 193 | switch -- [lindex ${arglist} 0] { 194 | "URL" { return [expr { ${s_sni} eq [lindex ${arglist} 1] } ? 1 : 0] } 195 | "URLGLOB" { return [expr { ${s_sni} ends_with [lindex ${arglist} 1] } ? 1 : 0] } 196 | "CAT" { if { [info exists catg] } { if { [catch { set match 0 ; foreach cat ${catg} { if { [lsearch -exact [lindex ${arglist} 1] ${cat}] >= 0 } { set match 1 }} ; set res [expr { ${match} } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } else { set URLF "CATEGORY\x3a\x3alookup" ; set test [catch "${URLF} https://${s_sni}/ request_default_and_custom" catg] ; if { ${catg} contains "Categorization engine returned an error" } { set test [catch "${URLF} https://${s_sni}/ custom" catg] } ; if { ![llength ${catg}] } { return 0 } else { if { [catch { set match 0 ; foreach cat ${catg} { if { [lsearch -exact [lindex ${arglist} 1] ${cat}] >= 0 } { set match 1 } } ; set res [expr { ${match} } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} }}}} 197 | "DG" { if { [catch { set res [expr { [class match -- ${s_sni} equals [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 198 | "DGGLOB" { if { [catch { set res [expr { [class match -- ${s_sni} ends_with [lindex ${arglist} 1]] } ? 1 : 0] } err] } { call SSLOLIB::errorlog ${err} ; return 0 } else { return ${res} } } 199 | } 200 | } else { return 0 } 201 | } 202 | -------------------------------------------------------------------------------- /internal-layered-architecture/transparent-proxy/pif-explicit-proxy-rule: -------------------------------------------------------------------------------- 1 | when HTTP_PROXY_REQUEST { 2 | sharedvar exphost 3 | sharedvar expproto 4 | 5 | ## parse incoming request URL 6 | if { [HTTP::uri] starts_with "http://" } { 7 | set exphost [findstr [findstr [HTTP::uri] "http://" 7] "" 0 "/"] 8 | set expproto "http" 9 | } else { 10 | set exphost [findstr [HTTP::uri] "" 0 ":"] 11 | set expproto "https" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /internal-layered-architecture/transparent-proxy/pif-sslo-layering-rule: -------------------------------------------------------------------------------- 1 | ## SSL Orchestrator Use Case: Internal Layered Architecture - Client-Facing Switching iRule (proxy-in-front using HTTP proxy URL) 2 | ## Version: 1.0 3 | ## Date: 2021-08-16 4 | ## Author: Kevin Stewart, F5 Networks 5 | ## Configuration: see PIFSSLOLIB iRule for build instructions 6 | ## 7 | ## Description: A variation of the proxy-in-front layering use case, where a manually defined LTM explicit proxy ise configured in front of the 8 | ## internal SSLO instances, and the steering virtual uses the HTTP proxy request URL for steering rules (vs. the TLS SNI). 9 | ## 10 | when RULE_INIT { 11 | ## User-defined: DEBUG logging flag (1=on, 0=off) 12 | set static::SSLODEBUG 1 13 | 14 | ## User-defined: Default topology if no rules match (the topology name as defined in SSLO) 15 | set static::default_topology "intercept" 16 | 17 | ## User-defined: URL category list (create as many lists as required) 18 | set static::URLCAT_Finance_Health { 19 | /Common/Financial_Data_and_Services 20 | /Common/Health_and_Medicine 21 | } 22 | } 23 | when CLIENT_ACCEPTED { 24 | #### DO NOT EDIT #### 25 | set cmd "catch { HTTP::disable }" ; eval ${cmd} 26 | sharedvar exphost ; sharedvar expproto 27 | ##################### 28 | 29 | 30 | ## Set a default topology assuming no other matches 31 | call PIFSSLOLIB::target $static::default_topology 32 | 33 | ## Standard certificate Pinners bypass rule (specify your bypass topology) 34 | if { [call PIFSSLOLIB::HOST CAT:/Common/sslo-urlCatPinners] } { call PIFSSLOLIB::target "bypass" ${exphost} "pinners" ; return} 35 | 36 | 37 | ################################# 38 | #### HOST CONDITIONS GO HERE #### 39 | ################################# 40 | #if { [call PIFSSLOLIB::SRCIP IP:10.1.0.0/16] } { call PIFSSLOLIB::target "bypass" ${exphost} "SRCIP" ; return } 41 | #if { [call PIFSSLOLIB::SRCIP DG:my-srcip-dg] } { call PIFSSLOLIB::target "bypass" ${exphost} "SRCIP" ; return } 42 | 43 | #if { [call PIFSLOLIB::SRCPORT PORT:5000] } { call PIFSSLOLIB::target "bypass" ${exphost} "SRCPORT" ; return } 44 | #if { [call PIFSSLOLIB::SRCPORT PORT:1000-60000] } { call PIFSSLOLIB::target "bypass" ${exphost} "SRCPORT" ; return } 45 | 46 | #if { [call PIFSSLOLIB::DSTIP IP:93.184.216.34] } { call PIFSSLOLIB::target "bypass" ${exphost} "DSTIP" ; return } 47 | #if { [call PIFSSLOLIB::DSTIP DG:my-destip-dg] } { call PIFSSLOLIB::target "bypass" ${exphost} "DSTIP" ; return } 48 | 49 | #if { [call PIFSSLOLIB::DSTPORT PORT:443] } { call PIFSSLOLIB::target "bypass" ${exphost} "DSTPORT" ; return } 50 | #if { [call PIFSSLOLIB::DSTPORT PORT:443-9999] } { call PIFSSLOLIB::target "bypass" ${exphost} "DSTPORT" ; return } 51 | 52 | #if { [call PIFSSLOLIB::HOST URL:www.example.com] } { call PIFSSLOLIB::target "bypass" ${exphost} "HOSTURL" ; return } 53 | #if { [call PIFSSLOLIB::HOST URLGLOB:.example.com] } { call PIFSSLOLIB::target "bypass" ${exphost} "HOSTURLGLOB" ; return } 54 | 55 | #if { [call PIFSSLOLIB::HOST CAT:$static::URLCAT_Finance_Health] } { call PIFSSLOLIB::target "bypass" ${exphost} "HOSTCAT" ; return } 56 | #if { [call PIFSSLOLIB::HOST CAT:/Common/Financial_Data_and_Services] } { call PIFSSLOLIB::target "bypass" ${exphost} "HOSTCAT" ; return } 57 | #if { [call PIFSSLOLIB::HOST CAT:/Common/testcat] } { call PIFSSLOLIB::target "bypass" ${exphost} "HOSTCAT" ; return } 58 | 59 | #if { [call PIFSSLOLIB::HOST DG:my-sni-dg] } { call PIFSSLOLIB::target "bypass" ${exphost} "HOSTDGGLOB" ; return } 60 | #if { [call PIFSSLOLIB::HOST DGGLOB:my-sniglob-dg] } { call PIFSSLOLIB::target "bypass" ${exphost} "HOSTDGGLOB" ; return } 61 | 62 | ## To combine these, you can use smple AND|OR logic: 63 | #if { ( [call PIFSSLOLIB::DSTIP DG:my-destip-dg] ) and ( [call PIFSSLOLIB::SRCIP DG:my-srcip-dg] ) } { call PIFSSLOLIB::target "bypass" ${exphost} "HOSTDGGLOB" ; return } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /internal-layered-architecture/transparent-proxy/sslo-layering-rule: -------------------------------------------------------------------------------- 1 | ## SSL Orchestrator Use Case: Internal Layered Architecture - Client-Facing Switching iRule 2 | ## Version: 2.3 3 | ## Date: 2024-10-18 4 | ## Author: Kevin Stewart, F5 Networks 5 | ## Configuration: see SSLOLIB iRule for build instructions 6 | 7 | ## Set a default topology assuming no other matches (do not edit) 8 | when CLIENT_ACCEPTED { 9 | call SSLOLIB::target $static::default_topology 10 | } 11 | 12 | 13 | ##################################################### 14 | ## EDIT BELOW THIS BLOCK ############################ 15 | ##################################################### 16 | when RULE_INIT { 17 | ## User-defined: DEBUG logging flag (1=on, 0=off) 18 | set static::SSLODEBUG 0 19 | 20 | ## User-defined: Default topology if no rules match (the topology name as defined in SSLO) 21 | set static::default_topology "intercept" 22 | 23 | ## User-defined: URL category list (create as many lists as required) 24 | set static::URLCAT_Finance_Health { 25 | /Common/Financial_Data_and_Services 26 | /Common/Health_and_Medicine 27 | } 28 | } 29 | when CLIENTSSL_CLIENTHELLO { 30 | ## Do not edit 31 | set cmd "catch { HTTP::disable }" ; eval ${cmd} 32 | 33 | ## Do not edit - create sni variable and set from TLS client hello (for logging) 34 | set sni ""; call SSLOLIB::SNI "" 35 | 36 | ## Standard certificate Pinners bypass rule (specify your bypass topology) 37 | if { [call SSLOLIB::SNI CAT:/Common/sslo-urlCatPinners] } { call SSLOLIB::target "bypass" ${sni} "pinners" ; return} 38 | 39 | 40 | ################################ 41 | #### SNI CONDITIONS GO HERE #### 42 | ################################ 43 | #if { [call SSLOLIB::SRCIP IP:10.1.0.0/16] } { call SSLOLIB::target "bypass" ${sni} "SRCIP" ; return } 44 | #if { [call SSLOLIB::SRCIP DG:my-srcip-dg] } { call SSLOLIB::target "bypass" ${sni} "SRCIP" ; return } 45 | 46 | #if { [call SSLOLIB::SRCPORT PORT:5000] } { call SSLOLIB::target "bypass" ${sni} "SRCPORT" ; return } 47 | #if { [call SSLOLIB::SRCPORT PORT:1000-60000] } { call SSLOLIB::target "bypass" ${sni} "SRCPORT" ; return } 48 | 49 | #if { [call SSLOLIB::DSTIP IP:93.184.216.34] } { call SSLOLIB::target "bypass" ${sni} "DSTIP" ; return } 50 | #if { [call SSLOLIB::DSTIP DG:my-destip-dg] } { call SSLOLIB::target "bypass" ${sni} "DSTIP" ; return } 51 | 52 | #if { [call SSLOLIB::DSTPORT PORT:443] } { call SSLOLIB::target "bypass" ${sni} "DSTPORT" ; return } 53 | #if { [call SSLOLIB::DSTPORT PORT:443-9999] } { call SSLOLIB::target "bypass" ${sni} "DSTPORT" ; return } 54 | 55 | #if { [call SSLOLIB::SNI URL:www.example.com] } { call SSLOLIB::target "bypass" ${sni} "SNIURLGLOB" ; return } 56 | #if { [call SSLOLIB::SNI URLGLOB:.example.com] } { call SSLOLIB::target "bypass" ${sni} "SNIURLGLOB" ; return } 57 | 58 | #if { [call SSLOLIB::SNI CAT:$static::URLCAT_Finance_Health] } { call SSLOLIB::target "bypass" ${sni} "SNICAT" ; return } 59 | #if { [call SSLOLIB::SNI CAT:/Common/Financial_Data_and_Services] } { call SSLOLIB::target "bypass" ${sni} "SNICAT" ; return } 60 | 61 | #if { [call SSLOLIB::SNI DG:my-sni-dg] } { call SSLOLIB::target "bypass" ${sni} "SNIDGGLOB" ; return } 62 | #if { [call SSLOLIB::SNI DGGLOB:my-sniglob-dg] } { call SSLOLIB::target "bypass" ${sni} "SNIDGGLOB" ; return } 63 | 64 | ## To combine these, you can use smple AND|OR logic: 65 | #if { ( [call SSLOLIB::DSTIP DG:my-destip-dg] ) and ( [call SSLOLIB::SRCIP DG:my-srcip-dg] ) } { call SSLOLIB::target "bypass" ${sni} "SNIDGGLOB" ; return } 66 | } 67 | -------------------------------------------------------------------------------- /internal-layered-architecture/transparent-proxy/use-case-encrypted-traffic-through-http-service/README.md: -------------------------------------------------------------------------------- 1 | # F5 SSL Orchestrator Layered Architecture Configuration 2 | # Transparent Proxy Configuration Use Cases 3 | This section defines use cases specific to a transparent forward proxy implementation using the layered architecture 4 | 5 | ## Encrypted Traffic to an HTTP Proxy Service: 6 | SSL Orchestrator employs "flow signaling" to maintain traffic context through the dynamic service chain. As a flow leaves the BIG-IP for an inline security service, its flow information is recorded (src+dst IP:port) so that when it returns from the service, context can be re-established. An inline HTTP proxy service, however, will always minimally change the source port, thus breaking the flow signal. For this reason, and to support HTTP proxy services in the dynamic service chain, SSL Orchestrator uses an HTTP header signal through this device type. This also requires that signaling through a proxy service can only happen for unencrypted/decrypted HTTP traffic. As an HTTP header cannot be injected into TLS bypassed connections, SSL Orchestrator will bypass any proxy service in the service chain for TLS bypassed traffic. 7 | 8 | If at all possible, configure the HTTP proxy security service as a transparent proxy device to alleviate complexity. In this case, the egress loop to this service is a simple routed path. The below guidance is **ONLY** needed if the HTTP proxy service must be in explicit proxy mode. There is no direct support for SSL Orchestrator in transparent proxy mode to egress to an explicit proxy, so this use case consolidates the "explicit proxy egress" use case (detailed separately) to build a "shim" to convert egress routed traffic to an explicit proxy connection. TLS bypass topology traffic has a "client" iRule attached that overrides the normal egress routing mechanism and VIP targets the re-encrypted traffic to a "shim" virtual server. This shim VIP has a "server" iRule that converts the routed traffic to an explicit proxy connection, which then directs that traffic back to the HTTP proxy security service inside the SSL Orchestrator service chain. Traffic leaving the HTTP proxy service is consumed by a control channel virtual server listening on the HTTP proxy service's "from-service" VLAN and directs that out to the Internet. 9 | 10 | ![SSL Orchestrator Internal Layered Architecture](../../images/sslo-encrypted-traffic-to-proxy.png) 11 | 12 | Traffic destined for TLS interception is steered to an intercept topology by the layered virtual server, and then egresses the normal routed path. Decrypted traffic to the service chain will flow through all of the defined services here, including the proxy service. Traffic destined for TLS bypass is steered to the bypass topology, which is then configured to proxy chain to the proxy service, looping back into the service chain. The SSL Orchestrator service chain would not accept this traffic, so a separate "control channel" virtual catches any HTTPS (port 443) traffic leaving the proxy service, directing that to the routed egress path. There are two methods for handling this use case, depending on the mode of the proxy security service: **explicit** or **transparent** proxy. 13 | 14 | Note again that the configuration for a transparent proxy security service is considerably easier here, and recommended. Given that the proxy security service is inside the SSL Orchestrator inspection zone, there is generally little benefit to an explicit proxy configuration on that device. 15 | 16 | _______________________________________ 17 | 18 | ## Encrypted Traffic to an HTTP Transparent Proxy Service: 19 | First, note that a transparent proxy will usually behave exactly the same as an explicit proxy, except that instead of directing traffic at the proxy listener IP:port, traffic is *routed* through the device. Under SSL Orchestrator this also behaves more or less the same way that an inline layer 3 service works, except that a proxy (explicit or transparent) will almost always change parts of the connection tuple, thus requires proxy-based signaling. In any case, the steps to configure this use case are as follows: 20 | 21 | - Deploy an Internal Layered Architecture configuration 22 | - Create any SSL Orchestrator TLS intercept topologies as required, define services and service chains 23 | - Create a TLS bypass topology and assign the proxy service pool as its gateway 24 | - Create a service control channel virtual server 25 | 26 | ### Deploy an Internal Layered Architecture configuration 27 | - Use the transparent proxy configuration, as detailed on the main page, to establish a transparent proxy internal layered architecture. 28 | 29 | ### Create any SSL Orchestrator TLS intercept topologies as required, define services and service chains 30 | - In the SSL Orchestrator UI, create any internal TLS intercept topologies as required. Define the services and service chains here. 31 | 32 | ### Create a TLS bypass topology and configure Proxy Connect to the proxy service 33 | - In the SSL Orchestrator UI, create a TLS bypass topology. On the Egress Settings page, select the proxy service pool as the gateway. Disable SNAT as this will be done at the service control channel virtual server. 34 | 35 | ### Create a service control channel virtual server 36 | - Under Local Traffic -> Virtual Servers, create a new virtual server: 37 | - Source: 0.0.0.0/0 38 | - Destination: 0.0.0.0/0 39 | - Port: 443 40 | - VLAN: enable and select the proxy service's "from-service" VLAN as defined in the SSL Orchestrator service configuration 41 | - Address Translation: disabled 42 | - Port Translation: disabled 43 | - Pool: select an existing or create a new pool that points to the routed gateway (the same that the TLS intercept topology uses) 44 | 45 | In this scenario, traffic directed to the TLS Bypass topology will egress from that topology and loop back around (encrypted) to the proxy service in the service chain. This is routed traffic so the proxy service would be configured as a transparent proxy. Its gateway is to the same BIG-IP return VLAN for all traffic, so the service control channel virtual server collects any port 443 traffic on that VLAN and directs that to the nexthop Internet gateway. 46 | 47 | Note that the control channel virtual server listens on the proxy service's "from-service" VLAN. SSL Orchestrator defines a wildcard (0.0.0.0/0:0) virtual server on this same VLAN, so the control channel listens on a **more specific** traffic flow (port 443). Any traffic leaving the HTTP proxy service inside the service chain of a TLS intercept topology would normally be HTTP port 80. You don't have to use a specific port here though. In fact, if your proxy device is capable of enabling and disabling source address translation (i.e. SNAT, or "client IP reflection") based on local policy, you could configure that proxy device to SNAT (use its own IP for HTTPS traffic), and not SNAT (enable client IP reflection) for HTTP traffic. In this case, the control channel virtual server could be configured to instead listen on the proxy service's self-IP as the source of traffic. This would also be useful for allowing the HTTP proxy service to reach out to Internet sites (from its own IP address), for example to reach a licensing or subscription update service. 48 | 49 | _______________________________________ 50 | 51 | ## Encrypted Traffic to an HTTP Explicit Proxy Service: 52 | The steps to configure this are as follows: 53 | 54 | - Deploy an Internal Layered Architecture "Transparent Forward Proxy" configuration 55 | - Create any SSL Orchestrator TLS intercept topologies as required, define services and service chains, and normal routed egress 56 | - Create a new proxy service pool 57 | - Create a "client" iRule that will be attached to an SSL Orchestrator bypass topology. This iRule will override normal outbound routing and force traffic to a "shim" virtual server. 58 | - Create a "server" iRule that will attached to the shim virtual server. This iRule will handle the conversion of encrypted routed traffic to an explicit proxy communication. 59 | - Create a "shim" virtual server that will sit between an SSL Orchestrator egress path and the upstream explicit proxy. 60 | - Create a TLS bypass topology and attach the client iRule 61 | - Create a service control channel virtual server 62 | 63 | ### Deploy an Internal Layered Architecture "Transparent Forward Proxy" configuration 64 | - Use the "Transparent Forward Proxy" configuration, as detailed on the main page, to establish a transparent proxy internal layered architecture. 65 | 66 | ### Create any SSL Orchestrator TLS intercept topologies as required, define services and service chains 67 | - In the SSL Orchestrator UI, create any internal TLS intercept topologies as required. Define the services and service chains here, and normal routed egress. 68 | 69 | ### Create a new proxy service pool 70 | - Under Local Traffic -> Pools, create a new pool that points to the explicit proxy service listener IP:port. This will be the same IP and port defined for the proxy service in the SSL Orchestrator service configuration. 71 | 72 | ### Create a "client" iRule that will be attached to an SSL Orchestrator bypass topology. This iRule will override normal outbound routing and force traffic to a "shim" virtual server. 73 | - Under Local Traffic -> iRules, click Create and import the client-rule under the **use-case-transparent-to-explicit-egress** folder. 74 | - In the RULE_INIT section of the iRule, change the static::PROXY_CHAIN_VIP value to point to the shim virtual server name. 75 | 76 | ### Create a "server" iRule that will attached to the shim virtual server. This iRule will handle the conversion of encrypted routed traffic to an explicit proxy communication. 77 | - Under Local Traffic -> iRules, click Create and import the server-rule under the **use-case-transparent-to-explicit-egress** folder. 78 | 79 | ### Create a "shim" virtual server that will sit between an SSL Orchestrator egress path and the upstream explicit proxy. 80 | - Under Local Traffic -> Virtual Servers, create a virtual server: 81 | - Source: 0.0.0.0/0 82 | - Destination: 0.0.0.0/0 83 | - Service Port: 0 84 | - HTTP Profile: http 85 | - VLAN: enabled and nothing selected 86 | - SNAT: enable as required to communicate with the upstream proxy 87 | - Address Translation: enabled 88 | - Port Translation: enabled 89 | - Pool: upstream proxy pool 90 | - iRule: Select the Server iRule 91 | 92 | ### Create a TLS bypass topology and attach the client iRule 93 | - In the SSL Orchestrator UI, create a TLS bypass topology. After deploying, edit the corresponding "-in-t-" interception rule and add the client iRule. 94 | 95 | ### Create a service control channel virtual server 96 | - Under Local Traffic -> Virtual Servers, create a new virtual server: 97 | - Source: 0.0.0.0/0 98 | - Destination: 0.0.0.0/0 99 | - Port: 443 100 | - VLAN: enable and select the proxy service's "from-service" VLAN as defined in the SSL Orchestrator service configuration 101 | - Address Translation: disabled 102 | - Port Translation: disabled 103 | - Pool: select an existing or create a new pool that points to the routed gateway (the same that the TLS intercept topology uses) 104 | 105 | In this scenario, traffic directed to the TLS Bypass topology will egress from that topology and loop back around (encrypted) to the proxy service in the service chain. As the HTTP service is an explicit proxy, the TLS Bypass topology must be configure to proxy chain. Note however that there is no direct support for transparent-to-explicit proxy chaining, so a SHIM virtual server and iRule are required to convert the egress routed traffic to the required explicit proxy format. The proxy service's gateway is to the same BIG-IP return VLAN for all traffic, so the service control channel virtual server collects any port 443 traffic on that VLAN and directs that to the nexthop Internet gateway. 106 | 107 | Note that the control channel virtual server listens on the proxy service's "from-service" VLAN. SSL Orchestrator defines a wildcard (0.0.0.0/0:0) virtual server on this same VLAN, so the control channel listens on a **more specific** traffic flow (port 443). Any traffic leaving the HTTP proxy service inside the service chain of a TLS intercept topology would normally be HTTP port 80. You don't have to use a specific port here though. In fact, if your proxy device is capable of enabling and disabling source address translation (i.e. SNAT, or "client IP reflection") based on local policy, you could configure that proxy device to SNAT (use its own IP for HTTPS traffic), and not SNAT (enable client IP reflection) for HTTP traffic. In this case, the control channel virtual server could be configured to instead listen on the proxy service's self-IP as the source of traffic. This would also be useful for allowing the HTTP proxy service to reach out to Internet sites (from its own IP address), for example to reach a licensing or subscription update service. 108 | -------------------------------------------------------------------------------- /internal-layered-architecture/transparent-proxy/use-case-transparent-to-explicit-egress/README.md: -------------------------------------------------------------------------------- 1 | # F5 SSL Orchestrator Layered Architecture Configuration 2 | # Transparent Proxy Configuration Use Cases 3 | This section defines use cases specific to a transparent forward proxy implementation using the layered architecture 4 | 5 | ## Transparent to Explicit Proxy Egress: 6 | In this scenario, clients egress through a transparent proxy solution but may need some traffic leaving SSL Orchestrator to flow through an upstream explicit proxy gateway. This requires special handling to convert the outbound routed traffic to an explicit proxy flow. The steps are as follows: 7 | 8 | - Create a "client" iRule that will be attached to an SSL Orchestrator topology. This iRule will override normal outbound routing and force traffic to a "shim" virtual server. 9 | - Create a "server" iRule that will attached to the shim virtual server. This iRule will handle the conversion of encrypted routed traffic to an explicit proxy communication. 10 | - Create a "shim" virtual server that will sit between an SSL Orchestrator egress path and the upstream explicit proxy. 11 | 12 | ![SSL Orchestrator Internal Layered Architecture](../../images/sslo-tp-to-ep-layered.png) 13 | 14 | ### Create the Client iRule 15 | - Under Local Traffic -> iRules, click Create and import the **client-rule** under the **use-case-transparent-to-explicit-egress** folder. 16 | - In the RULE_INIT section of the iRule, change the static::PROXY_CHAIN_VIP value to point to the shim virtual server name. 17 | 18 | 19 | ### Create the Server iRule 20 | - Under Local Traffic -> iRules, click Create and import the **server-rule** under the **use-case-transparent-to-explicit-egress** folder. 21 | 22 | 23 | ### Create the upstream proxy pool 24 | - Under Local Traffic -> Pools, create a pool that points to the upstream proxy resource. 25 | 26 | 27 | ### Create the Shim Virtual Server 28 | - Under Local Traffic -> Virtual Servers, create a virtual server: 29 | - Source: 0.0.0.0/0 30 | - Destination: 0.0.0.0/0 31 | - Service Port: 0 32 | - HTTP Profile: http 33 | - VLAN: enabled and nothing selected 34 | - SNAT: enable as required to communicate with the upstream proxy 35 | - Address Translation: enabled 36 | - Port Translation: enabled 37 | - Pool: upstream proxy pool 38 | - iRule: Select the Server iRule 39 | 40 | 41 | ### Add the client rule to an SSL Orchestrator topology 42 | - In the SSL Orchestrator UI, under the Interception Rules tab, select the "-in-t-" interception rule corresponding to the internal topology that requires explicit proxy egress. At the bottom of this page, select the **Client** iRule, then re-deploy. 43 | 44 | 45 | ### Create internal SSL Orchestrator topologies and configure the layered steering policy as required 46 | - Using this Internal Layered Architecture design, create any number of internal SSL Orchestrator topologies. Example: 47 | - intercept_direct (TLS intercept and routed egress) 48 | - intercept_proxy (TLS intercept and upstream proxy egress) 49 | - bypass_direct (TLS bypass and routed egress) 50 | - bypass_proxy (TLS bypass and upstream proxy egress) 51 | - Define the steering policy iRule to send traffic to one of the above topologies as required. Example: 52 | 53 | ``` 54 | when RULE_INIT { 55 | ## User-defined: DEBUG logging flag (1=on, 0=off) 56 | set static::SSLODEBUG 0 57 | 58 | ## User-defined: Default topology if no rules match (the topology name as defined in SSLO) 59 | set static::default_topology "intercept_proxy" 60 | 61 | ## User-defined: URL category list (create as many lists as required) 62 | set static::URLCAT_Finance_Health { 63 | /Common/Financial_Data_and_Services 64 | /Common/Health_and_Medicine 65 | } 66 | } 67 | when CLIENTSSL_CLIENTHELLO { 68 | 69 | ## set local sni variable (for logging) 70 | set sni "" 71 | 72 | ## Standard certificate Pinners bypass rule (specify your bypass topology) 73 | if { [call SSLOLIB::SNI CAT:/Common/sslo-urlCatPinners] } { call SSLOLIB::target "intercept_direct" ${sni} "pinners" ; return} 74 | 75 | ################################ 76 | #### SNI CONDITIONS GO HERE #### 77 | ################################ 78 | if { [call SSLOLIB::SNI CAT:$static::URLCAT_Finance_Health] } { call SSLOLIB::target "bypass_proxy" ${sni} "SNICAT" ; return } 79 | } 80 | ``` 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /internal-layered-architecture/transparent-proxy/use-case-transparent-to-explicit-egress/client-rule.tcl: -------------------------------------------------------------------------------- 1 | when RULE_INIT { 2 | ## USer-Defined: dynamic egress server-side layered VIP name 3 | set static::PROXY_CHAIN_VIP "/Common/upstream-proxy-vip" 4 | } 5 | 6 | 7 | ################################################### 8 | ######## NO CUSTOMIZATION BELOW THIS LINE ######### 9 | ################################################### 10 | 11 | when CLIENT_ACCEPTED priority 250 { 12 | ## DYNAMIC EGRESS UPDATE: disable corresponding -in-t event - the following otherwise mimics the original -in-t iRule except for necessary changes 13 | event disable 14 | 15 | sharedvar SNI 16 | sharedvar SEND_SNI 17 | if { [info exists SNI] } { 18 | set SEND_SNI ${SNI} 19 | } 20 | 21 | ## DYNAMIC EGRESS UPDATE: get the SSLO topology name 22 | set APP [findstr [virtual name] "/Common/sslo_" 13 ".app"] 23 | 24 | ## DYNAMIC EGRESS UPDATE: set a static outbound path 25 | virtual $static::PROXY_CHAIN_VIP 26 | 27 | SSL::disable clientside 28 | SSL::disable serverside 29 | HTTP::disable 30 | 31 | sharedvar ctx 32 | 33 | set ctx(log) 0 34 | set srcIP [IP::client_addr] 35 | set dstIP [IP::local_addr] 36 | set srcPort [TCP::client_port] 37 | set dstPort [TCP::local_port] 38 | set ctx(SNI) "" 39 | set ctx(ptcl) "unknown" 40 | set ctx(xpinfo) "" 41 | 42 | sharedvar XPHOST 43 | if { [info exists XPHOST] } { 44 | if { $XPHOST eq "" } { 45 | call /Common/sslo_${APP}.app/sslo_${APP}-lib::log 0 "CLIENT_ACCEPTED invalid host (${XPHOST}) for explicit-proxy client ${srcIP}_${srcPort}" 46 | TCP::respond "HTTP/1.1 500 Server Error\r\nConnection: close\r\n\r\n" 47 | TCP::close 48 | return 49 | } 50 | 51 | if {$ctx(log)} { 52 | set ctx(xpinfo) "\x20explicit-proxy request ${XPHOST}" 53 | } 54 | 55 | set ctx(ptcl) "http" 56 | } else { 57 | # maintain the next two lists in lockstep (!) 58 | if {[set x [lsearch -integer -sorted [list 21 22 25 53 80 110 115 143 443 465 587 990 993 995 3128 8080] [TCP::local_port]]] >= 0} { 59 | set ctx(ptcl) [lindex [list "ftp" "ssh" "smtp" "dns" "http" "pop3" "sftp" "imap" "https" "smtps" "smtp" "ftps" "imaps" "pop3s" "http" "http"] $x] 60 | } 61 | } 62 | 63 | if {$ctx(log) > 1} { 64 | call /Common/sslo_${APP}.app/sslo_${APP}-lib::log 2 "CLIENT_ACCEPTED TCP from ${srcIP}_${srcPort} to ${dstIP}_${dstPort}${ctx(xpinfo)} L7 guess=$ctx(ptcl)" 65 | } 66 | 67 | ## DYNAMIC EGRESS UPDATE: disable SSF detection 68 | TCP::collect 1 69 | } ; #CLIENT_ACCEPTED 70 | 71 | 72 | when CLIENT_DATA priority 250 { 73 | ## DYNAMIC EGRESS UPDATE: disable corresponding -in-t event 74 | event disable 75 | 76 | set len [TCP::payload length] 77 | if { [info exists ctx(ssf)] } { 78 | #someone beat us to it 79 | TCP::release 80 | return 81 | } elseif {!$len} { 82 | call /Common/sslo_${APP}.app/sslo_${APP}-lib::log 2 "CLIENT_DATA got empty payload, retrying" 83 | TCP::collect 84 | return 85 | } else { 86 | set ctx(csf) true 87 | set said [TCP::payload] 88 | # release accepted event, if held, to proxy for creating connection to server 89 | 90 | ## DYNAMIC EGRESS UPDATE: disable SSF detection 91 | #TCP::release 0 92 | } 93 | 94 | # got at least one octet 95 | 96 | if {($len < 44) && 97 | ( ([binary scan $said c type] == 1) && 98 | (($type & 0xff) == 22) )} { 99 | # may be partial TLS Client Hello (unusual) 100 | # allow up to 7 seconds for the rest to arrive 101 | # by modifying the connection idle timer. This will be 102 | # reset after we get the complete hello (or plaintext data) 103 | if {$ctx(log) > 1} { 104 | call /Common/sslo_${APP}.app/sslo_${APP}-lib::log 2 "CLIENT_DATA Incomplete Client Hello, set idle timeout to 7 sec" 105 | } 106 | set ipIdleTmo [IP::idle_timeout] 107 | IP::idle_timeout 7 108 | } ; #(partial Client Hello) 109 | 110 | if {[info exists ctx(httpconn)] && ([ACCESS::perflow get perflow.ssl_bypass_set] == 1)} { 111 | call /Common/sslo_${APP}.app/sslo_${APP}-lib::log 2 "CLIENT_DATA SSL bypass set inside HTTP CONNECT" 112 | CONNECTOR::enable 113 | } 114 | 115 | SSL::enable clientside 116 | 117 | after 0 { TCP::release } 118 | } ; #CLIENT_DATA 119 | -------------------------------------------------------------------------------- /internal-layered-architecture/transparent-proxy/use-case-transparent-to-explicit-egress/server-rule.tcl: -------------------------------------------------------------------------------- 1 | when CLIENT_ACCEPTED { 2 | ## Start with HTTP disabled 3 | HTTP::disable 4 | 5 | set THIS_POOL [LB::server pool] 6 | 7 | ## Set the option and detect_handshake flags 8 | set option 0 9 | set detect_handshake 1 10 | 11 | ## Collect sharedvar variables 12 | sharedvar ctx 13 | sharedvar SEND_SNI 14 | 15 | ## Collect the request payload -> trigger CLIENT_DATA 16 | TCP::collect 17 | } 18 | when CLIENT_DATA { 19 | ## Collect SNI from caller (sharedvar or binary parse) 20 | set SNI "" 21 | if { [info exists SEND_SNI] } { 22 | ## fetch SNI from client rule (sharedvar) 23 | set SNI ${SEND_SNI} 24 | } else { 25 | ## no SNI provided, binary parse TLS ClientHello (22) to get SNI 26 | binary scan [TCP::payload] c type 27 | if { ${type} == 22 } { 28 | set option 1 29 | 30 | ## Store the original payload 31 | binary scan [TCP::payload] H* orig 32 | 33 | ## Check for a properly formatted handshake request 34 | if { [binary scan [TCP::payload] cSS tls_xacttype tls_version tls_recordlen] < 3 } { 35 | reject 36 | return 37 | } 38 | 39 | switch $tls_version { 40 | "769" - 41 | "770" - 42 | "771" { 43 | if { ($tls_xacttype == 22) } { 44 | binary scan [TCP::payload] @5c tls_action 45 | if { not (($tls_action == 1) && ([TCP::payload length] > $tls_recordlen)) } { set detect_handshake 0 } 46 | } 47 | } 48 | "768" { set detect_handshake 0 } 49 | default { set detect_handshake 0 } 50 | } 51 | 52 | if { ($detect_handshake) } { 53 | # skip past the session id 54 | set record_offset 43 55 | binary scan [TCP::payload] @${record_offset}c tls_sessidlen 56 | set record_offset [expr {$record_offset + 1 + $tls_sessidlen}] 57 | 58 | # skip past the cipher list 59 | binary scan [TCP::payload] @${record_offset}S tls_ciphlen 60 | set record_offset [expr {$record_offset + 2 + $tls_ciphlen}] 61 | 62 | # skip past the compression list 63 | binary scan [TCP::payload] @${record_offset}c tls_complen 64 | set record_offset [expr {$record_offset + 1 + $tls_complen}] 65 | 66 | # check for the existence of ssl extensions 67 | if { ([TCP::payload length] > $record_offset) } { 68 | # skip to the start of the first extension 69 | binary scan [TCP::payload] @${record_offset}S tls_extenlen 70 | set record_offset [expr {$record_offset + 2}] 71 | # read all the extensions into a variable 72 | binary scan [TCP::payload] @${record_offset}a* tls_extensions 73 | 74 | # for each extension 75 | for { set ext_offset 0 } { $ext_offset < $tls_extenlen } { incr ext_offset 4 } { 76 | binary scan $tls_extensions @${ext_offset}SS etype elen 77 | if { ($etype == 0) } { 78 | # if it's a servername extension read the servername 79 | set grabstart [expr {$ext_offset + 9}] 80 | set grabend [expr {$elen - 5}] 81 | binary scan $tls_extensions @${grabstart}A${grabend} tls_servername_orig 82 | set tls_servername [string tolower ${tls_servername_orig}] 83 | set ext_offset [expr {$ext_offset + $elen}] 84 | break 85 | } else { 86 | # skip over other extensions 87 | set ext_offset [expr {$ext_offset + $elen}] 88 | } 89 | } 90 | } 91 | } 92 | if { [info exists tls_servername] } { 93 | set SNI ${tls_servername} 94 | } 95 | } 96 | } 97 | 98 | if { $ctx(ptcl) eq "https" } { 99 | if { ${SNI} ne "" } { 100 | ## HTTPS proxy chaining can only work if the request contains an SNI 101 | set option 1 102 | 103 | ## Store the original payload (would normally be the client TLS handshake) 104 | binary scan [TCP::payload] H* orig 105 | 106 | ## Point the traffic to the proxy server 107 | pool ${THIS_POOL} 108 | 109 | # Drop the client handshake 110 | TCP::payload replace 0 [TCP::payload length] "" 111 | 112 | # Form up the CONNECT call 113 | set px_connect "CONNECT ${SNI}:[TCP::local_port] HTTP/1.1\r\n\r\n" 114 | 115 | # Send the CONNECT 116 | TCP::payload replace 0 0 $px_connect 117 | TCP::release 118 | } elseif { ${SNI} eq "" } { 119 | ## HTTPS proxy chaining must fail without an SNI 120 | reject 121 | return 122 | } 123 | } elseif { $ctx(ptcl) eq "http" } { 124 | ## Enable HTTP processing 125 | HTTP::enable 126 | TCP::release 127 | } else { 128 | reject 129 | return 130 | } 131 | } 132 | when HTTP_REQUEST { 133 | if { [HTTP::header exists Host] } { 134 | set http_hostname [HTTP::host] 135 | } else { 136 | set http_hostname [IP::local_addr]:[TCP::local_port] 137 | } 138 | 139 | ## Point the traffic to the proxy server 140 | pool ${THIS_POOL} 141 | 142 | # Rewrite to proxified HTTP request 143 | HTTP::uri "http://${http_hostname}:[TCP::local_port][HTTP::uri]" 144 | } 145 | when HTTP_RESPONSE priority 300 { 146 | switch -glob -- [HTTP::status] { 147 | "2*" - 148 | "3*" { 149 | # drop the proxy status and replay the original handshake 150 | } 151 | "400" { 152 | # Bad Request 153 | reject 154 | return 155 | } 156 | "403" { 157 | # Forbidden 158 | reject 159 | return 160 | } 161 | "407" { 162 | # stub for when authentication is required 163 | HTTP::header replace ":S" "401 Unauthorized" 164 | } 165 | "502" { 166 | # Bad Gateway (proxy error) 167 | reject 168 | return 169 | } 170 | "503" { 171 | # Service Unavailable 172 | reject 173 | return 174 | } 175 | "504" { 176 | # Gateway Timeout 177 | reject 178 | return 179 | } 180 | default { 181 | reject 182 | return 183 | } 184 | } 185 | } 186 | when SERVER_CONNECTED priority 900 { 187 | ## Only do this for TLS traffic 188 | if { ${option} } { 189 | TCP::collect 12 190 | } 191 | } 192 | when SERVER_DATA priority 900 { 193 | switch -glob -- [TCP::payload] { 194 | "HTTP/1.[01] 200*" { 195 | # drop the proxy status and replay the original handshake 196 | TCP::payload replace 0 [TCP::payload length] "" 197 | TCP::respond [binary format H* $orig] 198 | } 199 | "HTTP/1.[01] 400*" { 200 | # Bad Request 201 | reject 202 | return 203 | } 204 | "HTTP/1.[01] 403*" { 205 | # Forbidden 206 | reject 207 | return 208 | } 209 | "HTTP/1.[01] 407*" { 210 | # stub for when authentication is required 211 | reject 212 | return 213 | } 214 | "HTTP/1.[01] 502*" { 215 | # Bad Gateway (proxy error) 216 | reject 217 | return 218 | } 219 | "HTTP/1.[01] 503*" { 220 | # Service Unavailable 221 | reject 222 | return 223 | } 224 | "HTTP/1.[01] 504*" { 225 | # Gateway Timeout 226 | reject 227 | return 228 | } 229 | default { 230 | reject 231 | return 232 | } 233 | } 234 | TCP::release 235 | } 236 | -------------------------------------------------------------------------------- /misc-tools/url-to-dg-convert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## This script will convert a custom URL category to a string data group. It is currently configured to convert the built-in 4 | ## SSL Orchestrator Pinners URL category, but can be used for other custom URL categories. Change the name of the custom URL 5 | ## category on the 'tmsh' line, and change the data group name on the 'ltm data-group' line. 6 | 7 | ## Create this script in a directory on the BIG-IP and 'chmod +x url-to-dg-convert.sh' to make executable. 8 | 9 | ## get url category entries 10 | tmsh list sys url-db url-category sslo-urlCatPinners urls |grep -E '^\s*http' |sed -E 's/\s*//;s/https?:\/\///;s/\\\*//;s/\/.*//' > tmp_cat 11 | 12 | ## create data group object 13 | echo "ltm data-group internal sslo-urlCatPinners {" > tmp_dg 14 | echo " records {" >> tmp_dg 15 | 16 | while read p 17 | do 18 | echo " ${p} { }" >> tmp_dg 19 | done < tmp_cat 20 | 21 | echo " }" >> tmp_dg 22 | echo " type string" >> tmp_dg 23 | echo "}" >> tmp_dg 24 | 25 | ## merge data group object into big-ip config 26 | tmsh load sys config merge file tmp_dg 27 | 28 | ## delete temp files 29 | rm -f tmp_cat 30 | rm -f tmp_dg 31 | -------------------------------------------------------------------------------- /saas-tenant-isolation/Readme.md: -------------------------------------------------------------------------------- 1 | ## SaaS Tenant Isolation iRule 2 | 3 | Full description here: 4 | 5 | https://community.f5.com/t5/technical-articles/ssl-orchestrator-advanced-use-cases-fun-with-saas-tenant/ta-p/303623 6 | -------------------------------------------------------------------------------- /saas-tenant-isolation/tenant-isolation-rule: -------------------------------------------------------------------------------- 1 | ## SaaS Tenant Isolation Rule 2 | ## Author: Kevin Stewart 3 | ## Version: 1.0 4 | ## Date: 10/2022 5 | ## 6 | ## Description: inserts one or more tenant isolation HTTP headers for the set of SaaS resources defined below: 7 | ## - Office365 8 | ## - Webex 9 | ## - Google G-Suite 10 | ## - Dropbox 11 | ## - Youtube 12 | ## - Slack 13 | ## 14 | ## Instructions: 15 | ## - RULE_INIT: Enter required header information for each needed SaaS resource 16 | ## - RULE_INIT: Enable/disable processing of each SaaS resource (ex. set static::use_office365 1) 17 | 18 | when RULE_INIT { 19 | ## clean up array data on load 20 | unset -nocomplain static::office365 21 | unset -nocomplain static::webex 22 | unset -nocomplain static::gsuite 23 | unset -nocomplain static::dropbox 24 | unset -nocomplain static::youtube 25 | unset -nocomplain static::slack 26 | 27 | ## set tenant isolation headers: Office365 28 | array set static::office365 { 29 | "Restrict-Access-To-Tenants" "enter-value-here" 30 | "Restrict-Access-Context" "enter-value-here" 31 | } 32 | 33 | ## set tenant isolation headers: Webex 34 | array set static::webex { 35 | "CiscoSpark-Allowed-Domains" "enter-value-here" 36 | } 37 | 38 | ## set tenant isolation headers: Gsuite 39 | array set static::gsuite { 40 | "X-GoogApps-Allowed-Domains" "enter-value-here" 41 | } 42 | 43 | ## set tenant isolation headers: Dropbox 44 | array set static::dropbox { 45 | "X-Dropbox-allowed-Team-Ids" "enter-value-here" 46 | } 47 | 48 | ## set tenant isolation headers: Youtube (options: Strict / Moderate) 49 | array set static::youtube { 50 | "YouTube-Restrict" "Strict" 51 | } 52 | 53 | ## set tenant isolation headers: Slack 54 | array set static::slack { 55 | "X-Slack-Allowed-Workspaces-Requester" "enter-value-here" 56 | "X-Slack-Allowed-Workspaces" "enter-value-here" 57 | } 58 | 59 | ## set tenant isolation triggers (0=off, 1=on) 60 | set static::use_office365 1 61 | set static::use_webex 0 62 | set static::use_gsuite 0 63 | set static::use_dropbox 0 64 | set static::use_youtube 0 65 | set static::use_slack 0 66 | } 67 | 68 | when HTTP_REQUEST { 69 | if { $static::use_office365 } { 70 | switch [string tolower [HTTP::host]] { 71 | "login.microsoftonline.com" - 72 | "login.microsoft.com" - 73 | "login.windows.net" { 74 | foreach {x y} [array get static::office365] { 75 | HTTP::header replace $x $y 76 | } 77 | } 78 | } 79 | } 80 | if { $static::use_webex } { 81 | switch -glob [string tolower [HTTP::host]] { 82 | "identity.webex.com" - 83 | "identity-eu.webex.com" - 84 | "idbroker.webex.com" - 85 | "idbroker-secondary.webex.com" - 86 | "idbroker-b-us.webex.com" - 87 | "idbroker-au.webex.com" - 88 | "atlas-a.wbx2.com" { 89 | foreach {x y} [array get static::webex] { 90 | HTTP::header replace $x $y 91 | } 92 | } 93 | } 94 | } 95 | if { $static::use_gsuite } { 96 | switch -glob [string tolower [HTTP::host]] { 97 | "*.google.com" - 98 | "*.googleusercontent.com" - 99 | "*.gstatic.com" { 100 | foreach {x y} [array get static::gsuite] { 101 | HTTP::header replace $x $y 102 | } 103 | } 104 | } 105 | } 106 | if { $static::use_dropbox } { 107 | switch -glob [string tolower [HTTP::host]] { 108 | "dropbox.com" - 109 | "*.dropbox.com" { 110 | foreach {x y} [array get static::dropbox] { 111 | HTTP::header replace $x $y 112 | } 113 | } 114 | } 115 | } 116 | if { $static::use_youtube } { 117 | switch -glob [string tolower [HTTP::host]] { 118 | "www.youtube.com" - 119 | "m.youtube.com" - 120 | "youtubei.googleapis.com" - 121 | "youtube.googleapis.com" - 122 | "www.youtube-nocookie.com" { 123 | foreach {x y} [array get static::youtube] { 124 | HTTP::header replace $x $y 125 | } 126 | } 127 | } 128 | } 129 | if { $static::use_slack } { 130 | switch -glob [string tolower [HTTP::host]] { 131 | "*.slack.com" { 132 | foreach {x y} [array get static::slack] { 133 | HTTP::header replace $x $y 134 | } 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /sni-switching/README.md: -------------------------------------------------------------------------------- 1 | # F5 SSL Orchestrator SNI Switching iRules 2 | A set of iRules useful in SSL Orchestrator inbound gateway mode topologies to dynamically select a client SSL profile based on incoming SNI in TLS Client Hello. 3 | 4 | ### Current version: 5 | 1.1.0 6 | 7 | ### Version support 8 | This utility works on BIG-IP 14.1 and above, SSL Orchestrator 5.x and above. 9 | 10 | ### How to install 11 | - Import the library-rule to the BIG-IP. Name it 'library-rule'. 12 | 13 | - Import either the static or data groups in-t-rule iRule. Its name is arbitrary. 14 | 15 | - If selecting the static version, SNI to client SSL profile matching is done directly in the iRule code. Any manipulation of the SNI-to-profile matching is managed directly in the iRule. 16 | 17 | - If selecting the data group version, SNI to client SSL profile matching is done in a data group. Create a string data group (SNI-value := client-ssl-profile-name). Reference this data group in the iRule. Any manipulation of the SNI-to-profile matching is managed directly in the data group. 18 | 19 | - Edit the corresponding inbound topology Interception Rule and add the static or datagroup iRule. Deploy and test. 20 | 21 | ### Updates 22 | The updated versions of these iRules now address the additional payload size of TLS1.3 handshakes. 23 | -------------------------------------------------------------------------------- /sni-switching/archive/in-t-rule-datagroup.tcl: -------------------------------------------------------------------------------- 1 | ## SSLO SNI Switching Rule (data group version) 2 | ## Author: Kevin Stewart 3 | ## Date: July 2020 4 | ## Purpose: Useful in SSLO versions 8.x and below to switch the client SSL profile based on ClientHello SNI 5 | ## in inbound SSLO topologies. This would be practical for L3 inbound gateway mode or L2 inbound topologies. 6 | ## Instructions: 7 | ## - Import server certificates and private keys 8 | ## - Create a separate client SSL profile for each cert/key pair 9 | ## - Create a string data group that maps the SNI to the client SSL profile name (ex. foo.f5labs.com := foo-clientssl) 10 | ## - Add the library-rule iRule to LTM (name "library-rule") 11 | ## - Add the SNI-switching-rule to LTM (name is arbitrary) 12 | ## - Create an L3 inbound gateway (or L2 inbound) mode SSLO topology. Define a server cert/key to be used as the "default" (when no SNI matches) 13 | ## - Edit the corresponding Interception Rule and add the SNI switching rule. Deploy and test. 14 | 15 | when CLIENT_ACCEPTED priority 250 { 16 | TCP::collect 17 | } 18 | when CLIENT_DATA priority 250 { 19 | ## call the external procedure 20 | set sni [call library-rule::getSNI [TCP::payload]] 21 | 22 | ## lookup SSL profile in data group 23 | set sslprofile [class lookup ${sni} sni-switching-dg] 24 | 25 | if { ${sslprofile} ne "" } { 26 | set cmd "SSL::profile /Common/${sslprofile}" ; eval $cmd 27 | } 28 | TCP::release 29 | } 30 | -------------------------------------------------------------------------------- /sni-switching/archive/in-t-rule-static.tcl: -------------------------------------------------------------------------------- 1 | ## SSLO SNI Switching Rule (static version) 2 | ## Author: Kevin Stewart 3 | ## Date: July 2020 4 | ## Purpose: Useful in SSLO versions 8.x and below to switch the client SSL profile based on ClientHello SNI 5 | ## in inbound SSLO topologies. This would be practical for L3 inbound gateway mode or L2 inbound topologies. 6 | ## Instructions: 7 | ## - Import server certificates and private keys 8 | ## - Create a separate client SSL profile for each cert/key pair 9 | ## - Add the library-rule iRule to LTM (name "library-rule") 10 | ## - Add the SNI-switching-rule to LTM (name is arbitrary) 11 | ## - Create an L3 inbound gateway (or L2 inbound) mode SSLO topology. Define a server cert/key to be used as the "default" (when no SNI matches) 12 | ## - Edit the corresponding Interception Rule and add the SNI switching rule. 13 | ## - Edit this iRule to select the correct client SSL profile based on the SNI. Deploy and test. 14 | 15 | when CLIENT_ACCEPTED priority 250 { 16 | TCP::collect 17 | } 18 | when CLIENT_DATA priority 250 { 19 | ## call the external procedure 20 | set sni [call library-rule::getSNI [TCP::payload]] 21 | 22 | switch [string tolower ${sni}] { 23 | "foo.f5labs.local" { 24 | set cmd1 "SSL::profile /Common/test1-clientssl" ; eval $cmd1 25 | } 26 | "bar.f5labs.local" { 27 | set cmd2 "SSL::profile /Common/test2-clientssl" ; eval $cmd2 28 | } 29 | "blah.f5labs.local" { 30 | set cmd3 "SSL::profile /Common/test3-clientssl" ; eval $cmd3 31 | } 32 | } 33 | TCP::release 34 | } 35 | -------------------------------------------------------------------------------- /sni-switching/archive/library-rule.tcl: -------------------------------------------------------------------------------- 1 | proc getSNI { payload } { 2 | set detect_handshake 1 3 | 4 | binary scan ${payload} H* orig 5 | if { [binary scan [TCP::payload] cSS tls_xacttype tls_version tls_recordlen] < 3 } { 6 | reject 7 | return 8 | } 9 | 10 | ## 768 SSLv3.0 11 | ## 769 TLSv1.0 12 | ## 770 TLSv1.1 13 | ## 771 TLSv1.2 14 | switch $tls_version { 15 | "769" - 16 | "770" - 17 | "771" { 18 | if { ($tls_xacttype == 22) } { 19 | binary scan ${payload} @5c tls_action 20 | if { not (($tls_action == 1) && ([string length ${payload}] > $tls_recordlen)) } { 21 | set detect_handshake 0 22 | } 23 | } 24 | } 25 | "768" { 26 | set detect_handshake 0 27 | } 28 | default { 29 | set detect_handshake 0 30 | } 31 | } 32 | 33 | if { ($detect_handshake) } { 34 | ## skip past the session id 35 | set record_offset 43 36 | binary scan ${payload} @${record_offset}c tls_sessidlen 37 | set record_offset [expr {$record_offset + 1 + $tls_sessidlen}] 38 | 39 | ## skip past the cipher list 40 | binary scan ${payload} @${record_offset}S tls_ciphlen 41 | set record_offset [expr {$record_offset + 2 + $tls_ciphlen}] 42 | 43 | ## skip past the compression list 44 | binary scan ${payload} @${record_offset}c tls_complen 45 | set record_offset [expr {$record_offset + 1 + $tls_complen}] 46 | 47 | ## check for the existence of ssl extensions 48 | if { ([string length ${payload}] > $record_offset) } { 49 | ## skip to the start of the first extension 50 | binary scan ${payload} @${record_offset}S tls_extenlen 51 | set record_offset [expr {$record_offset + 2}] 52 | ## read all the extensions into a variable 53 | binary scan ${payload} @${record_offset}a* tls_extensions 54 | 55 | ## for each extension 56 | for { set ext_offset 0 } { $ext_offset < $tls_extenlen } { incr ext_offset 4 } { 57 | binary scan $tls_extensions @${ext_offset}SS etype elen 58 | if { ($etype == 0) } { 59 | ## if it's a servername extension read the servername 60 | set grabstart [expr {$ext_offset + 9}] 61 | set grabend [expr {$elen - 5}] 62 | binary scan $tls_extensions @${grabstart}A${grabend} tls_servername_orig 63 | set tls_servername [string tolower ${tls_servername_orig}] 64 | set ext_offset [expr {$ext_offset + $elen}] 65 | break 66 | } else { 67 | ## skip over other extensions 68 | set ext_offset [expr {$ext_offset + $elen}] 69 | } 70 | } 71 | } 72 | } 73 | 74 | if { ![info exists tls_servername] } { 75 | ## This isn't TLS so we can't decrypt it anyway 76 | return "null" 77 | } else { 78 | return ${tls_servername} 79 | } 80 | TCP::release 81 | } 82 | -------------------------------------------------------------------------------- /sni-switching/in-t-rule-datagroup.tcl: -------------------------------------------------------------------------------- 1 | ## SSLO SNI Switching Rule (data group version) 2 | ## Author: Kevin Stewart 3 | ## Date: July 2020 4 | ## Purpose: Useful in SSLO versions 8.x and below to switch the client SSL profile based on ClientHello SNI 5 | ## in inbound SSLO topologies. This would be practical for L3 inbound gateway mode or L2 inbound topologies. 6 | ## Instructions: 7 | ## - Import server certificates and private keys 8 | ## - Create a separate client SSL profile for each cert/key pair 9 | ## - Create a string data group that maps the SNI to the client SSL profile name (ex. foo.f5labs.com := foo-clientssl) 10 | ## - Add the library-rule iRule to LTM (name "library-rule") 11 | ## - Add the SNI-switching-rule to LTM (name is arbitrary) 12 | ## - Create an L3 inbound gateway (or L2 inbound) mode SSLO topology. Define a server cert/key to be used as the "default" (when no SNI matches) 13 | ## - Edit the corresponding Interception Rule and add the SNI switching rule. Deploy and test. 14 | 15 | when CLIENT_ACCEPTED priority 250 { 16 | SSL::disable 17 | TCP::collect 18 | } 19 | when CLIENT_DATA priority 250 { 20 | binary scan [TCP::payload] cSS tls_xacttype tls_version tls_recordlen 21 | set tls_length [expr $tls_recordlen+5] 22 | if { ([info exists tls_length]) } { 23 | if { [TCP::payload length] < $tls_length } { 24 | TCP::collect 25 | return 26 | } 27 | } 28 | ## call the external procedure 29 | set sni [call library-rule::getSNI [TCP::payload]] 30 | ##log local0. "select sni is $sni" 31 | 32 | if { ${sni} eq "null" } { 33 | set cmd "SSL::disable" ; eval $cmd 34 | set cmd "SSL::disable serverside" ; eval $cmd 35 | } else { 36 | ## lookup SSL profile in data group 37 | set sslprofile [class match -value ${sni} ends_with sni-switching-dg] 38 | ##log local0. "select profile is $sslprofile" 39 | if { ${sslprofile} ne "" } { 40 | SSL::enable 41 | set cmd "SSL::profile /Common/${sslprofile}" ; eval $cmd 42 | #log local0. "SSL profile changed to $sslprofile" 43 | } 44 | 45 | } 46 | TCP::release 47 | } 48 | -------------------------------------------------------------------------------- /sni-switching/in-t-rule-static.tcl: -------------------------------------------------------------------------------- 1 | ## SSLO SNI Switching Rule (static version) 2 | ## Author: Kevin Stewart 3 | ## Date: May 2024 4 | ## Version: 1.1.0 (Updates for larger payloads in TLS1.3 handshakes) 5 | ## Purpose: Useful in SSLO versions 8.x and below to switch the client SSL profile based on ClientHello SNI 6 | ## in inbound SSLO topologies. This would be practical for L3 inbound gateway mode or L2 inbound topologies. 7 | ## Instructions: 8 | ## - Import server certificates and private keys 9 | ## - Create a separate client SSL profile for each cert/key pair 10 | ## - Add the library-rule iRule to LTM (name "library-rule") 11 | ## - Add the SNI-switching-rule to LTM (name is arbitrary) 12 | ## - Create an L3 inbound gateway (or L2 inbound) mode SSLO topology. Define a server cert/key to be used as the "default" (when no SNI matches) 13 | ## - Edit the corresponding Interception Rule and add the SNI switching rule. 14 | ## - Edit this iRule to select the correct client SSL profile based on the SNI. Deploy and test. 15 | 16 | when CLIENT_ACCEPTED priority 250 { 17 | SSL::disable 18 | TCP::collect 19 | } 20 | when CLIENT_DATA priority 250 { 21 | binary scan [TCP::payload] cSS tls_xacttype tls_version tls_recordlen 22 | set tls_length [expr $tls_recordlen+5] 23 | if { ([info exists tls_length]) } { 24 | if { [TCP::payload length] < $tls_length } { 25 | TCP::collect 26 | return 27 | } 28 | } 29 | ## call the external procedure 30 | set sni [call library-rule::getSNI [TCP::payload]] 31 | ##log local0. "select sni is $sni" 32 | 33 | if { ${sni} eq "null" } { 34 | set cmd "SSL::disable" ; eval $cmd 35 | set cmd "SSL::disable serverside" ; eval $cmd 36 | } else { 37 | ## lookup SSL profile in data group 38 | switch [string tolower ${sni}] { 39 | "foo.f5labs.local" { 40 | set cmd1 "SSL::profile /Common/test1-clientssl" ; eval $cmd1 41 | } 42 | "bar.f5labs.local" { 43 | set cmd2 "SSL::profile /Common/test2-clientssl" ; eval $cmd2 44 | } 45 | "blah.f5labs.local" { 46 | set cmd3 "SSL::profile /Common/test3-clientssl" ; eval $cmd3 47 | } 48 | } 49 | } 50 | TCP::release 51 | } 52 | -------------------------------------------------------------------------------- /sni-switching/library-rule.tcl: -------------------------------------------------------------------------------- 1 | ## SNI Fetching Rule 2 | ## Author: Kevin Stewart 3 | ## Date: May 2024 4 | ## Version: 1.1.0 5 | ## Updates: 6 | ## - Addresses larger payloads of TLS1.3 handshakes 7 | 8 | proc getSNI { payload } { 9 |     set detect_handshake 1 10 | 11 |     binary scan ${payload} H* orig 12 |     if { [binary scan [TCP::payload] cSS tls_xacttype tls_version tls_recordlen] < 3 } { 13 |         reject 14 |         return 15 |     } 16 |     log local0. "payload length [TCP::payload length]" 17 |     log local0. "CLIENT HELLO:- TLS Version: ${tls_version}" 18 |     log local0. "CLIENT HELLO:- TLS Length: ${tls_recordlen}" 19 | 20 |     ## 768 SSLv3.0 21 |     ## 769 TLSv1.0 22 |     ## 770 TLSv1.1 23 |     ## 771 TLSv1.2 24 |     switch $tls_version { 25 |         "769" - 26 |         "770" - 27 |         "771" { 28 |             if { ($tls_xacttype == 22) } { 29 |                 binary scan ${payload} @5c tls_action 30 |                 if { not (($tls_action == 1) && ([string length ${payload}] > $tls_recordlen)) } { 31 |                     set detect_handshake 0 32 |                 } 33 |             } 34 |         } 35 |         "768" { 36 |             set detect_handshake 0 37 |         } 38 |         default { 39 |             set detect_handshake 0 40 |         } 41 |     } 42 | 43 |     if { ($detect_handshake) } { 44 |         ## skip past the session id 45 |         set record_offset 43 46 |         binary scan ${payload} @${record_offset}c tls_sessidlen 47 |         set record_offset [expr {$record_offset + 1 + $tls_sessidlen}] 48 | 49 |         ## skip past the cipher list 50 |         binary scan ${payload} @${record_offset}S tls_ciphlen 51 |         set record_offset [expr {$record_offset + 2 + $tls_ciphlen}] 52 | 53 |         ## skip past the compression list 54 |         binary scan ${payload} @${record_offset}c tls_complen 55 |         set record_offset [expr {$record_offset + 1 + $tls_complen}] 56 | 57 |         ## check for the existence of ssl extensions 58 |         if { ([string length ${payload}] > $record_offset) } { 59 |             ## skip to the start of the first extension 60 |             binary scan ${payload} @${record_offset}S tls_extenlen 61 |             set record_offset [expr {$record_offset + 2}] 62 |             ## read all the extensions into a variable 63 |             binary scan ${payload} @${record_offset}a* tls_extensions 64 | 65 |             ## for each extension 66 |             for { set ext_offset 0 } { $ext_offset < $tls_extenlen } { incr ext_offset 4 } { 67 |                 binary scan $tls_extensions @${ext_offset}SS etype elen 68 |                 if { ($etype == 0) } { 69 |                     ## if it's a servername extension read the servername 70 |                     set grabstart [expr {$ext_offset + 9}] 71 |                     set grabend [expr {$elen - 5}] 72 |                     binary scan $tls_extensions @${grabstart}A${grabend} tls_servername_orig 73 |                     set tls_servername [string tolower ${tls_servername_orig}] 74 |                     set ext_offset [expr {$ext_offset + $elen}] 75 |                     break 76 |                 } else { 77 |                     ## skip over other extensions 78 |                     set ext_offset [expr {$ext_offset + $elen}] 79 |                 } 80 |             } 81 |         } 82 |     } 83 | 84 |     if { ![info exists tls_servername] } { 85 |         ## This isn't TLS so we can't decrypt it anyway 86 |         return "null" 87 |     } else { 88 |         return ${tls_servername} 89 |     } 90 |     TCP::release 91 | } 92 | -------------------------------------------------------------------------------- /sslo-dns-over-https-detection/README.md: -------------------------------------------------------------------------------- 1 | # SSL Orchestrator DNS-over-HTTPS Detection 2 | Set of iRule tools to add DoH detection to SSL Orchestrator 3 | 4 | - Current version: 3 (Oct 2022) 5 | - Includes support for DoT handling and minor bugfixes 6 | 7 | ## Version support 8 | These iRules work on all modern versions of BIG-IP (14.1+) and SSL Orchestrator (5.0+) 9 | 10 | ## Description 11 | Based on RFC8484, DNS-over-HTTPS (DoH) is a method of securely conveying DNS requests/responses over encrypted HTTPS. While DoH is intended to provide additional privacy (i.e. DNS traffic is not protected from eavesdropping), it can have other serious implications as it also potentially masks malware command-and-control. In the absence of running a local DoH-capable DNS resolver, detection of DoH traffic minimally requires decryption and inspection. This repository provides a set of iRules that can be attached to an SSL Orchestrator configuration to provide the following DoH controls: 12 | 13 | - **DoH Detection and Blocking**: This simply identifies any DoH request traffic, in any of the three DoH formats (Wireframe GET/POST and JSON) and sends a reject. A DoH-cable browser that receives reject on DoH request will revert to normal DNS. 14 | 15 | - **DoH Detection and Logging**: This identifies DoH request traffic, and can: 16 | - Parse the query name and type (A, AAAA, TXT, etc.) for local and remote high-speed logging 17 | - Enable a DoH blackhole response for A, AAAA and TXT requests based on a URL category match (of the requested name) 18 | 19 | ------------------------------ 20 | 21 | ## Installation 22 | Installation is as simple as adding the respective iRule to an SSL Orchestrator topology. 23 | 24 | - Under Local Traffic -> iRules in the BIG-IP UI, import the required iRule. 25 | - In the SSL Orchestrator UI, under the Interception Rules tab, click on the interception rule to edit (typically ending with "-in-t"), and at the bottom of this configuration page, add the iRule and then (re)deploy. 26 | - If using the DoH detection and logging iRule, additional configuration is required (see below). 27 | 28 | ------------------------------ 29 | 30 | ## Configuration 31 | Configuration in the DoH detection and logging iRule is performed through a set of static variables at the top of the rule: 32 | 33 | - **static::LOCAL_LOG**: (off=0, on=1) enables or disables local Syslog logging. It is recommended to disable this unless troubleshooting. 34 | 35 | - **static::HSL**: (off="none", on=[HSL pool name]) enables or disables remote high-speed logging (HSL). To create an HSL pool: 36 | - Under Local Traffic -> Pools, create a pool that points to the remote Syslog (using something on port 514). 37 | - Enable HSL logging in the iRule by specifying the pool name in the static variable. 38 | 39 | - **static::URLDB_LICENSED**: (off=0, on=1) the DoH detection iRule can use URL categorization to perform DoH blackholing. If subscription-based URLDB is licensed and provisioned, you can enable this (set to 1) to search the URLDB categories. Otherwise, set to 0 and continue to use custom URL categories. 40 | 41 | - **static::BLACKHOLE_URLCAT**: (off=empty, on=[list of category names]) if DoH blackholing is desired, add the list of URL categories to search here. This can be a combination of URLDB categories and/or custom URL categories. Leave it empty to disable URL categorization. For example: 42 | 43 | ``` 44 | set static::BLACKHOLE_URLCAT { 45 | "/Common/Advanced_Malware_Command_and_Control" 46 | "/Advanced_Malware_Payloads" 47 | "/Common/Spyware_and_Adware" 48 | "/Comomn/SPAM_URLs" 49 | "/Common/Financial_Data_and_Services" 50 | "/Commmon/my_custom_url_category" 51 | } 52 | ``` 53 | 54 | You can get the list of URLDB categories with this command: 55 | 56 | `tmsh list sys url-db url-category |grep "sys url-db url-category " |awk -F" " '{print $4}'` 57 | 58 | - **static::BLACKHOLE_RTYPE_A**: (off=0, on=1) enables or disables blackholing of DoH A record requests. This will send a response with 199.199.199.199. When the client attempts to connect to this IP through the SSL Orchestrator topology, the connection will be rejected. 59 | 60 | - **static::BLACKHOLE_RTYPE_AAAA**: (off=0, on=1) enables or disables blackholing of DoH AAAA record requests. This will send a response with 0:0:0:0:0:ffff:c7c7:c7c7. When the client attempts to connect to this IP through the SSL Orchestrator topology, the connection will be rejected. 61 | 62 | - **static::BLACKHOLE_RTYPE_TXT**: (off=0, on=1) enables or disables blackholing of DoH TXT record requests. This will send a response with generic "v=spf1 -all". 63 | 64 | - **static::dns_codes**: This value does not need to be edited, but contains an array of type:value values for different record types. This array is used to identify record types (ex. A, AAAA, TXT, CNAME) for logging. 65 | -------------------------------------------------------------------------------- /sslo-dns-over-https-detection/sslo-doh-blocking.tcl: -------------------------------------------------------------------------------- 1 | ## Rule: SSL Orchestrator DNS-over-HTTPS detection and blocking 2 | ## Author: Kevin Stewart 3 | ## Version: 1, 1/2021 4 | ## Function: Creates a mechanism to detect and block DNS-over-HTTPS requests through an SSL Orchestrator outbound topology. 5 | ## Instructions: 6 | ## - Under Local Traffic -> iRules in the BIG-IP UI, import the required iRule. 7 | ## - In the SSL Orchestrator UI, under the Interception Rules tab, click on the interception rule to edit (typically ending with "-in-t"), and at the bottom of this configuration page, add the iRule and then (re)deploy. 8 | when HTTP_REQUEST priority 750 { 9 | if { ( [HTTP::method] equals "GET" and [HTTP::header exists "accept"] and [HTTP::header "accept"] equals "application/dns-json" ) or \ 10 | ( [HTTP::method] equals "GET" and [HTTP::header exists "accept"] and [HTTP::header "accept"] equals "application/dns-message" ) or \ 11 | ( [HTTP::method] equals "POST" and [HTTP::header exists "content-type"] and [HTTP::header "content-type"] equals "application/dns-message" ) } { 12 | reject 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sslo-dns-over-https-detection/sslo-doh-logging.tcl: -------------------------------------------------------------------------------- 1 | ## Rule: SSL Orchestrator DNS-over-HTTPS detection, logging and blackholing 2 | ## Author: Kevin Stewart 3 | ## Version: 4, 10/2022 4 | ## Function: Creates a mechanism to detect, log, and potentially blackhole DNS-over-HTTPS requests through an SSL Orchestrator outbound topology. 5 | ## Instructions: 6 | ## - Under Local Traffic -> iRules in the BIG-IP UI, import the required iRule. 7 | ## - In the SSL Orchestrator UI, under the Interception Rules tab, click on the interception rule to edit (typically ending with "-in-t"), and at the bottom of this configuration page, add the iRule and then (re)deploy. 8 | ## - If using the DoH detection and logging iRule, additional configuration is required (see README). 9 | 10 | when RULE_INIT { 11 | ## User-defined: send log traffic to local syslog facility (not recommended under load) 12 | ## Disabled (value: 0) or enabled (value: 1) 13 | set static::LOCAL_LOG 0 14 | 15 | ## User-defined: send log traffic to external Syslog service via high-speed logging (HSL) 16 | ## Disable HSL (value: "none") or enable (value: syslog pool name) 17 | #set static::HSL "syslog-pool" 18 | set static::HSL "none" 19 | 20 | ## User-defined: is URLDB licensed and provisioned (otherwise local custom URL categories are still available) 21 | ## Disabled (value: 0) or enabled (value: 1) 22 | set static::URLDB_LICENSED 0 23 | 24 | ## User-defined: generate blackhole DNS responses for these URL categories. 25 | ## Disabled (value: empty) or enabled (value: names of URL categories to block) 26 | ## If URLDB is not licensed and provisioned, this list can still contain local custom URL categories 27 | ## 28 | ## For custom URL categories, enter the URLs to block in this format: https://[domain]/. Example: 29 | ## https://www.example.com/ 30 | ## 31 | ## Add the URL categories to the following block. Example: 32 | ## set static::BLACKHOLE_URLCAT { 33 | ## /Common/block-doh-urls 34 | ## } 35 | set static::BLACKHOLE_URLCAT { 36 | 37 | } 38 | 39 | ## User-defined: if enabled, generate blachole DNS responses for these record types 40 | ## Disabled (value: 0) or enabled (value: 1) 41 | set static::BLACKHOLE_RTYPE_A 1 42 | set static::BLACKHOLE_RTYPE_AAAA 1 43 | set static::BLACKHOLE_RTYPE_TXT 1 44 | 45 | ## Set DNS record types: (ex. A=1, AAAA=28) ref: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml 46 | #array set static::dns_codes { 1 A 2 NS 5 CNAME 6 SOA 12 PTR 16 TXT 28 AAAA 33 SRV 257 CAA } 47 | array set static::dns_codes { 1 A 2 NS 5 CNAME 6 SOA 12 PTR 13 HINFO 15 MX 16 TXT 17 RP 18 AFSDB 28 AAAA 29 LOC 33 SRV 35 NAPTR 37 CERT 39 DNAME 43 DS 46 RRSIG 47 NSEC 48 DNSKEY 49 DHCID 50 NSEC3 51 NSEC3PARAM 52 TLSA 65 HTTPS 99 SPF 257 CAA } 48 | } 49 | proc DOH_URL_BLOCK { id name ver hsl } { 50 | ## This procedure consumes the query type id (A,AAAA), query name, DoH version (WF or JSON) and HSL object 51 | ## and generates a blackhole DNS response is the name matches a defined URL category. Works for A, AAAA, and TXT records. 52 | set type [lindex [split ${name} ":"] 0] 53 | set name [lindex [split ${name} ":"] 1] 54 | 55 | if { ${static::URLDB_LICENSED}} { 56 | set match 0 ; if { [llength ${static::BLACKHOLE_URLCAT}] > 0 } { set res [CATEGORY::lookup "https://${name}/" request_default_and_custom] ; foreach url ${res} { if { [lsearch -exact ${static::BLACKHOLE_URLCAT} ${url}] >= 0 } { set match 1 } } } 57 | } else { 58 | set match 0 ; if { [llength ${static::BLACKHOLE_URLCAT}] > 0 } { set res [CATEGORY::lookup "https://${name}/" custom] ; foreach url ${res} { if { [lsearch -exact ${static::BLACKHOLE_URLCAT} ${url}] >= 0 } { set match 1 } } } 59 | } 60 | 61 | if { ${match} } { 62 | if { ( ${type} eq "A" ) and ( ${static::BLACKHOLE_RTYPE_A} ) } { 63 | if { ${ver} eq "WF" } { 64 | ## build DNS A record blackhole response 65 | set retstring "${id}81800001000100000000" 66 | foreach x [split ${name} "."] { 67 | append retstring [format %02x [string length ${x}]] 68 | foreach y [split ${x} ""] { 69 | append retstring [format %02x [scan ${y} %c]] 70 | } 71 | } 72 | ## c7c7c7c7c = 199.199.199.199 73 | append retstring {0000010001c00c00010001000118c30004c7c7c7c7} 74 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 75 | HTTP::respond 200 content [binary format H* ${retstring}] "Content-Type" "application/dns-message" "Access-Control-Allow-Origin" "*" 76 | } elseif { ${ver} eq "JSON" } { 77 | set template "\{\"Status\": 0,\"TC\": false,\"RD\": true,\"RA\": true,\"AD\": true,\"CD\": false,\"Question\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\": 1 \}\],\"Answer\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\":1,\"TTL\": 84078,\"data\": \"199.199.199.199\" \}\]\}" 78 | set template [string map [list "BLACKHOLE_TEMPLATE" ${name}] ${template}] 79 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 80 | HTTP::respond 200 content ${template} "Content-Type" "application/dns-json" "Access-Control-Allow-Origin" "*" 81 | } elseif { ${ver} eq "DoT" } { 82 | ## build DNS A record blackhole response 83 | set retstring "${id}81800001000100000000" 84 | foreach x [split ${name} "."] { 85 | append retstring [format %02x [string length ${x}]] 86 | foreach y [split ${x} ""] { 87 | append retstring [format %02x [scan ${y} %c]] 88 | } 89 | } 90 | ## c7c7c7c7c = 199.199.199.199 91 | append retstring {0000010001c00c00010001000118c30004c7c7c7c7} 92 | set lenhex [format %04x [string length ${retstring}]] 93 | set retstring "${lenhex}${retstring}" 94 | call DOH_LOG "Sending DoT Blackhole for Request" "${type}:${name}" ${hsl} 95 | SSL::respond [binary format H* ${retstring}] 96 | } 97 | } elseif { ( ${type} eq "AAAA" ) and ( ${static::BLACKHOLE_RTYPE_AAAA} ) } { 98 | if { ${ver} eq "WF" } { 99 | ## build DNS A record blackhole response 100 | set retstring "${id}81800001000100000000" 101 | foreach x [split ${name} "."] { 102 | append retstring [format %02x [string length ${x}]] 103 | foreach y [split ${x} ""] { 104 | append retstring [format %02x [scan ${y} %c]] 105 | } 106 | } 107 | ## 0:0:0:0:0:ffff:c7c7:c7c7 108 | append retstring {00001c0001c00c001c00010001488100100000000000000000000ffffc7c7c7c7} 109 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 110 | HTTP::respond 200 content [binary format H* ${retstring}] "Content-Type" "application/dns-message" "Access-Control-Allow-Origin" "*" 111 | } elseif { ${ver} eq "JSON" } { 112 | set template "\{\"Status\": 0,\"TC\": false,\"RD\": true,\"RA\": true,\"AD\": true,\"CD\": false,\"Question\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\": 28 \}\],\"Answer\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\":28,\"TTL\": 84078,\"data\": \"0:0:0:0:0:ffff:c7c7:c7c7\" \}\]\}" 113 | set template [string map [list "BLACKHOLE_TEMPLATE" ${name}] ${template}] 114 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 115 | HTTP::respond 200 content ${template} "Content-Type" "application/dns-json" "Access-Control-Allow-Origin" "*" 116 | } elseif { ${ver} eq "DoT" } { 117 | ## build DNS A record blackhole response 118 | set retstring "${id}81800001000100000000" 119 | foreach x [split ${name} "."] { 120 | append retstring [format %02x [string length ${x}]] 121 | foreach y [split ${x} ""] { 122 | append retstring [format %02x [scan ${y} %c]] 123 | } 124 | } 125 | ## 0:0:0:0:0:ffff:c7c7:c7c7 126 | append retstring {00001c0001c00c001c00010001488100100000000000000000000ffffc7c7c7c7} 127 | set lenhex [format %04x [string length ${retstring}]] 128 | set retstring "${lenhex}${retstring}" 129 | call DOH_LOG "Sending DoT Blackhole for Request" "${type}:${name}" ${hsl} 130 | SSL::respond [binary format H* ${retstring}] 131 | } 132 | } elseif { ( ${type} eq "TXT" ) and ( ${static::BLACKHOLE_RTYPE_TXT} ) } { 133 | if { ${ver} eq "WF" } { 134 | ## build DNS A record blackhole response 135 | set retstring "${id}81800001000100000000" 136 | foreach x [split ${name} "."] { 137 | append retstring [format %02x [string length ${x}]] 138 | foreach y [split ${x} ""] { 139 | append retstring [format %02x [scan ${y} %c]] 140 | } 141 | } 142 | ## generic "v=spf1 -all" 143 | append retstring {0000100001c00c0010000100002a30000c0b763d73706631202d616c6c} 144 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 145 | HTTP::respond 200 content [binary format H* ${retstring}] "Content-Type" "application/dns-message" "Access-Control-Allow-Origin" "*" 146 | } elseif { ${ver} eq "JSON" } { 147 | set template "\{\"Status\": 0,\"TC\": false,\"RD\": true,\"RA\": true,\"AD\": true,\"CD\": false,\"Question\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\": 16 \}\],\"Answer\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\":16,\"TTL\": 84078,\"data\": \"v=spf1 -all\" \}\]\}" 148 | set template [string map [list "BLACKHOLE_TEMPLATE" ${name}] ${template}] 149 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 150 | HTTP::respond 200 content ${template} "Content-Type" "application/dns-json" "Access-Control-Allow-Origin" "*" 151 | } elseif { ${ver} eq "DoT" } { 152 | ## build DNS A record blackhole response 153 | set retstring "${id}81800001000100000000" 154 | foreach x [split ${name} "."] { 155 | append retstring [format %02x [string length ${x}]] 156 | foreach y [split ${x} ""] { 157 | append retstring [format %02x [scan ${y} %c]] 158 | } 159 | } 160 | ## generic "v=spf1 -all" 161 | append retstring {0000100001c00c0010000100002a30000c0b763d73706631202d616c6c} 162 | set lenhex [format %04x [string length ${retstring}]] 163 | set retstring "${lenhex}${retstring}" 164 | call DOH_LOG "Sending DoT Blackhole for Request" "${type}:${name}" ${hsl} 165 | SSL::respond [binary format H* ${retstring}] 166 | } 167 | } 168 | } 169 | } 170 | proc DOH_LOG { msg name hsl } { 171 | ## This procedure consumes the message string, DoH question name, and HSL object to generates log messages. 172 | if { ${static::LOCAL_LOG} } { log -noname local0. "[IP::client_addr]:[TCP::client_port]-[IP::local_addr]:[TCP::local_port] :: ${msg}: ${name}" } 173 | #if { ${static::HSL} ne "none" } { HSL::send ${hsl} "<190> [IP::client_addr]:[TCP::client_port]-[IP::local_addr]:[TCP::local_port] :: ${msg}: ${name}" } 174 | if { ${static::HSL} ne "none" } { HSL::send ${hsl} "<34>1 [clock format [clock seconds] -gmt 1 -format {%Y-%m-%dT%H:%M:%S.000Z}] $static::tcl_platform(machine) sslo - [TMM::cmp_count] - ${msg}: ${name}"} 175 | } 176 | proc DECODE_DNS_REQ { data } { 177 | ## This procedure consumes the HEX-encoded DoH question and decodes to return the question name and type (A,AAAA,TXT, etc.). 178 | if { [catch { 179 | set name "" ; set pos 0 ; set num 0 ; set count 0 ; set typectr 0 ; set type "" 180 | ## process question 181 | foreach {i j} [split ${data} ""] { 182 | scan ${i}${j} %x num 183 | if { ${typectr} > 0 } { 184 | append type "${i}${j}" 185 | if { ${typectr} == 2 } { break } 186 | incr typectr 187 | } elseif { ${num} == 0 } { 188 | ## we're done 189 | set typectr 1 190 | #break 191 | } elseif { ${num} < 31 } { 192 | set pos 1 193 | set count ${num} 194 | append name "." 195 | } elseif { [expr { ${pos} <= ${count} }] } { 196 | set char [binary format H* ${i}${j}] 197 | append name $char 198 | incr pos 199 | } 200 | } 201 | set name [string range ${name} 1 end] 202 | ## process qtype 203 | if { [catch { 204 | scan ${type} %xx type 205 | set typestr $static::dns_codes(${type}) 206 | }] } { 207 | set typestr "UNK" 208 | } 209 | }] } { 210 | return "error" 211 | } else { 212 | return "${typestr}:${name}" 213 | } 214 | } 215 | proc SAFE_BASE64_DECODE { payload } { 216 | if { [catch {b64decode "${payload}[expr {[string length ${payload}] % 4 == 0 ? "":[string repeat "=" [expr {4 - [string length ${payload}] % 4}]]}]"} decoded_value] == 0 and ${decoded_value} ne "" } { 217 | return ${decoded_value} 218 | } else { 219 | return 0 220 | } 221 | } 222 | when CLIENT_ACCEPTED { 223 | ## This event establishes HSL connection (as required) and sends reject if destination address is the blackhole IP. 224 | if { ${static::HSL} ne "none" } { set hsl [HSL::open -proto UDP -pool ${static::HSL}] } else { set hsl "none" } 225 | if { [IP::local_addr] eq "199.199.199.199" } { reject } 226 | if { [IP::local_addr] eq "0:0:0:0:0:ffff:c7c7:c7c7" } { reject } 227 | } 228 | when CLIENTSSL_HANDSHAKE priority 50 { 229 | ## This event triggers on decrypted DoT requests. 230 | if { [TCP::local_port] eq "853" } { 231 | SSL::collect 232 | } 233 | } 234 | when CLIENTSSL_DATA priority 50 { 235 | ## This event is triggered parse the decrypted DoT request. 236 | if { [TCP::local_port] eq "853" } { 237 | binary scan [SSL::payload] H* tmp 238 | set id [string range ${tmp} 4 7] 239 | set tmp [string range ${tmp} 28 end] 240 | if { [set name [call DECODE_DNS_REQ ${tmp}]] ne "error" } { 241 | call DOH_LOG "DoT Request" ${name} ${hsl} 242 | call DOH_URL_BLOCK ${id} ${name} "DoT" ${hsl} 243 | } 244 | SSL::release 245 | } 246 | } 247 | when HTTP_REQUEST priority 750 { 248 | ## This event parses the request looking for DoH type messages. 249 | if { ( [HTTP::method] equals "GET" and [HTTP::header exists "accept"] and [HTTP::header "accept"] equals "application/dns-json" ) } { 250 | ## JSON DoH request 251 | set type [URI::query [HTTP::uri] type] ; if { ${type} eq "" } { set type "A" } 252 | set name [URI::query [HTTP::uri] name] ; if { ${name} ne "" } { call DOH_LOG "DoH (JSON GET) Request" "${type}:${name}" ${hsl} } 253 | call DOH_URL_BLOCK "null" "${type}:${name}" "JSON" ${hsl} 254 | } elseif { ( ( [HTTP::method] equals "GET" and [HTTP::header exists "content-type"] and [HTTP::header "content-type"] equals "application/dns-message" ) \ 255 | or ( [HTTP::method] equals "GET" and [HTTP::header exists "accept"] and [HTTP::header "accept"] equals "application/dns-message" ) ) } { 256 | ## DNS WireFormat DoH GET request 257 | if { [set name [URI::query [HTTP::uri] dns]] >= 0 } { 258 | ## Use this construct to handle potentially missing padding characters 259 | binary scan [call SAFE_BASE64_DECODE ${name}] H* tmp 260 | set id [string range ${tmp} 0 3] 261 | set tmp [string range ${tmp} 24 end] 262 | if { [set name [call DECODE_DNS_REQ ${tmp}]] ne "error" } { 263 | call DOH_LOG "DoH (WireFormat GET) Request" ${name} ${hsl} 264 | call DOH_URL_BLOCK ${id} ${name} "WF" ${hsl} 265 | } 266 | } 267 | } elseif { ( [HTTP::method] equals "POST" and [HTTP::header exists "content-type"] and [HTTP::header "content-type"] equals "application/dns-message" ) } { 268 | ## DNS WireFormat DoH POST request 269 | HTTP::collect 100 270 | } 271 | } 272 | when HTTP_REQUEST_DATA priority 250 { 273 | binary scan [HTTP::payload] H* tmp 274 | set id [string range ${tmp} 0 3] 275 | set tmp [string range ${tmp} 24 end] 276 | if { [set name [call DECODE_DNS_REQ ${tmp}]] ne "error" } { 277 | call DOH_LOG "DoH (WireFormat POST) Request" ${name} ${hsl} 278 | call DOH_URL_BLOCK ${id} ${name} "WF" ${hsl} 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /sslo-dns-sinkhole/README.md: -------------------------------------------------------------------------------- 1 | # DNS Sinkholing with SSL Orchestrator 2 | 3 | #### This tool creates a configuration on the F5 BIG-IP to support DNS sinkholing and decrypted blocking page injection with SSL Orchestrator. 4 | 5 | ----------------- 6 | 7 | DNS sinkholing is a method to divert traffic away from its intended target (divert to something). Contrast this with DNS blackholing, which causes all traffic to a target to be dropped (divert to nothing). Both of these security functions are performed with DNS record manipulation. With blackholing, an NXDOMAIN record is typically returned. With sinkholing, a separate target address is returned from the resolution query, often to a site that presents a blocking page. Sinkholing works well for cleartext traffic to enable injection of a blocking page, but under normal conditions a TLS client would receive the blocking page with a certificate warning, as the certificate received would not match the site requested. The following integration enables SSL Orchestrator for the purpose of generating (forging) a server certificate that is both locally trusted and matches the URL requested by the client. The configuration relies on two components: 8 | 9 | * A **sinkhole internal** virtual server with client SSL profile to host a sinkhole certificate and key. This is a certificate with empty Subject field used as the origin for forging a trusted certificate to the internal client. 10 | 11 | * An **SSL Orchestrator** outbound L3 topology that is modified to accept traffic on a specific client-facing IP:port and points to the internal virtual server. When an internal client makes a request to this virtual server, SSL Orchestrator fetches the origin "sinkhole" certificate, forges a new local certificate, and auto-injects a subject-alternative-name into the forged certificate to match the client's request. An iRule is added to insert an HTTP/HTML blocking page response on the explicitly decrypted traffic. 12 | 13 | DNS sinkholing to a blocking page effectively relies on two separate technologies: the DNS security solution itself - the thing that manipulates the client's DNS request for the purpose of preventing access to suspected malicious sites, and the sinkhole target - the thing that presents a valid certificate to the client and injects the blocking response. This integration specifically addresses the latter. The former is reasonably out-of-scope, and could be provided in any number of ways, as discussed below in the **Testing** topic. 14 | 15 | ----------------- 16 | 17 | ### Configuration: Sinkhole Internal 18 | 19 | *Note: Minimum BIG-IP requirement for this solution is version 16.x.* 20 | 21 | To create the **sinkhole internal** virtual server configuration: 22 | 23 | * **Optional easy-install step**: The following Bash script builds all of the necessary objects for the internal virtual server configuration. You can either use this to handle steps 1 to 4 automatically, or follow the steps below to create these manually. Step 5 must be done manually. 24 | 25 | ``` 26 | curl -s https://raw.githubusercontent.com/f5devcentral/sslo-script-tools/main/sslo-dns-sinkhole/create-sinkhole-internal-config.sh | bash 27 | ``` 28 | 29 | * **Step 1: Create the sinkhole certificate and key** The sinkhole certificate is specifically crafted to contain an empty Subject field. SSL Orchestrator is able to dynamically modify the subject-alternative-name field in the forged certificate, which is the only value of the two required by modern browsers. 30 | 31 | ``` 32 | openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 -nodes \ 33 | -keyout "sinkhole.key" \ 34 | -out "sinkhole.crt" \ 35 | -subj "/" \ 36 | -config <(printf "[req]\n 37 | distinguished_name=dn\n 38 | x509_extensions=v3_req\n 39 | [dn]\n\n 40 | [v3_req]\n 41 | keyUsage=critical,digitalSignature,keyEncipherment\n 42 | extendedKeyUsage=serverAuth,clientAuth") 43 | ``` 44 | 45 | * **Step 2: Install the sinkhole certificate and key to the BIG-IP** Either manually install the new certificate and key to the BIG-IP, or use the following TMSH transaction: 46 | ``` 47 | (echo create cli transaction 48 | echo install sys crypto key sinkhole-cert from-local-file "$(pwd)/sinkhole.key" 49 | echo install sys crypto cert sinkhole-cert from-local-file "$(pwd)/sinkhole.crt" 50 | echo submit cli transaction 51 | ) | tmsh 52 | ``` 53 | 54 | * **Step 3: Create a client SSL profile that uses the sinkhole certificate and key** Either manually create a client SSL profile and bind the sinkhole certificate and key, or use the following TMSH command: 55 | ``` 56 | tmsh create ltm profile client-ssl sinkhole-clientssl cert sinkhole-cert key sinkhole-cert > /dev/null 57 | ``` 58 | 59 | * **Step 4: Create the sinkhole "internal" virtual server** This virtual server simply hosts the client SSL profile and sinkhole certificate that SSL Orchestrator will use to forge a blocking certificate. 60 | 61 | - Type: Standard 62 | - Source Address: 0.0.0.0/0 63 | - Destination Address/Mask: 0.0.0.0/0 64 | - Service Port: 9999 (does not really matter) 65 | - HTTP Profile (Client): http 66 | - SSL Profile (Client): the sinkhole client SSL profile 67 | - VLANs and Tunnel Traffic: select "Enabled on..." and leave the Selected box empty 68 | 69 | or use the following TMSH command: 70 | 71 | ``` 72 | tmsh create ltm virtual sinkhole-internal-vip destination 0.0.0.0:9999 profiles replace-all-with { tcp http sinkhole-clientssl } vlans-enabled 73 | ``` 74 | 75 | * **Step 5: Create the sinkhole target iRule** This iRule will be placed on the SSL Orchestrator topology to steer traffic to the sinkhole internal virtual server. Notice the contents of the HTTP_REQUEST event. This is the HTML blocking page content. Edit this at will to meet your local requriements. 76 | 77 | ``` 78 | when CLIENT_ACCEPTED { 79 | virtual "sinkhole-internal-vip" 80 | } 81 | when CLIENTSSL_CLIENTHELLO priority 800 { 82 | if {[SSL::extensions exists -type 0]} { 83 | binary scan [SSL::extensions -type 0] @9a* SNI 84 | } 85 | 86 | if { [info exists SNI] } { 87 | SSL::forward_proxy extension 2.5.29.17 "critical,DNS:${SNI}" 88 | } 89 | } 90 | when HTTP_REQUEST { 91 | HTTP::respond 403 content "

Site Blocked!

" 92 | } 93 | ``` 94 | 95 | ----------------- 96 | 97 | ### Configuration: Sinkhole External (SSL Orchestrator) 98 | 99 | To create the **SSL Orchestrator** outbound L3 topology configuration, in the SSL Orchestrator UI, create a new Topology. Any section not mentioned below can be skipped. 100 | 101 | * **Topology Properties** 102 | 103 | - Protocol: TCP 104 | - SSL Orchestrator Topologies: select L3 Outbound 105 | 106 | * **SSL Configuration** 107 | 108 | - Click on "Show Advanced Setting" 109 | - CA Certificate Key Chain: select the correct client-trusted internal signing CA certificate and key 110 | - Expire Certificate Response: Mask 111 | - Untrusted Certificate Response: Mask 112 | 113 | * **Security Policy** 114 | 115 | - Delete the Pinners_Rule 116 | 117 | * **Interception Rule** 118 | 119 | - Destination Address/Mask: enter the client-facing IP address/mask. This will be the address sent to clients from the DNS for the sinkhole 120 | - Ingress Network/VLANs: select the client-facing VLAN 121 | - Protocol Settings/SSL Configurations: ensure the previously-created SSL configuration is selected 122 | 123 | Ignore all other settings and **Deploy**. Once deployed, navigate to the Interception Rules tab and edit the new topology interception rule. 124 | 125 | - Resources/iRules: add the **sinkhole-target-rule** iRule 126 | 127 | Ignore all other settings and **Deploy**. 128 | 129 | ----------------- 130 | 131 | ### Testing 132 | 133 | The easiest way to **test** this solution is to create an **/etc/hosts** file entry on a client for some Internet site (ex. www.example.com) and point that to your SSL Orchestrator (sinkhole external) listening IP. Attempt to access that site via HTTPS and HTTP from a browser on this client. The blocking page content will be returned along with a valid locally-issued server certificate. This minimally tests a single client through local DNS manipulation. To expand on this idea into more practical implementations, consider the following: 134 | 135 | * Any proper DNS security solution can work here, from any security vendor, assuming it supports sinkholing (returning a specified address to clients for blocked sites). 136 | 137 | * For environments that allow/enable DNS-over-HTTPS (DoH) and/or DNS-over-TLS (DoT) to third party DoH/DoT resolvers (ex. Cloudflare), a modification of the [SSL Orchestrator DNS-over-HTTPS Detection](https://github.com/f5devcentral/sslo-script-tools/tree/main/sslo-dns-over-https-detection) use case could be employed. This is an SSL Orchestrator use case for the detection, decryption, and management of outgoing DoH/DoT traffic. The modified DoH/DoT detection iRule is provided in **this** repository: **sslo-doh-logging.tcl**. Add this iRule to the BIG-IP, then add to the Interception Rule of a standard outbound L3 SSL Orchestrator topology. The local version of this iRule adds support for DNS sinkhole. In the RULE_INIT section, a new **set static::SINKHOLE_IP** variable exists. To enable sinkholing, plug the local sinkhole IP address into this variable. 138 | 139 | * Injecting a static HTML response blocking page is just one option among many. You could, for example, issue a redirect instead of static HTML, and send the client to a more formal "splash page". This redirect could inject additional metadata to provide to the splash page. For example: 140 | 141 | ``` 142 | when HTTP_REQUEST { 143 | HTTP::redirect "https://splash.f5labs.com/?cats=gis-dns-block&client_ip=[IP::client_addr]&type=dns&url=[URI::encode [b64encode [HTTP::host]]" 144 | } 145 | ``` 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /sslo-dns-sinkhole/create-sinkhole-internal-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SSLO DNS Sinkhole Internal Config Creator 3 | # Author: kevin-at-f5-dot-com 4 | # Version: 20230828-1 5 | # Creates the sinkhole cert/key and internal VIP configuration 6 | 7 | ## Create the sinkhole cert and key with empty Subject field 8 | openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 -nodes \ 9 | -keyout "sinkhole.key" \ 10 | -out "sinkhole.crt" \ 11 | -subj "/" \ 12 | -config <(printf "[req]\n 13 | distinguished_name=dn\n 14 | x509_extensions=v3_req\n 15 | [dn]\n\n 16 | [v3_req]\n 17 | keyUsage=critical,digitalSignature,keyEncipherment\n 18 | extendedKeyUsage=serverAuth,clientAuth") > /dev/null 2>&1 19 | 20 | ## Update the BIG-IP sinkhole cert/key objects 21 | (echo create cli transaction 22 | echo install sys crypto key sinkhole-cert from-local-file "$(pwd)/sinkhole.key" 23 | echo install sys crypto cert sinkhole-cert from-local-file "$(pwd)/sinkhole.crt" 24 | echo submit cli transaction 25 | ) | tmsh > /dev/null 2>&1 26 | 27 | ## Create sinkhole client SSL profile and virtual server 28 | tmsh create ltm profile client-ssl sinkhole-clientssl cert sinkhole-cert key sinkhole-cert > /dev/null 2>&1 29 | tmsh create ltm virtual sinkhole-internal-vip destination 0.0.0.0:9999 profiles replace-all-with { tcp http sinkhole-clientssl } vlans-enabled > /dev/null 2>&1 30 | 31 | rm -f sinkhole.crt sinkhole.key 32 | -------------------------------------------------------------------------------- /sslo-dns-sinkhole/sslo-doh-logging.tcl: -------------------------------------------------------------------------------- 1 | ## Rule: SSL Orchestrator DNS-over-HTTPS detection, logging and blackholing, and sinkhole support 2 | ## Author: Kevin Stewart 3 | ## Version: 20230828-5 4 | ## Function: Creates a mechanism to detect, log, and potentially blackhole DNS-over-HTTPS requests through an SSL Orchestrator outbound topology. 5 | ## Instructions: 6 | ## - Under Local Traffic -> iRules in the BIG-IP UI, import the required iRule. 7 | ## - In the SSL Orchestrator UI, under the Interception Rules tab, click on the interception rule to edit (typically ending with "-in-t"), and at the bottom of this configuration page, add the iRule and then (re)deploy. 8 | ## - If using the DoH detection and logging iRule, additional configuration is required (see README). 9 | ## Ref: https://github.com/f5devcentral/sslo-script-tools/tree/main/sslo-dns-over-https-detection 10 | 11 | when RULE_INIT { 12 | ## User-defined: send log traffic to local syslog facility (not recommended under load) 13 | ## Disabled (value: 0) or enabled (value: 1) 14 | set static::LOCAL_LOG 0 15 | 16 | ## User-defined: send log traffic to external Syslog service via high-speed logging (HSL) 17 | ## Disable HSL (value: "none") or enable (value: syslog pool name) 18 | #set static::HSL "syslog-pool" 19 | set static::HSL "none" 20 | 21 | ## User-defined: is URLDB licensed and provisioned (otherwise local custom URL categories are still available) 22 | ## Disabled (value: 0) or enabled (value: 1) 23 | set static::URLDB_LICENSED 0 24 | 25 | ## User-defined: generate blackhole DNS responses for these URL categories. 26 | ## Disabled (value: empty) or enabled (value: names of URL categories to block) 27 | ## If URLDB is not licensed and provisioned, this list can still contain local custom URL categories 28 | ## 29 | ## For custom URL categories, enter the URLs to block in this format: https://[domain]/. Example: 30 | ## https://www.example.com/ 31 | ## 32 | ## Add the URL categories to the following block. Example: 33 | ## set static::BLACKHOLE_URLCAT { 34 | ## /Common/block-doh-urls 35 | ## } 36 | set static::BLACKHOLE_URLCAT { 37 | 38 | } 39 | 40 | ## User-defined: sinkhole IP address 41 | set static::SINKHOLE_IP "" 42 | 43 | ## User-defined: if enabled, generate blachole DNS responses for these record types 44 | ## Disabled (value: 0) or enabled (value: 1) 45 | set static::BLACKHOLE_RTYPE_A 1 46 | set static::BLACKHOLE_RTYPE_AAAA 1 47 | set static::BLACKHOLE_RTYPE_TXT 1 48 | 49 | ## Set DNS record types: (ex. A=1, AAAA=28) ref: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml 50 | #array set static::dns_codes { 1 A 2 NS 5 CNAME 6 SOA 12 PTR 16 TXT 28 AAAA 33 SRV 257 CAA } 51 | array set static::dns_codes { 1 A 2 NS 5 CNAME 6 SOA 12 PTR 13 HINFO 15 MX 16 TXT 17 RP 18 AFSDB 28 AAAA 29 LOC 33 SRV 35 NAPTR 37 CERT 39 DNAME 43 DS 46 RRSIG 47 NSEC 48 DNSKEY 49 DHCID 50 NSEC3 51 NSEC3PARAM 52 TLSA 65 HTTPS 99 SPF 257 CAA } 52 | } 53 | proc ip_to_hex { ip4 } { 54 | set iplist [split ${ip4} "."] 55 | set ipint [expr { \ 56 | [expr { [lindex ${iplist} 3] }] + \ 57 | [expr { [lindex ${iplist} 2] * 256 }] + \ 58 | [expr { [lindex ${iplist} 1] * 65536 }] + \ 59 | [expr { [lindex ${iplist} 0] * 16777216 }] \ 60 | }] 61 | return [format %x ${ipint}] 62 | } 63 | proc DOH_URL_BLOCK { id name ver hsl } { 64 | ## This procedure consumes the query type id (A,AAAA), query name, DoH version (WF or JSON) and HSL object 65 | ## and generates a blackhole DNS response is the name matches a defined URL category. Works for A, AAAA, and TXT records. 66 | set type [lindex [split ${name} ":"] 0] 67 | set name [lindex [split ${name} ":"] 1] 68 | 69 | if { ${static::URLDB_LICENSED}} { 70 | set match 0 ; if { [llength ${static::BLACKHOLE_URLCAT}] > 0 } { set res [CATEGORY::lookup "https://${name}/" request_default_and_custom] ; foreach url ${res} { if { [lsearch -exact ${static::BLACKHOLE_URLCAT} ${url}] >= 0 } { set match 1 } } } 71 | } else { 72 | set match 0 ; if { [llength ${static::BLACKHOLE_URLCAT}] > 0 } { set res [CATEGORY::lookup "https://${name}/" custom] ; foreach url ${res} { if { [lsearch -exact ${static::BLACKHOLE_URLCAT} ${url}] >= 0 } { set match 1 } } } 73 | } 74 | 75 | if { ${match} } { 76 | ## Get sinkhole IP, or use default 77 | if { $static::SINKHOLE_IP ne "" } { 78 | set ipinjected $static::SINKHOLE_IP 79 | set iphexinjected [call ip_to_hex $static::SINKHOLE_IP] 80 | } else { 81 | set ipinjected "199.199.199.199" 82 | set iphexinjected [call ip_to_hex "199.199.199.199"] 83 | } 84 | 85 | if { ( ${type} eq "A" ) and ( ${static::BLACKHOLE_RTYPE_A} ) } { 86 | if { ${ver} eq "WF" } { 87 | ## build DNS A record blackhole response 88 | set retstring "${id}81800001000100000000" 89 | foreach x [split ${name} "."] { 90 | append retstring [format %02x [string length ${x}]] 91 | foreach y [split ${x} ""] { 92 | append retstring [format %02x [scan ${y} %c]] 93 | } 94 | } 95 | ## c7c7c7c7c = 199.199.199.199 96 | #append retstring {0000010001c00c00010001000118c30004c7c7c7c7} 97 | append retstring "0000010001c00c00010001000118c300040${iphexinjected}" 98 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 99 | HTTP::respond 200 content [binary format H* ${retstring}] "Content-Type" "application/dns-message" "Access-Control-Allow-Origin" "*" 100 | } elseif { ${ver} eq "JSON" } { 101 | #set template "\{\"Status\": 0,\"TC\": false,\"RD\": true,\"RA\": true,\"AD\": true,\"CD\": false,\"Question\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\": 1 \}\],\"Answer\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\":1,\"TTL\": 84078,\"data\": \"199.199.199.199\" \}\]\}" 102 | set template "\{\"Status\": 0,\"TC\": false,\"RD\": true,\"RA\": true,\"AD\": true,\"CD\": false,\"Question\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\": 1 \}\],\"Answer\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\":1,\"TTL\": 84078,\"data\": \"${ipinjected}\" \}\]\}" 103 | set template [string map [list "BLACKHOLE_TEMPLATE" ${name}] ${template}] 104 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 105 | HTTP::respond 200 content ${template} "Content-Type" "application/dns-json" "Access-Control-Allow-Origin" "*" 106 | } elseif { ${ver} eq "DoT" } { 107 | ## build DNS A record blackhole response 108 | set retstring "${id}81800001000100000000" 109 | foreach x [split ${name} "."] { 110 | append retstring [format %02x [string length ${x}]] 111 | foreach y [split ${x} ""] { 112 | append retstring [format %02x [scan ${y} %c]] 113 | } 114 | } 115 | ## c7c7c7c7c = 199.199.199.199 116 | #append retstring {0000010001c00c00010001000118c30004c7c7c7c7} 117 | append retstring "0000010001c00c00010001000118c300040${iphexinjected}" 118 | set lenhex [format %04x [string length ${retstring}]] 119 | set retstring "${lenhex}${retstring}" 120 | call DOH_LOG "Sending DoT Blackhole for Request" "${type}:${name}" ${hsl} 121 | SSL::respond [binary format H* ${retstring}] 122 | } 123 | } elseif { ( ${type} eq "AAAA" ) and ( ${static::BLACKHOLE_RTYPE_AAAA} ) } { 124 | if { ${ver} eq "WF" } { 125 | ## build DNS A record blackhole response 126 | set retstring "${id}81800001000100000000" 127 | foreach x [split ${name} "."] { 128 | append retstring [format %02x [string length ${x}]] 129 | foreach y [split ${x} ""] { 130 | append retstring [format %02x [scan ${y} %c]] 131 | } 132 | } 133 | ## 0:0:0:0:0:ffff:c7c7:c7c7 134 | append retstring {00001c0001c00c001c00010001488100100000000000000000000ffffc7c7c7c7} 135 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 136 | HTTP::respond 200 content [binary format H* ${retstring}] "Content-Type" "application/dns-message" "Access-Control-Allow-Origin" "*" 137 | } elseif { ${ver} eq "JSON" } { 138 | set template "\{\"Status\": 0,\"TC\": false,\"RD\": true,\"RA\": true,\"AD\": true,\"CD\": false,\"Question\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\": 28 \}\],\"Answer\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\":28,\"TTL\": 84078,\"data\": \"0:0:0:0:0:ffff:c7c7:c7c7\" \}\]\}" 139 | set template [string map [list "BLACKHOLE_TEMPLATE" ${name}] ${template}] 140 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 141 | HTTP::respond 200 content ${template} "Content-Type" "application/dns-json" "Access-Control-Allow-Origin" "*" 142 | } elseif { ${ver} eq "DoT" } { 143 | ## build DNS A record blackhole response 144 | set retstring "${id}81800001000100000000" 145 | foreach x [split ${name} "."] { 146 | append retstring [format %02x [string length ${x}]] 147 | foreach y [split ${x} ""] { 148 | append retstring [format %02x [scan ${y} %c]] 149 | } 150 | } 151 | ## 0:0:0:0:0:ffff:c7c7:c7c7 152 | append retstring {00001c0001c00c001c00010001488100100000000000000000000ffffc7c7c7c7} 153 | set lenhex [format %04x [string length ${retstring}]] 154 | set retstring "${lenhex}${retstring}" 155 | call DOH_LOG "Sending DoT Blackhole for Request" "${type}:${name}" ${hsl} 156 | SSL::respond [binary format H* ${retstring}] 157 | } 158 | } elseif { ( ${type} eq "TXT" ) and ( ${static::BLACKHOLE_RTYPE_TXT} ) } { 159 | if { ${ver} eq "WF" } { 160 | ## build DNS A record blackhole response 161 | set retstring "${id}81800001000100000000" 162 | foreach x [split ${name} "."] { 163 | append retstring [format %02x [string length ${x}]] 164 | foreach y [split ${x} ""] { 165 | append retstring [format %02x [scan ${y} %c]] 166 | } 167 | } 168 | ## generic "v=spf1 -all" 169 | append retstring {0000100001c00c0010000100002a30000c0b763d73706631202d616c6c} 170 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 171 | HTTP::respond 200 content [binary format H* ${retstring}] "Content-Type" "application/dns-message" "Access-Control-Allow-Origin" "*" 172 | } elseif { ${ver} eq "JSON" } { 173 | set template "\{\"Status\": 0,\"TC\": false,\"RD\": true,\"RA\": true,\"AD\": true,\"CD\": false,\"Question\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\": 16 \}\],\"Answer\": \[\{\"name\": \"BLACKHOLE_TEMPLATE\",\"type\":16,\"TTL\": 84078,\"data\": \"v=spf1 -all\" \}\]\}" 174 | set template [string map [list "BLACKHOLE_TEMPLATE" ${name}] ${template}] 175 | call DOH_LOG "Sending DoH Blackhole for Request" "${type}:${name}" ${hsl} 176 | HTTP::respond 200 content ${template} "Content-Type" "application/dns-json" "Access-Control-Allow-Origin" "*" 177 | } elseif { ${ver} eq "DoT" } { 178 | ## build DNS A record blackhole response 179 | set retstring "${id}81800001000100000000" 180 | foreach x [split ${name} "."] { 181 | append retstring [format %02x [string length ${x}]] 182 | foreach y [split ${x} ""] { 183 | append retstring [format %02x [scan ${y} %c]] 184 | } 185 | } 186 | ## generic "v=spf1 -all" 187 | append retstring {0000100001c00c0010000100002a30000c0b763d73706631202d616c6c} 188 | set lenhex [format %04x [string length ${retstring}]] 189 | set retstring "${lenhex}${retstring}" 190 | call DOH_LOG "Sending DoT Blackhole for Request" "${type}:${name}" ${hsl} 191 | SSL::respond [binary format H* ${retstring}] 192 | } 193 | } 194 | } 195 | } 196 | proc DOH_LOG { msg name hsl } { 197 | ## This procedure consumes the message string, DoH question name, and HSL object to generates log messages. 198 | if { ${static::LOCAL_LOG} } { log -noname local0. "[IP::client_addr]:[TCP::client_port]-[IP::local_addr]:[TCP::local_port] :: ${msg}: ${name}" } 199 | if { ${static::HSL} ne "none" } { HSL::send ${hsl} "<190> [IP::client_addr]:[TCP::client_port]-[IP::local_addr]:[TCP::local_port] :: ${msg}: ${name}" } 200 | } 201 | proc DECODE_DNS_REQ { data } { 202 | ## This procedure consumes the HEX-encoded DoH question and decodes to return the question name and type (A,AAAA,TXT, etc.). 203 | if { [catch { 204 | set name "" ; set pos 0 ; set num 0 ; set count 0 ; set typectr 0 ; set type "" 205 | ## process question 206 | foreach {i j} [split ${data} ""] { 207 | scan ${i}${j} %x num 208 | if { ${typectr} > 0 } { 209 | append type "${i}${j}" 210 | if { ${typectr} == 2 } { break } 211 | incr typectr 212 | } elseif { ${num} == 0 } { 213 | ## we're done 214 | set typectr 1 215 | #break 216 | } elseif { ${num} < 31 } { 217 | set pos 1 218 | set count ${num} 219 | append name "." 220 | } elseif { [expr { ${pos} <= ${count} }] } { 221 | set char [binary format H* ${i}${j}] 222 | append name $char 223 | incr pos 224 | } 225 | } 226 | set name [string range ${name} 1 end] 227 | ## process qtype 228 | if { [catch { 229 | scan ${type} %xx type 230 | set typestr $static::dns_codes(${type}) 231 | }] } { 232 | set typestr "UNK" 233 | } 234 | }] } { 235 | return "error" 236 | } else { 237 | return "${typestr}:${name}" 238 | } 239 | } 240 | proc SAFE_BASE64_DECODE { payload } { 241 | if { [catch {b64decode "${payload}[expr {[string length ${payload}] % 4 == 0 ? "":[string repeat "=" [expr {4 - [string length ${payload}] % 4}]]}]"} decoded_value] == 0 and ${decoded_value} ne "" } { 242 | return ${decoded_value} 243 | } else { 244 | return 0 245 | } 246 | } 247 | when CLIENT_ACCEPTED { 248 | ## This event establishes HSL connection (as required) and sends reject if destination address is the blackhole IP. 249 | if { ${static::HSL} ne "none" } { set hsl [HSL::open -proto UDP -pool ${static::HSL}] } else { set hsl "none" } 250 | if { [IP::local_addr] eq "199.199.199.199" } { reject } 251 | if { [IP::local_addr] eq "0:0:0:0:0:ffff:c7c7:c7c7" } { reject } 252 | } 253 | when CLIENTSSL_HANDSHAKE priority 50 { 254 | ## This event triggers on decrypted DoT requests. 255 | if { [TCP::local_port] eq "853" } { 256 | SSL::collect 257 | } 258 | } 259 | when CLIENTSSL_DATA priority 50 { 260 | ## This event is triggered parse the decrypted DoT request. 261 | if { [TCP::local_port] eq "853" } { 262 | binary scan [SSL::payload] H* tmp 263 | set id [string range ${tmp} 4 7] 264 | set tmp [string range ${tmp} 28 end] 265 | if { [set name [call DECODE_DNS_REQ ${tmp}]] ne "error" } { 266 | call DOH_LOG "DoT Request" ${name} ${hsl} 267 | call DOH_URL_BLOCK ${id} ${name} "DoT" ${hsl} 268 | } 269 | SSL::release 270 | } 271 | } 272 | when HTTP_REQUEST priority 750 { 273 | ## This event parses the request looking for DoH type messages. 274 | if { ( [HTTP::method] equals "GET" and [HTTP::header exists "accept"] and [HTTP::header "accept"] equals "application/dns-json" ) } { 275 | ## JSON DoH request 276 | set type [URI::query [HTTP::uri] type] ; if { ${type} eq "" } { set type "A" } 277 | set name [URI::query [HTTP::uri] name] ; if { ${name} ne "" } { call DOH_LOG "DoH (JSON GET) Request" "${type}:${name}" ${hsl} } 278 | call DOH_URL_BLOCK "null" "${type}:${name}" "JSON" ${hsl} 279 | } elseif { ( ( [HTTP::method] equals "GET" and [HTTP::header exists "content-type"] and [HTTP::header "content-type"] equals "application/dns-message" ) \ 280 | or ( [HTTP::method] equals "GET" and [HTTP::header exists "accept"] and [HTTP::header "accept"] equals "application/dns-message" ) ) } { 281 | ## DNS WireFormat DoH GET request 282 | if { [set name [URI::query [HTTP::uri] dns]] >= 0 } { 283 | ## Use this construct to handle potentially missing padding characters 284 | binary scan [call SAFE_BASE64_DECODE ${name}] H* tmp 285 | set id [string range ${tmp} 0 3] 286 | set tmp [string range ${tmp} 24 end] 287 | if { [set name [call DECODE_DNS_REQ ${tmp}]] ne "error" } { 288 | call DOH_LOG "DoH (WireFormat GET) Request" ${name} ${hsl} 289 | call DOH_URL_BLOCK ${id} ${name} "WF" ${hsl} 290 | } 291 | } 292 | } elseif { ( [HTTP::method] equals "POST" and [HTTP::header exists "content-type"] and [HTTP::header "content-type"] equals "application/dns-message" ) } { 293 | ## DNS WireFormat DoH POST request 294 | HTTP::collect 100 295 | } 296 | } 297 | when HTTP_REQUEST_DATA priority 250 { 298 | binary scan [HTTP::payload] H* tmp 299 | set id [string range ${tmp} 0 3] 300 | set tmp [string range ${tmp} 24 end] 301 | if { [set name [call DECODE_DNS_REQ ${tmp}]] ne "error" } { 302 | call DOH_LOG "DoH (WireFormat POST) Request" ${name} ${hsl} 303 | call DOH_URL_BLOCK ${id} ${name} "WF" ${hsl} 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /sslo-generative-ai-categories/Readme.md: -------------------------------------------------------------------------------- 1 | ## Detecting Generative AI tools with SSL Orchestrator 2 | 3 | #### This simple script creates a custom URL category on the F5 BIG-IP and populates with known generative AI URLs. You can then use this custom category in an SSL Orchestrator security policy rule to categorize on known generative AI tools. 4 | 5 | ----------------- 6 | To deploy: 7 | 8 | * **Step 1**: Create the custom URL category and populate with known AI URLs - Access the BIG-IP command shell and run the following command. This will initiate a script that creates and populates the URL category: 9 | 10 | ``` 11 | curl -s https://raw.githubusercontent.com/f5devcentral/sslo-script-tools/main/sslo-generative-ai-categories/sslo-create-ai-category.sh |bash 12 | ``` 13 | 14 | * **Step 2**: Create an SSL Orchestrator policy rule to use this data - The above script creates/re-populates a custom URL category named **SSLO_AI_TOOLS**, and in that category is a set of _known_ generative AI URLs. To use, navigate to the SSL Orchestrator UI and edit a Security Policy. Click **Add** to create a new policy rule, use the "Category Lookup (All)" policy condition, then add the above URL category. Set the policy rule actions accordingly: 15 | 16 | * **Action**: set this to Allow or Block depending on your local requirements 17 | * **SSL Proxy Action**: set this to Intercept to enable decryption and inspection 18 | * **Service Chain**: apply to whatever service chain you've already created 19 | 20 | 21 |
22 |
23 | 24 | To log category matches, edit the SSL Orchestrator Topology, and under Log Settings, set **SSL Orchestrator Generic** to "Information". This will enable a Traffic Summary log entry for each flow. By default this goes to local Syslog, but can be pushed to a remote collector by adjusting the Log Publisher. The following example Traffic Summary log entry lists both the policy rule match (GenerativeAI_Detect) and the URL category (/Common/SSLO_AI_TOOLS): 25 | 26 |
27 | 28 | ``` 29 | # tail -f /var/log/apm 30 | 31 | Jul 24 07:24:54 sslo1.f5labs.com info tmm3[10913]: 01c40000:6: /Common/sslo_demo.app/sslo_demo_accessProfile:Common:55e3d724: /Common/sslo_demo.app/sslo_demo-in-t-4 Traffic summary - tcp 10.1.10.50:45896 -> 13.107.213.70:443 clientSSL: TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 serverSSL: TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 L7 https (openai.com) decryption-status: decrypted duration: 138 msec service-path: ssloSC_all_services client-bytes-in: 1565 client-bytes-out: 99621 server-bytes-in: 103236 server-bytes-out: 2452 client-tls-handshake: completed server-tls-handshake: completed reset-cause: 'NA' policy-rule: 'GenerativeAI_Detect' url-category: /Common/SSLO_AI_TOOLS ingress: /Common/client-vlan egress: /Common/outbound-vlan 32 | ``` 33 | 34 |
35 | 36 | ----------------- 37 | **Note**: Officially, ChatGPT through SSL Orchestrator requires HTTP/2. The **Proxy ALPN** option was added in 9.0: https://clouddocs.f5.com/sslo-deployment-guide/sslo-09/chapter1/page1.02.html. 38 | 39 | ----------------- 40 | **Last Updated**: 08 April 2025 41 | 42 | ----------------- 43 | **Note**: The URLs provided here are in no way exhaustive of all things "AI", and generally includes sites that provide all manner of AI utilities and services. The URLs are collected from various sources: 44 | - https://aitoolsdirectory.com/ 45 | - https://github.com/filipecalegario/awesome-generative-ai 46 | - https://github.com/steven2358/awesome-generative-ai 47 | - https://doc.clickup.com/25598832/d/h/rd6vg-14247/0b79ca1dc0f7429/rd6vg-12207 48 | -------------------------------------------------------------------------------- /sslo-generative-ai-categories/ai-category-chat: -------------------------------------------------------------------------------- 1 | ab.bot 2 | activechat.ai 3 | *.activechat.ai 4 | ai.ls 5 | aibert.co 6 | www.aibot.how 7 | aichatbestie.com 8 | www.aichatting.net 9 | *.aissistify.com 10 | agentgpt.reworkd.ai 11 | *.aichatsms.com 12 | alethea.ai 13 | andisearch.com 14 | anonchatgpt.com 15 | anywebsite.ai 16 | asknotion.app 17 | *.askrobi.com 18 | askyourpdf.com 19 | automatic.chat 20 | autoseductionai.com 21 | *.autoseductionai.com 22 | banterai.app 23 | beehelp.net 24 | www.belva.ai 25 | *.botrush.io 26 | botmate.co 27 | www.botsheets.com 28 | app.bodt.io 29 | brainypdf.com 30 | *.brightbot.app 31 | callannie.ai 32 | chadview.com 33 | *.chaindesk.ai 34 | *.chatabc.ai 35 | *.chatbase.co 36 | chatbotkit.com 37 | www.chatcsv.co 38 | chatfai.com 39 | www.chatfast.io 40 | chatflux.io 41 | *.chatfuel.com 42 | chatgenie.xyz 43 | chathub.gg 44 | chatkit.app 45 | www.chatmate.ai 46 | *.chatnode.ai 47 | chatpad.ai 48 | www.chatshape.com 49 | app.chatspell.co 50 | www.chatsuggest.com 51 | chattab.app 52 | chattube.io 53 | chatwithdata.ai 54 | www.chatwithme.chat 55 | www.circlechat.co 56 | claude.ai 57 | coachvox.ai 58 | cometcore.co 59 | *.commandbar.com 60 | *.contlo.com 61 | *.crisp.chat 62 | *.customgpt.ai 63 | *.dailybot.com 64 | dappergpt.com 65 | deepai.org 66 | docsai.app 67 | *.docuchat.io 68 | app.doks.ai 69 | app.dropchat.co 70 | seneca.dylancastillo.co 71 | embra.app 72 | *.eesel.ai 73 | www.experai.com 74 | flowgpt.com 75 | *.forefront.ai 76 | franks.ai 77 | www.futuredesk.io 78 | fyli.ai 79 | *.getchunky.io 80 | chat.getgptapi.com 81 | *.getodin.ai 82 | www.ghola.ai 83 | glasgowgpt.com 84 | *.godinabox.co 85 | godly.ai 86 | godmode.space 87 | bard.google.com 88 | gpt-persona.com 89 | gpt3demo.com 90 | gptassistant.app 91 | hotpot.ai 92 | huggingface.co 93 | humanfest.chat 94 | *.humata.ai 95 | *.ingestai.io 96 | inputai.com 97 | interviewbot.com 98 | studio.inworld.ai 99 | *.jasper.ai 100 | jedigpt.ai 101 | kaya.chat 102 | *.kili.so 103 | knibble.ai 104 | *.knowbo.ai 105 | komo.ai 106 | www.konjer.xyz 107 | korewa.ai 108 | *.landbot.io 109 | www.langotalk.org 110 | webwhiz.lemonsqueezy.com 111 | letsview.com 112 | *.lexii.ai 113 | *.libraria.dev 114 | chat.lmsys.org 115 | lovegpt.co.in 116 | www.madlad.ai 117 | magicbuddy.chat 118 | *.magicform.ai 119 | www.messengerx.io 120 | *.meya.ai 121 | mindos.com 122 | *.mottle.com 123 | mst.ai 124 | deepsearch.mycelebs.com 125 | *.mymap.ai 126 | *.myshell.ai 127 | norby.io 128 | open-assistant.io 129 | openai.com 130 | *.openai.com 131 | cloud.openchat.so 132 | peopleai.app 133 | www.perplexity.ai 134 | poe.com 135 | www.practicetalking.net 136 | www.promptkeeps.com 137 | www.quickchat.ai 138 | *.quino.ai 139 | www.rizzgpt.app 140 | *.searchie.io 141 | *.seashore.ai 142 | *.shoppingbot.ai 143 | *.shopmate.chat 144 | gptflix.streamlit.app 145 | superapi.ai 146 | www.supportguy.co 147 | *.talkberry.ai 148 | talkface.ai 149 | *.teddyai.com 150 | telechat.ai 151 | www.textbff.com 152 | textgpt.net 153 | textsfrommyex.com 154 | theoassist.com 155 | *.thinkchain.ai 156 | *.threado.com 157 | *.tidio.com 158 | twinning.me 159 | typeset.io 160 | www.typingmind.com 161 | app.usechat.ai 162 | unschooler.me 163 | *.unschooler.me 164 | *.usefini.com 165 | www.userdesk.io 166 | gptme.vana.com 167 | happy-mama.vercel.app 168 | app.videosage.ai 169 | *.visus.ai 170 | vizologi.com 171 | *.voiceflow.com 172 | whot.tech 173 | app.winggg.com 174 | wiz.chat 175 | *.wonderchat.io 176 | *.workgpt.us 177 | wpchat.ai 178 | wpdocs.chat 179 | *.xata.io 180 | www.xmagic.ai 181 | *.yesilhealth.com 182 | youai.ai 183 | yourfriends.ai 184 | *.zbrain.ai 185 | zevbot.com 186 | *.zipchat.ai 187 | -------------------------------------------------------------------------------- /sslo-generative-ai-categories/sslo-create-ai-category.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Set category names 4 | AI_CATEGORY="SSLO_AI_TOOLS" 5 | 6 | ## Set category URLs 7 | AI_URLS="https://raw.githubusercontent.com/f5devcentral/sslo-script-tools/main/sslo-generative-ai-categories/ai-tools-urls" 8 | 9 | ## Fetch remote URL category 10 | curl -sO ${AI_URLS} 11 | 12 | ## Get current datetime 13 | TIMESTAMP=$(date +"%Y%m%d") 14 | 15 | ## Loop through URL list to build category data 16 | for url in $(cat ai-tools-urls) 17 | do 18 | ## If url contains "*", set as a glob-match URL, otherwise exact-match 19 | if [[ $url =~ "*" ]] 20 | then 21 | url=$(echo $url | sed -E 's/\*/\\*/') 22 | str_urls="${str_urls} urls add { \"https://${url}/\" { type glob-match }} urls add { \"http://${url}/\" { type glob-match }}" 23 | else 24 | str_urls="${str_urls} urls add { \"https://${url}/\" { type exact-match }} urls add { \"http://${url}/\" { type exact-match }}" 25 | fi 26 | done 27 | 28 | ## Test for existing custom URL category 29 | exists=true && [[ "$(tmsh list /sys url-db url-category ${AI_CATEGORY} 2>&1)" =~ "was not found" ]] && config=false 30 | if ($config) 31 | then 32 | ## Category exists - overwrite 33 | tmsh -a modify /sys url-db url-category ${AI_CATEGORY} display-name "${AI_CATEGORY}" urls replace-all-with { https://${TIMESTAMP}/ { type exact-match } } default-action allow 2>&1 34 | else 35 | ## Category doesn't exist - create 36 | tmsh -a create /sys url-db url-category ${AI_CATEGORY} display-name "${AI_CATEGORY}" urls replace-all-with { https://${TIMESTAMP}/ { type exact-match } } default-action allow 2>&1 37 | fi 38 | 39 | ## Populate category with URLs 40 | tmsh -a modify /sys url-db url-category ${AI_CATEGORY} ${str_urls} 2>&1 41 | 42 | ## Clean up temp files 43 | rm -f ai-tools-urls 44 | -------------------------------------------------------------------------------- /sslo-nuke-delete/README.md: -------------------------------------------------------------------------------- 1 | # F5 SSL Orchestrator Nuclear Delete Script 2 | A small Bash utility to completely remove all SSL Orchestrator configurations and objects. 3 | 4 | ### Current version: 5 | 7.0.0 6 | 7 | ### Version support 8 | This utility works on BIG-IP 14.1 and above, SSL Orchestrator 5.x and above. 9 | 10 | ### How to install 11 | - Download the script onto the F5 BIG-IP: 12 | 13 | `curl -k https://raw.githubusercontent.com/f5devcentral/sslo-script-tools/main/sslo-nuke-delete/sslo-nuke-delete.sh -o sslo-nuke-delete.sh` 14 | 15 | `chmod +x sslo-nuke-delete.sh` 16 | 17 | `./sslo-nuke-delete.sh` 18 | 19 | - Run the script more than once if any errors are returned. 20 | -------------------------------------------------------------------------------- /sslo-nuke-delete/sslo-nuke-delete.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SSL Orchestrator Nuclear Delete Script 3 | # Version: 7.0 4 | # Last Modified: October 2020 5 | # Update author: Kevin Stewart, Sr. SSA F5 Networks 6 | # 7 | # >>> NOTE: THIS VERSION OF THE NUCLEAR DELETE SUPPORTED BY SSL ORCHESTRATOR 5.0 OR HIGHER <<< 8 | # 9 | 10 | #----------------------------------------------------------------------- 11 | # User Options - Modify as necessary 12 | #----------------------------------------------------------------------- 13 | user_pass='admin:admin' 14 | 15 | 16 | #----------------------------------------------------------------------- 17 | # Fetch existing installed RPM 18 | #----------------------------------------------------------------------- 19 | installed_rpm=$(restcurl shared/iapp/installed-packages |grep "packageName" |awk -F"\"" '{print $4}') 20 | 21 | 22 | #----------------------------------------------------------------------- 23 | # Delete iApp blocks and packages 24 | #----------------------------------------------------------------------- 25 | echo "** Deleting iApp templates and installed package" 26 | blocks=$(restcurl shared/iapp/blocks | grep " \"id\":" |grep -v " \"id\":" |awk -F"\"" '{print $4}') 27 | for b in $blocks 28 | do 29 | restcurl -X DELETE shared/iapp/blocks/${b} > /dev/null 2>&1 30 | done 31 | packages=$(restcurl shared/iapp/installed-packages | grep " \"id\":" |grep -v " \"id\":" |awk -F"\"" '{print $4}') 32 | for p in $packages 33 | do 34 | restcurl -X DELETE shared/iapp/installed-packages/${p} > /dev/null 2>&1 35 | done 36 | 37 | 38 | #----------------------------------------------------------------------- 39 | # Delete application service templates 40 | #----------------------------------------------------------------------- 41 | echo "** Deleting application services" 42 | appsvcs=$(restcurl -u ${user_pass} mgmt/tm/sys/application/service | jq -r '.items[].fullPath' |sed 's/\/Common\///g' |grep ^sslo) 43 | for a in $appsvcs 44 | do 45 | tmsh modify sys application service ${a} strict-updates disabled 46 | tmsh delete sys application service ${a} 47 | done 48 | 49 | 50 | #----------------------------------------------------------------------- 51 | # Unbind SSLO objects 52 | #----------------------------------------------------------------------- 53 | echo "** Unbinding SSLO objects" 54 | for block in `curl -sk -X GET 'https://localhost/mgmt/shared/iapp/blocks?$select=id,state,name&$filter=state%20eq%20%27*%27%20and%20state%20ne%20%27TEMPLATE%27' -u ${user_pass} | jq -r '.items[] | [.name, .id] |join(":")' |grep -E '^sslo|f5-ssl-orchestrator' | awk -F":" '{print $2}'`; do 55 | curl -sk -X PATCH "https://localhost/mgmt/shared/iapp/blocks/${block}" -d '{state:"UNBINDING"}' -u ${user_pass} > /dev/null 2>&1 56 | sleep 15 57 | curl -sk -X DELETE "https://localhost/mgmt/shared/iapp/blocks/${block}" -u ${user_pass} > /dev/null 2>&1 58 | done 59 | 60 | 61 | #----------------------------------------------------------------------- 62 | # Delete SSLO objects 63 | #----------------------------------------------------------------------- 64 | echo "** Deleting SSLO objects" 65 | sslo_objects='' 66 | sslo_objects=`tmsh list |grep -v "^\s" |grep sslo |sed -e 's/{//g;s/}//g' |grep -v "apm profile access /Common/ssloDefault_accessProfile" |grep -v "apm log-setting /Common/default-sslo-log-setting" |grep -v "net dns-resolver /Common/ssloGS_global.app/ssloGS-net-resolver" |grep -v "sys application service /Common/ssloGS_global.app/ssloGS_global" |grep -v "sys provision sslo"` 67 | tmsh delete apm profile access /Common/ssloDefault_accessProfile > /dev/null 2>&1 68 | tmsh delete net dns-resolver /Common/ssloGS_global.app/ssloGS-net-resolver > /dev/null 2>&1 69 | tmsh delete sys application service /Common/ssloGS_global.app/ssloGS_global > /dev/null 2>&1 70 | tmsh delete apm policy access-policy /Common/ssloDefault_accessPolicy > /dev/null 2>&1 71 | 72 | while read -r line 73 | do 74 | if [ ! -z "$sslo_objects" ] 75 | then 76 | eval "tmsh delete $line" > /dev/null 2>&1 77 | fi 78 | done <<< "$sslo_objects" 79 | 80 | 81 | #----------------------------------------------------------------------- 82 | # Delete application service templates (again) 83 | #----------------------------------------------------------------------- 84 | appsvcs=$(restcurl -u ${user_pass} mgmt/tm/sys/application/service | jq -r '.items[].fullPath' |sed 's/\/Common\///g' |grep ^sslo) 85 | for a in $appsvcs 86 | do 87 | tmsh modify sys application service ${a} strict-updates disabled 88 | tmsh delete sys application service ${a} 89 | done 90 | 91 | 92 | #----------------------------------------------------------------------- 93 | # Unbind SSLO objects (again) 94 | #----------------------------------------------------------------------- 95 | echo "** Unbinding SSLO objects" 96 | for block in `curl -sk -X GET 'https://localhost/mgmt/shared/iapp/blocks?$select=id,state,name&$filter=state%20eq%20%27*%27%20and%20state%20ne%20%27TEMPLATE%27' -u ${user_pass} | jq -r '.items[] | [.name, .id] |join(":")' |grep -E '^sslo|f5-ssl-orchestrator' | awk -F":" '{print $2}'`; do 97 | curl -sk -X PATCH "https://localhost/mgmt/shared/iapp/blocks/${block}" -d '{state:"UNBINDING"}' -u ${user_pass} > /dev/null 2>&1 98 | sleep 15 99 | curl -sk -X DELETE "https://localhost/mgmt/shared/iapp/blocks/${block}" -u ${user_pass} > /dev/null 2>&1 100 | done 101 | 102 | 103 | #----------------------------------------------------------------------- 104 | # Clear REST storage 105 | #----------------------------------------------------------------------- 106 | echo "** Clearing REST storage" 107 | clear-rest-storage -l > /dev/null 2>&1 108 | 109 | 110 | #----------------------------------------------------------------------- 111 | # Pause for 5 seconds 112 | #----------------------------------------------------------------------- 113 | sleep 5 114 | 115 | 116 | #----------------------------------------------------------------------- 117 | # Re-install the previously-installed RPM 118 | #----------------------------------------------------------------------- 119 | DATA="{\"operation\":\"INSTALL\",\"packageFilePath\":\"/var/config/rest/downloads/${installed_rpm}.rpm\"}" 120 | restcurl -u ${user_pass} -X POST "shared/iapp/package-management-tasks" -d ${DATA} > /dev/null 2>&1 121 | 122 | 123 | echo "** Complete - Run this script again if any errors are output." 124 | -------------------------------------------------------------------------------- /sslofix/README.md: -------------------------------------------------------------------------------- 1 | # F5 SSL Orchestrator sslofix script 2 | 3 | The SSLOFIX script is used to diagnose and repair control plane errors and inconsistencies in SSL Orchestrator deployments. 4 | 5 | Future versions of the script will be included in BIG-IP releases and will not require this installation procedure. 6 | 7 | ### Current version: 8 | 9 | 1.0.10 10 | 11 | ### Version support 12 | This utility works on BIG-IP 15.1 and above, SSL Orchestrator 7.x and above. 13 | 14 | ### How to install 15 | - Download the script onto the F5 BIG-IP: 16 | 17 | `cd ~` 18 | 19 | `curl -k https://raw.githubusercontent.com/f5devcentral/sslo-script-tools/main/sslofix/sslofix -o sslofix` 20 | 21 | `chmod +x sslofix` 22 | 23 | ### Usage Instructions 24 | 25 | Full instructions are available in the [SSLO Troubleshooting Guide](https://clouddocs.f5.com/sslo-troubleshooting-guide/main/sslofix.html). --------------------------------------------------------------------------------