-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Improve instance wide ssh commit signing #34341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Improve instance wide ssh commit signing #34341
Conversation
* Signed SSH commits can look like on GitHub * No user account of the committer needed * SSH format can be added in gitea config * No gitconfig changes needed * Set gpg.format git key for signing command * Previously only the default gpg key had global trust in Gitea * SSH Signing worked before with DEFAULT_TRUST_MODEL=committer, but not with model default and manually changing the .gitconfig e.g. the following is all needed ``` [repository.signing] SIGNING_KEY = /data/id_ed25519.pub SIGNING_NAME = Gitea SIGNING_EMAIL = [email protected] SIGNING_FORMAT = ssh INITIAL_COMMIT = always CRUD_ACTIONS = always WIKI = always MERGES = always ``` `TRUSTED_SSH_KEYS` can be a list of additional ssh public keys to trust for every user of this instance
What do you think @brtwrst about this? Except of an absent automatic setup this should now be even easier, by just editing a single file. I found out that gpg supported global key verification for all users, but ssh not, this PR aims to change that. No I have no idea how to write tests for this |
That looks awesome. Makes it super simple to set up and the |
I tested this like this Since the ssh keys are so simple idk if a double quote are even needed / supported. File paths are not supported in this PR for this list. |
Ok, can't wait for this to make it in :) Thank you for your work. |
app.example.ini needs to be updated. |
Co-authored-by: Lunny Xiao <[email protected]>
…stopherHX/34341
…m/christopherhx/gitea into pr/ChristopherHX/34341
Co-authored-by: Lunny Xiao <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
services/pull/merge_prepare.go
Outdated
@@ -27,7 +27,7 @@ type mergeContext struct { | |||
doer *user_model.User | |||
sig *git.Signature | |||
committer *git.Signature | |||
signKeyID string // empty for no-sign, non-empty to sign | |||
signKey git.SigningKey // empty for no-sign, non-empty to sign |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment needs to be updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't want to risk anything here by using pointers to a struct (e.g. allow nil), so technically the comment was still correct.
The empty git.SigningKey struct <==> git.SigningKey.KeyID is an empty string <==> do not sign
services/pull/merge_prepare.go
Outdated
@@ -101,7 +101,7 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque | |||
// Determine if we should sign | |||
sign, keyID, signer, _ := asymkey_service.SignMerge(ctx, mergeCtx.pr, mergeCtx.doer, mergeCtx.tmpBasePath, "HEAD", trackingBranch) | |||
if sign { | |||
mergeCtx.signKeyID = keyID | |||
mergeCtx.signKey = keyID |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the variable name keyID
should be updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test could be an integration one. Change the settings dynamically and push a commit and verify the UI badge. |
Seems like using the integration test gpg_git_test is a start, the old test doesn't seem to test using defaults coming from gitconfig. Had to change how I verify those ssh commits to have the expected signer user/email.
|
Correct me if I'm wrong but doesn't this depend on ssh-keygen if git is signing commits? |
Yes this seems to be correct https://github.com/git/git/blob/7014b55638da979331baf8dc31c4e1d697cf2d67/gpg-interface.c#L1085 included in openssh version 8.2p1+ On my device is ssh-keygen a readonly system component (which I do not know how to hide from git) and the gitea docker container has it preinstalled as well. Maybe include this in the example ini explicitly as requirement? We might be able to consider adding this to the initial admin setup page to autogenerate the ssh key pair like in my test case or allow to configure this directly in the webui setup. I assume once Gitea is installed the config file should no longer be changed by Gitea |
I'm asking because I remember #33783, which comes from rootless container image not having ssh-keygen. I'll submit a PR for that sometime today then since it's a indirect requirement for this. |
I was not aware that the rootless image does not have it.
Gitea does not use git for validating commits, only signing. I just asked ai for a replacement for ssh-keygen to sign If this code could be added to gitea cli, we might not even need ssh-keygen tool if we tell git to use gitea for signing. Not tested at all xd, but not so much top of my mind. Commit signing stub as ssh-keygen replacementpackage main
import (
"crypto/rand"
"fmt"
"io"
"os"
"strings"
"golang.org/x/crypto/ssh"
)
func main() {
if len(os.Args) < 7 || os.Args[1] != "-Y" || os.Args[2] != "sign" || os.Args[3] != "-f" || os.Args[5] != "-n" {
fmt.Println("Usage: go run main.go -Y sign -f <private-key-file> -n <namespace> [file-to-sign]")
os.Exit(1)
}
keyFile := os.Args[4] // Private key file
namespace := os.Args[6] // Namespace (e.g., "git")
// Read private key file
keyData, err := os.ReadFile(keyFile)
if err != nil {
fmt.Println("Failed to read private key file:", err)
os.Exit(1)
}
// Parse private key
signer, err := ssh.ParsePrivateKey(keyData)
if err != nil {
fmt.Println("Failed to parse private key:", err)
os.Exit(1)
}
var message []byte
var sigFileName string
if len(os.Args) > 7 {
// Read from file if provided
fileToSign := os.Args[7]
message, err = os.ReadFile(fileToSign)
if err != nil {
fmt.Println("Failed to read file:", err)
os.Exit(1)
}
// Generate signature filename
sigFileName = fileToSign + ".sig"
} else {
fmt.Println("Signing data on standard input")
// Read from stdin
message, err = io.ReadAll(os.Stdin)
if err != nil {
fmt.Println("Failed to read stdin:", err)
os.Exit(1)
}
}
// Incorporate the namespace into the signed message
signedData := append([]byte(namespace+"\n"), message...)
// Sign the message
signature, err := signer.Sign(rand.Reader, signedData)
if err != nil {
fmt.Println("Failed to sign message:", err)
os.Exit(1)
}
sshSignature := fmt.Sprintf(
"-----BEGIN SSH SIGNATURE-----\n%s\n-----END SSH SIGNATURE-----\n",
strings.ToUpper(fmt.Sprintf("%x", signature.Blob)),
)
if sigFileName != "" {
// Save signature to file
err = os.WriteFile(sigFileName, []byte(sshSignature), 0644)
if err != nil {
fmt.Println("Failed to save signature file:", err)
os.Exit(1)
}
fmt.Println("Signature saved to", sigFileName)
} else {
// Print signature to stdout
fmt.Println(sshSignature)
}
} |
e.g. the following is all needed after ssh-keygen, no trouble with installing and setting up gpg or hacking around a hidden .gitconfig for ssh key usage
Where /data/id_ed25519 is the private key.
TRUSTED_SSH_KEYS
can be a list of additional ssh public key contents to trust for every user of this instanceCloses #34329
Related #31392