gitea

Зеркало из https://github.com/go-gitea/gitea
Форк
0
404 строки · 11.8 Кб
1
// Copyright 2021 The Gitea Authors. All rights reserved.
2
// SPDX-License-Identifier: MIT
3

4
package asymkey
5

6
import (
7
	"context"
8
	"fmt"
9
	"strings"
10

11
	asymkey_model "code.gitea.io/gitea/models/asymkey"
12
	"code.gitea.io/gitea/models/auth"
13
	"code.gitea.io/gitea/models/db"
14
	git_model "code.gitea.io/gitea/models/git"
15
	issues_model "code.gitea.io/gitea/models/issues"
16
	repo_model "code.gitea.io/gitea/models/repo"
17
	user_model "code.gitea.io/gitea/models/user"
18
	"code.gitea.io/gitea/modules/git"
19
	"code.gitea.io/gitea/modules/gitrepo"
20
	"code.gitea.io/gitea/modules/log"
21
	"code.gitea.io/gitea/modules/process"
22
	"code.gitea.io/gitea/modules/setting"
23
)
24

25
type signingMode string
26

27
const (
28
	never         signingMode = "never"
29
	always        signingMode = "always"
30
	pubkey        signingMode = "pubkey"
31
	twofa         signingMode = "twofa"
32
	parentSigned  signingMode = "parentsigned"
33
	baseSigned    signingMode = "basesigned"
34
	headSigned    signingMode = "headsigned"
35
	commitsSigned signingMode = "commitssigned"
36
	approved      signingMode = "approved"
37
	noKey         signingMode = "nokey"
38
)
39

40
func signingModeFromStrings(modeStrings []string) []signingMode {
41
	returnable := make([]signingMode, 0, len(modeStrings))
42
	for _, mode := range modeStrings {
43
		signMode := signingMode(strings.ToLower(strings.TrimSpace(mode)))
44
		switch signMode {
45
		case never:
46
			return []signingMode{never}
47
		case always:
48
			return []signingMode{always}
49
		case pubkey:
50
			fallthrough
51
		case twofa:
52
			fallthrough
53
		case parentSigned:
54
			fallthrough
55
		case baseSigned:
56
			fallthrough
57
		case headSigned:
58
			fallthrough
59
		case approved:
60
			fallthrough
61
		case commitsSigned:
62
			returnable = append(returnable, signMode)
63
		}
64
	}
65
	if len(returnable) == 0 {
66
		return []signingMode{never}
67
	}
68
	return returnable
69
}
70

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

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

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

87
// SigningKey returns the KeyID and git Signature for the repo
88
func SigningKey(ctx context.Context, repoPath string) (string, *git.Signature) {
89
	if setting.Repository.Signing.SigningKey == "none" {
90
		return "", nil
91
	}
92

93
	if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" {
94
		// Can ignore the error here as it means that commit.gpgsign is not set
95
		value, _, _ := git.NewCommand(ctx, "config", "--get", "commit.gpgsign").RunStdString(&git.RunOpts{Dir: repoPath})
96
		sign, valid := git.ParseBool(strings.TrimSpace(value))
97
		if !sign || !valid {
98
			return "", nil
99
		}
100

101
		signingKey, _, _ := git.NewCommand(ctx, "config", "--get", "user.signingkey").RunStdString(&git.RunOpts{Dir: repoPath})
102
		signingName, _, _ := git.NewCommand(ctx, "config", "--get", "user.name").RunStdString(&git.RunOpts{Dir: repoPath})
103
		signingEmail, _, _ := git.NewCommand(ctx, "config", "--get", "user.email").RunStdString(&git.RunOpts{Dir: repoPath})
104
		return strings.TrimSpace(signingKey), &git.Signature{
105
			Name:  strings.TrimSpace(signingName),
106
			Email: strings.TrimSpace(signingEmail),
107
		}
108
	}
109

110
	return setting.Repository.Signing.SigningKey, &git.Signature{
111
		Name:  setting.Repository.Signing.SigningName,
112
		Email: setting.Repository.Signing.SigningEmail,
113
	}
114
}
115

116
// PublicSigningKey gets the public signing key within a provided repository directory
117
func PublicSigningKey(ctx context.Context, repoPath string) (string, error) {
118
	signingKey, _ := SigningKey(ctx, repoPath)
119
	if signingKey == "" {
120
		return "", nil
121
	}
122

123
	content, stderr, err := process.GetManager().ExecDir(ctx, -1, repoPath,
124
		"gpg --export -a", "gpg", "--export", "-a", signingKey)
125
	if err != nil {
126
		log.Error("Unable to get default signing key in %s: %s, %s, %v", repoPath, signingKey, stderr, err)
127
		return "", err
128
	}
129
	return content, nil
130
}
131

