admin: document instance signing

- Rewrite the instance commit signing documentation from scratch.
- Includes how to configure SSH signing.
- Includes section that https://github.com/Foxboron/ssh-tpm-agent could
be used for secure SSH signing.
- Ref: forgejo/forgejo#6897
This commit is contained in:
Gusted 2025-03-31 23:08:59 +02:00 committed by Gusted
parent 075d17a76b
commit 0df093be8a
3 changed files with 163 additions and 123 deletions

View file

@ -149,7 +149,8 @@ In addition, there is _`StaticRootPath`_ which can be set as a built-in at build
### Repository - Signing (`repository.signing`)
- `SIGNING_KEY`: **default**: \[none, KEYID, default \]: Key to sign with.
- `FORMAT`: **openpgp**: \[openpgp, ssh\]: Signing format that Forgejo should use, openpgp uses GPG and ssh uses OpenSSH.
- `SIGNING_KEY`: **default**: \[none, KEYID, default, path/to/ssh/key\]: Key to sign with. If `FORMAT` is set to **ssh** this should be set to an absolute path to an public OpenSSH key.
- `SIGNING_NAME` & `SIGNING_EMAIL`: if a KEYID is provided as the `SIGNING_KEY`, use these as the Name and Email address of the signer. These should match publicized name and email address for the key.
- `INITIAL_COMMIT`: **always**: \[never, pubkey, twofa, always\]: Sign initial commit.
- `never`: Never sign

View file

@ -23,7 +23,7 @@ These documents are targeted to people who run Forgejo on their machines.
- [Forgejo CLI](./command-line/)
- [Search Engines and robots.txt](./search-engines-indexation/)
- [Recommended Settings and Tips](./recommendations/)
- [GPG Commit Signatures](./signing/)
- [Instance Commit Signing](./signing/)
- [Moderation tools](./moderation/)
- [Adopt existing git directories](./adopt/)
- [Interface customization](./customization/)

View file

