AWSTemplateFormatVersion: 2010-09-09 Description: Create the automation code for the IAM Credential Report Blog Post Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Role Attributes Parameters: - pIAMCredReportDeliveryBucketArn - pOrganizationId - pLogsRetentionInDays - pScheduleExpression - pOrgPrimaryLambdaRoleName - pIAMCredReportKeyArn - pTagKey1 - pTagValue1 ParameterLabels: pIAMCredReportDeliveryBucketArn: default: Arn of the IAM Credential S3 Bucket pOrganizationId: default: Organization ID pLogsRetentionInDays: default: Log retention pOrgPrimaryLambdaRoleName: default: Organization Primary Lambda Role Name pScheduleExpression: default: EventBridge Scheduler pIAMCredReportKeyArn: default: IAM Credential Report Delivery KMS Key Arn pTagKey1: default: Credential Report Role Tag Key pTagValue1: default: Credential Report Role Tag Key Value Parameters: pIAMCredReportDeliveryBucketArn: Type: String Description: The Arn of the IAM Credential Report S3 Bucket pOrganizationId: Type: String Description: AWS Organization ID MinLength: 12 MaxLength: 12 AllowedPattern: '^o-[a-z0-9]{10,32}$' ConstraintDescription: > The Organization Id must be a 12 character string starting with o- and followed by 10 lower case alphanumeric characters pLogsRetentionInDays: Description: Specifies the number of days you want to retain log events in the CloudWatch log group Type: Number Default: 90 AllowedValues: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653] pScheduleExpression: Type: String Description: EventBridge Schedule Expression to Trigger Lambda Function Default: rate(7 days) pOrgPrimaryLambdaRoleName: Type: String Description: Organization Primary Account Lambda Role Name pIAMCredReportKeyArn: Type: String Description: IAM Credential Report Delivery KMS Key Arn pTagKey1: Type: String Description: Tag key Default: managed-by pTagValue1: Type: String Description: Tag key value Default: credential-cfn Resources: rIAMRoleForLambdaFunction: Type: AWS::IAM::Role Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: IAM role should not allow * resource on its permissions policy - id: F3 reason: IAM role should not allow * resource on its permissions policy Properties: RoleName: !Ref pOrgPrimaryLambdaRoleName AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: iam-credential-report-lambda-policy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - logs:GetLogEvents - logs:StartQuery - logs:PutMetricFilter Resource: '*' - Effect: Allow Action: - cloudwatch:DescribeAlarms - cloudwatch:GetMetricData - cloudwatch:GetMetricStatistics - cloudwatch:ListMetrics - cloudwatch:PutMetricAlarm - cloudwatch:PutMetricData - cloudwatch:PutDashboard - cloudwatch:GetDashboard - cloudwatch:EnableAlarmActions Resource: '*' - Effect: Allow Action: - sts:AssumeRole Resource: 'arn:aws:iam::*:role/iam-credential-report' Condition: StringEquals: "aws:PrincipalOrgID": !Sub ${pOrganizationId} - Effect: Allow Action: - organizations:ListAccounts Resource: '*' - Effect: Allow Action: - s3:PutObject - s3:PutObjectAcl Resource: !Sub ${pIAMCredReportDeliveryBucketArn}/* - Effect: Allow Action: - kms:GenerateDataKey - kms:GenerateDataKeyWithoutPlaintext Resource: !Ref pIAMCredReportKeyArn Tags: - Key: !Ref pTagKey1 Value: !Ref pTagValue1 rIAMCredentialReportLambdaFunction: Type: "AWS::Lambda::Function" Properties: FunctionName: IAMCredentialReportLambda Handler: "index.lambda_handler" Environment: Variables: BUCKET_ARN: !Ref pIAMCredReportDeliveryBucketArn Role: !GetAtt rIAMRoleForLambdaFunction.Arn Code: ZipFile: | # coding=utf-8 ## Import boto3 and start clients import os import boto3 import time from botocore.exceptions import ClientError from datetime import date ## Define Organization and STS Clients orgClient = boto3.client('organizations') stsClient = boto3.client('sts') ## Define current date today = str(date.today()) ## Read environment Variables from Lambda Function BUCKET_ARN = os.environ['BUCKET_ARN'] ## Setup bucket for storing reports s3 = boto3.resource('s3') bucketName = BUCKET_ARN.split(":", -1)[-1] bucketConnection = s3.Bucket(bucketName) listedAccounts = orgClient.list_accounts() failedAccounts = [] def assumeRole(accountId): try: assumedRoleObject=stsClient.assume_role( RoleArn=f"arn:aws:iam::{accountId}:role/iam-credential-report", RoleSessionName="IAMCredentialReport") credentials=assumedRoleObject['Credentials'] iamClient=boto3.client('iam', aws_access_key_id=credentials['AccessKeyId'], aws_secret_access_key=credentials['SecretAccessKey'], aws_session_token=credentials['SessionToken']) # Generate Credential Report reportcomplete = False while not reportcomplete: gencredentialreport = iamClient.generate_credential_report() print('IAM credential report successfully generated for account Id: ' + accountId) reportcomplete = gencredentialreport['State'] == 'COMPLETE' time.sleep (1) # Obtain credential report and send to S3 if gencredentialreport['State'] == 'COMPLETE': credentialReport = iamClient.get_credential_report() decodedCredentialReport = credentialReport['Content'].decode("utf-8") ## Save credential Report into CSV file reportFileName = f"credentialReport_{accountId}.csv" with open("/tmp/"+reportFileName, "w") as file: file.write(decodedCredentialReport) s3.Object(bucketName, today+"/"+reportFileName).put(Body=open("/tmp/"+reportFileName, 'rb'),ACL='bucket-owner-full-control') return reportFileName except ClientError as error: failedAccounts.append(accountId) print(error) pass def lambda_handler(event, context): for account in listedAccounts['Accounts']: if account['Status'] != 'SUSPENDED': assumeRole(account['Id']) return("Completed! Failed Accounts: ", failedAccounts) Runtime: "python3.7" Timeout: 35 rLambdaLogGroup: Type: 'AWS::Logs::LogGroup' Properties: LogGroupName: !Sub /aws/lambda/${rIAMCredentialReportLambdaFunction} RetentionInDays: !Ref pLogsRetentionInDays rScheduledRule: Type: AWS::Events::Rule Properties: Description: IAM Credential Report Enabler Name: iam-credential-report-enabler ScheduleExpression: !Ref pScheduleExpression State: "ENABLED" Targets: - Arn: !GetAtt rIAMCredentialReportLambdaFunction.Arn Id: "DailyChecks" rIAMCredentialReportLambdaFunctionInvokePermissions: DependsOn: - rIAMCredentialReportLambdaFunction Type: "AWS::Lambda::Permission" Properties: FunctionName: !Ref rIAMCredentialReportLambdaFunction Action: "lambda:InvokeFunction" Principal: "events.amazonaws.com" Outputs: oIAMCredentialReportLambdaFunction: Value: !GetAtt rIAMCredentialReportLambdaFunction.Arn oIAMLambdaExecutionIAMRoleArn: Value: !GetAtt rIAMRoleForLambdaFunction.Arn