132
// SignInitialCommit determines if we should sign the initial commit to this repository
133
func SignInitialCommit(ctx context.Context, repoPath string, u *user_model.User) (bool, string, *git.Signature, error) {
134
	rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit)
135
	signingKey, sig := SigningKey(ctx, repoPath)
136
	if signingKey == "" {
137
		return false, "", nil, &ErrWontSign{noKey}
138
	}
139

140
Loop:
141
	for _, rule := range rules {
142
		switch rule {
143
		case never:
144
			return false, "", nil, &ErrWontSign{never}
145
		case always:
146
			break Loop
147
		case pubkey:
148
			keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
149
				OwnerID:        u.ID,
150
				IncludeSubKeys: true,
151
			})
152
			if err != nil {
153
				return false, "", nil, err
154
			}
155
			if len(keys) == 0 {
156
				return false, "", nil, &ErrWontSign{pubkey}
157
			}
158
		case twofa:
159
			twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID)
160
			if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
161
				return false, "", nil, err
162
			}
163
			if twofaModel == nil {
164
				return false, "", nil, &ErrWontSign{twofa}
165
			}
166
		}
167
	}
168
	return true, signingKey, sig, nil
169
}
170

171
// SignWikiCommit determines if we should sign the commits to this repository wiki
172
func SignWikiCommit(ctx context.Context, repo *repo_model.Repository, u *user_model.User) (bool, string, *git.Signature, error) {
173
	repoWikiPath := repo.WikiPath()
174
	rules := signingModeFromStrings(setting.Repository.Signing.Wiki)
175
	signingKey, sig := SigningKey(ctx, repoWikiPath)
176
	if signingKey == "" {
177
		return false, "", nil, &ErrWontSign{noKey}
178
	}
179

180
Loop:
181
	for _, rule := range rules {
182
		switch rule {
183
		case never:
184
			return false, "", nil, &ErrWontSign{never}
185
		case always:
186
			break Loop
187
		case pubkey:
188
			keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
189
				OwnerID:        u.ID,
190
				IncludeSubKeys: true,
191
			})
192
			if err != nil {
193
				return false, "", nil, err
194
			}
195
			if len(keys) == 0 {
196
				return false, "", nil, &ErrWontSign{pubkey}
197
			}
198
		case twofa:
199
			twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID)
200
			if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
201
				return false, "", nil, err
202
			}
203
			if twofaModel == nil {
204
				return false, "", nil, &ErrWontSign{twofa}
205
			}
206
		case parentSigned:
207
			gitRepo, err := gitrepo.OpenWikiRepository(ctx, repo)
208
			if err != nil {
209
				return false, "", nil, err
210
			}
211
			defer gitRepo.Close()
212
			commit, err := gitRepo.GetCommit("HEAD")
213
			if err != nil {
214
				return false, "", nil, err
215
			}
216
			if commit.Signature == nil {
217
				return false, "", nil, &ErrWontSign{parentSigned}
218
			}
219
			verification := asymkey_model.ParseCommitWithSignature(ctx, commit)
220
			if !verification.Verified {
221
				return false, "", nil, &ErrWontSign{parentSigned}
222
			}
223
		}
224
	}
225
	return true, signingKey, sig, nil
226
}
227

228
// SignCRUDAction determines if we should sign a CRUD commit to this repository
229
func SignCRUDAction(ctx context.Context, repoPath string, u *user_model.User, tmpBasePath, parentCommit string) (bool, string, *git.Signature, error) {
230
	rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions)
231
	signingKey, sig := SigningKey(ctx, repoPath)
232
	if signingKey == "" {
233
		return false, "", nil, &ErrWontSign{noKey}
234
	}
235

236
Loop:
237
	for _, rule := range rules {
238
		switch rule {
239
		case never:
240
			return false, "", nil, &ErrWontSign{never}
241
		case always:
242
			break Loop
243
		case pubkey:
244
			keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
245
				OwnerID:        u.ID,
246
				IncludeSubKeys: true,
247
			})
248
			if err != nil {
249
				return false, "", nil, err
250
			}
251
			if len(keys) == 0 {
252
				return false, "", nil, &ErrWontSign{pubkey}
253
			}
254
		case twofa:
255
			twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID)
256
			if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
257
				return false, "", nil, err
258
			}
259
			if twofaModel == nil {
260
				return false, "", nil, &ErrWontSign{twofa}
261
			}
262
		case parentSigned:
263
			gitRepo, err := git.OpenRepository(ctx, tmpBasePath)
264
			if err != nil {
265
				return false, "", nil, err
266
			}
267
			defer gitRepo.Close()
268
			commit, err := gitRepo.GetCommit(parentCommit)
269
			if err != nil {
270
				return false, "", nil, err
271
			}
