i Rules
Log URI Host - Path
when HTTP_REQUEST {
log local0. "This is the HTTP URI [HTTP::uri]"
log local0. "This is the HTTP Host [HTTP::host]"
log local0. "This is the HTTP Path [HTTP::path]"
}
Log all headers
To log all the HTTP Request headers you can use a similar code:
when HTTP_REQUEST {
foreach aHeader [HTTP::header names] {
log local0. "HTTP Request Headers: $aHeader: [HTTP::header value $aHeader]"
}
}
To log specific Request Headers you can use these example actions:
when HTTP_REQUEST {
log local0. "HTTP Method = [HTTP::method]"
log local0. "HTTP URI = [HTTP::uri]"
log local0. "HTTP Path = [HTTP::path]"
log local0. "HTTP Query = [HTTP::query]"
log local0. "HTTP Version = [HTTP::version]"
log local0. "HTTP Host Header = [HTTP::host]"
log local0. "HTTP User Agent Header = [HTTP::header value "user-agent"]"
Display VS Name and IP
when HTTP_REQUEST {
set http_reply "You have reached virtual server [virtual] [IP::local_addr],
This site is a test virtual server."
HTTP::respond 200 content $http_reply
}
Display DNS::question and DNS::answer, IP::client_addr and DNS::origin
ltm rule ltm_log {
when DNS_REQUEST {
log local2. "LTM irule: DNS Requests [DNS::question name] with record type of [DNS::question type] seen from [IP::client_addr]"
}
when DNS_RESPONSE {
log local2. "LTM irule: Client answer was provided by [DNS::origin], with the full response of [DNS::answer]"
}
}
Display DNS::rrtype, DNS::rrname and ip::client_addr
gtm rule gtm_log {
when DNS_REQUEST {
log local2. "GTM irule: A client [IP::client_addr] queried [DNS::rrname] with request type of [DNS::rrtype]"
}
}
TCP logger
when CLIENT_ACCEPTED {
set vip [IP::local_addr]:[TCP::local_port]
}
when SERVER_CONNECTED {
set client "[IP::client_addr]:[TCP::client_port]"
set node "[IP::server_addr]:[TCP::server_port]"
}
when CLIENT_CLOSED {
# log connection info
log local0.info "Client $client -> VIP: $vip -> Node: $node"
}
Security Headers Irule
when HTTP_RESPONSE_RELEASE {
if {!([HTTP::header exists "X-Frame-Options" ])} {
HTTP::header insert "X-Frame-Options" "DENY"
}
if {!([HTTP::header exists "X-XSS-Protection"])} {
HTTP::header insert "X-XSS-Protection" "1; mode=block"
}
if {!([HTTP::header exists "X-Content-Type-Options"])} {
HTTP::header insert "X-Content-Type-Options" "nosniff"
}
if {!([HTTP::header exists "Strict-Transport-Security"])} {
HTTP::header insert "Strict-Transport-Security" "max-age=16070400; includeSubDomains"
}
}
IPI rule
when HTTP_REQUEST {
set ip_reputation_categories [IP::reputation [IP::client_addr]]
set is_reject 0
if {($ip_reputation_categories contains "Windows Exploits")} {
set is_reject 1
}
if {($ip_reputation_categories contains "Web Attacks")} {
set is_reject 1
}
if {($is_reject)} {
log local0. "Attempted access from malicious IP address [IP::client_addr]
($ip_reputation_categories), request was rejected"
HTTP::respond 200 content"<HTML><HEAD><TITLE>Rejected Request</TITLE></HEAD><BODY>The request was rejected. <BR>Attempted access from malicious IP address</BODY></HTML>"
}
}
HTTP test page
when HTTP_REQUEST {
set http_reply "You have reached [HTTP::host],
This site is a test virtual server."
HTTP::respond 200 content $http_reply
}
Log all info
when HTTP_REQUEST {
# set the URL here, log it on the response
set url [HTTP::header Host][HTTP::uri]
set vip [IP::local_addr]:[TCP::local_port]
}
when HTTP_RESPONSE {
set client [IP::client_addr]:[TCP::client_port]
set node [IP::server_addr]:[TCP::server_port]
set nodeResp [HTTP::status]
# log connection info
log local0.info "Client: $client -> VIP:$vip$url -> Node: $node with response $nodeResp"
}
Log all headers
when HTTP_REQUEST {
set LogString "Client [IP::client_addr]:[TCP::client_port] -> [HTTP::host][HTTP::uri]"
log local0. "============================================="
log local0. "============================================="
log local0. "============================================="
log local0. "$LogString (request)"
foreach aHeader [HTTP::header names] {
log local0. "$aHeader: [HTTP::header value $aHeader]"
}
log local0. "============================================="
log local0. "============================================="
log local0. "============================================="
}
when HTTP_RESPONSE {
log local0. "============================================="
log local0. "============================================="
log local0. "============================================="
log local0. "$LogString (response) - status: [HTTP::status]"
foreach aHeader [HTTP::header names] {
log local0. "$aHeader: [HTTP::header value $aHeader]"
}
log local0. "============================================="
log local0. "============================================="
log local0. "============================================="
}
when CLIENTSSL_CLIENTHELLO {
log local0. [IP::client_addr]
log local0. [SSL::cipher name]
log local0. [SSL::cipher version]
}
HSTS
#FP DR
when HTTP_RESPONSE {
HTTP::cookie secure "JSESSIONID" enable
set ck [HTTP::header values "Set-Cookie"]
HTTP::header remove "Set-Cookie"
foreach acookie $ck {
if {$acookie starts_with "JSESSIONID"} {
HTTP::header insert "Set-Cookie" "${acookie}; HttpOnly; SameSite=none "
} else {
HTTP::header insert "Set-Cookie" "${acookie}; HttpOnly; SameSite=none "
}
}
}
Rewrite Host Header
# This iRule rewrites the host header
# in requests and responses based
# on the contents of a datagroup.
# Mapping external fqdns to internal hostnames
# and vice versa
# Datagroups: host_header_rewrite_request_dg
# host_header_rewrite_response_dg
#v1 20161123 - first cut
#v2 20161206 - added response dg and updated logic
when HTTP_REQUEST {
set requestedHost [HTTP::host]
#log local0. "HOST is $requestedHost"
#log local0. "URI is [HTTP::uri]"
if { [class match $requestedHost equals host_header_rewrite_request_dg] } {
HTTP::header replace Host [class lookup $requestedHost host_header_rewrite_request_dg]
#log local0. "Rewriting Host header from $requestedHost -> [class lookup $requestedHost host_header_rewrite_request_dg]"
}
}
when HTTP_RESPONSE {
if { [HTTP::is_redirect] } {
#log local0. "Location before [HTTP::header Location]"
# This assumes absolute urls in Location header. May need to cater for relative in future.
set locationFQDN [getfield [HTTP::header Location] "/" 3]
#log local0. "locationFQDN is $locationFQDN"
if { [class match $locationFQDN equals host_header_rewrite_response_dg] } {
HTTP::header replace Location [string map -nocase "$locationFQDN [class lookup $locationFQDN host_header_rewrite_response_dg]" [HTTP::header Location]]
}
#log local0. "Location after [HTTP::header Location]"
}
}
IP Block
iRule to allow defined list of Test Engineers to connect to the VIP as normal but block everyone not in the datagroup and return a 503
when CLIENT_ACCEPTED {
set disallowed 0
if { [class match [IP::client_addr] eq "client_allow_dg" ] }{
log local0. "Client [IP::client_addr]:[TCP::client_port] traffic is allowed. Client IP match found in client_allow_dg"
# return #return means to exit thecurrent event i.e. CLIENT_ACCEPTED EVENT
} else {
log local0. "Client [IP::client_addr]:[TCP::client_port] traffic is not allowed. Client IP match not found in client_allow_dg"
set disallowed 1
}
}
when HTTP_REQUEST {
if {$disallowed == 1}{
set vip [IP::local_addr]
set uri [HTTP::uri]
log local0. "Client [IP::client_addr]:[TCP::client_port] connected to VS ($vip)for URI $uri"
HTTP::respond 503 noserver
log local0. "A 503 was Returned"
}
}
TLS Decrypt
when CLIENTSSL_HANDSHAKE {
if {[IP::addr [IP::client_addr] equals 192.168.0.16] }
{
log local0. "TCP source port: [TCP::remote_port]"
log local0. "RSA Session-ID:[SSL::sessionid] Master-Key:[SSL::sessionsecret]"
}
}
when SERVERSSL_HANDSHAKE {
log local0. "TCP Source port: [TCP::local_port]"
log local0. "RSA Session-ID:[SSL::sessionid] Master-Key:[SSL::sessionsecret]"
}
MAC address check
when ACCESS_POLICY_AGENT_EVENT {
if { [ACCESS::policy agent_id] eq "chkmac" } {
set mac [ACCESS::session data get "session.client.mac_address" ]
log local0.info "$mac***"
if { [class match $mac equals macDG] } {
ACCESS::session data set "session.logon.custom.chkmac" 1
} else {
ACCESS::session data set "session.logon.custom.chkmac" 0
}
}
}
Sorry / Maintenance page
when HTTP_REQUEST {
if { [active_members [LB::server pool]] == 0 }
{ set http_reply "You have reached [HTTP::host],
This site is down
."
HTTP::respond 200 content $http_reply
}
}
---------------------**********------------
when HTTP_REQUEST {
if {[active_members [LB::server pool]] < 1} {
switch [HTTP::uri] {
"/mylogo.png" {HTTP::respond 200 content [ifile get "mylogo.png"] }
default {HTTP::respond 200 content [ifile get "mypage.html"] }
}
}
}
Limit connections Queuing systems
https://devcentral.f5.com/s/articles/Mitigate-Unplanned-Scale-Issues-with-an-iRule-Waiting-Room
when HTTP_REQUEST {
## Check if host name match otherwise exit.
## This is needed if you have multiple websites running on same Virtual Server
if {[HTTP::host] eq "test.test.local"} {
# waiting room js file, only necessary if you want a canvas animation
if { [HTTP::uri] eq "/bb.js"} {
HTTP::respond 200 content [ifile get bb.js]
TCP::close
return
}
## Set variable
#Your website (unique)shortcode, needed to divide multiple online waiting room iRules on same Virtual Server.
#In this example the shortcode is SITE1
set OWR SITE1
# Max visitor count
# How many concurrent visitors can you serve
set max_visitors 2
# Timeout in seconds
# IdleTimeout value is based your cart ideltimeout value. Must at least be equal to your cart IdleTimeout value.
set IdleTimeout 60
set WaitingRoomTimeout 60
# Decide vistors IP address. Visitors behind a proxy are seen for one visitor.
if { ([HTTP::header exists "True-Client-IP"]) and ([HTTP::header "True-Client-IP"] != "") } {
set Client_IP [HTTP::header "True-Client-IP"]
} else {
set Client_IP [IP::client_addr]
}
# Defining Tables
set VisitorsTable VisitorsTable-$OWR-$max_visitors
set WaitingRoomTable WaitingRoom-$OWR-$max_visitors
# Generic
set unique_id [format "%08d" [expr { int(100000000000 * rand()) }]]
set request_uri [HTTP::host][HTTP::uri]
set BYPASS $OWR-bypass-url-list
# Counters
set VisitorCount [table keys -subtable $VisitorsTable -count]
set WaitingRoomCount [table keys -subtable $WaitingRoomTable -count]
set TotalVisitors [expr {$VisitorCount + $WaitingRoomCount}]
## End Variable
## Monitoring
# Allow monitoring from internal IP's or subnets.
if { ($Client_IP starts_with "192.168.102") && ([HTTP::uri] equals "/getcount") } {
HTTP::respond 200 content "Total Visitors: \[$TotalVisitors\]
Max Visitors: \[$max_visitors\]
Waiting Room Count: \[$WaitingRoomCount\]"
TCP::close
return
}
## Start WaitingRoom
# Check if the visitor session still exists
set VisitorSession [table lookup -subtable $VisitorsTable $Client_IP]
if { $VisitorSession != "" } {
# We have a valid session... The lookup has reset the timer on it so just finish processing
} else {
# No valid session...
# Check if BYPASS URL
set bypass_url [class match -value [HTTP::uri] contains $BYPASS]
if { not ($bypass_url == "") } {
# BYPASS, do nothing
} else {
# NOT BYPASS, Check connection count for displaying WR Page
# So do we have a free 'slot'?
if {$VisitorCount < $max_visitors} {
# Yes we have a free slot... Allocate it..
# Register visitor
table add -subtable $VisitorsTable $Client_IP $unique_id $IdleTimeout
} else {
# Max visitors limit reached, show WaitingRoom
# Insert visitor into WaitingRoomTable
table add -subtable $WaitingRoomTable $Client_IP $unique_id $WaitingRoomTimeout
# Show waiting Room HTML
HTTP::respond 503 content [ifile get waitingroom.html]
}
TCP::close
}
}
}
}
From <https://devcentral.f5.com/s/articles/Mitigate-Unplanned-Scale-Issues-with-an-iRule-Waiting-Room>
1. <html>
2. <head>
3. <meta http-equiv="refresh" content="60">
4. <title>Online Waiting Room</title>
5. <style>
6. </style>
7. </head>
8. <script>
9. var surface;
10. var happy;
11. var x = 25;
12. var y = 25;
13. var dirX = 1;
14. var dirY = 1;
15.
16. function drawCanvas() {
17. // Get our Canvas element
18. surface = document.getElementById("myCanvas");
19. if (surface.getContext) {
20. // If Canvas is supported, load the image
21. dcjp = new Image();
22. dcjp.onload = loadingComplete;
23. dcjp.src = "dcjp_50px.png";
24. }
25. }
26.
27. function loadingComplete(e) {
28. // When the image has loaded begin the loop
29. setInterval(loop, 5);
30. }
31.
32. function loop() {
33. // Each loop we move the image by altering its x/y position
34. // Grab the context
35. var surfaceContext = surface.getContext('2d');
36. // Draw the image
37. surfaceContext.drawImage(dcjp, x, y);
38.
39. x += dirX;
40. y += dirY;
41.
42. if (x <= 0 || x > 700 - 25) {
43. dirX = -dirX;
44. }
45. if (y <= 0 || y > 350 - 40) {
46. dirY = -dirY;
47. }
48. }
49. </script>
50.
51. <body onload="drawCanvas();">
52. <center>
53. <h2>Online Waiting Room</h2>
54. <h3>Hey there...sorry about the wait!</h3>
55. <p>We currently have an exceptionally large number of visitors on the site and you are in the queue.</p>
56. <p>Please hold tight, it should only be a few minutes. Make sure you stay on this page. Be mesmerized below, and you will be automatically redirected shortly.</p>
57. <div>
58. <canvas id="myCanvas" width="700" height="350">
59. <p>Your browser doesn't support canvas.</p>
60. </canvas>
61. </div><br>
62. </center>
63. </body>
64. </html>
From <https://devcentral.f5.com/s/articles/Mitigate-Unplanned-Scale-Issues-with-an-iRule-Waiting-Room>
Set HTTP only and samesite
#FP DR
when HTTP_RESPONSE {
HTTP::cookie secure "JSESSIONID" enable
set ck [HTTP::header values "Set-Cookie"]
HTTP::header remove "Set-Cookie"
foreach acookie $ck {
if {$acookie starts_with "JSESSIONID"} {
HTTP::header insert "Set-Cookie" "${acookie}; HttpOnly; SameSite=none "
} else {
HTTP::header insert "Set-Cookie" "${acookie}; HttpOnly; SameSite=none "
}
}
IPI iRule
when HTTP_REQUEST {
set ip_reputation_categories [IP::reputation [IP::client_addr]]
set is_reject 0
if {($ip_reputation_categories contains "Windows Exploits")} {
set is_reject 1
}
if {($ip_reputation_categories contains "Web Attacks")} {
set is_reject 1
}
if {($ip_reputation_categories contains "Infected Sources")} {
set is_reject 1
}
if {($ip_reputation_categories contains "Tor Proxies")} {
set is_reject 1
}
if {($ip_reputation_categories contains "Denial-of-Service")} {
set is_reject 1
}
if {($ip_reputation_categories contains "Scanners")} {
set is_reject 1
}
if {($ip_reputation_categories contains "BotNets")} {
set is_reject 1
}
if {($is_reject)} {
log local0. "Attempted access from malicious IP address [IP::client_addr]
($ip_reputation_categories), request was rejected"
HTTP::respond 200 content "<HTML><HEAD><TITLE>Rejected Request</TITLE></HEAD><BODY>The request was rejected. <BR>Attempted access from malicious IP address</BODY></HTML>"
}
}
*****
when CLIENT_ACCEPTED {
log local0. "IP Intelligence for IP address [IP::client_addr]:
[IP::reputation [IP::client_addr]]"
}
Client IP- RSA key
when CLIENTSSL_HANDSHAKE {
if {[IP::addr [IP::client_addr] equals 10.10.10.10] } {
log local0. "========CLIENT SIDE==================="
log local0. "TCP source port: [TCP::remote_port]"
log local0. "Master-Key:[SSL::sessionsecret]"
log local0. "[TCP::client_port] :: RSA Session-ID:[SSL::sessionid] Master-Key:[SSL::sessionsecret]"
log local0. "======================================"
log local0. " "
}
}
rewrites the host header
# This iRule rewrites the host header
# in requests and responses based
# on the contents of a datagroup.
# Mapping external fqdns to internal hostnames
# and vice versa
# Datagroups: host_header_rewrite_request_dg
# host_header_rewrite_response_dg
when HTTP_REQUEST {
set requestedHost [HTTP::host]
#log local0. "HOST is $requestedHost"
#log local0. "URI is [HTTP::uri]"
if { [class match $requestedHost equals host_header_rewrite_request_dg] } {
HTTP::header replace Host [class lookup $requestedHost host_header_rewrite_request_dg]
#log local0. "Rewriting Host header from $requestedHost -> [class lookup $requestedHost host_header_rewrite_request_dg]"
}
}
when HTTP_RESPONSE {
if { [HTTP::is_redirect] } {
#log local0. "Location before [HTTP::header Location]"
# This assumes absolute urls in Location header. May need to cater for relative in future.
set locationFQDN [getfield [HTTP::header Location] "/" 3]
#log local0. "locationFQDN is $locationFQDN"
if { [class match $locationFQDN equals host_header_rewrite_response_dg] } {
HTTP::header replace Location [string map -nocase "$locationFQDN [class lookup $locationFQDN host_header_rewrite_response_dg]" [HTTP::header Location]]
}
#log local0. "Location after [HTTP::header Location]"
}
}
Waiting Room
when HTTP_REQUEST {
## Check if host name match otherwise exit.
## This is needed if you have multiple websites running on same Virtual Server
if {[HTTP::host] eq "192.168.51.132"} {
# waiting room js file, only necessary if you want a canvas animation
if { [HTTP::uri] eq "/nhs.png"} {
HTTP::respond 200 content [ifile get nhs.png]
TCP::close
return
}
## Set variable
#Your website (unique)shortcode, needed to divide multiple online waiting room iRules on same Virtual Server.
#In this example the shortcode is SITE1
set OWR SITE1
# Max visitor count
# How many concurrent visitors can you serve
set max_visitors 1
# Timeout in seconds
# IdleTimeout value is based your cart ideltimeout value. Must at least be equal to your cart IdleTimeout value.
set IdleTimeout 60
set WaitingRoomTimeout 60
# Decide vistors IP address. Visitors behind a proxy are seen for one visitor.
if { ([HTTP::header exists "True-Client-IP"]) and ([HTTP::header "True-Client-IP"] != "") } {
set Client_IP [HTTP::header "True-Client-IP"]
} else {
set Client_IP [IP::client_addr]
}
# Defining Tables
set VisitorsTable VisitorsTable-$OWR-$max_visitors
set WaitingRoomTable WaitingRoom-$OWR-$max_visitors
# Generic
set unique_id [format "%08d" [expr { int(100000000000 * rand()) }]]
set request_uri [HTTP::host][HTTP::uri]
set BYPASS $OWR-bypass-url-list
# Counters
set VisitorCount [table keys -subtable $VisitorsTable -count]
set WaitingRoomCount [table keys -subtable $WaitingRoomTable -count]
set TotalVisitors [expr {$VisitorCount + $WaitingRoomCount}]
## End Variable
## Monitoring
# Allow monitoring from internal IP's or subnets.
if { ($Client_IP starts_with "192.168.") && ([HTTP::uri] equals "/getcount") } {
HTTP::respond 200 content "Total Visitors: \[$TotalVisitors\]
Max Visitors: \[$max_visitors\]
Waiting Room Count: \[$WaitingRoomCount\]"
TCP::close
return
}
## Start WaitingRoom
# Check if the visitor session still exists
set VisitorSession [table lookup -subtable $VisitorsTable $Client_IP]
if { $VisitorSession != "" } {
# We have a valid session... The lookup has reset the timer on it so just finish processing
} else {
# No valid session...
# Check if BYPASS URL
set bypass_url [class match -value [HTTP::uri] contains $BYPASS]
if { not ($bypass_url == "") } {
# BYPASS, do nothing
} else {
# NOT BYPASS, Check connection count for displaying WR Page
# So do we have a free 'slot'?
if {$VisitorCount < $max_visitors} {
# Yes we have a free slot... Allocate it..
# Register visitor
table add -subtable $VisitorsTable $Client_IP $unique_id $IdleTimeout
} else {
# Max visitors limit reached, show WaitingRoom
# Insert visitor into WaitingRoomTable
table add -subtable $WaitingRoomTable $Client_IP $unique_id $WaitingRoomTimeout
# Show waiting Room HTML
HTTP::respond 503 content [ifile get waitingroom.html]
}
TCP::close
}
}
}
}
Upload iFiles
png file - example nhs.png
and
waiting-room.html
when HTTP_REQUEST {
## Check if host name match otherwise exit.
## This is needed if you have multiple websites running on same Virtual Server
if {[HTTP::host] eq "192.168.51.132"} {
# waiting room js file, only necessary if you want a canvas animation
if { [HTTP::uri] eq "/nhs.png"} {
HTTP::respond 200 content [ifile get nhs.png]
TCP::close
return
}
## Set variable
#Your website (unique)shortcode, needed to divide multiple online waiting room iRules on same Virtual Server.
#In this example the shortcode is SITE1
set OWR SITE1
# Max visitor count
# How many concurrent visitors can you serve
set max_visitors 1
# Timeout in seconds
# IdleTimeout value is based your cart ideltimeout value. Must at least be equal to your cart IdleTimeout value.
set IdleTimeout 60
set WaitingRoomTimeout 60
# Decide vistors IP address. Visitors behind a proxy are seen for one visitor.
if { ([HTTP::header exists "True-Client-IP"]) and ([HTTP::header "True-Client-IP"] != "") } {
set Client_IP [HTTP::header "True-Client-IP"]
} else {
set Client_IP [IP::client_addr]
}
# Defining Tables
set VisitorsTable VisitorsTable-$OWR-$max_visitors
set WaitingRoomTable WaitingRoom-$OWR-$max_visitors
# Generic
set unique_id [format "%08d" [expr { int(100000000000 * rand()) }]]
set request_uri [HTTP::host][HTTP::uri]
set BYPASS $OWR-bypass-url-list
# Counters
set VisitorCount [table keys -subtable $VisitorsTable -count]
set WaitingRoomCount [table keys -subtable $WaitingRoomTable -count]
set TotalVisitors [expr {$VisitorCount + $WaitingRoomCount}]
## End Variable
## Monitoring
# Allow monitoring from internal IP's or subnets.
if { ($Client_IP starts_with "192.168.") && ([HTTP::uri] equals "/getcount") } {
HTTP::respond 200 content "Total Visitors: \[$TotalVisitors\]
Max Visitors: \[$max_visitors\]
Waiting Room Count: \[$WaitingRoomCount\]"
TCP::close
return
}
## Start WaitingRoom
# Check if the visitor session still exists
set VisitorSession [table lookup -subtable $VisitorsTable $Client_IP]
if { $VisitorSession != "" } {
# We have a valid session... The lookup has reset the timer on it so just finish processing
} else {
# No valid session...
# Check if BYPASS URL
set bypass_url [class match -value [HTTP::uri] contains $BYPASS]
if { not ($bypass_url == "") } {
# BYPASS, do nothing
} else {
# NOT BYPASS, Check connection count for displaying WR Page
# So do we have a free 'slot'?
if {$VisitorCount < $max_visitors} {
# Yes we have a free slot... Allocate it..
# Register visitor
table add -subtable $VisitorsTable $Client_IP $unique_id $IdleTimeout
} else {
# Max visitors limit reached, show WaitingRoom
# Insert visitor into WaitingRoomTable
table add -subtable $WaitingRoomTable $Client_IP $unique_id $WaitingRoomTimeout
# Show waiting Room HTML
HTTP::respond 503 content [ifile get waitingroom.html]
}
TCP::close
}
}
}
}
Tcpdump TLS/SSL Traffic
ref: https://www.notion.so/TMSH-Commands-5e5158192243480391cbabb4701ee99c
First you will have to attach an iRule that captures key material when the handshake is taking place. To make sure you see the handshake on the capture, ALWAYS use a fresh incognito/private browser window on the testing client.
It's recommended that you duplicate your virtual-server and configure everything the same way (use the same pool, policies, profiles) but add the source address, so you can better direct your testing. You can just use the virtual-server you're having trouble but you will need to attach an iRule to it. In some environments this could be not possible, so creating a duplicate VS with the source-address of the testing client takes advantage of the order of precedence of a virtual-server: https://support.f5.com/csp/article/K14800
Create an iRule as detailed on the next block of code. This iRule captures client-ssl and server-ssl. If your virtual-server uses just one or the other, use the specific block on the iRule
when CLIENTSSL_HANDSHAKE {
if { [IP::addr [getfield [IP::client_addr] "%" 1] equals <client_IP_addr>] } {
log local0. "[TCP::client_port] :: RSA Session-ID:[SSL::sessionid] Master-Key:[SSL::sessionsecret]"
}
}
when SERVERSSL_HANDSHAKE {
if { [IP::addr [getfield [IP::client_addr] "%" 1] equals <client_IP_addr>] } {
log local0. "[TCP::client_port] :: RSA Session-ID:[SSL::sessionid] Master-Key:[SSL::sessionsecret]"
}
}
After the iRule is attached to your virtual-server, capture the traffic as follows, specifing the client's IP, for example 192.168.70.25.
tcpdump -nni 0.0:nnnp host 192.168.70.25 -s0 -w /var/tmp/app_tls.pcap -v -c 5000
The iRule will log to your /var/log/ltm some lines with the TLS/SSL. You will need those lines to open the capture inside Wireshark. You can use the following command to output just that information to a file to make your life easier.
tcpdump -nni 0.0:nnnp host 192.168.70.25 -s0 -w /var/tmp/app_tls.pcap -v -c 5000
Finally, grab the "sessionsecrets.pms" file using WinSCP or SCP and open the capture inside Wireshark and point to the sessionsecrets.pms.
**-nni** —> Do not resolve names and MACs and use the following interface
**0.0:nnnp** —> 0.0 is BIG-IP's internal alias to any interface. :nnnp injects internal TMM information with all details and p flag marks the flow so BIG-IP can capture the server-side
**host 192.168.70.25** —> Observe packet with IP address 192.168.70.25
**-s0** —> Unlimited snap lenght. Prevent packets getting truncated. Usefull for HTTP traffic with lot's of headers. As a rule of thumb, always use when capturing to a file
**-w /var/tmp/mother.pcap** —> Output to a file on the specified directory. WinSCP or SCP to the specified directory to download the file and open it on Wireshark
**-v** —> Tcpdump will let you know how many packet it capture right there on the terminal
**-c 5000** —> Capture the first 5000 packets an then stop. It's recommend to use a stop if you are capturing a heavy application. You can change this number to anything you like
The following article explains the :p modifier in further details: [https://support.f5.com/csp/article/K20233108](https://support.f5.com/csp/article/K20233108)
There is great DevCentral article by Rodrigo Albuquerque that explains the entire process: [https://devcentral.f5.com/s/articles/Decrypting-TLS-traffic-on-BIG-IP](https://devcentral.f5.com/s/articles/Decrypting-TLS-traffic-on-BIG-IP)
Some users reported issues while using the ":p" modifier. There's a F5 Article about it: [https://support.f5.com/csp/article/K13637](https://support.f5.com/csp/article/K13637)
Spamhouse IP list
cd /shared/
mkdir scripts