fix: enforce server trust boundaries

This commit is contained in:
2026-08-05 11:42:35 +02:00
parent 96fe5bfa79
commit a94affd6ac
10 changed files with 511 additions and 40 deletions
+62 -4
View File
@@ -100,6 +100,41 @@ sed '/^case "\$mode" in/,$d' "$root/scripts/verify-workspace-install-docs.sh" >"
# shellcheck source=/dev/null
source "$verifier_functions"
adapted_reorder="$negative_root/caddy-adapted-reorder.json"
node - "$adapted_reorder" <<'NODE'
const fs = require("fs");
const publicHeaders = [
"X-Thoth-Principal-Issuer", "X-Thoth-Principal-Subject",
"X-Thoth-Principal-Display-Name", "X-Thoth-Is-Admin",
];
const trustedHeaders = [
"X-Thoth-Trusted-Principal-Issuer", "X-Thoth-Trusted-Principal-Subject",
"X-Thoth-Trusted-Principal-Display-Name", "X-Thoth-Trusted-Is-Admin",
];
const clear = (name) => ({handler: "headers", request: {delete: [name]}});
const auth = {
handler: "reverse_proxy", upstreams: [{dial: "auth-gateway:4180"}],
handle_response: [{match: {status_code: [2]}, routes: [{handle: trustedHeaders.map((name, index) => ({
handler: "headers", request: {set: {[name]: [`{http.reverse_proxy.header.${publicHeaders[index]}}`]}},
}))}]}],
};
const document = {routes: [{handle: [
...publicHeaders.map(clear), auth, ...trustedHeaders.map(clear),
{handler: "reverse_proxy", upstreams: [{dial: "127.0.0.1:8080"}]},
]}]};
fs.writeFileSync(process.argv[2], JSON.stringify(document));
NODE
adapted_output="$negative_root/caddy-adapted-output"
set +e
verify_caddy_adapted_identity_order "$adapted_reorder" >"$adapted_output" 2>&1
adapted_status=$?
set -e
if [[ $adapted_status -eq 0 ]] || ! grep -Fq "Caddy adapted identity clears must execute before authentication" "$adapted_output"; then
echo "Caddy reordered adapted-handler fixture was not rejected correctly" >&2
cat "$adapted_output" >&2
exit 1
fi
negative_failures=0
expect_guide_rejected() {
local label="$1" validator="$2" source_guide="$3" relative_path="$4"
@@ -175,6 +210,14 @@ switch (mutation) {
case "nginx-no-admin-map":
changed = original.replace("proxy_set_header X-Thoth-Trusted-Is-Admin $thoth_is_admin;", "proxy_set_header X-Thoth-Trusted-Is-Admin \"\";");
break;
case "nginx-admin-clear-wrong-scope": {
const clear = ' proxy_set_header X-Thoth-Is-Admin "";';
const authAt = original.indexOf(clear);
changed = original.slice(0, authAt) + original.slice(authAt + clear.length + 1);
const frontendAt = changed.indexOf(clear);
changed = changed.slice(0, frontendAt) + clear + "\n" + clear + changed.slice(frontendAt + clear.length);
break;
}
case "caddy-no-auth":
changed = original.replace("forward_auth auth-gateway:4180 {", "# forward authentication omitted");
break;
@@ -196,6 +239,13 @@ switch (mutation) {
case "caddy-no-admin-map":
changed = original.replace("X-Thoth-Is-Admin>X-Thoth-Trusted-Is-Admin", "X-Thoth-Is-Admin");
break;
case "caddy-clears-after-auth": {
const clearPattern = /(?:\t\trequest_header -X-(?:Authenticated-User|Thoth-[^\n]+)\n)+/;
const clears = original.match(clearPattern)?.[0] || "";
changed = original.replace(clearPattern, "");
changed = changed.replace("\n\t\treverse_proxy 127.0.0.1:8080 {", "\n" + clears + "\n\t\treverse_proxy 127.0.0.1:8080 {");
break;
}
case "dirty-source":
changed = original.replaceAll("git status --porcelain --untracked-files=all", "git status --short");
break;
@@ -310,19 +360,23 @@ expect_guide_rejected \
expect_guide_rejected \
"Nginx issuer inbound claim not cleared" verify_reverse_proxy_nginx_guide \
"$root/docs/install/reverse-proxy-nginx.md" docs/install/reverse-proxy-nginx.md nginx-no-issuer-clear \
"Nginx proxy does not clear inbound issuer identity"
"Nginx auth location does not clear inbound issuer identity"
expect_guide_rejected \
"Nginx subject auth response not captured" verify_reverse_proxy_nginx_guide \
"$root/docs/install/reverse-proxy-nginx.md" docs/install/reverse-proxy-nginx.md nginx-no-subject-capture \
"Nginx proxy does not capture authenticated subject identity"
"Nginx frontend location does not capture authenticated subject identity"
expect_guide_rejected \
"Nginx display identity not mapped to private hop" verify_reverse_proxy_nginx_guide \
"$root/docs/install/reverse-proxy-nginx.md" docs/install/reverse-proxy-nginx.md nginx-no-display-map \
"Nginx proxy does not map authenticated display identity"
"Nginx frontend location does not map authenticated display identity"
expect_guide_rejected \
"Nginx admin identity not mapped to private hop" verify_reverse_proxy_nginx_guide \
"$root/docs/install/reverse-proxy-nginx.md" docs/install/reverse-proxy-nginx.md nginx-no-admin-map \
"Nginx proxy does not map authenticated admin identity"
"Nginx frontend location does not map authenticated admin identity"
expect_guide_rejected \
"Nginx admin clear moved out of auth scope" verify_reverse_proxy_nginx_guide \
"$root/docs/install/reverse-proxy-nginx.md" docs/install/reverse-proxy-nginx.md nginx-admin-clear-wrong-scope \
"Nginx auth location does not clear inbound admin identity"
expect_guide_rejected \
"Caddy identity without authentication" verify_reverse_proxy_caddy_guide \
"$root/docs/install/reverse-proxy-caddy.md" docs/install/reverse-proxy-caddy.md caddy-no-auth \
@@ -351,6 +405,10 @@ expect_guide_rejected \
"Caddy admin identity not mapped to private hop" verify_reverse_proxy_caddy_guide \
"$root/docs/install/reverse-proxy-caddy.md" docs/install/reverse-proxy-caddy.md caddy-no-admin-map \
"Caddy proxy does not map authenticated admin identity"
expect_guide_rejected \
"Caddy identity clears reordered after auth" verify_reverse_proxy_caddy_guide \
"$root/docs/install/reverse-proxy-caddy.md" docs/install/reverse-proxy-caddy.md caddy-clears-after-auth \
"Caddy identity clears must precede forward_auth"
expect_guide_rejected \
"dirty or untracked source tree" verify_local_guide \