AWSTemplateFormatVersion: "2010-09-09" Description: Deploys AWS resources to member accounts to list and send IAM policy documents to a central account for evaluation. Parameters: pSQSQueueUrl: Description: SQS queue in the central security account where IAM policy documents are sent for evaluation. Type: String pKMSKeyArn: Description: AWS KMS key ARN in the central security account used to encrypt resources. Type: String pLambdaPowertoolsPythonVersion: Type: String Default: 7 Conditions: IsSpokeAccountEqualToHubAccount: !Not - !Equals - !Sub - "${HubAccount}" - HubAccount: !Select - 3 - !Split - / - !Ref pSQSQueueUrl - !Ref "AWS::AccountId" Resources: ################################################################################# # AWS Lambda function list-iam-policy-for-access-analyzer resources # ################################################################################# LambdaFunctionListPolicies: Condition: IsSpokeAccountEqualToHubAccount Type: AWS::Lambda::Function Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: "Serverlesss implementation. Does not require to be deployed in a VPC." Properties: FunctionName: access-analyzer-list-iam-policy Runtime: python3.9 Architectures: - x86_64 Layers: - !Sub "arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPython:${pLambdaPowertoolsPythonVersion}" MemorySize: 1024 Role: !GetAtt LambdaRoleListPolicies.Arn Handler: index.handler Timeout: 300 Code: ZipFile: | import json import boto3 import os from botocore.exceptions import ClientError from aws_lambda_powertools import Logger logger = Logger() sqs = boto3.client('sqs') SQS_QUEUE_URL = os.environ['SQS_QUEUE_URL'] def send_message_to_sqs_queue(policy,entity_type,entity): try: sqs.send_message( QueueUrl=SQS_QUEUE_URL, DelaySeconds=10, MessageAttributes={ 'policyArn': { 'DataType': 'String', 'StringValue': policy.arn if entity_type == 'CUSTOMER_MANAGED_IAM_POLICY' else entity.arn }, 'policyType':{ 'DataType': 'String', 'StringValue': 'IDENTITY_POLICY' }, 'policyName':{ 'DataType': 'String', 'StringValue': policy.policy_name }, 'policyPath':{ 'DataType': 'String', 'StringValue': policy.path if entity_type == 'CUSTOMER_MANAGED_IAM_POLICY' else entity_type.upper()+'_INLINE_POLICY' }, 'policyId':{ 'DataType': 'String', 'StringValue': policy.policy_id if entity_type == 'CUSTOMER_MANAGED_IAM_POLICY' else 'N/A' }, 'policyDefaultVersionId':{ 'DataType': 'String', 'StringValue': policy.default_version_id if entity_type == 'CUSTOMER_MANAGED_IAM_POLICY' else 'N/A' }, 'policyAttachmentCount':{ 'DataType': 'Number', 'StringValue': str(policy.attachment_count) if entity_type == 'CUSTOMER_MANAGED_IAM_POLICY' else '1' }, 'policyPermissionsBoundaryUsageCount':{ 'DataType': 'Number', 'StringValue': str(policy.permissions_boundary_usage_count) if entity_type == 'CUSTOMER_MANAGED_IAM_POLICY' else '0' } }, MessageBody=( json.dumps(policy.default_version.document) if entity_type == 'CUSTOMER_MANAGED_IAM_POLICY' else json.dumps(policy.policy_document) ) ) logger.info(f"Details sent to SQS on {policy.policy_name}.") except ClientError as e: logger.error("An error occured: {0}".format(e)) def collect_and_send_policies_to_sqs(iterator,type): if type == 'INLINE': for entity in iterator: for inline_p in entity.policies.all(): #-- Get entity type 'user', 'role', 'group' entity_type = inline_p.get_available_subresources()[0] logger.info(f"Inline IAM Policy - Currently working on {entity_type}: {entity.name} and policy: {inline_p.policy_name}.") send_message_to_sqs_queue(inline_p,entity_type,entity) logger.info(f"Inline IAM Policy - Details sent to SQS on policy: {inline_p.policy_name} for {entity_type}: {entity.name}.") elif type == 'CUSTOMER_MANAGED': for p in iterator: logger.info(f"Customer Managed IAM Policy - Currently working on policy: {p.policy_name}.") send_message_to_sqs_queue(p,'CUSTOMER_MANAGED_IAM_POLICY',None) logger.info(f"Customer Managed IAM Policy - Details sent to SQS on policy: {p.policy_name}.") @logger.inject_lambda_context def handler(event, context): iam_resource = boto3.resource('iam') logger.info("Execution started. Building iterators for IAM policies, roles, groups and users ...") group_iterator = iam_resource.groups.all() role_iterator = iam_resource.roles.all() user_iterator = iam_resource.users.all() iam_policies_iterator = iam_resource.policies.filter(Scope='Local') logger.info("Iterators built. Looping through IAM resources...") collect_and_send_policies_to_sqs(group_iterator,'INLINE') collect_and_send_policies_to_sqs(role_iterator,'INLINE') collect_and_send_policies_to_sqs(user_iterator,'INLINE') collect_and_send_policies_to_sqs(iam_policies_iterator,'CUSTOMER_MANAGED') logger.info("Execution complete. Exiting...") Environment: Variables: SQS_QUEUE_URL: !Ref pSQSQueueUrl LOG_LEVEL: INFO POWERTOOLS_SERVICE_NAME: access_analyzer_list_policies Description: !Sub "Lambda function to list customer IAM policies and send them to the SQS queue ${pSQSQueueUrl}." LambdaFunctionListPoliciesDLQ: Condition: IsSpokeAccountEqualToHubAccount Type: AWS::SQS::Queue Metadata: cfn_nag: rules_to_suppress: - id: W48 reason: "No KMS key provided." Properties: KmsMasterKeyId: alias/aws/sqs LambdaRoleListPolicies: Condition: IsSpokeAccountEqualToHubAccount Type: AWS::IAM::Role Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: "Serverlesss implementation. Does not require to be deployed in a VPC." Properties: ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole" Path: /access-analyzer/ AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Action: - "sts:AssumeRole" Effect: Allow Principal: Service: - lambda.amazonaws.com Policies: - PolicyName: "ListIAMPolicyForAccessAnalyzer" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "iam:GetPolicyVersion" - "iam:ListPolicies" - "iam:GetUserPolicy" - "iam:GetRolePolicy" - "iam:GetGroupPolicy" - "iam:ListGroups" - "iam:ListUsers" - "iam:ListRoles" - "iam:ListGroupPolicies" - "iam:ListUserPolicies" - "iam:ListRolePolicies" Resource: - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/*" - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:group/*" - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/*" - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:user/*" - Effect: "Allow" Action: - "kms:Decrypt" - "kms:DescribeKey" - "kms:GenerateDataKey" Resource: !Ref pKMSKeyArn - Effect: "Allow" Action: - "sqs:SendMessage" Resource: - !Sub - >- arn:${AWS::Partition}:sqs:${AWS::Region}:${AccountId}:${QueueName} - QueueName: !Select - 4 - !Split - / - !Ref pSQSQueueUrl AccountId: !Select - 3 - !Split - / - !Ref pSQSQueueUrl - !GetAtt LambdaFunctionListPoliciesDLQ.Arn LambdaFunctionListLogGroup: Condition: IsSpokeAccountEqualToHubAccount Type: "AWS::Logs::LogGroup" Metadata: cfn_nag: rules_to_suppress: - id: W84 reason: "No KMS key provided." Properties: RetentionInDays: 14 LogGroupName: !Sub "/aws/lambda/${LambdaFunctionListPolicies}" ###################################### # AWS CloudWatch Events resources # ###################################### scheduledRule: Condition: IsSpokeAccountEqualToHubAccount Type: AWS::Events::Rule Properties: Description: Scheduled event rule to validate IAM policies using IAM Access Analyzer. ScheduleExpression: "rate(12 hours)" State: "ENABLED" Targets: - Arn: !GetAtt LambdaFunctionListPolicies.Arn Id: "TargetLambdaFunctionListPolicies" permissionForEventsToInvokeLambda: Condition: IsSpokeAccountEqualToHubAccount Type: AWS::Lambda::Permission Properties: FunctionName: !Ref LambdaFunctionListPolicies Action: "lambda:InvokeFunction" Principal: "events.amazonaws.com" SourceArn: Fn::GetAtt: - "scheduledRule" - "Arn"