2023-02-19 19:12:01 +03:00
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"os"
"path"
"path/filepath"
"strings"
"text/template"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
gossh "golang.org/x/crypto/ssh"
)
var SSH = struct {
Disabled bool ` ini:"DISABLE_SSH" `
StartBuiltinServer bool ` ini:"START_SSH_SERVER" `
BuiltinServerUser string ` ini:"BUILTIN_SSH_SERVER_USER" `
UseProxyProtocol bool ` ini:"SSH_SERVER_USE_PROXY_PROTOCOL" `
Domain string ` ini:"SSH_DOMAIN" `
Port int ` ini:"SSH_PORT" `
User string ` ini:"SSH_USER" `
ListenHost string ` ini:"SSH_LISTEN_HOST" `
ListenPort int ` ini:"SSH_LISTEN_PORT" `
RootPath string ` ini:"SSH_ROOT_PATH" `
ServerCiphers [ ] string ` ini:"SSH_SERVER_CIPHERS" `
ServerKeyExchanges [ ] string ` ini:"SSH_SERVER_KEY_EXCHANGES" `
ServerMACs [ ] string ` ini:"SSH_SERVER_MACS" `
ServerHostKeys [ ] string ` ini:"SSH_SERVER_HOST_KEYS" `
KeyTestPath string ` ini:"SSH_KEY_TEST_PATH" `
KeygenPath string ` ini:"SSH_KEYGEN_PATH" `
AuthorizedKeysBackup bool ` ini:"SSH_AUTHORIZED_KEYS_BACKUP" `
AuthorizedPrincipalsBackup bool ` ini:"SSH_AUTHORIZED_PRINCIPALS_BACKUP" `
AuthorizedKeysCommandTemplate string ` ini:"SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE" `
AuthorizedKeysCommandTemplateTemplate * template . Template ` ini:"-" `
MinimumKeySizeCheck bool ` ini:"-" `
MinimumKeySizes map [ string ] int ` ini:"-" `
CreateAuthorizedKeysFile bool ` ini:"SSH_CREATE_AUTHORIZED_KEYS_FILE" `
CreateAuthorizedPrincipalsFile bool ` ini:"SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE" `
ExposeAnonymous bool ` ini:"SSH_EXPOSE_ANONYMOUS" `
AuthorizedPrincipalsAllow [ ] string ` ini:"SSH_AUTHORIZED_PRINCIPALS_ALLOW" `
AuthorizedPrincipalsEnabled bool ` ini:"-" `
TrustedUserCAKeys [ ] string ` ini:"SSH_TRUSTED_USER_CA_KEYS" `
TrustedUserCAKeysFile string ` ini:"SSH_TRUSTED_USER_CA_KEYS_FILENAME" `
TrustedUserCAKeysParsed [ ] gossh . PublicKey ` ini:"-" `
PerWriteTimeout time . Duration ` ini:"SSH_PER_WRITE_TIMEOUT" `
PerWritePerKbTimeout time . Duration ` ini:"SSH_PER_WRITE_PER_KB_TIMEOUT" `
} {
Disabled : false ,
StartBuiltinServer : false ,
Domain : "" ,
Port : 22 ,
ServerCiphers : [ ] string { "chacha20-poly1305@openssh.com" , "aes128-ctr" , "aes192-ctr" , "aes256-ctr" , "aes128-gcm@openssh.com" , "aes256-gcm@openssh.com" } ,
ServerKeyExchanges : [ ] string { "curve25519-sha256" , "ecdh-sha2-nistp256" , "ecdh-sha2-nistp384" , "ecdh-sha2-nistp521" , "diffie-hellman-group14-sha256" , "diffie-hellman-group14-sha1" } ,
ServerMACs : [ ] string { "hmac-sha2-256-etm@openssh.com" , "hmac-sha2-256" , "hmac-sha1" } ,
2023-04-11 09:34:28 +03:00
KeygenPath : "" ,
2023-02-19 19:12:01 +03:00
MinimumKeySizeCheck : true ,
MinimumKeySizes : map [ string ] int { "ed25519" : 256 , "ed25519-sk" : 256 , "ecdsa" : 256 , "ecdsa-sk" : 256 , "rsa" : 2047 } ,
ServerHostKeys : [ ] string { "ssh/gitea.rsa" , "ssh/gogs.rsa" } ,
AuthorizedKeysCommandTemplate : "{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}" ,
PerWriteTimeout : PerWriteTimeout ,
PerWritePerKbTimeout : PerWritePerKbTimeout ,
}
func parseAuthorizedPrincipalsAllow ( values [ ] string ) ( [ ] string , bool ) {
anything := false
email := false
username := false
for _ , value := range values {
v := strings . ToLower ( strings . TrimSpace ( value ) )
switch v {
case "off" :
return [ ] string { "off" } , false
case "email" :
email = true
case "username" :
username = true
case "anything" :
anything = true
}
}
if anything {
return [ ] string { "anything" } , true
}
authorizedPrincipalsAllow := [ ] string { }
if username {
authorizedPrincipalsAllow = append ( authorizedPrincipalsAllow , "username" )
}
if email {
authorizedPrincipalsAllow = append ( authorizedPrincipalsAllow , "email" )
}
return authorizedPrincipalsAllow , true
}
func loadSSHFrom ( rootCfg ConfigProvider ) {
sec := rootCfg . Section ( "server" )
if len ( SSH . Domain ) == 0 {
SSH . Domain = Domain
}
homeDir , err := util . HomeDir ( )
if err != nil {
log . Fatal ( "Failed to get home directory: %v" , err )
}
homeDir = strings . ReplaceAll ( homeDir , "\\" , "/" )
SSH . RootPath = path . Join ( homeDir , ".ssh" )
serverCiphers := sec . Key ( "SSH_SERVER_CIPHERS" ) . Strings ( "," )
if len ( serverCiphers ) > 0 {
SSH . ServerCiphers = serverCiphers
}
serverKeyExchanges := sec . Key ( "SSH_SERVER_KEY_EXCHANGES" ) . Strings ( "," )
if len ( serverKeyExchanges ) > 0 {
SSH . ServerKeyExchanges = serverKeyExchanges
}
serverMACs := sec . Key ( "SSH_SERVER_MACS" ) . Strings ( "," )
if len ( serverMACs ) > 0 {
SSH . ServerMACs = serverMACs
}
SSH . KeyTestPath = os . TempDir ( )
if err = sec . MapTo ( & SSH ) ; err != nil {
log . Fatal ( "Failed to map SSH settings: %v" , err )
}
for i , key := range SSH . ServerHostKeys {
if ! filepath . IsAbs ( key ) {
SSH . ServerHostKeys [ i ] = filepath . Join ( AppDataPath , key )
}
}
2023-04-11 09:34:28 +03:00
SSH . KeygenPath = sec . Key ( "SSH_KEYGEN_PATH" ) . String ( )
2023-02-19 19:12:01 +03:00
SSH . Port = sec . Key ( "SSH_PORT" ) . MustInt ( 22 )
SSH . ListenPort = sec . Key ( "SSH_LISTEN_PORT" ) . MustInt ( SSH . Port )
SSH . UseProxyProtocol = sec . Key ( "SSH_SERVER_USE_PROXY_PROTOCOL" ) . MustBool ( false )
// When disable SSH, start builtin server value is ignored.
if SSH . Disabled {
SSH . StartBuiltinServer = false
}
SSH . TrustedUserCAKeysFile = sec . Key ( "SSH_TRUSTED_USER_CA_KEYS_FILENAME" ) . MustString ( filepath . Join ( SSH . RootPath , "gitea-trusted-user-ca-keys.pem" ) )
for _ , caKey := range SSH . TrustedUserCAKeys {
pubKey , _ , _ , _ , err := gossh . ParseAuthorizedKey ( [ ] byte ( caKey ) )
if err != nil {
log . Fatal ( "Failed to parse TrustedUserCaKeys: %s %v" , caKey , err )
}
SSH . TrustedUserCAKeysParsed = append ( SSH . TrustedUserCAKeysParsed , pubKey )
}
if len ( SSH . TrustedUserCAKeys ) > 0 {
// Set the default as email,username otherwise we can leave it empty
sec . Key ( "SSH_AUTHORIZED_PRINCIPALS_ALLOW" ) . MustString ( "username,email" )
} else {
sec . Key ( "SSH_AUTHORIZED_PRINCIPALS_ALLOW" ) . MustString ( "off" )
}
SSH . AuthorizedPrincipalsAllow , SSH . AuthorizedPrincipalsEnabled = parseAuthorizedPrincipalsAllow ( sec . Key ( "SSH_AUTHORIZED_PRINCIPALS_ALLOW" ) . Strings ( "," ) )
SSH . MinimumKeySizeCheck = sec . Key ( "MINIMUM_KEY_SIZE_CHECK" ) . MustBool ( SSH . MinimumKeySizeCheck )
minimumKeySizes := rootCfg . Section ( "ssh.minimum_key_sizes" ) . Keys ( )
for _ , key := range minimumKeySizes {
if key . MustInt ( ) != - 1 {
SSH . MinimumKeySizes [ strings . ToLower ( key . Name ( ) ) ] = key . MustInt ( )
} else {
delete ( SSH . MinimumKeySizes , strings . ToLower ( key . Name ( ) ) )
}
}
SSH . AuthorizedKeysBackup = sec . Key ( "SSH_AUTHORIZED_KEYS_BACKUP" ) . MustBool ( true )
SSH . CreateAuthorizedKeysFile = sec . Key ( "SSH_CREATE_AUTHORIZED_KEYS_FILE" ) . MustBool ( true )
SSH . AuthorizedPrincipalsBackup = false
SSH . CreateAuthorizedPrincipalsFile = false
if SSH . AuthorizedPrincipalsEnabled {
SSH . AuthorizedPrincipalsBackup = sec . Key ( "SSH_AUTHORIZED_PRINCIPALS_BACKUP" ) . MustBool ( true )
SSH . CreateAuthorizedPrincipalsFile = sec . Key ( "SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE" ) . MustBool ( true )
}
SSH . ExposeAnonymous = sec . Key ( "SSH_EXPOSE_ANONYMOUS" ) . MustBool ( false )
SSH . AuthorizedKeysCommandTemplate = sec . Key ( "SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE" ) . MustString ( SSH . AuthorizedKeysCommandTemplate )
SSH . AuthorizedKeysCommandTemplateTemplate = template . Must ( template . New ( "" ) . Parse ( SSH . AuthorizedKeysCommandTemplate ) )
SSH . PerWriteTimeout = sec . Key ( "SSH_PER_WRITE_TIMEOUT" ) . MustDuration ( PerWriteTimeout )
SSH . PerWritePerKbTimeout = sec . Key ( "SSH_PER_WRITE_PER_KB_TIMEOUT" ) . MustDuration ( PerWritePerKbTimeout )
// ensure parseRunModeSetting has been executed before this
SSH . BuiltinServerUser = rootCfg . Section ( "server" ) . Key ( "BUILTIN_SSH_SERVER_USER" ) . MustString ( RunUser )
SSH . User = rootCfg . Section ( "server" ) . Key ( "SSH_USER" ) . MustString ( SSH . BuiltinServerUser )
}