Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
Siecje committed Aug 3, 2016
0 parents commit 3834752
Show file tree
Hide file tree
Showing 9 changed files with 510 additions and 0 deletions.
25 changes: 25 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2014-2015 Nginx, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# NGINX Auth Proxy

## Problem

You have multiple services running on the same server on different ports or subdomains.
You want to use the same authentication (login and password) for every service without having to login to each one (Single Sign On).
You want passwords to validate against one source of truth.

## How does it work

Service (think JupyterHub) is running on port 9000 internally.
Auth Service (Python server) running on port 8000 internally.

Each request needs to have an auth token, which will be checked by the auth service.
If no auth token is provided or the token is not valid then redirect to then auth service login form.
If auth token is valid route to internal service on port 9000, passing the auth token and all additional headers required by all services.

When you login to the auth service it will provide an auth token which will be used for subsequent requests.

## Adding a new service

Add the nginx config to run the service locally on an available port.
Configure the new service to authenticate via ```REMOTE_USER```.
Add the required headers for the service to ```authenticator.py```
Restart ```nginx```.

## Running

You will need NGINX with the [ngx_http_auth_request_module](http://nginx.org/en/docs/http/ngx_http_auth_request_module.html) installed.

```shell
sudo apt-get install nginx-full
```

```shell
virtualenv venv
source venv/bin/activate
pip install -r requirements.txt
```

```shell
sudo mv /etc/nginx/nginx.conf /etc/nginx/nginx_backup.conf
sudo ln -s nginx.conf /etc/nginx/nginx.conf
```

```shell
python authenticator.py &
python service.py &
```

When you visit ```http://localhost:8081``` you will need to login.
As long as you use the username 'admin' you will be able to access the service.
73 changes: 73 additions & 0 deletions authenticator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import base64

from flask import Flask, abort, make_response, render_template, request
from flask_wtf import Form
from wtforms import HiddenField, StringField, PasswordField
from wtforms.validators import DataRequired

app = Flask(__name__)
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

AUTH_PORT = 8000


class LoginForm(Form):
login = StringField('Login', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
target = HiddenField('Target', validators=[DataRequired()])


def EncodeToken(user, password):
return base64.b64encode(user + ':' + password)


def DecodeToken(token):
auth_decoded = base64.b64decode(token)
user, password = auth_decoded.split(':', 2)
return user, password


def ValidUser(user, password):
if user == 'admin':
enc = EncodeToken(user, password)
return enc


@app.route('/', methods=['GET'])
def authenticate():
token = request.headers.get('token')
if token is None:
abort(401)
username, password = DecodeToken(token)
if ValidUser(username, password) is not None:
# Add headers to be authenticated with services
resp = make_response()
resp.headers['REMOTE_USER'] = username
resp.headers['X-WEBAUTH-USER'] = username
return resp
abort(401)


@app.route('/login/', methods=["GET", "POST"])
def login():
target = request.headers.get('X-Target', "")
print 'Target: ' + target
form = LoginForm(target = target)
if form.validate_on_submit():
print 'inside'
username = form.login.data
password = form.password.data
target = form.target.data
auth_token = ValidUser(username, password)
if auth_token:
resp = make_response()
resp.set_cookie('token', auth_token)
print "before target"
print target
resp.headers['Location'] = target
return resp
return render_template('login.html', form=form)


if __name__ == "__main__":
app.run(port = AUTH_PORT)
53 changes: 53 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
error_log /var/log/nginx/error.log debug;

events { }

http {
proxy_cache_path cache/ keys_zone=auth_cache:10m;

# The application listens on port 9000 as implemented
# in service.py.
upstream backend {
server 127.0.0.1:9000;
}

upstream authenticator {
server 127.0.0.1:8000;
}

# listen on port 8081 for requests that require
# authentication. Change the port number as appropriate.
server {
listen 8081;

# Protected application
location / {
auth_request /auth-proxy;

# redirect 401 and 403 to login form
error_page 401 403 =200 /login;

proxy_pass http://backend/;
}

location /login {
proxy_pass http://authenticator/login;
}

location = /auth-proxy {
internal;

# The authenticator listens on port 8000, as set
# in authenticator.py.
proxy_pass http://authenticator/;

proxy_pass_request_body off;
proxy_set_header Content-Length "";
# Login service returns a redirect to the original URI
# and sets the cookie for the authenticator
proxy_set_header X-Target $request_uri;
proxy_cache auth_cache;
proxy_cache_valid 200 403 10m;
}
}
}
Loading

0 comments on commit 3834752

Please sign in to comment.