use serde::Deserialize;
use crate::audit_log::AuditLogConfig;
use crate::body_size_limit::BodySizeLimitConfig;
use crate::ip_access_control::IpAccessControlConfig;
use crate::security_headers::SecurityHeadersConfig;
#[derive(Debug, Clone, Deserialize)]
pub struct SecuritySection {
#[serde(default)]
pub headers: SecurityHeadersConfig,
#[serde(default)]
pub ip_access: IpAccessControlConfig,
#[serde(default)]
pub audit: AuditLogConfig,
#[serde(default)]
pub body_size: BodySizeLimitConfig,
}
impl Default for SecuritySection {
fn default() -> Self {
Self {
headers: SecurityHeadersConfig::default(),
ip_access: IpAccessControlConfig::default(),
audit: AuditLogConfig::default(),
body_size: BodySizeLimitConfig::default(),
}
}
}
impl SecuritySection {
pub fn validate(&self) -> Result<(), SecurityConfigError> {
if self.ip_access.enabled {
for entry in &self.ip_access.ip_list {
if entry.parse::<ipnet::IpNet>().is_err() {
entry
.parse::<std::net::IpAddr>()
.map_err(|e| SecurityConfigError::InvalidIpOrCidr {
entry: entry.clone(),
source: e,
})?;
}
}
if self.ip_access.ip_list.len() > 10000 {
tracing::warn!(
count = self.ip_access.ip_list.len(),
"ip_list 超过 10000 条,建议使用 CIDR 聚合减少匹配开销"
);
}
}
if self.audit.sample_rate < 0.0 || self.audit.sample_rate > 1.0 {
return Err(SecurityConfigError::SampleRateOutOfRange {
value: self.audit.sample_rate,
});
}
if self.body_size.enabled && self.body_size.max_body_size == 0 {
return Err(SecurityConfigError::MaxBodySizeZero);
}
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum SecurityConfigError {
#[error("IP/CIDR 解析失败: {entry} — {source}")]
InvalidIpOrCidr {
entry: String,
#[source]
source: std::net::AddrParseError,
},
#[error("audit.sample_rate 越界: {value},应在 [0.0, 1.0]")]
SampleRateOutOfRange {
value: f64,
},
#[error("body_size.max_body_size 为 0,启用时必须大于 0")]
MaxBodySizeZero,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_section() {
let section = SecuritySection::default();
assert!(section.headers.enabled, "headers 默认启用");
assert!(!section.ip_access.enabled, "ip_access 默认不启用");
assert!(!section.audit.enabled, "audit 默认不启用");
assert!(!section.body_size.enabled, "body_size 默认不启用");
}
#[test]
fn test_validate_default_passes() {
let section = SecuritySection::default();
assert!(section.validate().is_ok());
}
#[test]
fn test_validate_invalid_cidr_fails() {
let mut section = SecuritySection::default();
section.ip_access.enabled = true;
section.ip_access.ip_list = vec!["not-a-valid-cidr".to_string()];
let err = section.validate().unwrap_err();
assert!(matches!(err, SecurityConfigError::InvalidIpOrCidr { .. }));
}
#[test]
fn test_validate_valid_cidr_passes() {
let mut section = SecuritySection::default();
section.ip_access.enabled = true;
section.ip_access.ip_list = vec![
"10.0.0.0/8".to_string(),
"192.168.1.1".to_string(),
"::1/128".to_string(),
];
assert!(section.validate().is_ok());
}
#[test]
fn test_validate_sample_rate_out_of_range() {
let mut section = SecuritySection::default();
section.audit.sample_rate = 1.5;
let err = section.validate().unwrap_err();
assert!(matches!(
err,
SecurityConfigError::SampleRateOutOfRange { value: 1.5 }
));
}
#[test]
fn test_validate_sample_rate_negative() {
let mut section = SecuritySection::default();
section.audit.sample_rate = -0.1;
let err = section.validate().unwrap_err();
assert!(matches!(err, SecurityConfigError::SampleRateOutOfRange { .. }));
}
#[test]
fn test_validate_max_body_size_zero() {
let mut section = SecuritySection::default();
section.body_size.enabled = true;
section.body_size.max_body_size = 0;
let err = section.validate().unwrap_err();
assert!(matches!(err, SecurityConfigError::MaxBodySizeZero));
}
#[test]
fn test_validate_disabled_body_size_zero_ok() {
let mut section = SecuritySection::default();
section.body_size.enabled = false;
section.body_size.max_body_size = 0;
assert!(section.validate().is_ok());
}
}