alien_permissions/
initial_setup.rs1use alien_core::Stack;
8
9use crate::generators::{AwsIamPolicy, AwsRuntimePermissionsGenerator};
10use crate::registry::get_permission_set;
11use crate::{BindingTarget, PermissionContext};
12
13fn normalize_resource_type(resource_type: &str) -> String {
17 resource_type.replace('_', "-")
18}
19
20pub fn initial_setup_permission_set_ids(stack: &Stack) -> Vec<String> {
27 let mut set_ids = Vec::new();
28
29 for (_, resource_entry) in stack.resources() {
30 let resource_type = normalize_resource_type(resource_entry.config.resource_type().as_ref());
31 let provision_id = format!("{resource_type}/provision");
32
33 if get_permission_set(&provision_id).is_some() && !set_ids.contains(&provision_id) {
34 set_ids.push(provision_id);
35 }
36 }
37
38 let cross_cutting = ["service-account/provision"];
39
40 for id in cross_cutting {
41 if get_permission_set(id).is_some() && !set_ids.contains(&id.to_string()) {
42 set_ids.push(id.to_string());
43 }
44 }
45
46 set_ids
47}
48
49pub fn generate_aws_initial_setup_policy(
60 context: &PermissionContext,
61) -> crate::error::Result<AwsIamPolicy> {
62 let generator = AwsRuntimePermissionsGenerator::new();
63 let all_provision_ids = crate::registry::list_permission_set_ids()
64 .into_iter()
65 .filter(|id| id.ends_with("/provision"))
66 .collect::<Vec<_>>();
67
68 let mut all_statements = Vec::new();
69
70 for perm_id in &all_provision_ids {
71 if let Some(perm_set) = get_permission_set(perm_id) {
72 match generator.generate_policy(perm_set, BindingTarget::Stack, context) {
73 Ok(policy) => {
74 all_statements.extend(policy.statement);
75 }
76 Err(_) => {
77 }
79 }
80 }
81 }
82
83 Ok(AwsIamPolicy {
84 version: "2012-10-17".to_string(),
85 statement: all_statements,
86 })
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use alien_core::{Function, FunctionCode, ResourceLifecycle, Storage};
93
94 fn test_function(name: &str) -> Function {
95 Function::new(name.to_string())
96 .code(FunctionCode::Image {
97 image: "rust:latest".to_string(),
98 })
99 .permissions("execution".to_string())
100 .build()
101 }
102
103 #[test]
104 fn function_stack_includes_function_provision() {
105 let function = test_function("my-fn");
106
107 let stack = Stack::new("test-stack".to_string())
108 .add(function, ResourceLifecycle::Live)
109 .build();
110
111 let ids = initial_setup_permission_set_ids(&stack);
112 assert!(
113 ids.contains(&"function/provision".to_string()),
114 "Expected function/provision in {ids:?}"
115 );
116 }
117
118 #[test]
119 fn storage_stack_includes_storage_provision() {
120 let storage = Storage::new("my-bucket".to_string()).build();
121
122 let stack = Stack::new("test-stack".to_string())
123 .add(storage, ResourceLifecycle::Frozen)
124 .build();
125
126 let ids = initial_setup_permission_set_ids(&stack);
127 assert!(
128 ids.contains(&"storage/provision".to_string()),
129 "Expected storage/provision in {ids:?}"
130 );
131 }
132
133 #[test]
134 fn cross_cutting_service_account_always_included() {
135 let stack = Stack::new("empty-stack".to_string()).build();
136
137 let ids = initial_setup_permission_set_ids(&stack);
138 assert!(
139 ids.contains(&"service-account/provision".to_string()),
140 "Expected service-account/provision in {ids:?}"
141 );
142 }
143
144 #[test]
145 fn no_duplicates() {
146 let s1 = Storage::new("bucket-a".to_string()).build();
147 let s2 = Storage::new("bucket-b".to_string()).build();
148
149 let stack = Stack::new("test-stack".to_string())
150 .add(s1, ResourceLifecycle::Frozen)
151 .add(s2, ResourceLifecycle::Frozen)
152 .build();
153
154 let ids = initial_setup_permission_set_ids(&stack);
155 let storage_count = ids.iter().filter(|id| *id == "storage/provision").count();
156 assert_eq!(
157 storage_count, 1,
158 "storage/provision should appear exactly once"
159 );
160 }
161
162 #[test]
163 fn combined_stack_includes_all_resource_types() {
164 let function = test_function("my-fn");
165 let storage = Storage::new("my-bucket".to_string()).build();
166
167 let stack = Stack::new("test-stack".to_string())
168 .add(function, ResourceLifecycle::Live)
169 .add(storage, ResourceLifecycle::Frozen)
170 .build();
171
172 let ids = initial_setup_permission_set_ids(&stack);
173 assert!(ids.contains(&"function/provision".to_string()));
174 assert!(ids.contains(&"storage/provision".to_string()));
175 assert!(ids.contains(&"service-account/provision".to_string()));
176 }
177}