I manage an IT infrastructure for a French makerspace with a large range of internal and publics tools.
Some of them doesn’t provide any authentication system and need to be accessible on internet and be protected with password.
Our architecture support different web technologies (java, php, nodejs), repartited in different LXC container, with an HAPROXY in front
as reverse SSL and router. It's a very classic implementation.  We have LDAP to manage users and groups.

The goal was to implement an authentication system in front of some tools and managed in a single point.
Haproxy doesn’t provide LDAP connector and we are looking for something lighter and more flexible than a full SSO system, 
despite less functionality and less granularity in term of access...

If by curiosity, you have search a way to link HAPROXY and LDAP, you have probably noticed than results on Google (or QWANT :-) ) are not very positive... 
This work shows a way, probably not perfect, but probably enough in term of functionalities and security for a makerspace. And good news, it's works! (YES)

To be perfectly fair, it's more a kind of hack, than a properly authentication system.
So, if you decide to implement this fonctionnality, I'm not responsable about any implementation problem or security issue.


The concept:
It's very basic: apache with mod_LDAP as authentication system in backend and route the requests with ACL in ha-proxy.

Problem:
- Ok, but all my requests will be routed on this backend in loop?
- How manage groups?
- How keep HAPROXY as LoadBalancer?
- What about the security of your system?

To start, a scheme is better than a long talk:



No, a long talk now:

A user requests an access to a protected area (like mywebsite.com/admin), haproxy catch with ACL this query and add a header on it.
This query is routed on the authentication backend (apache) and caught with the new header by Apache(vhost), who show the login box for user/pass.
If the authentication is valid, Apache return a php page, hidden through a rewrite rule with a specific parameter (mywebsite.com/admin --> mywebsite.com/index.php?unlock=admin).
This webpage set a cookie and reload (html header reloads). So, the request is reloaded from the start (browser) and arrive again on haproxy with the specific cookie! 
This cookie is caught by ACL and finally, the request is routed on the protected area.

This system supports multiple LDAP groups, websites and urls.


For this demonstration case, we have three grades: User, Moderator and admin. For each grade, we set a secret Key. This key will allow the access to the protected area.

Config haproxy:

  (.....)
  #  SecretKeyAdmin = ADMIN
  #  SecretKeyMod = MODERATOR  
  #  SecretKeyUser = USER 

  # Haproxy front-end

  # DOMAIN Example
  acl host_ldap_auth01 hdr(host) -i beer.fresh.pub                  # Match a domain  
  acl check_cookie01 hdr_sub(cookie) Auth-ldap=SecretKeyUser        # Check if a cookie "Auth-ldap" exist with "SecretKeyUser" as value
  http-request add-header X-LDAPLEVEL "USER" if host_ldap_auth01 # Add a specific header ( in this case, you need to be registered as user)
  use_backend ldap_auth if host_ldap_auth01 !check_cookie01         # Route to the authentication Backend


  # PATH Examples:

  acl auth_regpath_admin02 path_reg /admin                             # Match the path /admin
  acl check_cookie02 hdr_sub(cookie) Auth-ldap=SecretKeyAdmin          # Require Admin key
  http-request add-header X-LDAPLEVEL "ADMIN" if auth_regpath_admin02
  use_backend ldap_auth if auth_regpath_admin02 !check_cookie02

  acl auth_regpath_admin03 path_reg /moderator
  acl check_cookie03 hdr_sub(cookie) Auth-ldap=SecretKeyMod || Auth-ldap=SecretKeyAdmin # Admin is also valid 
  http-request add-header X-LDAPLEVEL "MODERATOR" if auth_regpath_admin03
  use_backend ldap_auth if auth_regpath_admin03 !check_cookie03
  (.....)

  default_backend webserver                                       # If nothing match, use the backend by default.
                                                                  # Can be a specific backend as well

  # Haproxy back-end
backend webserver
  mode http
  server webserver 172.16.0.2:80 check

backend ldap_auth
  mode http
  server auth_ldap 127.0.0.1:82

As descripted previously, we need now to catch the header "Auth-ldap=SecretKey..." with apache.
This functionality has been implemented from apache2.4, in the core. See: http://httpd.apache.org/docs/2.4/expr.html

In my case, haproxy and apache are on the same server. Port 443 and 80 are already bind with by Haproxy and for a more
clear configuration we prefer use the port 82 on localhost. You need to configure your Apache to accept this port.
Never bind this port on your public network interface !

