1use nacm_validator::{AccessRequest, NacmConfig, Operation, RuleEffect, RequestContext};
2use std::path::Path;
3
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let xml_path = Path::new(env!("CARGO_MANIFEST_DIR"))
7 .join("examples")
8 .join("data")
9 .join("tailf_acm_example.xml");
10
11 let xml_content = std::fs::read_to_string(&xml_path)
12 .map_err(|e| format!("Failed to read XML file at {:?}: {}", xml_path, e))?;
13
14 let config = NacmConfig::from_xml(&xml_content)?;
15
16 println!("š§ Tail-f ACM Configuration loaded:");
17 println!("- NACM enabled: {}", config.enable_nacm);
18 println!("- Default policies:");
19 println!(" * Data: read={:?}, write={:?}, exec={:?}",
20 config.read_default, config.write_default, config.exec_default);
21 println!(" * Commands: cmd_read={:?}, cmd_exec={:?}",
22 config.cmd_read_default, config.cmd_exec_default);
23 println!("- Logging: default_permit={}, default_deny={}",
24 config.log_if_default_permit, config.log_if_default_deny);
25 println!("- Groups: {:?}", config.groups.keys().collect::<Vec<_>>());
26
27 for (name, group) in &config.groups {
29 let gid_str = if let Some(gid) = group.gid {
30 format!(" (GID: {})", gid)
31 } else {
32 String::new()
33 };
34 println!(" * {}{}: {:?}", name, gid_str, group.users);
35 }
36
37 println!("- Rule lists: {}", config.rule_lists.len());
38 for rule_list in &config.rule_lists {
39 println!(" * {}: {} rules, {} command rules",
40 rule_list.name, rule_list.rules.len(), rule_list.command_rules.len());
41 }
42
43 println!("\nš Command access validation results:");
45
46 let cli_context = RequestContext::CLI;
47 let webui_context = RequestContext::WebUI;
48 let netconf_context = RequestContext::NETCONF;
49
50 let command_test_cases = vec![
51 ("Alice (operator) - CLI show status", AccessRequest {
52 user: "alice",
53 module_name: None,
54 rpc_name: None,
55 operation: Operation::Read,
56 path: None,
57 context: Some(&cli_context),
58 command: Some("show status"),
59 }),
60 ("Alice (operator) - CLI show interfaces", AccessRequest {
61 user: "alice",
62 module_name: None,
63 rpc_name: None,
64 operation: Operation::Read,
65 path: None,
66 context: Some(&cli_context),
67 command: Some("show interfaces"),
68 }),
69 ("Alice (operator) - WebUI help", AccessRequest {
70 user: "alice",
71 module_name: None,
72 rpc_name: None,
73 operation: Operation::Read,
74 path: None,
75 context: Some(&webui_context),
76 command: Some("help"),
77 }),
78 ("Alice (operator) - CLI reboot (should deny)", AccessRequest {
79 user: "alice",
80 module_name: None,
81 rpc_name: None,
82 operation: Operation::Exec,
83 path: None,
84 context: Some(&cli_context),
85 command: Some("reboot"),
86 }),
87 ("Admin - CLI reboot (should permit)", AccessRequest {
88 user: "admin",
89 module_name: None,
90 rpc_name: None,
91 operation: Operation::Exec,
92 path: None,
93 context: Some(&cli_context),
94 command: Some("reboot"),
95 }),
96 ("Bob (operator) - Unknown command (should use default)", AccessRequest {
97 user: "bob",
98 module_name: None,
99 rpc_name: None,
100 operation: Operation::Exec,
101 path: None,
102 context: Some(&cli_context),
103 command: Some("unknown-command"),
104 }),
105 ];
106
107 for (description, request) in command_test_cases {
108 let result = config.validate(&request);
109 let result_icon = match result.effect {
110 RuleEffect::Permit => "ā
",
111 RuleEffect::Deny => "ā",
112 };
113 let log_str = if result.should_log { " š[LOG]" } else { "" };
114 println!(" {} {}: {}{}", result_icon, description,
115 format!("{:?}", result.effect).to_uppercase(), log_str);
116 }
117
118 println!("\nš Mixed data access validation:");
120
121 let data_test_cases = vec![
122 ("Alice - NETCONF read interfaces", AccessRequest {
123 user: "alice",
124 module_name: Some("ietf-interfaces"),
125 rpc_name: None,
126 operation: Operation::Read,
127 path: Some("/interfaces"),
128 context: Some(&netconf_context),
129 command: None,
130 }),
131 ("Alice - NETCONF write interfaces (should deny)", AccessRequest {
132 user: "alice",
133 module_name: None,
134 rpc_name: Some("edit-config"),
135 operation: Operation::Exec,
136 path: None,
137 context: Some(&netconf_context),
138 command: None,
139 }),
140 ("Admin - NETCONF write (should permit)", AccessRequest {
141 user: "admin",
142 module_name: None,
143 rpc_name: Some("edit-config"),
144 operation: Operation::Exec,
145 path: None,
146 context: Some(&netconf_context),
147 command: None,
148 }),
149 ];
150
151 for (description, request) in data_test_cases {
152 let result = config.validate(&request);
153 let result_icon = match result.effect {
154 RuleEffect::Permit => "ā
",
155 RuleEffect::Deny => "ā",
156 };
157 let log_str = if result.should_log { " š[LOG]" } else { "" };
158 println!(" {} {}: {}{}", result_icon, description,
159 format!("{:?}", result.effect).to_uppercase(), log_str);
160 }
161
162 println!("\nšÆ Summary:");
163 println!("The Tail-f ACM extensions provide:");
164 println!("- Command-based access control for CLI/WebUI operations");
165 println!("- Enhanced logging controls for audit trails");
166 println!("- Group ID mapping for OS integration");
167 println!("- Context-aware rules (CLI vs NETCONF vs WebUI)");
168 println!("- Granular control over what gets logged");
169
170 Ok(())
171}