Skip to content

Instantly share code, notes, and snippets.

@pbzona
Created November 14, 2017 23:43
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save pbzona/91d6a6dd4b8cf27c9a032afe32703858 to your computer and use it in GitHub Desktop.
Save pbzona/91d6a6dd4b8cf27c9a032afe32703858 to your computer and use it in GitHub Desktop.
Roll your own VPN with AWS CloudFormation - Part two
# 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