Crates.io | iam-rs |
lib.rs | iam-rs |
version | 0.0.23 |
created_at | 2025-07-05 12:49:17.160409+00 |
updated_at | 2025-08-21 08:19:44.262411+00 |
description | Complete Rust library for parsing, validating, and evaluating IAM policies. Provider-agnostic authorization engine with full AWS IAM compatibility. |
homepage | |
repository | https://github.com/foorack/iam-rs |
max_upload_size | |
id | 1739054 |
size | 372,445 |
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.
${aws:username, 'anonymous'}
)cargo add iam-rs
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)"),
}
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)?;
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()))
);
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 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
]);
IAM-rs supports AWS policy variables with default fallback values, enabling dynamic resource paths and conditions.
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/*"
// 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
];
// 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}/*")
)
);
IAM-rs supports all AWS condition operators with full type safety:
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 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
// 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
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")
);
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.matched_statements.len());
// Examine detailed results
for statement_match in result.matched_statements {
println!("Statement '{}': {} - {}",
statement_match.sid.unwrap_or_default(),
if statement_match.conditions_satisfied { "MATCHED" } else { "NO MATCH" },
statement_match.reason
);
}
The evaluation engine implements proper AWS IAM logic:
// 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)
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());
// 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);
Run the comprehensive examples to see all features in action:
# 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
Contributions are welcome! This library aims to be the definitive Rust implementation of AWS IAM policy evaluation.
git checkout -b feature/amazing-feature
)cargo test
)cargo clippy
)git commit -m 'Add amazing feature'
)git push origin feature/amazing-feature
)This project is licensed under the MIT License - see the LICENSE file for details.