CIS AWS Foundations Benchmark

First and foremost, Happy Greek National Day! (It just so happens today is the day)

This article emanates directly from a very real need I encountered when trying to manage 9 AWS accounts simulatniously. I wanted to make sure to set some alarms and alerts for each account, in order to somewhat automate the overall account security.

I reality, the origins of this need are nested in the AWS Security Hub and the CIS AWS Foundations Benchmark standard (but let’s not make us look silly).

Here is a list of all the controls that the standard requires you to implement. It is easy to see how creating this manually would be a major pain in the back.

In come,… CloudFormation. (yes, yes Vali, the day has come where you see me praise CloudFormation).

The template below will set the alarms and alerts required to satisfy the CIS AWS Foundations Benchmark.

Disclamer: I did not create this! If I still had the source, I would definitely slap it here as it deserves credit!

---
AWSTemplateFormatVersion: '2010-09-09'
Description: Automate Section 3 - Monitoring of the CIS AWS Foundations Benchmark controls and send alarms to an email address.
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: ALARM SNS TOPIC
      Parameters:
      - SetEmail
      - SetALARMTopicName
      - SetALARMTopicDisplayName
    - Label:
        default: CLOUDTRAIL SNS TOPIC
      Parameters:
      - SetTRAILTopicName
      - SetTRAILTopicDisplayName
    - Label:
        default: S3 & CLOUDTRAIL
      Parameters:
      - PreExistingS3BucketCloudTrail
      - CrossAccount01
      - CrossAccount02
      - CrossAccount03
      - CloudTrailLogGroupName
      - NameCloudTrailRole
    - Label:
        default: CLOUDWATCH
      Parameters:
      - CloudWatchLogsRetention
    - Label:
        default: NAME YOUR METRIC NAMESPACE 
      Parameters:  
      - NameYourNameSpace
    - Label:
        default: NAME YOUR ALARMS & METRIC FILTER NAMES
      Parameters:
      - 31UnauthAPICalls
      - 32MgmtConsoleNoMFA
      - 33UseOfRootAcct
      - 34IAMPolicyChanges
      - 35CloudTrailConfigChanges
      - 36ConsoleAuthFailures
      - 37DisableDeleteCMK
      - 38S3BucketPolicyChanges
      - 39AWSConfigChanges
      - 310SecGroupChanges
      - 311NACLChanges
      - 312NetworkGatewayChanges
      - 313RouteTableChanges
      - 314VPCChanges
    ParameterLabels:
      SetEmail:
        default: Alarm Email
      SetALARMTopicName:
        default: (OPTIONAL) Alarm Topic Name
      SetALARMTopicDisplayName:
        default: (SEMI-OPTIONAL) Alarm Topic Display Name
      SetTRAILTopicName:
        default: (OPTIONAL) Cloudtrail Topic Name
      SetTRAILTopicDisplayName:
        default: (SEMI-OPTIONAL) Cloudtrail Topic Display Name
      CloudWatchLogsRetention:
        default: CloudWatch Log Retention
      PreExistingS3BucketCloudTrail:
        default: CloudTrail S3 Bucket
      CrossAccount01:
        default: Cross-Account 01
      CrossAccount02:
        default: Cross-Account 02
      CrossAccount03:
        default: Cross-Account 03
      CloudTrailLogGroupName:
        default: (OPTIONAL) CloudTrail Log Group Name
      NameCloudTrailRole:
        default: CloudTrail Role Name
      NameYourNameSpace:
        default: Metric Name Space
      31UnauthAPICalls:
        default: Unauthorized API Calls
      32MgmtConsoleNoMFA:
        default: Management Console Sign-in w/o MFA
      33UseOfRootAcct:
        default: Usage of "root" Account
      34IAMPolicyChanges:
        default: IAM Policy Changes
      35CloudTrailConfigChanges:
        default: CloudTrail Configuration Changes
      37DisableDeleteCMK:
        default: Disabling / Deletion of CMKs
      38S3BucketPolicyChanges:
        default: S3 Bucket Policy Changes
      39AWSConfigChanges:
        default: AWS Config Changes
      310SecGroupChanges:
        default: Security Group Changes
      311NACLChanges:
        default: NACL Changes
      312NetworkGatewayChanges:
        default: Network Gateway Changes
      313RouteTableChanges:
        default: Route Table Changes
      314VPCChanges:
        default: VPC Changes

