This iRule is meant to act as a full FTPS termination rule that behaves similarly to HTTPS termination. It forces either an implicit (port 990 by default) or explicit (port 21 with AUTH TLS) connection to be made before commands are passed to the member FTP servers. All traffic from the F5 to the pool member FTP servers is then passed in cleartext and all AUTH/SSL related commands are intercepted by the F5 so that the pool members handle the session just like any other non-SSL session (again, similar to HTTPS termination).
Please note that this is currently a work-in-progress. It is very close to complete but has some lingering issues. I am hoping that in sharing my work, some of the other gurus around here will take a look and help with optimization and correcting any remaining problems. In it's current form, you can put a pool of FTP servers behind it and connect from a variety of FTP clients (lftp, filezilla, and kftpgrabber tested). The rule intercepts pasv response strings, so no additional configuration is needed on the pool member FTP servers.
However, it seems there is some sort of race/timing problem that I have yet to pinpoint. My tests using Apache FTP server (see http://mina.apache.org/ftpserver/running-ftpserver-stand-alone-in-5-minutes.html for usage) on the pool members work in large part. However, my tests using proftpd on the pool members fail the logging indicates the pasv connection is never established to the pool member server (no 'SERVER_CONNECTED' log message for the pasv connection). I am currently working to correct this problem, but please feel free to let me know if you can spot the issue.
Another issue I am using an ad hoc method of allocating and tracking vserver pasv ports. I'd much rather use the F5's built-in TCP::unused_port in conjunction with a 'listen' directive right after a PASV request is intercepted, but I have not been able to determine the proper way to use 'listen'. It looks like 'listen' and 'relate_client' are potentially what are needed, but I cannot find much documentation on these commands,
Finally, the rule is littered with log statements. I'm definitely aware that this would slow the rule down in production. I would grep those out prior to deploying this. Otherwise, despite the length of the rule, it seems to run decently fast for what it is accomplishing. The control channel connections don't pass much data, and each can be matched via 'starts_with' as opposed to 'contains', and the pasv payloads aren't matched/altered at all. Much of the processing is done only during the initial control channel connection.
Please feel free to try this iRule. I'd be happy to receive feedback on it!
Code :
# Author: Bob Ziuchkovski
# Date: Sep 27, 2010
# Version: 1.0
#
# IMPORTANT: This is a "work-in-progress". It has some problems as noted in
# the Codeshare description.
#
# Summary: This irule is meant to implement FTPS termination transparently in
# a manner similar to F5 HTTPS termination. We try to intercept all commands
# related to SSL/TLS (RFC4217) and FTP Auth extensions in general (RFC2228).
#
# Usage:
# 1. Ensure the BIG-IP version is 10.2 or higher. Versions prior
# to 10.2 seem have problems with the SSL::respond command (and
# 9.x is missing the SSL::* commands entirely)
# 2. Add this iRule to the F5. Edit the configuration settings between
# the 'BEGIN' and 'END' configuration sections below
# 3. Create a client SSL profile for terminating the FTPS sessions.
# Make sure to disable the 'Unclean Shutdown' option in the client
# profile. Otherwise certain FTP clients will fail connections due
# to unexpected packet lengths (most notably clients using GnuTLS)
# 4. Create a custom TCP Full Open monitor and set it to monitor
# the control channel port of the member FTP servers. Set the
# receive string to match the FTP server's welcome message (i.e.
# 220 Service ready for new user.)
# 5. Create a pool for FTP servers. Apply the custom monitor created
# above
# 6. Add member FTP servers to the pool. The members must be added
# with the '*' (All ports) setting.
# 7. Create a virtual server with the following settings:
# - Listen on all ports ('*')
# - Port Translation setting enabled (under 'Advanced')
# - This iRule applied
# - Default pool set to the pool created above
# - Optional: Set a source address persistence profile. If all
# member FTP servers serve identical content from a shared mount
# then this isn't needed. The pasv data connections are matched
# and sent to the same member that is serving the control channel.
# However, if there is any delay in content synchronization between
# the member servers, then source address persistence is recommended
# since some FTP clients will open additional control connections to
# retrieve files.
# * Example: A user could see a file on one connection and instruct
# their FTP client try to retrieve the file. The FTP client opens
# a second control connection (a means of keeping a GUI control
# connection responsive during file transfer), and find the file
# missing on the second connection do to being load-balanced to a
# server that lacks the file (i.e. rsync or simimlar delay).
#
# Misc: 1. The member FTP servers can be configured to use any PASV port range
# and send any PASV IP response they want -- it doesn't matter because
# we intercept those responses and remap ourselves. Make sure to set
# the correct $static::pasv_max_port below, though!
# 2. The member FTP servers should be configured to reject active mode
# data connections (this rule supports passive mode only).
# 3. We don't incercept the 'FEAT' command to advertise TLS/SSL. I have
# not run into any FTP clients that take issue with this, since most
# will already establish TLS/SSL by the time they send the FEAT
# request. Those that don't seem to reissue FEAT again at a later
# time after the initial failure. That said, if running into problems
# with an obscure FTP client, this is one of the first things I would
# recommend checking.
# 4. This rule intercepts ADAT, MIC, CONF, and ENC commands. I did this
# to prevent our member FTP servers from seeing or responding to
# anything related to FTP Auth extensions. However, this could
# probably be omitted as an optimization -- the negative response sent
# by the member server would probably suffice, although the error code
# itself wouldn't make sense (the member server will respond that no
# auth tls/ssl is in use).
#
# Optimizations to consider:
#
# 1. I've left all sorts of DEBUG logging statements in the iRule for
# testing. I have been working with two copies of this rule. One
# with these log statements (a 'debug' version) and one with them
# removed (simple grep -v to remove). I don't like the overhead of
# additional conditional if {$debug == 1} types of checks, so these
# are not included.
# 2. Remove checks for ADAT, MIC, CONF, and ENC mentioned above.
when RULE_INIT {
#----------------------------------
# Begin configuration portion
#----------------------------------
# Member server port to use for control channel
set static::ftp_member_port 2121
# Vserver port to use for FTPS explicit control channel (-1 to disable)
set static::ftps_explicit_port 21
# Verserver Port to use for FTPS implicit control channel (-1 to disable)
set static::ftps_implicit_port 990
# Vserver Ports to use for PASV data channel connections -- does not need to
# match the member server PASV ports as we automatically track and map those
set static::pasv_min_port 50000
set static::pasv_max_port 50005
# Vserver IP to advertise for incoming PASV data channel connections
# NOTE: the commas are NOT a typo. This is the format used by FTP protocol
set static::pasv_max_port "10,0,0,46"
#----------------------------------
# End configuration portion
#----------------------------------
# TODO: Figure out how to properly use the 'listen' directive so that we can use
# 'listen' in combination with TCP::unused_port to avoid this ad hoc port tracking
# Track in-use PASV mappings (our pasv port -> client IP/member IP/member port)
array set ::pasv_to_client {}
array set ::pasv_to_mbr {}
array set ::pasv_to_mbr_port {}
# Clear out any mappings already in-use (i.e. rule reload/update)
array unset ::pasv_to_client *
array unset ::pasv_to_mbr *
array unset ::pasv_to_mbr_port *
# Track available PASV ports
set ::pasv_avail {}
# Make sure the list is cleaned-out from any prior states and add our ports
set ::pasv_avail [ lreplace $::pasv_avail 0 end ]
foreach {set i $static::pasv_max_port} { $i >= $static::pasv_min_port } {incr i -1} {
lappend ::pasv_avail $i
}
# Debugging-only -- set a numeric auto-incrementing ID for each connection
# (helps differentiate between events fired for different connections)
set ::cid 0
}
when LB_FAILED {
log local0. "DEBUG: ($our_cid) LB_FAILED fired"
}
when CLIENT_ACCEPTED {
incr ::cid
set our_cid $::cid
log local0. "DEBUG: ($our_cid) CLIENT_ACCEPTED fired"
# We need to handle control channel and data channel connections differently
# 0 = unknown/unauthorized, 1 = data channel, 2 = control channel
set conn_type 0
# Is this a pasv data channel connection? (first since triggered most often)
if { [TCP::local_port] >= $static::pasv_min_port && [TCP::local_port] <= $static::pasv_max_port } {
# Port is pasv port but does it match a client control connection?
if {[info exists ::pasv_to_client([TCP::local_port])] } {
set orig_client $::pasv_to_client([TCP::local_port])
if { $orig_client == [IP::client_addr] } {
log local0. "DEBUG: ($our_cid) PASV attempt from established client [IP::client_addr] on port [TCP::local_port]"
# Retrieve member IP/port waiting for connection
set orig_mbr $::pasv_to_mbr([TCP::local_port])
set orig_mbr_port $::pasv_to_mbr_port([TCP::local_port])
log local0. "DEBUG: ($our_cid) Member associated with PASV connnection: $orig_mbr (port $orig_mbr_port)"
if { $orig_mbr != "" && $orig_mbr_port != "" } {
# This is a legitimate pasv data connection and a member server is waiting for it.
set conn_type 1
# Connect client directly to the member server on proper port
log local0. "DEBUG: ($our_cid) Executing node command: 'node $orig_mbr $orig_mbr_port'"
node $orig_mbr $orig_mbr_port
log local0. "DEBUG: ($our_cid) Node command executed"
} else {
log local0. "DEBUG: ($our_cid) PASV connection not mapped to member server / member port"
}
} else {
log local0. "ALERT: Unauthorized connection to mapped pasv port [TCP::local_port] by [IP::client_addr] -- expected $orig_client instead!"
}
} else {
log local0. "ALERT: Unauthorized connection to unmapped pasv port [TCP::local_port] by [IP::client_addr]"
}
# Is this a control channel connection?
} elseif { [TCP::local_port] == $static::ftps_explicit_port || [TCP::local_port] == $static::ftps_implicit_port} {
set conn_type 2
# Keep a persistent vserver pasv port assignment per control channel connection
set our_pasv_port 0
set our_pasv_str ""
if { [TCP::local_port] == $static::ftps_explicit_port } {
log local0. "DEBUG: ($our_cid) new ftps explicit control connection from [IP::client_addr]"
# We need to track the ssl state of control channels
# 0 = disabled, 1 = enabled
set ctl_ssl_state 0
SSL::disable
} elseif { [TCP::local_port] == $static::ftps_implicit_port } {
log local0. "DEBUG: ($our_cid) new ftps implicit control connection from [IP::client_addr]"
# We need to track the ssl state of control channels
# 0 = disabled, 1 = enabled
set ctl_ssl_state 1
}
# Here we have the LB choose our node for the control channel, but we make
# the connection with the 'node' command since member control channel port
# might differ from vserver control channel port
set node_selection [getfield [LB::select] " " 4]
node $node_selection $static::ftp_member_port
# Invalid connection -- made to a port other than one we are using for FTP
} else {
log local0. "ALERT: Unauthorized connection non-ftp port [TCP::local_port] by [IP::client_addr]"
}
# If the connection isn't legitimate, close it
if { $conn_type == 0 } {
log local0. "DEBUG: ($our_cid) Rejecting unauthorized connection"
reject
}
}
when CLIENT_CLOSED {
log local0. "DEBUG: ($our_cid) CLIENT_CLOSED fired"
# Remove mappings associated with control connections
if {$conn_type == 1 } {
log local0. "DEBUG: ($our_cid) Closing pasv data connection (port [TCP::local_port]) from [IP::client_addr]"
} elseif {$conn_type == 2} {
log local0. "DEBUG: ($our_cid) Closing control channel to [IP::client_addr]"
# We need to clean up after ourselves if we had a pasv port in use
if {$our_pasv_port != 0 } {
log local0. "DEBUG: ($our_cid) Control channel for [IP::client_addr] was using pasv port $our_pasv_port. Unmapping."
log local0. "DEBUG: ($our_cid) Available PASV ports before freeing used port:"
foreach pport $::pasv_avail {
log local0. " $pport"
}
# Put our port back in avail, but only if it wasn't already there
# (avoids duplication if rule is reloaded while control channel is open)
if { [lsearch -exact $::pasv_avail $our_pasv_port] == -1 } {
lappend ::pasv_avail $our_pasv_port
} else {
log local0. "DEBUG: ($our_cid) Not adding our port to pasv pool...already there"
}
log local0. "DEBUG: ($our_cid) Available PASV ports after freeing used port:"
foreach pport $::pasv_avail {
log local0. " $pport"
}
# Same concept here...check first and unmap
if { [info exists ::pasv_to_client($our_pasv_port)] } {
unset ::pasv_to_client($our_pasv_port)
} else {
log local0. "DEBUG: ($our_cid) Nothing to remove in ::pasv_to_client array"
}
if { [info exists ::pasv_to_mbr($our_pasv_port)] } {
unset ::pasv_to_mbr($our_pasv_port)
} else {
log local0. "DEBUG: ($our_cid) Nothing to remove in ::pasv_to_mbr array"
}
if { [info exists ::pasv_to_mbr_port($our_pasv_port)] } {
unset ::pasv_to_mbr_port($our_pasv_port)
} else {
log local0. "DEBUG: ($our_cid) Nothing to remove in ::pasv_to_mbr_port array"
}
}
}
}
when CLIENT_DATA {
log local0. "DEBUG: ($our_cid) CLIENT_DATA fired"
log local0. "DEBUG: ($our_cid) Client data on connection type $conn_type, ssl_state $ctl_ssl_state:"
log local0. " PAYLOAD: [TCP::payload]"
# We are only interested in intercepting non-SSL control channel payloads here
if { $conn_type == 2 && $ctl_ssl_state == 0 } {
log local0. "DEBUG: ($our_cid) Handling intercepted client data payload"
# Here we intercept and handle all traffic prior to auth tls establishment
set response ""
if { [TCP::payload] starts_with "AUTH " } {
# We only allow TLS auth
if { [TCP::payload] starts_with "AUTH TLS" } {
set response "234 Command AUTH okay; starting TLS connection.\r\n"
set ctl_ssl_state 1
} else {
set response "534 Request denied for policy reasons.\r\n"
}
# Some clients send the following commands prior to their auth tls request
} elseif { [TCP::payload] starts_with "PBSZ 0" } {
# Essentially a no-op -- TODO: handle other PBSZ request sizes?
set response "200 Command PBSZ okay.\r\n"
} elseif { [TCP::payload] starts_with "PROT " } {
# Reject everything but 'PROT P' mode
if { [TCP::payload] starts_with "PROT P" } {
set response "200 Command PROT okay.\r\n"
} elseif { [TCP::payload] starts_with "PROT C" } {
set response "534 Insufficient data protection.\r\n"
} elseif { [TCP::payload] starts_with "PROT S" } {
set response "504 Command not implemented for that parameter.\r\n"
} elseif { [TCP::payload] starts_with "PROT E" } {
set response "504 Command not implemented for that parameter.\r\n"
}
# Don't allow any other commands until auth tls has been established
} else {
set response "530 Access denied.\r\n"
}
# Now we have the response we wish to send, so send it
log local0. "DEBUG: ($our_cid) Sending response to intercepted plaintext payload: $response"
TCP::respond $response
TCP::payload replace 0 [TCP::payload length] {}
TCP::release
# If we just received TLS request, then we will have transitioned this to 1
# We can now enable SSL.
if { $ctl_ssl_state == 1 } {
log local0. "DEBUG: ($our_cid) Enabling SSL on control channel connection"
SSL::enable
} else {
TCP::collect
}
} else {
log local0. "DEBUG: ($our_cid) Releasing unhandled collected client TCP payload"
TCP::release
#TCP::collect
}
}
when CLIENTSSL_HANDSHAKE {
log local0. "DEBUG: ($our_cid) CLIENTSSL_HANDSHAKE fired"
# We only want to intercept SSL payload for control channels
if { $conn_type == 2 } {
log local0. "DEBUG: ($our_cid) SSL handshake on control connection. Collecting."
SSL::collect
}
}
when CLIENTSSL_DATA {
log local0. "DEBUG: ($our_cid) CLIENTSSL_DATA fired"
log local0. "DEBUG: ($our_cid) Client SSL data on connection type $conn_type, ssl_state $ctl_ssl_state:"
log local0. " PAYLOAD: [SSL::payload]"
# We only want to intercept SSL payload for control channels
if { $conn_type == 2} {
log local0. "DEBUG: ($our_cid) Handling intercepted client SSL payload"
# Here we handle responses to auth-related commands after tls has been established
set response ""
if { [SSL::payload] starts_with "AUTH " } {
# Not allowed to change auth after TLS established
set response "534 Session already secured.\r\n"
# Yes we handled these above as well, but here they occur within SSL payload
} elseif { [SSL::payload] starts_with "PBSZ 0" } {
set response "200 Command PBSZ okay.\r\n"
} elseif { [SSL::payload] starts_with "PROT " } {
# Reject everything but 'PROT P' mode
if { [SSL::payload] starts_with "PROT P" } {
set response "200 Command PROT okay.\r\n"
} elseif { [SSL::payload] starts_with "PROT C" } {
set response "534 Insufficient data protection.\r\n"
} elseif { [SSL::payload] starts_with "PROT S" } {
set response "504 Command not implemented for that parameter.\r\n"
} elseif { [SSL::payload] starts_with "PROT E" } {
set response "504 Command not implemented for that parameter.\r\n"
}
# We don't accept any of the below commands and intercept before they reach our ftp servers
} elseif { [SSL::payload] starts_with "CCC" } {
# Don't allow client to turn off control channel encryption
set response "534 Request denied for policy reasons.\r\n"
} elseif { [SSL::payload] starts_with "ADAT " } {
set response "500 Syntax error, command unrecognized.\r\n"
} elseif { [SSL::payload] starts_with "MIC " } {
set response "500 Syntax error, command unrecognized.\r\n"
} elseif { [SSL::payload] starts_with "CONF " } {
set response "500 Syntax error, command unrecognized.\r\n"
} elseif { [SSL::payload] starts_with "ENC " } {
set response "500 Syntax error, command unrecognized.\r\n"
}
# In the main TCP portion, we're intercepting everything. Here we only intercept
# some commands and let the backend server respond to everything else
if { $response != "" } {
log local0. "DEBUG: ($our_cid) Sending response to intercepted SSL payload: $response"
SSL::respond $response
SSL::payload replace 0 [SSL::payload length] {}
}
log local0. "DEBUG: ($our_cid) Releasing intercepted SSL payload"
SSL::release
log local0. "DEBUG: ($our_cid) Collecting SSL again"
SSL::collect
} else {
log local0. "DEBUG: ($our_cid) Releasing unhandled collected SSL payload"
SSL::release
#SSL::collect
}
}
when SERVER_CONNECTED {
log local0. "DEBUG: ($our_cid) SERVER_CONNECTED fired"
# We only want to intercept server payloads for control channels
if { $conn_type == 2 } {
log local0. "DEBUG: ($our_cid) Control connection to member established. Collecting."
TCP::collect
} else {
log local0. "DEBUG: ($our_cid) Data connection to member established."
}
}
when SERVER_CLOSED {
log local0. "DEBUG: ($our_cid) SERVER_CLOSED fired"
}
when SERVER_DATA {
log local0. "DEBUG: ($our_cid) SERVER_DATA fired"
log local0. "DEBUG: ($our_cid) Server data on connection type $conn_type, ssl_state $ctl_ssl_state:"
log local0. " PAYLOAD: [TCP::payload]"
# We only want to intercept server payloads for control channels
if { $conn_type == 2 } {
log local0. "DEBUG: ($our_cid) Handling intercepted server data payload"
# Intercept the PASV response sent by the pool member
if { [TCP::payload] starts_with "227 Entering Passive Mode" } {
log local0. "DEBUG: ($our_cid) Intercepting member PASV response."
# Extract and save pasv port given by member server -- needed for lookup on data channel connection
set pasvstr [findstr [TCP::payload] "227 Entering Passive Mode" 27 ")"]
set mbr_pasv_port [ expr [getfield $pasvstr "," 5] * 256 + [getfield $pasvstr "," 6] ]
log local0. "DEBUG: ($our_cid) member server requesting connection be made to port $mbr_pasv_port"
# On the F5 side, we try to reuse the same port to avoid repeated search of port list
if { $our_pasv_port == 0 } {
# Make sure we have an available port to use
if { [llength $::pasv_avail] > 0 } {
log local0. "DEBUG: ($our_cid) Available PASV ports before assignment:"
foreach pport $::pasv_avail {
log local0. " $pport"
}
# Pop our port from end of available ports list
set our_pasv_port [ lindex $::pasv_avail end ]
set ::pasv_avail [ lreplace $::pasv_avail end end ]
log local0. "DEBUG: ($our_cid) Available PASV ports after assignment:"
foreach pport $::pasv_avail {
log local0. " $pport"
}
# Format our port in PASV response string format
set our_pasv_str "[expr $our_pasv_port / 256],[expr $our_pasv_port % 256]"
log local0. "DEBUG: ($our_cid) Setting this connection's pasv port to $our_pasv_port"
log local0. "DEBUG: ($our_cid) Setting this connection's pasv string to $our_pasv_str"
# The pasv port to client IP and member IP mappings will remain constant once we assign the port
set ::pasv_to_client($our_pasv_port) [IP::client_addr]
set ::pasv_to_mbr($our_pasv_port) [LB::server addr]
log local0. "DEBUG: ($our_cid) Saved mapping PASV port -> client IP == $our_pasv_port -> [IP::client_addr]"
log local0. "DEBUG: ($our_cid) Saved mapping PASV port -> member IP == $our_pasv_port -> [LB::server addr]"
} else {
log local0. "ALERT: No available PASV ports -- port range exhausted!"
TCP::payload replace 0 [TCP::payload length] "421 Max connections reached.\r\n"
}
}
# Make our PASV response substitution only if we have a port for use
if { $our_pasv_port != 0 } {
# Save mapping of our port to member port -- We have to update this on each PASV response since the member
# server isn't gauranteed to respond with the same port to us each time
set ::pasv_to_mbr_port($our_pasv_port) $mbr_pasv_port
log local0. "DEBUG: ($our_cid) Saved mapping PASV port -> member port == $our_pasv_port -> $mbr_pasv_port"
# Make our PASV response substitution
set pasv_response "227 Entering Passive Mode ($static::pasv_max_port,$our_pasv_str)\r\n"
log local0. "DEBUG: ($our_cid) Sending updated PASV response to client:"
log local0. " $pasv_response"
TCP::payload replace 0 [TCP::payload length] $pasv_response
}
}
# Continue intercepting server responses
log local0. "DEBUG: ($our_cid) Releasing intercepted server TCP payload"
TCP::release
log local0. "DEBUG: ($our_cid) Collecting server TCP again"
TCP::collect
# Depending on our SSL status, we either want to collect client-side TCP or SSL
if { $ctl_ssl_state == 0 } {
log local0. "DEBUG: ($our_cid) Signaling clientside TCP collect"
clientside { TCP::collect }
} elseif { $ctl_ssl_state == 1 } {
log local0. "DEBUG: ($our_cid) Signaling clientside SSL collect"
clientside { SSL::collect }
}
} else {
log local0. "DEBUG: ($our_cid) Releasing unhandled collected server TCP payload"
TCP::release
#TCP::collect
}
}
There are still a few small things that need to be adjusted to make it work.
1. The same variable again "static::pasv_max_port" is incorrect it is already in use. 2. To use this iRule on multiple virtual servers it needs a little more dynamic.
# -- REMOVE OLD --
# Vserver IP to advertise for incoming PASV data channel connections
# NOTE: the commas are NOT a typo. This is the format used by FTP protocol
set static::pasv_max_port "10,0,0,46"
# -- ADD (under "when CLIENTSSL_DATA" ) --
# uses the known virtual server ip address (client context)
set static::vserverip [IP::local_addr]
set static::vserverip_comma [string map {. ,} $static::vserverip]
# -- CHANGE ( under "when SERVER_DATA" ) --
# respoding the virtual server ip address
set pasv_response "227 Entering Passive Mode ($static::vserverip_comma,$our_pasv_str)\r\n"
"}},"componentScriptGroups({\"componentId\":\"custom.widget.Beta_Footer\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"component({\"componentId\":\"custom.widget.Tag_Manager_Helper\"})":{"__typename":"Component","render({\"context\":{\"component\":{\"entities\":[],\"props\":{}},\"page\":{\"entities\":[\"board:codeshare\",\"message:274110\"],\"name\":\"TkbMessagePage\",\"props\":{},\"url\":\"https://community.f5.com/kb/codeshare/ftps-ssl-termination/274110\"}}})":{"__typename":"ComponentRenderResult","html":" "}},"componentScriptGroups({\"componentId\":\"custom.widget.Tag_Manager_Helper\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"component({\"componentId\":\"custom.widget.Consent_Blackbar\"})":{"__typename":"Component","render({\"context\":{\"component\":{\"entities\":[],\"props\":{}},\"page\":{\"entities\":[\"board:codeshare\",\"message:274110\"],\"name\":\"TkbMessagePage\",\"props\":{},\"url\":\"https://community.f5.com/kb/codeshare/ftps-ssl-termination/274110\"}}})":{"__typename":"ComponentRenderResult","html":""}},"componentScriptGroups({\"componentId\":\"custom.widget.Consent_Blackbar\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/common/QueryHandler\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/community/NavbarDropdownToggle\"]})":[{"__ref":"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageView/MessageViewStandard\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageView/MessageViewStandard-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/ThreadedReplyList\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/ThreadedReplyList-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageReplyCallToAction\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageReplyCallToAction-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageSubject\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageSubject-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageBody\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageBody-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageCustomFields\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageCustomFields-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageRevision\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageRevision-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageReplyButton\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageReplyButton-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageAuthorBio\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/guides/GuideBottomNavigation\"]})":[{"__ref":"CachedAsset:text:en_US-components/guides/GuideBottomNavigation-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserLink\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserLink-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserRank\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserRank-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageTime\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageTime-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/customComponent/CustomComponent\"]})":[{"__ref":"CachedAsset:text:en_US-components/customComponent/CustomComponent-1744046271000"}],"message({\"id\":\"message:302776\"})":{"__ref":"TkbReplyMessage:message:302776"},"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/ranks/UserRankLabel\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserRegistrationDate\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserRegistrationDate-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/tags/TagView/TagViewChip\"]})":[{"__ref":"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1744046271000"}]},"CachedAsset:pages-1744511661180":{"__typename":"CachedAsset","id":"pages-1744511661180","value":[{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.MvpProgram","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/mvp-program","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"BlogViewAllPostsPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId/all-posts/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"CasePortalPage","type":"CASE_PORTAL","urlPath":"/caseportal","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"CreateGroupHubPage","type":"GROUP_HUB","urlPath":"/groups/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"CaseViewPage","type":"CASE_DETAILS","urlPath":"/case/:caseId/:caseNumber","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"InboxPage","type":"COMMUNITY","urlPath":"/inbox","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.AdvocacyProgram","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/advocacy-program","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.GetHelp.NonCustomer","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/non-customer","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HelpFAQPage","type":"COMMUNITY","urlPath":"/help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.GetHelp.F5Customer","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/f5-customer","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"IdeaMessagePage","type":"IDEA_POST","urlPath":"/idea/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"IdeaViewAllIdeasPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/all-ideas/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"LoginPage","type":"USER","urlPath":"/signin","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"BlogPostPage","type":"BLOG","urlPath":"/category/:categoryId/blogs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.GetInvolved","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.Learn","type":"COMMUNITY","urlPath":"/c/how-do-i/learn","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1739501996000,"localOverride":null,"page":{"id":"Test","type":"CUSTOM","urlPath":"/custom-test-2","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ThemeEditorPage","type":"COMMUNITY","urlPath":"/designer/themes","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"TkbViewAllArticlesPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId/all-articles/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"OccasionEditPage","type":"EVENT","urlPath":"/event/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"OAuthAuthorizationAllowPage","type":"USER","urlPath":"/auth/authorize/allow","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"PageEditorPage","type":"COMMUNITY","urlPath":"/designer/pages","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"PostPage","type":"COMMUNITY","urlPath":"/category/:categoryId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ForumBoardPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"TkbBoardPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"EventPostPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"UserBadgesPage","type":"COMMUNITY","urlPath":"/users/:login/:userId/badges","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"GroupHubMembershipAction","type":"GROUP_HUB","urlPath":"/membership/join/:nodeId/:membershipType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"MaintenancePage","type":"COMMUNITY","urlPath":"/maintenance","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"IdeaReplyPage","type":"IDEA_REPLY","urlPath":"/idea/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"UserSettingsPage","type":"USER","urlPath":"/mysettings/:userSettingsTab","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"GroupHubsPage","type":"GROUP_HUB","urlPath":"/groups","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ForumPostPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"OccasionRsvpActionPage","type":"OCCASION","urlPath":"/event/:boardId/:messageSubject/:messageId/rsvp/:responseType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"VerifyUserEmailPage","type":"USER","urlPath":"/verifyemail/:userId/:verifyEmailToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"AllOccasionsPage","type":"OCCASION","urlPath":"/category/:categoryId/events/:boardId/all-events/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"EventBoardPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"TkbReplyPage","type":"TKB_REPLY","urlPath":"/kb/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"IdeaBoardPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"CommunityGuideLinesPage","type":"COMMUNITY","urlPath":"/communityguidelines","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"CaseCreatePage","type":"SALESFORCE_CASE_CREATION","urlPath":"/caseportal/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"TkbEditPage","type":"TKB","urlPath":"/kb/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ForgotPasswordPage","type":"USER","urlPath":"/forgotpassword","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"IdeaEditPage","type":"IDEA","urlPath":"/idea/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"TagPage","type":"COMMUNITY","urlPath":"/tag/:tagName","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"BlogBoardPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"OccasionMessagePage","type":"OCCASION_TOPIC","urlPath":"/event/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ManageContentPage","type":"COMMUNITY","urlPath":"/managecontent","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ClosedMembershipNodeNonMembersPage","type":"GROUP_HUB","urlPath":"/closedgroup/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.GetHelp.Community","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/community","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"CommunityPage","type":"COMMUNITY","urlPath":"/","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.ContributeCode","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/contribute-code","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ForumMessagePage","type":"FORUM_TOPIC","urlPath":"/discussions/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"IdeaPostPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"BlogMessagePage","type":"BLOG_ARTICLE","urlPath":"/blog/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"RegistrationPage","type":"USER","urlPath":"/register","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"EditGroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ForumEditPage","type":"FORUM","urlPath":"/discussions/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ResetPasswordPage","type":"USER","urlPath":"/resetpassword/:userId/:resetPasswordToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"TkbMessagePage","type":"TKB_ARTICLE","urlPath":"/kb/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.Learn.AboutIrules","type":"COMMUNITY","urlPath":"/c/how-do-i/learn/about-irules","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"BlogEditPage","type":"BLOG","urlPath":"/blog/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.GetHelp.F5Support","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/f5-support","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ManageUsersPage","type":"USER","urlPath":"/users/manage/:tab?/:manageUsersTab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ForumReplyPage","type":"FORUM_REPLY","urlPath":"/discussions/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"PrivacyPolicyPage","type":"COMMUNITY","urlPath":"/privacypolicy","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"NotificationPage","type":"COMMUNITY","urlPath":"/notifications","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"UserPage","type":"USER","urlPath":"/users/:login/:userId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HealthCheckPage","type":"COMMUNITY","urlPath":"/health","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"OccasionReplyPage","type":"OCCASION_REPLY","urlPath":"/event/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ManageMembersPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/manage/:tab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"SearchResultsPage","type":"COMMUNITY","urlPath":"/search","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"BlogReplyPage","type":"BLOG_REPLY","urlPath":"/blog/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"GroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"TermsOfServicePage","type":"COMMUNITY","urlPath":"/termsofservice","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.GetHelp","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI.GetHelp.SecurityIncident","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/security-incident","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"CategoryPage","type":"CATEGORY","urlPath":"/category/:categoryId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"ForumViewAllTopicsPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/all-topics/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"TkbPostPage","type":"TKB","urlPath":"/category/:categoryId/kbs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"GroupHubPostPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744511661180,"localOverride":null,"page":{"id":"HowDoI","type":"COMMUNITY","urlPath":"/c/how-do-i","__typename":"PageDescriptor"},"__typename":"PageResource"}],"localOverride":false},"CachedAsset:text:en_US-components/context/AppContext/AppContextProvider-0":{"__typename":"CachedAsset","id":"text:en_US-components/context/AppContext/AppContextProvider-0","value":{"noCommunity":"Cannot find community","noUser":"Cannot find current user","noNode":"Cannot find node with id {nodeId}","noMessage":"Cannot find message with id {messageId}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-0":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-0","value":{"title":"Loading..."},"localOverride":false},"User:user:-1":{"__typename":"User","id":"user:-1","uid":-1,"login":"Former Member","email":"","avatar":null,"rank":null,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":"ANONYMOUS","registrationTime":null,"confirmEmailStatus":false,"registrationAccessLevel":"VIEW","ssoRegistrationFields":[]},"ssoId":null,"profileSettings":{"__typename":"ProfileSettings","dateDisplayStyle":{"__typename":"InheritableStringSettingWithPossibleValues","key":"layout.friendly_dates_enabled","value":"false","localValue":"true","possibleValues":["true","false"]},"dateDisplayFormat":{"__typename":"InheritableStringSetting","key":"layout.format_pattern_date","value":"dd-MMM-yyyy","localValue":"MM-dd-yyyy"},"language":{"__typename":"InheritableStringSettingWithPossibleValues","key":"profile.language","value":"en-US","localValue":null,"possibleValues":["en-US"]}},"deleted":false},"Theme:customTheme1":{"__typename":"Theme","id":"customTheme1"},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bi04Ny0xOTQ1NWk4ODNCOUNEMkFDNDZCQjI0\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bi04Ny0xOTQ1NWk4ODNCOUNEMkFDNDZCQjI0","mimeType":"image/png"},"Category:category:CrowdSRC":{"__typename":"Category","id":"category:CrowdSRC","entityType":"CATEGORY","displayId":"CrowdSRC","nodeType":"category","depth":1,"title":"CrowdSRC","shortTitle":"CrowdSRC","parent":{"__ref":"Category:category:top"},"categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:top":{"__typename":"Category","id":"category:top","displayId":"top","nodeType":"category","depth":0,"title":"Top","entityType":"CATEGORY","shortTitle":"Top"},"Tkb:board:codeshare":{"__typename":"Tkb","id":"board:codeshare","entityType":"TKB","displayId":"codeshare","nodeType":"board","depth":2,"conversationStyle":"TKB","title":"CodeShare","description":"Have some code. Share some code.","avatar":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bi04Ny0xOTQ1NWk4ODNCOUNEMkFDNDZCQjI0\"}"},"profileSettings":{"__typename":"ProfileSettings","language":null},"parent":{"__ref":"Category:category:CrowdSRC"},"ancestors":{"__typename":"CoreNodeConnection","edges":[{"__typename":"CoreNodeEdge","node":{"__ref":"Community:community:zihoc95639"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:CrowdSRC"}}]},"userContext":{"__typename":"NodeUserContext","canAddAttachments":false,"canUpdateNode":false,"canPostMessages":false,"isSubscribed":false},"boardPolicies":{"__typename":"BoardPolicies","canPublishArticleOnCreate":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","args":[]}},"canReadNode":{"__typename":"PolicyResult","failureReason":null}},"shortTitle":"CodeShare","isManualSortOrderAvailable":false,"tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"repliesProperties":{"__typename":"RepliesProperties","sortOrder":"PUBLISH_TIME","repliesFormat":"threaded"},"eventPath":"category:CrowdSRC/community:zihoc95639board:codeshare/","tagProperties":{"__typename":"TagNodeProperties","tagsEnabled":{"__typename":"PolicyResult","failureReason":null}},"requireTags":true,"tagType":"FREEFORM_AND_PRESET"},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/cmstNDEtSzFzVEth\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/cmstNDEtSzFzVEth","height":0,"width":0,"mimeType":"image/svg+xml"},"Rank:rank:41":{"__typename":"Rank","id":"rank:41","position":18,"name":"Nimbostratus","color":"CCCCCC","icon":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/cmstNDEtSzFzVEth\"}"},"rankStyle":"FILLED"},"User:user:14256":{"__typename":"User","id":"user:14256","uid":14256,"login":"bob_ziuchkovsk1","deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/m_assets/avatars/default/avatar-9.svg?time=0"},"rank":{"__ref":"Rank:rank:41"},"email":"","messagesCount":2,"biography":null,"topicsCount":2,"kudosReceivedCount":1,"kudosGivenCount":0,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2010-10-12T01:00:00.000-07:00","confirmEmailStatus":null},"followersCount":null,"solutionsCount":0},"TkbTopicMessage:message:274110":{"__typename":"TkbTopicMessage","uid":274110,"subject":"FTPS_SSL_ Termination","id":"message:274110","revisionNum":1,"repliesCount":1,"author":{"__ref":"User:user:14256"},"depth":0,"hasGivenKudo":false,"helpful":null,"board":{"__ref":"Tkb:board:codeshare"},"conversation":{"__ref":"Conversation:conversation:274110"},"messagePolicies":{"__typename":"MessagePolicies","canPublishArticleOnEdit":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","args":[]}},"canModerateSpamMessage":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","key":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","args":[]}}},"contentWorkflow":{"__typename":"ContentWorkflow","state":"PUBLISH","scheduledPublishTime":null,"scheduledTimezone":null,"userContext":{"__typename":"MessageWorkflowContext","canSubmitForReview":null,"canEdit":false,"canRecall":null,"canSubmitForPublication":null,"canReturnToAuthor":null,"canPublish":null,"canReturnToReview":null,"canSchedule":false},"shortScheduledTimezone":null},"readOnly":false,"editFrozen":false,"moderationData":{"__ref":"ModerationData:moderation_data:274110"},"teaser":"","body":"
Problem this snippet solves:
This iRule is meant to act as a full FTPS termination rule that behaves similarly to HTTPS termination. It forces either an implicit (port 990 by default) or explicit (port 21 with AUTH TLS) connection to be made before commands are passed to the member FTP servers. All traffic from the F5 to the pool member FTP servers is then passed in cleartext and all AUTH/SSL related commands are intercepted by the F5 so that the pool members handle the session just like any other non-SSL session (again, similar to HTTPS termination).
\n
Please note that this is currently a work-in-progress. It is very close to complete but has some lingering issues. I am hoping that in sharing my work, some of the other gurus around here will take a look and help with optimization and correcting any remaining problems. In it's current form, you can put a pool of FTP servers behind it and connect from a variety of FTP clients (lftp, filezilla, and kftpgrabber tested). The rule intercepts pasv response strings, so no additional configuration is needed on the pool member FTP servers.
\n
However, it seems there is some sort of race/timing problem that I have yet to pinpoint. My tests using Apache FTP server (see http://mina.apache.org/ftpserver/running-ftpserver-stand-alone-in-5-minutes.html for usage) on the pool members work in large part. However, my tests using proftpd on the pool members fail the logging indicates the pasv connection is never established to the pool member server (no 'SERVER_CONNECTED' log message for the pasv connection). I am currently working to correct this problem, but please feel free to let me know if you can spot the issue.
\n
Another issue I am using an ad hoc method of allocating and tracking vserver pasv ports. I'd much rather use the F5's built-in TCP::unused_port in conjunction with a 'listen' directive right after a PASV request is intercepted, but I have not been able to determine the proper way to use 'listen'. It looks like 'listen' and 'relate_client' are potentially what are needed, but I cannot find much documentation on these commands,
\n
Finally, the rule is littered with log statements. I'm definitely aware that this would slow the rule down in production. I would grep those out prior to deploying this. Otherwise, despite the length of the rule, it seems to run decently fast for what it is accomplishing. The control channel connections don't pass much data, and each can be matched via 'starts_with' as opposed to 'contains', and the pasv payloads aren't matched/altered at all. Much of the processing is done only during the initial control channel connection.
\n
Please feel free to try this iRule. I'd be happy to receive feedback on it!
Code :
# Author: Bob Ziuchkovski \n# Date: Sep 27, 2010\n# Version: 1.0\n#\n# IMPORTANT: This is a \"work-in-progress\". It has some problems as noted in\n# the Codeshare description.\n#\n# Summary: This irule is meant to implement FTPS termination transparently in\n# a manner similar to F5 HTTPS termination. We try to intercept all commands\n# related to SSL/TLS (RFC4217) and FTP Auth extensions in general (RFC2228).\n#\n# Usage: \n# 1. Ensure the BIG-IP version is 10.2 or higher. Versions prior\n# to 10.2 seem have problems with the SSL::respond command (and\n# 9.x is missing the SSL::* commands entirely)\n# 2. Add this iRule to the F5. Edit the configuration settings between\n# the 'BEGIN' and 'END' configuration sections below\n# 3. Create a client SSL profile for terminating the FTPS sessions.\n# Make sure to disable the 'Unclean Shutdown' option in the client\n# profile. Otherwise certain FTP clients will fail connections due\n# to unexpected packet lengths (most notably clients using GnuTLS)\n# 4. Create a custom TCP Full Open monitor and set it to monitor\n# the control channel port of the member FTP servers. Set the\n# receive string to match the FTP server's welcome message (i.e.\n# 220 Service ready for new user.)\n# 5. Create a pool for FTP servers. Apply the custom monitor created\n# above\n# 6. Add member FTP servers to the pool. The members must be added\n# with the '*' (All ports) setting.\n# 7. Create a virtual server with the following settings:\n# - Listen on all ports ('*')\n# - Port Translation setting enabled (under 'Advanced')\n# - This iRule applied\n# - Default pool set to the pool created above\n# - Optional: Set a source address persistence profile. If all\n# member FTP servers serve identical content from a shared mount\n# then this isn't needed. The pasv data connections are matched\n# and sent to the same member that is serving the control channel.\n# However, if there is any delay in content synchronization between\n# the member servers, then source address persistence is recommended\n# since some FTP clients will open additional control connections to\n# retrieve files.\n# * Example: A user could see a file on one connection and instruct\n# their FTP client try to retrieve the file. The FTP client opens\n# a second control connection (a means of keeping a GUI control\n# connection responsive during file transfer), and find the file\n# missing on the second connection do to being load-balanced to a\n# server that lacks the file (i.e. rsync or simimlar delay).\n# \n# Misc: 1. The member FTP servers can be configured to use any PASV port range\n# and send any PASV IP response they want -- it doesn't matter because\n# we intercept those responses and remap ourselves. Make sure to set\n# the correct $static::pasv_max_port below, though!\n# 2. The member FTP servers should be configured to reject active mode\n# data connections (this rule supports passive mode only).\n# 3. We don't incercept the 'FEAT' command to advertise TLS/SSL. I have\n# not run into any FTP clients that take issue with this, since most\n# will already establish TLS/SSL by the time they send the FEAT\n# request. Those that don't seem to reissue FEAT again at a later\n# time after the initial failure. That said, if running into problems\n# with an obscure FTP client, this is one of the first things I would\n# recommend checking.\n# 4. This rule intercepts ADAT, MIC, CONF, and ENC commands. I did this\n# to prevent our member FTP servers from seeing or responding to\n# anything related to FTP Auth extensions. However, this could\n# probably be omitted as an optimization -- the negative response sent\n# by the member server would probably suffice, although the error code\n# itself wouldn't make sense (the member server will respond that no\n# auth tls/ssl is in use).\n#\n# Optimizations to consider:\n#\n# 1. I've left all sorts of DEBUG logging statements in the iRule for\n# testing. I have been working with two copies of this rule. One\n# with these log statements (a 'debug' version) and one with them\n# removed (simple grep -v to remove). I don't like the overhead of\n# additional conditional if {$debug == 1} types of checks, so these\n# are not included.\n# 2. Remove checks for ADAT, MIC, CONF, and ENC mentioned above.\n\n\nwhen RULE_INIT {\n#----------------------------------\n# Begin configuration portion \n#----------------------------------\n\n# Member server port to use for control channel\nset static::ftp_member_port 2121\n\n# Vserver port to use for FTPS explicit control channel (-1 to disable)\nset static::ftps_explicit_port 21\n\n# Verserver Port to use for FTPS implicit control channel (-1 to disable)\nset static::ftps_implicit_port 990\n\n# Vserver Ports to use for PASV data channel connections -- does not need to\n# match the member server PASV ports as we automatically track and map those\nset static::pasv_min_port 50000\nset static::pasv_max_port 50005\n\n# Vserver IP to advertise for incoming PASV data channel connections\n# NOTE: the commas are NOT a typo. This is the format used by FTP protocol\nset static::pasv_max_port \"10,0,0,46\"\n\n#----------------------------------\n# End configuration portion\n#----------------------------------\n\n# TODO: Figure out how to properly use the 'listen' directive so that we can use\n# 'listen' in combination with TCP::unused_port to avoid this ad hoc port tracking\n\n# Track in-use PASV mappings (our pasv port -> client IP/member IP/member port)\narray set ::pasv_to_client {}\narray set ::pasv_to_mbr {}\narray set ::pasv_to_mbr_port {}\n\n# Clear out any mappings already in-use (i.e. rule reload/update)\narray unset ::pasv_to_client *\narray unset ::pasv_to_mbr *\narray unset ::pasv_to_mbr_port *\n\n# Track available PASV ports\nset ::pasv_avail {}\n\n# Make sure the list is cleaned-out from any prior states and add our ports\nset ::pasv_avail [ lreplace $::pasv_avail 0 end ]\nforeach {set i $static::pasv_max_port} { $i >= $static::pasv_min_port } {incr i -1} {\nlappend ::pasv_avail $i\n}\n\n# Debugging-only -- set a numeric auto-incrementing ID for each connection\n# (helps differentiate between events fired for different connections)\nset ::cid 0\n}\n\nwhen LB_FAILED {\n log local0. \"DEBUG: ($our_cid) LB_FAILED fired\"\n}\n\nwhen CLIENT_ACCEPTED {\n incr ::cid\n set our_cid $::cid\n\n log local0. \"DEBUG: ($our_cid) CLIENT_ACCEPTED fired\"\n # We need to handle control channel and data channel connections differently\n # 0 = unknown/unauthorized, 1 = data channel, 2 = control channel\n set conn_type 0\n\n # Is this a pasv data channel connection? (first since triggered most often)\n if { [TCP::local_port] >= $static::pasv_min_port && [TCP::local_port] <= $static::pasv_max_port } {\n # Port is pasv port but does it match a client control connection?\n if {[info exists ::pasv_to_client([TCP::local_port])] } { \n set orig_client $::pasv_to_client([TCP::local_port])\n if { $orig_client == [IP::client_addr] } {\n log local0. \"DEBUG: ($our_cid) PASV attempt from established client [IP::client_addr] on port [TCP::local_port]\"\n\n # Retrieve member IP/port waiting for connection\n set orig_mbr $::pasv_to_mbr([TCP::local_port])\n set orig_mbr_port $::pasv_to_mbr_port([TCP::local_port])\n log local0. \"DEBUG: ($our_cid) Member associated with PASV connnection: $orig_mbr (port $orig_mbr_port)\"\n if { $orig_mbr != \"\" && $orig_mbr_port != \"\" } {\n # This is a legitimate pasv data connection and a member server is waiting for it.\n set conn_type 1\n\n # Connect client directly to the member server on proper port\n log local0. \"DEBUG: ($our_cid) Executing node command: 'node $orig_mbr $orig_mbr_port'\"\n node $orig_mbr $orig_mbr_port\n log local0. \"DEBUG: ($our_cid) Node command executed\"\n } else {\n log local0. \"DEBUG: ($our_cid) PASV connection not mapped to member server / member port\"\n }\n } else {\n log local0. \"ALERT: Unauthorized connection to mapped pasv port [TCP::local_port] by [IP::client_addr] -- expected $orig_client instead!\"\n }\n } else {\n log local0. \"ALERT: Unauthorized connection to unmapped pasv port [TCP::local_port] by [IP::client_addr]\"\n }\n\n # Is this a control channel connection?\n } elseif { [TCP::local_port] == $static::ftps_explicit_port || [TCP::local_port] == $static::ftps_implicit_port} {\n set conn_type 2\n\n # Keep a persistent vserver pasv port assignment per control channel connection\n set our_pasv_port 0\n set our_pasv_str \"\"\n\n if { [TCP::local_port] == $static::ftps_explicit_port } {\n log local0. \"DEBUG: ($our_cid) new ftps explicit control connection from [IP::client_addr]\"\n\n # We need to track the ssl state of control channels\n # 0 = disabled, 1 = enabled\n set ctl_ssl_state 0\n SSL::disable\n } elseif { [TCP::local_port] == $static::ftps_implicit_port } {\n log local0. \"DEBUG: ($our_cid) new ftps implicit control connection from [IP::client_addr]\"\n\n # We need to track the ssl state of control channels\n # 0 = disabled, 1 = enabled\n set ctl_ssl_state 1\n }\n\n # Here we have the LB choose our node for the control channel, but we make\n # the connection with the 'node' command since member control channel port\n # might differ from vserver control channel port\n set node_selection [getfield [LB::select] \" \" 4]\n node $node_selection $static::ftp_member_port\n\n # Invalid connection -- made to a port other than one we are using for FTP\n } else {\n log local0. \"ALERT: Unauthorized connection non-ftp port [TCP::local_port] by [IP::client_addr]\"\n }\n\n # If the connection isn't legitimate, close it\n if { $conn_type == 0 } {\n log local0. \"DEBUG: ($our_cid) Rejecting unauthorized connection\"\n reject\n }\n}\n\nwhen CLIENT_CLOSED {\n log local0. \"DEBUG: ($our_cid) CLIENT_CLOSED fired\"\n # Remove mappings associated with control connections\n if {$conn_type == 1 } {\n log local0. \"DEBUG: ($our_cid) Closing pasv data connection (port [TCP::local_port]) from [IP::client_addr]\"\n } elseif {$conn_type == 2} {\n log local0. \"DEBUG: ($our_cid) Closing control channel to [IP::client_addr]\"\n \n # We need to clean up after ourselves if we had a pasv port in use\n if {$our_pasv_port != 0 } {\n log local0. \"DEBUG: ($our_cid) Control channel for [IP::client_addr] was using pasv port $our_pasv_port. Unmapping.\"\n\n\n log local0. \"DEBUG: ($our_cid) Available PASV ports before freeing used port:\"\n foreach pport $::pasv_avail {\n log local0. \" $pport\"\n }\n\n # Put our port back in avail, but only if it wasn't already there\n # (avoids duplication if rule is reloaded while control channel is open)\n if { [lsearch -exact $::pasv_avail $our_pasv_port] == -1 } {\n lappend ::pasv_avail $our_pasv_port\n } else {\n log local0. \"DEBUG: ($our_cid) Not adding our port to pasv pool...already there\"\n }\n\n log local0. \"DEBUG: ($our_cid) Available PASV ports after freeing used port:\"\n foreach pport $::pasv_avail {\n log local0. \" $pport\"\n }\n\n # Same concept here...check first and unmap\n if { [info exists ::pasv_to_client($our_pasv_port)] } { \n unset ::pasv_to_client($our_pasv_port)\n } else {\n log local0. \"DEBUG: ($our_cid) Nothing to remove in ::pasv_to_client array\"\n }\n if { [info exists ::pasv_to_mbr($our_pasv_port)] } { \n unset ::pasv_to_mbr($our_pasv_port)\n } else {\n log local0. \"DEBUG: ($our_cid) Nothing to remove in ::pasv_to_mbr array\"\n }\n if { [info exists ::pasv_to_mbr_port($our_pasv_port)] } { \n unset ::pasv_to_mbr_port($our_pasv_port)\n } else {\n log local0. \"DEBUG: ($our_cid) Nothing to remove in ::pasv_to_mbr_port array\"\n }\n }\n } \n}\n\nwhen CLIENT_DATA {\n log local0. \"DEBUG: ($our_cid) CLIENT_DATA fired\"\n log local0. \"DEBUG: ($our_cid) Client data on connection type $conn_type, ssl_state $ctl_ssl_state:\"\n log local0. \" PAYLOAD: [TCP::payload]\"\n # We are only interested in intercepting non-SSL control channel payloads here\n if { $conn_type == 2 && $ctl_ssl_state == 0 } {\n log local0. \"DEBUG: ($our_cid) Handling intercepted client data payload\"\n # Here we intercept and handle all traffic prior to auth tls establishment\n set response \"\"\n \n if { [TCP::payload] starts_with \"AUTH \" } {\n # We only allow TLS auth\n if { [TCP::payload] starts_with \"AUTH TLS\" } {\n set response \"234 Command AUTH okay; starting TLS connection.\\r\\n\"\n set ctl_ssl_state 1\n } else {\n set response \"534 Request denied for policy reasons.\\r\\n\"\n }\n # Some clients send the following commands prior to their auth tls request\n } elseif { [TCP::payload] starts_with \"PBSZ 0\" } {\n # Essentially a no-op -- TODO: handle other PBSZ request sizes?\n set response \"200 Command PBSZ okay.\\r\\n\"\n } elseif { [TCP::payload] starts_with \"PROT \" } {\n # Reject everything but 'PROT P' mode\n if { [TCP::payload] starts_with \"PROT P\" } {\n set response \"200 Command PROT okay.\\r\\n\"\n } elseif { [TCP::payload] starts_with \"PROT C\" } {\n set response \"534 Insufficient data protection.\\r\\n\"\n } elseif { [TCP::payload] starts_with \"PROT S\" } {\n set response \"504 Command not implemented for that parameter.\\r\\n\"\n } elseif { [TCP::payload] starts_with \"PROT E\" } {\n set response \"504 Command not implemented for that parameter.\\r\\n\"\n }\n # Don't allow any other commands until auth tls has been established\n } else {\n set response \"530 Access denied.\\r\\n\"\n }\n \n # Now we have the response we wish to send, so send it\n log local0. \"DEBUG: ($our_cid) Sending response to intercepted plaintext payload: $response\"\n TCP::respond $response\n TCP::payload replace 0 [TCP::payload length] {}\n TCP::release\n \n # If we just received TLS request, then we will have transitioned this to 1\n # We can now enable SSL.\n if { $ctl_ssl_state == 1 } {\n log local0. \"DEBUG: ($our_cid) Enabling SSL on control channel connection\"\n SSL::enable\n } else {\n TCP::collect\n }\n } else {\n log local0. \"DEBUG: ($our_cid) Releasing unhandled collected client TCP payload\"\n TCP::release\n #TCP::collect\n }\n}\n\nwhen CLIENTSSL_HANDSHAKE {\n log local0. \"DEBUG: ($our_cid) CLIENTSSL_HANDSHAKE fired\"\n # We only want to intercept SSL payload for control channels\n if { $conn_type == 2 } {\n log local0. \"DEBUG: ($our_cid) SSL handshake on control connection. Collecting.\"\n SSL::collect\n }\n}\n\nwhen CLIENTSSL_DATA {\n log local0. \"DEBUG: ($our_cid) CLIENTSSL_DATA fired\"\n log local0. \"DEBUG: ($our_cid) Client SSL data on connection type $conn_type, ssl_state $ctl_ssl_state:\"\n log local0. \" PAYLOAD: [SSL::payload]\"\n # We only want to intercept SSL payload for control channels\n if { $conn_type == 2} {\n log local0. \"DEBUG: ($our_cid) Handling intercepted client SSL payload\"\n # Here we handle responses to auth-related commands after tls has been established\n set response \"\"\n \n if { [SSL::payload] starts_with \"AUTH \" } {\n # Not allowed to change auth after TLS established\n set response \"534 Session already secured.\\r\\n\"\n # Yes we handled these above as well, but here they occur within SSL payload\n } elseif { [SSL::payload] starts_with \"PBSZ 0\" } {\n set response \"200 Command PBSZ okay.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"PROT \" } {\n # Reject everything but 'PROT P' mode\n if { [SSL::payload] starts_with \"PROT P\" } {\n set response \"200 Command PROT okay.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"PROT C\" } {\n set response \"534 Insufficient data protection.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"PROT S\" } {\n set response \"504 Command not implemented for that parameter.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"PROT E\" } {\n set response \"504 Command not implemented for that parameter.\\r\\n\" \n }\n # We don't accept any of the below commands and intercept before they reach our ftp servers\n } elseif { [SSL::payload] starts_with \"CCC\" } {\n # Don't allow client to turn off control channel encryption\n set response \"534 Request denied for policy reasons.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"ADAT \" } {\n set response \"500 Syntax error, command unrecognized.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"MIC \" } {\n set response \"500 Syntax error, command unrecognized.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"CONF \" } {\n set response \"500 Syntax error, command unrecognized.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"ENC \" } {\n set response \"500 Syntax error, command unrecognized.\\r\\n\"\n }\n \n # In the main TCP portion, we're intercepting everything. Here we only intercept\n # some commands and let the backend server respond to everything else\n if { $response != \"\" } {\n log local0. \"DEBUG: ($our_cid) Sending response to intercepted SSL payload: $response\"\n SSL::respond $response\n SSL::payload replace 0 [SSL::payload length] {}\n }\n \n log local0. \"DEBUG: ($our_cid) Releasing intercepted SSL payload\"\n SSL::release\n log local0. \"DEBUG: ($our_cid) Collecting SSL again\"\n SSL::collect\n\n } else {\n log local0. \"DEBUG: ($our_cid) Releasing unhandled collected SSL payload\"\n SSL::release\n #SSL::collect\n }\n}\n\nwhen SERVER_CONNECTED {\n log local0. \"DEBUG: ($our_cid) SERVER_CONNECTED fired\"\n # We only want to intercept server payloads for control channels\n if { $conn_type == 2 } {\n log local0. \"DEBUG: ($our_cid) Control connection to member established. Collecting.\"\n TCP::collect\n } else {\n log local0. \"DEBUG: ($our_cid) Data connection to member established.\"\n }\n}\n\nwhen SERVER_CLOSED {\n log local0. \"DEBUG: ($our_cid) SERVER_CLOSED fired\"\n}\n\nwhen SERVER_DATA {\n log local0. \"DEBUG: ($our_cid) SERVER_DATA fired\"\n log local0. \"DEBUG: ($our_cid) Server data on connection type $conn_type, ssl_state $ctl_ssl_state:\"\n log local0. \" PAYLOAD: [TCP::payload]\"\n # We only want to intercept server payloads for control channels\n if { $conn_type == 2 } {\n log local0. \"DEBUG: ($our_cid) Handling intercepted server data payload\"\n # Intercept the PASV response sent by the pool member\n if { [TCP::payload] starts_with \"227 Entering Passive Mode\" } {\n log local0. \"DEBUG: ($our_cid) Intercepting member PASV response.\"\n # Extract and save pasv port given by member server -- needed for lookup on data channel connection\n set pasvstr [findstr [TCP::payload] \"227 Entering Passive Mode\" 27 \")\"]\n set mbr_pasv_port [ expr [getfield $pasvstr \",\" 5] * 256 + [getfield $pasvstr \",\" 6] ]\n log local0. \"DEBUG: ($our_cid) member server requesting connection be made to port $mbr_pasv_port\"\n \n # On the F5 side, we try to reuse the same port to avoid repeated search of port list\n if { $our_pasv_port == 0 } {\n # Make sure we have an available port to use\n if { [llength $::pasv_avail] > 0 } {\n\n log local0. \"DEBUG: ($our_cid) Available PASV ports before assignment:\"\n foreach pport $::pasv_avail {\n log local0. \" $pport\"\n }\n\n # Pop our port from end of available ports list\n set our_pasv_port [ lindex $::pasv_avail end ]\n set ::pasv_avail [ lreplace $::pasv_avail end end ]\n\n log local0. \"DEBUG: ($our_cid) Available PASV ports after assignment:\"\n foreach pport $::pasv_avail {\n log local0. \" $pport\"\n }\n \n # Format our port in PASV response string format\n set our_pasv_str \"[expr $our_pasv_port / 256],[expr $our_pasv_port % 256]\"\n log local0. \"DEBUG: ($our_cid) Setting this connection's pasv port to $our_pasv_port\"\n log local0. \"DEBUG: ($our_cid) Setting this connection's pasv string to $our_pasv_str\"\n\n # The pasv port to client IP and member IP mappings will remain constant once we assign the port\n set ::pasv_to_client($our_pasv_port) [IP::client_addr]\n set ::pasv_to_mbr($our_pasv_port) [LB::server addr]\n log local0. \"DEBUG: ($our_cid) Saved mapping PASV port -> client IP == $our_pasv_port -> [IP::client_addr]\"\n log local0. \"DEBUG: ($our_cid) Saved mapping PASV port -> member IP == $our_pasv_port -> [LB::server addr]\"\n } else {\n log local0. \"ALERT: No available PASV ports -- port range exhausted!\"\n TCP::payload replace 0 [TCP::payload length] \"421 Max connections reached.\\r\\n\"\n }\n }\n\n # Make our PASV response substitution only if we have a port for use\n if { $our_pasv_port != 0 } {\n\n # Save mapping of our port to member port -- We have to update this on each PASV response since the member\n # server isn't gauranteed to respond with the same port to us each time\n set ::pasv_to_mbr_port($our_pasv_port) $mbr_pasv_port\n log local0. \"DEBUG: ($our_cid) Saved mapping PASV port -> member port == $our_pasv_port -> $mbr_pasv_port\"\n \n # Make our PASV response substitution\n set pasv_response \"227 Entering Passive Mode ($static::pasv_max_port,$our_pasv_str)\\r\\n\"\n log local0. \"DEBUG: ($our_cid) Sending updated PASV response to client:\"\n log local0. \" $pasv_response\"\n TCP::payload replace 0 [TCP::payload length] $pasv_response\n }\n }\n \n # Continue intercepting server responses\n log local0. \"DEBUG: ($our_cid) Releasing intercepted server TCP payload\"\n TCP::release\n log local0. \"DEBUG: ($our_cid) Collecting server TCP again\"\n TCP::collect\n \n # Depending on our SSL status, we either want to collect client-side TCP or SSL\n if { $ctl_ssl_state == 0 } {\n log local0. \"DEBUG: ($our_cid) Signaling clientside TCP collect\"\n clientside { TCP::collect }\n } elseif { $ctl_ssl_state == 1 } {\n log local0. \"DEBUG: ($our_cid) Signaling clientside SSL collect\"\n clientside { SSL::collect }\n }\n } else {\n log local0. \"DEBUG: ($our_cid) Releasing unhandled collected server TCP payload\"\n TCP::release\n #TCP::collect\n }\n}
","body@stringLength":"25811","rawBody":"
Problem this snippet solves:
This iRule is meant to act as a full FTPS termination rule that behaves similarly to HTTPS termination. It forces either an implicit (port 990 by default) or explicit (port 21 with AUTH TLS) connection to be made before commands are passed to the member FTP servers. All traffic from the F5 to the pool member FTP servers is then passed in cleartext and all AUTH/SSL related commands are intercepted by the F5 so that the pool members handle the session just like any other non-SSL session (again, similar to HTTPS termination).
\n
Please note that this is currently a work-in-progress. It is very close to complete but has some lingering issues. I am hoping that in sharing my work, some of the other gurus around here will take a look and help with optimization and correcting any remaining problems. In it's current form, you can put a pool of FTP servers behind it and connect from a variety of FTP clients (lftp, filezilla, and kftpgrabber tested). The rule intercepts pasv response strings, so no additional configuration is needed on the pool member FTP servers.
\n
However, it seems there is some sort of race/timing problem that I have yet to pinpoint. My tests using Apache FTP server (see http://mina.apache.org/ftpserver/running-ftpserver-stand-alone-in-5-minutes.html for usage) on the pool members work in large part. However, my tests using proftpd on the pool members fail the logging indicates the pasv connection is never established to the pool member server (no 'SERVER_CONNECTED' log message for the pasv connection). I am currently working to correct this problem, but please feel free to let me know if you can spot the issue.
\n
Another issue I am using an ad hoc method of allocating and tracking vserver pasv ports. I'd much rather use the F5's built-in TCP::unused_port in conjunction with a 'listen' directive right after a PASV request is intercepted, but I have not been able to determine the proper way to use 'listen'. It looks like 'listen' and 'relate_client' are potentially what are needed, but I cannot find much documentation on these commands,
\n
Finally, the rule is littered with log statements. I'm definitely aware that this would slow the rule down in production. I would grep those out prior to deploying this. Otherwise, despite the length of the rule, it seems to run decently fast for what it is accomplishing. The control channel connections don't pass much data, and each can be matched via 'starts_with' as opposed to 'contains', and the pasv payloads aren't matched/altered at all. Much of the processing is done only during the initial control channel connection.
\n
Please feel free to try this iRule. I'd be happy to receive feedback on it!
Code :
# Author: Bob Ziuchkovski \n# Date: Sep 27, 2010\n# Version: 1.0\n#\n# IMPORTANT: This is a \"work-in-progress\". It has some problems as noted in\n# the Codeshare description.\n#\n# Summary: This irule is meant to implement FTPS termination transparently in\n# a manner similar to F5 HTTPS termination. We try to intercept all commands\n# related to SSL/TLS (RFC4217) and FTP Auth extensions in general (RFC2228).\n#\n# Usage: \n# 1. Ensure the BIG-IP version is 10.2 or higher. Versions prior\n# to 10.2 seem have problems with the SSL::respond command (and\n# 9.x is missing the SSL::* commands entirely)\n# 2. Add this iRule to the F5. Edit the configuration settings between\n# the 'BEGIN' and 'END' configuration sections below\n# 3. Create a client SSL profile for terminating the FTPS sessions.\n# Make sure to disable the 'Unclean Shutdown' option in the client\n# profile. Otherwise certain FTP clients will fail connections due\n# to unexpected packet lengths (most notably clients using GnuTLS)\n# 4. Create a custom TCP Full Open monitor and set it to monitor\n# the control channel port of the member FTP servers. Set the\n# receive string to match the FTP server's welcome message (i.e.\n# 220 Service ready for new user.)\n# 5. Create a pool for FTP servers. Apply the custom monitor created\n# above\n# 6. Add member FTP servers to the pool. The members must be added\n# with the '*' (All ports) setting.\n# 7. Create a virtual server with the following settings:\n# - Listen on all ports ('*')\n# - Port Translation setting enabled (under 'Advanced')\n# - This iRule applied\n# - Default pool set to the pool created above\n# - Optional: Set a source address persistence profile. If all\n# member FTP servers serve identical content from a shared mount\n# then this isn't needed. The pasv data connections are matched\n# and sent to the same member that is serving the control channel.\n# However, if there is any delay in content synchronization between\n# the member servers, then source address persistence is recommended\n# since some FTP clients will open additional control connections to\n# retrieve files.\n# * Example: A user could see a file on one connection and instruct\n# their FTP client try to retrieve the file. The FTP client opens\n# a second control connection (a means of keeping a GUI control\n# connection responsive during file transfer), and find the file\n# missing on the second connection do to being load-balanced to a\n# server that lacks the file (i.e. rsync or simimlar delay).\n# \n# Misc: 1. The member FTP servers can be configured to use any PASV port range\n# and send any PASV IP response they want -- it doesn't matter because\n# we intercept those responses and remap ourselves. Make sure to set\n# the correct $static::pasv_max_port below, though!\n# 2. The member FTP servers should be configured to reject active mode\n# data connections (this rule supports passive mode only).\n# 3. We don't incercept the 'FEAT' command to advertise TLS/SSL. I have\n# not run into any FTP clients that take issue with this, since most\n# will already establish TLS/SSL by the time they send the FEAT\n# request. Those that don't seem to reissue FEAT again at a later\n# time after the initial failure. That said, if running into problems\n# with an obscure FTP client, this is one of the first things I would\n# recommend checking.\n# 4. This rule intercepts ADAT, MIC, CONF, and ENC commands. I did this\n# to prevent our member FTP servers from seeing or responding to\n# anything related to FTP Auth extensions. However, this could\n# probably be omitted as an optimization -- the negative response sent\n# by the member server would probably suffice, although the error code\n# itself wouldn't make sense (the member server will respond that no\n# auth tls/ssl is in use).\n#\n# Optimizations to consider:\n#\n# 1. I've left all sorts of DEBUG logging statements in the iRule for\n# testing. I have been working with two copies of this rule. One\n# with these log statements (a 'debug' version) and one with them\n# removed (simple grep -v to remove). I don't like the overhead of\n# additional conditional if {$debug == 1} types of checks, so these\n# are not included.\n# 2. Remove checks for ADAT, MIC, CONF, and ENC mentioned above.\n\n\nwhen RULE_INIT {\n#----------------------------------\n# Begin configuration portion \n#----------------------------------\n\n# Member server port to use for control channel\nset static::ftp_member_port 2121\n\n# Vserver port to use for FTPS explicit control channel (-1 to disable)\nset static::ftps_explicit_port 21\n\n# Verserver Port to use for FTPS implicit control channel (-1 to disable)\nset static::ftps_implicit_port 990\n\n# Vserver Ports to use for PASV data channel connections -- does not need to\n# match the member server PASV ports as we automatically track and map those\nset static::pasv_min_port 50000\nset static::pasv_max_port 50005\n\n# Vserver IP to advertise for incoming PASV data channel connections\n# NOTE: the commas are NOT a typo. This is the format used by FTP protocol\nset static::pasv_max_port \"10,0,0,46\"\n\n#----------------------------------\n# End configuration portion\n#----------------------------------\n\n# TODO: Figure out how to properly use the 'listen' directive so that we can use\n# 'listen' in combination with TCP::unused_port to avoid this ad hoc port tracking\n\n# Track in-use PASV mappings (our pasv port -> client IP/member IP/member port)\narray set ::pasv_to_client {}\narray set ::pasv_to_mbr {}\narray set ::pasv_to_mbr_port {}\n\n# Clear out any mappings already in-use (i.e. rule reload/update)\narray unset ::pasv_to_client *\narray unset ::pasv_to_mbr *\narray unset ::pasv_to_mbr_port *\n\n# Track available PASV ports\nset ::pasv_avail {}\n\n# Make sure the list is cleaned-out from any prior states and add our ports\nset ::pasv_avail [ lreplace $::pasv_avail 0 end ]\nforeach {set i $static::pasv_max_port} { $i >= $static::pasv_min_port } {incr i -1} {\nlappend ::pasv_avail $i\n}\n\n# Debugging-only -- set a numeric auto-incrementing ID for each connection\n# (helps differentiate between events fired for different connections)\nset ::cid 0\n}\n\nwhen LB_FAILED {\n log local0. \"DEBUG: ($our_cid) LB_FAILED fired\"\n}\n\nwhen CLIENT_ACCEPTED {\n incr ::cid\n set our_cid $::cid\n\n log local0. \"DEBUG: ($our_cid) CLIENT_ACCEPTED fired\"\n # We need to handle control channel and data channel connections differently\n # 0 = unknown/unauthorized, 1 = data channel, 2 = control channel\n set conn_type 0\n\n # Is this a pasv data channel connection? (first since triggered most often)\n if { [TCP::local_port] >= $static::pasv_min_port && [TCP::local_port] <= $static::pasv_max_port } {\n # Port is pasv port but does it match a client control connection?\n if {[info exists ::pasv_to_client([TCP::local_port])] } { \n set orig_client $::pasv_to_client([TCP::local_port])\n if { $orig_client == [IP::client_addr] } {\n log local0. \"DEBUG: ($our_cid) PASV attempt from established client [IP::client_addr] on port [TCP::local_port]\"\n\n # Retrieve member IP/port waiting for connection\n set orig_mbr $::pasv_to_mbr([TCP::local_port])\n set orig_mbr_port $::pasv_to_mbr_port([TCP::local_port])\n log local0. \"DEBUG: ($our_cid) Member associated with PASV connnection: $orig_mbr (port $orig_mbr_port)\"\n if { $orig_mbr != \"\" && $orig_mbr_port != \"\" } {\n # This is a legitimate pasv data connection and a member server is waiting for it.\n set conn_type 1\n\n # Connect client directly to the member server on proper port\n log local0. \"DEBUG: ($our_cid) Executing node command: 'node $orig_mbr $orig_mbr_port'\"\n node $orig_mbr $orig_mbr_port\n log local0. \"DEBUG: ($our_cid) Node command executed\"\n } else {\n log local0. \"DEBUG: ($our_cid) PASV connection not mapped to member server / member port\"\n }\n } else {\n log local0. \"ALERT: Unauthorized connection to mapped pasv port [TCP::local_port] by [IP::client_addr] -- expected $orig_client instead!\"\n }\n } else {\n log local0. \"ALERT: Unauthorized connection to unmapped pasv port [TCP::local_port] by [IP::client_addr]\"\n }\n\n # Is this a control channel connection?\n } elseif { [TCP::local_port] == $static::ftps_explicit_port || [TCP::local_port] == $static::ftps_implicit_port} {\n set conn_type 2\n\n # Keep a persistent vserver pasv port assignment per control channel connection\n set our_pasv_port 0\n set our_pasv_str \"\"\n\n if { [TCP::local_port] == $static::ftps_explicit_port } {\n log local0. \"DEBUG: ($our_cid) new ftps explicit control connection from [IP::client_addr]\"\n\n # We need to track the ssl state of control channels\n # 0 = disabled, 1 = enabled\n set ctl_ssl_state 0\n SSL::disable\n } elseif { [TCP::local_port] == $static::ftps_implicit_port } {\n log local0. \"DEBUG: ($our_cid) new ftps implicit control connection from [IP::client_addr]\"\n\n # We need to track the ssl state of control channels\n # 0 = disabled, 1 = enabled\n set ctl_ssl_state 1\n }\n\n # Here we have the LB choose our node for the control channel, but we make\n # the connection with the 'node' command since member control channel port\n # might differ from vserver control channel port\n set node_selection [getfield [LB::select] \" \" 4]\n node $node_selection $static::ftp_member_port\n\n # Invalid connection -- made to a port other than one we are using for FTP\n } else {\n log local0. \"ALERT: Unauthorized connection non-ftp port [TCP::local_port] by [IP::client_addr]\"\n }\n\n # If the connection isn't legitimate, close it\n if { $conn_type == 0 } {\n log local0. \"DEBUG: ($our_cid) Rejecting unauthorized connection\"\n reject\n }\n}\n\nwhen CLIENT_CLOSED {\n log local0. \"DEBUG: ($our_cid) CLIENT_CLOSED fired\"\n # Remove mappings associated with control connections\n if {$conn_type == 1 } {\n log local0. \"DEBUG: ($our_cid) Closing pasv data connection (port [TCP::local_port]) from [IP::client_addr]\"\n } elseif {$conn_type == 2} {\n log local0. \"DEBUG: ($our_cid) Closing control channel to [IP::client_addr]\"\n \n # We need to clean up after ourselves if we had a pasv port in use\n if {$our_pasv_port != 0 } {\n log local0. \"DEBUG: ($our_cid) Control channel for [IP::client_addr] was using pasv port $our_pasv_port. Unmapping.\"\n\n\n log local0. \"DEBUG: ($our_cid) Available PASV ports before freeing used port:\"\n foreach pport $::pasv_avail {\n log local0. \" $pport\"\n }\n\n # Put our port back in avail, but only if it wasn't already there\n # (avoids duplication if rule is reloaded while control channel is open)\n if { [lsearch -exact $::pasv_avail $our_pasv_port] == -1 } {\n lappend ::pasv_avail $our_pasv_port\n } else {\n log local0. \"DEBUG: ($our_cid) Not adding our port to pasv pool...already there\"\n }\n\n log local0. \"DEBUG: ($our_cid) Available PASV ports after freeing used port:\"\n foreach pport $::pasv_avail {\n log local0. \" $pport\"\n }\n\n # Same concept here...check first and unmap\n if { [info exists ::pasv_to_client($our_pasv_port)] } { \n unset ::pasv_to_client($our_pasv_port)\n } else {\n log local0. \"DEBUG: ($our_cid) Nothing to remove in ::pasv_to_client array\"\n }\n if { [info exists ::pasv_to_mbr($our_pasv_port)] } { \n unset ::pasv_to_mbr($our_pasv_port)\n } else {\n log local0. \"DEBUG: ($our_cid) Nothing to remove in ::pasv_to_mbr array\"\n }\n if { [info exists ::pasv_to_mbr_port($our_pasv_port)] } { \n unset ::pasv_to_mbr_port($our_pasv_port)\n } else {\n log local0. \"DEBUG: ($our_cid) Nothing to remove in ::pasv_to_mbr_port array\"\n }\n }\n } \n}\n\nwhen CLIENT_DATA {\n log local0. \"DEBUG: ($our_cid) CLIENT_DATA fired\"\n log local0. \"DEBUG: ($our_cid) Client data on connection type $conn_type, ssl_state $ctl_ssl_state:\"\n log local0. \" PAYLOAD: [TCP::payload]\"\n # We are only interested in intercepting non-SSL control channel payloads here\n if { $conn_type == 2 && $ctl_ssl_state == 0 } {\n log local0. \"DEBUG: ($our_cid) Handling intercepted client data payload\"\n # Here we intercept and handle all traffic prior to auth tls establishment\n set response \"\"\n \n if { [TCP::payload] starts_with \"AUTH \" } {\n # We only allow TLS auth\n if { [TCP::payload] starts_with \"AUTH TLS\" } {\n set response \"234 Command AUTH okay; starting TLS connection.\\r\\n\"\n set ctl_ssl_state 1\n } else {\n set response \"534 Request denied for policy reasons.\\r\\n\"\n }\n # Some clients send the following commands prior to their auth tls request\n } elseif { [TCP::payload] starts_with \"PBSZ 0\" } {\n # Essentially a no-op -- TODO: handle other PBSZ request sizes?\n set response \"200 Command PBSZ okay.\\r\\n\"\n } elseif { [TCP::payload] starts_with \"PROT \" } {\n # Reject everything but 'PROT P' mode\n if { [TCP::payload] starts_with \"PROT P\" } {\n set response \"200 Command PROT okay.\\r\\n\"\n } elseif { [TCP::payload] starts_with \"PROT C\" } {\n set response \"534 Insufficient data protection.\\r\\n\"\n } elseif { [TCP::payload] starts_with \"PROT S\" } {\n set response \"504 Command not implemented for that parameter.\\r\\n\"\n } elseif { [TCP::payload] starts_with \"PROT E\" } {\n set response \"504 Command not implemented for that parameter.\\r\\n\"\n }\n # Don't allow any other commands until auth tls has been established\n } else {\n set response \"530 Access denied.\\r\\n\"\n }\n \n # Now we have the response we wish to send, so send it\n log local0. \"DEBUG: ($our_cid) Sending response to intercepted plaintext payload: $response\"\n TCP::respond $response\n TCP::payload replace 0 [TCP::payload length] {}\n TCP::release\n \n # If we just received TLS request, then we will have transitioned this to 1\n # We can now enable SSL.\n if { $ctl_ssl_state == 1 } {\n log local0. \"DEBUG: ($our_cid) Enabling SSL on control channel connection\"\n SSL::enable\n } else {\n TCP::collect\n }\n } else {\n log local0. \"DEBUG: ($our_cid) Releasing unhandled collected client TCP payload\"\n TCP::release\n #TCP::collect\n }\n}\n\nwhen CLIENTSSL_HANDSHAKE {\n log local0. \"DEBUG: ($our_cid) CLIENTSSL_HANDSHAKE fired\"\n # We only want to intercept SSL payload for control channels\n if { $conn_type == 2 } {\n log local0. \"DEBUG: ($our_cid) SSL handshake on control connection. Collecting.\"\n SSL::collect\n }\n}\n\nwhen CLIENTSSL_DATA {\n log local0. \"DEBUG: ($our_cid) CLIENTSSL_DATA fired\"\n log local0. \"DEBUG: ($our_cid) Client SSL data on connection type $conn_type, ssl_state $ctl_ssl_state:\"\n log local0. \" PAYLOAD: [SSL::payload]\"\n # We only want to intercept SSL payload for control channels\n if { $conn_type == 2} {\n log local0. \"DEBUG: ($our_cid) Handling intercepted client SSL payload\"\n # Here we handle responses to auth-related commands after tls has been established\n set response \"\"\n \n if { [SSL::payload] starts_with \"AUTH \" } {\n # Not allowed to change auth after TLS established\n set response \"534 Session already secured.\\r\\n\"\n # Yes we handled these above as well, but here they occur within SSL payload\n } elseif { [SSL::payload] starts_with \"PBSZ 0\" } {\n set response \"200 Command PBSZ okay.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"PROT \" } {\n # Reject everything but 'PROT P' mode\n if { [SSL::payload] starts_with \"PROT P\" } {\n set response \"200 Command PROT okay.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"PROT C\" } {\n set response \"534 Insufficient data protection.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"PROT S\" } {\n set response \"504 Command not implemented for that parameter.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"PROT E\" } {\n set response \"504 Command not implemented for that parameter.\\r\\n\" \n }\n # We don't accept any of the below commands and intercept before they reach our ftp servers\n } elseif { [SSL::payload] starts_with \"CCC\" } {\n # Don't allow client to turn off control channel encryption\n set response \"534 Request denied for policy reasons.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"ADAT \" } {\n set response \"500 Syntax error, command unrecognized.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"MIC \" } {\n set response \"500 Syntax error, command unrecognized.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"CONF \" } {\n set response \"500 Syntax error, command unrecognized.\\r\\n\"\n } elseif { [SSL::payload] starts_with \"ENC \" } {\n set response \"500 Syntax error, command unrecognized.\\r\\n\"\n }\n \n # In the main TCP portion, we're intercepting everything. Here we only intercept\n # some commands and let the backend server respond to everything else\n if { $response != \"\" } {\n log local0. \"DEBUG: ($our_cid) Sending response to intercepted SSL payload: $response\"\n SSL::respond $response\n SSL::payload replace 0 [SSL::payload length] {}\n }\n \n log local0. \"DEBUG: ($our_cid) Releasing intercepted SSL payload\"\n SSL::release\n log local0. \"DEBUG: ($our_cid) Collecting SSL again\"\n SSL::collect\n\n } else {\n log local0. \"DEBUG: ($our_cid) Releasing unhandled collected SSL payload\"\n SSL::release\n #SSL::collect\n }\n}\n\nwhen SERVER_CONNECTED {\n log local0. \"DEBUG: ($our_cid) SERVER_CONNECTED fired\"\n # We only want to intercept server payloads for control channels\n if { $conn_type == 2 } {\n log local0. \"DEBUG: ($our_cid) Control connection to member established. Collecting.\"\n TCP::collect\n } else {\n log local0. \"DEBUG: ($our_cid) Data connection to member established.\"\n }\n}\n\nwhen SERVER_CLOSED {\n log local0. \"DEBUG: ($our_cid) SERVER_CLOSED fired\"\n}\n\nwhen SERVER_DATA {\n log local0. \"DEBUG: ($our_cid) SERVER_DATA fired\"\n log local0. \"DEBUG: ($our_cid) Server data on connection type $conn_type, ssl_state $ctl_ssl_state:\"\n log local0. \" PAYLOAD: [TCP::payload]\"\n # We only want to intercept server payloads for control channels\n if { $conn_type == 2 } {\n log local0. \"DEBUG: ($our_cid) Handling intercepted server data payload\"\n # Intercept the PASV response sent by the pool member\n if { [TCP::payload] starts_with \"227 Entering Passive Mode\" } {\n log local0. \"DEBUG: ($our_cid) Intercepting member PASV response.\"\n # Extract and save pasv port given by member server -- needed for lookup on data channel connection\n set pasvstr [findstr [TCP::payload] \"227 Entering Passive Mode\" 27 \")\"]\n set mbr_pasv_port [ expr [getfield $pasvstr \",\" 5] * 256 + [getfield $pasvstr \",\" 6] ]\n log local0. \"DEBUG: ($our_cid) member server requesting connection be made to port $mbr_pasv_port\"\n \n # On the F5 side, we try to reuse the same port to avoid repeated search of port list\n if { $our_pasv_port == 0 } {\n # Make sure we have an available port to use\n if { [llength $::pasv_avail] > 0 } {\n\n log local0. \"DEBUG: ($our_cid) Available PASV ports before assignment:\"\n foreach pport $::pasv_avail {\n log local0. \" $pport\"\n }\n\n # Pop our port from end of available ports list\n set our_pasv_port [ lindex $::pasv_avail end ]\n set ::pasv_avail [ lreplace $::pasv_avail end end ]\n\n log local0. \"DEBUG: ($our_cid) Available PASV ports after assignment:\"\n foreach pport $::pasv_avail {\n log local0. \" $pport\"\n }\n \n # Format our port in PASV response string format\n set our_pasv_str \"[expr $our_pasv_port / 256],[expr $our_pasv_port % 256]\"\n log local0. \"DEBUG: ($our_cid) Setting this connection's pasv port to $our_pasv_port\"\n log local0. \"DEBUG: ($our_cid) Setting this connection's pasv string to $our_pasv_str\"\n\n # The pasv port to client IP and member IP mappings will remain constant once we assign the port\n set ::pasv_to_client($our_pasv_port) [IP::client_addr]\n set ::pasv_to_mbr($our_pasv_port) [LB::server addr]\n log local0. \"DEBUG: ($our_cid) Saved mapping PASV port -> client IP == $our_pasv_port -> [IP::client_addr]\"\n log local0. \"DEBUG: ($our_cid) Saved mapping PASV port -> member IP == $our_pasv_port -> [LB::server addr]\"\n } else {\n log local0. \"ALERT: No available PASV ports -- port range exhausted!\"\n TCP::payload replace 0 [TCP::payload length] \"421 Max connections reached.\\r\\n\"\n }\n }\n\n # Make our PASV response substitution only if we have a port for use\n if { $our_pasv_port != 0 } {\n\n # Save mapping of our port to member port -- We have to update this on each PASV response since the member\n # server isn't gauranteed to respond with the same port to us each time\n set ::pasv_to_mbr_port($our_pasv_port) $mbr_pasv_port\n log local0. \"DEBUG: ($our_cid) Saved mapping PASV port -> member port == $our_pasv_port -> $mbr_pasv_port\"\n \n # Make our PASV response substitution\n set pasv_response \"227 Entering Passive Mode ($static::pasv_max_port,$our_pasv_str)\\r\\n\"\n log local0. \"DEBUG: ($our_cid) Sending updated PASV response to client:\"\n log local0. \" $pasv_response\"\n TCP::payload replace 0 [TCP::payload length] $pasv_response\n }\n }\n \n # Continue intercepting server responses\n log local0. \"DEBUG: ($our_cid) Releasing intercepted server TCP payload\"\n TCP::release\n log local0. \"DEBUG: ($our_cid) Collecting server TCP again\"\n TCP::collect\n \n # Depending on our SSL status, we either want to collect client-side TCP or SSL\n if { $ctl_ssl_state == 0 } {\n log local0. \"DEBUG: ($our_cid) Signaling clientside TCP collect\"\n clientside { TCP::collect }\n } elseif { $ctl_ssl_state == 1 } {\n log local0. \"DEBUG: ($our_cid) Signaling clientside SSL collect\"\n clientside { SSL::collect }\n }\n } else {\n log local0. \"DEBUG: ($our_cid) Releasing unhandled collected server TCP payload\"\n TCP::release\n #TCP::collect\n }\n}
","kudosSumWeight":1,"postTime":"2015-03-17T14:08:43.000-07:00","images":{"__typename":"AssociatedImageConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"attachments":{"__typename":"AttachmentConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"tags":{"__typename":"TagConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[{"__typename":"TagEdge","cursor":"MjUuMnwyLjF8b3wxMHxfTlZffDE","node":{"__typename":"Tag","id":"tag:application delivery","text":"application delivery","time":"2021-06-30T01:48:44.000-07:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuMnwyLjF8b3wxMHxfTlZffDI","node":{"__typename":"Tag","id":"tag:devops","text":"devops","time":"2011-10-19T17:50:55.000-07:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuMnwyLjF8b3wxMHxfTlZffDM","node":{"__typename":"Tag","id":"tag:iRules","text":"iRules","time":"2022-01-24T02:29:45.106-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuMnwyLjF8b3wxMHxfTlZffDQ","node":{"__typename":"Tag","id":"tag:security","text":"security","time":"2009-07-03T08:19:36.000-07:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}}]},"timeToRead":14,"rawTeaser":"","introduction":"","currentRevision":{"__ref":"Revision:revision:274110_1"},"latestVersion":{"__typename":"FriendlyVersion","major":"1","minor":"0"},"metrics":{"__typename":"MessageMetrics","views":971},"visibilityScope":"PUBLIC","canonicalUrl":null,"seoTitle":null,"seoDescription":null,"placeholder":false,"originalMessageForPlaceholder":null,"contributors":{"__typename":"UserConnection","edges":[]},"nonCoAuthorContributors":{"__typename":"UserConnection","edges":[]},"coAuthors":{"__typename":"UserConnection","edges":[{"__typename":"UserEdge","node":{"__ref":"User:user:14256"}}]},"tkbMessagePolicies":{"__typename":"TkbMessagePolicies","canDoAuthoringActionsOnTkb":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.tkb.policy_can_do_authoring_action.accessDenied","key":"error.lithium.policies.tkb.policy_can_do_authoring_action.accessDenied","args":[]}}},"archivalData":null,"replies":{"__typename":"MessageConnection","edges":[{"__typename":"MessageEdge","cursor":"MjUuMnwyLjF8aXwxMHwzOToxfGludCwzMDI3NzYsMzAyNzc2","node":{"__ref":"TkbReplyMessage:message:302776"}}],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"customFields":[],"revisions({\"constraints\":{\"isPublished\":{\"eq\":true}},\"first\":1})":{"__typename":"RevisionConnection","totalCount":1}},"Conversation:conversation:274110":{"__typename":"Conversation","id":"conversation:274110","solved":false,"topic":{"__ref":"TkbTopicMessage:message:274110"},"lastPostingActivityTime":"2022-10-13T09:57:43.292-07:00","lastPostTime":"2022-10-13T09:57:43.292-07:00","unreadReplyCount":1,"isSubscribed":false},"ModerationData:moderation_data:274110":{"__typename":"ModerationData","id":"moderation_data:274110","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"Revision:revision:274110_1":{"__typename":"Revision","id":"revision:274110_1","lastEditTime":"2015-03-17T14:08:43.000-07:00"},"CachedAsset:theme:customTheme1-1744511659007":{"__typename":"CachedAsset","id":"theme:customTheme1-1744511659007","value":{"id":"customTheme1","animation":{"fast":"150ms","normal":"250ms","slow":"500ms","slowest":"750ms","function":"cubic-bezier(0.07, 0.91, 0.51, 1)","__typename":"AnimationThemeSettings"},"avatar":{"borderRadius":"50%","collections":["custom"],"__typename":"AvatarThemeSettings"},"basics":{"browserIcon":{"imageAssetName":"JimmyPackets-512-1702592938213.png","imageLastModified":"1702592945815","__typename":"ThemeAsset"},"customerLogo":{"imageAssetName":"f5_logo_fix-1704824537976.svg","imageLastModified":"1704824540697","__typename":"ThemeAsset"},"maximumWidthOfPageContent":"1600px","oneColumnNarrowWidth":"800px","gridGutterWidthMd":"30px","gridGutterWidthXs":"10px","pageWidthStyle":"WIDTH_OF_PAGE_CONTENT","__typename":"BasicsThemeSettings"},"buttons":{"borderRadiusSm":"5px","borderRadius":"5px","borderRadiusLg":"5px","paddingY":"5px","paddingYLg":"7px","paddingYHero":"var(--lia-bs-btn-padding-y-lg)","paddingX":"12px","paddingXLg":"14px","paddingXHero":"42px","fontStyle":"NORMAL","fontWeight":"400","textTransform":"NONE","disabledOpacity":0.5,"primaryTextColor":"var(--lia-bs-white)","primaryTextHoverColor":"var(--lia-bs-white)","primaryTextActiveColor":"var(--lia-bs-white)","primaryBgColor":"var(--lia-bs-primary)","primaryBgHoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.85))","primaryBgActiveColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.7))","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","primaryBorderActive":"1px solid transparent","primaryBorderFocus":"1px solid var(--lia-bs-white)","primaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","secondaryTextColor":"var(--lia-bs-gray-900)","secondaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","secondaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","secondaryBgColor":"var(--lia-bs-gray-400)","secondaryBgHoverColor":"hsl(var(--lia-bs-gray-400-h), var(--lia-bs-gray-400-s), calc(var(--lia-bs-gray-400-l) * 0.96))","secondaryBgActiveColor":"hsl(var(--lia-bs-gray-400-h), var(--lia-bs-gray-400-s), calc(var(--lia-bs-gray-400-l) * 0.92))","secondaryBorder":"1px solid transparent","secondaryBorderHover":"1px solid transparent","secondaryBorderActive":"1px solid transparent","secondaryBorderFocus":"1px solid transparent","secondaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","tertiaryTextColor":"var(--lia-bs-gray-900)","tertiaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","tertiaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","tertiaryBgColor":"transparent","tertiaryBgHoverColor":"transparent","tertiaryBgActiveColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.04)","tertiaryBorder":"1px solid transparent","tertiaryBorderHover":"1px solid hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","tertiaryBorderActive":"1px solid transparent","tertiaryBorderFocus":"1px solid transparent","tertiaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","destructiveTextColor":"var(--lia-bs-danger)","destructiveTextHoverColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.95))","destructiveTextActiveColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.9))","destructiveBgColor":"var(--lia-bs-gray-300)","destructiveBgHoverColor":"hsl(var(--lia-bs-gray-300-h), var(--lia-bs-gray-300-s), calc(var(--lia-bs-gray-300-l) * 0.96))","destructiveBgActiveColor":"hsl(var(--lia-bs-gray-300-h), var(--lia-bs-gray-300-s), calc(var(--lia-bs-gray-300-l) * 0.92))","destructiveBorder":"1px solid transparent","destructiveBorderHover":"1px solid transparent","destructiveBorderActive":"1px solid transparent","destructiveBorderFocus":"1px solid transparent","destructiveBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","__typename":"ButtonsThemeSettings"},"border":{"color":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","mainContent":"NONE","sideContent":"NONE","radiusSm":"3px","radius":"5px","radiusLg":"9px","radius50":"100vw","__typename":"BorderThemeSettings"},"boxShadow":{"xs":"0 0 0 1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.08), 0 3px 0 -1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.08)","sm":"0 2px 4px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.06)","md":"0 5px 15px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.15)","lg":"0 10px 30px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.15)","__typename":"BoxShadowThemeSettings"},"cards":{"bgColor":"var(--lia-panel-bg-color)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":"var(--lia-box-shadow-xs)","__typename":"CardsThemeSettings"},"chip":{"maxWidth":"300px","height":"30px","__typename":"ChipThemeSettings"},"coreTypes":{"defaultMessageLinkColor":"var(--lia-bs-primary)","defaultMessageLinkDecoration":"none","defaultMessageLinkFontStyle":"NORMAL","defaultMessageLinkFontWeight":"400","defaultMessageFontStyle":"NORMAL","defaultMessageFontWeight":"400","forumColor":"#0C5C8D","forumFontFamily":"var(--lia-bs-font-family-base)","forumFontWeight":"var(--lia-default-message-font-weight)","forumLineHeight":"var(--lia-bs-line-height-base)","forumFontStyle":"var(--lia-default-message-font-style)","forumMessageLinkColor":"var(--lia-default-message-link-color)","forumMessageLinkDecoration":"var(--lia-default-message-link-decoration)","forumMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","forumMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","forumSolvedColor":"#62C026","blogColor":"#730015","blogFontFamily":"var(--lia-bs-font-family-base)","blogFontWeight":"var(--lia-default-message-font-weight)","blogLineHeight":"1.75","blogFontStyle":"var(--lia-default-message-font-style)","blogMessageLinkColor":"var(--lia-default-message-link-color)","blogMessageLinkDecoration":"var(--lia-default-message-link-decoration)","blogMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","blogMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","tkbColor":"#C20025","tkbFontFamily":"var(--lia-bs-font-family-base)","tkbFontWeight":"var(--lia-default-message-font-weight)","tkbLineHeight":"1.75","tkbFontStyle":"var(--lia-default-message-font-style)","tkbMessageLinkColor":"var(--lia-default-message-link-color)","tkbMessageLinkDecoration":"var(--lia-default-message-link-decoration)","tkbMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","tkbMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaColor":"#4099E2","qandaFontFamily":"var(--lia-bs-font-family-base)","qandaFontWeight":"var(--lia-default-message-font-weight)","qandaLineHeight":"var(--lia-bs-line-height-base)","qandaFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkColor":"var(--lia-default-message-link-color)","qandaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","qandaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaSolvedColor":"#3FA023","ideaColor":"#F3704B","ideaFontFamily":"var(--lia-bs-font-family-base)","ideaFontWeight":"var(--lia-default-message-font-weight)","ideaLineHeight":"var(--lia-bs-line-height-base)","ideaFontStyle":"var(--lia-default-message-font-style)","ideaMessageLinkColor":"var(--lia-default-message-link-color)","ideaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","ideaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","ideaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","contestColor":"#FCC845","contestFontFamily":"var(--lia-bs-font-family-base)","contestFontWeight":"var(--lia-default-message-font-weight)","contestLineHeight":"var(--lia-bs-line-height-base)","contestFontStyle":"var(--lia-default-message-link-font-style)","contestMessageLinkColor":"var(--lia-default-message-link-color)","contestMessageLinkDecoration":"var(--lia-default-message-link-decoration)","contestMessageLinkFontStyle":"ITALIC","contestMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","occasionColor":"#EE4B5B","occasionFontFamily":"var(--lia-bs-font-family-base)","occasionFontWeight":"var(--lia-default-message-font-weight)","occasionLineHeight":"var(--lia-bs-line-height-base)","occasionFontStyle":"var(--lia-default-message-font-style)","occasionMessageLinkColor":"var(--lia-default-message-link-color)","occasionMessageLinkDecoration":"var(--lia-default-message-link-decoration)","occasionMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","occasionMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","grouphubColor":"#491B62","categoryColor":"#949494","communityColor":"#FFFFFF","productColor":"#949494","__typename":"CoreTypesThemeSettings"},"colors":{"black":"#000000","white":"#FFFFFF","gray100":"#F7F7F7","gray200":"#F7F7F7","gray300":"#E8E8E8","gray400":"#D9D9D9","gray500":"#CCCCCC","gray600":"#949494","gray700":"#707070","gray800":"#545454","gray900":"#333333","dark":"#545454","light":"#F7F7F7","primary":"#0C5C8D","secondary":"#333333","bodyText":"#222222","bodyBg":"#F5F5F5","info":"#1D9CD3","success":"#62C026","warning":"#FFD651","danger":"#C20025","alertSystem":"#FF6600","textMuted":"#707070","highlight":"#FFFCAD","outline":"var(--lia-bs-primary)","custom":["#C20025","#081B85","#009639","#B3C6D7","#7CC0EB","#F29A36"],"__typename":"ColorsThemeSettings"},"divider":{"size":"3px","marginLeft":"4px","marginRight":"4px","borderRadius":"50%","bgColor":"var(--lia-bs-gray-600)","bgColorActive":"var(--lia-bs-gray-600)","__typename":"DividerThemeSettings"},"dropdown":{"fontSize":"var(--lia-bs-font-size-sm)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius-sm)","dividerBg":"var(--lia-bs-gray-300)","itemPaddingY":"5px","itemPaddingX":"20px","headerColor":"var(--lia-bs-gray-700)","__typename":"DropdownThemeSettings"},"email":{"link":{"color":"#0069D4","hoverColor":"#0061c2","decoration":"none","hoverDecoration":"underline","__typename":"EmailLinkSettings"},"border":{"color":"#e4e4e4","__typename":"EmailBorderSettings"},"buttons":{"borderRadiusLg":"5px","paddingXLg":"16px","paddingYLg":"7px","fontWeight":"700","primaryTextColor":"#ffffff","primaryTextHoverColor":"#ffffff","primaryBgColor":"#0069D4","primaryBgHoverColor":"#005cb8","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","__typename":"EmailButtonsSettings"},"panel":{"borderRadius":"5px","borderColor":"#e4e4e4","__typename":"EmailPanelSettings"},"__typename":"EmailThemeSettings"},"emoji":{"skinToneDefault":"#ffcd43","skinToneLight":"#fae3c5","skinToneMediumLight":"#e2cfa5","skinToneMedium":"#daa478","skinToneMediumDark":"#a78058","skinToneDark":"#5e4d43","__typename":"EmojiThemeSettings"},"heading":{"color":"var(--lia-bs-body-color)","fontFamily":"Inter","fontStyle":"NORMAL","fontWeight":"600","h1FontSize":"30px","h2FontSize":"25px","h3FontSize":"20px","h4FontSize":"18px","h5FontSize":"16px","h6FontSize":"16px","lineHeight":"1.2","subHeaderFontSize":"11px","subHeaderFontWeight":"500","h1LetterSpacing":"normal","h2LetterSpacing":"normal","h3LetterSpacing":"normal","h4LetterSpacing":"normal","h5LetterSpacing":"normal","h6LetterSpacing":"normal","subHeaderLetterSpacing":"2px","h1FontWeight":"var(--lia-bs-headings-font-weight)","h2FontWeight":"var(--lia-bs-headings-font-weight)","h3FontWeight":"var(--lia-bs-headings-font-weight)","h4FontWeight":"var(--lia-bs-headings-font-weight)","h5FontWeight":"var(--lia-bs-headings-font-weight)","h6FontWeight":"var(--lia-bs-headings-font-weight)","__typename":"HeadingThemeSettings"},"icons":{"size10":"10px","size12":"12px","size14":"14px","size16":"16px","size20":"20px","size24":"24px","size30":"30px","size40":"40px","size50":"50px","size60":"60px","size80":"80px","size120":"120px","size160":"160px","__typename":"IconsThemeSettings"},"imagePreview":{"bgColor":"var(--lia-bs-gray-900)","titleColor":"var(--lia-bs-white)","controlColor":"var(--lia-bs-white)","controlBgColor":"var(--lia-bs-gray-800)","__typename":"ImagePreviewThemeSettings"},"input":{"borderColor":"var(--lia-bs-gray-600)","disabledColor":"var(--lia-bs-gray-600)","focusBorderColor":"var(--lia-bs-primary)","labelMarginBottom":"10px","btnFontSize":"var(--lia-bs-font-size-sm)","focusBoxShadow":"0 0 0 3px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","checkLabelMarginBottom":"2px","checkboxBorderRadius":"3px","borderRadiusSm":"var(--lia-bs-border-radius-sm)","borderRadius":"var(--lia-bs-border-radius)","borderRadiusLg":"var(--lia-bs-border-radius-lg)","formTextMarginTop":"4px","textAreaBorderRadius":"var(--lia-bs-border-radius)","activeFillColor":"var(--lia-bs-primary)","__typename":"InputThemeSettings"},"loading":{"dotDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.2)","dotLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.5)","barDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.06)","barLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.4)","__typename":"LoadingThemeSettings"},"link":{"color":"var(--lia-bs-primary)","hoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) - 10%))","decoration":"none","hoverDecoration":"underline","__typename":"LinkThemeSettings"},"listGroup":{"itemPaddingY":"15px","itemPaddingX":"15px","borderColor":"var(--lia-bs-gray-300)","__typename":"ListGroupThemeSettings"},"modal":{"contentTextColor":"var(--lia-bs-body-color)","contentBg":"var(--lia-bs-white)","backgroundBg":"var(--lia-bs-black)","smSize":"440px","mdSize":"760px","lgSize":"1080px","backdropOpacity":0.3,"contentBoxShadowXs":"var(--lia-bs-box-shadow-sm)","contentBoxShadow":"var(--lia-bs-box-shadow)","headerFontWeight":"700","__typename":"ModalThemeSettings"},"navbar":{"position":"FIXED","background":{"attachment":null,"clip":null,"color":"var(--lia-bs-white)","imageAssetName":null,"imageLastModified":"0","origin":null,"position":"CENTER_CENTER","repeat":"NO_REPEAT","size":"COVER","__typename":"BackgroundProps"},"backgroundOpacity":0.8,"paddingTop":"15px","paddingBottom":"15px","borderBottom":"1px solid var(--lia-bs-border-color)","boxShadow":"var(--lia-bs-box-shadow-sm)","brandMarginRight":"30px","brandMarginRightSm":"10px","brandLogoHeight":"30px","linkGap":"10px","linkJustifyContent":"flex-start","linkPaddingY":"5px","linkPaddingX":"10px","linkDropdownPaddingY":"9px","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkColor":"var(--lia-bs-body-color)","linkHoverColor":"var(--lia-bs-primary)","linkFontSize":"var(--lia-bs-font-size-sm)","linkFontStyle":"NORMAL","linkFontWeight":"400","linkTextTransform":"NONE","linkLetterSpacing":"normal","linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkBgColor":"transparent","linkBgHoverColor":"transparent","linkBorder":"none","linkBorderHover":"none","linkBoxShadow":"none","linkBoxShadowHover":"none","linkTextBorderBottom":"none","linkTextBorderBottomHover":"none","dropdownPaddingTop":"10px","dropdownPaddingBottom":"15px","dropdownPaddingX":"10px","dropdownMenuOffset":"2px","dropdownDividerMarginTop":"10px","dropdownDividerMarginBottom":"10px","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","controllerIconColor":"var(--lia-bs-body-color)","controllerIconHoverColor":"var(--lia-bs-body-color)","controllerTextColor":"var(--lia-nav-controller-icon-color)","controllerTextHoverColor":"var(--lia-nav-controller-icon-hover-color)","controllerHighlightColor":"hsla(30, 100%, 50%)","controllerHighlightTextColor":"var(--lia-yiq-light)","controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerColor":"var(--lia-nav-controller-icon-color)","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","hamburgerBgColor":"transparent","hamburgerBgHoverColor":"transparent","hamburgerBorder":"none","hamburgerBorderHover":"none","collapseMenuMarginLeft":"20px","collapseMenuDividerBg":"var(--lia-nav-link-color)","collapseMenuDividerOpacity":0.16,"__typename":"NavbarThemeSettings"},"pager":{"textColor":"var(--lia-bs-link-color)","textFontWeight":"var(--lia-font-weight-md)","textFontSize":"var(--lia-bs-font-size-sm)","__typename":"PagerThemeSettings"},"panel":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-bs-border-radius)","borderColor":"var(--lia-bs-border-color)","boxShadow":"none","__typename":"PanelThemeSettings"},"popover":{"arrowHeight":"8px","arrowWidth":"16px","maxWidth":"300px","minWidth":"100px","headerBg":"var(--lia-bs-white)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius)","boxShadow":"0 0.5rem 1rem hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.15)","__typename":"PopoverThemeSettings"},"prism":{"color":"#000000","bgColor":"#f5f2f0","fontFamily":"var(--font-family-monospace)","fontSize":"var(--lia-bs-font-size-base)","fontWeightBold":"var(--lia-bs-font-weight-bold)","fontStyleItalic":"italic","tabSize":2,"highlightColor":"#b3d4fc","commentColor":"#62707e","punctuationColor":"#6f6f6f","namespaceOpacity":"0.7","propColor":"#990055","selectorColor":"#517a00","operatorColor":"#906736","operatorBgColor":"hsla(0, 0%, 100%, 0.5)","keywordColor":"#0076a9","functionColor":"#d3284b","variableColor":"#c14700","__typename":"PrismThemeSettings"},"rte":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":" var(--lia-panel-box-shadow)","customColor1":"#bfedd2","customColor2":"#fbeeb8","customColor3":"#f8cac6","customColor4":"#eccafa","customColor5":"#c2e0f4","customColor6":"#2dc26b","customColor7":"#f1c40f","customColor8":"#e03e2d","customColor9":"#b96ad9","customColor10":"#3598db","customColor11":"#169179","customColor12":"#e67e23","customColor13":"#ba372a","customColor14":"#843fa1","customColor15":"#236fa1","customColor16":"#ecf0f1","customColor17":"#ced4d9","customColor18":"#95a5a6","customColor19":"#7e8c8d","customColor20":"#34495e","customColor21":"#000000","customColor22":"#ffffff","defaultMessageHeaderMarginTop":"14px","defaultMessageHeaderMarginBottom":"10px","defaultMessageItemMarginTop":"0","defaultMessageItemMarginBottom":"10px","diffAddedColor":"hsla(170, 53%, 51%, 0.4)","diffChangedColor":"hsla(43, 97%, 63%, 0.4)","diffNoneColor":"hsla(0, 0%, 80%, 0.4)","diffRemovedColor":"hsla(9, 74%, 47%, 0.4)","specialMessageHeaderMarginTop":"14px","specialMessageHeaderMarginBottom":"10px","specialMessageItemMarginTop":"0","specialMessageItemMarginBottom":"10px","__typename":"RteThemeSettings"},"tags":{"bgColor":"var(--lia-bs-gray-200)","bgHoverColor":"var(--lia-bs-gray-400)","borderRadius":"var(--lia-bs-border-radius-sm)","color":"var(--lia-bs-body-color)","hoverColor":"var(--lia-bs-body-color)","fontWeight":"var(--lia-font-weight-md)","fontSize":"var(--lia-font-size-xxs)","textTransform":"UPPERCASE","letterSpacing":"0.5px","__typename":"TagsThemeSettings"},"toasts":{"borderRadius":"var(--lia-bs-border-radius)","paddingX":"12px","__typename":"ToastsThemeSettings"},"typography":{"fontFamilyBase":"Atkinson Hyperlegible","fontStyleBase":"NORMAL","fontWeightBase":"400","fontWeightLight":"300","fontWeightNormal":"400","fontWeightMd":"500","fontWeightBold":"700","letterSpacingSm":"normal","letterSpacingXs":"normal","lineHeightBase":"1.3","fontSizeBase":"15px","fontSizeXxs":"11px","fontSizeXs":"12px","fontSizeSm":"13px","fontSizeLg":"20px","fontSizeXl":"24px","smallFontSize":"14px","customFonts":[],"__typename":"TypographyThemeSettings"},"unstyledListItem":{"marginBottomSm":"5px","marginBottomMd":"10px","marginBottomLg":"15px","marginBottomXl":"20px","marginBottomXxl":"25px","__typename":"UnstyledListItemThemeSettings"},"yiq":{"light":"#ffffff","dark":"#000000","__typename":"YiqThemeSettings"},"colorLightness":{"primaryDark":0.36,"primaryLight":0.74,"primaryLighter":0.89,"primaryLightest":0.95,"infoDark":0.39,"infoLight":0.72,"infoLighter":0.85,"infoLightest":0.93,"successDark":0.24,"successLight":0.62,"successLighter":0.8,"successLightest":0.91,"warningDark":0.39,"warningLight":0.68,"warningLighter":0.84,"warningLightest":0.93,"dangerDark":0.41,"dangerLight":0.72,"dangerLighter":0.89,"dangerLightest":0.95,"__typename":"ColorLightnessThemeSettings"},"localOverride":false,"__typename":"Theme"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-1744046271000","value":{"title":"Loading..."},"localOverride":false},"CachedAsset:quilt:f5.prod:pages/kbs/TkbMessagePage:board:codeshare-1744511657127":{"__typename":"CachedAsset","id":"quilt:f5.prod:pages/kbs/TkbMessagePage:board:codeshare-1744511657127","value":{"id":"TkbMessagePage","container":{"id":"Common","headerProps":{"backgroundImageProps":null,"backgroundColor":null,"addComponents":null,"removeComponents":["community.widget.bannerWidget"],"componentOrder":null,"__typename":"QuiltContainerSectionProps"},"headerComponentProps":{"community.widget.breadcrumbWidget":{"disableLastCrumbForDesktop":false}},"footerProps":null,"footerComponentProps":null,"items":[{"id":"message-list","layout":"MAIN_SIDE","bgColor":"transparent","showTitle":true,"showDescription":true,"textPosition":"CENTER","textColor":"var(--lia-bs-body-color)","sectionEditLevel":null,"bgImage":null,"disableSpacing":null,"edgeToEdgeDisplay":null,"fullHeight":null,"showBorder":null,"__typename":"MainSideQuiltSection","columnMap":{"main":[{"id":"tkbs.widget.tkbArticleWidget","className":"lia-tkb-container","props":{"contributorListType":"panel","showHelpfulness":false,"showTimestamp":true,"showGuideNavigationSection":true,"showVersion":true,"lazyLoad":false,"editLevel":"CONFIGURE"},"__typename":"QuiltComponent"}],"side":[{"id":"featuredWidgets.widget.featuredContentWidget","className":null,"props":{"instanceId":"featuredWidgets.widget.featuredContentWidget-1702666556326","layoutProps":{"layout":"card","layoutOptions":{"useRepliesCount":false,"useAuthorRank":false,"useTimeToRead":true,"useKudosCount":false,"useViewCount":true,"usePreviewMedia":true,"useBody":false,"useCenteredCardContent":false,"useTags":true,"useTimestamp":false,"useBoardLink":true,"useAuthorLink":false,"useSolvedBadge":true}},"titleSrOnly":false,"showPager":true,"pageSize":3,"lazyLoad":true},"__typename":"QuiltComponent"},{"id":"messages.widget.relatedContentWidget","className":null,"props":{"hideIfEmpty":true,"enablePagination":true,"useTitle":true,"listVariant":{"type":"listGroup"},"pageSize":3,"style":"list","pagerVariant":{"type":"loadMore"},"viewVariant":{"type":"inline","props":{"useRepliesCount":true,"useMedia":true,"useAuthorRank":false,"useNode":true,"useTimeToRead":true,"useSpoilerFreeBody":true,"useKudosCount":true,"useNodeLink":true,"useViewCount":true,"usePreviewMedia":false,"useBody":false,"timeStampType":"postTime","useTags":true,"clampSubjectLines":2,"useBoardIcon":false,"useMessageTimeLink":true,"clampBodyLines":3,"useTextBody":true,"useSolvedBadge":true,"useAvatar":true,"useAuthorLogin":true,"useUnreadCount":true}},"lazyLoad":true,"panelType":"divider"},"__typename":"QuiltComponent"}],"__typename":"MainSideSectionColumns"}}],"__typename":"QuiltContainer"},"__typename":"Quilt","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/EmailVerification-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/common/EmailVerification-1744046271000","value":{"email.verification.title":"Email Verification Required","email.verification.message.update.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. To change your email, visit My Settings.","email.verification.message.resend.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. Resend email."},"localOverride":false},"CachedAsset:text:en_US-pages/kbs/TkbMessagePage-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-pages/kbs/TkbMessagePage-1744046271000","value":{"title":"{contextMessageSubject} | {communityTitle}","errorMissing":"This article cannot be found","name":"TKB Message Page","section.message-list.title":"","archivedMessageTitle":"This Content Has Been Archived","section.erPqcf.title":"","section.erPqcf.description":"","section.message-list.description":""},"localOverride":false},"CachedAsset:quiltWrapper:f5.prod:Common:1744511574646":{"__typename":"CachedAsset","id":"quiltWrapper:f5.prod:Common:1744511574646","value":{"id":"Common","header":{"backgroundImageProps":{"assetName":"header.jpg","backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"LEFT_CENTER","lastModified":"1702932449000","__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"custom.widget.Beta_MetaNav","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"community.widget.navbarWidget","props":{"showUserName":false,"showRegisterLink":true,"style":{"boxShadow":"var(--lia-bs-box-shadow-sm)","linkFontWeight":"700","controllerHighlightColor":"hsla(30, 100%, 50%)","dropdownDividerMarginBottom":"10px","hamburgerBorderHover":"none","linkFontSize":"15px","linkBoxShadowHover":"none","backgroundOpacity":0.4,"controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerBgColor":"transparent","linkTextBorderBottom":"none","hamburgerColor":"var(--lia-nav-controller-icon-color)","brandLogoHeight":"48px","linkLetterSpacing":"normal","linkBgHoverColor":"transparent","collapseMenuDividerOpacity":0.16,"paddingBottom":"10px","dropdownPaddingBottom":"15px","dropdownMenuOffset":"2px","hamburgerBgHoverColor":"transparent","borderBottom":"0","hamburgerBorder":"none","dropdownPaddingX":"10px","brandMarginRightSm":"10px","linkBoxShadow":"none","linkJustifyContent":"center","linkColor":"var(--lia-bs-primary)","collapseMenuDividerBg":"var(--lia-nav-link-color)","dropdownPaddingTop":"10px","controllerHighlightTextColor":"var(--lia-yiq-dark)","background":{"imageAssetName":"","color":"var(--lia-bs-white)","size":"COVER","repeat":"NO_REPEAT","position":"CENTER_CENTER","imageLastModified":""},"linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkHoverColor":"var(--lia-bs-primary)","position":"FIXED","linkBorder":"none","linkTextBorderBottomHover":"2px solid #0C5C8D","brandMarginRight":"30px","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","linkBorderHover":"none","collapseMenuMarginLeft":"20px","linkFontStyle":"NORMAL","linkPaddingX":"10px","paddingTop":"10px","linkPaddingY":"5px","linkTextTransform":"NONE","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkBgColor":"transparent","linkDropdownPaddingY":"9px","controllerIconColor":"#0C5C8D","dropdownDividerMarginTop":"10px","linkGap":"10px","controllerIconHoverColor":"#0C5C8D"},"links":{"sideLinks":[],"mainLinks":[{"children":[{"linkType":"INTERNAL","id":"migrated-link-1","params":{"boardId":"TechnicalForum","categoryId":"Forums"},"routeName":"ForumBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-2","params":{"boardId":"WaterCooler","categoryId":"Forums"},"routeName":"ForumBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-0","params":{"categoryId":"Forums"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-4","params":{"boardId":"codeshare","categoryId":"CrowdSRC"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-5","params":{"boardId":"communityarticles","categoryId":"CrowdSRC"},"routeName":"TkbBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-3","params":{"categoryId":"CrowdSRC"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-7","params":{"boardId":"TechnicalArticles","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"article-series","params":{"boardId":"article-series","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"security-insights","params":{"boardId":"security-insights","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-8","params":{"boardId":"DevCentralNews","categoryId":"Articles"},"routeName":"TkbBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-6","params":{"categoryId":"Articles"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-10","params":{"categoryId":"CommunityGroups"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"migrated-link-11","params":{"categoryId":"F5-Groups"},"routeName":"CategoryPage"}],"linkType":"INTERNAL","id":"migrated-link-9","params":{"categoryId":"GroupsCategory"},"routeName":"CategoryPage"},{"children":[],"linkType":"INTERNAL","id":"migrated-link-12","params":{"boardId":"Events","categoryId":"top"},"routeName":"EventBoardPage"},{"children":[],"linkType":"INTERNAL","id":"migrated-link-13","params":{"boardId":"Suggestions","categoryId":"top"},"routeName":"IdeaBoardPage"},{"children":[],"linkType":"EXTERNAL","id":"Common-external-link","url":"https://community.f5.com/c/how-do-i","target":"SELF"}]},"className":"QuiltComponent_lia-component-edit-mode__lQ9Z6","showSearchIcon":false},"__typename":"QuiltComponent"},{"id":"community.widget.bannerWidget","props":{"backgroundColor":"transparent","visualEffects":{"showBottomBorder":false},"backgroundImageProps":{"backgroundSize":"COVER","backgroundPosition":"CENTER_CENTER","backgroundRepeat":"NO_REPEAT"},"fontColor":"#222222"},"__typename":"QuiltComponent"},{"id":"community.widget.breadcrumbWidget","props":{"backgroundColor":"var(--lia-bs-primary)","linkHighlightColor":"#FFFFFF","visualEffects":{"showBottomBorder":false},"backgroundOpacity":60,"linkTextColor":"#FFFFFF"},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"footer":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"var(--lia-bs-body-color)","items":[{"id":"custom.widget.Beta_Footer","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"custom.widget.Tag_Manager_Helper","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"custom.widget.Consent_Blackbar","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"__typename":"QuiltWrapper","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/ActionFeedback-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/common/ActionFeedback-1744046271000","value":{"joinedGroupHub.title":"Welcome","joinedGroupHub.message":"You are now a member of this group and are subscribed to updates.","groupHubInviteNotFound.title":"Invitation Not Found","groupHubInviteNotFound.message":"Sorry, we could not find your invitation to the group. The owner may have canceled the invite.","groupHubNotFound.title":"Group Not Found","groupHubNotFound.message":"The grouphub you tried to join does not exist. It may have been deleted.","existingGroupHubMember.title":"Already Joined","existingGroupHubMember.message":"You are already a member of this group.","accountLocked.title":"Account Locked","accountLocked.message":"Your account has been locked due to multiple failed attempts. Try again in {lockoutTime} minutes.","editedGroupHub.title":"Changes Saved","editedGroupHub.message":"Your group has been updated.","leftGroupHub.title":"Goodbye","leftGroupHub.message":"You are no longer a member of this group and will not receive future updates.","deletedGroupHub.title":"Deleted","deletedGroupHub.message":"The group has been deleted.","groupHubCreated.title":"Group Created","groupHubCreated.message":"{groupHubName} is ready to use","accountClosed.title":"Account Closed","accountClosed.message":"The account has been closed and you will now be redirected to the homepage","resetTokenExpired.title":"Reset Password Link has Expired","resetTokenExpired.message":"Try resetting your password again","invalidUrl.title":"Invalid URL","invalidUrl.message":"The URL you're using is not recognized. Verify your URL and try again.","accountClosedForUser.title":"Account Closed","accountClosedForUser.message":"{userName}'s account is closed","inviteTokenInvalid.title":"Invitation Invalid","inviteTokenInvalid.message":"Your invitation to the community has been canceled or expired.","inviteTokenError.title":"Invitation Verification Failed","inviteTokenError.message":"The url you are utilizing is not recognized. Verify your URL and try again","pageNotFound.title":"Access Denied","pageNotFound.message":"You do not have access to this area of the community or it doesn't exist","eventAttending.title":"Responded as Attending","eventAttending.message":"You'll be notified when there's new activity and reminded as the event approaches","eventInterested.title":"Responded as Interested","eventInterested.message":"You'll be notified when there's new activity and reminded as the event approaches","eventNotFound.title":"Event Not Found","eventNotFound.message":"The event you tried to respond to does not exist.","redirectToRelatedPage.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.message":"The content you are trying to access is archived","redirectToRelatedPage.message":"The content you are trying to access is archived","relatedUrl.archivalLink.flyoutMessage":"The content you are trying to access is archived View Archived Content"},"localOverride":false},"CachedAsset:component:custom.widget.Beta_MetaNav-en-1744511675806":{"__typename":"CachedAsset","id":"component:custom.widget.Beta_MetaNav-en-1744511675806","value":{"component":{"id":"custom.widget.Beta_MetaNav","template":{"id":"Beta_MetaNav","markupLanguage":"HANDLEBARS","style":null,"texts":null,"defaults":{"config":{"applicablePages":[],"description":"MetaNav menu at the top of every page.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Beta_MetaNav","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"MetaNav menu at the top of every page.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Beta_Footer-en-1744511675806":{"__typename":"CachedAsset","id":"component:custom.widget.Beta_Footer-en-1744511675806","value":{"component":{"id":"custom.widget.Beta_Footer","template":{"id":"Beta_Footer","markupLanguage":"HANDLEBARS","style":null,"texts":null,"defaults":{"config":{"applicablePages":[],"description":"DevCentral´s custom footer.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Beta_Footer","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"DevCentral´s custom footer.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Tag_Manager_Helper-en-1744511675806":{"__typename":"CachedAsset","id":"component:custom.widget.Tag_Manager_Helper-en-1744511675806","value":{"component":{"id":"custom.widget.Tag_Manager_Helper","template":{"id":"Tag_Manager_Helper","markupLanguage":"HANDLEBARS","style":null,"texts":null,"defaults":{"config":{"applicablePages":[],"description":"Helper widget to inject Tag Manager scripts into head element","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Tag_Manager_Helper","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"Helper widget to inject Tag Manager scripts into head element","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Consent_Blackbar-en-1744511675806":{"__typename":"CachedAsset","id":"component:custom.widget.Consent_Blackbar-en-1744511675806","value":{"component":{"id":"custom.widget.Consent_Blackbar","template":{"id":"Consent_Blackbar","markupLanguage":"HTML","style":null,"texts":null,"defaults":{"config":{"applicablePages":[],"description":"","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Consent_Blackbar","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"TEXTHTML","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:text:en_US-components/community/Breadcrumb-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Breadcrumb-1744046271000","value":{"navLabel":"Breadcrumbs","dropdown":"Additional parent page navigation"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBanner-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBanner-1744046271000","value":{"messageMarkedAsSpam":"This post has been marked as spam","messageMarkedAsSpam@board:TKB":"This article has been marked as spam","messageMarkedAsSpam@board:BLOG":"This post has been marked as spam","messageMarkedAsSpam@board:FORUM":"This discussion has been marked as spam","messageMarkedAsSpam@board:OCCASION":"This event has been marked as spam","messageMarkedAsSpam@board:IDEA":"This idea has been marked as spam","manageSpam":"Manage Spam","messageMarkedAsAbuse":"This post has been marked as abuse","messageMarkedAsAbuse@board:TKB":"This article has been marked as abuse","messageMarkedAsAbuse@board:BLOG":"This post has been marked as abuse","messageMarkedAsAbuse@board:FORUM":"This discussion has been marked as abuse","messageMarkedAsAbuse@board:OCCASION":"This event has been marked as abuse","messageMarkedAsAbuse@board:IDEA":"This idea has been marked as abuse","preModCommentAuthorText":"This comment will be published as soon as it is approved","preModCommentModeratorText":"This comment is awaiting moderation","messageMarkedAsOther":"This post has been rejected due to other reasons","messageMarkedAsOther@board:TKB":"This article has been rejected due to other reasons","messageMarkedAsOther@board:BLOG":"This post has been rejected due to other reasons","messageMarkedAsOther@board:FORUM":"This discussion has been rejected due to other reasons","messageMarkedAsOther@board:OCCASION":"This event has been rejected due to other reasons","messageMarkedAsOther@board:IDEA":"This idea has been rejected due to other reasons","messageArchived":"This post was archived on {date}","relatedUrl":"View Related Content","relatedContentText":"Showing related content","archivedContentLink":"View Archived Content"},"localOverride":false},"CachedAsset:text:en_US-components/tkbs/TkbArticleWidget-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/tkbs/TkbArticleWidget-1744046271000","value":{},"localOverride":false},"Category:category:Forums":{"__typename":"Category","id":"category:Forums","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Forum:board:TechnicalForum":{"__typename":"Forum","id":"board:TechnicalForum","forumPolicies":{"__typename":"ForumPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Forum:board:WaterCooler":{"__typename":"Forum","id":"board:WaterCooler","forumPolicies":{"__typename":"ForumPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Articles":{"__typename":"Category","id":"category:Articles","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:TechnicalArticles":{"__typename":"Tkb","id":"board:TechnicalArticles","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:DevCentralNews":{"__typename":"Tkb","id":"board:DevCentralNews","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:GroupsCategory":{"__typename":"Category","id":"category:GroupsCategory","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:F5-Groups":{"__typename":"Category","id":"category:F5-Groups","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:CommunityGroups":{"__typename":"Category","id":"category:CommunityGroups","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Occasion:board:Events":{"__typename":"Occasion","id":"board:Events","boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"occasionPolicies":{"__typename":"OccasionPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Idea:board:Suggestions":{"__typename":"Idea","id":"board:Suggestions","boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"ideaPolicies":{"__typename":"IdeaPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:communityarticles":{"__typename":"Tkb","id":"board:communityarticles","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:security-insights":{"__typename":"Tkb","id":"board:security-insights","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:article-series":{"__typename":"Tkb","id":"board:article-series","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"CachedAsset:text:en_US-components/community/Navbar-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Navbar-1744046271000","value":{"community":"Community Home","inbox":"Inbox","manageContent":"Manage Content","tos":"Terms of Service","forgotPassword":"Forgot Password","themeEditor":"Theme Editor","edit":"Edit Navigation Bar","skipContent":"Skip to content","migrated-link-9":"Groups","migrated-link-7":"Technical Articles","migrated-link-8":"DevCentral News","migrated-link-1":"Technical Forum","migrated-link-10":"Community Groups","migrated-link-2":"Water Cooler","migrated-link-11":"F5 Groups","Common-external-link":"How Do I...?","migrated-link-0":"Forums","article-series":"Article Series","migrated-link-5":"Community Articles","migrated-link-6":"Articles","security-insights":"Security Insights","migrated-link-3":"CrowdSRC","migrated-link-4":"CodeShare","migrated-link-12":"Events","migrated-link-13":"Suggestions"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarHamburgerDropdown-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarHamburgerDropdown-1744046271000","value":{"hamburgerLabel":"Side Menu"},"localOverride":false},"CachedAsset:text:en_US-components/community/BrandLogo-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/BrandLogo-1744046271000","value":{"logoAlt":"Khoros","themeLogoAlt":"Brand Logo"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarTextLinks-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarTextLinks-1744046271000","value":{"more":"More"},"localOverride":false},"CachedAsset:text:en_US-components/authentication/AuthenticationLink-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/authentication/AuthenticationLink-1744046271000","value":{"title.login":"Sign In","title.registration":"Register","title.forgotPassword":"Forgot Password","title.multiAuthLogin":"Sign In"},"localOverride":false},"CachedAsset:text:en_US-components/nodes/NodeLink-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/nodes/NodeLink-1744046271000","value":{"place":"Place {name}"},"localOverride":false},"QueryVariables:TopicReplyList:message:274110:1":{"__typename":"QueryVariables","id":"TopicReplyList:message:274110:1","value":{"id":"message:274110","first":10,"sorts":{"postTime":{"direction":"ASC"}},"repliesFirst":3,"repliesFirstDepthThree":1,"repliesSorts":{"postTime":{"direction":"ASC"}},"useAvatar":true,"useAuthorLogin":true,"useAuthorRank":true,"useBody":true,"useKudosCount":true,"useTimeToRead":false,"useMedia":false,"useReadOnlyIcon":false,"useRepliesCount":true,"useSearchSnippet":false,"useAcceptedSolutionButton":false,"useSolvedBadge":false,"useAttachments":false,"attachmentsFirst":5,"useTags":true,"useNodeAncestors":false,"useUserHoverCard":false,"useNodeHoverCard":false,"useModerationStatus":true,"usePreviewSubjectModal":false,"useMessageStatus":true}},"ROOT_MUTATION":{"__typename":"Mutation"},"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/QueryHandler-1744046271000","value":{"title":"Query Handler"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarDropdownToggle-1744046271000","value":{"ariaLabelClosed":"Press the down arrow to open the menu"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageView/MessageViewStandard-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageView/MessageViewStandard-1744046271000","value":{"anonymous":"Anonymous","author":"{messageAuthorLogin}","authorBy":"{messageAuthorLogin}","board":"{messageBoardTitle}","replyToUser":" to {parentAuthor}","showMoreReplies":"Show More","replyText":"Reply","repliesText":"Replies","markedAsSolved":"Marked as Solved","movedMessagePlaceholder.BLOG":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.TKB":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.FORUM":"{count, plural, =0 {This reply has been} other {These replies have been} }","movedMessagePlaceholder.IDEA":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.OCCASION":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholderUrlText":"moved.","messageStatus":"Status: ","statusChanged":"Status changed: {previousStatus} to {currentStatus}","statusAdded":"Status added: {status}","statusRemoved":"Status removed: {status}","labelExpand":"expand replies","labelCollapse":"collapse replies","unhelpfulReason.reason1":"Content is outdated","unhelpfulReason.reason2":"Article is missing information","unhelpfulReason.reason3":"Content is for a different Product","unhelpfulReason.reason4":"Doesn't match what I was searching for"},"localOverride":false},"CachedAsset:text:en_US-components/messages/ThreadedReplyList-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/ThreadedReplyList-1744046271000","value":{"title":"{count, plural, one{# Reply} other{# Replies}}","title@board:BLOG":"{count, plural, one{# Comment} other{# Comments}}","title@board:TKB":"{count, plural, one{# Comment} other{# Comments}}","title@board:IDEA":"{count, plural, one{# Comment} other{# Comments}}","title@board:OCCASION":"{count, plural, one{# Comment} other{# Comments}}","noRepliesTitle":"No Replies","noRepliesTitle@board:BLOG":"No Comments","noRepliesTitle@board:TKB":"No Comments","noRepliesTitle@board:IDEA":"No Comments","noRepliesTitle@board:OCCASION":"No Comments","noRepliesDescription":"Be the first to reply","noRepliesDescription@board:BLOG":"Be the first to comment","noRepliesDescription@board:TKB":"Be the first to comment","noRepliesDescription@board:IDEA":"Be the first to comment","noRepliesDescription@board:OCCASION":"Be the first to comment","messageReadOnlyAlert:BLOG":"Comments have been turned off for this post","messageReadOnlyAlert:TKB":"Comments have been turned off for this article","messageReadOnlyAlert:IDEA":"Comments have been turned off for this idea","messageReadOnlyAlert:FORUM":"Replies have been turned off for this discussion","messageReadOnlyAlert:OCCASION":"Comments have been turned off for this event"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyCallToAction-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyCallToAction-1744046271000","value":{"leaveReply":"Leave a reply...","leaveReply@board:BLOG@message:root":"Leave a comment...","leaveReply@board:TKB@message:root":"Leave a comment...","leaveReply@board:IDEA@message:root":"Leave a comment...","leaveReply@board:OCCASION@message:root":"Leave a comment...","repliesTurnedOff.FORUM":"Replies are turned off for this topic","repliesTurnedOff.BLOG":"Comments are turned off for this topic","repliesTurnedOff.TKB":"Comments are turned off for this topic","repliesTurnedOff.IDEA":"Comments are turned off for this topic","repliesTurnedOff.OCCASION":"Comments are turned off for this topic","infoText":"Stop poking me!"},"localOverride":false},"User:user:262034":{"__typename":"User","id":"user:262034","uid":262034,"login":"steffen","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2019-11-06T00:12:19.000-08:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/m_assets/avatars/default/avatar-8.svg?time=0"},"rank":{"__ref":"Rank:rank:41"},"entityType":"USER","eventPath":"community:zihoc95639/user:262034"},"ModerationData:moderation_data:302776":{"__typename":"ModerationData","id":"moderation_data:302776","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"TkbReplyMessage:message:302776":{"__typename":"TkbReplyMessage","author":{"__ref":"User:user:262034"},"id":"message:302776","revisionNum":2,"uid":302776,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Tkb:board:codeshare"},"parent":{"__ref":"TkbTopicMessage:message:274110"},"conversation":{"__ref":"Conversation:conversation:274110"},"subject":"Re: FTPS_SSL_ Termination","moderationData":{"__ref":"ModerationData:moderation_data:302776"},"body":"
Hey @bob_ziuchkovsk1 thanks for your FTPS iRule
There are still a few small things that need to be adjusted to make it work.
1. The same variable again \"static::pasv_max_port\" is incorrect it is already in use. 2. To use this iRule on multiple virtual servers it needs a little more dynamic.
# -- REMOVE OLD --\n\n # Vserver IP to advertise for incoming PASV data channel connections\n # NOTE: the commas are NOT a typo. This is the format used by FTP protocol\n set static::pasv_max_port \"10,0,0,46\"\n\n\n # -- ADD (under \"when CLIENTSSL_DATA\" ) --\n\n # uses the known virtual server ip address (client context)\n set static::vserverip [IP::local_addr]\n set static::vserverip_comma [string map {. ,} $static::vserverip]\n\n\n\n # -- CHANGE ( under \"when SERVER_DATA\" ) --\n\n # respoding the virtual server ip address\n set pasv_response \"227 Entering Passive Mode ($static::vserverip_comma,$our_pasv_str)\\r\\n\"
Tested this on Version: 16.1
","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"203","kudosSumWeight":0,"repliesCount":0,"postTime":"2022-10-13T09:57:43.292-07:00","lastPublishTime":"2022-10-13T10:03:28.987-07:00","metrics":{"__typename":"MessageMetrics","views":511},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"TKB_REPLY","eventPath":"category:CrowdSRC/community:zihoc95639board:codeshare/message:274110/message:302776","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"CachedAsset:text:en_US-components/messages/MessageSubject-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageSubject-1744046271000","value":{"noSubject":"(no subject)"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBody-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBody-1744046271000","value":{"showMessageBody":"Show More","mentionsErrorTitle":"{mentionsType, select, board {Board} user {User} message {Message} other {}} No Longer Available","mentionsErrorMessage":"The {mentionsType} you are trying to view has been removed from the community.","videoProcessing":"Video is being processed. Please try again in a few minutes.","bannerTitle":"Video provider requires cookies to play the video. Accept to continue or {url} it directly on the provider's site.","buttonTitle":"Accept","urlText":"watch"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageCustomFields-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageCustomFields-1744046271000","value":{"CustomField.default.label":"Value of {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageRevision-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageRevision-1744046271000","value":{"lastUpdatedDatePublished":"{publishCount, plural, one{Published} other{Updated}} {date}","lastUpdatedDateDraft":"Created {date}","version":"Version {major}.{minor}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyButton-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyButton-1744046271000","value":{"repliesCount":"{count}","title":"Reply","title@board:BLOG@message:root":"Comment","title@board:TKB@message:root":"Comment","title@board:IDEA@message:root":"Comment","title@board:OCCASION@message:root":"Comment"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageAuthorBio-1744046271000","value":{"sendMessage":"Send Message","actionMessage":"Follow this blog board to get notified when there's new activity","coAuthor":"CO-PUBLISHER","contributor":"CONTRIBUTOR","userProfile":"View Profile","iconlink":"Go to {name} {type}"},"localOverride":false},"CachedAsset:text:en_US-components/guides/GuideBottomNavigation-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/guides/GuideBottomNavigation-1744046271000","value":{"nav.label":"Previous/Next Page","nav.previous":"Previous","nav.next":"Next"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserLink-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserLink-1744046271000","value":{"authorName":"View Profile: {author}","anonymous":"Anonymous"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserRank-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserRank-1744046271000","value":{"rankName":"{rankName}","userRank":"Author rank {rankName}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageTime-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageTime-1744046271000","value":{"postTime":"Published: {time}","lastPublishTime":"Last Update: {time}","conversation.lastPostingActivityTime":"Last posting activity time: {time}","conversation.lastPostTime":"Last post time: {time}","moderationData.rejectTime":"Rejected time: {time}"},"localOverride":false},"CachedAsset:text:en_US-components/customComponent/CustomComponent-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/customComponent/CustomComponent-1744046271000","value":{"errorMessage":"Error rendering component id: {customComponentId}","bannerTitle":"Video provider requires cookies to play the video. Accept to continue or {url} it directly on the provider's site.","buttonTitle":"Accept","urlText":"watch"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserAvatar-1744046271000","value":{"altText":"{login}'s avatar","altTextGeneric":"User's avatar"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/ranks/UserRankLabel-1744046271000","value":{"altTitle":"Icon for {rankName} rank"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserRegistrationDate-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserRegistrationDate-1744046271000","value":{"noPrefix":"{date}","withPrefix":"Joined {date}"},"localOverride":false},"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/tags/TagView/TagViewChip-1744046271000","value":{"tagLabelName":"Tag name {tagName}"},"localOverride":false}}}},"page":"/kbs/TkbMessagePage/TkbMessagePage","query":{"boardId":"codeshare","messageSubject":"ftps-ssl-termination","messageId":"274110"},"buildId":"q_bLpq2mflH0BeZigxpj6","runtimeConfig":{"buildInformationVisible":false,"logLevelApp":"info","logLevelMetrics":"info","openTelemetryClientEnabled":false,"openTelemetryConfigName":"f5","openTelemetryServiceVersion":"25.2.0","openTelemetryUniverse":"prod","openTelemetryCollector":"http://localhost:4318","openTelemetryRouteChangeAllowedTime":"5000","apolloDevToolsEnabled":false,"inboxMuteWipFeatureEnabled":false},"isFallback":false,"isExperimentalCompile":false,"dynamicIds":["./components/customComponent/CustomComponent/CustomComponent.tsx","./components/community/Navbar/NavbarWidget.tsx","./components/community/Breadcrumb/BreadcrumbWidget.tsx","./components/tkbs/TkbArticleWidget/TkbArticleWidget.tsx","./components/messages/MessageView/MessageViewStandard/MessageViewStandard.tsx","./components/messages/ThreadedReplyList/ThreadedReplyList.tsx","./components/customComponent/CustomComponentContent/TemplateContent.tsx","../shared/client/components/common/List/UnstyledList/UnstyledList.tsx","./components/messages/MessageView/MessageView.tsx","./components/customComponent/CustomComponentContent/HtmlContent.tsx","./components/customComponent/CustomComponentContent/CustomComponentScripts.tsx","../shared/client/components/common/List/UnwrappedList/UnwrappedList.tsx","./components/tags/TagView/TagView.tsx","./components/tags/TagView/TagViewChip/TagViewChip.tsx"],"appGip":true,"scriptLoader":[]}