Add SECURITY_AUTH.md for admin access and invite model.
Release rolling / release (push) Successful in 9m8s
Release rolling / release (push) Successful in 9m8s
Document seed proof, roles, pd1 capabilities, redeem/reconnect, and revoke, and link it from the README, threat model, operator, and protocol docs.
This commit is contained in:
+1
-1
@@ -118,7 +118,7 @@ PEARDOCK_AUDIT=1
|
||||
| Public key + `SERVER_SEED` | `admin` (HMAC seed proof; seed stays on the client) |
|
||||
| peardock invite (`pd1.…`) | Role in package (`viewer` / `operator` / `admin`) |
|
||||
|
||||
See [THREAT_MODEL.md](./THREAT_MODEL.md) for hardening rationale.
|
||||
See [SECURITY_AUTH.md](./SECURITY_AUTH.md) for the full auth and invite model, and [THREAT_MODEL.md](./THREAT_MODEL.md) for hardening rationale.
|
||||
|
||||
## Registry vault
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ Default peer role when unconfigured: **viewer** (`resolveRole` / `PEARDOCK_DEFAU
|
||||
|
||||
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.
|
||||
|
||||
Full model (roles, pd1 packages, redeem/reconnect, revoke): [SECURITY_AUTH.md](./SECURITY_AUTH.md).
|
||||
|
||||
---
|
||||
|
||||
## Session flow
|
||||
|
||||
@@ -33,6 +33,7 @@ PearDock is **decentralized Docker management**: a HyperDHT control plane, struc
|
||||
| Document | What you will learn |
|
||||
|----------|---------------------|
|
||||
| **[OPERATOR.md](./OPERATOR.md)** | Install, systemd, ACL, env flags, logging, backup |
|
||||
| **[SECURITY_AUTH.md](./SECURITY_AUTH.md)** | Admin seed proof, roles, pd1 invites, redeem/revoke (full model) |
|
||||
| **[HOLESAIL.md](./HOLESAIL.md)** | Data-plane tunnels in depth |
|
||||
| **[THREAT_MODEL.md](./THREAT_MODEL.md)** | Assets, adversaries, controls, hardening checklist |
|
||||
| **[RELEASE.md](./RELEASE.md)** | Host matrix, Bare + Electron builds, rolling CI |
|
||||
|
||||
@@ -0,0 +1,536 @@
|
||||
# peardock security: admin access and invites
|
||||
|
||||
**Audience:** operators and multi-user fleets who need a full mental model of authentication, roles, and invites.
|
||||
**Related:** [THREAT_MODEL.md](./THREAT_MODEL.md) (assets / adversaries / checklist) · [OPERATOR.md](./OPERATOR.md) (install and env) · [PROTOCOL.md](./PROTOCOL.md) (handshake and methods) · Website: [peardock.boats/docs/security](https://peardock.boats/docs/security)
|
||||
|
||||
Peardock is a **self-hosted P2P Docker control plane**. There is no central login service. Security is built from:
|
||||
|
||||
1. **Cryptographic identity** (who is the server / who is the client)
|
||||
2. **Encrypted transport** (HyperDHT Noise)
|
||||
3. **Roles** enforced on every RPC method
|
||||
4. **Elevation proofs** at handshake (seed admin, capability / `pd1.` invite, registration)
|
||||
|
||||
Anyone who learns the **server public key** can *dial* the server. That alone must not mean full Docker control. Default access is **viewer**. Admin and operator access need separate proofs.
|
||||
|
||||
---
|
||||
|
||||
## 1. What you are protecting
|
||||
|
||||
| Asset | Sensitivity | Notes |
|
||||
|-------|-------------|--------|
|
||||
| `SERVER_SEED` | Critical | Server identity + HMAC signing material + registry vault derivation |
|
||||
| `SERVER_PUBLIC_KEY` | Public / low | How clients dial; alone = read-only |
|
||||
| Docker socket | Critical | Full container/host control on that machine |
|
||||
| Capability / `pd1.` invites | Medium | Elevation without sharing the seed |
|
||||
| Client identity key | High | Stable “who is this laptop” for reconnect and revoke |
|
||||
| `hs://` tunnel URLs | High | Separate data-plane access to a published port |
|
||||
|
||||
Trust boundary:
|
||||
|
||||
```text
|
||||
Desktop client --Noise/HyperDHT--> peardock-server --unix socket--> dockerd
|
||||
|
|
||||
+-- /opt/peardock/.env (seed + public key)
|
||||
+-- peardock-peers.json (roles, jtis, revokes)
|
||||
+-- peardock-vault.json (encrypted registry creds)
|
||||
+-- peardock-audit.log (optional)
|
||||
```
|
||||
|
||||
Peardock does **not** open a public Docker API port. Peers hole-punch. That removes one class of attack (internet-facing dockerd) but **does not** replace key discipline.
|
||||
|
||||
---
|
||||
|
||||
## 2. Server identity: seed vs public key
|
||||
|
||||
On first successful start the server generates or loads a 32-byte seed and writes (typically `/opt/peardock/.env`, mode 600):
|
||||
|
||||
```bash
|
||||
SERVER_SEED=<64 hex>
|
||||
SERVER_PUBLIC_KEY=<64 hex>
|
||||
```
|
||||
|
||||
| Variable | Who may see it | Client use |
|
||||
|----------|----------------|------------|
|
||||
| `SERVER_PUBLIC_KEY` | Safe to share for read-only | Paste alone → **viewer** |
|
||||
| `SERVER_SEED` | **Admins only** | Paste with public key → **admin** (seed stays on the client; never on the wire) |
|
||||
|
||||
Rotating the seed **changes the public key**. Every client must re-add the peer.
|
||||
|
||||
From the seed, peardock derives a **MAC key** via HKDF (`peardock-hmac-v1` / `capability`). That key signs:
|
||||
|
||||
- Admin proofs at handshake
|
||||
- HMAC capability grants used by invites
|
||||
|
||||
The seed itself is **never sent on the wire**.
|
||||
|
||||
After install, always check the env file:
|
||||
|
||||
```bash
|
||||
sudo grep -E '^(SERVER_PUBLIC_KEY|SERVER_SEED)=' /opt/peardock/.env
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Client identity
|
||||
|
||||
Each desktop client keeps a **stable DHT keypair** (e.g. `~/.config/peardock/identity.json`).
|
||||
The server sees the client as a 64-hex **peer id** (client public key).
|
||||
|
||||
Used for:
|
||||
|
||||
- Role registration after first successful invite redeem
|
||||
- Reconnect without re-spending limited invites
|
||||
- Revoke / allowlist decisions
|
||||
- Binding admin proofs to “this client right now”
|
||||
|
||||
If you wipe client identity, the server treats you as a **new peer** even if you still know the server public key.
|
||||
|
||||
---
|
||||
|
||||
## 4. Transport
|
||||
|
||||
Connection lifecycle:
|
||||
|
||||
1. Client dials `SERVER_PUBLIC_KEY` on HyperDHT
|
||||
2. Noise establishes an encrypted, mutually authenticated stream
|
||||
3. protomux-rpc opens on that stream
|
||||
4. Client calls `handshake` with optional elevation fields
|
||||
5. Server assigns a **role** for the session
|
||||
6. Every later method is checked against that role
|
||||
|
||||
There is **no password login**. Auth is “can dial this public key” plus “what elevation did handshake prove?”
|
||||
|
||||
---
|
||||
|
||||
## 5. Roles (the real ACL)
|
||||
|
||||
Three roles, ordered:
|
||||
|
||||
```text
|
||||
viewer < operator < admin
|
||||
```
|
||||
|
||||
Every RPC method has a **minimum role** in `MethodRoles` (`shared/protocol.js`). Unknown methods default to **admin** (fail closed). The UI may hide actions, but **the server is authoritative**.
|
||||
|
||||
| Role | Typical powers |
|
||||
|------|----------------|
|
||||
| **viewer** | List/inspect containers, images, volumes, logs, stats, Swarm inspect, list peers. No mutate. **Cannot** create/list/delete invites. |
|
||||
| **operator** | Day-to-day mutate: start/stop/kill, pull, stacks, many Swarm ops, terminals, etc. Still **cannot** manage invites/ACL. |
|
||||
| **admin** | Everything operator can + invite/revoke ACL, vault management, destructive prune/recreate, unrestricted CLI if enabled, etc. |
|
||||
|
||||
Invite management is **admin-only**:
|
||||
|
||||
- `invitePeer`, `listInvites`, `deleteInvite`
|
||||
- `revokePeer`, `unrevokePeer`, `clearRevokedPeers`, `listRevokedPeers`
|
||||
|
||||
Operators run Docker work. Only admins mint access for other people.
|
||||
|
||||
---
|
||||
|
||||
## 6. How a session gets its role
|
||||
|
||||
### Baseline (before elevation)
|
||||
|
||||
When a peer connects, baseline role comes from `resolveRole(peerId)` (`server/core/acl.js`):
|
||||
|
||||
1. If `PEARDOCK_INSECURE_OPEN_ADMIN=1` → everyone is admin (**dev only**; loud warning).
|
||||
2. Else if peer id is in `PEARDOCK_ADMIN_KEYS` → admin.
|
||||
3. Else `PEARDOCK_DEFAULT_ROLE` (default **`viewer`**).
|
||||
4. Peer policy (`peardock-peers.json`) can apply registered role; revoke can block the peer.
|
||||
|
||||
Secure default: **public key alone = viewer**.
|
||||
|
||||
### Handshake elevation
|
||||
|
||||
Handshake tries elevations in order (`server/rpc/session.js`):
|
||||
|
||||
```text
|
||||
1) adminProof → seed ownership → admin (authMode: seed)
|
||||
2) capability → HMAC grant (often from pd1) (authMode: capability | registered)
|
||||
3) inviteToken → legacy / capability string path (if still applicable)
|
||||
4) otherwise → keep baseline (usually viewer)
|
||||
```
|
||||
|
||||
**Fail closed:** if the client **presents a capability** and it is invalid, spent, or expired, and the peer is **not** already registered as elevated, the handshake **fails**. It does **not** silently succeed as viewer.
|
||||
|
||||
If capability fails **but** this peer id is already registered as operator/admin, reconnect can fall back to the **registered** role so restarts keep working after grants are rotated or spent.
|
||||
|
||||
Final session role is effectively:
|
||||
|
||||
```text
|
||||
session.role = max(baseline, elevated)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Admin access (seed proof)
|
||||
|
||||
### Goal
|
||||
|
||||
Prove “I know `SERVER_SEED`” without sending the seed over the network.
|
||||
|
||||
### Flow
|
||||
|
||||
1. Operator pastes **public key + `SERVER_SEED`** in Add peer.
|
||||
2. Seed is kept **local/session** on the client.
|
||||
3. At handshake the client builds:
|
||||
|
||||
```text
|
||||
adminProof = {
|
||||
nonce: random hex,
|
||||
mac: HMAC-SHA256(macKey, "peardock-admin-v1" || nonce || peerId || serverPublicKey)
|
||||
}
|
||||
```
|
||||
|
||||
where `macKey` is derived from the seed (same HKDF family as capabilities).
|
||||
|
||||
4. Server recomputes the MAC with **its** seed-derived key and constant-time compares.
|
||||
5. Success → role **admin**, `authMode: seed`.
|
||||
6. Failure → `ADMIN_PROOF_FAILED` (handshake does not demote to a silent viewer).
|
||||
|
||||
Why bind **nonce + peerId + server public key**:
|
||||
|
||||
- **nonce** — replay resistance within practical bounds
|
||||
- **peerId** — proof is for this client identity
|
||||
- **serverPublicKey** — proof is for this server
|
||||
|
||||
### Alternate admin paths
|
||||
|
||||
| Path | Use |
|
||||
|------|-----|
|
||||
| Seed proof | Primary interactive admin |
|
||||
| `PEARDOCK_ADMIN_KEYS=<client peer public keys>` | Fixed admin machines without pasting seed each time |
|
||||
| `PEARDOCK_INSECURE_OPEN_ADMIN=1` | Dev only: every peer is admin |
|
||||
| Capability with `role=admin` | Possible via invite; treat carefully |
|
||||
|
||||
**Best practice:** one or two human admins use seed (or `PEARDOCK_ADMIN_KEYS`). Operators get **invites**, never the seed.
|
||||
|
||||
---
|
||||
|
||||
## 8. Invite system (capabilities + pd1)
|
||||
|
||||
Autopass / RocksDB is **not** used for invites. Invites are pure crypto plus a small policy file.
|
||||
|
||||
### Two layers
|
||||
|
||||
1. **Capability token** — cryptographic grant (what handshake verifies)
|
||||
2. **`pd1.` package** — share string that embeds public key + capability for convenience
|
||||
|
||||
Auth is always the capability. The pd1 string is packaging so operators paste one thing.
|
||||
|
||||
### Capability token format
|
||||
|
||||
```text
|
||||
base64url(JSON payload) + "." + base64url(HMAC-SHA256(macKey, payloadBytes))
|
||||
```
|
||||
|
||||
Canonical payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"v": 1,
|
||||
"role": "operator",
|
||||
"peerId": null,
|
||||
"exp": null,
|
||||
"jti": "<random 16-byte hex>",
|
||||
"iat": 1710000000000
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Meaning |
|
||||
|--------|---------|
|
||||
| `role` | Role granted when redeemed |
|
||||
| `exp` | Unix ms expiry, or `null` = never |
|
||||
| `jti` | Unique grant id (spend / delete / audit) |
|
||||
| `peerId` | Optional bind to one client; usually `null` so the first redeemer can use it |
|
||||
| `iat` | Issued-at |
|
||||
|
||||
Server verification (`shared/crypto-auth.js` + `server/core/peer-policy.js`):
|
||||
|
||||
- MAC over canonical payload with server MAC key
|
||||
- Version / role validity
|
||||
- Expiry if present
|
||||
- Optional peer binding
|
||||
- Spend / revocation via policy (`spentJtis` + capability metadata)
|
||||
|
||||
Forging a valid capability without the seed requires breaking HMAC-SHA256 under the derived key.
|
||||
|
||||
### Defaults when minting invites
|
||||
|
||||
Unless you set TTL / max uses:
|
||||
|
||||
- **Never expires** (`exp = null`)
|
||||
- **Unlimited uses** (`maxUses = 0`)
|
||||
- Intended as a durable operator grant package
|
||||
|
||||
You can tighten with TTL hours, max uses (e.g. `1` for one-shot), or env defaults such as `PEARDOCK_INVITE_MAX_USES`.
|
||||
|
||||
### Mint (admin: Access → Create invite)
|
||||
|
||||
1. Server requires **admin** for `invitePeer`.
|
||||
2. `mintCapability({ role, ttlHours, maxUses })` signs a token.
|
||||
3. Metadata stored in `peardock-peers.json` under `capabilities[jti]`.
|
||||
4. Server wraps a **pd1.** string:
|
||||
|
||||
```text
|
||||
pd1.<base64url JSON {
|
||||
v, publicKeyHex, capability, role, jti, alias?, expiresAt?
|
||||
}>
|
||||
```
|
||||
|
||||
5. Admin copies the **full** string (truncation breaks decode).
|
||||
|
||||
### Redeem (operator pastes pd1 in Add peer)
|
||||
|
||||
Client:
|
||||
|
||||
1. Collapses whitespace
|
||||
2. Decodes `pd1.` → `{ publicKeyHex, capability, role, jti, ... }`
|
||||
3. Dials `publicKeyHex`
|
||||
4. Handshakes with `capability` (not the seed)
|
||||
5. Caches capability for reconnect; uses stable client identity
|
||||
|
||||
Server:
|
||||
|
||||
1. `redeemCapability(token, peerId)`
|
||||
2. Verifies HMAC
|
||||
3. Checks spent / expiry / use limits
|
||||
4. **Registers** peer id with granted role in `peers{}`
|
||||
5. Session role becomes that grant (e.g. operator)
|
||||
|
||||
### First redeem vs reconnect
|
||||
|
||||
**First time** a new client identity redeems a **limited** grant:
|
||||
|
||||
- `uses` may increment
|
||||
- if `maxUses` reached, jti is spent and removed from active capabilities
|
||||
|
||||
**Unlimited / persistent grants** (default):
|
||||
|
||||
- jti stays active
|
||||
- multiple clients *can* use the same invite string unless you restrict maxUses or delete/rotate
|
||||
|
||||
**Reconnect after successful first elevate:**
|
||||
|
||||
- Peer is in `peers[peerId].role = operator` (or admin)
|
||||
- Later handshakes can succeed as `authMode: registered` even if that jti was later deleted or spent
|
||||
|
||||
Implications:
|
||||
|
||||
- **Revoking a person** means **revoke their peer id**, not only delete the invite string.
|
||||
- **Delete invite** stops *new* redemptions of that jti; it does not always kick already-registered operators.
|
||||
- For one-time share: set max uses = 1; after they connect, optionally delete invite and rely on registration + future revoke.
|
||||
|
||||
### Error codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| `CAPABILITY_INVALID` | Bad MAC / malformed / wrong server seed |
|
||||
| `CAPABILITY_EXPIRED` | Past `exp` |
|
||||
| `CAPABILITY_SPENT` | jti deleted/exhausted and peer not already elevated |
|
||||
| `CAPABILITY_PEER_MISMATCH` | Grant bound to another peer id |
|
||||
| `ADMIN_PROOF_FAILED` | Seed MAC proof failed |
|
||||
| `INVITE_INVALID` | Invite management / not found |
|
||||
|
||||
If a saved peer still has a **stale spent capability** cached, reconnect can fail with `CAPABILITY_SPENT`. Fix: paste a **new full pd1 string**, clear the cached capability, or rely on registration if already elevated.
|
||||
|
||||
### Why not Autopass
|
||||
|
||||
Older builds used Autopass (HyperDB / Corestore / RocksDB) to distribute packages. That path caused freezes, stale grants, and RocksDB/glibc issues on older hosts. **pd1 embeds everything needed at paste time.** Auth is pure HMAC. Server policy tracks jtis and peer roles. No RocksDB on the invite path.
|
||||
|
||||
---
|
||||
|
||||
## 9. Peer policy file
|
||||
|
||||
Default path: `./peardock-peers.json` (override with `PEARDOCK_PEER_POLICY`). Typical fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"revoked": ["deadbeef..."],
|
||||
"peers": {
|
||||
"clientpublickeyhex...": {
|
||||
"role": "operator",
|
||||
"alias": "alice-laptop",
|
||||
"note": "capability:3b3fdaa8"
|
||||
}
|
||||
},
|
||||
"capabilities": {
|
||||
"jti...": { "role": "operator", "exp": null, "maxUses": 0, "uses": 0 }
|
||||
},
|
||||
"spentJtis": ["oldjti..."],
|
||||
"invites": {}
|
||||
}
|
||||
```
|
||||
|
||||
| Piece | Role |
|
||||
|--------|------|
|
||||
| `peers` | Registered elevated clients for reconnect |
|
||||
| `capabilities` | Active grants admins minted |
|
||||
| `spentJtis` | Dead jtis (deleted / exhausted) |
|
||||
| `revoked` | Hard ban list of peer ids |
|
||||
| `invites` | Legacy tokens only if `PEARDOCK_LEGACY_INVITES=1` |
|
||||
|
||||
### Revoke
|
||||
|
||||
Admin `revokePeer(peerId)`:
|
||||
|
||||
- Peer id added to `revoked`
|
||||
- Live session destroyed if connected
|
||||
- Further connects from that identity should be refused
|
||||
|
||||
Unrevoke / clear revoked are admin recovery tools.
|
||||
|
||||
### Allowlist mode
|
||||
|
||||
`PEARDOCK_PEER_ALLOWLIST=1` tightens who may connect (registered / allowlisted peers, seed-admins, or valid capability holders, per policy). Use after operators are onboarded. Without allowlist, dial is open to public-key holders as **viewer**.
|
||||
|
||||
---
|
||||
|
||||
## 10. End-to-end workflows
|
||||
|
||||
### Single admin
|
||||
|
||||
1. Install server → keys in `/opt/peardock/.env`
|
||||
2. Client Add peer: public key + `SERVER_SEED` → **admin**
|
||||
3. Operate Docker yourself
|
||||
|
||||
### Multi-operator (recommended)
|
||||
|
||||
1. Admin connects with seed proof
|
||||
2. Access → Create invite (usually **operator**, persistent by default)
|
||||
3. Share **only** the full `pd1.…` string
|
||||
4. Operator pastes invite → elevated role; peer registered
|
||||
5. Never share `SERVER_SEED`
|
||||
6. Lost laptop → Access → **revoke** that peer id
|
||||
7. Leaked invite before use → delete invite, mint a new one
|
||||
|
||||
### Viewer guest
|
||||
|
||||
1. Share only `SERVER_PUBLIC_KEY`
|
||||
2. Observe-only; no mutate, no invites
|
||||
|
||||
### Connect summary
|
||||
|
||||
| Paste | Access |
|
||||
|-------|--------|
|
||||
| Public key only | `viewer` |
|
||||
| Public key + `SERVER_SEED` | `admin` |
|
||||
| Full `pd1.…` invite | Role in package (`viewer` / `operator` / `admin`) |
|
||||
|
||||
---
|
||||
|
||||
## 11. What each party knows
|
||||
|
||||
```text
|
||||
Public / intentionally shared
|
||||
SERVER_PUBLIC_KEY
|
||||
|
||||
Admin secret
|
||||
SERVER_SEED → mint any capability, admin proof, vault key material
|
||||
|
||||
Operator credential
|
||||
A pd1 invite and/or registered peer identity after redeem
|
||||
|
||||
Viewer credential
|
||||
SERVER_PUBLIC_KEY only
|
||||
|
||||
Server private state
|
||||
.env seed, peers policy, vault, audit
|
||||
```
|
||||
|
||||
Knowing the public key is like knowing a hostname fingerprint: you can reach the service. It is **not** root.
|
||||
|
||||
---
|
||||
|
||||
## 12. Mental model
|
||||
|
||||
```text
|
||||
┌─────────────────────────────┐
|
||||
│ SERVER_SEED (admin secret) │
|
||||
│ derives: DHT keys + MAC key│
|
||||
└─────────────┬───────────────┘
|
||||
│
|
||||
signs │ proves ownership
|
||||
┌────────────────────────────┼────────────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
capability token SERVER_PUBLIC_KEY adminProof HMAC
|
||||
(role, jti, exp) (dial target) (handshake)
|
||||
│ │ │
|
||||
│ packaged as pd1. │ dial only │
|
||||
▼ ▼ ▼
|
||||
operator invite viewer session admin session
|
||||
│
|
||||
└─ first success → register peerId → reconnect as registered
|
||||
```
|
||||
|
||||
Three legitimate ways in:
|
||||
|
||||
1. **Viewer** — public key
|
||||
2. **Admin** — public key + seed proof (or admin keys / insecure flag)
|
||||
3. **Operator (or other grant)** — pd1 invite → capability redeem → registration
|
||||
|
||||
---
|
||||
|
||||
## 13. Related controls (outside invite/admin)
|
||||
|
||||
| Control | Mechanism |
|
||||
|---------|-----------|
|
||||
| Transport E2E | HyperDHT Noise |
|
||||
| Rate limit | Per-peer limiter on RPC |
|
||||
| Audit | Append-only log for privileged methods (`PEARDOCK_AUDIT=1`) |
|
||||
| Feature gates | `ENABLE_SWARM`, `ENABLE_HOLESAIL`, `ENABLE_PLUGINS` |
|
||||
| Host FS browse | Default-deny unless roots / open mode |
|
||||
| Tunnel targets | Loopback / allowlisted hosts only |
|
||||
| Registry vault | AES-256-GCM keyed from server material |
|
||||
|
||||
Holesail `hs://` is a **separate** data-plane capability for a port. It is not the Docker control role. Treat tunnel URLs as secrets.
|
||||
|
||||
---
|
||||
|
||||
## 14. Threats (summary)
|
||||
|
||||
| Threat | Outcome with current model |
|
||||
|--------|----------------------------|
|
||||
| Only public key | Viewer (read-only) |
|
||||
| Stolen pd1 invite | Elevate to grant role until delete/spend/expire; after redeem, revoke peer |
|
||||
| Stolen `SERVER_SEED` | Full admin + mint grants + vault risk — catastrophic |
|
||||
| Compromised admin laptop | Until revoke / seed rotate |
|
||||
| Compromised operator laptop | Operator powers until revoke |
|
||||
| Attacker with server filesystem | Read `.env` if perms wrong — full compromise |
|
||||
| Bad invite silent viewer | Mitigated: fail closed on bad capability |
|
||||
|
||||
Residual risks and the hardening checklist live in [THREAT_MODEL.md](./THREAT_MODEL.md).
|
||||
|
||||
---
|
||||
|
||||
## 15. Operator rules of thumb
|
||||
|
||||
1. Treat `/opt/peardock/.env` like root SSH keys (`600`, backed up offline).
|
||||
2. Share **public key** only if read-only exposure is acceptable.
|
||||
3. Share **seed** only with true admins (ideally never over chat).
|
||||
4. Onboard operators with **full `pd1.` strings**, not the seed.
|
||||
5. To kick someone: **revoke peer id**, don’t only delete the invite.
|
||||
6. After delete/replace invite, old cached capabilities on clients can break until they paste the new string.
|
||||
7. Never set `PEARDOCK_INSECURE_OPEN_ADMIN=1` on multi-user production hosts.
|
||||
8. Prefer persistent invites for trusted operators; use maxUses/TTL when sharing more widely.
|
||||
|
||||
---
|
||||
|
||||
## 16. Code map
|
||||
|
||||
| Area | Location |
|
||||
|------|----------|
|
||||
| HMAC capability + admin proof + pd1 encode/decode | `shared/crypto-auth.js` |
|
||||
| Roles + `MethodRoles` | `shared/protocol.js` |
|
||||
| Baseline role resolution | `server/core/acl.js` |
|
||||
| Mint / redeem / register / revoke | `server/core/peer-policy.js` |
|
||||
| pd1 invite create/list/delete | `server/core/connection-invites.js` |
|
||||
| Handshake elevation | `server/rpc/session.js` |
|
||||
| Admin RPC surface | `server/handlers/peers.js` |
|
||||
| Client identity | `client/identity.js` |
|
||||
| Client handshake auth | `client/connection.js` |
|
||||
| Keys on disk | `server/core/keys.js`, `/opt/peardock/.env` |
|
||||
|
||||
Implementation details can change; this document describes the security **model**. When behavior and docs diverge, trust the code paths above and update this file.
|
||||
@@ -3,6 +3,8 @@
|
||||
**Audience:** operators deploying peardock in multi-operator or semi-trusted environments.
|
||||
**Scope:** HyperDHT P2P control plane + local Docker Engine socket (+ optional Holesail data plane).
|
||||
|
||||
For a full walkthrough of admin seed proof, roles, `pd1.` invites, redeem/reconnect, and revoke, see **[SECURITY_AUTH.md](./SECURITY_AUTH.md)**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Assets
|
||||
|
||||
Reference in New Issue
Block a user