alien_permissions/generators/
azure_runtime.rs1use crate::{
2 error::{ErrorData, Result},
3 variables::VariableInterpolator,
4 BindingTarget, PermissionContext,
5};
6use alien_core::PermissionSet;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11#[serde(rename_all = "PascalCase")]
12pub struct AzureRoleDefinition {
13 pub name: String,
15 #[serde(skip_serializing_if = "Option::is_none")]
17 pub id: Option<String>,
18 pub is_custom: bool,
20 pub description: String,
22 pub actions: Vec<String>,
24 pub not_actions: Vec<String>,
26 pub data_actions: Vec<String>,
28 pub not_data_actions: Vec<String>,
30 pub assignable_scopes: Vec<String>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
36#[serde(rename_all = "camelCase")]
37pub struct AzureRoleAssignmentProperties {
38 pub role_definition_id: String,
40 pub principal_id: String,
42 pub scope: String,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
48#[serde(rename_all = "camelCase")]
49pub struct AzureRoleAssignment {
50 pub properties: AzureRoleAssignmentProperties,
52}
53
54pub struct AzureRuntimePermissionsGenerator;
56
57impl AzureRuntimePermissionsGenerator {
58 pub fn new() -> Self {
60 Self
61 }
62
63 pub fn generate_role_definition(
68 &self,
69 permission_set: &PermissionSet,
70 binding_target: BindingTarget,
71 context: &PermissionContext,
72 ) -> Result<AzureRoleDefinition> {
73 let azure_platform_permissions =
74 permission_set.platforms.azure.as_ref().ok_or_else(|| {
75 alien_error::AlienError::new(ErrorData::PlatformNotSupported {
76 platform: "azure".to_string(),
77 permission_set_id: permission_set.id.clone(),
78 })
79 })?;
80
81 let base_role_name = self.generate_role_name(&permission_set.id);
82 let role_name = if let Some(ref prefix) = context.stack_prefix {
86 format!("{} ({})", base_role_name, prefix)
87 } else {
88 base_role_name
89 };
90
91 let mut all_actions = Vec::new();
93 let mut all_data_actions = Vec::new();
94 let mut assignable_scopes = Vec::new();
95
96 for platform_permission in azure_platform_permissions {
97 if let Some(actions) = &platform_permission.grant.actions {
99 all_actions.extend(actions.clone());
100 }
101 if let Some(data_actions) = &platform_permission.grant.data_actions {
102 all_data_actions.extend(data_actions.clone());
103 }
104
105 let binding_spec = match binding_target {
107 BindingTarget::Stack => {
108 platform_permission.binding.stack.as_ref().ok_or_else(|| {
109 alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
110 platform: "azure".to_string(),
111 binding_target: "stack".to_string(),
112 permission_set_id: permission_set.id.clone(),
113 })
114 })?
115 }
116 BindingTarget::Resource => platform_permission
117 .binding
118 .resource
119 .as_ref()
120 .ok_or_else(|| {
121 alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
122 platform: "azure".to_string(),
123 binding_target: "resource".to_string(),
124 permission_set_id: permission_set.id.clone(),
125 })
126 })?,
127 };
128
129 let interpolated_scope =
131 VariableInterpolator::interpolate_variables(&binding_spec.scope, context)?;
132 assignable_scopes.push(interpolated_scope);
133 }
134
135 all_actions.sort();
137 all_actions.dedup();
138 all_data_actions.sort();
139 all_data_actions.dedup();
140 assignable_scopes.sort();
141 assignable_scopes.dedup();
142
143 Ok(AzureRoleDefinition {
144 name: role_name,
145 id: None, is_custom: true,
147 description: permission_set.description.clone(),
148 actions: all_actions,
149 not_actions: vec![],
150 data_actions: all_data_actions,
151 not_data_actions: vec![],
152 assignable_scopes,
153 })
154 }
155
156 pub fn generate_role_assignment(
161 &self,
162 permission_set: &PermissionSet,
163 binding_target: BindingTarget,
164 context: &PermissionContext,
165 ) -> Result<AzureRoleAssignment> {
166 let azure_platform_permissions =
167 permission_set.platforms.azure.as_ref().ok_or_else(|| {
168 alien_error::AlienError::new(ErrorData::PlatformNotSupported {
169 platform: "azure".to_string(),
170 permission_set_id: permission_set.id.clone(),
171 })
172 })?;
173
174 let role_definition_id = format!(
176 "/subscriptions/{}/providers/Microsoft.Authorization/roleDefinitions/${{roleDefinitionGuid}}",
177 context.subscription_id.as_deref().unwrap_or("SUBSCRIPTION_ID")
178 );
179
180 let principal_id = context
181 .principal_id
182 .as_deref()
183 .unwrap_or("PRINCIPAL_ID")
184 .to_string();
185
186 let first_platform_permission = &azure_platform_permissions[0];
189 let binding_spec = match binding_target {
190 BindingTarget::Stack => first_platform_permission
191 .binding
192 .stack
193 .as_ref()
194 .ok_or_else(|| {
195 alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
196 platform: "azure".to_string(),
197 binding_target: "stack".to_string(),
198 permission_set_id: permission_set.id.clone(),
199 })
200 })?,
201 BindingTarget::Resource => first_platform_permission
202 .binding
203 .resource
204 .as_ref()
205 .ok_or_else(|| {
206 alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
207 platform: "azure".to_string(),
208 binding_target: "resource".to_string(),
209 permission_set_id: permission_set.id.clone(),
210 })
211 })?,
212 };
213
214 let interpolated_scope =
216 VariableInterpolator::interpolate_variables(&binding_spec.scope, context)?;
217
218 Ok(AzureRoleAssignment {
219 properties: AzureRoleAssignmentProperties {
220 role_definition_id,
221 principal_id,
222 scope: interpolated_scope,
223 },
224 })
225 }
226
227 fn generate_role_name(&self, permission_set_id: &str) -> String {
229 permission_set_id
230 .split('/')
231 .map(|part| {
232 part.split('-')
233 .map(|word| {
234 let mut chars = word.chars();
235 match chars.next() {
236 None => String::new(),
237 Some(first) => {
238 first.to_uppercase().collect::<String>() + chars.as_str()
239 }
240 }
241 })
242 .collect::<Vec<String>>()
243 .join(" ")
244 })
245 .collect::<Vec<String>>()
246 .join(" ")
247 }
248}
249
250impl Default for AzureRuntimePermissionsGenerator {
251 fn default() -> Self {
252 Self::new()
253 }
254}