code.gitea.io/gitea@v1.22.3/models/asymkey/ssh_key_authorized_keys.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package asymkey 5 6 import ( 7 "bufio" 8 "context" 9 "fmt" 10 "io" 11 "os" 12 "path/filepath" 13 "strings" 14 "sync" 15 16 "code.gitea.io/gitea/models/db" 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,no-user-rc,restrict %s` + "\n" 42 ) 43 44 var sshOpLocker sync.Mutex 45 46 func WithSSHOpLocker(f func() error) error { 47 sshOpLocker.Lock() 48 defer sshOpLocker.Unlock() 49 return f() 50 } 51 52 // AuthorizedStringForKey creates the authorized keys string appropriate for the provided key 53 func AuthorizedStringForKey(key *PublicKey) string { 54 sb := &strings.Builder{} 55 _ = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sb, map[string]any{ 56 "AppPath": util.ShellEscape(setting.AppPath), 57 "AppWorkPath": util.ShellEscape(setting.AppWorkPath), 58 "CustomConf": util.ShellEscape(setting.CustomConf), 59 "CustomPath": util.ShellEscape(setting.CustomPath), 60 "Key": key, 61 }) 62 63 return fmt.Sprintf(tplPublicKey, util.ShellEscape(sb.String()), key.Content) 64 } 65 66 // appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file. 67 func appendAuthorizedKeysToFile(keys ...*PublicKey) error { 68 // Don't need to rewrite this file if builtin SSH server is enabled. 69 if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile { 70 return nil 71 } 72 73 sshOpLocker.Lock() 74 defer sshOpLocker.Unlock() 75 76 if setting.SSH.RootPath != "" { 77 // First of ensure that the RootPath is present, and if not make it with 0700 permissions 78 // This of course doesn't guarantee that this is the right directory for authorized_keys 79 // but at least if it's supposed to be this directory and it doesn't exist and we're the 80 // right user it will at least be created properly. 81 err := os.MkdirAll(setting.SSH.RootPath, 0o700) 82 if err != nil { 83 log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err) 84 return err 85 } 86 } 87 88 fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys") 89 f, err := os.OpenFile(fPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600) 90 if err != nil { 91 return err 92 } 93 defer f.Close() 94 95 // Note: chmod command does not support in Windows. 96 if !setting.IsWindows { 97 fi, err := f.Stat() 98 if err != nil { 99 return err 100 } 101 102 // .ssh directory should have mode 700, and authorized_keys file should have mode 600. 103 if fi.Mode().Perm() > 0o600 { 104 log.Error("authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String()) 105 if err = f.Chmod(0o600); err != nil { 106 return err 107 } 108 } 109 } 110 111 for _, key := range keys { 112 if key.Type == KeyTypePrincipal { 113 continue 114 } 115 if _, err = f.WriteString(key.AuthorizedString()); err != nil { 116 return err 117 } 118 } 119 return nil 120 } 121 122 // RegeneratePublicKeys regenerates the authorized_keys file 123 func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error { 124 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 127 }); err != nil { 128 return err 129 } 130 131 fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys") 132 isExist, err := util.IsExist(fPath) 133 if err != nil { 134 log.Error("Unable to check if %s exists. Error: %v", fPath, err) 135 return err 136 } 137 if isExist { 138 f, err := os.Open(fPath) 139 if err != nil { 140 return err 141 } 142 defer f.Close() 143 144 scanner := bufio.NewScanner(f) 145 for scanner.Scan() { 146 line := scanner.Text() 147 if strings.HasPrefix(line, tplCommentPrefix) { 148 scanner.Scan() 149 continue 150 } 151 _, err = t.WriteString(line + "\n") 152 if err != nil { 153 return err 154 } 155 } 156 if err = scanner.Err(); err != nil { 157 return fmt.Errorf("RegeneratePublicKeys scan: %w", err) 158 } 159 } 160 return nil 161 }