Ubuntu22.04でのOpenVPNサーバーの設定

前提条件

※tun(ルーティング)方式で設定する
※自宅用なのでセキュリティリスク(CAの秘密鍵へのアクセス)を承知した上でOpenVPNサーバーとCAを同じマシンにインストールする

・ Ubuntu: 22.04.1
・ openvpn: 2.5.5
・ easy-rsa: 3.0.8
・ openssl: 3.0.2

サーバー設定手順

openvpnとeasy-rsaをインストールする。

# apt install openvpn easy-rsa

ルート証明書の有効期間(日数)を変更したい場合、事前に下記を参考に環境変数を変更する。

# export EASYRSA_CA_EXPIRE=7300

公開鍵基盤(PKI)を初期化して認証局(CA)を設置する。CA作成時に秘密鍵のパスフレーズを聞いてくる。このパスフレーズは新しい証明書発行時に必要になる。

# cd /usr/share/easy-rsa/
# ./easyrsa init-pki
# ./easyrsa build-ca

認証局(CA)が発行する証明書の有効期間(日数)を変更したい場合、事前に下記を参考に環境変数を変更する。

# export EASYRSA_CERT_EXPIRE=3650

VPNサーバー用の証明書を作成する。
※パスフレーズは不要なのでnopassを指定

# ./easyrsa build-server-full vpn.example.com nopass

Diffie-Hellmanパラメーターファイルを作成する。

# ./easyrsa gen-dh

TLS-AuthのHMACに使用する共通鍵を作成する。

# openvpn --genkey secret /etc/openvpn/server/ta.key

OpenVPNサーバーの設定ファイルをサンプルからコピーする。

# cp /usr/share/doc/openvpn/examples/sample-config-files/server.conf /etc/openvpn/

設定ファイルを下記のように変更する。
※重要そうな箇所のみ記載
※serverにはクライアントに割り当てられるipアドレスを定義
※push(route)にはクライアントにアクセスを許可するサーバーのプライベートネットワークを指定
※push(redirect-gateway)でクライアントのIPトラフィックがVPNを通過するよう設定
※push(dhcp-option DNS)でクライアントが使用するDNSサーバーを指定
※client-to-clientでクライアント同士の通信を許可
※duplicate-cnで同じ証明書を使用した複数のクライアント接続を許可(非推奨)

port 7716
proto udp
dev tun
ca /usr/share/easy-rsa/pki/ca.crt
cert /usr/share/easy-rsa/pki/issued/vpn.example.com.crt
key /usr/share/easy-rsa/pki/private/vpn.example.com.key
dh /usr/share/easy-rsa/pki/dh.pem
server 172.16.1.0 255.255.255.0
push "route 192.168.1.0 255.255.255.0"
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 192.168.1.11"
push "dhcp-option DNS 8.8.8.8"
client-to-client
duplicate-cn
tls-auth server/ta.key 0

IPフォワードを有効にして、異なるネットワーク(NIC)間でパケットが転送されるように設定変更する。

# sed -i "s/#net.ipv4.ip_forward/net.ipv4.ip_forward/g" /etc/sysctl.conf
# sysctl -p

iptablesでIPマスカレードを有効に設定する。

# apt install iptables-persistent
# iptables -t nat -A POSTROUTING -s 172.16.1.0/24 -o [デバイス] -j MASQUERADE
# netfilter-persistent save

サービスを有効化して起動する。

# systemctl enable --now openvpn@server

クライアント証明書の発行

クライアントに配る*.ovpnを作成するのが面倒なのでスクリプトを作成した。地味にハマったポイントはTLS-Authの「key-direction 1」サーバーの設定が0なら1にする必要がある。

#!/bin/bash
easyrsa_location=/usr/share/easy-rsa
sample_config_location=/usr/share/doc/openvpn/examples/sample-config-files
takey_location=/etc/openvpn/server/ta.key
server=vpn.example.com
port=7716
echo "作成するユーザーIDを入力してください。"
read -p "ユーザーID:" id
if [ -z "$id" ]; then
    exit 0
fi
fullname=$id.$server
echo
echo "クライアント接続設定ファイル「$id.ovpn」を作成します。"
read -p "Enterキーで続行します。" result
if [ ! -z "$result" ]; then
    exit 0
fi
echo
default_location=$(pwd)
cd $easyrsa_location
./easyrsa build-client-full $fullname nopass
if [ ! $? -eq 0 ]; then
    exit 0;
