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 flagread_default- Default policy for read operationswrite_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 definitionsrule_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: boolGlobal NACM enable flag - if false, all access is permitted
read_default: RuleEffectDefault policy for read operations when no rules match
write_default: RuleEffectDefault policy for write operations (create/update/delete) when no rules match
exec_default: RuleEffectDefault policy for exec operations (RPCs) when no rules match
cmd_read_default: RuleEffectDefault policy for command read operations when no command rules match (Tail-f extension)
cmd_exec_default: RuleEffectDefault policy for command exec operations when no command rules match (Tail-f extension)
log_if_default_permit: boolLog when default policies permit access (Tail-f extension)
log_if_default_deny: boolLog 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
impl NacmConfig
Sourcepub fn merge(configs: Vec<(NacmConfig, usize)>) -> Result<Self, Box<dyn Error>>
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 configurationErr(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 mergedSourcepub fn default() -> Self
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
Sourcepub fn from_xml(xml_content: &str) -> Result<Self, Box<dyn Error>>
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 configurationErr(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?
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
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}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}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}Sourcepub fn validate(&self, req: &AccessRequest<'_>) -> ValidationResult
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
- If NACM is disabled globally, permit all access
- Find all groups the user belongs to
- If this is a command request, check command rules first
- Otherwise, check standard NACM data access rules
- Sort rules by precedence (order field)
- Return the effect and logging info of the first matching rule
- 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 flagExamples found in repository?
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
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}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}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
impl Clone for NacmConfig
Source§fn clone(&self) -> NacmConfig
fn clone(&self) -> NacmConfig
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more