2021-10-26 12:36:56 +02:00
# standard imports
import logging
import os
import datetime
import base64
import sys
import argparse
2021-11-06 18:42:10 +01:00
import urllib . parse
2021-10-26 12:36:56 +02:00
# external imports
from http_hoba_auth import hoba_auth_request_string
from http_token_auth import SessionStore
import confini
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
2022-01-24 20:46:25 +01:00
# local imports
from cic_auth_helper . error import NotFoundError
2021-10-26 12:36:56 +02:00
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 ' )
2021-11-06 18:42:10 +01:00
argparser . add_argument ( ' --forward-to ' , type = str , dest = ' forward_to ' , help = ' server to forward request to ' )
2021-10-26 12:36:56 +02:00
argparser . add_argument ( ' -c ' , type = str , help = ' configuration override directory ' )
2021-11-02 09:22:25 +01:00
argparser . add_argument ( ' --env-prefix ' , default = os . environ . get ( ' CONFINI_ENV_PREFIX ' ) , dest = ' env_prefix ' , type = str , help = ' environment prefix for variables to overwrite configuration ' )
2021-10-26 12:36:56 +02:00
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 )
2021-11-02 09:22:25 +01:00
config = confini . Config ( config_dir , args . env_prefix , override_dirs = args . c )
2021-10-26 12:36:56 +02:00
config . process ( )
logg . debug ( ' config loaded: \n ' + str ( config ) )
2021-11-06 18:42:10 +01:00
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 ) )
2021-10-26 12:36:56 +02:00
# 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 ( )
2021-11-06 18:42:10 +01:00
2021-10-26 12:36:56 +02:00
# And to conclude, vanilla UWSGI stuff
def application ( env , start_response ) :
headers = [ ]
2021-11-02 09:22:25 +01:00
# fetch control headers
headers . append ( ( ' Access-Control-Allow-Origin ' , config . get ( ' HTTP_CORS_ORIGIN ' ) , ) )
headers . append ( ( ' Access-Control-Expose-Headers ' , ' WWW-Authenticate, Token ' , ) )
headers . append ( ( ' Access-Control-Allow-Headers ' , ' * ' , ) )
headers . append ( ( ' Access-Control-Allow-Credentials ' , ' true ' , ) )
# to appease fetch pre-flights
if env [ ' REQUEST_METHOD ' ] == ' OPTIONS ' :
start_response ( ' 200 OK ' , headers )
return [ b ' ' ]
2021-10-26 12:36:56 +02:00
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 ' ) ) ) , )
2022-01-24 20:46:25 +01:00
logg . debug ( ' headers {} ' . format ( headers ) )
2021-10-26 12:36:56 +02:00
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 ' ) , ) )
2021-11-06 18:42:10 +01:00
response_status = ' 200 OK '
content = b ' '
if reverse_proxy != None :
2022-01-24 20:46:25 +01:00
try :
( response_status , headers , content ) = reverse_proxy . proxy_pass ( env , headers )
except NotFoundError as e :
response_status = ' 404 ' + e . msg
headers = e . headers
content = b ' '
2021-11-06 18:42:10 +01:00
else :
content = str ( auth_resource ) . encode ( ' utf-8 ' )
2022-01-24 20:46:25 +01:00
start_response ( response_status , headers )
2021-11-06 18:42:10 +01:00
return [ content ]