Forum Discussion

Slillz's avatar
Slillz
Icon for Nimbostratus rankNimbostratus
Oct 26, 2018

One SSL profile for different pool members

I have a virtual interface that I have configured with an SSL profile to not allow any TSL1.1 or 1.2 traffic. I have an iRule also attached to that virtual interface that sends the user to a different pool according to what URL he uses.The issue is that the client only wants that SSL profile to effect one of the pool members. There are two members in the pool. How can I achieve this?

 

  • By URL, do you mean hostname (ex. ), or path (ex. /foo, /bar)?

     

    If the latter, then no. You'd basically be attempting to change the SSL profile after the SSL handshake and decryption, and you can't see the HTTP URL until after you've decrypted.

     

  • Before you've decrypted the traffic, there's no "URL" to look at. The best you can do is to rely on the Server Name Indication (SNI) value in the client's ClientHello TLS handshake message.

     

    The trick here is that if you want to switch the client SSL profile, you have to do it before the client SSL profile engages, in a layer 4 (TCP) event. And at layer 4, you don't yet have access to any SSL:: commands to help you grab the ClientHello SNI value, so that too has to be done at layer 4...in binary.

     

    There are a ton of versions of this iRule floating around, but I've found it makes code more manageable to shove all of the binary SNI code into a proc and call it remotely. So create a new iRule to use for your external procedures:

     

    Proc rule: library-rule

     

    proc getSNI { payload } {
        set detect_handshake 1
    
        binary scan ${payload} H* orig
        if { [binary scan [TCP::payload] cSS tls_xacttype tls_version tls_recordlen] < 3 } {
            reject
            return
        }
    
         768 SSLv3.0
         769 TLSv1.0
         770 TLSv1.1
         771 TLSv1.2
        switch $tls_version {
            "769" -
            "770" -
            "771" {
                if { ($tls_xacttype == 22) } {
                    binary scan ${payload} @5c tls_action
                    if { not (($tls_action == 1) && ([string length ${payload}] > $tls_recordlen)) } {
                        set detect_handshake 0
                    }
                }
            }
            "768" {
                set detect_handshake 0
            }
            default {
                set detect_handshake 0
            }
        }
    
        if { ($detect_handshake) } {
             skip past the session id
            set record_offset 43
            binary scan ${payload} @${record_offset}c tls_sessidlen
            set record_offset [expr {$record_offset + 1 + $tls_sessidlen}]
    
             skip past the cipher list
            binary scan ${payload} @${record_offset}S tls_ciphlen
            set record_offset [expr {$record_offset + 2 + $tls_ciphlen}]
    
             skip past the compression list
            binary scan ${payload} @${record_offset}c tls_complen
            set record_offset [expr {$record_offset + 1 + $tls_complen}]
    
             check for the existence of ssl extensions
            if { ([string length ${payload}] > $record_offset) } {
                 skip to the start of the first extension
                binary scan ${payload} @${record_offset}S tls_extenlen
                set record_offset [expr {$record_offset + 2}]
                 read all the extensions into a variable
                binary scan ${payload} @${record_offset}a* tls_extensions
    
                 for each extension
                for { set ext_offset 0 } { $ext_offset < $tls_extenlen } { incr ext_offset 4 } {
                    binary scan $tls_extensions @${ext_offset}SS etype elen
                    if { ($etype == 0) } {
                         if it's a servername extension read the servername
                        set grabstart [expr {$ext_offset + 9}]
                        set grabend [expr {$elen - 5}]
                        binary scan $tls_extensions @${grabstart}A${grabend} tls_servername_orig
                        set tls_servername [string tolower ${tls_servername_orig}]
                        set ext_offset [expr {$ext_offset + $elen}]
                        break
                    } else {
                         skip over other extensions
                        set ext_offset [expr {$ext_offset + $elen}]
                    }
                }
            }
        }
    
        if { ![info exists tls_servername] } {
             This isn't TLS so we can't decrypt it anyway
            return "null"
        } else {
            return ${tls_servername}
        }
        TCP::release
    }
    

    And then you can create a very simply iRule, attached to the VIP, to switch between client SSL profiles based on the SNI value in the ClientHello:

     

    when CLIENT_ACCEPTED {
        TCP::collect
    }
    when CLIENT_DATA {
         call the external procedure
        set sni [call library-rule::getSNI [TCP::payload]]    
        log local0. "sni = $sni"
    
        if { ${sni} equals "www2.site.com" } {
            set newprof "SSL::profile www2.site.com-clientssl"
            catch { eval ${newprof} }
        }
    
        TCP::release
    }
    

    The above calls the procedure to get the SNI, then if the SNI equals "www2.site.com", you use an eval command to switch the client SSL profile.

     

    Easy-Peasy 😉

     

    • MartinLin's avatar
      MartinLin
      Icon for Nimbostratus rankNimbostratus

       

      our production F5 use your irule to deal with ssl profile , recently we got customer message that https got default ssl profile not sni name profile.

      Just chrome 116~119 appear the issue. My colleague find chorme://flag a future (TLS 1.3 hybridized Kyber support) enable will trigger the issue, length of TCP::payload short than tls_recordlen

      ```
      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
      }
      }
      ```