#![deny(missing_docs)]
#![deny(warnings)]
use log::{debug, warn};
use nix::sys::epoll::EpollFlags;
use nix::sys::socket::sockopt::PeerCredentials;
use nix::sys::socket::UnixCredentials;
use serde::de::DeserializeOwned;
use std::collections::HashMap;
use std::io::Write;
use std::os::unix::io::AsRawFd;
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
use crate::common::{receive_from_stream, write_u64_le};
use crate::common::{
EnclaveProcessCommandType, EnclaveProcessReply, ExitGracefully, NitroCliErrorEnum,
NitroCliFailure, NitroCliResult,
};
use crate::new_nitro_cli_failure;
#[derive(PartialEq, Eq, Hash)]
enum CommandRequesterType {
User(libc::uid_t),
Group(libc::gid_t),
Others,
}
struct CommandRequesterPolicy {
policy: HashMap<CommandRequesterType, Vec<EnclaveProcessCommandType>>,
}
struct ConnectionData {
epoll_flags: EpollFlags,
input_stream: Option<UnixStream>,
}
#[derive(Clone)]
pub struct Connection {
data: Arc<Mutex<ConnectionData>>,
}
impl Drop for ConnectionData {
fn drop(&mut self) {
if let Some(input_stream) = &self.input_stream {
input_stream
.shutdown(std::net::Shutdown::Both)
.map_err(|e| {
new_nitro_cli_failure!(
&format!("Stream shutdown error: {e:?}"),
NitroCliErrorEnum::SocketCloseError
)
})
.ok_or_exit_with_errno(Some("Failed to shut down"));
}
}
}
impl CommandRequesterPolicy {
fn new_with_defaults() -> Self {
let cmds_read_write = vec![
EnclaveProcessCommandType::Run,
EnclaveProcessCommandType::Terminate,
EnclaveProcessCommandType::TerminateComplete,
EnclaveProcessCommandType::Describe,
EnclaveProcessCommandType::GetEnclaveCID,
EnclaveProcessCommandType::GetEnclaveFlags,
EnclaveProcessCommandType::GetEnclaveName,
EnclaveProcessCommandType::GetIDbyName,
EnclaveProcessCommandType::ConnectionListenerStop,
];
let cmds_read_only = vec![
EnclaveProcessCommandType::Describe,
EnclaveProcessCommandType::GetEnclaveCID,
EnclaveProcessCommandType::GetEnclaveFlags,
EnclaveProcessCommandType::GetEnclaveName,
EnclaveProcessCommandType::GetIDbyName,
];
let mut policy = HashMap::new();
policy.insert(
CommandRequesterType::User(unsafe { libc::getuid() }),
cmds_read_write.clone(),
);
policy.insert(CommandRequesterType::User(0_u32), cmds_read_write);
policy.insert(CommandRequesterType::Others, cmds_read_only);
CommandRequesterPolicy { policy }
}
fn find_policy_rule(
&self,
cmd: EnclaveProcessCommandType,
requester: &CommandRequesterType,
) -> bool {
match self.policy.get(requester) {
None => false,
Some(allowed_cmds) => allowed_cmds.contains(&cmd),
}
}
fn can_execute_command(&self, cmd: EnclaveProcessCommandType, creds: &UnixCredentials) -> bool {
if self.find_policy_rule(cmd, &CommandRequesterType::User(creds.uid())) {
return true;
}
if self.find_policy_rule(cmd, &CommandRequesterType::Group(creds.gid())) {
return true;
}
if self.find_policy_rule(cmd, &CommandRequesterType::Others) {
return true;
}
false
}
}
impl Connection {
pub fn new(epoll_flags: EpollFlags, input_stream: Option<UnixStream>) -> Self {
let conn_data = ConnectionData {
epoll_flags,
input_stream,
};
Connection {
data: Arc::new(Mutex::new(conn_data)),
}
}
pub fn read_command(&self) -> NitroCliResult<EnclaveProcessCommandType> {
let mut lock = self.data.lock().map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to acquire lock: {e:?}"),
NitroCliErrorEnum::LockAcquireFailure
)
})?;
if lock.input_stream.is_none() {
return Err(new_nitro_cli_failure!(
"Cannot read a command from this connection",
NitroCliErrorEnum::UnusableConnectionError
));
}
let mut cmd =
receive_from_stream::<EnclaveProcessCommandType>(lock.input_stream.as_mut().unwrap())?;
let conn_fd = lock.input_stream.as_ref().unwrap().as_raw_fd();
let socket_creds = nix::sys::socket::getsockopt(conn_fd, PeerCredentials);
let user_creds = match socket_creds {
Ok(creds) => creds,
Err(e) => {
warn!("Failed to get user credentials: {}", e);
return Ok(EnclaveProcessCommandType::NotPermitted);
}
};
let policy = CommandRequesterPolicy::new_with_defaults();
if !policy.can_execute_command(cmd, &user_creds) {
warn!(
"The requester with credentials ({:?}) is not allowed to perform '{:?}'.",
user_creds, cmd
);
cmd = EnclaveProcessCommandType::NotPermitted;
} else {
debug!(
"The requester with credentials ({:?}) is allowed to perform '{:?}'.",
user_creds, cmd
);
}
Ok(cmd)
}
pub fn read<T>(&self) -> NitroCliResult<T>
where
T: DeserializeOwned,
{
let mut lock = self.data.lock().map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to acquire lock: {e:?}"),
NitroCliErrorEnum::LockAcquireFailure
)
})?;
if lock.input_stream.is_none() {
return Err(new_nitro_cli_failure!(
"Cannot read from this connection",
NitroCliErrorEnum::SocketError
));
}
receive_from_stream::<T>(lock.input_stream.as_mut().unwrap())
}
pub fn write_u64(&self, value: u64) -> NitroCliResult<()> {
let mut lock = self.data.lock().map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to acquire lock: {e:?}"),
NitroCliErrorEnum::LockAcquireFailure
)
})?;
if lock.input_stream.is_none() {
return Err(new_nitro_cli_failure!(
"Cannot write a 64-bit value to this connection",
NitroCliErrorEnum::SocketError
));
}
write_u64_le(lock.input_stream.as_mut().unwrap(), value)
}
pub fn println(&self, msg: &str) -> NitroCliResult<()> {
let mut msg_str = msg.to_string();
msg_str.push('\n');
let reply = EnclaveProcessReply::StdOutMessage(msg_str);
self.write_reply(&reply)
}
pub fn eprintln(&self, msg: &str) -> NitroCliResult<()> {
let mut msg_str = msg.to_string();
msg_str.push('\n');
let reply = EnclaveProcessReply::StdErrMessage(msg_str);
self.write_reply(&reply)
}
pub fn write_status(&self, status: i32) -> NitroCliResult<()> {
let reply = EnclaveProcessReply::Status(status);
self.write_reply(&reply)
}
pub fn get_enclave_event_flags(&self) -> NitroCliResult<Option<EpollFlags>> {
let lock = self.data.lock().map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to acquire connection lock: {e:?}"),
NitroCliErrorEnum::LockAcquireFailure
)
})?;
match lock.input_stream {
None => Ok(Some(lock.epoll_flags)),
_ => Ok(None),
}
}
fn write_reply(&self, reply: &EnclaveProcessReply) -> NitroCliResult<()> {
let mut lock = self.data.lock().map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to acquire lock: {e:?}"),
NitroCliErrorEnum::LockAcquireFailure
)
})?;
if lock.input_stream.is_none() {
return Err(new_nitro_cli_failure!(
"Cannot write message to connection",
NitroCliErrorEnum::SocketError
));
}
let mut stream = lock.input_stream.as_mut().unwrap();
let mut reply_bytes = Vec::new();
ciborium::into_writer(reply, &mut reply_bytes).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to serialize reply: {e:?}"),
NitroCliErrorEnum::SerdeError
)
})?;
write_u64_le(&mut stream, reply_bytes.len() as u64)
.map_err(|e| e.add_subaction("Write reply".to_string()))?;
stream.write_all(&reply_bytes).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to write to stream: {e:?}"),
NitroCliErrorEnum::SocketError
)
})
}
}
pub fn safe_conn_println(conn: Option<&Connection>, msg: &str) -> NitroCliResult<()> {
if conn.is_none() {
return Ok(());
}
conn.unwrap().println(msg)
}
pub fn safe_conn_eprintln(conn: Option<&Connection>, msg: &str) -> NitroCliResult<()> {
if conn.is_none() {
return Ok(());
}
conn.unwrap().eprintln(msg)
}