This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-ssh / src / server.rs
6.1 kB 217 lines
1use std::collections::{HashMap, HashSet}; 2use std::net::{IpAddr, SocketAddr}; 3use std::sync::Arc; 4 5use knot_runtime::{Clock, HttpTransport}; 6use knot_types::{OfferedKey, OwnerRef}; 7use russh::keys::ssh_key; 8use russh::server::{Auth, Handler, Msg, Server, Session}; 9use russh::{Channel, ChannelId}; 10use tokio_util::task::TaskTracker; 11 12use crate::SshState; 13use crate::exec::run_exec; 14use crate::identity::{self, Asserted, Credential, Verdict}; 15 16pub(crate) struct KnotSshServer<H, C> { 17 pub(crate) state: Arc<SshState<H, C>>, 18 pub(crate) tracker: TaskTracker, 19} 20 21impl<H: HttpTransport, C: Clock> Server for KnotSshServer<H, C> { 22 type Handler = KnotSession<H, C>; 23 24 fn new_client(&mut self, peer: Option<SocketAddr>) -> Self::Handler { 25 KnotSession::new( 26 Arc::clone(&self.state), 27 self.tracker.clone(), 28 peer.map(|addr| addr.ip()), 29 ) 30 } 31} 32 33pub(crate) struct KnotSession<H, C> { 34 state: Arc<SshState<H, C>>, 35 tracker: TaskTracker, 36 credential: Option<Credential>, 37 asserted: Option<Asserted>, 38 channels: HashMap<ChannelId, Channel<Msg>>, 39 protocols: HashSet<ChannelId>, 40 peer: Option<IpAddr>, 41} 42 43fn reject() -> Auth { 44 Auth::Reject { 45 proceed_with_methods: None, 46 partial_success: false, 47 } 48} 49 50impl<H, C> KnotSession<H, C> { 51 fn new(state: Arc<SshState<H, C>>, tracker: TaskTracker, peer: Option<IpAddr>) -> Self { 52 Self { 53 state, 54 tracker, 55 credential: None, 56 asserted: None, 57 channels: HashMap::new(), 58 protocols: HashSet::new(), 59 peer, 60 } 61 } 62} 63 64impl<H: HttpTransport, C: Clock> KnotSession<H, C> { 65 async fn decide( 66 &mut self, 67 user: &str, 68 public_key: &ssh_key::PublicKey, 69 ) -> Option<(Verdict, OfferedKey)> { 70 let key = OfferedKey::from_bytes(public_key.to_bytes().ok()?); 71 let verdict = identity::verify( 72 &self.state, 73 OwnerRef::parse(user), 74 &key, 75 self.peer, 76 &mut self.asserted, 77 ) 78 .await; 79 Some((verdict, key)) 80 } 81} 82 83impl<H: HttpTransport, C: Clock> Handler for KnotSession<H, C> { 84 type Error = russh::Error; 85 86 async fn auth_publickey_offered( 87 &mut self, 88 user: &str, 89 public_key: &ssh_key::PublicKey, 90 ) -> Result<Auth, Self::Error> { 91 match self.decide(user, public_key).await { 92 Some((Verdict::Refused, _)) | None => Ok(reject()), 93 Some(_) => Ok(Auth::Accept), 94 } 95 } 96 97 async fn auth_publickey( 98 &mut self, 99 user: &str, 100 public_key: &ssh_key::PublicKey, 101 ) -> Result<Auth, Self::Error> { 102 match self.decide(user, public_key).await { 103 Some((Verdict::Identified(did), _)) => { 104 self.credential = Some(Credential::Identified(did)); 105 Ok(Auth::Accept) 106 } 107 Some((Verdict::Offered, key)) => { 108 self.credential = Some(Credential::Offered(key)); 109 Ok(Auth::Accept) 110 } 111 Some((Verdict::Refused, _)) | None => Ok(reject()), 112 } 113 } 114 115 async fn channel_open_session( 116 &mut self, 117 channel: Channel<Msg>, 118 _session: &mut Session, 119 ) -> Result<bool, Self::Error> { 120 self.channels.insert(channel.id(), channel); 121 Ok(true) 122 } 123 124 async fn env_request( 125 &mut self, 126 channel: ChannelId, 127 variable_name: &str, 128 variable_value: &str, 129 _session: &mut Session, 130 ) -> Result<(), Self::Error> { 131 if variable_name == "GIT_PROTOCOL" 132 && variable_value 133 .split(':') 134 .any(|token| token.trim() == "version=2") 135 { 136 self.protocols.insert(channel); 137 } 138 Ok(()) 139 } 140 141 async fn channel_eof( 142 &mut self, 143 channel: ChannelId, 144 _session: &mut Session, 145 ) -> Result<(), Self::Error> { 146 self.protocols.remove(&channel); 147 Ok(()) 148 } 149 150 async fn channel_close( 151 &mut self, 152 channel: ChannelId, 153 _session: &mut Session, 154 ) -> Result<(), Self::Error> { 155 self.channels.remove(&channel); 156 self.protocols.remove(&channel); 157 Ok(()) 158 } 159 160 #[allow(clippy::too_many_arguments)] 161 async fn pty_request( 162 &mut self, 163 channel: ChannelId, 164 _term: &str, 165 _col_width: u32, 166 _row_height: u32, 167 _pix_width: u32, 168 _pix_height: u32, 169 _modes: &[(russh::Pty, u32)], 170 session: &mut Session, 171 ) -> Result<(), Self::Error> { 172 session.channel_success(channel)?; 173 Ok(()) 174 } 175 176 async fn shell_request( 177 &mut self, 178 channel: ChannelId, 179 session: &mut Session, 180 ) -> Result<(), Self::Error> { 181 let (Some(handle), Some(credential)) = 182 (self.channels.remove(&channel), self.credential.clone()) 183 else { 184 session.channel_failure(channel)?; 185 return Ok(()); 186 }; 187 session.channel_success(channel)?; 188 let state = Arc::clone(&self.state); 189 self.tracker.spawn(async move { 190 crate::exec::run_greeting(state, credential, handle).await; 191 }); 192 Ok(()) 193 } 194 195 async fn exec_request( 196 &mut self, 197 channel: ChannelId, 198 data: &[u8], 199 session: &mut Session, 200 ) -> Result<(), Self::Error> { 201 let (Some(handle), Some(credential)) = 202 (self.channels.remove(&channel), self.credential.clone()) 203 else { 204 session.channel_failure(channel)?; 205 return Ok(()); 206 }; 207 session.channel_success(channel)?; 208 let protocol_v2 = self.protocols.remove(&channel); 209 let state = Arc::clone(&self.state); 210 let peer = self.peer; 211 let command = data.to_vec(); 212 self.tracker.spawn(async move { 213 run_exec(state, credential, handle, &command, protocol_v2, peer).await; 214 }); 215 Ok(()) 216 } 217}