diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md deleted file mode 100644 index 995887d..0000000 --- a/DEPLOYMENT.md +++ /dev/null @@ -1,83 +0,0 @@ -# Deployment - -Three ways to run the jump host, in increasing manual effort. - -## 1. Unified theta-env stack - -Set in `theta-env/setup.env`: - -``` -CFG_JUMP_HOST_ENABLED=true -CFG_JUMP_HOST=jump.example.com -JUMP_SSH_PORT=2222 -``` - -Re-run `./setup.sh`. It builds the submodule, writes `config/jump-secrets.js`, -mints the SSO API token, grants the `sshPublicKey` write-ACL to the shared -`cn=ldapclient` bind account, registers `jump.example.com` in the proxy, and -seeds a directory entry. - -Expose SSH: forward the public host's `:22` (or `:2222`) to the container's -published `JUMP_SSH_PORT`. - -## 2. Standalone Docker - -``` -cp secrets.js.example config/jump-secrets.js -$EDITOR config/jump-secrets.js # LDAP bind (+ sshPublicKey write ACL), SSO url + token -docker compose up -d --build -``` - -Host keys persist in the `jump-data` volume. The web UI is on `:3002`; front it -with your own TLS/proxy. - -## 3. Bare metal - -``` -curl -fsSL https://raw.githubusercontent.com/theta42/jump-host/master/ops/install.sh | sudo bash -sudo $EDITOR /etc/jump-host/secrets.js -sudo systemctl restart jump-host -journalctl -u jump-host -f -``` - -`ops/install.sh` installs Node 22 + Redis, hard-resets the checkout at -`/opt/theta42/jump-host` to the remote branch, symlinks the systemd unit, and -runs `npm ci`. Idempotent — re-run to update. Overridable via `REPO_DIR=`, -`BRANCH=`, `SECRETS_FILE=`. - -## The LDAP write-ACL (required) - -The bind account must be able to write the `sshPublicKey` attribute so the jump -host can inject its key. In the bundled OpenLDAP (`slapd.conf` / `olc`): - -``` -access to attrs=sshPublicKey - by dn.exact="cn=ldapclient,ou=people,dc=example,dc=com" write - by self write - by * read -``` - -Without it, key injection fails and every bridge attempt is audited -`key-inject-failed`. - -## Listening on port 22 - -Default is 2222 (unprivileged). For 22: set `ssh.listenPort: 22`, and either - -- systemd: uncomment `AmbientCapabilities=CAP_NET_BIND_SERVICE` in the unit; or -- Docker: publish `22:22`; or -- firewall: DNAT `22 → 2222`. - -## Verifying - -``` -# from a client whose key is in your LDAP sshPublicKey -ssh -p 2222 youruid@jump.example.com # TUI picker -ssh -p 2222 youruid_-_somehost@jump.example.com -sftp -P 2222 youruid_-_somehost@jump.example.com - -curl -s http://localhost:3002/health -``` - -Watch `journalctl -u jump-host -f` (or `docker logs -f jump-host`) and the -audit log at `/audit` in the web UI. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index fdcfe03..0000000 --- a/docs/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Documentation - -This directory is the GitHub Pages documentation site for the Jump Host project. - -**Live site:** https://theta42.github.io/jump-host/ - -## Pages - -- `index.md` — overview and quick start -- `connecting.md` — usage: the username grammar, the TUI picker, SFTP/WinSCP -- `architecture.md` — how auth, access resolution, key injection, and bridging work -- `installation.md` — Docker, bare-metal, and theta-env install; the LDAP write-ACL - -## Local preview - -```bash -gem install jekyll bundler -cd docs && jekyll serve -# http://localhost:4000/jump-host/ -``` - -## Updating - -Edit the markdown, push to `master`, and GitHub Pages rebuilds automatically. diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index eb44233..0000000 --- a/docs/_config.yml +++ /dev/null @@ -1,41 +0,0 @@ -title: Jump Host -description: An SSH jump host for the theta42 stack — directory-driven host bridging with audit and metrics; LDAP + SSO Manager by default, or fully standalone. -url: "https://theta42.github.io" -baseurl: "/jump-host" -logo: /assets/img/theta42.svg -lang: en_US - -plugins: - - jekyll-seo-tag - - jekyll-sitemap - -github: - repository_url: https://github.com/theta42/jump-host - zip_url: https://github.com/theta42/jump-host/archive/refs/heads/master.zip - tar_url: https://github.com/theta42/jump-host/archive/refs/heads/master.tar.gz - repository_name: theta42/jump-host - -nav: - - title: Home - page: / - icon: fa-house - - title: Connecting - page: /connecting.html - icon: fa-terminal - - title: Architecture - page: /architecture.html - icon: fa-sitemap - - title: Installation - page: /installation.html - icon: fa-download - - title: Changelog - url: https://github.com/theta42/jump-host/blob/master/CHANGELOG.md - icon: fa-list - -defaults: - - scope: - path: "" - type: "pages" - values: - layout: default - image: /assets/img/theta42.svg diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html deleted file mode 100644 index 132c524..0000000 --- a/docs/_layouts/default.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - {% seo title=false %} - {% if page.title %}{{ page.title }} · {% endif %}{{ site.title }} - - - - - - - - - -
-
-
-
-
-
- {{ content }} -
-
-
-
-
-
- - - - - - diff --git a/docs/architecture.md b/docs/architecture.md deleted file mode 100644 index 8104ee1..0000000 --- a/docs/architecture.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -layout: default -title: Architecture -description: How the jump host authenticates users, resolves reachable hosts from the directory, injects per-user keys, and bridges SSH — plus the web UI and audit model. ---- - -# Architecture - -The jump host is a Node.js service (using [`ssh2`](https://github.com/mscdex/ssh2) -as both an SSH **server** and **client**) with two faces: the SSH front door -(default `:2222`) and a web UI/API (`:3002`). It holds no user database of its -own — identity, authorization, and onward credentials all come from the shared -directory. - -``` - ┌────────────────────── jump host ──────────────────────┐ - ssh │ ssh2 Server (:2222) │ ssh2 Client - ─────┼─▶ 1. authenticate user ──▶ LDAP (sshPublicKey / bind) │ ───────────▶ downstream - user │ 2. resolve target ──▶ SSO /api/discovery │ sshd (as the - │ 3. inject key ──▶ LDAP (add sshPublicKey) │ real user) - │ 4. bridge channels ◀───────────────────────────────▶ │ - │ web UI/API (:3002) ──▶ audit + metrics (redis) │ - └───────────────────────────────────────────────────────┘ -``` - -## 1. Inbound authentication - -When a user connects, the jump host authenticates them against LDAP: - -- **Public key** — it looks up the user's `sshPublicKey` values in the directory - and matches the offered key (handling ssh2's probe-then-sign two-phase - publickey auth). The jump host's *own* injected key (identified by its comment - marker) is deliberately excluded from this match — only the jump host may hold - that private key, so accepting it inbound would be a bypass. -- **Password** — an LDAP simple bind as the user's DN. Policy is configurable: - `off` (keys only — recommended for a public host), `local` (passwords only - from loopback/RFC1918 clients, keys-only from the internet), or `all`. - -Every attempt — success or failure, with method and reason — is audited. - -## 2. Access & target resolution - -The hosts a user may reach are computed from the directory, not a local list: - -1. The user's LDAP group memberships (`(&(objectClass=groupOfNames)(member=…))`). -2. For each group, the SSO's - `GET /api/discovery/resources?group=` (authenticated with an API token), - unioned and filtered to `kind: host`. - -Each host's dial address is `metadata.ip` (or the hostname from -`metadata.address`) and port `metadata.sshPort` (default 22). Results are cached -briefly per user and shared by both the grammar path and the TUI picker. - -Target matching tries, in order: exact slug → `host_`-prefixed slug → display -name → IP → address hostname. A raw IP that isn't an accessible directory host -is refused unless explicitly allowed. - -> The directory auto-creates `_access` / `_admin` groups for every -> host and service (see the SSO's -> [Directory & Inventory](https://theta42.github.io/sso-manager-node/directory.html) -> docs), which is exactly what this authorization reads. - -## 3. Per-user key injection {#per-user-key-injection} - -The jump host holds **one** keypair. To connect downstream *as the user* -without asking them for anything, it must present a key the downstream `sshd` -will accept for that user. Downstream hosts (joined via -[ldap-client](https://github.com/theta42/ldap-client)) serve authorized keys -straight from LDAP via `AuthorizedKeysCommand`. So on a user's first connection, -the jump host appends its own public key to that user's `sshPublicKey` attribute -in LDAP — comment-marked so it's recognizable — then connects downstream with -its private key. - -- Idempotent: the key is added once; a redis flag skips the LDAP round-trip - afterwards. -- The jump host's bind account therefore needs **write access to the - `sshPublicKey` attribute** on user entries (an OpenLDAP ACL — see the README). - In the bundled theta-env deployment this is handled for you. -- Because the marker key is excluded from inbound auth (step 1), it grants only - the jump host's onward path, never inbound impersonation. - -## 4. Bridging - -Once the upstream connection is ready, the jump host splices SSH channels -between the two connections: - -- **shell / exec** — piped both ways, with window-change and exit-status - forwarded. -- **SFTP subsystem** — the two subsystem channels are raw-piped as opaque bytes; - no SFTP protocol parsing is needed, which is why WinSCP and `sftp` work - unchanged. -- Channel requests that arrive before the upstream is ready are buffered and - replayed, so nothing is dropped during the connect. -- The downstream host key's SHA256 fingerprint is recorded in the audit event - (trust-on-use in v1). - -Byte counts per direction are tallied cheaply for the audit record. - -## Web UI, API & audit - -An Express + EJS + Bootstrap app on `:3002` — the same front-end stack and -look/feel as the SSO Manager and Proxy. Login is OIDC against the SSO plus a -local anti-lockout admin (`auth.adminUsers`), with admin access gated by -`auth.adminGroups`. It exposes: - -- `GET /health` — open; `{status, activeSessions, version}` -- `GET /api/sessions` — active sessions -- `GET /api/audit?page=&uid=&target=&status=` — the paged audit log -- `GET /api/metrics` — counters (total, failures, top users/hosts) - -Audit events and counters live in redis. Each event captures: user, auth method, -mode (grammar/picker), target slug/address/port, channel type, client IP, -success + failure reason, downstream host-key fingerprint, timing, and bytes in/out. - -## Standalone mode - -Everything above describes the default backend. Set `standalone.enabled: true` -and two modules become conditional facades, swapping their entire -implementation at `require` time based on that flag — nothing else in the -codebase (`ssh_server.js`, `bridge.js`, `key_inject.js`, `tui_picker.js`, the -web UI) changes or even knows which mode it's running in: - -- **`models/user_ldap.js`** — LDAP client, or `models/user_file.js` (an - [@simpleworkjs/orm](https://www.npmjs.com/package/@simpleworkjs/orm)-backed - store implementing the same `getUser` / `getGroups` / `checkPassword` / - `addSshKey` interface). -- **`utils/access.js`** — LDAP groups + SSO `/api/discovery`, or - `utils/hosts_file.js` (same ORM package, same `accessibleHosts()` interface). - In standalone mode there's no group-based authorization: every stored host - is accessible to every stored user. - -The ORM is Sequelize underneath, defaulting to a local SQLite file but -accepting any Sequelize-supported dialect via `conf.orm`. See -[Installation](installation.html#standalone-mode) for config and how to add -users/hosts (there's no admin UI for standalone data yet). - -## Where it sits in the stack - -- **[SSO Manager](https://theta42.github.io/sso-manager-node/)** — provides the - OpenLDAP directory (users, groups, `sshPublicKey`) and the inventory API this - jump host reads. -- **[ldap-client](https://github.com/theta42/ldap-client)** — enrolls the - downstream Linux hosts (SSSD/PAM + `AuthorizedKeysCommand`) that the jump host - connects into. -- **[Proxy](https://theta42.github.io/proxy/)** — fronts the jump host's web UI - under TLS. -- **[theta-env](https://theta42.github.io/theta-env/)** — wires it all together. diff --git a/docs/assets/css/style.css b/docs/assets/css/style.css deleted file mode 100644 index e24a5de..0000000 --- a/docs/assets/css/style.css +++ /dev/null @@ -1,116 +0,0 @@ -/* theta42 docs site — shares the in-app dark navbar/footer + card look - (Bootstrap 5 + Font Awesome, same as the running apps) rather than a - generic Jekyll theme. */ - -body { - background-color: #f4f5f6; -} - -.navbar-brand img { - filter: drop-shadow(0 0 2px rgba(0, 0, 0, .4)); -} - -.navbar-nav .nav-link.active { - color: #fff; - font-weight: 600; -} - -/* Markdown content typography, scoped to the card body so it doesn't leak - into the nav/footer. */ -.site-content h1:first-child { - margin-top: 0; -} - -.site-content h1, -.site-content h2, -.site-content h3 { - font-weight: 700; -} - -.site-content h2 { - margin-top: 2.5rem; - padding-bottom: .4rem; - border-bottom: 1px solid #e9ecef; -} - -.site-content h3 { - margin-top: 1.75rem; -} - -.site-content a { - color: #a3671f; - text-decoration-color: rgba(163, 103, 31, .35); -} - -.site-content a:hover { - color: #8a5a16; -} - -.site-content pre { - background-color: #212529; - color: #f8f9fa; - padding: 1rem 1.25rem; - border-radius: .375rem; - overflow-x: auto; -} - -.site-content code { - color: #a3671f; - background-color: #f4f0e8; - padding: .15em .4em; - border-radius: .25rem; - font-size: .875em; -} - -.site-content pre code { - color: inherit; - background: none; - padding: 0; -} - -.site-content table { - display: block; - overflow-x: auto; - width: 100%; - border-collapse: collapse; - margin: 1.25rem 0; -} - -.site-content table th, -.site-content table td { - border: 1px solid #dee2e6; - padding: .5rem .75rem; - text-align: left; -} - -.site-content table th { - background-color: #f8f9fa; -} - -.site-content blockquote { - border-left: 4px solid #C59341; - padding: .5rem 1rem; - margin: 1.25rem 0; - background-color: #f8f6f1; - color: #495057; -} - -.site-content img { - max-width: 100%; - height: auto; -} - -/* Screenshot grids in the markdown use width="49%" inline attrs for a - two-up desktop layout -- stack them on narrow screens instead of - squeezing to illegibility. */ -@media (max-width: 576px) { - .site-content img[width] { - width: 100% !important; - margin-bottom: .75rem; - } -} - -.site-content hr { - margin: 2rem 0; - border-top: 1px solid #e9ecef; -} diff --git a/docs/assets/img/favicon.svg b/docs/assets/img/favicon.svg deleted file mode 100644 index 35e1881..0000000 --- a/docs/assets/img/favicon.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/docs/assets/img/theta42.svg b/docs/assets/img/theta42.svg deleted file mode 100644 index e598305..0000000 --- a/docs/assets/img/theta42.svg +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 42 - - diff --git a/docs/connecting.md b/docs/connecting.md deleted file mode 100644 index 966ba87..0000000 --- a/docs/connecting.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -layout: default -title: Connecting -description: How to reach downstream hosts through the jump host — the username grammar, the interactive picker, SFTP/WinSCP, and what access you get. ---- - -# Connecting - -You reach a downstream host two ways: name the target in your username, or log -in plain and pick it from a menu. Either way you authenticate **once**, to the -jump host, with your directory credentials. - -## The username grammar - -``` -{uid}_-_{target} -``` - -- `{uid}` — your directory username. -- `_-_` — the separator (legal in an SSH username everywhere, including WinSCP). -- `{target}` — the host to reach: a directory **slug** (`host_web01` or just - `web01`), the host's **display name**, its **IP**, or the hostname in its - directory `address`. - -```bash -ssh alice_-_web01@jump.example.com # by slug (host_ prefix optional) -ssh alice_-_10.0.0.10@jump.example.com # by IP (must be a host you can reach) -``` - -If the target matches a host your directory groups grant, you're bridged -straight to its `sshd` — same as if you'd SSH'd directly, but through the -audited jump host. - -## SFTP / WinSCP / scp - -Because the whole route is encoded in the username, file transfer tools that -only take one connection string work with no extra configuration: - -```bash -sftp -P 2222 alice_-_web01@jump.example.com -scp -P 2222 file.txt alice_-_web01@jump.example.com:/tmp/ -``` - -**WinSCP:** set Host name to `jump.example.com`, Port to `2222`, and User name -to `alice_-_web01`. SFTP is bridged as an opaque byte stream, so all operations -(browse, upload, download, rename) work normally. - -## The interactive picker - -Log in with just your username and you get a TUI list of every host you can -reach: - -```bash -ssh alice@jump.example.com -``` - -- **↑ / ↓** move the selection -- **type** to filter the list incrementally -- **Enter** connect to the highlighted host -- **number keys** jump straight to that row -- **q** or **Ctrl-C** to quit - -Pick a host and you're bridged into it. The picker only ever lists hosts your -directory access allows — it doubles as "what can I reach from here?" - -## What you can reach - -The set of hosts is computed per login: your LDAP group memberships intersected -with the SSO directory's hosts (via the `host__access` groups the -directory auto-creates for each machine). To get access to a new host, an admin -adds you to that host's access group in the SSO — nothing on the jump host -changes. - -Targets that don't resolve to a host you're allowed to reach are refused (and -audited). Raw IPs that aren't a known directory host are denied by default. - -> On a [standalone](architecture.html#standalone-mode) jump host (no LDAP/SSO), -> every registered host is reachable by every registered user — there's no -> group-based restriction to ask an admin about. - -## Authentication - -The jump host authenticates **you** against the directory: - -- **Public key** — matched against your `sshPublicKey` entries in LDAP. Use your - normal SSH key; the client picks it automatically. -- **Password** — your directory password (LDAP bind). Password auth is often - restricted to local networks or disabled entirely on a public jump host - (keys-only) — check with your operator. - -You never manage a separate credential for the downstream host: the jump host -handles onward authentication for you (see -[Architecture](architecture.html#per-user-key-injection)). - -## First connection to a host - -The very first time you reach a given downstream host, the jump host provisions -its access key for you behind the scenes. If that first attempt races the -directory's key-cache refresh you may see a brief - -``` -jump-host: first-time key propagation, retrying… -``` - -and it reconnects automatically. Subsequent connections are immediate. diff --git a/docs/images/audit.png b/docs/images/audit.png deleted file mode 100644 index 997974d..0000000 Binary files a/docs/images/audit.png and /dev/null differ diff --git a/docs/images/dashboard.png b/docs/images/dashboard.png deleted file mode 100644 index 8caa276..0000000 Binary files a/docs/images/dashboard.png and /dev/null differ diff --git a/docs/images/login.png b/docs/images/login.png deleted file mode 100644 index 5881ac9..0000000 Binary files a/docs/images/login.png and /dev/null differ diff --git a/docs/images/sessions.png b/docs/images/sessions.png deleted file mode 100644 index e52511c..0000000 Binary files a/docs/images/sessions.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index b7d73bc..0000000 --- a/docs/index.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -layout: default -title: Home -description: An SSH jump host for the theta42 stack — one public host and directory-driven access to every downstream machine you're entitled to; LDAP by default, or fully standalone. ---- - -# Jump Host - -An SSH jump host for the [theta42](https://github.com/theta42) self-hosted -stack. Users SSH into **one** public host and land on any downstream host -they're entitled to — authenticated against the shared LDAP directory, -authorized from the [SSO Manager](https://theta42.github.io/sso-manager-node/)'s -inventory graph, and audited end to end. - -No per-host accounts, no distributing keys, no VPN. The same people who log in -to your SSO are the people who can reach your machines — and only the machines -their directory groups grant. - -Part of the theta42 self-hosted identity stack, alongside -[SSO Manager](https://theta42.github.io/sso-manager-node/) and -[Proxy](https://theta42.github.io/proxy/), composable with one command via -[theta-env](https://theta42.github.io/theta-env/). - -## Screenshots - -Login -Dashboard -Active sessions -Audit log - -*(click any screenshot to view full size)* - -Don't want to run LDAP or the SSO Manager? **Standalone mode** stores users -and hosts in a local SQL database instead (SQLite by default, any -Sequelize-supported dialect if you want something else) — same SSH front door, -key injection, and audit trail. See -[Installation](installation.html#standalone-mode) to get started. - -## Two ways to connect - -**Direct (WinSCP/SFTP-friendly):** - -```bash -ssh alice_-_web01@jump.example.com -sftp -P 2222 alice_-_web01@jump.example.com -``` - -The username grammar is `{uid}_-_{target}` — `target` is a directory host slug -(with or without the `host_` prefix), a bare hostname, or an IP. One username -string, no interactive step, so it works cleanly in WinSCP and scripts. - -**Interactive picker:** - -```bash -ssh alice@jump.example.com -``` - -A plain login shows a TUI list of the hosts you can reach; arrow-key or type to -filter, Enter to connect. - -See **[Connecting](connecting.html)** for the full usage guide. - -## Why a jump host (and why this one) - -A bastion/jump host is the standard way to give SSH access to internal machines -through a single audited entry point. What's usually painful is *authorization* -and *credentials*: who may reach which host, and how the bastion authenticates -onward without you copying keys everywhere. - -This jump host answers both from your directory: - -- **Authorization is your directory graph.** The hosts you can reach are the - union of your LDAP groups × the SSO's inventory (the `host__access` - groups the directory already auto-creates). Add someone to a group; they can - reach the host. No bastion-side allow-list to maintain. -- **Onward auth is automatic.** The jump host holds one key and injects its - public half into your `sshPublicKey` on first use, then connects downstream - **as you**. Downstream hosts already serve keys from LDAP (via - [ldap-client](https://github.com/theta42/ldap-client)'s - `AuthorizedKeysCommand`), so nothing downstream needs configuring. - -## Features - -- **Username-grammar routing** (`uid_-_target`) — straight-through to the host, - SFTP included (WinSCP works) -- **Interactive TUI host picker** on plain login, scoped to your access -- **LDAP inbound auth** — public key or password (keys-only policy recommended - for a public host) -- **Directory-driven access** — reachable hosts come from the SSO inventory, not - a static list -- **Per-user key injection** — no downstream changes, no key distribution -- **Shell, exec, and SFTP** bridging -- **Web UI + HTTP API** for auditing and metrics — active sessions, a searchable - audit log, per-user/per-host counters -- **Full audit trail** — who, target, method, result, bytes, duration, and the - downstream host-key fingerprint -- **Standalone mode** — no LDAP, no SSO Manager; users and hosts live in a - local SQL database (Sequelize, any dialect — SQLite by default) -- Packaged like the rest of the stack: one-command Docker, idempotent bare-metal - installer, or bundled in theta-env - -## Get it - -```bash -git clone https://github.com/theta42/jump-host.git -cd jump-host -cp secrets.js.example config/jump-secrets.js # then edit it -docker compose up -d --build -``` - -That's the standalone quick start. For bare-metal and the bundled theta-env -option, see **[Installation](installation.html)**. - -## Related projects - -- **[SSO Manager](https://theta42.github.io/sso-manager-node/)** — the OpenLDAP - directory + OIDC provider + the inventory graph this jump host reads. -- **[Proxy](https://theta42.github.io/proxy/)** — puts your web apps behind the - same identity; fronts this jump host's web UI. -- **[theta-env](https://theta42.github.io/theta-env/)** — runs the whole stack, - jump host included, with one command. diff --git a/docs/installation.md b/docs/installation.md deleted file mode 100644 index 6a5f37b..0000000 --- a/docs/installation.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -layout: default -title: Installation -description: Install the jump host three ways — bundled in the theta-env stack, standalone Docker, or bare metal — plus standalone mode (no LDAP/SSO), the LDAP write-ACL, and port-22 options. ---- - -# Installation - -Three ways to run the jump host, in increasing manual effort. All read their -config through [@simpleworkjs/conf](https://www.npmjs.com/package/@simpleworkjs/conf) -(`conf/base.js` < `conf/.js` < the `CONF_SECRETS` file < `app_*` env). - -## Standalone mode (no LDAP/SSO) {#standalone-mode} - -Skip LDAP and the SSO Manager entirely. Not to be confused with "Standalone -Docker" below, which is still LDAP + SSO, just run outside theta-env. Set in your secrets/config: - -```js -standalone: { enabled: true }, -orm: { dialect: 'sqlite', storage: './data/standalone.sqlite', logging: false }, -``` - -`orm` is passed straight to Sequelize, so any supported dialect works — SQLite -is just the zero-dependency default. Everything downstream of auth (bridging, -key injection, the web UI, audit) is unchanged. - -There's no admin UI for standalone users/hosts yet, so add them directly with -the ORM models: - -```js -const StandaloneUser = require('./models/standalone_user'); -const StandaloneHost = require('./models/standalone_host'); -const bcrypt = require('bcrypt'); - -await StandaloneUser.create({ - uid: 'alice', - passwordHash: await bcrypt.hash('a real password', 10), - sshPublicKeys: ['ssh-ed25519 AAAA... alice@laptop'], - groups: [], -}); - -await StandaloneHost.create({ - slug: 'host_web01', - displayName: 'web01', - kind: 'host', - metadata: { ip: '10.0.0.5', sshPort: 22 }, -}); -``` - -Every host in the standalone inventory is reachable by every standalone user — -there's no group-based authorization yet (`groups` on `StandaloneUser` is -accepted for interface parity with the LDAP path, not enforced). - -The rest of this page (requirements, the LDAP write-ACL, the three install -paths) describes the default LDAP + SSO mode — skip it if you're running -standalone. - -## Requirements - -- The [SSO Manager](https://theta42.github.io/sso-manager-node/) (OpenLDAP - directory + `/api/discovery`), v1.3.0 or newer. -- Downstream hosts joined via - [ldap-client](https://github.com/theta42/ldap-client) (SSSD + - `AuthorizedKeysCommand`). -- An LDAP bind account with **write access to the `sshPublicKey` attribute** on - user entries (see below). -- An SSO API token (`sso_…`) for the directory queries. - -## 1. Unified theta-env stack (recommended) - -Enable it in `theta-env/setup.env`: - -```bash -CFG_JUMP_HOST_ENABLED=true -CFG_JUMP_HOST=jump.example.com -JUMP_SSH_PORT=2222 -``` - -Re-run `./setup.sh`. The stack builds the submodule (behind the `jump-host` -compose profile), mints the directory API token, writes -`./config/jump-secrets.js`, grants the `sshPublicKey` write-ACL, registers the -jump host in the proxy, and seeds a directory entry. Forward the public host's -`:22` (or `:2222`) to the container's published `JUMP_SSH_PORT`. - -## 2. Standalone Docker - -```bash -cp secrets.js.example config/jump-secrets.js -$EDITOR config/jump-secrets.js # LDAP bind (+ sshPublicKey write ACL), SSO url + token -docker compose up -d --build -``` - -Host keys persist in the `jump-data` volume. The web UI is on `:3002`; front it -with your own TLS/proxy. - -## 3. Bare metal - -```bash -curl -fsSL https://raw.githubusercontent.com/theta42/jump-host/master/ops/install.sh | sudo bash -sudo $EDITOR /etc/jump-host/secrets.js -sudo systemctl restart jump-host -journalctl -u jump-host -f -``` - -`ops/install.sh` installs Node 22 + Redis, hard-resets the checkout at -`/opt/theta42/jump-host` to the remote branch, symlinks the systemd unit, and -runs `npm ci`. Idempotent — re-run to update. Overridable via `REPO_DIR=`, -`BRANCH=`, `SECRETS_FILE=`. - -## The LDAP write-ACL (required) - -The jump host injects its public key into each user's `sshPublicKey`, so its -bind account must be able to **write** that attribute. In the bundled OpenLDAP: - -``` -access to attrs=sshPublicKey - by dn.exact="cn=ldapclient,ou=people,dc=example,dc=com" write - by self write - by * read -``` - -In the theta-env bundle this is handled for you (the jump host binds as the LDAP -admin). For a hardened standalone deployment, use a dedicated bind account with -exactly this attribute-scoped ACL. Without write access, key injection fails and -every bridge attempt is audited `key-inject-failed`. - -## Listening on port 22 - -The default SSH port is **2222** so the service needs no privilege. To listen on -22, set `ssh.listenPort: 22` and either: - -- **systemd:** uncomment `AmbientCapabilities=CAP_NET_BIND_SERVICE` in the unit; -- **Docker:** publish `22:22`; or -- **firewall:** DNAT `22 → 2222`. - -## Configuration reference - -Every key is documented in -[`secrets.js.example`](https://github.com/theta42/jump-host/blob/master/secrets.js.example): -`ldap` (bind + bases + TLS), `sso` (url + apiToken), `ssh` -(`listenPort`, `passwordAuth`, `allowRawIPs`, `keyComment`, timeouts, -`maxSessions`), `web.port`, `oidc` (web-UI SSO login), `auth` -(`adminGroups` / `adminUsers` / `localAdminPass`), and `redis`. - -## Verifying - -```bash -ssh -p 2222 youruid@jump.example.com # TUI picker -ssh -p 2222 youruid_-_somehost@jump.example.com # direct -sftp -P 2222 youruid_-_somehost@jump.example.com # WinSCP path -curl -s http://localhost:3002/health -``` - -Watch `journalctl -u jump-host -f` (or `docker logs -f jump-host`) and the audit -log at `/audit` in the web UI. diff --git a/docs/robots.txt b/docs/robots.txt deleted file mode 100644 index bd503ab..0000000 --- a/docs/robots.txt +++ /dev/null @@ -1,4 +0,0 @@ -User-agent: * -Allow: / - -Sitemap: https://theta42.github.io/proxy/sitemap.xml