Skip to content

Instantly share code, notes, and snippets.

@fishnix
Created August 9, 2013 18:54
Show Gist options
  • Select an option

  • Save fishnix/6196155 to your computer and use it in GitHub Desktop.

Select an option

Save fishnix/6196155 to your computer and use it in GitHub Desktop.
CAS Client F5 iRule
# yale_cas_auth iRule
#
# iRule to act as a CAS client for apps that can't do CAS natively
#
# Since the sideband itself can't do SSL, it requires an
# unencrypted vserver to proxy sideband connections to
# an encrypted validation service.
#
# CAS flow looks like this:
# 1. Browser GET http://foo.yale.edu/someapp
# 2. APP looks for a session, if none, 302 to CAS login
# 3. Browser GET CAS login, post to login form
# https://casserver/cas/login?service=http://foo.yale.edu/someapp
# 4. CAS login succeeds, 302 back to the app w/ticket:
# http://foo.yale.edu/someapp?ticket=xxxxxxxx
# 5. APP validates the ticket by calling CAS validate
# with the ticket and the service
# https://casserver/cas/validate?ticket=xxxx&service=http://foo.yale.edu/someapp
# 6. APP creates a session if ticket validates
#
# E. Camden Fisher
# [email protected]
#
# 20121115 ECF initial revision, from test
# 20121127 ECF remove return on valid session
# set valid_session after ticket validation
# set/remove X-SSO-NETID header for authz
#
# TODO:
# - determine service protocol (http/https)
# - SSO redirect will break for someone who's session expired,
# but has a ticket in the URL for some reason
#
when RULE_INIT {
# Set some static variables
# (once at load time instead of each request)
# enable/disable debug loggging
set static::cas_auth_debug 0
# set the location of CAS
set static::sso_server "my.cas.server.yale.edu"
set static::sso_server_login "/cas/login"
set static::sso_server_validate_ip "XXX.XXX.XXX.XXX"
set static::sso_server_validate_port "18443"
set static::sso_server_validate "/cas/validate"
# set timeout + idle for cas validate
set static::sso_server_validate_timeout 1000
set static::sso_server_validate_idle 1000
# set the name of the session cookie to use
set static::sso_cookie "F5SESSION"
# set the session timeout (reset on every access)
set static::sso_session_timeout 3600
# set the lifetime of the session, never reset, forces login
set static::sso_session_life 28800
# key prefix for table
set static::sso_table_prefix "F5SESS-"
set static::sso_authz_header "X-SSO-NETID"
}
when CLIENT_ACCEPTED {
# setup high speed logging
set hsl [HSL::open -proto UDP -pool dcsunix_syslog_hosts]
}
when HTTP_REQUEST {
set valid_session 0
set sso_ticket_valid 0
set validate_connect_failed 0
set sso_ticket ""
set sso_netid ""
set sso_session_id ""
set url [string tolower [HTTP::host]][HTTP::uri]
set proto "http"
set client_addr [IP::client_addr]
set virtual_name [virtual name]
if { $static::cas_auth_debug == 1 } {
log local0. "Got Connection from [IP::client_addr]"
log local0. "CAS Client: got URL $url"
}
if { [HTTP::header exists $static::sso_authz_header] }{
log local0. "Removing $static::sso_authz_header header [HTTP::header $static::sso_authz_header]... h4x?"
HTTP::header remove $static::sso_authz_header
}
# Check for active session
if { [HTTP::cookie exists $static::sso_cookie] } {
# if a session cookie exists, let's work with it
set sso_session_id [HTTP::cookie $static::sso_cookie]
# lookup the session in our table to see if the session is valid (updates timer too)
if { ($sso_session_id starts_with $static::sso_table_prefix) and [table lookup $sso_session_id] != "" } {
set valid_session 1
set sso_netid [table lookup $sso_session_id]
if { $static::cas_auth_debug == 1 } { log local0. "[IP::client_addr] has a valid session: $sso_session_id netid: $sso_netid" }
}
}
# check for Ticket in the URL
if { ([URI::query $url "ticket"] ne "") and ($valid_session == 0) } {
if { [URI::query $url "ticket"] starts_with "ST-" } {
set sso_ticket [URI::query $url "ticket"]
if { $static::cas_auth_debug == 1 } { log local0. "Found CAS ticket: $sso_ticket" }
# sideband to validate ticket
# GET https://my.cas.server.yale.edu/cas/validate?ticket=xxxxx&service=xxxxxx
# if we can get a connection
if { [catch { connect -timeout 1000 -idle 30 -status conn_status $static::sso_server_validate_ip:$static::sso_server_validate_port } conn_id ] == 0 && $conn_id ne ""} {
if { $static::cas_auth_debug == 1 } { log local0. "Connect returns: $conn_id and conn status: $conn_status" }
set service_url [URI::encode "${proto}://[string tolower [HTTP::host]][HTTP::path]"]
set validate_url "${static::sso_server_validate}?service=${service_url}&ticket=${sso_ticket}"
set send_data "GET ${validate_url} HTTP/1.0\r\n\r\n"
set send_bytes [send -timeout 1000 -status send_status $conn_id $send_data]
if { $static::cas_auth_debug == 1 } { log local0. "Sent $send_bytes with status $send_status" }
set recv_data [string trim [getfield [recv -timeout 1000 -status recv_status $conn_id] "\r\n\r\n" 2]]
if { $static::cas_auth_debug == 1 } { log local0. "RECV'd with status $recv_status and data $recv_data" }
close $conn_id
if { $recv_data ne "" } {
if { $recv_data starts_with "yes" } {
set sso_netid [getfield $recv_data "\n" 2]
# Log netid/client address; see RFC 3164 Section 4.1.1 - "PRI Part" for more info
# http://tools.ietf.org/html/rfc3164#section-4.1.1
HSL::send $hsl "<165> Successfully validated ticket for $sso_netid from [IP::client_addr]\n"
set sso_ticket_valid 1
set valid_session 1
set random_num [expr {int(rand()*1000000)}]
set sso_session_id $static::sso_table_prefix[b64encode [sha256 $client_addr-$virtual_name-$random_num]]
if { $static::cas_auth_debug == 1 } { log local0. "Setting SSO Session cookie to: $sso_session_id" }
# Log session creation; see RFC 3164 Section 4.1.1 - "PRI Part" for more info
# http://tools.ietf.org/html/rfc3164#section-4.1.1
HSL::send $hsl "<165> Setting up SSO new session for [IP::client_addr]\n"
table set $sso_session_id $sso_netid $static::sso_session_timeout $static::sso_session_life
} else { HTTP::respond 403 content "Unauthorized. Bad ticket." }
} else { HTTP::respond 500 content "Failed to connect to validation host." }
# else bail
} else {
if { $static::cas_auth_debug == 1 } { log local0. "Connection could not be established to $static::sso_server_validate_ip:$static::sso_server_validate_port" }
HTTP::respond 500 content "Failed to connect to validation host."
}
}
}
# redirect to CAS if there is no valid session or ticket
if { ($valid_session == 0) and ($sso_ticket eq "") } {
set service_url [URI::encode "${proto}://[string tolower [HTTP::host]][HTTP::uri]"]
set login_url "https://${static::sso_server}${static::sso_server_login}?service=${service_url}"
HTTP::redirect $login_url
} elseif { $valid_session == 1 } {
# Let'em in
if { $static::cas_auth_debug == 1 } { log local0. "Adding $static::sso_authz_header header $sso_netid ..." }
HTTP::header insert $static::sso_authz_header $sso_netid
}
}
when HTTP_RESPONSE {
# If we got a valid SSO ticket, we need to setup a cookie
if { ($sso_ticket_valid == 1) and ($sso_session_id ne "") } {
HTTP::cookie remove $static::sso_cookie
HTTP::cookie insert name $static::sso_cookie value $sso_session_id
}
if { $valid_session == 1 } { HTTP::header insert "X-F5-SSO" $static::sso_cookie }
if {[HTTP::header exists $static::sso_authz_header]}{ HTTP::header remove $static::sso_authz_header }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment