#![deny(missing_docs)]
#![deny(warnings)]
pub mod commands_parser;
pub mod document_errors;
pub mod json_output;
pub mod logger;
pub mod signal_handler;
use chrono::offset::Utc;
use log::error;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::env;
use std::io::{Read, Write};
#[cfg(test)]
use std::os::raw::c_char;
use std::os::unix::net::UnixStream;
use std::path::{Path, PathBuf};
use document_errors::ERROR_CODES;
use logger::get_log_file_base_path;
pub type NitroCliResult<T> = Result<T, NitroCliFailure>;
pub const VMADDR_CID_PARENT: u32 = 3;
pub const ENCLAVE_READY_VSOCK_PORT: u32 = 9000;
pub const ENCLAVE_PROC_WAIT_TIMEOUT_MSEC: isize = 3000;
pub const MSG_ENCLAVE_CONFIRM: u64 = 0xEEC0;
pub const SOCKETS_DIR_PATH_ENV_VAR: &str = "NITRO_CLI_SOCKETS_PATH";
const SOCKETS_DIR_PATH: &str = "/run/nitro_enclaves";
const BACKTRACE_VAR: &str = "BACKTRACE";
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq)]
pub enum NitroCliErrorEnum {
#[default]
UnspecifiedError = 0,
MissingArgument,
ConflictingArgument,
InvalidArgument,
SocketPairCreationFailure,
ProcessSpawnFailure,
DaemonizeProcessFailure,
ReadFromDiskFailure,
UnusableConnectionError,
SocketCloseError,
SocketConnectTimeoutError,
SocketError,
EpollError,
InotifyError,
InvalidCommand,
LockAcquireFailure,
ThreadJoinFailure,
SerdeError,
FilePermissionsError,
FileOperationFailure,
InvalidCpuConfiguration,
NoSuchCpuAvailableInPool,
InsufficientCpus,
MalformedCpuId,
CpuError,
NoSuchHugepageFlag,
InsufficientMemoryRequested,
InsufficientMemoryAvailable,
InvalidEnclaveFd,
IoctlFailure,
IoctlImageLoadInfoFailure,
IoctlSetMemoryRegionFailure,
IoctlAddVcpuFailure,
IoctlEnclaveStartFailure,
MemoryOverflow,
EifParsingError,
EnclaveBootFailure,
EnclaveEventWaitError,
EnclaveProcessCommandNotExecuted,
EnclaveProcessConnectionFailure,
SocketPathNotFound,
EnclaveProcessSendReplyFailure,
EnclaveMmapError,
EnclaveMunmapError,
EnclaveConsoleConnectionFailure,
EnclaveConsoleReadError,
EnclaveConsoleWriteOutputError,
IntegerParsingError,
EifBuildingError,
DockerImageBuildError,
DockerImagePullError,
ArtifactsPathNotSet,
BlobsPathNotSet,
ClockSkewError,
SignalMaskingError,
SignalUnmaskingError,
LoggerError,
HasherError,
EnclaveNamingError,
EIFSignatureCheckerError,
EIFSigningError,
}
impl Eq for NitroCliErrorEnum {}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum EnclaveProcessCommandType {
Run = 0,
Terminate,
TerminateComplete,
Describe,
GetEnclaveCID,
GetEnclaveFlags,
GetEnclaveName,
GetIDbyName,
ConnectionListenerStop,
NotPermitted,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum EnclaveProcessReply {
StdOutMessage(String),
StdErrMessage(String),
Status(i32),
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct NitroCliFailure {
pub action: String,
pub subactions: Vec<String>,
pub error_code: NitroCliErrorEnum,
pub file: String,
pub line: u32,
pub additional_info: Vec<String>,
}
impl NitroCliFailure {
pub fn new() -> Self {
NitroCliFailure {
action: String::new(),
subactions: vec![],
error_code: NitroCliErrorEnum::default(),
file: String::new(),
line: 0,
additional_info: vec![],
}
}
pub fn set_action(mut self, action: String) -> Self {
self.action = action;
self
}
pub fn add_subaction(mut self, subaction: String) -> Self {
self.subactions.push(subaction);
self
}
pub fn set_error_code(mut self, error_code: NitroCliErrorEnum) -> Self {
self.error_code = error_code;
self
}
pub fn set_file(mut self, file: &str) -> Self {
self.file = file.to_string();
self
}
pub fn set_line(mut self, line: u32) -> Self {
self.line = line;
self
}
pub fn set_file_and_line(mut self, file: &str, line: u32) -> Self {
self.file = file.to_string();
self.line = line;
self
}
pub fn add_info(mut self, info: Vec<&str>) -> Self {
for info_ in info {
self.additional_info.push(info_.to_string());
}
self
}
}
#[macro_export]
macro_rules! new_nitro_cli_failure {
($subaction:expr, $error_code:expr) => {
NitroCliFailure::new()
.add_subaction(($subaction).to_string())
.set_error_code($error_code)
.set_file_and_line(file!(), line!())
};
}
fn log_backtrace(backtrace: String) -> Result<String, &'static str> {
let log_path_base = get_log_file_base_path();
if !Path::new(&log_path_base).exists() {
let create_logs_dir = std::fs::create_dir_all(&log_path_base);
if create_logs_dir.is_err() {
return Err("Could not create backtrace logs directory");
}
}
let utc_time_now = Utc::now().to_rfc3339();
let log_path_str = format!("{}/err{}.log", log_path_base, utc_time_now);
let log_path = Path::new(&log_path_str);
let log_file = std::fs::File::create(log_path);
if log_file.is_err() {
return Err("Could not create backtrace log file");
}
let write_result = log_file.unwrap().write_all(backtrace.as_bytes());
if write_result.is_err() {
return Err("Could not write to backtrace log file");
}
match log_path.to_str() {
Some(log_path) => Ok(log_path.to_string()),
None => Err("Could not return log file path"),
}
}
pub fn construct_error_message(failure: &NitroCliFailure) -> String {
let error_info: String = document_errors::get_detailed_info(
(*ERROR_CODES.get(&failure.error_code).unwrap_or(&"E00")).to_string(),
&failure.additional_info,
);
let help_link: String = document_errors::construct_help_link(
(*ERROR_CODES.get(&failure.error_code).unwrap_or(&"E00")).to_string(),
);
let backtrace: String = document_errors::construct_backtrace(failure);
let log_path = log_backtrace(backtrace.clone());
match std::env::var(BACKTRACE_VAR) {
Ok(display_backtrace) => match display_backtrace.as_str() {
"1" => {
if let Ok(log_path) = log_path {
format!(
"{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}\""
)
} else {
format!(
"{error_info}\n\nFor more details, please visit {help_link}\n\nBacktrace:\n{backtrace}"
)
}
}
_ => {
if let Ok(log_path) = log_path {
format!(
"{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}\""
)
} else {
format!("{error_info}\n\nFor more details, please visit {help_link}")
}
}
},
_ => {
if let Ok(log_path) = log_path {
format!(
"{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}\""
)
} else {
format!("{error_info}\n\nFor more details, please visit {help_link}")
}
}
}
}
pub trait ExitGracefully<T> {
fn ok_or_exit_with_errno(self, additional_info: Option<&str>) -> T;
}
impl<T> ExitGracefully<T> for NitroCliResult<T> {
fn ok_or_exit_with_errno(self, additional_info: Option<&str>) -> T {
match self {
Ok(val) => val,
Err(err) => {
let err_str = construct_error_message(&err);
if let Some(additional_info_str) = additional_info {
notify_error(&format!("{additional_info_str} | {err_str}"));
} else {
notify_error(&err_str);
}
std::process::exit(err.error_code as i32);
}
}
}
}
pub fn notify_error(err_msg: &str) {
eprintln!("{err_msg}");
error!("{}", err_msg);
}
pub fn read_u64_le(socket: &mut dyn Read) -> NitroCliResult<u64> {
let mut bytes = [0u8; std::mem::size_of::<u64>()];
socket.read_exact(&mut bytes).map_err(|e| {
new_nitro_cli_failure!(
&format!(
"Failed to read {} bytes from the given socket: {:?}",
std::mem::size_of::<u64>(),
e
),
NitroCliErrorEnum::SocketError
)
})?;
Ok(u64::from_le_bytes(bytes))
}
pub fn write_u64_le(socket: &mut dyn Write, value: u64) -> NitroCliResult<()> {
let bytes = value.to_le_bytes();
socket.write_all(&bytes).map_err(|e| {
new_nitro_cli_failure!(
&format!(
"Failed to write {} bytes to the given socket: {:?}",
std::mem::size_of::<u64>(),
e
),
NitroCliErrorEnum::SocketError
)
})
}
pub fn enclave_proc_command_send_single<T>(
cmd: EnclaveProcessCommandType,
args: Option<&T>,
mut socket: &mut UnixStream,
) -> NitroCliResult<()>
where
T: Serialize,
{
let mut cmd_bytes = Vec::new();
ciborium::ser::into_writer(&cmd, &mut cmd_bytes).map_err(|e| {
new_nitro_cli_failure!(
&format!("Invalid command format: {e:?}"),
NitroCliErrorEnum::InvalidCommand
)
})?;
for _ in 0..2 {
write_u64_le(&mut socket, cmd_bytes.len() as u64)
.map_err(|e| e.add_subaction("Failed to send single command size".to_string()))?;
socket.write_all(&cmd_bytes[..]).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to send single command: {e:?}"),
NitroCliErrorEnum::SocketError
)
})?;
}
if let Some(args) = args {
let mut arg_bytes = Vec::new();
ciborium::ser::into_writer(args, &mut arg_bytes).map_err(|e| {
new_nitro_cli_failure!(
&format!("Invalid single command arguments: {e:?}"),
NitroCliErrorEnum::InvalidCommand
)
})?;
write_u64_le(&mut socket, arg_bytes.len() as u64)
.map_err(|e| e.add_subaction("Failed to send arguments size".to_string()))?;
socket.write_all(&arg_bytes).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to send arguments: {e:?}"),
NitroCliErrorEnum::SocketError
)
})?;
}
Ok(())
}
pub fn receive_from_stream<T>(input_stream: &mut dyn Read) -> NitroCliResult<T>
where
T: DeserializeOwned,
{
let size = read_u64_le(input_stream)
.map_err(|e| e.add_subaction("Failed to receive data size".to_string()))?
as usize;
let mut raw_data: Vec<u8> = vec![0; size];
let data: T =
ciborium::de::from_reader_with_buffer(input_stream, &mut raw_data[..]).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to decode received data: {e:?}"),
NitroCliErrorEnum::SerdeError
)
})?;
Ok(data)
}
pub fn get_sockets_dir_path() -> PathBuf {
let log_path = match env::var(SOCKETS_DIR_PATH_ENV_VAR) {
Ok(env_path) => env_path,
Err(_) => SOCKETS_DIR_PATH.to_string(),
};
Path::new(&log_path).to_path_buf()
}
pub fn get_socket_path(enclave_id: &str) -> NitroCliResult<PathBuf> {
let tokens: Vec<_> = enclave_id.rsplit("-enc").collect();
let sockets_path = get_sockets_dir_path();
Ok(sockets_path.join(tokens[0]).with_extension("sock"))
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
use crate::common::commands_parser::EmptyArgs;
const TMP_DIR_STR: &str = "./tmp_sock_dir";
fn unset_envvar(varname: &str) {
unsafe {
libc::unsetenv(varname.as_ptr() as *const c_char);
};
}
#[test]
fn test_read_write_u64() {
let (mut sock0, mut sock1) = UnixStream::pair().unwrap();
let _ = write_u64_le(&mut sock0, 127);
let result = read_u64_le(&mut sock1);
if let Ok(result) = result {
assert_eq!(result, 127);
}
}
#[test]
fn test_enclave_proc_command_send_single() {
let (mut sock0, mut sock1) = UnixStream::pair().unwrap();
let cmd = EnclaveProcessCommandType::Describe;
let args: std::option::Option<&EmptyArgs> = None;
let result0 = enclave_proc_command_send_single::<EmptyArgs>(cmd, args, &mut sock0);
assert!(result0.is_ok());
let result1 = receive_from_stream::<EnclaveProcessCommandType>(&mut sock1);
assert!(result1.is_ok());
assert_eq!(result1.unwrap(), EnclaveProcessCommandType::Describe);
}
#[test]
fn test_get_sockets_dir_path_default() {
let sockets_dir = env::var(SOCKETS_DIR_PATH_ENV_VAR);
let sockets_dir_path_f = get_sockets_dir_path();
if let Ok(sockets_dir) = sockets_dir {
assert_eq!(sockets_dir, sockets_dir_path_f.as_path().to_str().unwrap());
} else {
assert_eq!(
SOCKETS_DIR_PATH,
sockets_dir_path_f.as_path().to_str().unwrap()
);
}
}
#[test]
fn test_get_sockets_dir_path_custom_envvar() {
let old_sockets_dir = env::var(SOCKETS_DIR_PATH_ENV_VAR);
env::set_var(SOCKETS_DIR_PATH_ENV_VAR, TMP_DIR_STR);
let sockets_dir_path_f = get_sockets_dir_path();
assert_eq!(TMP_DIR_STR, sockets_dir_path_f.as_path().to_str().unwrap());
if let Ok(old_sockets_dir) = old_sockets_dir {
env::set_var(SOCKETS_DIR_PATH_ENV_VAR, old_sockets_dir);
} else {
env::set_var(SOCKETS_DIR_PATH_ENV_VAR, "");
unset_envvar(&String::from(SOCKETS_DIR_PATH_ENV_VAR));
}
}
#[test]
fn test_get_socket_path_valid_id() {
let enclave_id = "i-0000000000000000-enc0123456789012345";
let tokens: Vec<_> = enclave_id.rsplit("-enc").collect();
let sockets_path = get_sockets_dir_path();
let result = get_socket_path(enclave_id);
assert!(result.is_ok());
assert_eq!(
result.unwrap().as_path().to_str().unwrap(),
format!(
"{}/{}.sock",
sockets_path.as_path().to_str().unwrap(),
tokens[0]
)
);
}
#[test]
fn test_get_socket_path_invalid_id() {
let enclave_id = "i-0000000000000000_enc0123456789012345";
let sockets_path = get_sockets_dir_path();
let result = get_socket_path(enclave_id);
assert!(result.is_ok());
assert_eq!(
result.unwrap().as_path().to_str().unwrap(),
format!(
"{}/{}.sock",
sockets_path.as_path().to_str().unwrap(),
enclave_id
)
);
}
}