Skip to content

Commit 5d26d16

Browse files
committed
add test
1 parent 00fe4b3 commit 5d26d16

File tree

2 files changed

+108
-14
lines changed

2 files changed

+108
-14
lines changed

models/asymkey/ssh_key_authorized_keys.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,28 +51,32 @@ func WriteAuthorizedStringForValidKey(key *PublicKey, w io.Writer) error {
5151
return err
5252
}
5353

54-
// principalRegexp expresses whether a principal is considered valid.
55-
// This reverse engineers how sshd parses the authorized keys file,
56-
// see e.g. https://github.com/openssh/openssh-portable/blob/32deb00b38b4ee2b3302f261ea1e68c04e020a08/auth2-pubkeyfile.c#L221-L256
57-
// Any newline or # comment will be stripped when parsing, so don't allow
58-
// those. Also, if any space or tab is present in the principal, the part
59-
// proceeding this would be parsed as an option, so just avoid any whitespace
60-
// altogether.
61-
var principalRegexp = regexp.MustCompile(`^[^\s#]+$`)
54+
var globalVars = sync.OnceValue(func() (ret struct {
55+
principalRegexp *regexp.Regexp
56+
},
57+
) {
58+
// principalRegexp expresses whether a principal is considered valid.
59+
// This reverse engineers how sshd parses the authorized keys file,
60+
// see e.g. https://github.com/openssh/openssh-portable/blob/32deb00b38b4ee2b3302f261ea1e68c04e020a08/auth2-pubkeyfile.c#L221-L256
61+
// Any newline or # comment will be stripped when parsing, so don't allow
62+
// those. Also, if any space or tab is present in the principal, the part
63+
// proceeding this would be parsed as an option, so just avoid any whitespace
64+
// altogether.
65+
ret.principalRegexp = regexp.MustCompile(`^[^\s#]+$`)
66+
return ret
67+
})
6268

6369
func writeAuthorizedStringForKey(key *PublicKey, w io.Writer) (keyValid bool, err error) {
6470
const tpl = AuthorizedStringCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s` + "\n"
6571

6672
var sshKey string
6773

6874
if key.Type == KeyTypePrincipal {
69-
principal := strings.TrimSpace(key.Content)
70-
71-
if matched := principalRegexp.MatchString(principal); !matched {
72-
return false, fmt.Errorf("does not match %s", principalRegexp.String())
75+
// TODO: actually using PublicKey to store "principal" is an abuse
76+
if !globalVars().principalRegexp.MatchString(key.Content) {
77+
return false, fmt.Errorf("invalid principal key: %s", key.Content)
7378
}
74-
75-
sshKey = fmt.Sprintf("%s # user-%d", principal, key.OwnerID)
79+
sshKey = fmt.Sprintf("%s # user-%d", key.Content, key.OwnerID)
7680
} else {
7781
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key.Content))
7882
if err != nil {
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package asymkey
5+
6+
import (
7+
"strings"
8+
"testing"
9+
10+
"code.gitea.io/gitea/modules/setting"
11+
"code.gitea.io/gitea/modules/test"
12+
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestWriteAuthorizedStringForKey(t *testing.T) {
17+
defer test.MockVariableValue(&setting.AppPath, "/tmp/gitea")()
18+
defer test.MockVariableValue(&setting.CustomConf, "/tmp/app.ini")()
19+
writeKey := func(t *testing.T, key *PublicKey) (bool, string, error) {
20+
sb := &strings.Builder{}
21+
valid, err := writeAuthorizedStringForKey(key, sb)
22+
return valid, sb.String(), err
23+
}
24+
const validKeyContent = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf`
25+
26+
testValid := func(t *testing.T, key *PublicKey, expected string) {
27+
valid, content, err := writeKey(t, key)
28+
assert.True(t, valid)
29+
assert.Equal(t, expected, content)
30+
assert.NoError(t, err)
31+
}
32+
33+
testInvalid := func(t *testing.T, key *PublicKey) {
34+
valid, content, err := writeKey(t, key)
35+
assert.False(t, valid)
36+
assert.Empty(t, content)
37+
assert.Error(t, err)
38+
}
39+
40+
t.Run("PublicKey", func(t *testing.T) {
41+
testValid(t, &PublicKey{
42+
OwnerID: 123,
43+
Content: validKeyContent + " any-comment",
44+
Type: KeyTypeUser,
45+
}, `# gitea public key
46+
command="/tmp/gitea --config=/tmp/app.ini serv key-0",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf user-123
47+
`)
48+
})
49+
50+
t.Run("PublicKeyWithNewLine", func(t *testing.T) {
51+
testValid(t, &PublicKey{
52+
OwnerID: 123,
53+
Content: validKeyContent + "\nany-more", // the new line should be ignored
54+
Type: KeyTypeUser,
55+
}, `# gitea public key
56+
command="/tmp/gitea --config=/tmp/app.ini serv key-0",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf user-123
57+
`)
58+
})
59+
60+
t.Run("PublicKeyInvalid", func(t *testing.T) {
61+
testInvalid(t, &PublicKey{
62+
OwnerID: 123,
63+
Content: validKeyContent + "any-more",
64+
Type: KeyTypeUser,
65+
})
66+
})
67+
68+
t.Run("Principal", func(t *testing.T) {
69+
testValid(t, &PublicKey{
70+
OwnerID: 123,
71+
Content: "any-content",
72+
Type: KeyTypePrincipal,
73+
}, `# gitea public key
74+
command="/tmp/gitea --config=/tmp/app.ini serv key-0",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict any-content # user-123
75+
`)
76+
})
77+
78+
t.Run("PrincipalInvalid", func(t *testing.T) {
79+
testInvalid(t, &PublicKey{
80+
OwnerID: 123,
81+
Content: "a b",
82+
Type: KeyTypePrincipal,
83+
})
84+
testInvalid(t, &PublicKey{
85+
OwnerID: 123,
86+
Content: "a\nb",
87+
Type: KeyTypePrincipal,
88+
})
89+
})
90+
}

0 commit comments

Comments
 (0)