How do you implement GPG and SSH commit signing, enforce signature verification in branch protection rules, and integrate signing into CI/CD pipelines?
Quick Answer
Signed commits use GPG or SSH keys to cryptographically verify authorship, since Git's author/committer fields are trivially spoofable. Configure with git config commit.gpgsign true and user.signingkey. Enforce via GitHub/GitLab branch protection requiring signed commits. CI pipelines verify signatures with git verify-commit and maintain trusted keyrings for deployment gates.
Detailed Answer
Think of commit signing like the difference between a printed letter and a notarized document. Anyone can print a letter with any name at the bottom (git config user.name is purely cosmetic). A notarized document has a wax seal from a registered notary whose identity has been independently verified. Commit signing adds that notary seal: a cryptographic proof that the person who claims to be the committer actually possesses the private key associated with that identity. Without it, Git's authorship claims are trust-on-first-use at best and trivially forged at worst.
Git supports three signing methods: GPG (the traditional approach using GNU Privacy Guard key pairs), SSH keys (available since Git 2.34, simpler because developers already have SSH keys), and S/MIME certificates (common in enterprises with existing PKI infrastructure). For GPG: generate a key with gpg --full-generate-key, upload the public key to GitHub/GitLab, and configure Git with git config user.signingkey <KEY_ID> and git config commit.gpgsign true. For SSH: configure git config gpg.format ssh and git config user.signingkey ~/.ssh/id_ed25519.pub. SSH signing also requires an allowed_signers file for local verification, mapping email addresses to public keys.
Internally, when you create a signed commit, Git computes the full commit object content (tree hash, parent hash(es), author, committer, message) and signs this data with your private key. The signature is embedded in the commit object as a 'gpgsig' header field. Verification extracts the signature, reconstructs the signed content from the commit object, and validates the signature against the signer's public key. GitHub and GitLab compare the signing key against uploaded keys and display a 'Verified' badge. The distinction between author and committer is important: the author is who wrote the change (preserved during cherry-pick and rebase), while the committer is who committed it. Only the committer signs, so cherry-picked commits carry the original author but the cherry-picker's signature.
In a DevSecOps pipeline, signing fits into a layered supply chain security model. Branch protection rules on GitHub/GitLab can require signed commits on protected branches, rejecting unsigned pushes. CI pipelines add a second verification layer by running git verify-commit HEAD and checking the signer against a trusted keyring. Deployment gates can require that the release tag is a signed annotated tag (git tag -s v3.1.0) verified with git verify-tag. This defense-in-depth prevents commit injection attacks where a compromised CI system or stolen credentials could push malicious unsigned code. Organizations following the SLSA framework (Supply Chain Levels for Software Artifacts) require signing as part of provenance attestation. For automation, CI bots and merge operations need their own signing keys: GitHub's merge button creates GitHub-signed merge commits, but custom merge automation must be configured with a bot GPG or SSH key.
A critical gotcha: GPG key expiry causes sudden signing failures that break developer workflows. Set calendar reminders to extend key expiry before it expires, or use GPG subkeys that can be rotated independently of the primary key. Another trap: requiring signed commits creates friction for automated dependency updates (Dependabot, Renovate) and GitHub Actions workflows that commit changes. These need their own signing configuration, which is often overlooked during setup. Also, SSH signing is significantly easier to roll out at scale because teams already manage SSH keys for repository access, eliminating the need to install GPG tooling and manage a separate keyring. The tradeoff is that GPG keys support expiry dates, revocation, and a web of trust model that SSH keys lack.
Code Example
# GPG signing setup gpg --full-generate-key gpg --list-secret-keys --keyid-format=long # sec ed25519/3AA5C34371567BD2 2025-01-15 git config --global user.signingkey 3AA5C34371567BD2 git config --global commit.gpgsign true # SSH signing setup (simpler, Git 2.34+) git config --global gpg.format ssh git config --global user.signingkey ~/.ssh/id_ed25519.pub # Create allowed_signers file for SSH verification echo "[email protected] $(cat ~/.ssh/id_ed25519.pub)" >> ~/.ssh/allowed_signers git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers # Verify a commit signature locally git verify-commit HEAD git verify-commit a3f2c1d # Show signatures in log output git log --show-signature -3 # Sign an annotated release tag git tag -s v3.1.0 -m "Signed release: payments-api v3.1.0" git verify-tag v3.1.0 # Enforce signed commits via GitHub branch protection gh api repos/acme-corp/payments-api/branches/main/protection \ --method PUT \ --field required_signatures=true # CI pipeline verification script #!/bin/bash # Verify the HEAD commit is signed by a trusted key if ! git verify-commit HEAD 2>&1 | grep -q 'Good signature'; then echo 'ERROR: Commit is unsigned or signature invalid' exit 1 fi echo 'Commit signature verified ✓' # Sign all commits in an interactive rebase (preserves signatures) git rebase -i --gpg-sign=3AA5C34371567BD2 main
Interview Tip
A junior engineer typically has no awareness that Git's author field is spoofable. At the advanced level, open with the threat model: anyone can set git config user.email [email protected] and make commits that appear to come from the CEO. Then walk through signing as the countermeasure. Compare GPG and SSH signing: GPG has richer features (expiry, revocation, web of trust) but higher setup friction; SSH is simpler since developers already have keys but lacks built-in expiry. Describe the full enforcement chain: signing at the developer's machine, verification badge on GitHub, branch protection rules rejecting unsigned pushes, CI pipeline checking signatures against a trusted keyring, and signed release tags. Connect this to supply chain security frameworks like SLSA and real attacks like SolarWinds. Mentioning Sigstore or Gitsign for keyless signing shows cutting-edge awareness.