- hostfeatures.lua: replace basic-auth-only enforcement with a combined
apply_auth() that allows if EITHER basic auth OR a valid SSO session passes.
A "Basic" Authorization header takes the basic path (401 on failure);
otherwise a browser is 302'd to /__proxy_auth/start. SSO sessions are read
straight from Redis (proxy_SsoSession_<sid>, sid from the __proxy_sso cookie,
character-restricted) and matched to the host.
- proxy.conf: add a /__proxy_auth/ location (outside the gate) that forwards to
the nodejs app so the OIDC flow can run and set the cookie on every host.
- nginx.conf: add the proxy_auth_backend upstream (defaults to 127.0.0.1:3000).
Needs live verification on an OpenResty box (no Lua/nginx runtime in CI here).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds opt-in basic auth per Host, following the existing per-host controls
pattern:
- Host fields basicauth_enabled / basicauth_realm / basicauth_users
({user: base64(sha1(pw))}). Credentials are parsed to plaintext by the pure
host_features normalizer and hashed at the route layer (utils/basicauth.js),
so plaintext never reaches Redis.
- ops/nginx_conf/hostfeatures.lua enforces it in access phase: verifies the
Authorization header against base64(sha1(password)), fails closed with a 401
WWW-Authenticate challenge.
- hosts.ejs gains an enable toggle, realm, and a username:password textarea
(passwords never echoed back; blank keeps the current set).
Unit tests cover hashing (matches the htpasswd {SHA} vector), credential
parsing, and normalization. Note: the Lua path needs verification on a live
OpenResty box.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump proxy_cache_valid from 10m to 48h and match proxy_cache_path
`inactive` (must be >= the TTL or entries evict before expiring).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every proxied request flows through one shared OpenResty location whose
behavior is chosen at request time from the host's Redis hash. Add per-host
controls as new Host fields enforced in Lua rather than static nginx config
(which can't key off a per-request variable):
- Rate limiting: per-client-IP token bucket via resty.limit.req
(ratelimit_enabled/rate/burst), backed by a new `ratelimit` shared dict.
- Response caching: opt-in per host via a global proxy_cache zone gated by
$skip_cache (respcache_enabled). Off by default; upstream Cache-Control
still honored.
- Custom/security headers: req_headers (upstream) + resp_headers (client) and
hsts_enabled, applied in access/header_filter phases.
- IP allow/deny CIDR lists via resty.ipmatcher (deny wins; non-empty allow is
default-deny).
New ops/nginx_conf/hostfeatures.lua holds the enforcement; proxy.conf's
access_by_lua string becomes a block that calls it, plus a header_filter block.
nodejs/utils/host_features.js is the pure, unit-tested normalize/validate layer
(header/CIDR parsing, range clamping, injection-safe values) applied in
routes/host.js and mirrored by the hosts.ejs edit form. install.sh gains the
ipmatcher rock, the cache dir, and the hostfeatures.lua symlink.
Per-host cache TTL is intentionally deferred (global default only) — see the
plan's limitations.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hosts that share one wildcard cert (e.g. hassio.718it.biz and
metrics.718it.biz under *.718it.biz) resolve to the same IP, so browsers
coalesce them onto a single HTTP/2 connection. The SSL request_domain
phase resolves the connection's first host and caches it in
ngx.ctx.targetInfo; the unguarded `if targetInfo then return targetInfo`
then handed that first host's target to every coalesced request on the
connection -- e.g. hassio.718it.biz served from metrics' 192.168.1.8:8088.
Confirmed via debug logging: for GET requests to hassio.718it.biz on a
coalesced connection, ngx.ctx already held metrics' IP and the function
short-circuited to it.
Guard the reuse by host: only return the cached target when
ngx.ctx.targetInfo_domain matches the requested domain, and record that
domain whenever a target is resolved. A coalesced request for a different
host now re-resolves against its actual Host header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- host.remove() now deletes the cert under the host key instead of the
Domain relation object, so certs are actually removed from redis
- targetinfo.lua returns the redis connection to the pool via
set_keepalive instead of leaking one connection per request
- autossl.conf drops TLSv1/1.1 and 3DES, adds TLSv1.3
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>