You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ssh.go 8.5KB

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