Forum Discussion
APM Irule multiple access profile for one virtual server
Hello F5 Team,
i was asking if is possible to create irule to use multiple profile for one virtual, currently i have one access profile for one vs apm . Thank you for your help. Ahmed
- Kevin_StewartEmployee
You cannot, but you can usually work around this with VIP targeting. This is where you have one external VIP that sends traffic to internal VIPs using the virtual command. How you do this depends on how you have services divided on that external VIP. For example, if you have multiple host names all resolving to that one VIP, then the iRule might look like this:
when HTTP_REQUEST { switch [string tolower [HTTP::host]] { "app1.domain.com" { virtual app1_vs } "app2.domain.com" { virtual app2_vs } "app3.domain.com" { virtual app3_vs } "app4.domain.com" { virtual app4_vs } } }
You can technically also do this with different URIs, but it's a lot trickier.
- Kuerten_772Nimbostratus
Thank you kevin for your help As usual :)
- Kevin_StewartEmployee
Maybe a bit simplistic, but perhaps something like this will work:
when HTTP_REQUEST { if { [class match [string tolower [HTTP::uri]] starts_with my_app_dg] } { virtual [class match -value [string tolower [HTTP::uri]] starts_with my_app_dg] } elseif { [HTTP::cookie exists MRHSession] } { if { [class match [ACCESS::session data get -sid [HTTP::cookie value MRHSession] session.server.landinguri] starts_with my_app_dg] } { virtual [class match -value [ACCESS::session data get -sid [HTTP::cookie value MRHSession] session.server.landinguri] starts_with my_app_dg] } } }
where "my_app_dg" is a string-based data group that lists the application URI and corresponding internal VIP. Example:
/app1 := app1-internal /app2 := app2-internal
If the request URI starts with one of the defined app URIs, VIP target to that. Otherwise, if the MRHSession cookie exists, look at the session.server.landinguri session variable to determine what the original path was, look that up in the data group, and VIP target accordingly. I didn't put any logic in the above to account for non-matching URI requests (an else statement), but that should be pretty straight forward. In my testing I created an external 443 LTM VIP with this iRule and 2 internal port 80 APM VIPs (though port doesn't really matter).
- GahanP_31299Nimbostratus
Thanks Kevin
I already had a string based data group so I'll stick with it. The part I hadn't coded was the cookie logic as yet. One thing I found out is that with HTTPS (ssl-client) on the target VS, then for the "final destination" VS it was necessary to remove any further Client-SSL profiles since the 'double HTTPS offload' was breaking the TLS handshake for some reason. As soon as I did this (and set the 'final' VS to HTTP only) the /my.policy redirect was seen to happen as expected (with the APM session cookies set MRHSession etc.)
i'll post any further progress. thanks again
- GahanP_31299Nimbostratus
just loading this iRule (named: virtual_selection), my data group is called "landing_uri"
when HTTP_REQUEST { if { [class match [string tolower [HTTP::uri]] starts_with landing_uri] } { virtual [class match -value [string tolower [HTTP::uri]] starts_with landing_uri] log local0.info "URI = [HTTP::uri] and 1st Virtual Match" } elseif { [HTTP::cookie exists MRHSession] } { if { [class match [ACCESS::session data get -sid [HTTP::cookie value MRHSession] session.server.landinguri] starts_with landing_uri] } { virtual [class match -value [ACCESS::session data get -sid [HTTP::cookie value MRHSession] session.server.landinguri] starts_with landing_uri] } } }
though it seems to be complaining about the logic in the ELSEIF line 6 (I tried wrapping the Cookie name in "" but didn't help)
6: error: [undefined procedure: elseif][elseif { [HTTP::cookie exists MRHSession] }
- Kevin_StewartEmployee
Semantically your code is correct, but the elseif needs to be on the same line as the if's closing bracket.
when HTTP_REQUEST { if { [class match [string tolower [HTTP::uri]] starts_with landing_uri] } { virtual [class match -value [string tolower [HTTP::uri]] starts_with landing_uri] log local0.info "URI = [HTTP::uri] and 1st Virtual Match" } elseif { [HTTP::cookie exists MRHSession] } { if { [class match [ACCESS::session data get -sid [HTTP::cookie value MRHSession] session.server.landinguri] starts_with landing_uri] } { virtual [class match -value [ACCESS::session data get -sid [HTTP::cookie value MRHSession] session.server.landinguri] starts_with landing_uri] } } }
- GahanP_31299Nimbostratus
Thanks Kevin, daft of me to miss that subtle difference!
- GahanP_31299Nimbostratus
just a quick note on this
seems to be working OK though i believe the logic needs to be tweaked to handle the logout URI /vdesk/hangup.php3
one other thing I was thinking about, can the "virtual" command return a VS that exists in the context of an iApp template. From what I have tested this does not seem immediately possible, but there may be a different way to achieve this
e.g. the requested URI is /vdi so the data group would look like
/vdi := /Common/My_VDI.app/My_VDI_apm_https (which is actually the iApp-created APM VS with the port changed to 80 and the ssl-client profile disabled)
anything else in the Datagroup causes the "Virtual selection" iRule to complain. Yet it doesn't seem that the iRule can tap into the Application VS resource as it can with other VS in /Common
- Wolf46_144992NimbostratusHi GahanP, Have you received any feedback with the regards to calling a Virtual server inside an iApp? I have tried various paths but every time the system fails to locate the Virtual server inside the iApp :(.
- GahanP_31299Nimbostratus
Hi Wolf46
whilst on the face of it the VIP targeting seemed to work for APM, on more detailed testing I ran into several other problems; such as portal apps not working (when they did work 'directly' through APM), and also on Full Network Access, the RDP component wouldn't load correctly.
So I have an outstanding request to F5 PS to see if this is a viable approach in a production environment - if they confirm, then depending on cost we might recommend it for the customer. Other options include, having multiple FQDNS each with their unique external IP and DMZ VS address, then covering them all with a wildcard certificate.
Or other fallback option is to have 1 FQDN with 1 Cert and 1 BIG Access Policy Profile ... which is what I wanted to avoid in the first place :-(
I'll keep you posted
- Baron_of_StrathHistoric F5 Account
What you are asking is quite easy to do. I cannot take full credit for it completely. I took the SNI irule published by Joel Moses a few years back and changed it to simply extract the SNI and put that info into the variable called "tls_servername"
If you look at the very bottom, you will see some generic logic to make a virtual call to the value of tls_servername and if that is unsuccessful, to look up the mapping in a datagroup called "Content_Switch_dg" which is simply a mapping of requested hostname to virtualserver name.
when CLIENT_ACCEPTED { We have a clientssl profile attached to this VIP but we need to find an SNI record in the client handshake. To do so, we'll disable SSL processing and collect the initial TCP payload. set default_tls_pool [LB::server pool] set detect_handshake 1 TCP::collect } when CLIENT_DATA { set detect_handshake 1 TCP::collect if { ($detect_handshake) } { If we're in a handshake detection, look for an SSL/TLS header. binary scan [TCP::payload] cSS tls_xacttype tls_version tls_recordlen TLS is the only thing we want to process because it's the only version that allows the servername extension to be present. When we find a supported TLS version, we'll check to make sure we're getting only a Client Hello transaction -- those are the only ones we can pull the servername from prior to connection establishment. switch $tls_version { "769" - "770" - "771" { if { ($tls_xacttype == 22) } { binary scan [TCP::payload] @5c tls_action if { not (($tls_action == 1) && ([TCP::payload length] > $tls_recordlen)) } { set detect_handshake 0 } } } default { set detect_handshake 0 } } if { ($detect_handshake) } { If we made it this far, we're still processing a TLS client hello. Skip the TLS header (43 bytes in) and process the record body. For TLS/1.0 we expect this to contain only the session ID, cipher list, and compression list. All but the cipher list will be null since we're handling a new transaction (client hello) here. We have to determine how far out to parse the initial record so we can find the TLS extensions if they exist. set record_offset 43 binary scan [TCP::payload] @${record_offset}c tls_sessidlen set record_offset [expr {$record_offset + 1 + $tls_sessidlen}] binary scan [TCP::payload] @${record_offset}S tls_ciphlen set record_offset [expr {$record_offset + 2 + $tls_ciphlen}] binary scan [TCP::payload] @${record_offset}c tls_complen set record_offset [expr {$record_offset + 1 + $tls_complen}] If we're in TLS and we've not parsed all the payload in the record at this point, then we have TLS extensions to process. We will detect the TLS extension package and parse each record individually. if { ([TCP::payload length] > $record_offset) } { binary scan [TCP::payload] @${record_offset}S tls_extenlen set record_offset [expr {$record_offset + 2}] binary scan [TCP::payload] @${record_offset}a* tls_extensions Loop through the TLS extension data looking for a type 00 extension record. This is the IANA code for server_name in the TLS transaction. for { set x 0 } { $x < $tls_extenlen } { incr x 4 } { set start [expr {$x}] binary scan $tls_extensions @${start}SS etype elen if { ($etype == "00") } { A servername record is present. Pull this value out of the packet data and save it for later use. We start 9 bytes into the record to bypass type, length, and SNI encoding header (which is itself 5 bytes long), and capture the servername text (minus the header). set grabstart [expr {$start + 9}] set grabend [expr {$elen - 5}] binary scan $tls_extensions @${grabstart}A${grabend} tls_servername set start [expr {$start + $elen}] } else { Bypass all other TLS extensions. set start [expr {$start + $elen}] } set x $start } Check to see whether we got a servername indication from TLS. If so, make the appropriate changes. if { ([info exists tls_servername] ) } { log local0. "Requested host: $tls_servername" if { [catch { virtual $tls_servername } ]} { if { $::DEBUG == 1 } { log local0. "Virtual Server $tls_servername doesn't exist...Searching Datagroup"} if { [class match $tls_servername equals Content_Switch_dg]} { if { $::DEBUG == 1 } { log local0. "Server found in Datagroup" } virtual [class lookup $tls_servername Content_Switch_dg] TCP::release } } else { if { $::DEBUG == 1 } { log local0. "switching to virtual server $tls_servername" } virtual $tls_servername TCP::release } } } else { Servername not found log local0. "Servername could not be determined" } } } }
Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com