2014-03-16 13:24:13 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-06-16 10:50:46 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2014-03-16 13:24:13 +04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2014-02-17 19:57:23 +04:00
package models
import (
2017-03-02 19:36:47 +03:00
"bufio"
2019-07-25 22:33:38 +03:00
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
2015-01-02 16:38:11 +03:00
"encoding/base64"
"encoding/binary"
2019-07-25 22:33:38 +03:00
"encoding/pem"
2014-03-16 13:48:20 +04:00
"errors"
2014-02-17 19:57:23 +04:00
"fmt"
2020-04-06 13:44:47 +03:00
"io"
2014-03-17 22:03:58 +04:00
"io/ioutil"
2016-02-17 01:01:56 +03:00
"math/big"
2014-02-17 19:57:23 +04:00
"os"
"path/filepath"
2020-12-25 12:59:32 +03:00
"strconv"
2014-03-16 13:24:13 +04:00
"strings"
"sync"
2014-02-17 19:57:23 +04:00
"time"
2014-03-03 00:25:09 +04:00
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2020-08-11 23:05:34 +03:00
"code.gitea.io/gitea/modules/util"
2018-09-13 05:33:48 +03:00
"golang.org/x/crypto/ssh"
2019-06-23 18:22:43 +03:00
"xorm.io/builder"
2019-10-17 12:26:49 +03:00
"xorm.io/xorm"
2014-02-17 19:57:23 +04:00
)
2014-03-17 22:03:58 +04:00
const (
2017-03-02 19:36:47 +03:00
tplCommentPrefix = ` # gitea public key `
2020-09-29 04:16:52 +03:00
tplCommand = "%s --config=%s serv key-%d"
tplPublicKey = tplCommentPrefix + "\n" + ` command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s ` + "\n"
2020-10-11 03:38:09 +03:00
authorizedPrincipalsFile = "authorized_principals"
2014-03-17 22:03:58 +04:00
)
2016-07-26 05:47:25 +03:00
var sshOpLocker sync . Mutex
2014-02-25 12:13:47 +04:00
2016-11-26 03:36:03 +03:00
// KeyType specifies the key type
2015-08-06 17:48:11 +03:00
type KeyType int
const (
2016-11-26 03:36:03 +03:00
// KeyTypeUser specifies the user key
2016-11-07 19:53:22 +03:00
KeyTypeUser = iota + 1
2016-11-26 03:36:03 +03:00
// KeyTypeDeploy specifies the deploy key
2016-11-07 19:53:22 +03:00
KeyTypeDeploy
2020-10-11 03:38:09 +03:00
// KeyTypePrincipal specifies the authorized principal key
KeyTypePrincipal
2015-08-06 17:48:11 +03:00
)
2016-07-26 05:47:25 +03:00
// PublicKey represents a user or deploy SSH public key.
2014-02-17 19:57:23 +04:00
type PublicKey struct {
2018-05-24 07:59:02 +03:00
ID int64 ` xorm:"pk autoincr" `
OwnerID int64 ` xorm:"INDEX NOT NULL" `
Name string ` xorm:"NOT NULL" `
2019-02-04 02:56:53 +03:00
Fingerprint string ` xorm:"INDEX NOT NULL" `
2018-05-24 07:59:02 +03:00
Content string ` xorm:"TEXT NOT NULL" `
Mode AccessMode ` xorm:"NOT NULL DEFAULT 2" `
Type KeyType ` xorm:"NOT NULL DEFAULT 1" `
LoginSourceID int64 ` xorm:"NOT NULL DEFAULT 0" `
2016-03-10 03:53:30 +03:00
2019-08-15 17:46:21 +03:00
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"updated" `
HasRecentActivity bool ` xorm:"-" `
HasUsed bool ` xorm:"-" `
2015-08-06 17:48:11 +03:00
}
2017-10-01 19:52:35 +03:00
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func ( key * PublicKey ) AfterLoad ( ) {
2017-12-11 07:37:04 +03:00
key . HasUsed = key . UpdatedUnix > key . CreatedUnix
2019-08-15 17:46:21 +03:00
key . HasRecentActivity = key . UpdatedUnix . AddDuration ( 7 * 24 * time . Hour ) > timeutil . TimeStampNow ( )
2014-02-17 19:57:23 +04:00
}
2016-07-26 05:47:25 +03:00
// OmitEmail returns content of public key without email address.
2016-11-26 03:36:03 +03:00
func ( key * PublicKey ) OmitEmail ( ) string {
return strings . Join ( strings . Split ( key . Content , " " ) [ : 2 ] , " " )
2014-11-23 10:33:47 +03:00
}
2016-07-26 05:47:25 +03:00
// AuthorizedString returns formatted public key string for authorized_keys file.
func ( key * PublicKey ) AuthorizedString ( ) string {
2020-09-29 04:16:52 +03:00
return fmt . Sprintf ( tplPublicKey , util . ShellEscape ( fmt . Sprintf ( tplCommand , util . ShellEscape ( setting . AppPath ) , util . ShellEscape ( setting . CustomConf ) , key . ID ) ) , key . Content )
2014-02-17 19:57:23 +04:00
}
2015-01-02 16:38:11 +03:00
func extractTypeFromBase64Key ( key string ) ( string , error ) {
b , err := base64 . StdEncoding . DecodeString ( key )
if err != nil || len ( b ) < 4 {
2016-07-26 05:47:25 +03:00
return "" , fmt . Errorf ( "invalid key format: %v" , err )
2015-01-02 16:38:11 +03:00
}
keyLength := int ( binary . BigEndian . Uint32 ( b ) )
if len ( b ) < 4 + keyLength {
2016-07-26 05:47:25 +03:00
return "" , fmt . Errorf ( "invalid key format: not enough length %d" , keyLength )
2015-01-02 16:38:11 +03:00
}
return string ( b [ 4 : 4 + keyLength ] ) , nil
}
2019-07-25 22:33:38 +03:00
const ssh2keyStart = "---- BEGIN SSH2 PUBLIC KEY ----"
2016-07-26 05:47:25 +03:00
// parseKeyString parses any key string in OpenSSH or SSH2 format to clean OpenSSH string (RFC4253).
2015-08-06 17:48:11 +03:00
func parseKeyString ( content string ) ( string , error ) {
2019-07-23 16:25:06 +03:00
// remove whitespace at start and end
2016-11-24 03:52:55 +03:00
content = strings . TrimSpace ( content )
2015-01-02 16:38:11 +03:00
var keyType , keyContent , keyComment string
2019-11-03 14:08:18 +03:00
if strings . HasPrefix ( content , ssh2keyStart ) {
2019-07-25 22:33:38 +03:00
// Parse SSH2 file format.
// Transform all legal line endings to a single "\n".
content = strings . NewReplacer ( "\r\n" , "\n" , "\r" , "\n" ) . Replace ( content )
lines := strings . Split ( content , "\n" )
continuationLine := false
for _ , line := range lines {
// Skip lines that:
// 1) are a continuation of the previous line,
// 2) contain ":" as that are comment lines
// 3) contain "-" as that are begin and end tags
if continuationLine || strings . ContainsAny ( line , ":-" ) {
continuationLine = strings . HasSuffix ( line , "\\" )
} else {
keyContent += line
}
}
t , err := extractTypeFromBase64Key ( keyContent )
if err != nil {
return "" , fmt . Errorf ( "extractTypeFromBase64Key: %v" , err )
}
keyType = t
} else {
if strings . Contains ( content , "-----BEGIN" ) {
// Convert PEM Keys to OpenSSH format
// Transform all legal line endings to a single "\n".
content = strings . NewReplacer ( "\r\n" , "\n" , "\r" , "\n" ) . Replace ( content )
block , _ := pem . Decode ( [ ] byte ( content ) )
if block == nil {
return "" , fmt . Errorf ( "failed to parse PEM block containing the public key" )
}
pub , err := x509 . ParsePKIXPublicKey ( block . Bytes )
if err != nil {
var pk rsa . PublicKey
_ , err2 := asn1 . Unmarshal ( block . Bytes , & pk )
if err2 != nil {
return "" , fmt . Errorf ( "failed to parse DER encoded public key as either PKIX or PEM RSA Key: %v %v" , err , err2 )
}
pub = & pk
}
sshKey , err := ssh . NewPublicKey ( pub )
if err != nil {
return "" , fmt . Errorf ( "unable to convert to ssh public key: %v" , err )
}
content = string ( ssh . MarshalAuthorizedKey ( sshKey ) )
}
2016-07-26 05:47:25 +03:00
// Parse OpenSSH format.
2019-07-23 16:25:06 +03:00
// Remove all newlines
content = strings . NewReplacer ( "\r\n" , "" , "\n" , "" ) . Replace ( content )
parts := strings . SplitN ( content , " " , 3 )
2015-01-02 16:38:11 +03:00
switch len ( parts ) {
case 0 :
2016-07-26 05:47:25 +03:00
return "" , errors . New ( "empty key" )
2015-01-02 16:38:11 +03:00
case 1 :
keyContent = parts [ 0 ]
case 2 :
keyType = parts [ 0 ]
keyContent = parts [ 1 ]
default :
keyType = parts [ 0 ]
keyContent = parts [ 1 ]
keyComment = parts [ 2 ]
}
2016-07-26 05:47:25 +03:00
// If keyType is not given, extract it from content. If given, validate it.
t , err := extractTypeFromBase64Key ( keyContent )
if err != nil {
return "" , fmt . Errorf ( "extractTypeFromBase64Key: %v" , err )
}
2015-01-02 16:38:11 +03:00
if len ( keyType ) == 0 {
2016-07-26 05:47:25 +03:00
keyType = t
} else if keyType != t {
return "" , fmt . Errorf ( "key type and content does not match: %s - %s" , keyType , t )
2015-01-02 16:38:11 +03:00
}
2019-07-25 22:33:38 +03:00
}
// Finally we need to check whether we can actually read the proposed key:
_ , _ , _ , _ , err := ssh . ParseAuthorizedKey ( [ ] byte ( keyType + " " + keyContent + " " + keyComment ) )
if err != nil {
return "" , fmt . Errorf ( "invalid ssh public key: %v" , err )
2015-01-02 16:38:11 +03:00
}
return keyType + " " + keyContent + " " + keyComment , nil
}
2016-02-28 04:48:39 +03:00
// writeTmpKeyFile writes key content to a temporary file
// and returns the name of that file, along with any possible errors.
func writeTmpKeyFile ( content string ) ( string , error ) {
2016-12-28 11:33:21 +03:00
tmpFile , err := ioutil . TempFile ( setting . SSH . KeyTestPath , "gitea_keytest" )
2016-02-28 04:48:39 +03:00
if err != nil {
return "" , fmt . Errorf ( "TempFile: %v" , err )
}
defer tmpFile . Close ( )
if _ , err = tmpFile . WriteString ( content ) ; err != nil {
2016-07-26 05:47:25 +03:00
return "" , fmt . Errorf ( "WriteString: %v" , err )
2016-02-28 04:48:39 +03:00
}
return tmpFile . Name ( ) , nil
}
// SSHKeyGenParsePublicKey extracts key type and length using ssh-keygen.
2016-02-17 01:01:56 +03:00
func SSHKeyGenParsePublicKey ( key string ) ( string , int , error ) {
2016-02-28 04:48:39 +03:00
tmpName , err := writeTmpKeyFile ( key )
2016-02-17 01:01:56 +03:00
if err != nil {
2016-02-28 04:48:39 +03:00
return "" , 0 , fmt . Errorf ( "writeTmpKeyFile: %v" , err )
2016-02-17 01:01:56 +03:00
}
2020-08-11 23:05:34 +03:00
defer func ( ) {
if err := util . Remove ( tmpName ) ; err != nil {
log . Warn ( "Unable to remove temporary key file: %s: Error: %v" , tmpName , err )
}
} ( )
2016-02-17 01:01:56 +03:00
2017-01-17 08:58:58 +03:00
stdout , stderr , err := process . GetManager ( ) . Exec ( "SSHKeyGenParsePublicKey" , setting . SSH . KeygenPath , "-lf" , tmpName )
2016-02-17 01:01:56 +03:00
if err != nil {
2016-07-26 05:47:25 +03:00
return "" , 0 , fmt . Errorf ( "fail to parse public key: %s - %s" , err , stderr )
2016-02-17 01:01:56 +03:00
}
2016-02-28 04:48:39 +03:00
if strings . Contains ( stdout , "is not a public key file" ) {
return "" , 0 , ErrKeyUnableVerify { stdout }
2016-02-17 01:01:56 +03:00
}
2016-02-28 04:48:39 +03:00
2016-02-17 01:01:56 +03:00
fields := strings . Split ( stdout , " " )
if len ( fields ) < 4 {
2016-07-26 05:47:25 +03:00
return "" , 0 , fmt . Errorf ( "invalid public key line: %s" , stdout )
2016-02-17 01:01:56 +03:00
}
keyType := strings . Trim ( fields [ len ( fields ) - 1 ] , "()\r\n" )
2020-12-25 12:59:32 +03:00
length , err := strconv . ParseInt ( fields [ 0 ] , 10 , 32 )
if err != nil {
return "" , 0 , err
}
return strings . ToLower ( keyType ) , int ( length ) , nil
2016-02-17 01:01:56 +03:00
}
2016-02-28 04:48:39 +03:00
// SSHNativeParsePublicKey extracts the key type and length using the golang SSH library.
2016-02-17 01:01:56 +03:00
func SSHNativeParsePublicKey ( keyLine string ) ( string , int , error ) {
fields := strings . Fields ( keyLine )
if len ( fields ) < 2 {
2017-01-27 18:03:32 +03:00
return "" , 0 , fmt . Errorf ( "not enough fields in public key line: %s" , keyLine )
2016-02-17 01:01:56 +03:00
}
raw , err := base64 . StdEncoding . DecodeString ( fields [ 1 ] )
if err != nil {
return "" , 0 , err
}
pkey , err := ssh . ParsePublicKey ( raw )
if err != nil {
2016-02-28 04:48:39 +03:00
if strings . Contains ( err . Error ( ) , "ssh: unknown key algorithm" ) {
return "" , 0 , ErrKeyUnableVerify { err . Error ( ) }
2016-02-17 01:01:56 +03:00
}
2016-07-26 05:47:25 +03:00
return "" , 0 , fmt . Errorf ( "ParsePublicKey: %v" , err )
2016-02-17 01:01:56 +03:00
}
2016-02-28 04:48:39 +03:00
// The ssh library can parse the key, so next we find out what key exactly we have.
2016-02-17 01:01:56 +03:00
switch pkey . Type ( ) {
case ssh . KeyAlgoDSA :
rawPub := struct {
Name string
P , Q , G , Y * big . Int
} { }
if err := ssh . Unmarshal ( pkey . Marshal ( ) , & rawPub ) ; err != nil {
return "" , 0 , err
}
// as per https://bugzilla.mindrot.org/show_bug.cgi?id=1647 we should never
// see dsa keys != 1024 bit, but as it seems to work, we will not check here
return "dsa" , rawPub . P . BitLen ( ) , nil // use P as per crypto/dsa/dsa.go (is L)
case ssh . KeyAlgoRSA :
rawPub := struct {
Name string
E * big . Int
N * big . Int
} { }
if err := ssh . Unmarshal ( pkey . Marshal ( ) , & rawPub ) ; err != nil {
return "" , 0 , err
}
return "rsa" , rawPub . N . BitLen ( ) , nil // use N as per crypto/rsa/rsa.go (is bits)
case ssh . KeyAlgoECDSA256 :
return "ecdsa" , 256 , nil
case ssh . KeyAlgoECDSA384 :
return "ecdsa" , 384 , nil
case ssh . KeyAlgoECDSA521 :
return "ecdsa" , 521 , nil
2017-10-26 04:49:41 +03:00
case ssh . KeyAlgoED25519 :
2016-02-17 01:01:56 +03:00
return "ed25519" , 256 , nil
2021-01-20 23:36:55 +03:00
case ssh . KeyAlgoSKECDSA256 :
return "ecdsa-sk" , 256 , nil
case ssh . KeyAlgoSKED25519 :
return "ed25519-sk" , 256 , nil
2016-02-17 01:01:56 +03:00
}
2016-07-26 05:47:25 +03:00
return "" , 0 , fmt . Errorf ( "unsupported key length detection for type: %s" , pkey . Type ( ) )
2016-02-17 01:01:56 +03:00
}
2014-07-26 08:24:27 +04:00
// CheckPublicKeyString checks if the given public key string is recognized by SSH.
2016-07-26 05:47:25 +03:00
// It returns the actual public key line on success.
2015-08-06 17:48:11 +03:00
func CheckPublicKeyString ( content string ) ( _ string , err error ) {
2016-02-28 04:48:39 +03:00
if setting . SSH . Disabled {
2017-11-21 06:49:33 +03:00
return "" , ErrSSHDisabled { }
2016-02-28 04:48:39 +03:00
}
2015-08-06 17:48:11 +03:00
content , err = parseKeyString ( content )
if err != nil {
return "" , err
}
2014-11-18 23:13:08 +03:00
content = strings . TrimRight ( content , "\n\r" )
2014-07-26 08:24:27 +04:00
if strings . ContainsAny ( content , "\n\r" ) {
2015-08-06 17:48:11 +03:00
return "" , errors . New ( "only a single line with a single key please" )
2014-07-26 08:24:27 +04:00
}
2016-02-01 06:00:55 +03:00
// remove any unnecessary whitespace now
content = strings . TrimSpace ( content )
2017-10-26 04:49:41 +03:00
if ! setting . SSH . MinimumKeySizeCheck {
return content , nil
}
2016-02-17 01:01:56 +03:00
var (
2016-07-26 05:47:25 +03:00
fnName string
2016-02-17 01:01:56 +03:00
keyType string
length int
)
2016-02-28 04:48:39 +03:00
if setting . SSH . StartBuiltinServer {
2016-07-26 05:47:25 +03:00
fnName = "SSHNativeParsePublicKey"
2016-02-17 01:01:56 +03:00
keyType , length , err = SSHNativeParsePublicKey ( content )
} else {
2016-07-26 05:47:25 +03:00
fnName = "SSHKeyGenParsePublicKey"
2016-02-28 04:48:39 +03:00
keyType , length , err = SSHKeyGenParsePublicKey ( content )
2014-07-26 08:24:27 +04:00
}
2016-01-15 13:39:51 +03:00
if err != nil {
2016-07-26 05:47:25 +03:00
return "" , fmt . Errorf ( "%s: %v" , fnName , err )
2014-07-26 08:24:27 +04:00
}
2016-02-28 04:48:39 +03:00
log . Trace ( "Key info [native: %v]: %s-%d" , setting . SSH . StartBuiltinServer , keyType , length )
2014-07-26 08:24:27 +04:00
2016-02-28 04:48:39 +03:00
if minLen , found := setting . SSH . MinimumKeySizes [ keyType ] ; found && length >= minLen {
2016-02-17 01:01:56 +03:00
return content , nil
} else if found && length < minLen {
2016-07-26 05:47:25 +03:00
return "" , fmt . Errorf ( "key length is not enough: got %d, needs %d" , length , minLen )
2016-02-17 01:01:56 +03:00
}
2016-07-26 05:47:25 +03:00
return "" , fmt . Errorf ( "key type is not allowed: %s" , keyType )
2014-07-26 08:24:27 +04:00
}
2016-07-26 05:47:25 +03:00
// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
func appendAuthorizedKeysToFile ( keys ... * PublicKey ) error {
2018-01-16 14:07:47 +03:00
// Don't need to rewrite this file if builtin SSH server is enabled.
2020-11-01 03:57:41 +03:00
if setting . SSH . StartBuiltinServer || ! setting . SSH . CreateAuthorizedKeysFile {
2018-01-16 14:07:47 +03:00
return nil
}
2014-05-07 20:09:30 +04:00
sshOpLocker . Lock ( )
defer sshOpLocker . Unlock ( )
2019-10-13 17:35:19 +03:00
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.
2021-03-14 21:52:12 +03:00
err := os . MkdirAll ( setting . SSH . RootPath , 0 o700 )
2019-10-13 17:35:19 +03:00
if err != nil {
log . Error ( "Unable to MkdirAll(%s): %v" , setting . SSH . RootPath , err )
return err
}
}
2017-06-28 04:35:35 +03:00
fPath := filepath . Join ( setting . SSH . RootPath , "authorized_keys" )
2021-03-14 21:52:12 +03:00
f , err := os . OpenFile ( fPath , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0 o600 )
2014-05-07 20:09:30 +04:00
if err != nil {
return err
}
2014-08-07 13:06:42 +04:00
defer f . Close ( )
2015-02-02 01:21:56 +03:00
2016-07-26 05:47:25 +03:00
// Note: chmod command does not support in Windows.
2014-09-16 21:34:09 +04:00
if ! setting . IsWindows {
2016-07-26 05:47:25 +03:00
fi , err := f . Stat ( )
if err != nil {
return err
}
2015-03-12 08:15:01 +03:00
// .ssh directory should have mode 700, and authorized_keys file should have mode 600.
2021-03-14 21:52:12 +03:00
if fi . Mode ( ) . Perm ( ) > 0 o600 {
2019-04-02 10:48:31 +03:00
log . Error ( "authorized_keys file has unusual permission flags: %s - setting to -rw-------" , fi . Mode ( ) . Perm ( ) . String ( ) )
2021-03-14 21:52:12 +03:00
if err = f . Chmod ( 0 o600 ) ; err != nil {
2014-09-16 16:32:13 +04:00
return err
}
2014-08-07 13:06:42 +04:00
}
2014-08-07 12:00:57 +04:00
}
2014-12-31 21:07:51 +03:00
for _ , key := range keys {
2020-10-11 03:38:09 +03:00
if key . Type == KeyTypePrincipal {
continue
}
2016-07-26 05:47:25 +03:00
if _ , err = f . WriteString ( key . AuthorizedString ( ) ) ; err != nil {
2014-12-31 21:07:51 +03:00
return err
}
}
return nil
2014-05-07 20:09:30 +04:00
}
2017-02-14 09:12:52 +03:00
// checkKeyFingerprint only checks if key fingerprint has been used as public key,
2015-08-24 22:30:39 +03:00
// it is OK to use same key as deploy key for multiple repositories/users.
2017-02-14 09:12:52 +03:00
func checkKeyFingerprint ( e Engine , fingerprint string ) error {
has , err := e . Get ( & PublicKey {
Fingerprint : fingerprint ,
2015-08-24 22:30:39 +03:00
} )
2014-03-16 14:25:16 +04:00
if err != nil {
return err
} else if has {
2017-02-14 09:12:52 +03:00
return ErrKeyAlreadyExist { 0 , fingerprint , "" }
2014-03-16 14:25:16 +04:00
}
2015-08-06 17:48:11 +03:00
return nil
}
2014-03-16 14:25:16 +04:00
2019-06-16 10:50:46 +03:00
func calcFingerprintSSHKeygen ( publicKeyContent string ) ( string , error ) {
2014-03-16 14:16:03 +04:00
// Calculate fingerprint.
2017-05-29 14:51:16 +03:00
tmpPath , err := writeTmpKeyFile ( publicKeyContent )
if err != nil {
2017-02-14 09:12:52 +03:00
return "" , err
2014-02-17 19:57:23 +04:00
}
2020-08-11 23:05:34 +03:00
defer func ( ) {
if err := util . Remove ( tmpPath ) ; err != nil {
log . Warn ( "Unable to remove temporary key file: %s: Error: %v" , tmpPath , err )
}
} ( )
2017-01-17 08:58:58 +03:00
stdout , stderr , err := process . GetManager ( ) . Exec ( "AddPublicKey" , "ssh-keygen" , "-lf" , tmpPath )
2014-02-17 19:57:23 +04:00
if err != nil {
2020-03-28 20:24:55 +03:00
if strings . Contains ( stderr , "is not a public key file" ) {
return "" , ErrKeyUnableVerify { stderr }
}
2017-02-14 09:12:52 +03:00
return "" , fmt . Errorf ( "'ssh-keygen -lf %s' failed with error '%s': %s" , tmpPath , err , stderr )
2014-03-16 14:16:03 +04:00
} else if len ( stdout ) < 2 {
2017-02-14 09:12:52 +03:00
return "" , errors . New ( "not enough output for calculating fingerprint: " + stdout )
}
return strings . Split ( stdout , " " ) [ 1 ] , nil
}
2019-06-16 10:50:46 +03:00
func calcFingerprintNative ( publicKeyContent string ) ( string , error ) {
// Calculate fingerprint.
pk , _ , _ , _ , err := ssh . ParseAuthorizedKey ( [ ] byte ( publicKeyContent ) )
if err != nil {
return "" , err
}
return ssh . FingerprintSHA256 ( pk ) , nil
}
func calcFingerprint ( publicKeyContent string ) ( string , error ) {
2021-03-14 21:52:12 +03:00
// Call the method based on configuration
2019-06-16 10:50:46 +03:00
var (
fnName , fp string
err error
)
if setting . SSH . StartBuiltinServer {
fnName = "calcFingerprintNative"
fp , err = calcFingerprintNative ( publicKeyContent )
} else {
fnName = "calcFingerprintSSHKeygen"
fp , err = calcFingerprintSSHKeygen ( publicKeyContent )
}
if err != nil {
2020-03-28 20:24:55 +03:00
if IsErrKeyUnableVerify ( err ) {
log . Info ( "%s" , publicKeyContent )
return "" , err
}
2019-06-16 10:50:46 +03:00
return "" , fmt . Errorf ( "%s: %v" , fnName , err )
}
return fp , nil
}
2017-02-14 09:12:52 +03:00
func addKey ( e Engine , key * PublicKey ) ( err error ) {
2018-10-21 00:25:14 +03:00
if len ( key . Fingerprint ) == 0 {
2017-02-14 09:12:52 +03:00
key . Fingerprint , err = calcFingerprint ( key . Content )
if err != nil {
return err
}
2014-03-16 14:16:03 +04:00
}
// Save SSH key.
2015-08-06 17:48:11 +03:00
if _ , err = e . Insert ( key ) ; err != nil {
2014-03-16 14:16:03 +04:00
return err
2015-08-06 17:48:11 +03:00
}
2015-12-11 13:02:33 +03:00
2016-07-26 05:47:25 +03:00
return appendAuthorizedKeysToFile ( key )
2015-08-06 17:48:11 +03:00
}
// AddPublicKey adds new public key to database and authorized_keys file.
2019-06-12 22:41:28 +03:00
func AddPublicKey ( ownerID int64 , name , content string , loginSourceID int64 ) ( * PublicKey , error ) {
2016-02-17 01:01:56 +03:00
log . Trace ( content )
2017-02-14 09:12:52 +03:00
fingerprint , err := calcFingerprint ( content )
if err != nil {
return nil , err
}
2019-02-04 02:56:53 +03:00
sess := x . NewSession ( )
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return nil , err
}
if err := checkKeyFingerprint ( sess , fingerprint ) ; err != nil {
2015-12-03 08:24:37 +03:00
return nil , err
2014-02-17 19:57:23 +04:00
}
2015-08-06 17:48:11 +03:00
// Key name of same user cannot be duplicated.
2019-02-04 02:56:53 +03:00
has , err := sess .
2016-11-10 18:16:32 +03:00
Where ( "owner_id = ? AND name = ?" , ownerID , name ) .
Get ( new ( PublicKey ) )
2015-08-06 17:48:11 +03:00
if err != nil {
2015-12-03 08:24:37 +03:00
return nil , err
2015-08-06 17:48:11 +03:00
} else if has {
2015-12-03 08:24:37 +03:00
return nil , ErrKeyNameAlreadyUsed { ownerID , name }
2015-08-06 17:48:11 +03:00
}
key := & PublicKey {
2018-05-24 07:59:02 +03:00
OwnerID : ownerID ,
Name : name ,
Fingerprint : fingerprint ,
Content : content ,
Mode : AccessModeWrite ,
Type : KeyTypeUser ,
2019-06-12 22:41:28 +03:00
LoginSourceID : loginSourceID ,
2015-08-06 17:48:11 +03:00
}
if err = addKey ( sess , key ) ; err != nil {
2015-12-03 08:24:37 +03:00
return nil , fmt . Errorf ( "addKey: %v" , err )
2015-08-06 17:48:11 +03:00
}
2015-12-03 08:24:37 +03:00
return key , sess . Commit ( )
2014-02-17 19:57:23 +04:00
}
2015-08-06 17:48:11 +03:00
// GetPublicKeyByID returns public key by given ID.
func GetPublicKeyByID ( keyID int64 ) ( * PublicKey , error ) {
2014-08-10 02:40:10 +04:00
key := new ( PublicKey )
2016-11-10 18:16:32 +03:00
has , err := x .
2020-03-22 18:12:55 +03:00
ID ( keyID ) .
2016-11-10 18:16:32 +03:00
Get ( key )
2014-08-10 02:40:10 +04:00
if err != nil {
return nil , err
} else if ! has {
2015-08-06 17:48:11 +03:00
return nil , ErrKeyNotExist { keyID }
2014-08-10 02:40:10 +04:00
}
return key , nil
}
2018-12-27 20:28:48 +03:00
func searchPublicKeyByContentWithEngine ( e Engine , content string ) ( * PublicKey , error ) {
2015-11-09 00:59:56 +03:00
key := new ( PublicKey )
2018-12-27 20:28:48 +03:00
has , err := e .
2016-11-10 18:16:32 +03:00
Where ( "content like ?" , content + "%" ) .
Get ( key )
2015-11-09 00:59:56 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrKeyNotExist { }
}
return key , nil
}
2018-12-27 20:28:48 +03:00
// SearchPublicKeyByContent searches content as prefix (leak e-mail part)
// and returns public key found.
func SearchPublicKeyByContent ( content string ) ( * PublicKey , error ) {
return searchPublicKeyByContentWithEngine ( x , content )
}
2020-10-11 03:38:09 +03:00
func searchPublicKeyByContentExactWithEngine ( e Engine , content string ) ( * PublicKey , error ) {
key := new ( PublicKey )
has , err := e .
Where ( "content = ?" , content ) .
Get ( key )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrKeyNotExist { }
}
return key , nil
}
// SearchPublicKeyByContentExact searches content
// and returns public key found.
func SearchPublicKeyByContentExact ( content string ) ( * PublicKey , error ) {
return searchPublicKeyByContentExactWithEngine ( x , content )
}
2018-11-01 06:40:49 +03:00
// SearchPublicKey returns a list of public keys matching the provided arguments.
func SearchPublicKey ( uid int64 , fingerprint string ) ( [ ] * PublicKey , error ) {
keys := make ( [ ] * PublicKey , 0 , 5 )
cond := builder . NewCond ( )
if uid != 0 {
cond = cond . And ( builder . Eq { "owner_id" : uid } )
}
if fingerprint != "" {
cond = cond . And ( builder . Eq { "fingerprint" : fingerprint } )
}
return keys , x . Where ( cond ) . Find ( & keys )
}
2014-11-12 14:48:50 +03:00
// ListPublicKeys returns a list of public keys belongs to given user.
2020-01-24 22:00:29 +03:00
func ListPublicKeys ( uid int64 , listOptions ListOptions ) ( [ ] * PublicKey , error ) {
2020-10-11 03:38:09 +03:00
sess := x . Where ( "owner_id = ? AND type != ?" , uid , KeyTypePrincipal )
2020-01-24 22:00:29 +03:00
if listOptions . Page != 0 {
sess = listOptions . setSessionPagination ( sess )
keys := make ( [ ] * PublicKey , 0 , listOptions . PageSize )
return keys , sess . Find ( & keys )
}
2014-07-26 08:24:27 +04:00
keys := make ( [ ] * PublicKey , 0 , 5 )
2020-01-24 22:00:29 +03:00
return keys , sess . Find ( & keys )
2014-05-07 20:09:30 +04:00
}
2018-05-24 07:59:02 +03:00
// ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user and login source.
2021-03-14 21:52:12 +03:00
func ListPublicLdapSSHKeys ( uid , loginSourceID int64 ) ( [ ] * PublicKey , error ) {
2018-05-24 07:59:02 +03:00
keys := make ( [ ] * PublicKey , 0 , 5 )
return keys , x .
2019-06-12 22:41:28 +03:00
Where ( "owner_id = ? AND login_source_id = ?" , uid , loginSourceID ) .
2018-05-24 07:59:02 +03:00
Find ( & keys )
}
2017-04-08 03:40:38 +03:00
// UpdatePublicKeyUpdated updates public key use time.
func UpdatePublicKeyUpdated ( id int64 ) error {
2017-07-20 06:15:10 +03:00
// Check if key exists before update as affected rows count is unreliable
// and will return 0 affected rows if two updates are made at the same time
if cnt , err := x . ID ( id ) . Count ( & PublicKey { } ) ; err != nil {
return err
} else if cnt != 1 {
return ErrKeyNotExist { id }
}
_ , err := x . ID ( id ) . Cols ( "updated_unix" ) . Update ( & PublicKey {
2019-08-15 17:46:21 +03:00
UpdatedUnix : timeutil . TimeStampNow ( ) ,
2017-04-08 03:40:38 +03:00
} )
if err != nil {
return err
}
return nil
}
2016-07-26 12:26:48 +03:00
// deletePublicKeys does the actual key deletion but does not update authorized_keys file.
2019-02-04 02:56:53 +03:00
func deletePublicKeys ( e Engine , keyIDs ... int64 ) error {
2016-07-26 12:26:48 +03:00
if len ( keyIDs ) == 0 {
2015-08-06 17:48:11 +03:00
return nil
2014-03-22 22:27:03 +04:00
}
2014-05-07 00:28:52 +04:00
2016-11-12 11:29:18 +03:00
_ , err := e . In ( "id" , keyIDs ) . Delete ( new ( PublicKey ) )
2016-07-26 12:26:48 +03:00
return err
2014-02-17 19:57:23 +04:00
}
2014-12-31 21:07:51 +03:00
2020-12-26 07:24:47 +03:00
// PublicKeysAreExternallyManaged returns whether the provided KeyID represents an externally managed Key
func PublicKeysAreExternallyManaged ( keys [ ] * PublicKey ) ( [ ] bool , error ) {
sources := make ( [ ] * LoginSource , 0 , 5 )
externals := make ( [ ] bool , len ( keys ) )
keyloop :
for i , key := range keys {
if key . LoginSourceID == 0 {
externals [ i ] = false
continue keyloop
}
var source * LoginSource
sourceloop :
for _ , s := range sources {
if s . ID == key . LoginSourceID {
source = s
break sourceloop
}
}
if source == nil {
var err error
source , err = GetLoginSourceByID ( key . LoginSourceID )
if err != nil {
if IsErrLoginSourceNotExist ( err ) {
externals [ i ] = false
sources [ i ] = & LoginSource {
ID : key . LoginSourceID ,
}
continue keyloop
}
return nil , err
}
}
ldapSource := source . LDAP ( )
if ldapSource != nil &&
source . IsSyncEnabled &&
( source . Type == LoginLDAP || source . Type == LoginDLDAP ) &&
len ( strings . TrimSpace ( ldapSource . AttributeSSHPublicKey ) ) > 0 {
// Disable setting SSH keys for this user
externals [ i ] = true
}
}
return externals , nil
}
// PublicKeyIsExternallyManaged returns whether the provided KeyID represents an externally managed Key
func PublicKeyIsExternallyManaged ( id int64 ) ( bool , error ) {
key , err := GetPublicKeyByID ( id )
if err != nil {
return false , err
}
if key . LoginSourceID == 0 {
return false , nil
}
source , err := GetLoginSourceByID ( key . LoginSourceID )
if err != nil {
if IsErrLoginSourceNotExist ( err ) {
return false , nil
}
return false , err
}
ldapSource := source . LDAP ( )
if ldapSource != nil &&
source . IsSyncEnabled &&
( source . Type == LoginLDAP || source . Type == LoginDLDAP ) &&
len ( strings . TrimSpace ( ldapSource . AttributeSSHPublicKey ) ) > 0 {
// Disable setting SSH keys for this user
return true , nil
}
return false , nil
}
2015-08-06 17:48:11 +03:00
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
2015-12-03 08:24:37 +03:00
func DeletePublicKey ( doer * User , id int64 ) ( err error ) {
key , err := GetPublicKeyByID ( id )
2015-08-20 12:11:29 +03:00
if err != nil {
2017-12-06 13:27:10 +03:00
return err
2015-12-03 08:24:37 +03:00
}
// Check if user has access to delete this key.
2016-07-23 20:08:22 +03:00
if ! doer . IsAdmin && doer . ID != key . OwnerID {
return ErrKeyAccessDenied { doer . ID , key . ID , "public" }
2015-08-20 12:11:29 +03:00
}
2015-08-06 17:48:11 +03:00
sess := x . NewSession ( )
2017-06-21 03:57:05 +03:00
defer sess . Close ( )
2015-08-06 17:48:11 +03:00
if err = sess . Begin ( ) ; err != nil {
return err
}
2016-07-26 12:26:48 +03:00
if err = deletePublicKeys ( sess , id ) ; err != nil {
2015-08-06 17:48:11 +03:00
return err
}
2016-07-26 12:26:48 +03:00
if err = sess . Commit ( ) ; err != nil {
return err
}
2018-12-18 19:26:26 +03:00
sess . Close ( )
2016-07-26 12:26:48 +03:00
2020-10-11 03:38:09 +03:00
if key . Type == KeyTypePrincipal {
return RewriteAllPrincipalKeys ( )
}
2016-07-26 12:26:48 +03:00
return RewriteAllPublicKeys ( )
2015-08-06 17:48:11 +03:00
}
2015-02-02 01:21:56 +03:00
// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
2016-07-26 12:26:48 +03:00
// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function
2017-01-05 03:50:34 +03:00
// outside any session scope independently.
2014-12-31 21:07:51 +03:00
func RewriteAllPublicKeys ( ) error {
2018-12-18 19:26:26 +03:00
return rewriteAllPublicKeys ( x )
}
func rewriteAllPublicKeys ( e Engine ) error {
2021-03-14 21:52:12 +03:00
// Don't rewrite key if internal server
2018-11-01 16:41:07 +03:00
if setting . SSH . StartBuiltinServer || ! setting . SSH . CreateAuthorizedKeysFile {
2018-01-16 14:07:47 +03:00
return nil
}
2015-02-02 01:21:56 +03:00
sshOpLocker . Lock ( )
defer sshOpLocker . Unlock ( )
2019-10-13 17:35:19 +03:00
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.
2021-03-14 21:52:12 +03:00
err := os . MkdirAll ( setting . SSH . RootPath , 0 o700 )
2019-10-13 17:35:19 +03:00
if err != nil {
log . Error ( "Unable to MkdirAll(%s): %v" , setting . SSH . RootPath , err )
return err
}
}
2017-06-28 04:35:35 +03:00
fPath := filepath . Join ( setting . SSH . RootPath , "authorized_keys" )
tmpPath := fPath + ".tmp"
2021-03-14 21:52:12 +03:00
t , err := os . OpenFile ( tmpPath , os . O_RDWR | os . O_CREATE | os . O_TRUNC , 0 o600 )
2014-12-31 21:07:51 +03:00
if err != nil {
return err
}
2017-03-02 19:36:47 +03:00
defer func ( ) {
2017-06-28 04:35:35 +03:00
t . Close ( )
2020-08-11 23:05:34 +03:00
if err := util . Remove ( tmpPath ) ; err != nil {
log . Warn ( "Unable to remove temporary authorized keys file: %s: Error: %v" , tmpPath , err )
}
2017-03-02 19:36:47 +03:00
} ( )
2014-12-31 21:07:51 +03:00
2020-11-28 05:42:08 +03:00
if setting . SSH . AuthorizedKeysBackup {
isExist , err := util . IsExist ( fPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , fPath , err )
2017-06-28 04:35:35 +03:00
return err
}
2020-11-28 05:42:08 +03:00
if isExist {
bakPath := fmt . Sprintf ( "%s_%d.gitea_bak" , fPath , time . Now ( ) . Unix ( ) )
2020-12-25 12:59:32 +03:00
if err = util . CopyFile ( fPath , bakPath ) ; err != nil {
2020-11-28 05:42:08 +03:00
return err
}
}
2017-06-28 04:35:35 +03:00
}
2020-04-06 13:44:47 +03:00
if err := regeneratePublicKeys ( e , t ) ; err != nil {
return err
}
t . Close ( )
return os . Rename ( tmpPath , fPath )
}
// RegeneratePublicKeys regenerates the authorized_keys file
func RegeneratePublicKeys ( t io . StringWriter ) error {
return regeneratePublicKeys ( x , t )
}
func regeneratePublicKeys ( e Engine , t io . StringWriter ) error {
2020-10-11 03:38:09 +03:00
if err := e . Where ( "type != ?" , KeyTypePrincipal ) . Iterate ( new ( PublicKey ) , func ( idx int , bean interface { } ) ( err error ) {
2017-06-28 04:35:35 +03:00
_ , err = t . WriteString ( ( bean . ( * PublicKey ) ) . AuthorizedString ( ) )
2015-02-02 01:21:56 +03:00
return err
2020-10-11 03:38:09 +03:00
} ) ; err != nil {
2015-02-02 01:21:56 +03:00
return err
2014-12-31 21:07:51 +03:00
}
2015-02-02 01:21:56 +03:00
2020-04-06 13:44:47 +03:00
fPath := filepath . Join ( setting . SSH . RootPath , "authorized_keys" )
2020-11-28 05:42:08 +03:00
isExist , err := util . IsExist ( fPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , fPath , err )
return err
}
if isExist {
2017-06-28 04:35:35 +03:00
f , err := os . Open ( fPath )
2017-03-02 19:36:47 +03:00
if err != nil {
2015-02-02 01:21:56 +03:00
return err
}
2017-06-28 04:35:35 +03:00
scanner := bufio . NewScanner ( f )
2017-03-02 19:36:47 +03:00
for scanner . Scan ( ) {
line := scanner . Text ( )
if strings . HasPrefix ( line , tplCommentPrefix ) {
scanner . Scan ( )
continue
}
2017-06-28 04:35:35 +03:00
_ , err = t . WriteString ( line + "\n" )
2017-03-02 19:36:47 +03:00
if err != nil {
2019-08-06 09:32:11 +03:00
f . Close ( )
2017-03-02 19:36:47 +03:00
return err
}
}
2019-08-06 09:32:11 +03:00
f . Close ( )
2015-02-02 01:21:56 +03:00
}
2020-04-06 13:44:47 +03:00
return nil
2014-12-31 21:07:51 +03:00
}
2015-08-06 17:48:11 +03:00
// ________ .__ ____ __.
// \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__.
// | | \_/ __ \\____ \| | / _ < | || <_/ __ < | |
// | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ |
// /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____|
// \/ \/|__| \/ \/ \/\/
// DeployKey represents deploy key information and its relation with repository.
type DeployKey struct {
2016-03-10 03:53:30 +03:00
ID int64 ` xorm:"pk autoincr" `
KeyID int64 ` xorm:"UNIQUE(s) INDEX" `
RepoID int64 ` xorm:"UNIQUE(s) INDEX" `
Name string
Fingerprint string
Content string ` xorm:"-" `
2018-01-07 01:55:53 +03:00
Mode AccessMode ` xorm:"NOT NULL DEFAULT 1" `
2019-08-15 17:46:21 +03:00
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"updated" `
HasRecentActivity bool ` xorm:"-" `
HasUsed bool ` xorm:"-" `
2015-08-06 17:48:11 +03:00
}
2017-10-01 19:52:35 +03:00
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func ( key * DeployKey ) AfterLoad ( ) {
2017-12-14 02:45:31 +03:00
key . HasUsed = key . UpdatedUnix > key . CreatedUnix
2019-08-15 17:46:21 +03:00
key . HasRecentActivity = key . UpdatedUnix . AddDuration ( 7 * 24 * time . Hour ) > timeutil . TimeStampNow ( )
2015-08-06 17:48:11 +03:00
}
2015-11-19 05:21:47 +03:00
// GetContent gets associated public key content.
2016-11-26 03:36:03 +03:00
func ( key * DeployKey ) GetContent ( ) error {
pkey , err := GetPublicKeyByID ( key . KeyID )
2015-11-19 05:21:47 +03:00
if err != nil {
return err
}
2016-11-26 03:36:03 +03:00
key . Content = pkey . Content
2015-11-19 05:21:47 +03:00
return nil
}
2018-01-07 01:55:53 +03:00
// IsReadOnly checks if the key can only be used for read operations
func ( key * DeployKey ) IsReadOnly ( ) bool {
return key . Mode == AccessModeRead
}
2015-08-06 17:48:11 +03:00
func checkDeployKey ( e Engine , keyID , repoID int64 , name string ) error {
// Note: We want error detail, not just true or false here.
2016-11-10 18:16:32 +03:00
has , err := e .
Where ( "key_id = ? AND repo_id = ?" , keyID , repoID ) .
Get ( new ( DeployKey ) )
2015-08-06 17:48:11 +03:00
if err != nil {
return err
} else if has {
return ErrDeployKeyAlreadyExist { keyID , repoID }
}
2016-11-10 18:16:32 +03:00
has , err = e .
Where ( "repo_id = ? AND name = ?" , repoID , name ) .
Get ( new ( DeployKey ) )
2015-08-06 17:48:11 +03:00
if err != nil {
return err
} else if has {
return ErrDeployKeyNameAlreadyUsed { repoID , name }
}
return nil
}
// addDeployKey adds new key-repo relation.
2018-01-07 01:55:53 +03:00
func addDeployKey ( e * xorm . Session , keyID , repoID int64 , name , fingerprint string , mode AccessMode ) ( * DeployKey , error ) {
2015-11-19 05:21:47 +03:00
if err := checkDeployKey ( e , keyID , repoID , name ) ; err != nil {
return nil , err
2015-08-06 17:48:11 +03:00
}
2015-11-19 05:21:47 +03:00
key := & DeployKey {
2015-08-06 17:48:11 +03:00
KeyID : keyID ,
RepoID : repoID ,
Name : name ,
Fingerprint : fingerprint ,
2018-01-07 01:55:53 +03:00
Mode : mode ,
2015-11-19 05:21:47 +03:00
}
_ , err := e . Insert ( key )
return key , err
2015-08-06 17:48:11 +03:00
}
// HasDeployKey returns true if public key is a deploy key of given repository.
func HasDeployKey ( keyID , repoID int64 ) bool {
2016-11-10 18:16:32 +03:00
has , _ := x .
Where ( "key_id = ? AND repo_id = ?" , keyID , repoID ) .
Get ( new ( DeployKey ) )
2015-08-06 17:48:11 +03:00
return has
}
// AddDeployKey add new deploy key to database and authorized_keys file.
2018-01-07 01:55:53 +03:00
func AddDeployKey ( repoID int64 , name , content string , readOnly bool ) ( * DeployKey , error ) {
2017-02-14 09:12:52 +03:00
fingerprint , err := calcFingerprint ( content )
if err != nil {
2015-11-19 05:21:47 +03:00
return nil , err
2015-08-06 17:48:11 +03:00
}
2018-01-07 01:55:53 +03:00
accessMode := AccessModeRead
if ! readOnly {
accessMode = AccessModeWrite
}
2019-02-04 02:56:53 +03:00
sess := x . NewSession ( )
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return nil , err
}
2015-11-19 05:21:47 +03:00
pkey := & PublicKey {
2017-02-14 09:12:52 +03:00
Fingerprint : fingerprint ,
2015-08-06 17:48:11 +03:00
}
2019-02-04 02:56:53 +03:00
has , err := sess . Get ( pkey )
2015-08-06 17:48:11 +03:00
if err != nil {
2015-11-19 05:21:47 +03:00
return nil , err
2015-08-06 17:48:11 +03:00
}
2019-02-04 02:56:53 +03:00
if has {
if pkey . Type != KeyTypeDeploy {
return nil , ErrKeyAlreadyExist { 0 , fingerprint , "" }
}
} else {
// First time use this deploy key.
pkey . Mode = accessMode
pkey . Type = KeyTypeDeploy
2017-02-14 09:12:52 +03:00
pkey . Content = content
pkey . Name = name
2015-11-19 05:21:47 +03:00
if err = addKey ( sess , pkey ) ; err != nil {
return nil , fmt . Errorf ( "addKey: %v" , err )
2015-08-06 17:48:11 +03:00
}
}
2018-01-07 01:55:53 +03:00
key , err := addDeployKey ( sess , pkey . ID , repoID , name , pkey . Fingerprint , accessMode )
2015-11-19 05:21:47 +03:00
if err != nil {
2018-09-16 18:27:43 +03:00
return nil , err
2015-08-06 17:48:11 +03:00
}
2015-11-19 05:21:47 +03:00
return key , sess . Commit ( )
}
// GetDeployKeyByID returns deploy key by given ID.
func GetDeployKeyByID ( id int64 ) ( * DeployKey , error ) {
2019-02-04 02:56:53 +03:00
return getDeployKeyByID ( x , id )
}
func getDeployKeyByID ( e Engine , id int64 ) ( * DeployKey , error ) {
2015-11-19 05:21:47 +03:00
key := new ( DeployKey )
2019-02-04 02:56:53 +03:00
has , err := e . ID ( id ) . Get ( key )
2015-11-19 05:21:47 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrDeployKeyNotExist { id , 0 , 0 }
}
return key , nil
2015-08-06 17:48:11 +03:00
}
// GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
func GetDeployKeyByRepo ( keyID , repoID int64 ) ( * DeployKey , error ) {
2019-02-04 02:56:53 +03:00
return getDeployKeyByRepo ( x , keyID , repoID )
}
func getDeployKeyByRepo ( e Engine , keyID , repoID int64 ) ( * DeployKey , error ) {
2015-08-06 17:48:11 +03:00
key := & DeployKey {
KeyID : keyID ,
RepoID : repoID ,
}
2019-02-04 02:56:53 +03:00
has , err := e . Get ( key )
2015-11-19 05:21:47 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrDeployKeyNotExist { 0 , keyID , repoID }
}
return key , nil
2015-08-06 17:48:11 +03:00
}
2017-12-14 02:45:31 +03:00
// UpdateDeployKeyCols updates deploy key information in the specified columns.
func UpdateDeployKeyCols ( key * DeployKey , cols ... string ) error {
_ , err := x . ID ( key . ID ) . Cols ( cols ... ) . Update ( key )
return err
}
2015-08-06 17:48:11 +03:00
// UpdateDeployKey updates deploy key information.
func UpdateDeployKey ( key * DeployKey ) error {
2017-10-05 07:43:04 +03:00
_ , err := x . ID ( key . ID ) . AllCols ( ) . Update ( key )
2015-08-06 17:48:11 +03:00
return err
}
// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
2015-12-03 08:24:37 +03:00
func DeleteDeployKey ( doer * User , id int64 ) error {
2019-02-04 02:56:53 +03:00
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
if err := deleteDeployKey ( sess , doer , id ) ; err != nil {
return err
}
return sess . Commit ( )
}
func deleteDeployKey ( sess Engine , doer * User , id int64 ) error {
key , err := getDeployKeyByID ( sess , id )
2015-08-06 17:48:11 +03:00
if err != nil {
2015-12-03 08:24:37 +03:00
if IsErrDeployKeyNotExist ( err ) {
return nil
}
return fmt . Errorf ( "GetDeployKeyByID: %v" , err )
}
// Check if user has access to delete this key.
2015-12-06 01:13:13 +03:00
if ! doer . IsAdmin {
2019-02-04 02:56:53 +03:00
repo , err := getRepositoryByID ( sess , key . RepoID )
2015-12-06 01:13:13 +03:00
if err != nil {
return fmt . Errorf ( "GetRepositoryByID: %v" , err )
}
2019-02-04 02:56:53 +03:00
has , err := isUserRepoAdmin ( sess , repo , doer )
2015-12-06 01:13:13 +03:00
if err != nil {
2018-11-28 14:26:14 +03:00
return fmt . Errorf ( "GetUserRepoPermission: %v" , err )
} else if ! has {
2016-07-23 20:08:22 +03:00
return ErrKeyAccessDenied { doer . ID , key . ID , "deploy" }
2015-12-06 01:13:13 +03:00
}
2015-08-06 17:48:11 +03:00
}
2017-10-05 07:43:04 +03:00
if _ , err = sess . ID ( key . ID ) . Delete ( new ( DeployKey ) ) ; err != nil {
2016-07-26 05:47:25 +03:00
return fmt . Errorf ( "delete deploy key [%d]: %v" , key . ID , err )
2015-08-06 17:48:11 +03:00
}
// Check if this is the last reference to same key content.
2016-11-10 18:16:32 +03:00
has , err := sess .
Where ( "key_id = ?" , key . KeyID ) .
Get ( new ( DeployKey ) )
2015-08-06 17:48:11 +03:00
if err != nil {
return err
} else if ! has {
2016-07-26 12:26:48 +03:00
if err = deletePublicKeys ( sess , key . KeyID ) ; err != nil {
2015-08-06 17:48:11 +03:00
return err
}
2019-01-09 21:10:46 +03:00
// after deleted the public keys, should rewrite the public keys file
if err = rewriteAllPublicKeys ( sess ) ; err != nil {
return err
}
2015-08-06 17:48:11 +03:00
}
2019-02-04 02:56:53 +03:00
return nil
2015-08-06 17:48:11 +03:00
}
// ListDeployKeys returns all deploy keys by given repository ID.
2020-01-24 22:00:29 +03:00
func ListDeployKeys ( repoID int64 , listOptions ListOptions ) ( [ ] * DeployKey , error ) {
return listDeployKeys ( x , repoID , listOptions )
2019-02-04 02:56:53 +03:00
}
2020-01-24 22:00:29 +03:00
func listDeployKeys ( e Engine , repoID int64 , listOptions ListOptions ) ( [ ] * DeployKey , error ) {
sess := e . Where ( "repo_id = ?" , repoID )
if listOptions . Page != 0 {
sess = listOptions . setSessionPagination ( sess )
keys := make ( [ ] * DeployKey , 0 , listOptions . PageSize )
return keys , sess . Find ( & keys )
}
2015-08-06 17:48:11 +03:00
keys := make ( [ ] * DeployKey , 0 , 5 )
2020-01-24 22:00:29 +03:00
return keys , sess . Find ( & keys )
2015-08-06 17:48:11 +03:00
}
2018-11-01 06:40:49 +03:00
// SearchDeployKeys returns a list of deploy keys matching the provided arguments.
2021-03-14 21:52:12 +03:00
func SearchDeployKeys ( repoID , keyID int64 , fingerprint string ) ( [ ] * DeployKey , error ) {
2018-11-01 06:40:49 +03:00
keys := make ( [ ] * DeployKey , 0 , 5 )
cond := builder . NewCond ( )
if repoID != 0 {
cond = cond . And ( builder . Eq { "repo_id" : repoID } )
}
if keyID != 0 {
cond = cond . And ( builder . Eq { "key_id" : keyID } )
}
if fingerprint != "" {
cond = cond . And ( builder . Eq { "fingerprint" : fingerprint } )
}
return keys , x . Where ( cond ) . Find ( & keys )
}
2020-10-11 03:38:09 +03:00
// __________ .__ .__ .__
// \______ _______|__| ____ ____ |_____________ | | ______
// | ___\_ __ | |/ \_/ ___\| \____ \__ \ | | / ___/
// | | | | \| | | \ \___| | |_> / __ \| |__\___ \
// |____| |__| |__|___| /\___ |__| __(____ |____/____ >
// \/ \/ |__| \/ \/
// AddPrincipalKey adds new principal to database and authorized_principals file.
func AddPrincipalKey ( ownerID int64 , content string , loginSourceID int64 ) ( * PublicKey , error ) {
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return nil , err
}
// Principals cannot be duplicated.
has , err := sess .
Where ( "content = ? AND type = ?" , content , KeyTypePrincipal ) .
Get ( new ( PublicKey ) )
if err != nil {
return nil , err
} else if has {
return nil , ErrKeyAlreadyExist { 0 , "" , content }
}
key := & PublicKey {
OwnerID : ownerID ,
Name : content ,
Content : content ,
Mode : AccessModeWrite ,
Type : KeyTypePrincipal ,
LoginSourceID : loginSourceID ,
}
if err = addPrincipalKey ( sess , key ) ; err != nil {
return nil , fmt . Errorf ( "addKey: %v" , err )
}
if err = sess . Commit ( ) ; err != nil {
return nil , err
}
sess . Close ( )
return key , RewriteAllPrincipalKeys ( )
}
func addPrincipalKey ( e Engine , key * PublicKey ) ( err error ) {
// Save Key representing a principal.
if _ , err = e . Insert ( key ) ; err != nil {
return err
}
return nil
}
// CheckPrincipalKeyString strips spaces and returns an error if the given principal contains newlines
func CheckPrincipalKeyString ( user * User , content string ) ( _ string , err error ) {
if setting . SSH . Disabled {
return "" , ErrSSHDisabled { }
}
content = strings . TrimSpace ( content )
if strings . ContainsAny ( content , "\r\n" ) {
return "" , errors . New ( "only a single line with a single principal please" )
}
// check all the allowed principals, email, username or anything
// if any matches, return ok
for _ , v := range setting . SSH . AuthorizedPrincipalsAllow {
switch v {
case "anything" :
return content , nil
case "email" :
emails , err := GetEmailAddresses ( user . ID )
if err != nil {
return "" , err
}
for _ , email := range emails {
if ! email . IsActivated {
continue
}
if content == email . Email {
return content , nil
}
}
case "username" :
if content == user . Name {
return content , nil
}
}
}
return "" , fmt . Errorf ( "didn't match allowed principals: %s" , setting . SSH . AuthorizedPrincipalsAllow )
}
// RewriteAllPrincipalKeys removes any authorized principal and rewrite all keys from database again.
// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function
// outside any session scope independently.
func RewriteAllPrincipalKeys ( ) error {
return rewriteAllPrincipalKeys ( x )
}
func rewriteAllPrincipalKeys ( e Engine ) error {
// Don't rewrite key if internal server
if setting . SSH . StartBuiltinServer || ! setting . SSH . CreateAuthorizedPrincipalsFile {
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.
2021-03-14 21:52:12 +03:00
err := os . MkdirAll ( setting . SSH . RootPath , 0 o700 )
2020-10-11 03:38:09 +03:00
if err != nil {
log . Error ( "Unable to MkdirAll(%s): %v" , setting . SSH . RootPath , err )
return err
}
}
fPath := filepath . Join ( setting . SSH . RootPath , authorizedPrincipalsFile )
tmpPath := fPath + ".tmp"
2021-03-14 21:52:12 +03:00
t , err := os . OpenFile ( tmpPath , os . O_RDWR | os . O_CREATE | os . O_TRUNC , 0 o600 )
2020-10-11 03:38:09 +03:00
if err != nil {
return err
}
defer func ( ) {
t . Close ( )
os . Remove ( tmpPath )
} ( )
2020-11-28 05:42:08 +03:00
if setting . SSH . AuthorizedPrincipalsBackup {
isExist , err := util . IsExist ( fPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , fPath , err )
2020-10-11 03:38:09 +03:00
return err
}
2020-11-28 05:42:08 +03:00
if isExist {
bakPath := fmt . Sprintf ( "%s_%d.gitea_bak" , fPath , time . Now ( ) . Unix ( ) )
2020-12-25 12:59:32 +03:00
if err = util . CopyFile ( fPath , bakPath ) ; err != nil {
2020-11-28 05:42:08 +03:00
return err
}
}
2020-10-11 03:38:09 +03:00
}
if err := regeneratePrincipalKeys ( e , t ) ; err != nil {
return err
}
t . Close ( )
return os . Rename ( tmpPath , fPath )
}
// ListPrincipalKeys returns a list of principals belongs to given user.
func ListPrincipalKeys ( uid int64 , listOptions ListOptions ) ( [ ] * PublicKey , error ) {
sess := x . Where ( "owner_id = ? AND type = ?" , uid , KeyTypePrincipal )
if listOptions . Page != 0 {
sess = listOptions . setSessionPagination ( sess )
keys := make ( [ ] * PublicKey , 0 , listOptions . PageSize )
return keys , sess . Find ( & keys )
}
keys := make ( [ ] * PublicKey , 0 , 5 )
return keys , sess . Find ( & keys )
}
// RegeneratePrincipalKeys regenerates the authorized_principals file
func RegeneratePrincipalKeys ( t io . StringWriter ) error {
return regeneratePrincipalKeys ( x , t )
}
func regeneratePrincipalKeys ( e Engine , t io . StringWriter ) error {
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 , authorizedPrincipalsFile )
2020-11-28 05:42:08 +03:00
isExist , err := util . IsExist ( fPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , fPath , err )
return err
}
if isExist {
2020-10-11 03:38:09 +03:00
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
}