Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc9505a2

Browse files
ChristopherHXlunnytechknowlogickwxiaoguang
authored
Improve instance wide ssh commit signing (#34341)
* Signed SSH commits can look in the UI like on GitHub, just like gpg keys today in Gitea* SSH format can be added in gitea config* SSH Signing worked before with DEFAULT_TRUST_MODEL=committer`TRUSTED_SSH_KEYS` can be a list of additional ssh public key contentsto trust for every user of this instanceCloses#34329Related#31392---------Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>Co-authored-by: techknowlogick <techknowlogick@gitea.com>Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1 parentfbc3796 commitc9505a2

File tree

22 files changed

+467
-122
lines changed

22 files changed

+467
-122
lines changed

‎custom/conf/app.example.ini‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,17 +1186,24 @@ LEVEL = Info
11861186
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
11871187
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
11881188
;;
1189-
;; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
1189+
;; GPG or SSH key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
1190+
;; Depending on the value of SIGNING_FORMAT this is either:
1191+
;; - openpgp: the GPG key ID
1192+
;; - ssh: the path to the ssh public key "/path/to/key.pub": where "/path/to/key" is the private key, use ssh-keygen -t ed25519 to generate a new key pair without password
11901193
;; run in the context of the RUN_USER
11911194
;; Switch to none to stop signing completely
11921195
;SIGNING_KEY = default
11931196
;;
1194-
;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer.
1197+
;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer and the signing format.
11951198
;; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to
1196-
;; the results of git config --get user.name andgit config --get user.email respectively and can only be overridden
1199+
;; the results of git config --get user.name,git config --get user.email and git config --default openpgp --get gpg.format respectively and can only be overridden
11971200
;; by setting the SIGNING_KEY ID to the correct ID.)
11981201
;SIGNING_NAME =
11991202
;SIGNING_EMAIL =
1203+
;; SIGNING_FORMAT can be one of:
1204+
;; - openpgp (default): use GPG to sign commits
1205+
;; - ssh: use SSH to sign commits
1206+
;SIGNING_FORMAT = openpgp
12001207
;;
12011208
;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter
12021209
;DEFAULT_TRUST_MODEL = collaborator
@@ -1223,6 +1230,13 @@ LEVEL = Info
12231230
;; - commitssigned: require that all the commits in the head branch are signed.
12241231
;; - approved: only sign when merging an approved pr to a protected branch
12251232
;MERGES = pubkey, twofa, basesigned, commitssigned
1233+
;;
1234+
;; Determines which additional ssh keys are trusted for all signed commits regardless of the user
1235+
;; This is useful for ssh signing key rotation.
1236+
;; Exposes the provided SIGNING_NAME and SIGNING_EMAIL as the signer, regardless of the SIGNING_FORMAT value.
1237+
;; Multiple keys should be comma separated.
1238+
;; E.g."ssh-<algorithm> <key>". or "ssh-<algorithm> <key1>, ssh-<algorithm> <key2>".
1239+
;TRUSTED_SSH_KEYS =
12261240

12271241
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
12281242
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

‎modules/git/command.go‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type Command struct {
4747
globalArgsLengthint
4848
brokenArgs []string
4949
cmd*exec.Cmd// for debug purpose only
50+
configArgs []string
5051
}
5152

