alien_permissions/generators/
gcp_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 = "camelCase")]
12pub struct GcpCustomRole {
13 pub title: String,
15 pub description: String,
17 pub stage: String,
19 pub included_permissions: Vec<String>,
21 pub name: String,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
27#[serde(rename_all = "camelCase")]
28pub struct GcpIamCondition {
29 pub title: String,
31 pub description: String,
33 pub expression: String,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
39#[serde(rename_all = "camelCase")]
40pub struct GcpIamBinding {
41 pub role: String,
43 pub members: Vec<String>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub condition: Option<GcpIamCondition>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
52#[serde(rename_all = "camelCase")]
53pub struct GcpIamBindings {
54 pub bindings: Vec<GcpIamBinding>,
56}
57
58pub struct GcpRuntimePermissionsGenerator;
60
61impl GcpRuntimePermissionsGenerator {
62 pub fn new() -> Self {
64 Self
65 }
66
67 pub fn generate_custom_role(
72 &self,
73 permission_set: &PermissionSet,
74 context: &PermissionContext,
75 ) -> Result<GcpCustomRole> {
76 let gcp_platform_permissions = permission_set.platforms.gcp.as_ref().ok_or_else(|| {
77 alien_error::AlienError::new(ErrorData::PlatformNotSupported {
78 platform: "gcp".to_string(),
79 permission_set_id: permission_set.id.clone(),
80 })
81 })?;
82
83 let mut all_permissions = Vec::new();
85
86 for platform_permission in gcp_platform_permissions {
87 if let Some(permissions) = &platform_permission.grant.permissions {
88 all_permissions.extend(permissions.clone());
89 }
90 }
91
92 if all_permissions.is_empty() {
93 return Err(alien_error::AlienError::new(ErrorData::GeneratorError {
94 platform: "gcp".to_string(),
95 message: "GCP permission grant must have 'permissions' field".to_string(),
96 }));
97 }
98
99 let role_name = self.generate_role_name(&permission_set.id);
100 let role_id = self.generate_role_id(&permission_set.id);
101
102 let project = context.project_name.as_deref().unwrap_or("PROJECT_NAME");
104 let full_role_name = format!("projects/{}/roles/{}", project, role_id);
105
106 Ok(GcpCustomRole {
107 title: role_name,
108 description: permission_set.description.clone(),
109 stage: "GA".to_string(),
110 included_permissions: all_permissions,
111 name: full_role_name,
112 })
113 }
114
115 pub fn generate_bindings(
120 &self,
121 permission_set: &PermissionSet,
122 binding_target: BindingTarget,
123 context: &PermissionContext,
124 ) -> Result<GcpIamBindings> {
125 let gcp_platform_permissions = permission_set.platforms.gcp.as_ref().ok_or_else(|| {
126 alien_error::AlienError::new(ErrorData::PlatformNotSupported {
127 platform: "gcp".to_string(),
128 permission_set_id: permission_set.id.clone(),
129 })
130 })?;
131
132 let role_id = self.generate_role_id(&permission_set.id);
133 let project = context.project_name.as_deref().unwrap_or("PROJECT_NAME");
134 let full_role_name = format!("projects/{}/roles/{}", project, role_id);
135
136 let service_account = format!(
138 "serviceAccount:{}@{}.iam.gserviceaccount.com",
139 context
140 .service_account_name
141 .as_deref()
142 .unwrap_or("SERVICE_ACCOUNT"),
143 project
144 );
145
146 let mut bindings = Vec::new();
147
148 for platform_permission in gcp_platform_permissions {
150 let binding_spec = match binding_target {
151 BindingTarget::Stack => {
152 platform_permission.binding.stack.as_ref().ok_or_else(|| {
153 alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
154 platform: "gcp".to_string(),
155 binding_target: "stack".to_string(),
156 permission_set_id: permission_set.id.clone(),
157 })
158 })?
159 }
160 BindingTarget::Resource => platform_permission
161 .binding
162 .resource
163 .as_ref()
164 .ok_or_else(|| {
165 alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
166 platform: "gcp".to_string(),
167 binding_target: "resource".to_string(),
168 permission_set_id: permission_set.id.clone(),
169 })
170 })?,
171 };
172
173 let mut binding = GcpIamBinding {
174 role: full_role_name.clone(),
175 members: vec![service_account.clone()],
176 condition: None,
177 };
178
179 if let Some(gcp_condition) = &binding_spec.condition {
181 let interpolated_condition = self.interpolate_condition(gcp_condition, context)?;
182 binding.condition = Some(GcpIamCondition {
183 title: interpolated_condition.title.clone(),
184 description: format!("Limit to {}", interpolated_condition.title),
185 expression: interpolated_condition.expression,
186 });
187 }
188
189 bindings.push(binding);
190 }
191
192 Ok(GcpIamBindings { bindings })
193 }
194
195 fn generate_role_name(&self, permission_set_id: &str) -> String {
197 permission_set_id
198 .split('/')
199 .map(|part| {
200 part.split('-')
201 .map(|word| {
202 let mut chars = word.chars();
203 match chars.next() {
204 None => String::new(),
205 Some(first) => {
206 first.to_uppercase().collect::<String>() + chars.as_str()
207 }
208 }
209 })
210 .collect::<Vec<String>>()
211 .join(" ")
212 })
213 .collect::<Vec<String>>()
214 .join(" ")
215 }
216
217 fn generate_role_id(&self, permission_set_id: &str) -> String {
219 let all_parts: Vec<&str> = permission_set_id
221 .split('/')
222 .flat_map(|part| part.split('-'))
223 .collect();
224
225 all_parts
226 .iter()
227 .enumerate()
228 .map(|(i, word)| {
229 if i == 0 {
230 word.to_lowercase()
231 } else {
232 let mut chars = word.chars();
233 match chars.next() {
234 None => String::new(),
235 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
236 }
237 }
238 })
239 .collect::<String>()
240 }
241
242 fn interpolate_condition(
244 &self,
245 condition: &alien_core::GcpCondition,
246 context: &PermissionContext,
247 ) -> Result<alien_core::GcpCondition> {
248 let interpolated_title =
249 VariableInterpolator::interpolate_variables(&condition.title, context)?;
250 let interpolated_expression =
251 VariableInterpolator::interpolate_variables(&condition.expression, context)?;
252
253 Ok(alien_core::GcpCondition {
254 title: interpolated_title,
255 expression: interpolated_expression,
256 })
257 }
258}
259
260impl Default for GcpRuntimePermissionsGenerator {
261 fn default() -> Self {
262 Self::new()
263 }
264}