use std::fmt;
use crate::domain::steps::StepKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuditStatus {
Safe,
Partial,
Missing,
NeedsUpdate,
}
impl AuditStatus {
pub fn icon(&self) -> &'static str {
match self {
Self::Safe => "✅",
Self::Partial => "⚠️",
Self::Missing => "❌",
Self::NeedsUpdate => "🔄",
}
}
}
#[derive(Debug, Clone)]
pub struct AuditItem {
pub name: &'static str,
pub status: AuditStatus,
pub detail: String,
}
impl AuditItem {
pub fn new(name: &'static str, status: AuditStatus, detail: String) -> Self {
Self { name, status, detail }
}
pub fn safe(name: &'static str, detail: String) -> Self {
Self::new(name, AuditStatus::Safe, detail)
}
pub fn missing(name: &'static str, detail: String) -> Self {
Self::new(name, AuditStatus::Missing, detail)
}
#[allow(dead_code)]
pub fn partial(name: &'static str, detail: String) -> Self {
Self::new(name, AuditStatus::Partial, detail)
}
pub fn needs_update(name: &'static str, detail: String) -> Self {
Self::new(name, AuditStatus::NeedsUpdate, detail)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PackageManager {
Apt,
Yum,
Dnf,
Unknown,
}
impl PackageManager {
pub fn name(&self) -> &'static str {
match self {
Self::Apt => "apt",
Self::Yum => "yum",
Self::Dnf => "dnf",
Self::Unknown => "unknown",
}
}
pub fn update_cmd(&self) -> &'static [&'static str] {
match self {
Self::Apt => &["apt", "update"],
Self::Yum => &["yum", "update", "-y"],
Self::Dnf => &["dnf", "update", "-y"],
Self::Unknown => &[],
}
}
pub fn upgrade_cmd(&self) -> &'static [&'static str] {
match self {
Self::Apt => &["apt", "upgrade", "-y"],
Self::Yum => &["yum", "upgrade", "-y"],
Self::Dnf => &["dnf", "upgrade", "-y"],
Self::Unknown => &[],
}
}
}
impl fmt::Display for PackageManager {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone)]
pub struct AuditReport {
pub items: Vec<AuditItem>,
#[allow(dead_code)]
pub is_root: bool,
#[allow(dead_code)]
pub package_manager: PackageManager,
pub ssh_port: u16,
pub password_auth_disabled: bool,
pub root_login_disabled: bool,
pub sudo_users: Vec<String>,
pub fail2ban_installed: bool,
pub ufw_enabled: bool,
pub auto_updates_enabled: bool,
pub system_up_to_date: bool,
}
impl AuditReport {
#[allow(dead_code)]
pub fn summary_lines(&self) -> Vec<(&'static str, AuditStatus)> {
use AuditStatus as S;
vec![
("系统更新", if self.system_up_to_date { S::Safe } else { S::NeedsUpdate }),
("非 root 用户创建", if self.sudo_users.is_empty() { S::Missing } else { S::Safe }),
("禁止 root SSH 登录", if self.root_login_disabled { S::Safe } else { S::Missing }),
("SSH 端口修改", if self.ssh_port != 22 { S::Safe } else { S::Missing }),
("禁止密码登录", if self.password_auth_disabled { S::Safe } else { S::Missing }),
("UFW 防火墙", if self.ufw_enabled { S::Safe } else { S::Missing }),
("Fail2ban", if self.fail2ban_installed { S::Safe } else { S::Missing }),
("自动安全更新", if self.auto_updates_enabled { S::Safe } else { S::Missing }),
]
}
pub fn status_for(&self, step: StepKind) -> AuditStatus {
match step {
StepKind::SystemUpdate => {
if self.system_up_to_date { AuditStatus::Safe } else { AuditStatus::NeedsUpdate }
}
StepKind::UserCreation => {
if self.sudo_users.is_empty() { AuditStatus::Missing } else { AuditStatus::Safe }
}
StepKind::SshRootLogin => {
if self.root_login_disabled { AuditStatus::Safe } else { AuditStatus::Missing }
}
StepKind::SshPortChange => {
if self.ssh_port != 22 { AuditStatus::Safe } else { AuditStatus::Missing }
}
StepKind::SshPasswordAuth => {
if self.password_auth_disabled { AuditStatus::Safe } else { AuditStatus::Missing }
}
StepKind::SshKeySetup => {
if self.sudo_users.is_empty() { AuditStatus::Missing } else { AuditStatus::Partial }
}
StepKind::Ufw => {
if self.ufw_enabled { AuditStatus::Safe } else { AuditStatus::Missing }
}
StepKind::Fail2ban => {
if self.fail2ban_installed { AuditStatus::Safe } else { AuditStatus::Missing }
}
StepKind::AutoUpdates => {
if self.auto_updates_enabled { AuditStatus::Safe } else { AuditStatus::Missing }
}
StepKind::SecurityScan | StepKind::LogAudit | StepKind::RestartSsh => {
AuditStatus::Missing
}
}
}
}