Parameters:
  
  SetEmail:
    Type: String
    Default: "example@example.com"
    Description: Triggered alarms will alert to this email address. Verification required and you can opt-out later. Make sure to validate otherwise you won't be able to delete the subscription and have to wait 3 days for automatic deletion.
    # REGEX Help:
    # We could get fancy with some serious REGEX action here but keep it simple.
    # This allows one or more characters (.+) in front and behind the @ symbol.
    AllowedPattern: ".+@.+"
    ConstraintDescription: You must enter one or more characters before and after the @ symbol. 
  
  SetALARMTopicName:
    Type: String
    Default: CIS-BENCHMARK-ALARMS
    Description: "Set a unique name for your CloudWatch Alarm SNS Topic. If you don't specify a name, a random name like <stack name>-AlarmNotificationTopic-9VWR9AVOB5QS will be automatically generated."
    MinLength: '0'
    MaxLength: '256'
    AllowedPattern: "[\\w_-]*"
  
  SetALARMTopicDisplayName:
    Type: String
    Default: cis-alarm
    Description: "REQUIRED for SMS. Can only use up to 10 characters from the AWS console."
    MinLength: '0'
    MaxLength: '10'
    AllowedPattern: "[\\w_-]*"
  
  SetTRAILTopicName:
    Type: String
    Default: CIS-BENCHMARK-CLOUDTRAIL
    Description: "Set a unique name for your CloudTrail SNS Topic. If you don't specify a name, a random name like <stack name>-TrailTopic-9VWR9AVOB5QS will be automatically generated."
    MinLength: '0'
    MaxLength: '256'
    AllowedPattern: "[\\w_-]*"
  
  SetTRAILTopicDisplayName:
    Type: String
    Default: cis-cldtrl
    Description: "REQUIRED for SMS. Can only use up to 10 characters from the AWS console."
    MinLength: '0'
    MaxLength: '10'
    AllowedPattern: "[\\w_-]*"
  
  CloudWatchLogsRetention:
    Description: The number of days log events are kept in CloudWatch Logs
    Type: Number
    Default: 90
    AllowedValues:
    - 1
    - 3
    - 5
    - 7
    - 14
    - 30
    - 60
    - 90
    - 120
    - 150
    - 180
    - 365
    - 400
    - 545
    - 731
    - 1827
    - 3653
  
  PreExistingS3BucketCloudTrail:
    #Description: "Enter an unique name for your EXISTING CloudTrail S3 bucket that only contains lowercase letters, numbers, periods (.), and dashes (-). If you don't specify a name, a random name like \"<stack name>-cloudtrails3bucket-2wpe5iffv606\" will be automatically generated. Note that this bucket's DeletionPolicy is set to Retain. (i.e. Stack deletion will not delete the bucket.)"
    Description: "Enter the name of your EXISTING root S3 bucket for S3 log collection or the export name of the bucket to send logs to. Note that the templates have to be in the same region."
    Type: String
    # Typically S3 bucket names must be between 3 and 63 characters but since we want to allow an empty string, set MinLength to '0'.
    # Ref: http://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
    MinLength: '0'
    MaxLength: '63'
    # REGEX Help:
    # This allows one or more (+) lower case alphanumeric characters (a-z0-9), no spaces, and the characters '.' (period) and '-' (hyphen) OR an empty string: ^(|)$
    AllowedPattern: "^(|[a-z0-9.-]+)$"
    ConstraintDescription: The bucket name must contain only lowercase letters, numbers, periods (.), and dashes (-).
  
  CrossAccount01:
    Description: "Enter the account name from where you'd like to receive CloudTrail logs. Leave empty if none."
    Type: String
    AllowedPattern: "(|\\d{12})"
    ConstraintDescription: AWS accounts are 12 digits long. Enter 12 digits or leave blank.

  CrossAccount02:
    Description: "Enter the account name from where you'd like to receive CloudTrail logs. Leave empty if none."
    Type: String
    AllowedPattern: "(|\\d{12})"
    ConstraintDescription: AWS accounts are 12 digits long. Enter 12 digits or leave blank.

  CrossAccount03:
    Description: "Enter the account name from where you'd like to receive CloudTrail logs. Leave empty if none."
    Type: String
    AllowedPattern: "(|\\d{12})"
    ConstraintDescription: AWS accounts are 12 digits long. Enter 12 digits or leave blank.

  CloudTrailLogGroupName:
    Description: Enter a name for your CloudTrail LogGroup.
    Type: String
    Default: CloudTrail/DefaultLogGroup
    MinLength: '0'
    MaxLength: '512'
    AllowedPattern: "[\\w-/.]+"
    ConstraintDescription: Log group names can be between 1 and 512 characters long.
      Allowed characters include a-z, A-Z, 0-9, '_' (underscore), '-' (hyphen), '/'
      (forward slash), and '.' (period).
  
  NameCloudTrailRole:
    Description: "A unique name for a CloudTrail Role to be created that contains alphanumeric characters, no spaces, and the characters: = , . @ -. If you don't specify a name, a random name like \"<stack name>-TrailLogGroupRole-1OFN28AF4AUPV\" will be automatically generated."
    Type: String
    MinLength: '0'
    MaxLength: '64'
    # REGEX Help:
    # This allows one or more (+) alphanumeric characters (\w) with the symbols =,.@- OR an empty string: ^(|)$
    AllowedPattern: "^(|[|\\w=,.@-]+)$"
    ConstraintDescription: "The role name must contain only upper and lowercase alphanumeric characters with no spaces. These characters are alllowed: = , . @ -"
  
  NameYourNameSpace:
    Description: "A namespace is a container for CloudWatch metrics. Metrics in different namespaces are isolated from each other, so that metrics from different applications are not mistakenly aggregated into the same statistics. Possible characters are: alphanumeric characters (0-9A-Za-z), period (.), hyphen (-), underscore (_), forward slash (/), hash (#), and colon (:)."
    Type: String
    MaxLength: '256'
    Default: CIS-3-MONITORING
  
  31UnauthAPICalls:
    Description: 3.01 Ensure a log metric filter and alarm exist for unauthorized API calls 
    Type: String
    Default: CIS Benchmark v.1.1 - 3.01 Unauthorized API Calls
    MinLength: '1'
    MaxLength: '255'
    # REGEX Help:
    # This allows one or more (+) alphanumeric characters (\w) with the symbols -_/.
    # Must escape "\" and symbols with "\"
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  32MgmtConsoleNoMFA:
    Description: 3.02 Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)
    Type: String
    Default: CIS Benchmark v.1.1 - 3.02 Management Console Sign-in Without MFA
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  33UseOfRootAcct:
    Description: 3.03 Ensure a log metric filter and alarm exist for usage of "root" account (Scored)
    Type: String
    # Be careful - can't use quotes in Metric Filter name so "root" becomes root.
    Default: CIS Benchmark v.1.1 - 3.03 Usage of root Account
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  34IAMPolicyChanges:
    Description: 3.04 Ensure a log metric filter and alarm exist for IAM policy changes (Scored)
    Type: String
    Default: CIS Benchmark v.1.1 - 3.04 IAM Policy Changes
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  35CloudTrailConfigChanges:
    Description: 3.05 Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored)
    Type: String
    Default: CIS Benchmark v.1.1 - 3.05 CloudTrail Config Changes
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  36ConsoleAuthFailures:
    Description: 3.06 Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored)
    Type: String
    Default: CIS Benchmark v.1.1 - 3.06 AWS Management Console Authentication Failures
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  37DisableDeleteCMK:
    Description: 3.07 Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored)
    Type: String
    Default: CIS Benchmark v.1.1 - 3.07 Disabling or Scheduled Deletion of Customer Created CMKs
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  38S3BucketPolicyChanges:
    Description: 3.08 Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored)
    Type: String
    Default: CIS Benchmark v.1.1 - 3.08 S3 Bucket Policy Changes
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  39AWSConfigChanges:
    Description: 3.09 Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)
    Type: String
    Default: CIS Benchmark v.1.1 - 3.09 AWS Config Configuration Changes
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  310SecGroupChanges:
    Description: 3.10 Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)
    Type: String
    Default: CIS Benchmark v.1.1 - 3.10 Security Group Changes
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  311NACLChanges:
    Description: 3.11 Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored)
    Type: String
    # Be careful - "()" not allowed so (NACL) becomes NACL.
    Default: CIS Benchmark v.1.1 - 3.11 Changes to Network Access Control Lists NACL
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  312NetworkGatewayChanges:
    Description: 3.12 Ensure a log metric filter and alarm exist for changes to network gateways (Scored)
    Type: String
    Default: CIS Benchmark v.1.1 - 3.12 Changes to Network Gateways
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  313RouteTableChanges:
    Description: 3.13 Ensure a log metric filter and alarm exist for route table changes (Scored)
    Type: String
    Default: CIS Benchmark v.1.1 - 3.13 Route Table Changes
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"
  
  314VPCChanges:
    Description: 3.14 Ensure a log metric filter and alarm exist for VPC changes (Scored)
    Type: String
    Default: CIS Benchmark v.1.1 - 3.14 VPC Changes
    MinLength: '1'
    MaxLength: '255'
    AllowedPattern: "[\\w\\s-_/.]+"
    ConstraintDescription: "Metric names can contain up to 255 alphanumeric characters, with the following also allowed: -_./"

