Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ba15ed9
Add require signed commit for protected branch
zeripath Jan 11, 2020
f482e28
Fix fmt
zeripath Jan 11, 2020
43c5df5
Merge branch 'master' into protect-branch-signed-commits-only
zeripath Jan 11, 2020
f9f8557
Make editor show if they will be signed
zeripath Jan 11, 2020
27c0354
Merge branch 'protect-branch-signed-commits-only' of github.com:zerip…
zeripath Jan 11, 2020
f2a28e8
bugfix
zeripath Jan 11, 2020
9a7ec7f
Merge branch 'master' into protect-branch-signed-commits-only
zeripath Jan 11, 2020
e4d987a
Merge branch 'master' into protect-branch-signed-commits-only
zeripath Jan 12, 2020
594126a
Add basic merge check and better information for CRUD
zeripath Jan 12, 2020
dd05f6a
linting comment
zeripath Jan 12, 2020
de67865
Add descriptors to merge signing
zeripath Jan 12, 2020
97b811d
Slight refactor
zeripath Jan 12, 2020
ea21e79
Merge branch 'master' into protect-branch-signed-commits-only
zeripath Jan 12, 2020
68a8e11
Slight improvement to appearances
zeripath Jan 12, 2020
acf011a
Handle Merge API
zeripath Jan 12, 2020
24eb4a2
manage CRUD API
zeripath Jan 12, 2020
bd8d89f
Move error to error.go
zeripath Jan 13, 2020
bfb7df0
Remove fix to delete.go
zeripath Jan 13, 2020
50cf492
prep for merge
zeripath Jan 13, 2020
6793e8d
Merge branch 'master' into protect-branch-signed-commits-only
zeripath Jan 13, 2020
7a04a47
need to tolerate \r\n in message
zeripath Jan 14, 2020
161d110
check protected branch before trying to load it
zeripath Jan 14, 2020
335ec50
Apply suggestions from code review
zeripath Jan 14, 2020
5ad5a56
Merge branch 'master' into protect-branch-signed-commits-only
zeripath Jan 14, 2020
ea40bd6
fix commit-reader
zeripath Jan 14, 2020
d554a62
Merge branch 'master' into protect-branch-signed-commits-only
zeripath Jan 14, 2020
2d454cd
Merge branch 'master' into protect-branch-signed-commits-only
zeripath Jan 14, 2020
788ed2f
Merge branch 'master' into protect-branch-signed-commits-only
zeripath Jan 15, 2020
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
1 change: 1 addition & 0 deletions models/branches.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type ProtectedBranch struct {
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`

CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ var migrations = []Migration{
NewMigration("Fix migrated repositories' git service type", fixMigratedRepositoryServiceType),
// v120 -> v121
NewMigration("Add owner_name on table repository", addOwnerNameOnRepository),
// v121 -> v122
NewMigration("Add Require Signed Commits to ProtectedBranch", addRequireSignedCommits),
}

// Migrate database to current version
Expand Down
18 changes: 18 additions & 0 deletions models/migrations/v121.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

import (
"xorm.io/xorm"
)

func addRequireSignedCommits(x *xorm.Engine) error {

type ProtectedBranch struct {
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
}

return x.Sync2(new(ProtectedBranch))
}
59 changes: 34 additions & 25 deletions models/pull_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import (
)

// SignMerge determines if we should sign a PR merge commit to the base repository
func (pr *PullRequest) SignMerge(u *User, tmpBasePath, baseCommit, headCommit string) (bool, string) {
func (pr *PullRequest) SignMerge(u *User, tmpBasePath, baseCommit, headCommit string) (bool, string, error) {
if err := pr.GetBaseRepo(); err != nil {
log.Error("Unable to get Base Repo for pull request")
return false, ""
return false, "", err
}
repo := pr.BaseRepo

signingKey := signingKey(repo.RepoPath())
if signingKey == "" {
return false, ""
return false, "", &ErrWontSign{noKey}
}
rules := signingModeFromStrings(setting.Repository.Signing.Merges)

Expand All @@ -30,92 +30,101 @@ func (pr *PullRequest) SignMerge(u *User, tmpBasePath, baseCommit, headCommit st
for _, rule := range rules {
switch rule {
case never:
return false, ""
return false, "", &ErrWontSign{never}
case always:
break
case pubkey:
keys, err := ListGPGKeys(u.ID)
if err != nil || len(keys) == 0 {
return false, ""
if err != nil {
return false, "", err
}
if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
}
case twofa:
twofa, err := GetTwoFactorByUID(u.ID)
if err != nil || twofa == nil {
return false, ""
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil {
return false, "", err
}
if twofaModel == nil {
return false, "", &ErrWontSign{twofa}
}
case approved:
protectedBranch, err := GetProtectedBranchBy(repo.ID, pr.BaseBranch)
if err != nil || protectedBranch == nil {
return false, ""
if err != nil {
return false, "", err
}
if protectedBranch == nil {
return false, "", &ErrWontSign{approved}
}
if protectedBranch.GetGrantedApprovalsCount(pr) < 1 {
return false, ""
return false, "", &ErrWontSign{approved}
}
case baseSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil {
return false, ""
return false, "", err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(baseCommit)
if err != nil {
return false, ""
return false, "", err
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
return false, ""
return false, "", &ErrWontSign{baseSigned}
}
case headSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil {
return false, ""
return false, "", err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(headCommit)
if err != nil {
return false, ""
return false, "", err
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
return false, ""
return false, "", &ErrWontSign{headSigned}
}
case commitsSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil {
return false, ""
return false, "", err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(headCommit)
if err != nil {
return false, ""
return false, "", err
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
return false, ""
return false, "", &ErrWontSign{commitsSigned}
}
// need to work out merge-base
mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit)
if err != nil {
return false, ""
return false, "", err
}
commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit)
if err != nil {
return false, ""
return false, "", err
}
for e := commitList.Front(); e != nil; e = e.Next() {
commit = e.Value.(*git.Commit)
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
return false, ""
return false, "", &ErrWontSign{commitsSigned}
}
}
}
}
return true, signingKey
return true, signingKey, nil
}
106 changes: 71 additions & 35 deletions models/repo_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package models

import (
"fmt"
"strings"

"code.gitea.io/gitea/modules/git"
Expand All @@ -25,6 +26,7 @@ const (
headSigned signingMode = "headsigned"
commitsSigned signingMode = "commitssigned"
approved signingMode = "approved"
noKey signingMode = "nokey"
)

func signingModeFromStrings(modeStrings []string) []signingMode {
Expand Down Expand Up @@ -78,6 +80,22 @@ func signingKey(repoPath string) string {
return setting.Repository.Signing.SigningKey
}

// ErrWontSign explains the first reason why a commit would not be signed
// There may be other reasons - this is just the first reason found
type ErrWontSign struct {
Reason signingMode
}

func (e *ErrWontSign) Error() string {
return fmt.Sprintf("wont sign: %s", e.Reason)
}

// IsErrWontSign checks if an error is a ErrWontSign
func IsErrWontSign(err error) bool {
_, ok := err.(*ErrWontSign)
return ok
}

// PublicSigningKey gets the public signing key within a provided repository directory
func PublicSigningKey(repoPath string) (string, error) {
signingKey := signingKey(repoPath)
Expand All @@ -95,122 +113,140 @@ func PublicSigningKey(repoPath string) (string, error) {
}

// SignInitialCommit determines if we should sign the initial commit to this repository
func SignInitialCommit(repoPath string, u *User) (bool, string) {
func SignInitialCommit(repoPath string, u *User) (bool, string, error) {
rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit)
signingKey := signingKey(repoPath)
if signingKey == "" {
return false, ""
return false, "", &ErrWontSign{noKey}
}

for _, rule := range rules {
switch rule {
case never:
return false, ""
return false, "", &ErrWontSign{never}
case always:
break
case pubkey:
keys, err := ListGPGKeys(u.ID)
if err != nil || len(keys) == 0 {
return false, ""
if err != nil {
return false, "", err
}
if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
}
case twofa:
twofa, err := GetTwoFactorByUID(u.ID)
if err != nil || twofa == nil {
return false, ""
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil {
return false, "", err
}
if twofaModel == nil {
return false, "", &ErrWontSign{twofa}
}
}
}
return true, signingKey
return true, signingKey, nil
}

// SignWikiCommit determines if we should sign the commits to this repository wiki
func (repo *Repository) SignWikiCommit(u *User) (bool, string) {
func (repo *Repository) SignWikiCommit(u *User) (bool, string, error) {
rules := signingModeFromStrings(setting.Repository.Signing.Wiki)
signingKey := signingKey(repo.WikiPath())
if signingKey == "" {
return false, ""
return false, "", &ErrWontSign{noKey}
}

for _, rule := range rules {
switch rule {
case never:
return false, ""
return false, "", &ErrWontSign{never}
case always:
break
case pubkey:
keys, err := ListGPGKeys(u.ID)
if err != nil || len(keys) == 0 {
return false, ""
if err != nil {
return false, "", err
}
if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
}
case twofa:
twofa, err := GetTwoFactorByUID(u.ID)
if err != nil || twofa == nil {
return false, ""
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil {
return false, "", err
}
if twofaModel == nil {
return false, "", &ErrWontSign{twofa}
}
case parentSigned:
gitRepo, err := git.OpenRepository(repo.WikiPath())
if err != nil {
return false, ""
return false, "", err
}
defer gitRepo.Close()
commit, err := gitRepo.GetCommit("HEAD")
if err != nil {
return false, ""
return false, "", err
}
if commit.Signature == nil {
return false, ""
return false, "", &ErrWontSign{parentSigned}
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
return false, ""
return false, "", &ErrWontSign{parentSigned}
}
}
}
return true, signingKey
return true, signingKey, nil
}

// SignCRUDAction determines if we should sign a CRUD commit to this repository
func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string) {
func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string, error) {
rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions)
signingKey := signingKey(repo.RepoPath())
if signingKey == "" {
return false, ""
return false, "", &ErrWontSign{noKey}
}

for _, rule := range rules {
switch rule {
case never:
return false, ""
return false, "", &ErrWontSign{never}
case always:
break
case pubkey:
keys, err := ListGPGKeys(u.ID)
if err != nil || len(keys) == 0 {
return false, ""
if err != nil {
return false, "", err
}
if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
}
case twofa:
twofa, err := GetTwoFactorByUID(u.ID)
if err != nil || twofa == nil {
return false, ""
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil {
return false, "", err
}
if twofaModel == nil {
return false, "", &ErrWontSign{twofa}
}
case parentSigned:
gitRepo, err := git.OpenRepository(tmpBasePath)
if err != nil {
return false, ""
return false, "", err
}
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(parentCommit)
if err != nil {
return false, ""
return false, "", err
}
if commit.Signature == nil {
return false, ""
return false, "", &ErrWontSign{parentSigned}
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
return false, ""
return false, "", &ErrWontSign{parentSigned}
}
}
}
return true, signingKey
return true, signingKey, nil
}
Loading