Skip to content

jmg-duarte/alpaca

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

alpaca

alpaca

An isolated Lima dev VM on macOS, with all SSH auth and git signing routed through a YubiKey. The host holds the FIDO2 credentials; the VM never sees a private key. Lima reverse-forwards a user-space ssh-agent socket into the VM, scoped to github.com only.

What you get

  • Ubuntu 26.04 LTS ARM64 in Lima, hardened: no host filesystem mounts, reversed agent socket forward, USB only as needed.
  • Two ed25519-sk resident credentials on the YubiKey:
    • auth key (PIN + touch per use) for git push/pull to GitHub.
    • sign key (touch per use) for git commit -S / git tag -s.
  • Inside the VM: Docker, Rust (rustup), fish as default shell, git pre-configured to sign with the forwarded SK key, ~/.ssh/config pinned to github.com only.

Prerequisites

  • macOS on Apple Silicon.
  • Homebrew + Lima: brew install lima.
  • ssh-askpass on a known path: brew install theseal/ssh-askpass/ssh-askpass.
  • A YubiKey with FIDO2 enabled and a PIN already set (ykman fido access change-pin).
  • Fish shell on the host (the post-bootstrap instructions assume fish/conf.d).
  • The following env exported in your fish config (e.g. ~/.config/fish/conf.d/ssh-askpass.fish):
    set -gx SSH_AUTH_SOCK $HOME/.ssh/agent.sock
    set -gx SSH_ASKPASS /opt/homebrew/bin/ssh-askpass
    set -gx SSH_ASKPASS_REQUIRE force

Install

No package manager. Clone the repo and either add it to PATH or symlink the dispatcher:

git clone <repo-url> ~/src/alpaca
ln -s ~/src/alpaca/alpaca ~/bin/alpaca   # or fish_add_path the repo dir

alpaca resolves its own symlinks, so it finds the sibling scripts regardless of where you symlink it from.

Quickstart

alpaca prerequisites install  # one-time: brew-install lima, ssh-askpass, ykman, libfido2
alpaca enroll                 # one-time: create the two SK credentials on the YubiKey
alpaca bootstrap              # build the VM and load the keys into a fresh user-space agent
alpaca doctor                 # verify everything end-to-end

After bootstrap, paste the printed pubkeys into https://github.com/settings/ssh/new (one as Authentication, one as Signing).

Commands

Command What it does
alpaca prerequisites check List host prerequisites (lima, theseal/ssh-askpass, ykman, libfido2) with present/missing status. Read-only; exits non-zero if anything is missing.
alpaca prerequisites install Interactively brew install any missing prerequisites. Skips ones already present.
alpaca enroll Interactive: generates two ed25519-sk resident keys via ssh-keygen. Touch always required; PIN-per-signature opt-in (default Y for auth, N for sign).
alpaca bootstrap Destructive. Stops + deletes any existing Lima VM with the chosen name, recreates it from isolated-dev.yaml, runs vm-setup.sh inside. Replaces the user-space ssh-agent at ~/.ssh/agent.sock and re-ssh-adds both keys.
alpaca doctor Read-only diagnostics across host tools, YubiKey, FIDO2 PIN status, ssh-agent env, and (if running) the VM. Prints PASS/WARN/FAIL with deduplicated next-steps.
alpaca reset-agent Lightweight: tears down the host agent, restarts it, reloads both SK keys. Faster than bootstrap when you just need to re-arm the agent. The running VM picks up the new agent automatically (forward routes by path).

vm-setup.sh is intentionally not exposed via alpaca — it runs inside the VM, invoked by bootstrap.sh.

Each underlying script in cmd/ also takes --help directly (e.g. cmd/bootstrap.sh --help).

Environment variables

