OpenVPNをセットアップする

IKEv2のstrongSwanはすでに構築してあるので今更OpenVPNを構築する意味はありませんが、SSLを利用したVPNでシンプルという理由で構築することにしました。

メリットはファイヤーウォールを通すのがIKEv2に比べてとても楽なことです。またコマンドラインから簡単に起動できます。

デビアン系ではapt install openvpnでインストールします。OpenWrtはopkg install openvpn-openssl(またはopenvpn-mbedtls)としてインストールします。

構築手順

  1. 同じセグメントで共通鍵でセットアップ
  2. nat越しで共通鍵でセットアップ
  3. TLS/SSLでセットアップ

いきなりインタネット越しにTLSでセットアップしても良いのですがエラーがある場合に問題の切り出しが面倒なのでシンプルな方法から始めます。

1. 同じセグメントで共通鍵でセットアップ

VPNのセットアップではファイヤーウォールで躓くことが多いので、単純なネットワーク構成で設定ファイルのテンプレートを作ります。

共通鍵を作成

$ sudo openvpn --genkey --secret static.key

static.keyをscp等でサーバー側にもコピーします。

Serverの設定

server.conf:

proto udp 
dev tun 
port 1194 
# サーバのアドレスが10.8.0.1、クライアントが10.8.0.2です
ifconfig 10.8.0.1 10.8.0.2 
secret /tmp/static.key 
comp-lzo 
keepalive 10 60 
ping-timer-rem 
persist-tun 
persist-key 
route 10.8.0.0 255.255.255.0
verb 5

Clientの設定

client.conf:

サーバ、クライアントの起動

$ sudo openvpn --config client.conf

サーバのOpenWrtでは次のようなエラーがでましたが接続はできました。

write to TUN/TAP : Invalid argument (code=22)

2. nat越しで共通鍵でセットアップ

同じようにサーバに共通鍵をコピーします。

Serverの設定

proto udp 
dev tun 
port 1194 
ifconfig 10.8.0.1 10.8.0.2 
secret static.key 
comp-lzo  
keepalive 10 60 
ping-timer-rem 
persist-tun 
persist-key 
route 10.8.0.0 255.255.255.0

Clientの設定

remote <VPS等のIPまたはFQDN>
proto udp 
dev tun 
port 1194 
ifconfig 10.8.0.2 10.8.0.1 
static.key 
comp-lzo 
keepalive 10 60 
ping-timer-rem 
persist-tun 
persist-key 
route 10.8.0.0 255.255.255.0

ファイヤーウォール(FW)の設定

サーバ側

  • Port 1194を開ける
  • VPN用のゾーンを作る
  • VPNからネットへアプセスを許可する
  • VPNからFWへアクセスを許可する
  • FWからVPNにアクセスを許可する

クライアント側

  • IPマスカレードする

または

  • またはポートをのみマスカレードする

例)

$ sudo iptables -t nat -A POSTROUTING -o  $INETIF -p udp --dport 1194 -j MASQUERADE

サーバの場合、1194 udp をポートフォワーディングする。

3. TLS/SSLでセットアップ

OpenSSLを使って鍵と証明書を作成します。

$ sudo mkdir -m 700 /root/certs
$ sudo touch /root/certs/index.txt
$ sudo touch /root/certs/index.txt.attr
$ sudo echo "01" > /root/certs/serial

/etc/ssl/openssl.cnfを編集します。

#
# OpenSSL configuration based on sample configuration shipped 
# with OpenVPN. 
# 
 
############################################################# 
# modify according to your needs 
 
KEY_SIZE               = 4096 
KEY_COUNTRY            = JP 
KEY_PROVINCE           = SA 
KEY_CITY               = NOWHERE 
KEY_ORG                = MYEXAMPLE.COM 
KEY_ORGUNIT            = IT-DIVISION 
KEY_EMAIL              = root@server.myvpn.com 
HOME                   = /root 
KEY_DIR                = $ENV::HOME/certs 
RANDFILE               = $ENV::HOME/.rnd 
 
