Skip to content

Using nginx sso with haproxy and SPOE

mjbnz edited this page Jun 22, 2020 · 8 revisions
Using:

Docker containers

Two docker containers are used. One for nginx-sso itself, and the other is an SPOE agent for haproxy that can run the python script.

haproxy-spoe-server
mkdir -p /srv/spoe-server
docker run                               \
    --name=spoe-server                   \
    -p 172.17.0.1:12345:12345            \
    --restart=unless-stopped             \
    --volume="/srv/spoe-server:/data"    \
    -d mjbnz/haproxy-spoe-server:latest
nginx-sso
mkdir -p /srv/nginx-sso
docker run                             \
    --name=nginx-sso                   \
    -p 172.17.0.1:8082:8082            \
    --restart=unless-stopped           \
    --volume="/srv/nginx-sso:/data"    \
    -d luzifer/nginx-sso:latest

spoe-server python script

A python script is used as the glue between haproxy-spoe-server and nginx-sso. After deploying the haproxy-spoe-server docker container, replace the content of spoe_python.py in the /data volume (/srv/spoe-server/spoe_python.py if you used the above command) with the following, then restart the haproxy-spoa-server container:

import spoa
import ipaddress
import requests

auth_url   = "http://172.17.0.1:8082/auth"

def check_sso_auth(args):
    raw_args = args
    args = dict()
    for a in raw_args:
        if a['value'] is not None:
            args[a['name']] = str(a['value'])

    authed = False

    headers = {
        'X-Real-IP':    args['ip'],
        'X-Host':       args['host'],
        'X-Origin-URI': args['uri']
    }
    if 'ff'      in args: headers['X-Forwarded-For'] = args['ff']
    if 'cookies' in args: headers['Cookie']          = args['cookies']
    r = requests.get(auth_url, headers=headers)
    if r.status_code == 200:
        authed = True
        spoa.set_var_str("cookie", spoa.scope_txn, r.headers['Set-Cookie'])

    spoa.set_var_boolean("auth", spoa.scope_txn, authed)
    return

spoa.register_message("check-sso-auth", check_sso_auth)

haproxy config

The following contains the absolute basics, you should ensure that the rest of the configuration is fleshed out how you need it for your environment. It covers the configuration for SPOE, and how to deal with the redirects to nginx-sso for the login form. It also doesn't have any SSL configuration, that's left up to you.

Note that the filter being applied can be placed in the frontend, but you cannot selectively apply it based on the Host: header (to not apply it to the nginx-sso login domain name or path), so take care if you need to do this. The python script would need a small addition made to not bother asking nginx-sso for auth if the Host: header is the login domain for example.

haproxy.conf

frontend fe-http
        mode http
        bind :80
        acl is_login   hdr(host) -i login.yourdomain.com
        use_backend be-login-nginx-sso if is_login
        default_backend be-http

backend be-http
        description Primary webserver

        filter spoe engine spoa-server config spoa-server.spoe.conf

        acl is_authed var(txn.sso.auth) -m bool

        http-request set-var(req.sch) str(https) if  { ssl_fc }
        http-request set-var(req.sch) str(http)  if !{ ssl_fc }
        http-request redirect code 302 location http://login.yourdomain.com/login?go=%[var(req.sch)]://%[hdr(host)]%[url] unless is_authed

        http-response add-header Set-Cookie %[var(txn.sso.cookie)] if is_authed

        server webserver-1 ip.of.your.webserver:80

backend be-login-nginx-sso
        description nginx-sso login page
        option forwardfor
        server nginx-sso 172.17.0.1:8082

backend be-spoe-server
        mode tcp
        server spoe-server 172.17.0.1:12345

spoa-server.spoe.conf

[spoa-server]

spoe-agent spoa-server
        messages check-sso-auth
        option var-prefix  sso
        timeout hello      100ms
        timeout idle       30s
        timeout processing 15ms
        use-backend be-spoe-server

spoe-message check-sso-auth
        args ip=src ff=req.fhdr(x-forwarded-for) host=req.fhdr(host) uri=url cookies=req.fhdr(cookie)
        event on-backend-http-request
Clone this wiki locally