Architecture¶
Component Overview¶
graph TB
subgraph "shed microVM"
SSH[SSH client / git] --> SSA[shed-ext-ssh-agent<br/>Unix socket]
AWS[AWS SDK] --> AWP[shed-ext-aws-credentials<br/>HTTP :499]
DCK[Docker CLI] --> DCS[docker-credential-shed<br/>one-shot CLI]
SSA --> BUS[shed-agent<br/>127.0.0.1:498]
AWP --> BUS
DCS --> BUS
end
BUS -->|vsock port 1026| SRV[shed-server<br/>Plugin message bus]
SRV -->|SSE| HA[shed-host-agent]
subgraph "Host macOS"
HA --> BE[SSH Backend<br/>agent-forward / local-keys]
HA --> STS[AWS STS<br/>AssumeRole + cache]
HA --> DCB[Docker Backend<br/>credential helpers + config]
HA --> TID[Touch ID gate]
HA --> AL[Audit log]
end
Message Flow¶
SSH Sign Request¶
- SSH client connects to
shed-ext-ssh-agentviaSSH_AUTH_SOCK shed-ext-ssh-agenttranslates the SSH agent protocolSign()call into a JSON envelope- Envelope is POSTed to
http://127.0.0.1:498/v1/publish(shed-agent's HTTP endpoint) - shed-agent sends the message over vsock to shed-server
- shed-server routes the message to the
ssh-agentnamespace listener via SSE shed-host-agentreceives the envelope, dispatches to the SSH backend- SSH backend performs the signing operation using host keys
- Response envelope flows back: host-agent -> shed-server -> shed-agent -> shed-ext-ssh-agent
shed-ext-ssh-agentreturns the signature to the SSH client
AWS Credential Request¶
- AWS SDK calls
GET http://127.0.0.1:499/credentials(viaAWS_CONTAINER_CREDENTIALS_FULL_URI) shed-ext-aws-credentialstranslates the HTTP request into a JSON envelope- Envelope is POSTed to the shed-agent publish endpoint
- shed-server routes the message to the
aws-credentialsnamespace listener via SSE shed-host-agentreceives the envelope, checks its credential cache- If cached credentials are still valid (>5 min remaining), return immediately
- If stale, call
sts:AssumeRolewith the configured role, cache result - Response flows back to
shed-ext-aws-credentials, which returns the AWS SDK-expected format - The SDK caches the credential in memory and manages its own refresh
sequenceDiagram
participant SDK as AWS SDK
participant Proxy as shed-ext-aws-credentials
participant Bus as Message Bus
participant Host as shed-host-agent
participant STS as AWS STS
SDK->>Proxy: GET /credentials
Proxy->>Bus: publish(aws-credentials)
Bus->>Host: SSE event
alt Cache hit
Host-->>Bus: cached credentials
else Cache miss
Host->>STS: AssumeRole
STS-->>Host: temp credentials
Host-->>Bus: fresh credentials
end
Bus-->>Proxy: response
Proxy-->>SDK: {AccessKeyId, SecretAccessKey, Token, Expiration}
Docker Credential Request¶
- Docker CLI execs
docker-credential-shed getwith the registry hostname on stdin docker-credential-shedtranslates the request into a JSON envelope and POSTs to the shed-agent publish endpoint- shed-server routes the message to the
docker-credentialsnamespace listener via SSE shed-host-agentchecks the registry allowlist, reads~/.docker/config.json, and shells out to the appropriate credential helper (gcloud, osxkeychain, ecr-login, etc.)- Response flows back to
docker-credential-shed, which writes credentials to stdout and exits
Package Structure¶
Guest-Side¶
internal/sshagent/— Implementsgolang.org/x/crypto/ssh/agent.Agent. Each method marshals a request, POSTs to the publish endpoint, and unmarshals the response.internal/awsproxy/— HTTP handler for the AWS container credential endpoint. TranslatesGET /credentialsinto message bus requests. Returns the PascalCase JSON format the AWS SDK expects.internal/dockercred/— Docker credential helper bus client. Translates Docker credential helper protocol operations (get,list) into message bus requests. One-shot usage (not a daemon).cmd/shed-ext-ssh-agent/— Unix socket listener. Creates a new agent instance per connection. Handles startup health check and graceful shutdown.cmd/shed-ext-aws-credentials/— HTTP server on port 499. Routes/credentialsto the proxy handler.cmd/docker-credential-shed/— One-shot CLI binary. Docker execs this binary per credential operation. Reads stdin, publishes to bus, writes stdout, exits.
Host-Side¶
internal/hostclient/— SSE client for shed-server's plugin API. Handles subscription, reconnection, and response delivery.cmd/shed-host-agent/— Main binary. Loads config, initializes backends, subscribes to namespaces, dispatches requests to handlers. Runs SSH, AWS, and Docker handlers concurrently.
Shared¶
internal/protocol/— Envelope and payload types. Defined locally (not imported from shed) to avoid dependency coupling. JSON wire format matches shed'sinternal/plugintypes.
SSH Backend Selection¶
graph TD
A[Start] --> B{config.ssh.mode set?}
B -->|Yes| C[Use configured mode]
B -->|No| D{SSH_AUTH_SOCK exists?}
D -->|Yes| E[agent-forward mode]
D -->|No| F[local-keys mode]
Agent-forward: Proxies to the developer's existing SSH agent (Secretive, 1Password, ssh-agent, yubikey-agent). Zero disruption to existing key management.
Local-keys: Reads keys directly from ~/.ssh/. Fallback when no agent is running.
AWS Credential Caching¶
The caching strategy is asymmetric:
- Guest proxy: Pure passthrough, no caching. Every SDK request goes to the bus.
- Host handler: Caches STS credentials per shed, keyed by shed name.
This avoids cache coherence complexity. The bus round trip is sub-millisecond (vsock, same machine), and the SDK only fetches credentials when its in-memory cache is stale (~once per hour).
Distribution¶
Artifacts are split across two channels:
| Component | Channel | Trigger |
|---|---|---|
shed-host-agent (darwin + linux, arm64 + amd64) |
GitHub Release (GoReleaser) | git tag |
| Guest binaries + systemd units + env config | Multi-arch Docker image (ghcr.io/charliek/shed-extensions:<tag>) |
git tag |
The Docker image is a scratch-based container with just the binaries and config files. It's consumed by shed's VZ and Firecracker Dockerfiles via COPY --from=ghcr.io/charliek/shed-extensions:<version>. Shed pins the version with an ARG SHED_EXT_VERSION in each Dockerfile.
Both are published by the same release workflow (.github/workflows/release.yaml) from the same git tag.
Security Boundaries¶
| Boundary | What crosses | What doesn't |
|---|---|---|
| VM -> Host (SSH sign) | Challenge data, public key reference | Private keys |
| Host -> VM (SSH response) | Signature blob | Private keys |
| VM -> Host (AWS request) | Operation type only | Role ARN, source credentials |
| Host -> VM (AWS response) | Short-lived STS token (1h) | Long-lived AWS credentials |
| VM -> Host (Docker get) | Registry hostname | Docker config.json contents |
| Host -> VM (Docker response) | Registry credentials (tokens or passwords) | Host Docker config, other registries' credentials |