AWSTemplateFormatVersion: "2010-09-09" Description: "Cross-account IAM role for Cloud-Sweeper. Grants read‑only discovery and tagging." Parameters: VendorAccountId: Type: String Description: "12‑digit AWS account ID of the SaaS vendor." AllowedPattern: "^[0-9]{12}$" ExternalId: Type: String Description: "External ID your vendor will use in sts:AssumeRole. Leave blank to auto‑generate." Default: "" NoEcho: true Conditions: GenExternalId: !Equals [ !Ref ExternalId, "" ] Resources: ExternalIdGenerator: Type: AWS::SecretsManager::Secret Condition: GenExternalId Properties: Description: "Auto-generated External ID for Cloud-Sweeper." GenerateSecretString: SecretStringTemplate: "{}" GenerateStringKey: "external_id" PasswordLength: 18 ExcludePunctuation: true CloudSweeperRole: Type: AWS::IAM::Role Properties: RoleName: CloudSweeperAccess Path: / Description: "Role assumed by Cloud-Sweeper to discover and tag idle resources." AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: AWS: !Sub "arn:aws:iam::${VendorAccountId}:root" Action: sts:AssumeRole Condition: StringEquals: sts:ExternalId: !If - GenExternalId - !Sub "{{resolve:secretsmanager:${ExternalIdGenerator}:SecretString:external_id}}" - !Ref ExternalId Policies: - PolicyName: CloudSweeperPolicy PolicyDocument: Version: "2012-10-17" Statement: # 1. Read‑only discovery - Sid: SweeperReadDescribe Effect: Allow Action: - ec2:Describe* - elasticloadbalancing:Describe* - lambda:ListFunctions - lambda:GetFunctionConfiguration - dynamodb:DescribeTable - dynamodb:ListTables - s3:ListAllMyBuckets - s3:GetBucketLocation - elasticache:DescribeCacheClusters - elasticache:DescribeCacheParameterGroups - elasticache:DescribeCacheSecurityGroups - elasticache:DescribeCacheSubnetGroups - elasticache:DescribeReplicationGroups - elasticache:ListTagsForResource # Adding EKS permissions - eks:ListClusters - eks:DescribeCluster # Adding ECR permissions - ecr:DescribeRepositories - ecr:ListImages - ecr:DescribeImages # Adding ECS permissions - ecs:ListClusters - ecs:DescribeClusters - ecs:ListServices - ecs:DescribeServices - ecs:ListTasks # Adding RDS permissions - rds:DescribeDBInstances - rds:DescribeDBClusters # Adding SQS permissions - sqs:ListQueues - sqs:GetQueueAttributes Resource: "*" # 2. CloudWatch metrics (read‑only) - Sid: SweeperReadMetrics Effect: Allow Action: - cloudwatch:GetMetricStatistics - cloudwatch:ListMetrics Resource: "*" # 3. Tag / untag for audit & grace‑period - Sid: SweeperTag Effect: Allow Action: - ec2:CreateTags - ec2:DeleteTags - elasticloadbalancing:AddTags - elasticloadbalancing:RemoveTags - lambda:TagResource - lambda:UntagResource - dynamodb:TagResource - dynamodb:UntagResource - s3:PutBucketTagging - s3:DeleteBucketTagging - elasticache:AddTagsToResource - elasticache:RemoveTagsFromResource # Adding tagging permissions for EKS, ECR, and ECS - eks:TagResource - eks:UntagResource - ecr:TagResource - ecr:UntagResource - ecs:TagResource - ecs:UntagResource # Adding RDS tagging permissions - rds:AddTagsToResource - rds:RemoveTagsFromResource # Adding SQS tagging permissions - sqs:TagQueue - sqs:UntagQueue Resource: "*" Outputs: RoleArn: Description: "ARN of the Cloud‑Sweeper cross‑account role." Value: !GetAtt CloudSweeperRole.Arn ExternalIdActual: Description: "External ID to configure in the vendor SaaS (auto‑generated if blank)." Value: !If - GenExternalId - !Sub "{{resolve:secretsmanager:${ExternalIdGenerator}:SecretString:external_id}}" - !Ref ExternalId