mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 02:17:46 +08:00
feat(security): harden gateway request and runtime controls
This commit is contained in:
@@ -1,31 +1,68 @@
|
||||
use crate::state::AdminSecurityBlacklistEntry;
|
||||
use crate::{AppState, GatewayError};
|
||||
use std::net::IpAddr;
|
||||
use std::sync::LazyLock;
|
||||
use std::time::Duration;
|
||||
|
||||
const ADMIN_SECURITY_BLACKLIST_PREFIX: &str = "ip:blacklist:";
|
||||
const ADMIN_SECURITY_WHITELIST_KEY: &str = "ip:whitelist";
|
||||
const ADMIN_SECURITY_CACHE_TTL_MS_ENV: &str = "AETHER_GATEWAY_SECURITY_CACHE_TTL_MS";
|
||||
const DEFAULT_ADMIN_SECURITY_CACHE_TTL_MS: u64 = 1_000;
|
||||
const MAX_ADMIN_SECURITY_CACHE_TTL_MS: u64 = 30_000;
|
||||
const ADMIN_SECURITY_WHITELIST_CACHE_KEY: &str = "rules";
|
||||
|
||||
static ADMIN_SECURITY_CACHE_TTL: LazyLock<Duration> = LazyLock::new(|| {
|
||||
let ttl_ms = std::env::var(ADMIN_SECURITY_CACHE_TTL_MS_ENV)
|
||||
.ok()
|
||||
.and_then(|value| value.trim().parse::<u64>().ok())
|
||||
.unwrap_or(DEFAULT_ADMIN_SECURITY_CACHE_TTL_MS)
|
||||
.min(MAX_ADMIN_SECURITY_CACHE_TTL_MS);
|
||||
Duration::from_millis(ttl_ms)
|
||||
});
|
||||
|
||||
fn admin_security_cache_ttl() -> Duration {
|
||||
*ADMIN_SECURITY_CACHE_TTL
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn admin_security_ip_blacklisted(
|
||||
&self,
|
||||
ip_address: IpAddr,
|
||||
) -> Result<bool, GatewayError> {
|
||||
const ADMIN_SECURITY_BLACKLIST_PREFIX: &str = "ip:blacklist:";
|
||||
|
||||
self.runtime_state
|
||||
.kv_exists(&format!("{ADMIN_SECURITY_BLACKLIST_PREFIX}{ip_address}"))
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
let cache_key = ip_address.to_string();
|
||||
let runtime_key = format!("{ADMIN_SECURITY_BLACKLIST_PREFIX}{cache_key}");
|
||||
Ok(self
|
||||
.admin_security_blacklist_cache
|
||||
.get_or_load_once(cache_key, admin_security_cache_ttl(), || async {
|
||||
self.runtime_state
|
||||
.kv_exists(&runtime_key)
|
||||
.await
|
||||
.map(Some)
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
})
|
||||
.await?
|
||||
.unwrap_or(false))
|
||||
}
|
||||
|
||||
pub(crate) async fn admin_security_ip_whitelisted(
|
||||
&self,
|
||||
ip_address: IpAddr,
|
||||
) -> Result<bool, GatewayError> {
|
||||
const ADMIN_SECURITY_WHITELIST_KEY: &str = "ip:whitelist";
|
||||
|
||||
let rules = self
|
||||
.runtime_state
|
||||
.set_members(ADMIN_SECURITY_WHITELIST_KEY)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
.admin_security_whitelist_cache
|
||||
.get_or_load_once(
|
||||
ADMIN_SECURITY_WHITELIST_CACHE_KEY.to_string(),
|
||||
admin_security_cache_ttl(),
|
||||
|| async {
|
||||
self.runtime_state
|
||||
.set_members(ADMIN_SECURITY_WHITELIST_KEY)
|
||||
.await
|
||||
.map(Some)
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
},
|
||||
)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
Ok(rules
|
||||
.iter()
|
||||
.any(|rule| crate::handlers::shared::ip_rule_pattern_matches(rule.trim(), ip_address)))
|
||||
@@ -37,8 +74,6 @@ impl AppState {
|
||||
reason: &str,
|
||||
ttl_seconds: Option<u64>,
|
||||
) -> Result<bool, GatewayError> {
|
||||
const ADMIN_SECURITY_BLACKLIST_PREFIX: &str = "ip:blacklist:";
|
||||
|
||||
let key = format!("{ADMIN_SECURITY_BLACKLIST_PREFIX}{ip_address}");
|
||||
self.runtime_state
|
||||
.kv_set(
|
||||
@@ -47,28 +82,40 @@ impl AppState {
|
||||
ttl_seconds.map(std::time::Duration::from_secs),
|
||||
)
|
||||
.await
|
||||
.map(|_| true)
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
if let Ok(ip_address) = ip_address.parse::<IpAddr>() {
|
||||
self.admin_security_blacklist_cache.insert(
|
||||
ip_address.to_string(),
|
||||
Some(true),
|
||||
admin_security_cache_ttl(),
|
||||
);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_admin_security_blacklist(
|
||||
&self,
|
||||
ip_address: &str,
|
||||
) -> Result<bool, GatewayError> {
|
||||
const ADMIN_SECURITY_BLACKLIST_PREFIX: &str = "ip:blacklist:";
|
||||
|
||||
let key = format!("{ADMIN_SECURITY_BLACKLIST_PREFIX}{ip_address}");
|
||||
self.runtime_state
|
||||
let removed = self
|
||||
.runtime_state
|
||||
.kv_delete(&key)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
if let Ok(ip_address) = ip_address.parse::<IpAddr>() {
|
||||
self.admin_security_blacklist_cache.insert(
|
||||
ip_address.to_string(),
|
||||
Some(false),
|
||||
admin_security_cache_ttl(),
|
||||
);
|
||||
}
|
||||
Ok(removed)
|
||||
}
|
||||
|
||||
pub(crate) async fn admin_security_blacklist_stats(
|
||||
&self,
|
||||
) -> Result<(bool, usize, Option<String>), GatewayError> {
|
||||
const ADMIN_SECURITY_BLACKLIST_PREFIX: &str = "ip:blacklist:";
|
||||
|
||||
let total = self
|
||||
.runtime_state
|
||||
.scan_keys(&format!("{ADMIN_SECURITY_BLACKLIST_PREFIX}*"), 100)
|
||||
@@ -81,8 +128,6 @@ impl AppState {
|
||||
pub(crate) async fn list_admin_security_blacklist(
|
||||
&self,
|
||||
) -> Result<Vec<AdminSecurityBlacklistEntry>, GatewayError> {
|
||||
const ADMIN_SECURITY_BLACKLIST_PREFIX: &str = "ip:blacklist:";
|
||||
|
||||
let keys = self
|
||||
.runtime_state
|
||||
.scan_keys(&format!("{ADMIN_SECURITY_BLACKLIST_PREFIX}*"), 100)
|
||||
@@ -123,30 +168,28 @@ impl AppState {
|
||||
&self,
|
||||
ip_address: &str,
|
||||
) -> Result<bool, GatewayError> {
|
||||
const ADMIN_SECURITY_WHITELIST_KEY: &str = "ip:whitelist";
|
||||
|
||||
self.runtime_state
|
||||
.set_add(ADMIN_SECURITY_WHITELIST_KEY, ip_address)
|
||||
.await
|
||||
.map(|_| true)
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
self.admin_security_whitelist_cache.clear();
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_admin_security_whitelist(
|
||||
&self,
|
||||
ip_address: &str,
|
||||
) -> Result<bool, GatewayError> {
|
||||
const ADMIN_SECURITY_WHITELIST_KEY: &str = "ip:whitelist";
|
||||
|
||||
self.runtime_state
|
||||
let removed = self
|
||||
.runtime_state
|
||||
.set_remove(ADMIN_SECURITY_WHITELIST_KEY, ip_address)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
self.admin_security_whitelist_cache.clear();
|
||||
Ok(removed)
|
||||
}
|
||||
|
||||
pub(crate) async fn list_admin_security_whitelist(&self) -> Result<Vec<String>, GatewayError> {
|
||||
const ADMIN_SECURITY_WHITELIST_KEY: &str = "ip:whitelist";
|
||||
|
||||
self.runtime_state
|
||||
.set_members(ADMIN_SECURITY_WHITELIST_KEY)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user