Conditions:
  
  # Set these little monkeys to FALSE if no input from the user.
  NeedCloudTrailS3Bucket: !Equals [!Ref PreExistingS3BucketCloudTrail, ""]
  NeedCrossAccount01: !Equals [!Ref CrossAccount01, ""]
  NeedCrossAccount02: !Equals [!Ref CrossAccount02, ""]
  NeedCrossAccount03: !Equals [!Ref CrossAccount03, ""]
  WantALARMTopicName: !Equals [!Ref SetALARMTopicName, ""]
  NeedTRAILTopicName: !Equals [!Ref SetTRAILTopicName, ""]
  WantCloudTrailRoleName: !Equals [!Ref NameCloudTrailRole, ""]

Resources:
  
  CloudTrailS3Bucket:
    Condition: NeedCloudTrailS3Bucket
    DeletionPolicy: Retain
    Type: AWS::S3::Bucket
    Properties:
      # BucketName not required but auto-generated one looks sloppy. 
      # Reference: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html#cfn-s3-bucket-name
      BucketName: !If [NeedCloudTrailS3Bucket, !Ref "AWS::NoValue", !Ref PreExistingS3BucketCloudTrail]
  
  CloudTrailS3BucketPolicy:
    #Condition: NeedCloudTrailS3Bucket
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !If [NeedCloudTrailS3Bucket, !Ref CloudTrailS3Bucket, !Ref PreExistingS3BucketCloudTrail]
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Sid: AWSCloudTrailAclCheck
          Effect: Allow
          Principal:
            Service: cloudtrail.amazonaws.com
          Action: s3:GetBucketAcl
          Resource:
            Fn::Join:
            - ''
            - - 'arn:aws:s3:::'
              - !If [NeedCloudTrailS3Bucket, !Ref CloudTrailS3Bucket, !Ref PreExistingS3BucketCloudTrail]
        - Sid: AWSCloudTrailWriteAdminS3
          Effect: Allow
          Principal:
            Service: cloudtrail.amazonaws.com
          Action: s3:PutObject
          Resource:
            - Fn::Join:
              - ''
              - - 'arn:aws:s3:::'
                - !If [NeedCloudTrailS3Bucket, !Ref CloudTrailS3Bucket, !Ref PreExistingS3BucketCloudTrail]
                - "/AWSLogs/"
                - Ref: AWS::AccountId
                - "/*"
            - Fn::Join:
              - ''
              - - 'arn:aws:s3:::'
                - !If [NeedCloudTrailS3Bucket, !Ref CloudTrailS3Bucket, !Ref PreExistingS3BucketCloudTrail]
                - "/AWSLogs/"
                # Not sure if this is the best way, but use default account if not CrossAccount specified.
                - !If [NeedCrossAccount01, !Ref "AWS::AccountId", !Ref CrossAccount01]
                - "/*"
            - Fn::Join:
              - ''
              - - 'arn:aws:s3:::'
                - !If [NeedCloudTrailS3Bucket, !Ref CloudTrailS3Bucket, !Ref PreExistingS3BucketCloudTrail]
                - "/AWSLogs/"
                - !If [NeedCrossAccount02, !Ref "AWS::AccountId", !Ref CrossAccount02]
                - "/*"
            - Fn::Join:
              - ''
              - - 'arn:aws:s3:::'
                - !If [NeedCloudTrailS3Bucket, !Ref CloudTrailS3Bucket, !Ref PreExistingS3BucketCloudTrail]
                - "/AWSLogs/"
                - !If [NeedCrossAccount03, !Ref "AWS::AccountId", !Ref CrossAccount03]
                - "/*"
          Condition:
            StringEquals:
              s3:x-amz-acl: bucket-owner-full-control
  
  TrailLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      RetentionInDays:
        Ref: CloudWatchLogsRetention
  
  TrailLogGroupRole:
    Type: AWS::IAM::Role
    Properties:
      # RoleName not required but looks sloppy if not set.
      # If the user didn't input a name (i.e. FALSE condition), "AWS::NoValue" removes the RoleName property and a random name for the IAM role will be created.
      RoleName: !If [WantCloudTrailRoleName, !Ref "AWS::NoValue", !Ref NameCloudTrailRole]
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Sid: AssumeRole1
          Effect: Allow
          Principal:
            Service: cloudtrail.amazonaws.com
          Action: sts:AssumeRole
      Policies:
      - PolicyName: cloudtrail-policy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Sid: AWSCloudTrailCreateLogStream2014110
            Effect: Allow
            Action:
            - logs:CreateLogStream
            Resource:
            - Fn::GetAtt:
              - TrailLogGroup
              - Arn
          - Sid: AWSCloudTrailPutLogEvents20141101
            Effect: Allow
            Action:
            - logs:PutLogEvents
            Resource:
            - Fn::GetAtt:
              - TrailLogGroup
              - Arn
  
  TrailTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName:
        Ref: SetTRAILTopicDisplayName
      TopicName: !If [NeedTRAILTopicName, !Ref "AWS::NoValue", !Ref SetTRAILTopicName]
  
  TrailTopicPolicy:
    DependsOn:
    - TrailTopic
    Type: AWS::SNS::TopicPolicy
    Properties:
      PolicyDocument:
        Version: '2008-10-17'
        Statement:
        - Sid: AWSCloudTrailSNSPolicy
          Effect: Allow
          Principal:
            Service: cloudtrail.amazonaws.com
          Resource:
            Ref: TrailTopic
          Action: sns:Publish
      Topics:
      - Ref: TrailTopic
  
  Trail:
    DependsOn:
    - CloudTrailS3BucketPolicy
    - TrailTopicPolicy
    Type: AWS::CloudTrail::Trail
    Properties:
      IncludeGlobalServiceEvents: true
      IsLogging: true
      IsMultiRegionTrail: true
      # If the user didn't input a name (i.e. FALSE condition), use the auto-generated bucket name created in resource name CloudTrailS3Bucket. Cannot use "AWS::NoValue" because property S3BucketName is required. Ref: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudtrail-trail.html#cfn-cloudtrail-trail-s3bucketname
      S3BucketName: !If [NeedCloudTrailS3Bucket, !Ref CloudTrailS3Bucket, !Ref PreExistingS3BucketCloudTrail]
      CloudWatchLogsLogGroupArn:
        Fn::GetAtt:
        - TrailLogGroup
        - Arn
      CloudWatchLogsRoleArn:
        Fn::GetAtt:
        - TrailLogGroupRole
        - Arn
      SnsTopicName:
        Fn::GetAtt:
        - TrailTopic
        - TopicName
  
  EmailAlarmNotificationTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName:
        Ref: SetALARMTopicDisplayName
      TopicName: !If [WantALARMTopicName, !Ref "AWS::NoValue", !Ref SetALARMTopicName]
      Subscription:
      # Reference for the different type of endpoints available: http://docs.aws.amazon.com/sns/latest/api/API_Subscribe.html
      - Endpoint: !Ref SetEmail
        Protocol: email

