AWS Config with RDK (Rule Development Kit)

In this lab you will learn how to use AWS Config RDK (Rule Development Kit) to create AWS Config Custom Rule and deploy it across AWS Control Tower linked account. In this lab you also learn how to send the findings from AWS Config Custom Rule into AWS Security Hub. Example use case: check whether VPC with Internet Gateway (IGW) is deployed on the account. More info about RDK can be found here https://github.com/awslabs/aws-config-rdk.

Overview

The RDK is designed to support a “Compliance-as-Code” workflow that is intuitive and productive. It abstracts away much of the undifferentiated heavy lifting associated with deploying AWS Config rules backed by custom lambda functions, and provides a streamlined develop-deploy-monitor iterative process.

Architecture for the lab: Architecture In this lab, we will create Lambda function and deploy it on AWS Control Tower Management account. AWS Config Custom Rule is deployed to linked account using AWS CloudFormation StackSet. The AWS Config Custom Rule on each linked account sends the event to Lambda function to be evaluated. Lambda returns the results back to each linked account. All AWS Config Rule results will be aggregated to Audit account.

NOTE: : Before you start writing AWS Config Custom Rule, see if the existing AWS Config Managed Rules can satisfy the requirements.

Prerequisites:

  • This lab presume you have basic understanding of Python programming language.
  • This lab requires fully provisioned AWS Control Tower and administrator role access to AWS Control Tower management account.
  • Select one AWS Organization Organizatinal Units (OU) and record the OU id, you will deploy the AWS Config Custom Rule to all accounts in this OU.

Task 1: Create Custom Config Rule

On this section we will use AWS Config RDK to quickly create new AWS Config Custom Rule. We use AWS Cloud9 as IDE + CLI terminal.

1.1 Sign in to AWS Control Tower Management account using Administrator role.

1.2 Navigate to AWS Cloud9 console, make sure you select the region where AWS Control Tower is deployed.

1.3 Click Create environment

1.4 Enter the environment name, i.e. AWS RDK and select Next Step

1.5 Use the use the default configuration, including default VPC. Select Next Step Architecture

1.6 Confirm the setup by clicking Create environment

1.7 Wait until the Cloud9 terminal and IDE is ready.

1.8 In the Cloud 9 terminal, run the following command to install AWS RDK and initialize sample customer rule called MyRule

sudo pip install rdk
rdk init
rdk create MyRule --runtime python3.8 --maximum-frequency One_Hour --input-parameters '{"desiredVPCType":"private","ExecutionRoleName":"config-role"}'
rdk deploy MyRule -f

There are couple things going on here, let’s inspect it:

  • RDK will automatically initialize and creates boilerplate for MyRule, including Python lambda functions Myrule.py.
  • RDK deploy the resources via AWS CloudFormation, you can find stack RDK-Config-Rule-Functions and inspect the Lambda function.
  • Notice we specify input parameter here desiredVPCType":"private" and "ExecutionRoleName":"config-role".
  • Lambda function uses parameter ExecutionRoleName to assume role to the account where the rule is running and perform custom checks.
  • Lambda function uses parameter desiredVPCType to compare against list of VPCs in the account where the rule is deployed (more on this later).

1.9 From Cloud9 console, use the side bar to navigate the folder structure, you should see directory MyRule.

1.10 Open file MyRule.py and find the line with statement ASSUME_ROLE_MODE and modify it as below.

ASSUME_ROLE_MODE = True
  • With this changes, the Lambda function will assume role on each Linked account to perform the custom checks.
  • As noted previously, Lambda expects that IAM role called config-role exist on each Linked account. We’ll need to deploy this IAM role later.

1.11 On the same file MyRule.py locate the statement below inside function evaluate_compliance and change the return value to NON_COMPLIANT.


    ###############################
    # Add your custom logic here. #
    ###############################

    return 'NON_COMPLIANT'

  • In this example, the function evaluate_compliance is still empty, we will just return blanket response of NON_COMPLIANT for each triggers.
  • We’ll expand this to perform custom checks, for example by assuming role to describe VPCs on the source account.

