Skip to content

Commit 7980bb7

Browse files
author
Taniya Mathur
committed
Update install_service.py: always deploy service role and validate nested stacks
- Remove existence check for service role, always deploy - Rename get_existing_service_role_arn to get_service_role_arn - Enhance validate_permission_boundary to include nested CloudFormation stacks
1 parent 9d4c785 commit 7980bb7

File tree

1 file changed

+44
-26
lines changed

1 file changed

+44
-26
lines changed

scripts/sdlc/idp-cli/src/idp_cli/service/install_service.py

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: MIT-0
33

4-
from typing import Optional
4+
import json
55
import os
66
import subprocess
7-
import datetime
87
import time
9-
import json
8+
from typing import Optional
9+
1010
import boto3
1111
from botocore.exceptions import ClientError
1212
from loguru import logger
1313

14+
1415
class InstallService():
1516
def __init__(self,
1617
account_id: str,
@@ -195,7 +196,7 @@ def cleanup_failed_stack(self, stack_name):
195196
logger.error(f"Failed to cleanup stack {stack_name}: {e}")
196197
return False
197198

198-
def get_existing_service_role_arn(self):
199+
def get_service_role_arn(self):
199200
"""
200201
Check if CloudFormation service role stack exists and return its ARN.
201202
@@ -237,17 +238,11 @@ def get_existing_service_role_arn(self):
237238

238239
def deploy_service_role(self):
239240
"""
240-
Deploy the CloudFormation service role stack only if it doesn't exist.
241+
Deploy the CloudFormation service role stack.
241242
242243
Returns:
243244
str: The ARN of the service role, or None if deployment failed
244245
"""
245-
# First check if service role already exists
246-
existing_arn = self.get_existing_service_role_arn()
247-
if existing_arn:
248-
logger.info("CloudFormation service role already exists, skipping deployment")
249-
return existing_arn
250-
251246
service_role_stack_name = f"{self.cfn_prefix}-cloudformation-service-role"
252247
service_role_template = 'iam-roles/cloudformation-management/IDP-Cloudformation-Service-Role.yaml'
253248

@@ -284,7 +279,7 @@ def deploy_service_role(self):
284279
logger.debug(f"Service role deploy stderr: {process.stderr}")
285280

286281
# Get the service role ARN from stack outputs
287-
service_role_arn = self.get_existing_service_role_arn()
282+
service_role_arn = self.get_service_role_arn()
288283
if service_role_arn:
289284
logger.info(f"Successfully deployed service role: {service_role_arn}")
290285
return service_role_arn
@@ -359,27 +354,50 @@ def create_permission_boundary_policy(self):
359354
return None
360355

361356
def validate_permission_boundary(self, stack_name, boundary_arn):
362-
"""Validate that all IAM roles in the stack have the permission boundary"""
357+
"""Validate that all IAM roles in the stack and nested stacks have the permission boundary"""
363358
cfn = boto3.client('cloudformation')
364359
iam = boto3.client('iam')
365360

361+
def get_all_stacks(stack_name):
362+
"""Recursively get all nested stacks"""
363+
stacks = [stack_name]
364+
try:
365+
paginator = cfn.get_paginator('list_stack_resources')
366+
page_iterator = paginator.paginate(StackName=stack_name)
367+
368+
for page in page_iterator:
369+
for resource in page['StackResourceSummaries']:
370+
if resource['ResourceType'] == 'AWS::CloudFormation::Stack':
371+
nested_stack_name = resource['PhysicalResourceId']
372+
stacks.extend(get_all_stacks(nested_stack_name))
373+
except ClientError:
374+
pass
375+
return stacks
376+
366377
try:
367-
# Get all IAM roles in the stack
368-
paginator = cfn.get_paginator('list_stack_resources')
369-
page_iterator = paginator.paginate(StackName=stack_name)
378+
# Get all stacks (main + nested)
379+
all_stacks = get_all_stacks(stack_name)
380+
logger.info(f"Checking {len(all_stacks)} stacks for IAM roles")
370381

371382
roles = []
372-
for page in page_iterator:
373-
for resource in page['StackResourceSummaries']:
374-
if resource['ResourceType'] == 'AWS::IAM::Role':
375-
role_name = resource['PhysicalResourceId']
376-
roles.append(role_name)
383+
for stack in all_stacks:
384+
try:
385+
paginator = cfn.get_paginator('list_stack_resources')
386+
page_iterator = paginator.paginate(StackName=stack)
387+
388+
for page in page_iterator:
389+
for resource in page['StackResourceSummaries']:
390+
if resource['ResourceType'] == 'AWS::IAM::Role':
391+
role_name = resource['PhysicalResourceId']
392+
roles.append(role_name)
393+
except ClientError:
394+
continue
377395

378396
if not roles:
379-
logger.info("No IAM roles found in the stack")
397+
logger.info("No IAM roles found in any stack")
380398
return True
381399

382-
logger.info(f"Found {len(roles)} IAM roles in the stack")
400+
logger.info(f"Found {len(roles)} IAM roles across all stacks")
383401
failed_roles = []
384402

385403
# Check each role
@@ -433,11 +451,11 @@ def install(self, admin_email: str, idp_pattern: str):
433451
logger.error("Failed to create permission boundary policy. Aborting deployment.")
434452
return False
435453

436-
# Step 2: Ensure CloudFormation service role exists
437-
logger.info("Step 2: Ensuring CloudFormation service role exists...")
454+
# Step 2: Deploy CloudFormation service role
455+
logger.info("Step 2: Deploying CloudFormation service role...")
438456
service_role_arn = self.deploy_service_role()
439457
if not service_role_arn:
440-
logger.error("Failed to deploy or find service role. Aborting IDP deployment.")
458+
logger.error("Failed to deploy service role. Aborting IDP deployment.")
441459
return False
442460

443461
# Step 3: Deploy IDP stack using the service role and permission boundary

0 commit comments

Comments
 (0)