diff --git a/aws_quickstart/CHANGELOG.md b/aws_quickstart/CHANGELOG.md index ab5ae8c..77cc9f7 100644 --- a/aws_quickstart/CHANGELOG.md +++ b/aws_quickstart/CHANGELOG.md @@ -1,3 +1,11 @@ +# 4.5.1 (February 11, 2026) + +- Scope IAM policy names to role name to prevent `EntityAlreadyExists` errors on stack re-creation + +# 4.5.0 (February 11, 2026) + +- Add `main_extended_workflow.yaml` CloudFormation template for full AWS Integration onboarding with workflow status reporting including Cloud Security product configuration. + # 4.4.0 (February 9, 2026) - Remove all enumerated IAM permissions from the datadog_integration_role stack diff --git a/aws_quickstart/attach_integration_permissions.py b/aws_quickstart/attach_integration_permissions.py index 16bcc76..977cb4a 100644 --- a/aws_quickstart/attach_integration_permissions.py +++ b/aws_quickstart/attach_integration_permissions.py @@ -35,31 +35,30 @@ def fetch_permissions_from_datadog(api_url): json_response = json.loads(response.read()) return json_response["data"]["attributes"]["permissions"] +def _detach_and_delete_policy(iam_client, role_name, policy_arn, policy_name): + """Detach a managed policy from a role and delete it. Ignores missing entities.""" + try: + iam_client.detach_role_policy(RoleName=role_name, PolicyArn=policy_arn) + except iam_client.exceptions.NoSuchEntityException: + pass + except Exception as e: + LOGGER.error(f"Error detaching policy {policy_name}: {str(e)}") + + try: + iam_client.delete_policy(PolicyArn=policy_arn) + except iam_client.exceptions.NoSuchEntityException: + pass + except iam_client.exceptions.DeleteConflictException: + LOGGER.warning(f"Policy {policy_name} still attached, skipping delete") + except Exception as e: + LOGGER.error(f"Error deleting policy {policy_name}: {str(e)}") + def cleanup_existing_policies(iam_client, role_name, account_id, partition, max_policies=10): - # Remove resource collection permissions + # Remove role-scoped resource collection policies for i in range(max_policies): - policy_name = f"{BASE_POLICY_PREFIX_RESOURCE_COLLECTION}-{i+1}" + policy_name = f"{BASE_POLICY_PREFIX_RESOURCE_COLLECTION}-{role_name}-{i+1}" policy_arn = f"arn:{partition}:iam::{account_id}:policy/{policy_name}" - try: - iam_client.detach_role_policy( - RoleName=role_name, - PolicyArn=policy_arn - ) - except iam_client.exceptions.NoSuchEntityException: - pass - except Exception as e: - LOGGER.error(f"Error detaching policy {policy_name}: {str(e)}") - - try: - iam_client.delete_policy( - PolicyArn=policy_arn - ) - except iam_client.exceptions.NoSuchEntityException: - pass - except iam_client.exceptions.DeleteConflictException: - LOGGER.warning(f"Policy {policy_name} still attached, skipping delete") - except Exception as e: - LOGGER.error(f"Error deleting policy {policy_name}: {str(e)}") + _detach_and_delete_policy(iam_client, role_name, policy_arn, policy_name) # Remove standard permissions try: @@ -96,8 +95,7 @@ def attach_resource_collection_permissions(iam_client, role_name): # Create and attach new policies for i, chunk in enumerate(permission_chunks): - # Create policy - policy_name = f"{BASE_POLICY_PREFIX_RESOURCE_COLLECTION}-{i+1}" + policy_name = f"{BASE_POLICY_PREFIX_RESOURCE_COLLECTION}-{role_name}-{i+1}" policy_document = { "Version": "2012-10-17", "Statement": [ diff --git a/aws_quickstart/datadog_integration_role.yaml b/aws_quickstart/datadog_integration_role.yaml index 17fb792..6bd37ba 100644 --- a/aws_quickstart/datadog_integration_role.yaml +++ b/aws_quickstart/datadog_integration_role.yaml @@ -143,31 +143,30 @@ Resources: json_response = json.loads(response.read()) return json_response["data"]["attributes"]["permissions"] + def _detach_and_delete_policy(iam_client, role_name, policy_arn, policy_name): + """Detach a managed policy from a role and delete it. Ignores missing entities.""" + try: + iam_client.detach_role_policy(RoleName=role_name, PolicyArn=policy_arn) + except iam_client.exceptions.NoSuchEntityException: + pass + except Exception as e: + LOGGER.error(f"Error detaching policy {policy_name}: {str(e)}") + + try: + iam_client.delete_policy(PolicyArn=policy_arn) + except iam_client.exceptions.NoSuchEntityException: + pass + except iam_client.exceptions.DeleteConflictException: + LOGGER.warning(f"Policy {policy_name} still attached, skipping delete") + except Exception as e: + LOGGER.error(f"Error deleting policy {policy_name}: {str(e)}") + def cleanup_existing_policies(iam_client, role_name, account_id, partition, max_policies=10): - # Remove resource collection permissions + # Remove role-scoped resource collection policies for i in range(max_policies): - policy_name = f"{BASE_POLICY_PREFIX_RESOURCE_COLLECTION}-{i+1}" + policy_name = f"{BASE_POLICY_PREFIX_RESOURCE_COLLECTION}-{role_name}-{i+1}" policy_arn = f"arn:{partition}:iam::{account_id}:policy/{policy_name}" - try: - iam_client.detach_role_policy( - RoleName=role_name, - PolicyArn=policy_arn - ) - except iam_client.exceptions.NoSuchEntityException: - pass - except Exception as e: - LOGGER.error(f"Error detaching policy {policy_name}: {str(e)}") - - try: - iam_client.delete_policy( - PolicyArn=policy_arn - ) - except iam_client.exceptions.NoSuchEntityException: - pass - except iam_client.exceptions.DeleteConflictException: - LOGGER.warning(f"Policy {policy_name} still attached, skipping delete") - except Exception as e: - LOGGER.error(f"Error deleting policy {policy_name}: {str(e)}") + _detach_and_delete_policy(iam_client, role_name, policy_arn, policy_name) # Remove standard permissions try: @@ -204,8 +203,7 @@ Resources: # Create and attach new policies for i, chunk in enumerate(permission_chunks): - # Create policy - policy_name = f"{BASE_POLICY_PREFIX_RESOURCE_COLLECTION}-{i+1}" + policy_name = f"{BASE_POLICY_PREFIX_RESOURCE_COLLECTION}-{role_name}-{i+1}" policy_document = { "Version": "2012-10-17", "Statement": [ diff --git a/aws_quickstart/version.txt b/aws_quickstart/version.txt index c2d2cb0..af6f175 100644 --- a/aws_quickstart/version.txt +++ b/aws_quickstart/version.txt @@ -1 +1 @@ -v4.5.0 +v4.5.1