NacmConfig

Struct NacmConfig 

Source
pub struct NacmConfig {
    pub enable_nacm: bool,
    pub read_default: RuleEffect,
    pub write_default: RuleEffect,
    pub exec_default: RuleEffect,
    pub cmd_read_default: RuleEffect,
    pub cmd_exec_default: RuleEffect,
    pub log_if_default_permit: bool,
    pub log_if_default_deny: bool,
    pub groups: HashMap<String, NacmGroup>,
    pub rule_lists: Vec<NacmRuleList>,
}
Expand description

Full NACM configuration

The main configuration object that contains all NACM settings:

  • Global enable/disable flag
  • Default policies for different operation types
  • User groups and their members
  • Rule lists with access control rules

§Fields

  • enable_nacm - Global NACM enable flag
  • read_default - Default policy for read operations
  • write_default - Default policy for write operations (create/update/delete)
  • exec_default - Default policy for exec operations (RPC calls)
  • cmd_read_default - Default policy for command read operations (Tail-f extension)
  • cmd_exec_default - Default policy for command exec operations (Tail-f extension)
  • log_if_default_permit - Log when default policies permit access (Tail-f extension)
  • log_if_default_deny - Log when default policies deny access (Tail-f extension)
  • groups - Map of group names to group definitions
  • rule_lists - List of rule lists, processed in order

§Examples

use nacm_validator::{NacmConfig, RuleEffect};
use std::collections::HashMap;
 
let config = NacmConfig {
    enable_nacm: true,
    read_default: RuleEffect::Deny,
    write_default: RuleEffect::Deny,
    exec_default: RuleEffect::Deny,
    cmd_read_default: RuleEffect::Permit,
    cmd_exec_default: RuleEffect::Permit,
    log_if_default_permit: false,
    log_if_default_deny: false,
    groups: HashMap::new(),
    rule_lists: vec![],
};

Fields§

§enable_nacm: bool

Global NACM enable flag - if false, all access is permitted

§read_default: RuleEffect

Default policy for read operations when no rules match

§write_default: RuleEffect

Default policy for write operations (create/update/delete) when no rules match

§exec_default: RuleEffect

Default policy for exec operations (RPCs) when no rules match

§cmd_read_default: RuleEffect

Default policy for command read operations when no command rules match (Tail-f extension)

§cmd_exec_default: RuleEffect

Default policy for command exec operations when no command rules match (Tail-f extension)

§log_if_default_permit: bool

Log when default policies permit access (Tail-f extension)

§log_if_default_deny: bool

Log when default policies deny access (Tail-f extension)

§groups: HashMap<String, NacmGroup>

Map of group name to group definition

§rule_lists: Vec<NacmRuleList>

Ordered list of rule lists

Implementations§

Source§

impl NacmConfig

Source

pub fn merge(configs: Vec<(NacmConfig, usize)>) -> Result<Self, Box<dyn Error>>

Merge multiple NACM configurations into a single configuration

Implements YANG merge semantics where:

  • Leaf values (global settings) use last-wins strategy
  • List elements (groups, rules) are merged additively
  • Rule precedence is adjusted based on source file ordering
§Arguments
  • configs - Vector of (config, file_index) tuples where file_index determines precedence
§Returns
  • Ok(NacmConfig) - Successfully merged configuration
  • Err(Box<dyn Error>) - Merge operation failed
§Examples
use nacm_validator::NacmConfig;
 
let xml1 = r#"<config xmlns="http://tail-f.com/ns/config/1.0">
  <nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
    <enable-nacm>true</enable-nacm>
    <read-default>deny</read-default>
    <write-default>deny</write-default>
    <exec-default>deny</exec-default>
    <groups><group><name>admin</name><user-name>alice</user-name></group></groups>
    <rule-list><name>admin-rules</name><group>admin</group></rule-list>
  </nacm>
</config>"#;
let xml2 = r#"<config xmlns="http://tail-f.com/ns/config/1.0">
  <nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
    <enable-nacm>true</enable-nacm>
    <read-default>permit</read-default>
    <write-default>deny</write-default>
    <exec-default>deny</exec-default>
    <groups><group><name>ops</name><user-name>bob</user-name></group></groups>
    <rule-list><name>ops-rules</name><group>ops</group></rule-list>
  </nacm>
</config>"#;
 
let config1 = NacmConfig::from_xml(xml1).unwrap();
let config2 = NacmConfig::from_xml(xml2).unwrap();
 
