mirror of
https://github.com/go-gitea/gitea
synced 2025-07-23 10:48:37 +00:00
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:
@@ -507,6 +507,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
m.Get("/swagger", misc.Swagger)
|
||||
}
|
||||
m.Get("/version", misc.Version)
|
||||
m.Get("/signing-key.gpg", misc.SigningKey)
|
||||
m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
|
||||
m.Post("/markdown/raw", misc.MarkdownRaw)
|
||||
|
||||
@@ -771,6 +772,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
m.Delete("", bind(api.DeleteFileOptions{}), repo.DeleteFile)
|
||||
}, reqRepoWriter(models.UnitTypeCode), reqToken())
|
||||
}, reqRepoReader(models.UnitTypeCode))
|
||||
m.Get("/signing-key.gpg", misc.SigningKey)
|
||||
m.Group("/topics", func() {
|
||||
m.Combo("").Get(repo.ListTopics).
|
||||
Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
|
||||
|
@@ -12,6 +12,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@@ -84,17 +85,21 @@ func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
|
||||
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
|
||||
func ToVerification(c *git.Commit) *api.PayloadCommitVerification {
|
||||
verif := models.ParseCommitWithSignature(c)
|
||||
var signature, payload string
|
||||
commitVerification := &api.PayloadCommitVerification{
|
||||
Verified: verif.Verified,
|
||||
Reason: verif.Reason,
|
||||
}
|
||||
if c.Signature != nil {
|
||||
signature = c.Signature.Signature
|
||||
payload = c.Signature.Payload
|
||||
commitVerification.Signature = c.Signature.Signature
|
||||
commitVerification.Payload = c.Signature.Payload
|
||||
}
|
||||
return &api.PayloadCommitVerification{
|
||||
Verified: verif.Verified,
|
||||
Reason: verif.Reason,
|
||||
Signature: signature,
|
||||
Payload: payload,
|
||||
if verif.SigningUser != nil {
|
||||
commitVerification.Signer = &structs.PayloadUser{
|
||||
Name: verif.SigningUser.Name,
|
||||
Email: verif.SigningUser.Email,
|
||||
}
|
||||
}
|
||||
return commitVerification
|
||||
}
|
||||
|
||||
// ToPublicKey convert models.PublicKey to api.PublicKey
|
||||
|
62
routers/api/v1/misc/signing.go
Normal file
62
routers/api/v1/misc/signing.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
// SigningKey returns the public key of the default signing key if it exists
|
||||
func SigningKey(ctx *context.Context) {
|
||||
// swagger:operation GET /signing-key.gpg miscellaneous getSigningKey
|
||||
// ---
|
||||
// summary: Get default signing-key.gpg
|
||||
// produces:
|
||||
// - text/plain
|
||||
// responses:
|
||||
// "200":
|
||||
// description: "GPG armored public key"
|
||||
// schema:
|
||||
// type: string
|
||||
|
||||
// swagger:operation GET /repos/{owner}/{repo}/signing-key.gpg repository repoSigningKey
|
||||
// ---
|
||||
// summary: Get signing-key.gpg for given repository
|
||||
// produces:
|
||||
// - text/plain
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// description: "GPG armored public key"
|
||||
// schema:
|
||||
// type: string
|
||||
|
||||
path := ""
|
||||
if ctx.Repo != nil && ctx.Repo.Repository != nil {
|
||||
path = ctx.Repo.Repository.RepoPath()
|
||||
}
|
||||
|
||||
content, err := models.PublicSigningKey(path)
|
||||
if err != nil {
|
||||
ctx.ServerError("gpg export", err)
|
||||
return
|
||||
}
|
||||
_, err = ctx.Write([]byte(content))
|
||||
if err != nil {
|
||||
log.Error("Error writing key content %v", err)
|
||||
ctx.Error(http.StatusInternalServerError, fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user