{"id":48693756,"url":"https://github.com/driversti/ssh-vault","last_synced_at":"2026-04-11T06:45:09.392Z","repository":{"id":345985267,"uuid":"1188137279","full_name":"driversti/ssh-vault","owner":"driversti","description":"Centralized SSH key distribution for your personal devices. One binary, zero dependencies, no database.","archived":false,"fork":false,"pushed_at":"2026-03-29T11:25:20.000Z","size":373,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-11T06:45:07.978Z","etag":null,"topics":["devops","golang","homelab","self-hosted","ssh","ssh-keys"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/driversti.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-21T17:04:56.000Z","updated_at":"2026-03-29T11:25:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/driversti/ssh-vault","commit_stats":null,"previous_names":["driversti/ssh-vault"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/driversti/ssh-vault","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/driversti%2Fssh-vault","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/driversti%2Fssh-vault/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/driversti%2Fssh-vault/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/driversti%2Fssh-vault/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/driversti","download_url":"https://codeload.github.com/driversti/ssh-vault/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/driversti%2Fssh-vault/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31671629,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-10T17:19:37.612Z","status":"online","status_checked_at":"2026-04-11T02:00:05.776Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["devops","golang","homelab","self-hosted","ssh","ssh-keys"],"created_at":"2026-04-11T06:45:08.382Z","updated_at":"2026-04-11T06:45:09.384Z","avatar_url":"https://github.com/driversti.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SSH Vault\n\nCentralized SSH key distribution for your personal devices. One binary, zero dependencies, no database.\n\nSSH Vault solves a simple problem: when you add a new laptop, server, or Raspberry Pi to your collection, you shouldn't have to manually copy SSH keys to every other device. Enroll a device once, approve it from the web dashboard, and its public key is automatically distributed to all your other devices within minutes.\n\n```\n┌──────────────┐     enroll     ┌──────────┐     sync       ┌─────────────┐\n│  New Device  │───────────────▶│   Hub    │◀───────────────│  Device A   │\n│  (agent)     │  token + key   │ :8080    │  GET /api/keys │  (agent)    │\n└──────────────┘                │          │                └─────────────┘\n                                │ dashboard│◀──────────────┐\n                   approve ───▶ │ + API    │               │\n                                └──────────┘     sync      │\n                                                           │\n                                                    ┌─────────────┐\n                                                    │  Device B   │\n                                                    │  (agent)    │\n                                                    └─────────────┘\n```\n\n## Features\n\n- **Single binary** — `ssh-vault` ships as one executable with three subcommands: `hub`, `agent`, `enroll`\n- **Quick enrollment** — generate a 6-digit code on the dashboard, run `curl -sSL https://hub/e/CODE | sh` on any device to enroll in one command\n- **Challenge-response enrollment** — devices prove private key ownership during enrollment via SSH signature verification\n- **Web dashboard** — view devices, generate onboarding tokens, approve/revoke with a clean Pico CSS interface\n- **Managed key block** — agent writes keys between `BEGIN/END SSH-VAULT MANAGED BLOCK` markers in `authorized_keys`, preserving your manual entries\n- **Atomic file writes** — `authorized_keys` is never half-written; uses temp file + rename\n- **Offline resilience** — if the hub goes down, agents retain the last synced keys and resume when it returns\n- **Revocation propagation** — revoke a device from the dashboard and its key is removed from all devices within one sync cycle\n- **Audit log** — enrollments, approvals, revocations, and failed auth attempts are logged\n- **Optional TLS** — pass `--tls-cert` and `--tls-key`, or run behind a reverse proxy\n- **No external dependencies** — standard library + `golang.org/x/crypto/ssh`, file-based JSON storage\n\n## Quick Start\n\n### Build\n\n```bash\ngo build -o ssh-vault ./cmd/ssh-vault\n```\n\n### 1. Start the Hub\n\nOn your always-on server (home server, VPS, etc.):\n\n```bash\nexport VAULT_PASSWORD=\"your-secret-passphrase\"\n./ssh-vault hub --addr :8080 --data ./data \\\n  --external-url https://your-hub:8080 \\\n  --dist-dir ./dist\n```\n\n\u003e The `--external-url` flag enables quick enrollment links on the dashboard. The `--dist-dir` flag points to a directory of pre-built binaries so the enrollment script can download the agent directly from the hub. Without `--external-url`, only manual token-based enrollment is available.\n\n### 2. Enroll a Device\n\n**Option A: Quick enrollment (recommended)**\n\nOn the dashboard, go to **Tokens** and click **Generate Enrollment Link**. Copy the displayed command and run it on the target device:\n\n```bash\ncurl -sSL https://your-hub:8080/e/123456 | sh\n```\n\nThe script automatically detects the platform, downloads the binary, finds SSH keys, and enrolls the device.\n\n**Option B: Manual enrollment**\n\nGenerate a token on the dashboard (**Tokens** → **Generate Token**), then on the target device:\n\n```bash\n./ssh-vault enroll \\\n  --hub-url http://your-hub:8080 \\\n  --token \u003cpaste-token\u003e \\\n  --key ~/.ssh/id_ed25519\n```\n\n### 3. Approve\n\nBack on the dashboard, click **Approve** next to the new device.\n\n### 4. Start the Agent\n\n```bash\n./ssh-vault agent \\\n  --hub-url http://your-hub:8080 \\\n  --interval 5m \\\n  --key ~/.ssh/id_ed25519\n```\n\nThe agent syncs approved keys every 5 minutes into a managed block in `~/.ssh/authorized_keys`.\n\n### 5. Verify\n\nFrom another enrolled device:\n\n```bash\nssh user@new-device  # no password prompt — key was distributed automatically\n```\n\n## CLI Reference\n\n### `ssh-vault hub`\n\nStart the hub server (dashboard + API).\n\n| Flag | Default | Description |\n|------|---------|-------------|\n| `--addr` | `:8080` | Listen address |\n| `--data` | `./data` | Data directory for `data.json` |\n| `--password` | — | Dashboard passphrase (or `VAULT_PASSWORD` env) |\n| `--tls-cert` | — | TLS certificate file (optional) |\n| `--tls-key` | — | TLS private key file (optional) |\n| `--external-url` | — | Public URL for enrollment links (or `VAULT_EXTERNAL_URL` env) |\n| `--dist-dir` | — | Directory of pre-built binaries for enrollment downloads (or `VAULT_DIST_DIR` env) |\n\n### `ssh-vault enroll`\n\nEnroll this device with a hub.\n\n| Flag | Default | Description |\n|------|---------|-------------|\n| `--hub-url` | — | Hub base URL (required) |\n| `--token` | — | Onboarding token (required) |\n| `--key` | `~/.ssh/id_ed25519` | SSH private key path |\n| `--name` | hostname | Device display name |\n\n### `ssh-vault agent`\n\nStart the sync agent.\n\n| Flag | Default | Description |\n|------|---------|-------------|\n| `--hub-url` | — | Hub base URL |\n| `--interval` | `5m` | Sync interval |\n| `--key` | `~/.ssh/id_ed25519` | SSH private key path |\n| `--auth-keys` | `~/.ssh/authorized_keys` | authorized_keys file path |\n\n## How It Works\n\n### Enrollment Flow\n\n**Quick enrollment** (via short code):\n\n1. Admin clicks **Generate Enrollment Link** on the dashboard\n2. Hub creates a 6-digit short code (valid 15 min, single-use) linked to an auto-generated token (valid 24h)\n3. User runs `curl -sSL https://hub/e/CODE | sh` on the target device\n4. Script detects platform (Linux/macOS, amd64/arm64), downloads the binary from the hub via `GET /download/{os}/{arch}`, verifies its SHA-256 checksum, finds SSH keys, and runs the enrollment\n5. The standard challenge-response handshake completes automatically\n6. Owner approves via dashboard\n\n**Manual enrollment** (via token):\n\n1. Hub generates a single-use onboarding token (valid 24h)\n2. Agent sends the token + SSH public key to `POST /api/enroll`\n3. Hub returns a random challenge\n4. Agent signs the challenge with its SSH private key\n5. Agent sends the signature to `POST /api/enroll/verify`\n6. Hub verifies the signature, marks device as \"pending\"\n7. Owner approves via dashboard → hub generates an API bearer token\n8. Agent uses the bearer token for all subsequent sync requests\n\n### Sync Loop\n\nEvery interval (default 5 minutes), the agent:\n\n1. Calls `GET /api/keys` with its bearer token\n2. Receives the list of all approved devices' public keys (excluding its own)\n3. Writes them into the managed block in `authorized_keys`\n4. Keys outside the managed block are never touched\n\n### Managed Block\n\n```\n# existing manual keys are preserved\nssh-rsa AAAA... admin@jumpbox\n\n# BEGIN SSH-VAULT MANAGED BLOCK — DO NOT EDIT\nssh-ed25519 AAAA... laptop\nssh-ed25519 BBBB... desktop\nssh-ed25519 CCCC... raspberry-pi\n# END SSH-VAULT MANAGED BLOCK\n```\n\n### Revocation\n\nClick **Revoke** on the dashboard. The revoked device:\n- Gets a `401 \"device revoked\"` on its next sync and stops\n- Is excluded from other devices' key lists on their next sync\n- Record is preserved in the audit log\n\n## Architecture\n\n```\ncmd/ssh-vault/         # Single binary entry point\ninternal/\n├── hub/               # Hub server — HTTP handlers, auth, storage, templates\n├── agent/             # Sync agent — enrollment, config, sync loop\n├── keyblock/          # authorized_keys file manipulation (atomic writes)\n└── model/             # Shared types — Device, Token, ShortCode, AuditEntry\n```\n\n- **Storage**: Single `data.json` file (human-readable, easy to back up)\n- **Dashboard**: Server-rendered HTML with [Pico CSS](https://picocss.com), embedded in the binary via `//go:embed`\n- **Auth**: Bearer tokens for agents, password sessions for dashboard\n- **Concurrency**: `sync.RWMutex` protects the in-memory store\n\n## Design Decisions\n\n| Decision | Rationale |\n|----------|-----------|\n| Single binary | Simplest distribution — copy one file |\n| File-based JSON storage | No database dependency; sufficient for \u003c50 devices |\n| `golang.org/x/crypto/ssh` only | Quasi-stdlib; no third-party dependencies |\n| Challenge-response enrollment | Proves the agent holds the private key, not just a copy of the public key |\n| Bearer tokens for sync | Simpler than per-request SSH signatures; identity verified once at enrollment |\n| Server-rendered HTML | No frontend build step; the dashboard is embedded in the binary |\n| Managed block pattern | Coexists with manually managed keys; `sshd` sees one coherent file |\n\n## Security Considerations\n\n- **TLS**: Use `--tls-cert`/`--tls-key` or run behind a reverse proxy. Without TLS, tokens and keys traverse the network in cleartext.\n- **Password**: The dashboard password is compared with constant-time comparison (`crypto/subtle`). Set a strong passphrase.\n- **Tokens**: Onboarding tokens are 32 bytes of `crypto/rand`, single-use, 24h expiry.\n- **Short codes**: 6-digit codes are `crypto/rand`, single-use, 15-minute expiry. The enrollment endpoint (`/e/`) is rate-limited to 10 requests per minute per IP.\n- **File permissions**: `authorized_keys` is written with `0600` permissions via atomic rename.\n- **Single-user system**: Designed for one owner managing their personal devices. Not suited for multi-tenant or team use.\n\n## Development\n\n```bash\ngo build -o ssh-vault ./cmd/ssh-vault   # Build\ngo test ./...                            # Run tests\ngo vet ./...                             # Static analysis\n```\n\n## Releasing\n\nReleases are automated via GitHub Actions and [GoReleaser](https://goreleaser.com). To publish a new version:\n\n```bash\ngit tag v1.0.0\ngit push origin v1.0.0\n```\n\nThis triggers the workflow which runs tests, cross-compiles for Linux (amd64, arm64) and macOS (amd64, arm64), and uploads binaries + `checksums.txt` to GitHub Releases.\n\nTo build a release locally (without publishing):\n\n```bash\ngoreleaser release --snapshot --clean\n```\n\n## Requirements\n\n- Go 1.22+ (uses `log/slog`, `embed`)\n- Linux or macOS (hub and agents)\n- SSH key pair on each device (e.g., `ssh-keygen -t ed25519`)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdriversti%2Fssh-vault","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdriversti%2Fssh-vault","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdriversti%2Fssh-vault/lists"}