Created
November 14, 2017 23:43
-
-
Save pbzona/91d6a6dd4b8cf27c9a032afe32703858 to your computer and use it in GitHub Desktop.
Roll your own VPN with AWS CloudFormation - Part two
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Credit to John Creecy | |
# Original can be found at https://gist.github.com/zugdud/f5453af2c827eba38bb036b19e10b371 | |
AWSTemplateFormatVersion: '2010-09-09' | |
Description: OpenVPN Stack | |
Parameters: | |
OpenVPNPort: | |
Type: Number | |
Default: 1194 | |
Description: OpenVPN UDP port | |
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 | |
Mappings: | |
RegionMap: | |
us-east-1: | |
"AMAZONLINUXAMI" : "ami-8c1be5f6" # Amazon Linux AMI 2017.09 | |
us-east-2: | |
"AMAZONLINUXAMI" : "ami-c5062ba0" # Amazon Linux AMI 2017.09 | |
us-west-1: | |
"AMAZONLINUXAMI" : "ami-02eada62" # Amazon Linux AMI 2017.09 | |
us-west-2: | |
"AMAZONLINUXAMI" : "ami-e689729e" # Amazon Linux AMI 2017.09 | |
ca-central-1: | |
"AMAZONLINUXAMI" : "ami-fd55ec99" # Amazon Linux AMI 2017.09 | |
eu-west-1: | |
"AMAZONLINUXAMI" : "ami-acd005d5" # Amazon Linux AMI 2017.09 | |
eu-central-1: | |
"AMAZONLINUXAMI" : "ami-c7ee5ca8" # Amazon Linux AMI 2017.09 | |
eu-west-2: | |
"AMAZONLINUXAMI" : "ami-1a7f6d7e" # Amazon Linux AMI 2017.09 | |
ap-southeast-1: | |
"AMAZONLINUXAMI" : "ami-0797ea64" # Amazon Linux AMI 2017.09 | |
ap-southeast-2: | |
"AMAZONLINUXAMI" : "ami-8536d6e7" # Amazon Linux AMI 2017.09 | |
ap-northeast-2: | |
"AMAZONLINUXAMI" : "ami-9bec36f5" # Amazon Linux AMI 2017.09 | |
ap-northeast-1: | |
"AMAZONLINUXAMI" : "ami-2a69be4c" # Amazon Linux AMI 2017.09 | |
ap-south-1: | |
"AMAZONLINUXAMI" : "ami-4fc58420" # Amazon Linux AMI 2017.09 | |
sa-east-1: | |
"AMAZONLINUXAMI" : "ami-f1344b9d" # Amazon Linux AMI 2017.09 | |
Resources: | |
# Our VPC, most of our resources will be provisioned within | |
myVPC: | |
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 myVPC | |
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 myVPC | |
Tags: | |
- Key: Name | |
Value: personal-OpenVPN-myRouteTablePublic | |
# Attach the Internet Gateway to myVPC | |
AttachInternetGateway: | |
Type: AWS::EC2::VPCGatewayAttachment | |
Properties: | |
VpcId: !Ref myVPC | |
InternetGatewayId: !Ref myInternetGateway | |
# Add a default route to our VPCs internet gateway | |
RouteDefaultPublic: | |
Type: "AWS::EC2::Route" | |
DependsOn: myInternetGateway | |
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 | |
myEIP: | |
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 myEIP.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 myVPC | |
SecurityGroupIngress: | |
- IpProtocol: udp | |
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 IAM policy which will be attached to our EC2 instance role | |
myAccessPolicy: | |
Type: AWS::IAM::Policy | |
Properties: | |
PolicyName: myAccessPolicy | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Action: | |
- s3:* | |
Effect: Allow | |
Resource: "*" | |
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 | |
# This is the S3 bucket where our client profile and secrets will be stored | |
myS3Bucket: | |
Type: AWS::S3::Bucket | |
Properties: | |
AccessControl: Private | |
# The EC2 instance which will host OpenVPN | |
EC2OpenVPNInstance: | |
Type: "AWS::EC2::Instance" | |
Properties: | |
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMAZONLINUXAMI] | |
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 update -y aws-cfn-bootstrap | |
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2OpenVPNInstance --configsets myCfnConfigSet --region ${AWS::Region} | |
yum -y update | |
/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 recieve 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_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: [] | |
commands: | |
01_install_software_install_easyrsa: | |
command: "yum install easy-rsa -y --enablerepo=epel" | |
02_install_software_copy_easyrsa: | |
command: "cp -R /usr/share/easy-rsa/2.0 /opt/easy-rsa" | |
# Use easy-rsa to generate our certificate authority (CA) and encryption keys | |
# I'm not sure if it's possible to source files into the cfn-init environment, so I am just doing it inline with each command | |
# The easy-rsa scripts use an interactive mode flag which is what the sed command is removing | |
# Use openssl 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_clean_keysdir: | |
cwd: "/opt/easy-rsa" | |
test: "test -e /opt/easy-rsa/clean-all" | |
command: "source /opt/easy-rsa/vars;/opt/easy-rsa/clean-all" | |
02_generate_secrets_update_build-ca: | |
cwd: "/opt/easy-rsa" | |
test: "test -e /opt/easy-rsa/build-ca" | |
command: !Sub | | |
sed -i 's/--interact//g' /opt/easy-rsa/build-ca | |
03_generate_secrets_run_build-ca: | |
cwd: "/opt/easy-rsa" | |
test: "test -e /opt/easy-rsa/build-ca" | |
command: "source /opt/easy-rsa/vars;/opt/easy-rsa/build-ca" | |
04_generate_secrets_run_build-dh: | |
cwd: "/opt/easy-rsa" | |
test: "test -e /opt/easy-rsa/build-dh" | |
command: "source /opt/easy-rsa/vars;/opt/easy-rsa/build-dh" | |
05_generate_secrets_update_build-key-server: | |
cwd: "/opt/easy-rsa" | |
test: "test -e /opt/easy-rsa/build-key-server" | |
command: !Sub | | |
sed -i 's/--interact//g' /opt/easy-rsa/build-key-server | |
06_generate_secrets_run_build-key-server: | |
cwd: "/opt/easy-rsa" | |
test: "test -e /opt/easy-rsa/build-key-server" | |
command: "source /opt/easy-rsa/vars;/opt/easy-rsa/build-key-server server" | |
07_generate_secrets_statictlssecret: | |
cwd: "/opt/easy-rsa/keys" | |
command: "openvpn --genkey --secret statictlssecret.key" | |
# Generate the openvpn client configuration files | |
# Generate a script which will concatinate the client configuration with the cert and encryption key to generate the ovpn profile | |
generate_client: | |
files: | |
/opt/easy-rsa/openvpn_client.conf: | |
content: !Sub | | |
client | |
dev tun | |
proto udp | |
remote ${myEIP} ${OpenVPNPort} | |
ca ca.crt | |
cert clientuser.crt | |
key clientuser.key | |
tls-client | |
tls-auth statictlssecret.key 1 | |
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 | |
ns-cert-type server | |
comp-lzo | |
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 keys/clientuser.key | |
echo '</key>' | |
echo '<cert>' | |
cat keys/clientuser.crt | |
echo '</cert>' | |
echo '<ca>' | |
cat keys/ca.crt | |
echo '</ca>' | |
) > /opt/easy-rsa/keys/openvpn_clientuser.ovpn | |
mode: "000700" | |
owner: root | |
group: root | |
commands: | |
01_generate_client_update_build-key: | |
cwd: "/opt/easy-rsa" | |
test: "test -e /opt/easy-rsa/build-key" | |
command: !Sub | | |
sed -i 's/--interact//g' /opt/easy-rsa/build-key | |
02_generate_client_run_build-key: | |
cwd: "/opt/easy-rsa" | |
test: "test -e /opt/easy-rsa/build-key" | |
command: "source /opt/easy-rsa/vars;/opt/easy-rsa/build-key clientuser" | |
03_generate_client_generate_ovpn_profile: | |
cwd: "/opt/easy-rsa" | |
test: "test -e /opt/easy-rsa/gen_ovpn_profile.sh" | |
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 udp | |
dev tun | |
server 172.16.0.0 255.255.252.0 | |
push "redirect-gateway def1" | |
ca /opt/easy-rsa/keys/ca.crt | |
cert /opt/easy-rsa/keys/server.crt | |
key /opt/easy-rsa/keys/server.key | |
dh /opt/easy-rsa/keys/dh2048.pem | |
tls-server | |
tls-auth /opt/easy-rsa/keys/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 | |
comp-lzo | |
persist-key | |
persist-tun | |
status openvpn-status.log | |
log-append /var/log/openvpn.log | |
verb 3 | |
max-clients 100 | |
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: | |
command: "cp -rf /opt/openvpn/server.conf /etc/openvpn/server.conf" | |
05_configure_server_openvpn_start: | |
command: "service openvpn start" | |
# Zip the client files | |
# Upload the client file archive and cfn-init log to S3 | |
upload_files: | |
commands: | |
01_upload_files_zipfiles: | |
cwd: "/opt/easy-rsa/keys" | |
command: "zip openVPNClientFiles.zip ca.crt statictlssecret.key clientuser.key clientuser.crt openvpn_clientuser.ovpn" | |
02_upload_files_s3cp_openVPNClientFiles: | |
cwd: "/opt/easy-rsa/keys" | |
command: !Sub | | |
aws s3 cp openVPNClientFiles.zip s3://${myS3Bucket}/client/openVPNClientFiles.zip | |
03_upload_files_s3cp_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://${myS3Bucket}/log/genSecrets_cfn-init.log | |
Outputs: | |
myS3BucketOut: | |
Description: S3 bucket name | |
Value: !Ref myS3Bucket | |
myEIPOut: | |
Description: Instance EIP | |
Value: !Ref myEIP | |
EC2OpenVPNInstanceOut: | |
Description: EC2 Instance | |
Value: !Ref EC2OpenVPNInstance |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment