1use iam_rs::{
2 Action, Context, ContextValue, Decision, Effect, EvaluationOptions, IAMPolicy, IAMRequest,
3 IAMStatement, Operator, PolicyEvaluator, Resource, evaluate_policy,
4};
5use serde_json::json;
6
7fn main() -> Result<(), Box<dyn std::error::Error>> {
8 println!("=== IAM Policy Evaluation Engine Demo ===\n");
9
10 println!("1. Simple Allow Policy:");
12 let allow_policy = IAMPolicy::new()
13 .with_id("550e8400-e29b-41d4-a716-446655440000")
14 .add_statement(
15 IAMStatement::new(Effect::Allow)
16 .with_sid("AllowS3Read")
17 .with_action(Action::Single("s3:GetObject".to_string()))
18 .with_resource(Resource::Single("arn:aws:s3:::my-bucket/*".to_string())),
19 );
20
21 let request = IAMRequest::new(
22 "arn:aws:iam::123456789012:user/alice",
23 "s3:GetObject",
24 "arn:aws:s3:::my-bucket/file.txt",
25 );
26
27 match evaluate_policy(&allow_policy, &request)? {
28 Decision::Allow => println!("✓ Access ALLOWED"),
29 Decision::Deny => println!("✗ Access DENIED"),
30 Decision::NotApplicable => println!("? No applicable policy (implicit deny)"),
31 }
32 println!();
33
34 println!("2. Simple Deny Policy:");
36 let deny_policy = IAMPolicy::new()
37 .with_id("550e8400-e29b-41d4-a716-446655440001")
38 .add_statement(
39 IAMStatement::new(Effect::Deny)
40 .with_sid("DenyS3Delete")
41 .with_action(Action::Single("s3:DeleteObject".to_string()))
42 .with_resource(Resource::Single(
43 "arn:aws:s3:::protected-bucket/*".to_string(),
44 )),
45 );
46
47 let delete_request = IAMRequest::new(
48 "arn:aws:iam::123456789012:user/alice",
49 "s3:DeleteObject",
50 "arn:aws:s3:::protected-bucket/important.txt",
51 );
52
53 match evaluate_policy(&deny_policy, &delete_request)? {
54 Decision::Allow => println!("✓ Access ALLOWED"),
55 Decision::Deny => println!("✗ Access DENIED"),
56 Decision::NotApplicable => println!("? No applicable policy (implicit deny)"),
57 }
58 println!();
59
60 println!("3. Wildcard Action Matching:");
62 let wildcard_policy = IAMPolicy::new()
63 .with_id("550e8400-e29b-41d4-a716-446655440002")
64 .add_statement(
65 IAMStatement::new(Effect::Allow)
66 .with_sid("AllowAllS3")
67 .with_action(Action::Single("s3:*".to_string()))
68 .with_resource(Resource::Single("arn:aws:s3:::my-bucket/*".to_string())),
69 );
70
71 let wildcard_request = IAMRequest::new(
72 "arn:aws:iam::123456789012:user/alice",
73 "s3:PutObject",
74 "arn:aws:s3:::my-bucket/new-file.txt",
75 );
76
77 match evaluate_policy(&wildcard_policy, &wildcard_request)? {
78 Decision::Allow => println!("✓ Wildcard action matched - Access ALLOWED"),
79 Decision::Deny => println!("✗ Access DENIED"),
80 Decision::NotApplicable => println!("? No applicable policy"),
81 }
82 println!();
83
84 println!("4. Condition-Based Policy:");
86 let mut context = Context::new();
87 context.insert(
88 "aws:userid".to_string(),
89 ContextValue::String("alice".to_string()),
90 );
91 context.insert(
92 "aws:CurrentTime".to_string(),
93 ContextValue::String("2024-01-15T10:00:00Z".to_string()),
94 );
95
96 let condition_policy = IAMPolicy::new()
97 .with_id("550e8400-e29b-41d4-a716-446655440003")
98 .add_statement(
99 IAMStatement::new(Effect::Allow)
100 .with_sid("AllowWithCondition")
101 .with_action(Action::Single("s3:GetObject".to_string()))
102 .with_resource(Resource::Single(
103 "arn:aws:s3:::private-bucket/*".to_string(),
104 ))
105 .with_condition(
106 Operator::StringEquals,
107 "aws:userid".to_string(),
108 json!("alice"),
109 ),
110 );
111
112 let condition_request = IAMRequest::new_with_context(
113 "arn:aws:iam::123456789012:user/alice",
114 "s3:GetObject",
115 "arn:aws:s3:::private-bucket/personal.txt",
116 context,
117 );
118
119 match evaluate_policy(&condition_policy, &condition_request)? {
120 Decision::Allow => println!("✓ Condition satisfied - Access ALLOWED"),
121 Decision::Deny => println!("✗ Access DENIED"),
122 Decision::NotApplicable => println!("? Condition not satisfied"),
123 }
124 println!();
125
126 println!("5. Failed Condition:");
128 let mut wrong_context = Context::new();
129 wrong_context.insert(
130 "aws:userid".to_string(),
131 ContextValue::String("bob".to_string()),
132 );
133
134 let failed_condition_request = IAMRequest::new_with_context(
135 "arn:aws:iam::123456789012:user/bob",
136 "s3:GetObject",
137 "arn:aws:s3:::private-bucket/personal.txt",
138 wrong_context,
139 );
140
141 match evaluate_policy(&condition_policy, &failed_condition_request)? {
142 Decision::Allow => println!("✓ Access ALLOWED"),
143 Decision::Deny => println!("✗ Access DENIED"),
144 Decision::NotApplicable => println!("? Condition failed - No applicable policy"),
145 }
146 println!();
147
148 println!("6. Explicit Deny Overrides Allow:");
150 let combined_policies = vec![
151 IAMPolicy::new()
152 .with_id("550e8400-e29b-41d4-a716-446655440004")
153 .add_statement(
154 IAMStatement::new(Effect::Allow)
155 .with_sid("AllowAll")
156 .with_action(Action::Single("s3:*".to_string()))
157 .with_resource(Resource::Single("*".to_string())),
158 ),
159 IAMPolicy::new()
160 .with_id("550e8400-e29b-41d4-a716-446655440005")
161 .add_statement(
162 IAMStatement::new(Effect::Deny)
163 .with_sid("DenyProtected")
164 .with_action(Action::Single("s3:DeleteObject".to_string()))
165 .with_resource(Resource::Single(
166 "arn:aws:s3:::protected-bucket/*".to_string(),
167 )),
168 ),
169 ];
170
171 let evaluator = PolicyEvaluator::with_policies(combined_policies);
172 let protected_request = IAMRequest::new(
173 "arn:aws:iam::123456789012:user/alice",
174 "s3:DeleteObject",
175 "arn:aws:s3:::protected-bucket/critical.txt",
176 );
177
178 match evaluator.evaluate(&protected_request)?.decision {
179 Decision::Allow => println!("✓ Access ALLOWED"),
180 Decision::Deny => println!("✗ Explicit DENY overrides Allow"),
181 Decision::NotApplicable => println!("? No applicable policy"),
182 }
183 println!();
184
185 println!("7. Numeric Condition:");
187 let mut numeric_context = Context::new();
188 numeric_context.insert("aws:RequestCount".to_string(), ContextValue::Number(5.0));
189
190 let numeric_policy = IAMPolicy::new()
191 .with_id("550e8400-e29b-41d4-a716-446655440006")
192 .add_statement(
193 IAMStatement::new(Effect::Allow)
194 .with_sid("AllowLimitedRequests")
195 .with_action(Action::Single("s3:GetObject".to_string()))
196 .with_resource(Resource::Single("*".to_string()))
197 .with_condition(
198 Operator::NumericLessThan,
199 "aws:RequestCount".to_string(),
200 json!(10),
201 ),
202 );
203
204 let numeric_request = IAMRequest::new_with_context(
205 "arn:aws:iam::123456789012:user/alice",
206 "s3:GetObject",
207 "arn:aws:s3:::any-bucket/file.txt",
208 numeric_context,
209 );
210
211 match evaluate_policy(&numeric_policy, &numeric_request)? {
212 Decision::Allow => println!("✓ Numeric condition satisfied - Access ALLOWED"),
213 Decision::Deny => println!("✗ Access DENIED"),
214 Decision::NotApplicable => println!("? Numeric condition failed"),
215 }
216 println!();
217
218 println!("8. Detailed Evaluation with Options:");
220 let detailed_evaluator = PolicyEvaluator::with_policies(vec![allow_policy.clone()])
221 .with_options(EvaluationOptions {
222 collect_match_details: true,
223 stop_on_explicit_deny: false,
224 max_statements: 100,
225 });
226
227 let detailed_result = detailed_evaluator.evaluate(&request)?;
228 println!("Decision: {:?}", detailed_result.decision);
229 println!("Matched Statements:");
230 for (i, statement_match) in detailed_result.matched_statements.iter().enumerate() {
231 println!(
232 " {}. SID: {:?}, Effect: {:?}, Satisfied: {}, Reason: {}",
233 i + 1,
234 statement_match.sid,
235 statement_match.effect,
236 statement_match.conditions_satisfied,
237 statement_match.reason
238 );
239 }
240 println!();
241
242 println!("9. No Applicable Policy (Implicit Deny):");
244 let unrelated_request = IAMRequest::new(
245 "arn:aws:iam::123456789012:user/alice",
246 "ec2:DescribeInstances",
247 "arn:aws:ec2:us-east-1:123456789012:instance/*",
248 );
249
250 match evaluate_policy(&allow_policy, &unrelated_request)? {
251 Decision::Allow => println!("✓ Access ALLOWED"),
252 Decision::Deny => println!("✗ Access DENIED"),
253 Decision::NotApplicable => println!("? No applicable policy - Implicit DENY"),
254 }
255 println!();
256
257 println!("10. Resource Pattern Matching:");
259 let pattern_policy = IAMPolicy::new()
260 .with_id("550e8400-e29b-41d4-a716-446655440007")
261 .add_statement(
262 IAMStatement::new(Effect::Allow)
263 .with_sid("AllowBucketAccess")
264 .with_action(Action::Multiple(vec![
265 "s3:GetObject".to_string(),
266 "s3:PutObject".to_string(),
267 ]))
268 .with_resource(Resource::Single("arn:aws:s3:::user-data-*/*".to_string())),
269 );
270
271 let pattern_request = IAMRequest::new(
272 "arn:aws:iam::123456789012:user/alice",
273 "s3:GetObject",
274 "arn:aws:s3:::user-data-alice/profile.json",
275 );
276
277 match evaluate_policy(&pattern_policy, &pattern_request)? {
278 Decision::Allow => println!("✓ Resource pattern matched - Access ALLOWED"),
279 Decision::Deny => println!("✗ Access DENIED"),
280 Decision::NotApplicable => println!("? Resource pattern didn't match"),
281 }
282
283 println!("\n=== Policy Evaluation Demo Complete ===");
284 println!("\nThe Policy Evaluation Engine successfully:");
285 println!("• ✅ Evaluates Allow/Deny effects");
286 println!("• ✅ Handles wildcard actions and resources");
287 println!("• ✅ Processes condition blocks with various operators");
288 println!("• ✅ Implements proper IAM logic (explicit deny overrides)");
289 println!("• ✅ Supports detailed evaluation with match information");
290 println!("• ✅ Handles multiple policies with complex interactions");
291 println!("• ✅ Provides clear Allow/Deny/NotApplicable decisions");
292
293 Ok(())
294}