use crate::config::Config;
use serde::Serialize;
use std::io::ErrorKind;
use std::sync::Arc;
use std::{
fmt::{Debug, Formatter},
net::SocketAddr,
time::{Duration, Instant},
};
use tokio::io::{AsyncWriteExt, BufWriter};
use tokio::{fs::OpenOptions, task::JoinHandle};
use uuid::Uuid;
pub fn start_audit_writer(
config: Arc<Config>,
) -> (
tokio::sync::mpsc::UnboundedSender<AuditLog>,
JoinHandle<Result<(), std::io::Error>>,
) {
let (send, mut recv) = tokio::sync::mpsc::unbounded_channel();
let handle = tokio::spawn(async move {
let file = OpenOptions::default()
.create(true)
.append(true)
.open(&config.audit_output_file)
.await?;
let mut writer = BufWriter::new(file);
while let Some(log) = recv.recv().await {
let log =
serde_json::to_vec(&log).map_err(|e| std::io::Error::new(ErrorKind::Other, e))?;
writer.write_all(&log).await?;
}
writer.flush().await?;
Ok(())
});
(send, handle)
}
#[derive(Serialize)]
pub struct AuditLog {
pub connection_id: Uuid,
pub peer_address: Option<SocketAddr>,
pub environment_variables: Vec<(Box<str>, Box<str>)>,
pub events: Vec<AuditLogEvent>,
#[serde(skip)]
pub start: Instant,
}
impl Default for AuditLog {
fn default() -> Self {
Self {
connection_id: Uuid::default(),
peer_address: None,
environment_variables: vec![],
events: vec![],
start: Instant::now(),
}
}
}
impl Debug for AuditLog {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AuditLog")
.field("connection_id", &self.connection_id)
.field("peer_address", &self.peer_address)
.field("environment_variables", &self.environment_variables)
.field("events", &self.events)
.finish()
}
}
impl AuditLog {
pub fn push_action(&mut self, action: AuditLogAction) {
self.events.push(AuditLogEvent {
start_offset: self.start.elapsed(),
action,
});
}
}
#[derive(Debug, Serialize)]
pub struct AuditLogEvent {
pub start_offset: Duration,
pub action: AuditLogAction,
}
#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum AuditLogAction {
LoginAttempt(LoginAttemptEvent),
PtyRequest(PtyRequestEvent),
X11Request(X11RequestEvent),
OpenX11(OpenX11Event),
OpenDirectTcpIp(OpenDirectTcpIpEvent),
ExecCommand(ExecCommandEvent),
WindowAdjusted(WindowAdjustedEvent),
ShellRequested,
SubsystemRequest(SubsystemRequestEvent),
WindowChangeRequest(WindowChangeRequestEvent),
Signal(SignalEvent),
TcpIpForward(TcpIpForwardEvent),
CancelTcpIpForward(TcpIpForwardEvent),
}
#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub struct ExecCommandEvent {
pub args: Box<[String]>,
}
#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub struct WindowAdjustedEvent {
pub new_size: usize,
}
#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub struct SubsystemRequestEvent {
pub name: Box<str>,
}
#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub struct SignalEvent {
pub name: Box<str>,
}
#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum LoginAttemptEvent {
UsernamePassword {
username: Box<str>,
password: Box<str>,
},
PublicKey {
kind: &'static str,
fingerprint: Box<str>,
},
}
#[derive(Debug, Serialize)]
pub struct PtyRequestEvent {
pub term: Box<str>,
pub col_width: u32,
pub row_height: u32,
pub pix_width: u32,
pub pix_height: u32,
pub modes: Box<[(u8, u32)]>,
}
#[derive(Debug, Serialize)]
pub struct OpenX11Event {
pub originator_address: Box<str>,
pub originator_port: u32,
}
#[derive(Debug, Serialize)]
pub struct X11RequestEvent {
pub single_connection: bool,
pub x11_auth_protocol: Box<str>,
pub x11_auth_cookie: Box<str>,
pub x11_screen_number: u32,
}
#[derive(Debug, Serialize)]
pub struct OpenDirectTcpIpEvent {
pub host_to_connect: Box<str>,
pub port_to_connect: u32,
pub originator_address: Box<str>,
pub originator_port: u32,
}
#[derive(Debug, Serialize)]
pub struct WindowChangeRequestEvent {
pub col_width: u32,
pub row_height: u32,
pub pix_width: u32,
pub pix_height: u32,
}
#[derive(Debug, Serialize)]
pub struct TcpIpForwardEvent {
pub address: Box<str>,
pub port: u32,
}