Sign commits made in the sandbox
The sandbox held no signing material, so its commits arrived unverified and a branch rule requiring signatures rejected them outright. AI_SBX_SIGNING_KEY copies an SSH signing key into the sandbox and points both git and jj at it. The private half genuinely lands in the sandbox, which is why this is opt-in and documented as signing-only: an agent that can read the key can sign as you. A signing key grants no repository access and is revocable on its own, so the exposure is forged attestation rather than reach. Forwarding an agent socket would avoid the copy, but a socket passed over virtiofs is visible and unconnectable from the guest, and the TCP workaround is a worse trade. Setup refuses a passphrase-protected key rather than letting the failure surface on the agent's first commit, and writes an allowed_signers entry so the sandbox can verify what it just signed. jj is configured through conf.d, which is read after config.toml and so overrides the host key path a copied dotfile carries.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source-path=SCRIPTDIR
|
||||
# shellcheck source=tasks/ai/sbx
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/../tasks/ai/sbx"
|
||||
|
||||
failures=0
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
failures=$((failures + 1))
|
||||
}
|
||||
|
||||
SANDBOX_NAME=ai-test
|
||||
SANDBOX_HOME="$work/sandbox-home"
|
||||
REPO_ROOT="$work/repo"
|
||||
mkdir -p "$SANDBOX_HOME"
|
||||
|
||||
git init --quiet "$REPO_ROOT"
|
||||
git -C "$REPO_ROOT" config user.email [email protected]
|
||||
|
||||
sbx() {
|
||||
[[ "$1" == exec ]] ||
|
||||
fail "unexpected sbx invocation: $*"
|
||||
shift
|
||||
|
||||
if [[ "$1" == -i ]]; then
|
||||
shift 2
|
||||
"$@"
|
||||
return
|
||||
fi
|
||||
|
||||
shift
|
||||
|
||||
if [[ "$1" == bash && "$2" == -c ]]; then
|
||||
local script="$3"
|
||||
shift 3
|
||||
HOME="$SANDBOX_HOME" bash -c "$script" "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
fail "unexpected sbx exec command: $*"
|
||||
}
|
||||
|
||||
sandbox_git() {
|
||||
HOME="$SANDBOX_HOME" git config --global --get "$1"
|
||||
}
|
||||
|
||||
mode() {
|
||||
stat -c '%a' "$1"
|
||||
}
|
||||
|
||||
ssh-keygen -q -t ed25519 -N '' -C signing -f "$work/signing" </dev/null
|
||||
ssh-keygen -q -t ed25519 -N 'locked' -C locked -f "$work/locked" </dev/null
|
||||
|
||||
DEFAULT_SIGNING_KEY=""
|
||||
install_sandbox_signing_key
|
||||
|
||||
[[ ! -e "$SANDBOX_HOME/.ssh" ]] ||
|
||||
fail "an unset AI_SBX_SIGNING_KEY still put a key in the sandbox"
|
||||
|
||||
DEFAULT_SIGNING_KEY="$work/absent"
|
||||
(install_sandbox_signing_key) 2>/dev/null &&
|
||||
fail "a missing signing key was accepted"
|
||||
|
||||
DEFAULT_SIGNING_KEY="$work/signing"
|
||||
mv "$work/signing.pub" "$work/signing.pub.hidden"
|
||||
(install_sandbox_signing_key) 2>/dev/null &&
|
||||
fail "a signing key with no public half was accepted"
|
||||
mv "$work/signing.pub.hidden" "$work/signing.pub"
|
||||
|
||||
DEFAULT_SIGNING_KEY="$work/locked"
|
||||
(install_sandbox_signing_key) 2>/dev/null &&
|
||||
fail "a passphrase-protected signing key was accepted"
|
||||
|
||||
DEFAULT_SIGNING_KEY="$work/signing"
|
||||
install_sandbox_signing_key >/dev/null
|
||||
|
||||
[[ -f "$SANDBOX_HOME/.ssh/signing" ]] ||
|
||||
fail "the private signing key was not installed"
|
||||
|
||||
[[ "$(mode "$SANDBOX_HOME/.ssh")" == 700 ]] ||
|
||||
fail ".ssh is mode $(mode "$SANDBOX_HOME/.ssh"), expected 700"
|
||||
|
||||
[[ "$(mode "$SANDBOX_HOME/.ssh/signing")" == 600 ]] ||
|
||||
fail "the private key is mode $(mode "$SANDBOX_HOME/.ssh/signing"), expected 600"
|
||||
|
||||
diff -q "$work/signing" "$SANDBOX_HOME/.ssh/signing" >/dev/null ||
|
||||
fail "the installed private key does not match the host key"
|
||||
|
||||
[[ "$(cat "$SANDBOX_HOME/.ssh/allowed_signers")" == \
|
||||
"[email protected] $(cat "$work/signing.pub")" ]] ||
|
||||
fail "allowed_signers does not map the repository principal to the key"
|
||||
|
||||
[[ "$(sandbox_git commit.gpgsign)" == true ]] ||
|
||||
fail "commit signing was not enabled in the sandbox"
|
||||
|
||||
[[ "$(sandbox_git tag.gpgsign)" == true ]] ||
|
||||
fail "tag signing was not enabled in the sandbox"
|
||||
|
||||
[[ "$(sandbox_git gpg.format)" == ssh ]] ||
|
||||
fail "the signing format is $(sandbox_git gpg.format), expected ssh"
|
||||
|
||||
[[ "$(sandbox_git user.signingkey)" == "$SANDBOX_HOME/.ssh/signing.pub" ]] ||
|
||||
fail "user.signingkey points at $(sandbox_git user.signingkey)"
|
||||
|
||||
[[ "$(sandbox_git gpg.ssh.allowedSignersFile)" == \
|
||||
"$SANDBOX_HOME/.ssh/allowed_signers" ]] ||
|
||||
fail "allowedSignersFile points at $(sandbox_git gpg.ssh.allowedSignersFile)"
|
||||
|
||||
override="$SANDBOX_HOME/.config/jj/conf.d/10-ai-sbx-signing.toml"
|
||||
|
||||
grep -Fqx "key = \"$SANDBOX_HOME/.ssh/signing.pub\"" "$override" 2>/dev/null ||
|
||||
fail "jj was not pointed at the key installed in the sandbox"
|
||||
|
||||
grep -Fqx 'backend = "ssh"' "$override" 2>/dev/null ||
|
||||
fail "the jj signing backend was not set to ssh"
|
||||
|
||||
signed="$work/signed"
|
||||
git init --quiet "$signed"
|
||||
HOME="$SANDBOX_HOME" git -C "$signed" \
|
||||
-c user.name=Malcolm -c user.email=[email protected] \
|
||||
commit --quiet --allow-empty -m probe
|
||||
|
||||
status="$(HOME="$SANDBOX_HOME" git -C "$signed" log -1 --format='%G?')"
|
||||
|
||||
[[ "$status" == G ]] ||
|
||||
fail "the sandbox produced a commit with signature status $status, expected G"
|
||||
|
||||
((failures == 0)) ||
|
||||
exit 1
|
||||
|
||||
printf 'ok: the sandbox signs and verifies its own commits\n'
|
||||
Reference in New Issue
Block a user