use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InputMethod {
Keyboard,
VirtualKeyboard,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SecurityTier {
Standard = 0,
Hardened = 1,
Lockdown = 2,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(clippy::struct_excessive_bools)]
pub struct SecurityConfig {
pub tier: SecurityTier,
pub input_method: InputMethod,
#[serde(default = "default_true")]
pub x11_auto_upgrade: bool,
#[serde(default)]
pub challenge_required: bool,
#[serde(default)]
pub approval_delay_secs: u32,
#[serde(default = "default_auto_lock")]
pub auto_lock_secs: u32,
pub totp_required: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub totp_secret_encrypted: Option<String>,
#[serde(default = "default_true")]
pub audit_logging: bool,
#[serde(default)]
pub relay_required: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub relay_endpoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_ui_cmd: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relay_device_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relay_pairing_key: Option<String>,
#[serde(default)]
pub approval_cooldown_secs: u32,
#[serde(default)]
pub max_approvals_per_minute: u32,
#[serde(default = "default_true")]
pub warn_untrusted_binary: bool,
#[serde(default)]
pub signal_overrides: std::collections::BTreeMap<String, String>,
#[serde(default)]
pub tier_overrides: std::collections::BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_rule_expiry_secs: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_rule_expiry_secs: Option<u64>,
#[serde(default)]
pub policy_generation: u64,
}
fn default_true() -> bool {
true
}
fn default_auto_lock() -> u32 {
60
}
impl Default for SecurityConfig {
fn default() -> Self {
Self::preset_standard()
}
}
impl SecurityConfig {
pub fn preset_standard() -> Self {
Self {
tier: SecurityTier::Standard,
input_method: InputMethod::Keyboard,
x11_auto_upgrade: true,
challenge_required: false,
approval_delay_secs: 0,
auto_lock_secs: 60,
totp_required: false,
totp_secret_encrypted: None,
audit_logging: true,
relay_required: false,
relay_endpoint: None,
relay_device_id: None,
relay_pairing_key: None,
custom_ui_cmd: None,
approval_cooldown_secs: 0,
max_approvals_per_minute: 0, warn_untrusted_binary: true,
signal_overrides: std::collections::BTreeMap::new(),
tier_overrides: std::collections::BTreeMap::new(),
default_rule_expiry_secs: None, max_rule_expiry_secs: None,
policy_generation: 0,
}
}
pub fn preset_hardened() -> Self {
Self {
tier: SecurityTier::Hardened,
input_method: InputMethod::VirtualKeyboard,
x11_auto_upgrade: true,
challenge_required: false,
approval_delay_secs: 2,
auto_lock_secs: 30,
totp_required: false,
totp_secret_encrypted: None,
audit_logging: true,
relay_required: false,
relay_endpoint: None,
relay_device_id: None,
relay_pairing_key: None,
custom_ui_cmd: None,
approval_cooldown_secs: 3,
max_approvals_per_minute: 10,
warn_untrusted_binary: true,
signal_overrides: std::collections::BTreeMap::new(),
tier_overrides: std::collections::BTreeMap::new(),
default_rule_expiry_secs: Some(30 * 24 * 3600), max_rule_expiry_secs: Some(90 * 24 * 3600), policy_generation: 0,
}
}
pub fn preset_lockdown() -> Self {
Self {
tier: SecurityTier::Lockdown,
input_method: InputMethod::VirtualKeyboard,
x11_auto_upgrade: true,
challenge_required: true,
approval_delay_secs: 5,
auto_lock_secs: 15,
totp_required: false,
totp_secret_encrypted: None,
audit_logging: true,
relay_required: false,
relay_endpoint: None,
relay_device_id: None,
relay_pairing_key: None,
custom_ui_cmd: None,
approval_cooldown_secs: 10,
max_approvals_per_minute: 5,
warn_untrusted_binary: true,
signal_overrides: std::collections::BTreeMap::new(),
tier_overrides: std::collections::BTreeMap::new(),
default_rule_expiry_secs: Some(3600), max_rule_expiry_secs: Some(24 * 3600),
policy_generation: 0,
}
}
pub fn apply_preset(&mut self, tier: SecurityTier) {
let fresh = match tier {
SecurityTier::Standard => Self::preset_standard(),
SecurityTier::Hardened => Self::preset_hardened(),
SecurityTier::Lockdown => Self::preset_lockdown(),
};
let totp_enc = self.totp_secret_encrypted.take();
let totp_req = self.totp_required;
let relay_ep = self.relay_endpoint.take();
let relay_id = self.relay_device_id.take();
let relay_pairing_key = self.relay_pairing_key.take();
let relay_require = self.relay_required;
let custom_ui = self.custom_ui_cmd.take();
let signal_ov = std::mem::take(&mut self.signal_overrides);
let tier_ov = std::mem::take(&mut self.tier_overrides);
let policy_gen = self.policy_generation;
*self = fresh;
self.totp_secret_encrypted = totp_enc;
self.totp_required = totp_req;
self.relay_endpoint = relay_ep;
self.relay_device_id = relay_id;
self.relay_pairing_key = relay_pairing_key;
self.relay_required = relay_require;
self.custom_ui_cmd = custom_ui;
self.signal_overrides = signal_ov;
self.tier_overrides = tier_ov;
self.policy_generation = policy_gen;
}
pub fn effective_input_method(&self) -> InputMethod {
if self.x11_auto_upgrade && is_x11_session() {
InputMethod::VirtualKeyboard
} else {
self.input_method
}
}
pub fn required_auth_tier(&self) -> SecurityTier {
self.tier
}
pub fn validate_tier_change(&self, new_tier: SecurityTier) -> SecurityTier {
if new_tier < self.tier {
self.tier
} else {
SecurityTier::Standard
}
}
}
fn is_x11_session() -> bool {
#[cfg(target_os = "linux")]
{
if let Ok(session_type) = std::env::var("XDG_SESSION_TYPE") {
return session_type == "x11";
}
std::env::var("DISPLAY").is_ok() && std::env::var("WAYLAND_DISPLAY").is_err()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}