stephane-martin / bouncer

Authentication with Nginx and LDAP backend
Apache License 2.0
5 stars 0 forks source link
authentication consul golang ldap linux nginx service-discovery

Motivation

Nginx does not provide natively LDAP authentication. But it provides a generic authentication module, that performs HTTP requests to a backend service to check if a user is allowed to access the ressource (see ngx_http_auth_request_module).

bouncer provides such a backend.

Features

Consul integration

LDAP authentication

bouncer can authenticate users based on a LDAP directory. Two authentication schemes are supported: "direct LDAP bind" or "LDAP search and bind".

Multiple LDAP directories can be defined. In that case, bouncer will load-balance the LDAP requests among them.

On the front-end side, the user credentials can be given as "HTTP Basic" authentication headers, or via a classical form/session cookie.

Backend services

Your backend services don't have to deal with authentication anymore. Instead they rely on nginx and bouncer to perform the authentication separately. The services are given information about the current user by HTTP headers.

Check-health and statistics

For reliable monitoring, bouncer provides a simple health-check and some basic statistics.

Logging

Install

Prerequisites

Get it

go get -u github.com/stephane-martin/bouncer

The dependencies are vendored.

Configuration

Configuration sources

File

See the configuration example.

The configuration file has to be bouncer.toml. It is looked for in /etc and in the directory specified by the commandline flag --config=XXX.

Environment variables

It is also possible to configure bouncer through environment variables. See the function in envmapping.go for the mappings.

NAL_CACHE_EXPIRES="3M" NAL_REALM="My Realm" ... bouncer serve

Consul

The configuration can be stored in the key-value part of Consul. Put the parameters under the bouncer/conf/ prefix in Consul KV, and run bouncer with the --consul flag.

For example:

consul kv put -http-addr=127.0.0.1:8500 bouncer/conf/cache/expires 3m
consul kv put -http-addr=127.0.0.1:8500 bouncer/conf/http/realm 'My Realm'
bouncer serve --consul=http://127.0.0.1:8500

Configuring the LDAP servers

Multiple LDAP directories can be configured, for example one master and some slaves. In that case the LDAP requests used to authenticate users will be randomly load-balanced through the directories. Moreover, if one LDAP directory is not responding, then another will be tried.

Configuring the LDAP directories has two steps :

In the configuration file

The default parameters are inside the [defaultldap] section. Here you define the LDAP parameters than you want to share among all the directories.

Each individual LDAP directory must then be defined in an additional [[ldap]] section.

bouncer does not reload automatically when the configuration file is modified. You need to send a SIGHUP to the process.

In Consul KV

In Consul KV you define the default LDAP parameters under the prefix bouncer/conf/defaultldap.

The individual LDAP servers can be defined under bouncer/ldap/[ID]/, where [ID] is a meaningless identifier.

bouncer automatically reloads when the configuration items in Consul KV are modified.

consul kv put -http-addr=127.0.0.1:8500 bouncer/conf/defaultldap/port 389
...

consul kv put -http-addr=127.0.0.1:8500 bouncer/ldap/1/host 127.0.0.1

consul kv put -http-addr=127.0.0.1:8500 bouncer/ldap/2/host 10.1.1.1
consul kv put -http-addr=127.0.0.1:8500 bouncer/ldap/2/port 636
consul kv put -http-addr=127.0.0.1:8500 bouncer/ldap/2/tls_type tls
consul kv put -http-addr=127.0.0.1:8500 bouncer/ldap/2/insecure true

By Consul discovery

If your LDAP servers are registered in Consul as service with health checks, you don't need to (statically) define the LDAP servers in bouncer configuration. Instead, just tell bouncer where to find the LDAP services. bouncer will try the discovery if you provide the --ldap-service-name commandline option. Only LDAP servers that are registered in Consul with a passing health-check will be discovered.

bouncer serve --consul=http://127.0.0.1:8500 --ldap-service-name slapd --ldap-datacenter ldapdc

This means: look for services called slapd, that's defined in the ldapdc Consul datacenter. The slapd hosts and ports, discovered in Consul, completed by defaultldap parameters, will be used as LDAP servers to perform the authentication.

It is also possible to filter the discovered LDAP services by a Consul tag with --ldap-tag.

To print the currently discovered and visible LDAP servers from Consul:

bouncer discovered --consul=http://127.0.0.1:8500 --ldap-service-name slapd --ldap-datacenter ldapdc

The discovery is dynamic: LDAP servers will be added/removed to the list when their Concul health-checks succeed/fail.

Check configuration

To check bouncer configuration, use the print-config command:

# Without consul
bouncer print-config

# With consul (merge configuration from file and Consul KV)
bouncer print-config --consul=http://127.0.0.1:8500

# Also print the current discovered LDAP directories:
bouncer print-config --consul=http://127.0.0.1:8500 --discover --ldap-service-name slapd --ldap-datacenter ldapdc

Main commands

Running

Launching bouncer

bouncer listens on two ports: one for the main Auth service, the other one for the API service. The ports are configurable.

To start listening, use bouncer serve.

Logging

