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
114 lines
2.9 KiB
Nginx Configuration File
114 lines
2.9 KiB
Nginx Configuration File
#user nobody;
|
|
worker_processes 8;
|
|
|
|
#error_log logs/error.log;
|
|
#error_log logs/error.log notice;
|
|
#error_log logs/error.log info;
|
|
|
|
#pid logs/nginx.pid;
|
|
|
|
|
|
events {
|
|
worker_connections 1024;
|
|
}
|
|
|
|
http {
|
|
client_max_body_size 4g;
|
|
|
|
lua_shared_dict auto_ssl 100m;
|
|
lua_shared_dict auto_ssl_settings 64k;
|
|
|
|
# Per-host rate limiting (resty.limit.req) counter storage.
|
|
lua_shared_dict ratelimit 10m;
|
|
|
|
# Per-host response cache. Enabled per request via $skip_cache in proxy.conf;
|
|
# 48h is the default TTL when the upstream doesn't send its own Cache-Control.
|
|
# `inactive` must be >= the TTL or entries get evicted before they expire.
|
|
proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=proxycache:100m
|
|
max_size=2g inactive=48h use_temp_path=off;
|
|
proxy_cache_valid 200 301 302 48h;
|
|
|
|
resolver 8.8.4.4 8.8.8.8;
|
|
|
|
# Backend for per-host SSO endpoints (/__proxy_auth, see proxy.conf). Point
|
|
# this at the nodejs app that serves the admin UI. Default assumes it is
|
|
# colocated on this box; change the address for a split deployment.
|
|
upstream proxy_auth_backend {
|
|
server 127.0.0.1:3000;
|
|
}
|
|
|
|
init_by_lua_block {
|
|
|
|
auto_ssl = (require "resty.auto-ssl").new()
|
|
auto_ssl:set("storage_adapter", "resty.auto-ssl.storage_adapters.redis")
|
|
|
|
local targetInfo = require "targetinfo"
|
|
|
|
auto_ssl:set("allow_domain", function(domain)
|
|
return ngx.ctx.toAllow
|
|
end)
|
|
|
|
auto_ssl:set("request_domain", function(ssl, ssl_options)
|
|
local domain, err = ssl.server_name()
|
|
|
|
-- targetInfo.get() returns (nil, httpStatus) for an unknown/unregistered
|
|
-- domain rather than erroring -- that's expected here (e.g. a probe with
|
|
-- no matching Host record, or no SNI at all). ngx.ctx.toAllow is only
|
|
-- set on a successful lookup, so allow_domain() correctly denies
|
|
-- issuance and auto-ssl falls back to the static ssl_certificate in
|
|
-- autossl.conf.
|
|
local res = targetInfo.get(ngx, domain, ngx.ctx.targetInfo)
|
|
|
|
if res and res['wildcard_parent'] then
|
|
return res['wildcard_parent'], err
|
|
end
|
|
|
|
return domain, err
|
|
end)
|
|
|
|
auto_ssl:init()
|
|
}
|
|
|
|
init_worker_by_lua_block {
|
|
auto_ssl:init_worker()
|
|
}
|
|
|
|
ssl_session_cache shared:SSL:10m;
|
|
ssl_session_timeout 10m;
|
|
|
|
server {
|
|
listen 127.0.0.1:8999;
|
|
|
|
# Increase the body buffer size, to ensure the internal POSTs can always
|
|
# parse the full POST contents into memory.
|
|
client_body_buffer_size 128k;
|
|
client_max_body_size 128k;
|
|
|
|
location / {
|
|
content_by_lua_block {
|
|
auto_ssl:hook_server()
|
|
}
|
|
}
|
|
}
|
|
|
|
include mime.types;
|
|
default_type application/octet-stream;
|
|
|
|
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
|
# '$status $body_bytes_sent "$http_referer" '
|
|
# '"$http_user_agent" "$http_x_forwarded_for"';
|
|
|
|
access_log /var/log/nginx/access.log;
|
|
error_log /var/log/nginx/error.log;
|
|
|
|
sendfile on;
|
|
#tcp_nopush on;
|
|
|
|
#keepalive_timeout 0;
|
|
keepalive_timeout 65;
|
|
|
|
gzip on;
|
|
include sites-enabled/*;
|
|
|
|
}
|