Skip to content

Make internal SSH server host key path configurable #14918

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 8, 2021
12 changes: 6 additions & 6 deletions cmd/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand All @@ -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)
}
Expand Down
16 changes: 8 additions & 8 deletions cmd/web_graceful.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions cmd/web_letsencrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [email protected], 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 =
Expand Down
1 change: 1 addition & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `SSH_SERVER_CIPHERS`: **aes128-ctr, aes192-ctr, aes256-ctr, [email protected], 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, [email protected]**: 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`: **[email protected], 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.
Expand Down
6 changes: 3 additions & 3 deletions modules/graceful/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
Expand Down
16 changes: 8 additions & 8 deletions modules/graceful/server_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
}
7 changes: 7 additions & 0 deletions modules/setting/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
34 changes: 22 additions & 12 deletions modules/ssh/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion modules/ssh/ssh_graceful.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down