Forum Discussion
Authenticate customer using SSL client certificate or LDAP
1.check incoming request for ssl client certificate
2.if certificate is present: validate user against trusted certificates and ocsp
3.add client cert information to http headers if ssl client authentication is successful or move to LDAP authentication if not successful
4.if no certificate is present or certificate is invalid: use LDAP username and password
5.add LDAP username and password (other client information that can be pulled from LDAP) into http headers
6.send client to a redirect page if both ssl and LDAP authentication fail
Thank you very much,
Rob
14 Replies
- Robert_Decker_2
Nimbostratus
Is it possible to call/use existing IRules within an IRule? I have a client ssl certificate IRule from previous post and the default IRule for LDAP, which both work great. I would like to try the following:
1. If you have a client certificate --> use client certificate IRule
2. If you don't have a client certificate --> use LDAP IRule
I tried to work with the dual authentication rule that was given, however I could not get it to work (I am a novice at both programming and editing IRules).
Thank you very much for your help,
Rob - Colin_Walker_12Historic F5 AccountThere is currently no way to nest rules, or to call one rule from another.
You can, however, assign multiple rules to a VIP, or pass the request from one VIP to another with the rule you want to run.
You may want to look into the event disable command Click here, this would allow you to control at a more granular level what parts of the rule are evaluated based on certain criteria, if you do end up combining the statements into a single rule.
-Colin - Not sure if this is what Colin was getting at or not, but you could assign multiple rules to a vip and pass variables between them. This way you can "simulate" calling multiple iRules using conditions.
Here's some pseudo code for the three iRules.**** rule check_for_client_cert **** when CLIENTSSL_CLIENTCERT { set has_client_cert 1 } **** rule client_cert_rule **** when HTTP_REQUEST { if { [info exists has_client_cert] } { code goes here } } **** rule ldap_rule **** when HTTP_REQUEST { if { ! [info exists has_client_cert] } { code goes here. } }
In this code, if the CLIENTSSL_CLIENTCERT event is raises, then the variable has_client_cert is set. Then in the HTTP_REQUEST events, you can check whether the variable has been set. The "info exists" command is a builtin TCL command to check whether a variable has been defined.
Hope this helps...
-Joe - Robert_Decker_2
Nimbostratus
Thank you for the help Joe and Colin. I really appreciate your patience. I have tried to combine rules using Joe's suggestion. I tried to combine the existing sys_auth_ldap rule with an ssl client cert rule from previous posts. Below is my Irule:
when CLIENT_ACCEPTED {
set tmm_auth_http_collect_count 0
array set tmm_auth_http_sids {ldap -1}
}
when CLIENTSSL_CLIENTCERT {
set $has_client_cert 1
}
when CLIENTSSL_HANDSHAKE {
set cur [SSL::sessionid]
set ask [session lookup ssl $cur]
if { $ask eq "" } { session add ssl [SSL::sessionid] [SSL::cert 0] }
}
when HTTP_REQUEST {
if { [info exists has_client_cert] } {
set id [SSL::sessionid]
set the_cert [session lookup ssl $id]
if { $the_cert != ""} {
HTTP::header insert SSLClientCertSubject [X509::subject $the_cert]
HTTP::header insert SSLClientCertIssuer [X509::issuer $the_cert]
HTTP::header insert SSLClientCertValidFrom [X509::not_valid_before $the_cert]
HTTP::header insert SSLClientCertValidUntil [X509::not_valid_after $the_cert]
HTTP::header insert SSLClientCert [b64encode $the_cert]
}
}
if { ! [info exists has_client_cert] } {
set tmm_auth_sid $tmm_auth_http_sids(ldap)
if {$tmm_auth_sid == -1} {
set tmm_auth_sid [AUTH::start pam default_ldap]
array set tmm_auth_http_sids [list ldap $tmm_auth_sid]
}
AUTH::username_credential $tmm_auth_sid [HTTP::username]
AUTH::password_credential $tmm_auth_sid [HTTP::password]
AUTH::authenticate $tmm_auth_sid
if {$tmm_auth_http_collect_count == 0} {
HTTP::collect
set tmm_auth_http_successes 0
}
incr tmm_auth_http_collect_count
}
}
when AUTH_SUCCESS {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
incr tmm_auth_http_successes
if {[info exists tmm_auth_http_sufficient_successes]} {
if {$tmm_auth_http_successes >=
$tmm_auth_http_sufficient_successes} {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::release
} else {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
}
} else {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::release
}
}
}
}
when AUTH_FAILURE {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
if {[llength [array names tmm_auth_http_sids]] > 1} {
if {[info exists tmm_auth_http_sufficient_successes]} {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
} else {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
} else {
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
}
}
when AUTH_WANTCREDENTIAL {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
if {[llength [array names tmm_auth_http_sids]] > 1} {
if {[info exists tmm_auth_http_sufficient_successes]} {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
} else {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
} else {
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
}
}
when AUTH_ERROR {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
if {[llength [array names tmm_auth_http_sids]] > 1} {
if {[info exists tmm_auth_http_sufficient_successes]} {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
} else {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
} else {
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
}
}
The Irule seems to work for the LDAP portion, however it doesn't seem like it can check the certificate. I check the local traffic log and see the following:
TCL error: Rule op CLIENTSSL_CLIENTCERT - cant read has_client_cert: no such variable while executing set $has_client_cert 1
Am I missing something when setting or naming variable? Any help would be greatly appreciated.
Thanks again,
Rob - Robert_Decker_2
Nimbostratus
Thank you for the help Joe. Do you know what I am doing wrong as far as the SSL function is concerned? Every time I use the rule it goes right to LDAP. I have put a logging statement on each piece to track the usage and I don't see a message for the section under "info exists has_client_cert".
Thanks again,
Rob - Did you put a log statement inside the CLIENTSSL_CLIENTCERT event where you were setting the has_client_cert variable to make sure that the variable is getting set. If the info exists command returns 0 then that means the variable is undefined.
-Joe - Robert_Decker_2
Nimbostratus
Thank you both for your help! I think I am slowly starting to understand the global variable thing. It seems like any certificate will pass the ssl portion, even if it is not supposed to. Other than that, it seems to be doing what I would like it to at this point. First, I am prompted for ssl certificate. I can then cancel out and use LDAP. Could you guys point out what I am doing wrong with regards to the SSL certificate? In addition, how would I redirect a failed LDAP connection to a custom web page? Currently, IE displays a blank page when I fail the LDAP connection.
Thanks again,
Rob
when CLIENT_ACCEPTED {
set tmm_auth_http_collect_count 0
array set tmm_auth_http_sids {ldap -1}
}
when CLIENTSSL_HANDSHAKE {
set cur [SSL::sessionid]
set ask [session lookup ssl $cur]
log local0. "ask equals $ask"
if { $ask eq "" } { session add ssl [SSL::sessionid] [SSL::cert 0] }
log local0. "ask equals $ask"}
when HTTP_REQUEST {
set id [SSL::sessionid]
set the_cert [session lookup ssl $id]
if { $the_cert != ""} {
HTTP::header insert SSLClientCertSubject [X509::subject $the_cert]
HTTP::header insert SSLClientCertIssuer [X509::issuer $the_cert]
HTTP::header insert SSLClientCertValidFrom [X509::not_valid_before $the_cert]
HTTP::header insert SSLClientCertValidUntil [X509::not_valid_after $the_cert]
HTTP::header insert SSLClientCert [b64encode $the_cert]
}
elseif { ! [info exists has_client_cert] } {
set tmm_auth_sid $tmm_auth_http_sids(ldap)
if {$tmm_auth_sid == -1} {
set tmm_auth_sid [AUTH::start pam default_ldap]
array set tmm_auth_http_sids [list ldap $tmm_auth_sid]
}
AUTH::username_credential $tmm_auth_sid [HTTP::username]
AUTH::password_credential $tmm_auth_sid [HTTP::password]
AUTH::authenticate $tmm_auth_sid
if {$tmm_auth_http_collect_count == 0} {
HTTP::collect
set tmm_auth_http_successes 0
}
incr tmm_auth_http_collect_count
}
}
when AUTH_SUCCESS {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
incr tmm_auth_http_successes
if {[info exists tmm_auth_http_sufficient_successes]} {
if {$tmm_auth_http_successes >=
$tmm_auth_http_sufficient_successes} {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::release
} else {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
}
} else {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::release
}
}
}
}
when AUTH_FAILURE {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
if {[llength [array names tmm_auth_http_sids]] > 1} {
if {[info exists tmm_auth_http_sufficient_successes]} {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
} else {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
} else {
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
}
}
when AUTH_WANTCREDENTIAL {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
if {[llength [array names tmm_auth_http_sids]] > 1} {
if {[info exists tmm_auth_http_sufficient_successes]} {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
} else {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
} else {
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
}
}
when AUTH_ERROR {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
if {[llength [array names tmm_auth_http_sids]] > 1} {
if {[info exists tmm_auth_http_sufficient_successes]} {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
} else {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
} else {
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
}
} - Robert_Decker_2
Nimbostratus
Possible problem-- I need to set the client profile to "request cert" in order for the LDAP to work. It seems like any cert will work with this setting. Could it be possible that the user provides an invalid certificate and is still able to get the page because the "request" setting is used?
Thank you,
Rob - unRuleY_95363Historic F5 AccountYou could add some extra logic to your rule that verifies the cert is from a legitamate issuer or other criteria.
- Robert_Decker_2
Nimbostratus
Thank you for all the help! I've made some changes and they seem to be working. The Irule will begin by prompting for a client cert. It will complete the request if the cert is valid or redirect to another website if not. You can cancel out of the client cert portion and be prompted for a username and password. The connection will complete if you have valid credentials or redirect to yet another website if you fail. Could someone look this over to see if I am making any rookie mistakes(I am a programming newbie). Thank you in advance.
when CLIENT_ACCEPTED {
set tmm_auth_http_collect_count 0
array set tmm_auth_http_sids {ldap -1}
}
when CLIENTSSL_CLIENTCERT {
set ssl_cert [SSL::cert 0]
set ssl_errstr [X509::verify_cert_error_string [SSL::verify_result]]
set ssl_stuff [list $ssl_cert $ssl_errstr]
session add ssl [SSL::sessionid] $ssl_stuff 180
}
when HTTP_REQUEST {
set ssl_stuff2 [session lookup ssl [SSL::sessionid]]
set ssl_cert2 [lindex $ssl_stuff2 0]
set ssl_errstr2 [lindex $ssl_stuff2 1]
if { $ssl_stuff2 != ""} {
if { $ssl_errstr2 eq "ok"} {
HTTP::header insert SSLClientCertStatus $ssl_errstr2
HTTP::header insert SSLClientCertValidFrom [X509::not_valid_before $ssl_cert2]
HTTP::header insert SSLClientCertValidUtil [X509::not_valid_after $ssl_cert2]
HTTP::header insert SSLClientCertSubject [X509::subject $ssl_cert2]
HTTP::header insert SSLClientCertIssuer [X509::issuer $ssl_cert2]
}
if { $ssl_errstr2 != "ok"} {
HTTP::redirect "http://x.x.x.x"
}
}
if { $ssl_stuff2 eq ""} {
set tmm_auth_sid $tmm_auth_http_sids(ldap)
if {$tmm_auth_sid == -1} {
set tmm_auth_sid [AUTH::start pam default_ldap]
array set tmm_auth_http_sids [list ldap $tmm_auth_sid]
}
AUTH::username_credential $tmm_auth_sid [HTTP::username]
AUTH::password_credential $tmm_auth_sid [HTTP::password]
AUTH::authenticate $tmm_auth_sid
if {$tmm_auth_http_collect_count == 0} {
HTTP::collect
set tmm_auth_http_successes 0
}
incr tmm_auth_http_collect_count
}
}
when AUTH_SUCCESS {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
incr tmm_auth_http_successes
if {[info exists tmm_auth_http_sufficient_successes]} {
if {$tmm_auth_http_successes >=
$tmm_auth_http_sufficient_successes} {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::release
} else {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
}
} else {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::release
}
}
}
}
when AUTH_FAILURE {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
if {[llength [array names tmm_auth_http_sids]] > 1} {
if {[info exists tmm_auth_http_sufficient_successes]} {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
} else {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
} else {
set tmm_auth_http_collect_count 0
HTTP::respond 302 Location "http://y.y.y.y"
}
}
}
when AUTH_WANTCREDENTIAL {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
if {[llength [array names tmm_auth_http_sids]] > 1} {
if {[info exists tmm_auth_http_sufficient_successes]} {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
} else {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
} else {
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
}
}
when AUTH_ERROR {
if {$tmm_auth_http_sids(ldap) eq [AUTH::last_event_session_id]} {
if {[llength [array names tmm_auth_http_sids]] > 1} {
if {[info exists tmm_auth_http_sufficient_successes]} {
incr tmm_auth_http_collect_count -1
if {$tmm_auth_http_collect_count == 0} {
HTTP::respond 401
}
} else {
foreach {type sid} [array get tmm_auth_http_sids] {
if {$type ne "ldap" && $sid ne -1} {
AUTH::abort $sid
array set tmm_auth_http_sids [list $type -1]
}
}
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
} else {
set tmm_auth_http_collect_count 0
HTTP::respond 401
}
}
}
Thank you again,
Rob
Help guide the future of your DevCentral Community!
What tools do you use to collaborate? (1min - anonymous)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