Technical Forum
Ask questions. Discover Answers.
cancel
Showing results for 
Search instead for 
Did you mean: 

Turn off client auth if uri equals

RiverFish
Altostratus
Altostratus

Customers connect to one IP. They connect with an app, not a browser. They use port 5443 to register (obtain a cert we issue) for the service, and port 443 for the actual service.

register: https://5.5.5.5:5443/register (1-way ssl) (get the cert)

service: https://5.5.5.5/service (2-way ssl) (use the cert)

We have a registration VS and a service VS. Both VS's have their own client ssl profile (no server profile). Below are the differences in the ssl profiles:

0691T000006ApRsQAK.png

I am tasked with getting rid of port 5443 and making it so that customers can both register and hit the service on port 443/https. The majority of the traffic comes in on 443 with the occasional new customer registering first on 5443. The VS's point to different pools. We are running BIG-IP 11.3.0 Build 2806.0 Final.

With that being said, I'd like to make the 2-way ssl profile the default and turn off client auth if uri equals "/register". Here is what I have so far...

when HTTP_REQUEST {
     Turn off client auth for registration requests
    if { [HTTP::uri] contains "/register" } {
      SSL::cert mode ignore
      SSL::renegotiate enable
      SSL::renegotiate
      pool registration-pool  
      return
    } elseif { ([SSL::cert count] > 0) && ([HTTP::uri] contains "/service") } {
     scrub header
      HTTP::header remove chain
      HTTP::header remove client
      HTTP::header remove testCert
      HTTP::header remove ClientCert-Subject
      HTTP::header remove SSLClientCertSubject
      HTTP::header remove SSLClientCertThumbprint
       insert cert subject
      HTTP::header insert SSLClientCertSubject [X509::subject [SSL::cert 0]]
    } else {
      drop
    }
}

Concerns:

  • When to use "return" and "SSL::renegotiate"
  • Security (ssl renegotiate vulnerabilities)
  • Do I need to set variables?

Very grateful for any help on this. Thanks.

19 REPLIES 19

Kevin_Davies_40
Nacreous
Nacreous

Just brainstorming here.... you will have to use a less secure profile until they come in then change the profile and force a SSL::renegotiate if they are not using /register. Something like...

when HTTP_REQUEST {
  if {!([HTTP::uri] eq "/register")} {
    SSL::profile ssl_2way
    SSL::renegotiate
  }
}

But this will make normal connections take longer to establish as a result because its a two step process for them instead of one.

This assumes your application is sending HTTP traffic.

Thanks for your response, Kevin. I believe the "SSL::profile" command cannot be used in the "when HTTP_REQUEST" event, so I have shifted focus to the "SSL::mode ignore" command. We tested my iRule above and the ignore command is not working as hoped. The 2-way ssl profile that is assigned to the Virtual Server is requesting a cert from the client. Followed by a "Warning, No Certificate", then "Fatal, Handshake Failure".

RiverFish
Altostratus
Altostratus

Does anyone have any suggestions? Is setting the 2-way ssl profile to "ignore" going to work? Currently it does not appear to work.

 

Kevin_Davies_40
Nacreous
Nacreous

The problem here is you want to make layer 7 decisions on something that has happened in the past. The SSL connection has already been negotiated. My recommendation is when you make your decision, redirect them to virtual servers which have the SSL setup the way you want it.

when HTTP_REQUEST {
  if {not ([HTTP::uri] eq "/register")} {
    HTTP::redirect "https://[HTTP::host]:4443/"
  }
}

Then on port 4443 setup a virtual server with 2way SSL to the same backend pool.

Kevin_Stewart
F5 Employee
F5 Employee

This sort of thing becomes a bit easier with APM, but here's an iRule that comes close to the same functionality:

when CLIENTSSL_CLIENTCERT {
    if { [SSL::cert count] < 1 } {
         if the client did not present a certificate - fail
        reject
    } else {
         do something with the cert here
    }
    HTTP::release
}
when HTTP_REQUEST {
     non-registration URI space requested and F5AUTH cookie does not exist - prompt for client certificate
    if { not ( [HTTP::uri] equals "/favicon.ico" ) and not ( [HTTP::uri] starts_with "/register" ) and not ( [HTTP::cookie exists F5AUTH] ) } { 
         invalidate SSL and renegotiate
        HTTP::collect
        SSL::session invalidate
        SSL::authenticate always
        SSL::authenticate depth 9
        SSL::cert mode require
        SSL::renegotiate
    } elseif { [HTTP::cookie exists F5AUTH] } {
         F5AUTH cookie exists - send HTTP header data
        if { [table lookup -subtable CERTDATA [HTTP::cookie value F5AUTH]] ne "" } {
            HTTP::header replace X-CLIENT-CERT [table lookup -subtable CERTDATA [HTTP::cookie value F5AUTH]] 
        }
    }
}
when HTTP_REQUEST_SEND {    
    clientside {
        if { ( [SSL::cert count] > 0 ) and not ( [HTTP::cookie exists F5AUTH] ) } {
             generate a GUID
            set uniqueid "_[string range [AES::key 256] 34 end]"

             add cert to local session table
            table add -subtable CERTDATA $uniqueid [X509::subject [SSL::cert 0]]
        }
    }
}
when HTTP_RESPONSE {
     if the uniqueid variable is set - send the F5AUTH cookie to client
    if { [info exists uniqueid] } {
        HTTP::header insert "Set-Cookie" "F5AUTH=$uniqueid; path=/; secure; HTTPOnly"
        unset uniqueid
    }
}

