SUNY Federation with SimpleSAMLphp in HA
SimpleSAMLphp is the ideal choice as a SAML 2.0 IdP for SUNY Federation. As it will be the core authentication mechanism for SUNY services, high-availability and load-balancing are an important consideration. Here is one approach to achieving this with HAProxy and Keepalived.
We intend to have a pair (at least) of Apache servers running identical SimpleSAMLphp installations. These will sit behind a pair of load-balancer machines running HAProxy and Keepalived, the first to balance HTTP traffic to the SAML servers, the second to provide virtual IP failover between the balancers. This forward-facing virtual IP will be stunnel-encryped, and advertised as our SAML Identity Provider (IdP).
__________vip__________
______keepalived______
balance1____balance2
|____\__haproxy__/____|
saml1___________saml2
Senior sysadmin Greg Kuchyt pioneered our use of these technologies for load-balancing and failover of our 389-DS LDAP services in a manner much like this, an approach that has served us well for many years, and which we have used for other services.
Our SAML setup is based on the document collection in the SUNY IDM Wiki, particularly the excellent Installing SimpleSAMLphp for SUNY Federation guide. Suffice it to say that I will not go into any detail on our IdP setup, instead covering only what SimpleSAMLphp changes are necessary to share session data for load-balancing and failover.
Shared Session Data with Memcache
On the SAML servers, in order to pave the way for balancing load across multiple machines, we need to ensure that SimpleSAMLphp session handling is switched over to memcache:
yum install memcached php-pecl-memcache service memcached start chkconfig memcached on
Note that SimpleSAMLphp uses PHP memcache and not the newer PHP memcached, but both use the system memcached daemon.
Firewalls will need to be opened to allow incoming memcache connections on port 11211.
In the SimpleSAMLphp configuration, set the store.type, and then adjust the memcache_store.servers array to taste (see docs). The following will cause all session data to be saved to the memcache servers on both boxes:
config.php:
'store.type' => 'memcache', 'memcache_store.servers' => array( array( array('hostname' => 'saml1.potsdam.edu'), ), array( array('hostname' => 'saml2.potsdam.edu'), ), ),
Load-Balancing with HAProxy
On the balance servers we first install HAProxy. The following configuration uses the least connection algorithm to balance HTTP sessions between the two SAML servers behind them.
/etc/haproxy/haproxy.cfg:
defaults mode http log global option httplog option dontlognull option http-server-close option forwardfor except 127.0.0.0/8 option redispatch retries 3 timeout http-request 10s timeout queue 1m timeout connect 10s timeout client 1m timeout server 1m timeout http-keep-alive 10s timeout check 10s maxconn 3000 listen https balance leastconn bind 127.0.0.1:4430 option httpclose option forwardfor option httplog option httpchk GET / server saml1 saml1.potsdam.edu:80 check inter 5000 downinter 500 server saml2 saml2.potsdam.edu:80 check inter 5000 downinter 500
Note that hostnames are used here for clarity, but it is probably preferable to use IP addresses for server config lines. Also note that we bind our instance to localhost:4430, since we do not intend for public connections to hit this port directly. Instead they will hit SSL on port 443 provided by stunnel (later).
Start haproxy:
service haproxy start
We now have two balancers that will each balance HTTP connections to the SAML servers.
Failover with Keepalived
On the balance servers we next install Keepalived. On balance1, the following configuration establishes a virtual IP address to be maintained between the balance servers.
/etc/keepalived/keepalived.conf:
global_defs { notification_email { devnull@potsdam.edu } notification_email_from devnull@potsdam.edu smtp_server 10.137.110.104 smtp_connect_timeout 30 } vrrp_instance VI_1 { virtual_router_id 1 state MASTER priority 100 interface eth2 smtp_alert authentication { auth_type AH auth_pass SomeKindofPasswordHere! } virtual_ipaddress { 10.137.100.101/24 brd 10.137.100.255 dev eth2 } }
On balance2, we install the same configuration with two key differences:
state BACKUP priority 50
Start keepalived:
service keepalived start
We now have a front-facing virtual IP address 10.137.100.101 providing failover between the balancers. You can view the status of the IP with: ip addr show.
SSL with Stunnel
On the balance servers we next install stunnel to encrypt communication on the virtual IP port 443.
/etc/stunnel/https.conf:
#CAfile = /etc/pki/tls/certs/entrust-chain.crt cert = /etc/pki/tls/certs/saml.potsdam.edu.crt key = /etc/pki/tls/private/saml.potsdam.edu.key [https] # public virtual ip address accept = 10.137.100.101:443 connect = 127.0.0.1:4430 verify = 0
An example self-signed cert:
openssl genrsa -aes256 -out pass.key 2048 openssl rsa -in pass.key -out server.key openssl req -new -key server.key -x509 -out server.crt -days 999
Note that key and cert will need to be copied into the locations referenced above.
Start stunnel with the following, probably in an init script:
stunnel /etc/stunnel/https.conf
With stunnel running, we now have encrypted communication on our virtual IP.
Hacking SimpleSAMLphp for Offloaded SSL
SimpleSAMLphp makes use of internal functions to determine port number, HTTP versus HTTPS, and to build links based on those determinations. Because we offloaded SSL to our balancer, it throws off these functions, with the result that links and such are improperly constructed.
Discussion at https://groups.google.com/forum/#!topic/simplesamlphp/m0aqJoURl7I suggests that there may be simpler ways to do this, eventually, but I settled on the changes represented in the following patch to lib/SimpleSAML/Utilities.php:
--- Utilities.dist.php 2013-04-08 04:44:05.000000000 -0400 +++ Utilities.php 2013-12-14 15:35:04.000000000 -0500 @@ -87,6 +87,8 @@ */ public static function isHTTPS() { +return TRUE; + $url = self::getBaseURL(); $end = strpos($url,'://'); @@ -105,6 +107,8 @@ */ private static function getServerHTTPS() { +return TRUE; + if(!array_key_exists('HTTPS', $_SERVER)) { /* Not an https-request. */ return FALSE; @@ -128,6 +132,8 @@ */ private static function getServerPort() { +return ''; + if (isset($_SERVER["SERVER_PORT"])) { $portnumber = $_SERVER["SERVER_PORT"]; } else {
After applying the patch, we should find that all clicks everywhere remain based on https://saml.potsdam.edu.
Jeff,
This is great information; is it OK to include it on the IDM Wiki with the other SimpleSAML documentation?
Thanks,
Paul Hebert/SUNY ITEC
(716) 878-4832
Definitely, I only caution that we have not yet put it through its full paces just yet.
Jeff,
This is exactly the kind of solution sharing that makes working in IT such an exciting endeavor. With the SUNY community sharing these kinds of solutions there’s really nothing we can’t accomplish. Can we expect to see a presentation on this at STC????
Ken Runyon
SUNY System Administration
(518) 320-1368
Thanks – looks great! The memcached information is especially useful.
I’ll get this setup at New Paltz (early this spring hopefully).