VPCFlowLogsにCloudWatchLogsのAlarmを設定する（CloudFormation編）

# はじめに
[前回](https://qiita.com/a_b_/items/54252d2ba7944ecefcad)コンソールから作成した、VPCの不正トラフィックを検出する環境を、CloudFormationで記述しました。

# 構成
今回は複数環境の作成を想定して、「共通で使うリソース」と「個別の環境で使うリソース」で分けて作成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/233457e2-2181-4abc-71f8-1c84e282640d.png)


- 共通で使うリソース
    - Log Group
    - SNS
    - VPCにアタッチする、フローログをLog Groupに送信する用のロール
        - 図上では、環境ごとの場所に入っています
- 個別の環境で使うリソース
    - VPCとそれに関連するもの
    - S3バケット
    - メトリクスとアラート
        - 監視するロググループは一つなので、環境ごとのIDなどをフィルターに入れて分別しています。
  
# CloudFormation

コードは以下になります、説明等に使われている英語の拙さはご容赦ください。

## 共通で使うリソース
01_createBaseEnvForTrail.yml

```
AWSTemplateFormatVersion: 2010-09-09
Description: Create Base Env for Trail on VPC

Resources: 
##################################################
# Role
##################################################
  ## VPCにアタッチする、VPCフローログをロググループに出力可能なロール作成
  VpcFlowlogsOutRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: for-vpcflowlogs
      Path: /
      AssumeRolePolicyDocument: 
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "vpc-flow-logs.amazonaws.com"
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: out-vpcflowlogs
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                  - "logs:DescribeLogGroups"
                  - "logs:DescribeLogStreams"
                Resource: '*'
      Tags:
          - Key: "usefor"
            Value: "VPC Flowlogs"

##################################################
# SNS
##################################################
  IllegalTraficSnsTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: illegal-trafic-sns-topic
      Tags:
          - Key: "usefor"
            Value: "send e-mail detection of illegal trafic"
##################################################
# Cloudwatch log Group
##################################################
  VpcFlowlogsLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: /custom/vpcflowlogs
      RetentionInDays: 3653   # 未指定時は「失効しない」

Outputs:
  RoleForVpcFlowlogsOnTrail:
    Value: !GetAtt VpcFlowlogsOutRole.Arn
    Export:
      Name: RoleForVpcFlowlogsOnTrail
  IllegalTraficSnsTopic:
    Value: !Ref IllegalTraficSnsTopic
    Export:
      Name: IllegalTraficSnsTopic
  VpcFlowlogsLogGroup:
    Value: !Ref VpcFlowlogsLogGroup
    Export:
      Name: VpcFlowlogsLogGroup

```

- Outputは、個別の環境で使うリソース作成時に参照します。

## 個別の環境で使うリソース

02_createEachEnvForTrail.yml

