diff options
Diffstat (limited to 'models')
-rw-r--r-- | models/gpg_key.go | 89 | ||||
-rw-r--r-- | models/migrations/migrations.go | 2 | ||||
-rw-r--r-- | models/migrations/v152.go | 14 | ||||
-rw-r--r-- | models/pull_sign.go | 50 | ||||
-rw-r--r-- | models/repo.go | 56 | ||||
-rw-r--r-- | models/repo_sign.go | 93 |
6 files changed, 212 insertions, 92 deletions
diff --git a/models/gpg_key.go b/models/gpg_key.go index 662eac939b..b944fdcbff 100644 --- a/models/gpg_key.go +++ b/models/gpg_key.go @@ -831,7 +831,7 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l newCommits = list.New() e = oldCommits.Front() ) - memberMap := map[int64]bool{} + keyMap := map[string]bool{} for e != nil { c := e.Value.(UserCommit) @@ -840,7 +840,7 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l Verification: ParseCommitWithSignature(c.Commit), } - _ = CalculateTrustStatus(signCommit.Verification, repository, &memberMap) + _ = CalculateTrustStatus(signCommit.Verification, repository, &keyMap) newCommits.PushBack(signCommit) e = e.Next() @@ -849,31 +849,70 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l } // CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository -func CalculateTrustStatus(verification *CommitVerification, repository *Repository, memberMap *map[int64]bool) (err error) { - if verification.Verified { - verification.TrustStatus = "trusted" - if verification.SigningUser.ID != 0 { - var isMember bool - if memberMap != nil { - var has bool - isMember, has = (*memberMap)[verification.SigningUser.ID] - if !has { - isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID) - (*memberMap)[verification.SigningUser.ID] = isMember - } - } else { - isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID) - } +func CalculateTrustStatus(verification *CommitVerification, repository *Repository, keyMap *map[string]bool) (err error) { + if !verification.Verified { + return + } - if !isMember { - verification.TrustStatus = "untrusted" - if verification.CommittingUser.ID != verification.SigningUser.ID { - // The committing user and the signing user are not the same and are not the default key - // This should be marked as questionable unless the signing user is a collaborator/team member etc. - verification.TrustStatus = "unmatched" - } - } + // There are several trust models in Gitea + trustModel := repository.GetTrustModel() + + // In the Committer trust model a signature is trusted if it matches the committer + // - it doesn't matter if they're a collaborator, the owner, Gitea or Github + // NB: This model is commit verification only + if trustModel == CommitterTrustModel { + // default to "unmatched" + verification.TrustStatus = "unmatched" + + // We can only verify against users in our database but the default key will match + // against by email if it is not in the db. + if (verification.SigningUser.ID != 0 && + verification.CommittingUser.ID == verification.SigningUser.ID) || + (verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 && + verification.SigningUser.Email == verification.CommittingUser.Email) { + verification.TrustStatus = "trusted" } + return } + + // Now we drop to the more nuanced trust models... + verification.TrustStatus = "trusted" + + if verification.SigningUser.ID == 0 { + // This commit is signed by the default key - but this key is not assigned to a user in the DB. + + // However in the CollaboratorCommitterTrustModel we cannot mark this as trusted + // unless the default key matches the email of a non-user. + if trustModel == CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 || + verification.SigningUser.Email != verification.CommittingUser.Email) { + verification.TrustStatus = "untrusted" + } + return + } + + var isMember bool + if keyMap != nil { + var has bool + isMember, has = (*keyMap)[verification.SigningKey.KeyID] + if !has { + isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID) + (*keyMap)[verification.SigningKey.KeyID] = isMember + } + } else { + isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID) + } + + if !isMember { + verification.TrustStatus = "untrusted" + if verification.CommittingUser.ID != verification.SigningUser.ID { + // The committing user and the signing user are not the same + // This should be marked as questionable unless the signing user is a collaborator/team member etc. + verification.TrustStatus = "unmatched" + } + } else if trustModel == CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID { + // The committing user and the signing user are not the same and our trustmodel states that they must match + verification.TrustStatus = "unmatched" + } + return } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 5317cc5743..ea1bf59649 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -237,6 +237,8 @@ var migrations = []Migration{ NewMigration("add primary key to repo_topic", addPrimaryKeyToRepoTopic), // v151 -> v152 NewMigration("set default password algorithm to Argon2", setDefaultPasswordToArgon2), + // v152 -> v153 + NewMigration("add TrustModel field to Repository", addTrustModelToRepository), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v152.go b/models/migrations/v152.go new file mode 100644 index 0000000000..f71f71e22f --- /dev/null +++ b/models/migrations/v152.go @@ -0,0 +1,14 @@ +// 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 addTrustModelToRepository(x *xorm.Engine) error { + type Repository struct { + TrustModel int + } + return x.Sync2(new(Repository)) +} diff --git a/models/pull_sign.go b/models/pull_sign.go index ab61232d65..10a6522ebe 100644 --- a/models/pull_sign.go +++ b/models/pull_sign.go @@ -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, error) { +func (pr *PullRequest) SignMerge(u *User, tmpBasePath, baseCommit, headCommit string) (bool, string, *git.Signature, error) { if err := pr.LoadBaseRepo(); err != nil { log.Error("Unable to get Base Repo for pull request") - return false, "", err + return false, "", nil, err } repo := pr.BaseRepo - signingKey := signingKey(repo.RepoPath()) + signingKey, signer := SigningKey(repo.RepoPath()) if signingKey == "" { - return false, "", &ErrWontSign{noKey} + return false, "", nil, &ErrWontSign{noKey} } rules := signingModeFromStrings(setting.Repository.Signing.Merges) @@ -31,101 +31,101 @@ Loop: for _, rule := range rules { switch rule { case never: - return false, "", &ErrWontSign{never} + return false, "", nil, &ErrWontSign{never} case always: break Loop case pubkey: keys, err := ListGPGKeys(u.ID, ListOptions{}) if err != nil { - return false, "", err + return false, "", nil, err } if len(keys) == 0 { - return false, "", &ErrWontSign{pubkey} + return false, "", nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := GetTwoFactorByUID(u.ID) if err != nil && !IsErrTwoFactorNotEnrolled(err) { - return false, "", err + return false, "", nil, err } if twofaModel == nil { - return false, "", &ErrWontSign{twofa} + return false, "", nil, &ErrWontSign{twofa} } case approved: protectedBranch, err := GetProtectedBranchBy(repo.ID, pr.BaseBranch) if err != nil { - return false, "", err + return false, "", nil, err } if protectedBranch == nil { - return false, "", &ErrWontSign{approved} + return false, "", nil, &ErrWontSign{approved} } if protectedBranch.GetGrantedApprovalsCount(pr) < 1 { - return false, "", &ErrWontSign{approved} + return false, "", nil, &ErrWontSign{approved} } case baseSigned: if gitRepo == nil { gitRepo, err = git.OpenRepository(tmpBasePath) if err != nil { - return false, "", err + return false, "", nil, err } defer gitRepo.Close() } commit, err := gitRepo.GetCommit(baseCommit) if err != nil { - return false, "", err + return false, "", nil, err } verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{baseSigned} + return false, "", nil, &ErrWontSign{baseSigned} } case headSigned: if gitRepo == nil { gitRepo, err = git.OpenRepository(tmpBasePath) if err != nil { - return false, "", err + return false, "", nil, err } defer gitRepo.Close() } commit, err := gitRepo.GetCommit(headCommit) if err != nil { - return false, "", err + return false, "", nil, err } verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{headSigned} + return false, "", nil, &ErrWontSign{headSigned} } case commitsSigned: if gitRepo == nil { gitRepo, err = git.OpenRepository(tmpBasePath) if err != nil { - return false, "", err + return false, "", nil, err } defer gitRepo.Close() } commit, err := gitRepo.GetCommit(headCommit) if err != nil { - return false, "", err + return false, "", nil, err } verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{commitsSigned} + return false, "", nil, &ErrWontSign{commitsSigned} } // need to work out merge-base mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit) if err != nil { - return false, "", err + return false, "", nil, err } commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit) if err != nil { - return false, "", err + return false, "", nil, err } for e := commitList.Front(); e != nil; e = e.Next() { commit = e.Value.(*git.Commit) verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{commitsSigned} + return false, "", nil, &ErrWontSign{commitsSigned} } } } } - return true, signingKey, nil + return true, signingKey, signer, nil } diff --git a/models/repo.go b/models/repo.go index 2bbc74f439..25fe3f63d3 100644 --- a/models/repo.go +++ b/models/repo.go @@ -143,6 +143,47 @@ const ( RepositoryBeingMigrated // repository is migrating ) +// TrustModelType defines the types of trust model for this repository +type TrustModelType int + +// kinds of TrustModel +const ( + DefaultTrustModel TrustModelType = iota // default trust model + CommitterTrustModel + CollaboratorTrustModel + CollaboratorCommitterTrustModel +) + +// String converts a TrustModelType to a string +func (t TrustModelType) String() string { + switch t { + case DefaultTrustModel: + return "default" + case CommitterTrustModel: + return "committer" + case CollaboratorTrustModel: + return "collaborator" + case CollaboratorCommitterTrustModel: + return "collaboratorcommitter" + } + return "default" +} + +// ToTrustModel converts a string to a TrustModelType +func ToTrustModel(model string) TrustModelType { + switch strings.ToLower(strings.TrimSpace(model)) { + case "default": + return DefaultTrustModel + case "collaborator": + return CollaboratorTrustModel + case "committer": + return CommitterTrustModel + case "collaboratorcommitter": + return CollaboratorCommitterTrustModel + } + return DefaultTrustModel +} + // Repository represents a git repository. type Repository struct { ID int64 `xorm:"pk autoincr"` @@ -198,6 +239,8 @@ type Repository struct { CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"` Topics []string `xorm:"TEXT JSON"` + TrustModel TrustModelType + // Avatar: ID(10-20)-md5(32) - must fit into 64 symbols Avatar string `xorm:"VARCHAR(64)"` @@ -1038,6 +1081,7 @@ type CreateRepoOptions struct { IsMirror bool AutoInit bool Status RepositoryStatus + TrustModel TrustModelType } // GetRepoInitFile returns repository init files @@ -2383,6 +2427,18 @@ func UpdateRepositoryCols(repo *Repository, cols ...string) error { return updateRepositoryCols(x, repo, cols...) } +// GetTrustModel will get the TrustModel for the repo or the default trust model +func (repo *Repository) GetTrustModel() TrustModelType { + trustModel := repo.TrustModel + if trustModel == DefaultTrustModel { + trustModel = ToTrustModel(setting.Repository.Signing.DefaultTrustModel) + if trustModel == DefaultTrustModel { + return CollaboratorTrustModel + } + } + return trustModel +} + // DoctorUserStarNum recalculate Stars number for all user func DoctorUserStarNum() (err error) { const batchSize = 100 diff --git a/models/repo_sign.go b/models/repo_sign.go index c9dd5ea4dc..be9309ed4e 100644 --- a/models/repo_sign.go +++ b/models/repo_sign.go @@ -31,7 +31,7 @@ const ( func signingModeFromStrings(modeStrings []string) []signingMode { returnable := make([]signingMode, 0, len(modeStrings)) for _, mode := range modeStrings { - signMode := signingMode(strings.ToLower(mode)) + signMode := signingMode(strings.ToLower(strings.TrimSpace(mode))) switch signMode { case never: return []signingMode{never} @@ -59,9 +59,10 @@ func signingModeFromStrings(modeStrings []string) []signingMode { return returnable } -func signingKey(repoPath string) string { +// SigningKey returns the KeyID and git Signature for the repo +func SigningKey(repoPath string) (string, *git.Signature) { if setting.Repository.Signing.SigningKey == "none" { - return "" + return "", nil } if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" { @@ -69,19 +70,27 @@ func signingKey(repoPath string) string { value, _ := git.NewCommand("config", "--get", "commit.gpgsign").RunInDir(repoPath) sign, valid := git.ParseBool(strings.TrimSpace(value)) if !sign || !valid { - return "" + return "", nil } signingKey, _ := git.NewCommand("config", "--get", "user.signingkey").RunInDir(repoPath) - return strings.TrimSpace(signingKey) + signingName, _ := git.NewCommand("config", "--get", "user.name").RunInDir(repoPath) + signingEmail, _ := git.NewCommand("config", "--get", "user.email").RunInDir(repoPath) + return strings.TrimSpace(signingKey), &git.Signature{ + Name: strings.TrimSpace(signingName), + Email: strings.TrimSpace(signingEmail), + } } - return setting.Repository.Signing.SigningKey + return setting.Repository.Signing.SigningKey, &git.Signature{ + Name: setting.Repository.Signing.SigningName, + Email: setting.Repository.Signing.SigningEmail, + } } // PublicSigningKey gets the public signing key within a provided repository directory func PublicSigningKey(repoPath string) (string, error) { - signingKey := signingKey(repoPath) + signingKey, _ := SigningKey(repoPath) if signingKey == "" { return "", nil } @@ -96,143 +105,143 @@ 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, error) { +func SignInitialCommit(repoPath string, u *User) (bool, string, *git.Signature, error) { rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit) - signingKey := signingKey(repoPath) + signingKey, sig := SigningKey(repoPath) if signingKey == "" { - return false, "", &ErrWontSign{noKey} + return false, "", nil, &ErrWontSign{noKey} } Loop: for _, rule := range rules { switch rule { case never: - return false, "", &ErrWontSign{never} + return false, "", nil, &ErrWontSign{never} case always: break Loop case pubkey: keys, err := ListGPGKeys(u.ID, ListOptions{}) if err != nil { - return false, "", err + return false, "", nil, err } if len(keys) == 0 { - return false, "", &ErrWontSign{pubkey} + return false, "", nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := GetTwoFactorByUID(u.ID) if err != nil && !IsErrTwoFactorNotEnrolled(err) { - return false, "", err + return false, "", nil, err } if twofaModel == nil { - return false, "", &ErrWontSign{twofa} + return false, "", nil, &ErrWontSign{twofa} } } } - return true, signingKey, nil + return true, signingKey, sig, nil } // SignWikiCommit determines if we should sign the commits to this repository wiki -func (repo *Repository) SignWikiCommit(u *User) (bool, string, error) { +func (repo *Repository) SignWikiCommit(u *User) (bool, string, *git.Signature, error) { rules := signingModeFromStrings(setting.Repository.Signing.Wiki) - signingKey := signingKey(repo.WikiPath()) + signingKey, sig := SigningKey(repo.WikiPath()) if signingKey == "" { - return false, "", &ErrWontSign{noKey} + return false, "", nil, &ErrWontSign{noKey} } Loop: for _, rule := range rules { switch rule { case never: - return false, "", &ErrWontSign{never} + return false, "", nil, &ErrWontSign{never} case always: break Loop case pubkey: keys, err := ListGPGKeys(u.ID, ListOptions{}) if err != nil { - return false, "", err + return false, "", nil, err } if len(keys) == 0 { - return false, "", &ErrWontSign{pubkey} + return false, "", nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := GetTwoFactorByUID(u.ID) if err != nil && !IsErrTwoFactorNotEnrolled(err) { - return false, "", err + return false, "", nil, err } if twofaModel == nil { - return false, "", &ErrWontSign{twofa} + return false, "", nil, &ErrWontSign{twofa} } case parentSigned: gitRepo, err := git.OpenRepository(repo.WikiPath()) if err != nil { - return false, "", err + return false, "", nil, err } defer gitRepo.Close() commit, err := gitRepo.GetCommit("HEAD") if err != nil { - return false, "", err + return false, "", nil, err } if commit.Signature == nil { - return false, "", &ErrWontSign{parentSigned} + return false, "", nil, &ErrWontSign{parentSigned} } verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{parentSigned} + return false, "", nil, &ErrWontSign{parentSigned} } } } - return true, signingKey, nil + return true, signingKey, sig, nil } // SignCRUDAction determines if we should sign a CRUD commit to this repository -func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string, error) { +func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string, *git.Signature, error) { rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions) - signingKey := signingKey(repo.RepoPath()) + signingKey, sig := SigningKey(repo.RepoPath()) if signingKey == "" { - return false, "", &ErrWontSign{noKey} + return false, "", nil, &ErrWontSign{noKey} } Loop: for _, rule := range rules { switch rule { case never: - return false, "", &ErrWontSign{never} + return false, "", nil, &ErrWontSign{never} case always: break Loop case pubkey: keys, err := ListGPGKeys(u.ID, ListOptions{}) if err != nil { - return false, "", err + return false, "", nil, err } if len(keys) == 0 { - return false, "", &ErrWontSign{pubkey} + return false, "", nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := GetTwoFactorByUID(u.ID) if err != nil && !IsErrTwoFactorNotEnrolled(err) { - return false, "", err + return false, "", nil, err } if twofaModel == nil { - return false, "", &ErrWontSign{twofa} + return false, "", nil, &ErrWontSign{twofa} } case parentSigned: gitRepo, err := git.OpenRepository(tmpBasePath) if err != nil { - return false, "", err + return false, "", nil, err } defer gitRepo.Close() commit, err := gitRepo.GetCommit(parentCommit) if err != nil { - return false, "", err + return false, "", nil, err } if commit.Signature == nil { - return false, "", &ErrWontSign{parentSigned} + return false, "", nil, &ErrWontSign{parentSigned} } verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{parentSigned} + return false, "", nil, &ErrWontSign{parentSigned} } } } - return true, signingKey, nil + return true, signingKey, sig, nil } |