Skip to content

Commit e390ccd

Browse files
feat(agent): sync ai agent enforcement policy to bpf
1 parent 5525f70 commit e390ccd

3 files changed

Lines changed: 198 additions & 1 deletion

File tree

agent/crates/enterprise-utils/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,16 @@ pub mod ai_agent_enforcement {
510510
Block,
511511
}
512512

513+
#[derive(Clone, Debug, PartialEq, Eq)]
514+
pub struct ExecRuleInput {
515+
pub id: String,
516+
pub mode: EnforcementMode,
517+
pub exact: Vec<String>,
518+
pub prefix: Vec<String>,
519+
pub suffix: Vec<String>,
520+
pub argv_contains_any: Vec<String>,
521+
}
522+
513523
#[derive(Clone, Debug, PartialEq, Eq)]
514524
pub struct PolicyHit {
515525
pub rule_index: u32,
@@ -526,8 +536,23 @@ pub mod ai_agent_enforcement {
526536
pub fn match_exec(&self, _exec_path: &str, _cmdline: &str) -> Option<PolicyHit> {
527537
None
528538
}
539+
540+
pub fn sync_to_bpf_maps(
541+
&self,
542+
_exec_rules_fd: i32,
543+
_policy_epoch_fd: i32,
544+
_max_records: usize,
545+
) -> Result<(), String> {
546+
Ok(())
547+
}
548+
}
549+
550+
pub fn compile_exec_rules(_rules: &[ExecRuleInput]) -> Result<CompiledExecPolicy, String> {
551+
Ok(CompiledExecPolicy { epoch: 0 })
529552
}
530553

554+
pub fn set_global_exec_policy(_policy: Option<CompiledExecPolicy>) {}
555+
531556
pub fn global_exec_policy() -> Option<Arc<CompiledExecPolicy>> {
532557
None
533558
}

agent/src/config/handler.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,7 @@ pub struct EbpfConfig {
13681368
pub epc_id: u32,
13691369
pub l7_log_packet_size: usize,
13701370
pub ai_agent_max_payload_size: usize,
1371+
pub ai_agent_enforcement: AiAgentEnforcementConfig,
13711372
// 静态配置
13721373
pub l7_protocol_inference_max_fail_count: usize,
13731374
pub l7_protocol_inference_ttl: usize,
@@ -1394,6 +1395,7 @@ impl fmt::Debug for EbpfConfig {
13941395
.field("epc_id", &self.epc_id)
13951396
.field("l7_log_packet_size", &self.l7_log_packet_size)
13961397
.field("ai_agent_max_payload_size", &self.ai_agent_max_payload_size)
1398+
.field("ai_agent_enforcement", &self.ai_agent_enforcement)
13971399
.field(
13981400
"l7_protocol_inference_max_fail_count",
13991401
&self.l7_protocol_inference_max_fail_count,
@@ -2469,6 +2471,7 @@ impl TryFrom<(Config, UserConfig)> for ModuleConfig {
24692471
l7_log_packet_size: crate::ebpf::CAP_LEN_MAX
24702472
.min(conf.processors.request_log.tunning.payload_truncation as usize),
24712473
ai_agent_max_payload_size: conf.inputs.proc.ai_agent.max_payload_size,
2474+
ai_agent_enforcement: conf.inputs.proc.ai_agent.enforcement.clone(),
24722475
l7_log_tap_types: generate_tap_types_array(
24732476
&conf.outputs.flow_log.filters.l7_capture_network_types,
24742477
),

agent/src/ebpf_dispatcher.rs

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub mod memory_profile;
4343
use std::ffi::{CStr, CString};
4444
use std::ptr::{self, null_mut};
4545
use std::slice;
46-
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
46+
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU64, Ordering};
4747
use std::sync::Arc;
4848
use std::thread::{self, JoinHandle};
4949
use std::time::Duration;
@@ -679,6 +679,65 @@ static mut ON_CPU_PROFILE_FREQUENCY: u32 = 0;
679679
static mut PROFILE_STACK_COMPRESSION: bool = true;
680680
#[allow(static_mut_refs)]
681681
static mut TIME_DIFF: Option<Arc<AtomicI64>> = None;
682+
#[cfg(feature = "enterprise")]
683+
static AI_AGENT_EXEC_RULES_MAP_FD: AtomicI32 = AtomicI32::new(-1);
684+
#[cfg(feature = "enterprise")]
685+
static AI_AGENT_POLICY_EPOCH_MAP_FD: AtomicI32 = AtomicI32::new(-1);
686+
#[cfg(feature = "enterprise")]
687+
const AI_AGENT_EXEC_RULES_BPF_MAX: usize = 256;
688+
689+
#[cfg(feature = "enterprise")]
690+
fn ai_agent_enforcement_mode_eq(value: &str, expected: &str) -> bool {
691+
value.trim().eq_ignore_ascii_case(expected)
692+
}
693+
694+
#[cfg(feature = "enterprise")]
695+
fn ai_agent_enforcement_lsm_allowed(
696+
config: &crate::config::config::AiAgentEnforcementConfig,
697+
) -> bool {
698+
let mechanism_allowed = config
699+
.allowed_mechanisms
700+
.iter()
701+
.any(|m| ai_agent_enforcement_mode_eq(m, "lsm"));
702+
let strategy_allows_lsm = matches!(
703+
config.strategy.trim().to_ascii_lowercase().as_str(),
704+
"auto" | "lsm_only"
705+
);
706+
mechanism_allowed && strategy_allows_lsm
707+
}
708+
709+
#[cfg(feature = "enterprise")]
710+
fn ai_agent_enforcement_inputs(
711+
config: &crate::config::config::AiAgentEnforcementConfig,
712+
mode: enterprise_utils::ai_agent_enforcement::EnforcementMode,
713+
) -> Vec<enterprise_utils::ai_agent_enforcement::ExecRuleInput> {
714+
config
715+
.rules
716+
.iter()
717+
.filter(|rule| {
718+
ai_agent_enforcement_mode_eq(&rule.scope, "ai_agent_tree")
719+
&& ai_agent_enforcement_mode_eq(&rule.target_type, "exec")
720+
})
721+
.map(|rule| {
722+
let rule_mode = if mode
723+
== enterprise_utils::ai_agent_enforcement::EnforcementMode::Block
724+
&& ai_agent_enforcement_mode_eq(&rule.action.action_type, "deny")
725+
{
726+
enterprise_utils::ai_agent_enforcement::EnforcementMode::Block
727+
} else {
728+
enterprise_utils::ai_agent_enforcement::EnforcementMode::AuditOnly
729+
};
730+
enterprise_utils::ai_agent_enforcement::ExecRuleInput {
731+
id: rule.id.clone(),
732+
mode: rule_mode,
733+
exact: rule.exec.exact.clone(),
734+
prefix: rule.exec.prefix.clone(),
735+
suffix: rule.exec.suffix.clone(),
736+
argv_contains_any: rule.exec.argv_contains_any.clone(),
737+
}
738+
})
739+
.collect()
740+
}
682741

683742
pub unsafe fn string_from_null_terminated_c_str(ptr: *const u8) -> String {
684743
CStr::from_ptr(ptr as *const libc::c_char)
@@ -1444,6 +1503,28 @@ impl EbpfCollector {
14441503
} else {
14451504
warn!("AI Agent: could not find __ai_agent_pids BPF map (fd={}), file I/O monitoring will not work", fd);
14461505
}
1506+
1507+
let exec_rules_fd = unsafe {
1508+
ebpf::bpf_table_get_map_fd(
1509+
c"socket-trace".as_ptr(),
1510+
c"__ai_agent_exec_rules".as_ptr(),
1511+
)
1512+
};
1513+
AI_AGENT_EXEC_RULES_MAP_FD.store(exec_rules_fd, Ordering::Relaxed);
1514+
let policy_epoch_fd = unsafe {
1515+
ebpf::bpf_table_get_map_fd(
1516+
c"socket-trace".as_ptr(),
1517+
c"__ai_agent_policy_epoch".as_ptr(),
1518+
)
1519+
};
1520+
AI_AGENT_POLICY_EPOCH_MAP_FD.store(policy_epoch_fd, Ordering::Relaxed);
1521+
if exec_rules_fd < 0 || policy_epoch_fd < 0 {
1522+
warn!(
1523+
"AI Agent enforcement: BPF maps unavailable (__ai_agent_exec_rules={}, __ai_agent_policy_epoch={}), block mode will downgrade to audit-only",
1524+
exec_rules_fd, policy_epoch_fd
1525+
);
1526+
}
1527+
Self::sync_ai_agent_enforcement_policy(&config.ai_agent_enforcement);
14471528
}
14481529

14491530
Ok(handle)
@@ -1484,6 +1565,92 @@ impl EbpfCollector {
14841565
}
14851566
}
14861567

1568+
#[cfg(feature = "enterprise")]
1569+
fn clear_ai_agent_enforcement_bpf_maps(max_records: usize) {
1570+
let exec_rules_fd = AI_AGENT_EXEC_RULES_MAP_FD.load(Ordering::Relaxed);
1571+
let policy_epoch_fd = AI_AGENT_POLICY_EPOCH_MAP_FD.load(Ordering::Relaxed);
1572+
if exec_rules_fd < 0 || policy_epoch_fd < 0 {
1573+
return;
1574+
}
1575+
match enterprise_utils::ai_agent_enforcement::compile_exec_rules(&[]) {
1576+
Ok(policy) => {
1577+
if let Err(e) = policy.sync_to_bpf_maps(exec_rules_fd, policy_epoch_fd, max_records)
1578+
{
1579+
warn!("AI Agent enforcement: failed to clear BPF maps: {}", e);
1580+
}
1581+
}
1582+
Err(e) => warn!("AI Agent enforcement: failed to build empty policy: {}", e),
1583+
}
1584+
}
1585+
1586+
#[cfg(feature = "enterprise")]
1587+
fn sync_ai_agent_enforcement_policy(config: &crate::config::config::AiAgentEnforcementConfig) {
1588+
use enterprise_utils::ai_agent_enforcement::{
1589+
compile_exec_rules, set_global_exec_policy, EnforcementMode,
1590+
};
1591+
1592+
let max_records = config.max_rules.min(AI_AGENT_EXEC_RULES_BPF_MAX);
1593+
if !config.enabled {
1594+
set_global_exec_policy(None);
1595+
Self::clear_ai_agent_enforcement_bpf_maps(max_records);
1596+
return;
1597+
}
1598+
1599+
let exec_rules_fd = AI_AGENT_EXEC_RULES_MAP_FD.load(Ordering::Relaxed);
1600+
let policy_epoch_fd = AI_AGENT_POLICY_EPOCH_MAP_FD.load(Ordering::Relaxed);
1601+
let bpf_maps_available = exec_rules_fd >= 0 && policy_epoch_fd >= 0;
1602+
let lsm_allowed = ai_agent_enforcement_lsm_allowed(config);
1603+
let requested_block = ai_agent_enforcement_mode_eq(&config.mode, "block");
1604+
let effective_mode = if requested_block && bpf_maps_available && lsm_allowed {
1605+
EnforcementMode::Block
1606+
} else {
1607+
if requested_block {
1608+
warn!(
1609+
"AI Agent enforcement: block mode requested but BPF LSM is unavailable or disallowed; downgrade to audit-only (maps_available={}, lsm_allowed={})",
1610+
bpf_maps_available, lsm_allowed
1611+
);
1612+
}
1613+
EnforcementMode::AuditOnly
1614+
};
1615+
1616+
let inputs = ai_agent_enforcement_inputs(config, effective_mode);
1617+
let policy = match compile_exec_rules(&inputs) {
1618+
Ok(policy) => policy,
1619+
Err(e) => {
1620+
warn!("AI Agent enforcement: failed to compile policy: {}", e);
1621+
set_global_exec_policy(None);
1622+
Self::clear_ai_agent_enforcement_bpf_maps(max_records);
1623+
return;
1624+
}
1625+
};
1626+
1627+
if effective_mode == EnforcementMode::Block {
1628+
if let Err(e) = policy.sync_to_bpf_maps(exec_rules_fd, policy_epoch_fd, max_records) {
1629+
warn!(
1630+
"AI Agent enforcement: failed to sync BPF policy, downgrade to audit-only: {}",
1631+
e
1632+
);
1633+
let audit_inputs = ai_agent_enforcement_inputs(config, EnforcementMode::AuditOnly);
1634+
match compile_exec_rules(&audit_inputs) {
1635+
Ok(audit_policy) => set_global_exec_policy(Some(audit_policy)),
1636+
Err(e) => {
1637+
warn!(
1638+
"AI Agent enforcement: failed to compile audit policy: {}",
1639+
e
1640+
);
1641+
set_global_exec_policy(None);
1642+
}
1643+
}
1644+
Self::clear_ai_agent_enforcement_bpf_maps(max_records);
1645+
return;
1646+
}
1647+
} else {
1648+
Self::clear_ai_agent_enforcement_bpf_maps(max_records);
1649+
}
1650+
1651+
set_global_exec_policy(Some(policy));
1652+
}
1653+
14871654
fn ebpf_start() {
14881655
debug!("ebpf collector starting ebpf-kernel.");
14891656
unsafe {
@@ -1693,6 +1860,8 @@ impl EbpfCollector {
16931860
config.l7_log_packet_size,
16941861
config.ai_agent_max_payload_size,
16951862
);
1863+
#[cfg(feature = "enterprise")]
1864+
Self::sync_ai_agent_enforcement_policy(&config.ai_agent_enforcement);
16961865

16971866
#[cfg(feature = "extended_observability")]
16981867
{

0 commit comments

Comments
 (0)