dsh-remote 0.4.0: edge auth – slaves log in to an auth proxy in front of the master
- lib/edge-auth.mjs: basic / bearer / oauth2 client_credentials (Authentik M2M) / custom headers (Cloudflare Access), proxy session cookie jar, retry with fresh token, clear edge-refusal vs DSH-refusal errors - link + enroll go through the edge; master.edgeAuth stored at pair time, secrets as DSH credential refs only; config edgeAuth override - /dsh-remote/edge-auth: show/change/probe; Settings UI section + pair form - pair.mjs / install-mac.sh: --edge … flags, secrets prompted via /dev/tty - tests: fake Authentik-like proxy for all modes + WebSocket through the edge
This commit is contained in:
1 parent
5dff41a3d0
commit
78fce8e1ce
12 files changed
+833
-40
No files matched your search
@@ -50,7 +50,13 @@ On first link-up the Mac pulls the master's plugins and config. With `sync.resta
|
||||
|
||||
Re-running the script updates everything and keeps the pairing. To remove it: `launchctl bootout gui/$(id -u)/dev.rizzy.dsh-web`, then delete `~/Library/LaunchAgents/dev.rizzy.dsh-web.plist`, `~/.dsh` and `~/.local/opt/node`.
|
||||
|
||||
Away from home, use `--master https://dsh.rizzy.dev` once the edge exempts `/dsh-remote/link` and `/dsh-remote/enroll` from Authentik (see below).
|
||||
Away from home, point `--master` at the public URL. If an auth proxy guards it, add the `--edge …` flags (see *Masters behind an auth proxy*). Secrets are prompted for on the terminal and never passed on the command line:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.rizzy.dev/DSH-Plugins/dsh-remote/raw/branch/main/deploy/install-mac.sh \
|
||||
| bash -s -- --master https://dsh.rizzy.dev --code XXXX-XXXX-XXXX-XXXX --name my-mac \
|
||||
--edge basic --edge-user dsh-remote-mac
|
||||
```
|
||||
|
||||
## Pair a device
|
||||
|
||||
@@ -143,16 +149,32 @@ Local plugins are compared by content hash and re-shipped only when they change.
|
||||
- Use TLS (`https://` master URL) whenever the link crosses an untrusted network.
|
||||
- **Agent control is real remote code execution.** `remote_exec`, `remote_file` and `remote_task` run as the device's DSH user. Anyone who can prompt a master session can run code on every paired device. Revoke a device, or set `agentTools.enabled: false` on it, to opt it out.
|
||||
|
||||
## Public access via rizzy.dev
|
||||
## Masters behind an auth proxy (edge auth)
|
||||
|
||||
`dsh.rizzy.dev` is served by the edge Traefik behind **Authentik forward-auth**. Browsers log in through Authentik as usual. A slave's DSH can't do an interactive login, so the edge must let these two paths through without forward-auth (they carry their own device-key / pairing-code authentication):
|
||||
A master published behind an identity-aware proxy (Authentik, oauth2-proxy, Cloudflare Access, Traefik/nginx Basic auth) redirects every request that isn't logged in. Browsers log in normally. A slave's DSH logs in to the proxy itself with a machine credential, keeps the proxy's session cookie, and sends both on `/dsh-remote/enroll` and on the `/dsh-remote/link` WebSocket. It never sends them anywhere else. Behind the proxy, the usual device key still authenticates the slave to DSH.
|
||||
|
||||
```
|
||||
/dsh-remote/link (WebSocket upgrade)
|
||||
/dsh-remote/enroll (POST, one-time pairing code)
|
||||
```
|
||||
| Mode | Sends | Typical use |
|
||||
| --- | --- | --- |
|
||||
| `basic` | `Authorization: Basic user:password` | **Authentik**: a service-account user plus an *App password*, with *Intercept header authentication* enabled on the proxy provider (the default). Also plain Basic-auth proxies. Authentik accepts user `goauthentik.io/token` with an API token as the password too. |
|
||||
| `oauth2` | `client_credentials` grant at `tokenUrl`, then `Authorization: Bearer <JWT>`, refreshed before it expires | Authentik machine-to-machine (`client_id` of the proxy provider, service-account `username` + app password, optional `client_secret`), Keycloak, oauth2-proxy |
|
||||
| `bearer` | `Authorization: Bearer <static token>` | gateways that accept a static API token |
|
||||
| `headers` | any headers, e.g. `CF-Access-Client-Id` / `CF-Access-Client-Secret` | Cloudflare Access service tokens, custom gateways |
|
||||
|
||||
For example, add a second Traefik router with a higher priority, `Host(\`dsh.rizzy.dev\`) && (Path(\`/dsh-remote/link\`) || Path(\`/dsh-remote/enroll\`))`, that uses the same service but no Authentik middleware. Until that exists, pair and link slaves over the LAN (`http://192.168.0.111:3080`).
|
||||
**Setting it up:**
|
||||
|
||||
- **New slave:** the *Pair with a master* form has an **Auth proxy** section. On the command line, use `pair.mjs` or `install-mac.sh` with `--edge basic --edge-user USER`, or `--edge oauth2 --edge-token-url URL --edge-client-id ID [--edge-user USER] [--edge-client-secret]`, or `--edge bearer`, or `--edge headers --edge-header "Name: value"`.
|
||||
- **Paired slave:** Settings → *Remote devices* → **Auth proxy (edge) login** → **Change**. **Test** checks whether the proxy now lets this device through to the master's DSH.
|
||||
- **Config override:** an `edgeAuth: { mode, … }` block in the slave's dsh-remote config takes precedence. Secrets are referenced as `passwordRef` / `tokenRef` / `clientSecretRef` / `"ref:NAME"`, never inline.
|
||||
|
||||
**Secrets:** passwords, tokens and header values are stored as DSH credentials (`DSH_REMOTE_EDGE_PASSWORD`, `DSH_REMOTE_EDGE_TOKEN`, `DSH_REMOTE_EDGE_CLIENT_SECRET`, `DSH_REMOTE_EDGE_HEADER_n` in `~/.dsh/.credentials.yaml`, 0600). The link state holds only the ref names.
|
||||
|
||||
**How it behaves:**
|
||||
|
||||
- A login redirect, or a 401/403 that isn't from DSH, counts as an *edge refusal*. The link reports "auth proxy … blocked the link / rejected the edgeAuth credentials". It retries with a fresh token and a cleared cookie jar, then backs off to 15–30 s.
|
||||
- DSH's own "unknown or revoked device" 401 still means *re-pair*.
|
||||
- The proxy's session cookie is reused, so the proxy isn't asked to verify the credential on every reconnect, as Authentik recommends.
|
||||
|
||||
**rizzy.dev with Authentik:** create a service-account user (e.g. `dsh-remote-mac`), make sure the DSH application's policies allow it, and create an *App password* for it under Directory → Tokens and App passwords. Then pair with `--edge basic --edge-user dsh-remote-mac`. The alternative is to exempt `/dsh-remote/link` and `/dsh-remote/enroll` from forward-auth with a higher-priority Traefik router; those paths carry their own device-key / pairing-code auth.
|
||||
|
||||
## HTTP API (used by the UI)
|
||||
|
||||
@@ -169,6 +191,7 @@ For example, add a second Traefik router with a higher priority, `Host(\`dsh.riz
|
||||
| `GET /dsh-remote/sync/status` | manifest (master) / last result (slave) |
|
||||
| `POST /dsh-remote/new-session` | `{device, workspaceId? , cwd?}` |
|
||||
| `GET /dsh-remote/debug/inspect` | `?sessionId=…&device=…`: last events of a session on any device |
|
||||
| `GET/POST /dsh-remote/edge-auth` | slave: show / set `{edgeAuth}` and probe whether the auth proxy lets it through |
|
||||
| `* /r/<device>/…` | that device's DSH |
|
||||
|
||||
## Development
|
||||
|
||||
Reference in new issue
Block a user