Skip to content

Commit 140a479

Browse files
committed
initial commit
0 parents  commit 140a479

File tree

5 files changed

+215
-0
lines changed

5 files changed

+215
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
config.yaml
2+
Gemfile.lock

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
source 'https://rubygems.org'
2+
3+
gem 'net-ldap'

README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Ruby LDAP authentication servlet for nginx HTTP auth request module
2+
3+
nginx does not offer HTTP authentication using LDAP (or any other type of databases) as backend out of the box. Fortunately nginx features a module named ngx_http_auth_request_module which enables client authorization based on the result of an HTTP/HTTPS subrequest. Using this module in combination with the nginx HTTP proxy module it is possible to authenticate against any web service returning either an HTTP 200 code on authentication success or HTTP 401 code on authentication failure.
4+
5+
This simple Ruby script implements a WEBrick HTTPS servlet listening by default on port 8888 in order to authenticate against an LDAP server using STARTTLS and thus enabling you to provide LDAP authentication for your nginx website.
6+
7+
## Installation
8+
9+
You will need Ruby and the bundler gem in order to install and run this script. Read below for the installation instructions.
10+
11+
### Install bunlder
12+
13+
$ gem install bundler
14+
15+
### Install dependencies
16+
Currently the only required gem is net-ldap.
17+
18+
$ bundle
19+
20+
### Configure the script
21+
22+
Copy the sample `config.sample.yaml` file as `config.yaml` and adapt it for your LDAP environment.
23+
24+
### Configure nginx
25+
26+
1. Add to your nginx http configuration (e.g. `/etc/nginx/conf.d/auth_cache.conf`):
27+
28+
```
29+
proxy_cache_path cache/ keys_zone=auth_cache:5m;
30+
```
31+
32+
The credentials are cached for 5 minutes, feel free to increase or decrease. If you change this parameter do not forget to also adapt `proxy_cache_valid` under point 2. below.
33+
34+
2. Add to your nginx server configuration (e.g. `/etc/nginx/conf.d/mywebsite.ch`):
35+
36+
```
37+
satisfy any;
38+
auth_basic "Ruby LDAP authentication servlet";
39+
auth_basic_user_file "/etc/nginx/empty.htpasswd";
40+
auth_request /auth;
41+
42+
location = /auth {
43+
proxy_pass https://localhost:8888;
44+
proxy_cache auth_cache;
45+
proxy_cache_valid 200 5m;
46+
proxy_pass_request_body off;
47+
proxy_set_header Content-Length "";
48+
proxy_set_header X-Original-URI $request_uri;
49+
}
50+
```
51+
52+
This will protect the whole website. It is also possible to protect parts of it by including the first block in an nginx specific location such as `/private`:
53+
54+
```
55+
location /private {
56+
satisfy any;
57+
auth_basic "Ruby LDAP authentication servlet";
58+
auth_basic_user_file "/etc/nginx/empty.htpasswd";
59+
auth_request /auth;
60+
}
61+
```
62+
63+
3. Create an empty htpasswd file
64+
65+
This is required and I did not find any way around it.
66+
67+
$ touch /etc/nginx/empty.htpasswd
68+
69+
4. Reload nginx
70+
71+
$ systemctl reload nginx
72+
73+
### Start the script
74+
75+
$ ./ldap-auth-servlet.rb
76+
77+
Once you have tested that everything works well it is recommended to run the script in background in daemon mode by changing the `daemonize` parameter in the `config.yaml` file to `true`.
78+
79+
## Tested with
80+
81+
This script has been tested with the following setup:
82+
83+
- Debian 8
84+
- Ruby 2.4.0
85+
- nginx 1.6.2
86+
- OpenLDAP 2.4
87+
88+
## TODO
89+
90+
- Support multiple LDAP servers

config.sample.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Config file for Ruby LDAP authentication servlet for nginx HTTP auth request module
2+
daemonize: false
3+
http_auth_realm: 'Ruby LDAP authentication servlet'
4+
ldap_host: 'ldap.enterprise.ch'
5+
ldap_port: 389
6+
ldap_auth_attribute: 'uid'
7+
ldap_auth_base_dn: 'ou=people,dc=enterprise,dc=ch'
8+
servlet_port: 8888
9+
servlet_ssl_certificate_cn: 'localhost'

