Skip to content

Instantly share code, notes, and snippets.

@chalfant
Created June 7, 2016 12:57
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save chalfant/d4fe1b2bffbc4c1cc55e552133db961a to your computer and use it in GitHub Desktop.
Save chalfant/d4fe1b2bffbc4c1cc55e552133db961a to your computer and use it in GitHub Desktop.
Create monitoring metric filters and alarms for CIS Benchmarks for AWS
#!/usr/bin/env ruby
# Implement CIS Benchmarks for AWS Section 3.x
# Details on each benchmark from https://benchmarks.cisecurity.org/downloads/show-single/?file=awsfoundations.100
# name should be in camelcase since we'll use it for filter and alarm names
filters = [
{
benchmark: '3.1',
description: 'Ensure a log metric filter and alarm exist for unauthorized API calls',
level: 1,
name: 'UnauthorizedApiCalls',
pattern: '{ ($.errorCode = "*UnauthorizedOperation") || ($.errorCode = "AccessDenied*") }'
},
{
benchmark: '3.2',
description: 'Ensure a log metric filter and alarm exist for Management Console sign-in without MFA',
level: 1,
name: 'NoMfaConsoleLogins',
pattern: '{ $.userIdentity.sessionContext.attributes.mfaAuthenticated != "true" && $.userIdentity.invokedBy = "signin.amazonaws.com" }'
},
{
benchmark: '3.3',
description: 'Ensure a log metric filter and alarm exist for usage of "root" account',
level: 1,
name: 'RootAccountLogins',
pattern: '{ $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }'
},
{
benchmark: '3.4',
description: 'Ensure a log metric filter and alarm exist for IAM policy changes',
level: 1,
name: 'IamPolicyChanges',
pattern: '{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)}'
},
{
benchmark: '3.5',
description: 'Ensure a log metric filter and alarm exist for CloudTrail configuration changes',
level: 1,
name: 'CloudTrailConfigurationChanges',
pattern: '{ ($.eventName = CreateTrail) ||($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }'
},
{
benchmark: '3.6',
description: 'Ensure a log metric filter and alarm exist for AWS Management Console authentication failures',
level: 2,
name: 'FailedConsoleLogins',
pattern: '{ ($.eventName = ConsoleLogin) && ($.errorMessage = "Failed authentication") }'
},
{
benchmark: '3.7',
description: 'Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs',
level: 2,
name: 'DisabledOrDeletedCmks',
pattern: '{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion))}'
},
{
benchmark: '3.8',
description: 'Ensure a log metric filter and alarm exist for S3 bucket policy changes',
level: 1,
name: 'S3BucketPolicyChanges',
pattern: '{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }'
},
{
benchmark: '3.9',
description: 'Ensure a log metric filter and alarm exist for AWS Config configuration changes',
level: 2,
name: 'AwsConfigChanges',
pattern: '{($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel)||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder))}'
},
{
benchmark: '3.10',
description: 'Ensure a log metric filter and alarm exist for security group changes',
level: 2,
name: 'SecurityGroupChanges',
pattern: '{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup)}'
},
{
benchmark: '3.11',
description: 'Ensure a log metric filter and alarm exist for changes to Network Access Control Lists',
level: 2,
name: 'NetworkAccessControlListChanges',
pattern: '{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }'
},
{
benchmark: '3.12',
description: 'Ensure a log metric filter and alarm exist for changes to network gateways',
level: 1,
name: 'NetworkGatewayChanges',
pattern: '{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }'
}
]
require 'aws-sdk'
# The name (not ARN) of your CloudTrail Log Group
CLOUDTRAIL_LOG_GROUP = ENV['CLOUDTRAIL_LOG_GROUP']
# The ARN of an SNS topic the alarm will post to
ALARM_SNS_TOPIC = ENV['ALARM_SNS_TOPIC']
# Namespace used for metrics created by metric filters
METRIC_NAMESPACE = 'CisBenchmarks'
def filter_name(filter)
"#{filter[:name]}Filter"
end
def alarm_name(filter)
"#{filter[:name]}Alarm"
end
def metric_filter_exists?(filter)
cwl = Aws::CloudWatchLogs::Client.new(region: 'us-east-1')
resp = cwl.describe_metric_filters({
log_group_name: CLOUDTRAIL_LOG_GROUP,
filter_name_prefix: filter_name(filter)
})
resp.metric_filters.each do |metric_filter|
if metric_filter.filter_name == filter_name(filter)
return true
end
end
return false
end
# filter: a record from the hash above
def create_metric_filter(filter)
filter_name = filter_name(filter)
cwl = Aws::CloudWatchLogs::Client.new(region: 'us-east-1')
resp = cwl.put_metric_filter({
log_group_name: CLOUDTRAIL_LOG_GROUP,
filter_name: filter_name,
filter_pattern: filter[:pattern],
metric_transformations: [
{
metric_name: filter[:name],
metric_namespace: METRIC_NAMESPACE,
metric_value: '1'
}
]
})
end
def metric_alarm_exists?(filter)
cw = Aws::CloudWatch::Resource.new(region: 'us-east-1')
alarms = cw.alarms({
alarm_names: [ alarm_name(filter) ]
})
alarms.each do |alarm|
if alarm.alarm_name == alarm_name(filter)
return true
end
end
return false
end
# filter: a record from the hash above
def create_metric_alarm(filter)
alarm_name = alarm_name(filter)
alarm_description = "CIS Benchmark Alarm for #{filter[:benchmark]} #{filter[:name]}"
cw = Aws::CloudWatch::Client.new(region: 'us-east-1')
resp = cw.put_metric_alarm({
alarm_name: alarm_name,
actions_enabled: true,
alarm_actions: [ ALARM_SNS_TOPIC ],
metric_name: filter[:name],
namespace: METRIC_NAMESPACE,
statistic: 'Sum',
period: 300,
evaluation_periods: 1,
threshold: 1.0,
comparison_operator: 'GreaterThanOrEqualToThreshold'
})
end
## MAIN
filters.each do |filter|
puts "Checking #{filter[:benchmark]}: #{filter[:description]}..."
if !metric_filter_exists?(filter)
puts "\tCreating #{filter_name(filter)}."
create_metric_filter(filter)
else
puts "\t#{filter_name(filter)} exists."
end
if !metric_alarm_exists?(filter)
puts "\tCreating #{alarm_name(filter)}."
create_metric_alarm(filter)
else
puts "\t#{alarm_name(filter)} exists."
end
puts ""
end
@Osias-Nepomuceno-Jr
Copy link

Hi chalfant, how can I load this to my AWS account. do I need to load this in Cloudformation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment