AWS Config & Security Hub

Prerequisites: - ControlTower deployed - Custom OU with Sandbox Account - AWS Config Enabled in Account/Region

Architecture:

Architecture

Task: Create Custom Config Rule

Start a new AWS Cloud 9 instance (default settings) in your ROOT Account (CT DEFAULT REGION)

In Cloud 9 terminal execute

$ sudo pip install rdk
$ rdk init
$ rdk create MyRule --runtime python3.8 --maximum-frequency One_Hour
$ rdk deploy MyRule -f

Modify MyRule.py and perform the following changes

...
ASSUME_ROLE_MODE = True
...

Change compliance state for testing

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

    return 'NON_COMPLIANT'
...

Find and replace function ‘get_execution_role_arn’

...
# Get execution role for Lambda function MODIFIED
def get_execution_role_arn(event):
    role_arn = None
    if 'invokingEvent' in event:
        rule_params = json.loads(event['invokingEvent'])
        role_account = rule_params.get("awsAccountId")
        if role_account:
            role_arn = "arn:aws:iam::{}:role/rdk/config-role".format(role_account)
    
    print(role_arn)
    return role_arn
...

Cloud9

Save file and deploy again

$ rdk deploy MyRule -f

More info can be found here https://github.com/awslabs/aws-config-rdk.

Task: Provision Custom Config Rule to target OUs

In your ROOT account, deploy the following template as StackSet through CloudFormation (CT DEFAULT REGION). Pass the respective ROOT Account ID (where your Lambda has been deployed).

CF

CF

Note: The AWS OU ID can be found either in AWS Organizations or in Control Tower!

{
  "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.",
  "Resources": {
    "ConfigRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "RoleName": "config-role",
        "Path": "/rdk/",
        "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": {}
      }
    }
  },
  "Conditions": {},
  "Parameters": {
    "LambdaAccountId": {
      "Description": "Account ID that contains Lambda functions for Config Rules.",
      "Type": "String",
      "MinLength": "12",
      "MaxLength": "12"
    }
  },
  "Metadata": {
    "AWS::CloudFormation::Interface": {
      "ParameterGroups": [
        {
          "Label": {
            "default": "Lambda Account ID"
          },
          "Parameters": [
            "LambdaAccountId"
          ]
        },
        {
          "Label": {
            "default": "Required"
          },
          "Parameters": []
        },
        {
          "Label": {
            "default": "Optional"
          },
          "Parameters": []
        }
      ],
      "ParameterLabels": {
        "LambdaAccountId": {
          "default": "REQUIRED: Account ID that contains Lambda Function(s) that back the Rules in this template."
        }
      }
    }
  }
}

Task: Provision Custom Rule as Conformance Pack

As an alternative to single rule deployment, we can also use conformance packs. The CloudFormation template below does that for you. In your ROOT account, simply deploy it as StackSet again (CT DEFAULT REGION).

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

Note: Make sure to replace ${LambdaSourceAccountID} by the respective ROOT Account ID.

Description: "Conformance Pack with Custom Rule(s)"
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}:${LambdaSourceAccountID}:function:RDK-Rule-Function-MyRule"
                SourceDetails: 
                - EventSource: "aws.config"
                  MessageType: "ScheduledNotification"

AWS Config

Task: Enable Security Hub in child accounts

The CloudFormation template below enables AWS Security Hub. In your ROOT account, simply deploy it as StackSet again (CT DEFAULT REGION).

Description: Example Hub with Tags
Resources:
  ExampleHubWithTags:
    Type: 'AWS::SecurityHub::Hub'
    Properties:
      Tags:
        key1: value1
        key2: value2
Outputs:
  HubArn:
    Value: !Ref ExampleHubWithTags

Task: Deploy Security Hub Config Solution

In this step, we will deploy a solution, that allows us to propagate custom rule findings to Security Hub: https://aws.amazon.com/blogs/security/how-to-import-aws-config-rules-evaluations-findings-security-hub/

For deployment simply use the CloudFormation template url below. Go to your ROOT account amd simply deploy it as StackSet again (CT DEFAULT REGION).

https://awsiammedia.s3.amazonaws.com/public/sample/ImportConfigRulesFindingsSecHub/cloudformation_template.yaml

Security Hub

Task: Enable/Elevate Security Hub in Audit Account

Login to your Audit Account. Open Security Hub (CT DEFAULT REGION) and manually invite a single target account. You will need to logon to the child 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 you might wanna use this approach) (not subject to this lab!)

Clean Up

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