fi
cd $default_location
cp $sample_config_location/client.conf $id.ovpn
sed -i "s/my-server-1 1194/$server $port/g" $id.ovpn
sed -i "s/ca ca.crt/#ca ca.crt/g" $id.ovpn
sed -i "s/cert client.crt/#cert client.crt/g" $id.ovpn
sed -i "s/key client.key/#key client.key/g" $id.ovpn
sed -i "s/tls-auth ta.key 1/#tls-auth ta.key 1\nkey-direction 1/g" $id.ovpn
echo "" >>$id.ovpn
# CA
echo "<ca>" >>$id.ovpn
grep -A 100 'BEGIN CERTIFICATE' $easyrsa_location/pki/ca.crt >>$id.ovpn
echo "</ca>" >>$id.ovpn
echo "" >>$id.ovpn
# Key
echo "<key>" >>$id.ovpn
grep -A 100 'BEGIN PRIVATE KEY' $easyrsa_location/pki/private/$fullname.key >>$id.ovpn
echo "</key>" >>$id.ovpn
echo "" >>$id.ovpn
# Cert
echo "<cert>" >>$id.ovpn
grep -A 100 'BEGIN CERTIFICATE' $easyrsa_location/pki/issued/$fullname.crt >>$id.ovpn
echo "</cert>" >>$id.ovpn
echo "" >>$id.ovpn
# TLS-auth
echo "<tls-auth>" >>$id.ovpn
cat $takey_location >>$id.ovpn
echo "</tls-auth>" >>$id.ovpn
echo "" >>$id.ovpn
echo "作成が完了しました。"

実行可能にしてスクリプトを実行すれば対話型でクライアント用の*.ovpnを作成できる。作成された*.ovpnのパーミッションには気をつけが方が良い。

# chmod +x build-client-ovpn.sh
# ./build-client-ovpn.sh

クライアント証明書の更新

更新が可能になる期間を変更したい場合、事前に下記を参考に環境変数を変更する。

# export EASYRSA_CERT_RENEW=365

これも面倒なので更新用のスクリプトを作成した。

#!/bin/bash
easyrsa_location=/usr/share/easy-rsa
sample_config_location=/usr/share/doc/openvpn/examples/sample-config-files
takey_location=/etc/openvpn/server/ta.key
server=vpn.example.com
port=7716
echo "更新するユーザーIDを入力してください。"
read -p "ユーザーID:" id
if [ -z "$id" ]; then
    exit 0
fi
fullname=$id.$server
echo
if [ ! -f "$easyrsa_location/pki/issued/$fullname.crt" ]; then
    echo "既存の証明書が見つかりませんでした。"
    exit 1
fi
echo "クライアント接続設定ファイル「$id.ovpn」を再作成します。"
read -p "Enterキーで続行します。" result
if [ ! -z "$result" ]; then
    exit 0
fi
echo
default_location=$(pwd)
cd $easyrsa_location
./easyrsa renew $fullname nopass
if [ ! $? -eq 0 ]; then
    exit 0;
fi
cd $default_location
cp $sample_config_location/client.conf $id.ovpn
sed -i "s/my-server-1 1194/$server $port/g" $id.ovpn
sed -i "s/ca ca.crt/#ca ca.crt/g" $id.ovpn
sed -i "s/cert client.crt/#cert client.crt/g" $id.ovpn
sed -i "s/key client.key/#key client.key/g" $id.ovpn
sed -i "s/tls-auth ta.key 1/#tls-auth ta.key 1\nkey-direction 1/g" $id.ovpn
echo "" >>$id.ovpn
# CA
echo "<ca>" >>$id.ovpn
grep -A 100 'BEGIN CERTIFICATE' $easyrsa_location/pki/ca.crt >>$id.ovpn
echo "</ca>" >>$id.ovpn
echo "" >>$id.ovpn
# Key
echo "<key>" >>$id.ovpn
grep -A 100 'BEGIN PRIVATE KEY' $easyrsa_location/pki/private/$fullname.key >>$id.ovpn
echo "</key>" >>$id.ovpn
echo "" >>$id.ovpn
# Cert
echo "<cert>" >>$id.ovpn
grep -A 100 'BEGIN CERTIFICATE' $easyrsa_location/pki/issued/$fullname.crt >>$id.ovpn
echo "</cert>" >>$id.ovpn
echo "" >>$id.ovpn
# TLS-auth
echo "<tls-auth>" >>$id.ovpn
cat $takey_location >>$id.ovpn
echo "</tls-auth>" >>$id.ovpn
echo "" >>$id.ovpn
echo "作成が完了しました。"

実行可能にすれば対話型で更新ができる。作成された*.ovpnのパーミッションには気をつけが方が良い。

# chmod +x renew-client-ovpn.sh
# ./renew-client-ovpn.sh