############################################################# 
 
openssl_conf           = openssl_init 
 
[ openssl_init ] 
 
oid_section            = new_oids 
engines                = engine_section 
 
[ new_oids ] 
 
[ engine_section ] 
 
[ ca ] 
 
default_ca             = CA_default 
 
[ CA_default ] 
 
dir                    = $ENV::KEY_DIR 
certs                  = $dir 
crl_dir                = $dir 
database               = $dir/index.txt 
new_certs_dir          = $dir 
certificate            = $dir/vpn_ca.crt 
serial                 = $dir/serial 
crl                    = $dir/crl.pem 
private_key            = $dir/vpn_ca.key 
RANDFILE               = $dir/.rand 
x509_extensions        = usr_cert 
default_days           = 3650 
default_crl_days       = 30 
# IMPORTANT: The next must no longer be md5, if used with 
# Debian's OpenLDAP package being compiled against libgnutls. 
default_md             = sha1 
preserve               = no 
policy                 = policy_match 
 
 
[ policy_match ] 
 
countryName            = match 
stateOrProvinceName    = match 
organizationName       = match 
organizationalUnitName = optional 
commonName             = supplied 
emailAddress           = optional 
 
[ policy_anything ] 
 
countryName            = optional 
stateOrProvinceName    = optional 
localityName           = optional 
organizationName       = optional 
organizationalUnitName = optional 
commonName             = supplied 
emailAddress           = optional 
 
[ req ] 
 
default_bits           = $ENV::KEY_SIZE 
default_keyfile        = privkey.pem 
distinguished_name     = req_distinguished_name 
attributes             = req_attributes 
x509_extensions        = v3_ca 
string_mask            = nombstr 
 
[ req_distinguished_name ] 
 
countryName            = Country Name (2 letter code) 
countryName_default    = $ENV::KEY_COUNTRY 
countryName_min        = 2 
countryName_max        = 2 
 
stateOrProvinceName    = State or Province Name (full name) 
stateOrProvinceName_default = $ENV::KEY_PROVINCE 
 
localityName           = Locality Name (eg, city) 
localityName_default   = $ENV::KEY_CITY 
 
0.organizationName     = Organization Name (eg, company) 
0.organizationName_default = $ENV::KEY_ORG 
 
organizationalUnitName = Organizational Unit Name (eg, section) 
organizationalUnitName_default = $ENV::KEY_ORGUNIT 
 
