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 }