Secure connections with AutoPass invites, HMAC capabilities, and viewer default.
Release rolling / release (push) Successful in 12m24s
Release rolling / release (push) Successful in 12m24s
Default peers are read-only; admin requires seed proof and operators redeem AutoPass packages with signed grants. ACL UI and docs match the new trust model.
This commit is contained in:
+14
-3
@@ -92,9 +92,16 @@ PEARDOCK_BROWSE_ROOTS=/var/lib/docker/volumes
|
||||
PEARDOCK_AUDIT=1
|
||||
```
|
||||
|
||||
3. As admin: **Access → Create invite** → send token to operator.
|
||||
4. Operator connects with server public key + invite token as supported.
|
||||
5. **Revoke** lost devices from Access.
|
||||
3. As **admin** (public key + `SERVER_SEED` in the client Add peer form): **Access → Create invite** → share the **AutoPass invite** string (never share `SERVER_SEED`).
|
||||
4. Operator pastes the AutoPass invite in **Add peer** — the client pairs, receives `{ publicKey, capability }`, and connects with the granted role.
|
||||
5. Public key alone is **viewer** (read-only). Optional: `PEARDOCK_INSECURE_OPEN_ADMIN=1` restores the old “everyone is admin” dev convenience (loud warning).
|
||||
6. **Revoke** lost devices from Access.
|
||||
|
||||
| Connect with | Role |
|
||||
|--------------|------|
|
||||
| Server public key only | `viewer` |
|
||||
| Public key + `SERVER_SEED` | `admin` (HMAC seed proof; seed stays on the client) |
|
||||
| AutoPass invite (z32) | Role in package (`viewer` / `operator` / `admin`) |
|
||||
|
||||
See [THREAT_MODEL.md](./THREAT_MODEL.md) for hardening rationale.
|
||||
|
||||
@@ -117,6 +124,10 @@ Credentials encrypted at rest (AES-GCM derived from `SERVER_SEED`):
|
||||
| `PEARDOCK_TUNNEL_HOSTS` | Extra allowed tunnel target hosts (comma-separated) |
|
||||
| `PEARDOCK_UNRESTRICTED_CLI=1` | Broader `docker` CLI for **admin** |
|
||||
| `PEARDOCK_BROWSE_OPEN=1` | Legacy open host FS browse (discouraged) |
|
||||
| `PEARDOCK_DEFAULT_ROLE` | Baseline peer role (`viewer` default; avoid `admin` in multi-op) |
|
||||
| `PEARDOCK_INSECURE_OPEN_ADMIN=1` | Dev only: every peer is admin (loud warning) |
|
||||
| `PEARDOCK_LEGACY_INVITES=1` | Allow old random 48-hex invite tokens |
|
||||
| `PEARDOCK_AUTOPASS_DIR` | AutoPass vault directory (default `./peardock-autopass`) |
|
||||
|
||||
## Logging
|
||||
|
||||
|
||||
+22
-5
@@ -11,7 +11,7 @@ Wire protocol for PearDock v2 control plane.
|
||||
| Name | Value | Meaning |
|
||||
|------|-------|---------|
|
||||
| `PROTOCOL` | `peardock/rpc` | protomux-rpc protocol id |
|
||||
| `PROTOCOL_VERSION` | `2` | Negotiated in `handshake` |
|
||||
| `PROTOCOL_VERSION` | `3` | Negotiated in `handshake` (HMAC auth) |
|
||||
| Encoding | compact-encoding JSON-ish values | Via `shared/encodings.js` |
|
||||
|
||||
Bump `PROTOCOL_VERSION` when breaking request/response shapes.
|
||||
@@ -34,7 +34,17 @@ flowchart LR
|
||||
Rank: viewer (1) < operator (2) < admin (3).
|
||||
`roleAllows(role, method)` requires rank ≥ `MethodRoles[method]` (default **admin** if method missing).
|
||||
|
||||
Default peer role when unconfigured: **admin** (`resolveRole` / `PEARDOCK_DEFAULT_ROLE`).
|
||||
Default peer role when unconfigured: **viewer** (`resolveRole` / `PEARDOCK_DEFAULT_ROLE`).
|
||||
|
||||
### Auth modes (handshake)
|
||||
|
||||
| Mode | Client provides | Resulting role |
|
||||
|------|-----------------|----------------|
|
||||
| `viewer` | Public key only | viewer (read-only) |
|
||||
| `seed` | `adminProof` HMAC from `SERVER_SEED` | admin |
|
||||
| `capability` | HMAC capability grant (often via AutoPass package) | role in grant |
|
||||
|
||||
Admin proof and capabilities are verified with `HMAC-SHA256` over a key derived from `SERVER_SEED` via HKDF (`shared/crypto-auth.js`). The seed is never sent on the wire.
|
||||
|
||||
---
|
||||
|
||||
@@ -60,9 +70,12 @@ sequenceDiagram
|
||||
|
||||
### Handshake
|
||||
|
||||
- Client may send invite token for peer registration.
|
||||
- Server validates protocol version compatibility.
|
||||
- Returns assigned **role** and **peerId** (client public key hex).
|
||||
- Client may send:
|
||||
- `adminProof: { nonce, mac }` — seed ownership
|
||||
- `capability` — HMAC grant (AutoPass package)
|
||||
- `inviteToken` — legacy / capability string (prefer AutoPass)
|
||||
- Server validates protocol version compatibility + HMAC proofs.
|
||||
- Returns assigned **role**, **peerId** (stable client public key hex), and `auth.mode`.
|
||||
|
||||
### Errors
|
||||
|
||||
@@ -74,6 +87,10 @@ Normalized codes (non-exhaustive):
|
||||
| `RATE_LIMIT_EXCEEDED` | Per-peer limiter |
|
||||
| `INVALID_ARGS` | Schema validation failed |
|
||||
| `FEATURE_DISABLED` | Swarm/Holesail/plugins gated off |
|
||||
| `CAPABILITY_INVALID` / `EXPIRED` / `SPENT` | HMAC grant failed |
|
||||
| `ADMIN_PROOF_FAILED` | Seed ownership proof rejected |
|
||||
| `PEER_DENIED` | Revoked or not on allowlist |
|
||||
| `INVITE_INVALID` | Legacy/capability invite redeem failed |
|
||||
| Docker-derived | Sanitized via `dockerErrors.js` |
|
||||
|
||||
---
|
||||
|
||||
+8
-6
@@ -105,7 +105,7 @@ All Engine access goes through **dockerode** on the machine running peardock-ser
|
||||
|
||||
### 5. Capabilities are gated by role
|
||||
|
||||
Every method has a minimum role in `MethodRoles` (`viewer` < `operator` < `admin`). Unknown methods default to **admin**. Default peer role is **admin** for single-operator convenience; harden for fleets.
|
||||
Every method has a minimum role in `MethodRoles` (`viewer` < `operator` < `admin`). Unknown methods default to **admin**. Default peer role is **viewer** (read-only); elevate with admin seed proof, AutoPass HMAC capability, `PEARDOCK_ADMIN_KEYS`, or `PEARDOCK_INSECURE_OPEN_ADMIN=1` for single-operator dev.
|
||||
|
||||
---
|
||||
|
||||
@@ -123,8 +123,8 @@ sequenceDiagram
|
||||
C->>DHT: connect(publicKey)
|
||||
DHT-->>C: secret stream (Noise)
|
||||
C->>S: protomux-rpc open
|
||||
C->>S: handshake { protocolVersion, inviteToken? }
|
||||
S-->>C: { peerId, role, protocolVersion, docker ok }
|
||||
C->>S: handshake { protocolVersion, adminProof? | capability? }
|
||||
S-->>C: { peerId, role, protocolVersion, auth.mode }
|
||||
C->>S: listContainers / deploy / …
|
||||
S->>D: dockerode API
|
||||
D-->>S: result
|
||||
@@ -214,11 +214,13 @@ Details: [OPERATOR.md](./OPERATOR.md), [THREAT_MODEL.md](./THREAT_MODEL.md).
|
||||
flowchart TB
|
||||
PK["Knows public key"] --> DIAL["Can dial DHT"]
|
||||
DIAL --> HS["handshake"]
|
||||
HS --> ROLE{"Role resolution"}
|
||||
ROLE -->|default| ADMIN["admin if unconfigured"]
|
||||
ROLE -->|hardened| VIEW["viewer / operator / admin keys"]
|
||||
HS --> AUTH{"Auth mode"}
|
||||
AUTH -->|pubkey only| VIEW["viewer"]
|
||||
AUTH -->|adminProof seed| ADMIN["admin"]
|
||||
AUTH -->|capability AutoPass| CAP["grant role"]
|
||||
VIEW --> MR["MethodRoles check"]
|
||||
ADMIN --> MR
|
||||
CAP --> MR
|
||||
MR --> RL["Rate limiter"]
|
||||
RL --> SCH["schema validateMethodArgs"]
|
||||
SCH --> HND["Handler → dockerode"]
|
||||
|
||||
+17
-9
@@ -12,7 +12,7 @@
|
||||
| `SERVER_SEED` | Critical: identity + vault key derivation |
|
||||
| Docker socket access | Critical: full host container control |
|
||||
| Registry passwords (vault) | High: encrypted at rest |
|
||||
| Peer invite tokens | Medium: short-lived capability grants |
|
||||
| HMAC capability grants / AutoPass invites | Medium: short-lived elevated access without seed |
|
||||
| Audit log | Medium: forensic integrity |
|
||||
| Container data / env secrets | High: via inspect, logs, exec, archive |
|
||||
| Holesail `hs://` URLs | High: capability to a published port |
|
||||
@@ -31,7 +31,7 @@
|
||||
```
|
||||
|
||||
- **Anyone with the server public key** can attempt a DHT connection.
|
||||
- **Default role is admin** unless `PEARDOCK_DEFAULT_ROLE` / `PEARDOCK_ADMIN_KEYS` / peer policy tighten it.
|
||||
- **Default role is viewer (read-only)** unless elevated via admin seed proof, HMAC capability (AutoPass), `PEARDOCK_ADMIN_KEYS`, peer policy, or `PEARDOCK_INSECURE_OPEN_ADMIN=1`.
|
||||
- **Plugins** are off unless `ENABLE_PLUGINS=1`.
|
||||
- **Swarm** APIs are on by default (`ENABLE_SWARM=0` to disable).
|
||||
- **Holesail tunnels** are on by default (`ENABLE_HOLESAIL=0` to disable). Each `hs://` URL is a capability to the target port.
|
||||
@@ -42,8 +42,8 @@
|
||||
|
||||
## 3. Adversaries
|
||||
|
||||
1. **Remote peer with public key only:** should not get Docker control if allowlist + non-admin default are set.
|
||||
2. **Stolen invite token:** limited by TTL / max uses; rotate after use.
|
||||
1. **Remote peer with public key only:** gets **viewer** only (read-only lists/inspect/logs).
|
||||
2. **Stolen AutoPass invite / capability:** limited by TTL / max uses + HMAC; revoke peer jti path via revokePeer after redeem.
|
||||
3. **Compromised client machine:** can use any role the peer holds until revoke.
|
||||
4. **Local host attacker with filesystem:** can steal `SERVER_SEED` and vault if file perms are wrong.
|
||||
5. **Holder of an `hs://` URL:** can reach the tunneled service until the tunnel is closed / key rotated.
|
||||
@@ -57,7 +57,12 @@
|
||||
|---------|-----------|
|
||||
| Transport E2E | HyperDHT Noise |
|
||||
| Capability ACL | `viewer` / `operator` / `admin` + `MethodRoles` |
|
||||
| Peer policy | Invite, register, revoke, optional allowlist |
|
||||
| Default role | **viewer** (secure); elevate via seed HMAC / capability |
|
||||
| Admin proof | HMAC-SHA256 of seed-derived macKey (never sends seed on wire) |
|
||||
| HMAC grants | Signed capability tokens (role, exp, jti); constant-time verify |
|
||||
| AutoPass invites | Distributes `{publicKey, capability}` packages without seed |
|
||||
| Peer policy | Register, revoke, capability jti spend, optional allowlist |
|
||||
| Stable client id | Persistent DHT keypair (`~/.config/peardock/identity.json`) |
|
||||
| Audit | Append-only log for privileged methods |
|
||||
| Rate limit | Per-peer limiter on RPC |
|
||||
| Registry secrets | AES-256-GCM vault |
|
||||
@@ -69,7 +74,7 @@
|
||||
|
||||
## 5. Residual risks
|
||||
|
||||
- **Default admin** is intentional for single-operator setup. Change it for multi-tenant fleets.
|
||||
- **Default viewer** is intentional for production. Use `PEARDOCK_INSECURE_OPEN_ADMIN=1` only for single-operator dev convenience.
|
||||
- Binary image/export streams are size-capped but still large; DoS via memory if many concurrent transfers.
|
||||
- JSON-over-RPC is not hyperschema-validated end-to-end; malformed args rely on handler validation.
|
||||
- Swarm secrets/configs once enabled are highly privileged.
|
||||
@@ -79,13 +84,16 @@
|
||||
## 6. Operator hardening checklist
|
||||
|
||||
- [ ] Generate unique `SERVER_SEED`; back up offline; never commit `.env`
|
||||
- [ ] Set `PEARDOCK_DEFAULT_ROLE=viewer` or `operator`
|
||||
- [ ] Set `PEARDOCK_ADMIN_KEYS=<your client public key hex>`
|
||||
- [ ] Connect as admin with public key + `SERVER_SEED` in the client (seed is session-only)
|
||||
- [ ] Share operators via Access → AutoPass invite (never share `SERVER_SEED`)
|
||||
- [ ] Confirm public-key-only peers are viewer (read-only)
|
||||
- [ ] Set `PEARDOCK_ADMIN_KEYS=<client public key hex>` for fixed admin machines (optional)
|
||||
- [ ] Enable `PEARDOCK_PEER_ALLOWLIST=1` after registering operators
|
||||
- [ ] Do **not** set `PEARDOCK_INSECURE_OPEN_ADMIN` in production
|
||||
- [ ] Set `PEARDOCK_BROWSE_ROOTS` only if host path pickers are needed
|
||||
- [ ] Set `ENABLE_SWARM=0` if Swarm APIs are not needed; leave `ENABLE_PLUGINS` off unless required
|
||||
- [ ] Treat `hs://` tunnel URLs as secrets; set `ENABLE_HOLESAIL=0` if unused
|
||||
- [ ] File mode `600` on vault, peer policy, tunnels, audit, `.env`
|
||||
- [ ] File mode `600` on vault, peer policy, autopass dir, tunnels, audit, `.env`, identity
|
||||
- [ ] Run server as non-root in `docker` group (see `deploy/peardock.service`)
|
||||
- [ ] Enable `PEARDOCK_AUDIT=1` and review `peardock-audit.log` periodically
|
||||
- [ ] Revoke lost client keys immediately (`revokePeer` / Access UI)
|
||||
|
||||
Reference in New Issue
Block a user