Skip to main content

23_permissions_boundaries/
23_permissions_boundaries.rs

1//! Example 23: Permissions Boundaries
2//!
3//! This example demonstrates how to use permissions boundaries to set the maximum
4//! permissions that identity-based policies can grant to users and roles.
5//!
6//! Key Concepts:
7//! - Permissions boundaries act as a ceiling for effective permissions
8//! - Effective permissions = identity-based policies ∩ permissions boundary
9//! - Both must allow an action for it to be permitted
10//!
11//! Run with: cargo run --example 23_permissions_boundaries
12
13use std::sync::Arc;
14use tokio::sync::RwLock;
15use wami::{
16    arn::{TenantPath, WamiArn},
17    context::WamiContext,
18    service::{
19        EvaluationService, PermissionsBoundaryService, PolicyService, RoleService, UserService,
20    },
21    store::memory::InMemoryWamiStore,
22    wami::identity::role::requests::CreateRoleRequest,
23    wami::identity::user::requests::CreateUserRequest,
24    wami::policies::evaluation::SimulatePrincipalPolicyRequest,
25    wami::policies::permissions_boundary::{
26        DeletePermissionsBoundaryRequest, PrincipalType, PutPermissionsBoundaryRequest,
27    },
28    wami::policies::policy::requests::CreatePolicyRequest,
29};
30
31#[tokio::main]
32async fn main() -> Result<(), Box<dyn std::error::Error>> {
33    println!("=== WAMI Example 23: Permissions Boundaries ===\n");
34
35    // Initialize store and services
36    let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
37
38    // Create context
39    let context = WamiContext::builder()
40        .instance_id("123456789012")
41        .tenant_path(TenantPath::single(0))
42        .caller_arn(
43            WamiArn::builder()
44                .service(wami::arn::Service::Iam)
45                .tenant_path(TenantPath::single(0))
46                .wami_instance("123456789012")
47                .resource("user", "admin")
48                .build()?,
49        )
50        .is_root(false)
51        .build()?;
52
53    let user_service = UserService::new(store.clone());
54    let role_service = RoleService::new(store.clone());
55    let policy_service = PolicyService::new(store.clone());
56    let boundary_service =
57        PermissionsBoundaryService::new(store.clone(), "123456789012".to_string());
58    let evaluation_service = EvaluationService::new(store.clone(), "123456789012".to_string());
59
60    // ==========================================
61    // Part 1: Create User with Admin Policy
62    // ==========================================
63    println!("šŸ“‹ Part 1: Creating User with Admin Policy\n");
64
65    // Create a user
66    let alice_req = CreateUserRequest {
67        user_name: "alice".to_string(),
68        path: Some("/developers/".to_string()),
69        permissions_boundary: None,
70        tags: None,
71    };
72    let alice = user_service.create_user(&context, alice_req).await?;
73    println!("āœ… Created user: {}", alice.user_name);
74    println!("   ARN: {}\n", alice.arn);
75
76    // Create an admin policy (allows all actions)
77    let admin_policy_doc = r#"{
78        "Version": "2012-10-17",
79        "Statement": [{
80            "Effect": "Allow",
81            "Action": "*",
82            "Resource": "*"
83        }]
84    }"#;
85    let admin_policy = policy_service
86        .create_policy(
87            &context,
88            CreatePolicyRequest {
89                policy_name: "AdminPolicy".to_string(),
90                policy_document: admin_policy_doc.to_string(),
91                path: Some("/".to_string()),
92                description: Some("Full admin access".to_string()),
93                tags: None,
94            },
95        )
96        .await?;
97    println!("āœ… Created admin policy: {}", admin_policy.policy_name);
98    println!("   ARN: {}", admin_policy.arn);
99    println!("   Allows: All actions on all resources\n");
100
101    // ==========================================
102    // Part 2: Create S3-Only Boundary Policy
103    // ==========================================
104    println!("šŸ“‹ Part 2: Creating S3-Only Boundary Policy\n");
105
106    let s3_boundary_doc = r#"{
107        "Version": "2012-10-17",
108        "Statement": [{
109            "Effect": "Allow",
110            "Action": "s3:*",
111            "Resource": "*"
112        }]
113    }"#;
114    let s3_boundary = policy_service
115        .create_policy(
116            &context,
117            CreatePolicyRequest {
118                policy_name: "S3OnlyBoundary".to_string(),
119                policy_document: s3_boundary_doc.to_string(),
120                path: Some("/boundaries/".to_string()),
121                description: Some("Limits permissions to S3 only".to_string()),
122                tags: None,
123            },
124        )
125        .await?;
126    println!("āœ… Created boundary policy: {}", s3_boundary.policy_name);
127    println!("   ARN: {}", s3_boundary.arn);
128    println!("   Allows: Only S3 actions\n");
129
130    // ==========================================
131    // Part 3: Test Without Boundary
132    // ==========================================
133    println!("šŸ“‹ Part 3: Testing Permissions WITHOUT Boundary\n");
134
135    // Simulate alice's permissions (admin policy allows everything)
136    let sim_req = SimulatePrincipalPolicyRequest {
137        policy_source_arn: alice.arn.clone(),
138        action_names: vec![
139            "s3:GetObject".to_string(),
140            "ec2:RunInstances".to_string(),
141            "iam:CreateUser".to_string(),
142        ],
143        resource_arns: Some(vec!["*".to_string()]),
144        policy_input_list: Some(vec![admin_policy_doc.to_string()]),
145        context_entries: None,
146    };
147
148    let results = evaluation_service
149        .simulate_principal_policy(sim_req)
150        .await?;
151
152    println!("Action Evaluation Results:");
153    for result in &results.evaluation_results {
154        println!(
155            "  • {} on {} → {}",
156            result.eval_action_name, result.eval_resource_name, result.eval_decision
157        );
158    }
159    println!();
160
161    // ==========================================
162    // Part 4: Attach Boundary to User
163    // ==========================================
164    println!("šŸ“‹ Part 4: Attaching S3-Only Boundary to User\n");
165
166    let put_boundary_req = PutPermissionsBoundaryRequest {
167        principal_type: PrincipalType::User,
168        principal_name: "alice".to_string(),
169        permissions_boundary: s3_boundary.arn.clone(),
170    };
171    boundary_service
172        .put_permissions_boundary(&context, put_boundary_req)
173        .await?;
174    println!("āœ… Attached permissions boundary to alice");
175    println!("   Boundary: {}", s3_boundary.arn);
176    println!("   Effect: Alice's permissions are now limited to S3 actions only\n");
177
178    // ==========================================
179    // Part 5: Test WITH Boundary
180    // ==========================================
181    println!("šŸ“‹ Part 5: Testing Permissions WITH Boundary\n");
182
183    let sim_req_with_boundary = SimulatePrincipalPolicyRequest {
184        policy_source_arn: alice.arn.clone(),
185        action_names: vec![
186            "s3:GetObject".to_string(),
187            "s3:PutObject".to_string(),
188            "ec2:RunInstances".to_string(),
189            "iam:CreateUser".to_string(),
190        ],
191        resource_arns: Some(vec!["*".to_string()]),
192        policy_input_list: Some(vec![admin_policy_doc.to_string()]),
193        context_entries: None,
194    };
195
196    let results_with_boundary = evaluation_service
197        .simulate_principal_policy(sim_req_with_boundary)
198        .await?;
199
200    println!("Action Evaluation Results (with boundary):");
201    for result in &results_with_boundary.evaluation_results {
202        let status = match result.eval_decision.as_str() {
203            "allowed" => "āœ… ALLOWED",
204            "denied" => "āŒ DENIED",
205            _ => "āš ļø  IMPLICIT DENY",
206        };
207        println!(
208            "  {} → {} ({})",
209            result.eval_action_name, status, result.eval_decision
210        );
211    }
212    println!("\nšŸ“ Notice:");
213    println!("   • S3 actions are ALLOWED (both policy and boundary allow)");
214    println!("   • EC2 and IAM actions are DENIED (boundary restricts them)\n");
215
216    // ==========================================
217    // Part 6: Boundary with Roles
218    // ==========================================
219    println!("šŸ“‹ Part 6: Using Boundaries with Roles\n");
220
221    // Create a role with assume policy
222    let assume_policy_doc = r#"{
223        "Version": "2012-10-17",
224        "Statement": [{
225            "Effect": "Allow",
226            "Principal": {"Service": "ec2.amazonaws.com"},
227            "Action": "sts:AssumeRole"
228        }]
229    }"#;
230
231    let dev_role_req = CreateRoleRequest {
232        role_name: "DeveloperRole".to_string(),
233        assume_role_policy_document: assume_policy_doc.to_string(),
234        path: Some("/roles/".to_string()),
235        description: Some("Role for developers".to_string()),
236        max_session_duration: None,
237        permissions_boundary: None,
238        tags: None,
239    };
240    let dev_role = role_service.create_role(&context, dev_role_req).await?;
241    println!("āœ… Created role: {}", dev_role.role_name);
242    println!("   ARN: {}\n", dev_role.arn);
243
244    // Create a read-only boundary
245    let read_only_boundary_doc = r#"{
246        "Version": "2012-10-17",
247        "Statement": [{
248            "Effect": "Allow",
249            "Action": [
250                "s3:Get*",
251                "s3:List*",
252                "ec2:Describe*"
253            ],
254            "Resource": "*"
255        }]
256    }"#;
257    let read_only_boundary = policy_service
258        .create_policy(
259            &context,
260            CreatePolicyRequest {
261                policy_name: "ReadOnlyBoundary".to_string(),
262                policy_document: read_only_boundary_doc.to_string(),
263                path: Some("/boundaries/".to_string()),
264                description: Some("Limits to read-only operations".to_string()),
265                tags: None,
266            },
267        )
268        .await?;
269    println!(
270        "āœ… Created read-only boundary: {}",
271        read_only_boundary.policy_name
272    );
273
274    // Attach boundary to role
275    let put_role_boundary = PutPermissionsBoundaryRequest {
276        principal_type: PrincipalType::Role,
277        principal_name: "DeveloperRole".to_string(),
278        permissions_boundary: read_only_boundary.arn.clone(),
279    };
280    boundary_service
281        .put_permissions_boundary(&context, put_role_boundary)
282        .await?;
283    println!("āœ… Attached read-only boundary to DeveloperRole\n");
284
285    // Test role with boundary
286    let role_sim_req = SimulatePrincipalPolicyRequest {
287        policy_source_arn: dev_role.arn.clone(),
288        action_names: vec![
289            "s3:GetObject".to_string(),
290            "s3:PutObject".to_string(),
291            "ec2:DescribeInstances".to_string(),
292            "ec2:RunInstances".to_string(),
293        ],
294        resource_arns: Some(vec!["*".to_string()]),
295        policy_input_list: Some(vec![admin_policy_doc.to_string()]),
296        context_entries: None,
297    };
298
299    let role_results = evaluation_service
300        .simulate_principal_policy(role_sim_req)
301        .await?;
302
303    println!("Role Action Evaluation (with read-only boundary):");
304    for result in &role_results.evaluation_results {
305        let status = match result.eval_decision.as_str() {
306            "allowed" => "āœ… ALLOWED",
307            "denied" => "āŒ DENIED",
308            _ => "āš ļø  IMPLICIT DENY",
309        };
310        println!("  {} → {} ", result.eval_action_name, status);
311    }
312    println!("\nšŸ“ Notice:");
313    println!("   • Read operations (Get*, Describe*) are ALLOWED");
314    println!("   • Write operations (Put*, Run*) are DENIED by boundary\n");
315
316    // ==========================================
317    // Part 7: Removing Boundaries
318    // ==========================================
319    println!("šŸ“‹ Part 7: Removing Permissions Boundaries\n");
320
321    let delete_user_boundary = DeletePermissionsBoundaryRequest {
322        principal_type: PrincipalType::User,
323        principal_name: "alice".to_string(),
324    };
325    boundary_service
326        .delete_permissions_boundary(&context, delete_user_boundary)
327        .await?;
328    println!("āœ… Removed boundary from alice");
329    println!("   Effect: Alice now has full admin permissions again\n");
330
331    let delete_role_boundary = DeletePermissionsBoundaryRequest {
332        principal_type: PrincipalType::Role,
333        principal_name: "DeveloperRole".to_string(),
334    };
335    boundary_service
336        .delete_permissions_boundary(&context, delete_role_boundary)
337        .await?;
338    println!("āœ… Removed boundary from DeveloperRole\n");
339
340    // ==========================================
341    // Part 8: Real-World Use Cases
342    // ==========================================
343    println!("šŸ“‹ Part 8: Real-World Use Cases\n");
344    println!("Use Case 1: Sandbox Environments");
345    println!("  - Attach boundaries to prevent developers from:");
346    println!("    • Creating IAM users/roles");
347    println!("    • Modifying billing/account settings");
348    println!("    • Accessing production resources\n");
349
350    println!("Use Case 2: Contractor Access");
351    println!("  - Limit contractors to specific services:");
352    println!("    • Allow S3 and Lambda only");
353    println!("    • Prevent infrastructure changes");
354    println!("    • Ensure audit trail compliance\n");
355
356    println!("Use Case 3: Delegated Administration");
357    println!("  - Allow team leads to create users but:");
358    println!("    • Boundary prevents privilege escalation");
359    println!("    • New users inherit safe permission limits");
360    println!("    • Central security team controls boundaries\n");
361
362    println!("Use Case 4: Multi-Tenant SaaS");
363    println!("  - Each tenant gets a boundary policy:");
364    println!("    • Restricts access to tenant-specific resources");
365    println!("    • Prevents cross-tenant data access");
366    println!("    • Simplifies per-tenant permission management\n");
367
368    println!("=== Example 23 Complete ===");
369    println!("\nšŸŽ“ Key Takeaways:");
370    println!("  1. Boundaries set maximum permissions (ceiling)");
371    println!("  2. Effective permissions = identity policies ∩ boundary");
372    println!("  3. Both identity policy AND boundary must allow an action");
373    println!("  4. Boundaries prevent privilege escalation");
374    println!("  5. Use for security controls, sandboxes, and delegation");
375
376    Ok(())
377}