Variable Used by Purpose
SSH_ASKPASS bootstrap, reset-agent Path to ssh-askpass binary; required so the agent can prompt for the FIDO2 PIN.
SSH_ASKPASS_REQUIRE bootstrap, reset-agent Must be force or prefer.
VM_NAME bootstrap, doctor Lima instance name. Defaults to default.
GIT_NAME, GIT_EMAIL bootstrap Falls back to git config user.name / user.email if unset.
SK_SIGN_KEY, SK_AUTH_KEY bootstrap Substring matching the desired pubkey file path; skips the interactive picker.
YAML_FILE, VM_SETUP_FILE bootstrap Path overrides; default to siblings of bootstrap.sh.
SIGN_FILE, AUTH_FILE enroll, doctor, reset-agent Handle file paths. Default: $HOME/.ssh/id_ed25519_sk_{sign,auth}.
USER_AGENT_SOCK doctor, reset-agent Canonical user-space agent socket. Default: $HOME/.ssh/agent.sock.
PROBE_VM doctor Set 0 to skip the in-VM probe.

Threat model

        ┌──────────────┐
        │   YubiKey    │  ed25519-sk credentials
        │  auth │ sign │  never leave the device
        └──────┬───────┘
               │ libfido2
               │ PIN (auth only) + touch (always)
        ───────┼──────── trust boundary ────────
               │
        ┌──────┴───────────────────────┐
        │ macOS host                   │
        │  ssh-askpass → ssh-agent     │
        │             ~/.ssh/agent.sock│
        └──────────────┬───────────────┘
                       │ reverse-forwarded unix socket
                       │ (Lima exposes the agent to the VM —
                       │  signatures only, no key material)
        ┌──────────────┴───────────────┐
        │ Lima VM                      │
        │  • no host filesystem mounts │
        │  • ssh pinned to github.com  │
        │    (~/.ssh/config in VM)     │
        └──────────────┬───────────────┘
                       │ git over ssh
                       ▼
                  github.com

What the VM isolation gives you:

  • No host filesystem access. The VM cannot read ~, /etc, or anything else on the Mac — there are no shared mounts.
  • Keys never enter the VM. Lima reverse-forwards the host's user-space ssh-agent as a Unix socket; the VM can request signatures, but the credentials live on the YubiKey.
  • SSH scoped to GitHub. ~/.ssh/config in the VM pins the forwarded agent to github.com only — outbound SSH to anywhere else won't draw on the SK keys.
  • Hardware-attested signatures. The auth key has verify-required set, so every signature requires a PIN via host ssh-askpass plus a YubiKey touch. The sign key requires touch only.

Residual risks:

  • Kernel-level VM escape. Lima/QEMU isn't a Type-1 hypervisor — the VM shares the host kernel, so a kernel exploit can cross the boundary.
  • Host compromise reaches the VM. limactl shell and the VM's Lima-managed SSH run with your macOS user's privileges; any process running as you can drop into the VM.
  • Signing oracle while the YubiKey is plugged in. A compromised process in the VM can ask the forwarded agent to sign on its behalf. The auth key's PIN+touch requirement blocks silent abuse, but the sign key only requires touch — don't approve touch prompts you didn't trigger.

Troubleshooting

Problem Fix
FIDO2 PIN exhausted (doctor reports 0 attempts) ykman fido reset (destroys all FIDO2 credentials), then alpaca enroll, then alpaca bootstrap.
Lost or replaced YubiKey alpaca enroll on the new key, upload pubkeys to GitHub, alpaca bootstrap.
Agent stuck / "agent refused operation" alpaca reset-agent. If that fails, alpaca doctor to find the missing env or socket.
VM in a bad state alpaca bootstrap (it stops + deletes + recreates the VM).

Caveats

  • alpaca bootstrap is destructive — it deletes the named VM. If you have state inside the VM you care about, copy it out first.
  • Apple Silicon only (isolated-dev.yaml pins arch: aarch64).
  • Host-side post-install instructions assume fish. If you use a different shell, translate the set -gx lines.
  • ykman is recommended (for doctor to inspect FIDO2 PIN status) but not required for the bootstrap path itself — OpenSSH talks to the YubiKey via libfido2 directly.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages