-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 3834752
Showing
9 changed files
with
510 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
Oops, something went wrong.