Skip to content

Instantly share code, notes, and snippets.

@heitorlessa
Last active April 3, 2020 15:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save heitorlessa/5d2295655f9d76483969d215986e53b0 to your computer and use it in GitHub Desktop.
Save heitorlessa/5d2295655f9d76483969d215986e53b0 to your computer and use it in GitHub Desktop.
L@E - Aggregate Logs from all regions
AWSTemplateFormatVersion: 2010-09-09
Description: Quick snippet for main resources to aggregate L@E Logs
Parameters:
Stage:
Type: String
EdgeFunction:
Type: String
Description: Lambda@Edge Function Name
Resources:
EdgeAuthParameter:
Type: "AWS::SSM::Parameter"
Properties:
Name: !Sub /${Stage}/service/auth/edgeAuthFunctionName
Description: Edge Auth Lambda function name
Type: String
Value: !Ref EdgeAuthFunction
# Lambda@Edge Log aggregate
AggregateS3Bucket:
Type: AWS::S3::Bucket
EdgeLogsKinesisFirehoseStreamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
-
Effect: Allow
Principal:
Service: firehose.amazonaws.com
Action: 'sts:AssumeRole'
Policies:
-
PolicyName: kinesis_delivery_lambda_edge
PolicyDocument:
Version: 2012-10-17
Statement:
-
Effect: Allow
Action:
- 's3:AbortMultipartUpload'
- 's3:GetBucketLocation'
- 's3:GetObject'
- 's3:ListBucket'
- 's3:ListBucketMultipartUploads'
- 's3:PutObject'
Resource:
- !Sub "${AggregateS3Bucket.Arn}"
- !Sub "${AggregateS3Bucket.Arn}/*"
# NOTE: Enable it if you attach Kinesis Data Streams to Firehose
# -
# Effect: Allow
# Action:
# - 'kinesis:DescribeStream'
# - 'kinesis:GetShardIterator'
# - 'kinesis:GetRecords'
# - 'kinesis:ListShards'
# Resource: '*'
# NOTE: Enable it if you enable Log delivery in Kinesis Firehose
# NOTE2: We need both Log Group and Stream created upfront
# -
# Effect: Allow
# Action:
# - 'logs:PutLogEvents'
# Resource: !Sub KinesisFirehoseCloudWatchLogGroup.Arn
EdgeLogsKinesisFirehoseStream:
Type: AWS::KinesisFirehose::DeliveryStream
Properties:
ExtendedS3DestinationConfiguration:
BucketARN: !Sub "arn:aws:s3:::${AggregateS3Bucket}"
BufferingHints:
IntervalInSeconds: 60
SizeInMBs: 3
CompressionFormat: UNCOMPRESSED
Prefix: processed/
ErrorOutputPrefix: failed/
RoleARN: !GetAtt EdgeLogsKinesisFirehoseStreamRole.Arn
CloudwatchSubscriptionFiltersRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
-
Effect: Allow
Principal:
Service:
- logs.us-east-1.amazonaws.com
- logs.us-west-2.amazonaws.com
- logs.us-east-2.amazonaws.com
- logs.eu-central-1.amazonaws.com
- logs.eu-west-1.amazonaws.com
- logs.eu-west-2.amazonaws.com
- logs.eu-central-1.amazonaws.com
- logs.ap-south-1.amazonaws.com
- logs.ap-southeast-1.amazonaws.com
- logs.ap-southeast-2.amazonaws.com
- logs.ap-northeast-1.amazonaws.com
- logs.ap-northeast-2.amazonaws.com
- logs.sa-east-1.amazonaws.com
Action: 'sts:AssumeRole'
CloudwatchSubscriptionFilterPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: firehose_subscription_filter
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'kinesis:PutRecord'
- 'firehose:PutRecord'
- 'firehose:PutRecordBatch'
Resource: !Sub ${EdgeLogsKinesisFirehoseStream.Arn}
- Effect: Allow
Action: 'iam:PassRole'
Resource: !Sub ${CloudwatchSubscriptionFiltersRole.Arn}
Roles:
-
Ref: CloudwatchSubscriptionFiltersRole
CloudWatchSubscriptionRoleParameter:
Type: "AWS::SSM::Parameter"
Properties:
Name: !Sub /${Stage}/service/auth/cloudwatchSubscriptionRole
Description: CloudWatch IAM Role used for cross-region logs ingestion to Kinesis Firehose
Type: String
Value: !Sub ${CloudwatchSubscriptionFiltersRole.Arn}
KinesisFirehoseDeliveryParameter:
Type: "AWS::SSM::Parameter"
Properties:
Name: !Sub /${Stage}/service/auth/firehoseStream
Description: Kinesis Firehose Delivery Stream ARN
Type: String
Value: !Sub ${EdgeLogsKinesisFirehoseStream.Arn}
EdgeLogsAggregateS3BucketParameter:
Type: "AWS::SSM::Parameter"
Properties:
Name: !Sub /${Stage}/service/auth/logsBucket
Description: S3 Bucket where Lambda@Edge Logs are aggregated
Type: String
Value: !Ref AggregateS3Bucket
CloudformationStackSetAdminRole:
Type: "AWS::IAM::Role"
Properties:
RoleName: AWSCloudFormationStackSetAdministrationRole
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: cloudformation.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: AssumeRole-AWSCloudFormationStackSetExecutionRole
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Resource:
- "arn:aws:iam::*:role/AWSCloudFormationStackSetExecutionRole"
CloudformationStackSetExecutionRole:
Type: "AWS::IAM::Role"
Properties:
RoleName: AWSCloudFormationStackSetExecutionRole
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: cloudformation.amazonaws.com
Action: sts:AssumeRole
- Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
Action: sts:AssumeRole
Policies:
- PolicyName: StackSet-Execution-Admin0Permissions
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: '*'
Resource: '*'
Outputs :
AggregateS3BucketDomainName:
Description: S3 bucket domain name
Value: !Sub ${AggregateS3Bucket}
EdgeLogsKinesisFirehoseStreamArn:
Description: Kinesis Delivery Stream
Value: !Sub ${EdgeLogsKinesisFirehoseStream.Arn}
CloudwatchSubscriptionFiltersRole:
Description: CloudWatch role for subscription filters
Value: !Sub ${CloudwatchSubscriptionFiltersRole.Arn}
## Lambda-Edge centralize log stacks
STACKSET_REGIONS = eu-west-2 eu-central-1 ap-south-1 ap-northeast-2 ap-northeast-1 ap-southeast-1 ap-southeast-2 sa-east-1 us-east-1 us-east-2 us-west-2
centralize-auth-logs:
$(info [+] Deploying Log centralization for Lambda@Edge - Log aggregation across regions)
export STACK_SET=$(shell aws cloudformation list-stack-sets --query 'Summaries[?Status==`ACTIVE` && StackSetName==`lambda-edge-log-groups-stackset`].StackSetName' --output text)
echo $(STACK_SET)
# If CloudFormation Stackset already exists, execute new version only
test -n $(STACK_SET) \
&& $(MAKE) _execute_stackset \
|| echo "[*] No stackset found... creating one"
# If a CloudFormation Stackset does not exist, create and execute one
test -z $(STACK_SET) \
&& $(MAKE) _create_stackset && $(MAKE) _execute_stackset \
|| echo "[!] Failed to create stackset..."
_create_stackset:
$(info [+] Deploying Log centralization for Lambda@Edge - Creating Stackset)
aws cloudformation create-stack-set --template-body file://regional-stack.yaml \
--stack-set-name lambda-edge-log-groups-stackset \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--parameters \
ParameterKey=LambdaEdgeFunctionName,ParameterValue=$(shell aws ssm get-parameter --name /prod/service/auth/edgeAuthFunctionName --query 'Parameter.Value' --output text --region ${INFRASTRUCTURE_REGION}) \
ParameterKey=CloudWatchRoleArn,ParameterValue=$(shell aws ssm get-parameter --name /prod/service/auth/cloudwatchSubscriptionRole --query 'Parameter.Value' --output text --region ${INFRASTRUCTURE_REGION}) \
ParameterKey=FirehoseDestinationArn,ParameterValue=$(shell aws ssm get-parameter --name /prod/service/auth/firehoseStream --query 'Parameter.Value' --output text --region ${INFRASTRUCTURE_REGION})
_execute_stackset:
$(info [+] Deploying Log centralization for Lambda@Edge - Creating CloudFormation Stack across regions)
aws cloudformation create-stack-instances --stack-set-name lambda-edge-log-groups-stackset \
--accounts $(shell aws sts get-caller-identity --query 'Account' --output text) \
--regions $(STACKSET_REGIONS) \
--operation-preferences FailureToleranceCount=1
AWSTemplateFormatVersion: 2010-09-09
Description: Quick snippet for regional resources to ship L@E Logs to a central location via CW Subscription Filters -> Kinesis FireHose
Parameters:
LambdaEdgeFunctionName:
Description: The name of the LambdaEdge to apply the subscription filter to.
Type: String
MinLength: 1
CloudWatchRoleArn:
Description: The CloudWatch role ARN used to apply the subscription filter.
Type: String
MinLength: 1
FirehoseDestinationArn:
Description: The ARN for the Firehose delivery stream where logs will be aggregated.
Type: String
MinLength: 1
FilterPattern:
Description: A symbolic description of how CloudWatch Logs should interpret the data in each log event, along with filtering expressions that restrict what gets delivered to the destination AWS resource. Please note - If left blank, the subscription filter will match all log events.
Type: String
Default: ''
Conditions:
isLondonRegion: !Equals [ !Ref 'AWS::Region', 'eu-west-2' ]
isFrankfurtRegion: !Equals [ !Ref 'AWS::Region', 'eu-central-1' ]
isMumbaiRegion: !Equals [ !Ref 'AWS::Region', 'ap-south-1' ]
isSeoulRegion: !Equals [ !Ref 'AWS::Region', 'ap-northeast-2' ]
isTokyoRegion: !Equals [ !Ref 'AWS::Region', 'ap-northeast-1' ]
isSingaporeRegion: !Equals [ !Ref 'AWS::Region', 'ap-southeast-1' ]
isSydneyRegion: !Equals [ !Ref 'AWS::Region', 'ap-southeast-2' ]
isSaoPauloRegion: !Equals [ !Ref 'AWS::Region', 'sa-east-1' ]
isNVirginiaRegion: !Equals [ !Ref 'AWS::Region', 'us-east-1' ]
isOhioRegion: !Equals [ !Ref 'AWS::Region', 'us-east-2' ]
isOregonRegion: !Equals [ !Ref 'AWS::Region', 'us-west-2' ]
IsCloudFrontRegionalCachePt1: !Or # Max of 10 conditions
- !Condition isLondonRegion
- !Condition isFrankfurtRegion
- !Condition isMumbaiRegion
- !Condition isSeoulRegion
- !Condition isTokyoRegion
- !Condition isSingaporeRegion
- !Condition isSydneyRegion
- !Condition isSaoPauloRegion
IsCloudFrontRegionalCachePt2: !Or
- !Condition isNVirginiaRegion
- !Condition isOhioRegion
- !Condition isOregonRegion
IsCloudFrontRegionalCache: !Or
- !Condition IsCloudFrontRegionalCachePt1
- !Condition IsCloudFrontRegionalCachePt2
Resources:
# Key resources that should be deployed where CloudFront has Regional Cache
RegionalCloudWatchLogGroupCustomResource:
Condition: IsCloudFrontRegionalCache
Type: 'AWS::Lambda::Function'
Properties:
Code:
ZipFile: >
const response = require('cfn-response');
const aws = require('aws-sdk');
exports.handler = async (event, context, callback) => {
const logGroupName = event.ResourceProperties.logGroupName;
const cloudwatchlogs = new aws.CloudWatchLogs();
const doesLogGroupExist = async (logGroupName) => {
console.info(`Verifying if ${logGroupName} exists`)
let params = {
logGroupNamePrefix: logGroupName
}
let logGroupStatus = false
try {
const ret = await cloudwatchlogs.describeLogGroups(params)
const logGroups = ret && ret.data && ret.data.logGroups
console.log(`Log Group found: ${logGroups}`)
if (!logGroups) return logGroupStatus
if (logGroups.length > 0) logGroupStatus = true
return logGroupStatus
} catch (error) {
console.error(error)
return logGroupStatus
}
}
const createLogGroup = async (logGroupName) => {
var params = {
logGroupName: logGroupName
}
console.info(`Creating Log Group ${logGroupName}`)
return cloudwatchlogs.createLogGroup(params).promise()
}
if (event.RequestType == 'Delete' || event.RequestType == 'Update') {
// don't delete
console.info("No action to be taken upon DELETE || UPDATE.")
return response.send(event, context, response.SUCCESS, {})
}
if (event.RequestType == 'Create') {
const logGroupExists = await doesLogGroupExist(logGroupName)
if (!logGroupExists) {
try {
console.info(`Log Group ${logGroupName} doesn't exist`)
await createLogGroup(logGroupName)
console.log(`Log Group ${logGroupName} created successfully`)
return response.send(event, context, response.SUCCESS, {})
} catch (error) {
console.error("Error while creating Log Group")
return response.send(event, context, response.FAILED, { error })
}
} else {
console.info(`Log Group ${logGroupName} already exists; ignoring Create signal`)
return response.send(event, context, response.SUCCESS, {})
}
}
}
Handler: index.handler
Runtime: nodejs10.x
Timeout: 300
Role: !Sub ${RegionalCloudWatchLogGroupCustomResourceRole.Arn}
RegionalCloudWatchLogGroupCustomResourceRole:
Condition: IsCloudFrontRegionalCache
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
-
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: 'sts:AssumeRole'
Policies:
-
PolicyName: cloudwatch_log_groups
PolicyDocument:
Version: 2012-10-17
Statement:
-
Effect: Allow
Action:
- 'logs:*' # FIXME - Describe/Create should work
Resource: '*'
RegionalCloudWatchLogGroup:
Condition: IsCloudFrontRegionalCache
Type: 'Custom::CloudWatchLogGroupEdge'
Properties:
ServiceToken: !Sub ${RegionalCloudWatchLogGroupCustomResource.Arn}
logGroupName: !Sub '/aws/lambda/us-east-1.${LambdaEdgeFunctionName}'
SubscriptionFilter:
Condition: IsCloudFrontRegionalCache
Type: AWS::Logs::SubscriptionFilter
Properties:
DestinationArn: !Ref FirehoseDestinationArn
FilterPattern: !Ref FilterPattern
LogGroupName: !Sub '/aws/lambda/us-east-1.${LambdaEdgeFunctionName}'
RoleArn: !Ref CloudWatchRoleArn
DependsOn: RegionalCloudWatchLogGroup
# Key resources that should be deployed where CloudFront has Regional Cache
Outputs:
RegionalCloudWatchLogGroup:
Condition: IsCloudFrontRegionalCache
Description: Custom Resource for CloudWatch Log Group
Value: !Sub ${RegionalCloudWatchLogGroupCustomResource.Arn}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment