alien_permissions/generators/
aws_runtime.rs1use crate::{
2 error::{ErrorData, Result},
3 generators::labels::{entry_pascal_label, has_explicit_label},
4 variables::VariableInterpolator,
5 BindingTarget, PermissionContext,
6};
7use alien_core::{PermissionGrant, PermissionSet};
8use indexmap::IndexMap;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
13#[serde(rename_all = "PascalCase")]
14pub struct AwsIamStatement {
15 pub sid: String,
17 pub effect: String,
19 pub action: Vec<String>,
21 pub resource: Vec<String>,
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub condition: Option<IndexMap<String, IndexMap<String, String>>>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
30#[serde(rename_all = "PascalCase")]
31pub struct AwsIamPolicy {
32 pub version: String,
34 pub statement: Vec<AwsIamStatement>,
36}
37
38pub struct AwsRuntimePermissionsGenerator;
40
41impl AwsRuntimePermissionsGenerator {
42 pub fn new() -> Self {
44 Self
45 }
46
47 pub fn generate_policy(
52 &self,
53 permission_set: &PermissionSet,
54 binding_target: BindingTarget,
55 context: &PermissionContext,
56 ) -> Result<AwsIamPolicy> {
57 let aws_platform_permissions = permission_set.platforms.aws.as_ref().ok_or_else(|| {
58 alien_error::AlienError::new(ErrorData::PlatformNotSupported {
59 platform: "aws".to_string(),
60 permission_set_id: permission_set.id.clone(),
61 })
62 })?;
63
64 let mut statements = Vec::new();
65
66 for platform_permission in aws_platform_permissions {
68 let actions = platform_permission.grant.actions.as_ref().ok_or_else(|| {
69 alien_error::AlienError::new(ErrorData::GeneratorError {
70 platform: "aws".to_string(),
71 message: "AWS permission grant must have 'actions' field".to_string(),
72 })
73 })?;
74
75 let binding_spec = match binding_target {
76 BindingTarget::Stack => {
77 platform_permission.binding.stack.as_ref().ok_or_else(|| {
78 alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
79 platform: "aws".to_string(),
80 binding_target: "stack".to_string(),
81 permission_set_id: permission_set.id.clone(),
82 })
83 })?
84 }
85 BindingTarget::Resource => platform_permission
86 .binding
87 .resource
88 .as_ref()
89 .ok_or_else(|| {
90 alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
91 platform: "aws".to_string(),
92 binding_target: "resource".to_string(),
93 permission_set_id: permission_set.id.clone(),
94 })
95 })?,
96 };
97
98 let resources =
99 VariableInterpolator::interpolate_string_list(&binding_spec.resources, context)?;
100 let conditions = self.extract_conditions(binding_spec, context)?;
101
102 let statement_id = self.statement_id(
103 permission_set,
104 &platform_permission.grant,
105 platform_permission.label.as_deref(),
106 aws_platform_permissions.len() > 1,
107 );
108
109 let statement = AwsIamStatement {
110 sid: statement_id,
111 effect: platform_permission.effect.as_str().to_string(),
112 action: actions.clone(),
113 resource: resources,
114 condition: if conditions.is_empty() {
115 None
116 } else {
117 Some(conditions)
118 },
119 };
120
121 statements.push(statement);
122 }
123
124 Ok(AwsIamPolicy {
125 version: "2012-10-17".to_string(),
126 statement: statements,
127 })
128 }
129
130 fn extract_conditions(
132 &self,
133 binding_spec: &alien_core::AwsBindingSpec,
134 context: &PermissionContext,
135 ) -> Result<IndexMap<String, IndexMap<String, String>>> {
136 if let Some(condition_template) = &binding_spec.condition {
137 let mut interpolated_conditions = IndexMap::new();
138
139 for (condition_key, condition_values) in condition_template {
140 let condition_key =
141 VariableInterpolator::interpolate_variables(condition_key, context)?;
142 let mut interpolated_values = IndexMap::new();
143
144 for (value_key, value_template) in condition_values {
145 let value_key =
146 VariableInterpolator::interpolate_variables(value_key, context)?;
147 let interpolated_value =
148 VariableInterpolator::interpolate_variables(value_template, context)?;
149 interpolated_values.insert(value_key, interpolated_value);
150 }
151
152 interpolated_conditions.insert(condition_key, interpolated_values);
153 }
154
155 Ok(interpolated_conditions)
156 } else {
157 Ok(IndexMap::new())
158 }
159 }
160
161 fn generate_statement_id(&self, permission_set_id: &str) -> String {
163 permission_set_id
165 .split('/')
166 .map(|part| {
167 part.split('-')
168 .map(|word| {
169 let mut chars = word.chars();
170 match chars.next() {
171 None => String::new(),
172 Some(first) => {
173 first.to_uppercase().collect::<String>()
174 + &chars.as_str().to_lowercase()
175 }
176 }
177 })
178 .collect::<String>()
179 })
180 .collect::<String>()
181 }
182
183 fn statement_id(
184 &self,
185 permission_set: &PermissionSet,
186 grant: &PermissionGrant,
187 explicit_label: Option<&str>,
188 include_entry_label: bool,
189 ) -> String {
190 if has_explicit_label(explicit_label) {
191 return entry_pascal_label(explicit_label, grant);
192 }
193
194 let base = self.generate_statement_id(&permission_set.id);
195 if include_entry_label {
196 format!("{base}{}", entry_pascal_label(explicit_label, grant))
197 } else {
198 base
199 }
200 }
201}
202
203impl Default for AwsRuntimePermissionsGenerator {
204 fn default() -> Self {
205 Self::new()
206 }
207}