Skip to content
Clone

fix(githost): validate ssh server config at boot and exit when the ssh task dies

githost kept serving smart HTTP after the SSH listener panicked or refused to
start; it now validates the ssh config before binding, treats an early ssh task
exit as fatal, and logs session-limit rejections and host key loading.
ZhenYi committed
parent d935855 commit a435f0e Browse files

2 files changed

+33 −5

File tree 2
Modified app/githost/src/main.rs View app/githost/src/main.rs
@@ -43,6 +43,8 @@
4343 let ssh_addr: SocketAddr = format!("{ssh_bind}:{ssh_port}")
4444 .parse()
4545 .with_context(|| format!("parsing ssh address {ssh_bind}:{ssh_port}"))?;
46 git::ssh::build_server_config(&cfg.git.ssh)
47 .with_context(|| format!("validating ssh server config for {ssh_addr}"))?;
4648 let ssh_listener = TcpListener::bind(ssh_addr)
4749 .await
4850 .with_context(|| format!("binding SSH {ssh_addr}"))?;
@@ -61,20 +63,35 @@
6163 .map_err(anyhow::Error::from)
6264 });
6365
64 let ssh_task = tokio::spawn(async move {
66 let mut ssh_task = tokio::spawn(async move {
6567 tokio::select! {
6668 res = git::ssh::serve_on_listener(ssh_svc, ssh_listener) => res.map_err(anyhow::Error::from),
6769 _ = ssh_rx => Ok(()),
6870 }
6971 });
7072
71 shutdown_signal().await;
73 let ssh_crashed = tokio::select! {
74 _ = shutdown_signal() => false,
75 res = &mut ssh_task => {
76 match res {
77 Err(e) => warn!(error = %e, "ssh task panicked"),
78 Ok(Err(e)) => warn!(error = %e, "git ssh server exited unexpectedly"),
79 Ok(Ok(())) => {}
80 }
81 true
82 }
83 };
84 if ssh_crashed {
85 let _ = http_tx.send(());
86 let _ = http_task.await;
87 anyhow::bail!("git ssh server on {ssh_addr} exited; refusing to keep running without it");
88 }
7289 info!("shutdown signal received; stopping listeners");
7390 let _ = http_tx.send(());
7491 let _ = ssh_tx.send(());
7592
93 let _ = ssh_task.await;
7694 http_task.await.context("smart HTTP task panicked")??;
77 let _ = ssh_task.await;
7895 info!("githost shut down");
7996 Ok(())
8097}
Modified lib/git/src/ssh/server.rs View lib/git/src/ssh/server.rs
@@ -32,6 +32,11 @@
3232
3333 fn new_client(&mut self, peer_addr: Option<SocketAddr>) -> Self::Handler {
3434 let permit = self.session_slots.clone().try_acquire_owned().ok();
35 if permit.is_none() {
36 tracing::warn!(?peer_addr, "ssh session limit reached; rejecting client");
37 } else {
38 tracing::info!(?peer_addr, "new ssh client connected");
39 }
3540 GitSshHandler::new(self.service.clone(), peer_addr, permit)
3641 }
3742
@@ -43,8 +48,10 @@
4348pub fn load_host_key(path: &str) -> SshResult<PrivateKey> {
4449 let data = std::fs::read_to_string(path)
4550 .map_err(|e| SshError::KeyLoadFailed(format!("cannot read {path}: {e}")))?;
46 PrivateKey::from_openssh(&data)
47 .map_err(|e| SshError::KeyLoadFailed(format!("openssh decode failed for {path}: {e}")))
51 let key = PrivateKey::from_openssh(&data)
52 .map_err(|e| SshError::KeyLoadFailed(format!("openssh decode failed for {path}: {e}")))?;
53 tracing::debug!(path, "ssh host key loaded");
54 Ok(key)
4855}
4956
5057pub fn build_server_config(config: &GitSshConfig) -> SshResult<server::Config> {
@@ -67,12 +74,15 @@
6774pub async fn run_ssh(service: GitSshService) -> SshResult<()> {
6875 let bind_addr = service.config.bind_addr.clone();
6976 let port = service.config.port;
77 let max_sessions = service.config.max_sessions;
7078 let config = Arc::new(build_server_config(&service.config)?);
7179 let mut server = GitSshServer::new(Arc::new(service));
80 tracing::info!(%bind_addr, port, max_sessions, "starting git ssh server");
7281 server
7382 .run_on_address(config, format!("{bind_addr}:{port}"))
7483 .await
7584 .map_err(SshError::Io)?;
85 tracing::info!(%bind_addr, port, "git ssh server stopped");
7686 Ok(())
7787}
7888
@@ -82,6 +92,7 @@
8292) -> SshResult<()> {
8393 let config = Arc::new(build_server_config(&service.config)?);
8494 let mut server = GitSshServer::new(Arc::new(service));
95 tracing::info!(local_addr = ?listener.local_addr().ok(), "serving git ssh on listener");
8596 let running = server.run_on_socket(config, &listener);
8697 running.await.map_err(SshError::Io)?;
8798 Ok(())