code.gitea.io/gitea@v1.19.3/modules/setting/ssh.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package setting
     5  
     6  import (
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"strings"
    11  	"text/template"
    12  	"time"
    13  
    14  	"code.gitea.io/gitea/modules/log"
    15  	"code.gitea.io/gitea/modules/util"
    16  
    17  	gossh "golang.org/x/crypto/ssh"
    18  )
    19  
    20  var SSH = struct {
    21  	Disabled                              bool               `ini:"DISABLE_SSH"`
    22  	StartBuiltinServer                    bool               `ini:"START_SSH_SERVER"`
    23  	BuiltinServerUser                     string             `ini:"BUILTIN_SSH_SERVER_USER"`
    24  	UseProxyProtocol                      bool               `ini:"SSH_SERVER_USE_PROXY_PROTOCOL"`
    25  	Domain                                string             `ini:"SSH_DOMAIN"`
    26  	Port                                  int                `ini:"SSH_PORT"`
    27  	User                                  string             `ini:"SSH_USER"`
    28  	ListenHost                            string             `ini:"SSH_LISTEN_HOST"`
    29  	ListenPort                            int                `ini:"SSH_LISTEN_PORT"`
    30  	RootPath                              string             `ini:"SSH_ROOT_PATH"`
    31  	ServerCiphers                         []string           `ini:"SSH_SERVER_CIPHERS"`
    32  	ServerKeyExchanges                    []string           `ini:"SSH_SERVER_KEY_EXCHANGES"`
    33  	ServerMACs                            []string           `ini:"SSH_SERVER_MACS"`
    34  	ServerHostKeys                        []string           `ini:"SSH_SERVER_HOST_KEYS"`
    35  	KeyTestPath                           string             `ini:"SSH_KEY_TEST_PATH"`
    36  	KeygenPath                            string             `ini:"SSH_KEYGEN_PATH"`
    37  	AuthorizedKeysBackup                  bool               `ini:"SSH_AUTHORIZED_KEYS_BACKUP"`
    38  	AuthorizedPrincipalsBackup            bool               `ini:"SSH_AUTHORIZED_PRINCIPALS_BACKUP"`
    39  	AuthorizedKeysCommandTemplate         string             `ini:"SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE"`
    40  	AuthorizedKeysCommandTemplateTemplate *template.Template `ini:"-"`
    41  	MinimumKeySizeCheck                   bool               `ini:"-"`
    42  	MinimumKeySizes                       map[string]int     `ini:"-"`
    43  	CreateAuthorizedKeysFile              bool               `ini:"SSH_CREATE_AUTHORIZED_KEYS_FILE"`
    44  	CreateAuthorizedPrincipalsFile        bool               `ini:"SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE"`
    45  	ExposeAnonymous                       bool               `ini:"SSH_EXPOSE_ANONYMOUS"`
    46  	AuthorizedPrincipalsAllow             []string           `ini:"SSH_AUTHORIZED_PRINCIPALS_ALLOW"`
    47  	AuthorizedPrincipalsEnabled           bool               `ini:"-"`
    48  	TrustedUserCAKeys                     []string           `ini:"SSH_TRUSTED_USER_CA_KEYS"`
    49  	TrustedUserCAKeysFile                 string             `ini:"SSH_TRUSTED_USER_CA_KEYS_FILENAME"`
    50  	TrustedUserCAKeysParsed               []gossh.PublicKey  `ini:"-"`
    51  	PerWriteTimeout                       time.Duration      `ini:"SSH_PER_WRITE_TIMEOUT"`
    52  	PerWritePerKbTimeout                  time.Duration      `ini:"SSH_PER_WRITE_PER_KB_TIMEOUT"`
    53  }{
    54  	Disabled:                      false,
    55  	StartBuiltinServer:            false,
    56  	Domain:                        "",
    57  	Port:                          22,
    58  	ServerCiphers:                 []string{"chacha20-poly1305@openssh.com", "aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "aes256-gcm@openssh.com"},
    59  	ServerKeyExchanges:            []string{"curve25519-sha256", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group14-sha256", "diffie-hellman-group14-sha1"},
    60  	ServerMACs:                    []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1"},
    61  	KeygenPath:                    "ssh-keygen",
    62  	MinimumKeySizeCheck:           true,
    63  	MinimumKeySizes:               map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 2047},
    64  	ServerHostKeys:                []string{"ssh/gitea.rsa", "ssh/gogs.rsa"},
    65  	AuthorizedKeysCommandTemplate: "{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}",
    66  	PerWriteTimeout:               PerWriteTimeout,
    67  	PerWritePerKbTimeout:          PerWritePerKbTimeout,
    68  }
    69  
    70  func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) {
    71  	anything := false
    72  	email := false
    73  	username := false
    74  	for _, value := range values {
    75  		v := strings.ToLower(strings.TrimSpace(value))
    76  		switch v {
    77  		case "off":
    78  			return []string{"off"}, false
    79  		case "email":
    80  			email = true
    81  		case "username":
    82  			username = true
    83  		case "anything":
    84  			anything = true
    85  		}
    86  	}
    87  	if anything {
    88  		return []string{"anything"}, true
    89  	}
    90  
    91  	authorizedPrincipalsAllow := []string{}
    92  	if username {
    93  		authorizedPrincipalsAllow = append(authorizedPrincipalsAllow, "username")
    94  	}
    95  	if email {
    96  		authorizedPrincipalsAllow = append(authorizedPrincipalsAllow, "email")
    97  	}
    98  
    99  	return authorizedPrincipalsAllow, true
   100  }
   101  
   102  func loadSSHFrom(rootCfg ConfigProvider) {
   103  	sec := rootCfg.Section("server")
   104  	if len(SSH.Domain) == 0 {
   105  		SSH.Domain = Domain
   106  	}
   107  
   108  	homeDir, err := util.HomeDir()
   109  	if err != nil {
   110  		log.Fatal("Failed to get home directory: %v", err)
   111  	}
   112  	homeDir = strings.ReplaceAll(homeDir, "\\", "/")
   113  
   114  	SSH.RootPath = path.Join(homeDir, ".ssh")
   115  	serverCiphers := sec.Key("SSH_SERVER_CIPHERS").Strings(",")
   116  	if len(serverCiphers) > 0 {
   117  		SSH.ServerCiphers = serverCiphers
   118  	}
   119  	serverKeyExchanges := sec.Key("SSH_SERVER_KEY_EXCHANGES").Strings(",")
   120  	if len(serverKeyExchanges) > 0 {
   121  		SSH.ServerKeyExchanges = serverKeyExchanges
   122  	}
   123  	serverMACs := sec.Key("SSH_SERVER_MACS").Strings(",")
   124  	if len(serverMACs) > 0 {
   125  		SSH.ServerMACs = serverMACs
   126  	}
   127  	SSH.KeyTestPath = os.TempDir()
   128  	if err = sec.MapTo(&SSH); err != nil {
   129  		log.Fatal("Failed to map SSH settings: %v", err)
   130  	}
   131  	for i, key := range SSH.ServerHostKeys {
   132  		if !filepath.IsAbs(key) {
   133  			SSH.ServerHostKeys[i] = filepath.Join(AppDataPath, key)
   134  		}
   135  	}
   136  
   137  	SSH.KeygenPath = sec.Key("SSH_KEYGEN_PATH").MustString("ssh-keygen")
   138  	SSH.Port = sec.Key("SSH_PORT").MustInt(22)
   139  	SSH.ListenPort = sec.Key("SSH_LISTEN_PORT").MustInt(SSH.Port)
   140  	SSH.UseProxyProtocol = sec.Key("SSH_SERVER_USE_PROXY_PROTOCOL").MustBool(false)
   141  
   142  	// When disable SSH, start builtin server value is ignored.
   143  	if SSH.Disabled {
   144  		SSH.StartBuiltinServer = false
   145  	}
   146  
   147  	SSH.TrustedUserCAKeysFile = sec.Key("SSH_TRUSTED_USER_CA_KEYS_FILENAME").MustString(filepath.Join(SSH.RootPath, "gitea-trusted-user-ca-keys.pem"))
   148  
   149  	for _, caKey := range SSH.TrustedUserCAKeys {
   150  		pubKey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(caKey))
   151  		if err != nil {
   152  			log.Fatal("Failed to parse TrustedUserCaKeys: %s %v", caKey, err)
   153  		}
   154  
   155  		SSH.TrustedUserCAKeysParsed = append(SSH.TrustedUserCAKeysParsed, pubKey)
   156  	}
   157  	if len(SSH.TrustedUserCAKeys) > 0 {
   158  		// Set the default as email,username otherwise we can leave it empty
   159  		sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").MustString("username,email")
   160  	} else {
   161  		sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").MustString("off")
   162  	}
   163  
   164  	SSH.AuthorizedPrincipalsAllow, SSH.AuthorizedPrincipalsEnabled = parseAuthorizedPrincipalsAllow(sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").Strings(","))
   165  
   166  	SSH.MinimumKeySizeCheck = sec.Key("MINIMUM_KEY_SIZE_CHECK").MustBool(SSH.MinimumKeySizeCheck)
   167  	minimumKeySizes := rootCfg.Section("ssh.minimum_key_sizes").Keys()
   168  	for _, key := range minimumKeySizes {
   169  		if key.MustInt() != -1 {
   170  			SSH.MinimumKeySizes[strings.ToLower(key.Name())] = key.MustInt()
   171  		} else {
   172  			delete(SSH.MinimumKeySizes, strings.ToLower(key.Name()))
   173  		}
   174  	}
   175  
   176  	SSH.AuthorizedKeysBackup = sec.Key("SSH_AUTHORIZED_KEYS_BACKUP").MustBool(true)
   177  	SSH.CreateAuthorizedKeysFile = sec.Key("SSH_CREATE_AUTHORIZED_KEYS_FILE").MustBool(true)
   178  
   179  	SSH.AuthorizedPrincipalsBackup = false
   180  	SSH.CreateAuthorizedPrincipalsFile = false
   181  	if SSH.AuthorizedPrincipalsEnabled {
   182  		SSH.AuthorizedPrincipalsBackup = sec.Key("SSH_AUTHORIZED_PRINCIPALS_BACKUP").MustBool(true)
   183  		SSH.CreateAuthorizedPrincipalsFile = sec.Key("SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE").MustBool(true)
   184  	}
   185  
   186  	SSH.ExposeAnonymous = sec.Key("SSH_EXPOSE_ANONYMOUS").MustBool(false)
   187  	SSH.AuthorizedKeysCommandTemplate = sec.Key("SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE").MustString(SSH.AuthorizedKeysCommandTemplate)
   188  
   189  	SSH.AuthorizedKeysCommandTemplateTemplate = template.Must(template.New("").Parse(SSH.AuthorizedKeysCommandTemplate))
   190  
   191  	SSH.PerWriteTimeout = sec.Key("SSH_PER_WRITE_TIMEOUT").MustDuration(PerWriteTimeout)
   192  	SSH.PerWritePerKbTimeout = sec.Key("SSH_PER_WRITE_PER_KB_TIMEOUT").MustDuration(PerWritePerKbTimeout)
   193  
   194  	// ensure parseRunModeSetting has been executed before this
   195  	SSH.BuiltinServerUser = rootCfg.Section("server").Key("BUILTIN_SSH_SERVER_USER").MustString(RunUser)
   196  	SSH.User = rootCfg.Section("server").Key("SSH_USER").MustString(SSH.BuiltinServerUser)
   197  }