5253
funclogArgSanitize(argstring)string {
@@ -196,6 +197,16 @@ func (c *Command) AddDashesAndList(list ...string) *Command {
196197
returnc
197198
}
198199

200+
func (c*Command)AddConfig(key,valuestring)*Command {
201+
kv:=key+"="+value
202+
if!isSafeArgumentValue(kv) {
203+
c.brokenArgs=append(c.brokenArgs,key)
204+
}else {
205+
c.configArgs=append(c.configArgs,"-c",kv)
206+
}
207+
returnc
208+
}
209+
199210
// ToTrustedCmdArgs converts a list of strings (trusted as argument) to TrustedCmdArgs
200211
// In most cases, it shouldn't be used. Use NewCommand().AddXxx() function instead
201212
funcToTrustedCmdArgs(args []string)TrustedCmdArgs {
@@ -321,7 +332,7 @@ func (c *Command) run(ctx context.Context, skip int, opts *RunOpts) error {
321332

322333
startTime:=time.Now()
323334

324-
cmd:=exec.CommandContext(ctx,c.prog,c.args...)
335+
cmd:=exec.CommandContext(ctx,c.prog,append(c.configArgs,c.args...)...)
325336
c.cmd=cmd// for debug purpose only
326337
ifopts.Env==nil {
327338
cmd.Env=os.Environ()

‎modules/git/key.go‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package git
5+
6+
// Based on https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgformat
7+
const (
8+
SigningKeyFormatOpenPGP="openpgp"// for GPG keys, the expected default of git cli
9+
SigningKeyFormatSSH="ssh"
10+
)
11+
12+
typeSigningKeystruct {
13+
KeyIDstring
14+
Formatstring
15+
}

‎modules/git/repo.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type GPGSettings struct {
2828
Emailstring
2929
Namestring
3030
PublicKeyContentstring
31+
Formatstring
3132
}
3233

3334
constprettyLogFormat=`--pretty=format:%H`

‎modules/git/repo_gpg.go‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,22 @@ package git
66

77
import (
88
"fmt"
9+
"os"
910
"strings"
1011

1112
"code.gitea.io/gitea/modules/process"
1213
)
1314

1415
// LoadPublicKeyContent will load the key from gpg
1516
func (gpgSettings*GPGSettings)LoadPublicKeyContent()error {
17+
ifgpgSettings.Format==SigningKeyFormatSSH {
18+
content,err:=os.ReadFile(gpgSettings.KeyID)
19+
iferr!=nil {
20+
returnfmt.Errorf("unable to read SSH public key file: %s, %w",gpgSettings.KeyID,err)
21+
}
22+
gpgSettings.PublicKeyContent=string(content)
23+
returnnil
24+
}
1625
content,stderr,err:=process.GetManager().Exec(
1726
"gpg -a --export",
1827
"gpg","-a","--export",gpgSettings.KeyID)
@@ -44,6 +53,9 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
4453
signingKey,_,_:=NewCommand("config","--get","user.signingkey").RunStdString(repo.Ctx,&RunOpts{Dir:repo.Path})
4554
gpgSettings.KeyID=strings.TrimSpace(signingKey)
4655

56+
format,_,_:=NewCommand("config","--default",SigningKeyFormatOpenPGP,"--get","gpg.format").RunStdString(repo.Ctx,&RunOpts{Dir:repo.Path})
57+
gpgSettings.Format=strings.TrimSpace(format)
58+
4759
defaultEmail,_,_:=NewCommand("config","--get","user.email").RunStdString(repo.Ctx,&RunOpts{Dir:repo.Path})
4860
gpgSettings.Email=strings.TrimSpace(defaultEmail)
4961

‎modules/git/repo_tree.go‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
typeCommitTreeOptsstruct {
1616
Parents []string
1717
Messagestring
18-
KeyIDstring
18+
Key*SigningKey
1919
NoGPGSignbool
2020
AlwaysSignbool
2121
}
@@ -43,8 +43,13 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
4343
_,_=messageBytes.WriteString(opts.Message)
4444
_,_=messageBytes.WriteString("\n")
4545

46-
ifopts.KeyID!=""||opts.AlwaysSign {
47-
cmd.AddOptionFormat("-S%s",opts.KeyID)
46+
ifopts.Key!=nil {
47+
ifopts.Key.Format!="" {
48+
cmd.AddConfig("gpg.format",opts.Key.Format)
49+
}
50+
cmd.AddOptionFormat("-S%s",opts.Key.KeyID)
51+
}elseifopts.AlwaysSign {
52+
cmd.AddOptionFormat("-S")
4853
}
4954

5055
ifopts.NoGPGSign {

‎modules/setting/repository.go‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,13 @@ var (
100100
SigningKeystring
101101
SigningNamestring
102102
SigningEmailstring
103+
SigningFormatstring
103104
InitialCommit []string
104105
CRUDActions []string`ini:"CRUD_ACTIONS"`
105106
Merges []string
106107
Wiki []string
107108
DefaultTrustModelstring
109+
TrustedSSHKeys []string`ini:"TRUSTED_SSH_KEYS"`
108110
}`ini:"repository.signing"`
109111
}{
110112
DetectedCharsetsOrder: []string{
@@ -242,20 +244,24 @@ var (
242244
SigningKeystring
243245
SigningNamestring
244246
SigningEmailstring
247+
SigningFormatstring
245248
InitialCommit []string
246249
CRUDActions []string`ini:"CRUD_ACTIONS"`
247250
Merges []string
248251
Wiki []string
249252
DefaultTrustModelstring
253+
TrustedSSHKeys []string`ini:"TRUSTED_SSH_KEYS"`
250254
}{
251255
SigningKey:"default",
252256
SigningName:"",
253257
SigningEmail:"",
258+
SigningFormat:"openpgp",// git.SigningKeyFormatOpenPGP
254259
InitialCommit: []string{"always"},
255260
CRUDActions: []string{"pubkey","twofa","parentsigned"},
256261
Merges: []string{"pubkey","twofa","basesigned","commitssigned"},
257262
Wiki: []string{"never"},
258263
DefaultTrustModel:"collaborator",
264+
TrustedSSHKeys: []string{},
259265
},
260266
}
261267
RepoRootPathstring

‎routers/api/v1/api.go‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,8 @@ func Routes() *web.Router {
971971
// Misc (public accessible)
972972
m.Group("",func() {
973973
m.Get("/version",misc.Version)
974-
m.Get("/signing-key.gpg",misc.SigningKey)
974+
m.Get("/signing-key.gpg",misc.SigningKeyGPG)
975+
m.Get("/signing-key.pub",misc.SigningKeySSH)
975976
m.Post("/markup",reqToken(),bind(api.MarkupOption{}),misc.Markup)
976977
m.Post("/markdown",reqToken(),bind(api.MarkdownOption{}),misc.Markdown)
977978
m.Post("/markdown/raw",reqToken(),misc.MarkdownRaw)
@@ -1427,7 +1428,8 @@ func Routes() *web.Router {
14271428
m.Combo("/file-contents",reqRepoReader(unit.TypeCode),context.ReferencesGitRepo()).
14281429
Get(repo.GetFileContentsGet).
14291430
Post(bind(api.GetFilesOptions{}),repo.GetFileContentsPost)// POST method requires "write" permission, so we also support "GET" method above
1430-
m.Get("/signing-key.gpg",misc.SigningKey)
1431+
m.Get("/signing-key.gpg",misc.SigningKeyGPG)
1432+
m.Get("/signing-key.pub",misc.SigningKeySSH)
14311433
m.Group("/topics",func() {
14321434
m.Combo("").Get(repo.ListTopics).
14331435
Put(reqToken(),reqAdmin(),bind(api.RepoTopicOptions{}),repo.UpdateTopics)

‎routers/api/v1/misc/signing.go‎

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,35 @@
44
package misc
55

66
import (
7-
"fmt"
8-
7+
"code.gitea.io/gitea/modules/git"
98
asymkey_service"code.gitea.io/gitea/services/asymkey"
109
"code.gitea.io/gitea/services/context"
1110
)
1211

13-
// SigningKey returns the public key of the default signing key if it exists
14-
funcSigningKey(ctx*context.APIContext) {
12+
funcgetSigningKey(ctx*context.APIContext,expectedFormatstring) {
13+
// if the handler is in the repo's route group, get the repo's signing key
14+
// otherwise, get the global signing key
15+
path:=""
16+
ifctx.Repo!=nil&&ctx.Repo.Repository!=nil {
17+
path=ctx.Repo.Repository.RepoPath()
18+
}
19+
content,format,err:=asymkey_service.PublicSigningKey(ctx,path)
20+
iferr!=nil {
21+
ctx.APIErrorInternal(err)
22+
return
23+
}
24+
ifformat=="" {
25+
ctx.APIErrorNotFound("no signing key")
26+
return
27+
}elseifformat!=expectedFormat {
28+
ctx.APIErrorNotFound("signing key format is "+format)
29+
return
30+
}
31+
_,_=ctx.Write([]byte(content))
32+
}
33+
34+
// SigningKeyGPG returns the public key of the default signing key if it exists
35+
funcSigningKeyGPG(ctx*context.APIContext) {
1536
// swagger:operation GET /signing-key.gpg miscellaneous getSigningKey
1637
// ---
1738
// summary: Get default signing-key.gpg
@@ -44,19 +65,42 @@ func SigningKey(ctx *context.APIContext) {
4465
// description: "GPG armored public key"
4566
// schema:
4667
// type: string
68+
getSigningKey(ctx,git.SigningKeyFormatOpenPGP)
69+
}
4770

48-
path:=""
49-
ifctx.Repo!=nil&&ctx.Repo.Repository!=nil {
50-
path=ctx.Repo.Repository.RepoPath()
51-
}
71+
// SigningKeySSH returns the public key of the default signing key if it exists
72+
funcSigningKeySSH(ctx*context.APIContext) {
73+
// swagger:operation GET /signing-key.pub miscellaneous getSigningKeySSH
74+
// ---
75+
// summary: Get default signing-key.pub
76+
// produces:
77+
// - text/plain
78+
// responses:
79+
// "200":
80+
// description: "ssh public key"
81+
// schema:
82+
// type: string
5283

53-
content,err:=asymkey_service.PublicSigningKey(ctx,path)
54-
iferr!=nil {
55-
ctx.APIErrorInternal(err)
56-
return
57-
}
58-
_,err=ctx.Write([]byte(content))
59-
iferr!=nil {
60-
ctx.APIErrorInternal(fmt.Errorf("Error writing key content %w",err))
61-
}
84+
// swagger:operation GET /repos/{owner}/{repo}/signing-key.pub repository repoSigningKeySSH
85+
// ---
86+
// summary: Get signing-key.pub for given repository
87+
// produces:
88+
// - text/plain
89+
// parameters:
90+
// - name: owner
91+
// in: path
92+
// description: owner of the repo
93+
// type: string
94+
// required: true
95+
// - name: repo
96+
// in: path
97+
// description: name of the repo
98+
// type: string
99+
// required: true
100+
// responses:
101+
// "200":
102+
// description: "ssh public key"
103+
// schema:
104+
// type: string
105+
getSigningKey(ctx,git.SigningKeyFormatSSH)
62106
}

‎routers/web/repo/setting/setting.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func SettingsCtxData(ctx *context.Context) {
6262
ctx.Data["CanConvertFork"]=ctx.Repo.Repository.IsFork&&ctx.Doer.CanCreateRepoIn(ctx.Repo.Repository.Owner)
6363

6464
signing,_:=asymkey_service.SigningKey(ctx,ctx.Repo.Repository.RepoPath())
65-
ctx.Data["SigningKeyAvailable"]=len(signing)>0
65+
ctx.Data["SigningKeyAvailable"]=signing!=nil
6666
ctx.Data["SigningSettings"]=setting.Repository.Signing
6767
ctx.Data["IsRepoIndexerEnabled"]=setting.Indexer.RepoIndexerEnabled
6868

@@ -105,7 +105,7 @@ func SettingsPost(ctx *context.Context) {
105105
ctx.Data["MinimumMirrorInterval"]=setting.Mirror.MinInterval
106106

107107
signing,_:=asymkey_service.SigningKey(ctx,ctx.Repo.Repository.RepoPath())
108-
ctx.Data["SigningKeyAvailable"]=len(signing)>0
108+
ctx.Data["SigningKeyAvailable"]=signing!=nil
109109
ctx.Data["SigningSettings"]=setting.Repository.Signing
110110
ctx.Data["IsRepoIndexerEnabled"]=setting.Indexer.RepoIndexerEnabled
111111

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp