I tried WafCharm notification and reporting functions using CloudFormation

2022.04.06

WafCharm is AWS WAF automation service

Benifits:

●Stronger Defense Create and set rules that are optimal for the user's environment.

●Automation by AI Complete automation of WAF operations from applying rules to handling new vulnerabilities.

●Extensive Support System Reliable support system in case of false-positives. 24/7 technical support (excluding entry plans)

Hands on:

Reporting/notification functions Note:
To be able to use the reporting function, the following conditions must be met
there were detections in the previous month
If there were no detections in the previous month -> no monthly report will be generated.
WafCharm management screen
Select 'Report' from the top right menu.
monthly report on the WafCharm management screen

CloudFormationTemplate for Deploying AWS Resources:

AWSTemplateFormatVersion: 2010-09-09
Description: WafCharm production machine

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Kinesis Data Firehose Configuration"
        Parameters:
          - BufferSize
          - BufferInterval
          - CompressionFormat

Parameters:
  SystemPrefix:
    Description: "System prefix of each resource names."
    Type: String
    Default: "xxx"

  ExportServerStackName:
    Description:  the name of Server Stack
    Type: String
    Default: xxx-server-stack

  StreamName:
    Type: "String"
   # AllowedPattern: "aws-waf-logs-[A-Za-z0-9]+"
    Default: "aws-waf-logs-xxx-wafcharm"

  BufferSize:
    Type: String
    Default: '5'
  BufferInterval:
    Type: String
    Default: '60'
  CompressionFormat:
    Type: String
    AllowedValues: [GZIP,HADOOP_SNAPPY,Snappy,UNCOMPRESSED,ZIP]
    Default: 'GZIP'