# This section configures Metric Filters and associated Alarms.
# Note that Metric Filter names cannot be set from CloudFormation.
# Ref: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-metricfilter.html
# Metric Filter Names will display as <stack name>-<resource ID>-<random number>
# Eg. cis-controls-3-monitoring-31UnAuthAPICallsMetricFilter-1GNOXICLU4ZZD
  31UnAuthAPICallsMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ ($.errorCode = \"*UnauthorizedOperation\") || ($.errorCode = \"AccessDenied*\" ) }"
      MetricTransformations:
      - MetricName: !Ref 31UnauthAPICalls
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  31UnAuthAPICallsAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 31UnauthAPICalls
      AlarmDescription: Alarm for Unauthorized API Calls.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 31UnauthAPICalls
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  32MgmtConsoleNoMFAMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ ($.additionalEventData.MFAUsed = \"No\") && ($.eventName = \"ConsoleLogin\") }"
      MetricTransformations:
      - MetricName: !Ref 32MgmtConsoleNoMFA
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  32MgmtConsoleNoMFAAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 32MgmtConsoleNoMFA
      AlarmDescription: Alarm for Management Console Sign-in without MFA .
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 32MgmtConsoleNoMFA
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  33UseOfRootAcctMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }"
      MetricTransformations:
      - MetricName: !Ref 33UseOfRootAcct
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  33UseOfRootAcctAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 33UseOfRootAcct
      AlarmDescription: Alarm for Usage of "root" Account.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 33UseOfRootAcct
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  34IAMPolicyChangesMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ ($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteUserPolicy)
        || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy)
        || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion)
        || ($.eventName=DeletePolicyVersion)  || ($.eventName=AttachRolePolicy) }"
      MetricTransformations:
      - MetricName: !Ref 34IAMPolicyChanges
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  34IAMPolicyChangesAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 34IAMPolicyChanges
      AlarmDescription: Alarm for IAM Policy Changes.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 34IAMPolicyChanges
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  35CloudTrailConfigChangesMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail)
        || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName
        = StopLogging) }"
      MetricTransformations:
      - MetricName: !Ref 35CloudTrailConfigChanges
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  35CloudTrailConfigChangesAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 35CloudTrailConfigChanges
      AlarmDescription: Alarm for CloudTrail Configuration Changes.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 35CloudTrailConfigChanges
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  36ConsoleAuthFailuresMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: '{ ($.eventName = ConsoleLogin) && ($.errorMessage = "Failedauthentication")
        }'
      MetricTransformations:
      - MetricName: !Ref 36ConsoleAuthFailures
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  36ConsoleAuthFailuresAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 36ConsoleAuthFailures
      AlarmDescription: Alarm for Failed Console Logins.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 36ConsoleAuthFailures
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '10'
  
  37DisableDeleteCMKMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ ($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey)
        || ($.eventName=ScheduleKeyDeletion)) }"
      MetricTransformations:
      - MetricName: !Ref 37DisableDeleteCMK
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  37DisableDeleteCMKAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 37DisableDeleteCMK
      AlarmDescription: Alarm for Disabling or Scheduled Deletion of Customer Created CMKs.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 37DisableDeleteCMK
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  38S3BucketPolicyChangesMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ ($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl)
        || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName
        = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName
        = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle)
        || ($.eventName = DeleteBucketReplication)) }"
      MetricTransformations:
      - MetricName: !Ref 38S3BucketPolicyChanges
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  38S3BucketPolicyChangesAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 38S3BucketPolicyChanges
      AlarmDescription: Alarm for S3 Bucket Policy Changes.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 38S3BucketPolicyChanges
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  39AWSConfigChangesMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel)||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder))}"
      MetricTransformations:
      - MetricName: !Ref 39AWSConfigChanges
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  39AWSConfigChangesAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 39AWSConfigChanges
      AlarmDescription: Alarm for AWS Config Configuration Changes.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 39AWSConfigChanges
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  310SecGroupChangesMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName
        = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress)
        || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup)
        || ($.eventName = DeleteSecurityGroup)}"
      MetricTransformations:
      - MetricName: !Ref 310SecGroupChanges
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  310SecGroupChangesAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 310SecGroupChanges
      AlarmDescription: Alarm for Security Group Changes.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 310SecGroupChanges
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  311NACLChangesMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry)
        || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry)
        || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation)
        }"
      MetricTransformations:
      - MetricName: !Ref 311NACLChanges
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  311NACLChangesAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 311NACLChanges
      AlarmDescription: Alarm for Changes to Network Access Control Lists (NACL).
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 311NACLChanges
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  312NetworkGatewayChangesMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway)
        || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway)
        || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway)
        }"
      MetricTransformations:
      - MetricName: !Ref 312NetworkGatewayChanges
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  312NetworkGatewayChangesAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 312NetworkGatewayChanges
      AlarmDescription: Alarm for Changes to Network Gateways.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 312NetworkGatewayChanges
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  313RouteTableChangesMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable)
        || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation)
        || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName
        = DisassociateRouteTable) }"
      MetricTransformations:
      - MetricName: !Ref 313RouteTableChanges
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  313RouteTableChangesAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 313RouteTableChanges
      AlarmDescription: Alarm for Route Table Changes.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 313RouteTableChanges
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'
  
  314VPCChangesMetricFilter:
    DependsOn:
    - TrailLogGroup
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: CloudTrailLogGroupName
      FilterPattern: "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) ||
        ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection)
        || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection)
        || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc)
        || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink)
        || ($.eventName = EnableVpcClassicLink) }"
      MetricTransformations:
      - MetricName: !Ref 314VPCChanges
        MetricNamespace: !Ref NameYourNameSpace
        MetricValue: '1'
  
  314VPCChangesChangesAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref 314VPCChanges
      AlarmDescription: Alarm for VPC Changes.
      AlarmActions:
      - Ref: EmailAlarmNotificationTopic
      MetricName: !Ref 314VPCChanges
      Namespace: !Ref NameYourNameSpace
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '1'

Outputs:
  CloudTrailS3BucketName:
    Description: CloudTrail S3 Target Bucket Name
    Value: !If [NeedCloudTrailS3Bucket, !Ref CloudTrailS3Bucket, !Ref PreExistingS3BucketCloudTrail]
    Export:
      Name: !Sub "${AWS::StackName}-BucketName"
  NotificationEmail:
    Description: Email for Alarms
    Value: !Ref SetEmail
  TrailLogGroupRole:
    Description: IAM Role Name for CloudTrail Logs
    Value: !GetAtt TrailLogGroupRole.Arn