1.12 Save all files in Cloud9 and re-run the deployment in the terminal.

$ rdk deploy MyRule -f

So far we have deployed Lambda function that will handle the custom rule, the Lambda didn’t do much except assuming IAM role config-role and returning NON_COMPLIANT as the results.

Task 2: Provision Custom Config Rule to target OUs

On the previous section, you have deployed simple Lambda function to handle the custom rule, on this section you will deploy the AWS Config Custom Rule into the target accounts or OUs. We will use AWS CloudFormation StackSet to scale this deployment.

2.1 Still on your Cloud9 console.

  • create a new file called stackset.yaml
  • copy the template content below into the file stackset.yaml
  • don’t forget to save the file
---
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation template to create custom AWS Config rules. You will
  be billed for the AWS resources used if you create a stack from this template.
Parameters:
  LambdaAccountId:
    Description: Account ID that contains Lambda functions for Config Rules.
    Type: String
    MinLength: '12'
    MaxLength: '12'
  desiredVPCType:
    Description: Desired VPC type (private or public)
    Type: String
    Default: private
Resources:
  ConfigRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: config-role
      Path: "/"
      ManagedPolicyArns:
      - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AWSConfigRole
      - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/ReadOnlyAccess
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Sid: LOCAL
          Effect: Allow
          Principal:
            Service:
            - config.amazonaws.com
          Action: sts:AssumeRole
        - Sid: REMOTE
          Effect: Allow
          Principal:
            AWS:
              Fn::Sub: arn:${AWS::Partition}:iam::${LambdaAccountId}:root
          Action: sts:AssumeRole
  MyRuleConfigRule:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName: MyRule
      Description: MyRule
      Source:
        SourceDetails:
        - EventSource: aws.config
          MessageType: ScheduledNotification
          MaximumExecutionFrequency: One_Hour
        Owner: CUSTOM_LAMBDA
        SourceIdentifier:
          Fn::Sub: arn:${AWS::Partition}:lambda:${AWS::Region}:${LambdaAccountId}:function:RDK-Rule-Function-MyRule
      InputParameters:
        desiredVPCType: private
        ExecutionRoleName: config-role
  • this template consisted of two resources: IAM role called config-role with basic read-only access and AWS Config rule called MyRule

2.2 From Cloud9 console, select the terminal tab.

2.3 Replace the command below with your target region and target OU, then run in on the Cloud9 terminal.

ACCOUNTID=`aws sts get-caller-identity --query Account`
TARGETREGIONS=ENTER_REGION_NAME
TARGETOUID=ENTER_OU_ID
  • For TARGETOUID use comma delimited if you wish to include multiple OUs, i.e. ou-3ia8-XXXXXXXX,ou-3ia8-XXXXXXXX
  • For TARGETREGIONS use space delimited and escape it with “ ‘ ” if you wish to include multiple regions, i.e. 'us-east-1 us-west-2'

TIPS : you can find the OU id by navigating to AWS Organization or AWS Control Tower console.

WARNING : this tutorial only supports deployment in single region, to do multi-region deployment, you need to deploy the Lambda in each region.

2.4 Run command below in the Cloud9 terminal to create the StackSet.

aws cloudformation create-stack-set --stack-set-name CustomConfigRuleLambda --template-body file://stackset.yaml --parameters ParameterKey=LambdaAccountId,ParameterValue=$ACCOUNTID ParameterKey=desiredVPCType,ParameterValue=private --permission-model SERVICE_MANAGED --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false --capabilities CAPABILITY_NAMED_IAM

2.5 Run command below in the Cloud9 terminal to launch StackSet instance in the target OU:

aws cloudformation create-stack-instances --stack-set-name CustomConfigRuleLambda --deployment-targets OrganizationalUnitIds=$TARGETOUID --regions $TARGETREGIONS

2.6 Monitor the StackSet progress from AWS CloudFormation stackset console.