let merged = NacmConfig::merge(vec![
    (config1, 0),
    (config2, 1),
]).unwrap();
assert_eq!(merged.groups.len(), 2); // Both groups merged
Source

pub fn default() -> Self

Create a default NACM configuration

Returns a configuration with reasonable defaults:

  • NACM enabled
  • All operations denied by default
  • No logging by default
  • Empty groups and rule lists
Source

pub fn from_xml(xml_content: &str) -> Result<Self, Box<dyn Error>>

Parse NACM configuration from XML string

This function takes an XML string containing NACM configuration and parses it into a NacmConfig struct. It handles the conversion from the XML schema to our internal representation.

§Arguments
  • xml_content - String slice containing the XML configuration
§Returns
  • Ok(NacmConfig) - Successfully parsed configuration
  • Err(Box<dyn Error>) - Parsing failed (malformed XML, unknown values, etc.)
§Examples
use nacm_validator::NacmConfig;
 
let xml = r#"
<config xmlns="http://tail-f.com/ns/config/1.0">
  <nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
    <enable-nacm>true</enable-nacm>
    <read-default>deny</read-default>
    <write-default>deny</write-default>
    <exec-default>deny</exec-default>
    <groups>
      <group>
        <name>admin</name>
        <user-name>alice</user-name>
      </group>
    </groups>
    <rule-list>
      <name>admin-rules</name>
      <group>admin</group>
    </rule-list>
  </nacm>
</config>
"#;
 
let config = NacmConfig::from_xml(xml).unwrap();
assert_eq!(config.enable_nacm, true);
Examples found in repository?
examples/readme_quick_start_test.rs (line 5)
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4    let xml_content = std::fs::read_to_string("examples/data/tailf_acm_example.xml")?;
5    let config = NacmConfig::from_xml(&xml_content)?;
6    let context = RequestContext::CLI;
7
8    let request = AccessRequest {
9        user: "alice",
10        operation: Operation::Read,
11        context: Some(&context),
12        command: Some("show status"),
13        module_name: None,
14        rpc_name: None,
15        path: None,
16    };
17
18    let result = config.validate(&request);
19    println!("Access {}: {}", 
20             if result.effect == nacm_validator::RuleEffect::Permit { "GRANTED" } else { "DENIED" },
21             if result.should_log { "[LOGGED]" } else { "" });
22    
23    Ok(())
24}
More examples
Hide additional examples
examples/validate_access.rs (line 15)
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5    // Load the NACM configuration from the XML file
6    // Use a more robust path resolution
7    let xml_path = Path::new(env!("CARGO_MANIFEST_DIR"))
8        .join("examples")
9        .join("data")
10        .join("aaa_ncm_init.xml");
11    
12    let xml_content = std::fs::read_to_string(&xml_path)
13        .map_err(|e| format!("Failed to read XML file at {:?}: {}", xml_path, e))?;
14    
15    let config = NacmConfig::from_xml(&xml_content)?;
16    
17    println!("NACM Configuration loaded:");
18    println!("- NACM enabled: {}", config.enable_nacm);
19    println!("- Default policies: read={:?}, write={:?}, exec={:?}",
20             config.read_default, config.write_default, config.exec_default);
21    println!("- Command default policies: cmd_read={:?}, cmd_exec={:?}",
22             config.cmd_read_default, config.cmd_exec_default);
23    println!("- Groups: {:?}", config.groups.keys().collect::<Vec<_>>());
24    println!("- Rule lists: {}", config.rule_lists.len());
25    
26    // Test different access scenarios including Tail-f ACM features
27    let netconf_context = RequestContext::NETCONF;
28    let cli_context = RequestContext::CLI;
29    let webui_context = RequestContext::WebUI;
30    
31    let test_cases = vec![
32        ("Admin executing edit-config (NETCONF)", AccessRequest {
33            user: "admin",
34            module_name: None,
35            rpc_name: Some("edit-config"),
36            operation: Operation::Exec,
37            path: None,
38            context: Some(&netconf_context),
39            command: None,
40        }),
41        ("Oper executing edit-config (NETCONF)", AccessRequest {
42            user: "oper",
43            module_name: None,
44            rpc_name: Some("edit-config"),
45            operation: Operation::Exec,
46            path: None,
47            context: Some(&netconf_context),
48            command: None,
49        }),
50        ("Oper modifying NACM config (NETCONF)", AccessRequest {
51            user: "oper",
52            module_name: Some("ietf-netconf-acm"),
53            rpc_name: None,
54            operation: Operation::Update,
55            path: Some("/"),
56            context: Some(&netconf_context),
57            command: None,
58        }),
59        ("Guest reading example/misc/data (NETCONF)", AccessRequest {
60            user: "Guest",
61            module_name: Some("example"),
62            rpc_name: None,
63            operation: Operation::Read,
64            path: Some("/misc/data"),
65            context: Some(&netconf_context),
66            command: None,
67        }),
68        ("Guest creating example/misc (NETCONF)", AccessRequest {
69            user: "Guest",
70            module_name: Some("example"),
71            rpc_name: None,
72            operation: Operation::Create,
73            path: Some("/misc"),
74            context: Some(&netconf_context),
75            command: None,
76        }),
77        ("Unknown user reading data (NETCONF)", AccessRequest {
78            user: "unknown",
79            module_name: Some("test"),
80            rpc_name: None,
81            operation: Operation::Read,
82            path: Some("/data"),
83            context: Some(&netconf_context),
84            command: None,
85        }),
86        // Additional test cases for context awareness
87        ("Admin via CLI (no command - should use data rules)", AccessRequest {
88            user: "admin",
89            module_name: Some("ietf-interfaces"),
90            rpc_name: None,
91            operation: Operation::Read,
92            path: Some("/interfaces"),
93            context: Some(&cli_context),
94            command: None,
95        }),
96        ("Admin via WebUI (no command - should use data rules)", AccessRequest {
97            user: "admin",
98            module_name: Some("ietf-interfaces"),
99            rpc_name: None,
100            operation: Operation::Read,
101            path: Some("/interfaces"),
102            context: Some(&webui_context),
103            command: None,
104        }),
105    ];
106    
107    println!("\nAccess validation results:");
108    for (description, request) in test_cases {
109        let result = config.validate(&request);
110        let result_str = match result.effect {
111            RuleEffect::Permit => "✅ PERMIT",
112            RuleEffect::Deny => "❌ DENY",
113        };
114        let log_str = if result.should_log { " [LOG]" } else { "" };
115        println!("- {}: {}{}", description, result_str, log_str);
116    }
117    
118    Ok(())
119}
examples/tailf_acm_demo.rs (line 14)
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5    // Load the Tail-f ACM example configuration
6    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    // Show group details with GIDs
28    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    // Test command access scenarios
44    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    // Test mixed data and command access
119    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}
examples/tailf_acm_comprehensive_demo.rs (line 17)
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}
Source

