; by setting the SIGNING_KEY ID to the correct ID.)
SIGNING_NAME =
SIGNING_EMAIL =
+; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter
+DEFAULT_TRUST_MODEL=collaborator
; Determines when gitea should sign the initial commit when creating a repository
; Either:
; - never
- `twofa`: Only sign if the user is logged in with twofa
- `always`: Always sign
- Options other than `never` and `always` can be combined as a comma separated list.
+- `DEFAULT_TRUST_MODEL`: **collaborator**: \[collaborator, committer, collaboratorcommitter\]: The default trust model used for verifying commits.
+ - `collaborator`: Trust signatures signed by keys of collaborators.
+ - `committer`: Trust signatures that match committers (This matches GitHub and will force Gitea signed commits to have Gitea as the commmitter).
+ - `collaboratorcommitter`: Trust signatures signed by keys of collaborators which match the commiter.
- `WIKI`: **never**: \[never, pubkey, twofa, always, parentsigned\]: Sign commits to wiki.
- `CRUD_ACTIONS`: **pubkey, twofa, parentsigned**: \[never, pubkey, twofa, parentsigned, always\]: Sign CRUD actions.
- Options as above, with the addition of:
newCommits = list.New()
e = oldCommits.Front()
)
- memberMap := map[int64]bool{}
+ keyMap := map[string]bool{}
for e != nil {
c := e.Value.(UserCommit)
Verification: ParseCommitWithSignature(c.Commit),
}
- _ = CalculateTrustStatus(signCommit.Verification, repository, &memberMap)
+ _ = CalculateTrustStatus(signCommit.Verification, repository, &keyMap)
newCommits.PushBack(signCommit)
e = e.Next()
}
// 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
}
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
--- /dev/null
+// 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))
+}
)
// 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)
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
}
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"`
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)"`
IsMirror bool
AutoInit bool
Status RepositoryStatus
+ TrustModel TrustModelType
}
// GetRepoInitFile returns repository init files
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
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}
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 == "" {
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
}
}
// 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
}
Webhooks bool
Avatar bool
Labels bool
+ TrustModel string
}
// Validate validates the fields
EnableIssueDependencies bool
IsArchived bool
+ // Signing Settings
+ TrustModel string
+
// Admin settings
EnableHealthCheck bool
EnableCloseIssuesViaCommitInAnyBranch bool
requireSigned = protectedBranch.RequireSignedCommits
}
- sign, keyID, err := r.Repository.SignCRUDAction(doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
+ sign, keyID, _, err := r.Repository.SignCRUDAction(doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
canCommit := r.CanEnableEditor() && userCanPush
if requireSigned {
}
// CommitTree creates a commit from a given tree id for the user with provided message
-func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) {
+func (repo *Repository) CommitTree(author *Signature, committer *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) {
err := LoadGitVersion()
if err != nil {
return SHA1{}, err
// 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_NAME="+author.Name,
+ "GIT_AUTHOR_EMAIL="+author.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
- "GIT_COMMITTER_NAME="+sig.Name,
- "GIT_COMMITTER_EMAIL="+sig.Email,
+ "GIT_COMMITTER_NAME="+committer.Name,
+ "GIT_COMMITTER_EMAIL="+committer.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
cmd := NewCommand("commit-tree", tree.ID.String())
}
}
if protectedBranch.RequireSignedCommits {
- _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
+ _, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
if err != nil {
if !models.IsErrWontSign(err) {
return nil, err
"GIT_AUTHOR_NAME="+authorSig.Name,
"GIT_AUTHOR_EMAIL="+authorSig.Email,
"GIT_AUTHOR_DATE="+authorDate.Format(time.RFC3339),
- "GIT_COMMITTER_NAME="+committerSig.Name,
- "GIT_COMMITTER_EMAIL="+committerSig.Email,
"GIT_COMMITTER_DATE="+committerDate.Format(time.RFC3339),
)
// Determine if we should sign
if git.CheckGitVersionConstraint(">= 1.7.9") == nil {
- sign, keyID, _ := t.repo.SignCRUDAction(author, t.basePath, "HEAD")
+ sign, keyID, signer, _ := t.repo.SignCRUDAction(author, t.basePath, "HEAD")
if sign {
args = append(args, "-S"+keyID)
+ if t.repo.GetTrustModel() == models.CommitterTrustModel || t.repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
+ if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email {
+ // Add trailers
+ _, _ = messageBytes.WriteString("\n")
+ _, _ = messageBytes.WriteString("Co-Authored-By: ")
+ _, _ = messageBytes.WriteString(committerSig.String())
+ _, _ = messageBytes.WriteString("\n")
+ _, _ = messageBytes.WriteString("Co-Committed-By: ")
+ _, _ = messageBytes.WriteString(committerSig.String())
+ _, _ = messageBytes.WriteString("\n")
+ }
+ committerSig = signer
+ }
} else if git.CheckGitVersionConstraint(">= 2.0.0") == nil {
args = append(args, "--no-gpg-sign")
}
}
+ env = append(env,
+ "GIT_COMMITTER_NAME="+committerSig.Name,
+ "GIT_COMMITTER_EMAIL="+committerSig.Email,
+ )
+
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
if err := git.NewCommand(args...).RunInDirTimeoutEnvFullPipeline(env, -1, t.basePath, stdout, stderr, messageBytes); err != nil {
}
}
if protectedBranch.RequireSignedCommits {
- _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
+ _, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
if err != nil {
if !models.IsErrWontSign(err) {
return nil, err
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
Status: opts.Status,
IsEmpty: !opts.AutoInit,
+ TrustModel: opts.TrustModel,
}
err = models.WithTx(func(ctx models.DBContext) error {
IsEmpty: !opts.GitContent || templateRepo.IsEmpty,
IsFsckEnabled: templateRepo.IsFsckEnabled,
TemplateID: templateRepo.ID,
+ TrustModel: templateRepo.TrustModel,
}
if err = models.CreateRepository(ctx, doer, owner, generateRepo); err != nil {
"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,
)
+ committerName := sig.Name
+ committerEmail := sig.Email
if stdout, err := git.NewCommand("add", "--all").
SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)).
}
if git.CheckGitVersionConstraint(">= 1.7.9") == nil {
- sign, keyID, _ := models.SignInitialCommit(tmpPath, u)
+ sign, keyID, signer, _ := models.SignInitialCommit(tmpPath, u)
if sign {
args = append(args, "-S"+keyID)
+
+ if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
+ // need to set the committer to the KeyID owner
+ committerName = signer.Name
+ committerEmail = signer.Email
+ }
} else if git.CheckGitVersionConstraint(">= 2.0.0") == nil {
args = append(args, "--no-gpg-sign")
}
}
+ env = append(env,
+ "GIT_COMMITTER_NAME="+committerName,
+ "GIT_COMMITTER_EMAIL="+committerEmail,
+ )
+
if stdout, err := git.NewCommand(args...).
SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)).
RunInDirWithEnv(tmpPath, env); err != nil {
} `ini:"repository.issue"`
Signing struct {
- SigningKey string
- SigningName string
- SigningEmail string
- InitialCommit []string
- CRUDActions []string `ini:"CRUD_ACTIONS"`
- Merges []string
- Wiki []string
+ SigningKey string
+ SigningName string
+ SigningEmail string
+ InitialCommit []string
+ CRUDActions []string `ini:"CRUD_ACTIONS"`
+ Merges []string
+ Wiki []string
+ DefaultTrustModel string
} `ini:"repository.signing"`
}{
DetectedCharsetsOrder: []string{
// Signing settings
Signing: struct {
- SigningKey string
- SigningName string
- SigningEmail string
- InitialCommit []string
- CRUDActions []string `ini:"CRUD_ACTIONS"`
- Merges []string
- Wiki []string
+ SigningKey string
+ SigningName string
+ SigningEmail string
+ InitialCommit []string
+ CRUDActions []string `ini:"CRUD_ACTIONS"`
+ Merges []string
+ Wiki []string
+ DefaultTrustModel string
}{
- SigningKey: "default",
- SigningName: "",
- SigningEmail: "",
- InitialCommit: []string{"always"},
- CRUDActions: []string{"pubkey", "twofa", "parentsigned"},
- Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"},
- Wiki: []string{"never"},
+ SigningKey: "default",
+ SigningName: "",
+ SigningEmail: "",
+ InitialCommit: []string{"always"},
+ CRUDActions: []string{"pubkey", "twofa", "parentsigned"},
+ Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"},
+ Wiki: []string{"never"},
+ DefaultTrustModel: "collaborator",
},
}
RepoRootPath string
log.Fatal("Failed to map Repository.PullRequest settings: %v", err)
}
+ // Handle default trustmodel settings
+ Repository.Signing.DefaultTrustModel = strings.ToLower(strings.TrimSpace(Repository.Signing.DefaultTrustModel))
+ if Repository.Signing.DefaultTrustModel == "default" {
+ Repository.Signing.DefaultTrustModel = "collaborator"
+ }
+
+ // Handle preferred charset orders
preferred := make([]string, 0, len(Repository.DetectedCharsetsOrder))
for _, charset := range Repository.DetectedCharsetsOrder {
canonicalCharset := strings.ToLower(strings.TrimSpace(charset))
Readme string `json:"readme"`
// DefaultBranch of the repository (used when initializes and in template)
DefaultBranch string `json:"default_branch" binding:"GitRefName;MaxSize(100)"`
+ // TrustModel of the repository
+ // enum: default,collaborator,committer,collaboratorcommitter
+ TrustModel string `json:"trust_model"`
}
// EditRepoOption options when editing a repository's properties
settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user.
settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own.
settings.transfer_form_title = Enter the repository name as confirmation:
+settings.signing_settings = Signing Verification Settings
+settings.trust_model = Signature Trust Model
+settings.trust_model.default = Default Trust Model
+settings.trust_model.default.desc= Use the default repository trust model for this installation.
+settings.trust_model.collaborator = Collaborator
+settings.trust_model.collaborator.long = Collaborator: Trust signatures by collaborators
+settings.trust_model.collaborator.desc = Valid signatures by collaborators of this repository will be marked "trusted" - (whether they match the committer or not). Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" if not.
+settings.trust_model.committer = Committer
+settings.trust_model.committer.long = Committer: Trust signatures that match committers (This matches GitHub and will force Gitea signed commits to have Gitea as the committer)
+settings.trust_model.committer.desc = Valid signatures will only be marked "trusted" if they match the committer, otherwise they will be marked "unmatched". This will force Gitea to be the committer on signed commits with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default Gitea key must match a User in the database.
+settings.trust_model.collaboratorcommitter = Collaborator+Committer
+settings.trust_model.collaboratorcommitter.long = Collaborator+Committer: Trust signatures by collaborators which match the committer
+settings.trust_model.collaboratorcommitter.desc = Valid signatures by collaborators of this repository will be marked "trusted" if they match the committer. Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" otherwise. This will force Gitea to be marked as the committer on signed commits with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default Gitea key must match a User in the database,
settings.wiki_delete = Delete Wiki Data
settings.wiki_delete_desc = Deleting repository wiki data is permanent and cannot be undone.
settings.wiki_delete_notices_1 = - This will permanently delete and disable the repository wiki for %s.
IsPrivate: opt.Private,
AutoInit: opt.AutoInit,
DefaultBranch: opt.DefaultBranch,
+ TrustModel: models.ToTrustModel(opt.TrustModel),
})
if err != nil {
if models.IsErrRepoAlreadyExist(err) {
}
ctx.Data["WillSign"] = false
if ctx.User != nil {
- sign, key, err := pull.SignMerge(ctx.User, pull.BaseRepo.RepoPath(), pull.BaseBranch, pull.GetGitRefName())
+ sign, key, _, err := pull.SignMerge(ctx.User, pull.BaseRepo.RepoPath(), pull.BaseBranch, pull.GetGitRefName())
ctx.Data["WillSign"] = sign
ctx.Data["SigningKey"] = key
if err != nil {
IsPrivate: form.Private || setting.Repository.ForcePrivate,
DefaultBranch: form.DefaultBranch,
AutoInit: form.AutoInit,
+ TrustModel: models.ToTrustModel(form.TrustModel),
})
if err == nil {
log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsOptions"] = true
ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
+
+ signing, _ := models.SigningKey(ctx.Repo.Repository.RepoPath())
+ ctx.Data["SigningKeyAvailable"] = len(signing) > 0
+ ctx.Data["SigningSettings"] = setting.Repository.Signing
+
ctx.HTML(200, tplSettingsOptions)
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
+ case "signing":
+ changed := false
+
+ trustModel := models.ToTrustModel(form.TrustModel)
+ if trustModel != repo.TrustModel {
+ repo.TrustModel = trustModel
+ changed = true
+ }
+
+ if changed {
+ if err := models.UpdateRepository(repo, false); err != nil {
+ ctx.ServerError("UpdateRepository", err)
+ return
+ }
+ }
+ log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
+
+ ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
+ ctx.Redirect(ctx.Repo.RepoLink + "/settings")
+
case "admin":
if !ctx.User.IsAdmin {
ctx.Error(403)
outbuf.Reset()
errbuf.Reset()
+ sig := doer.NewGitSig()
+ committer := sig
+
// Determine if we should sign
signArg := ""
if git.CheckGitVersionConstraint(">= 1.7.9") == nil {
- sign, keyID, _ := pr.SignMerge(doer, tmpBasePath, "HEAD", trackingBranch)
+ sign, keyID, signer, _ := pr.SignMerge(doer, tmpBasePath, "HEAD", trackingBranch)
if sign {
signArg = "-S" + keyID
+ if pr.BaseRepo.GetTrustModel() == models.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
+ committer = signer
+ }
} else if git.CheckGitVersionConstraint(">= 2.0.0") == nil {
signArg = "--no-gpg-sign"
}
}
- sig := doer.NewGitSig()
commitTimeStr := time.Now().Format(time.RFC3339)
// Because this may call hooks we should pass in the environment
"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_NAME="+committer.Name,
+ "GIT_COMMITTER_EMAIL="+committer.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
}
} else {
+ if committer != sig {
+ // add trailer
+ message += fmt.Sprintf("\nCo-Authored-By: %s\nCo-Committed-By: %s\n", sig.String(), sig.String())
+ }
if err := git.NewCommand("commit", signArg, fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, &outbuf, &errbuf); err != nil {
log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
return true, nil
}
- sign, _, err := pr.SignMerge(doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName())
+ sign, _, _, err := pr.SignMerge(doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName())
return sign, err
}
Message: message,
}
- sign, signingKey, _ := repo.SignWikiCommit(doer)
+ committer := doer.NewGitSig()
+
+ sign, signingKey, signer, _ := repo.SignWikiCommit(doer)
if sign {
commitTreeOpts.KeyID = signingKey
+ if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
+ committer = signer
+ }
} else {
commitTreeOpts.NoGPGSign = true
}
if hasMasterBranch {
commitTreeOpts.Parents = []string{"HEAD"}
}
- commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts)
+
+ commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts)
if err != nil {
log.Error("%v", err)
return err
Parents: []string{"HEAD"},
}
- sign, signingKey, _ := repo.SignWikiCommit(doer)
+ committer := doer.NewGitSig()
+
+ sign, signingKey, signer, _ := repo.SignWikiCommit(doer)
if sign {
commitTreeOpts.KeyID = signingKey
+ if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
+ committer = signer
+ }
} else {
commitTreeOpts.NoGPGSign = true
}
- commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts)
+ commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts)
if err != nil {
return err
}
<label for="default_branch">{{.i18n.Tr "repo.default_branch"}}</label>
<input id="default_branch" name="default_branch" value="{{.default_branch}}" placeholder="{{.default_branch}}">
</div>
+ <div class="inline field">
+ <label>{{.i18n.Tr "repo.settings.trust_model"}}</label>
+ <div class="ui selection owner dropdown">
+ <input type="hidden" id="trust_model" name="trust_model" value="default" required>
+ <div class="default text">{{.i18n.Tr "repo.settings.trust_model"}}</div>
+ <i class="dropdown icon"></i>
+ <div class="menu">
+ <div class="item" data-value="default">{{.i18n.Tr "repo.settings.trust_model.default"}}</div>
+ <div class="item" data-value="collaborator">{{.i18n.Tr "repo.settings.trust_model.collaborator"}}</div>
+ <div class="item" data-value="committer">{{.i18n.Tr "repo.settings.trust_model.committer"}}</div>
+ <div class="item" data-value="collaboratorcommitter">{{.i18n.Tr "repo.settings.trust_model.collaboratorcommitter"}}</div>
+ </div>
+ </div>
</div>
<br/>
</form>
</div>
+ <h4 class="ui top attached header">
+ {{.i18n.Tr "repo.settings.signing_settings"}}
+ </h4>
+ <div class="ui attached segment">
+ <form class="ui form" method="post">
+ {{.CsrfTokenHtml}}
+ <input type="hidden" name="action" value="signing">
+ <div class="field">
+ <label>{{.i18n.Tr "repo.settings.trust_model"}}</label>
+ <div class="field">
+ <div class="ui radio checkbox">
+ <input type="radio" id="trust_model_default" name="trust_model" {{if eq .Repository.TrustModel.String "default"}}checked="checked"{{end}} value="default">
+ <label for="trust_model_default">{{.i18n.Tr "repo.settings.trust_model.default"}}</label>
+ <p class="help">{{.i18n.Tr "repo.settings.trust_model.default.desc"}}</p>
+ </div>
+ </div>
+ <div class="field">
+ <div class="ui radio checkbox">
+ <input type="radio" id="trust_model_collaborator" name="trust_model" {{if eq .Repository.TrustModel.String "collaborator"}}checked="checked"{{end}} value="collaborator">
+ <label for="trust_model_collaborator">{{.i18n.Tr "repo.settings.trust_model.collaborator.long"}}</label>
+ <p class="help">{{.i18n.Tr "repo.settings.trust_model.collaborator.desc"}}</p>
+ </div>
+ </div>
+ <div class="field">
+ <div class="ui radio checkbox">
+ <input type="radio" name="trust_model" id="trust_model_committer" {{if eq .Repository.TrustModel.String "committer"}}checked="checked"{{end}} value="committer">
+ <label for="trust_model_committer">{{.i18n.Tr "repo.settings.trust_model.committer.long"}}</label>
+ <p class="help">{{.i18n.Tr "repo.settings.trust_model.committer.desc"}}</p>
+ </div>
+ </div>
+ <div class="field">
+ <div class="ui radio checkbox">
+ <input type="radio" name="trust_model" id="trust_model_collaboratorcommitter" {{if eq .Repository.TrustModel.String "collaboratorcommitter"}}checked="checked"{{end}} value="collaboratorcommitter">
+ <label for="trust_model_collaboratorcommitter">{{.i18n.Tr "repo.settings.trust_model.collaboratorcommitter.long"}}</label>
+ <p class="help">{{.i18n.Tr "repo.settings.trust_model.collaboratorcommitter.desc"}}</p>
+ </div>
+ </div>
+ </div>
+
+ <div class="ui divider"></div>
+ <div class="field">
+ <button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
+ </div>
+ </form>
+ </div>
+
{{if .IsAdmin}}
<h4 class="ui top attached header">
{{.i18n.Tr "repo.settings.admin_settings"}}
"description": "Readme of the repository to create",
"type": "string",
"x-go-name": "Readme"
+ },
+ "trust_model": {
+ "description": "TrustModel of the repository",
+ "type": "string",
+ "enum": [
+ "default",
+ "collaborator",
+ "committer",
+ "collaboratorcommitter"
+ ],
+ "x-go-name": "TrustModel"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"