a9a48c3445
Personal access tokens so scripts/CI can call the management API without an OIDC browser session. Each logged-in user mints their own token; it authenticates as the creator (groups snapshotted at mint, mirroring the proxy's browser AuthToken), and the existing authz layer (Permission.effectiveFor / roles.resolveEffective) applies unchanged. Local groups and owned-domain rights are recomputed live; only SSO/LDAP group membership is the mint-time snapshot. - models/api_token.js: new ApiToken model (prx_<id>_<secret> format; id is the lookup key, secret bcrypt-hashed + isPrivate, shown once). add()/rotate()/ authenticate(); optional expires_at; best-effort last_used_on; groups snapshot. No _ttl (persists). Deliberately NOT wrapped in ModelPs (so the last_used_on write on the auth path doesn't spam the socket). - routes/api_token.js: self-service CRUD (list/get/update/delete/rotate), owner-scoped (created_by === reqUsername(req), 403 otherwise). - middleware/auth.js + models/auth.js: accept `Authorization: Bearer prx_...` (precedence over the auth-token session header). Builds a synthetic req.token that satisfies the only three req.token reads (auth.js .user/.groupsArray, authz.js reqUsername .created_by) so the authz layer works unchanged. checkApiToken collapses every failure to one generic 401 (no leak). - views/api_tokens.ejs + routes/render.js (GET /api-tokens): self-service page (forceLogin, no admin gate) — create (token shown once), rotate, revoke. - views/top.ejs: "API Tokens" nav entry visible to all logged-in users. - public/js/app.js: app.apiToken client module. - DEPLOYMENT.md + docs/docker.md: API tokens section. Co-authored-by: Claude <noreply@anthropic.com>
140 lines
6.3 KiB
Markdown
140 lines
6.3 KiB
Markdown
---
|
|
layout: default
|
|
title: Docker
|
|
---
|
|
|
|
# Docker Deployment
|
|
|
|
[← Back to Home](index.html)
|
|
|
|
The proxy ships as a single all-in-one Docker image bundling **OpenResty + the
|
|
Node management app + Redis** in one container, mirroring the bare-metal
|
|
[`ops/install.sh`](https://github.com/theta42/proxy/blob/master/ops/install.sh)
|
|
layout. This is the easiest way to run the proxy standalone, or as part of the
|
|
unified [theta-env](https://github.com/theta42/theta-env) stack.
|
|
|
|
## Quick start (standalone)
|
|
|
|
```bash
|
|
git clone https://github.com/theta42/proxy.git
|
|
cd proxy
|
|
mkdir -p config && chmod 700 config
|
|
cp secrets.js.example config/proxy-secrets.js # set OIDC/LDAP wiring
|
|
$EDITOR config/proxy-secrets.js
|
|
docker compose up -d --build
|
|
```
|
|
|
|
- Proxy (public, auto-SSL): `https://<host>/`
|
|
- Management UI / API: `http://127.0.0.1:3000/` (bound to localhost)
|
|
- Health: `http://127.0.0.1:3000/health` → `{"status":"ok"}`
|
|
|
|
## How configuration works
|
|
|
|
The app loads config via [`@simpleworkjs/conf`](https://www.npmjs.com/package/@simpleworkjs/conf),
|
|
which deep-merges, in order:
|
|
|
|
1. `conf/base.js` (committed defaults)
|
|
2. `conf/<NODE_ENV>.js` (optional)
|
|
3. `conf/secrets.js` (gitignored)
|
|
4. **`app_*` environment variables** — the highest-precedence layer
|
|
|
|
The bundled `docker-compose.yml` mount `./config/proxy-secrets.js` at `/config`,
|
|
and `docker-entrypoint.sh` symlinks it into `/app/conf/secrets.js` so the app
|
|
reads the OIDC + LDAP + auth wiring from the file. **No `app_*` env is passed** —
|
|
`app_*` env beats `secrets.js`, so the file is authoritative only if the matching
|
|
`app_*` env is absent. See `secrets.js.example` for the shape.
|
|
|
|
Any env var starting with `app_` overrides the merged config; the rest of the
|
|
name splits on **double-underscore** (`__`) into a nested path. Values are
|
|
`JSON.parse`-coerced when possible, kept as strings otherwise. `app_*` env is
|
|
still supported for advanced/standalone use — add the vars to the compose
|
|
`environment:` block yourself (the bundled compose no longer sets them).
|
|
|
|
> **Requires `@simpleworkjs/conf` >= 1.1.0.** The `app_*` env layer is not
|
|
> honored on 1.0.0. The lock is already on `^1.1.0`.
|
|
|
|
### Key `app_*` variables
|
|
|
|
| Env var | Sets |
|
|
|---------|------|
|
|
| `app_oidc__issuer` | `conf.oidc.issuer` (browser-facing SSO URL) |
|
|
| `app_oidc__authorizationEndpoint` | `conf.oidc.authorizationEndpoint` |
|
|
| `app_oidc__tokenEndpoint` | `conf.oidc.tokenEndpoint` (server-to-server; can be internal) |
|
|
| `app_oidc__userinfoEndpoint` | `conf.oidc.userinfoEndpoint` (server-to-server) |
|
|
| `app_oidc__endSessionEndpoint` | `conf.oidc.endSessionEndpoint` |
|
|
| `app_oidc__clientId` / `app_oidc__clientSecret` | OIDC client creds |
|
|
| `app_oidc__redirectUri` | `conf.oidc.redirectUri` (must match the SSO client) |
|
|
| `app_oidc__enabled` | `conf.oidc.enabled` (boolean) |
|
|
| `app_ldap__url` | `conf.ldap.url` (`ldaps://…:636` or `ldap://…:389`) |
|
|
| `app_ldap__bindDN` / `app_ldap__bindPassword` | LDAP service account |
|
|
| `app_ldap__searchBase` / `app_ldap__userFilter` | user search |
|
|
| `app_ldap__tlsOptions__rejectUnauthorized` | `false` for self-signed LDAPS |
|
|
| `app_ldap__tlsOptions__ca` | path to a CA cert for strict trust |
|
|
| `app_auth__adminUsers` | local anti-lockout admin (uid) |
|
|
| `app_auth__adminGroups` | SSO/LDAP groups that are global admin (JSON array) |
|
|
| `app_redis__prefix` | `conf.redis.prefix` (default `proxy_`) |
|
|
|
|
See [`DEPLOYMENT.md`](https://github.com/theta42/proxy/blob/master/DEPLOYMENT.md)
|
|
for the complete reference.
|
|
|
|
## OpenResty runtime env
|
|
|
|
| Variable | Default | Description |
|
|
|----------|---------|-------------|
|
|
| `RESOLVER` | `127.0.0.11` | DNS for upstream names in Host records (Docker's embedded DNS) |
|
|
| `REAL_IP_FROM` | _empty_ | Trusted CIDR for `X-Real-IP`. Empty = the proxy is the front (removes the real_ip block). Set to an upstream proxy's CIDR if one sits in front. |
|
|
|
|
## Auto-SSL / Let's Encrypt
|
|
|
|
`lua-resty-auto-ssl` stores certs in the bundled Redis. Redis is now AOF+RDB
|
|
persisted to the `proxy-data` volume (not in-memory), so **Let's Encrypt certs
|
|
survive container recreation** — no re-issue / rate-limit on rebuild. Port 80 is
|
|
required for HTTP-01 challenges (mapped in the compose). Back up + restore Redis
|
|
to back up + restore cert state (see *Backups and restore* in `DEPLOYMENT.md`).
|
|
|
|
## Fronting an SSO Manager
|
|
|
|
The proxy is a natural front for
|
|
[`theta42/sso-manager-node`](https://github.com/theta42/sso-manager-node): it
|
|
terminates TLS for the SSO's UI and protects it with OIDC login, while also
|
|
binding to the SSO's LDAP directly for user lookups. To run both together:
|
|
|
|
1. **One Docker network** so the proxy reaches the SSO internally at
|
|
`http://sso-manager:3001` (token/userinfo) and `ldaps://sso-manager:636`.
|
|
2. **Set the SSO's `app_oauth__issuer`** to the browser-facing HTTPS URL the
|
|
proxy serves the SSO at (e.g. `https://sso.example.com`).
|
|
3. **Register the proxy as an OIDC client** in the SSO, with `redirectUri`
|
|
matching `https://proxy.example.com/api/auth/oidc/callback`.
|
|
4. **LDAP**: point `app_ldap__url` at `ldaps://sso-manager:636`, create a
|
|
dedicated service account (`cn=ldapclient,ou=people,…`), and for the SSO's
|
|
self-signed LDAPS cert set `app_ldap__tlsOptions__rejectUnauthorized=false`
|
|
(or mount the cert and use `app_ldap__tlsOptions__ca=<path>`).
|
|
|
|
The [`theta42/theta-env`](https://github.com/theta42/theta-env) unified repo
|
|
automates all four steps with `./setup.sh` — see
|
|
[theta-env docs](https://theta42.github.io/theta-env/).
|
|
|
|
## API tokens (personal access tokens)
|
|
|
|
Any logged-in user can mint a long-lived bearer token to call the management API
|
|
from scripts/CI without an OIDC browser session. Self-service; authenticates as
|
|
the creator (groups snapshotted at mint; authz layer unchanged).
|
|
|
|
Create one under **API Tokens** in the UI (shown once), then:
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer prx_<id>_<secret>" https://proxy.example.com/api/host
|
|
```
|
|
|
|
Rotate/revoke from the same page (immediate effect). Optional expiry at
|
|
creation. The token carries the creator's rights (admin → full mgmt API;
|
|
domain manager → those domains; `requireAdmin` routes 403). To tighten after
|
|
group changes, revoke + re-mint. Tokens persist in Redis (AOF) and survive
|
|
rebuilds.
|
|
|
|
## Bare metal
|
|
|
|
Prefer a systemd install? See the [Installation Guide](installation.html) for
|
|
the `ops/install.sh` automated installer on Debian/Ubuntu.
|
|
|
|
[← Back to Home](index.html) |