- Notifications
You must be signed in to change notification settings - Fork0
Authentication with Nginx and LDAP backend
License
stephane-martin/bouncer
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Nginx does not provide natively LDAP authentication. But it provides a genericauthentication module, that performs HTTP requests to a backend service to check ifa user is allowed to access the ressource(seengx_http_auth_request_module).
bouncer
provides such a backend.
bouncer
configuration can be defined in Consul KV- if your LDAP directories are services registered in Consul,
bouncer
can find them bouncer
can register itself as a Consul service
bouncer
can authenticate users based on a LDAP directory. Two authenticationschemes are supported: "direct LDAP bind" or "LDAP search and bind".
Multiple LDAP directories can be defined. In that case,bouncer
willload-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.
Your backend services don't have to deal with authentication anymore. Insteadthey rely on nginx andbouncer
to perform the authentication separately.The services are given information about the current user by HTTP headers.
For reliable monitoring,bouncer
provides a simple health-check and some basic statistics.
- logs are structured (using logrus), in a text or JSON format
- logs and request logs are clearly separated
- logs can be sent to syslog
- Nginx, compiled with the
ngx_http_auth_request_module
module. - LDAP server(s)
- Redis is needed for some features (statistics, watching request flow)
- Linux (should work on other UNIX too)
- Only compiles on Go >= 1.8
go get -u github.com/stephane-martin/bouncer
The dependencies are vendored.
The configuration file has to bebouncer.toml
. It is looked for in/etc
andin the directory specified by the commandline flag--config=XXX
.
It is also possible to configurebouncer
through environment variables.See the function inenvmapping.gofor the mappings.
NAL_CACHE_EXPIRES="3M" NAL_REALM="My Realm" ... bouncer serve
The configuration can be stored in the key-value part of Consul. Put the parameters underthebouncer/conf/
prefix in Consul KV, and runbouncer
withthe--consul
flag.
For example:
consul kv put -http-addr=127.0.0.1:8500 bouncer/conf/cache/expires 3mconsul kv put -http-addr=127.0.0.1:8500 bouncer/conf/http/realm 'My Realm'bouncer serve --consul=http://127.0.0.1:8500
Multiple LDAP directories can be configured, for example one master and someslaves. In that case the LDAP requests used to authenticate users will berandomly load-balanced through the directories. Moreover, if one LDAP directoryis not responding, then another will be tried.
Configuring the LDAP directories has two steps :
- configure the defaults parameters that would apply to all directories
- configure the specific paramaters (mostly the
host
) for each directory
The default parameters are inside the[defaultldap]
section. Here you definethe 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 ismodified. You need to send a SIGHUP to the process.
In Consul KV you define the default LDAP parameters under the prefixbouncer/conf/defaultldap
.
The individual LDAP servers can be defined underbouncer/ldap/[ID]/
,where[ID]
is a meaningless identifier.
bouncer automatically reloads when the configuration items in Consul KVare 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.1consul kv put -http-addr=127.0.0.1:8500 bouncer/ldap/2/host 10.1.1.1consul kv put -http-addr=127.0.0.1:8500 bouncer/ldap/2/port 636consul kv put -http-addr=127.0.0.1:8500 bouncer/ldap/2/tls_type tlsconsul kv put -http-addr=127.0.0.1:8500 bouncer/ldap/2/insecure true
If your LDAP servers are registered in Consul as service with healthchecks, you don't need to (statically) define the LDAP servers in bouncerconfiguration. Instead, just tell bouncer where to find the LDAPservices. bouncer will try the discovery if you provide the--ldap-service-name
commandline option. Only LDAP servers that are registeredin 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 calledslapd
, that's defined in theldapdc
Consul datacenter. Theslapd
hosts and ports, discovered in Consul, completed bydefaultldap
parameters, will be used as LDAP servers to perform theauthentication.
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 whentheir Concul health-checks succeed/fail.
To check bouncer configuration, use theprint-config
command:
# Without consulbouncer 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
bouncer
listens on two ports: one for the main Auth service, the otherone for the API service. The ports are configurable.
To start listening, usebouncer serve
.
There are two kinds of logs: the 'normal logs', that tracebouncer
activity, and the 'request logs', that trace the received requests and theirresults.
By default, the normals logs are written tostderr
, and the request logs tostdout
.
If the--syslog
flag is provided, they will both be sent to thelocal 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 themas JSON instead.
Log verbosity can be set for the normal logs and the request logs respectivelywith the--loglevel
and--req-loglevel
flags. Successful authenticationswill only be logged in the request logs at level 'DEBUG'.
If Redis is enabled in configuration, the request logs:
- will be written to some Redis sorted sets. (We can calculate some metrics fromRedis afterwards).
- will be erased from Redis after some configurable period.
- will be published as a Redis PUBSUB. (We can synchronously expose the requestlogs this way).
bouncer
can register itself as a Consul service:bouncer serve --consul=XXX --register
.
Send SIGTERM or SIGINT to thebouncer
process.
Send SIGHUP to thebouncer
process.
If Redis in enabled, you can watch the request logs as they are generated withthemonitor
command. For example, try:
bouncer serve --req-loglevel=DEBUG
Then in another terminal:
bouncer monitor --json
The Auth service listens on address/port defined by configuration parametershttp.bind_addr
andhttp.port
.
It provides the following endpoints:
/nginx
: where Nginx should send the authentication subrequests./auth
: authentication through HTTP POST: in the incoming request,username
andpassword
should be set as HTTPapplication/x-www-form-urlencoded
parameters. Returns 200, 401, 403... like the /nginx endpoint./health
: simple health-check endpoint. Returns 200 if everything looks OK.
The API service listens on address/port defined by configuration parametersapi.bind_addr
andapi.port
.
It provides the following endpoints:
/status
: returns 200 and a dummy message ifbouncer
is running/health
: simple health-check endpoint. Returns 200 if everything looks OK./conf
: returns the current configuration/reload
: POST there to trigger a configuration reload/stats
: if Redis is enabled, some metrics are returned in a JSON format./events
: if Redis is enabled, the request logs are posted there as ServerSide Events.
The backend services that are protected by bouncer get informationabout the authenticated user through the following ways:
- The
Authorization
HTTP header is passed. Optionaly, the user passwordcan be masked in that header ifhttp.mask_password
is true. - The username is passed in the
X-REMOTE-USER
HTTP header. - If a RSA private key is defined in
bouncer
configuration, a signedJWT token is passed in theX-REMOTE-JWT
header.
To define a RSA private key, provide the path to a PEM-encoded file insignature.private_key_path
, or directly the PEM-encoded key insignature.private_key_content
.
(To generate such a private key, you can usebouncer generate-rsa-keys
.)
Adapt it to your needs.
server {location/backend_service_to_protect{auth_request /nginx;# gather info from HTTP response headersauth_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 serviceproxy_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-Port443;proxy_set_header X-Forwarded-Proto https; }}
server {listen4343 ssl http2;server_name myapp.example.org; ...location/{error_page401=200 /nal-login-page;auth_request /nginx;# gather info from HTTP response headersauth_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 serviceproxy_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-Port443;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-Port443;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-Port443;proxy_set_header X-Forwarded-Proto https; }}