```
AWSTemplateFormatVersion: 2010-09-09
Description: Create Each Env for Trail on VPC
Parameters:
  EnvId:
    Type: String
  VpcCidr:
    Type: String
    Default: 192.168.209.0/24
  SubnetACidr:
    Type: String
    Default: 192.168.209.0/26
  SubnetCCidr:
    Type: String
    Default: 192.168.209.64/26
  SubnetDCidr:
    Type: String
    Default: 192.168.209.128/26
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: User Setting.
        Parameters:
          - EnvId
          - VpcCidr
          - SubnetACidr
          - SubnetCCidr
          - SubnetDCidr

Resources:
##################################################
# VPC
##################################################
  Vpc:
    Type: AWS::EC2::VPC
    Properties: 
      CidrBlock: !Ref VpcCidr
      # EnableDnsHostnames: true
      # EnableDnsSupport: true
      Tags: 
        - Key: Name
          Value: !Sub "${EnvId}-vpc"
##################################################
# Security Group
##################################################
  SecGrpOnlyInternal:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Enable access from EC2 Instance Connect IP
      GroupName: !Sub "${EnvId}-sg"
      VpcId: !Ref Vpc
      # For EC2 Instance Connect
      SecurityGroupIngress:
        -
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 3.112.23.0/29
  SGBaseIngress:
    Type: 'AWS::EC2::SecurityGroupIngress'
    Properties:
      GroupId: !Ref SecGrpOnlyInternal
      IpProtocol: -1
      SourceSecurityGroupId: !GetAtt SecGrpOnlyInternal.GroupId
##################################################
# IGW
##################################################
  Igw:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - Key: Name
          Value: !Sub ${EnvId}-igw
  IgwAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties: 
      InternetGatewayId: !Ref Igw
      VpcId: !Ref Vpc
##################################################
# Subnets
##################################################
  SubnetA:
    Type: AWS::EC2::Subnet
    Properties: 
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref SubnetACidr
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: !Sub ${EnvId}-subnet-a
  SubnetC:
    Type: AWS::EC2::Subnet
    Properties: 
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref SubnetCCidr
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: !Sub ${EnvId}-subnet-c
  SubnetD:
    Type: AWS::EC2::Subnet
    Properties: 
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1d
      CidrBlock: !Ref SubnetDCidr
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: !Sub ${EnvId}-subnet-d
##################################################
# Routing
##################################################
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref Vpc
      Tags: 
        - Key: Name
          Value: !Sub ${EnvId}-public-rtb
  RouteTableAssocA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetA
  RouteTableAssocC:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetC
  RouteTableAssocD:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetD
  Route:
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref Igw

##################################################
# S3
##################################################
  PrivateBucket:
    Type: "AWS::S3::Bucket"
    Properties:
        BucketName: !Sub "for-${EnvId}-${AWS::AccountId}"
        BucketEncryption: 
            ServerSideEncryptionConfiguration: 
              - 
                ServerSideEncryptionByDefault: 
                    SSEAlgorithm: "AES256"
                BucketKeyEnabled: false
        PublicAccessBlockConfiguration:
          BlockPublicAcls: true
          BlockPublicPolicy: true
          IgnorePublicAcls: true
          RestrictPublicBuckets: true
  # SSLがないのはNG
  PrivateBucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref PrivateBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: DenyNotSecureTransport
            Effect: Deny
            Principal: "*"
            Resource:
              - !Sub "arn:aws:s3:::${PrivateBucket}"
              - !Sub "arn:aws:s3:::${PrivateBucket}/*"
            Action: "*"
            Condition:
                Bool:
                    aws:SecureTransport: false

##################################################
# VPC Endpoint to S3
##################################################
  VPCS3Endpoint:
    Type: "AWS::EC2::VPCEndpoint"
    Properties:
      VpcEndpointType: Gateway
      RouteTableIds: 
        - !Ref RouteTable
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
      VpcId: !Ref Vpc
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal: '*'
            Action:
              - "s3:*"
            Resource:
              - !Sub "arn:aws:s3:::${PrivateBucket}"
              - !Sub "arn:aws:s3:::${PrivateBucket}/*"
            
##################################################
# VPC FlowLogs
##################################################
  VpcFlowLogs:
    Type: "AWS::EC2::FlowLog"
    Properties:
      ResourceType: VPC
      ResourceId: !Ref Vpc
      TrafficType: ALL
      DeliverLogsPermissionArn: !ImportValue RoleForVpcFlowlogsOnTrail
      LogGroupName: !ImportValue VpcFlowlogsLogGroup
      LogFormat: '${account-id} ${action} ${az-id} ${bytes} ${dstaddr} ${dstport} ${end} ${flow-direction} ${instance-id} ${interface-id} ${log-status} ${packets} ${pkt-dst-aws-service} ${pkt-dstaddr} ${pkt-src-aws-service} ${pkt-srcaddr} ${protocol} ${region} ${srcaddr} ${srcport} ${start} ${sublocation-id} ${sublocation-type} ${subnet-id} ${tcp-flags} ${traffic-path} ${type} ${version} ${vpc-id}'
      Tags: 
        - Key: Name
          Value: !Sub ${EnvId}-flowlog
##################################################
# MetricFilter
##################################################
  MetricFilter:
    # フィルタ名は [CFnのスタック名]-[リソース名]-[ランダム文字列]が付く
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName: !ImportValue VpcFlowlogsLogGroup
      FilterPattern: !Sub "[account_id, action, az_id, bytes > 100000, dstaddr!=52.219.* && dstaddr!=3.5.* && dstaddr!=35.72.164.* && dstaddr!=35.73.115.* , dstport, end, flow_direction = egress, ..., vpc_id=${Vpc}]"
      MetricTransformations: 
        - MetricValue: 1
          MetricNamespace: Custom/VPC
          MetricName: !Sub ${EnvId}-vpc-IllegalTrafic
          DefaultValue: 0
##################################################
# Alarm
##################################################
  LogFilterAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      Namespace: Custom/VPC
      MetricName: !Sub ${EnvId}-vpc-IllegalTrafic
      Statistic: Sum
      Period: 300                                           # の間に
      ComparisonOperator: GreaterThanOrEqualToThreshold     # の状態を
      Threshold: 1                                          # 回超えることが
      EvaluationPeriods: 1                                  # 周期中
      DatapointsToAlarm: 1                                  # 回なら
      TreatMissingData: notBreaching
      AlarmActions: 
        - !ImportValue IllegalTraficSnsTopic
      AlarmName: !Sub ${EnvId}-vpc-IllegalTraficAlarm
      AlarmDescription: !Sub "${EnvId} vpc にて、不正トラフィック検出"

##################################################
# Role
##################################################
  ## EC2アタッチ用ロール作成
  Ec2Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub for-ec2-on-${EnvId}
      Path: /
      AssumeRolePolicyDocument: 
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "ec2.amazonaws.com"
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: s3fullaccess
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "s3:*"
                Resource:
                  - !Sub "arn:aws:s3:::${PrivateBucket}"
                  - !Sub "arn:aws:s3:::${PrivateBucket}/*"
  ## インスタンスプロファイル作成
  MyInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref Ec2Role
```

