Compare commits
4 Commits
eb287050dc
...
54ee61efa8
| Author | SHA1 | Date | |
|---|---|---|---|
| 54ee61efa8 | |||
|
|
f7e67df19d | ||
| 2e9b2c6678 | |||
| 4649bf610b |
16
.drone.yml
16
.drone.yml
@ -1,16 +0,0 @@
|
|||||||
kind: pipeline
|
|
||||||
name: default
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: docker
|
|
||||||
image: plugins/docker
|
|
||||||
environment:
|
|
||||||
DOCKER_BUILDKIT: 1
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: docker.grassecon.net/cic-auth-helper-gitea
|
|
||||||
registry: docker.grassecon.net
|
|
||||||
tags: latest
|
|
||||||
7
.envrc_dev_example
Normal file
7
.envrc_dev_example
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
set -a
|
||||||
|
CICTEST_PGP_TRUSTED_PUBLICKEY_FINGERPRINT=CCE2E1D2D0E36ADE0405E2D0995BB21816313BD5
|
||||||
|
CICTEST_PGP_IMPORT_DIR=tests/testdata/pgp/dev
|
||||||
|
CICTEST_HTTP_HOBA_CIPHER_ID=969
|
||||||
|
CICTEST_HTTP_AUTH_ORIGIN=http://127.0.0.1:5000
|
||||||
|
CICTEST_HTTP_CORS_ORIGIN=http://localhost:4200
|
||||||
|
set +a
|
||||||
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
|
||||||
@ -1,4 +1,5 @@
|
|||||||
[http]
|
[http]
|
||||||
auth_origin =
|
auth_origin =
|
||||||
|
cors_origin =
|
||||||
auth_realm = "ge"
|
auth_realm = "ge"
|
||||||
hoba_cipher_id = 00
|
hoba_cipher_id = 00
|
||||||
|
|||||||
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 base64
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
from http_hoba_auth import hoba_auth_request_string
|
from http_hoba_auth import hoba_auth_request_string
|
||||||
from http_token_auth import SessionStore
|
from http_token_auth import SessionStore
|
||||||
import confini
|
import confini
|
||||||
|
|
||||||
# local imports
|
|
||||||
from usumbufu.challenge import Challenger
|
from usumbufu.challenge import Challenger
|
||||||
#from usumbufu.filter.sha256 import SHA256Filter
|
#from usumbufu.filter.sha256 import SHA256Filter
|
||||||
from usumbufu.filter.hoba import HobaFilter
|
from usumbufu.filter.hoba import HobaFilter
|
||||||
@ -32,7 +31,9 @@ data_dir = os.path.join(script_dir, '..', 'data')
|
|||||||
config_dir = os.path.join(data_dir, 'config')
|
config_dir = os.path.join(data_dir, 'config')
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser('Authentication helper for ingress routers')
|
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('-c', type=str, help='configuration override directory')
|
||||||
|
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')
|
||||||
argparser.add_argument('-v', action='store_true', help='be verbose')
|
argparser.add_argument('-v', action='store_true', help='be verbose')
|
||||||
argparser.add_argument('-vv', action='store_true', help='be more verbose')
|
argparser.add_argument('-vv', action='store_true', help='be more verbose')
|
||||||
args = argparser.parse_args(sys.argv[1:])
|
args = argparser.parse_args(sys.argv[1:])
|
||||||
@ -42,10 +43,21 @@ if args.vv:
|
|||||||
elif args.v:
|
elif args.v:
|
||||||
logg.setLevel(logging.INFO)
|
logg.setLevel(logging.INFO)
|
||||||
|
|
||||||
config = confini.Config(config_dir, override_dirs=args.c)
|
config = confini.Config(config_dir, args.env_prefix, override_dirs=args.c)
|
||||||
config.process()
|
config.process()
|
||||||
logg.debug('config loaded:\n' + str(config))
|
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.
|
# 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
|
# It will run all the decoding steps from all the filters, ultimately resolving to a auth resource (typically ACL) and an identity
|
||||||
challenger = Challenger()
|
challenger = Challenger()
|
||||||
@ -103,6 +115,18 @@ def do_auth(env):
|
|||||||
# And to conclude, vanilla UWSGI stuff
|
# And to conclude, vanilla UWSGI stuff
|
||||||
def application(env, start_response):
|
def application(env, start_response):
|
||||||
headers = []
|
headers = []
|
||||||
|
|
||||||
|
# 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'']
|
||||||
|
|
||||||
print('env {}'.format(env))
|
print('env {}'.format(env))
|
||||||
result = do_auth(env)
|
result = do_auth(env)
|
||||||
if result == None:
|
if result == None:
|
||||||
@ -129,5 +153,12 @@ def application(env, start_response):
|
|||||||
elif session.auth != auth_string:
|
elif session.auth != auth_string:
|
||||||
headers.append(('Token', base64.b64encode(session.auth).decode('utf-8'),))
|
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)
|
start_response('200 OK', headers)
|
||||||
return [str(auth_resource).encode('utf-8')]
|
return [content]
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
export PGP_TRUSTED_PUBLICKEY_FINGERPRINT=CCE2E1D2D0E36ADE0405E2D0995BB21816313BD5
|
|
||||||
export PGP_IMPORT_DIR=tests/pgp/dev
|
|
||||||
Loading…
Reference in New Issue
Block a user