Skip to content

Commit 1116a1f

Browse files
committed
Restructure ssh_key.go
- move functions from models/user.go that relate to ssh_key to ssh_key - split ssh_key.go to try create clearer function domains for allow for future refactors here. Signed-off-by: Andrew Thornton <[email protected]>
1 parent d98bbbc commit 1116a1f

8 files changed

+1288
-1130
lines changed

models/ssh_key.go

Lines changed: 98 additions & 992 deletions
Large diffs are not rendered by default.

models/ssh_key_authorized_keys.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
import (
8+
"bufio"
9+
"fmt"
10+
"io"
11+
"os"
12+
"path/filepath"
13+
"strings"
14+
"sync"
15+
"time"
16+
17+
"code.gitea.io/gitea/modules/log"
18+
"code.gitea.io/gitea/modules/setting"
19+
"code.gitea.io/gitea/modules/util"
20+
)
21+
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 %s` + "\n"
42+
)
43+
44+
var sshOpLocker sync.Mutex
45+
46+
func AuthorizedStringForKey(key *PublicKey) string {
47+
sb := &strings.Builder{}
48+
_ = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sb, map[string]interface{}{
49+
"AppPath": util.ShellEscape(setting.AppPath),
50+
"AppWorkPath": util.ShellEscape(setting.AppWorkPath),
51+
"CustomConf": util.ShellEscape(setting.CustomConf),
52+
"CustomPath": util.ShellEscape(setting.CustomPath),
53+
"Key": key,
54+
})
55+
56+
return fmt.Sprintf(tplPublicKey, util.ShellEscape(sb.String()), key.Content)
57+
}
58+
59+
// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
60+
func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
61+
// Don't need to rewrite this file if builtin SSH server is enabled.
62+
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
63+
return nil
64+
}
65+
66+
sshOpLocker.Lock()
67+
defer sshOpLocker.Unlock()
68+
69+
if setting.SSH.RootPath != "" {
70+
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
71+
// This of course doesn't guarantee that this is the right directory for authorized_keys
72+
// but at least if it's supposed to be this directory and it doesn't exist and we're the
73+
// right user it will at least be created properly.
74+
err := os.MkdirAll(setting.SSH.RootPath, 0o700)
75+
if err != nil {
76+
log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
77+
return err
78+
}
79+
}
80+
81+
fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
82+
f, err := os.OpenFile(fPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
83+
if err != nil {
84+
return err
85+
}
86+
defer f.Close()
87+
88+
// Note: chmod command does not support in Windows.
89+
if !setting.IsWindows {
90+
fi, err := f.Stat()
91+
if err != nil {
92+
return err
93+
}
94+
95+
// .ssh directory should have mode 700, and authorized_keys file should have mode 600.
96+
if fi.Mode().Perm() > 0o600 {
97+
log.Error("authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String())
98+
if err = f.Chmod(0o600); err != nil {
99+
return err
100+
}
101+
}
102+
}
103+
104+
for _, key := range keys {
105+
if key.Type == KeyTypePrincipal {
106+
continue
107+
}
108+
if _, err = f.WriteString(key.AuthorizedString()); err != nil {
109+
return err
110+
}
111+
}
112+
return nil
113+
}
114+
115+
// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
116+
// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function
117+
// outside any session scope independently.
118+
func RewriteAllPublicKeys() error {
119+
return rewriteAllPublicKeys(x)
120+
}
121+
122+
func rewriteAllPublicKeys(e Engine) error {
123+
// Don't rewrite key if internal server
124+
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
125+
return nil
126+
}
127+
128+
sshOpLocker.Lock()
129+
defer sshOpLocker.Unlock()
130+
131+
if setting.SSH.RootPath != "" {
132+
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
133+
// This of course doesn't guarantee that this is the right directory for authorized_keys
134+
// but at least if it's supposed to be this directory and it doesn't exist and we're the
135+
// right user it will at least be created properly.
136+
err := os.MkdirAll(setting.SSH.RootPath, 0o700)
137+
if err != nil {
138+
log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
139+
return err
140+
}
141+
}
142+
143+
fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
144+
tmpPath := fPath + ".tmp"
145+
t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
146+
if err != nil {
147+
return err
148+
}
149+
defer func() {
150+
t.Close()
151+
if err := util.Remove(tmpPath); err != nil {
152+
log.Warn("Unable to remove temporary authorized keys file: %s: Error: %v", tmpPath, err)
153+
}
154+
}()
155+
156+
if setting.SSH.AuthorizedKeysBackup {
157+
isExist, err := util.IsExist(fPath)
158+
if err != nil {
159+
log.Error("Unable to check if %s exists. Error: %v", fPath, err)
160+
return err
161+
}
162+
if isExist {
163+
bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix())
164+
if err = util.CopyFile(fPath, bakPath); err != nil {
165+
return err
166+
}
167+
}
168+
}
169+
170+
if err := regeneratePublicKeys(e, t); err != nil {
171+
return err
172+
}
173+
174+
t.Close()
175+
return os.Rename(tmpPath, fPath)
176+
}
177+
178+
// RegeneratePublicKeys regenerates the authorized_keys file
179+
func RegeneratePublicKeys(t io.StringWriter) error {
180+
return regeneratePublicKeys(x, t)
181+
}
182+
183+
func regeneratePublicKeys(e Engine, t io.StringWriter) error {
184+
if err := e.Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
185+
_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
186+
return err
187+
}); err != nil {
188+
return err
189+
}
190+
191+
fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
192+
isExist, err := util.IsExist(fPath)
193+
if err != nil {
194+
log.Error("Unable to check if %s exists. Error: %v", fPath, err)
195+
return err
196+
}
197+
if isExist {
198+
f, err := os.Open(fPath)
199+
if err != nil {
200+
return err
201+
}
202+
scanner := bufio.NewScanner(f)
203+
for scanner.Scan() {
204+
line := scanner.Text()
205+
if strings.HasPrefix(line, tplCommentPrefix) {
206+
scanner.Scan()
207+
continue
208+
}
209+
_, err = t.WriteString(line + "\n")
210+
if err != nil {
211+
f.Close()
212+
return err
213+
}
214+
}
215+
f.Close()
216+
}
217+
return nil
218+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
import (
8+
"bufio"
9+
"fmt"
10+
"io"
11+
"os"
12+
"path/filepath"
13+
"strings"
14+
"time"
15+
16+
"code.gitea.io/gitea/modules/log"
17+
"code.gitea.io/gitea/modules/setting"
18+
"code.gitea.io/gitea/modules/util"
19+
)
20+
21+
// _____ __ .__ .__ .___
22+
// / _ \ __ ___/ |_| |__ ___________|__|_______ ____ __| _/
23+
// / /_\ \| | \ __\ | \ / _ \_ __ \ \___ // __ \ / __ |
24+
// / | \ | /| | | Y ( <_> ) | \/ |/ /\ ___// /_/ |
25+
// \____|__ /____/ |__| |___| /\____/|__| |__/_____ \\___ >____ |
26+
// \/ \/ \/ \/ \/
27+
// __________ .__ .__ .__
28+
// \______ _______|__| ____ ____ |_____________ | | ______
29+
// | ___\_ __ | |/ \_/ ___\| \____ \__ \ | | / ___/
30+
// | | | | \| | | \ \___| | |_> / __ \| |__\___ \
31+
// |____| |__| |__|___| /\___ |__| __(____ |____/____ >
32+
// \/ \/ |__| \/ \/
33+
//
34+
// This file contains functions for creating authorized_principals files
35+
//
36+
// There is a dependence on the database within RewriteAllPrincipalKeys & RegeneratePrincipalKeys
37+
// The sshOpLocker is used from ssh_key_authorized_keys.go
38+
39+
const authorizedPrincipalsFile = "authorized_principals"
40+
41+
// RewriteAllPrincipalKeys removes any authorized principal and rewrite all keys from database again.
42+
// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function
43+
// outside any session scope independently.
44+
func RewriteAllPrincipalKeys() error {
45+
return rewriteAllPrincipalKeys(x)
46+
}
47+
48+
func rewriteAllPrincipalKeys(e Engine) error {
49+
// Don't rewrite key if internal server
50+
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedPrincipalsFile {
51+
return nil
52+
}
53+
54+
sshOpLocker.Lock()
55+
defer sshOpLocker.Unlock()
56+
57+
if setting.SSH.RootPath != "" {
58+
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
59+
// This of course doesn't guarantee that this is the right directory for authorized_keys
60+
// but at least if it's supposed to be this directory and it doesn't exist and we're the
61+
// right user it will at least be created properly.
62+
err := os.MkdirAll(setting.SSH.RootPath, 0o700)
63+
if err != nil {
64+
log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
65+
return err
66+
}
67+
}
68+
69+
fPath := filepath.Join(setting.SSH.RootPath, authorizedPrincipalsFile)
70+
tmpPath := fPath + ".tmp"
71+
t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
72+
if err != nil {
73+
return err
74+
}
75+
defer func() {
76+
t.Close()
77+
os.Remove(tmpPath)
78+
}()
79+
80+
if setting.SSH.AuthorizedPrincipalsBackup {
81+
isExist, err := util.IsExist(fPath)
82+
if err != nil {
83+
log.Error("Unable to check if %s exists. Error: %v", fPath, err)
84+
return err
85+
}
86+
if isExist {
87+
bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix())
88+
if err = util.CopyFile(fPath, bakPath); err != nil {
89+
return err
90+
}
91+
}
92+
}
93+
94+
if err := regeneratePrincipalKeys(e, t); err != nil {
95+
return err
96+
}
97+
98+
t.Close()
99+
return os.Rename(tmpPath, fPath)
100+
}
101+
102+
// RegeneratePrincipalKeys regenerates the authorized_principals file
103+
func RegeneratePrincipalKeys(t io.StringWriter) error {
104+
return regeneratePrincipalKeys(x, t)
105+
}
106+
107+
func regeneratePrincipalKeys(e Engine, t io.StringWriter) error {
108+
if err := e.Where("type = ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
109+
_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
110+
return err
111+
}); err != nil {
112+
return err
113+
}
114+
115+
fPath := filepath.Join(setting.SSH.RootPath, authorizedPrincipalsFile)
116+
isExist, err := util.IsExist(fPath)
117+
if err != nil {
118+
log.Error("Unable to check if %s exists. Error: %v", fPath, err)
119+
return err
120+
}
121+
if isExist {
122+
f, err := os.Open(fPath)
123+
if err != nil {
124+
return err
125+
}
126+
scanner := bufio.NewScanner(f)
127+
for scanner.Scan() {
128+
line := scanner.Text()
129+
if strings.HasPrefix(line, tplCommentPrefix) {
130+
scanner.Scan()
131+
continue
132+
}
133+
_, err = t.WriteString(line + "\n")
134+
if err != nil {
135+
f.Close()
136+
return err
137+
}
138+
}
139+
f.Close()
140+
}
141+
return nil
142+
}

0 commit comments

Comments
 (0)