My Vhost is set by default (no servername), to accept all query routed by Haproxy.
And the root directory uses the value by default (/var/www/html/).
This configuration is interesting to limit the changes required in case of new website. 
If your rules are general enough, the only change to apply will be on HAPROXY.


<VirtualHost 127.0.0.1:82>

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

	# Rewrite module required
        RewriteEngine On

        # Check a HTTP header for a list of values
        <If "%{HTTP:X-LDAPLEVEL} in {'USER'}">
 		# LDAP authen
                AuthName "LDAP USER authentication"
                AuthBasicProvider ldap
                AuthType Basic
                AuthLDAPURL ldap://564.246.89.896:3695/ou=beer,dc=fresh,dc=pub
		# Required ldap group 
                Require ldap-group cn=users,ou=groups,dc=fresh,dc=pub
                AuthLDAPGroupAttributeIsDN off
                AuthLDAPGroupAttribute memberUid

		# Rewrite url
                RewriteCond %{REQUEST_URI} !^/index.php # Avoid loop
                RewriteRule    "^/"  "/index.php?level=USER" [PT]
        </If>

        <If "%{HTTP:X-LDAPLEVEL} in {'ADMIN'}">
                AuthName "LDAP HATLAB INFRA authentication"
                AuthBasicProvider ldap
                AuthType Basic
                AuthLDAPURL ldap://564.246.89.896:3695/ou=beer,dc=fresh,dc=pub
                Require ldap-group cn=admin,ou=groups,dc=fresh,dc=pub
                AuthLDAPGroupAttributeIsDN off
                AuthLDAPGroupAttribute memberUid
                RewriteCond %{REQUEST_URI} !^/index.php
                RewriteRule    "^/"  "/index.php?level=ADMIN" [PT]
        </If>

        <If "%{HTTP:X-LDAPLEVEL} in {'MODERATOR'}">
                AuthName "LDAP Moderator Authentication"
                AuthBasicProvider ldap
                AuthType Basic
                AuthLDAPURL ldap://564.246.89.896:3695/ou=beer,dc=fresh,dc=pub
                Require ldap-group cn=moderator,ou=groups,dc=fresh,dc=pub
		Require ldap-group cn=admin,ou=groups,dc=fresh,dc=pub
                AuthLDAPGroupAttributeIsDN off
                AuthLDAPGroupAttribute memberUid
                RewriteCond %{REQUEST_URI} !^/index.php
                RewriteRule    "^/"  "/index.php?level=MODERATOR" [PT]
        </If>

</VirtualHost>


Rewrite rules:
	"""""
	RewriteCond %{REQUEST_URI} !^/index.php # Avoid loop
	RewriteRule    "^/"  "/index.php?level=USER" [PT]
	"""""


All our requests will be rewrite as "/index.php?level=USER".
If the request is "http://beer.fresh.pub", the user will see this domain in his web-browser, but the real request will be "http://beer.fresh.pub/index.php?level=USER" on 
the authentication backend. Whatever the request (http://beer.fresh.pub/amber/), it's rewrite as "http://beer.fresh.pub/index.php?level=USER".
Index.php is the webpage used to show a validation message and set the cookie.


What’s the trick?
If the authentication is valid, apache return our php page, with two important things:
- The cookie: setcookie("Auth-ldap", "SecretKeyUser", time()+3600, "/", $domain);
- Reload: <meta http-equiv="refresh" content="5">

After five second, the request will be reloaded by the web-browser, and because the rewrite doesn't change the URL,
finally the request is routed by haproxy with the cookie on the good backend.



PHP page:


<?php
$domain=$_SERVER['HTTP_HOST'];
if (htmlspecialchars($_GET["level"]) == "USER") {
        setcookie("Auth-ldap", "SecretKeyUser", time()+3600, "/", $domain);
}
elseif (htmlspecialchars($_GET["level"]) == "MODERATOR") {
        setcookie("Auth-ldap", "SecretKeyMod", time()+3600, "/", $domain);
}
elseif (htmlspecialchars($_GET["level"]) == "ADMIN") {
        setcookie("Auth-ldap", "SecretKeyAdmin", time()+3600, "/", $domain);
}
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Authentification</title>
    <meta http-equiv="refresh" content="5">
</head>
<body>

WELCOME ! You will be redirected in 5s...

</body>

</html>

# Ok and now ... about the security ?
The main issue is the key visible in the cookie. A typical session replay or just a manual access to the secretkey (web-browser inception), 
can compromise the security. I'm looking for a way to fix this issue (ex: dynamic key).