AWSTemplateFormatVersion: 2010-09-09 Description: >- Creates an Automation Document for distributing info about nonencrypted EBS volumes to Slack. To accomplish this, creates an AWS Systems Manager Automation Document creates a role: Policy for Config to execute SSM Policy for SSM to read EBS and EC2 properties enables the AWS Config Rule encrypted-volumes hooks up the Automation Document created as the Config rule's automatic remediation action Parameters: SlackSecretARN: Type: String Description: The ARN of the Secrets Manager Slack entry ExistingRoleName: Type: String MinLength : 0 Default: "" Description: (Optional) Enter an existing role name. Leave blank to create role. Conditions: CreateRoleCondition: !Equals ["", !Ref ExistingRoleName] Resources: UnencryptedVolToSlackRemediationRole: Type: 'AWS::IAM::Role' Condition: CreateRoleCondition Properties: Description: Example policies for Config to execute SSM, and for SSM to read EBS and EC2 properties over all resources in the account. Please tailor to your needs. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: ssm.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole' Policies: - PolicyName: DescribeEc2VolumesPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: 'ec2:DescribeVolumes' Resource: '*' - PolicyName: DescribeEc2instancesPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: 'ec2:DescribeInstances' Resource: '*' PolicyName: ReadSlackSecret PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: 'secretsmanager:GetSecretValue' Resource: !Ref SlackSecretARN UnencryptedVolToSlackAutomationDoc: Type: "AWS::SSM::Document" Properties: Content: description: Automation Document For finding unencrypted EBS volumes and notifying Slack channel schemaVersion: "0.3" assumeRole: "{{ AutomationAssumeRole }}" parameters: ResourceId: type: String description: (Required) The EBS volume ID AutomationAssumeRole: type: String description: >- (Optional) The ARN of the role that allows Automation to perform the actions on your behalf. mainSteps: - name: extractInfo action: 'aws:executeScript' outputs: - Name: ebsInfoMsg Selector: $.Payload.message Type: String inputs: Runtime: python3.6 Handler: script_handler Script: |- import boto3 def script_handler(events, context): ec2 = boto3.client('ec2') response = ec2.describe_volumes( Filters=[ { 'Name': 'volume-id', 'Values': [ events['ebsVolumeId'] ], } ], ) state = response['Volumes'][0]['State'] iops = 'N/A' if 'Iops' in response['Volumes'][0]: iops = response['Volumes'][0]['Iops'] volumeType = response['Volumes'][0]['VolumeType'] attachments = response['Volumes'][0]['Attachments'] ec2Attached = "No EC2 attached" if attachments: ec2Attached = attachments[0]['InstanceId'] theMsg = 'Account {} - Unencrypted Volume {} found! ' \ '[type:{} state:{} Iops:{} EC2-attached:{}' \ .format(events['accountId'], events['ebsVolumeId'], volumeType, state, iops, ec2Attached) print(theMsg) return {'message': theMsg} InputPayload: ebsVolumeId: '{{ResourceId}}' accountId: '{{global:ACCOUNT_ID}}' description: Gather information for the resource - name: publishToSlack action: 'aws:executeScript' inputs: Runtime: python3.6 Handler: script_handler Script: |- import json import urllib from botocore.exceptions import ClientError import boto3 def script_handler(events, context): slack_secret_arn = events['SlackSecretARN'] slack_info = json.loads(get_slack_secret(slack_secret_arn)) slack_message = { 'channel': slack_info["channel"], 'Content': events['theMsgToSend'] } data = json.dumps(slack_message).encode('utf-8') req = urllib.request.Request(slack_info["URL"], data) try: response = urllib.request.urlopen(req) the_page = response.read() except HTTPError as e: print('Request failed: {} {}'.format(e.code, e.reason)) except URLError as e: print('Server connection failed: {}'.format(e.reason)) return {"msg": 'Message posted to Slack Channel {}'.format(slack_message['channel'])} def get_slack_secret(secret_arn): secret_name = "SlackInfo" region_name = secret_arn[23: secret_arn.find(":", 23)] # Create a Secrets Manager client session = boto3.session.Session() client = session.client( service_name='secretsmanager', region_name=region_name ) try: get_secret_value_response = client.get_secret_value( SecretId=secret_name ) except ClientError as e: if e.response['Error']['Code'] == 'DecryptionFailureException': # Secrets Manager can't decrypt the protected secret text using the provided KMS key. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'InternalServiceErrorException': # An error occurred on the server side. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'InvalidParameterException': # You provided an invalid value for a parameter. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'InvalidRequestException': # You provided a parameter value that is not valid for the current state of the resource. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'ResourceNotFoundException': # We can't find the resource that you asked for. # Deal with the exception here, and/or rethrow at your discretion. raise e else: # Decrypts secret using the associated KMS CMK. # Depending on whether the secret is a string or binary, one of these fields will be populated. secret = get_secret_value_response['SecretString'] return secret InputPayload: theMsgToSend: '{{extractInfo.ebsInfoMsg}}' SlackSecretARN: !Ref SlackSecretARN description: Send message to Slack channel DocumentType: Automation AWSConfigEncryptedVolumesRule: Type: 'AWS::Config::ConfigRule' Properties: Description: >- Checks whether EBS volumes that are in an attached state are encrypted. Source: Owner: AWS SourceIdentifier: ENCRYPTED_VOLUMES AWSConfigEncryptedVolumesRuleRemediation: Type: "AWS::Config::RemediationConfiguration" Properties: ConfigRuleName: !Ref AWSConfigEncryptedVolumesRule Automatic: true MaximumAutomaticAttempts: 1 RetryAttemptSeconds: 60 Parameters: AutomationAssumeRole: StaticValue: Values: - !Join - '' - - 'arn:aws:iam::' - !Ref 'AWS::AccountId' - ':role/' - !If ["CreateRoleCondition", !Ref UnencryptedVolToSlackRemediationRole, !Ref ExistingRoleName] ResourceId: ResourceValue: Value: "RESOURCE_ID" TargetId: !Ref UnencryptedVolToSlackAutomationDoc TargetType: "SSM_DOCUMENT" Outputs: RoleCreated: Description: The IAM Role that was created Value: !If ["CreateRoleCondition", !Ref UnencryptedVolToSlackRemediationRole, "None"] SSMDocumentCreated: Description: The System Manager Automation Document that was created Value: !Ref UnencryptedVolToSlackAutomationDoc AWSConfigRuleEnabled: Description: The AWS Config Rule that was enabled Value: !Ref AWSConfigEncryptedVolumesRule