{"id":50712120,"url":"https://github.com/ba0f3/luna-ztrust","last_synced_at":"2026-06-09T16:04:11.095Z","repository":{"id":362881934,"uuid":"1245930992","full_name":"ba0f3/luna-ztrust","owner":"ba0f3","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-06T11:42:53.000Z","size":598,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-06T12:07:51.412Z","etag":null,"topics":[],"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/ba0f3.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-21T17:46:17.000Z","updated_at":"2026-06-06T11:42:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ba0f3/luna-ztrust","commit_stats":null,"previous_names":["ba0f3/luna-ztrust"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/ba0f3/luna-ztrust","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ba0f3%2Fluna-ztrust","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ba0f3%2Fluna-ztrust/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ba0f3%2Fluna-ztrust/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ba0f3%2Fluna-ztrust/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ba0f3","download_url":"https://codeload.github.com/ba0f3/luna-ztrust/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ba0f3%2Fluna-ztrust/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34114456,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"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":[],"created_at":"2026-06-09T16:04:07.647Z","updated_at":"2026-06-09T16:04:11.090Z","avatar_url":"https://github.com/ba0f3.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Luna Z-Trust\n\nEphemeral SSH authentication for AI agents, DevOps runners, and automation. A self-hosted **luna-proxy** keeps signing keys in an in-memory keystore loaded by local operators or enrolled CLI devices, issues short-lived SSH **certificates** (`local-ca`) or **hosted-key signatures** (`local-key`), and gates access with out-of-band approval (Telegram v1; mobile enroll/approve API on the server).\n\n## Components\n\n| Component | Role |\n|-----------|------|\n| **luna-sdk** | Publishable Go library: ephemeral keys, PoP, mTLS + HMAC to proxy, cert/signature lifecycle |\n| **luna-proxy** | Central gateway: auth pipeline, control socket, in-memory keystore, signing, Telegram OOB, session leases |\n| **luna-agent** | OS daemon: `SSH_AUTH_SOCK` interceptor; blocking `Sign` for unmodified `ssh` |\n\n**Out of scope in this repository:** [lunacli](https://github.com/ba0f3/lunacli) (separate project; imports `luna-sdk`), target `sshd` provisioning.\n\n## Architecture\n\n```mermaid\ngraph TD\n    subgraph CENTRAL[\"Central host\"]\n        LP[\"luna-proxy serve :8443 mTLS\"]\n        CTRL[\"/run/luna/control.sock\"]\n        KS[\"in-memory key pool\"]\n        KS --\u003e LP\n        CTRL --\u003e LP\n        LP --\u003e TG[\"Telegram API\"]\n    end\n\n    subgraph CLIENT[\"Client host\"]\n        LA[\"luna-agent\"]\n        SDK[\"luna-sdk\"]\n        SSH[\"ssh / automation\"]\n        SSH --\u003e|\"SSH_AUTH_SOCK\"| LA\n        LA --\u003e SDK\n        SDK --\u003e|\"HTTPS mTLS\"| LP\n    end\n\n    SDK --\u003e Target[\"Target sshd\"]\n    LA --\u003e Target\n```\n\n**Sign flow (transaction + wait):**\n\n1. Client generates an ephemeral Ed25519 keypair (memory only).\n2. `POST /api/v1/ssh/sign` with JSON body, `pop_signature`, mTLS, `X-Luna-Body-Mac`.\n3. Proxy validates the auth pipeline; if no signer is loaded → `503`. Otherwise lease fast-path or new `tx_id` + Telegram (or auto-approve in dev).\n4. `GET /api/v1/ssh/sign/{tx_id}/wait` blocks until approved, denied, or timeout.\n5. Proxy signs via `local-ca` (SSH user cert + `source-address`) or `local-key` after validating OpenSSH `session-bind@openssh.com` and the SSH user-auth payload.\n6. SDK returns cert + private key, or signature; agent returns `ssh.Signature` to OpenSSH.\n\n## Repository layout\n\n```\nluna-ztrust/\n  go.work\n  sdk/          # github.com/ba0f3/luna-ztrust/sdk\n  proxy/        # github.com/ba0f3/luna-ztrust/proxy\n  agent/        # github.com/ba0f3/luna-ztrust/agent\n  docs/\n    superpowers/specs/\n      2026-05-30-self-hosted-central-design.md\n      2026-05-31-proxy-cli-keystore-design.md\n      2026-05-31-cli-remote-key-load-design.md\n```\n\n**Module dependency rules:**\n\n- `agent` → `sdk`\n- `proxy` does not import `sdk`\n- `sdk` does not import `agent` or `proxy`\n\n## Requirements\n\n- Go 1.25+ (see `go.work`)\n- Linux recommended (keystore `mlock`, Unix control socket peer credentials)\n- Internal mTLS CA (client certs for SDK/agent; admin OU for admin HTTP/device enrollment; CLI OU for remote key load)\n- Encrypted signing keys loaded by `luna-proxy key load` or mobile pending upload + `key confirm`\n- Telegram bot (production approval path)\n\n## Build and test\n\n```bash\ngo work sync\nmake test\nmake testdata   # mTLS + encrypted SSH keys for CI\nmake build      # bin/luna-proxy, bin/luna-agent\nmake ci         # fmt-check, lint, test, build (local CI parity)\n```\n\n**CI:** GitHub Actions runs `make ci` on push/PR and Docker E2E (`make e2e-up`, `e2e-wait`, `e2e-test`) in [`.github/workflows/ci.yml`](.github/workflows/ci.yml).\n\n**Releases:** Tag `v*` (e.g. `v0.1.0`) triggers [GoReleaser](.goreleaser.yaml) and a [GHCR](https://github.com/ba0f3/luna-ztrust/pkgs/container/luna-ztrust%2Fluna-proxy) image via [`.github/workflows/release.yml`](.github/workflows/release.yml) — cross-platform `luna-proxy` and `luna-agent` archives plus `ghcr.io/ba0f3/luna-ztrust/luna-proxy:\u003ctag\u003e`.\n\n**Deployment:** [docs/deploy.md](docs/deploy.md) — GitHub release binaries, systemd (`luna-proxy install systemd`, `luna-agent install systemd`), Docker Compose.\n\n## E2E\n\nDocker Compose runs `luna-proxy serve` with test mTLS and an encrypted CA key. Tests load the encrypted test CA through the control socket before signing.\n\n```bash\nmake testdata\nmake e2e-up\nmake e2e-test\nmake e2e-down\n```\n\n```bash\nLUNA_PROXY_URL=https://localhost:8443 go test -tags=e2e ./sdk/sign/... -v\n```\n\n`make e2e-test` runs `luna-proxy --socket /run/luna/control.sock key load /etc/luna/ssh/encrypted_ca.key --passphrase-stdin` inside the compose service.\n\n## Configuration (overview)\n\n### luna-proxy\n\n| Variable | Purpose |\n|----------|---------|\n| `LUNA_SIGNER_MODE` | `local-key` (default) or `local-ca` |\n| `LUNA_CONTROL_SOCKET` | Unix control socket path (default `/run/luna/control.sock`) |\n| `LUNA_CONTROL_SOCKET_GROUP` | Group allowed to use the control socket (default `luna-admin`) |\n| `LUNA_ADMIN_CLIENT_OU` | Client cert OU for `/api/v1/admin/*` (default `luna-admin`) |\n| `LUNA_CLI_CLIENT_OU` | Client cert OU for enrolled CLI devices (default `luna-cli`) |\n| `LUNA_MTLS_SERVER_CERT` / `KEY` / `LUNA_MTLS_CLIENT_CA` | mTLS listener material; default `/etc/luna/certs/{server.crt,server.key,ca.crt}` (same as `setup mtls`) |\n| `LUNA_MTLS_CA_CERT` | mTLS issuing CA cert path (default `/etc/luna/certs/ca.crt`; CSR enrollment for CLI devices) |\n| `LUNA_MTLS_CA_KEY` | mTLS issuing CA key path (default `/etc/luna/certs/ca.key`; required for `cli enroll`) |\n| `LUNA_ENV=dev` | Auto-approve (proxy env only) |\n| `TELEGRAM_BOT_TOKEN` | Outbound Telegram API |\n| `TELEGRAM_WEBHOOK_SECRET` | Webhook validation |\n| `TELEGRAM_CHAT_ID` | Admin chat for approval prompts |\n| `TELEGRAM_USER_IDS` | Comma-separated Telegram user IDs allowed to approve in group chats |\n| `FCM_CREDENTIALS` | P5 hook for mobile push (stub until implemented) |\n\nVault / `LUNA_VAULT_*` are removed from the runtime path; see [docs/legacy-vault-migration.md](docs/legacy-vault-migration.md).\n\n`POST /api/v1/admin/unseal` and `GET /api/v1/admin/seal-status` are compatibility routes that return `410 Gone`; use `luna-proxy key load` and `luna-proxy status` instead.\n\n### Operator control socket\n\nRun the server explicitly:\n\n```bash\nluna-proxy serve\n```\n\nFirst-time mTLS CA and server certs (Linux, root):\n\n```bash\nsudo luna-proxy setup mtls --dir /etc/luna/certs --san luna.example.com\n```\n\nInstall a systemd unit (Linux, root):\n\n```bash\nsudo luna-proxy install systemd --enable\n```\n\nOperator commands use the Unix control socket on the central host:\n\n```bash\nluna-proxy status\nluna-proxy key load /etc/luna/ssh/encrypted_ca.key --passphrase-stdin\nluna-proxy key list\nluna-proxy key remove \u003cfingerprint\u003e\nluna-proxy key confirm \u003cpending-id\u003e\nluna-proxy key reject \u003cpending-id\u003e\nluna-proxy mobile enroll --label phone --pubkey \u003cbase64-ed25519-pubkey\u003e \\\n  --cert-fingerprint \u003csha256-mobile-client-cert\u003e\nluna-proxy mobile list\n```\n\nIn `local-ca`, `key load` replaces the single CA signer. In `local-key`, it adds a signer to the in-memory pool. Process restart clears loaded signers, CLI enrollments, mobile enrollments, and pending uploads in v1.\n\nMobile approvals and pending-key uploads must use the exact mTLS client\ncertificate fingerprint recorded at enrollment.\n\n### Remote key load (`local-key`)\n\nWhen `signer_mode=local-key`, operators can load host signing keys without copying encrypted PEM to the central host. On-host load uses the control socket (`luna-proxy key load /path/on/server` on the central machine). From a laptop, use enrolled CLI mTLS (`OU=luna-cli`).\n\n**1. Enroll a CLI device (CSR; private key never leaves the laptop)**\n\n```bash\nluna-proxy cli init                    # ~/.config/luna/cli.key\nluna-proxy cli csr                     # ~/.config/luna/cli.csr.pem\n# On central host (admin control socket):\nluna-proxy cli enroll --label alice-macbook --csr-file ~/.config/luna/cli.csr.pem\n# Writes cli.crt beside the CSR (or use --cert-out)\n```\n\nThe proxy must have `mtls_ca_cert_path` / `mtls_ca_key_path` set (or `LUNA_MTLS_CA_CERT` / `LUNA_MTLS_CA_KEY`). Without CA key material, enroll returns `503`.\n\n**2. Configure the operator profile**\n\n`~/.config/luna/cli.yaml`:\n\n```yaml\nproxy_url: https://luna.example:443\ncli_cert: ~/.config/luna/cli.crt\ncli_key: ~/.config/luna/cli.key\nca: ~/.config/luna/ca.crt\n```\n\nFlags override the file: `--proxy-url`, `--cli-cert`, `--cli-key`, `--ca`.\n\n**3. Load from the laptop**\n\n```bash\nluna-proxy key load ./encrypted-host.key --label deploy-prod\n```\n\nUploads base64 encrypted PEM + passphrase over `POST /api/v1/cli/keys/load` inside mTLS with `X-Luna-Body-Mac` and `timestamp` (same auth binding as sign API). Requires `--label` for remote load and `signer_mode=local-key`.\n\n**v1 notes:** CLI device registry is in-memory; proxy restart drops enrollments (re-enroll and re-load). Mobile pending upload + `key confirm` is unchanged.\n\nSee [docs/superpowers/specs/2026-05-31-cli-remote-key-load-design.md](docs/superpowers/specs/2026-05-31-cli-remote-key-load-design.md).\n\n### luna-agent\n\n| Variable | Purpose |\n|----------|---------|\n| `LUNA_PROXY_URL` | Proxy base URL |\n| `LUNA_SIGNER_MODE` | `local-ca` or `local-key` (must match proxy) |\n| `LUNA_MTLS_CERT` / `LUNA_MTLS_KEY` / `LUNA_MTLS_CA` | Client mTLS material |\n| `LUNA_TARGET_USER` | Default SSH principal |\n| `LUNA_TARGET_HOST` | Target IP/hostname for PoP / cert binding |\n| `LUNA_HOST_KEY_FINGERPRINT` | Optional `local-key` signer hint when multiple host keys are loaded |\n\nIn `local-key` mode, the verified destination SSH host-key fingerprint from\nOpenSSH session binding is authoritative. `LUNA_TARGET_HOST` / `target_ip` is\ndisplay and audit metadata only. Forwarded agents and OpenSSH clients that do\nnot send `session-bind@openssh.com` are rejected.\n\nDirect in-process `x/crypto/ssh` SDK clients cannot produce the OpenSSH agent\nsession-binding extension. They must pass the server host key accepted by their\n`ssh.HostKeyCallback` as `SignatureRequest.DestinationHostPublicKey`. The proxy\nlabels this destination as client-reported and requires approval for every\nsignature; direct SDK requests never reuse approval leases.\n| `LUNA_HOSTED_PUBLIC_KEY` | Optional host `.pub` path or authorized_keys line for identity discovery |\n\nAgent socket: `/run/luna/agent.sock` (mode `0600`).\n\nPersistent daemon — interactive setup (Linux, root):\n\n```bash\nsudo luna-agent setup\nexport SSH_AUTH_SOCK=/run/luna/agent.sock\n```\n\n## HTTP API\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `POST` | `/api/v1/admin/unseal` | `410 Gone`; use control socket `key load` |\n| `GET` | `/api/v1/admin/seal-status` | `410 Gone`; use control socket `status` |\n| `GET` | `/api/v1/capabilities` | mTLS: signer mode, TTLs, sealed |\n| `POST` | `/api/v1/ssh/sign` | Create transaction; `202` + `tx_id` |\n| `GET` | `/api/v1/ssh/sign/{tx_id}/wait` | Block until cert/signature or error |\n| `POST` | `/api/v1/mobile/enroll` | Admin mTLS: register device |\n| `POST` | `/api/v1/mobile/approve` | mTLS + device signature |\n| `POST` | `/api/v1/mobile/keys/pending` | Enrolled mobile device: upload encrypted key for local confirmation |\n| `DELETE` | `/api/v1/mobile/devices/{device_id}` | Admin mTLS: revoke device |\n| `POST` | `/api/v1/cli/enroll` | Admin mTLS: sign CSR, register CLI device |\n| `GET` | `/api/v1/cli/devices` | Admin mTLS: list CLI devices |\n| `DELETE` | `/api/v1/cli/devices/{device_id}` | Admin mTLS: revoke CLI device |\n| `POST` | `/api/v1/cli/keys/load` | Enrolled CLI mTLS: upload encrypted key (`local-key` only) |\n| `GET` | `/healthz` | Health check (no client cert required) |\n\nAuth order on sign requests: mTLS → HMAC → timestamp → replay LRU → PoP → tx/lease/sign.\n\n## Security principles\n\n- **Fail-closed:** Auth and key-load failures never create transactions or Telegram prompts.\n- **Sealed by default:** Sign returns `503` until a signer is loaded into the in-memory keystore.\n- **Zero disk keys on clients:** Ephemeral private keys stay in memory.\n- **IP binding:** `source-address` on user certs from mTLS `RemoteAddr`.\n- **Session leases:** Re-approval skipped for same client + target + approver within TTL.\n- **Local administration:** Key load, key list/remove, status, CLI enrollment, and mobile enrollment have Unix socket paths guarded by peer credentials.\n- **Telegram approvals:** Private-chat callbacks must come from the configured chat/user ID. Group chats require `TELEGRAM_USER_IDS`.\n\n## Documentation\n\n| Document | Contents |\n|----------|----------|\n| [docs/deploy.md](docs/deploy.md) | Install proxy/agent, systemd, Docker Compose, GHCR |\n| [docs/setup.md](docs/setup.md) | Target `sshd` trust, operator runbooks, legacy Vault notes |\n| [docs/legacy-vault-migration.md](docs/legacy-vault-migration.md) | Vault → self-hosted mapping |\n| [docs/superpowers/specs/2026-05-30-self-hosted-central-design.md](docs/superpowers/specs/2026-05-30-self-hosted-central-design.md) | Self-hosted central server design |\n| [docs/superpowers/plans/2026-05-30-self-hosted-central.md](docs/superpowers/plans/2026-05-30-self-hosted-central.md) | Implementation plan |\n| [docs/superpowers/specs/2026-05-31-proxy-cli-keystore-design.md](docs/superpowers/specs/2026-05-31-proxy-cli-keystore-design.md) | Control socket, Cobra CLI, multi-key keystore |\n| [docs/superpowers/specs/2026-05-31-cli-remote-key-load-design.md](docs/superpowers/specs/2026-05-31-cli-remote-key-load-design.md) | Enrolled CLI remote key loading |\n| [AGENTS.md](AGENTS.md) | Guidance for AI coding agents |\n\n## License\n\nSee repository license file when published.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fba0f3%2Fluna-ztrust","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fba0f3%2Fluna-ztrust","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fba0f3%2Fluna-ztrust/lists"}