AWS CloudFormation StackSet deploys StackSet instance on each account and region that you selected earlier. The AWS Config custom rule is deployed on each accounts under TARGETOUID and on the next step you will verify the custom rule status.

Task 3: Verify AWS Config Custom Rule deployment

On this task, you will navigate to one of the AWS linked accounts under TARGETOUID OU and confirm the new AWS Config custom rule is in place.

3.1 Sign in to one of the AWS linked account under TARGETOUID using Administrator role.

3.2 Navigate to AWS Config rules, ensure you selected AWS region that included in the TARGETREGIONS

3.3 Locate custom rule called MyRule with status Noncompliant. Remember on step 1 earlier, we setup Lambda function to return NON_COMPLIANT regardless the status. We will modify the Lambda function in the next section.

3.4 By default, custom rule MyRule evaluates resources every 1 hour. To trigger immediate evaluation:

  • Select the radio button next to MyRule
  • Select Actions from the drop down, select Re-evaluate
  • The rule status will stay until we do further modification to Lambda function in the AWS Control Tower Management account.

Task 4: Modify AWS Config Custom Rule

On the following section, we will modify the Lambda function called MyRuleLambdaFunction in AWS Control Tower Management account to perform custom checks and return the evaluation to each linked account where the AWS Config custom rule MyRule is deployed.

4.1 Sign in to AWS Control Tower Management account using Administrator role.

4.2 Navigate to AWS Cloud9 console, make sure you select the region where AWS Control Tower is deployed.

4.3 Return to Cloud9 IDE that you created earlier (AWS RDK)

4.4 Open file MyRule.py, find statement for function evaluate_compliance and modify it as below:

    ###############################
    # Add your custom logic here. #
    ###############################
    if evaluate_vpc_igw(event, valid_rule_parameters):
        return 'COMPLIANT'
    else:
        return 'NON_COMPLIANT'
  • With this changes, the custom logic for AWS Config rule evaluation will call a new function called evaluate_vpc_igw
  • If the function evaluate_vpc_igw returns True then the result is COMPLIANT

4.4 Still on file MyRule.py, add the following statement to create new function evaluate_vpc_igw

def evaluate_vpc_igw(event, parameters):
    try:
        vpc_client = get_client('ec2', event)
        target_status = parameters['desiredVPCType']
        igw_paginator = vpc_client.get_paginator('describe_internet_gateways')
        igw_iterator = igw_paginator.paginate(
            Filters=[{'Name': 'attachment.state','Values': ['available']}]
        )
        igw_list = []
        for page in igw_iterator:
            if 'InternetGateways' in page:
                igw_list.extend(page['InternetGateways'])
        if len(igw_list) > 0:
            if target_status == 'private':
                print("Found VPCs with attached IGW : {}".format(igw_list))
                return False
            else:
                print("No VPCs with attached IGW")
                return True
        else:
            print("No IGWs found")
            return True
    except:
        return False
  • Function evaluate_vpc_igw will assume IAM role config-role on the account that initiated the AWS Config rule evaluation, remember earlier you pass parameter "ExecutionRoleName":"config-role"
  • The function evaluate_vpc_igw collects all IGW and returns True if it found IGW attached to VPC.

4.5 Save the file MyRule.py to save all changes

4.6 From the Cloud9 terminal, re-run the deployment to update the Lambda function:

$ rdk deploy MyRule -f

You have successfully modified the AWS Config custom rule and redeploy the Lambda function. On the next section, you will re-evaluate the rule to check the results.

Task 5: Re-verify AWS Config Custom Rule changes

On this task, you will navigate to one of the AWS linked accounts under TARGETOUID OU and confirm the changes that you made on AWS Config custom rule earlier.

5.1 Sign in to one of the AWS linked account under TARGETOUID using Administrator role.

5.2 Navigate to AWS Config rules, ensure you selected AWS region that included in the TARGETREGIONS

5.3 Locate custom rule called MyRule

  • Select the radio button next to MyRule
  • Select Actions from the drop down, select Re-evaluate
  • Wait for couple minute until the rule evaluation changes.

