Skip to content

Instantly share code, notes, and snippets.

@kwilczynski
Last active February 12, 2018 19:54
Show Gist options
  • Save kwilczynski/5b86f367874cadafbf00135d94343b18 to your computer and use it in GitHub Desktop.
Save kwilczynski/5b86f367874cadafbf00135d94343b18 to your computer and use it in GitHub Desktop.
OpenVPN for TCP and UDP for use with EC2 user data.
#!/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