Configure your client SSL profile to ignore client certificates. The above iRule will trigger an SSL renegotiation for any URL that isn't in the /register namespace, and if the F5AUTH cookie does not exist. Once the client has presented a certificate, the F5AUTH cookie is written to the client so that the routine is never triggered again. The iRule also creates a session table entry, based on the unique ID of the F5AUTH cookie, so that you can store certificate data for later requests.

Kevin_Stewart
F5 Employee
F5 Employee

Try this (minor update):

when CLIENTSSL_CLIENTCERT {
    if { [SSL::cert count] < 1 } {
         if the client did not present a certificate - fail
        reject
    } else {
         do something with the cert here
    }
    HTTP::release
}
when CLIENT_ACCEPTED {
    set default_pool [LB::server pool]
}
when HTTP_REQUEST {
     non-registration URI space requested and F5AUTH cookie does not exist - prompt for client certificate
    if { not ( [HTTP::uri] equals "/favicon.ico" ) and not ( [HTTP::uri] starts_with "/register" ) and not ( [HTTP::cookie exists F5AUTH] ) } { 
         invalidate SSL and renegotiate
        HTTP::collect
        SSL::session invalidate
        SSL::authenticate always
        SSL::authenticate depth 9
        SSL::cert mode require
        SSL::renegotiate
    } elseif { [HTTP::cookie exists F5AUTH] } {
         F5AUTH cookie exists - send HTTP header data
        if { [table lookup -subtable CERTDATA [HTTP::cookie value F5AUTH]] ne "" } {
            HTTP::header replace X-CLIENT-CERT [table lookup -subtable CERTDATA [HTTP::cookie value F5AUTH]] 
        }
    }

    if { [HTTP::uri] starts_with "/register" } {
        pool local-pool-2
    } else {
        pool $default_pool
    }
}
when HTTP_REQUEST_SEND {    
    clientside {
        if { ( [SSL::cert count] > 0 ) and not ( [HTTP::cookie exists F5AUTH] ) } {
             generate a GUID
            set uniqueid "_[string range [AES::key 256] 34 end]"

             add cert to local session table
            table add -subtable CERTDATA $uniqueid [X509::subject [SSL::cert 0]]
        }
    }
}
when HTTP_RESPONSE {
     if the uniqueid variable is set - send the F5AUTH cookie to client
    if { [info exists uniqueid] } {
        HTTP::header insert "Set-Cookie" "F5AUTH=$uniqueid; path=/; secure; HTTPOnly"
        unset uniqueid
    }
}

Dumb questions:

1. Whats' the purpose of HTTP::release? What's the harm not adding this?

2. Why should we do SSL::session invalidate when we are doing SSL::renogotiate

3. What happens if we skip add F5AUTH Cookie portions? clientside if SSL::cert count > 1 we insert filed values

Thank you! 

Kevin_Stewart
F5 Employee
F5 Employee

Three things:

 

  1. The HTTP::header insert SSLClientCertSubject string appears to be the biggest issue. Since you're generating the SSL renegotiation in this condition, there's no certificate data yet.

     

  2. The header scrub/insert stuff shouldn't be killing the connection where they are, but they're not going to do what you'd expect since that condition is causing an SSL renegotiation (which exits the event). At what point are you trying to insert a header?

     

  3. If you look at the if { [HTTP::uri] starts_with "/register" } section, you still have "pool local-pool-2" assigned, which works in my environment, but not likely in yours.

     

RiverFish
Altostratus
Altostratus

I just need the client cert subject inserted into the header before the request reaches the servers. Our app uses that cert subject to identify the customer. I have removed all of my scrub/insert commands and the resets have gone away, but now the LTM (servers) are sending " Level: Fatal, Description: Insufficient Security)". After this happens there is a bunch of "ACK, Ignored Unkown Record, ACK, Ignored Unknown Record" back and forth talk. I'm still not seeing the client present its cert.

 

No worries about the local-pool-2, I covered that.

 

