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