Skip to content

ssh: support multiple builtin SSH server listener #27969

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ RUN_USER = ; git
;; SSH username displayed in clone URLs.
;SSH_USER = %(BUILTIN_SSH_SERVER_USER)s
;;
;; The network interface the builtin SSH server should listen on
;; The network interface(s) the builtin SSH server should listen on.
;; Individual addresses can include a port statement to override SSH_LISTEN_PORT value, like 127.0.0.1:2022,0.0.0.0
;SSH_LISTEN_HOST =
;;
;; Port number to be exposed in clone URL
Expand Down
2 changes: 1 addition & 1 deletion docs/content/administration/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,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.
Expand Down
6 changes: 6 additions & 0 deletions modules/graceful/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion modules/setting/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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"},
Expand Down
21 changes: 16 additions & 5 deletions modules/ssh/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -22,11 +23,21 @@ 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
}

Expand Down
7 changes: 3 additions & 4 deletions modules/ssh/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
Expand Down Expand Up @@ -280,10 +279,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 {
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/git_helper_for_declarative_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down