Add reverse proxy
This commit is contained in:
		
							parent
							
								
									8f2cc1beb9
								
							
						
					
					
						commit
						f7e67df19d
					
				
							
								
								
									
										6
									
								
								CHANGELOG
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								CHANGELOG
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
			
		||||
							
								
								
									
										61
									
								
								cic_auth_helper/proxy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								cic_auth_helper/proxy.py
									
									
									
									
									
										Normal file
									
								
							@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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]
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user