commonName             = Common Name (eg, your name or your server\'s hostname) 
commonName_max         = 64 
 
emailAddress           = Email Address 
emailAddress_default   = $ENV::KEY_EMAIL 
emailAddress_max       = 40 
 
[ req_attributes ] 
 
challengePassword      = A challenge password 
challengePassword_min  = 4 
challengePassword_max  = 20 
unstructuredName       = An optional company name 
 
[ usr_cert ] 
 
basicConstraints       = CA:FALSE 
nsComment              = "OpenSSL Generated Certificate" 
subjectKeyIdentifier   = hash 
authorityKeyIdentifier = keyid,issuer:always 
extendedKeyUsage       = clientAuth 
keyUsage               = digitalSignature 
 
[ server ] 
 
basicConstraints       = CA:FALSE 
nsCertType             = server 
nsComment              = "OpenSSL Generated Server Certificate" 
subjectKeyIdentifier   = hash 
authorityKeyIdentifier = keyid,issuer:always 
extendedKeyUsage       = serverAuth 
keyUsage               = digitalSignature, keyEncipherment 
 
[ v3_req ] 
 
basicConstraints       = CA:FALSE 
keyUsage               = nonRepudiation,digitalSignature,keyEncipherment 
 
[ v3_ca ] 
 
subjectKeyIdentifier   = hash 
authorityKeyIdentifier = keyid:always,issuer:always 
basicConstraints       = CA:true 
 
[ crl_ext ] 
 
authorityKeyIdentifier = keyid:always,issuer:always

CA証明書の作成

$ sudo cd /root/certs
$ sudo openssl req -nodes -new -x509 -keyout vpn_ca.key -out vpn_ca.crt
...
Common Name (eg, your name or your server's hostname) []:
...

サーバの鍵と証明書の作成

$ sudo cd /root/certs
$ sudo openssl req -nodes -new -extensions server \
  -keyout vpn_server.key -out vpn_server.csr
...
Common Name (eg, your name or your server's hostname) []:
...

$ sudo openssl ca -extensions server \
  -out vpn_server.crt -in vpn_server.csr
...
Sign the certificate? [y/n]:y 
 
 
1 out of 1 certificate requests certified, commit? [y/n]y 
Write out database with 1 new entries 
Data Base Updated

クライアントの鍵と証明書の作成

$ sudo openssl req -days 1095 -new -keyout vpn_client.key -out vpn_client.csr     
Generating a 4096 bit RSA private key 
...................................................................++ 
..............................................................++ 
writing new private key to 'client.key' 
Enter PEM pass phrase: 
Verifying - Enter PEM pass phrase:
...
Common Name (eg, your name or your server's hostname) []:
...
openssl ca -days 1095 -out vpn_client.crt -in vpn_client.csr

Common Nameはユーザごとユニークな名前にします。

Diffie-Hellman(DH)鍵の作成

$ sudo openssl dhparam -out dh4096.pem 4096
または
$ sudo sudo openssl dhparam -dhparam -out dh4096.pem 4096

TLS認証鍵作成

$ sudo openvpn --genkey --secret ta.key
$ sudo cp ta.key /etc/openvpn

この同じキーをClient側にもインストールします。

所定の場所に鍵と証明書をコピーします。

$ sudo cp vpn_ca.crt vpn_server.crt /etc/ssl/certs
$ sudo cp dh4096.pem vpn_server.key /etc/ssl/private
$ sudo chown root:ssl-cert /etc/ssl/private/vpn_server.key /etc/ssl/private/dh4096.pem
$ sudo chmod 600 /etc/ssl/private/vpn_server.key
$ sudo chmod 640 /etc/ssl/private/dh4096.pem

設定ファイルのServer用テンプレートをコピーします。

$ sudo cp /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz /etc/openvpn
$ sudo gunzip /etc/openvpn/server.conf.gz

/etc/openvpn/server.conf:

port 1194 
proto udp 
dev tun 
ca /etc/ssl/certs/vpn_ca.crt 
cert /etc/ssl/certs/vpn_server.crt 
key /etc/ssl/private/vpn_server.key  # This file should be kept secret 
#crl-verify /etc/ssl/certs/crl.pem
dh /etc/ssl/private/dh4096.pem 
server 10.8.0.0 255.255.255.0 
ifconfig-pool-persist ipp.txt 
keepalive 10 60 
tls-auth ta.key 0 # This file is secret 
cipher AES-256-CBC 
comp-lzo 
user nobody 
group nogroup 
persist-key 
persist-tun 
status openvpn-status.log 
verb 3 
explicit-exit-notify 1

起動テスト

$ sudo openvpn --config server.conf

すべてのClient側のトラフィックをサーバ経由にする場合に追加します。

push "redirect-gateway def1 bypass-dhcp"

サーバの自動設定

/etc/default/openvpn:

AUTOSTART="all"
$ sudo systemctl enable openvpn
$ sudo systemctl start openvpn

サーバで作ったClient用の鍵と証明書をClient側にコピーしてOpenVPNの設定ディレクトリに移動します。

$ sudo mv ta.key vpn_ca.crt vpn_client.crt vpn_client.key /etc/openvpn

/usr/share/doc/openvpn/examples/sample-config-filesからClient用の設定をコピーし編集します。

/etc/openvpn/client.conf:

client 
dev tun 
proto udp 
remote <server> 1194 
resolv-retry infinite 
nobind 
user nobody 
group nogroup 
persist-key 
persist-tun 
ca /etc/openvpn/vpn_ca.crt 
cert/etc/openvpn/vpn_client.crt 
key/etc/openvpn/vpn_client.key 
remote-cert-tls server 
tls-auth ta.key 1 
cipher AES-256-CBC 
comp-lzo 
verb 3

起動テスト

$ sudo openvpn --config client.conf

Network Managerを使えばより便利になります。

Revocation Listの設定

Clientの証明書を無効にする場合、Revocationを行います。

例)RevocationされるとindexがRに変わります。

$ sudo cd /root/certs
$ sudo grep libreelec *.pem
04.pem:        Subject: C=JP, ST=SA, O=MYEXAMPLE.COM, OU=IT-DIVISION, CN=libreelec/emailAddress=root@server.
vpn.com
 
$ sudo cat index.txt 
R       201115165255Z   171116165340Z   04      unknown /C=JP/ST=SA/O=MYEXAMPLE.COM/OU=IT-DIVISION/CN=libree
lec/emailAddress=root@server.vpn.com

$ sudo openssl ca -revoke 04.pem
$ sudo openssl ca -gencrl -out crl.pem
$ sudo cp crl.pem /etc/ssl/certs/

サーバーの設定ファイルに追加します。

$ sudo crl-verify /etc/ssl/certs/crl.pem

RevocationされたClientがアクセスしようとすると拒否されます。

$ sudo journalctl -xf
...
OpenSSL: error:14089086:SSL routines:ssl3_get_client_certificate:certificate verify failed

Troubleshoot

鍵と証明書の作成で次のようなエラーが出た場合

failed to update database
 TXT_DB error number 2

次のようにリボーケーションしてデータベースを更新します。

$ sudo openssl ca -revoke 01.pem

追記:OpenWrtの設定

OpenWrtは独特なエコシステムなのでダッシュボードでFirewallとNetwork関係を設定します。まずOpenVPNのポート開放します。

次にインターフェースの設定を行います。Tun0デバイスを選択します。Tun+みたいな使い方はちょっと分かりません。

最後にfirewallの設定をします。ゾーンを作成してOpenVpn->Lan、Wanにフォワーディングを出来るようにします。またインターフェースのopenvpnとのリンクもします。

以上で完了です。

追記2:ovpnファイルの作成

ちょっと手間がかかるのでシェルスクリプトで対応しました。スクリプトで作成して余計な部分はエディタで修正します。

mk_ovpn.conf

output='client.ovpn'
server='tset.mydns.jp'
client_conf='client.conf'
cacert_file='test_openvpn_ca.crt'
cert_file='test@openvpn.crt'
key_file='test@openvpn.pem'
ta_key='ta.key

mk_ovpn.sh

#!/bin/bash

. ./mk_ovpn.conf

echo "client" > $output
echo "remote $server" >> $output
[ -f $client_conf ] && cat $client_conf >> $output
sed -i '/^#/d' $output
sed -i '/^;/d' $output
sed -i '/^$/d' $output
sed -i '/ca\s*/d' $output
sed -i '/cert\s*/d' $output
sed -i '/key\s*/d' $output
sed -i '/tls-auth\s*/d' $output
echo -e "\nkey-direction 1\n" >> $output
echo "<ca>" >> $output
[ -f $cacert_file ] && cat $cacert_file >> $output
echo -e "</ca>\n" >> $output
echo "<cert>" >> $output
[ -f $cert_file ] && cat $cert_file >> $output
echo -e "</cert>\n" >> $output
echo "<key>" >> $output
[ -f $key_file ] && cat $key_file >> $output
echo -e "</key>\n" >> $output
echo "<tls-auth>" >> $output
[ -f $ta_key ] && cat $ta_key >> $output
echo -e "</tls-auth>\n" >> $output

References