v0.24.13
This commit is contained in:
1 parent
bd077754a9
commit
ed7e3377c1
40 files changed
+1764
-365
No files matched your search
Executable
+124
@@ -0,0 +1,124 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
admin=ROOT/'src/admin'
|
||||
router=(admin/'router.rs').read_text()
|
||||
main=router.split('fn admin_app_javascript()',1)[1].split('fn admin_vhosts_javascript()',1)[0]
|
||||
web=admin/'web'
|
||||
vhosts=['core.js','protocol.js','health.js','rates.js','editor.js','render.js']
|
||||
chunk='\n'.join((web/'vhosts'/f).read_text() for f in vhosts)
|
||||
app=['"use strict";', 'const ADMIN_DASHBOARD_DEFAULTS = Object.freeze('+ (web/'dashboard-defaults.json').read_text()+');']
|
||||
parts=re.findall(r'include_str!\("(web/[^"\n]+)"\)|(?<!fn )admin_vhosts_javascript\(\)', main)
|
||||
# Regex gets extra empty for vhosts function
|
||||
for line in parts:
|
||||
if line:
|
||||
if line=='web/dashboard-defaults.json': continue
|
||||
if line=='web/dashboard-widgets.json':
|
||||
app.append('const ADMIN_DASHBOARD_WIDGET_REGISTRY = Object.freeze('+ (admin/line).read_text()+');')
|
||||
else: app.append((admin/line).read_text())
|
||||
else: app.append('(()=>{\n'+chunk+'\n})();')
|
||||
js='\n'.join(app)
|
||||
startup='if (document.readyState === "complete") startAdminPanel();\nelse document.addEventListener("DOMContentLoaded", startAdminPanel, { once: true });'
|
||||
assert startup in js, 'runtime script bootstrap not found'
|
||||
js=js.replace(startup,'// Browser mock harness: startup disabled')
|
||||
# App JS is reconstructed in the same module order as router.rs.
|
||||
|
||||
html=(web/'index.html').read_text()
|
||||
for marker,rel,split in [('RP_METRICS_MAIN','partials/overview-metrics.html','<!-- RP_METRICS_SPLIT -->'),('RP_CHARTS_MAIN','partials/overview-charts.html','<!-- RP_CHARTS_SPLIT -->')]:
|
||||
raw=(web/rel).read_text(); one,two=raw.split(split)
|
||||
html=html.replace(f'<!-- {marker} -->',one).replace(f'<!-- {marker.replace("MAIN","WEB")} -->',two)
|
||||
html=re.sub(r'<script[^>]*src="/assets/app_[^\"]+"[^>]*></script>', '', html)
|
||||
html=re.sub(r'<link[^>]*href="/assets/style_[^\"]+"[^>]*>', '', html)
|
||||
html=html.replace('__RUSTPROXY_VERSION__','0.34.13')
|
||||
|
||||
mock_cfg={
|
||||
'global_security': {'user_agent_blocking':{'enabled':True,'patterns':['bad-bot','curl/*']},'country_access':{'mode':'block','countries':['RU']},'acl':None,'method_policy':{'mode':'block','methods':['TRACE']},'content_type_policy':{'enabled':False,'blocked':[]},'rate_limit':{'enabled':False,'requests':100,'window_seconds':60,'burst':20},'security_headers':{'hsts':{'enabled':False,'max_age_seconds':31536000,'include_subdomains':False,'preload':False},'nosniff':True,'frame_options':'deny','referrer_policy':None,'content_security_policy':None,'permissions_policy':None}},
|
||||
'geoip_updates':{'enabled':False,'format':'csv_pair','ipv4_url':'','ipv6_url':'','mmdb_url':'','max_bytes':1000000,'interval_seconds':86400},
|
||||
'logging': {'capture_user_agent': True, 'capture_country': False,'file':{'enabled':False},'loki':{'enabled':False},'logstash':{'enabled':False},'format':{}},
|
||||
'error_log':{'enabled':False,'path':'/var/log/rustproxy/error.log','format':'JSON'},
|
||||
'cache':{},'transform':{},'compression':{},'server_header':{},'diagnostic_headers':{},
|
||||
'vhosts':[],'redirects':[],'streams':[],'not_found_hosts':[],'host_policies':[],'acl_profiles':[],'listeners':{},'tls':{},
|
||||
}
|
||||
with sync_playwright() as p:
|
||||
browser=p.chromium.launch(headless=True,executable_path='/usr/bin/chromium',args=['--no-sandbox','--disable-dev-shm-usage'])
|
||||
page=browser.new_page()
|
||||
page.set_default_timeout(4000)
|
||||
errors=[]
|
||||
page.on('pageerror',lambda exc: errors.append(str(exc)))
|
||||
page.set_content(html,wait_until='load')
|
||||
page.evaluate('''() => { window.__calls=[]; window.__mockFetch=async function(path,options={}) {
|
||||
const request={path:typeof path==='string'?path:path.url,method:options.method||'GET',body:options.body?JSON.parse(options.body):null};
|
||||
__calls.push(request);
|
||||
const reply=JSON.parse(JSON.stringify(envelope||{revision:0,config:{}}));
|
||||
if (request.method==='PUT') {
|
||||
if (request.path==='/api/config/global-security') reply.config.global_security=request.body.value;
|
||||
if (request.path==='/api/config/logging') {reply.config.logging=request.body.value.logging;reply.config.error_log=request.body.value.error_log;}
|
||||
if (request.path==='/api/config/geoip') reply.config.geoip_updates=request.body.value;
|
||||
reply.revision+=1;
|
||||
}
|
||||
if(request.method==='POST' && request.path==='/api/config/geoip/update')return new Response(JSON.stringify({outcome:'current'}),{status:200,headers:{'content-type':'application/json'}});
|
||||
return new Response(JSON.stringify(reply),{status:200,headers:{'content-type':'application/json'}});
|
||||
}; window.fetch=window.__mockFetch; }''')
|
||||
try:
|
||||
page.add_script_tag(content=js)
|
||||
except Exception as e:
|
||||
print('INIT ERRORS',errors)
|
||||
raise
|
||||
print('script page errors:', errors[:8]); assert not errors, errors
|
||||
page.evaluate('''(config) => {authenticated=true; csrf='test'; envelope={revision:10,config};
|
||||
renderConfig=()=>{}; refresh=async()=>{}; renderSettings();}''',mock_cfg)
|
||||
assert not errors,errors
|
||||
print('initial global UA:',page.locator('#global-ua-block-patterns').input_value())
|
||||
# Global rules save: UA + header untouched
|
||||
page.locator('#global-ua-block-patterns').evaluate('(el)=>{el.value="bad-bot\\nvery-bad";el.dispatchEvent(new Event("input",{bubbles:true}));}')
|
||||
page.locator('#global-security-form').evaluate('(form)=>form.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}))')
|
||||
page.wait_for_function('window.__calls.length >= 1')
|
||||
result=page.evaluate('window.__calls.at(-1)')
|
||||
print('global rules save',result['path'],result['body']['value']['user_agent_blocking'],result['body']['value']['security_headers']['frame_options'])
|
||||
assert result['path']=='/api/config/global-security'
|
||||
assert result['body']['value']['user_agent_blocking']['patterns']==['bad-bot','very-bad']
|
||||
assert result['body']['value']['security_headers']['frame_options']=='deny'
|
||||
page.wait_for_timeout(200)
|
||||
# Global headers save: UA untouched, changed nosniff
|
||||
page.locator('#global-nosniff').evaluate('(el)=>{el.checked=false;el.dispatchEvent(new Event("change",{bubbles:true}));}')
|
||||
page.locator('#global-security-headers-form').evaluate('(form)=>form.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}))')
|
||||
page.wait_for_function('window.__calls.length >= 2')
|
||||
result=page.evaluate('window.__calls.at(-1)')
|
||||
print('global headers save',result['path'],result['body']['value']['user_agent_blocking'],result['body']['value']['security_headers']['nosniff'])
|
||||
assert result['body']['value']['user_agent_blocking']['patterns']==['bad-bot','very-bad']
|
||||
assert result['body']['value']['security_headers']['nosniff']==False
|
||||
page.wait_for_timeout(200)
|
||||
# GeoIP triggered as action not section save
|
||||
page.locator('#geoip-update-now').evaluate('(el)=>el.click()')
|
||||
page.wait_for_function('window.__calls.some(x=>x.method==="POST" && x.path==="/api/config/geoip/update")')
|
||||
result=page.evaluate('window.__calls.at(-1)')
|
||||
print('geoip update',result['method'],result['path'])
|
||||
assert result['path']=='/api/config/geoip/update'
|
||||
# Error-log subsection must save logging and error_log together.
|
||||
page.evaluate('renderSettings()')
|
||||
page.locator('#error-log-enabled').evaluate('(el)=>{el.checked=true;el.dispatchEvent(new Event("change",{bubbles:true}));}')
|
||||
page.locator('#error-log-form').evaluate('(form)=>form.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}))')
|
||||
page.wait_for_function('window.__calls.some(x=>x.path==="/api/config/logging" && x.method==="PUT")')
|
||||
result=page.evaluate('window.__calls.at(-1)')
|
||||
print('logging group save',result['path'],list(result['body']['value']))
|
||||
assert result['path']=='/api/config/logging'
|
||||
assert set(result['body']['value'])=={'logging','error_log'}
|
||||
assert result['body']['value']['error_log']['enabled']==True
|
||||
assert result['body']['value']['logging']['capture_user_agent']==True
|
||||
page.wait_for_timeout(120)
|
||||
# GeoIP settings are saved through its section, while update remains POST.
|
||||
page.evaluate('renderSettings()')
|
||||
page.locator('#geoip-interval-hours').evaluate('(el)=>{el.value="48";el.dispatchEvent(new Event("input",{bubbles:true}));}')
|
||||
page.locator('#geoip-form').evaluate('(form)=>form.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}))')
|
||||
page.wait_for_function('window.__calls.some(x=>x.path==="/api/config/geoip" && x.method==="PUT")')
|
||||
result=page.evaluate('window.__calls.at(-1)')
|
||||
print('geoip section save',result['path'],result['body']['value']['interval_seconds'])
|
||||
assert result['path']=='/api/config/geoip'
|
||||
assert result['body']['value']['interval_seconds']==48*3600
|
||||
assert result['body']['expected_revision']==13
|
||||
print('browser final errors',errors[:10]); assert not errors,errors
|
||||
browser.close()
|
||||
Reference in new issue
Block a user