Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
OpenVPNWalkthrough-Part2.yaml
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: wget -qO- https://github.com/OpenVPN/easy-rsa/releases/download/2.2.2/EasyRSA-2.2.2.tgz | tar xvz -C /opt/
02_install_software_copy_easyrsa:
command: cp -R /opt/EasyRSA-2.2.2 /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
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.