1use nacm_validator::{AccessRequest, NacmConfig, Operation, RuleEffect, RequestContext};
2use std::path::Path;
3
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 println!("๐ง Comprehensive Tail-f ACM Extensions Demo");
6 println!("{}", "=".repeat(50));
7
8 let xml_path = Path::new(env!("CARGO_MANIFEST_DIR"))
10 .join("examples")
11 .join("data")
12 .join("tailf_acm_example.xml");
13
14 let xml_content = std::fs::read_to_string(&xml_path)
15 .map_err(|e| format!("Failed to read XML file at {:?}: {}", xml_path, e))?;
16
17 let config = NacmConfig::from_xml(&xml_content)?;
18
19 println!("๐ Configuration Summary:");
20 println!("- NACM enabled: {}", config.enable_nacm);
21 println!("- Standard defaults: read={:?}, write={:?}, exec={:?}",
22 config.read_default, config.write_default, config.exec_default);
23 println!("- Command defaults: cmd_read={:?}, cmd_exec={:?}",
24 config.cmd_read_default, config.cmd_exec_default);
25 println!("- Logging: default_permit={}, default_deny={}",
26 config.log_if_default_permit, config.log_if_default_deny);
27 println!();
28
29 println!("๐ฅ Groups:");
31 for (name, group) in &config.groups {
32 let gid_info = group.gid.map(|g| format!(" (GID: {})", g)).unwrap_or_default();
33 println!(" {} {}: {:?}", name, gid_info, group.users);
34 }
35 println!();
36
37 println!("๐ Rule Lists:");
39 for (i, rule_list) in config.rule_lists.iter().enumerate() {
40 println!(" {}. {} (groups: {:?})", i + 1, rule_list.name, rule_list.groups);
41 println!(" - Standard rules: {}", rule_list.rules.len());
42 for rule in &rule_list.rules {
43 let context_info = rule.context.as_ref()
44 .map(|c| format!(" [context: {}]", c))
45 .unwrap_or_default();
46 println!(" โข {} -> {:?}{}", rule.name, rule.effect, context_info);
47 }
48 println!(" - Command rules: {}", rule_list.command_rules.len());
49 for cmd_rule in &rule_list.command_rules {
50 let context_info = cmd_rule.context.as_ref()
51 .map(|c| format!(" [context: {}]", c))
52 .unwrap_or_default();
53 let command_info = cmd_rule.command.as_ref()
54 .map(|c| format!(" [command: {}]", c))
55 .unwrap_or_default();
56 println!(" โข {} -> {:?}{}{}", cmd_rule.name, cmd_rule.effect, context_info, command_info);
57 }
58 }
59 println!();
60
61 let cli_context = RequestContext::CLI;
63 let webui_context = RequestContext::WebUI;
64 let netconf_context = RequestContext::NETCONF;
65
66 println!("๐งช Test Scenarios:");
67 println!();
68
69 println!("1๏ธโฃ Command-Based Access Control:");
71 let command_tests = vec![
72 ("alice (operator) - CLI 'show status'", AccessRequest {
73 user: "alice",
74 module_name: None,
75 rpc_name: None,
76 operation: Operation::Read,
77 path: None,
78 context: Some(&cli_context),
79 command: Some("show status"),
80 }),
81 ("bob (operator) - CLI 'show interfaces'", AccessRequest {
82 user: "bob",
83 module_name: None,
84 rpc_name: None,
85 operation: Operation::Read,
86 path: None,
87 context: Some(&cli_context),
88 command: Some("show interfaces"),
89 }),
90 ("alice (operator) - WebUI 'help'", AccessRequest {
91 user: "alice",
92 module_name: None,
93 rpc_name: None,
94 operation: Operation::Read,
95 path: None,
96 context: Some(&webui_context),
97 command: Some("help"),
98 }),
99 ("charlie (not in group) - CLI 'show status'", AccessRequest {
100 user: "charlie",
101 module_name: None,
102 rpc_name: None,
103 operation: Operation::Read,
104 path: None,
105 context: Some(&cli_context),
106 command: Some("show status"),
107 }),
108 ("alice (operator) - CLI 'reboot' (exec operation)", AccessRequest {
109 user: "alice",
110 module_name: None,
111 rpc_name: None,
112 operation: Operation::Exec,
113 path: None,
114 context: Some(&cli_context),
115 command: Some("reboot"),
116 }),
117 ("admin - CLI 'reboot' (exec operation)", AccessRequest {
118 user: "admin",
119 module_name: None,
120 rpc_name: None,
121 operation: Operation::Exec,
122 path: None,
123 context: Some(&cli_context),
124 command: Some("reboot"),
125 }),
126 ];
127
128 for (description, request) in command_tests {
129 let result = config.validate(&request);
130 let result_icon = match result.effect {
131 RuleEffect::Permit => "โ
",
132 RuleEffect::Deny => "โ",
133 };
134 let log_indicator = if result.should_log { " ๐" } else { "" };
135 println!(" {} {}: {:?}{}", result_icon, description,
136 result.effect, log_indicator);
137 }
138 println!();
139
140 println!("2๏ธโฃ Context-Aware Data Access:");
142 let data_tests = vec![
143 ("alice - NETCONF read interfaces", AccessRequest {
144 user: "alice",
145 module_name: Some("ietf-interfaces"),
146 rpc_name: None,
147 operation: Operation::Read,
148 path: Some("/interfaces"),
149 context: Some(&netconf_context),
150 command: None,
151 }),
152 ("alice - CLI read interfaces (no command rule)", AccessRequest {
153 user: "alice",
154 module_name: Some("ietf-interfaces"),
155 rpc_name: None,
156 operation: Operation::Read,
157 path: Some("/interfaces"),
158 context: Some(&cli_context),
159 command: None,
160 }),
161 ("alice - WebUI read interfaces (no command rule)", AccessRequest {
162 user: "alice",
163 module_name: Some("ietf-interfaces"),
164 rpc_name: None,
165 operation: Operation::Read,
166 path: Some("/interfaces"),
167 context: Some(&webui_context),
168 command: None,
169 }),
170 ("admin - NETCONF edit-config RPC", AccessRequest {
171 user: "admin",
172 module_name: None,
173 rpc_name: Some("edit-config"),
174 operation: Operation::Exec,
175 path: None,
176 context: Some(&netconf_context),
177 command: None,
178 }),
179 ("alice - NETCONF edit-config RPC (should deny)", AccessRequest {
180 user: "alice",
181 module_name: None,
182 rpc_name: Some("edit-config"),
183 operation: Operation::Exec,
184 path: None,
185 context: Some(&netconf_context),
186 command: None,
187 }),
188 ];
189
190 for (description, request) in data_tests {
191 let result = config.validate(&request);
192 let result_icon = match result.effect {
193 RuleEffect::Permit => "โ
",
194 RuleEffect::Deny => "โ",
195 };
196 let log_indicator = if result.should_log { " ๐" } else { "" };
197 println!(" {} {}: {:?}{}", result_icon, description,
198 result.effect, log_indicator);
199 }
200 println!();
201
202 println!("3๏ธโฃ Default Behavior Testing:");
204 let default_tests = vec![
205 ("unknown_user - CLI unknown command (cmd_read_default)", AccessRequest {
206 user: "unknown_user",
207 module_name: None,
208 rpc_name: None,
209 operation: Operation::Read,
210 path: None,
211 context: Some(&cli_context),
212 command: Some("unknown-command"),
213 }),
214 ("unknown_user - CLI exec unknown command (cmd_exec_default)", AccessRequest {
215 user: "unknown_user",
216 module_name: None,
217 rpc_name: None,
218 operation: Operation::Exec,
219 path: None,
220 context: Some(&cli_context),
221 command: Some("unknown-exec-command"),
222 }),
223 ("unknown_user - NETCONF read data (read_default)", AccessRequest {
224 user: "unknown_user",
225 module_name: Some("unknown-module"),
226 rpc_name: None,
227 operation: Operation::Read,
228 path: Some("/unknown/path"),
229 context: Some(&netconf_context),
230 command: None,
231 }),
232 ("unknown_user - NETCONF write data (write_default)", AccessRequest {
233 user: "unknown_user",
234 module_name: Some("unknown-module"),
235 rpc_name: None,
236 operation: Operation::Update,
237 path: Some("/unknown/path"),
238 context: Some(&netconf_context),
239 command: None,
240 }),
241 ];
242
243 for (description, request) in default_tests {
244 let result = config.validate(&request);
245 let result_icon = match result.effect {
246 RuleEffect::Permit => "โ
",
247 RuleEffect::Deny => "โ",
248 };
249 let log_indicator = if result.should_log { " ๐" } else { "" };
250 println!(" {} {}: {:?}{}", result_icon, description,
251 result.effect, log_indicator);
252 }
253 println!();
254
255 println!("4๏ธโฃ Mixed Command and Data Access:");
257 let mixed_tests = vec![
258 ("alice - CLI with both command and module (command takes priority)", AccessRequest {
259 user: "alice",
260 module_name: Some("ietf-interfaces"),
261 rpc_name: None,
262 operation: Operation::Read,
263 path: Some("/interfaces"),
264 context: Some(&cli_context),
265 command: Some("show status"),
266 }),
267 ("bob - WebUI with both command and RPC (command takes priority)", AccessRequest {
268 user: "bob",
269 module_name: None,
270 rpc_name: Some("get"),
271 operation: Operation::Read,
272 path: None,
273 context: Some(&webui_context),
274 command: Some("help"),
275 }),
276 ];
277
278 for (description, request) in mixed_tests {
279 let result = config.validate(&request);
280 let result_icon = match result.effect {
281 RuleEffect::Permit => "โ
",
282 RuleEffect::Deny => "โ",
283 };
284 let log_indicator = if result.should_log { " ๐" } else { "" };
285 println!(" {} {}: {:?}{}", result_icon, description,
286 result.effect, log_indicator);
287 }
288 println!();
289
290 println!("๐ Summary of Tail-f ACM Features Demonstrated:");
291 println!("โ Command-based access control (cmdrule elements)");
292 println!("โ Context-aware rules (cli, webui, netconf contexts)");
293 println!("โ Enhanced logging controls (log-if-permit, log-if-deny)");
294 println!("โ Group ID mapping (gid attributes)");
295 println!("โ Command defaults (cmd-read-default, cmd-exec-default)");
296 println!("โ Mixed command and data access scenarios");
297 println!("โ Fallback to traditional NACM rules when no command rules match");
298 println!();
299
300 println!("๐ฏ Key Takeaways:");
301 println!("1. Command rules have priority over data rules when command is specified");
302 println!("2. Context matters - same user can have different access via different interfaces");
303 println!("3. Enhanced logging provides audit trail capabilities");
304 println!("4. Default command policies provide fallback behavior");
305 println!("5. Tail-f ACM is backward compatible with standard NACM");
306
307 Ok(())
308}