Learn F5 Technologies, Get Answers & Share Community Solutions Join DevCentral

Filter by:
  • Solution
  • Technology
Answers

Match SSL SNI and redirect SSl traffic, without SSL termination

Hi,

I have BIG-IP 11.4 in SSL pass-through mode. There is no SSL offloading or termination, just forwarding SSL traffic to servers in pool.

Now we have request to chosse pool based on URL which we can not see because traffic is SSL encrypted. However, I saw in Wireshark host name of server in TCP header and I suppose that it is SNI (Server Name Idication).

Is it possible to match this filed and choose destination pool based on this value? How should iRule looks like?

Br, Mate

0
Rate this 0

Replies to this 0

placeholder+image

ProxySSL has the limitation that you cannot switch servers midstream. The SSL handshake is between the client and server directly, so forcing a request to a different server would break that. The built-in SNI functions require, at a minimum, a client SSL profile, which you cannot use if you're not terminating the SSL. The better, and perhaps only solution is to capture the servername value in the TCP stream. The following is a minor modification of Joel's TLS-ServerNameIndication iRule in the above referenced wiki article.

when CLIENT_ACCEPTED {
    set default_pool [LB::server pool]
    set detect_handshake 1
    TCP::collect    
}
when CLIENT_DATA {
    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) } {
        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 the servername exists - send to the appropriate pool
                ## you could also use a data group for this

                switch $tls_servername {
                    "test1.domain.com" { pool test1_pool }
                    "test2.domain.com" { pool test2_pool }
                    "test3.domain.com" { pool test3_pool }
                    "test4.domain.com" { pool test4_pool }
                    default { pool default_pool }
                }
            }
        }
    }
    set detect_handshake 0
    TCP::release
}

The only portion above that needs modification is the switch $tls_servername clause. You'll want to define your server names and matching pools accordingly. You could also use a data group instead of the switch clause:

if { [class match [string tolower $tls_servername] equals my_servername_dg] } {
    pool [class match -value [string tolower $tls_servername] equals my_servername_dg] 
} else {
    pool default_pool
}

where the data group might look like this:

ltm data-group internal my_servername_dg {
    records {
        test1.domain.com {
            data test1_pool       
        }
        test2.domain.com {
            data test2_pool
        }
        test3.domain.com {
            data test3_pool
        }
        test4.domain.com {
            data test4_pool
        }
    }
    type string
}

One more interesting thing to keep in mind. The TLS SNI extension is only available when the client negotiates TLS. The servername value won't be in an SSLv3 CLIENTHELLO message. That precludes some older browsers from being able to use this.

2
placeholder+image

I'm not fully familiar with iRules and SSL and I hoped that someone have maybe simpler configuration.

i think decrypting and re-encrypting is pretty easier than manually parsing sni field. also, you can do not only hostname based pool selection but full url.

just my 2 cents.

1
placeholder+image

i believe this does what you want, it is a bit old but still i don't believe there is nice [ssl:sni] available yet

https://devcentral.f5.com/wiki/iRules.TLS-ServerNameIndication.ashx

0
placeholder+image

Yes, I already saw this. This is for chossing SSL profile hich is needed to me.

I'm not fully familiar with iRules and SSL and I hoped that someone have maybe simpler configuration.

Br, Mate Grbavac

0
Comments on this Reply
Comment made 25-Aug-2014 by boneyard 5621
from what i see at 2) it does support pool selection also. yes it is a bit complex but that is mainly due to the fact there is no easy way to get the SNI value.
0
placeholder+image

Yes, I know that, but business request is just to forward SSL untouched to server so re-encryption is not an option.

0
placeholder+image

Terminating SSL on the BIG-IP (and re-encrypting) would be the best option. Why is the business objecting to this?

0
placeholder+image

It is a Financial institution and they want to use SSL end-to-end between client and server, without re-encryption.

0
placeholder+image
placeholder+image

The event CLIENTSSL_HELLO that was introduced in v11.0 can do much of the legwork. Here's the equivalent iRule:

when CLIENTSSL_CLIENTHELLO {

    if { [SSL::extensions exists -type 0] } {

        ## if the servername exists - send to the appropriate pool
        ## you could also use a data group for this     

        set tls_servername [string range [SSL::extensions -type 0] 9 [string length [SSL::extensions -type 0]]]

        switch $tls_servername {
            "test1.domain.com" { pool test1_pool }
            "test2.domain.com" { pool test2_pool }
            "test3.domain.com" { pool test3_pool }
            "test4.domain.com" { pool test4_pool }
            default { pool default_pool }
        }

    }

}
0
Comments on this Reply
Comment made 23-Dec-2014 by nitass 13357
i understand it requires clientssl profile. 01070394:3: CLIENTSSL_CLIENTHELLO event in rule (/Common/qux) requires an associated CLIENTSSL or PERSIST profile on the virtual server (/Common/bar).
0
Comment made 31-Jul-2015 by F.Barth 0
Even when this answer does not solve the problem, it is very usefull. This is the first place where I've found a very short and nice solution to extract the SNI. Thanks for that!
0
placeholder+image

