-
Notifications
You must be signed in to change notification settings - Fork 16
/
cfn-openvpn.yaml
575 lines (526 loc) · 20.3 KB
/
cfn-openvpn.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
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
# Personal OpenVPN Server Deployment
# Updates applied by Derek Ardolf / @ScriptAutomate / https://icanteven.io
# 08/06/2021 - Version 2.1:
# - Update default OpenVPN version to latest in epel
# - Update README to direct users to use awscli v2 and not v1
# 11/08/2019 - Version 2.x:
# - Released with many updates, and tracked here: https://github.com/ScriptAutomate/openvpn-cfn
#
# Created by John Creecy / @zugdug
# 10/30/2017 - Version 1.0:
# - Original: https://gist.github.com/zugdud/b39eea02faa6926305f57fbde8d31b68
# Original Linux Academy blog series walkthrough on building the old OpenVPN template:
# - Part 1: https://linuxacademy.com/blog/tutorials/roll-vpn-aws-cloudformation-part-one/
# - Part 2: https://linuxacademy.com/blog/tutorials/roll-vpn-aws-cloudformation-part-two/
# - Part 3: https://linuxacademy.com/blog/tutorials/how-to-roll-your-own-vpn-with-aws-cloudformation-part-three/
AWSTemplateFormatVersion: '2010-09-09'
Description: OpenVPN Stack
Parameters:
OpenVPNProtocol:
Type: String
Default: udp
AllowedValues:
- udp
- tcp
OpenVPNPort:
Type: Number
Default: 1194
Description: OpenVPN port. Standards - 443 for TCP / 1194 for UDP
AllowedValues:
- 443
- 1194
SSHKeyName:
Type: AWS::EC2::KeyPair::KeyName
Description: SSH Key for the OpenVPN Instance
ConstraintDescription: Must be the name of an existing EC2 KeyPair.
ClientIPCIDR:
Type: String
Default: 0.0.0.0/0
Description: CIDR IP to be granted access by the SG, use 0.0.0.0/0 to accept all IPs
AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'
# Parameter store makes finding latest AMI image id easy
# Is region locked, by default. Wherever CFN is spun up,
# resulting image id is specific to source region!
# Source: https://aws.amazon.com/blogs/compute/query-for-the-latest-amazon-linux-ami-ids-using-aws-systems-manager-parameter-store/
LatestAmiId:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
OpenVPNVersion:
Type: "String"
Default: "2.4.11" # Latest Tested Version on Amazon Linux 2
EasyRSAREQCN:
Type: "String"
Default: "ChangeMe"
EasyRSAREQCNAlgorithm:
Type: 'String'
Default: rsa
AllowedValues:
- rsa
- ec
Resources:
# Our VPC, most of our resources will be provisioned within
OpenVPNVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/22 # We only need 1 IPaddress for our OpenVPN server, I just like even numbers and 8-bit subnets
Tags:
- Key: Name
Value: personal-OpenVPN-vpc
# The only subnet we will create within our VPC, our OpenVPN server will be provisioned within
# This subnet will be assigned a default route out to the internet, hence the name.
MyPublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref OpenVPNVPC
CidrBlock: 10.0.0.0/24 # 8-bit subnet provides 256 addresses, 251 of which are usable
Tags:
- Key: Name
Value: personal-OpenVPN-publicSubnet
# We will need our VPC to have access to the internet
myInternetGateway:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: Name
Value: personal-OpenVPN-myIGW
# The VPC route table
myRouteTablePublic:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref OpenVPNVPC
Tags:
- Key: Name
Value: personal-OpenVPN-myRouteTablePublic
# Attach the Internet Gateway to OpenVPNVPC
AttachInternetGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref OpenVPNVPC
InternetGatewayId: !Ref myInternetGateway
# Add a default route to our VPCs internet gateway
RouteDefaultPublic:
Type: "AWS::EC2::Route"
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref myInternetGateway
RouteTableId: !Ref myRouteTablePublic
# Associate our route table to our subnet
MyPublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref MyPublicSubnet
RouteTableId: !Ref myRouteTablePublic
# Request a new Elastic IP Address
OpenVPNEIP:
Type: "AWS::EC2::EIP"
Properties:
Domain: vpc
# Bind our Elastic IP Address to an Elastic Network Interface
AssociateManagementAccessPort:
Type: AWS::EC2::EIPAssociation
Properties:
AllocationId: !GetAtt OpenVPNEIP.AllocationId
NetworkInterfaceId: !Ref myNetworkInterface
# Create a security group for the ENI that will be attached to our OpenVPN server
# OpenVPN and SSH port access
OpenVPNInstanceSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SG for OpenVPN Server
VpcId: !Ref OpenVPNVPC
SecurityGroupIngress:
- IpProtocol: !Ref OpenVPNProtocol
FromPort: !Ref OpenVPNPort
ToPort: !Ref OpenVPNPort
CidrIp: !Ref ClientIPCIDR
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref ClientIPCIDR
# This is the IAM role which will be associated with our EC2 instance
myEC2InstanceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
# This is the S3 bucket where our client profile and secrets will be stored
OpenVPNS3Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
# This is the IAM policy which will be attached to our EC2 instance role
OpenVPNAccessPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: OpenVPNAccessPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- s3:*
Effect: Allow
Resource: !Join
- ''
- - 'arn:aws:s3:::'
- !Ref OpenVPNS3Bucket
- /*
Roles:
- !Ref myEC2InstanceRole
# Binding profile for our myEC2InstanceRole to the actual EC2 instance
ec2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: "/"
Roles:
- !Ref myEC2InstanceRole
# The Elastic Network Interface which will be attached to our EC2 instance
# Our security group, OpenVPNInstanceSG is also associated with this interface
myNetworkInterface:
Type: AWS::EC2::NetworkInterface
Properties:
SubnetId: !Ref MyPublicSubnet
Description: Public Interface
GroupSet:
- !Ref OpenVPNInstanceSG
SourceDestCheck: false
Tags:
-
Key: Name
Value: Public ENI
# The EC2 instance which will host OpenVPN
EC2OpenVPNInstance:
Type: "AWS::EC2::Instance"
DependsOn: OpenVPNS3Bucket
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t2.micro
SourceDestCheck: false
KeyName: !Ref SSHKeyName
NetworkInterfaces:
- NetworkInterfaceId: !Ref myNetworkInterface
DeviceIndex: "0"
IamInstanceProfile: !Ref ec2InstanceProfile
Tags:
-
Key: Name
Value: OpenVPN Server
# User data is passed into the instance, executed as a shell script, and run only once on first boot
# Here we invoke cfn-init on our configSet myCfnConfigSet
# The last command emits a cfn-signal to the CloudFormation stack which completes the associated CreationPolicy timer
UserData:
"Fn::Base64":
!Sub |
#!/bin/bash
yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
yum update -y
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2OpenVPNInstance --configsets myCfnConfigSet --region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource EC2OpenVPNInstance --region ${AWS::Region}
# The CloudFormation stack will wait to mark the EC2OpenVPNInstance as CREATE_COMPLETE until we receive a signal from the instance, or 10 minutes elapses.
CreationPolicy:
ResourceSignal:
Count: "1"
Timeout: PT10M
Metadata:
AWS::CloudFormation::Init:
# Our cfn-init config set rules, divided into logical sections to make reading it easier, hopefully :)
configSets:
myCfnConfigSet:
- "configure_cfn"
- "install_software"
- "generate_easyrsa_vars"
- "generate_secrets"
- "generate_client"
- "configure_server"
- "upload_files"
# Configure and start cfn-hup
# cfn-hup will poll the stack for changes, and if possible, apply instance changes in place on the instance
configure_cfn:
files:
/etc/cfn/hooks.d/cfn-auto-reloader.conf:
content: !Sub |
[cfn-auto-reloader-hook]
triggers=post.update
path=Resources.EC2OpenVPNInstance.Metadata.AWS::CloudFormation::Init
action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2OpenVPNInstance --configsets myCfnConfigSet --region ${AWS::Region}
mode: "000400"
owner: root
group: root
/etc/cfn/cfn-hup.conf:
content: !Sub |
[main]
stack=${AWS::StackId}
region=${AWS::Region}
verbose=true
interval=1
mode: "000400"
owner: root
group: root
services:
sysvinit:
cfn-hup:
enabled: "true"
ensureRunning: "true"
files:
- "/etc/cfn/cfn-hup.conf"
- "/etc/cfn/hooks.d/cfn-auto-reloader.conf"
# Install the latest version of openvpn via the yum package manager
# Install easy-rsa via the EPEL repo
# Make a copy of the installed files to /opt/easy-rsa as our working directory
install_software:
packages:
yum:
openvpn: [ !Ref OpenVPNVersion ]
easy-rsa: []
yum-plugin-versionlock: []
commands:
01_install_software_copy_easyrsa:
command: "cp -RL /usr/share/easy-rsa/3 /opt/easy-rsa"
02_lock_openvpn_version:
command: "yum versionlock openvpn"
# Default is rsa for algorithm
# Changing to ECDSA
generate_easyrsa_vars:
files:
/opt/easy-rsa/vars:
content: !Sub |
set_var EASYRSA_ALGO ${EasyRSAREQCNAlgorithm}
set_var EASYRSA_REQ_CN ${EasyRSAREQCN}
mode: "000644"
owner: "root"
group: "root"
# Use easy-rsa to generate our certificate authority (CA) and encryption keys
# Use openvpn to generate a static TLS client cert, this is what the client will use authenticate with the the OpenVPN server
generate_secrets:
commands:
01_generate_secrets_init_pkidir:
cwd: "/opt/easy-rsa"
test: "test ! -e /opt/easy-rsa/pki/openssl-easyrsa.cnf"
command: "/opt/easy-rsa/easyrsa init-pki"
02_generate_secrets_run_build-ca:
cwd: "/opt/easy-rsa"
test: "test ! -e /opt/easy-rsa/pki/ca.crt"
command: "/opt/easy-rsa/easyrsa --batch build-ca nopass"
03_generate_secrets_run_build-dh:
cwd: "/opt/easy-rsa"
test: "test ! -e /opt/easy-rsa/pki/dh.pem"
command: "/opt/easy-rsa/easyrsa gen-dh"
04_generate_secrets_run_build-server-full:
cwd: "/opt/easy-rsa"
test: "test ! -e /opt/easy-rsa/pki/issued/server.crt"
command: "/opt/easy-rsa/easyrsa build-server-full server nopass"
05_generate_empty_revoke_list:
cwd: "/opt/easy-rsa"
test: "test ! -e /opt/easy-rsa/pki/crl.pem"
command: "/opt/easy-rsa/easyrsa gen-crl"
06_generate_secrets_statictlssecret:
cwd: "/opt/easy-rsa"
test: "test ! -e /opt/easy-rsa/pki/statictlssecret.key"
command: "openvpn --genkey --secret pki/statictlssecret.key"
# Generate the openvpn client configuration files
# Generate a script which will collect the client configuration into an OVPN profile
generate_client:
files:
/opt/easy-rsa/openvpn_client.conf:
content: !Sub |
client
dev tun
proto ${OpenVPNProtocol}
remote ${OpenVPNEIP} ${OpenVPNPort}
tls-client
tls-version-min 1.2
tls-cipher TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256
cipher AES-256-CBC
auth SHA512
resolv-retry infinite
auth-retry none
nobind
persist-key
persist-tun
remote-cert-tls server
verb 3
mode: "000700"
owner: root
group: root
/opt/easy-rsa/gen_ovpn_profile.sh:
content: !Sub |
(cat /opt/easy-rsa/openvpn_client.conf
echo '<key>'
cat pki/private/clientuser.key
echo '</key>'
echo '<cert>'
cat pki/issued/clientuser.crt
echo '</cert>'
echo '<ca>'
cat pki/ca.crt
echo '</ca>'
echo 'key-direction 1'
echo '<tls-auth>'
cat pki/statictlssecret.key
echo '</tls-auth>'
) > /opt/easy-rsa/pki/openvpn_clientuser.ovpn
mode: "000700"
owner: root
group: root
commands:
01_generate_client_run_build-key:
cwd: "/opt/easy-rsa"
test: "test ! -e /opt/easy-rsa/pki/issued/clientuser.crt"
command: "/opt/easy-rsa/easyrsa build-client-full clientuser nopass"
02_generate_client_generate_ovpn_profile:
cwd: "/opt/easy-rsa"
test: "test ! -e /opt/easy-rsa/pki/openvpn_clientuser.ovpn"
command: "/opt/easy-rsa/gen_ovpn_profile.sh"
# Generate configuration file for the OpenVPN server
# Enable IP forwarding in Linux
# Start OpenVPN
configure_server:
files:
/opt/openvpn/server.conf:
content: !Sub |
port ${OpenVPNPort}
proto ${OpenVPNProtocol}
dev tun
server 172.16.0.0 255.255.252.0
push "redirect-gateway def1"
ca /opt/easy-rsa/pki/ca.crt
cert /opt/easy-rsa/pki/issued/server.crt
key /opt/easy-rsa/pki/private/server.key
dh /opt/easy-rsa/pki/dh.pem
crl-verify /opt/easy-rsa/pki/crl.pem
tls-server
tls-auth /opt/easy-rsa/pki/statictlssecret.key 0
tls-version-min 1.2
tls-cipher TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256
cipher AES-256-CBC
auth SHA512
ifconfig-pool-persist ipp.txt
keepalive 10 120
ping-timer-rem
persist-key
persist-tun
status openvpn-status.log
log-append /var/log/openvpn.log
verb 3
max-clients 5
user nobody
group nobody
mode: "000644"
owner: "root"
group: "root"
commands:
01_configure_server_sysctl_ipforward:
command: echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
02_configure_server_sysctl_reload:
command: "sysctl -p"
03_configure_server_iptables_nat:
command: "iptables -t nat -A POSTROUTING -s 172.16.0.0/22 -o eth0 -j MASQUERADE"
04_configure_server_update_config:
test: "test -e /opt/openvpn/server.conf"
command: "cp -f /opt/openvpn/server.conf /etc/openvpn/server/server.conf"
05_configure_systemctl_openvpn_enable:
test: "test -e /lib/systemd/system/[email protected]"
command: "systemctl enable openvpn-server@server"
06_configure_systemctl_openvpn_start:
test: "test -e /lib/systemd/system/[email protected]"
command: "systemctl start openvpn-server@server"
# Zip the client files
# Upload the client file archive and cfn-init log to S3
upload_files:
commands:
01_upload_file_openvpn-clientuser.ovpn:
cwd: "/opt/easy-rsa/pki"
command: !Sub |
aws s3 cp openvpn_clientuser.ovpn s3://${OpenVPNS3Bucket}/client/openvpn_clientuser.ovpn
02_upload_file_cfn_init_log:
cwd: "/var/log"
test: "test -e /var/log/cfn-init.log"
command: !Sub |
aws s3 cp /var/log/cfn-init.log s3://${OpenVPNS3Bucket}/log/genSecrets_cfn-init.log
# Original Source for the following Python 3.7 CustomResource section:
# https://github.com/mike-mosher/custom-resource-s3-bucket-delete
EmptyS3BucketLambdaExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action:
- "sts:AssumeRole"
Policies:
- PolicyName: LoggingPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
- PolicyName: S3Policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:List*
- s3:DeleteObject
Resource: "*"
EmptyS3BucketLambdaFunction:
Type: "AWS::Lambda::Function"
Properties:
Code:
ZipFile: |
import cfnresponse
import boto3
def handler(event, context):
print(event)
print('boto version ' + boto3.__version__)
# Globals
responseData = {}
ResponseStatus = cfnresponse.SUCCESS
s3bucketName = event['ResourceProperties']['s3bucketName']
if event['RequestType'] == 'Create':
responseData['Message'] = "Resource creation successful!"
elif event['RequestType'] == 'Update':
responseData['Message'] = "Resource update successful!"
elif event['RequestType'] == 'Delete':
# Need to empty the S3 bucket before it is deleted
s3 = boto3.resource('s3')
bucket = s3.Bucket(s3bucketName)
bucket.objects.all().delete()
responseData['Message'] = "Resource deletion successful!"
cfnresponse.send(event, context, ResponseStatus, responseData)
Handler: index.handler
Runtime: python3.7
Role: !GetAtt EmptyS3BucketLambdaExecutionRole.Arn
EmptyS3Bucket:
Type: Custom::CustomResource
Properties:
ServiceToken: !GetAtt EmptyS3BucketLambdaFunction.Arn
s3bucketName: !Ref OpenVPNS3Bucket
Outputs:
VPNClientsS3Bucket:
Description: S3 bucket name
Value: !Ref OpenVPNS3Bucket
VPNClientUserFile:
Description: Initial generated OVPN of clientuser
Value: !Sub |
s3://${OpenVPNS3Bucket}/client/openvpn_clientuser.ovpn
OpenVPNEIP:
Description: Instance EIP
Value: !Ref OpenVPNEIP
EC2OpenVPNInstance:
Description: EC2 Instance
Value: !Ref EC2OpenVPNInstance