Kevin_Stewart
F5 Employee
F5 Employee

Okay, I've made some improvements. I was getting random faults using the table command in HTTP_REQUEST_SEND. Also notice that I've added the registration pool definition at the top as a global variable, and that you can insert multiple certificate values (if you need to).

when RULE_INIT {
     user-defined: registation pool
    set static::REGPOOL "local-pool-2"
}
when CLIENTSSL_CLIENTCERT {
    if { [SSL::cert count] < 1 } {
         if the client did not present a certificate - fail
        reject
    } else {
         set a temporary cert variable here
        set clientcertdata [X509::subject [SSL::cert 0]]

         multiple cert values could be inserted as a list of lists: [list [list name value] [list name value]]
         example: 
        set clientcertdata [list [list "CertSubject" [X509::subject [SSL::cert 0]]] [list "CertIssuer" [X509::issuer [SSL::cert 0]]]]
    }
    HTTP::release
}
when CLIENT_ACCEPTED {
    set default_pool [LB::server pool]
}
when HTTP_REQUEST {
     non-registration URI space requested and F5AUTH cookie does not exist - prompt for client certificate
    if { not ( [HTTP::uri] equals "/favicon.ico" ) and not ( [HTTP::uri] starts_with "/private" ) and not ( [HTTP::cookie exists F5AUTH] ) } { 
         invalidate SSL and renegotiate
        HTTP::collect
        SSL::session invalidate
        SSL::authenticate always
        SSL::authenticate depth 9
        SSL::cert mode require
        SSL::renegotiate
    } elseif { [HTTP::cookie exists F5AUTH] } {
         F5AUTH cookie exists - send HTTP header data
        if { [table lookup -subtable CERTDATA [HTTP::cookie value F5AUTH]] ne "" } {
             insert cert subject          
            HTTP::header replace SSLClientCertSubject [table lookup -subtable CERTDATA [HTTP::cookie value F5AUTH]]

             if choosing to use a list of cert data:
            foreach x [table lookup -subtable CERTDATA [HTTP::cookie value F5AUTH]] {
               HTTP::header replace [lindex $x 0] [lindex $x 1]
            }
        }
    }

    if { [HTTP::uri] starts_with "/private" } {
        pool $static::REGPOOL
    } else {
        pool $default_pool
    }
}
when HTTP_REQUEST_SEND {    
    clientside {
        if { ( [info exists clientcertdata] ) and not ( [HTTP::cookie exists F5AUTH] ) } {
             send first header            
            HTTP::header replace SSLClientCertSubject $clientcertdata

             if choosing to use a list of cert data:
            foreach x $clientcertdata {
               HTTP::header replace [lindex $x 0] [lindex $x 1]
            }
        }
    }
}
when HTTP_RESPONSE {
     if the uniqueid variable is set - send the F5AUTH cookie to client
    if { [info exists clientcertdata] } {
         generate a GUID
        set uniqueid "_[string range [AES::key 256] 34 end]"

         insert the table entry
        table add -subtable CERTDATA $uniqueid $clientcertdata 3600

         send the cookie
        HTTP::header insert "Set-Cookie" "F5AUTH=$uniqueid; path=/; secure; HTTPOnly"

         unset the clientcertdata variable
        unset clientcertdata
    }
}

Kevin_Stewart
F5 Employee
F5 Employee

That's a tough one. This entire model relies on the ability to initiate (and process) an SSL renegotiation. One alternative may simply be to use two separate VIPs, one for regular traffic and one for registration traffic, and switch between them based on the URI.

 

RiverFish
Altostratus
Altostratus

After some serious devcentral digging I was finally able to piece together a working iRule. The key was to set the cert mode to request (not require) within the client ssl profile, and then use the [SSL::verify_result] command in the iRule. Below is the iRule so far, please let me know if you see any potential flaws or security holes or maybe some tweaks to make it more efficient.

 

when CLIENTSSL_CLIENTCERT {
    if {[SSL::cert count] < 1}{set cert_count 0}
    if {[SSL::cert count] > 0}{set cert_count 1}
    set ssl_status_code [SSL::verify_result]
    set ssl_status_desc [X509::verify_cert_error_string [SSL::verify_result]]
    log local0. "$ssl_status_code $ssl_status_desc"
}
when HTTP_REQUEST {
     Reject requests to the service if a cert was not presented
    if {[HTTP::uri] contains "/service"}{
            if {$cert_count == 0}{reject}
             Permit requests to the service if a cert was presented and the cert was verified against the CA assigned to the clientssl profile
             A status code of "0" (0 X509_V_OK) means the cert was verified successfully as defined here: http://www.openssl.org/docs/apps/verify.htmlDIAGNOSTICS
            if {($cert_count == 1) and ($ssl_status_code == 0)}{
                scrub existing headers  
               HTTP::header remove chain
               HTTP::header remove client
               HTTP::header remove testCert
               HTTP::header remove ClientCert-Subject
               HTTP::header remove SSLClientCertSubject
               HTTP::header remove SSLClientCertThumbprint
                insert cert subject
               HTTP::header insert SSLClientCertSubject [X509::subject [SSL::cert 0]]
             }
        Forward registration requests to the registration pool
        Reject all other scenarios
     } elseif {[HTTP::uri] contains "/register"}{pool register-pl}
       else {reject}
}

