sha256:be5bc3eb74582ceec1f9b32d52a5e8aa17c575f75e553bceef2df26eccff363f
Last pushed
2 days by dockerpublicbot
Type
Sandbox Kit
Manifest digest
sha256:be5bc3eb74582ceec1f9b32d52a5e8aa17c575f75e553bceef2df26eccff363f
schemaVersion: "2"
kind: mixin
name: git-ssh-sign
displayName: Git SSH Commit Signing
description: Configures git to sign commits using the SSH key forwarded from the host's SSH agent.
agentInstructions:
content: |
## Git commit signing
This sandbox signs git commits with your host's SSH key, forwarded over
the sandbox's SSH agent relay. Use `git log --show-signature` to verify
signatures on existing commits.
A host agent often forwards more than one key. When the repository's
remote is on github.com and the `gh` CLI is authenticated, the agent's
keys are cross-checked against the signing keys registered on the
committer's GitHub account, so the commit shows as Verified there and
not only locally. On any other host, or without `gh`, the first key
the agent offers is used.
If no agent key is registered there, the commit still signs but shows
Unverified on GitHub. Git hides that warning (it only surfaces the key
command's stderr when the command fails), so to see it run
`/bin/sh ~/.config/git/ssh-signing-key-command` directly.
Automatic signing is scoped to repositories that **have at least one
remote**. A repository is exempt only for as long as it has no remote:
a test fixture that runs `git init` and stops there commits unsigned,
but one that goes on to `git remote add` is signed like any other
repository. `git commit -S` still signs anywhere.
The scoping needs git >= 2.36. On an older git the install falls back
to signing machine-wide and says so on stderr, and then nothing is
exempt — if `git init` fixtures are failing to commit, check
`git config --system --get commit.gpgSign` before believing the
paragraph above.
### When signing fails
The key is resolved at signing time from the forwarded agent, so a
signing failure almost always means the agent is unreachable or holds
no usable key, not that a config value is missing. Git reports it as:
warning: gpg.ssh.defaultKeyCommand failed: [git-ssh-sign] ...
error: user.signingKey needs to be set for ssh signing
**Ignore the `user.signingKey` line** — this kit deliberately leaves
`user.signingKey` unset. Read the `[git-ssh-sign]` lines above it; they
name the actual state and the recovery.
Diagnose in this order:
```console
echo "$SSH_AUTH_SOCK" # always set inside a sandbox; proves nothing
test -S "$SSH_AUTH_SOCK" # the real check — is the relay socket there?
ssh-add -l # 0 = keys; 1 = connected, no usable key; 2 = no connection
```
Recovery, all of it **on the host**, not in the sandbox:
1. Check that forwarding is switched on at all:
```console
sbx settings get ssh.agentForwardingEnabled # must be true
sbx settings set ssh.agentForwardingEnabled true
```
Do this first. While it is `false` the forwarded agent is never
wired up, so `ssh-add` inside the sandbox reports a broken agent
(exit 1 or 2, depending on how far the connection gets) rather than
a disabled feature — and no amount of reloading keys or restarting
the sandbox changes it. The setting defaults to `true`, so a
`false` means it was turned off explicitly, e.g. by declining agent
forwarding during `sbx setup`.
2. Make sure the host agent actually holds the key:
`ssh-add -l`, and `ssh-add ~/.ssh/id_ed25519` if it does not.
3. If step 1 changed the setting, restart the daemon:
`sbx daemon restart`. Changes to `ssh.agentForwardingEnabled` and
`ssh.agentSocketPath` only reach sandboxes that already exist once
the daemon has restarted.
4. From that same shell — the daemon adopts the requesting client's
`SSH_AUTH_SOCK` — restart this sandbox's container so the relay
comes back:
```console
sbx stop <sandbox-name>
sbx run --name <sandbox-name>
```
Stop-then-run is the supported restart cycle; there is no
`sbx start`. Substitute the sandbox's own name: `hostname` prints
it *inside* the sandbox, but `$SANDBOX_NAME` does not exist in a
host shell, so type the literal name there.
To make one commit without a signature while you sort that out:
```console
git -c commit.gpgsign=false commit -m "..."
```
permissions:
network:
allow:
- api.github.com
setup:
install:
- command: |
set -e
# Signing *machinery* stays machine-wide: it is inert unless something
# asks for a signature, and keeping it here means `git commit -S` works
# in any repository in the sandbox.
git config --system gpg.format ssh
# The key command is invoked through /bin/sh rather than executed
# directly. Files shipped under a kit's files/home/ tree land mode
# 0644 when the kit is installed from its OCI artifact -- per-file
# executable bits are not representable in a v2 layer (spec/OCI-v2.md)
# -- and git execs this value itself, so a bare path would die with
# "cannot exec: Permission denied" before a single one of the script's
# own diagnostics could run.
git config --system gpg.ssh.defaultKeyCommand '/bin/sh /home/agent/.config/git/ssh-signing-key-command'
git config --system gpg.ssh.allowedSignersFile /home/agent/.config/git/allowed_signers
# user.signingKey MUST stay unset: git only runs
# gpg.ssh.defaultKeyCommand when it is empty. Setting it to any value
# (even a placeholder) disables dynamic key resolution entirely.
git config --system --unset-all user.signingKey || true
# Signing *policy* is scoped, not machine-wide. Setting
# commit.gpgSign=true in /etc/gitconfig makes every `git commit` in the
# sandbox depend on a live SSH agent — including throwaway repositories
# created by test suites that have nothing to do with the user's work.
# When the forwarded agent goes away, those all fail too.
mkdir -p /etc/git
cat > /etc/git/signing-enabled.inc <<'INC'
# Included by /etc/gitconfig for repositories with a remote.
# See the git-ssh-sign kit.
[commit]
gpgSign = true
[tag]
gpgSign = true
INC
chmod 0644 /etc/git/signing-enabled.inc
# Clear anything an older version of this kit left machine-wide, so a
# re-install converges rather than layering.
git config --system --unset-all commit.gpgSign || true
git config --system --unset-all tag.gpgSign || true
git config --system --unset-all 'includeIf.hasconfig:remote.*.url:**.path' || true
# `includeIf "hasconfig:remote.*.url:**"` needs git >= 2.36. Probe for
# it rather than parsing a version string: build a throwaway repo with
# a remote and see whether the include actually fires. Note that `**`
# is only a cross-slash wildcard when it is a whole path component --
# `[email protected]:**` does NOT match `[email protected]:org/repo.git`, so
# a bare `**` ("has any remote at all") is the pattern to use here.
probe=$(mktemp -d)
git init -q "$probe"
git -C "$probe" remote add origin https://example.invalid/probe.git
printf '[commit]\n\tgpgSign = true\n' > "$probe/inc"
printf '[includeIf "hasconfig:remote.*.url:**"]\n\tpath = %s/inc\n' "$probe" > "$probe/sys"
supported=$(GIT_CONFIG_SYSTEM="$probe/sys" GIT_CONFIG_GLOBAL=/dev/null \
git -C "$probe" config --get commit.gpgSign 2>/dev/null || true)
rm -rf "$probe"
if [ "$supported" = "true" ]; then
git config --system 'includeIf.hasconfig:remote.*.url:**.path' /etc/git/signing-enabled.inc
else
# Old git: an unrecognised includeIf keyword evaluates false, which
# would silently disable signing. Fail closed (sign everywhere)
# rather than fail open (sign nothing).
echo "[git-ssh-sign] git $(git --version | awk '{print $3}') lacks includeIf hasconfig; enabling signing machine-wide" >&2
git config --system commit.gpgSign true
git config --system tag.gpgSign true
fi
if [ "$(git config --system --get core.hooksPath || true)" = "/home/agent/.config/git/hooks" ]; then
git config --system --unset-all core.hooksPath
fi
user: "0"
description: Configure SSH commit signing, scoped to repositories with a remote