IMPORTANT : make sure to double check if the account you selected has any VPC with IGW attached.

Congratulation, you have completed the deployment of AWS Config custom rule using custom Lambda functions. Explore the following lab tasks to expand your lab.

Task 6: Provision Custom Rule as Conformance Pack

As an alternative to single rule deployment via AWS CloudFormation StackSet, we can also use conformance packs. The CloudFormation template below does that for you. In your AWS Control Tower management account, simply deploy it as StackSet in home region where you deployed AWS Control Tower.

Hint : you could use the same CLI command as shown in Task 2 to create the stackset and deploy stackset instance. Remember that you need to delete the previous StackSet instance and StackSet template first.

6.1 Sign in to AWS Control Tower Management account using Administrator role.

6.2 Navigate to AWS Cloud9 console, make sure you select the region where AWS Control Tower is deployed.

6.3 Return to Cloud9 IDE that you created earlier (AWS RDK)

  • create a new file called conformance-pack.yaml
  • copy the template content below into the file conformance-pack.yaml
  • don’t forget to save the file
Description: "Conformance Pack with Custom Rule(s)"
Parameters:
  LambdaAccountId:
    Description: Account ID that contains Lambda functions for Config Rules.
    Type: String
    MinLength: '12'
    MaxLength: '12'
  desiredVPCType:
    Description: Desired VPC type (private or public)
    Type: String
    Default: private
Resources:
  ConfBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "config-conf-pack-bucket-${AWS::AccountId}-${AWS::Region}"
  ConfPack:
    Type: AWS::Config::ConformancePack
    DependsOn: ConfBucket
    Properties: 
      ConformancePackName: MyFirstCustomConformancePack
      DeliveryS3Bucket: !Ref ConfBucket
      TemplateBody: !Sub |
        Resources:
          IamPolicyInUse:
            Type: "AWS::Config::ConfigRule"
            Properties:
              ConfigRuleName: IamPolicyInUse
              Description: "Checks whether the IAM policy ARN is attached to an IAM user, or an IAM group with one or more IAM users, or an IAM role with one or more trusted entity."
              MaximumExecutionFrequency: TwentyFour_Hours
              InputParameters:
                policyARN: "arn:aws:iam::aws:policy/IAMFullAccess"
              Source:
                Owner: AWS
                SourceIdentifier: IAM_POLICY_IN_USE
          
          SampleMyRule:
            Type: "AWS::Config::ConfigRule"
            Properties:
              ConfigRuleName: SampleMyRule
              Description: "Sample Custom Rule"
              MaximumExecutionFrequency: TwentyFour_Hours
              Source:
                Owner: "CUSTOM_LAMBDA"
                SourceIdentifier:
                  !Sub "arn:${AWS::Partition}:lambda:${AWS::Region}:${LambdaAccountId}:function:RDK-Rule-Function-MyRule"
                SourceDetails: 
                - EventSource: "aws.config"
                  MessageType: "ScheduledNotification"
              InputParameters:
                desiredVPCType: private
                ExecutionRoleName: config-role
  ConfigRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: config-role
      Path: "/"
      ManagedPolicyArns:
      - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AWSConfigRole
      - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/ReadOnlyAccess
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Sid: LOCAL
          Effect: Allow
          Principal:
            Service:
            - config.amazonaws.com
          Action: sts:AssumeRole
        - Sid: REMOTE
          Effect: Allow
          Principal:
            AWS:
              Fn::Sub: arn:${AWS::Partition}:iam::${LambdaAccountId}:root
          Action: sts:AssumeRole
  • this template consisted of three resources: IAM role called config-role with basic read-only access, AWS Config Conformance pack called MyFirstCustomConformancePack with two rules on it (including the custom rule) and S3 bucket to store the Conformance pack results.

6.4 From Cloud9 console, select the terminal tab.

6.5 Replace the command below with your target region and target OU, then run in on the Cloud9 terminal. Skip this if you still have the terminal session from the previous Task 2.

