Files

90 lines
3.8 KiB
Python
Raw Permalink Normal View History

"""Validated private-interface settings and a fixed-identity reverse proxy."""
import ipaddress
import json
PROXY_IMAGE = 'nginx:stable-alpine@sha256:0985e772fb9f729e6fa0980da05fca5d9c468e870eed43071545afa9d2e27d94'
NETWORK = 'adamwen-test-console-net'
PROXY = 'adamwen-test-console-proxy'
def validate_settings(value):
if not isinstance(value, dict) or set(value) != {'address', 'allowed_cidrs', 'port'}:
raise ValueError('Exactly address, allowed_cidrs and port must be configured')
address = ipaddress.ip_address(value['address'])
if not isinstance(value['allowed_cidrs'],list) or not 1 <= len(value['allowed_cidrs']) <= 8:
raise ValueError('Configure one to eight explicit private source networks')
networks = [ipaddress.ip_network(n, strict=True) for n in value['allowed_cidrs']]
private = [ipaddress.ip_network(n) for n in ('10.0.0.0/8','172.16.0.0/12','192.168.0.0/16')]
if address.version != 4 or any(n.version != 4 or not any(n.subnet_of(p) for p in private) for n in networks):
raise ValueError('An explicit RFC1918 IPv4 interface and network are required')
if not any(address in n and address not in (n.network_address,n.broadcast_address) for n in networks):
raise ValueError('The interface must belong to the allowed LAN subnet')
if type(value['port']) is not int or not 1024 <= value['port'] <= 65535:
raise ValueError('The port must be an integer between 1024 and 65535')
if len({str(n) for n in networks}) != len(networks):
raise ValueError('Duplicate source networks are not allowed')
return {'address': str(address), 'allowed_cidrs': [str(n) for n in networks], 'port': value['port']}
def load_settings(path):
return validate_settings(json.loads(path.read_text(encoding='utf-8')))
def proxy_config(settings):
value = validate_settings(settings)
address, port = value['address'], value['port']
allows = '\n '.join(f'allow {cidr};' for cidr in value['allowed_cidrs'])
origin = f'http://{address}:{port}'
return f'''worker_processes 1;
pid /tmp/nginx.pid;
error_log /dev/stderr warn;
events {{ worker_connections 256; }}
http {{
client_body_temp_path /tmp/client_body;
proxy_temp_path /tmp/proxy;
fastcgi_temp_path /tmp/fastcgi;
uwsgi_temp_path /tmp/uwsgi;
scgi_temp_path /tmp/scgi;
log_format limited '$remote_addr [$time_local] "$request_method $uri $server_protocol" $status $body_bytes_sent';
access_log /dev/stdout limited;
map $http_origin $origin_allowed {{ default 0; "" 1; "{origin}" 1; }}
map $http_sec_fetch_site $cross_site {{ default 0; cross-site 1; }}
server {{
listen 8080;
server_name {address};
{allows}
deny all;
if ($host != {address}) {{ return 403; }}
if ($origin_allowed = 0) {{ return 403; }}
if ($cross_site = 1) {{ return 403; }}
if ($arg_authtoken != "") {{ return 403; }}
client_max_body_size 1m;
keepalive_timeout 30s;
send_timeout 30s;
location / {{
proxy_pass http://127.0.0.1:4440;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host {address}:{port};
proxy_set_header X-Forwarded-Host {address}:{port};
proxy_set_header X-Forwarded-Port {port};
proxy_set_header X-Forwarded-Proto http;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Forwarded "";
proxy_set_header X-Forwarded-Uuid xas-intranet;
proxy_set_header X-Forwarded-Roles xas-operator;
proxy_set_header X-Forwarded-User-FirstName "";
proxy_set_header X-Forwarded-User-LastName "";
proxy_set_header X-Forwarded-User-Email "";
proxy_set_header Authorization "";
proxy_set_header X-Rundeck-Auth-Token "";
proxy_buffering off;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 30s;
}}
}}
}}
'''