diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 16e1e8223a..54d710b3d7 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -327,7 +327,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `SSH_USER`: **%(BUILTIN_SSH_SERVER_USER)s**: SSH username displayed in clone URLs. This is only for people who configure the SSH server themselves; in most cases, you want to leave this blank and modify the `BUILTIN_SSH_SERVER_USER`. - `SSH_DOMAIN`: **%(DOMAIN)s**: Domain name of this server, used for displayed clone URL. - `SSH_PORT`: **22**: SSH port displayed in clone URL. -- `SSH_LISTEN_HOST`: **0.0.0.0**: Listen address for the built-in SSH server. +- `SSH_LISTEN_HOST`: **0.0.0.0**: Listen address(es) for the built-in SSH server; multiple addresses can be separated by comma, addresses can include a port statement to override SSH_LISTEN_PORT. - `SSH_LISTEN_PORT`: **%(SSH\_PORT)s**: Port for the built-in SSH server. - `SSH_ROOT_PATH`: **~/.ssh**: Root path of SSH directory. - `SSH_CREATE_AUTHORIZED_KEYS_FILE`: **true**: Gitea will create a authorized_keys file by default when it is not using the internal ssh server. If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go index 068de21076..1e1a8239e8 100644 --- a/modules/graceful/manager.go +++ b/modules/graceful/manager.go @@ -272,6 +272,12 @@ func (g *Manager) InformCleanup() { g.createServerWaitGroup.Done() } +// Should we need to create multile listener for one type (e.g. SSH built-in server), +// the number of expected routines needs to be increased accordingly. +func (g *Manager) IncreaseListenerCountBy(extraNumberOfServersToCreate int) { + g.createServerWaitGroup.Add(extraNumberOfServersToCreate) +} + // Done allows the manager to be viewed as a context.Context, it returns a channel that is closed when the server is finished terminating func (g *Manager) Done() <-chan struct{} { return g.managerCtx.Done() diff --git a/modules/setting/ssh.go b/modules/setting/ssh.go index a5a9da0b36..e73aa0115a 100644 --- a/modules/setting/ssh.go +++ b/modules/setting/ssh.go @@ -25,7 +25,7 @@ var SSH = struct { Domain string `ini:"SSH_DOMAIN"` Port int `ini:"SSH_PORT"` User string `ini:"SSH_USER"` - ListenHost string `ini:"SSH_LISTEN_HOST"` + ListenHost []string `ini:"SSH_LISTEN_HOST"` ListenPort int `ini:"SSH_LISTEN_PORT"` RootPath string `ini:"SSH_ROOT_PATH"` ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"` @@ -54,6 +54,7 @@ var SSH = struct { Disabled: false, StartBuiltinServer: false, Domain: "", + ListenHost: []string{"0.0.0.0"}, Port: 22, ServerCiphers: []string{"chacha20-poly1305@openssh.com", "aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "aes256-gcm@openssh.com"}, ServerKeyExchanges: []string{"curve25519-sha256", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group14-sha256", "diffie-hellman-group14-sha1"}, diff --git a/modules/ssh/init.go b/modules/ssh/init.go index 21d4f89936..3800762632 100644 --- a/modules/ssh/init.go +++ b/modules/ssh/init.go @@ -11,6 +11,7 @@ import ( "strconv" "strings" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" ) @@ -22,12 +23,23 @@ func Init() error { } if setting.SSH.StartBuiltinServer { - Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) - log.Info("SSH server started on %s. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", - net.JoinHostPort(setting.SSH.ListenHost, strconv.Itoa(setting.SSH.ListenPort)), - setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs, - ) + if len(setting.SSH.ListenHost) > 1 { + graceful.GetManager().IncreaseListenerCountBy(len(setting.SSH.ListenHost) - 1) + } + for _, listenHost := range setting.SSH.ListenHost { + var addr string + if _, _, err := net.SplitHostPort(listenHost); err == nil { + addr = listenHost + } else { + addr = net.JoinHostPort(listenHost, strconv.Itoa(setting.SSH.ListenPort)) + } + Listen(addr, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) + log.Info("SSH server started on %s. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", + addr, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs, + ) + } return nil + } builtinUnused() diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index a5af5c129b..ab2021cfbd 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -18,7 +18,6 @@ import ( "os/exec" "path/filepath" "reflect" - "strconv" "strings" "sync" "syscall" @@ -279,10 +278,10 @@ func sshConnectionFailed(conn net.Conn, err error) { log.Warn("Failed authentication attempt from %s", conn.RemoteAddr()) } -// Listen starts a SSH server listens on given port. -func Listen(host string, port int, ciphers, keyExchanges, macs []string) { +// Listen starts a SSH server listens on given addr (host, port combination). +func Listen(addr string, ciphers, keyExchanges, macs []string) { srv := ssh.Server{ - Addr: net.JoinHostPort(host, strconv.Itoa(port)), + Addr: addr, PublicKeyHandler: publicKeyHandler, Handler: sessionHandler, ServerConfigCallback: func(ctx ssh.Context) *gossh.ServerConfig { diff --git a/tests/integration/git_helper_for_declarative_test.go b/tests/integration/git_helper_for_declarative_test.go index 1e99783096..a099d414c6 100644 --- a/tests/integration/git_helper_for_declarative_test.go +++ b/tests/integration/git_helper_for_declarative_test.go @@ -52,7 +52,7 @@ func createSSHUrl(gitPath string, u *url.URL) *url.URL { u2 := *u u2.Scheme = "ssh" u2.User = url.User("git") - u2.Host = net.JoinHostPort(setting.SSH.ListenHost, strconv.Itoa(setting.SSH.ListenPort)) + u2.Host = net.JoinHostPort(setting.SSH.ListenHost[0], strconv.Itoa(setting.SSH.ListenPort)) u2.Path = gitPath return &u2 }