ACCOUNTID=`aws sts get-caller-identity --query Account`
TARGETREGIONS=ENTER_REGION_NAME
TARGETOUID=ENTER_OU_ID
  • For TARGETOUID use comma delimited if you wish to include multiple OUs, i.e. ou-3ia8-XXXXXXXX,ou-3ia8-XXXXXXXX
  • For TARGETREGIONS use space delimited and escape it with “ ‘ ” if you wish to include multiple regions, i.e. 'us-east-1 us-west-2'

TIPS : you can find the OU id by navigating to AWS Organization or AWS Control Tower console.

WARNING : this tutorial only supports deployment in single region, to do multi-region deployment, you need to deploy the Lambda in each region.

6.6 Run command below in the Cloud9 terminal to create delete the previous StackSet instances.

aws cloudformation delete-stack-instances --stack-set-name CustomConfigRuleLambda --deployment-targets OrganizationalUnitIds=$TARGETOUID --regions $TARGETREGIONS --no-retain-stacks 

6.7 Run command below in the Cloud9 terminal to delete the StackSet.

aws cloudformation delete-stack-set --stack-set-name CustomConfigRuleLambda
  • If you receive error, ensure that you have waited long enough to ensure the StackSet instances were deleted completely from the previous step.

6.8 Run command below in the Cloud9 terminal to create the new StackSet with Conformance pack.

aws cloudformation create-stack-set --stack-set-name CustomConfigRuleLambda --template-body file://conformance-pack.yaml --parameters ParameterKey=LambdaAccountId,ParameterValue=$ACCOUNTID ParameterKey=desiredVPCType,ParameterValue=private --permission-model SERVICE_MANAGED --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false --capabilities CAPABILITY_NAMED_IAM

6.9 Run command below in the Cloud9 terminal to launch StackSet instance in the target OU:

aws cloudformation create-stack-instances --stack-set-name CustomConfigRuleLambda --deployment-targets OrganizationalUnitIds=$TARGETOUID --regions $TARGETREGIONS

If you would like to verify the deployment, follow the steps on Task 3 to login to each individual linked account to verify the AWS Config rule deployment.

IMPORTANT you must login to the individual linked account to verify this.

For more info see also https://docs.aws.amazon.com/config/latest/developerguide/custom-conformance-pack.html

Task 7: Enable Security Hub in the linked accounts

On this task, you will enable Security Hub in the linked account and later uses it to propage AWS Config rules compliance status as Security Hub findings. We will use simple CloudFormation StackSet to enable Security Hub on each linked account.

7.1 Sign in to AWS Control Tower Management account using Administrator role.

7.2 Navigate to AWS Cloud9 console, make sure you select the region where AWS Control Tower is deployed.

7.3 Return to Cloud9 IDE that you created earlier (AWS RDK)

  • create a new file called security-hub.yaml
  • copy the template content below into the file security-hub.yaml
  • don’t forget to save the file
Description: Example Hub with Tags
Resources:
  ExampleHubWithTags:
    Type: 'AWS::SecurityHub::Hub'
    Properties:
      Tags:
        key1: value1
        key2: value2
Outputs:
  HubArn:
    Value: !Ref ExampleHubWithTags

7.4 From Cloud9 console, select the terminal tab.

7.5 Replace the command below with your target region and target OU, then run in on the Cloud9 terminal. Skip this if you still have the terminal session from the previous Task 2.

ACCOUNTID=`aws sts get-caller-identity --query Account`
TARGETREGIONS=ENTER_REGION_NAME
TARGETOUID=ENTER_OU_ID
  • For TARGETOUID use comma delimited if you wish to include multiple OUs, i.e. ou-3ia8-XXXXXXXX,ou-3ia8-XXXXXXXX
  • For TARGETREGIONS use space delimited and escape it with “ ‘ ” if you wish to include multiple regions, i.e. 'us-east-1 us-west-2'

TIPS : you can find the OU id by navigating to AWS Organization or AWS Control Tower console.

