nitro_cli/enclave_proc/
connection.rs1#![deny(missing_docs)]
4#![deny(warnings)]
5
6use log::{debug, warn};
7use nix::sys::epoll::EpollFlags;
8use nix::sys::socket::sockopt::PeerCredentials;
9use nix::sys::socket::UnixCredentials;
10use serde::de::DeserializeOwned;
11use std::collections::HashMap;
12use std::io::Write;
13use std::os::unix::io::AsRawFd;
14use std::os::unix::net::UnixStream;
15use std::sync::{Arc, Mutex};
16
17use crate::common::{receive_from_stream, write_u64_le};
18use crate::common::{
19 EnclaveProcessCommandType, EnclaveProcessReply, ExitGracefully, NitroCliErrorEnum,
20 NitroCliFailure, NitroCliResult,
21};
22use crate::new_nitro_cli_failure;
23
24#[derive(PartialEq, Eq, Hash)]
26enum CommandRequesterType {
27 User(libc::uid_t),
29 Group(libc::gid_t),
31 Others,
33}
34
35struct CommandRequesterPolicy {
37 policy: HashMap<CommandRequesterType, Vec<EnclaveProcessCommandType>>,
39}
40
41struct ConnectionData {
43 epoll_flags: EpollFlags,
45 input_stream: Option<UnixStream>,
47}
48
49#[derive(Clone)]
51pub struct Connection {
52 data: Arc<Mutex<ConnectionData>>,
54}
55
56impl Drop for ConnectionData {
57 fn drop(&mut self) {
58 if let Some(input_stream) = &self.input_stream {
59 input_stream
61 .shutdown(std::net::Shutdown::Both)
62 .map_err(|e| {
63 new_nitro_cli_failure!(
64 &format!("Stream shutdown error: {e:?}"),
65 NitroCliErrorEnum::SocketCloseError
66 )
67 })
68 .ok_or_exit_with_errno(Some("Failed to shut down"));
69 }
70 }
71}
72
73impl CommandRequesterPolicy {
74 fn new_with_defaults() -> Self {
79 let cmds_read_write = vec![
80 EnclaveProcessCommandType::Run,
81 EnclaveProcessCommandType::Terminate,
82 EnclaveProcessCommandType::TerminateComplete,
83 EnclaveProcessCommandType::Describe,
84 EnclaveProcessCommandType::GetEnclaveCID,
85 EnclaveProcessCommandType::GetEnclaveFlags,
86 EnclaveProcessCommandType::GetEnclaveName,
87 EnclaveProcessCommandType::GetIDbyName,
88 EnclaveProcessCommandType::ConnectionListenerStop,
89 ];
90 let cmds_read_only = vec![
91 EnclaveProcessCommandType::Describe,
92 EnclaveProcessCommandType::GetEnclaveCID,
93 EnclaveProcessCommandType::GetEnclaveFlags,
94 EnclaveProcessCommandType::GetEnclaveName,
95 EnclaveProcessCommandType::GetIDbyName,
96 ];
97 let mut policy = HashMap::new();
98
99 policy.insert(
101 CommandRequesterType::User(unsafe { libc::getuid() }),
102 cmds_read_write.clone(),
103 );
104
105 policy.insert(CommandRequesterType::User(0_u32), cmds_read_write);
107
108 policy.insert(CommandRequesterType::Others, cmds_read_only);
110
111 CommandRequesterPolicy { policy }
112 }
113
114 fn find_policy_rule(
116 &self,
117 cmd: EnclaveProcessCommandType,
118 requester: &CommandRequesterType,
119 ) -> bool {
120 match self.policy.get(requester) {
121 None => false,
122 Some(allowed_cmds) => allowed_cmds.contains(&cmd),
123 }
124 }
125
126 fn can_execute_command(&self, cmd: EnclaveProcessCommandType, creds: &UnixCredentials) -> bool {
128 if self.find_policy_rule(cmd, &CommandRequesterType::User(creds.uid())) {
130 return true;
131 }
132
133 if self.find_policy_rule(cmd, &CommandRequesterType::Group(creds.gid())) {
135 return true;
136 }
137
138 if self.find_policy_rule(cmd, &CommandRequesterType::Others) {
140 return true;
141 }
142
143 false
145 }
146}
147
148impl Connection {
149 pub fn new(epoll_flags: EpollFlags, input_stream: Option<UnixStream>) -> Self {
151 let conn_data = ConnectionData {
152 epoll_flags,
153 input_stream,
154 };
155
156 Connection {
157 data: Arc::new(Mutex::new(conn_data)),
158 }
159 }
160
161 pub fn read_command(&self) -> NitroCliResult<EnclaveProcessCommandType> {
163 let mut lock = self.data.lock().map_err(|e| {
164 new_nitro_cli_failure!(
165 &format!("Failed to acquire lock: {e:?}"),
166 NitroCliErrorEnum::LockAcquireFailure
167 )
168 })?;
169 if lock.input_stream.is_none() {
170 return Err(new_nitro_cli_failure!(
171 "Cannot read a command from this connection",
172 NitroCliErrorEnum::UnusableConnectionError
173 ));
174 }
175
176 let mut cmd =
178 receive_from_stream::<EnclaveProcessCommandType>(lock.input_stream.as_mut().unwrap())?;
179
180 let conn_fd = lock.input_stream.as_ref().unwrap().as_raw_fd();
182 let socket_creds = nix::sys::socket::getsockopt(conn_fd, PeerCredentials);
183
184 let user_creds = match socket_creds {
186 Ok(creds) => creds,
187 Err(e) => {
188 warn!("Failed to get user credentials: {}", e);
189 return Ok(EnclaveProcessCommandType::NotPermitted);
190 }
191 };
192
193 let policy = CommandRequesterPolicy::new_with_defaults();
195 if !policy.can_execute_command(cmd, &user_creds) {
196 warn!(
198 "The requester with credentials ({:?}) is not allowed to perform '{:?}'.",
199 user_creds, cmd
200 );
201
202 cmd = EnclaveProcessCommandType::NotPermitted;
204 } else {
205 debug!(
207 "The requester with credentials ({:?}) is allowed to perform '{:?}'.",
208 user_creds, cmd
209 );
210 }
211
212 Ok(cmd)
213 }
214
215 pub fn read<T>(&self) -> NitroCliResult<T>
217 where
218 T: DeserializeOwned,
219 {
220 let mut lock = self.data.lock().map_err(|e| {
221 new_nitro_cli_failure!(
222 &format!("Failed to acquire lock: {e:?}"),
223 NitroCliErrorEnum::LockAcquireFailure
224 )
225 })?;
226 if lock.input_stream.is_none() {
227 return Err(new_nitro_cli_failure!(
228 "Cannot read from this connection",
229 NitroCliErrorEnum::SocketError
230 ));
231 }
232
233 receive_from_stream::<T>(lock.input_stream.as_mut().unwrap())
234 }
235
236 pub fn write_u64(&self, value: u64) -> NitroCliResult<()> {
238 let mut lock = self.data.lock().map_err(|e| {
239 new_nitro_cli_failure!(
240 &format!("Failed to acquire lock: {e:?}"),
241 NitroCliErrorEnum::LockAcquireFailure
242 )
243 })?;
244 if lock.input_stream.is_none() {
245 return Err(new_nitro_cli_failure!(
246 "Cannot write a 64-bit value to this connection",
247 NitroCliErrorEnum::SocketError
248 ));
249 }
250
251 write_u64_le(lock.input_stream.as_mut().unwrap(), value)
252 }
253
254 pub fn println(&self, msg: &str) -> NitroCliResult<()> {
256 let mut msg_str = msg.to_string();
257
258 msg_str.push('\n');
260
261 let reply = EnclaveProcessReply::StdOutMessage(msg_str);
262 self.write_reply(&reply)
263 }
264
265 pub fn eprintln(&self, msg: &str) -> NitroCliResult<()> {
267 let mut msg_str = msg.to_string();
268
269 msg_str.push('\n');
271
272 let reply = EnclaveProcessReply::StdErrMessage(msg_str);
273 self.write_reply(&reply)
274 }
275
276 pub fn write_status(&self, status: i32) -> NitroCliResult<()> {
278 let reply = EnclaveProcessReply::Status(status);
279 self.write_reply(&reply)
280 }
281
282 pub fn get_enclave_event_flags(&self) -> NitroCliResult<Option<EpollFlags>> {
284 let lock = self.data.lock().map_err(|e| {
285 new_nitro_cli_failure!(
286 &format!("Failed to acquire connection lock: {e:?}"),
287 NitroCliErrorEnum::LockAcquireFailure
288 )
289 })?;
290 match lock.input_stream {
291 None => Ok(Some(lock.epoll_flags)),
292 _ => Ok(None),
293 }
294 }
295
296 fn write_reply(&self, reply: &EnclaveProcessReply) -> NitroCliResult<()> {
298 let mut lock = self.data.lock().map_err(|e| {
299 new_nitro_cli_failure!(
300 &format!("Failed to acquire lock: {e:?}"),
301 NitroCliErrorEnum::LockAcquireFailure
302 )
303 })?;
304 if lock.input_stream.is_none() {
305 return Err(new_nitro_cli_failure!(
306 "Cannot write message to connection",
307 NitroCliErrorEnum::SocketError
308 ));
309 }
310
311 let mut stream = lock.input_stream.as_mut().unwrap();
312 let mut reply_bytes = Vec::new();
313 ciborium::into_writer(reply, &mut reply_bytes).map_err(|e| {
314 new_nitro_cli_failure!(
315 &format!("Failed to serialize reply: {e:?}"),
316 NitroCliErrorEnum::SerdeError
317 )
318 })?;
319
320 write_u64_le(&mut stream, reply_bytes.len() as u64)
321 .map_err(|e| e.add_subaction("Write reply".to_string()))?;
322 stream.write_all(&reply_bytes).map_err(|e| {
323 new_nitro_cli_failure!(
324 &format!("Failed to write to stream: {e:?}"),
325 NitroCliErrorEnum::SocketError
326 )
327 })
328 }
329}
330
331pub fn safe_conn_println(conn: Option<&Connection>, msg: &str) -> NitroCliResult<()> {
333 if conn.is_none() {
334 return Ok(());
335 }
336
337 conn.unwrap().println(msg)
338}
339
340pub fn safe_conn_eprintln(conn: Option<&Connection>, msg: &str) -> NitroCliResult<()> {
342 if conn.is_none() {
343 return Ok(());
344 }
345
346 conn.unwrap().eprintln(msg)
347}