Skip to content

Commit 9d4c785

Browse files
author
Taniya Mathur
committed
Add permission boundary support to GitLab CI pipeline
1 parent 88ec2c3 commit 9d4c785

File tree

1 file changed

+131
-7
lines changed

1 file changed

+131
-7
lines changed

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

Lines changed: 131 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import subprocess
77
import datetime
88
import time
9+
import json
10+
import boto3
11+
from botocore.exceptions import ClientError
912
from loguru import logger
1013

1114
class InstallService():
@@ -307,9 +310,113 @@ def deploy_service_role(self):
307310
logger.error(f"Unexpected error during service role deployment: {e}")
308311
return None
309312

313+
def create_permission_boundary_policy(self):
314+
"""Create an 'allow everything' permission boundary policy if it doesn't exist"""
315+
316+
policy_name = "IDPPermissionBoundary"
317+
iam = boto3.client('iam')
318+
319+
try:
320+
# First, check if the policy already exists
321+
account_id = boto3.client('sts').get_caller_identity()['Account']
322+
policy_arn = f"arn:aws:iam::{account_id}:policy/{policy_name}"
323+
324+
# Try to get the existing policy
325+
iam.get_policy(PolicyArn=policy_arn)
326+
logger.info(f"Permission boundary policy already exists: {policy_arn}")
327+
return policy_arn
328+
329+
except ClientError as e:
330+
if e.response['Error']['Code'] == 'NoSuchEntity':
331+
# Policy doesn't exist, create it
332+
policy_document = {
333+
"Version": "2012-10-17",
334+
"Statement": [
335+
{
336+
"Effect": "Allow",
337+
"Action": "*",
338+
"Resource": "*"
339+
}
340+
]
341+
}
342+
343+
try:
344+
response = iam.create_policy(
345+
PolicyName=policy_name,
346+
PolicyDocument=json.dumps(policy_document),
347+
Description="Permission boundary for IDP deployment - allows all actions"
348+
)
349+
350+
policy_arn = response['Policy']['Arn']
351+
logger.info(f"Created permission boundary policy: {policy_arn}")
352+
return policy_arn
353+
354+
except ClientError as create_error:
355+
logger.error(f"Error creating permission boundary policy: {create_error}")
356+
return None
357+
else:
358+
logger.error(f"Error checking for existing permission boundary policy: {e}")
359+
return None
360+
361+
def validate_permission_boundary(self, stack_name, boundary_arn):
362+
"""Validate that all IAM roles in the stack have the permission boundary"""
363+
cfn = boto3.client('cloudformation')
364+
iam = boto3.client('iam')
365+
366+
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)
370+
371+
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)
377+
378+
if not roles:
379+
logger.info("No IAM roles found in the stack")
380+
return True
381+
382+
logger.info(f"Found {len(roles)} IAM roles in the stack")
383+
failed_roles = []
384+
385+
# Check each role
386+
for role_name in roles:
387+
try:
388+
response = iam.get_role(RoleName=role_name)
389+
role = response['Role']
390+
391+
if 'PermissionsBoundary' in role:
392+
actual_boundary = role['PermissionsBoundary']['PermissionsBoundaryArn']
393+
if actual_boundary == boundary_arn:
394+
logger.debug(f"✅ {role_name}: Has correct permission boundary")
395+
else:
396+
logger.error(f"❌ {role_name}: Has wrong permission boundary: {actual_boundary}")
397+
failed_roles.append(role_name)
398+
else:
399+
logger.error(f"❌ {role_name}: Missing permission boundary")
400+
failed_roles.append(role_name)
401+
402+
except ClientError as e:
403+
logger.error(f"Error checking role {role_name}: {e}")
404+
failed_roles.append(role_name)
405+
406+
if failed_roles:
407+
logger.error(f"FAILED: {len(failed_roles)} roles do not have the correct permission boundary")
408+
return False
409+
else:
410+
logger.info(f"SUCCESS: All {len(roles)} roles have the correct permission boundary")
411+
return True
412+
413+
except ClientError as e:
414+
logger.error(f"Error validating permission boundary: {e}")
415+
return False
416+
310417
def install(self, admin_email: str, idp_pattern: str):
311418
"""
312-
Install the IDP stack using CloudFormation with service role.
419+
Install the IDP stack using CloudFormation with service role and permission boundary.
313420
314421
Args:
315422
admin_email: Email address for the admin user
@@ -319,22 +426,30 @@ def install(self, admin_email: str, idp_pattern: str):
319426
s3_prefix = f"{self.cfn_prefix}/0.2.2" # TODO: Make version configurable
320427

321428
try:
322-
# Step 1: Ensure CloudFormation service role exists
323-
logger.info("Step 1: Ensuring CloudFormation service role exists...")
429+
# Step 1: Create permission boundary policy
430+
logger.info("Step 1: Creating permission boundary policy...")
431+
permission_boundary_arn = self.create_permission_boundary_policy()
432+
if not permission_boundary_arn:
433+
logger.error("Failed to create permission boundary policy. Aborting deployment.")
434+
return False
435+
436+
# Step 2: Ensure CloudFormation service role exists
437+
logger.info("Step 2: Ensuring CloudFormation service role exists...")
324438
service_role_arn = self.deploy_service_role()
325439
if not service_role_arn:
326440
logger.error("Failed to deploy or find service role. Aborting IDP deployment.")
327441
return False
328442

329-
# Step 2: Deploy IDP stack using the service role
330-
logger.info("Step 2: Deploying IDP stack using service role...")
443+
# Step 3: Deploy IDP stack using the service role and permission boundary
444+
logger.info("Step 3: Deploying IDP stack using service role and permission boundary...")
331445

332446
# Verify template file exists
333447
template_path = os.path.join(self.abs_cwd, template_file)
334448
if not os.path.exists(template_path):
335449
raise FileNotFoundError(f"Template file not found: {template_path}")
336450

337-
# Construct the CloudFormation deploy command with service role
451+
logger.info(f"Using permission boundary ARN: {permission_boundary_arn}")
452+
338453
cmd = [
339454
'aws', 'cloudformation', 'deploy',
340455
'--region', self.region,
@@ -347,6 +462,7 @@ def install(self, admin_email: str, idp_pattern: str):
347462
"DocumentKnowledgeBase=DISABLED",
348463
f"IDPPattern={idp_pattern}",
349464
f"AdminEmail={admin_email}",
465+
f"PermissionsBoundaryArn={permission_boundary_arn}",
350466
'--stack-name', self.stack_name
351467
]
352468

@@ -371,7 +487,15 @@ def install(self, admin_email: str, idp_pattern: str):
371487
if process.stderr:
372488
logger.debug(f"CloudFormation deploy stderr: {process.stderr}")
373489

374-
logger.info(f"Successfully deployed stack {self.stack_name} in {self.region} using service role")
490+
logger.info(f"Successfully deployed stack {self.stack_name} in {self.region}")
491+
492+
# Step 4: Validate permission boundary on all roles
493+
logger.info("Step 4: Validating permission boundary on all IAM roles...")
494+
if not self.validate_permission_boundary(self.stack_name, permission_boundary_arn):
495+
logger.error("Permission boundary validation failed!")
496+
return False
497+
498+
logger.info("Deployment and validation completed successfully!")
375499
return True
376500

377501
except FileNotFoundError as e:

0 commit comments

Comments
 (0)