@@ -17,30 +17,14 @@ import (
1717 "code.gitea.io/gitea/modules/log"
1818 "code.gitea.io/gitea/modules/setting"
1919 "code.gitea.io/gitea/modules/util"
20- )
2120
22- // _____ __ .__ .__ .___
23- // / _ \ __ ___/ |_| |__ ___________|__|_______ ____ __| _/
24- // / /_\ \| | \ __\ | \ / _ \_ __ \ \___ // __ \ / __ |
25- // / | \ | /| | | Y ( <_> ) | \/ |/ /\ ___// /_/ |
26- // \____|__ /____/ |__| |___| /\____/|__| |__/_____ \\___ >____ |
27- // \/ \/ \/ \/ \/
28- // ____ __.
29- // | |/ _|____ ___.__. ______
30- // | <_/ __ < | |/ ___/
31- // | | \ ___/\___ |\___ \
32- // |____|__ \___ > ____/____ >
33- // \/ \/\/ \/
34- //
35- // This file contains functions for creating authorized_keys files
36- //
37- // There is a dependence on the database within RegeneratePublicKeys however most of these functions probably belong in a module
38-
39- const (
40- tplCommentPrefix = `# gitea public key`
41- tplPublicKey = tplCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s` + "\n"
21+ "golang.org/x/crypto/ssh"
4222)
4323
24+ // AuthorizedStringCommentPrefix is a magic tag
25+ // some functions like RegeneratePublicKeys needs this tag to skip the keys generated by Gitea, while keep other keys
26+ const AuthorizedStringCommentPrefix = `# gitea public key`
27+
4428var sshOpLocker sync.Mutex
4529
4630func WithSSHOpLocker(f func() error) error {
@@ -50,17 +34,45 @@ func WithSSHOpLocker(f func() error) error {
5034}
5135
5236// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
53- func AuthorizedStringForKey(key *PublicKey) string {
37+ func AuthorizedStringForKey(key *PublicKey) ( string, error) {
5438 sb := &strings.Builder{}
55- _ = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sb, map[string]any{
39+ _, err := writeAuthorizedStringForKey(key, sb)
40+ return sb.String(), err
41+ }
42+
43+ // WriteAuthorizedStringForValidKey writes the authorized key for the provided key. If the key is invalid, it does nothing.
44+ func WriteAuthorizedStringForValidKey(key *PublicKey, w io.Writer) error {
45+ validKey, err := writeAuthorizedStringForKey(key, w)
46+ if !validKey {
47+ log.Debug("WriteAuthorizedStringForValidKey: key %s is not valid: %v", key, err)
48+ return nil
49+ }
50+ return err
51+ }
52+
53+ func writeAuthorizedStringForKey(key *PublicKey, w io.Writer) (keyValid bool, err error) {
54+ const tpl = AuthorizedStringCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s %s` + "\n"
55+ pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key.Content))
56+ if err != nil {
57+ return false, err
58+ }
59+ // now the key is valid, the code below could only return template/IO related errors
60+ sbCmd := &strings.Builder{}
61+ err = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sbCmd, map[string]any{
5662 "AppPath": util.ShellEscape(setting.AppPath),
5763 "AppWorkPath": util.ShellEscape(setting.AppWorkPath),
5864 "CustomConf": util.ShellEscape(setting.CustomConf),
5965 "CustomPath": util.ShellEscape(setting.CustomPath),
6066 "Key": key,
6167 })
62-
63- return fmt.Sprintf(tplPublicKey, util.ShellEscape(sb.String()), key.Content)
68+ if err != nil {
69+ return true, err
70+ }
71+ sshCommandEscaped := util.ShellEscape(sbCmd.String())
72+ sshKeyMarshalled := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pubKey)))
73+ sshKeyComment := fmt.Sprintf("user-%d", key.OwnerID)
74+ _, err = fmt.Fprintf(w, tpl, sshCommandEscaped, sshKeyMarshalled, sshKeyComment)
75+ return true, err
6476}
6577
6678// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
@@ -112,18 +124,17 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
112124 if key.Type == KeyTypePrincipal {
113125 continue
114126 }
115- if _, err = f.WriteString (key.AuthorizedString() ); err != nil {
127+ if err = WriteAuthorizedStringForValidKey (key, f ); err != nil {
116128 return err
117129 }
118130 }
119131 return nil
120132}
121133
122134// RegeneratePublicKeys regenerates the authorized_keys file
123- func RegeneratePublicKeys(ctx context.Context, t io.StringWriter ) error {
135+ func RegeneratePublicKeys(ctx context.Context, t io.Writer ) error {
124136 if err := db.GetEngine(ctx).Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) {
125- _, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
126- return err
137+ return WriteAuthorizedStringForValidKey(bean.(*PublicKey), t)
127138 }); err != nil {
128139 return err
129140 }
@@ -144,11 +155,11 @@ func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
144155 scanner := bufio.NewScanner(f)
145156 for scanner.Scan() {
146157 line := scanner.Text()
147- if strings.HasPrefix(line, tplCommentPrefix ) {
158+ if strings.HasPrefix(line, AuthorizedStringCommentPrefix ) {
148159 scanner.Scan()
149160 continue
150161 }
151- _, err = t .WriteString(line + "\n")
162+ _, err = io .WriteString(t, line+ "\n")
152163 if err != nil {
153164 return err
154165 }
0 commit comments