はじめに
以前作成した、VPCの内部から不正なデータ送信のアラームを送信する環境と、S3の不正アクセスを検出する環境を、一つにしたCloudFormationを作成しました。
構成
今回も、複数の環境を想定して、「共通で使うリソース」と「個別の環境で使うリソース」で分けて作成しました。
以前と少し異なる点は、VPCとCloudTrailにアタッチさせるIAMロール(CloudWatch Logs出力権限)を共通にしました。
それ以外のリソース等は、以前(VPCとS3)のものと同じです。
CloudFormation
コードは以下になります、説明等に使われている英語の拙さはご容赦ください。
共通で使うリソース
01_createBaseEnvForTrail.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Create Base Env for Trail on VPC and S3. And Add target Bucket for trail.
Parameters:
TrailBuckets:
Description: Set S3Bucket Arn(arn:aws:s3:::xxxxxxxxxxx/) for trail. delimiter is connma(,). If bukcet don't exists, set empty (Don't Create CloudTrail) .
Type: CommaDelimitedList
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
-
Label:
default: User Setting.
Parameters:
- TrailBuckets
# パラメータのリストを連結させて空文字なら、CloudTrailを作らない
Conditions:
CreateTrail:
!Not [!Equals [ !Join [ '', !Ref TrailBuckets ] , '']]
Resources:
##################################################
# Common Resource
##################################################
# SNS
IllegalTraficSnsTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: illegal-trafic-sns-topic
Tags:
- Key: "usefor"
Value: "send e-mail detection of illegal trafic"
# Role for Output to Log Group
## ロググループに出力可能なロール作成
OutputRoleToLogGroup:
Type: AWS::IAM::Role
Properties:
RoleName: for-outputto-loggroup
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- "vpc-flow-logs.amazonaws.com"
- "cloudtrail.amazonaws.com"
Action: 'sts:AssumeRole'
Policies:
# IAMの参照などを追加
- PolicyName: outputto-loggroup
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
- "logs:DescribeLogGroups"
- "logs:DescribeLogStreams"
Resource: '*'
Tags:
- Key: "usefor"
Value: "output to LogGroup"
##################################################
# VPC Resource
##################################################
# Cloudwatch log Group
VpcFlowlogsLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /custom/vpcflowlogs
RetentionInDays: 3653 # 未指定時は「失効しない」
##################################################
# S3 Resource
##################################################
# Cloudwatch log Group
S3AccesslogLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /custom/s3accesslog
RetentionInDays: 3653 # 未指定時は「失効しない」
# Bucket for CloudTrail
OutCloudTrailBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "out-cloudtrail-s3accesslogs-${AWS::AccountId}"
BucketEncryption:
ServerSideEncryptionConfiguration:
-
ServerSideEncryptionByDefault:
SSEAlgorithm: "AES256"
BucketKeyEnabled: false
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
## SSLがないのはNG
OutCloudTrailBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref OutCloudTrailBucket
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: DenyNotSecureTransport
Effect: Deny
Principal: "*"
Resource:
- !Sub "arn:aws:s3:::${OutCloudTrailBucket}"
- !Sub "arn:aws:s3:::${OutCloudTrailBucket}/*"
Action: "*"
Condition:
Bool:
aws:SecureTransport: false
- Sid: AWSCloudTrailAclCheck
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Action: s3:GetBucketAcl
Resource: !Sub "arn:aws:s3:::${OutCloudTrailBucket}"
- Sid: AWSCloudTrailWrite
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Action: s3:PutObject
Resource: !Sub "arn:aws:s3:::${OutCloudTrailBucket}/s3AccessLogTrail/AWSLogs/${AWS::AccountId}/*"
Condition:
StringEquals:
s3:x-amz-acl: bucket-owner-full-control
# CloudTrail
Trail:
Type: AWS::CloudTrail::Trail
Condition: CreateTrail
Properties:
S3BucketName: !Ref OutCloudTrailBucket
S3KeyPrefix: "s3AccessLogTrail"
CloudWatchLogsLogGroupArn: !GetAtt S3AccesslogLogGroup.Arn
CloudWatchLogsRoleArn: !GetAtt OutputRoleToLogGroup.Arn
EnableLogFileValidation: true
IsLogging: true
TrailName: S3AccessLogTrail
IncludeGlobalServiceEvents: true
EventSelectors:
- DataResources:
- Type: AWS::S3::Object
Values: !Ref TrailBuckets
ReadWriteType: All
IncludeManagementEvents: false
Outputs:
##################################################
# Common Resource
##################################################
IllegalTraficSnsTopic:
Value: !Ref IllegalTraficSnsTopic
Export:
Name: IllegalTraficSnsTopic
OutputRoleArnToLogGroup:
Value: !GetAtt OutputRoleToLogGroup.Arn
Export:
Name: OutputRoleArnToLogGroup
##################################################
# VPC Resource
##################################################
VpcFlowlogsLogGroup:
Value: !Ref VpcFlowlogsLogGroup
Export:
Name: VpcFlowlogsLogGroup
##################################################
# S3 Resource
##################################################
S3AccesslogLogGroup:
Value: !Ref S3AccesslogLogGroup
Export:
Name: S3AccesslogLogGroup
個別の環境で使うリソース
02_createEachEnvForTrail.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Create Each Env for Trail on VPC and S3.
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 OutputRoleArnToLogGroup
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
##################################################
# 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
##################################################
# MetricFilter
# フィルタ名は [CFnのスタック名]-[リソース名]-[ランダム文字列]が付く
##################################################
# VPC FlowLogs
MetricFilterOfVpcFlowLogs:
Type: AWS::Logs::MetricFilter
Properties:
LogGroupName: !ImportValue VpcFlowlogsLogGroup
FilterPattern: !Sub "[account_id, action, az_id, bytes > 1000000, dstaddr!=52.219.* && dstaddr!=3.5.* && dstaddr!=35.72.164.* && dstaddr!=35.73.115.* , dstport, end, flow_direction = egress, instance_id, interface_id, log_status, packets, pkt_dst_aws_service!=EC2_INSTANCE_CONNECT, ..., vpc_id=${Vpc}]"
MetricTransformations:
- MetricValue: 1
MetricNamespace: Custom/VPC
MetricName: !Sub ${EnvId}-vpc-IllegalTrafic
DefaultValue: 0
# S3 AccessLog
MetricFilterOfS3AccessLog:
Type: AWS::Logs::MetricFilter
Properties:
LogGroupName: !ImportValue S3AccesslogLogGroup
FilterPattern: !Sub '{($.eventName="GetObject") && (($.sourceIPAddress!="123.123.123.123") && ($.sourceIPAddress!="234.234.*") && ($.vpcEndpointId!="${VPCS3Endpoint}" || $.vpcEndpointId NOT EXISTS)) && ($.requestParameters.bucketName="${PrivateBucket}")}'
MetricTransformations:
- MetricValue: 1
MetricNamespace: Custom/S3
MetricName: !Sub ${EnvId}-s3-IllegalTrafic
DefaultValue: 0
##################################################
# Alarm
##################################################
# VPC FlowLogs
LogFilterAlarmOfVpcFlowLogs:
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 にて、不正トラフィック検出"
# S3 AccessLog
LogFilterAlarmOfS3AccessLog:
Type: AWS::CloudWatch::Alarm
Properties:
Namespace: Custom/S3
MetricName: !Sub ${EnvId}-s3-IllegalTrafic
Statistic: Sum
Period: 300 # の間に
ComparisonOperator: GreaterThanOrEqualToThreshold # の状態を
Threshold: 1 # 回超えることが
EvaluationPeriods: 1 # 周期中
DatapointsToAlarm: 1 # 回なら
TreatMissingData: notBreaching
AlarmActions:
- !ImportValue IllegalTraficSnsTopic
AlarmName: !Sub ${EnvId}-s3-IllegalTraficAlarm
AlarmDescription: !Sub "arn:aws:s3:::${PrivateBucket} にて、不正トラフィック検出"
##################################################
# Output
##################################################
# CloudTrailを作成するCFnのパラメータに渡す文字列を出力
# (指定時に末尾スラッシュ必要)
Outputs:
PrivateBucket:
Value: !Sub "arn:aws:s3:::${PrivateBucket}"
Export:
Name: !Sub "PrivateBucket-${EnvId}"
以前と異なる点は以下になります。
- VPCフローログのフィルタ
- サイズを"1MBを超える"にしました。
- "EC2_INSTANCE_CONNECT経由のトラフィック"を除外
pkt_dst_aws_service!=EC2_INSTANCE_CONNECT,
- 使ってみると、ここのトラフィックに流れるデータのサイズが大きかったため除外。
- S3アクセスログのフィルタ
- 同じスタックで作成したVPCのエンドポイント経由を除外
($.vpcEndpointId!="${VPCS3Endpoint}" || $.vpcEndpointId NOT EXISTS)
-
$.vpcEndpointId!=xxxx
だけだと、VPCエンドポイントを経由しないアクセスにもFalseとなるようだったので、$.vpcEndpointId NOT EXISTS
をORで結合しました。
- 同じスタックで作成したVPCのエンドポイント経由を除外
環境作成手順、片づけ手順
まとめ
PC&S3バケットの不正トラフィックの検出環境の構築をCloudFormation化しました。
課題として「パブリックのサービスからのアクセスに対してアラームになる」という点ありますので、折を見て探ってみたいと思います。