Last active
February 12, 2018 19:54
-
-
Save kwilczynski/5b86f367874cadafbf00135d94343b18 to your computer and use it in GitHub Desktop.
OpenVPN for TCP and UDP for use with EC2 user data.
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
#!/bin/bash | |
set -e | |
set -u | |
set -o pipefail | |
# Return netmask for a given network and CIDR. | |
cidr_to_netmask() { | |
value=$(( 0xffffffff ^ ((1 << (32 - $1)) - 1) )) | |
echo "$(( (value >> 24) & 0xff )).$(( (value >> 16) & 0xff )).$(( (value >> 8) & 0xff )).$(( value & 0xff ))" | |
} | |
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin | |
# The name of the EC2 tag that holds the Elastic IP address to use. | |
readonly TAG_NAME='PublicIP' | |
readonly EC2_METADATA_URL='http://169.254.169.254/latest/meta-data' | |
readonly UBUNTU_RELEASE=$(lsb_release -sc) | |
# Make sure files are 644 and directories are 755. | |
umask 022 | |
export DEBIAN_FRONTEND=noninteractive | |
export DEBCONF_NONINTERACTIVE_SEEN=true | |
curl -s -k \ | |
https://swupdate.openvpn.net/repos/repo-public.gpg | \ | |
apt-key add - | |
cat <<EOF > /etc/apt/sources.list.d/openvpn.list | |
deb http://build.openvpn.net/debian/openvpn/stable $UBUNTU_RELEASE main | |
EOF | |
# Refresh packages index only when needed. | |
UPDATE_STAMP='/var/lib/apt/periodic/update-success-stamp' | |
if [[ ! -f $UPDATE_STAMP ]] || \ | |
(( $(date +%s) - $(date -r $UPDATE_STAMP +%s) > 300 )); then | |
apt-get -qq update | |
fi | |
# Only refresh packages index from OpenVPN's repository. | |
apt-get -qq update \ | |
-o Dir::Etc::SourceList='/etc/apt/sources.list.d/openvpn.list' \ | |
-o Dir::Etc::SourceParts='-' -o APT::Get::List-Cleanup='0' | |
PACKAGES=( | |
awscli | |
easy-rsa | |
openvpn | |
iptables-persistent | |
zip | |
) | |
for package in "${PACKAGES[@]}"; do | |
apt-get -qq install --assume-yes $package >/dev/null | |
done | |
service openvpn stop || true | |
# Stop with extreme prejudice. | |
pgrep -f openvpn &>/dev/null && pkill -9 -f openvpn | |
# Add missing log rotation. | |
cat <<'EOF' > /etc/logrotate.d/openvpn | |
/var/log/openvpn.log | |
/var/log/openvpn/*.log | |
{ | |
daily | |
rotate 7 | |
copytruncate | |
notifempty | |
missingok | |
compress | |
sharedscripts | |
} | |
EOF | |
rm -Rf \ | |
/etc/openvpn/{easy-rsa,keys} \ | |
/etc/openvpn/{tcp,udp}-server.conf | |
if which make-cadir &>/dev/null; then | |
# This will create symbolic links. | |
make-cadir /etc/openvpn/easy-rsa | |
else | |
cp -R /usr/share/easy-rsa \ | |
/etc/openvpn | |
fi | |
EASY_RSA_VARS=( | |
'CA_EXPIRE 365' | |
'KEY_CITY London' | |
'KEY_COUNTRY UK' | |
'KEY_EMAIL security@example.com' | |
'KEY_EXPIRE 365' | |
'KEY_NAME OpenVPN Server' | |
'KEY_ORG Example Co.' | |
'KEY_OU Operations' | |
'KEY_PROVINCE Greater London' | |
'KEY_SIZE 2048' | |
) | |
for value in "${EASY_RSA_VARS[@]}"; do | |
SETTING=( $value ) | |
VALUE=$(echo $value | \ | |
awk '{ for (i = 2; i <= NF; i++) printf $i " "}' | \ | |
sed -e 's/^\s\+//;s/\s\+$//') | |
sed -i -e \ | |
"s/^#\?.*${SETTING[0]}.*/export ${SETTING[0]}=\"${VALUE}\"/" \ | |
/etc/openvpn/easy-rsa/vars | |
# Add settings should they be missing from the file. | |
grep -qF "${SETTING[0]}" /etc/openvpn/easy-rsa/vars || \ | |
echo "export ${SETTING[0]}=\"${VALUE}\"" | \ | |
tee -a /etc/openvpn/easy-rsa/vars 1>/dev/null | |
done | |
pushd /etc/openvpn &>/dev/null | |
pushd ${PWD}/easy-rsa &>/dev/null | |
source ./vars | |
./clean-all | |
./build-ca --batch | |
./build-key-server --batch server | |
./build-key --batch client | |
DH_FILE="./keys/dh2048.pem" | |
if [[ ! -f $DH_FILE ]]; then | |
# Fetch the Diffie-Hellman Parameter set from | |
# the company that offers continuusly fresh | |
# copy as a public service. | |
curl -k -s https://2ton.com.au/dhparam/2048 | \ | |
tee -a $DH_FILE 1>/dev/null | |
# Check if the file contains a certificate, | |
# otherwise generate a new one, which might | |
# take long time to finish. | |
if ! grep -qF 'BEGIN DH PARAMETERS' $DH_FILE; then | |
./build-dh | |
fi | |
fi | |
# Generate a pre-shared static key which is | |
# going to be used alongside the client and | |
# server certificates to sign the requests | |
# with a HMAC signature. | |
openvpn --genkey --secret ./keys/ta.key | |
popd &>/dev/null | |
mv -f ./easy-rsa/keys . | |
chmod 700 ./keys | |
# Clean files that are not needed any more. | |
find ./keys -type f \( ! -name '*.pem' ! -name '*.crt' ! -name '*.csr' ! -name '*.key' \) -delete | |
tar -zcf \ | |
/root/easy-rsa.tar.gz \ | |
./easy-rsa | |
sha256sum -b /root/easy-rsa.tar.gz | \ | |
tee /root/easy-rsa.tar.gz.SHA256SUM 1>/dev/null | |
# Correct permission... | |
chmod 600 \ | |
/root/easy-rsa.tar.gz \ | |
/root/easy-rsa.tar.gz.SHA256SUM | |
rm -Rf ./easy-rsa | |
popd &>/dev/null | |
for directory in /var/lib/openvpn{,/tmp} /var/log/openvpn; do | |
if [[ -d $directory ]]; then | |
rm -Rf ${directory}/* | |
else | |
mkdir -p $directory | |
fi | |
chmod 755 $directory | |
chown root:nogroup $directory | |
done | |
# Fetch the EC2 instance ID. | |
INSTANCE_ID=$(curl -s ${EC2_METADATA_URL}/instance-id) | |
# Extract the region name. | |
REGION=$(curl -s ${EC2_METADATA_URL}/placement/availability-zone | sed -e 's/\w$//') | |
# Fetch current "StackName" tag. | |
INSTANCE_NAME_TAG=$(aws ec2 describe-tags \ | |
--query 'Tags[*].Value' \ | |
--filters "Name=resource-id,Values=${INSTANCE_ID}" 'Name=key,Values=StackName' \ | |
--region $REGION --output text 2>/dev/null) | |
# Fetch current private IP address of this instance. | |
PRIVATE_IP_ADDRESS=$(curl -s ${EC2_METADATA_URL}/local-ipv4) | |
# Fetch the EC2 instance tag which contains | |
# the Elastic IP that should be used when | |
# requesting the re-assotiation. | |
PUBLIC_IP_ADDRESS=$(aws ec2 describe-tags \ | |
--query 'Tags[*].Value' \ | |
--filters "Name=resource-id,Values=${INSTANCE_ID}" "Name=key,Values=${TAG_NAME}" \ | |
--region $REGION --output 'text' 2>/dev/null) | |
# Fetech current CIDR block for this VPC. | |
VPC_CIDR=$(aws ec2 describe-vpcs \ | |
--query 'Vpcs[].CidrBlock[]' \ | |
--filters "Name=tag-key,Values=StackName" "Name=tag-value,Values=${INSTANCE_NAME_TAG}" \ | |
--region $REGION --output text 2>/dev/null) | |
# Extract network and netmaks. | |
VPC_NETMASK=(${VPC_CIDR%%\/*} $(cidr_to_netmask ${VPC_CIDR##*\/})) | |
for mode in TCP UDP; do | |
NAME="$(echo $mode | tr 'A-Z' 'a-z')-server" | |
# Render configuration for both the TCP and UDP servers. | |
cat <<EOF > /etc/openvpn/${NAME}.conf | |
$(if [[ $mode == "TCP" ]]; then | |
cat <<'EOS' | |
proto tcp-server | |
tcp-nodelay | |
EOS | |
else | |
cat <<'EOS' | |
proto udp | |
fast-io | |
EOS | |
fi) | |
push "block-ipv6" | |
local $PRIVATE_IP_ADDRESS | |
$(if [[ $mode == "TCP" ]]; then | |
cat <<'EOS' | |
port 443 | |
EOS | |
else | |
cat <<'EOS' | |
port 1194 | |
EOS | |
fi) | |
$(if [[ $mode == "TCP" ]]; then | |
cat <<'EOS' | |
dev tun0s0 | |
EOS | |
else | |
cat <<'EOS' | |
dev tun0s1 | |
EOS | |
fi) | |
dev-type tun | |
user nobody | |
group nogroup | |
chroot "/var/lib/openvpn" | |
tmp-dir "/tmp" | |
topology subnet | |
push "topology subnet" | |
$(if [[ $mode == "TCP" ]]; then | |
cat <<EOS | |
ifconfig 172.31.0.1 255.255.248.0 | |
ifconfig-pool 172.31.0.2 172.31.0.254 255.255.248.0 | |
push "route $(printf '%s %s' "${VPC_NETMASK[@]}")" | |
push "route-gateway 172.31.0.1" | |
EOS | |
else | |
cat <<EOS | |
ifconfig 172.31.8.1 255.255.248.0 | |
ifconfig-pool 172.31.8.2 172.31.8.254 255.255.248.0 | |
push "route $(printf '%s %s' "${VPC_NETMASK[@]}")" | |
push "route-gateway 172.31.8.1" | |
EOS | |
fi) | |
push "redirect-gateway def1" | |
push "redirect-gateway bypass-dhcp" | |
push "redirect-gateway autolocal" | |
$(grep -E '^nameserver' /etc/resolv.conf | awk '{ print $2 }' | \ | |
while read line; do | |
echo "push \"dhcp-option DNS $line\"" | |
done) | |
$(grep -qP '8.8.[\d\.]+' /etc/resolv.conf || echo 'push "dhcp-option DNS 8.8.8.8"') | |
$(grep -qP '4.2.[\d\.]+' /etc/resolv.conf || echo 'push "dhcp-option DNS 4.2.2.2"') | |
push "dhcp-option DOMAIN $(grep -E '^search' /etc/resolv.conf | grep -oE '[^.]*\.[^.]*$' )" | |
push "dhcp-option DOMAIN-SEARCH $(grep -E '^search' /etc/resolv.conf | awk '{ print $2 }')" | |
ca /etc/openvpn/keys/ca.crt | |
cert /etc/openvpn/keys/server.crt | |
key /etc/openvpn/keys/server.key | |
dh /etc/openvpn/keys/dh2048.pem | |
tls-version-min 1.2 or-highest | |
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 | |
mode server | |
tls-server | |
tls-auth /etc/openvpn/keys/ta.key 0 | |
duplicate-cn | |
x509-track "CN" | |
remote-cert-tls client | |
reneg-sec 14400 | |
comp-lzo yes | |
push "comp-lzo yes" | |
persist-key | |
persist-tun | |
push "persist-key" | |
push "persist-tun" | |
keepalive 10 120 | |
push "explicit-exit-notify 3" | |
push "route-delay 5 30" | |
push "dhcp-pre-release" | |
push "dhcp-renew" | |
push "dhcp-release" | |
push "route-metric 101" | |
status /var/log/openvpn/${NAME}-status.log 30 | |
status-version 3 | |
log-append /var/log/openvpn/${NAME}.log | |
verb 3 | |
opt-verify | |
mute-replay-warnings | |
fragment 0 | |
tun-mtu 1500 | |
tun-mtu-extra 32 | |
mssfix 1450 | |
hash-size 4096 4096 | |
max-clients 256 | |
sndbuf 131072 | |
rcvbuf 131072 | |
txqueuelen 256 | |
EOF | |
# Correct permission... | |
chmod 600 /etc/openvpn/${NAME}.conf | |
done | |
# Render self-contained client configuration file. | |
cat <<EOF > /root/client.ovpn | |
setenv FORWARD_COMPATIBLE 1 | |
setenv PUSH_PEER_INFO | |
client | |
nobind | |
dev tun | |
dev-type tun | |
remote $PUBLIC_IP_ADDRESS 443 tcp | |
remote $PUBLIC_IP_ADDRESS 1194 udp | |
push-peer-info | |
server-poll-timeout 5 | |
fragment 0 | |
tun-mtu 1500 | |
tun-mtu-extra 32 | |
mssfix 1450 | |
persist-key | |
persist-tun | |
resolv-retry infinite | |
tls-version-min 1.2 or-higest | |
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 | |
auth-retry none | |
auth-nocache | |
remote-cert-tls server | |
reneg-sec 14400 | |
sndbuf 131072 | |
rcvbuf 131072 | |
comp-lzo adaptive | |
verb 3 | |
<ca> | |
$(cat /etc/openvpn/keys/ca.crt) | |
</ca> | |
<cert> | |
$(sed -n -e '/BEGIN/,/END/p' /etc/openvpn/keys/client.crt) | |
</cert> | |
<key> | |
$(cat /etc/openvpn/keys/client.key) | |
</key> | |
key-direction 1 | |
<tls-auth> | |
$(sed -n -e '/BEGIN/,/END/p' /etc/openvpn/keys/ta.key) | |
</tls-auth> | |
EOF | |
# Correct permission... | |
chmod 600 /root/client.ovpn | |
pushd /root &>/dev/null | |
rm -f \ | |
client.zip \ | |
client.zip.SHA256SUM | |
zip --move client.zip client.ovpn | |
sha256sum -b client.zip | \ | |
tee client.zip.SHA256SUM 1>/dev/null | |
# Correct permission... | |
chmod 600 \ | |
client.zip \ | |
client.zip.SHA256SUM | |
popd &>/dev/null | |
# Enable IP packets forwarding... | |
echo 1 > /proc/sys/net/ipv4/ip_forward | |
cat <<'EOF' > /etc/sysctl.d/99-network-forward.conf | |
net.ipv4.ip_forward = 1 | |
EOF | |
# Reset firewall rules... | |
iptables -F | |
iptables -X | |
iptables -t nat -F | |
iptables -t nat -X | |
iptables -t mangle -F | |
iptables -t mangle -X | |
iptables -P INPUT ACCEPT | |
iptables -P FORWARD ACCEPT | |
iptables -P OUTPUT ACCEPT | |
# Configure source NAT, forwarding, etc. | |
iptables -I INPUT -i tun0s+ -j ACCEPT | |
iptables -I FORWARD -i tun0s+ -o eth0 -m conntrack --ctstate NEW -j ACCEPT | |
iptables -I FORWARD -i eth0 -o tun0s+ -m conntrack --ctstate NEW -j ACCEPT | |
iptables -I FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT | |
for network in 172.31.0.0/21 172.31.8.0/21; do | |
iptables -t nat -I POSTROUTING -s $network -j SNAT --to $PRIVATE_IP_ADDRESS | |
done | |
iptables -I OUTPUT -o tun0s+ -j ACCEPT | |
# Save current rules. | |
iptables-save > /etc/iptables/rules.v4 | |
# Ensure that OpenVPN servers start automatically. | |
update-rc.d -f openvpn enable | |
# Start OpenVPN servers... | |
service openvpn start |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment