fix: ignore commented nginx auth directives
This commit is contained in:
@@ -245,6 +245,27 @@ switch (mutation) {
|
|||||||
case "nginx-additional-bypass":
|
case "nginx-additional-bypass":
|
||||||
changed = original.replace(" location / {", " location /bypass {\n proxy_pass http://127.0.0.1:8080;\n }\n\n location / {");
|
changed = original.replace(" location / {", " location /bypass {\n proxy_pass http://127.0.0.1:8080;\n }\n\n location / {");
|
||||||
break;
|
break;
|
||||||
|
case "nginx-comment-only-auth":
|
||||||
|
changed = original.replace(" location / {", ` location /comment-only-auth {
|
||||||
|
# auth_request /_authenticate;
|
||||||
|
# auth_request_set $thoth_principal_issuer $upstream_http_x_thoth_principal_issuer;
|
||||||
|
# auth_request_set $thoth_principal_subject $upstream_http_x_thoth_principal_subject;
|
||||||
|
# auth_request_set $thoth_principal_display_name $upstream_http_x_thoth_principal_display_name;
|
||||||
|
# auth_request_set $thoth_is_admin $upstream_http_x_thoth_is_admin;
|
||||||
|
# proxy_set_header X-Thoth-Principal-Issuer "";
|
||||||
|
# proxy_set_header X-Thoth-Principal-Subject "";
|
||||||
|
# proxy_set_header X-Thoth-Principal-Display-Name "";
|
||||||
|
# proxy_set_header X-Thoth-Is-Admin "";
|
||||||
|
# proxy_set_header X-Thoth-Trusted-Principal-Issuer $thoth_principal_issuer;
|
||||||
|
# proxy_set_header X-Thoth-Trusted-Principal-Subject $thoth_principal_subject;
|
||||||
|
# proxy_set_header X-Thoth-Trusted-Principal-Display-Name $thoth_principal_display_name;
|
||||||
|
# proxy_set_header X-Thoth-Trusted-Is-Admin $thoth_is_admin;
|
||||||
|
proxy_set_header X-Comment-Literal "quoted#value"; # preserve the quoted hash
|
||||||
|
proxy_pass http://127.0.0.1:8080; # active frontend path
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {`);
|
||||||
|
break;
|
||||||
case "caddy-no-auth":
|
case "caddy-no-auth":
|
||||||
changed = original.replace("forward_auth auth-gateway:4180 {", "# forward authentication omitted");
|
changed = original.replace("forward_auth auth-gateway:4180 {", "# forward authentication omitted");
|
||||||
break;
|
break;
|
||||||
@@ -412,6 +433,10 @@ expect_guide_rejected \
|
|||||||
"Nginx additional frontend bypass location" verify_reverse_proxy_nginx_guide \
|
"Nginx additional frontend bypass location" verify_reverse_proxy_nginx_guide \
|
||||||
"$root/docs/install/reverse-proxy-nginx.md" docs/install/reverse-proxy-nginx.md nginx-additional-bypass \
|
"$root/docs/install/reverse-proxy-nginx.md" docs/install/reverse-proxy-nginx.md nginx-additional-bypass \
|
||||||
"Nginx frontend upstream location bypasses complete authentication contract"
|
"Nginx frontend upstream location bypasses complete authentication contract"
|
||||||
|
expect_guide_rejected \
|
||||||
|
"Nginx frontend auth directives only in comments" verify_reverse_proxy_nginx_guide \
|
||||||
|
"$root/docs/install/reverse-proxy-nginx.md" docs/install/reverse-proxy-nginx.md nginx-comment-only-auth \
|
||||||
|
"Nginx frontend upstream location bypasses complete authentication contract"
|
||||||
expect_guide_rejected \
|
expect_guide_rejected \
|
||||||
"Caddy identity without authentication" verify_reverse_proxy_caddy_guide \
|
"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 \
|
"$root/docs/install/reverse-proxy-caddy.md" docs/install/reverse-proxy-caddy.md caddy-no-auth \
|
||||||
|
|||||||
@@ -639,6 +639,48 @@ verify_reverse_proxy_nginx_guide() {
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const source = fs.readFileSync(process.argv[2], "utf8");
|
const source = fs.readFileSync(process.argv[2], "utf8");
|
||||||
const block = [...source.matchAll(/```nginx\n([\s\S]*?)```/g)].map((match) => match[1]).join("\n");
|
const block = [...source.matchAll(/```nginx\n([\s\S]*?)```/g)].map((match) => match[1]).join("\n");
|
||||||
|
function stripNginxComments(text) {
|
||||||
|
let effective = "";
|
||||||
|
let quote = null;
|
||||||
|
let escaped = false;
|
||||||
|
let comment = false;
|
||||||
|
for (const character of text) {
|
||||||
|
if (comment) {
|
||||||
|
if (character === "\n") {
|
||||||
|
effective += character;
|
||||||
|
comment = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (escaped) {
|
||||||
|
effective += character;
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (character === "\\") {
|
||||||
|
effective += character;
|
||||||
|
escaped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (quote !== null) {
|
||||||
|
effective += character;
|
||||||
|
if (character === quote) quote = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (character === '"' || character === "'") {
|
||||||
|
effective += character;
|
||||||
|
quote = character;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (character === "#") {
|
||||||
|
comment = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
effective += character;
|
||||||
|
}
|
||||||
|
return effective;
|
||||||
|
}
|
||||||
|
const effectiveBlock = stripNginxComments(block);
|
||||||
const tokens = [
|
const tokens = [
|
||||||
"listen 443 ssl;", "ssl_certificate ", "ssl_certificate_key ",
|
"listen 443 ssl;", "ssl_certificate ", "ssl_certificate_key ",
|
||||||
"location = /_authenticate {", "internal;", "proxy_pass http://auth-gateway:4180/verify;",
|
"location = /_authenticate {", "internal;", "proxy_pass http://auth-gateway:4180/verify;",
|
||||||
@@ -646,13 +688,13 @@ const tokens = [
|
|||||||
"proxy_http_version 1.1;", "proxy_buffering off;", "proxy_cache off;",
|
"proxy_http_version 1.1;", "proxy_buffering off;", "proxy_cache off;",
|
||||||
"proxy_read_timeout 3600s;",
|
"proxy_read_timeout 3600s;",
|
||||||
];
|
];
|
||||||
if (/127\.0\.0\.1:8787|\bcore:8787\b/.test(block) || !block.includes("http://127.0.0.1:8080")) {
|
if (/127\.0\.0\.1:8787|\bcore:8787\b/.test(effectiveBlock) || !effectiveBlock.includes("http://127.0.0.1:8080")) {
|
||||||
throw new Error("Nginx proxy must forward only to frontend on 127.0.0.1:8080");
|
throw new Error("Nginx proxy must forward only to frontend on 127.0.0.1:8080");
|
||||||
}
|
}
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
if (!block.includes(token)) throw new Error(`Nginx proxy lacks structural token: ${token}`);
|
if (!effectiveBlock.includes(token)) throw new Error(`Nginx proxy lacks structural token: ${token}`);
|
||||||
}
|
}
|
||||||
if (/proxy_set_header\s+X-Thoth-Trusted-[^;]+\$http_/i.test(block)) {
|
if (/proxy_set_header\s+X-Thoth-Trusted-[^;]+\$http_/i.test(effectiveBlock)) {
|
||||||
throw new Error("Nginx proxy trusts a client-supplied identity header");
|
throw new Error("Nginx proxy trusts a client-supplied identity header");
|
||||||
}
|
}
|
||||||
const identities = [
|
const identities = [
|
||||||
@@ -681,7 +723,7 @@ function nginxLocations(text) {
|
|||||||
}
|
}
|
||||||
return locations;
|
return locations;
|
||||||
}
|
}
|
||||||
const locations = nginxLocations(block);
|
const locations = nginxLocations(effectiveBlock);
|
||||||
const authLocations = locations.filter((location) => location.selector === "= /_authenticate");
|
const authLocations = locations.filter((location) => location.selector === "= /_authenticate");
|
||||||
if (authLocations.length !== 1) {
|
if (authLocations.length !== 1) {
|
||||||
throw new Error("Nginx proxy must define exactly one authentication location");
|
throw new Error("Nginx proxy must define exactly one authentication location");
|
||||||
|
|||||||
Reference in New Issue
Block a user