Zdenda
Cirrus
Cirrus

Hi, I need to revive this topic.

I got request to configure LB to ask for client certificate when client goes to certain path, but NOT to ask in any another case. It means I cannot use "Client certificate - Request", but "Client certificate - Ignore" as default method in ssl profile.

I tried some iRules when example was taken from here and from the last post in this channel.

My current iRule is as follows:

when CLIENTSSL_CLIENTCERT {
    if {[SSL::cert count] &amp;gt; 0} { set cert_count 1 }
    else { set cert_count 0}
    set ssl_status_code [SSL::verify_result]
    set ssl_status_desc [X509::verify_cert_error_string [SSL::verify_result]]
    log local0. &amp;quot;$ssl_status_code $ssl_status_desc, cert_count = $cert_count&amp;quot;
}
when HTTP_REQUEST {
   if { [info exists cert_count] } {
      return
   }
   else {
      set cert_count 0
   }

    Reject requests to the service if a cert was not presented
   if { [HTTP::host] eq &amp;quot;fqdn.com&amp;quot; and [string tolower [HTTP::path]] starts_with &amp;quot;/secure_path&amp;quot; } {
            if {$cert_count == 0}{
               log local0. &amp;quot;1 - RENEGOTIATE cert_count = $cert_count, hostname: [HTTP::host][HTTP::uri]&amp;quot;
               HTTP::collect
               SSL::session invalidate
               SSL::authenticate always
               SSL::authenticate depth 9
               SSL::cert mode require
               SSL::renegotiate
}           elseif {$cert_count == 1 and $ssl_status_code == 0}{
               log local0. &amp;quot;2 - ACCEPT cert_count = $cert_count, \$ssl_status_code = $ssl_status_code, hostname: [HTTP::host][HTTP::uri]&amp;quot;
                scrub existing headers  
               HTTP::header remove chain
               HTTP::header remove client
               HTTP::header remove testCert
               HTTP::header remove ClientCert-Subject
               HTTP::header remove SSLClientCertSubject
               HTTP::header remove SSLClientCertThumbprint
                insert cert subject
               HTTP::header insert SSLClientCertSubject [X509::subject [SSL::cert 0]]
            }
            else {
               HTTP::respond 403 &amp;quot;Test page, you are rejected&amp;quot;
               log local0. &amp;quot;3 - REJECT cert_count = $cert_count, hostname: [HTTP::host][HTTP::uri]&amp;quot;
            }
   }
   else {
      return
   }
}

It is not working because of variable cert_count. Eventhough it has value 1 in CLIENTSSL_CLIENTCERT part, it looks like it does not exists at all in HTTP_REQUEST part. Anyway, I think main reason is that I use Ignore as value of CLient Certificate in SSL profile, so then it looks it is somehow very hard to make user to renegotiate properly and show the cert.

I am surprised all solutions comes with default method "Request" in ssl profile, but this way client gets option to select which client certificate to choose, and that is reasn why we cant use it as basic/default value in profile.

Zdenda
Cirrus
Cirrus

Hmm, silence :-) Is it even possible to apply client cert auth for specific uri and have SSL profile settings to be "Ignore" the client SSl certificate (so LB is not asking for client cert by default)?

 

RiverFish
Altostratus
Altostratus

In my particular scenario the clients didn't use browsers, they used a program similar to SOAP UI so there was no pop up asking the client to select a cert. Other than forcing SSL renegotiation, you could redirect the client to another VIP with say port 444 ( that has SSL mode set to Require in the profile.

 

Zdenda
Cirrus
Cirrus

"you could redirect the client to another VIP with say port 444 ( that has SSL mode set to Require in the profile" ---> yes, we decided to use "plan B", it means use completely different domain and VIP for this service and it makes everything easier and straightforward. So one VIP for anonymous traffic and second VIP for authenticated based on client cert and everything is allright.

 

Thanks anyway 😉

 

prt1969_120570
Nimbostratus
Nimbostratus

I'm assuming plan B would work as well with the same domain and VIP but with different ports and different virtual servers?

 

Yes. You can make a layer 6 (TLS) decision based on layer 3 (IP) or layer 4 (port) data, and without any of the complex iRules.