pub fn validate(&self, req: &AccessRequest<'_>) -> ValidationResult

Validate an access request against the NACM configuration

This is the main validation function that determines whether an access request should be permitted or denied based on the NACM rules, including command rules from the Tail-f ACM extensions.

§Algorithm
  1. If NACM is disabled globally, permit all access
  2. Find all groups the user belongs to
  3. If this is a command request, check command rules first
  4. Otherwise, check standard NACM data access rules
  5. Sort rules by precedence (order field)
  6. Return the effect and logging info of the first matching rule
  7. If no rules match, apply the appropriate default policy
§Arguments
  • req - The access request to validate
§Returns
  • ValidationResult - Contains the access decision and logging flag
§Examples
use nacm_validator::{NacmConfig, AccessRequest, Operation, RequestContext, ValidationResult, RuleEffect};
 
let request = AccessRequest {
    user: "alice",
    module_name: Some("ietf-interfaces"),
    rpc_name: None,
    operation: Operation::Read,
    path: Some("/interfaces"),
    context: Some(&RequestContext::NETCONF),
    command: None,
};
 
let result = config.validate(&request);
// Result contains both the access decision and logging flag
Examples found in repository?
examples/readme_quick_start_test.rs (line 18)
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4    let xml_content = std::fs::read_to_string("examples/data/tailf_acm_example.xml")?;
5    let config = NacmConfig::from_xml(&xml_content)?;
6    let context = RequestContext::CLI;
7
8    let request = AccessRequest {
9        user: "alice",
10        operation: Operation::Read,
11        context: Some(&context),
12        command: Some("show status"),
13        module_name: None,
14        rpc_name: None,
15        path: None,
16    };
17
18    let result = config.validate(&request);
19    println!("Access {}: {}", 
20             if result.effect == nacm_validator::RuleEffect::Permit { "GRANTED" } else { "DENIED" },
21             if result.should_log { "[LOGGED]" } else { "" });
22    
23    Ok(())
24}
More examples
Hide additional examples
examples/validate_access.rs (line 109)
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5    // Load the NACM configuration from the XML file
6    // Use a more robust path resolution
7    let xml_path = Path::new(env!("CARGO_MANIFEST_DIR"))
8        .join("examples")
9        .join("data")
10        .join("aaa_ncm_init.xml");
11    
12    let xml_content = std::fs::read_to_string(&xml_path)
13        .map_err(|e| format!("Failed to read XML file at {:?}: {}", xml_path, e))?;
14    
15    let config = NacmConfig::from_xml(&xml_content)?;
16    
17    println!("NACM Configuration loaded:");
18    println!("- NACM enabled: {}", config.enable_nacm);
19    println!("- Default policies: read={:?}, write={:?}, exec={:?}",
20             config.read_default, config.write_default, config.exec_default);
21    println!("- Command default policies: cmd_read={:?}, cmd_exec={:?}",
22             config.cmd_read_default, config.cmd_exec_default);
23    println!("- Groups: {:?}", config.groups.keys().collect::<Vec<_>>());
24    println!("- Rule lists: {}", config.rule_lists.len());
25    
26    // Test different access scenarios including Tail-f ACM features
27    let netconf_context = RequestContext::NETCONF;
28    let cli_context = RequestContext::CLI;
29    let webui_context = RequestContext::WebUI;
30    
31    let test_cases = vec![
32        ("Admin executing edit-config (NETCONF)", AccessRequest {
33            user: "admin",
34            module_name: None,
35            rpc_name: Some("edit-config"),
36            operation: Operation::Exec,
37            path: None,
38            context: Some(&netconf_context),
39            command: None,
40        }),
41        ("Oper executing edit-config (NETCONF)", AccessRequest {
42            user: "oper",
43            module_name: None,
44            rpc_name: Some("edit-config"),
45            operation: Operation::Exec,
46            path: None,
47            context: Some(&netconf_context),
48            command: None,
49        }),
50        ("Oper modifying NACM config (NETCONF)", AccessRequest {
51            user: "oper",
52            module_name: Some("ietf-netconf-acm"),
53            rpc_name: None,
54            operation: Operation::Update,
55            path: Some("/"),
56            context: Some(&netconf_context),
57            command: None,
58        }),
59        ("Guest reading example/misc/data (NETCONF)", AccessRequest {
60            user: "Guest",
61            module_name: Some("example"),
62            rpc_name: None,
63            operation: Operation::Read,
64            path: Some("/misc/data"),
65            context: Some(&netconf_context),
66            command: None,
67        }),
68        ("Guest creating example/misc (NETCONF)", AccessRequest {
69            user: "Guest",
70            module_name: Some("example"),
71            rpc_name: None,
72            operation: Operation::Create,
73            path: Some("/misc"),
74            context: Some(&netconf_context),
75            command: None,
76        }),
77        ("Unknown user reading data (NETCONF)", AccessRequest {
78            user: "unknown",
79            module_name: Some("test"),
80            rpc_name: None,
81            operation: Operation::Read,
82            path: Some("/data"),
83            context: Some(&netconf_context),
84            command: None,
85        }),
86        // Additional test cases for context awareness
87        ("Admin via CLI (no command - should use data rules)", AccessRequest {
88            user: "admin",
89            module_name: Some("ietf-interfaces"),
90            rpc_name: None,
91            operation: Operation::Read,
92            path: Some("/interfaces"),
93            context: Some(&cli_context),
94            command: None,
95        }),
96        ("Admin via WebUI (no command - should use data rules)", AccessRequest {
97            user: "admin",
98            module_name: Some("ietf-interfaces"),
99            rpc_name: None,
100            operation: Operation::Read,
101            path: Some("/interfaces"),
102            context: Some(&webui_context),
103            command: None,
104        }),
105    ];
106    
107    println!("\nAccess validation results:");
108    for (description, request) in test_cases {
109        let result = config.validate(&request);
110        let result_str = match result.effect {
111            RuleEffect::Permit => "✅ PERMIT",
112            RuleEffect::Deny => "❌ DENY",
113        };
114        let log_str = if result.should_log { " [LOG]" } else { "" };
115        println!("- {}: {}{}", description, result_str, log_str);
116    }
117    
118    Ok(())
119}
examples/tailf_acm_demo.rs (line 108)
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5    // Load the Tail-f ACM example configuration
6    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    // Show group details with GIDs
28    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    // Test command access scenarios
44    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    // Test mixed data and command access
119    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}
examples/tailf_acm_comprehensive_demo.rs (line 129)
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}

Trait Implementations§

Source§

impl Clone for NacmConfig

Source§

fn clone(&self) -> NacmConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for NacmConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.