Forward the host SSH agent into project containers (1Password, Bitwarden, YubiKey)#227
Open
sbaerlocher wants to merge 3 commits into
Open
Forward the host SSH agent into project containers (1Password, Bitwarden, YubiKey)#227sbaerlocher wants to merge 3 commits into
sbaerlocher wants to merge 3 commits into
Conversation
16cade5 to
7778f8d
Compare
The .kiro/ directory holds local feature specs and steering notes that are scratch artifacts, not part of the distributed project. Keep them untracked. Signed-off-by: Simon Bärlocher <s.baerlocher@sbaerlocher.ch>
Add a `host` SSH-agent mode as an alternative to the managed `dde-ssh-agent`
container. In host mode dde forwards the developer's existing host SSH agent
(1Password, Bitwarden, a classic ssh-agent, or a hardware token such as a
YubiKey) into project containers and holds no keys itself. The default
`managed` mode is unchanged; leaving `ssh.agent` unset preserves it.
Configuration is machine-global in ~/.dde/config.yml:
ssh:
agent:
mode: host # managed (default) | host
source: 1password # null | auto | env | 1password | bitwarden | <path>
The in-container contract is constant in both modes — the socket lives at
/tmp/ssh-agent/socket and SSH_AUTH_SOCK points at it, so a container cannot tell
which mode is active. Only the host side of the mount differs.
HostSshAgentResolver is the single source of truth for "where is the host agent
socket and is it usable", shared by the forwarding path (DockerComposeManager)
and the doctor check. Platform/provider resolution:
- macOS: 1password -> group-container socket, bitwarden ->
~/.bitwarden-ssh-agent.sock, both bind-mounted directly; auto/env -> Docker
Desktop host-services bridge. Direct mounts sidestep the launchd-visibility
trap that leaves the bridge empty.
- Linux: 1password -> ~/.1password/agent.sock; bitwarden/env/auto ->
SSH_AUTH_SOCK (Bitwarden's Linux path is version-variable, never hardcoded).
- Unsupported OS -> reported unavailable, never crashes.
The host socket is bind-mounted read-write in Compose long-syntax: on Docker
Desktop the mount lands as root:root with no host->container UID mapping, so the
container entrypoint chowns it to the dde user while still root (a read-only
mount would forbid that, and :ro buys nothing on a socket). Long-syntax avoids a
short-form source:target split corruption when the socket path contains a ":".
The managed dde-ssh-agent container is suppressed at the service level in host
mode: the lifecycle starts every global service unconditionally, so the gate
lives in SshAgentService::start()/build() rather than at each call site. The
system:install start and system:up interactive key-add are likewise skipped.
A mode-aware system:doctor check verifies the prerequisite per mode/platform,
and project:up warns and continues when the host agent cannot be resolved
instead of aborting the bring-up.
Signed-off-by: Simon Bärlocher <s.baerlocher@sbaerlocher.ch>
Add docs/guides/ssh-agent.md covering both modes, the provider x platform resolution matrix, the read-write/chown rationale, and the macOS launchd caveat. Includes a troubleshooting section for the three states that pass the doctor check yet still fail inside a container (locked vault -> Permission denied, stale socket after the host agent restarts -> Connection refused, empty agent -> no identities), a hardware-token/YubiKey section (touch policy keeps the token closed even with the socket forwarded), and a security note that host mode exposes the forwarded agent to all project container code. Register HostSshAgentResolver and HostSshAgentResolution in the AGENTS.md architecture list. Signed-off-by: Simon Bärlocher <s.baerlocher@sbaerlocher.ch>
7778f8d to
58dec68
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
New
hostSSH-agent mode: instead of the manageddde-ssh-agentcontainer, dde forwards the developer's existing host agent into project containers (1Password, Bitwarden, a classicssh-agent, or a hardware token such as a YubiKey). dde holds no keys itself. The existingmanagedmode stays the default and is unchanged; leavingssh.agentunset changes nothing.This addresses two recurring pain points of the managed agent: keys missing in project containers and an awkward key-add flow (Refs #113), and keys gone after a host or agent restart (Refs #1). In host mode the host agent owns the keys, dde only forwards the socket.
Configuration is machine-global in
~/.dde/config.yml:How to test
Verified locally on macOS (Docker Desktop) with 1Password, Bitwarden, and the managed fallback:
ssh -T git@dev.whatwedo.chinside the container authenticates through the forwarded agent.dde system:doctorreports the resolved socket. The provider×platform matrix and gotchas (locked vault, stale socket after an agent restart → re-up, empty agent) are indocs/guides/ssh-agent.md.What to watch for in review
root:rootwith no UID mapping, so the container entrypoint chowns it to the dde user as root (a read-only mount would forbid that;:robuys nothing on a socket). Long-syntax keeps a socket path containing a:from corrupting the short-form split.dde-ssh-agentcontainer is suppressed at the service level in host mode (SshAgentService::start()/build()), because the lifecycle starts every global service unconditionally.ssh -A) — with a hardware token's touch policy the key still stays locked until a physical tap.The
buildcheck (website docs) is pre-existingly red on v2 (astro/starlight peer conflict) and is fixed by #228 — it goes green once that merges or after a rebase. This PR does not touchwebsite/.Refs #113
Refs #1