OpenVPN on AWS VPC with LDAP
I recently just fought my way through getting OpenVPN community edition running on our AWS VPC environment and wanted to share so that other can learn. There are a few key take aways and I'm just going to focus on the key elements.
- Past experience has shown that you don't want to use 192.168.X.X or 10.0.X.X as your VPN networks. They are frequently used by home routers and having people configure into the VPN with that network conflict isn't worth the effort.
- The Ubuntu package
openvpn-auth-ldap
will coredump, don't use it go via the PAM route. - You need to have separate authentication from your distributed configuration. e.g. Don't assume that the key files are all that you need.
A little credit - Found these posts to be quite helpful, along with about 50 others.
- How to create an openvpn bastion machine in aws
- Setup PAM authentication with OpenVPNs auth PAM module
This guide is only a few steps long:
- Creating an instance
- Configuration OpenVPN
- UserData for your instance (boot scripts)
Step 1 - Creating an Instance
Create your VPN instance as a bastion host, where it's on the "public" subnet of your VPC with an IP address assigned. Do not forget that you need an instance that has Source/Dest checks disabled
Note: Configuration Information
- VPC Cidr is 172.20.0.0/16
- VPN network will be 172.29.0.0/20
- VPN public network is 172.20.101.0/20
- Using a Ubuntu 16.04 image
You need to allow UDP to port 1194 to have access to the server.
Step 1.5 - LDAP Directory
Cut to the chase, I really don't want to manage LDAP directories. This is a great cloud function and have deligated it to Jumpcloud. As of this writing I've used their services for 1 day, but happy so far.
Step 2 - Configure OpenVPN
In general you should follow any one of the many online guides for creating your keys, this is pretty boiler plate. At the end you'll want to have three sets of files:
Server Keys
These will be generated by the guides that you follow for OpenVPN key generation
server.crt
server.key
dh2048.pem
Server Configuration
server.conf
ldap.conf
server.conf
1port 1194
2proto udp
3dev tun
4server 172.29.0.0 255.255.240.0
5push "route 172.20.0.0 255.255.0.0"
6ca /etc/openvpn/keys/ca.crt
7cert /etc/openvpn/keys/server.crt
8key /etc/openvpn/keys/server.key
9dh /etc/openvpn/keys/dh2048.pem
10tls-version-min 1.2
11tls-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
12cipher AES-256-CBC
13auth SHA512
14ifconfig-pool-persist ipp.txt
15keepalive 10 120
16comp-lzo
17persist-key
18persist-tun
19status openvpn-status.log
20log-append /var/log/openvpn.log
21verb 3
22max-clients 100
23user nobody
24group nogroup
25plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so openvpn
ldap.conf - using jumpcloud
1host ldap.jumpcloud.com
2base ou=Users,o=SECRET,dc=jumpcloud,dc=com
3binddn uid=ldap-aws,ou=Users,o=SECRET,dc=jumpcloud,dc=com
4bindpw VERYSECRET
5scope one
6timelimit 5
7bind_timelimit 2
8bind_policy soft
9idle_timelimit 6
10
11pam_login_attribute uid
12
13pam_min_uid 1000
14pam_password exop
15
16nss_base_passwd ou=Users,o=SECRET,dc=jumpcloud,dc=com
17nss_base_shadow ou=Users,o=SECRET,dc=jumpcloud,dc=com
18
19ssl start_tls
20tls_checkpeer no
Client Configuration -- Tunnelblick files
ca.crt
yourorg.crt
yourorg.key
client.conf
client.conf
1client
2dev tun
3proto udp
4remote YOUR.DNS.NAME 1194
5ca ca.crt
6cert yourorg.crt
7key yourorg.key
8tls-version-min 1.2
9tls-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
10cipher AES-256-CBC
11auth SHA512
12resolv-retry infinite
13auth-retry none
14nobind
15persist-key
16persist-tun
17ns-cert-type server
18comp-lzo
19verb 3
20auth-user-pass
Step 3 - Setting User Data for the instance
Since we're putting this instance in a auto scaling group, we need to make sure that it will recover when failed, rebooted etc. Everything for this machine is in the UserData.
What this does:
- Install necessary packages: aws-cli, curl and openvpn
- Copies the "secrets" from S3
- Disable the Source/Dest checks for this instance
- Configure IP Tables for a routed environment
- Adds PAM configuration for LDAP
- Installs LDAP client in a non-interactive way
- Leaves a nice log in /tmp so you can read it
TODO: Update with an IP Tables that survies a reboot.
1#!/bin/bash -x
2function base {
3 echo "=== Boostrap Starting "
4 apt-get update
5 apt-get install -y curl python-pip
6 DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent
7 pip install awscli
8
9 # IP Configuration
10 aws ec2 modify-instance-attribute --no-source-dest-check \
11 --instance-id `curl http://169.254.169.254/latest/meta-data/instance-id` --region """, Ref("AWS::Region"), """
12
13 cat <<EOT > /etc/iptables/rules.v4
14*nat
15:PREROUTING ACCEPT [85:4110]
16:INPUT ACCEPT [84:4046]
17:OUTPUT ACCEPT [70:11051]
18:POSTROUTING ACCEPT [0:0]
19-A POSTROUTING -s 172.20.0.0/16 -o eth0 -j MASQUERADE
20-A POSTROUTING -s 172.29.0.0/20 -o eth0 -j MASQUERADE
21COMMIT
22# Completed on Tue Mar 28 11:19:00 2017
23# Generated by iptables-save v1.6.0 on Tue Mar 28 11:19:00 2017
24*filter
25:INPUT ACCEPT [2584:919039]
26:FORWARD ACCEPT [0:0]
27:OUTPUT ACCEPT [2388:528346]
28-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
29-A FORWARD -s 172.20.0.0/16 -i eth0 -o eth0 -m conntrack --ctstate NEW -j ACCEPT
30-A FORWARD -s 172.29.0.0/20 -i tun0 -o eth0 -m conntrack --ctstate NEW -j ACCEPT
31-A FORWARD -s 172.29.0.0/20 -d 172.20.0.0/16 -i tun0 -o eth0 -m conntrack --ctstate NEW -j ACCEPT
32COMMIT
33EOT
34
35 iptables-restore < /etc/iptables/rules.v4
36
37 echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
38 sysctl -p
39
40 # OpenVPN configuration
41 mkdir -p /etc/openvpn/keys
42 aws s3 cp s3://my-bucket /etc/openvpn/keys --recursive --include "ca.crt" --include "server.crt" --include "server.key" --include "dh2048.pem"
43 chmod -R 0600 /etc/openvpn/keys
44 aws s3 cp s3://my-bucket/server.conf /etc/openvpn
45 aws s3 cp s3://my-bucket/ldap.conf /etc/openvpn
46
47 cat <<EOT >/etc/pam.d/openvpn
48auth sufficient pam_ldap.so config=/etc/openvpn/ldap.conf
49auth required pam_deny.so
50account required pam_ldap.so config=/etc/openvpn/ldap.conf
51account required pam_permit.so
52EOT
53
54 DEBIAN_FRONTEND=noninteractive apt-get install -y libpam-ldap
55
56 apt-get install -y openvpn
57 systemctl start openvpn@server
58
59 echo "=== Boostrap complete "
60}
61
62base 2>&1 | tee /tmp/bootstrap.log
Step 4 - Putting it all together
I'm currently using stacker to build my CloudFormation scripts. Here's the generated CF script for your viewing.
I'm using JSON in production, but I've also created a YAML version of the CloudFormation template for your quick review.
Note: The one thing this setup doesn't do is associate the Public IP of the instance to a DNS name. That's on my future projects list...
Cloudformation OpenVPN template
1Description: EC2 OpenVPN host
2Mappings:
3 AmiMap:
4 us-east-1:
5 bastion: ami-2757f631
6 us-west-2:
7 bastion: ami-7ac6491a
8Parameters:
9 AvailabilityZones:
10 Description: Availability Zones to deploy instances in.
11 Type: CommaDelimitedList
12 DefaultSG:
13 Description: Top level security group.
14 Type: AWS::EC2::SecurityGroup::Id
15 ImageName:
16 Default: bastion
17 Description: The image name to use from the AMIMap (usually found in the config
18 file.)
19 Type: String
20 InstanceType:
21 Default: m3.medium
22 Description: EC2 Instance Type
23 Type: String
24 MaxSize:
25 Default: "1"
26 Description: "Maximum # of instances."
27 Type: Number
28 MinSize:
29 Default: "1"
30 Description: "Minimum # of instances."
31 Type: Number
32 OfficeNetwork:
33 Default: 0.0.0.0/0
34 Description: CIDR block allowed to connect to bastion hosts.
35 Type: String
36 PrivateSubnets:
37 Description: Subnets to deploy private instances in.
38 Type: List<AWS::EC2::Subnet::Id>
39 PublicSubnets:
40 Description: Subnets to deploy public instances in.
41 Type: List<AWS::EC2::Subnet::Id>
42 S3VpnKeysBucketName:
43 Description: The S3 bucket that contains the keys for the OpenVPN server
44 Type: String
45 SshKeyName:
46 Type: AWS::EC2::KeyPair::KeyName
47 VpcCidr:
48 Description: The name of this VPC for tagging
49 Type: String
50 VpcId:
51 Description: Vpc Id
52 Type: AWS::EC2::VPC::Id
53 VpcName:
54 Description: The name of this VPC for tagging
55 Type: String
56Resources:
57 AllowSSHAnywhere:
58 Type: AWS::EC2::SecurityGroupIngress
59 Properties:
60 FromPort: 22
61 GroupId:
62 Ref: DefaultSG
63 IpProtocol: tcp
64 SourceSecurityGroupId:
65 Ref: BastionSG
66 ToPort: 22
67 BastionAccessPolicy:
68 Type: AWS::IAM::Policy
69 Properties:
70 PolicyDocument:
71 Statement:
72 - Action:
73 - ec2:AssociateAddress
74 - ec2:Describe
75 - ec2:ModifyInstanceAttribute
76 Effect: Allow
77 Resource: "*"
78 - Action:
79 - s3:Get*
80 Effect: Allow
81 Resource:
82 - Fn::Join:
83 - ""
84 - - "arn:aws:s3:::"
85 - Ref: S3VpnKeysBucketName
86 - Fn::Join:
87 - ""
88 - - "arn:aws:s3:::"
89 - Ref: S3VpnKeysBucketName
90 - /*
91 PolicyName: BastionAccessPolicy
92 Roles:
93 - Ref: BastionRole
94 BastionAutoscalingGroup:
95 Type: AWS::AutoScaling::AutoScalingGroup
96 Properties:
97 AvailabilityZones:
98 Ref: AvailabilityZones
99 LaunchConfigurationName:
100 Ref: BastionLaunchConfig
101 MaxSize:
102 Ref: MaxSize
103 MinSize:
104 Ref: MinSize
105 Tags:
106 - Key: Name
107 PropagateAtLaunch: true
108 Value: stage-railz.openvpn
109 - Key: Application
110 PropagateAtLaunch: true
111 Value:
112 Ref: AWS::StackId
113 - Key: network
114 PropagateAtLaunch: true
115 Value: public
116 VPCZoneIdentifier:
117 Ref: PublicSubnets
118 BastionInstanceProfile:
119 Type: AWS::IAM::InstanceProfile
120 Properties:
121 Path: /
122 Roles:
123 - Ref: BastionRole
124 BastionLaunchConfig:
125 Type: AWS::AutoScaling::LaunchConfiguration
126 Properties:
127 AssociatePublicIpAddress: "true"
128 IamInstanceProfile:
129 Ref: BastionInstanceProfile
130 ImageId:
131 Fn::FindInMap:
132 - AmiMap
133 - Ref: AWS::Region
134 - Ref: ImageName
135 InstanceType:
136 Ref: InstanceType
137 KeyName:
138 Ref: SshKeyName
139 SecurityGroups:
140 - Ref: DefaultSG
141 - Ref: BastionSG
142 UserData:
143 Fn::Base64:
144 Fn::Join:
145 - ""
146 - - "#!/bin/bash -x\nfunction base {\n echo \"=== Boostrap Starting \"\n\
147 \ apt-get update\n apt-get install -y curl python-pip\n DEBIAN_FRONTEND=noninteractive\
148 \ apt-get install -y iptables-persistent\n pip install awscli\n\n \
149 \ # IP Configuration\n aws ec2 modify-instance-attribute --no-source-dest-check\
150 \ --instance-id `curl http://169.254.169.254/latest/meta-data/instance-id`\
151 \ --region "
152 - Ref: AWS::Region
153 - "\n\n cat <<EOT > /etc/iptables/rules.v4\n*nat\n:PREROUTING ACCEPT\
154 \ [85:4110]\n:INPUT ACCEPT [84:4046]\n:OUTPUT ACCEPT [70:11051]\n:POSTROUTING\
155 \ ACCEPT [0:0]\n-A POSTROUTING -s 172.20.0.0/16 -o eth0 -j MASQUERADE\n\
156 -A POSTROUTING -s 172.29.0.0/20 -o eth0 -j MASQUERADE\nCOMMIT\n# Completed\
157 \ on Tue Mar 28 11:19:00 2017\n# Generated by iptables-save v1.6.0 on\
158 \ Tue Mar 28 11:19:00 2017\n*filter\n:INPUT ACCEPT [2584:919039]\n:FORWARD\
159 \ ACCEPT [0:0]\n:OUTPUT ACCEPT [2388:528346]\n-A FORWARD -m conntrack\
160 \ --ctstate RELATED,ESTABLISHED -j ACCEPT\n-A FORWARD -s 172.20.0.0/16\
161 \ -i eth0 -o eth0 -m conntrack --ctstate NEW -j ACCEPT\n-A FORWARD -s\
162 \ 172.29.0.0/20 -i tun0 -o eth0 -m conntrack --ctstate NEW -j ACCEPT\n\
163 -A FORWARD -s 172.29.0.0/20 -d 172.20.0.0/16 -i tun0 -o eth0 -m conntrack\
164 \ --ctstate NEW -j ACCEPT\nCOMMIT\nEOT\n\n iptables-restore < /etc/iptables/rules.v4\n\
165 \n echo \"net.ipv4.ip_forward = 1\" >> /etc/sysctl.conf\n sysctl -p\n\
166 \n # OpenVPN configuration\n\n mkdir -p /etc/openvpn/keys\n aws s3\
167 \ cp s3://"
168 - Ref: S3VpnKeysBucketName
169 - " /etc/openvpn/keys --recursive --include \"ca.crt\" --include\
170 \ \"server.crt\" --include \"server.key\" --include \"dh2048.pem\"\n\
171 \ chmod -R 0600 /etc/openvpn/keys\n aws s3 cp s3://"
172 - Ref: S3VpnKeysBucketName
173 - "/server.conf /etc/openvpn\n aws s3 cp s3://"
174 - Ref: S3VpnKeysBucketName
175 - "/ldap.conf /etc/openvpn\n\n cat <<EOT >/etc/pam.d/openvpn\nauth sufficient\
176 \ pam_ldap.so config=/etc/openvpn/ldap.conf\nauth required pam_deny.so\n\
177 account required pam_ldap.so config=/etc/openvpn/ldap.conf\naccount\
178 \ required pam_permit.so\nEOT\n\n DEBIAN_FRONTEND=noninteractive apt-get\
179 \ install -y libpam-ldap\n\n apt-get install -y openvpn \n systemctl\
180 \ start openvpn@server\n\n echo \"=== Boostrap complete \"\n}\n\nbase\
181 \ 2>&1 | tee /tmp/bootstrap.log\n"
182 BastionRole:
183 Type: AWS::IAM::Role
184 Properties:
185 AssumeRolePolicyDocument:
186 Statement:
187 - Action: sts:AssumeRole
188 Effect: Allow
189 Principal:
190 Service: ec2.amazonaws.com
191 Path: /
192 BastionSG:
193 Type: AWS::EC2::SecurityGroup
194 Properties:
195 GroupDescription: BastionSecurityGroup
196 SecurityGroupIngress:
197 - CidrIp:
198 Ref: OfficeNetwork
199 FromPort: 1194
200 IpProtocol: udp
201 ToPort: 1194
202 VpcId:
203 Ref: VpcId