Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ does not publish its own `CONTRIBUTING.md`.
4. Add or update tests for the behavior you change, and make sure CI is green.
5. Fill in the pull request template, including the version bump (`#major`, `#minor`, `#patch` or `#none`).
6. Do not commit secrets, credentials or internal hostnames. Secret scanning blocks pushes that contain them.
7. Sign your commits. Protected branches require verified signatures: see
[Signing commits with an SSH key](https://github.com/aziontech/.github/blob/main/docs/commit-signing.md).

Pull requests are merged after review by the code owners of the changed files.

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ A file in a repository always takes precedence over the default here.
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Branches, Conventional Commits, pull requests, versioning |
| [`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE/) | Bug report and feature request forms; blank issues disabled |
| [`.github/pull_request_template.md`](.github/pull_request_template.md) | Pull request description |
| [`docs/commit-signing.md`](docs/commit-signing.md) | Signing commits with an SSH key (not inherited; linked from `CONTRIBUTING.md`) |

`CODEOWNERS` is **not** inherited by other repositories: each repository keeps its own. The `CODEOWNERS` here only
covers this repository.
Expand Down
151 changes: 151 additions & 0 deletions docs/commit-signing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Signing commits with an SSH key

The `aziontech` organization requires signed commits on protected branches. Once the rule is enforced, a push or
merge that carries a commit without a verified signature is rejected. The rule is announced before it is enforced.

Setup takes about five minutes and can reuse the SSH key you already push with.

- Commits made in the GitHub web interface (web edits, squash merges) are signed by GitHub. They need nothing.
- SSH signing needs git 2.34 or later (`git --version`).

## 1. Have an SSH key

The key you already push with works. To check whether one exists:

```bash
ls ~/.ssh/id_ed25519.pub ~/.ssh/id_rsa.pub 2>/dev/null
```

If a file is listed, go to step 2. Otherwise, create an ed25519 key with a passphrase:

```bash
ssh-keygen -t ed25519 -C "you@azion.com"
```

Accept the default path (`~/.ssh/id_ed25519`) and set a passphrase. On macOS, store the passphrase in the Keychain so
you are not asked for it on every commit:

```bash
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
```

To keep a separate key for signing only, create one with `-f ~/.ssh/id_ed25519_signing` and use that path in the
steps below.

## 2. Configure git

Run once per machine:

```bash
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
```

Check that git's email is a verified email on your GitHub account. Without that, the commit shows as Unverified even
with a valid signature.

```bash
git config --global user.email # must be an email verified on your GitHub account
```

Optional, so that `git log --show-signature` can verify your signatures locally:

```bash
echo "$(git config --global user.email) $(cat ~/.ssh/id_ed25519.pub)" >> ~/.ssh/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers
```

## 3. Add the key to GitHub as a Signing Key

This is the step most often missed. The key you push with is registered as an Authentication Key, which does not count
for signatures. The same key has to be added again, as a Signing Key.

1. Copy the public key: `pbcopy < ~/.ssh/id_ed25519.pub` (on Linux, `cat` it and copy).
2. Open [github.com/settings/ssh/new](https://github.com/settings/ssh/new).
3. Under **Key type**, choose **Signing Key**.
4. Give it a title (for example `MacBook – signing`), paste the key and save.

The same with the GitHub CLI:

```bash
gh ssh-key add ~/.ssh/id_ed25519.pub --type signing --title "MacBook - signing"
```

If `gh` asks for permission, run `gh auth refresh -s admin:ssh_signing_key` first.

## 4. Verify

Make a commit and check it locally:

```bash
git commit --allow-empty -m "test: signed commit"
git log --show-signature -1
```

With `allowed_signers` configured, the output shows `Good "git" signature for you@azion.com`. Without it, it shows
`gpg.ssh.allowedSignersFile needs to be configured`, and the commit is signed all the same.

After pushing to a test branch, the commit shows the **Verified** badge in the pull request or the repository's commit
list. From the command line:

```bash
gh api repos/aziontech/<repo>/commits/<sha> --jq '.commit.verification | {verified, reason}'
```

Expect `verified: true` and `reason: valid`. Remember to drop the test commit.

## Special cases

**Several machines.** Repeat steps 2 and 3 on each machine, with one Signing Key per machine. An account can hold as
many as needed.

**Coder, devcontainers and remote SSH.** On the remote machine, let git sign through the forwarded ssh-agent instead of
copying the private key there. On the remote machine, run:

```bash
git config --global gpg.format ssh
git config --global user.signingkey "key::$(ssh-add -L | head -1)"
git config --global commit.gpgsign true
```

This needs a forwarded agent (`ssh -A`, or `ForwardAgent yes` in `~/.ssh/config`). VS Code Remote and devcontainers
forward it on their own.

**1Password.** If the key lives in 1Password, use its signing program:

```bash
git config --global gpg.ssh.program "/Applications/1Password.app/Contents/MacOS/op-ssh-sign"
```

The rest is the same, with `user.signingkey` set to the public key copied from 1Password.

**IDEs.** VS Code, JetBrains IDEs and GitHub Desktop read git's global configuration. Nothing to set in them.

**Unsigned commits already on your branch.** Sign them again and force-push. Only on your own branches, never on
shared ones.

```bash
git rebase --exec 'git commit --amend --no-edit -S' origin/<default-branch>
git push --force-with-lease
```

**Automations that commit.** CI jobs that run `git commit` and `git push` (version bumps, changelogs, generated code)
are rejected on protected branches. Either commit through the GitHub API with `GITHUB_TOKEN`, which GitHub signs, or
give the bot a signing key of its own.

## Common problems

The `reason` field from the verification command (step 4) usually names the cause.

| Symptom | Cause | Fix |
|---|---|---|
| Unverified, `reason: unknown_key` | Key registered only as an Authentication Key, or not at all | Add it as a Signing Key (step 3) |
| Unverified, `reason: unverified_email` or `bad_email` | git's `user.email` is not a verified email on the account | Fix `user.email`, or verify the email on GitHub |
| Commit with no badge at all | `commit.gpgsign` is off in that repository or on that machine | Run step 2; check with `git config commit.gpgsign` |
| `unsupported value for gpg.format: ssh` | git older than 2.34 | Upgrade git (`brew upgrade git`) |
| `agent refused operation`, or a passphrase prompt on every commit | Key not loaded in the ssh-agent | `ssh-add --apple-use-keychain ~/.ssh/id_ed25519` |
| Push rejected with `Commits must have verified signatures` | An unsigned commit on the branch | Sign the commits again (Special cases) |

Azion engineers with questions not answered here: ask `@aziontech/team-delivery-engineering`.
Loading