iam-rs 0.7.0

Complete Rust library for parsing, validating, and evaluating IAM policies. Provider-agnostic authorization engine with full AWS IAM compatibility.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# iam-rs

## USAGE NOTICE: This library is still in final phase of verification and may have breaking changes. Pin exact version. It is functional and ready to use, but it has not been externally verified. Small differences may exist compared to AWS's own internal implementation. Please report any discrepancies.

[![Crates.io](https://img.shields.io/crates/v/iam-rs.svg)](https://crates.io/crates/iam-rs)
[![Documentation](https://docs.rs/iam-rs/badge.svg)](https://docs.rs/iam-rs)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A comprehensive Rust library for parsing, validating, and evaluating AWS IAM (Identity and Access Management) policies. Provider-agnostic and designed for building flexible authorization systems with full AWS IAM compatibility.

## ๐Ÿš€ Key Features

- **๐Ÿ”’ Complete IAM Policy Support**: Full implementation of AWS IAM policy language including conditions, principals, actions, and resources
- **โš–๏ธ Policy Evaluation Engine**: Production-ready authorization engine with proper AWS IAM precedence rules
- **๐Ÿท๏ธ Advanced ARN Support**: Comprehensive ARN parsing, validation, and wildcard pattern matching
- **๐ŸŽฏ Rich Condition Engine**: Support for all AWS condition operators (String, Numeric, Date, Boolean, IP, ARN, Binary, Null)
- **๏ฟฝ Variable Interpolation**: Dynamic policy variables with default fallback values (e.g., `${aws:username, 'anonymous'}`)
- **๐Ÿ“ฆ Type-Safe APIs**: Strong typing with comprehensive enums, builder patterns, and Serde integration
- **โšก High Performance**: Zero-copy parsing, efficient evaluation, and minimal dependencies
- **๐Ÿงช Production Ready**: Extensive test suite with 100+ tests covering real-world scenarios

## ๐Ÿ“ฆ Installation

```bash
cargo add iam-rs
```

## ๐Ÿƒ Quick Start

### Simple Authorization Check

```rust
use iam_rs::{evaluate_policy, Arn, IAMRequest, IAMPolicy, IAMStatement, Effect, Action, IAMResource, Decision, Principal, PrincipalId};

// Create a policy allowing S3 read access
let policy = IAMPolicy::new()
    .add_statement(
        IAMStatement::new(IAMEffect::Allow)
            .with_action(IAMAction::Single("s3:GetObject".to_string()))
            .with_resource(IAMResource::Single("arn:aws:s3:::my-bucket/*".to_string()))
    );

// Create an authorization request
let request = IAMRequest::new(
    Principal::Aws(PrincipalId::String("arn:aws:iam::123456789012:user/alice".to_string()))
    "s3:GetObject",
    Arn::parse("arn:aws:s3:::my-bucket/file.txt").unwrap()
);

// Evaluate the request
match evaluate_policy(&policy, &request)? {
    Decision::Allow => println!("โœ“ Access granted"),
    Decision::Deny => println!("โœ— Access denied"),
    Decision::NotApplicable => println!("? No applicable policy (implicit deny)"),
}
```

### Policy with Conditions

```rust
use iam_rs::{IAMPolicy, IAMStatement, IAMEffect, IAMAction, IAMResource, IAMOperator, Context, ContextValue};
use serde_json::json;

// Create context for condition evaluation
let mut context = Context::new();
context.insert("aws:username".to_string(), ContextValue::String("alice".to_string()));
context.insert("aws:CurrentTime".to_string(), ContextValue::String("2024-06-01T12:00:00Z".to_string()));
context.insert("s3:prefix".to_string(), ContextValue::String("uploads/".to_string()));

// Policy with string and date conditions
let policy = IAMPolicy::new()
    .with_id("ConditionalPolicy")
    .add_statement(
        IAMStatement::new(IAMEffect::Allow)
            .with_sid("AllowUploadToUserFolder")
            .with_action(IAMAction::Single("s3:PutObject".to_string()))
            .with_resource(IAMResource::Single("arn:aws:s3:::my-bucket/${aws:username}/*".to_string()))
            .with_condition(
                IAMOperator::StringEquals,
                "s3:prefix".to_string(),
                json!("uploads/")
            )
            .with_condition(
                IAMOperator::DateGreaterThan,
                "aws:CurrentTime".to_string(),
                json!("2024-01-01T00:00:00Z")
            )
    );

let request = IAMRequest::new_with_context(
    Principal::Aws(PrincipalId::String("arn:aws:iam::123456789012:user/alice".to_string())),
    "s3:PutObject",
    Arn::parse("arn:aws:s3:::my-bucket/alice/uploads/document.pdf").unwrap(),
    context
);

let decision = evaluate_policy(&policy, &request)?;
```

## ๐Ÿ“‹ Core Components

### IAM Policy Structure

```rust
use iam_rs::{IAMPolicy, IAMStatement, Effect, Action, Resource, Principal};

let policy = IAMPolicy::new()
    .with_version(IAMVersion::V20121017)  // AWS standard version
    .with_id("MySecurityPolicy")
    .add_statement(
        IAMStatement::new(IAMEffect::Allow)
            .with_sid("AllowSpecificUsers")
            .with_principal(Principal::from_aws_users(&[
                "arn:aws:iam::123456789012:user/alice",
                "arn:aws:iam::123456789012:user/bob"
            ]))
            .with_action(IAMAction::Multiple(vec![
                "s3:GetObject".to_string(),
                "s3:PutObject".to_string()
            ]))
            .with_resource(IAMResource::Single("arn:aws:s3:::secure-bucket/*".to_string()))
    );
```

### Advanced Pattern Matching

#### ARN Wildcard Patterns

```rust
use iam_rs::Arn;

let arn = Arn::parse("arn:aws:s3:::my-bucket/users/alice/documents/file.pdf")?;

// Test various wildcard patterns
let patterns = [
    "arn:aws:s3:::my-bucket/*",           // โœ“ Matches any object in bucket
    "arn:aws:s3:::my-bucket/users/*",     // โœ“ Matches any user path
    "arn:aws:s3:::my-bucket/users/alice/*", // โœ“ Matches Alice's files
    "arn:aws:s3:::*/documents/*",         // โœ“ Matches any bucket documents
    "arn:aws:s3:::my-bucket/*/file.pdf",  // โœ“ Matches file.pdf anywhere
    "arn:aws:s3:::my-bucket/users/bob/*", // โœ— Different user path
];

for pattern in patterns {
    if arn.matches(Arn::parse(pattern).unwrap()).unwrap() {
        println!("โœ“ ARN matches pattern: {}", pattern);
    }
}
```

#### Action Wildcards

```rust
// Action wildcard matching
let actions = IAMAction::Multiple(vec![
    "s3:*".to_string(),           // All S3 actions
    "s3:Get*".to_string(),        // All S3 Get actions
    "s3:Put*".to_string(),        // All S3 Put actions
    "iam:List*".to_string(),      // All IAM List actions
]);
```

## ๐Ÿ”ง Variable Interpolation

IAM-rs supports AWS policy variables with default fallback values, enabling dynamic resource paths and conditions.

### Basic Variable Usage

```rust
use iam_rs::{interpolate_variables, Context, ContextValue};

// Set up context
let mut context = Context::new();
context.insert("aws:username".to_string(), ContextValue::String("alice".to_string()));
context.insert("aws:PrincipalTag/team".to_string(), ContextValue::String("red".to_string()));

// Basic variable interpolation
let resource_pattern = "arn:aws:s3:::company-bucket/${aws:username}/*";
let resolved = interpolate_variables(resource_pattern, &context)?;
// Result: "arn:aws:s3:::company-bucket/alice/*"

// Variable with default fallback
let team_pattern = "arn:aws:s3:::team-bucket-${aws:PrincipalTag/team, 'default'}/*";
let resolved = interpolate_variables(team_pattern, &context)?;
// Result: "arn:aws:s3:::team-bucket-red/*"
```

### Variables with Default Values

```rust
// When context key is missing, use default value
let empty_context = Context::new();

let pattern = "arn:aws:s3:::bucket-${aws:PrincipalTag/department, 'general'}/*";
let resolved = interpolate_variables(pattern, &empty_context)?;
// Result: "arn:aws:s3:::bucket-general/*" (uses default)

// Common variable patterns
let patterns = [
    "${aws:username}",                          // Current user
    "${aws:userid}",                            // User ID
    "${aws:PrincipalTag/team, 'default'}",     // Principal tag with fallback
    "${aws:RequestedRegion, 'us-east-1'}",     // Region with fallback
    "${aws:CurrentTime}",                       // Current timestamp
    "${s3:prefix, 'uploads/'}",                 // S3 prefix with fallback
];
```

### Dynamic Policy Example

```rust
// Policy that grants access to user-specific paths with team fallback
let policy = IAMPolicy::new()
    .add_statement(
        IAMStatement::new(IAMEffect::Allow)
            .with_action(IAMAction::Single("s3:*".to_string()))
            .with_resource(IAMResource::Multiple(vec![
                // User's personal folder
                "arn:aws:s3:::company-data/${aws:username}/*".to_string(),
                // Team shared folder (with fallback)
                "arn:aws:s3:::team-data/${aws:PrincipalTag/team, 'shared'}/*".to_string(),
                // Department folder (with fallback)
                "arn:aws:s3:::dept-data/${aws:PrincipalTag/department, 'general'}/*".to_string(),
            ]))
            .with_condition(
                IAMOperator::StringLike,
                "s3:prefix".to_string(),
                json!("${aws:username}/*")
            )
    );
```

## ๐ŸŽฏ Condition Operators

IAM-rs supports all AWS condition operators with full type safety:

### String Conditions

```rust
use iam_rs::Operator;

// Basic string operations
IAMOperator::StringEquals         // Exact match
IAMOperator::StringNotEquals      // Not equal
IAMOperator::StringEqualsIgnoreCase // Case-insensitive match
IAMOperator::StringLike           // Wildcard matching (*, ?)
IAMOperator::StringNotLike        // Inverse wildcard matching

// Set-based string operations
IAMOperator::ForAnyValueStringEquals     // At least one value matches
IAMOperator::ForAllValuesStringEquals    // All values match
```

### Numeric and Date Conditions

```rust
// Numeric comparisons
IAMOperator::NumericEquals
IAMOperator::NumericNotEquals
IAMOperator::NumericLessThan
IAMOperator::NumericLessThanEquals
IAMOperator::NumericGreaterThan
IAMOperator::NumericGreaterThanEquals

// Date/time comparisons
IAMOperator::DateEquals
IAMOperator::DateNotEquals
IAMOperator::DateLessThan
IAMOperator::DateGreaterThan
IAMOperator::DateLessThanEquals
IAMOperator::DateGreaterThanEquals
```

### Specialized Conditions

```rust
// Boolean conditions
IAMOperator::Bool

// IP address conditions
IAMOperator::IpAddress            // IP within CIDR range
IAMOperator::NotIpAddress         // IP not in CIDR range

// ARN conditions
IAMOperator::ArnEquals            // Exact ARN match
IAMOperator::ArnLike              // ARN wildcard matching
IAMOperator::ArnNotEquals
IAMOperator::ArnNotLike

// Null checks
IAMOperator::Null                 // Key exists/doesn't exist

// Binary data
IAMOperator::BinaryEquals         // Base64 binary comparison
```

### Complex Condition Example

```rust
let statement = IAMStatement::new(IAMEffect::Allow)
    .with_action(IAMAction::Single("s3:GetObject".to_string()))
    .with_resource(IAMResource::Single("arn:aws:s3:::secure-bucket/*".to_string()))
    // Must be from trusted IP range
    .with_condition(
        IAMOperator::IpAddress,
        "aws:SourceIp".to_string(),
        json!(["203.0.113.0/24", "198.51.100.0/24"])
    )
    // Must have MFA
    .with_condition(
        IAMOperator::Bool,
        "aws:MultiFactorAuthPresent".to_string(),
        json!(true)
    )
    // Must be during business hours
    .with_condition(
        IAMOperator::DateGreaterThan,
        "aws:CurrentTime".to_string(),
        json!("08:00:00Z")
    )
    .with_condition(
        IAMOperator::DateLessThan,
        "aws:CurrentTime".to_string(),
        json!("18:00:00Z")
    )
    // User must have required tag
    .with_condition(
        IAMOperator::StringEquals,
        "aws:PrincipalTag/clearance".to_string(),
        json!("high")
    );
```

## โš–๏ธ Policy Evaluation Engine

### Advanced Evaluation Options

```rust
use iam_rs::{PolicyEvaluator, EvaluationOptions};

let evaluator = PolicyEvaluator::with_policies(vec![policy1, policy2, policy3])
    .with_options(EvaluationOptions {
        stop_on_explicit_deny: true,        // Stop at first explicit deny
        collect_match_details: true,        // Collect debug information
        max_statements: 1000,               // Safety limit
        ignore_resource_constraints: false, // Ignore Resource/NotResource constraints
    });

let result = evaluator.evaluate(&request)?;

println!("Decision: {:?}", result.decision);
println!("Evaluated {} statements", result.statement_details.len());

// Examine detailed results
for statement_match in result.statement_details {
    println!("Statement '{}': {} - {}",
        statement_match.sid.unwrap_or_default(),
        if statement_match.conditions_satisfied { "MATCHED" } else { "NO MATCH" },
        statement_match.reason
    );
}
```

### IAM Precedence Rules

The evaluation engine implements proper AWS IAM logic:

1. **Explicit Deny**: Always takes precedence over Allow
2. **Explicit Allow**: Required for access (no implicit allow)
3. **Implicit Deny**: Default when no Allow statements match
4. **Conditions**: Must be satisfied for statement to apply
5. **Multiple Policies**: Combined with proper precedence

```rust
// Example demonstrating precedence
let allow_policy = IAMPolicy::new()
    .add_statement(
        IAMStatement::new(IAMEffect::Allow)
            .with_action(IAMAction::Single("s3:*".to_string()))
            .with_resource(IAMResource::Single("*".to_string()))
    );

let deny_policy = IAMPolicy::new()
    .add_statement(
        IAMStatement::new(IAMEffect::Deny)  // This will override the Allow
            .with_action(IAMAction::Single("s3:DeleteObject".to_string()))
            .with_resource(IAMResource::Single("arn:aws:s3:::protected-bucket/*".to_string()))
    );

let policies = vec![allow_policy, deny_policy];
let result = evaluate_policies(&policies, &delete_request)?;
// Result: Decision::Deny (Explicit deny wins)
```

## ๐Ÿ“ JSON Policy Support

### Parsing from JSON

```rust
let json_policy = r#"
{
  "Version": "2012-10-17",
  "Id": "S3BucketPolicy",
  "Statement": [
    {
      "Sid": "AllowUserAccess",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:user/alice"
      },
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::my-bucket/${aws:username}/*",
      "Condition": {
        "StringEquals": {
          "s3:x-amz-server-side-encryption": "AES256"
        },
        "NumericLessThan": {
          "s3:max-keys": "10"
        }
      }
    }
  ]
}
"#;

let policy = IAMPolicy::from_json(json_policy)?;
println!("Loaded policy with {} statements", policy.statement.len());
```

### Generating JSON

```rust
// Create policy programmatically
let policy = IAMPolicy::new()
    .with_id("GeneratedPolicy")
    .add_statement(
        IAMStatement::new(IAMEffect::Allow)
            .with_sid("S3Access")
            .with_action(IAMAction::Single("s3:GetObject".to_string()))
            .with_resource(IAMResource::Single("arn:aws:s3:::my-bucket/*".to_string()))
    );

// Export to JSON
let json_output = policy.to_json()?;
println!("{}", json_output);
```

## ๐Ÿงช Examples

Run the comprehensive examples to see all features in action:

```bash
# ARN parsing and wildcard matching
cargo run --example arn_demo

# Policy validation and structure
cargo run --example validation_demo

# Complete evaluation engine with conditions
cargo run --example evaluation_demo
```

### Example Scenarios Covered

- โœ… **Basic Allow/Deny policies** with simple action/resource matching
- โœ… **Wildcard patterns** for actions, resources, and principals
- โœ… **Complex conditions** with String, Numeric, Date, Boolean, IP, and ARN operators
- โœ… **Variable interpolation** with fallback values for dynamic policies
- โœ… **Multi-policy evaluation** with proper precedence handling
- โœ… **Real-world scenarios** like user folder access, time-based restrictions
- โœ… **Resource-based policies** for S3 buckets, Lambda functions, etc.
- โœ… **Cross-account access** with proper principal validation

## ๐Ÿค Contributing

Contributions are welcome! This library aims to be the definitive Rust implementation of AWS IAM policy evaluation.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Add tests for new functionality
4. Run the test suite (`cargo test`)
5. Check code quality (`cargo clippy`)
6. Commit your changes (`git commit -m 'Add amazing feature'`)
7. Push to the branch (`git push origin feature/amazing-feature`)
8. Open a Pull Request

## ๐Ÿ“„ License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.