Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631)

This PR fixes #7598 by providing a configurable way of signing commits across the Gitea instance. Per repository configurability and import/generation of trusted secure keys is not provided by this PR - from a security PoV that's probably impossible to do properly. Similarly web-signing, that is asking the user to sign something, is not implemented - this could be done at a later stage however.

## Features
- [x] If commit.gpgsign is set in .gitconfig sign commits and files created through repofiles. (merges should already have been signed.)
- [x] Verify commits signed with the default gpg as valid
- [x] Signer, Committer and Author can all be different
    - [x] Allow signer to be arbitrarily different - We still require the key to have an activated email on Gitea. A more complete implementation would be to use a keyserver and mark external-or-unactivated with an "unknown" trust level icon.
- [x] Add a signing-key.gpg endpoint to get the default gpg pub key if available
    - Rather than add a fake web-flow user I've added this as an endpoint on /api/v1/signing-key.gpg
    - [x] Try to match the default key with a user on gitea - this is done at verification time
- [x] Make things configurable?
    - app.ini configuration done
    - [x] when checking commits are signed need to check if they're actually verifiable too
- [x] Add documentation

I have decided that adjusting the docker to create a default gpg key is not the correct thing to do and therefore have not implemented this.
This commit is contained in:
zeripath 2019-10-16 14:42:42 +01:00 committed by GitHub
parent 1b72690cb8
commit fcb535c5c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1630 additions and 124 deletions

View file

@ -13,6 +13,7 @@ import (
"os"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/cache"
@ -28,6 +29,11 @@ import (
// Merge merges pull request to base repository.
// FIXME: add repoWorkingPull make sure two merges does not happen at same time.
func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repository, mergeStyle models.MergeStyle, message string) (err error) {
binVersion, err := git.BinVersion()
if err != nil {
return fmt.Errorf("Unable to get git version: %v", err)
}
if err = pr.GetHeadRepo(); err != nil {
return fmt.Errorf("GetHeadRepo: %v", err)
} else if err = pr.GetBaseRepo(); err != nil {
@ -176,6 +182,30 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
return fmt.Errorf("git read-tree HEAD: %s", errbuf.String())
}
// Determine if we should sign
signArg := ""
if version.Compare(binVersion, "1.7.9", ">=") {
sign, keyID := pr.BaseRepo.SignMerge(doer, tmpBasePath, "HEAD", trackingBranch)
if sign {
signArg = "-S" + keyID
} else if version.Compare(binVersion, "2.0.0", ">=") {
signArg = "--no-gpg-sign"
}
}
sig := doer.NewGitSig()
commitTimeStr := time.Now().Format(time.RFC3339)
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+sig.Name,
"GIT_AUTHOR_EMAIL="+sig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+sig.Name,
"GIT_COMMITTER_EMAIL="+sig.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
// Merge commits.
switch mergeStyle {
case models.MergeStyleMerge:
@ -183,9 +213,14 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, errbuf.String())
}
sig := doer.NewGitSig()
if err := git.NewCommand("commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, errbuf.String())
if signArg == "" {
if err := git.NewCommand("commit", "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, errbuf.String())
}
} else {
if err := git.NewCommand("commit", signArg, "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, errbuf.String())
}
}
case models.MergeStyleRebase:
// Checkout head branch
@ -223,9 +258,14 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
}
// Set custom message and author and create merge commit
sig := doer.NewGitSig()
if err := git.NewCommand("commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, errbuf.String())
if signArg == "" {
if err := git.NewCommand("commit", "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, errbuf.String())
}
} else {
if err := git.NewCommand("commit", signArg, "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, errbuf.String())
}
}
case models.MergeStyleSquash:
@ -234,8 +274,14 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
return fmt.Errorf("git merge --squash [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String())
}
sig := pr.Issue.Poster.NewGitSig()
if err := git.NewCommand("commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, errbuf.String())
if signArg == "" {
if err := git.NewCommand("commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, errbuf.String())
}
} else {
if err := git.NewCommand("commit", signArg, fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, errbuf.String())
}
}
default:
return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle}
@ -270,7 +316,7 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
headUser = doer
}
env := models.FullPushingEnvironment(
env = models.FullPushingEnvironment(
headUser,
doer,
pr.BaseRepo,