272
			if commit.Signature == nil {
273
				return false, "", nil, &ErrWontSign{parentSigned}
274
			}
275
			verification := asymkey_model.ParseCommitWithSignature(ctx, commit)
276
			if !verification.Verified {
277
				return false, "", nil, &ErrWontSign{parentSigned}
278
			}
279
		}
280
	}
281
	return true, signingKey, sig, nil
282
}
283

284
// SignMerge determines if we should sign a PR merge commit to the base repository
285
func SignMerge(ctx context.Context, pr *issues_model.PullRequest, u *user_model.User, tmpBasePath, baseCommit, headCommit string) (bool, string, *git.Signature, error) {
286
	if err := pr.LoadBaseRepo(ctx); err != nil {
287
		log.Error("Unable to get Base Repo for pull request")
288
		return false, "", nil, err
289
	}
290
	repo := pr.BaseRepo
291

292
	signingKey, signer := SigningKey(ctx, repo.RepoPath())
293
	if signingKey == "" {
294
		return false, "", nil, &ErrWontSign{noKey}
295
	}
296
	rules := signingModeFromStrings(setting.Repository.Signing.Merges)
297

298
	var gitRepo *git.Repository
299
	var err error
300

301
Loop:
302
	for _, rule := range rules {
303
		switch rule {
304
		case never:
305
			return false, "", nil, &ErrWontSign{never}
306
		case always:
307
			break Loop
308
		case pubkey:
309
			keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
310
				OwnerID:        u.ID,
311
				IncludeSubKeys: true,
312
			})
313
			if err != nil {
314
				return false, "", nil, err
315
			}
316
			if len(keys) == 0 {
317
				return false, "", nil, &ErrWontSign{pubkey}
318
			}
319
		case twofa:
320
			twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID)
321
			if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
322
				return false, "", nil, err
323
			}
324
			if twofaModel == nil {
325
				return false, "", nil, &ErrWontSign{twofa}
326
			}
327
		case approved:
328
			protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, pr.BaseBranch)
329
			if err != nil {
330
				return false, "", nil, err
331
			}
332
			if protectedBranch == nil {
333
				return false, "", nil, &ErrWontSign{approved}
334
			}
335
			if issues_model.GetGrantedApprovalsCount(ctx, protectedBranch, pr) < 1 {
336
				return false, "", nil, &ErrWontSign{approved}
337
			}
338
		case baseSigned:
339
			if gitRepo == nil {
340
				gitRepo, err = git.OpenRepository(ctx, tmpBasePath)
341
				if err != nil {
342
					return false, "", nil, err
343
				}
344
				defer gitRepo.Close()
345
			}
346
			commit, err := gitRepo.GetCommit(baseCommit)
347
			if err != nil {
348
				return false, "", nil, err
349
			}
350
			verification := asymkey_model.ParseCommitWithSignature(ctx, commit)
351
			if !verification.Verified {
352
				return false, "", nil, &ErrWontSign{baseSigned}
353
			}
354
		case headSigned:
355
			if gitRepo == nil {
356
				gitRepo, err = git.OpenRepository(ctx, tmpBasePath)
357
				if err != nil {
358
					return false, "", nil, err
359
				}
360
				defer gitRepo.Close()
361
			}
362
			commit, err := gitRepo.GetCommit(headCommit)
363
			if err != nil {
364
				return false, "", nil, err
365
			}
366
			verification := asymkey_model.ParseCommitWithSignature(ctx, commit)
367
			if !verification.Verified {
368
				return false, "", nil, &ErrWontSign{headSigned}
369
			}
370
		case commitsSigned:
371
			if gitRepo == nil {
372
				gitRepo, err = git.OpenRepository(ctx, tmpBasePath)
373
				if err != nil {
374
					return false, "", nil, err
375
				}
376
				defer gitRepo.Close()
377
			}
378
			commit, err := gitRepo.GetCommit(headCommit)
379
			if err != nil {
380
				return false, "", nil, err
381
			}
382
			verification := asymkey_model.ParseCommitWithSignature(ctx, commit)
383
			if !verification.Verified {
384
				return false, "", nil, &ErrWontSign{commitsSigned}
385
			}
386
			// need to work out merge-base
387
			mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit)
388
			if err != nil {
389
				return false, "", nil, err
390
			}
391
			commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit)
392
			if err != nil {
393
				return false, "", nil, err
394
			}
395
			for _, commit := range commitList {
396
				verification := asymkey_model.ParseCommitWithSignature(ctx, commit)
397
				if !verification.Verified {
398
					return false, "", nil, &ErrWontSign{commitsSigned}
399
				}
400
			}
401
		}
402
	}
403
	return true, signingKey, signer, nil
404
}
405

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.