134 lines
5.3 KiB
Markdown
134 lines
5.3 KiB
Markdown
# Theta Gateway
|
|
|
|
An SSH jump gateway and integrated WireGuard mesh network router for the [Theta Suite](https://github.com/theta42/theta-suite) ecosystem. Users SSH into one entry point (`:2222`) and reach target downstream hosts according to directory permissions, with full auditing end-to-end.
|
|
|
|
Theta Gateway authenticates users against the shared OpenLDAP directory, authorizes access using **Theta Directory** (`theta-directory`), and routes cross-site mesh traffic with native WireGuard subnets and NETMAP shadow network support.
|
|
|
|
## Access Flow
|
|
|
|
**Direct (WinSCP/SFTP-friendly):**
|
|
|
|
```bash
|
|
ssh alice_-_web01@jump.example.com # -> target host slug 'web01'
|
|
sftp -P 2222 alice_-_web01@jump.example.com # SFTP passes through unchanged
|
|
```
|
|
|
|
The username grammar is `{uid}_-_{target}`. `target` is a directory host slug or hostname.
|
|
|
|
**Interactive host picker:**
|
|
|
|
```bash
|
|
ssh alice@jump.example.com
|
|
```
|
|
|
|
Plain login displays a TUI list of target hosts assigned to the local site (`SITE_SLUG`) that the user is authorized to reach.
|
|
|
|
## How it Works
|
|
|
|
1. **Inbound Auth** — OpenLDAP authentication via public key matching (`sshPublicKey`) or LDAP password bind.
|
|
2. **Authorization** — Calls Theta Directory's access API (`GET /api/discovery/access/:uid`) to evaluate LDAP group memberships and site-filtered host entitlement.
|
|
3. **Key Injection** — Appends its gateway public key to user `sshPublicKey` in LDAP and connects downstream as the user.
|
|
4. **Bridge & Audit** — Slices shell/SFTP subsystem to downstream sshd with session audit logging.
|
|
|
|
## Deployment
|
|
|
|
Theta Gateway is deployed exclusively via Docker Compose as an integrated service within **Theta Suite**:
|
|
|
|
```bash
|
|
git clone --recursive https://github.com/theta42/theta-suite.git
|
|
cd theta-suite
|
|
cp setup.env.example setup.env # set CFG_DOMAIN to your domain
|
|
./setup.sh # generates config, builds, and starts Theta Suite
|
|
```
|
|
|
|
See the main [Theta Suite README](https://github.com/theta42/theta-suite) for full details on multi-site configuration, WireGuard mesh routing, and network setup.
|
|
- An LDAP bind account with **write access to the `sshPublicKey` attribute** on
|
|
user entries (see the ACL note in `secrets.js.example`).
|
|
- An SSO API token (`sso_…`) for the directory queries.
|
|
|
|
## Install
|
|
|
|
### Unified theta-env stack (recommended)
|
|
|
|
Enable it in `theta-env/setup.env` (`CFG_JUMP_HOST_ENABLED=true`) and re-run
|
|
`./setup.sh`. The stack wires the LDAP bind account, the write-ACL, the API
|
|
token, and a directory entry automatically.
|
|
|
|
### Standalone Docker
|
|
|
|
```
|
|
cp secrets.js.example config/jump-secrets.js # then edit it
|
|
docker compose up -d --build
|
|
```
|
|
|
|
### Bare metal
|
|
|
|
```
|
|
curl -fsSL https://raw.githubusercontent.com/theta42/jump-host/master/ops/install.sh | sudo bash
|
|
sudo $EDITOR /etc/jump-host/secrets.js # fill in LDAP + SSO
|
|
sudo systemctl restart jump-host
|
|
```
|
|
|
|
Installs to `/opt/theta42/jump-host`; idempotent (re-run to update).
|
|
|
|
## Ports
|
|
|
|
| Port | Purpose |
|
|
|------|---------|
|
|
| 2222 | SSH front door (default; see below for :22) |
|
|
| 3002 | Web UI + HTTP API (audit, metrics) |
|
|
|
|
The default SSH port is **2222** so the service needs no privilege. To listen on
|
|
22, set `ssh.listenPort: 22` in your secrets and either uncomment
|
|
`AmbientCapabilities=CAP_NET_BIND_SERVICE` in the systemd unit, or DNAT
|
|
22 → 2222 at the firewall.
|
|
|
|
## Web UI / API
|
|
|
|
`https://jump.example.com/` (behind the proxy) — built on the same
|
|
Express + EJS + Bootstrap stack as the [SSO Manager](https://theta42.github.io/sso-manager-node/)
|
|
and [Proxy](https://theta42.github.io/proxy/), so it looks and behaves like the
|
|
rest of the stack. Login is **OIDC against the SSO** (the "Log in with SSO"
|
|
button) plus a **local anti-lockout admin** that works even if the SSO is
|
|
unreachable. Admin access requires membership in `auth.adminGroups` (default
|
|
`app_sso_admin`) or being the local `auth.adminUsers` account.
|
|
|
|
- `GET /health` — open; `{status, activeSessions, version}`
|
|
- `GET /api/sessions` — active sessions
|
|
- `GET /api/audit?page=&uid=&target=&status=` — paged audit log
|
|
- `GET /api/metrics` — counters (total, failures, top users/hosts)
|
|
|
|
## Configuration
|
|
|
|
Config layers via [@simpleworkjs/conf](https://www.npmjs.com/package/@simpleworkjs/conf):
|
|
`conf/base.js` < `conf/<NODE_ENV>.js` < the `CONF_SECRETS` file < `app_*` env.
|
|
See `secrets.js.example` for every key.
|
|
|
|
## Secrets
|
|
|
|
At boot, [@simpleworkjs/bao-conf](https://simpleworkjs.github.io/bao-conf/)
|
|
deep-merges `secret/jump-host/conf` from **OpenBao** over the file-loaded
|
|
config. The jump host's OIDC `clientSecret` is captured at require time
|
|
(inside `createOidcClient` during `require('../models')`), so `bin/www` runs
|
|
`bao-conf.init()` **before** `require('../models')`. Fail-soft: if OpenBao is
|
|
unreachable, boot continues from `CONF_SECRETS`. The jump host authenticates to
|
|
OpenBao with the scoped `VAULT_TOKEN` (env, policy `jump-host` — read only
|
|
`secret/jump-host/conf`), never the root token.
|
|
|
|
The `config/jump-secrets.js` file is an operator-edit seed artifact
|
|
(gitignored); the bootstrap writes the generated API token + OAuth client
|
|
into OpenBao, which is authoritative. For the full architecture see
|
|
theta-env's **[Secrets docs](https://theta42.github.io/theta-env/secrets/)**.
|
|
|
|
## Development
|
|
|
|
```
|
|
cd nodejs && npm install
|
|
npm test # unit + integration (node --test)
|
|
NODE_ENV=development npm run dev
|
|
```
|
|
|
|
## License
|
|
|
|
MIT
|