This commit is contained in:
+24
-67
@@ -141,23 +141,9 @@ Then in the UI:
|
||||
3. Optionally set `remote_path_prefix` (default `/`) — restricts upload/download paths
|
||||
4. Click **Test** → first call returns `host_key_first_observe` with a fingerprint
|
||||
5. **Verify** in the dialog (compare fingerprint with what you expect from `ssh-keyscan <host>`)
|
||||
6. Add the connection's UUID to a piece's `allowed_ssh_connections`:
|
||||
6. Enable the `ssh` tool category for the workspace in **Settings → Tools**. Once enabled, every connection registered to that workspace becomes reachable from any task in it — there is no per-piece opt-in and no piece YAML to edit.
|
||||
|
||||
```yaml
|
||||
# pieces/example.yaml
|
||||
name: ssh-example
|
||||
movements:
|
||||
- name: deploy
|
||||
allowed_tools: [SshExec, SshUpload]
|
||||
allowed_ssh_connections: ["abcd1234-..."]
|
||||
rules:
|
||||
- condition: done
|
||||
next: COMPLETE
|
||||
instruction: |
|
||||
Use SshExec to ...
|
||||
```
|
||||
|
||||
7. Test the piece via the normal task UI.
|
||||
7. Test via the normal task UI. The worker resolves the workspace's SSH connections once per job (`connectionRepo.listBySpace(spaceId)`) and exposes that exact set to every movement.
|
||||
|
||||
## `config.yaml` Reference
|
||||
|
||||
@@ -361,60 +347,31 @@ an allowlist. SHA1-RSA and other weak algorithms are rejected before
|
||||
the key is recorded (`host_key_alg_not_allowed`). This is hard-coded
|
||||
in `src/ssh/session.ts` to avoid misconfiguration.
|
||||
|
||||
## Per-piece `allowed_ssh_connections`
|
||||
## Workspace-scoped SSH connections
|
||||
|
||||
A piece's movement must explicitly opt in to SSH usage. The
|
||||
piece-runner enforces three invariants:
|
||||
SSH reachability is a **workspace** property, not a piece property. The
|
||||
per-piece `allowed_ssh_connections` field was removed in the tool-consolidation
|
||||
work; if it still appears on a submitted piece it is tolerated but ignored.
|
||||
|
||||
1. If a movement's `allowed_tools` contains any SSH tool name
|
||||
(`SshExec`/`SshUpload`/`SshDownload`), `allowed_ssh_connections`
|
||||
**must be declared** on that movement (even if empty)
|
||||
2. The field must be an array of strings
|
||||
3. Each entry must be `*` or a lowercase hex+hyphen UUID (≥ 8 chars)
|
||||
The gate now works like this:
|
||||
|
||||
Lint failures abort piece load.
|
||||
1. The workspace's tool policy must enable the `ssh` category
|
||||
(**Settings → Tools**). If it does not, the SSH tools are not offered to any
|
||||
movement.
|
||||
2. When `ssh` is enabled, the worker resolves the connections registered to the
|
||||
job's space (`connectionRepo.listBySpace(spaceId)`) once per job and applies
|
||||
that set uniformly to every movement. This space-scoped list is the **sole**
|
||||
connection gate.
|
||||
3. The list is always an explicit set of connection IDs — never a `*` wildcard —
|
||||
so a job can only ever reach connections registered in its own workspace.
|
||||
Cross-space isolation is guaranteed.
|
||||
4. The [access grant check](#access-grants) still applies on top: a user without
|
||||
a grant for a given connection cannot use it even when the workspace exposes
|
||||
it.
|
||||
|
||||
### Forms
|
||||
|
||||
```yaml
|
||||
# Explicit allowlist (most common)
|
||||
allowed_ssh_connections: ["abcd1234-...", "ef567890-..."]
|
||||
|
||||
# Wildcard (admin-style — use sparingly)
|
||||
allowed_ssh_connections: ["*"]
|
||||
|
||||
# Deny-all (still allows SSH tool in allowed_tools but refuses every UUID)
|
||||
allowed_ssh_connections: []
|
||||
```
|
||||
|
||||
The `*` form skips the per-piece check but **does not** skip the
|
||||
[access grant check](#access-grants). A user without a grant for a
|
||||
given connection still cannot use it even when the piece says `*`.
|
||||
|
||||
### Example
|
||||
|
||||
```yaml
|
||||
name: backup-rotation
|
||||
description: Daily backup rotation on prod servers
|
||||
movements:
|
||||
- name: list
|
||||
allowed_tools: [SshExec]
|
||||
allowed_ssh_connections: ["abcd1234-...", "ef567890-..."]
|
||||
instruction: |
|
||||
List the existing backup files on each server.
|
||||
rules:
|
||||
- condition: ready to rotate
|
||||
next: rotate
|
||||
|
||||
- name: rotate
|
||||
allowed_tools: [SshExec, SshUpload]
|
||||
allowed_ssh_connections: ["abcd1234-...", "ef567890-..."]
|
||||
instruction: |
|
||||
Rotate the oldest backup ...
|
||||
rules:
|
||||
- condition: done
|
||||
next: COMPLETE
|
||||
```
|
||||
If SSH is enabled but the SSH subsystem is not initialised (e.g. a test
|
||||
environment without the bridge), the resolved list is empty and every SSH call
|
||||
is rejected (fail-closed).
|
||||
|
||||
## Access Grants
|
||||
|
||||
@@ -870,7 +827,7 @@ curl -X DELETE 'http://localhost:3000/api/ssh/admin/audit?older_than_days=90'
|
||||
| `access denied (no_grant)` | User lacks grant for connection | Admin creates a grant, or user uses an owned connection |
|
||||
| `access denied (disabled)` | Admin disabled the connection | Admin re-enables, or use different connection |
|
||||
| `access denied (abuse_locked)` | Counter triggered | Wait for lock window, or admin force-unlocks |
|
||||
| `piece "X" does not list connection Y` | `allowed_ssh_connections` missing UUID | Add UUID to the movement's `allowed_ssh_connections` |
|
||||
| `connection Y is not registered to this workspace` | Connection not registered to the job's workspace | Register it under **Settings → SSH** and enable the `ssh` category in **Settings → Tools** |
|
||||
| `host_key_first_observe` | First time exercising connection | Verify fingerprint in UI |
|
||||
| `host_key_not_verified` | Key recorded but never verified | Click Verify in UI |
|
||||
| `host_key_mismatch` | Server key changed | Investigate (legitimate rotation? MITM?), then Replace via UI |
|
||||
|
||||
Reference in New Issue
Block a user