ldap-auth-servlet.rb

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/usr/bin/env ruby
2+
3+
### Ruby LDAP authentication servlet for nginx HTTP auth request module ###
4+
5+
# Purpose: This simple Ruby script implements a WEBrick HTTPS servlet listening by default on port 8888 in order to authenticate against an LDAP server using STARTTLS and thus enabling you to provide LDAP authentication for your nginx website.
6+
7+
require 'webrick'
8+
require 'webrick/https'
9+
require 'base64'
10+
require 'net/ldap'
11+
require 'yaml'
12+
13+
# Path to config file and default configuration parameters
14+
config_file = File.join(__dir__, 'config.yaml')
15+
config = {}
16+
config['daemonize'] = false
17+
config['http_auth_realm'] = 'Ruby LDAP authentication servlet'
18+
config['ldap_host'] = 'localhost'
19+
config['ldap_port'] = 389
20+
config['ldap_auth_attribute'] = 'uid'
21+
config['ldap_auth_base_dn'] = 'ou=people,dc=domain,dc=tld'
22+
config['servlet_port'] = 8888
23+
config['servlet_ssl_certificate_cn'] = 'localhost'
24+
25+
class LDAPAuthServlet < WEBrick::HTTPServlet::AbstractServlet
26+
def initialize server, config
27+
super server
28+
@config = config
29+
end
30+
31+
def do_GET (request, response)
32+
# Check for authorization HTTP header to perform authentication
33+
if request.header.include?('authorization')
34+
http_auth_header = request.header['authorization']
35+
36+
# Extract basic auth string
37+
auth_encoded = http_auth_header[0].split(' ').last
38+
39+
# Decode basic auth string
40+
auth_decoded = Base64.decode64(auth_encoded)
41+
42+
# Split auth string into an array with username and password
43+
credentials = auth_decoded.split(':')
44+
45+
# Check for empty credentials, empty password or empty username
46+
unless credentials.empty? or credentials.count == 1 or credentials[0].to_s.strip.length == 0
47+
# Define LDAP connection using STARTTLS for encryption
48+
ldap = Net::LDAP.new(:encryption => { :method => :start_tls })
49+
ldap.host = @config['ldap_host']
50+
ldap.port = @config['ldap_port']
51+
52+
# Generate bind DN using passed username
53+
bind_dn = @config['ldap_auth_attribute']+'='+credentials[0].strip+','+@config['ldap_auth_base_dn']
54+
55+
# Authenticate with LDAP server
56+
ldap.auth bind_dn, credentials[1].strip
57+
58+
# Catch errors such as Errno::ECONNREFUSED
59+
begin
60+
# Check for succesfull authentication
61+
if ldap.bind
62+
# Return HTTP OK on successful auth
63+
puts '[INFO]: Authentication succesful for user '+credentials[0]
64+
response.status = 200
65+
return
66+
else
67+
# HTTP Unauthorized on failed auth
68+
puts '[INFO]: Authentication failed for user '+credentials[0]
69+
end
70+
rescue => e
71+
$stderr.puts '[ERROR]: Class -> '+e.class.to_s
72+
$stderr.puts '[ERROR]: Message -> '+e.message
73+
$stderr.puts '[ERROR]: LDAP result object -> '+ldap.get_operation_result.to_s
74+
$stderr.puts '[ERROR]: LDAP result message -> '+ldap.get_operation_result.message
75+
end
76+
end
77+
else
78+
# First HTTP authentication attempt
79+
puts '[INFO]: Authentication started'
80+
end
81+
# Default behaviour is to return HTTP Unauthorized
82+
response.status = 401
83+
response.header['Cache-control'] = 'no-cache'
84+
response.header['WWW-Authenticate'] = 'Basic realm="'+@config['http_auth_realm']+'"'
85+
end
86+
end
87+
88+
puts "\n### Ruby LDAP authentication servlet for nginx HTTP auth request module ###\n\n"
89+
90+
if File.exist?(config_file)
91+
puts '[INFO]: Config file config.yaml found, using this file for configuration parameters.'
92+
config = YAML::load_file(config_file)
93+
else
94+
puts '[WARNING]: Config file config.yaml does not exist, using default configuration parameters.'
95+
end
96+
97+
# Setup WEBrick with servlet
98+
cert_name = [['CN', config['servlet_ssl_certificate_cn']]]
99+
server = WEBrick::HTTPServer.new(:Port => config['servlet_port'], :SSLEnable => true, :SSLCertName => cert_name)
100+
server.mount '/', LDAPAuthServlet, config
101+
102+
# Start WEBrick as daemon in background
103+
if config['daemonize'] == true
104+
WEBrick::Daemon.start
105+
end
106+
107+
trap('INT') {
108+
server.shutdown
109+
}
110+
111+
server.start

0 commit comments

Comments
 (0)