mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 10:56:10 +01:00 
			
		
		
		
	Make internal SSH server host key path configurable (#14918)
* Make SSH server host key path configurable * make it possible to have multiple keys * Make gitea.rsa the default key * Add some more logging Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
		
							
								
								
									
										12
									
								
								cmd/web.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								cmd/web.go
									
									
									
									
									
								
							| @@ -64,7 +64,7 @@ func runHTTPRedirector() { | ||||
| 		http.Redirect(w, r, target, http.StatusTemporaryRedirect) | ||||
| 	}) | ||||
|  | ||||
| 	var err = runHTTP("tcp", source, context2.ClearHandler(handler)) | ||||
| 	var err = runHTTP("tcp", source, "HTTP Redirector", context2.ClearHandler(handler)) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Failed to start port redirection: %v", err) | ||||
| @@ -198,7 +198,7 @@ func listen(m http.Handler, handleRedirector bool) error { | ||||
| 		if handleRedirector { | ||||
| 			NoHTTPRedirector() | ||||
| 		} | ||||
| 		err = runHTTP("tcp", listenAddr, context2.ClearHandler(m)) | ||||
| 		err = runHTTP("tcp", listenAddr, "Web", context2.ClearHandler(m)) | ||||
| 	case setting.HTTPS: | ||||
| 		if setting.EnableLetsEncrypt { | ||||
| 			err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m)) | ||||
| @@ -211,22 +211,22 @@ func listen(m http.Handler, handleRedirector bool) error { | ||||
| 				NoHTTPRedirector() | ||||
| 			} | ||||
| 		} | ||||
| 		err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m)) | ||||
| 		err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, context2.ClearHandler(m)) | ||||
| 	case setting.FCGI: | ||||
| 		if handleRedirector { | ||||
| 			NoHTTPRedirector() | ||||
| 		} | ||||
| 		err = runFCGI("tcp", listenAddr, context2.ClearHandler(m)) | ||||
| 		err = runFCGI("tcp", listenAddr, "FCGI Web", context2.ClearHandler(m)) | ||||
| 	case setting.UnixSocket: | ||||
| 		if handleRedirector { | ||||
| 			NoHTTPRedirector() | ||||
| 		} | ||||
| 		err = runHTTP("unix", listenAddr, context2.ClearHandler(m)) | ||||
| 		err = runHTTP("unix", listenAddr, "Web", context2.ClearHandler(m)) | ||||
| 	case setting.FCGIUnix: | ||||
| 		if handleRedirector { | ||||
| 			NoHTTPRedirector() | ||||
| 		} | ||||
| 		err = runFCGI("unix", listenAddr, context2.ClearHandler(m)) | ||||
| 		err = runFCGI("unix", listenAddr, "Web", context2.ClearHandler(m)) | ||||
| 	default: | ||||
| 		log.Fatal("Invalid protocol: %s", setting.Protocol) | ||||
| 	} | ||||
|   | ||||
| @@ -14,16 +14,16 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| ) | ||||
|  | ||||
| func runHTTP(network, listenAddr string, m http.Handler) error { | ||||
| 	return graceful.HTTPListenAndServe(network, listenAddr, m) | ||||
| func runHTTP(network, listenAddr, name string, m http.Handler) error { | ||||
| 	return graceful.HTTPListenAndServe(network, listenAddr, name, m) | ||||
| } | ||||
|  | ||||
| func runHTTPS(network, listenAddr, certFile, keyFile string, m http.Handler) error { | ||||
| 	return graceful.HTTPListenAndServeTLS(network, listenAddr, certFile, keyFile, m) | ||||
| func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler) error { | ||||
| 	return graceful.HTTPListenAndServeTLS(network, listenAddr, name, certFile, keyFile, m) | ||||
| } | ||||
|  | ||||
| func runHTTPSWithTLSConfig(network, listenAddr string, tlsConfig *tls.Config, m http.Handler) error { | ||||
| 	return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, tlsConfig, m) | ||||
| func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error { | ||||
| 	return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m) | ||||
| } | ||||
|  | ||||
| // NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector | ||||
| @@ -43,9 +43,9 @@ func NoInstallListener() { | ||||
| 	graceful.GetManager().InformCleanup() | ||||
| } | ||||
|  | ||||
| func runFCGI(network, listenAddr string, m http.Handler) error { | ||||
| func runFCGI(network, listenAddr, name string, m http.Handler) error { | ||||
| 	// This needs to handle stdin as fcgi point | ||||
| 	fcgiServer := graceful.NewServer(network, listenAddr) | ||||
| 	fcgiServer := graceful.NewServer(network, listenAddr, name) | ||||
|  | ||||
| 	err := fcgiServer.ListenAndServe(func(listener net.Listener) error { | ||||
| 		return fcgi.Serve(listener, m) | ||||
|   | ||||
| @@ -46,14 +46,14 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler) | ||||
| 		go func() { | ||||
| 			log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect) | ||||
| 			// all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here) | ||||
| 			var err = runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) | ||||
| 			var err = runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) | ||||
| 			if err != nil { | ||||
| 				log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err) | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	return runHTTPSWithTLSConfig("tcp", listenAddr, tlsConfig, context2.ClearHandler(m)) | ||||
| 	return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, context2.ClearHandler(m)) | ||||
| } | ||||
|  | ||||
| func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) { | ||||
|   | ||||
| @@ -319,6 +319,10 @@ SSH_SERVER_KEY_EXCHANGES = diffie-hellman-group1-sha1, diffie-hellman-group14-sh | ||||
| ; For the built-in SSH server, choose the MACs to support for SSH connections, | ||||
| ; for system SSH this setting has no effect | ||||
| SSH_SERVER_MACS = hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1, hmac-sha1-96 | ||||
| ; For the built-in SSH server, choose the keypair to offer as the host key | ||||
| ; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub | ||||
| ; relative paths are made absolute relative to the APP_DATA_PATH | ||||
| SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa | ||||
| ; Directory to create temporary files in when testing public keys using ssh-keygen, | ||||
| ; default is the system temporary directory. | ||||
| SSH_KEY_TEST_PATH = | ||||
|   | ||||
| @@ -256,6 +256,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. | ||||
| - `SSH_SERVER_CIPHERS`: **aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128**: For the built-in SSH server, choose the ciphers to support for SSH connections, for system SSH this setting has no effect. | ||||
| - `SSH_SERVER_KEY_EXCHANGES`: **diffie-hellman-group1-sha1, diffie-hellman-group14-sha1, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, curve25519-sha256@libssh.org**: For the built-in SSH server, choose the key exchange algorithms to support for SSH connections, for system SSH this setting has no effect. | ||||
| - `SSH_SERVER_MACS`: **hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1, hmac-sha1-96**: For the built-in SSH server, choose the MACs to support for SSH connections, for system SSH this setting has no effect | ||||
| - `SSH_SERVER_HOST_KEYS`: **ssh/gitea.rsa, ssh/gogs.rsa**: For the built-in SSH server, choose the keypairs to offer as the host key. The private key should be at `SSH_SERVER_HOST_KEY` and the public `SSH_SERVER_HOST_KEY.pub`. Relative paths are made absolute relative to the `APP_DATA_PATH`. If no key exists a 4096 bit RSA key will be created for you. | ||||
| - `SSH_KEY_TEST_PATH`: **/tmp**: Directory to create temporary files in when testing public keys using ssh-keygen, default is the system temporary directory. | ||||
| - `SSH_KEYGEN_PATH`: **ssh-keygen**: Path to ssh-keygen, default is 'ssh-keygen' which means the shell is responsible for finding out which one to call. | ||||
| - `SSH_EXPOSE_ANONYMOUS`: **false**: Enable exposure of SSH clone URL to anonymous visitors, default is false. | ||||
|   | ||||
| @@ -48,11 +48,11 @@ type Server struct { | ||||
| } | ||||
|  | ||||
| // NewServer creates a server on network at provided address | ||||
| func NewServer(network, address string) *Server { | ||||
| func NewServer(network, address, name string) *Server { | ||||
| 	if GetManager().IsChild() { | ||||
| 		log.Info("Restarting new server: %s:%s on PID: %d", network, address, os.Getpid()) | ||||
| 		log.Info("Restarting new %s server: %s:%s on PID: %d", name, network, address, os.Getpid()) | ||||
| 	} else { | ||||
| 		log.Info("Starting new server: %s:%s on PID: %d", network, address, os.Getpid()) | ||||
| 		log.Info("Starting new %s server: %s:%s on PID: %d", name, network, address, os.Getpid()) | ||||
| 	} | ||||
| 	srv := &Server{ | ||||
| 		wg:      sync.WaitGroup{}, | ||||
|   | ||||
| @@ -9,8 +9,8 @@ import ( | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| func newHTTPServer(network, address string, handler http.Handler) (*Server, ServeFunction) { | ||||
| 	server := NewServer(network, address) | ||||
| func newHTTPServer(network, address, name string, handler http.Handler) (*Server, ServeFunction) { | ||||
| 	server := NewServer(network, address, name) | ||||
| 	httpServer := http.Server{ | ||||
| 		ReadTimeout:    DefaultReadTimeOut, | ||||
| 		WriteTimeout:   DefaultWriteTimeOut, | ||||
| @@ -25,21 +25,21 @@ func newHTTPServer(network, address string, handler http.Handler) (*Server, Serv | ||||
|  | ||||
| // HTTPListenAndServe listens on the provided network address and then calls Serve | ||||
| // to handle requests on incoming connections. | ||||
| func HTTPListenAndServe(network, address string, handler http.Handler) error { | ||||
| 	server, lHandler := newHTTPServer(network, address, handler) | ||||
| func HTTPListenAndServe(network, address, name string, handler http.Handler) error { | ||||
| 	server, lHandler := newHTTPServer(network, address, name, handler) | ||||
| 	return server.ListenAndServe(lHandler) | ||||
| } | ||||
|  | ||||
| // HTTPListenAndServeTLS listens on the provided network address and then calls Serve | ||||
| // to handle requests on incoming connections. | ||||
| func HTTPListenAndServeTLS(network, address, certFile, keyFile string, handler http.Handler) error { | ||||
| 	server, lHandler := newHTTPServer(network, address, handler) | ||||
| func HTTPListenAndServeTLS(network, address, name, certFile, keyFile string, handler http.Handler) error { | ||||
| 	server, lHandler := newHTTPServer(network, address, name, handler) | ||||
| 	return server.ListenAndServeTLS(certFile, keyFile, lHandler) | ||||
| } | ||||
|  | ||||
| // HTTPListenAndServeTLSConfig listens on the provided network address and then calls Serve | ||||
| // to handle requests on incoming connections. | ||||
| func HTTPListenAndServeTLSConfig(network, address string, tlsConfig *tls.Config, handler http.Handler) error { | ||||
| 	server, lHandler := newHTTPServer(network, address, handler) | ||||
| func HTTPListenAndServeTLSConfig(network, address, name string, tlsConfig *tls.Config, handler http.Handler) error { | ||||
| 	server, lHandler := newHTTPServer(network, address, name, handler) | ||||
| 	return server.ListenAndServeTLSConfig(tlsConfig, lHandler) | ||||
| } | ||||
|   | ||||
| @@ -132,6 +132,7 @@ var ( | ||||
| 		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"` | ||||
| @@ -157,6 +158,7 @@ var ( | ||||
| 		KeygenPath:          "ssh-keygen", | ||||
| 		MinimumKeySizeCheck: true, | ||||
| 		MinimumKeySizes:     map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 2048}, | ||||
| 		ServerHostKeys:      []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, | ||||
| 	} | ||||
|  | ||||
| 	// Security settings | ||||
| @@ -698,6 +700,11 @@ func NewContext() { | ||||
| 	if err = Cfg.Section("server").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) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	SSH.KeygenPath = sec.Key("SSH_KEYGEN_PATH").MustString("ssh-keygen") | ||||
| 	SSH.Port = sec.Key("SSH_PORT").MustInt(22) | ||||
|   | ||||
| @@ -259,29 +259,39 @@ func Listen(host string, port int, ciphers []string, keyExchanges []string, macs | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	keyPath := filepath.Join(setting.AppDataPath, "ssh/gogs.rsa") | ||||
| 	isExist, err := util.IsExist(keyPath) | ||||
| 	keys := make([]string, 0, len(setting.SSH.ServerHostKeys)) | ||||
| 	for _, key := range setting.SSH.ServerHostKeys { | ||||
| 		isExist, err := util.IsExist(key) | ||||
| 		if err != nil { | ||||
| 		log.Fatal("Unable to check if %s exists. Error: %v", keyPath, err) | ||||
| 			log.Fatal("Unable to check if %s exists. Error: %v", setting.SSH.ServerHostKeys, err) | ||||
| 		} | ||||
| 	if !isExist { | ||||
| 		filePath := filepath.Dir(keyPath) | ||||
| 		if isExist { | ||||
| 			keys = append(keys, key) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(keys) == 0 { | ||||
| 		filePath := filepath.Dir(setting.SSH.ServerHostKeys[0]) | ||||
|  | ||||
| 		if err := os.MkdirAll(filePath, os.ModePerm); err != nil { | ||||
| 			log.Error("Failed to create dir %s: %v", filePath, err) | ||||
| 		} | ||||
|  | ||||
| 		err := GenKeyPair(keyPath) | ||||
| 		err := GenKeyPair(setting.SSH.ServerHostKeys[0]) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Failed to generate private key: %v", err) | ||||
| 		} | ||||
| 		log.Trace("New private key is generated: %s", keyPath) | ||||
| 		log.Trace("New private key is generated: %s", setting.SSH.ServerHostKeys[0]) | ||||
| 		keys = append(keys, setting.SSH.ServerHostKeys[0]) | ||||
| 	} | ||||
|  | ||||
| 	err = srv.SetOption(ssh.HostKeyFile(keyPath)) | ||||
| 	for _, key := range keys { | ||||
| 		log.Info("Adding SSH host key: %s", key) | ||||
| 		err := srv.SetOption(ssh.HostKeyFile(key)) | ||||
| 		if err != nil { | ||||
| 			log.Error("Failed to set Host Key. %s", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	go listen(&srv) | ||||
|  | ||||
| @@ -291,7 +301,7 @@ func Listen(host string, port int, ciphers []string, keyExchanges []string, macs | ||||
| // Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file. | ||||
| // Private Key generated is PEM encoded | ||||
| func GenKeyPair(keyPath string) error { | ||||
| 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048) | ||||
| 	privateKey, err := rsa.GenerateKey(rand.Reader, 4096) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|   | ||||
| @@ -12,7 +12,7 @@ import ( | ||||
| ) | ||||
|  | ||||
| func listen(server *ssh.Server) { | ||||
| 	gracefulServer := graceful.NewServer("tcp", server.Addr) | ||||
| 	gracefulServer := graceful.NewServer("tcp", server.Addr, "SSH") | ||||
|  | ||||
| 	err := gracefulServer.ListenAndServe(server.Serve) | ||||
| 	if err != nil { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user