1#![deny(missing_docs)]
4#![deny(warnings)]
5
6pub mod commands_parser;
8pub mod document_errors;
10pub mod json_output;
12pub mod logger;
14pub mod signal_handler;
16
17use chrono::offset::Utc;
18use log::error;
19use serde::de::DeserializeOwned;
20use serde::{Deserialize, Serialize};
21use std::env;
22use std::io::{Read, Write};
23
24#[cfg(test)]
25use std::os::raw::c_char;
26
27use std::os::unix::net::UnixStream;
28use std::path::{Path, PathBuf};
29
30use document_errors::ERROR_CODES;
31use logger::get_log_file_base_path;
32
33pub type NitroCliResult<T> = Result<T, NitroCliFailure>;
35
36pub const VMADDR_CID_PARENT: u32 = 3;
38
39pub const ENCLAVE_READY_VSOCK_PORT: u32 = 9000;
41
42pub const ENCLAVE_PROC_WAIT_TIMEOUT_MSEC: isize = 3000;
44
45pub const MSG_ENCLAVE_CONFIRM: u64 = 0xEEC0;
48
49pub const SOCKETS_DIR_PATH_ENV_VAR: &str = "NITRO_CLI_SOCKETS_PATH";
51
52const SOCKETS_DIR_PATH: &str = "/run/nitro_enclaves";
54
55const BACKTRACE_VAR: &str = "BACKTRACE";
57
58#[derive(Debug, Default, Clone, Copy, Hash, PartialEq)]
60pub enum NitroCliErrorEnum {
61 #[default]
62 UnspecifiedError = 0,
64 MissingArgument,
66 ConflictingArgument,
68 InvalidArgument,
70 SocketPairCreationFailure,
72 ProcessSpawnFailure,
74 DaemonizeProcessFailure,
76 ReadFromDiskFailure,
78 UnusableConnectionError,
80 SocketCloseError,
82 SocketConnectTimeoutError,
84 SocketError,
86 EpollError,
88 InotifyError,
90 InvalidCommand,
92 LockAcquireFailure,
94 ThreadJoinFailure,
96 SerdeError,
98 FilePermissionsError,
100 FileOperationFailure,
102 InvalidCpuConfiguration,
104 NoSuchCpuAvailableInPool,
106 InsufficientCpus,
108 MalformedCpuId,
110 CpuError,
112 NoSuchHugepageFlag,
114 InsufficientMemoryRequested,
116 InsufficientMemoryAvailable,
118 InvalidEnclaveFd,
120 IoctlFailure,
122 IoctlImageLoadInfoFailure,
124 IoctlSetMemoryRegionFailure,
126 IoctlAddVcpuFailure,
128 IoctlEnclaveStartFailure,
130 MemoryOverflow,
132 EifParsingError,
134 EnclaveBootFailure,
136 EnclaveEventWaitError,
138 EnclaveProcessCommandNotExecuted,
140 EnclaveProcessConnectionFailure,
142 SocketPathNotFound,
144 EnclaveProcessSendReplyFailure,
146 EnclaveMmapError,
148 EnclaveMunmapError,
150 EnclaveConsoleConnectionFailure,
152 EnclaveConsoleReadError,
154 EnclaveConsoleWriteOutputError,
156 IntegerParsingError,
158 EifBuildingError,
160 DockerImageBuildError,
162 DockerImagePullError,
164 ArtifactsPathNotSet,
166 BlobsPathNotSet,
168 ClockSkewError,
170 SignalMaskingError,
172 SignalUnmaskingError,
174 LoggerError,
176 HasherError,
178 EnclaveNamingError,
180 EIFSignatureCheckerError,
182 EIFSigningError,
184}
185
186impl Eq for NitroCliErrorEnum {}
187
188#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
190pub enum EnclaveProcessCommandType {
191 Run = 0,
193 Terminate,
195 TerminateComplete,
197 Describe,
199 GetEnclaveCID,
201 GetEnclaveFlags,
203 GetEnclaveName,
205 GetIDbyName,
207 ConnectionListenerStop,
209 NotPermitted,
211}
212
213#[derive(Debug, Serialize, Deserialize)]
215pub enum EnclaveProcessReply {
216 StdOutMessage(String),
218 StdErrMessage(String),
220 Status(i32),
222}
223
224#[derive(Debug, Default, PartialEq, Eq)]
226pub struct NitroCliFailure {
227 pub action: String,
229 pub subactions: Vec<String>,
231 pub error_code: NitroCliErrorEnum,
233 pub file: String,
235 pub line: u32,
237 pub additional_info: Vec<String>,
239}
240
241impl NitroCliFailure {
242 pub fn new() -> Self {
244 NitroCliFailure {
245 action: String::new(),
246 subactions: vec![],
247 error_code: NitroCliErrorEnum::default(),
248 file: String::new(),
249 line: 0,
250 additional_info: vec![],
251 }
252 }
253
254 pub fn set_action(mut self, action: String) -> Self {
256 self.action = action;
257 self
258 }
259
260 pub fn add_subaction(mut self, subaction: String) -> Self {
262 self.subactions.push(subaction);
263 self
264 }
265
266 pub fn set_error_code(mut self, error_code: NitroCliErrorEnum) -> Self {
268 self.error_code = error_code;
269 self
270 }
271
272 pub fn set_file(mut self, file: &str) -> Self {
274 self.file = file.to_string();
275 self
276 }
277
278 pub fn set_line(mut self, line: u32) -> Self {
280 self.line = line;
281 self
282 }
283
284 pub fn set_file_and_line(mut self, file: &str, line: u32) -> Self {
286 self.file = file.to_string();
287 self.line = line;
288 self
289 }
290
291 pub fn add_info(mut self, info: Vec<&str>) -> Self {
293 for info_ in info {
294 self.additional_info.push(info_.to_string());
295 }
296 self
297 }
298}
299
300#[macro_export]
302macro_rules! new_nitro_cli_failure {
303 ($subaction:expr, $error_code:expr) => {
304 NitroCliFailure::new()
305 .add_subaction(($subaction).to_string())
306 .set_error_code($error_code)
307 .set_file_and_line(file!(), line!())
308 };
309}
310
311fn log_backtrace(backtrace: String) -> Result<String, &'static str> {
314 let log_path_base = get_log_file_base_path();
315
316 if !Path::new(&log_path_base).exists() {
318 let create_logs_dir = std::fs::create_dir_all(&log_path_base);
319 if create_logs_dir.is_err() {
320 return Err("Could not create backtrace logs directory");
321 }
322 }
323
324 let utc_time_now = Utc::now().to_rfc3339();
325 let log_path_str = format!("{}/err{}.log", log_path_base, utc_time_now);
326 let log_path = Path::new(&log_path_str);
327 let log_file = std::fs::File::create(log_path);
328 if log_file.is_err() {
329 return Err("Could not create backtrace log file");
330 }
331
332 let write_result = log_file.unwrap().write_all(backtrace.as_bytes());
333 if write_result.is_err() {
334 return Err("Could not write to backtrace log file");
335 }
336
337 match log_path.to_str() {
338 Some(log_path) => Ok(log_path.to_string()),
339 None => Err("Could not return log file path"),
340 }
341}
342
343pub fn construct_error_message(failure: &NitroCliFailure) -> String {
345 let error_info: String = document_errors::get_detailed_info(
347 (*ERROR_CODES.get(&failure.error_code).unwrap_or(&"E00")).to_string(),
348 &failure.additional_info,
349 );
350
351 let help_link: String = document_errors::construct_help_link(
353 (*ERROR_CODES.get(&failure.error_code).unwrap_or(&"E00")).to_string(),
354 );
355 let backtrace: String = document_errors::construct_backtrace(failure);
356
357 let log_path = log_backtrace(backtrace.clone());
359
360 match std::env::var(BACKTRACE_VAR) {
362 Ok(display_backtrace) => match display_backtrace.as_str() {
363 "1" => {
364 if let Ok(log_path) = log_path {
365 format!(
366 "{error_info}\n\nFor more details, please visit {help_link}\n\nBacktrace:\n{backtrace}\n\nIf you open a support ticket, please provide the error log found at \"{log_path}\""
367 )
368 } else {
369 format!(
370 "{error_info}\n\nFor more details, please visit {help_link}\n\nBacktrace:\n{backtrace}"
371 )
372 }
373 }
374 _ => {
375 if let Ok(log_path) = log_path {
376 format!(
377 "{error_info}\n\nFor more details, please visit {help_link}\n\nIf you open a support ticket, please provide the error log found at \"{log_path}\""
378 )
379 } else {
380 format!("{error_info}\n\nFor more details, please visit {help_link}")
381 }
382 }
383 },
384 _ => {
385 if let Ok(log_path) = log_path {
386 format!(
387 "{error_info}\n\nFor more details, please visit {help_link}\n\nIf you open a support ticket, please provide the error log found at \"{log_path}\""
388 )
389 } else {
390 format!("{error_info}\n\nFor more details, please visit {help_link}")
391 }
392 }
393 }
394}
395
396pub trait ExitGracefully<T> {
399 fn ok_or_exit_with_errno(self, additional_info: Option<&str>) -> T;
401}
402
403impl<T> ExitGracefully<T> for NitroCliResult<T> {
404 fn ok_or_exit_with_errno(self, additional_info: Option<&str>) -> T {
406 match self {
407 Ok(val) => val,
408 Err(err) => {
409 let err_str = construct_error_message(&err);
410 if let Some(additional_info_str) = additional_info {
411 notify_error(&format!("{additional_info_str} | {err_str}"));
412 } else {
413 notify_error(&err_str);
414 }
415 std::process::exit(err.error_code as i32);
416 }
417 }
418 }
419}
420
421pub fn notify_error(err_msg: &str) {
423 eprintln!("{err_msg}");
424 error!("{}", err_msg);
425}
426
427pub fn read_u64_le(socket: &mut dyn Read) -> NitroCliResult<u64> {
429 let mut bytes = [0u8; std::mem::size_of::<u64>()];
430 socket.read_exact(&mut bytes).map_err(|e| {
431 new_nitro_cli_failure!(
432 &format!(
433 "Failed to read {} bytes from the given socket: {:?}",
434 std::mem::size_of::<u64>(),
435 e
436 ),
437 NitroCliErrorEnum::SocketError
438 )
439 })?;
440
441 Ok(u64::from_le_bytes(bytes))
442}
443
444pub fn write_u64_le(socket: &mut dyn Write, value: u64) -> NitroCliResult<()> {
446 let bytes = value.to_le_bytes();
447 socket.write_all(&bytes).map_err(|e| {
448 new_nitro_cli_failure!(
449 &format!(
450 "Failed to write {} bytes to the given socket: {:?}",
451 std::mem::size_of::<u64>(),
452 e
453 ),
454 NitroCliErrorEnum::SocketError
455 )
456 })
457}
458
459pub fn enclave_proc_command_send_single<T>(
461 cmd: EnclaveProcessCommandType,
462 args: Option<&T>,
463 mut socket: &mut UnixStream,
464) -> NitroCliResult<()>
465where
466 T: Serialize,
467{
468 let mut cmd_bytes = Vec::new();
470 ciborium::ser::into_writer(&cmd, &mut cmd_bytes).map_err(|e| {
471 new_nitro_cli_failure!(
472 &format!("Invalid command format: {e:?}"),
473 NitroCliErrorEnum::InvalidCommand
474 )
475 })?;
476
477 for _ in 0..2 {
480 write_u64_le(&mut socket, cmd_bytes.len() as u64)
481 .map_err(|e| e.add_subaction("Failed to send single command size".to_string()))?;
482 socket.write_all(&cmd_bytes[..]).map_err(|e| {
483 new_nitro_cli_failure!(
484 &format!("Failed to send single command: {e:?}"),
485 NitroCliErrorEnum::SocketError
486 )
487 })?;
488 }
489
490 if let Some(args) = args {
492 let mut arg_bytes = Vec::new();
493 ciborium::ser::into_writer(args, &mut arg_bytes).map_err(|e| {
494 new_nitro_cli_failure!(
495 &format!("Invalid single command arguments: {e:?}"),
496 NitroCliErrorEnum::InvalidCommand
497 )
498 })?;
499
500 write_u64_le(&mut socket, arg_bytes.len() as u64)
502 .map_err(|e| e.add_subaction("Failed to send arguments size".to_string()))?;
503 socket.write_all(&arg_bytes).map_err(|e| {
504 new_nitro_cli_failure!(
505 &format!("Failed to send arguments: {e:?}"),
506 NitroCliErrorEnum::SocketError
507 )
508 })?;
509 }
510
511 Ok(())
512}
513
514pub fn receive_from_stream<T>(input_stream: &mut dyn Read) -> NitroCliResult<T>
516where
517 T: DeserializeOwned,
518{
519 let size = read_u64_le(input_stream)
520 .map_err(|e| e.add_subaction("Failed to receive data size".to_string()))?
521 as usize;
522 let mut raw_data: Vec<u8> = vec![0; size];
523 let data: T =
524 ciborium::de::from_reader_with_buffer(input_stream, &mut raw_data[..]).map_err(|e| {
525 new_nitro_cli_failure!(
526 &format!("Failed to decode received data: {e:?}"),
527 NitroCliErrorEnum::SerdeError
528 )
529 })?;
530 Ok(data)
531}
532
533pub fn get_sockets_dir_path() -> PathBuf {
535 let log_path = match env::var(SOCKETS_DIR_PATH_ENV_VAR) {
536 Ok(env_path) => env_path,
537 Err(_) => SOCKETS_DIR_PATH.to_string(),
538 };
539 Path::new(&log_path).to_path_buf()
540}
541
542pub fn get_socket_path(enclave_id: &str) -> NitroCliResult<PathBuf> {
544 let tokens: Vec<_> = enclave_id.rsplit("-enc").collect();
546 let sockets_path = get_sockets_dir_path();
547 Ok(sockets_path.join(tokens[0]).with_extension("sock"))
548}
549
550#[cfg(test)]
551mod tests {
552 #[allow(unused_imports)]
553 use super::*;
554
555 use crate::common::commands_parser::EmptyArgs;
556
557 const TMP_DIR_STR: &str = "./tmp_sock_dir";
558
559 fn unset_envvar(varname: &str) {
560 unsafe {
561 libc::unsetenv(varname.as_ptr() as *const c_char);
562 };
563 }
564
565 #[test]
568 fn test_read_write_u64() {
569 let (mut sock0, mut sock1) = UnixStream::pair().unwrap();
570
571 let _ = write_u64_le(&mut sock0, 127);
572 let result = read_u64_le(&mut sock1);
573
574 if let Ok(result) = result {
575 assert_eq!(result, 127);
576 }
577 }
578
579 #[test]
582 fn test_enclave_proc_command_send_single() {
583 let (mut sock0, mut sock1) = UnixStream::pair().unwrap();
584 let cmd = EnclaveProcessCommandType::Describe;
585 let args: std::option::Option<&EmptyArgs> = None;
586
587 let result0 = enclave_proc_command_send_single::<EmptyArgs>(cmd, args, &mut sock0);
588 assert!(result0.is_ok());
589
590 let result1 = receive_from_stream::<EnclaveProcessCommandType>(&mut sock1);
591 assert!(result1.is_ok());
592 assert_eq!(result1.unwrap(), EnclaveProcessCommandType::Describe);
593 }
594
595 #[test]
598 fn test_get_sockets_dir_path_default() {
599 let sockets_dir = env::var(SOCKETS_DIR_PATH_ENV_VAR);
600 let sockets_dir_path_f = get_sockets_dir_path();
601
602 if let Ok(sockets_dir) = sockets_dir {
603 assert_eq!(sockets_dir, sockets_dir_path_f.as_path().to_str().unwrap());
604 } else {
605 assert_eq!(
606 SOCKETS_DIR_PATH,
607 sockets_dir_path_f.as_path().to_str().unwrap()
608 );
609 }
610 }
611
612 #[test]
615 fn test_get_sockets_dir_path_custom_envvar() {
616 let old_sockets_dir = env::var(SOCKETS_DIR_PATH_ENV_VAR);
617 env::set_var(SOCKETS_DIR_PATH_ENV_VAR, TMP_DIR_STR);
618
619 let sockets_dir_path_f = get_sockets_dir_path();
620
621 assert_eq!(TMP_DIR_STR, sockets_dir_path_f.as_path().to_str().unwrap());
622
623 if let Ok(old_sockets_dir) = old_sockets_dir {
625 env::set_var(SOCKETS_DIR_PATH_ENV_VAR, old_sockets_dir);
626 } else {
627 env::set_var(SOCKETS_DIR_PATH_ENV_VAR, "");
628 unset_envvar(&String::from(SOCKETS_DIR_PATH_ENV_VAR));
629 }
630 }
631
632 #[test]
635 fn test_get_socket_path_valid_id() {
636 let enclave_id = "i-0000000000000000-enc0123456789012345";
637 let tokens: Vec<_> = enclave_id.rsplit("-enc").collect();
638 let sockets_path = get_sockets_dir_path();
639 let result = get_socket_path(enclave_id);
640
641 assert!(result.is_ok());
642 assert_eq!(
643 result.unwrap().as_path().to_str().unwrap(),
644 format!(
645 "{}/{}.sock",
646 sockets_path.as_path().to_str().unwrap(),
647 tokens[0]
648 )
649 );
650 }
651
652 #[test]
655 fn test_get_socket_path_invalid_id() {
656 let enclave_id = "i-0000000000000000_enc0123456789012345";
657 let sockets_path = get_sockets_dir_path();
658 let result = get_socket_path(enclave_id);
659
660 assert!(result.is_ok());
661 assert_eq!(
662 result.unwrap().as_path().to_str().unwrap(),
663 format!(
664 "{}/{}.sock",
665 sockets_path.as_path().to_str().unwrap(),
666 enclave_id
667 )
668 );
669 }
670}