- VPCやサブネットのIPレンジは、パラメータで指定できるようにしました。
- S3は、SSL暗号化通信されている通信のみ可としました。
- VPCエンドポイントは、対応するS3バケットのみ使用可能なポリシーにしました。
- セキュリティグループのインバウンドには、はEC2 Instance Connectを使用するため[特定のIPレンジを許可](https://qiita.com/hayao_k/items/7b44ed1cd11651272985)しました。
- フィルターは、「100KBを超える、[S3のIP](https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-find-ip-address-ranges/)以外に対しての送信で、構築したVPCのトラフィック」としました。
    - 他のバケットへの送信はフィルターに引っかからないですが、VPCエンドポイントのポリシーで許可していないので、できない（はず）です。
- アラームの設定値の説明は推測です。わかる方はご指摘いただけると助かります。

# 環境作成・確認手順

1. 01_createBaseEnvForTrail.ymlを実行。
1. 02_createEachEnvForTrail.ymlを、構築したい環境分だけ、EnvIdを指定して実行。今回は「cyclone」と「joker」という環境を作りました。
1. 「cyclone」にEC2インスタンスを作成し、決められたS3以外にデータ送信すると、以下のように「cyclone」のアラームだけ反応しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/539aeebe-43d5-a4a8-6f9e-dc3ff2e16dea.png)

# 片づけ
1. 各環境のS3バケットを空にします。
1. CloudFormationで、各環境のスタックを削除します。
1. CloudFormationで、共通環境のスタックを削除します。

# まとめ
「VPCの不正トラフィックを検出できる環境」を、CloudFormationで作成しました。
次はS3アクセスログで、同様のことを行おうと思います。