22f382b968
Reported: fallback SSL doesn't work in the Docker build. Reproduced — it's worse than the fallback specifically: TLS was broken for nearly every connection, including ones with no SNI at all: $ curl -vk https://127.0.0.1/ * TLSv1.3 (IN), TLS alert, internal error (592) * OpenSSL/3.0.13: error:0A000438:SSL routines::tlsv1 alert internal error Root cause: targetinfo.lua's M.get() is shared by two call sites in two incompatible nginx phases — - proxy.conf's access_by_lua_block (a normal HTTP request phase, where ngx.exit() is valid) - nginx.conf's request_domain callback, which runs during the TLS handshake itself (ssl_certificate_by_lua*), where ngx.exit() is NOT a supported API M.get() called ngx.exit() on every lookup failure (no domain/SNI, a Redis error, or an unregistered host). When invoked from the SSL phase, that aborted the handshake with a bare "internal error" alert and produced no log output anywhere — silent and total, not limited to the unregistered-domain case, since even a connection with no SNI hits the same code path immediately. Fix: M.get() no longer calls ngx.exit() itself — it returns (nil, httpStatus) on failure. proxy.conf now checks the return value and calls ngx.exit() itself (the phase where that's actually supported). nginx.conf's request_domain guards the now-possibly-nil result before indexing it, and leaves ngx.ctx.toAllow unset on failure so allow_domain() correctly denies issuance and auto-ssl falls through to the static fallback cert in autossl.conf. Verified end to end against a running Docker build (deployed the changed files into a live container and reloaded, rather than relying on a full rebuild each iteration): - No SNI at all: TLS now completes; HTTP layer correctly returns 406 (previously: broken handshake, no response at all) - Unregistered SNI: same — TLS completes, 406, and openssl s_client confirms the cert served is genuinely the fallback (CN=sni-support-required-for-valid-ssl) - A real registered Host: TLS completes and proxies through to the backend correctly (confirms the success path is unaffected) - npm test: 192/192 pass
107 lines
2.9 KiB
Plaintext
107 lines
2.9 KiB
Plaintext
map $http_upgrade $connection_upgrade {
|
|
default Upgrade;
|
|
'' close;
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
|
|
include autossl.conf;
|
|
|
|
set_real_ip_from 192.168.1.0/24;
|
|
real_ip_header X-Real-IP;
|
|
real_ip_recursive on;
|
|
|
|
# Per-host SSO endpoints (#57), served on EVERY proxied host by the nodejs app.
|
|
# This location deliberately sits OUTSIDE the auth gate in `location /` (so the
|
|
# login flow itself is never gated) and forwards to the app, which runs the
|
|
# OIDC flow and sets the __proxy_sso session cookie for this host.
|
|
location /__proxy_auth/ {
|
|
proxy_pass http://proxy_auth_backend;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
|
|
location / {
|
|
|
|
set $target '';
|
|
set $target_scheme 'http';
|
|
set $target_port '';
|
|
set $header_host $host;
|
|
set $skip_cache 1;
|
|
|
|
access_by_lua_block {
|
|
local targetInfo = require "targetinfo"
|
|
local hostfeatures = require "hostfeatures"
|
|
local host = ngx.var.host
|
|
local uri = ngx.var.uri
|
|
local scheme = ngx.var.scheme
|
|
local res, errCode = targetInfo.get(ngx, host, ngx.ctx.targetInfo)
|
|
if not res then
|
|
return ngx.exit(errCode or 500)
|
|
end
|
|
|
|
if scheme == "http" then
|
|
if res["forcessl"] == "true" then
|
|
return ngx.redirect("https://"..host..uri, 301)
|
|
end
|
|
end
|
|
|
|
if res["targetssl"] == "true" then
|
|
ngx.var.target_scheme = "https"
|
|
end
|
|
|
|
if res["host-pass-though"] == "false" then
|
|
ngx.var.header_host = res["ip"]
|
|
end
|
|
|
|
ngx.var.target = res["ip"]
|
|
ngx.var.target_port = res["targetPort"]
|
|
|
|
-- Per-host controls: IP allow/deny, rate limit, upstream headers, and
|
|
-- the $skip_cache gate. May ngx.exit() (403/429).
|
|
hostfeatures.access(ngx, res)
|
|
}
|
|
|
|
header_filter_by_lua_block {
|
|
require("hostfeatures").header(ngx)
|
|
}
|
|
|
|
|
|
resolver 192.168.1.1 ipv6=off; #8.8.4.4; # use Google's open DNS server
|
|
|
|
proxy_http_version 1.1;
|
|
proxy_pass_request_headers on;
|
|
|
|
# Response cache. Opt-in per host: $skip_cache is 0 only when the Host
|
|
# record sets respcache_enabled. Global default TTL (48h) lives in
|
|
# nginx.conf; upstream Cache-Control (private/no-store) is still honored.
|
|
proxy_cache proxycache;
|
|
proxy_cache_key $scheme$host$request_uri;
|
|
proxy_cache_bypass $skip_cache;
|
|
proxy_no_cache $skip_cache;
|
|
|
|
proxy_pass $target_scheme://$target:$target_port;
|
|
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection $connection_upgrade;
|
|
proxy_ssl_session_reuse on;
|
|
proxy_intercept_errors off;
|
|
|
|
proxy_set_header Host $header_host;
|
|
add_header X-Target-Host $target;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $target_scheme;
|
|
proxy_set_header Referer $target_scheme://$header_host;
|
|
proxy_set_header Accept-Language $http_accept_language;
|
|
proxy_set_header User-Agent $http_user_agent;
|
|
|
|
sub_filter $target $host;
|
|
sub_filter_once off;
|
|
}
|
|
}
|