@ -1,144 +1,183 @@
---
title: 'GPG Commit Signatures'
license: 'Apache-2.0'
origin_url: 'https://github.com/go-gitea/gitea/blob/e865de1e9d65dc09797d165a51c8e705d2a86030/docs/content/administration/signing.en-us.md'
title: Instance Commit Signing
license: 'CC-BY-SA-4.0'
---
Forgejo will verify GPG commit signatures in the provided tree by
checking if the commits are signed by a key within the Forgejo database,
or if the commit matches the default key for Git.
Forgejo has the ability to sign commits when Forgejo themselves generates those commits, such as:
Keys are not checked to determine if they have expired or revoked.
Keys are also not checked with keyservers.
- Repository initialisation
- Wiki changes
- CRUD actions using the web editor or the API
- Merges from pull requests
A commit will be marked with an unlocked icon if no key can be
found to verify it.
## Configuration
## Automatic Signing
In order for Forgejo to sign commits, it has to know how it should be signing commits and when to sign commits.
Unless otherwise indicated, all configuration settings discussed on this page are for the `[repository.signing]` section.
There are a number of places where Forgejo will generate commits itself:
### Signing key
- Repository Initialisation
- Wiki Changes
- CRUD actions using the editor or the API
- Merges from Pull Requests
Forgejo offers two formats to sign commits with: GPG and SSH.
If you meet the requirements of SSH, then it is strongly preferred to use that instead of GPG.
## Installing and generating a GPG key for Forgejo
#### SSH
Forgejo generates all its commits using the server `git`
command - and the `gpg` command will be used for
signing.
For Forgejo to do SSH commit signing, it needs a Git version equal to or newer than 2.34.0 and `ssh-keygen` binary equal to or newer than version 8.2p1.[^1]
## General Configuration
[^1]: The git version check is already done by Forgejo, but for `ssh-keygen` only the presence of the binary is checked.
Forgejo's configuration for signing can be found with the
`[repository.signing]` section of `app.ini`:
You need a dedicated OpenSSH key pair for instance signing.
If you don't have such key pair yet you can generate one via `ssh-keygen`[^2] or you also could store the SSH key in TPM, there is [a dedicated section](#using-ssh-tpm-agent) with instructions on how to do that.
[^2]: https://docs.codeberg.org/security/ssh-key/ contains instructions for generating an SSH key pair, you should not generate a FIDO2 (`-sk` type) key pair as that will not work with Forgejo.
Forgejo needs to be told that it should use SSH signing and which SSH key to use, this should be configured as followed:
```ini
FORMAT = ssh
SIGNING_KEY = /absolute/path/to/public/ssh/key.pub
```
The value for the `SIGNING_KEY` setting needs to be an absolute path to the public key, where the private key needs to be available in the path without the `.pub` suffix.
Forgejo also needs to be told who the committer of the commit is, which requires a name and email and should be configured as followed:
```ini
SIGNING_NAME = "forgejo.org Instance"
SIGNING_EMAIL = "noreply@forgejo.org"
```
#### GPG
There are two ways to tell Forgejo which GPG key should be used for commit signing.
```ini
...
[repository.signing]
SIGNING_KEY = default
SIGNING_NAME =
SIGNING_EMAIL =
INITIAL_COMMIT = always
CRUD_ACTIONS = pubkey, twofa, parentsigned
WIKI = never
MERGES = pubkey, twofa, basesigned, commitssigned
...
```
### `SIGNING_KEY`
Will use the git config to determine the signing key: if the value of `commit.gpgsign` is set to true, then it will use the values of `user.signingkey`, `user.name` and `user.email` for the signing key, committer name and committer email respectively.
There are three main options:
---
- `none` - this prevents Forgejo from signing any commits
- `default` - Forgejo will default to the key configured within `git config`
- `KEYID` - Forgejo will sign commits with the GPG key with the ID
`KEYID`. In this case you should provide a `SIGNING_NAME` and
`SIGNING_EMAIL` to be displayed for this key.
The `default` option will interrogate `git config` for
`commit.gpgsign` option - if this is set, then it will use the results
of the `user.signingkey`, `user.name` and `user.email`.
By default, Forgejo will look for the signing key in `[git].HOME_PATH/.gnupg`.
However, this path differs from where GnuPG stores keys by default (`$HOME/.gnupg`).
There are 2 possible solutions here:
1. Move the `.gnupg` folder after importing/generating keys;
2. Set the `GNUPGHOME` environment variable to help Forgejo find the correct keychain.
### `INITIAL_COMMIT`
This option determines whether Forgejo should sign the initial commit
when creating a repository. The possible values are:
- `never`: Never sign
- `pubkey`: Only sign if the user has a public key
- `twofa`: Only sign if the user logs in with two factor authentication
- `always`: Always sign
Options other than `never` and `always` can be combined as a comma
separated list. The commit will be signed if all selected options are true.
### `WIKI`
This options determines if Forgejo should sign commits to the Wiki.
The possible values are:
- `never`: Never sign
- `pubkey`: Only sign if the user has a public key
- `twofa`: Only sign if the user logs in with two-factor authentication
- `parentsigned`: Only sign if the parent commit is signed.
- `always`: Always sign
Options other than `never` and `always` can be combined as a comma
separated list. The commit will be signed if all selected options are true.
### `CRUD_ACTIONS`
This option determines if Forgejo should sign commits from the web
editor or API CRUD actions. The possible values are:
- `never`: Never sign
- `pubkey`: Only sign if the user has a public key
- `twofa`: Only sign if the user logs in with two-factor authentication
- `parentsigned`: Only sign if the parent commit is signed.
- `always`: Always sign
Options other than `never` and `always` can be combined as a comma
separated list. The change will be signed if all selected options are true.
### `MERGES`
This option determines if Forgejo should sign merge commits from PRs.
The possible options are:
- `never`: Never sign
- `pubkey`: Only sign if the user has a public key
- `twofa`: Only sign if the user logs in with two-factor authentication
- `basesigned`: Only sign if the parent commit in the base repository is signed.
- `headsigned`: Only sign if the head commit in the head branch is signed.
- `commitssigned`: Only sign if all the commits in the head branch to the merge point are signed.
- `approved`: Only sign approved merges to a protected branch.
- `always`: Always sign
Options other than `never` and `always` can be combined as a comma
separated list. The merge will be signed if all selected options are true.
## Obtaining the Public Key of the Signing Key
The public key used to sign Forgejo's commits can be obtained from the API at:
```sh
/api/v1/signing-key.gpg
```ini
SIGNING_KEY = GPG-KEY-ID
SIGNING_NAME = "forgejo.org Instance"
SIGNING_EMAIL = "noreply@forgejo.org"
```
In cases where there is a repository specific key this can be obtained from:
Will use the GPG keyid to search for the key in the GPG keyring. Forgejo searches for this key in a directory, which can be computed as follows: If a `GNUPGHOME` environment variable is set, this is used.
Otherwise the `.gnupg` directory in the directory corresponding to the value of the `HOME_PATH` setting in the `[git]` section is used (`[git].HOME_PATH/.gnupg` so to say).
It should be noted that by default, GPG does not use that keyring and you should take extra care when importing or generating the key, for example by setting the value of the `GNUPGHOME` environment to the directory Forgejo uses.
```sh
/api/v1/repos/:username/:reponame/signing-key.gpg
### Signing operations
There are several operations for which Forgejo will generate a commit and thus be able to sign the commit.
For each operation you can specify under which conditions Forgejo should sign the commit.
For each operation, you can combine the values as a comma-separated list.
There are two special values that are valid values for each operation and cannot be combined with any other value for that operation: `always` and `never`.
The first value, if set, will always sign the commit and the second value, if set, will never sign the commit.
#### Initial commit
When should Forgejo sign the initial commit when creating a repository.
The possible values for the `INITIAL_COMMIT` setting are:
- `pubkey`: Only if the user has added a GPG key to its account.
- `twofa`: Only if the user is enrolled into two-factor authentication.
#### Wiki
When should Forgejo sign commits to the wiki.
The possible values for the `WIKI` setting are:
- `pubkey`: Only if the user has added a GPG key to its account.
- `twofa`: Only if the user is enrolled into two-factor authentication.
- `parentsigned`: Only if the parent commit is signed.
#### CRUD actions
When should Forgejo sign commits that are created for file changes via the web editor or API.
The possible values for the `CRUD_ACTIONS` setting are:
- `pubkey`: Only if the user has added a GPG key to its account.
- `twofa`: Only if the user is enrolled into two-factor authentication.
- `parentsigned`: Only if the parent commit is signed.
#### Pull request merges
When should Forgejo sign merge commits from pull requests.
The possible values for the `MERGES` setting are:
- `pubkey`: Only if the user has added a GPG key to its account.
- `twofa`: Only if the user is enrolled into two-factor authentication.
- `basesigned`: Only if the parent commit in the base repository is signed.
- `headsigned`: Only if the head commit in the head branch is signed.
- `commitssigned`: Only if all the commits in the head branch to the merge point are signed.
- `approved`: Only if the pull request targets a protected branch and has at least one approval.
## Obtaining the instance signing key
If a GPG instance signing key is set, the GPG public key can be obtained at the API route, `/api/v1/signing-key.gpg`.
If a repository specific GPG key is set, it can be obtained at the API route, `/api/v1/repos/{username}/{reponame}/signing-key.gpg`
If a SSH instance signing key is set, the SSH public key can be obtained at the API route, `/api/v1/signing-key.ssh`.
## Using ssh-tpm-agent
It is possible to use [ssh-tpm-agent](https://github.com/Foxboron/ssh-tpm-agent) so that the SSH private key resides in a [Trusted Platform Module (TPM)](https://en.wikipedia.org/wiki/Trusted_Platform_Module) and therefore makes it harder to leak the SSH private key as it does not reside on the filesystem. To use this, the server that Forgejo runs on must have access to TPM 2.0.
This section only explains how to make the SSH private key available to Forgejo, not how to configure Forgejo to use it.
Follow [the instruction from ssh-tpm-agent](https://github.com/Foxboron/ssh-tpm-agent#usage) to create a key or import an existing key.
An instance key is expected to be a long-lived key[^3] and therefore it is advisable to follow the 'Import existing key' guide as it allows you to backup the private key in a safe place and in case of a recovery, restore the instance SSH key.
[^3]: Rotating instance keys is currently not possible.
ssh-tpm-agent acts as an [`ssh-agent(1)`](https://man.archlinux.org/man/ssh-agent.1) and in order for Forgejo to use ssh-tpm-agent to sign commits with, it needs to have a `SSH_AUTH_SOCK` environment set when launching the Forgejo binary.
How to pass this to Forgejo depends on how you run Forgejo, we consider two situation: a Systemd service on bare-metal or containerized (for example, via Docker).
In either case, the host will need to install the systemd unit service by running `ssh-tpm-agent --install-user-units`.
### Systemd service
In the `[Service]` section, add the following (it is fine to have multiple `Environment` keys):
```toml
Environment=SSH_AUTH_SOCK="/socket/path"
```
Where `/socket/path` is replaced with the value of `ssh-tpm-agent --print-socket`.
### Containerized
We take [the default docker-compose file](../installation-docker/#docker) as an example.
We add an environment variable and a volume mount to the compose file:
```yaml
networks:
forgejo:
external: false
services:
server:
image: codeberg.org/forgejo/forgejo:10
container_name: forgejo
environment:
- USER_UID=1000
- USER_GID=1000
+ - SSH_AUTH_SOCK=$SOCKET_PATH
restart: always
networks:
- forgejo
volumes:
- ./forgejo:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
+ - $SOCKET_PATH:$SOCKET_PATH
ports:
- "3000:3000"
- "222:22"
```
Where `$SOCKET_PATH` is to be replaced with the value of `ssh-tpm-agent --print-socket`.
Another volume would need to be added that exposes the public OpenSSH key, the container path should match with the path that is specified for the `SIGNING_KEY` setting.