WARNING : this tutorial only supports deployment in single region, to do multi-region deployment, you need to deploy the Lambda in each region.

7.6 Run command below in the Cloud9 terminal to create the new StackSet for Security Hub.

aws cloudformation create-stack-set --stack-set-name SH-StackSet --template-body file://security-hub.yaml --permission-model SERVICE_MANAGED --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false --capabilities CAPABILITY_NAMED_IAM

7.7 Run command below in the Cloud9 terminal to launch StackSet instance in the target OU:

aws cloudformation create-stack-instances --stack-set-name SH-StackSet --deployment-targets OrganizationalUnitIds=$TARGETOUID --regions $TARGETREGIONS

On the following section, you will deploy the additional solution to propagate AWS Config rules compliance status into Security Hub findings.

Task 8: Deploy Security Hub Config Solution

In this step, we will deploy a solution, that allows us to propagate custom rule findings to Security Hub. Please check this blog post for the full detail walkthrough.

8.1 Sign in to AWS Control Tower Management account using Administrator role.

8.2 Navigate to AWS Cloud9 console, make sure you select the region where AWS Control Tower is deployed.

8.3 Return to Cloud9 IDE that you created earlier (AWS RDK)

  • create a new file called config_to_securityhub.yaml
  • copy the template content below into the file config_to_securityhub.yaml
  • don’t forget to save the file
AWSTemplateFormatVersion: 2010-09-09
Description: This CloudFormation template will automate the importing of aws config findings into aws security hub
Resources:
  LambdaZipsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: 'AES256'

  CopyZips:
    Type: Custom::CopyZips
    Properties:
      ServiceToken: !GetAtt 'CopyZipsFunction.Arn'
      DestBucket: !Ref 'LambdaZipsBucket'
      SourceBucket: 'awsiammedia'
      Prefix: ''
      Objects:
        - 'public/sample/ImportConfigRulesFindingsSecHub/config-cwe-sh.zip'

  CopyZipsRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Path: /
      Policies:
        - PolicyName: lambda-copier
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectTagging
                Resource:
                  - !Sub 'arn:aws:s3:::awsiammedia/public/sample/ImportConfigRulesFindingsSecHub*'
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:DeleteObject
                  - s3:PutObjectTagging
                Resource:
                  - !Sub 'arn:aws:s3:::${LambdaZipsBucket}/public/sample/ImportConfigRulesFindingsSecHub*'

  CopyZipsFunction:
    Type: AWS::Lambda::Function
    Properties:
      Description: Copies objects from a source S3 bucket to a destination
      Handler: index.handler
      Runtime: python3.7
      Role: !GetAtt 'CopyZipsRole.Arn'
      Timeout: 240
      Code:
        ZipFile: |
          import json
          import logging
          import threading
          import boto3
          import cfnresponse
          def copy_objects(source_bucket, dest_bucket, prefix, objects):
              s3 = boto3.client('s3')
              for o in objects:
                  key = prefix + o
                  copy_source = {
                      'Bucket': source_bucket,
                      'Key': key
                  }
                  print('copy_source: %s' % copy_source)
                  print('dest_bucket = %s'%dest_bucket)
                  print('key = %s' %key)
                  s3.copy_object(CopySource=copy_source, Bucket=dest_bucket,
                        Key=key)
          def delete_objects(bucket, prefix, objects):
              s3 = boto3.client('s3')
              objects = {'Objects': [{'Key': prefix + o} for o in objects]}
              s3.delete_objects(Bucket=bucket, Delete=objects)
          def timeout(event, context):
              logging.error('Execution is about to time out, sending failure response to CloudFormation')
              cfnresponse.send(event, context, cfnresponse.FAILED, {}, None)
          def handler(event, context):
              # make sure we send a failure to CloudFormation if the function
              # is going to timeout
              timer = threading.Timer((context.get_remaining_time_in_millis()
                        / 1000.00) - 0.5, timeout, args=[event, context])
              timer.start()
              print('Received event: %s' % json.dumps(event))
              status = cfnresponse.SUCCESS
              try:
                  source_bucket = event['ResourceProperties']['SourceBucket']
                  dest_bucket = event['ResourceProperties']['DestBucket']
                  prefix = event['ResourceProperties']['Prefix']
                  objects = event['ResourceProperties']['Objects']
                  if event['RequestType'] == 'Delete':
                      delete_objects(dest_bucket, prefix, objects)
                  else:
                      copy_objects(source_bucket, dest_bucket, prefix, objects)
              except Exception as e:
                  logging.error('Exception: %s' % e, exc_info=True)
                  status = cfnresponse.FAILED
              finally:
                  timer.cancel()
                  cfnresponse.send(event, context, status, {}, None)

  LambdaServiceRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: 'config-sechub-lambda-role'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: lambda-service-policy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - 'securityhub:BatchImportFindings'
                Resource:
                  - '*'
              - Effect: Allow
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: '*'
              - Effect: Allow
                Action: 
                  - 'config:DescribeConfigRules'
                Resource: '*'

  ConfigSecHubFunction:
    DependsOn: CopyZips
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: !Ref LambdaZipsBucket
        S3Key: 'public/sample/ImportConfigRulesFindingsSecHub/config-cwe-sh.zip'
      FunctionName : 'Config-SecHub-Lambda'
      Handler: 'lambda_function.lambda_handler'
      Role:
        Fn::GetAtt:
          - LambdaServiceRole
          - Arn
      Runtime: python3.7
      Timeout: 300  
  ConfigSecHubCWRule:
    Type: AWS::Events::Rule
    Properties:
      Description: This CW rule integrates AWS Config Compliance events with AWS Lambda as a target
      Name: 'Config-Sechub-CW-Rule'
      EventPattern:
        source:
          - aws.config
        detail-type:
          - Config Rules Compliance Change
        detail:
          messageType:
            - ComplianceChangeNotification
      State: 'ENABLED'
      Targets:
        - 
          Arn: 
            Fn::GetAtt:
              - 'ConfigSecHubFunction'
              - 'Arn'
          Id: 'TargetFunctionV1'      
  PermissionForEventsToInvokeLambda:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName:
        Ref: 'ConfigSecHubFunction'
      Action: 'lambda:InvokeFunction'
      Principal: 'events.amazonaws.com'
      SourceArn:
        Fn::GetAtt:
          - 'ConfigSecHubCWRule'
          - 'Arn'

