🛰️ export from upstream (251518c)

This commit is contained in:
2026-05-24 19:52:42 +02:00
parent 0cd116aff2
commit 2e0c340768
4 changed files with 631 additions and 88 deletions
+224
View File
@@ -0,0 +1,224 @@
# SSH Key Access
## Overview
Containers launched by sandcage often need SSH access to clone private
repositories or push code. Since the agent user inside the container does not
have access to your host SSH keys by default, sandcage provides a setup command
that copies or mounts the relevant keys into the container environment.
## Modes
Sandcage supports two SSH modes, configured via the `ssh_mode` field:
### Volume mode (recommended)
```
ssh_mode: volume
```
A Docker named volume (`sandcage-ssh`) is created and populated with only the
keys required for your git remotes. The volume is mounted read-only at
`/home/agent/.ssh` inside the container. A synthesized SSH config is written
into the volume so the agent can connect to the correct hosts with the correct
keys.
Advantages:
- Only selected keys are copied (not your entire `~/.ssh` directory).
- A minimal SSH config is synthesized from your host config.
- The volume persists across container restarts without re-reading host files.
- Works identically on Linux, macOS, and Windows (Docker Desktop).
### Bind mode
```
ssh_mode: bind
```
Your host `~/.ssh` directory is bind-mounted read-only into the container at
`/home/agent/.ssh:ro`. This exposes all files in that directory to the
container.
Advantages:
- Zero setup after initial configuration.
- Changes to host keys are immediately visible.
Disadvantages:
- Exposes all keys and config, not just the ones needed.
- On some platforms, file permission translation can cause issues.
### None
```
ssh_mode: none
```
No SSH mount is added. Use this if you do not need SSH inside containers or
handle key injection through other means.
## Setup workflow
Run the interactive setup:
```
sandcage setup ssh
```
The command:
1. Scans `~/.ssh/config` (including `Include` directives) for `Host` blocks
with `User git`.
2. Runs `git remote -v` in the current directory to discover SSH remote hosts.
3. Merges the two sources and displays discovered host/key pairs.
4. Prompts you to select which keys to copy.
5. Asks whether to include `known_hosts`.
6. Populates the `sandcage-ssh` Docker volume with the selected keys and a
synthesized SSH config.
7. Writes the configuration to the appropriate config file.
### Flags
| Flag | Effect |
|------|--------|
| `--global` | Write to `~/.sandcage/config.toml` instead of project-local config |
| `--yes` | Accept all defaults without prompting |
| `--bind` | Use bind-mount mode instead of volume mode |
| `--refresh` | Re-populate the volume from existing config (no interactive selection) |
### Examples
```
# Interactive setup for current project
sandcage setup ssh
# Non-interactive, global config
sandcage setup ssh --global --yes
# Legacy bind-mount mode
sandcage setup ssh --bind
# Refresh volume after adding a new key to ~/.ssh
sandcage setup ssh --refresh
```
## Configuration
### Project config (`.sandcage.local.yml` or `.sandcage.yml`)
```yaml
ssh_mode: volume
ssh_keys:
- host: github.com
identity_file: ~/.ssh/id_ed25519
- host: gitea
identity_file: ~/.ssh/work_gitea
```
### Global config (`~/.sandcage/config.toml`)
```toml
ssh_mode = "volume"
[[ssh_keys]]
host = "github.com"
identity_file = "~/.ssh/id_ed25519"
[[ssh_keys]]
host = "gitea"
identity_file = "~/.ssh/work_gitea"
```
### Fields
| Field | Type | Description |
|-------|------|-------------|
| `ssh_mode` | string | One of `volume`, `bind`, or `none` |
| `ssh_keys` | array | List of host/key pairs to include in the volume |
| `ssh_keys[].host` | string | SSH host alias (matches `Host` line in SSH config) |
| `ssh_keys[].identity_file` | string | Path to the private key (supports `~/` expansion) |
Configuration layers are merged in order: global, project, local. Later layers
override earlier ones.
## Refreshing keys
After adding or rotating SSH keys on the host, update the container volume:
```
sandcage setup ssh --refresh
```
This re-reads the existing `ssh_keys` configuration and the current
`~/.ssh/config`, then repopulates the `sandcage-ssh` Docker volume with fresh
copies.
For bind mode, no refresh is needed since the host directory is mounted
directly.
## Security considerations
- **Volume mode** copies only the selected private keys into a Docker volume.
The volume is mounted read-only into containers. Keys are owned by the
`agent` user with `600` permissions.
- **Bind mode** mounts the entire `~/.ssh` directory read-only. The container
can read all keys and config present in that directory.
- Neither mode exposes keys to other containers or Docker networks. Keys exist
only on the filesystem inside the running container.
- The `sandcage-ssh` volume persists on disk until explicitly removed
(`docker volume rm sandcage-ssh`). Run `--refresh` or delete the volume when
decommissioning keys.
## Troubleshooting
### Volume not found
```
sandcage: SSH volume not found — run 'sandcage setup ssh --refresh' to populate it
```
The `sandcage-ssh` volume does not exist or was removed. Re-run:
```
sandcage setup ssh --refresh
```
Or, if no config exists yet:
```
sandcage setup ssh
```
### Permission denied inside container
If the agent cannot read keys, the volume population step may have failed or
the image does not have an `agent` user at the expected UID. Verify:
```
docker run --rm -v sandcage-ssh:/home/agent/.ssh:ro sandcage:latest ls -la /home/agent/.ssh
```
Private keys should be `600` and owned by `agent:agent`.
### No git SSH hosts found
```
sandcage: no git SSH hosts found in remotes or ~/.ssh/config
```
This means:
- The current directory has no SSH git remotes (all remotes use HTTPS).
- `~/.ssh/config` has no `Host` blocks with `User git`.
Add an SSH remote or configure your SSH config, then re-run setup.
### Migration from legacy bind mount
If your config contains a raw `~/.ssh:/home/agent/.ssh:ro` entry in `mounts`
without an `ssh_mode` field, sandcage detects this as a legacy configuration
and offers to migrate to volume mode. Accepting the migration removes the old
mount entry and writes `ssh_mode: volume` with discovered keys. Declining sets
`ssh_mode: bind` explicitly to suppress future prompts.