Last active
May 4, 2021 07:39
-
-
Save adamcousins/59bafe50b20e2cb24b453bdfe3919ef4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AWSTemplateFormatVersion: '2010-09-09' | |
Description: "Security: Enable AWS SecurityHub in a centralised Account within an AWS Organisation." | |
Parameters: | |
AlertsTopicArn: | |
Description: Alert topic ARN | |
Type: String | |
LogRetention: | |
Type: String | |
Description: Log retention in number of days | |
Default: '7' | |
Conditions: | |
HasAlertsTopicArn: !Not [!Equals [!Ref AlertsTopicArn, '']] | |
Resources: | |
### Cloudformation Custom Resource ### | |
### AWS Security Hub - Centralised service with auto-enrol enabled ### | |
### Required Properties: | |
### ServiceToken: [Type: String] | |
### AdminAccountId: [Type: String] | |
### Attributes (Output): | |
### None | |
### Notes: | |
### This resource will enable auto-enrolment in an AWS Organisation | |
### This resource will also associate all pending AWS accounts in an AWS Organisation | |
### Example Usage ### | |
# EnableSecurityHubAutoEnrolment: | |
# Type: Custom::EnableSecurityHubAutoEnrolment | |
# Properties: | |
# ServiceToken: !GetAtt Function.Arn | |
# AdminAccountId: !Ref AWS::AccountId | |
### End Cloudformation Custom Resource ### | |
AlertsSnsTopicPolicy: | |
Condition: HasAlertsTopicArn | |
Type: AWS::SNS::TopicPolicy | |
Properties: | |
PolicyDocument: | |
Id: SnsTopicPolicy | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: | |
- securityhub.amazonaws.com | |
- events.amazonaws.com | |
Action: sns:Publish | |
Resource: !Ref AlertsTopicArn | |
Condition: | |
StringEquals: | |
AWS:SourceAccount: !Ref AWS::AccountId | |
Topics: | |
- !Ref AlertsTopicArn | |
SecurityHubEvent: | |
Condition: HasAlertsTopicArn | |
Type: AWS::Events::Rule | |
Properties: | |
Description: "AWS SecurityHub findings Event Rule" | |
EventPattern: | |
source: | |
- "aws.securityhub" | |
detail-type: | |
- "SecurityHub Finding" | |
State: "ENABLED" | |
Targets: | |
- Arn: !Ref AlertsTopicArn | |
Id: SecurityHubEvent | |
InputTransformer: | |
InputTemplate: | | |
"AWS SecurityHub from account <account> has discovered a <detail-type> at <at> in region <region>." | |
"Finding: <finding>" | |
"Reason: <title>." | |
"Description: <description>." | |
"More information can be found here : https://<region>.console.aws.amazon.com/securityhub/home?region=<region>#/findings" | |
InputPathsMap: | |
detail: "$.detail" | |
detail-type: "$.detail-type" | |
at: "$.time" | |
account: "$.detail.accountId" | |
region: "$.region" | |
finding: "$.detail.type" | |
title: "$.detail.title" | |
description: "$.detail.description" | |
id: "$.detail.id" | |
FunctionLogGroup: | |
Type: "AWS::Logs::LogGroup" | |
DeletionPolicy: Retain | |
Properties: | |
RetentionInDays: !Ref LogRetention | |
LogGroupName: !Sub "/aws/lambda/${Function}" | |
Function: | |
Type: AWS::Lambda::Function | |
Properties: | |
Description: Adds or Removes all accounts in the AWS Organisation to Enable AWS SecurityHub in the centralied Admin Account. | |
Handler: index.lambda_handler | |
Code: | |
ZipFile: | | |
import logging, boto3, os | |
from botocore.exceptions import ClientError | |
import cfnresponse | |
log = logging.getLogger() | |
log.setLevel(logging.INFO) | |
client = boto3.client('securityhub') | |
org_client = boto3.client('organizations') | |
region = os.environ['AWS_REGION'] | |
def list_hub(admin_account_id): | |
log.info('finding AWS SecurityHub Hub Arn in AWS Account: ' + admin_account_id) | |
hub_arn = None | |
hub = client.describe_hub(HubArn="arn:aws:securityhub:" + region + ":" + admin_account_id + ":hub/default")['HubArn'] | |
log.info('found ' + hub) | |
if hub: | |
hub_arn = hub | |
else: | |
log.info('can not find AWS SecurityHub Arn in AWS Account: ' + admin_account_id) | |
return hub_arn | |
def list_accounts(excluded_account_id, action): | |
paginator = org_client.get_paginator('list_accounts') | |
pages = paginator.paginate() | |
organization_accounts = [] | |
for page in pages: | |
for obj in page['Accounts']: | |
current_account_id = (obj["Id"]) | |
if excluded_account_id in current_account_id: | |
continue | |
accounts_map = {} | |
if action == 'Create': | |
accounts_map = {'AccountId': (obj["Id"]), 'Email': (obj["Email"])} | |
elif action == 'Delete': | |
accounts_map = (obj["Id"]) | |
organization_accounts.append(accounts_map) | |
return organization_accounts | |
def lambda_handler(event, context): | |
log.info('REQUEST RECEIVED:\n %s', event) | |
admin_account_id = event['ResourceProperties']['AdminAccountId'] | |
return_msg = {} | |
return_status = "FAILED" | |
hub_arn = list_hub(admin_account_id) | |
try: | |
if(event['RequestType'] == 'Create'): | |
organization_accounts = list_accounts(admin_account_id, event['RequestType']) | |
if hub_arn: | |
log.info('updating master account id: ' + admin_account_id + ' with hub arn ' + hub_arn + ' to auto enrol new aws accounts') | |
client.update_organization_configuration(AutoEnable=True) | |
log.info('enrolling all accounts currently available in the organization') | |
response = client.create_members( | |
AccountDetails=organization_accounts) | |
print(str(response)) | |
return_msg = {"Message": "master account id: " + admin_account_id + " with hub arn " + hub_arn + " now will auto enrol new aws accounts"} | |
return_status = "SUCCESS" | |
else: | |
log.info('master account id: ' + admin_account_id + ' could not find any hubs') | |
return_msg = {"Message": "master account id: " + admin_account_id + " could not find any hubs"} | |
elif(event['RequestType'] == 'Update'): | |
log.info("Update account not supported") | |
return_msg = {"Message": "Update account not supported"} | |
return_status = "SUCCESS" | |
else: | |
organization_accounts = list_accounts(admin_account_id, event['RequestType']) | |
if hub_arn: | |
log.info('updating master account id: ' + admin_account_id + ' with hub arn ' + hub_arn + ' to no longer auto enrol new aws accounts') | |
client.update_organization_configuration(AutoEnable=False) | |
log.info('removing all accounts currently available in the organization') | |
response = client.disassociate_members(AccountIds=organization_accounts) | |
print(str(response)) | |
return_msg = {"Message": "master account id: " + admin_account_id + " with hub arn " + hub_arn + " will no longer auto enrol new aws accounts"} | |
return_status = "SUCCESS" | |
else: | |
log.info('master account id: ' + admin_account_id + ' could not find any hubs') | |
return_msg = {"Message": "master account id: " + admin_account_id + " could not find any hubs"} | |
return_status = "SUCCESS" | |
except ClientError as e: | |
log.info(e.response['Error']['Message']) | |
return_msg = {"Message": "Exception Found: " + e.response['Error']['Message']} | |
cfnresponse.send(event, context, return_status, return_msg) | |
Runtime: python3.6 | |
Role: !GetAtt FunctionRole.Arn | |
Timeout: 120 | |
FunctionRole: | |
Type: AWS::IAM::Role | |
Properties: | |
AssumeRolePolicyDocument: | |
Statement: | |
- Action: | |
- sts:AssumeRole | |
Effect: Allow | |
Principal: | |
Service: | |
- lambda.amazonaws.com | |
Version: '2012-10-17' | |
Path: "/" | |
Policies: | |
- PolicyName: root | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Resource: "*" | |
Effect: Allow | |
Action: | |
- logs:CreateLogGroup | |
- logs:CreateLogStream | |
- logs:PutLogEvents | |
- Resource: '*' | |
Effect: Allow | |
Action: | |
- organizations:ListAccounts | |
- securityhub:UpdateOrganizationConfiguration | |
- securityhub:DescribeHub | |
- securityhub:CreateMembers | |
- securityhub:ListMembers | |
- securityhub:DeleteMembers | |
- securityhub:DisassociateMembers | |
## Usage ### | |
EnableSecurityHubAutoEnrolment: | |
DependsOn: FunctionLogGroup | |
Type: Custom::EnableSecurityHubAutoEnrolment | |
Properties: | |
ServiceToken: !GetAtt Function.Arn | |
AdminAccountId: !Ref AWS::AccountId | |
Outputs: | |
FunctionArn: | |
Description: The Lambda Function Arn | |
Value: !GetAtt Function.Arn |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment