Control: 3.2 Ensure a log metric filter and alarm exist for Management Console sign-in without MFA
Description
Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. It is recommended that a metric filter and alarm be established for console logins that are not protected by multi-factor authentication (MFA).
Monitoring for single-factor console logins will increase visibility into accounts that are not protected by MFA.
Remediation
Perform the following to setup the metric filter, alarm, SNS topic, and subscription:
- Create a metric filter based on filter pattern provided which checks for AWS Management Console sign-in without MFA and the
<cloudtrail_log_group_name>
taken from audit step 1.
aws logs put-metric-filter --log-group-name <cloudtrail_log_group_name> -- filter-name `<no_mfa_console_signin_metric>` --metric-transformations metricName= `<no_mfa_console_signin_metric>` ,metricNamespace='CISBenchmark',metricValue=1 --filter-pattern '{ ($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") }'
Note: You can choose your own metricName and metricNamespace strings. Using the same metricNamespace for all Foundations Benchmark metrics will group them together.
- Create an SNS topic that the alarm will notify
aws sns create-topic --name <sns_topic_name>
Note: You can execute this command once and then re-use the same topic for all monitoring alarms.
- Create an SNS subscription to the topic created in step 2
aws sns subscribe --topic-arn <sns_topic_arn> --protocol <protocol_for_sns> - -notification-endpoint <sns_subscription_endpoints>
Note: You can execute this command once and then re-use the SNS subscription for all monitoring alarms.
- Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 1 and an SNS topic created in step 2
aws cloudwatch put-metric-alarm --alarm-name `<no_mfa_console_signin_alarm>` --metric-name `<no_mfa_console_signin_metric>` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold -- evaluation-periods 1 --namespace 'CISBenchmark' --alarm-actions <sns_topic_arn>
Usage
Run the control in your terminal:
powerpipe control run aws_compliance.control.cis_v120_3_2
Snapshot and share results via Turbot Pipes:
powerpipe loginpowerpipe control run aws_compliance.control.cis_v120_3_2 --share
SQL
This control uses a named query:
with trails as ( select trail.account_id, trail.name as trail_name, trail.is_logging, split_part(trail.log_group_arn, ':', 7) as log_group_name from aws_cloudtrail_trail as trail, jsonb_array_elements(trail.event_selectors) as se where trail.is_multi_region_trail is true and trail.is_logging and se ->> 'ReadWriteType' = 'All' and trail.log_group_arn is not null order by trail_name),alarms as ( select metric_name, action_arn as topic_arn from aws_cloudwatch_alarm, jsonb_array_elements_text(aws_cloudwatch_alarm.alarm_actions) as action_arn order by metric_name),topic_subscriptions as ( select subscription_arn, topic_arn from aws_sns_topic_subscription order by subscription_arn),metric_filters as ( select filter.name as filter_name, filter_pattern, log_group_name, metric_transformation_name from aws_cloudwatch_log_metric_filter as filter where filter.filter_pattern ~ '\(\s*\$\.eventName\s*=\s*"ConsoleLogin"\)\s+&&\s+\(\s*\$.additionalEventData\.MFAUsed\s*!=\s*"Yes"' order by filter_name),filter_data as ( select t.account_id, t.trail_name, f.filter_name from trails as t join metric_filters as f on f.log_group_name = t.log_group_name join alarms as alarm on alarm.metric_name = f.metric_transformation_name join topic_subscriptions as subscription on subscription.topic_arn = alarm.topic_arn)select distinct 'arn:' || a.partition || ':::' || a.account_id as resource, case when f.trail_name is null then 'alarm' else 'ok' end as status, case when f.trail_name is null then 'No log metric filter and alarm exist for console sign-in without MFA.' else filter_name || ' forwards events for console sign-in without MFA.' end as reason , a.account_idfrom aws_account as a left join filter_data as f on a.account_id = f.account_id;