tailf_acm_comprehensive_demo/
tailf_acm_comprehensive_demo.rs

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    // Load the Tail-f ACM example configuration
9    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    // Show detailed group information
30    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    // Show rule lists with detailed breakdown
38    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    // Test comprehensive scenarios
62    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    // Group 1: Command-based access control
70    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    // Group 2: Context-aware data access
141    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    // Group 3: Default behavior testing
203    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    // Group 4: Mixed scenarios
256    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}