This repository has no description
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}