From c03f530212249b18ffb73dfa47c99e9a4ed7c86c Mon Sep 17 00:00:00 2001 From: zeripath Date: Mon, 8 Mar 2021 02:43:59 +0000 Subject: [PATCH] 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 --- cmd/web.go | 12 +++---- cmd/web_graceful.go | 16 ++++----- cmd/web_letsencrypt.go | 4 +-- custom/conf/app.example.ini | 4 +++ .../doc/advanced/config-cheat-sheet.en-us.md | 1 + modules/graceful/server.go | 6 ++-- modules/graceful/server_http.go | 16 ++++----- modules/setting/setting.go | 7 ++++ modules/ssh/ssh.go | 34 ++++++++++++------- modules/ssh/ssh_graceful.go | 2 +- 10 files changed, 62 insertions(+), 40 deletions(-) diff --git a/cmd/web.go b/cmd/web.go index 79915ad19..423917ba4 100644 --- a/cmd/web.go +++ b/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) } diff --git a/cmd/web_graceful.go b/cmd/web_graceful.go index 9e039de69..5db065818 100644 --- a/cmd/web_graceful.go +++ b/cmd/web_graceful.go @@ -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) diff --git a/cmd/web_letsencrypt.go b/cmd/web_letsencrypt.go index cded53a49..775439372 100644 --- a/cmd/web_letsencrypt.go +++ b/cmd/web_letsencrypt.go @@ -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) { diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index b86c8f1fa..6edf09e8a 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -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 = diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 70870d3ec..1c4c3d033 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -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. diff --git a/modules/graceful/server.go b/modules/graceful/server.go index e7394f349..c5021a9ba 100644 --- a/modules/graceful/server.go +++ b/modules/graceful/server.go @@ -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{}, diff --git a/modules/graceful/server_http.go b/modules/graceful/server_http.go index 1052637d5..b101a10d9 100644 --- a/modules/graceful/server_http.go +++ b/modules/graceful/server_http.go @@ -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) } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 4976c0007..60e433b1a 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -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) diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index 925f9615b..22683b003 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -259,28 +259,38 @@ func Listen(host string, port int, ciphers []string, keyExchanges []string, macs }, } - keyPath := filepath.Join(setting.AppDataPath, "ssh/gogs.rsa") - isExist, err := util.IsExist(keyPath) - if err != nil { - log.Fatal("Unable to check if %s exists. Error: %v", keyPath, err) + 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", setting.SSH.ServerHostKeys, err) + } + if isExist { + keys = append(keys, key) + } } - if !isExist { - filePath := filepath.Dir(keyPath) + + 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)) - if err != nil { - log.Error("Failed to set Host Key. %s", err) + 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 } diff --git a/modules/ssh/ssh_graceful.go b/modules/ssh/ssh_graceful.go index a30e6fc29..c213aa7b8 100644 --- a/modules/ssh/ssh_graceful.go +++ b/modules/ssh/ssh_graceful.go @@ -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 {