From f7e67df19d3d24dd077904cd6718541d7931400d Mon Sep 17 00:00:00 2001 From: nolash Date: Sat, 6 Nov 2021 18:42:10 +0100 Subject: [PATCH] Add reverse proxy --- CHANGELOG | 6 +++ VERSION | 1 + cic_auth_helper/proxy.py | 61 ++++++++++++++++++++++++++++++ cic_auth_helper/runnable/server.py | 26 +++++++++++-- 4 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 CHANGELOG create mode 100644 VERSION create mode 100644 cic_auth_helper/proxy.py diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..c0424b5 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,6 @@ +- 0.0.2 + * Optional reverse proxy + * Header merge with reverse proxy, with option to selectively exclude which headers get overwritten +- 0.0.1 + * Implement HOBA PGP and bearer token issuance + * Return 200 OK and bearer token on successful authentication diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0ea3a94 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.2.0 diff --git a/cic_auth_helper/proxy.py b/cic_auth_helper/proxy.py new file mode 100644 index 0000000..46f5534 --- /dev/null +++ b/cic_auth_helper/proxy.py @@ -0,0 +1,61 @@ +# standard imports +import re +import urllib.request +import os +import logging + +logg = logging.getLogger(__name__) + + +re_x = r'^HTTP_(X_.+)$' +def add_x_headers(env, header_f): + for x in env: + m = re.match(re_x, x) + if m != None: + header_orig = m[1].replace('_', '-') + header_f(header_orig, env[x]) + + +class ReverseProxy: + + def __init__(self, base_url, ignore_proxy_headers=[]): + self.base_url = base_url + if not isinstance(ignore_proxy_headers, list): + raise ValueError('ignore_proxy_headers parameter must be a list of header keys') + self.ignore_proxy_headers = [] + for h in ignore_proxy_headers: + self.ignore_proxy_headers.append(h.lower()) + + + def proxy_pass(self, env, headers=[]): + url = os.path.join(self.base_url, env['REQUEST_URI'][1:]) + logg.debug('access ok -> {}'.format(url)) + req = urllib.request.Request(url, method=env['REQUEST_METHOD']) + add_x_headers(env, req.add_header) + req.add_header('Content-Type', env.get('CONTENT_TYPE', 'application/octet-stream')) + req.data = env.get('wsgi.input') + res = urllib.request.urlopen(req) + + logg.debug('headers before reverse proxy {}'.format(headers)) + + header_keys = {} + for i, pair in enumerate(headers): + header_keys[pair[0].lower()] = i + + for h in res.getheaders(): + k = h[0].lower() + if k in self.ignore_proxy_headers: + continue + try: + i = header_keys[k] + headers[i] = h + except KeyError: + headers.append(h) + + logg.debug('headers after reverse proxy {}'.format(headers)) + + status = '{} {}'.format(res.status, res.reason) + content = res.read() + return (status, headers, content) + + diff --git a/cic_auth_helper/runnable/server.py b/cic_auth_helper/runnable/server.py index fef1397..711eb01 100644 --- a/cic_auth_helper/runnable/server.py +++ b/cic_auth_helper/runnable/server.py @@ -5,13 +5,12 @@ import datetime import base64 import sys import argparse +import urllib.parse # external imports from http_hoba_auth import hoba_auth_request_string from http_token_auth import SessionStore import confini - -# local imports from usumbufu.challenge import Challenger #from usumbufu.filter.sha256 import SHA256Filter from usumbufu.filter.hoba import HobaFilter @@ -32,6 +31,7 @@ data_dir = os.path.join(script_dir, '..', 'data') config_dir = os.path.join(data_dir, 'config') argparser = argparse.ArgumentParser('Authentication helper for ingress routers') +argparser.add_argument('--forward-to', type=str, dest='forward_to', help='server to forward request to') argparser.add_argument('-c', type=str, help='configuration override directory') argparser.add_argument('-v', action='store_true', help='be verbose') argparser.add_argument('-vv', action='store_true', help='be more verbose') @@ -46,6 +46,17 @@ config = confini.Config(config_dir, override_dirs=args.c) config.process() logg.debug('config loaded:\n' + str(config)) +reverse_proxy = None +if args.forward_to: + from cic_auth_helper.proxy import ReverseProxy + forward_to = urllib.parse.urlsplit(args.forward_to) + forward_to = urllib.parse.urlunsplit(forward_to) + reverse_proxy = ReverseProxy(forward_to, ignore_proxy_headers=[ + 'access-control-allow-origin', + ]) + logg.info('will forward requests to {}'.format(forward_to)) + + # The Retriever is at the heart of the setup. # It will run all the decoding steps from all the filters, ultimately resolving to a auth resource (typically ACL) and an identity challenger = Challenger() @@ -98,7 +109,7 @@ def do_auth(env): logg.debug('not a http hoba request: {}'.format(e)) return authenticator.check() - + # And to conclude, vanilla UWSGI stuff def application(env, start_response): @@ -129,5 +140,12 @@ def application(env, start_response): elif session.auth != auth_string: headers.append(('Token', base64.b64encode(session.auth).decode('utf-8'),)) + response_status = '200 OK' + content = b'' + if reverse_proxy != None: + (response_status, headers, content) = reverse_proxy.proxy_pass(env, headers) + else: + content = str(auth_resource).encode('utf-8') + start_response('200 OK', headers) - return [str(auth_resource).encode('utf-8')] + return [content]