Limit proxy body rewriting to URLs and guard legacy certificate repair
This commit is contained in:
parent
c354954b6d
commit
f6603a57ef
25
deploy.py
25
deploy.py
@ -38,6 +38,16 @@ def remove_legacy_wildcards(text):
|
||||
return text
|
||||
|
||||
|
||||
def update_legacy_fuc_certificate(text, directory):
|
||||
for directive, filename in [('ssl_certificate', 'fullchain.pem'), ('ssl_certificate_key', 'privkey.pem')]:
|
||||
old = '/etc/letsencrypt/live/tomshell.com.de/' + filename
|
||||
pattern = r'(?m)^(\s*' + directive + r'\s+)' + re.escape(old) + r'(\s*;)'
|
||||
text, count = re.subn(pattern, lambda m: m[1] + directory + '/' + filename + m[2], text)
|
||||
if count != 1:
|
||||
raise RuntimeError('Expected exactly one old fuc certificate directive; refusing ambiguous edit')
|
||||
return text
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('role', choices=['gateway', 'white'])
|
||||
@ -45,6 +55,7 @@ def main():
|
||||
parser.add_argument('--skip-legacy', action='store_true')
|
||||
parser.add_argument('--legacy-certificate', default='/etc/white-proxy-letsencrypt/live/tomshell.com.de')
|
||||
parser.add_argument('--expected-legacy-sha256')
|
||||
parser.add_argument('--expected-fuc-sha256')
|
||||
parser.add_argument('--white-ip', default='104.168.30.40')
|
||||
args = parser.parse_args()
|
||||
ipaddress.ip_address(args.white_ip)
|
||||
@ -86,9 +97,19 @@ def main():
|
||||
if not args.expected_legacy_sha256 or sha(current) != args.expected_legacy_sha256:
|
||||
parser.error('Legacy file needs an exact current hash before modifying wildcard blocks')
|
||||
changes[legacy_file] = remove_legacy_wildcards(current.decode()).encode()
|
||||
fuc_file = pathlib.Path('/etc/nginx/conf.d/fu.tomshell.my.conf')
|
||||
current = fuc_file.read_bytes()
|
||||
if str(fuc_file) in old_state['files']:
|
||||
if sha(current) != old_state['files'][str(fuc_file)]:
|
||||
parser.error('Legacy fuc file changed after previous deployment')
|
||||
changes[fuc_file] = current
|
||||
else:
|
||||
if not args.expected_fuc_sha256 or sha(current) != args.expected_fuc_sha256:
|
||||
parser.error('Legacy fuc certificate update needs an exact current file hash')
|
||||
changes[fuc_file] = update_legacy_fuc_certificate(current.decode(), args.legacy_certificate).encode()
|
||||
if args.stage == 'https':
|
||||
for path, data in changes.items():
|
||||
if path.name == 'master.tomshell.my.conf':
|
||||
if path.name in ('master.tomshell.my.conf', 'fu.tomshell.my.conf'):
|
||||
continue
|
||||
for cert in re.findall(rb'(?m)^\s*ssl_certificate\s+([^;]+);', data):
|
||||
subprocess.run(['openssl', 'x509', '-in', cert.decode(), '-checkend', '86400', '-noout'],
|
||||
@ -100,7 +121,7 @@ def main():
|
||||
parser.error('Legacy proxy requires root and wildcard SANs, not a single-host certificate')
|
||||
for path, data in changes.items():
|
||||
if path.exists() and str(path) not in old_state['files']:
|
||||
if path.name != 'master.tomshell.my.conf':
|
||||
if path.name not in ('master.tomshell.my.conf', 'fu.tomshell.my.conf'):
|
||||
raise RuntimeError('Unmanaged file already exists: ' + str(path))
|
||||
elif path.exists() and sha(path.read_bytes()) != old_state['files'].get(str(path)):
|
||||
raise RuntimeError('Managed file changed outside this deployment: ' + str(path))
|
||||
|
||||
28
render.py
28
render.py
@ -58,6 +58,28 @@ def http_server(names, redirect=True):
|
||||
'''
|
||||
|
||||
|
||||
def body_rewrites(source, alias, key):
|
||||
# Rewrite URL hosts, preserving plain brand names such as DP-LINK.NET.
|
||||
rules = f''' sub_filter_once off;
|
||||
sub_filter_types text/css application/javascript application/json;
|
||||
sub_filter 'https://$gateway_admin_origin_host' 'https://$host';
|
||||
sub_filter 'http://$gateway_admin_origin_host' 'https://$host';
|
||||
sub_filter '//$gateway_admin_origin_host' '//$host';
|
||||
'''
|
||||
if key in ('new', 'old'):
|
||||
rules += f''' sub_filter 'https://{source}' 'https://{alias}';
|
||||
sub_filter 'http://{source}' 'https://{alias}';
|
||||
sub_filter '//{source}' '//{alias}';
|
||||
sub_filter '.{source}/' '.{alias}/';
|
||||
'''
|
||||
elif key == 'fuc-site':
|
||||
rules += ''' sub_filter 'https://福云.com' 'https://fuc.tomtojapan.online';
|
||||
sub_filter 'http://福云.com' 'https://fuc.tomtojapan.online';
|
||||
sub_filter '//福云.com' '//fuc.tomtojapan.online';
|
||||
'''
|
||||
return rules
|
||||
|
||||
|
||||
def https_server(names, certificate, content, cloudflare=False, white_ip=None, key_name='privatekey.pem'):
|
||||
trusted = f' include {CF_TRUST};\n' if cloudflare else ''
|
||||
if white_ip:
|
||||
@ -155,17 +177,13 @@ map "$host|$http_referer" $gateway_admin_referer {
|
||||
extras = f''' proxy_redirect ~^https?://([a-z0-9-]+\\.)?{source_regex}(/.*)$ https://$1{alias}$2;
|
||||
proxy_cookie_domain {source} {alias};
|
||||
'''
|
||||
elif key == 'fuc-site':
|
||||
extras = " sub_filter '福云.com' 'fuc.tomtojapan.online';\n"
|
||||
location = f''' resolver 1.1.1.1 1.0.0.1 valid=300s ipv6=off;
|
||||
resolver_timeout 5s;
|
||||
if ($gateway_admin_origin_host = '') {{ return 444; }}
|
||||
location / {{
|
||||
{proxy_lines('$gateway_admin_origin_host', '$gateway_admin_origin_host', '$gateway_admin_origin_host', alias)}{extras}
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once off;
|
||||
sub_filter_types text/css application/javascript application/json;
|
||||
sub_filter '{source}' '{alias}';
|
||||
{body_rewrites(source, alias, key)}
|
||||
}}
|
||||
'''
|
||||
data += https_server(names, cert, location, cloudflare=cf,
|
||||
|
||||
@ -22,6 +22,23 @@ with tempfile.TemporaryDirectory(prefix='gateway-proxy-maps-') as folder:
|
||||
sock.bind(('127.0.0.1', 0))
|
||||
port = sock.getsockname()[1]
|
||||
maps = render.render_white('https', '/unused')['00-gateway-admin-proxy-maps.conf']
|
||||
bodies = [
|
||||
('dpl', 'dpl.tomtojapan.online', 'dp-link.net', 'dpl.tomtojapan.online', 'dpl-site', 'DP-LINK.NET'),
|
||||
('fuc', 'fuc.tomtojapan.online', 'xn--9kqy16h.com', 'fuc.tomtojapan.online', 'fuc-site', '福云.com'),
|
||||
('new', 'getway-dpl.tomshell.online', 'gemcook.net', 'tomshell.online', 'new', 'GEMCOOK.NET'),
|
||||
('old', 'nasa.tomshell.com.de', 'diaodu.us', 'tomshell.com.de', 'old', 'DIAODU.US'),
|
||||
]
|
||||
locations = ''
|
||||
for name, host, source, alias, key, brand in bodies:
|
||||
upstream = ('getway-dpl.gemcook.net' if key == 'new' else
|
||||
'nasa.diaodu.us' if key == 'old' else source)
|
||||
body = f'<title>{brand}</title><a href="https://{upstream}/api?q=1">link</a><img src="//{upstream}/logo">'
|
||||
locations += f''' location /body/{name} {{
|
||||
default_type text/html;
|
||||
{render.body_rewrites(source, alias, key)}
|
||||
return 200 '{body}';
|
||||
}}
|
||||
'''
|
||||
config = root / 'nginx.conf'
|
||||
config.write_text(f'''daemon off;
|
||||
pid {root}/nginx.pid;
|
||||
@ -35,6 +52,7 @@ http {{
|
||||
location / {{
|
||||
return 200 "$gateway_admin_origin_host|$gateway_admin_origin|$gateway_admin_referer";
|
||||
}}
|
||||
{locations}
|
||||
}}
|
||||
}}
|
||||
''')
|
||||
@ -75,6 +93,14 @@ http {{
|
||||
assert actual[1] == ('https://' + upstream if own else origin), (host, origin, actual[1])
|
||||
assert actual[2] == ('https://' + upstream + '/example?keep=1' if own else referer)
|
||||
assertions += 3
|
||||
for name, host, source, alias, key, brand in bodies:
|
||||
req = urllib.request.Request(f'http://127.0.0.1:{port}/body/{name}', headers={'Host': host})
|
||||
with urllib.request.urlopen(req, timeout=3) as response:
|
||||
actual = response.read().decode()
|
||||
assert f'<title>{brand}</title>' in actual, (name, actual)
|
||||
assert f'https://{host}/api?q=1' in actual, (name, actual)
|
||||
assert f'//{host}/logo' in actual, (name, actual)
|
||||
assertions += 3
|
||||
print(f'PASS: {assertions} real-Nginx map/header checks; localhost fixture only, no origin requests')
|
||||
finally:
|
||||
process.terminate()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user