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

{
  "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:

  1. Register target (without known_hosts)
  2. Call /verify — connects to the server, returns fingerprint
  3. User verifies fingerprint — compare with the server's actual fingerprint
  4. Update target with the known_hosts entry 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 the known_hosts entry.


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_encrypted or password_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.