Files
proxy/nodejs/conf/base.js
wmantly 1e38ef8dc5
Pull Request Tests / Run Tests (18.x) (push) Successful in 33s
Pull Request Tests / Run Tests (20.x) (push) Successful in 28s
Pull Request Tests / Run Tests (22.x) (push) Successful in 31s
Pull Request Tests / Test Summary (push) Successful in 3s
feat: SSO group autocomplete for per-host SSO allow-lists (v1.34.0)
The per-host "Allowed groups" field suggested only local groups,
permission subjects and conf.auth maps. None of those can ever match an
SSO-gated host: its allow-list is checked against the `groups` claim the
SSO issues (utils/host_sso.js), so only SSO groups are candidates.

Adds a conf.sso block (url + read-only apiToken, minted by theta-suite's
bootstrap) and a cached /api/group lookup merged into the suggestions.
Degrades silently to the previous local-only list when unset, and never
fails the request.

Authenticates with `Authorization: Bearer <token>` -- the SSO's
`auth-token` header is for browser session UUIDs and rejects a minted
API token.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 18:42:50 -04:00

110 lines
4.0 KiB
JavaScript

'use strict';
// Using https://github.com/simpleworkjs/conf to handle configuration
module.exports = {
name: "Dynamic Proxy", // displayed in the UI
logo: "/static/img/theta42.svg", // shown in the nav; point at your own file under public/ (or an absolute URL) to white-label
userModel: 'redis', // pam, redis, ldap
ldap: {
url: 'ldap://localhost',
bindDN: 'cn=ldapclient service,ou=people,dc=example,dc=com',
bindPassword: '__IN SRECREST FILE__',
searchBase: 'ou=people,dc=example,dc=com',
userFilter: '(objectClass=inetOrgPerson)',
userNameAttribute: 'uid'
},
socketFile: '/var/run/proxy_lookup.socket',
redis: {
prefix: 'proxy_'
},
// Lifetime, in seconds, of on-demand wildcard-subdomain cache entries
// (the is_cache Host records created by Host.addCache). They expire on
// their own via redis TTL so they stop accumulating and stale routes
// self-correct. 0 disables expiry (entries live until bustCache/clearCache).
cacheTTL: 3600,
// OpenID Connect login against the SSO. Endpoints come from the SSO's
// /.well-known/openid-configuration. clientSecret lives in secrets.js.
// redirectUri MUST be registered on the SSO client and match exactly.
oidc: {
enabled: true,
issuer: 'https://sso.example.com',
authorizationEndpoint: 'https://sso.example.com/oauth/authorize',
tokenEndpoint: 'https://sso.example.com/oauth/token',
userinfoEndpoint: 'https://sso.example.com/oauth/userinfo',
endSessionEndpoint: 'https://sso.example.com/oauth/logout',
clientId: '__SET_ME__',
// Where the SSO sends the user back. Must be an absolute URL reachable
// by the browser and registered on the SSO client.
redirectUri: 'http://localhost:3000/api/auth/oidc/callback',
scopes: ['openid', 'profile', 'email', 'groups'],
// Claim on the userinfo response that carries group membership.
groupsClaim: 'groups',
// Claim used as the local username.
usernameClaim: 'preferred_username',
},
// Read-only SSO management API access, used to populate the per-host SSO
// allow-list autocomplete with the groups that actually exist in the
// directory. Without it the "Allowed groups" field can only suggest groups
// the proxy already knows locally, which for an SSO-gated host is usually
// none of the ones the operator wants. `apiToken` is a machine token minted
// by the theta-suite bootstrap and lives in secrets.js; leaving it unset
// simply falls back to the local-only suggestions.
sso: {
url: '', // e.g. https://sso.example.com
apiToken: '',
},
// Authorization: how groups map to roles, and which groups are global admin.
// Per-user overrides are Grant records managed in the app.
auth: {
// Members of these SSO/LDAP groups are always global admins.
// app_super_admin is the cross-app super admin group (sso, proxy, jump-host).
adminGroups: ['app_super_admin'],
// Optional default role mapping for groups, e.g.
// { 'dns-team': { role: 'manager', scope: 'domain', domain: 'foo.com' } }
// { 'proxy-viewers': { role: 'viewer', scope: 'global' } }
groupRoleMap: {},
// Local users always treated as global admin (anti-lockout bootstrap).
adminUsers: ['proxyadmin2'],
},
service:{
hostScheduler:{
enabled: true,
initial: 30000,
interval: 86400000,
},
dynamicDns:{
enabled: true,
initial: 15000, // first refresh 15s after start
interval: 14400000, // then every 4 hours
},
updateCheck:{
enabled: true,
initial: 30000, // first check 30s after start
interval: 86400000, // then every 24h
},
},
// Dynamic DNS: services queried (in order) to learn this box's public IP.
dynamicDns:{
ipServices: [
'https://api.ipify.org',
'https://icanhazip.com',
'https://ifconfig.me/ip',
],
},
// Per-host SSO (#57). Reuses conf.oidc for the identity provider. Sessions
// are Redis-backed and read directly by OpenResty; the cookie only carries a
// random session id.
hostSso:{
enabled: true,
sessionTtl: 28800, // 8 hours, in seconds
cookieName: '__proxy_sso',
},
};