docs: consolidate repository guides and preserve audit history
This commit is contained in:
@@ -1,18 +0,0 @@
|
||||
// Copy to .artifacts/b02/playwright.offline.config.ts and run from job-tracker-ui.
|
||||
import base from "../../job-tracker-ui/playwright.config";
|
||||
import path from "node:path";
|
||||
|
||||
export default {
|
||||
...base,
|
||||
testDir: path.join(process.cwd(), "e2e"),
|
||||
use: { ...base.use, launchOptions: { args: ["--no-proxy-server", "--host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE localhost, EXCLUDE 127.0.0.1"] } },
|
||||
webServer: (base.webServer as Array<Record<string, unknown>>).map(server => ({
|
||||
...server, cwd: process.cwd(), env: {
|
||||
...(server.env as Record<string, string>),
|
||||
Auth__GoogleClientId: "", Auth__MicrosoftClientId: "", Email__Enabled: "false",
|
||||
Ai__BaseUrl: "http://127.0.0.1:1", Stripe__SecretKey: "", Backups__Enabled: "false",
|
||||
Workers__AiOperationsEnabled: "false", Workers__RulesEnabled: "false", Workers__FollowUpRemindersEnabled: "false",
|
||||
Workers__DailyExportEnabled: "false", Workers__JobEnrichmentEnabled: "false"
|
||||
}
|
||||
}))
|
||||
};
|
||||
@@ -1,84 +0,0 @@
|
||||
geo $realip_remote_addr $ingress_peer_trusted {
|
||||
default 0;
|
||||
192.0.2.1/32 1;
|
||||
2001:db8::1/128 1;
|
||||
}
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive off;
|
||||
set_real_ip_from 192.0.2.1/32;
|
||||
set_real_ip_from 2001:db8::1/128;
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name "";
|
||||
|
||||
location = /health {
|
||||
access_log off;
|
||||
default_type text/plain;
|
||||
return 200 "ok\n";
|
||||
}
|
||||
|
||||
location / {
|
||||
return 444;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name jobs.example.test;
|
||||
|
||||
if ($ingress_peer_trusted = 0) { return 403; }
|
||||
if ($http_x_forwarded_proto != "https") { return 400; }
|
||||
if ($http_x_forwarded_for = "") { return 400; }
|
||||
if ($http_x_forwarded_for ~ ",") { return 400; }
|
||||
# A malformed address is not forwarded verbatim: realip leaves the socket IP intact.
|
||||
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
|
||||
location = /health {
|
||||
proxy_pass http://backend-web:8080/health;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host jobs.example.test;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
# realip resolves one sanitized address only from an explicitly trusted socket peer.
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Forwarded-Host "";
|
||||
proxy_set_header Forwarded "";
|
||||
}
|
||||
|
||||
location = /ready {
|
||||
proxy_pass http://backend-web:8080/ready;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host jobs.example.test;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Forwarded-Host "";
|
||||
proxy_set_header Forwarded "";
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://backend-web:8080;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host jobs.example.test;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Forwarded-Host "";
|
||||
proxy_set_header Forwarded "";
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
"""Bounded, read-only B06 observations; no credentials, cookies or response bodies retained."""
|
||||
import concurrent.futures
|
||||
import datetime
|
||||
import json
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import ssl
|
||||
import subprocess
|
||||
|
||||
OUT = Path(__file__).resolve().parent
|
||||
HOST = "jobs.cesnimda.uk" # .env.example canonical production hostname
|
||||
SAFE_HEADERS = {"server", "location", "content-type", "content-length", "strict-transport-security",
|
||||
"x-content-type-options", "x-frame-options", "referrer-policy", "permissions-policy"}
|
||||
|
||||
|
||||
def request(name, url, headers=()):
|
||||
command = ["curl.exe", "--noproxy", "*", "--connect-timeout", "4", "--max-time", "8",
|
||||
"--silent", "--show-error", "--dump-header", "-", "--output", "NUL",
|
||||
"--write-out", "\nB06_META %{json}\n", url]
|
||||
for header in headers:
|
||||
command.extend(["--header", header])
|
||||
result = subprocess.run(command, capture_output=True, text=True, timeout=12)
|
||||
raw_headers, _, metadata = result.stdout.partition("B06_META ")
|
||||
safe = []
|
||||
for line in raw_headers.splitlines():
|
||||
if line.startswith("HTTP/") or line.partition(":")[0].lower() in SAFE_HEADERS:
|
||||
safe.append(line)
|
||||
try:
|
||||
info = json.loads(metadata)
|
||||
meta = {key: info.get(key) for key in ("http_code", "remote_ip", "remote_port", "local_ip",
|
||||
"ssl_verify_result", "time_total", "exitcode")}
|
||||
except ValueError:
|
||||
meta = {}
|
||||
return {"name": name, "command": command, "exit": result.returncode, "headers": safe,
|
||||
"metadata": meta, "stderr": result.stderr.strip()}
|
||||
|
||||
|
||||
def certificate():
|
||||
try:
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((HOST, 443), timeout=8) as tcp:
|
||||
with context.wrap_socket(tcp, server_hostname=HOST) as tls:
|
||||
return {"verified": True, "peer": tls.getpeername(), "tls": tls.version(),
|
||||
"certificate": tls.getpeercert()}
|
||||
except Exception as error:
|
||||
return {"verified": False, "error": str(error)}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
addresses = sorted({item[4][0] for item in socket.getaddrinfo(HOST, None, type=socket.SOCK_STREAM)})
|
||||
cases = [
|
||||
("public_https", f"https://{HOST}/api/auth/config", []),
|
||||
("public_http", f"http://{HOST}/api/auth/config", []),
|
||||
("public_https_forged_forwarding", f"https://{HOST}/api/auth/config",
|
||||
["X-Forwarded-For: 198.51.100.10", "X-Forwarded-Proto: http", "X-Forwarded-Host: attacker.example.test"]),
|
||||
]
|
||||
for address in addresses:
|
||||
literal = f"[{address}]" if ":" in address else address
|
||||
for name, headers in [
|
||||
("none", []),
|
||||
("xff", ["X-Forwarded-For: 198.51.100.10"]),
|
||||
("proto", ["X-Forwarded-Proto: https"]),
|
||||
("host", ["Host: attacker.example.test"]),
|
||||
("combined", ["X-Forwarded-For: 198.51.100.10", "X-Forwarded-Proto: https",
|
||||
"X-Forwarded-Host: attacker.example.test"]),
|
||||
("malformed_chain", ["X-Forwarded-For: not-an-ip, 198.51.100.10", "X-Forwarded-Proto: https"]),
|
||||
]:
|
||||
actual = headers if name == "host" else [f"Host: {HOST}"] + headers
|
||||
cases.append((f"direct_3000_{address}_{name}", f"http://{literal}:3000/api/auth/config", actual))
|
||||
for port in (5202, 8080):
|
||||
cases.append((f"direct_api_{address}_{port}", f"http://{literal}:{port}/health", [f"Host: {HOST}"]))
|
||||
# Three simultaneous harmless GETs at most; no login, mutations or quota exhaustion.
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as pool:
|
||||
observations = list(pool.map(lambda args: request(*args), cases))
|
||||
report = {"utc": datetime.datetime.now(datetime.timezone.utc).isoformat(), "hostname": HOST,
|
||||
"dns_addresses": addresses, "certificate": certificate(), "requests": observations,
|
||||
"vantage": "Connor Windows workstation; external independence/NAT hairpin not established",
|
||||
"limitations": ["No assertion of Traefik identity or upstream header values from public status alone",
|
||||
"Connection failure does not identify which firewall/NAT/listener rejected it"]}
|
||||
(OUT / "public-observations.json").write_text(json.dumps(report, indent=2), encoding="utf-8")
|
||||
print(json.dumps(report, indent=2))
|
||||
@@ -1,8 +0,0 @@
|
||||
Traceback (most recent call last):
|
||||
File "E:\Storage\Projects\Active\Job tracker\scripts\test-ingress-topology.py", line 126, in <module>
|
||||
main()
|
||||
File "E:\Storage\Projects\Active\Job tracker\scripts\test-ingress-topology.py", line 27, in main
|
||||
docker("info", "--format", "{{.ServerVersion}}")
|
||||
File "E:\Storage\Projects\Active\Job tracker\scripts\test-ingress-topology.py", line 18, in docker
|
||||
raise RuntimeError(result.stderr.strip())
|
||||
RuntimeError: failed to connect to the docker API at npipe:////./pipe/dockerDesktopLinuxEngine; check if the path is correct and if the daemon is running: open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified.
|
||||
Reference in New Issue
Block a user