There are two kinds of logs: the 'normal logs', that trace bouncer activity, and the 'request logs', that trace the received requests and their results.

By default, the normals logs are written to stderr, and the request logs to stdout.

If the --syslog flag is provided, they will both be sent to the local syslog daemon. (They use different syslog tags).

To write the normal logs to a file, use --logfile=XXX. Similarly use --req-logfile=YYY for the request logs.

Logs are written in text format by default. Use the --json flag to write them as JSON instead.

Log verbosity can be set for the normal logs and the request logs respectively with the --loglevel and --req-loglevel flags. Successful authentications will only be logged in the request logs at level 'DEBUG'.

Redis and the request logs

If Redis is enabled in configuration, the request logs:

Registering in Consul

bouncer can register itself as a Consul service: bouncer serve --consul=XXX --register.

Stopping

Send SIGTERM or SIGINT to the bouncer process.

Reload the configuration file

Send SIGHUP to the bouncer process.

Watch the flow of requests

If Redis in enabled, you can watch the request logs as they are generated with the monitor command. For example, try:

bouncer serve --req-loglevel=DEBUG

Then in another terminal:

bouncer monitor --json

HTTP endpoints

Auth service

The Auth service listens on address/port defined by configuration parameters http.bind_addr and http.port.

It provides the following endpoints:

API service

The API service listens on address/port defined by configuration parameters api.bind_addr and api.port.

It provides the following endpoints:

How to get user information in the backend services

The backend services that are protected by bouncer get information about the authenticated user through the following ways:

To define a RSA private key, provide the path to a PEM-encoded file in signature.private_key_path, or directly the PEM-encoded key in signature.private_key_content.

(To generate such a private key, you can use bouncer generate-rsa-keys.)

HTTP Basic Authentication: Nginx configuration example

Adapt it to your needs.

server {
    location /backend_service_to_protect {
        auth_request /nginx;

        # gather info from HTTP response headers
        auth_request_set $remote $upstream_http_x_remote_user;
        auth_request_set $auth $upstream_http_authorization;
        auth_request_set $backendjwt $upstream_http_x_remote_jwt;

        # pass info to the backend service
        proxy_set_header REMOTE_USER $remote;
        proxy_set_header REMOTE-USER $remote;
        proxy_set_header X-REMOTE-USER $remote;
        proxy_set_header X-REMOTE-JWT $backendjwt;
        proxy_set_header Authorization $auth;

    }

    location = /nginx {
        internal;
        proxy_pass http://BOUNCER_HOST:BOUNCER_PORT;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";

        # pass some information to bouncer about the incoming request
        # (useful for the request logs)
        proxy_set_header X-Forwarded-Server $http_host;
        proxy_set_header X-Forwarded-Host $http_host:443;
        proxy_set_header X-Original-URI $request_uri;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Forwarded "for=$remote_addr; proto=https";
        proxy_set_header X-Forwarded-Port 443;
        proxy_set_header X-Forwarded-Proto https;
    }
}

Cookie-based Authentication: Nginx configuration example

server {
    listen 4343 ssl http2;
    server_name myapp.example.org;

    ...

    location / {
        error_page 401 =200 /nal-login-page;
        auth_request /nginx;

        # gather info from HTTP response headers
        auth_request_set $remote $upstream_http_x_remote_user;
        auth_request_set $auth $upstream_http_authorization;
        auth_request_set $backendjwt $upstream_http_x_remote_jwt;

        # pass info to the backend service
        proxy_set_header REMOTE_USER $remote;
        proxy_set_header REMOTE-USER $remote;
        proxy_set_header X-REMOTE-USER $remote;
        proxy_set_header X-REMOTE-JWT $backendjwt;
        proxy_set_header Authorization $auth;
    }

    location /login {
        proxy_pass http://BOUNCER_HOST:BOUNCER_PORT/nal-login-page;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Host $http_host:443;
        proxy_set_header X-Forwarded-Server $http_host;
        proxy_set_header X-Forwarded-Port 443;
        proxy_set_header Forwarded "for=$remote_addr; proto=https";
        proxy_set_header X-Scheme https;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_set_header X-Url-Scheme https;
        proxy_set_header X-Original-Uri $request_uri;
        proxy_set_header X-Login-Uri $uri;
    }

    location /logout {
        proxy_pass http://BOUNCER_HOST:BOUNCER_PORT/nal-logout-page;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Host $http_host:443;
        proxy_set_header X-Forwarded-Server $http_host;
        proxy_set_header X-Forwarded-Port 443;
        proxy_set_header Forwarded "for=$remote_addr; proto=https";
        proxy_set_header X-Scheme https;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_set_header X-Url-Scheme https;
        proxy_set_header X-Original-Uri $request_uri;
    }

    location = /nginx {
        internal;
        proxy_pass http://BOUNCER_HOST:BOUNCER_PORT;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Forwarded-Server $http_host;
        proxy_set_header X-Forwarded-Host $http_host:443;
        proxy_set_header X-Original-URI $request_uri;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Forwarded "for=$remote_addr; proto=https";
        proxy_set_header X-Forwarded-Port 443;
        proxy_set_header X-Forwarded-Proto https;
    }

}