@@ -136,106 +136,6 @@ def publish(self):
136136 return False
137137
138138
139- def cleanup_failed_stack (self , stack_name ):
140- """
141- Clean up failed stack if it exists in ROLLBACK_COMPLETE state.
142-
143- Args:
144- stack_name: Name of the stack to clean up
145-
146- Returns:
147- bool: True if cleanup successful or not needed, False if cleanup failed
148- """
149- try :
150- # Check stack status
151- cmd = [
152- 'aws' , 'cloudformation' , 'describe-stacks' ,
153- '--region' , self .region ,
154- '--stack-name' , stack_name ,
155- '--query' , 'Stacks[0].StackStatus' ,
156- '--output' , 'text'
157- ]
158-
159- process = subprocess .run (
160- cmd ,
161- check = True ,
162- text = True ,
163- stdout = subprocess .PIPE ,
164- stderr = subprocess .PIPE
165- )
166-
167- stack_status = process .stdout .strip ()
168-
169- if stack_status in ['ROLLBACK_COMPLETE' , 'CREATE_FAILED' , 'DELETE_FAILED' ]:
170- logger .info (f"Cleaning up failed stack { stack_name } (status: { stack_status } )" )
171-
172- delete_cmd = [
173- 'aws' , 'cloudformation' , 'delete-stack' ,
174- '--region' , self .region ,
175- '--stack-name' , stack_name
176- ]
177-
178- subprocess .run (delete_cmd , check = True , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
179-
180- # Wait for deletion to complete
181- wait_cmd = [
182- 'aws' , 'cloudformation' , 'wait' , 'stack-delete-complete' ,
183- '--region' , self .region ,
184- '--stack-name' , stack_name
185- ]
186-
187- subprocess .run (wait_cmd , check = True , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
188- logger .info (f"Successfully cleaned up failed stack { stack_name } " )
189-
190- return True
191-
192- except subprocess .CalledProcessError :
193- # Stack doesn't exist - no cleanup needed
194- return True
195- except Exception as e :
196- logger .error (f"Failed to cleanup stack { stack_name } : { e } " )
197- return False
198-
199- def get_service_role_arn (self ):
200- """
201- Check if CloudFormation service role stack exists and return its ARN.
202-
203- Returns:
204- str: The ARN of the existing service role, or None if not found
205- """
206- service_role_stack_name = f"{ self .cfn_prefix } -cloudformation-service-role"
207-
208- try :
209- describe_cmd = [
210- 'aws' , 'cloudformation' , 'describe-stacks' ,
211- '--region' , self .region ,
212- '--stack-name' , service_role_stack_name ,
213- '--query' , 'Stacks[0].Outputs[?OutputKey==`ServiceRoleArn`].OutputValue' ,
214- '--output' , 'text'
215- ]
216-
217- process = subprocess .run (
218- describe_cmd ,
219- check = True ,
220- text = True ,
221- stdout = subprocess .PIPE ,
222- stderr = subprocess .PIPE
223- )
224-
225- service_role_arn = process .stdout .strip ()
226- if service_role_arn and service_role_arn != "None" :
227- logger .info (f"Found existing service role: { service_role_arn } " )
228- return service_role_arn
229- else :
230- return None
231-
232- except subprocess .CalledProcessError :
233- logger .debug (f"Service role stack { service_role_stack_name } not found" )
234- return None
235- except Exception as e :
236- logger .error (f"Error checking for existing service role: { e } " )
237- return None
238-
239139 def deploy_service_role (self ):
240140 """
241141 Deploy the CloudFormation service role stack.
@@ -278,8 +178,8 @@ def deploy_service_role(self):
278178 if process .stderr :
279179 logger .debug (f"Service role deploy stderr: { process .stderr } " )
280180
281- # Get the service role ARN from stack outputs
282- service_role_arn = self .get_service_role_arn ( )
181+ # Get the service role ARN from the deployed stack
182+ service_role_arn = self ._get_service_role_arn_from_stack ( service_role_stack_name )
283183 if service_role_arn :
284184 logger .info (f"Successfully deployed service role: { service_role_arn } " )
285185 return service_role_arn
@@ -296,62 +196,78 @@ def deploy_service_role(self):
296196 logger .debug (f"Command stdout: { e .stdout } " )
297197 if e .stderr :
298198 logger .debug (f"Command stderr: { e .stderr } " )
299-
300- # Cleanup failed service role deployment
301- logger .info ("Cleaning up failed service role deployment..." )
302- self .cleanup_failed_stack (service_role_stack_name )
303199 return None
304200 except Exception as e :
305201 logger .error (f"Unexpected error during service role deployment: { e } " )
306202 return None
307203
204+ def _get_service_role_arn_from_stack (self , stack_name ):
205+ """
206+ Get service role ARN from a specific stack.
207+
208+ Returns:
209+ str: The ARN of the service role, or None if not found
210+ """
211+ try :
212+ describe_cmd = [
213+ 'aws' , 'cloudformation' , 'describe-stacks' ,
214+ '--region' , self .region ,
215+ '--stack-name' , stack_name ,
216+ '--query' , 'Stacks[0].Outputs[?OutputKey==`ServiceRoleArn`].OutputValue' ,
217+ '--output' , 'text'
218+ ]
219+
220+ process = subprocess .run (
221+ describe_cmd ,
222+ check = True ,
223+ text = True ,
224+ stdout = subprocess .PIPE ,
225+ stderr = subprocess .PIPE
226+ )
227+
228+ service_role_arn = process .stdout .strip ()
229+ if service_role_arn and service_role_arn != "None" :
230+ return service_role_arn
231+ else :
232+ return None
233+
234+ except subprocess .CalledProcessError :
235+ return None
236+ except Exception as e :
237+ logger .error (f"Error getting service role ARN from stack { stack_name } : { e } " )
238+ return None
239+
308240 def create_permission_boundary_policy (self ):
309- """Create an 'allow everything' permission boundary policy if it doesn't exist """
241+ """Create an 'allow everything' permission boundary policy"""
310242
311- policy_name = " IDPPermissionBoundary"
243+ policy_name = f" { self . cfn_prefix } - IDPPermissionBoundary"
312244 iam = boto3 .client ('iam' )
313245
314246 try :
315- # First, check if the policy already exists
316- account_id = boto3 .client ('sts' ).get_caller_identity ()['Account' ]
317- policy_arn = f"arn:aws:iam::{ account_id } :policy/{ policy_name } "
247+ policy_document = {
248+ "Version" : "2012-10-17" ,
249+ "Statement" : [
250+ {
251+ "Effect" : "Allow" ,
252+ "Action" : "*" ,
253+ "Resource" : "*"
254+ }
255+ ]
256+ }
318257
319- # Try to get the existing policy
320- iam .get_policy (PolicyArn = policy_arn )
321- logger .info (f"Permission boundary policy already exists: { policy_arn } " )
258+ response = iam .create_policy (
259+ PolicyName = policy_name ,
260+ PolicyDocument = json .dumps (policy_document ),
261+ Description = "Permission boundary for IDP deployment - allows all actions"
262+ )
263+
264+ policy_arn = response ['Policy' ]['Arn' ]
265+ logger .info (f"Created permission boundary policy: { policy_arn } " )
322266 return policy_arn
323267
324- except ClientError as e :
325- if e .response ['Error' ]['Code' ] == 'NoSuchEntity' :
326- # Policy doesn't exist, create it
327- policy_document = {
328- "Version" : "2012-10-17" ,
329- "Statement" : [
330- {
331- "Effect" : "Allow" ,
332- "Action" : "*" ,
333- "Resource" : "*"
334- }
335- ]
336- }
337-
338- try :
339- response = iam .create_policy (
340- PolicyName = policy_name ,
341- PolicyDocument = json .dumps (policy_document ),
342- Description = "Permission boundary for IDP deployment - allows all actions"
343- )
344-
345- policy_arn = response ['Policy' ]['Arn' ]
346- logger .info (f"Created permission boundary policy: { policy_arn } " )
347- return policy_arn
348-
349- except ClientError as create_error :
350- logger .error (f"Error creating permission boundary policy: { create_error } " )
351- return None
352- else :
353- logger .error (f"Error checking for existing permission boundary policy: { e } " )
354- return None
268+ except ClientError as create_error :
269+ logger .error (f"Error creating permission boundary policy: { create_error } " )
270+ return None
355271
356272 def validate_permission_boundary (self , stack_name , boundary_arn ):
357273 """Validate that all IAM roles in the stack and nested stacks have the permission boundary"""
@@ -511,6 +427,7 @@ def install(self, admin_email: str, idp_pattern: str):
511427 logger .info ("Step 4: Validating permission boundary on all IAM roles..." )
512428 if not self .validate_permission_boundary (self .stack_name , permission_boundary_arn ):
513429 logger .error ("Permission boundary validation failed!" )
430+ logger .error ("Deployment failed due to security policy violations." )
514431 return False
515432
516433 logger .info ("Deployment and validation completed successfully!" )
@@ -525,10 +442,6 @@ def install(self, admin_email: str, idp_pattern: str):
525442 logger .debug (f"Command stdout: { e .stdout } " )
526443 if e .stderr :
527444 logger .debug (f"Command stderr: { e .stderr } " )
528-
529- # Cleanup failed deployment for next attempt
530- logger .info ("Cleaning up failed deployment for next attempt..." )
531- self .cleanup_failed_stack (self .stack_name )
532445 return False
533446 except Exception as e :
534447 logger .error (f"Unexpected error during stack deployment: { e } " )
0 commit comments