2021-07-24 13:16:34 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2021-12-10 11:14:24 +03:00
package asymkey
2021-07-24 13:16:34 +03:00
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"time"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2021-07-24 13:16:34 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// _____ __ .__ .__ .___
// / _ \ __ ___/ |_| |__ ___________|__|_______ ____ __| _/
// / /_\ \| | \ __\ | \ / _ \_ __ \ \___ // __ \ / __ |
// / | \ | /| | | Y ( <_> ) | \/ |/ /\ ___// /_/ |
// \____|__ /____/ |__| |___| /\____/|__| |__/_____ \\___ >____ |
// \/ \/ \/ \/ \/
// ____ __.
// | |/ _|____ ___.__. ______
// | <_/ __ < | |/ ___/
// | | \ ___/\___ |\___ \
// |____|__ \___ > ____/____ >
// \/ \/\/ \/
//
// This file contains functions for creating authorized_keys files
//
// There is a dependence on the database within RegeneratePublicKeys however most of these functions probably belong in a module
const (
tplCommentPrefix = ` # gitea public key `
2021-11-23 05:44:26 +03:00
tplPublicKey = tplCommentPrefix + "\n" + ` command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s ` + "\n"
2021-07-24 13:16:34 +03:00
)
var sshOpLocker sync . Mutex
// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
func AuthorizedStringForKey ( key * PublicKey ) string {
sb := & strings . Builder { }
_ = setting . SSH . AuthorizedKeysCommandTemplateTemplate . Execute ( sb , map [ string ] interface { } {
"AppPath" : util . ShellEscape ( setting . AppPath ) ,
"AppWorkPath" : util . ShellEscape ( setting . AppWorkPath ) ,
"CustomConf" : util . ShellEscape ( setting . CustomConf ) ,
"CustomPath" : util . ShellEscape ( setting . CustomPath ) ,
"Key" : key ,
} )
return fmt . Sprintf ( tplPublicKey , util . ShellEscape ( sb . String ( ) ) , key . Content )
}
// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
func appendAuthorizedKeysToFile ( keys ... * PublicKey ) error {
// Don't need to rewrite this file if builtin SSH server is enabled.
if setting . SSH . StartBuiltinServer || ! setting . SSH . CreateAuthorizedKeysFile {
return nil
}
sshOpLocker . Lock ( )
defer sshOpLocker . Unlock ( )
if setting . SSH . RootPath != "" {
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
// This of course doesn't guarantee that this is the right directory for authorized_keys
// but at least if it's supposed to be this directory and it doesn't exist and we're the
// right user it will at least be created properly.
err := os . MkdirAll ( setting . SSH . RootPath , 0 o700 )
if err != nil {
log . Error ( "Unable to MkdirAll(%s): %v" , setting . SSH . RootPath , err )
return err
}
}
fPath := filepath . Join ( setting . SSH . RootPath , "authorized_keys" )
f , err := os . OpenFile ( fPath , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0 o600 )
if err != nil {
return err
}
defer f . Close ( )
// Note: chmod command does not support in Windows.
if ! setting . IsWindows {
fi , err := f . Stat ( )
if err != nil {
return err
}
// .ssh directory should have mode 700, and authorized_keys file should have mode 600.
if fi . Mode ( ) . Perm ( ) > 0 o600 {
log . Error ( "authorized_keys file has unusual permission flags: %s - setting to -rw-------" , fi . Mode ( ) . Perm ( ) . String ( ) )
if err = f . Chmod ( 0 o600 ) ; err != nil {
return err
}
}
}
for _ , key := range keys {
if key . Type == KeyTypePrincipal {
continue
}
if _ , err = f . WriteString ( key . AuthorizedString ( ) ) ; err != nil {
return err
}
}
return nil
}
// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
2021-09-23 18:45:36 +03:00
// Note: db.GetEngine(db.DefaultContext).Iterate does not get latest data after insert/delete, so we have to call this function
2021-07-24 13:16:34 +03:00
// outside any session scope independently.
func RewriteAllPublicKeys ( ) error {
// Don't rewrite key if internal server
if setting . SSH . StartBuiltinServer || ! setting . SSH . CreateAuthorizedKeysFile {
return nil
}
sshOpLocker . Lock ( )
defer sshOpLocker . Unlock ( )
if setting . SSH . RootPath != "" {
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
// This of course doesn't guarantee that this is the right directory for authorized_keys
// but at least if it's supposed to be this directory and it doesn't exist and we're the
// right user it will at least be created properly.
err := os . MkdirAll ( setting . SSH . RootPath , 0 o700 )
if err != nil {
log . Error ( "Unable to MkdirAll(%s): %v" , setting . SSH . RootPath , err )
return err
}
}
fPath := filepath . Join ( setting . SSH . RootPath , "authorized_keys" )
tmpPath := fPath + ".tmp"
t , err := os . OpenFile ( tmpPath , os . O_RDWR | os . O_CREATE | os . O_TRUNC , 0 o600 )
if err != nil {
return err
}
defer func ( ) {
t . Close ( )
if err := util . Remove ( tmpPath ) ; err != nil {
log . Warn ( "Unable to remove temporary authorized keys file: %s: Error: %v" , tmpPath , err )
}
} ( )
if setting . SSH . AuthorizedKeysBackup {
isExist , err := util . IsExist ( fPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , fPath , err )
return err
}
if isExist {
bakPath := fmt . Sprintf ( "%s_%d.gitea_bak" , fPath , time . Now ( ) . Unix ( ) )
if err = util . CopyFile ( fPath , bakPath ) ; err != nil {
return err
}
}
}
2021-12-10 11:14:24 +03:00
if err := RegeneratePublicKeys ( t ) ; err != nil {
2021-07-24 13:16:34 +03:00
return err
}
t . Close ( )
return util . Rename ( tmpPath , fPath )
}
// RegeneratePublicKeys regenerates the authorized_keys file
func RegeneratePublicKeys ( t io . StringWriter ) error {
2021-09-23 18:45:36 +03:00
return regeneratePublicKeys ( db . GetEngine ( db . DefaultContext ) , t )
2021-07-24 13:16:34 +03:00
}
2021-09-19 14:49:59 +03:00
func regeneratePublicKeys ( e db . Engine , t io . StringWriter ) error {
2021-07-24 13:16:34 +03:00
if err := e . Where ( "type != ?" , KeyTypePrincipal ) . Iterate ( new ( PublicKey ) , func ( idx int , bean interface { } ) ( err error ) {
_ , err = t . WriteString ( ( bean . ( * PublicKey ) ) . AuthorizedString ( ) )
return err
} ) ; err != nil {
return err
}
fPath := filepath . Join ( setting . SSH . RootPath , "authorized_keys" )
isExist , err := util . IsExist ( fPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , fPath , err )
return err
}
if isExist {
f , err := os . Open ( fPath )
if err != nil {
return err
}
scanner := bufio . NewScanner ( f )
for scanner . Scan ( ) {
line := scanner . Text ( )
if strings . HasPrefix ( line , tplCommentPrefix ) {
scanner . Scan ( )
continue
}
_ , err = t . WriteString ( line + "\n" )
if err != nil {
f . Close ( )
return err
}
}
f . Close ( )
}
return nil
}