I'm encountering a challenge with CLIENTSSL_CLIENTHELLO. Based on the documentation I assumed that I would be able to issue a "drop" if, say, the hostname in the SSL extension doesn't match a hostname in a data class.

However, it seems that the SSL cert is sent back to the client regardless of using "drop" if there's no match (in the CLIENTSSL_CLIENTHELLO event).

Any idea how to kill the connection (hard) during the CLIENTSSL_CLIENTHELLO event?

0
Comments on this Reply
Comment made 30-Dec-2014 by shaggy 2250
it may be a moot point, but did you also try "reject"? You could try using the "SSL::handshake hold/resume" commands to pause SSL handshake activity while you make a pool decision, and then resume and select a pool if the SNI matches the data group or drop/reject if the SNI doesn't match.
0
Comment made 02-Jan-2015 by Arie 2069
I already tried "reject", but to no avail. Perhaps SSL::handshake will work. However, I'm in the process of upgrading to 11.6. Nitass reported that it's working for him (on 11.6).
0
placeholder+image

However, it seems that the SSL cert is sent back to the client regardless of using "drop" if there's no match (in the CLIENTSSL_CLIENTHELLO event).

it seems okay here. can you post the configuration? what version are you using? mine is 11.6.0.

# configuration

[root@ve11a:Active:In Sync] config # tmsh list ltm virtual bar
ltm virtual bar {
    destination 172.28.24.10:443
    ip-protocol tcp
    mask 255.255.255.255
    pool foo
    profiles {
        clientssl {
            context clientside
        }
        http { }
        tcp { }
    }
    rules {
        qux
    }
    source 0.0.0.0/0
    source-address-translation {
        type automap
    }
    vs-index 2
}
[root@ve11a:Active:In Sync] config # tmsh list ltm pool foo
ltm pool foo {
    members {
        200.200.200.101:80 {
            address 200.200.200.101
        }
    }
}
[root@ve11a:Active:In Sync] config # tmsh list ltm rule qux
ltm rule qux {
    when CLIENT_ACCEPTED {
  log local0. ""
}
when CLIENTSSL_CLIENTHELLO {
  log local0. ""
  drop
}
when CLIENTSSL_HANDSHAKE {
  log local0. ""
}
when HTTP_REQUEST {
  log local0. ""
}
}

# /var/log/ltm

[root@ve11a:Active:In Sync] config # cat /var/log/ltm
Dec 31 10:33:19 ve11a info tmm1[10569]: Rule /Common/qux <CLIENT_ACCEPTED>:
Dec 31 10:33:19 ve11a info tmm1[10569]: Rule /Common/qux <CLIENTSSL_CLIENTHELLO>:

# trace

[root@ve11a:Active:In Sync] config # ssldump -Aed -nni 0.0 port 443 or port 80
New TCP connection #1: 172.28.24.1(44512) <-> 172.28.24.10(443)
1 1  1419993199.6897 (0.0206)  C>S SSLv2 compatible client hello
  Version 3.1
  cipher suites
  TLS_DHE_RSA_WITH_AES_256_CBC_SHA
  TLS_DHE_DSS_WITH_AES_256_CBC_SHA
  TLS_RSA_WITH_AES_256_CBC_SHA
  TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
  TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
  TLS_RSA_WITH_3DES_EDE_CBC_SHA
  SSL2_CK_3DES
  TLS_DHE_RSA_WITH_AES_128_CBC_SHA
  TLS_DHE_DSS_WITH_AES_128_CBC_SHA
  TLS_RSA_WITH_AES_128_CBC_SHA
  SSL2_CK_RC2
  TLS_RSA_WITH_RC4_128_SHA
  TLS_RSA_WITH_RC4_128_MD5
  SSL2_CK_RC4
  TLS_DHE_RSA_WITH_DES_CBC_SHA
  TLS_DHE_DSS_WITH_DES_CBC_SHA
  TLS_RSA_WITH_DES_CBC_SHA
  SSL2_CK_DES
  TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
  TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
  TLS_RSA_EXPORT_WITH_DES40_CBC_SHA
  TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5
  SSL2_CK_RC2_EXPORT40
  TLS_RSA_EXPORT_WITH_RC4_40_MD5
  SSL2_CK_RC4_EXPORT40
  Unknown value 0xff

0
Comments on this Reply
Comment made 02-Jan-2015 by Arie 2069
I'm currently running 11.3, but I'm upgrading to 11.6 (PCI DSS requirement). Perhaps that'll fix it.
0