AWS WAFで、テナント別サブドメインごとに接続元IPアドレス制限をやってみた
テナントが増えることにサブドメインを切る運用で、テナントごとにIP制限をかけたい
おのやんです。
みなさん、テナントが増えることにサブドメインを切る運用を考えているアプリケーションにおいて、テナントごとに接続元IPアドレス制限をかけたいと思ったことはありませんか?私はあります。
たとえば、大元のexample.com
のドメインがあったとしましょう。ここに対してテナントが1個、2個と増えるごとに、tenant1.example.com
、tenant2.example.com
とサブドメインを切っていきます。テナント1ではこのIP、テナント2ではこのIPからのみアクセスできるよう設定する、ということですね。
こちらを、今回はAWS WAF(以下、WAF)で設定する機会がありましたので、紹介します。
検証に際しての構成図
今回は、接続元テナントを2つのパブリックなAmazon EC2(以下、EC2)インスタンスに見立てて、ここからALBにアタッチされたサブドメインにアクセスします。その際、EC2インスタンスにアタッチされたElastic IPことに、xxxx.example.com
へのアクセスを許可していきます。逆に条件を満たさないアクセスがブロックされるかどうかも検証します。
今回は、サブドメインごとに異なるデータを表示させるようなアプリの挙動は再現していません。同じALBに対して、複数のサブドメインを切って検証しています。
検証
ALBとパブリックEC2インスタンス・プライベートEC2インスタンスは、一枚のAWS CloudFornation(以下、CFn)テンプレートで作成しておきます。参考までに、テンプレートを作成しておきます。
CFnテンプレート
AWSTemplateFormatVersion: "2010-09-09"
Description: VPC, Security Groups, IAM, and EC2
# ============================================================#
# Input parameters
# ============================================================#
Parameters:
SystemName:
Description: System name of each resource names.
Type: String
Default: aws
EnvName:
Description: Environment name of each resource names.
Type: String
Default: test
#============================================================#
# Resources
#============================================================#
Resources:
#============================================================#
# VPC and subnets
#============================================================#
AWSTestVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.1.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-vpc
- Key: Env
Value: !Sub ${EnvName}
AWSTestPublicSubnet1a:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1a
CidrBlock: 10.1.0.0/24
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-public-subnet-1a
- Key: Env
Value: !Sub ${EnvName}
AWSTestPublicSubnet1c:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1c
CidrBlock: 10.1.1.0/24
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-public-subnet-1c
- Key: Env
Value: !Sub ${EnvName}
AWSTestProtectedSubnet1a:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1a
CidrBlock: 10.1.2.0/24
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-protected-subnet-1a
- Key: Env
Value: !Sub ${EnvName}
AWSTestProtectedSubnet1c:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1c
CidrBlock: 10.1.3.0/24
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-protected-subnet-1c
- Key: Env
Value: !Sub ${EnvName}
AWSTestPrivateSubnet1a:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1a
CidrBlock: 10.1.4.0/24
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-private-subnet-1a
- Key: Env
Value: !Sub ${EnvName}
AWSTestPrivateSubnet1c:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1c
CidrBlock: 10.1.5.0/24
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-private-subnet-1c
- Key: Env
Value: !Sub ${EnvName}
#============================================================#
# Internet gateway and NAT gateway
#============================================================#
AWSTestIgw:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-igw
- Key: Env
Value: !Sub ${EnvName}
AWSTestIgwAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref AWSTestIgw
VpcId: !Ref AWSTestVPC
AWSTestEIP:
Type: AWS::EC2::EIP
Properties:
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-eip
- Key: Env
Value: !Sub ${EnvName}
AWSTestEIPPublic1:
Type: AWS::EC2::EIP
Properties:
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-eip-public-1
- Key: Env
Value: !Sub ${EnvName}
AWSTestEIPPublic2:
Type: AWS::EC2::EIP
Properties:
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-eip-public-2
- Key: Env
Value: !Sub ${EnvName}
AWSTestNgw:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt AWSTestEIP.AllocationId
SubnetId: !Ref AWSTestPublicSubnet1a
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-ngw
- Key: Env
Value: !Sub ${EnvName}
#============================================================#
# Route tables
#============================================================#
AWSTestPublicRtb:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-public-rtb
- Key: Env
Value: !Sub ${EnvName}
AWSTestProtectedRtb:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-protected-rtb
- Key: Env
Value: !Sub ${EnvName}
AWSTestPrivateRtb:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-private-rtb
- Key: Env
Value: !Sub ${EnvName}
#============================================================#
# Routes
#============================================================#
AWSTestPublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref AWSTestPublicRtb
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref AWSTestIgw
AWSTestProtectedRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref AWSTestProtectedRtb
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref AWSTestNgw
AWSTestPrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref AWSTestPrivateRtb
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref AWSTestNgw
#============================================================#
# Route Tables Subnet Association
#============================================================#
AWSTestPublicRtbAssociation1a:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref AWSTestPublicSubnet1a
RouteTableId: !Ref AWSTestPublicRtb
AWSTestPublicRtbAssociation1c:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref AWSTestPublicSubnet1c
RouteTableId: !Ref AWSTestPublicRtb
AWSTestProtectedRtbAssociation1a:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref AWSTestProtectedSubnet1a
RouteTableId: !Ref AWSTestProtectedRtb
AWSTestProtectedRtbAssociation1c:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref AWSTestProtectedSubnet1c
RouteTableId: !Ref AWSTestProtectedRtb
AWSTestPrivateRtbAssociation1a:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref AWSTestPrivateSubnet1a
RouteTableId: !Ref AWSTestPrivateRtb
AWSTestPrivateRtbAssociation1c:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref AWSTestPrivateSubnet1c
RouteTableId: !Ref AWSTestPrivateRtb
#============================================================#
# IAM Role for EC2 instances
#============================================================#
AWSTestEC2Role:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${SystemName}-${EnvName}-ec2-role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
AWSTestEC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: !Sub ${SystemName}-${EnvName}-ec2-role
Path: /
Roles:
- Ref: AWSTestEC2Role
#============================================================#
# Security Groups
#============================================================#
AWSTestSgVPCEndpoint:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub ${SystemName}-${EnvName}-sg-vpc-endpoint
GroupName: !Sub ${SystemName}-${EnvName}-sg-vpc-endpoint
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.1.0.0/16
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-sg-vpc-endpoint
- Key: Env
Value: !Sub ${EnvName}
AWSTestSgALB:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub ${SystemName}-${EnvName}-sg-alb
GroupName: !Sub ${SystemName}-${EnvName}-sg-alb
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-sg-alb
- Key: Env
Value: !Sub ${EnvName}
AWSTestSgEC2Private:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub ${SystemName}-${EnvName}-sg-ec2-private
GroupName: !Sub ${SystemName}-${EnvName}-sg-ec2-private
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref AWSTestSgALB
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-sg-ec2-private
- Key: Env
Value: !Sub ${EnvName}
AWSTestSgRDS:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub ${SystemName}-${EnvName}-sg-rds
GroupName: !Sub ${SystemName}-${EnvName}-sg-rds
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref AWSTestSgEC2Private
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-sg-rds
- Key: Env
Value: !Sub ${EnvName}
AWSTestSgEC2Public:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub ${SystemName}-${EnvName}-sg-ec2-public
GroupName: !Sub ${SystemName}-${EnvName}-sg-ec2-public
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
VpcId: !Ref AWSTestVPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-sg-ec2-public
- Key: Env
Value: !Sub ${EnvName}
#============================================================#
# EC2 Launch Template
#============================================================#
AWSTestLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub ${SystemName}-${EnvName}-ec2-template
LaunchTemplateData:
ImageId: ami-039e8f15ccb15368a
InstanceType: t2.micro
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeType: gp2
VolumeSize: 20
DeleteOnTermination: true
DisableApiTermination: false
UserData:
Fn::Base64: !Sub |
#!/bin/bash
sudo yum install httpd -y
sudo systemctl start httpd
sudo systemctl enable httpd
#============================================================#
# EC2 Instances
#============================================================#
AWSTestEC2Private1a:
Type: AWS::EC2::Instance
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref AWSTestLaunchTemplate
Version: 1
SubnetId: !Ref AWSTestPrivateSubnet1a
SecurityGroupIds:
- !Ref AWSTestSgEC2Private
IamInstanceProfile: !Ref AWSTestEC2InstanceProfile
DisableApiTermination: false
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-ec2-private-1a
- Key: Env
Value: !Sub ${EnvName}
AWSTestEC2Private1c:
Type: AWS::EC2::Instance
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref AWSTestLaunchTemplate
Version: 1
SubnetId: !Ref AWSTestPrivateSubnet1c
SecurityGroupIds:
- !Ref AWSTestSgEC2Private
IamInstanceProfile: !Ref AWSTestEC2InstanceProfile
DisableApiTermination: false
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-ec2-private-1c
- Key: Env
Value: !Sub ${EnvName}
AWSTestEC2Public1a:
Type: AWS::EC2::Instance
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref AWSTestLaunchTemplate
Version: 1
SubnetId: !Ref AWSTestPublicSubnet1a
SecurityGroupIds:
- !Ref AWSTestSgEC2Public
IamInstanceProfile: !Ref AWSTestEC2InstanceProfile
DisableApiTermination: false
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-ec2-public-1a
- Key: Env
Value: !Sub ${EnvName}
AWSTestEC2Public1c:
Type: AWS::EC2::Instance
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref AWSTestLaunchTemplate
Version: 1
SubnetId: !Ref AWSTestPublicSubnet1c
SecurityGroupIds:
- !Ref AWSTestSgEC2Public
IamInstanceProfile: !Ref AWSTestEC2InstanceProfile
DisableApiTermination: false
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-ec2-public-1c
- Key: Env
Value: !Sub ${EnvName}
#============================================================#
# EIP Associations
#============================================================#
AWSTestEIPAssociation1:
Type: AWS::EC2::EIPAssociation
Properties:
AllocationId: !GetAtt AWSTestEIPPublic1.AllocationId
InstanceId: !Ref AWSTestEC2Public1a
AWSTestEIPAssociation2:
Type: AWS::EC2::EIPAssociation
Properties:
AllocationId: !GetAtt AWSTestEIPPublic2.AllocationId
InstanceId: !Ref AWSTestEC2Public1c
#============================================================#
# Application Load Balancer
#============================================================#
AWSTestALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub ${SystemName}-${EnvName}-alb
Type: application
Scheme: internet-facing
IpAddressType: ipv4
Subnets:
- !Ref AWSTestPublicSubnet1a
- !Ref AWSTestPublicSubnet1c
SecurityGroups:
- !Ref AWSTestSgALB
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-alb
- Key: Env
Value: !Sub ${EnvName}
AWSTestTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${SystemName}-${EnvName}-tg
Port: 80
Protocol: HTTP
VpcId: !Ref AWSTestVPC
TargetType: instance
Targets:
- Id: !Ref AWSTestEC2Private1a
Port: 80
- Id: !Ref AWSTestEC2Private1c
Port: 80
HealthCheckEnabled: true
HealthCheckIntervalSeconds: 30
HealthCheckPath: /
HealthCheckPort: traffic-port
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
UnhealthyThresholdCount: 5
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvName}-tg
- Key: Env
Value: !Sub ${EnvName}
AWSTestALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
ForwardConfig:
TargetGroups:
- TargetGroupArn: !Ref AWSTestTargetGroup
Weight: 1
LoadBalancerArn: !Ref AWSTestALB
Port: 80
Protocol: HTTP
#============================================================#
# VPC Endpoints for SSM
#============================================================#
AWSTestVPCEndpointSSM:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref AWSTestVPC
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
VpcEndpointType: Interface
SubnetIds:
- !Ref AWSTestPrivateSubnet1a
- !Ref AWSTestPrivateSubnet1c
SecurityGroupIds:
- !Ref AWSTestSgVPCEndpoint
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal: '*'
Action:
- ssm:UpdateInstanceInformation
- ssm:SendCommand
- ssm:ListCommandInvocations
- ssm:DescribeInstanceInformation
- ssm:GetDeployablePatchSnapshotForInstance
- ssm:GetDefaultPatchBaseline
- ssm:GetManifest
- ssm:GetParameter
- ssm:GetParameters
- ssm:ListAssociations
- ssm:ListInstanceAssociations
- ssm:PutInventory
- ssm:PutComplianceItems
- ssm:PutConfigurePackageResult
- ssm:UpdateAssociationStatus
- ssm:UpdateInstanceAssociationStatus
Resource: '*'
AWSTestVPCEndpointSSMMessages:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref AWSTestVPC
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
VpcEndpointType: Interface
SubnetIds:
- !Ref AWSTestPrivateSubnet1a
- !Ref AWSTestPrivateSubnet1c
SecurityGroupIds:
- !Ref AWSTestSgVPCEndpoint
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal: '*'
Action:
- ssmmessages:CreateControlChannel
- ssmmessages:CreateDataChannel
- ssmmessages:OpenControlChannel
- ssmmessages:OpenDataChannel
Resource: '*'
AWSTestVPCEndpointEC2Messages:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref AWSTestVPC
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
VpcEndpointType: Interface
SubnetIds:
- !Ref AWSTestPrivateSubnet1a
- !Ref AWSTestPrivateSubnet1c
SecurityGroupIds:
- !Ref AWSTestSgVPCEndpoint
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal: '*'
Action:
- ec2messages:AcknowledgeMessage
- ec2messages:DeleteMessage
- ec2messages:FailMessage
- ec2messages:GetEndpoint
- ec2messages:GetMessages
- ec2messages:SendReply
Resource: '*'
# ============================================================#
# Output Parameters
# ============================================================#
Outputs:
# ============================================================#
# VPC Outputs
# ============================================================#
AWSTestVPC:
Value: !Ref AWSTestVPC
Export:
Name: !Sub ${SystemName}-${EnvName}-vpc
AWSTestPublicSubnet1a:
Value: !Ref AWSTestPublicSubnet1a
Export:
Name: !Sub ${SystemName}-${EnvName}-public-subnet-1a
AWSTestPublicSubnet1c:
Value: !Ref AWSTestPublicSubnet1c
Export:
Name: !Sub ${SystemName}-${EnvName}-public-subnet-1c
AWSTestProtectedSubnet1a:
Value: !Ref AWSTestProtectedSubnet1a
Export:
Name: !Sub ${SystemName}-${EnvName}-protected-subnet-1a
AWSTestProtectedSubnet1c:
Value: !Ref AWSTestProtectedSubnet1c
Export:
Name: !Sub ${SystemName}-${EnvName}-protected-subnet-1c
AWSTestPrivateSubnet1a:
Value: !Ref AWSTestPrivateSubnet1a
Export:
Name: !Sub ${SystemName}-${EnvName}-private-subnet-1a
AWSTestPrivateSubnet1c:
Value: !Ref AWSTestPrivateSubnet1c
Export:
Name: !Sub ${SystemName}-${EnvName}-private-subnet-1c
# IAM Outputs
AWSTestEC2Role:
Value: !Ref AWSTestEC2Role
Export:
Name: !Sub ${SystemName}-${EnvName}-ec2-role
AWSTestEC2InstanceProfile:
Value: !Ref AWSTestEC2InstanceProfile
Export:
Name: !Sub ${SystemName}-${EnvName}-ec2-instance-profile
# Security Group Outputs
AWSTestSgEC2Private:
Value: !Ref AWSTestSgEC2Private
Export:
Name: !Sub ${SystemName}-${EnvName}-sg-ec2-private
AWSTestSgALB:
Value: !Ref AWSTestSgALB
Export:
Name: !Sub ${SystemName}-${EnvName}-sg-alb
AWSTestSgRDS:
Value: !Ref AWSTestSgRDS
Export:
Name: !Sub ${SystemName}-${EnvName}-sg-rds
AWSTestSgVPCEndpoint:
Value: !Ref AWSTestSgVPCEndpoint
Export:
Name: !Sub ${SystemName}-${EnvName}-sg-vpc-endpoint
# ============================================================#
# EC2 Outputs
# ============================================================#
AWSTestEC2Private1a:
Value: !Ref AWSTestEC2Private1a
Export:
Name: !Sub ${SystemName}-${EnvName}-ec2-private-1a
AWSTestEC2Private1c:
Value: !Ref AWSTestEC2Private1c
Export:
Name: !Sub ${SystemName}-${EnvName}-ec2-private-1c
AWSTestEC2Public1a:
Value: !Ref AWSTestEC2Public1a
Export:
Name: !Sub ${SystemName}-${EnvName}-ec2-public-1a
AWSTestEC2Public1c:
Value: !Ref AWSTestEC2Public1c
Export:
Name: !Sub ${SystemName}-${EnvName}-ec2-public-1c
AWSTestEIPPublic1:
Value: !Ref AWSTestEIPPublic1
Export:
Name: !Sub ${SystemName}-${EnvName}-eip-public-1
AWSTestEIPPublic2:
Value: !Ref AWSTestEIPPublic2
Export:
Name: !Sub ${SystemName}-${EnvName}-eip-public-2
# ============================================================#
# Application Load Balancer Outputs
# ============================================================#
AWSTestALB:
Value: !Ref AWSTestALB
Export:
Name: !Sub ${SystemName}-${EnvName}-alb
AWSTestTargetGroup:
Value: !Ref AWSTestTargetGroup
Export:
Name: !Sub ${SystemName}-${EnvName}-target-group
AWSTestALBDNSName:
Value: !GetAtt AWSTestALB.DNSName
Export:
Name: !Sub ${SystemName}-${EnvName}-alb-dns-name
# ============================================================#
# VPC Endpoint Outputs
# ============================================================#
AWSTestVPCEndpointSSM:
Value: !Ref AWSTestVPCEndpointSSM
Export:
Name: !Sub ${SystemName}-${EnvName}-vpc-endpoint-ssm
AWSTestVPCEndpointSSMMessages:
Value: !Ref AWSTestVPCEndpointSSMMessages
Export:
Name: !Sub ${SystemName}-${EnvName}-vpc-endpoint-ssmmessages
AWSTestVPCEndpointEC2Messages:
Value: !Ref AWSTestVPCEndpointEC2Messages
Export:
Name: !Sub ${SystemName}-${EnvName}-vpc-endpoint-ec2messages
Amazon Route 53では、筆者が持っているパブリックホストゾーンのドメインのCNAMEレコードとして、tenant1
とtenant2
をそれぞれ設定しておきます。宛先は、上記のテンプレートで作成したALBのドメインです。
次に、WAFで許可するためのIP setsを作成します。ここはテナントごとに設定するので、今回はEC2インスタンスのElastic IPごとに1つのIP setsを作っていきます。
このIP setsを使って、WAFのルールを設定してきます。ALBには、WAFのWeb ACLを関連づけ、そこにルールを作成します。JSONで表記すると、こんな感じになります。
{
"Name": "aws-test-tenant1-allow",
"Priority": 0,
"Action": {
"Allow": {}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "aws-test-tenant1-allow"
},
"Statement": {
"AndStatement": {
"Statements": [
{
"ByteMatchStatement": {
"FieldToMatch": {
"SingleHeader": {
"Name": "host"
}
},
"PositionalConstraint": "EXACTLY",
"SearchString": "tenant1.example.com",
"TextTransformations": [
{
"Type": "NONE",
"Priority": 0
}
]
}
},
{
"IPSetReferenceStatement": {
"ARN": "arn:aws:wafv2:ap-northeast-1:************:regional/ipset/aws-test-ipset-1/********-****-****-****-************"
}
}
]
}
}
}
やっていることは、2つの条件式をANDで組み合わせて、そのときのみ許可する、というものです。
IP制限を行いたいため、WebACL全体では デフォルトはBlock にしています。
ANDで繋げる1つ目の条件式は、HTTPリクエストヘッダーのHostヘッダーフィールドです。ここがtenant1.example.com
に完全に一致する条件を指定しています。
ANDで繋げる2つ目の条件式は、接続元IPアドレスです。ここは、先ほど作成したIP setsで指定できます。
上記の2つを共に満たすときのみ、許可(Allow)するルールとして、最後設定しています。
上記で見せたのはtenant1.example.com
です。tenant2.example.com
分のルールも作ると、Web ACLのルールはこんな感じになります。
上記が設定されていれば、各サブドメインへのアクセスは、各サブドメインごとに許可された特定のIPアドレスからしかアクセスできないようになっているはずです。
検証
検証します。まず1つ目のEC2インスタンス内に入ってみます。このEC2インスタンスのElastic IPは1.2.3.4
なので、tenant1.example.com
へのアクセスのみ可能です。意図した通りに、特定のIPからのみ許可されていますね。
$ curl tenant1.onoyama.classmethod.info
<html><body><h1>It works!</h1></body></html>
$ curl tenant2.onoyama.classmethod.info
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
</body>
</html>
次に2つ目のEC2インスタンス内に入ってみます。このEC2インスタンスのElastic IPは5.6.7.8
なので、tenant2.example.com
へのアクセスのみ可能です。こちらも、意図した通りに特定のIPからのみ許可されていますね。
$ curl tenant1.onoyama.classmethod.info
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
</body>
</html>
$ curl tenant2.onoyama.classmethod.info
<html><body><h1>It works!</h1></body></html>
Web ACLのルールで細かい制御も可能
特に、テナントが増えるごとにサブドメインを切る運用の場合は、テナントが増えるごとに、Web ACLのルールとIP setsが増えていくことになります。そのため、サブドメイン自体もAmazon Route 53のパブリックホストゾーンではなく、CMANEレコードとして追加していくのがいいと思います。ホストゾーン自体に料金がかかりますし、各サブドメインが指すリソースは一緒なので、CNAMEレコードで十分管理できる範疇かな、という印象です。
この記事がどなたかの参考になれば幸いです。では!