Sign commits on managed computers
Store a GPG or SSH signing key as a runtime secret, load it in Droid Computer and Environment sessions, and push signed commits to self-managed source control.
Factory does not create, store, or manage commit-signing keys for you. To sign commits on a Factory-managed computer, store your own signing key as a runtime secret, load it into gpg or ssh-keygen inside the session, and point Git at it. This is the supported path for both persistent Droid Computers and instances started from an Environment, because both read the same secrets from Settings → Account → Secrets.
No secret name is special to Factory. GPG_SIGNING_KEY_B64, SSH_SIGNING_KEY_B64, and GPG_SIGNING_PASSPHRASE on this page are examples that the setup script below reads. You can use any name that is not runtime-reserved, as long as the script reads the same name.
How it works
- 1You create a dedicated signing key on your own workstation and register its public half with your source-control server.
- 2You base64-encode the private key so it fits in one line, and save it as a runtime secret.
- 3When a session starts, the secret is available as an environment variable. A setup script imports the key and sets
user.signingkey,gpg.format, andcommit.gpgsignin the computer's global Git config. - 4Every commit Droid makes is signed. Your server verifies the signature against the public key you registered.
Runtime secrets are not available during an Environment build, so signing setup always runs inside a session, never in the Environment's setup script. See where to run the setup.
Choose a signature format
| Format | Git setting | Use it when |
|---|---|---|
| GPG (OpenPGP) | gpg.format=openpgp | Your server or a custom pre-receive hook requires GPG signatures specifically. |
| SSH | gpg.format=ssh | Your server accepts SSH signatures and you prefer to manage SSH keys. |
GitLab's Reject unsigned commits push rule accepts GPG, SSH, and X.509 signatures. A custom server hook can be stricter; for example, an error such as Commit must be signed with a GPG key can come from a GPG-only hook. Check which rule your project uses before you choose. GitHub Enterprise Server also verifies both GPG and SSH signatures. X.509 signing (gpg.format=x509) needs gpgsm and a certificate setup that this page does not cover.
Before you start
- Use a dedicated key. Create a key used only for commits that Droid makes on Factory computers. Do not reuse a personal key that signs other work, so that you can revoke it without affecting anything else.
- Match the commit email. Your server marks a signature as verified only when the commit's committer email matches a verified email on the account that owns the key. For GPG, that email must also be a user ID on the key. See commit identity.
- Plan for no prompts. Sessions cannot answer a passphrase prompt. Use a key without a passphrase, or store the passphrase as a second secret, which the setup script passes to
gpgin loopback mode. - Check the size limit. A secret value can hold at most 10,000 characters. Ed25519 keys are well under that limit after encoding. See secret limits.
Create and encode the key
Run these commands on your own workstation, not in a Droid session, so the private key never passes through a session transcript.
Create a signing-only Ed25519 key. Use the email that your commits will carry.
gpg --quick-gen-key "Factory Droid <droid@example.com>" ed25519 sign 1yExport the secret key in binary form and encode it as one base64 line. Binary export is smaller than ASCII armor.
gpg --export-secret-keys droid@example.com | base64 | tr -d '\n' > gpg-signing-key.b64Export the public key and add it to the source-control account that will own the commits. In GitLab, open Preferences → GPG keys. In GitHub Enterprise Server, open Settings → SSH and GPG keys.
gpg --armor --export droid@example.comCreate an Ed25519 key without a passphrase, used only for signing.
ssh-keygen -t ed25519 -C "droid@example.com" -N "" -f factory-signing-keyEncode the private key as one base64 line.
base64 < factory-signing-key | tr -d '\n' > ssh-signing-key.b64Add factory-signing-key.pub to the source-control account that will own the commits. In GitLab, open Preferences → SSH Keys and set Usage type to Signing or Authentication & Signing. In GitHub Enterprise Server, add it as a Signing key.
tr -d '\n' removes the line breaks that base64 adds on Linux, so the same command works on Linux and macOS. Delete the .b64 file after you save the secret.
Save the key as a runtime secret
- 1Open Settings → Account → Secrets.
- 2Choose Personal for a key that signs only your sessions. A manager or owner can choose Organization for a key that every member and service account shares, though a per-person or per-service-account key keeps attribution clearer.
- 3Click Add secret. Name it
GPG_SIGNING_KEY_B64orSSH_SIGNING_KEY_B64and paste the contents of the.b64file as the value. - 4If your GPG key has a passphrase, add a second secret named
GPG_SIGNING_PASSPHRASE.
To give a service account its own key, for example for Delegations, save the secret while acting as that service account or as an organization secret. A session that runs as a service account does not see the configuring user's personal secrets.
Add the setup script
The script below reads whichever key secret is present, imports it, and configures Git. It does nothing when neither secret is set, so it is safe to commit to a repository that people also use locally.
#!/usr/bin/env bash
# Configures Git commit signing from a Factory runtime secret.
# Safe to run repeatedly. Does nothing when no signing secret is set.
set -euo pipefail
if [ -n "${GPG_SIGNING_KEY_B64:-}" ]; then
install -d -m 700 "$HOME/.gnupg"
key_fpr="$(printf '%s' "$GPG_SIGNING_KEY_B64" | base64 -d \
| gpg --batch --with-colons --import-options show-only --import 2>/dev/null \
| awk -F: '/^fpr:/ { print $10; exit }')"
printf '%s' "$GPG_SIGNING_KEY_B64" | base64 -d | gpg --batch --quiet --import 2>/dev/null
git config --global gpg.format openpgp
git config --global user.signingkey "$key_fpr"
if [ -n "${GPG_SIGNING_PASSPHRASE:-}" ]; then
install -d -m 700 "$HOME/.local/bin"
cat > "$HOME/.local/bin/gpg-loopback" <<'WRAPPER'
#!/usr/bin/env bash
exec gpg --batch --pinentry-mode loopback --passphrase-fd 3 "$@" 3<<<"$GPG_SIGNING_PASSPHRASE"
WRAPPER
chmod 700 "$HOME/.local/bin/gpg-loopback"
git config --global gpg.program "$HOME/.local/bin/gpg-loopback"
fi
elif [ -n "${SSH_SIGNING_KEY_B64:-}" ]; then
install -d -m 700 "$HOME/.ssh"
(umask 077 && printf '%s' "$SSH_SIGNING_KEY_B64" | base64 -d > "$HOME/.ssh/factory_signing_key")
ssh-keygen -y -f "$HOME/.ssh/factory_signing_key" > "$HOME/.ssh/factory_signing_key.pub"
git config --global gpg.format ssh
git config --global user.signingkey "$HOME/.ssh/factory_signing_key"
printf '%s namespaces="git" %s\n' "$(git config --global user.email)" \
"$(cat "$HOME/.ssh/factory_signing_key.pub")" > "$HOME/.ssh/allowed_signers"
git config --global gpg.ssh.allowedSignersFile "$HOME/.ssh/allowed_signers"
else
exit 0
fi
git config --global commit.gpgsign true
git config --global tag.gpgSign true
echo "Git commit signing is configured ($(git config --global gpg.format))."The passphrase wrapper reads GPG_SIGNING_PASSPHRASE from the environment each time Git signs, so the passphrase is never written to disk. gpg and ssh-keygen are preinstalled on Factory-managed computers.
Where to run the setup
| Target | What persists | When to run the script |
|---|---|---|
| Droid Computer | The imported key and Git config stay on the computer across sessions. | Once, then again after you rotate the key. |
| Environment instance | Each instance starts from the build snapshot, which never contains runtime secrets. | At the start of every session on a new instance. |
To run it automatically, commit the script and a project SessionStart hook to the repository:
{
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "\"$FACTORY_PROJECT_DIR\"/.factory/hooks/setup-commit-signing.sh",
"timeout": 30
}
]
}
]
}Make the script executable (chmod +x) before you commit it. Without a hook, run the script yourself through droid computer ssh, or ask Droid to run it before it commits.
Do not put the key, the passphrase, or the decoding commands in an Environment setup script. Secrets are not available during the build, and anything the build writes becomes part of the shared snapshot.
Commit identity with self-managed source control
Factory writes a Git identity into each managed computer's global Git config and can add per-host identities for connected source-control servers. Which identity applies depends on who runs the session:
| Session runs as | Commits are authored as | Key to register |
|---|---|---|
| Human user (organization OAuth) | The identity Factory configures for that user on the computer. | Add the public key to the server account whose verified email matches that identity. |
| Service account, GitLab Self-Hosted | The commit author name and email set on the service account's GitLab token, or the token owner's email when unset. | Add the public key to the GitLab user that owns that email. |
| Service account, GitHub Enterprise Server | The factory-droid[bot] identity of the GitHub App. | A bot email cannot own your key. Set a repository user.email for the machine user that owns the key, as shown below. |
Check the identity a repository will use before the first signed commit:
git config user.name
git config user.emailIf the email does not match the key, set it for that repository. Repository config takes precedence over the global and per-host identities that Factory manages, and Factory never changes it:
git config user.email droid@example.comSelf-Managed Source Control describes how human users and service accounts get repository credentials on GitLab Self-Hosted and GitHub Enterprise Server.
Verify a signed commit
After the setup script runs, make a commit and check it locally before you push:
git log --show-signature -1
git cat-file commit HEAD | head -n 8A GPG commit shows Good signature from and a gpgsig header. An SSH commit shows Good "git" signature for. After you push, the commit shows Verified in GitLab or GitHub Enterprise Server when the email and registered public key match.
Rotate or revoke a key
- 1Create a new key and register its public half with your server, as in create and encode the key.
- 2In Settings → Account → Secrets, open the secret's menu, select Edit, and paste the new encoded value. Saved values cannot be viewed again.
- 3New Environment instances get the new value right away. An existing Droid Computer keeps its current environment until credentials refresh and its processes restart; restart the computer if a session still sees the old value.
- 4Run the setup script again. It sets
user.signingkeyto the newly imported key. - 5Remove the old public key from your server and, for GPG, delete it from the computer's keyring with
gpg --delete-secret-and-public-keys <old-fingerprint>.
Deleting a personal secret lets an organization secret with the same name apply after the next refresh.
Keep the key out of commits and transcripts
- Never commit the private key, the
.b64file, or~/.gnupg. Droid Shield blocks commits that add PGP and SSH private keys, but it cannot recognize a base64-encoded key reliably, so do not rely on it for this. - Do not ask Droid to print or
echothe secret. Command output becomes part of the session transcript. - Signing does not change approvals. If your autonomy level requires approval for
git commitorgit push, Droid still asks before it runs them.