forked from Surfline/http-requests-mirroring
-
Notifications
You must be signed in to change notification settings - Fork 0
/
replay-handler-cloudformation.yaml
448 lines (411 loc) · 19.3 KB
/
replay-handler-cloudformation.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: BSD-3-Clause
AWSTemplateFormatVersion: '2010-09-09'
Description: Replay handler of HTTP requests.
Parameters:
HandlerVpcId:
Type: 'AWS::EC2::VPC::Id'
Description: Specify the VPC for the replay handler.
ConstraintDescription: must be the VPC Id of an existing Virtual Private Cloud.
HandlerSubnetIds:
Type: 'List<AWS::EC2::Subnet::Id>'
Description: >-
Specify the subnets for the replay handler. Use subnets that have the following features:
a route to and from the test and production environments;
a route to a NAT Gateway (so that the EC2 instances of the replay handler can download the handler script from the internet).
For high availability use at least two subnets in separate availability zones.
ConstraintDescription: >-
must be a list of at least one existing subnets. They should be residing in
the selected Virtual Private Cloud. it is recommended to provide at least two
subnets in separate availability zones, for high availability purposes.
InstanceType:
Description: EC2 instance type for the replay handler.
Type: String
Default: t4g.small
AllowedValues: [
t4g.nano, t4g.micro, t4g.small, t4g.medium, t4g.large, t4g.xlarge, t4g.2xlarge,
t3.nano, t3.micro, t3.small, t3.medium, t3.large, t3.xlarge, t3.2xlarge,
m6g.medium, m6g.large, m6g.xlarge, m6g.2xlarge, m6g.4xlarge, m6g.8xlarge, m6g.12xlarge, m6g.16xlarge,
m5.large, m5.xlarge, m5.2xlarge, m5.4xlarge, m5.8xlarge, m5.12xlarge, m5.16xlarge, m5.24xlarge,
m5n.large, m5n.xlarge, m5n.2xlarge, m5n.4xlarge, m5n.8xlarge, m5n.12xlarge, m5n.16xlarge, m5n.24xlarge,
c6g.medium, c6g.large, c6g.xlarge, c6g.2xlarge, c6g.4xlarge, c6g.8xlarge, c6g.12xlarge, c6g.16xlarge,
c5.large, c5.xlarge, c5.2xlarge, c5.4xlarge, c5.9xlarge, c5.12xlarge, c5.18xlarge, c5.24xlarge,
c5n.large, c5n.xlarge, c5n.2xlarge, c5n.4xlarge, c5n.9xlarge, c5n.18xlarge,
r6g.medium, r6g.large, r6g.xlarge, r6g.2xlarge, r6g.4xlarge, r6g.8xlarge, r6g.12xlarge, r6g.16xlarge,
r5.large, r5.xlarge, r5.2xlarge, r5.4xlarge, r5.8xlarge, r5.12xlarge, r5.16xlarge, r5.24xlarge,
r5n.large, r5n.xlarge, r5n.2xlarge, r5n.4xlarge, r5n.8xlarge, r5n.12xlarge, r5n.16xlarge, r5n.24xlarge,
]
ConstraintDescription: must be a valid EC2 instance type.
InstanceNumber:
Type: Number
Default: 2
Description: Number of EC2 instances in the replay handler.
KeyName:
Description: This parameter is optional. The name of the EC2 Key Pair to allow SSH access to the instances.
Type: 'String'
ConstraintDescription: This parameter is optional. If provided, must be the name of an existing EC2 KeyPair.
SSHLocation:
Description: This parameter is optional. The IP address range that can be used to SSH to the EC2 instances in the replay handler.
Type: String
MinLength: '0'
MaxLength: '18'
AllowedPattern: '((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2}))?'
ConstraintDescription: This parameter is optional. If provided, must be a valid IP CIDR range of the form x.x.x.x/x.
FilterByDestinationCidrBlock:
Description: Filter the mirrored requests by destination IP.
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
Default: '0.0.0.0/0'
FilterByDestinationPort:
Description: Filter the mirrored requests by destination port.
Type: Number
MinValue: 0
MaxValue: 65535
ConstraintDescription: must be a valid port.
Default: 80
FilterBySourceCidrBlock:
Description: Filter the mirrored requests by source IP.
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
Default: '0.0.0.0/0'
TrafficMirroringVNI:
Description: The VXLAN ID for traffic mirroring session.
Type: Number
MinValue: 1
MaxValue: 16777215
ConstraintDescription: must be an integer between 1 and 16777215.
LatestAmiIdX86:
Description: DO NOT change this parameter. This refers to the latest Amazon Linux 2 AMI.
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
ConstraintDescription: 'only use /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
LatestAmiIdArm:
Description: DO NOT change this parameter. This refers to the latest Amazon Linux 2 AMI.
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2'
ConstraintDescription: 'only use /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2'
RequestsForwardDestination:
Description: >-
The destination endpoint of the replayed HTTP requests. The endpoint can be an IP address or a DNS name.
Enter the endpoint AND its protocol, like: http://172.0.0.1 or https://www.example.com.
Type: String
ConstraintDescription: 'Enter the endpoint AND its protocol, like: http://172.0.0.1 or https://www.example.com.'
ForwardPercentage:
Description: The percentage of traffic that gets replicated. Enter a number between 0 and 100.
Type: Number
Default: 100
MinValue: 0
MaxValue: 100
ConstraintDescription: 'Enter a number between 0 and 100.'
PercentageBy:
Description: >-
How traffic percentage is determined. If empty, then forward only a certain percentage of requests.
If header/remoteaddr, then forward only requests from a certain percentage of headers/remoteaddresses.
Type: String
AllowedValues:
- ''
- 'header'
- 'remoteaddr'
PercentageByHeader:
Description: If the parameter PercentageBy is set to 'header', then enter the name of the header. Otherwise, leave it empty.
Type: String
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
-
Label:
default: "Infrastructure"
Parameters:
- HandlerVpcId
- HandlerSubnetIds
- RequestsForwardDestination
- TrafficMirroringVNI
- InstanceType
- InstanceNumber
- KeyName
- SSHLocation
-
Label:
default: "Filters of mirroring"
Parameters:
- ForwardPercentage
- PercentageBy
- PercentageByHeader
- FilterBySourceCidrBlock
- FilterByDestinationCidrBlock
- FilterByDestinationPort
-
Label:
default: "Do NOT change these"
Parameters:
- LatestAmiIdX86
- LatestAmiIdArm
Mappings:
AmiMap:
{
t4g.nano: {arch: arm}, t4g.micro: {arch: arm}, t4g.small: {arch: arm}, t4g.medium: {arch: arm}, t4g.large: {arch: arm}, t4g.xlarge: {arch: arm}, t4g.2xlarge: {arch: arm}, m6g.medium: {arch: arm}, m6g.large: {arch: arm}, m6g.xlarge: {arch: arm}, m6g.2xlarge: {arch: arm}, m6g.4xlarge: {arch: arm}, m6g.8xlarge: {arch: arm}, m6g.12xlarge: {arch: arm}, m6g.16xlarge: {arch: arm}, c6g.medium: {arch: arm}, c6g.large: {arch: arm}, c6g.xlarge: {arch: arm}, c6g.2xlarge: {arch: arm}, c6g.4xlarge: {arch: arm}, c6g.8xlarge: {arch: arm}, c6g.12xlarge: {arch: arm}, c6g.16xlarge: {arch: arm}, r6g.medium: {arch: arm}, r6g.large: {arch: arm}, r6g.xlarge: {arch: arm}, r6g.2xlarge: {arch: arm}, r6g.4xlarge: {arch: arm}, r6g.8xlarge: {arch: arm}, r6g.12xlarge: {arch: arm}, r6g.16xlarge: {arch: arm}
,t3.nano: {arch: x86}, t3.micro: {arch: x86}, t3.small: {arch: x86}, t3.medium: {arch: x86}, t3.large: {arch: x86}, t3.xlarge: {arch: x86}, t3.2xlarge: {arch: x86}, m5.large: {arch: x86}, m5.xlarge: {arch: x86}, m5.2xlarge: {arch: x86}, m5.4xlarge: {arch: x86}, m5.8xlarge: {arch: x86}, m5.12xlarge: {arch: x86}, m5.16xlarge: {arch: x86}, m5.24xlarge: {arch: x86}, m5n.large: {arch: x86}, m5n.xlarge: {arch: x86}, m5n.2xlarge: {arch: x86}, m5n.4xlarge: {arch: x86}, m5n.8xlarge: {arch: x86}, m5n.12xlarge: {arch: x86}, m5n.16xlarge: {arch: x86}, m5n.24xlarge: {arch: x86}, c5.large: {arch: x86}, c5.xlarge: {arch: x86}, c5.2xlarge: {arch: x86}, c5.4xlarge: {arch: x86}, c5.9xlarge: {arch: x86}, c5.12xlarge: {arch: x86}, c5.18xlarge: {arch: x86}, c5.24xlarge: {arch: x86}, c5n.large: {arch: x86}, c5n.xlarge: {arch: x86}, c5n.2xlarge: {arch: x86}, c5n.4xlarge: {arch: x86}, c5n.9xlarge: {arch: x86}, c5n.18xlarge: {arch: x86}, r5.large: {arch: x86}, r5.xlarge: {arch: x86}, r5.2xlarge: {arch: x86}, r5.4xlarge: {arch: x86}, r5.8xlarge: {arch: x86}, r5.12xlarge: {arch: x86}, r5.16xlarge: {arch: x86}, r5.24xlarge: {arch: x86}, r5n.large: {arch: x86}, r5n.xlarge: {arch: x86}, r5n.2xlarge: {arch: x86}, r5n.4xlarge: {arch: x86}, r5n.8xlarge: {arch: x86}, r5n.12xlarge: {arch: x86}, r5n.16xlarge: {arch: x86}, r5n.24xlarge: {arch: x86}
}
Conditions:
UseArm: !Equals [!FindInMap [AmiMap, !Ref "InstanceType", "arch"], arm]
KeyNameEmpty: !Equals [!Ref KeyName, '']
HasSSHLocation: !Not [!Equals [!Ref SSHLocation, '']]
Resources:
InstancesGroup:
Type: 'AWS::AutoScaling::AutoScalingGroup'
Properties:
VPCZoneIdentifier: !Ref HandlerSubnetIds
LaunchConfigurationName: !Ref LaunchConfig
MinSize: !Ref InstanceNumber
MaxSize: !Ref InstanceNumber
HealthCheckType: ELB
HealthCheckGracePeriod: 180
TargetGroupARNs:
- !Ref NLBTargetGroup
TerminationPolicies:
- OldestInstance
UpdatePolicy:
AutoScalingRollingUpdate:
MinInstancesInService: 0
LaunchConfig:
Type: 'AWS::AutoScaling::LaunchConfiguration'
Properties:
KeyName: !If [KeyNameEmpty, !Ref 'AWS::NoValue', !Ref KeyName]
ImageId: !If [UseArm, !Ref LatestAmiIdArm, !Ref LatestAmiIdX86]
SecurityGroups:
- !Ref InstanceSecurityGroup
InstanceType: !Ref InstanceType
AssociatePublicIpAddress: "false"
UserData: !Base64
'Fn::Join':
- ''
- - |
#!/bin/bash -xe
#
#
### update and install ###
#
export HOME=~
yum update -y
yum install git -y
yum install go -y
# dependency of github.com/google/gopacket
yum install libpcap-devel -y
#
#
### dependency of main.go ###
go env GOPATH
echo 'export GOPATH=$HOME/go' >>~/.bash_profile
source ~/.bash_profile
#
#
#
### create a virtual network interface that gets decapsulated VXLAN packets
#### compile & run go script ###
mkdir "vxlan-to-http-request"
cd vxlan-to-http-request/
go mod init vxlan-to-http-request
go get "github.com/google/gopacket"
wget https://github.com/Surfline/http-requests-mirroring-all-services/raw/main/main.go
go build ./
go install vxlan-to-http-request
- 'Fn::Sub': |
sudo ip link add vxlan0 type vxlan id ${TrafficMirroringVNI} dev eth0 dstport 4789
sudo ip link set vxlan0 up
$GOPATH"/bin/vxlan-to-http-request" -destination "${RequestsForwardDestination}" -percentage "${ForwardPercentage}" -percentage-by "${PercentageBy}" -percentage-by-header "${PercentageByHeader}" -filter-request-port "${FilterByDestinationPort}"
NetworkLoadBalancer:
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
Properties:
Scheme: internal
Subnets: !Ref HandlerSubnetIds
Type: network
LoadBalancerAttributes:
- Key: load_balancing.cross_zone.enabled
Value: "true"
NLBListener:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref NLBTargetGroup
LoadBalancerArn: !Ref NetworkLoadBalancer
Port: 4789
Protocol: UDP
NLBTargetGroup:
Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
Properties:
Port: 4789
Protocol: UDP
VpcId: !Ref HandlerVpcId
InstanceSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Enable SSH access, VXLAN UDP (for traffic), and VXLAN TCP (for health checks) from the load balancer only
SecurityGroupIngress:
- IpProtocol: udp
FromPort: 4789
ToPort: 4789
CidrIp: !Ref FilterByDestinationCidrBlock
- IpProtocol: tcp
FromPort: 4789
ToPort: 4789
CidrIp: !GetAtt VpcInfo.CidrBlock
VpcId: !Ref HandlerVpcId
InstanceSecurityGroupSSHIngress:
Type: 'AWS::EC2::SecurityGroupIngress'
Condition: HasSSHLocation
Properties:
GroupId: !Ref InstanceSecurityGroup
IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref SSHLocation
TrafficMirrorTarget:
Type: 'AWS::EC2::TrafficMirrorTarget'
Properties:
NetworkLoadBalancerArn: !Ref NetworkLoadBalancer
TrafficMirrorFilter:
Type: 'AWS::EC2::TrafficMirrorFilter'
TrafficMirrorFilterRule:
Type: 'AWS::EC2::TrafficMirrorFilterRule'
Properties:
DestinationCidrBlock: !Ref FilterByDestinationCidrBlock
DestinationPortRange:
FromPort: !Ref FilterByDestinationPort
ToPort: !Ref FilterByDestinationPort
Protocol: 6
RuleAction: accept
RuleNumber: 100
SourceCidrBlock: !Ref FilterBySourceCidrBlock
TrafficDirection: ingress
TrafficMirrorFilterId: !Ref TrafficMirrorFilter
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: root
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*'
- Effect: Allow
Action:
- ec2:DescribeVpcs
Resource: '*'
# NOTE: Pay special attention to the indentatiion in the Python code below.
# Lines that appear blank are likely not blank, but have leading spaces.
GetAttFromParam:
Type: AWS::Lambda::Function
Properties:
Description: Look up info from a VPC or subnet ID
Handler: index.handler
MemorySize: 128
Role: !GetAtt LambdaExecutionRole.Arn
Runtime: "python3.7"
Timeout: 30
Code:
ZipFile: |
import json
import boto3
import cfnresponse
import logging
def handler(event, context):
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# initialize our responses, assume failure by default
response_data = {}
response_status = cfnresponse.FAILED
logger.info('Received event: {}'.format(json.dumps(event)))
if event['RequestType'] == 'Delete':
response_status = cfnresponse.SUCCESS
cfnresponse.send(event, context, response_status, response_data)
try:
ec2=boto3.client('ec2')
except Exception as e:
logger.info('boto3.client failure: {}'.format(e))
cfnresponse.send(event, context, response_status, response_data)
name_filter = event['ResourceProperties']['NameFilter']
name_filter_parts = name_filter.split('-')
resource_type=name_filter_parts[0]
if resource_type == "vpc":
try:
vpcs=ec2.describe_vpcs(VpcIds=[name_filter])
except Exception as e:
logger.info('ec2.describe_vpcs failure: {}'.format(e))
cfnresponse.send(event, context, response_status, response_data)
number_of_vpcs = len(vpcs['Vpcs'])
logger.info('number of vpcs returned: {}'.format(number_of_vpcs))
if number_of_vpcs == 1:
CidrBlock = vpcs['Vpcs'][0]['CidrBlock']
response_data['CidrBlock'] = CidrBlock
logger.info('vpc CidrBlock {}'.format(CidrBlock))
response_status = cfnresponse.SUCCESS
cfnresponse.send(event, context, response_status, response_data)
elif number_of_vpcs == 0:
logger.info('no matching vpcs for filter {}'.format(name_filter))
cfnresponse.send(event, context, response_status, response_data)
else:
logger.info('multiple matching vpcs for filter {}'.format(name_filter))
cfnresponse.send(event, context, response_status, response_data)
elif resource_type == "subnet":
try:
subnets = ec2.describe_subnets(SubnetIds=[name_filter])
except Exception as e:
logger.info('ec2.describe_subnets failure: {}'.format(e))
cfnresponse.send(event, context, response_status, response_data)
number_of_subnets = len(subnets['Subnets'])
logger.info('number of subnets returned: {}'.format(number_of_subnets))
if number_of_subnets == 1:
CidrBlock = subnets['Subnets'][0]['CidrBlock']
VpcId = subnets['Subnets'][0]['VpcId']
AvailabilityZone = subnets['Subnets'][0]['AvailabilityZone']
response_data['AvailabilityZone'] = AvailabilityZone
response_data['CidrBlock'] = CidrBlock
response_data['VpcId'] = VpcId
logger.info('subnet AvailabilityZone {}'.format(AvailabilityZone))
logger.info('subnet CidrBlock {}'.format(CidrBlock))
logger.info('subnet VpcId {}'.format(VpcId))
response_status = cfnresponse.SUCCESS
cfnresponse.send(event, context, response_status, response_data)
elif number_of_subnets == 0:
logger.info('no matching subnet for filter {}'.format(name_filter))
cfnresponse.send(event, context, response_status, response_data)
else:
logger.info('multiple matching subnets for filter {}'.format(name_filter))
cfnresponse.send(event, context, response_status, response_data)
else:
logger.info('invalid resource type {}'.resource_type)
cfnresponse.send(event, context, response_status, response_data)
VpcInfo:
Type: Custom::VpcInfo
Properties:
ServiceToken: !GetAtt GetAttFromParam.Arn
NameFilter: !Ref HandlerVpcId
Outputs:
TrafficMirrorTarget:
Description: The ID of the Traffic Mirror target.
Value: !Ref TrafficMirrorTarget
TrafficMirrorFilter:
Description: The ID of the Traffic Mirror filter.
Value: !Ref TrafficMirrorFilter