8.4 Run command below in the Cloud9 terminal to create the new StackSet for pushing AWS Config Rule to Security Hub

aws cloudformation create-stack-set --stack-set-name Config-to-SH --template-body file://config_to_securityhub.yaml --permission-model SERVICE_MANAGED --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false --capabilities CAPABILITY_NAMED_IAM

8.5 Run command below in the Cloud9 terminal to launch StackSet instance in the target OU:

aws cloudformation create-stack-instances --stack-set-name Config-to-SH --deployment-targets OrganizationalUnitIds=$TARGETOUID --regions $TARGETREGIONS

This StackSet deploys a new Lambda function ConfigSecHubFunction on each AWS Linked account. This Lambda function listen for any changes to AWS Config rule evaluations and push the evaluation as findings to AWS Security Hub.

To validate the results, login to one of the AWS linked account and inspect the non compliant AWS Config rule and it’s respective findings on AWS Security Hub. Tips you might need to re-run the evaluation to gather findings for existing results.

Security Hub

IMPORTANT you must login to the individual linked account to verify this.

Task 9: Enable/Elevate Security Hub in Audit Account

Login to your Audit Account. Navigate to Security Hub and ensure you selected home region where AWS Control Tower is deployed. Go ahead and manually invite a single linked account.

Hint : You will need to logon to the linked account as well to accept the invite.

For more info see https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-accounts-add-invite.html

Note: In a larger environment we recoomend you to utilize AWS Security Hub integration with AWS Organization

Clean Up

Make sure to undeploy the StackSets to especially remove Security Hub and any Config Rules. Verify that Security Hub is disabled again!