c68fcc9ddb
* Fix TLS handshake failure for any host without a cached target 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 * Fix footer not sticking to the bottom on short pages body had no sticky-footer layout at all (sso-manager-node already had this; proxy never did), so on any page with little content (e.g. /login) the footer sat right after the content instead of at the bottom of the viewport, leaving a large gap below it. Added the same flex-based pattern already used in sso-manager-node: body is a column flex container, #spa-shell grows to fill the remaining space, pushing the footer (the next sibling) to the bottom. Verified visually (screenshot) and via computed layout (footer.getBoundingClientRect().bottom === window.innerHeight) before and after. * Fix commit hash not showing in Docker builds build_info.js computed buildHash via `git rev-parse --short HEAD` at runtime, but the final image intentionally has no git binary and no .git directory (kept lean, per .dockerignore) — so this always failed silently and the footer's version line showed "unknown" for every Docker deployment. Working correctly only for bare-metal/dev, where git + .git are actually present. Added a throwaway gitinfo build stage that reuses the main base image (no extra pull) with git installed just for this stage, reads .git from the build context (now no longer excluded — see .dockerignore), and bakes the resolved short hash into a small file that IS copied into the final image. build_info.js reads that file first, falling back to the old git-rev-parse behavior (still needed for bare-metal). Verified against a real build: `docker exec proxy cat /app/.build_commit` matches `git rev-parse --short HEAD` on the host, and the footer now shows the real hash instead of "unknown". * Allow the local anti-lockout admin's initial password to be configured The local "proxyadmin2" bootstrap account was always created with username == password == "proxyadmin2" — a hardcoded, publicly-known default with no way to set it to something else before first boot. Fine for a quick local test, not for anything exposed publicly, and orchestrators like theta-env's setup.sh (which already generates a random password for the SSO admin) had no way to do the same here. Added conf.auth.localAdminPass (proxy-secrets.js / app_auth__localAdminPass): if set, it's used as the initial password instead of the hardcoded default. Only read on first creation — once the account exists this is never consulted again, so it's safe to leave set. Falls back to the previous behavior (password == username) when unset, so this is fully backward compatible. Verified: with app_auth__localAdminPass set, login with the new password succeeds and the old default ("proxyadmin2") is correctly rejected. Confirmed in a real Docker build too (secrets.js auth.localAdminPass), and npm test 192/192 pass. * Support GIT_COMMIT build-arg override for submodule builds The gitinfo stage from the previous commit works for a standalone clone (.git is a real directory) but not when this repo is built as a git submodule (e.g. from theta-env): a submodule's .git is a pointer FILE, not a directory — the real object database lives in the superproject's .git/modules/, outside this repo's own directory and therefore outside Docker's build context entirely. `git rev-parse` can never resolve it from in here no matter what, so builds via theta-env still baked in "unknown" despite the earlier fix. Add an optional GIT_COMMIT build-arg that, when set, wins over the in-context git resolution. theta-env's setup.sh now computes it on the host (where the submodule DOES resolve correctly) and passes it via docker-compose.yml's build.args. Verified via theta-env's actual setup.sh end to end: rebuilding with this change, `docker exec proxy cat /app/.build_commit` now matches `git -C proxy rev-parse --short HEAD` on the host (previously: "unknown", confirmed via the "[Warning] One or more build-args [GIT_COMMIT] were not consumed" message before this fix synced into the docker-compose.yml side).
80 lines
3.8 KiB
Plaintext
80 lines
3.8 KiB
Plaintext
'use strict';
|
|
|
|
// Example secrets configuration for the theta42/proxy.
|
|
//
|
|
// The proxy is an OIDC client of an SSO Manager (or any OIDC provider) AND a
|
|
// direct LDAP client for user lookups. This file supplies that wiring.
|
|
//
|
|
// Docker / unified stack: place at ./config/proxy-secrets.js and bind-mount
|
|
// ./config at /config (see docker-compose.yml); docker-entrypoint.sh symlinks
|
|
// it into /app/conf/secrets.js so @simpleworkjs/conf reads it. No app_* env
|
|
// should be passed — app_* env beats this file in @simpleworkjs/conf, so the
|
|
// file is authoritative only if the matching app_* env is absent.
|
|
//
|
|
// Bare-metal: copy to nodejs/conf/secrets.js and fill in your values. Values
|
|
// here override conf/base.js and win over <environment>.js.
|
|
//
|
|
// Only the keys the app reads are listed below. The `stack` key is read by the
|
|
// theta-env orchestrator (setup.sh) and ignored by the app.
|
|
|
|
module.exports = {
|
|
// OpenID Connect — point at your SSO Manager. Issuer + authorization/
|
|
// endSession are browser-facing URLs; token/userinfo can be the internal
|
|
// URL if the SSO is on the same docker network (avoids a TLS hairpin).
|
|
oidc: {
|
|
enabled: true,
|
|
issuer: 'https://sso.example.com',
|
|
authorizationEndpoint: 'https://sso.example.com/oauth/authorize',
|
|
tokenEndpoint: 'http://sso-manager:3001/oauth/token',
|
|
userinfoEndpoint: 'http://sso-manager:3001/oauth/userinfo',
|
|
endSessionEndpoint: 'https://sso.example.com/oauth/logout',
|
|
clientId: 'set-me', // registered on the SSO
|
|
clientSecret: 'set-me', // from the SSO client record
|
|
redirectUri: 'https://proxy.example.com/api/auth/oidc/callback',
|
|
scopes: ['openid', 'profile', 'email', 'groups'],
|
|
groupsClaim: 'groups',
|
|
usernameClaim: 'preferred_username',
|
|
},
|
|
|
|
// Direct LDAP user lookups. ldaps:// + rejectUnauthorized:false for a
|
|
// self-signed cert (the SSO's default), or set tlsOptions.ca to a CA path
|
|
// for strict verification. bindPassword MUST match the
|
|
// serviceAccountPass in the SSO's sso-secrets.js (the proxy binds as that
|
|
// service account).
|
|
ldap: {
|
|
url: 'ldaps://sso-manager:636',
|
|
bindDN: 'cn=ldapclient,ou=people,dc=example,dc=com',
|
|
bindPassword: 'set-me',
|
|
searchBase: 'ou=people,dc=example,dc=com',
|
|
userFilter: '(objectClass=inetOrgPerson)',
|
|
userNameAttribute: 'uid',
|
|
tlsOptions: {
|
|
rejectUnauthorized: false, // true + ca for a CA-signed cert
|
|
},
|
|
},
|
|
|
|
// Authorization. adminUsers is the local anti-lockout admin (matches
|
|
// auth.adminUsers in conf/base.js). adminGroups: SSO/LDAP groups whose
|
|
// members are always global admins.
|
|
auth: {
|
|
adminGroups: [],
|
|
adminUsers: ['proxyadmin'],
|
|
groupRoleMap: {},
|
|
// Optional: the local anti-lockout admin's initial password, used
|
|
// ONLY the first time that account is created. Leave unset and it
|
|
// defaults to the username itself ("proxyadmin2") — fine for a quick
|
|
// local test, but change it (or set this) before exposing the proxy
|
|
// publicly. Once the account exists, this key is never read again;
|
|
// change the password via the app itself (or delete the Redis user
|
|
// to force it to be re-bootstrapped with a new value here).
|
|
// localAdminPass: 'change-me',
|
|
},
|
|
|
|
// ── Orchestrator-only (ignored by the app) ───────────────────────────────
|
|
// Read by the theta-env setup.sh (e.g. to seed the OAuth client). Omit for
|
|
// bare-metal use.
|
|
stack: {
|
|
ssoHost: 'sso.example.com', // public SSO hostname
|
|
proxyHost: 'proxy.example.com', // public proxy hostname
|
|
},
|
|
}; |