iam-rs

Crates.ioiam-rs
lib.rsiam-rs
version0.0.23
created_at2025-07-05 12:49:17.160409+00
updated_at2025-08-21 08:19:44.262411+00
descriptionComplete Rust library for parsing, validating, and evaluating IAM policies. Provider-agnostic authorization engine with full AWS IAM compatibility.
homepage
repositoryhttps://github.com/foorack/iam-rs
max_upload_size
id1739054
size372,445
Foorack / Max Faxรคlv (Foorack)

documentation

README

iam-rs

STILL IN FINAL DEV AND VERIFICATION PHASE - CAUTION ON USAGE

STILL IN FINAL DEV AND VERIFICATION PHASE - CAUTION ON USAGE

STILL IN FINAL DEV AND VERIFICATION PHASE - CAUTION ON USAGE

Crates.io Documentation License: 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

cargo add iam-rs

๐Ÿƒ Quick Start

Simple Authorization Check

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

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

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

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

// 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

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

// 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

// 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

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

// 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

// 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

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

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
    );
}

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
// 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

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

// 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:

# 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 file for details.

Commit count: 107

cargo fmt