AWS Cloud Operations Blog
How to enable VPC Flow Logs automatically using AWS Config rules
This post discusses an automated process for enabling Amazon Virtual Private Cloud (Amazon VPC) Flow Logs using AWS Config rule remediation. Customers use Amazon VPC Flow logs to capture information about the IP traffic going to and from network interfaces in an Amazon VPC. You can deploy this solution with the help of AWS Control Tower. AWS Control Tower will help enforce governance for centralizing Amazon VPC Flow Logs in an Amazon Simple Storage Service (S3) bucket in the log archive account for monitoring, troubleshooting, anomaly detection, and archival purposes at scale in a AWS multi-account environment. The log archive account works as a repository for logs from all accounts in your organization.
In this solution we will be using AWS Config to evaluate the configurations of VPC Flow logs and resolve it if they are missing. AWS Config is a service that enables you to assess, audit, and evaluate the configurations of your AWS resources. AWS Config continuously monitors and records your AWS resource configurations and allows you to automate the evaluation of recorded configurations against desired configurations. The benefit of using AWS Config in this solution is to provide the ability to configure resource evaluation rules to validate configuration of the resource and trigger an automated process to remediate the configuration. An AWS Systems Manager automation document is used to perform the remediation task.
This setup is deployed in new AWS Accounts with the help of AWS Control Tower life cycle events and AWS CloudFormation stack. Additionally, you could leverage this solution to enable VPC Flow logs on the existing AWS accounts by manually running the CloudFormation StackSets on the selected AWS accounts. This solution can be used with individual AWS accounts by deploying this as a CloudFormation stack.
Please note that data ingestion and archival charges for vended logs apply when you publish flow logs to CloudWatch Logs or to Amazon S3. See AWS documentation for VPC Flow logs pricing for more information and examples.
Solution Overview
When a VPC is created without enabling VPC flow logs configuration in any account, this solution will identify and mark the non-compliant VPC in AWS Config, and automatically initiate the remediation task to enable the VPC flow logs. Once the VPC flow logs are enabled, the logs will be stored in a centralized S3 bucket in the log archive account.
The following diagram illustrates the solution architecture:
Workflow:
- When a new AWS account is created, an AWS Control tower lifecycle event will be triggered.
- The lifecycle event further triggers a Lambda function, which updates the CloudFormation StackSets with new account ID.
- The CloudFormation StackSet deploys
- VPC_FLOW_LOGS_ENABLED managed AWS Config rule with AWS-EnableVPCFlowLogs SSM Automation document Remediation action.
- An IAM role executes the automation on behalf of the SSM Automation document.
- AWS Config rule VPC_FLOW_fLOGS_ENABLED evaluates all VPCs in a given region for flow log enabled status, and it flags the ones missing with flow log as “non-compliant” resource. The SSM automation document, AWS-EnableVPCFlowLogs, is executed when the resource becomes “non-compliant”.
- The RESOURCE_ID (VPC id) is passed to the SSM automation document as a parameter. The VPC flow log will be enabled on the VPC using permission defined in the IAM role.
- VPC Flow log is configured with “LogDestinationType” as an Amazon S3 bucket (this is configurable and can be changed to CloudWatch if required), along with a “Traffic type”.
Note: There is another approach to achieving a similar use case by using AmazonEventBridge and AWS Lambda for orchestration, which is explained in this blog.
Getting Started
Prerequisites
Here are the prerequisites before you deploy this solution:
- You must have AWS Control Tower setup with an AWS account for centralized logging.
- You must have an S3 bucket in the centralized logging account with the following bucket policy to allow VPC flow logs to be delivered to the S3 bucket. Please refer to creating S3 Bucket if this is your first time creating an S3 bucket.
Deploying the Solution
To deploy this solution across multiple accounts and Regions, we will use AWS CloudFormation StackSets.
-
- Copy the CloudFormation template below and save in a YAML file (aws-config-remediate-vpc-flow-logs.yaml).
AWSTemplateFormatVersion: '2010-09-09' Description: 'This template will deploy an AWS Config rule to automatically remediate VPC Flow Logs enablement' Parameters: CustomConfigRuleName: Description: Name that you want to give to the AWS Config Rule. Type: String Default: ConfigRuleForEnableVpcFlowLogs TrafficType: Type: String AllowedValues: - ACCEPT - REJECT - ALL Description: The value for the VPC Flow Logs traffic type. Default: ALL MaxExecutionFrequency: Type: String AllowedValues: - One_Hour - Three_Hours - Six_Hours - Twelve_Hours - TwentyFour_Hours Description: The maximum frequency with which AWS Config runs evaluations for a rule. Default: One_Hour CentralizedS3LoggingBucket: Description: Name of the S3 bucket in the logging account to send VPC Flow Logs. Type: String Resources: ConfigRemediationRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Principal: Service: - 'ssm.amazonaws.com' Action: - 'sts:AssumeRole' Path: '/' Policies: - PolicyName: aws-config-remediate-vpc-flow-logs-policy PolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Action: - logs:CreateLogDelivery - logs:DeleteLogDelivery - logs:CreateLogGroup Resource: arn:aws:logs:*:*:* - Effect: 'Allow' Action: - ec2:CreateFlowLogs Resource: '*' ConfigRuleForEnableVpcFlowLogs: Type: AWS::Config::ConfigRule Properties: ConfigRuleName: !Ref CustomConfigRuleName Description: ConfigPermissionToInvokeAnAutomaticRemediation InputParameters: trafficType: !Ref TrafficType MaximumExecutionFrequency: !Ref MaxExecutionFrequency Scope: ComplianceResourceTypes: - AWS::EC2::VPC Source: Owner: AWS SourceIdentifier: VPC_FLOW_LOGS_ENABLED VpcFlowLogsRemediationConfiguration: DependsOn: ConfigRuleForEnableVpcFlowLogs Type: AWS::Config::RemediationConfiguration Properties: ConfigRuleName: !Ref CustomConfigRuleName Automatic: true MaximumAutomaticAttempts: 5 #minutes RetryAttemptSeconds: 50 #seconds ResourceType: AWS::EC2::VPC Parameters: VPCIds: ResourceValue: Value: 'RESOURCE_ID' LogDestinationType: StaticValue: Values: - s3 LogDestinationArn: StaticValue: Values: - !Sub 'arn:aws:s3:::${CentralizedS3LoggingBucket}' TrafficType: StaticValue: Values: - !Ref TrafficType AutomationAssumeRole: StaticValue: Values: - !GetAtt ConfigRemediationRole.Arn TargetId: AWS-EnableVPCFlowLogs TargetType: SSM_DOCUMENT TargetVersion: 1 Outputs: ConfigRuleForEnableVpcFlowLogsArn: Description: Arn of the AWS Config Rule to enable VPC Flow Logs Value: !GetAtt ConfigRuleForEnableVpcFlowLogs.Arn ConfigRemediationRoleArn: Description: Arn of the IAM Role to perform auto-emediation Value: !GetAtt ConfigRemediationRole.Arn
- Log in to the AWS Control Tower management account and select the AWS Region where AWS Control Tower is deployed.
- In the AWS CloudFormation StackSets console, select Create StackSet.
- Under Permissions select “Service-managed permissions”.
- Choose Template is ready, and Upload a template file. Select Choose file and upload the template file you created in Step 1.
- Provide StackSet details and Parameters as shown in the below screenshot.
- Copy the CloudFormation template below and save in a YAML file (aws-config-remediate-vpc-flow-logs.yaml).
-
-
- CentralizedS3LoggingBucket – Name of the S3 Bucket dedicated to store VPC Flow logs in the log archival account
- CustomConfigRuleName – Name of the AWS Config Rule Example :- ConfigRuleForEnableVpcFlowLogs
- MaxExecutionFrequency – Select the frequency with which AWS Config rule evaluation should occurs. Default :- One_Hour
- TrafficType – The type of traffic that VPC Flow logs should capture, default :- ALL
-
- On the Accounts section you have 2 deployment options to choose as described below.
Option 1 : To deploy the StackSet in OUs, select Deploy stacks in organizational units.
Option 2 : To deploy stacks in selected AWS accounts, choose Deploy Stacks in accounts and provide comma delimited list of AWS account IDs. you want to include.
- On the Specify Regions section, select AWS regions where you want to deploy the automation.
- On the Review page, validate all of the parameters and settings. Make sure you select the checkbox I acknowledge that AWS CloudFormation might create IAM resource with custom names. When you’re ready, select Submit.
- Navigate to the StackSets on the CloudFormation console and select the StackSet that was created in Step 9. Click on the StackSet name and go to StackSet info tab and notedown the StackSet ARN. You will need this for the next step.
- Setup the lifecycle event.
-
- Copy the CloudFormation template below and save in a YAML file (ct-lifecycle-event.yaml).
AWSTemplateFormatVersion: 2010-09-09 Description: FlowLog - Infrastructure at the Control tower Management account for setting up Control tower life cycle event Parameters: StackSetArn: Type: String Description: ARN of the StackSet deployed from Control Tower Management account Resources: CTLifeCycleTrigger: Type: AWS::Lambda::Function Properties: Description: CT Lifecycle Trigger Lambda function Handler: lambda_handler Runtime: python3.8 Role: !GetAtt 'FlowLogLifeCycleRole.Arn' Timeout: 600 Environment: Variables: ct_stack_set_arn: !Ref StackSetArn Code: ZipFile: | import json import boto3 import logging import os from botocore.exceptions import ClientError LOGGER = logging.getLogger() LOGGER.setLevel(logging.INFO) logging.getLogger('boto3').setLevel(logging.CRITICAL) logging.getLogger('botocore').setLevel(logging.CRITICAL) session = boto3.Session() def list_stack_instance_by_account(target_session, stack_set_name, account_id): ''' List all stack instances based on the StackSet name and Account Id ''' try: cfn_client = target_session.client('cloudformation') cfn_paginator = cfn_client.get_paginator('list_stack_instances') operation_parameters = { 'StackSetName': stack_set_name, 'StackInstanceAccount': account_id } stackset_result = cfn_paginator.paginate(**operation_parameters) stackset_list = [] for page in stackset_result: if 'Summaries' in page: stackset_list.extend(page['Summaries']) return stackset_list except Exception as e: LOGGER.error("List Stack Instance error: %s" % e) return [] def list_stack_instance_region(target_session, stack_set_name): ''' List all stack instances based on the StackSet name ''' try: cfn_client = target_session.client('cloudformation') cfn_paginator = cfn_client.get_paginator('list_stack_instances') operation_parameters = { 'StackSetName': stack_set_name } stackset_result = cfn_paginator.paginate(**operation_parameters) stackset_list_region = [] for page in stackset_result: for instance in page['Summaries']: stackset_list_region.append(instance['Region']) stackset_list_region=list(set(stackset_list_region)) return stackset_list_region except Exception as e: LOGGER.error("List Stack Instance error: %s" % e) return [] def create_stack_instance(target_session, stackset_name, account, regions): ''' Create stackset in particular account + region ''' try: cfn_client = target_session.client('cloudformation') response = cfn_client.create_stack_instances( StackSetName=stackset_name, Accounts=account, Regions=regions ) LOGGER.debug(response) LOGGER.info("Launched stackset instance {} for account {} in regions: {} with Operation id: {}".format(stackset_name, account, regions, response["OperationId"])) return True except Exception as e: LOGGER.error("Could not create stackset instance : {}".format(e)) return False def get_accounts_by_ou(target_session, ou_id): ''' List all active accounts by the OU id ''' try: org_client = target_session.client('organizations') org_paginator = org_client.get_paginator('list_accounts_for_parent') operation_parameters = { 'ParentId': ou_id } accounts_response = org_paginator.paginate(**operation_parameters) accounts_list = [] active_accounts_list = [] for page in accounts_response: if 'Accounts' in page: accounts_list.extend(page['Accounts']) for account in accounts_list: if account['Status'] == 'ACTIVE': active_accounts_list.append(account['Id']) return active_accounts_list except ClientError as e: LOGGER.error("Organization get accounts by OU error : {}".format(e)) return [] def lambda_handler(event, context): LOGGER.info('Lambda Handler - Start') LOGGER.info('REQUEST RECEIVED: {}'.format(json.dumps(event, default=str))) # Check if lifecycle even matches if 'detail' in event and event['detail']['eventName'] == 'CreateManagedAccount': if event['detail']['serviceEventDetails']['createManagedAccountStatus']['state'] == 'SUCCEEDED': account_id = event['detail']['serviceEventDetails']['createManagedAccountStatus']['account']['accountId'] #find if existing stackset instance for this account already exist stackset_name = (str(os.environ["ct_stack_set_arn"]).split(":")[5]).split("/")[1] stackset_instances = list_stack_instance_by_account(session, stackset_name, account_id) stackset_instances_regions = list_stack_instance_region(session, stackset_name) #stackset instance does not exist, create a new one if len(stackset_instances) == 0: create_stack_instance(session, stackset_name, [account_id], stackset_instances_regions) #stackset instance already exist, check for missing region elif len(stackset_instances) > 0: stackset_region = [] for instance in stackset_instances: stackset_region.append(instance['Region']) next_region = list(set(stackset_instances_regions) - set(stackset_region)) if len(next_region) > 0: create_stack_instance(session, stackset_name, [account_id], next_region) else: LOGGER.info("Stackset instance already exist : {}".format(stackset_instances)) else: LOGGER.error("Invalid event state, expected: SUCCEEDED : {}".format(event)) else: LOGGER.error("Invalid event received : {}".format(event)) LOGGER.info('Lambda Handler - End') FlowLogLifeCycleRole: Type: AWS::IAM::Role Properties: Description: FlowLog - Role used by lambda for life cycle / new account creation AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: StackSetPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - cloudformation:ListStackInstances - cloudformation:CreateStackInstances Resource: - !Ref StackSetArn - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*' CreateAccountLifeCycleRule: Type: AWS::Events::Rule Properties: Description: FlowLog - CT Life Cycle for CreateManageAccount EventPattern: { "source": [ "aws.controltower" ], "detail-type": [ "AWS Service Event via CloudTrail" ], "detail": { "eventSource": [ "controltower.amazonaws.com" ], "eventName": [ "CreateManagedAccount" ] } } State: ENABLED Targets: - Arn: !GetAtt CTLifeCycleTrigger.Arn Id: "OrganizationalUnitLifeCycle" CreateAccountLifeCycleRulePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !GetAtt CTLifeCycleTrigger.Arn Action: "lambda:InvokeFunction" Principal: "events.amazonaws.com" SourceArn: !GetAtt CreateAccountLifeCycleRule.Arn
- In this step, we will deploy a CloudFormation stack on the Control Tower management account. This will create an IAM Role, a Lambda function, and an EventBridge rule. These are required to setup the Control Tower lifecycle event.
- In the AWS CloudFormation console, navigate to Stacks and create new Stack.
- Choose Template is ready, and Upload a template file. Select Choose file and upload the template file (ct-lifecycle-event.yaml) that you created in the beginning of Step 11.
- In Specify Stack details provide a name for the stack.
- Provide the parameter for StackSetArn copied from Step 10 and choose Next.
- On the Configure Stack options page, choose Next. On the Review page, select the check box I acknowledge that AWS CloudFormation might create IAM resources with custom names, and choose Create stack.
- Copy the CloudFormation template below and save in a YAML file (ct-lifecycle-event.yaml).
Testing the solution
To test this solution in an AWS Account that is part of your Control Tower environment, create a new VPC without enabling VPC flow logs. The Config rule VPC_FLOW_LOGS_ENABLED will evaluate the missing flow log configuration on this VPC, and it executes the SSM automation document to enable the VPC flow logs.
To validate the solution, verify the centralized S3 bucket in the log archive account for the logs from your test VPC.
Cleaning up
You would need to pay for the storage cost for the VPC Flow logs being stored in the centralized S3 bucket part of this testing. If you would like to keep the logs for a longer period of time, we encourage you to transition the logs to Cold Storage (S3 Glacier) using S3 Lifecycle policies.
Please perform below steps for the clean-up.
- To remove the VPC Flow Logs StackSets, you must first delete the stack instances from the StackSets deployed in Step 3. Follow the instructions from AWS Documentation to remove the Stack instances.
- Once all the stack instances are deleted, you can proceed with deleting the StackSets created in Step 3. You can refer to Delete a stack set for the instructions.
- To remove the Lifecycle event from the Control Tower management account, navigate to the CloudFormation console and delete the stack that you’ve deployed in Step 11 for the Lifecycle event.
- Delete the VPC flow logs configuration of the test VPC that was created by the auto remediation. Follow the instructions here to delete the VPC flow logs configuration.
- The logs in the central logging S3 bucket can be deleted if they are not needed. Follow the instructions here to delete the objects from the S3 bucket content.
Conclusion
In this post, we demonstrated how to automate VPC Flow logs setup in a multi-account environment using AWS Config Rule Remediation. CloudFormation template and a Lambda function that deploys the entire solution.
We recommend you to consider packaging this solution as part of your customizations for Control Tower.
Author: