Add self signed cert support to the test dashboard.

This commit is contained in:
Kalzu Rekku committed 2026-10-27 12:32:20 +02:00
1 parent ff16548585
commit be278b3c7c
3 files changed
+161 -14

No files matched your search

+124 -4
View File
@@ -1,18 +1,26 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Lightweight test server and transparent proxy for the Records API. Lightweight test server and transparent proxy for the Records API.
Serves index.html on http://127.0.0.1:8080 and adds CORS headers to Axum requests. Serves index.html on http://127.0.0.1:8080 and transparently proxies
requests to cluster instances, bypassing self-signed TLS certificate checks.
""" """
from http.server import SimpleHTTPRequestHandler, HTTPServer from http.server import SimpleHTTPRequestHandler, HTTPServer
import urllib.request import urllib.request
import urllib.error import urllib.error
import urllib.parse
import ssl
import json
import os
PORT = 8080 PORT = int(os.environ.get("DASHBOARD_PORT", 8080))
# Create an SSL context that ignores self-signed certificate errors
SSL_CTX = ssl._create_unverified_context()
class ProxyHandler(SimpleHTTPRequestHandler): class ProxyHandler(SimpleHTTPRequestHandler):
def end_headers(self): def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, HEAD')
self.send_header('Access-Control-Allow-Headers', '*') self.send_header('Access-Control-Allow-Headers', '*')
super().end_headers() super().end_headers()
@@ -20,11 +28,123 @@ class ProxyHandler(SimpleHTTPRequestHandler):
self.send_response(204) self.send_response(204)
self.end_headers() self.end_headers()
def do_GET(self):
if self.path.startswith('/proxy'):
self.handle_proxy('GET')
else:
super().do_GET()
def do_HEAD(self):
if self.path.startswith('/proxy'):
self.handle_proxy('HEAD')
else:
super().do_HEAD()
def do_POST(self):
if self.path.startswith('/proxy'):
self.handle_proxy('POST')
else:
self.send_error(405, "Method Not Allowed")
def do_PUT(self):
if self.path.startswith('/proxy'):
self.handle_proxy('PUT')
else:
self.send_error(405, "Method Not Allowed")
def do_DELETE(self):
if self.path.startswith('/proxy'):
self.handle_proxy('DELETE')
else:
self.send_error(405, "Method Not Allowed")
def extract_target_url(self):
parsed = urllib.parse.urlparse(self.path)
if parsed.path.rstrip('/') == '/proxy':
if '?url=' in self.path:
return urllib.parse.unquote(self.path.split('?url=', 1)[1])
qs = urllib.parse.parse_qs(parsed.query)
return qs.get('url', [None])[0]
elif parsed.path.startswith('/proxy/'):
rest = self.path[len('/proxy/'):]
if rest.startswith('http://') or rest.startswith('https://'):
return rest
parts = rest.split('/', 1)
host_or_port = parts[0]
subpath = '/' + parts[1] if len(parts) > 1 else ''
if parsed.query:
subpath += '?' + parsed.query
if host_or_port.isdigit():
return f"https://127.0.0.1:{host_or_port}{subpath}"
return f"https://{host_or_port}{subpath}"
return None
def handle_proxy(self, method):
target_url = self.extract_target_url()
if not target_url:
self.send_response(400)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({'error': 'Missing or invalid target URL'}).encode())
return
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length) if content_length > 0 else None
# Filter hop-by-hop headers and compression to ensure clean raw data transfer
req_headers = {}
for k, v in self.headers.items():
if k.lower() in ('host', 'content-length', 'connection', 'transfer-encoding', 'accept-encoding'):
continue
req_headers[k] = v
req = urllib.request.Request(target_url, data=body, headers=req_headers, method=method)
try:
with urllib.request.urlopen(req, context=SSL_CTX, timeout=15) as resp:
status_code = resp.status
resp_headers = resp.getheaders()
resp_body = resp.read() if method != 'HEAD' else b''
except urllib.error.HTTPError as e:
status_code = e.code
resp_headers = e.headers.items()
resp_body = e.read() if method != 'HEAD' else b''
except urllib.error.URLError as e:
self.send_response(502)
self.send_header('Content-Type', 'application/json')
self.end_headers()
err_msg = json.dumps({'error': f'Failed to connect to backend: {e.reason}'}).encode()
self.wfile.write(err_msg)
return
except Exception as e:
self.send_response(500)
self.send_header('Content-Type', 'application/json')
self.end_headers()
err_msg = json.dumps({'error': f'Proxy exception: {str(e)}'}).encode()
self.wfile.write(err_msg)
return
self.send_response(status_code)
for k, v in resp_headers:
if k.lower() in ('transfer-encoding', 'connection', 'content-length',
'access-control-allow-origin', 'access-control-allow-methods',
'access-control-allow-headers'):
continue
self.send_header(k, v)
if method != 'HEAD':
self.send_header('Content-Length', str(len(resp_body)))
self.end_headers()
if method != 'HEAD' and resp_body:
self.wfile.write(resp_body)
if __name__ == '__main__': if __name__ == '__main__':
print(f"🚀 Dashboard running at http://127.0.0.1:{PORT}") print(f"🚀 Dashboard running at http://127.0.0.1:{PORT}")
print(f"🔒 TLS unverified proxy active at /proxy?url=<target>")
print("Press Ctrl+C to stop.") print("Press Ctrl+C to stop.")
server = HTTPServer(('127.0.0.1', PORT), ProxyHandler) server = HTTPServer(('127.0.0.1', PORT), ProxyHandler)
try: try:
server.serve_forever() server.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nStopping...") print("\nStopping...")
+31 -2
View File
@@ -176,16 +176,21 @@
<div style="display:flex; gap: 20px; align-items:center; flex-wrap:wrap; font-size:0.85rem;"> <div style="display:flex; gap: 20px; align-items:center; flex-wrap:wrap; font-size:0.85rem;">
<div style="flex:1; min-width:240px;"> <div style="flex:1; min-width:240px;">
<label>Primary Base URL:</label> <label>Primary Base URL:</label>
<input type="text" id="primaryUrl" value="http://127.0.0.1:3000" style="margin-bottom:0;" /> <input type="text" id="primaryUrl" value="https://127.0.0.1:3000" style="margin-bottom:0;" />
</div> </div>
<div style="flex:1; min-width:240px;"> <div style="flex:1; min-width:240px;">
<label>Replica Base URL:</label> <label>Replica Base URL:</label>
<input type="text" id="replicaUrl" value="http://127.0.0.1:3001" style="margin-bottom:0;" /> <input type="text" id="replicaUrl" value="https://127.0.0.1:3001" style="margin-bottom:0;" />
</div> </div>
<div style="flex:1; min-width:180px;"> <div style="flex:1; min-width:180px;">
<label>Replication Bearer Token:</label> <label>Replication Bearer Token:</label>
<input type="password" id="replToken" value="change-me" style="margin-bottom:0;" /> <input type="password" id="replToken" value="change-me" style="margin-bottom:0;" />
</div> </div>
<div style="min-width:220px; display:flex; align-items:flex-end;">
<label style="font-size:0.8rem; display:flex; align-items:center; gap:6px; cursor:pointer; padding-bottom:6px; color:var(--success);">
<input type="checkbox" id="trustAllCerts" checked style="width:auto; margin:0;" /> 🛡️ Trust Self-Signed Certs (Proxy)
</label>
</div>
</div> </div>
</div> </div>
@@ -286,6 +291,30 @@
</div> </div>
<script> <script>
// Transparently proxy external/HTTPS instance calls via dashboard proxy when self-signed cert trust is active
const nativeFetch = window.fetch;
window.fetch = function(input, init) {
const trustAll = document.getElementById('trustAllCerts')?.checked ?? true;
if (trustAll) {
const urlStr = typeof input === 'string' ? input : (input && input.url ? input.url : null);
if (urlStr) {
try {
const parsed = new URL(urlStr, window.location.href);
if (parsed.origin !== window.location.origin) {
const proxyBase = (window.location.protocol === 'file:') ? 'http://127.0.0.1:8080' : '';
const proxiedUrl = `${proxyBase}/proxy?url=${encodeURIComponent(urlStr)}`;
if (typeof input === 'string') {
input = proxiedUrl;
} else {
input = new Request(proxiedUrl, input);
}
}
} catch (_) {}
}
}
return nativeFetch(input, init);
};
let primaryHeadSeq = 0; let primaryHeadSeq = 0;
let replicaHeadSeq = 0; let replicaHeadSeq = 0;
Regular → Executable
+6 -8
View File
@@ -51,14 +51,12 @@ for port in "$PRIMARY_PORT" "$REPLICA_PORT"; do
fi fi
done done
if [[ ! -x "$BIN" ]]; then if [[ -f "$ROOT_DIR/Cargo.toml" ]]; then
if [[ -f "$ROOT_DIR/Cargo.toml" ]]; then echo "Ensuring debug binary is up to date with cargo..."
echo "Binary not found at $BIN. Building debug binary with cargo..." ( cd "$ROOT_DIR" && cargo build )
( cd "$ROOT_DIR" && cargo build ) elif [[ ! -x "$BIN" ]]; then
else echo "Error: Binary not found or not executable: $BIN" >&2
echo "Error: Binary not found or not executable: $BIN" >&2 exit 1
exit 1
fi
fi fi
# --- Workspace & Schema Setup --- # --- Workspace & Schema Setup ---