Forum Discussion

Rob_78395's avatar
Rob_78395
Icon for Nimbostratus rankNimbostratus
Jan 28, 2013

Identify and pass-through HTTPS using iRules

I am using a single IP to proxy many different domains. I have a VS on this IP for HTTP traffic and no default pool set becasue I'm using this iRule which works great:

 

 

when HTTP_REQUEST {

 

if { [set hostpool [class match -value [HTTP::host] equals host-data-group ]] ne ""}

 

{ pool $hostpool }

 

}

 

 

I configure my pool names to equal the domains I'm proxying

 

 

 

Now I would like to create another a VS on the same IP to pass-through HTTPS traffic to the right pool. The problem - I have to derive the destination pool during the SSL handshake becasue I cannot decrypt clinet nor server (I'm using Standard TCP).

 

 

Can I create a similar iRule which would match the domain in the SSL CONNECT message to the right pool? I think I could then use source address affinity persistence to keep the traffic flowing between clinet/server based on the session.

 

 

Any help is appreciated!

 

7 Replies

  • Rob, I'm pretty sure the client does not specify a domain when using SSL or TLS with the exception of in TLS1.2 if Server Name Indication (SNI) is supported by the client and server. Event there, as you are not terminating the SSL/TLS can inspect the relevant record information? I'm not sure you can. You could always do some research if you think it's worthwhile and your servers and likely clients support SNI by doing some testing and using tcpdump to capture some traffic.
  • I'm not sure it's an option, but if IP addresses are at a premium you could use different ports instead?
  • Hi Steve!

     

     

    Thanks for your help. How can I identify domains resolving to the same IP with different ports? BTW I dont have access to the servers. I am proxying to provide cloud security filters for traffic to registered domains. The domains point the CNAME to my VS.

     

     

    I also tried Route Domains but ran into problems identifying the initial HTTPS clinet request.

     

     

    Rob

     

     

    Rob
  • Ok - been all over Devcentral and I get why I can't do this (this is a great site btw). SNI with inspection is the only way. Thanks for the help.
  • Hey Rob. You're welcome. If it is an option, you could still use different ports; one for each domain. This isn't scalable but it's workable if there are not too many. You'd simply use a wildcard VS and a iRule to select a Pool based on the destination port.
  • A couple of things to consider:

    1. SNI is a method for selecting different client SSL profiles based on the TLS v1.0 server name extension. As the description implies, you need at least one client SSL profile applied to the virtual server (decrypting the traffic). If you can't decrypt the traffic with a client SSL profile, then you cannot use the SNI feature in the client SSL profile.

    2. SNI is an extension to TLS v1.0, so a client that didn't support this extension would be sent the "default" option (client SSL profile certificate or default pool selection in this case).

    Assuming you cannot terminate the SSL, and that you're okay with limitations in 2 above, have a look at Joel Moses' most-excellent SNI iRule on DevCentral (https://devcentral.f5.com/wiki/iRules.TLS-ServerNameIndication.ashx). I've taken the liberty of boiling this iRule down to something that might work for you. The below doesn't require a client SSL profile. Assign a "default" pool to the virtual server, this iRule, and create a string data group. Adjust the static::SERVERNAME_GROUP variable to match your data group's name.

    server name := pool name

    I've stripped out all of the client SSL profile selection stuff and simply added a pool selection based on the returned tls_servername variable and data group match. If the client supports the extension, and the requested server name is in the data group, they'll be pooled accordingly. Otherwise the client will go to the default pool. You might consider using a local web server in the default pool that tells the client to use the name (versus IP address) and a browser that supports TLS v1.0 (and higher).

    
    when RULE_INIT {
     User-defined: server name to pool matching data group
    set static::SERVERNAME_GROUP "my_sni_group"
    }
    when CLIENT_ACCEPTED {
    TCP::collect
    }
    when CLIENT_DATA {
    set detect_handshake 1
    binary scan [TCP::payload] cSS tls_xacttype tls_version tls_recordlen
    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 { [catch {
    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 { ([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
    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") } {
    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 {
    set start [expr {$start + $elen}]
    }
    set x $start
    }
    if { ([info exists tls_servername] ) } {
     If tls_servername exists then the client supports the TLS server name extension.
     Otherwise send traffic to the default pool
    if { [class match $tls_servername equals $static::SERVERNAME_GROUP] } {
    log local0. "Server name match - sending to: [class match -value $tls_servername equals $static::SERVERNAME_GROUP]"
    pool [class match -value $tls_servername equals $static::SERVERNAME_GROUP]
    } else {
     server name doesn't exist in data group - send to default pool
    log local0. "No server name match - using default pool"
    }
    } else {
    log local0. "No server name support - using default pool"
    }
    }
    set detect_handshake 0
    TCP::release
    } fid] } {
    log local0. "Error: probably not supporting TLS server name extension - using default pool"
    set detect_handshake 0
    TCP::release
    }
    } else {
    log local0. "Not a handshake"
    set detect_handshake 0
    TCP::release
    }
    }
    

  • By the way the '&&" above should have been double ampersands, or the 'and' keyword.