commit 66e15a8a18e2c64fa5bd1a827e60f274f18cc57d Author: nolash Date: Tue Oct 26 12:36:56 2021 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b60ab2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__ +*.egg-info +*.pyc +.config +build/ diff --git a/cic_auth_helper/data/config/http.ini b/cic_auth_helper/data/config/http.ini new file mode 100644 index 0000000..8d36d60 --- /dev/null +++ b/cic_auth_helper/data/config/http.ini @@ -0,0 +1,4 @@ +[http] +auth_origin = +auth_realm = "ge" +hoba_cipher_id = 00 diff --git a/cic_auth_helper/data/config/pgp.ini b/cic_auth_helper/data/config/pgp.ini new file mode 100644 index 0000000..e3f06f1 --- /dev/null +++ b/cic_auth_helper/data/config/pgp.ini @@ -0,0 +1,6 @@ +[pgp] +trusted_publickey_fingerprint = +import_dir = /var/lib/cic-auth-helper/pgp +publickey_filename = publickeys.asc +signature_filename = signature.asc +run_dir = diff --git a/cic_auth_helper/runnable/server_pgp_hoba_session.py b/cic_auth_helper/runnable/server_pgp_hoba_session.py new file mode 100644 index 0000000..fef1397 --- /dev/null +++ b/cic_auth_helper/runnable/server_pgp_hoba_session.py @@ -0,0 +1,133 @@ +# standard imports +import logging +import os +import datetime +import base64 +import sys +import argparse + +# 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 +from usumbufu.filter.fetcher import FetcherFilter +from usumbufu.filter.pgp import PGPFilter +from usumbufu.filter.session import SessionFilter +from usumbufu.filter import Filter +from usumbufu.retrieve import Retriever +from usumbufu.retrieve.file import FileFetcher +from usumbufu.adapters.uwsgi import UWSGIHTTPAuthorization +from usumbufu.adapters.uwsgi import UWSGIAdapter + +logging.basicConfig(level=logging.WARNING) +logg = logging.getLogger() + +script_dir = os.path.dirname(os.path.realpath(__file__)) +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('-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') +args = argparser.parse_args(sys.argv[1:]) + +if args.vv: + logg.setLevel(logging.DEBUG) +elif args.v: + logg.setLevel(logging.INFO) + +config = confini.Config(config_dir, override_dirs=args.c) +config.process() +logg.debug('config loaded:\n' + str(config)) + +# 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() + +# parse a hoba auth string to key, signature, nonce etc, to the "to be signed" format +hoba_filter = HobaFilter(config.get('HTTP_AUTH_ORIGIN'), config.get('HTTP_AUTH_REALM'), challenger, alg=config.get('HTTP_HOBA_CIPHER_ID')) + +trusted_publickeys = config.get('PGP_TRUSTED_PUBLICKEY_FINGERPRINT').split(',') + +fetcher_pgp_trusted = FileFetcher(config.get('PGP_IMPORT_DIR')) +pgp_filter = PGPFilter(trusted_publickeys, fetcher_pgp_trusted) + +pgp_filter.import_keys(config.get('PGP_PUBLICKEY_FILENAME'), config.get('PGP_SIGNATURE_FILENAME')) + +# set a session token for the identity, if not yet set +# the session store can be used outside the pipeline to get session info (like the auth token for a Token header response) +session_store = SessionStore(auth_expire_delta=1) +session_filter = SessionFilter(session_store) + + +# the decoders will be run in SEQUENCE. let's wire them up, like so: +hoba_retriever = Retriever() +hoba_retriever.add_decoder(hoba_filter) +hoba_retriever.add_decoder(pgp_filter) +hoba_retriever.add_decoder(session_filter) + +bearer_retriever = Retriever() +bearer_retriever.add_decoder(session_filter) + + +# Below here is the runtime code for the UWSGI application +# Most important to notice here is that the ChallengeRetriever is being passed to the UWSGIHTTPAuthorization object. This object can identify a HOBA request, and will attempt to validate the HOBA auth string using ChallengeRetriever. +# cic_eth.Auth.check() (overloaded) will attempt to FETCH the ACL using the key (if any) resulting from the validation +def do_auth(env): + authenticator = UWSGIAdapter() + http_authenticator = UWSGIHTTPAuthorization(hoba_retriever, env, config.get('HTTP_AUTH_REALM'), origin=config.get('HTTP_AUTH_ORIGIN')) + bearer_authenticator = UWSGIHTTPAuthorization(bearer_retriever, env, config.get('HTTP_AUTH_REALM'), origin=config.get('HTTP_AUTH_ORIGIN')) + http_authenticator.component_id = 'http-hoba' + bearer_authenticator.component_id = 'http-bearer' + try: + authenticator.register(bearer_authenticator) + authenticator.activate(bearer_authenticator.component_id) + except TypeError as e: + logg.debug('not a http bearer request: {}'.format(e)) + + try: + authenticator.register(http_authenticator) + authenticator.activate(http_authenticator.component_id) + except TypeError as e: + logg.debug('not a http hoba request: {}'.format(e)) + + return authenticator.check() + + +# And to conclude, vanilla UWSGI stuff +def application(env, start_response): + headers = [] + print('env {}'.format(env)) + result = do_auth(env) + if result == None: + #if env.get('HTTP_AUTHORIZATION') != None: + # start_response('403 failed miserably', headers) + # return [b''] + (challenge, expire) = challenger.request(env['REMOTE_ADDR']) + headers.append(('WWW-Authenticate', hoba_auth_request_string(challenge, expire.timestamp(), realm=config.get('HTTP_AUTH_REALM'))),) + start_response('401 authenticate or I will SCREAM_SNAKE_CASE at you', headers) + return [b''] + + # name the successful auth result parts + auth_method_component_id = result[0] + auth_string = result[1] + auth_resource = result[2] + auth_identity = result[3] + logg.debug('result {}'.format(result)) + + session = session_store.get(auth_identity) + logg.debug(f'session token: {session.auth}') + + if auth_method_component_id == 'http-hoba': + headers.append(('Token', base64.b64encode(session.auth).decode('utf-8'),)) + elif session.auth != auth_string: + headers.append(('Token', base64.b64encode(session.auth).decode('utf-8'),)) + + start_response('200 OK', headers) + return [str(auth_resource).encode('utf-8')] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..51e1ff9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +usumbufu==0.3.1a5 +uWSGI==2.0.19.1 +confini==0.4.2