Resources:
# --------
# WAF
# --------
  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: !Sub '${SystemPrefix}-wafcharm'
      DefaultAction:
        Allow: {}
      Description: AWS WAFv2 WebACL
      Scope: REGIONAL
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: !Sub '${SystemPrefix}'
        SampledRequestsEnabled: true

  WAFAssociation:
    Type: AWS::WAFv2::WebACLAssociation
    Properties:
      WebACLArn: !GetAtt WebACL.Arn
      ResourceArn : 
         Fn::ImportValue: !Sub ${ExportServerStackName}-LoadBalancerArn



  S3Bucket:
  # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html
    Type: "AWS::S3::Bucket"
    DependsOn: "WAFAssociation"
    DeletionPolicy: "Delete"
    Properties:
      BucketName: !Sub 'aws-waf-logs-${AWS::AccountId}'
      LifecycleConfiguration:
        Rules:
          -
            ExpirationInDays: "365"
            Id: s3-lifecycle-rule
            NoncurrentVersionExpirationInDays: "365"
            Status: "Enabled"

  IAMRoleForKinesis:
    Type: "AWS::IAM::Role"
    DependsOn: "S3Bucket"
    DeletionPolicy: "Delete"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              Service: "firehose.amazonaws.com"
            Action: "sts:AssumeRole"
            Condition:
              StringEquals:
                sts:ExternalId: !Ref "AWS::AccountId"

      MaxSessionDuration: 3600
      Path: "/role/"
      Policies:
        - 
          PolicyName: !Sub policy-kinesis-${StreamName}
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              -
                Effect: "Allow"
                Action: "glue:GetTableVersions"
                Resource: "*"
              -
                Effect: "Allow"
                Action:
                  - "s3:AbortMultipartUpload"
                  - "s3:GetBucketLocation"
                  - "s3:GetObject"
                  - "s3:ListBucket"
                  - "s3:ListBucketMultipartUploads"
                  - "s3:PutObject"
                Resource:
                  - !Sub arn:aws:s3:::aws-waf-logs-${AWS::AccountId}
                  - !Sub arn:aws:s3:::aws-waf-logs-${AWS::AccountId}/*
              - 
                Effect: "Allow"
                Action: "logs:PutLogEvents"
                Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kinesisfirehose/${StreamName}:*
              - 
                Effect: "Allow"
                Action:
                  - "kinesis:DescribeStream"
                  - "kinesis:GetShardIterator"
                  - "kinesis:GetRecords"
                Resource: !Sub arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${StreamName}
      RoleName: FirehoseRoleForWafCharm
  KinesisFirehose:
  # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-kinesisfirehose-deliverystream.html
    Type: "AWS::KinesisFirehose::DeliveryStream"
    DeletionPolicy: "Delete"
    Properties:
      DeliveryStreamName: !Ref StreamName
      DeliveryStreamType: "DirectPut"
      S3DestinationConfiguration:
        BucketARN: !Sub arn:aws:s3:::aws-waf-logs-${AWS::AccountId}
        BufferingHints:
          IntervalInSeconds: !Ref BufferInterval
          SizeInMBs: !Ref BufferSize
        CloudWatchLoggingOptions:
          Enabled: True
          LogGroupName: !Sub /aws/kinesisfirehose/${StreamName}
          LogStreamName: !Ref StreamName
        CompressionFormat: !Ref CompressionFormat
       # ErrorOutputPrefix: !Sub error/${StreamName}/
        Prefix: !Sub ${StreamName}/
        RoleARN: !GetAtt IAMRoleForKinesis.Arn
      Tags:
        - Key: Name
          Value: xxx-firehose-waf-logs
  IAMRoleForLambda:
    Type: "AWS::IAM::Role"
    DependsOn: "KinesisFirehose"
    DeletionPolicy: "Delete"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              Service: "lambda.amazonaws.com"
            Action: "sts:AssumeRole"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AWSLambdaExecute"
      MaxSessionDuration: 3600
      Path: "/role/"
      # PermissionsBoundary: String
      Policies:
        - 
          PolicyName: "wafcharm-waflog-s3-read"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              -
                Effect: "Allow"
                Action:
                  - "s3:GetObject"
                Resource:
                  - !Sub arn:aws:s3:::aws-waf-logs-${AWS::AccountId}/*
        - 
          PolicyName: "wafcharm-waf-log-s3-put"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              -
                Effect: "Allow"
                Action:
                  - "s3:*"
                Resource:
                  - "arn:aws:s3:::wafcharm.com/"
                  - "arn:aws:s3:::wafcharm.com/*"
      RoleName: "LambdaRoleForWafCharm"
  Lambda:
  # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
    Type: "AWS::Lambda::Function"
    DeletionPolicy: "Delete"
    Properties:
      Code: 
        ZipFile: |
          'use strict';

          const toBucket = process.env.WAFCHARM_BUCKET || 'wafcharm.com';
          const toPath = process.env.WAFCHARM_PATH || 'waflog/acceptance/v2';
          const acl = 'bucket-owner-full-control';

          const AWS = require('aws-sdk');
          const s3 = new AWS.S3({
              apiVersion: '2006-03-01',
          });

          exports.handler = (event) => {
              let fromParams = {
                  Bucket: event.Records[0].s3.bucket.name,
                  Key: event.Records[0].s3.object.key
              };
              
              let match = event.Records[0].s3.object.key.match(/\d{4}\/\d{2}\/\d{2}\/\d{2}\/[^\/]*$/);
              if (!match) {
                  console.error('Not match the AWS WAF full log event. : ', event.Records[0].s3.object.key);
                  return;
              }

              s3.getObject(fromParams, (err, data) => {
                  if (err) {
                      console.error('Cannot get object.');
                      console.error(err);
                      return;
                  }
                  let toParams = {
                      Bucket: toBucket,
                      Key: [toPath, match[0]].join('/'),
                      ACL: acl,
                      Body: data.Body
                  };
                  
                  send();
                  
                  function send() {
                      s3.putObject(toParams, (err, data) => {
                          if (err) {
                              console.error('Cannot put object.');
                              console.error(err);
                              send();
                          } else {
                              return;
                          }
                      });
                  }
              });
          };

      Description: "For WafCharm"
      FunctionName: "xxx-lambda-wafcharm"
      Handler: "index.handler"
      MemorySize: 128
      Role: !GetAtt IAMRoleForLambda.Arn
      Runtime: "nodejs14.x"
      Tags: 
        - Key: name
          Value: xxx-lambda-wafcharm
      Timeout: 60
  WAFCharmLoggingConfiguration:
    Type: AWS::WAFv2::LoggingConfiguration
    Properties:
      LogDestinationConfigs: 
          - !GetAtt KinesisFirehose.Arn
      ResourceArn: !GetAtt WebACL.Arn

  LambdaPermission:
  # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-permission.html
    Type: "AWS::Lambda::Permission"
    DeletionPolicy: "Delete"
    Properties:
      Action: "lambda:InvokeFunction"
      # EventSourceToken: String
      FunctionName: !GetAtt Lambda.Arn
      Principal: "s3.amazonaws.com"
      SourceAccount: !Ref "AWS::AccountId"
      SourceArn: !GetAtt S3Bucket.Arn
The source code differs depending on the AWS WAF version.
Please note that this may vary.

Resources Created using Console

Lambda build (trigger)
Add trigger :
Select S3 as trigger
Trigger configuration
Bucket: S3 bucket configured in 1.4
Event type: object creation (all)
Prefix : same prefix as kinesis firehose (ex : waflog/) Prefixes should always be prefixed with a "/", e.g. "waflog/".
Add."

Iam User

https://www.wafcharm.com/en/blog/aws-iam-setting-for-wafcharm/ 1. open IAM from Services

  1. click on add user

  2. Write user name e.g. WAFCharm and check Access key - Programmatic access

  3. Click next permissions

  4. Attach existing policy directly:

  5. AWSWafFullAccess

  6. S3ObjectReadOnly

  7. you can skip tags

  8. Review and Create User

WAFCharm Reports and email example

https://dev.classmethod.jp/articles/wafcharm-freetrial/

Refrences:

AWS WAF v2でWafCharmを導入設定する際に知っておきたいこと