]> source.dussan.org Git - gitea.git/commitdiff
Make internal SSH server host key path configurable (#14918)
authorzeripath <art27@cantab.net>
Mon, 8 Mar 2021 02:43:59 +0000 (02:43 +0000)
committerGitHub <noreply@github.com>
Mon, 8 Mar 2021 02:43:59 +0000 (03:43 +0100)
* 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>
cmd/web.go
cmd/web_graceful.go
cmd/web_letsencrypt.go
custom/conf/app.example.ini
docs/content/doc/advanced/config-cheat-sheet.en-us.md
modules/graceful/server.go
modules/graceful/server_http.go
modules/setting/setting.go
modules/ssh/ssh.go
modules/ssh/ssh_graceful.go

index 79915ad1948f157c4400c991195e62f720b184a7..423917ba4e11273616ed129e91893219832d7713 100644 (file)
@@ -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)
        }
index 9e039de69996232eaae41597435c06afa40ed54f..5db065818a19f956a1c49131b38da2d4ae798a9d 100644 (file)
@@ -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)
index cded53a49d2b79e563c1226740e90ee225110911..7754393729173125cef833dd811fdc084442ffc0 100644 (file)
@@ -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) {
index b86c8f1fae3294f68357a98ebefe38c8a5cf7355..6edf09e8ae00639fbd09f6afe99a87b65af98aa2 100644 (file)
@@ -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 =
index 70870d3ec5e11164923738fc0bfe2da4b20ea239..1c4c3d033b34dde479d6de40f226d453a63a7599 100644 (file)
@@ -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.
index e7394f349e79c66093a6341640f6194dd534714e..c5021a9ba5d5e9c6e66f076a028c6933f6015d55 100644 (file)
@@ -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{},
index 1052637d5e74a218a713f33da8adffe8d48f7a57..b101a10d9197d9850e7573c83ed076451c4abfe3 100644 (file)
@@ -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)
 }
index 4976c0007c648c97bcb6931759fe3c0e988fde2d..60e433b1a24727cb1e4b97dbe9e7d07ef316d169 100644 (file)
@@ -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)
index 925f9615b4b38692e1a2f9c502b7f2cb3f00c8ce..22683b003be10c0632cc7f90a705da2d2e4e7ef8 100644 (file)
@@ -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
        }
index a30e6fc29741a123349238f37dff9225fe01f88f..c213aa7b88a9941335ae94701d8300f137b52858 100644 (file)
@@ -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 {