SSH Targets
SSH Targets¶
SSH targets are user-registered remote servers that Intelligence Packs can execute commands on via ctx.ssh.run(). This guide covers how to register, verify, and manage SSH targets.
Overview¶
┌─────────────────────────────────────────────────────────────────┐
│ SSH Target Lifecycle │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Register Target │
│ POST /api/v1/ssh/targets │
│ Provide: name, host, port, username, auth_method, creds │
│ │
│ 2. Verify Connectivity │
│ POST /api/v1/ssh/targets/{id}/verify │
│ Returns: connected, fingerprint │
│ │
│ 3. Confirm Host Key │
│ PUT /api/v1/ssh/targets/{id} │
│ Store: known_hosts entry │
│ │
│ 4. Use from Pack │
│ ctx.ssh.run("target-name", "command") │
│ │
└─────────────────────────────────────────────────────────────────┘
Registering a Target¶
Via API¶
curl -X POST https://huitzo.ai/api/v1/ssh/targets \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "gpu-cluster",
"host": "10.0.1.50",
"port": 22,
"username": "huitzo",
"auth_method": "key",
"private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----"
}'
Required Fields¶
| Field | Type | Description |
|---|---|---|
name |
string | Unique name for the target (kebab-case, max 128 chars) |
host |
string | Hostname or IP address (max 253 chars) |
username |
string | SSH login username (max 128 chars) |
auth_method |
string | "key" or "password" |
Optional Fields¶
| Field | Type | Default | Description |
|---|---|---|---|
port |
int | 22 |
SSH port |
private_key |
string | — | PEM-encoded private key (required if auth_method: "key") |
password |
string | — | SSH password (required if auth_method: "password") |
known_hosts |
string | — | OpenSSH known_hosts line for host key verification |
Authentication Methods¶
SSH Key Authentication (Recommended)¶
{
"name": "gpu-cluster",
"host": "10.0.1.50",
"username": "huitzo",
"auth_method": "key",
"private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----"
}
Supported key formats: RSA, Ed25519, ECDSA in OpenSSH or PEM format.
Password Authentication¶
{
"name": "legacy-server",
"host": "192.168.1.100",
"username": "admin",
"auth_method": "password",
"password": "your-password-here"
}
Note: Key-based authentication is strongly recommended. Password authentication is supported for legacy systems that don't support SSH keys.
Verifying Connectivity¶
After registering a target, verify connectivity and retrieve the host key fingerprint:
curl -X POST https://huitzo.ai/api/v1/ssh/targets/{id}/verify \
-H "Authorization: Bearer $TOKEN"
Response:
{
"connected": true,
"fingerprint": "SHA256:AbCdEfGhIjKlMnOpQrStUvWxYz1234567890abcdef="
}
Trust-On-First-Use (TOFU) Flow¶
The verify endpoint is the only code path that connects without host key verification. This implements a TOFU model:
- Register target (without
known_hosts) - Call
/verify— connects to the server, returns fingerprint - User verifies fingerprint — compare with the server's actual fingerprint
- Update target with the
known_hostsentry to lock in the host key
# Step 1: User gets fingerprint from their server
ssh-keyscan -t ed25519 10.0.1.50
# Step 2: Verify via API
curl -X POST .../ssh/targets/{id}/verify
# Step 3: Compare fingerprints, then store known_hosts
curl -X PUT .../ssh/targets/{id} \
-H "Content-Type: application/json" \
-d '{"known_hosts": "10.0.1.50 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA..."}'
Important: After storing
known_hosts, all subsequent connections verify the host key. If the server's key changes (e.g., after reinstallation), connections will fail until the user updates theknown_hostsentry.
Managing Targets¶
List Targets¶
GET /api/v1/ssh/targets
Returns all targets for the current user/tenant. Credentials are never returned.
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "gpu-cluster",
"host": "10.0.1.50",
"port": 22,
"username": "huitzo",
"auth_method": "key",
"has_credentials": true,
"known_hosts": "10.0.1.50 ssh-ed25519 AAAA...",
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-01T10:05:00Z"
}
]
Get Target¶
GET /api/v1/ssh/targets/{id}
Update Target¶
PUT /api/v1/ssh/targets/{id}
Update any mutable fields. Credentials can be replaced by providing new values.
Delete Target¶
DELETE /api/v1/ssh/targets/{id}
Permanently removes the target and its encrypted credentials.
Using Targets from Packs¶
Pack Manifest¶
Packs must declare which SSH targets they can access:
# huitzo.yaml
ssh_targets:
allowed:
- "*" # Allow all user-registered targets
Or restrict to specific names:
ssh_targets:
allowed:
- "gpu-cluster"
- "preprocessing-server"
Pack Command¶
from huitzo_sdk import command, Context
from huitzo_sdk.errors import SSHError
@command("run-inference", namespace="ml")
async def run_inference(args: InferenceArgs, ctx: Context) -> dict:
try:
result = await ctx.ssh.run(
"gpu-cluster",
f"python /opt/inference/predict.py --model {args.model}",
timeout=120,
)
return {"predictions": result.stdout, "exit_code": result.exit_code}
except SSHError as e:
return {"error": e.message}
Credential Security¶
Encryption at Rest¶
All SSH credentials are Fernet-encrypted before database storage:
| What | How |
|---|---|
| Private keys | Fernet-encrypted PEM string |
| Passwords | Fernet-encrypted plaintext |
| Encryption key | HUITZO_SSH_ENCRYPTION_KEY env var |
| Algorithm | AES-128-CBC + HMAC-SHA256 (Fernet) |
Access Control¶
- API responses never include
private_key_encryptedorpassword_encrypted - Credentials are only decrypted at command execution time, in memory
- RLS policies ensure tenants cannot see each other's targets
- Logs never contain decrypted credentials
Troubleshooting¶
"No host key fingerprint stored"¶
The target is missing a known_hosts entry. Run the /verify endpoint to get the fingerprint, then update the target.
"Target not allowed by this pack's manifest"¶
The pack's huitzo.yaml doesn't include this target in ssh_targets.allowed. Update the manifest.
"SSH target not found"¶
The user hasn't registered a target with that name. Register it via the API.
"Connection failed"¶
Check that: - The host is reachable from the Huitzo backend - The port is correct and open - The username exists on the remote server - The credentials are valid (key or password)
"Command timed out"¶
The command exceeded the timeout. Increase the timeout parameter or optimize the remote command.
Related Documentation¶
- SSH SDK Reference —
ctx.ssh.run()API documentation - SSH Execution Architecture — Security model and data flow
- Error Handling — SSHError reference
- Pack Manifest - SSH Targets — Manifest configuration
- Secrets Management — Huitzo encryption and secrets model