はじめに
前回コンソールから作成した、VPCの不正トラフィックを検出する環境を、CloudFormationで記述しました。
構成
今回は複数環境の作成を想定して、「共通で使うリソース」と「個別の環境で使うリソース」で分けて作成しました。
- 共通で使うリソース
- 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レンジを許可しました。
- フィルターは、「100KBを超える、S3のIP以外に対しての送信で、構築したVPCのトラフィック」としました。
- 他のバケットへの送信はフィルターに引っかからないですが、VPCエンドポイントのポリシーで許可していないので、できない(はず)です。
- アラームの設定値の説明は推測です。わかる方はご指摘いただけると助かります。
環境作成・確認手順
- 01_createBaseEnvForTrail.ymlを実行。
- 02_createEachEnvForTrail.ymlを、構築したい環境分だけ、EnvIdを指定して実行。今回は「cyclone」と「joker」という環境を作りました。
- 「cyclone」にEC2インスタンスを作成し、決められたS3以外にデータ送信すると、以下のように「cyclone」のアラームだけ反応しました。
片づけ
- 各環境のS3バケットを空にします。
- CloudFormationで、各環境のスタックを削除します。
- CloudFormationで、共通環境のスタックを削除します。
まとめ
「VPCの不正トラフィックを検出できる環境」を、CloudFormationで作成しました。
次はS3アクセスログで、同様のことを行おうと思います。