kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

OpenVPNサーバ・クライアントで2点間をつないでみる

自分用メモです。

過去に、VPNルータとかのHW製品を使ってVPNを構築したことはあるのだけど、諸般の事情によりOpenVPNを使うことになりました。OpenVPN使ったことないしVPNネットワークもあまり良くわかってないので、例によってVagrantを使ってこんな感じのネットワークを作って試してみます。

f:id:kun432:20200510152404p:plain

基本的な構成

最終的に作るのはざっくりこんな感じになります。

  • a.privateとb.privateが別のネットワークになっていて、それぞれのgwでつながっている想定です。
  • vpn-client.a.privateからvpn-server.b.privateにVPNで接続して、web-server.b.privateに接続します。OpenVPNを使ってルーティング方式(TUN)で接続します。
  • gw-a、gw-bはルータ兼FWです。 a.privateとb.privateのそれぞれ内部から外向きにIPマスカレードしたり、gw-b.b.privateではvpn-serverへのudp:1194をポートフォワードしたりします。
  • 一つ大事なこととして、Vagrantの場合、eth0はいろいろ手当が必要です。
    • ホスト・ゲスト間の通信(vagrant sshとか)に使うため必須ですが、ゲスト間の通信には使えません。
    • 各ネットワーク内のホストでeth0がデフォルトゲートウェイになってしまうと、お互いのネットワークへの経路がない状態ではこちらのIFから抜けようとします。それだと都合が悪いので、それぞれのgwをデフォルトゲートウェイにします。
    • そうなると今度はyumのアップデートなどでインターネットへの経路がなくなってしまいますので、gwのeth0にもIPマスカレードを指定してここから抜けるようにします。
    • 名前解決は今回あまり凝ったことはせずに、dnsmasqを使って最低限の名前解決だけができればよいという感じにしてます。

まずは以下のレポジトリをcloneしてvagrant upします。ちょっと時間がかかります。

これで5台のCentOS7サーバと最低限の設定が入ったネットワークが出来上がります。細かいところはプロビジョニングで使っているシェルスクリプトを見てもらえればと思うのですが、a.private側でかんたんに説明します。

f:id:kun432:20200510183142p:plain

gw-aにログインします。

$ vagrant ssh gw-a
[vagrant@gw ~]$

gw-aのインタフェースは以下の3つです。

$ nmcli
eth0: 接続済み to System eth0
        ・・・
        ip4 デフォルト
        inet4 10.0.2.15/24
        route4 0.0.0.0/0
        route4 10.0.2.0/24
        ・・・

eth1: 接続済み to System eth1
        ・・・
        inet4 10.0.0.10/24
        route4 10.0.0.0/24
        ・・・

eth2: 接続済み to System eth2
        ・・・
        inet4 192.168.0.10/24
        route4 192.168.0.0/24
        ・・・

eth0がVagrant(というかVirtualbox)デフォルトのホスト・ゲスト間をつなぐIF、eth1がprivate.bとつながる外部NW想定、eth2がLANの想定です。

これにあわせてfirewalldの設定は以下となっています。

$ sudo firewall-cmd --get-active-zones
external
  interfaces: eth1 eth0
trusted
  interfaces: eth2

以下のようにexternalにはIPマスカレードを設定してあります。これによりLAN内の複数のホストからこのIFを通じて外にアクセスができるようになっています。

$ sudo firewall-cmd --list-all --zone=external
external (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth1
  sources:
  services: ssh
  ports:
  protocols:
  masquerade: yes
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

ということで、実際に疎通を確認します。vpn-clientにログインします。

$ vagrant ssh vpn-cleint
[vagrant@vpn-cleint ~]$

最初に記載したとおり、Vagrantのデフォルトだと、eth0がホスト・ゲスト間をつなぐIFになっていて、その先にあるホスト(つまりMac)がデフォルトゲートウェイになるので、gwをデフォルトゲートウェイに変更してあります。

$ ip route
default via 192.168.0.10 dev eth1 proto static metric 103
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 102
192.168.0.0/24 dev eth1 proto kernel scope link src 192.168.0.20 metric 103

では、疎通を確認します。

$ ping gw                # 同一ネットワーク内のgw
$ ping gw.b.private      # 対向のgw
$ ping www.google.com    # インターネット(gw→ホストのMac経由)

すべて疎通OKですね。b.private側も同じです。それぞれのネットワーク内の全ホストと、対向のGWだけが名前解決できるようにしてあります。

OpenVPNサーバの設定

ここからが本題です(前フリが長い)。b.privateのvpn-serverにログインして設定していきます。

$ vagrant ssh vpn-server
[vagrant@vpn-server ~]$

OpenVPNとeasyrsaはすでにインストールされていますので、設定をしていくだけです。まずは各種鍵・証明書を作っていきましょう。面倒なのでrootで。

$ sudo su -
# cd /etc/openvpn/easyrsa3

初期化。これで/etc/openvpn/easyrsa3/pkiディレクトリが作成されます。

# ./easyrsa init-pki

init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /etc/openvpn/easyrsa3/pki

CAの証明書・秘密鍵を作成します。ここで入力したパスフレーズはサーバ/クライアント証明書を作成する際に必要になります。

# ./easyrsa build-ca
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017

Enter New CA Key Passphrase:      ※CA用パスフレーズを入力
Re-Enter New CA Key Passphrase:       ※確認のため再度入力
Generating RSA private key, 2048 bit long modulus
..................................+++
..............................................................................................................................................+++
e is 65537 (0x10001)
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Common Name (eg: your user, host, or server name) [Easy-RSA CA]: vpn-server.b.private  ※vpnサーバのホスト名を入力

CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/etc/openvpn/easyrsa3/pki/ca.crt

サーバー証明書を作成します。パスフレーズを聞かれたら、先程のパスフレーズで。

# ./easyrsa build-server-full vpn-server.b.private nopass
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
Generating a 2048 bit RSA private key
...+++
..+++
writing new private key to '/etc/openvpn/easyrsa3/pki/easy-rsa-21404.ahmyOb/tmp.dAJJUu'
-----
Using configuration from /etc/openvpn/easyrsa3/pki/easy-rsa-21404.ahmyOb/tmp.SxJ5M6
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:      ※CA用パスフレーズを入力
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'vpn-server.b.private'
Certificate is to be certified until Aug 13 11:36:13 2022 GMT (825 days)

Write out database with 1 new entries
Data Base Updated

DHパラメータを作成。鍵交換アルゴリズムに使う素数の作成だそうです。

# ./easyrsa gen-dh
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
..............................................................................................+
..........................+..............+....................+.........................+..
・・・
DH parameters of size 2048 created at /etc/openvpn/easyrsa3/pki/dh.pem

で、クライアント証明書の作成の前に、証明書廃止のための証明書失効リストを作成しておきます。そのために一旦ダミーでクライアント証明書を作成します。

# ./easyrsa build-client-full dummy nopass
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
Generating a 2048 bit RSA private key
................................................+++
.............................+++
writing new private key to '/etc/openvpn/easyrsa3/pki/easy-rsa-21504.5lPkNt/tmp.wC2u8r'
-----
Using configuration from /etc/openvpn/easyrsa3/pki/easy-rsa-21504.5lPkNt/tmp.7H0LDk
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:      ※CA用パスフレーズを入力
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'dummy'
Certificate is to be certified until Aug 13 11:49:42 2022 GMT (825 days)

Write out database with 1 new entries
Data Base Updated

ダミーのクライアント証明書を廃止します。

# ./easyrsa revoke dummy
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017


Please confirm you wish to revoke the certificate with the following subject:

subject=
    commonName                = dummy


Type the word 'yes' to continue, or any other input to abort.
  Continue with revocation: yes     ※確認のためyesを入力
Using configuration from /etc/openvpn/easyrsa3/pki/easy-rsa-21573.GoXnz4/tmp.Rw7Etz
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:     ※CA用パスフレーズを入力
Revoking Certificate 6D1DE2D9B68DF1306278AF56E4F165C0.
Data Base Updated

IMPORTANT!!!

Revocation was successful. You must run gen-crl and upload a CRL to your
infrastructure in order to prevent the revoked cert from being accepted.

証明書失効リストを作成します。

# ./easyrsa gen-crl
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
Using configuration from /etc/openvpn/easyrsa3/pki/easy-rsa-21636.7Nbh6C/tmp.EBwBUj
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:    ※CA用パスフレーズを入力

An updated CRL has been created.
CRL file: /etc/openvpn/easyrsa3/pki/crl.pem

TLS認証鍵を作成します。

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

最後に出力された各種鍵・証明書を/etc/openvpn/server配下にコピーします。

# cp pki/ca.crt pki/issued/vpn-server.b.private.crt pki/private/vpn-server.b.private.key pki/dh.pem pki/crl.pem /etc/openvpn/server/.

ではOpenVPNサーバの設定です。雛形から作成します。

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

関連するところだけ。

port 1194
proto udp
dev tun
ca ca.crt
cert vpn-server.b.private.crt
key vpn-server.b.private.key
dh dh.pem
server 172.16.0.0 255.255.255.0
push "route 192.168.100.0 255.255.255.0"
tls-auth ta.key 0
user nobody
group nobody
log-append  openvpn.log
management localhost 7505
crl-verify crl.pem
  • server 172.16.0.0 255.255.255.0のところはVPNネットワークのセグメントを指定します。このセグメントの172.16.0.0.1がVPNサーバになり、残りをクライアント側に割り当てます。
  • push "route 192.168.100.0 255.255.255.0"により、クライアント側にIPアドレスが払い出されると同時にルーティングが設定されます。つまりb.privateのLANである192.168.100.0/24へのルーティングに172.16.0.Xを経由するようになります。

OpenVPNサーバを起動します。

# sudo systemctl start openvpn-server@server.service
# sudo systemctl status openvpn-server@server.service
# sudo systemctl enable openvpn-server@server.service

systemctl statusでactiveになっていて、tunデバイスが上がっていればOKです。

$ nmcli
・・・
tun0: connected to tun0
        "tun0"
        tun, sw, mtu 1500
        inet4 172.16.0.1/32
        route4 172.16.0.2/32
        route4 172.16.0.0/24
       ・・・

172.16.0.1がVPNセグメント上のVPNサーバのIPアドレスですね。

で、vpn-server.b.privateはgw.b.privateの中にあるのでポートフォワードを行います。

$ vagrant ssh gw-b
[vagrant@gw ~]$
$ sudo firewall-cmd --zone=external --add-forward-port=port=1194:proto=udp:toport=1194:toaddr=192.168.100.20 --permanent
$ sudo firewall-cmd --reload

OpenVPNクライアントの設定

では、クライアント側の設定です。まずサーバ側でクライアント証明書を作成します。

$ vagrant ssh vpn-server
[vagrant@vpn-server ~]$ sudo su -
# cd /etc/openvpn/easyrsa3
# ./easyrsa build-client-full vpn-client.a.private nopass

CA証明書といっしょにクライアント証明書・鍵をクライアントにコピーします。/shareが共有パスになっているのでそこにコピーします。

# cp pki/ca.crt pki/private/vpn-client.a.private.key pki/issued/vpn-client.a.private.crt /etc/openvpn/server/ta.key /share

では、クライアント側です。

$ vagrant ssh vpn-client
[vagrant@vpn-client ~]$

/shareから証明書等を/etc/openvpn/client以下にコピーします。

$ sudo cp -pi /share/vpn-client.a.private.key /share/vpn-client.a.private.crt /share/ca.crt /share/ta.key /etc/openvpn/client/.

サーバと同じように設定ファイルを雛形からコピーして作成します。

$ sudo cp -pi /usr/share/doc/openvpn-2.4.9/sample/sample-config-files/client.conf /etc/openvpn/client/.
$ sudo vi /etc/openvpn/client/client.conf

設定ファイルのポイントはこんな感じです。

client
dev tun
proto udp
remote vpn-server.b.private 1194
user nobody
group nobody
ca ca.crt
cert vpn-client.a.private.crt
key vpn-client.a.private.key
tls-auth ta.key 1

サービスを立ち上げます。

$ sudo systemctl start openvpn-client@client.service
$ sudo systemctl status openvpn-client@client.service
$ sudo systemctl enable openvpn-client@client.service

systemctl statusで確認、tunデバイスが作成されていることを確認できればOKです。

$ nmcli
・・・
tun0: connected to tun0
        "tun0"
        tun, sw, mtu 1500
        inet4 172.16.0.6/32
        route4 172.16.0.5/32
        route4 192.168.100.0/24
        route4 172.16.0.1/32
       ・・・

172.16.0.6がクライアント側に割り当てられたIPアドレスです。

VPNサーバへの疎通を確認してみましょう。

$ ping 172.16.0.1
PING 172.16.0.1 (172.16.0.1) 56(84) bytes of data.
64 bytes from 172.16.0.1: icmp_seq=1 ttl=64 time=2.28 ms
64 bytes from 172.16.0.1: icmp_seq=2 ttl=64 time=1.45 ms

つながってますね!VPNサーバ内のLANにつながるか確認します。

$ ping 192.168.100.20
PING 192.168.100.20 (192.168.100.20) 56(84) bytes of data.
64 bytes from 192.168.100.20: icmp_seq=1 ttl=64 time=1.74 ms
64 bytes from 192.168.100.20: icmp_seq=2 ttl=64 time=1.55 ms
$curl http://192.168.100.30/
^C
$ ping 192.168.100.30
PING 192.168.100.30 (192.168.100.30) 56(84) bytes of data.
^C
--- 192.168.100.30 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3001ms

VPNサーバまでは繋がりますが、LAN内のホストにはつながらないですね。はい、ここでもIPマスカレードの設定は必要です。

ということで、vpn-server内でIPマスカレードの設定をします。ここはちょっと手抜きします・・・

$ vagrant ssh vpn-server
[vagrant@vpn-server ~]$
$ sudo systemctl start firewalld
$ sudo firewall-cmd --get-active-zones
public
  interfaces: eth0 eth1
$ firewall-cmd --permanent --zone=trusted --add-interface=tun+ 
$ firewall-cmd --zone=trusted --add-interface=tun+
$ firewall-cmd --direct --passthrough ipv4 -t nat -A POSTROUTING -s 172.16.0.0/24 -o eth1 -j MASQUERADE
$ firewall-cmd --permanent --direct --passthrough ipv4 -t nat -A POSTROUTING -s 172.16.0.0/24 -o eth1 -j MASQUERADE

では確認してみましょう。

[root@vpn-client ~]# ping 192.168.100.30
PING 192.168.100.30 (192.168.100.30) 56(84) bytes of data.
64 bytes from 192.168.100.30: icmp_seq=1 ttl=63 time=1.94 ms
64 bytes from 192.168.100.30: icmp_seq=2 ttl=63 time=2.18 ms
--- 192.168.100.30 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2006ms
rtt min/avg/max/mdev = 1.554/1.895/2.182/0.259 ms

[root@vpn-client ~]# curl 192.168.100.30
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
        <title>Apache HTTP Server Test Page powered by CentOS</title>
・・・

b.privateのLAN内にもアクセスできていますねー

その他

実はVagrantのprivate networkでちょいちょいハマりました。vagrant reloadしちゃうと多分インタフェースが初期化されちゃうのですね。nmcliとかfirewalldとあいまって非常に相性が悪い感じです。やっつけですが、起動時に必ずネットワーク設定するようにしました。

      c.vm.provision :shell, :path => "set-host-nw.sh", run: "always"

nmcli使うのであれば:auto_config => falseにしちゃってプロビジョニングスクリプトの中で全部やっちゃうほうがいいかもしれません。

あと、きちんと確認できてないのですが、private networkで異なるセグメントからping通っちゃうという事象があって、見てる感じprivate networkの第4オクテット.1、つまりホストのMac経由で流れてきているようなのですね。なので、Vagrantfileの中で、virtualbox__intnetでネットワーク名を設定しました。これが効いてるのかどうかわからないけど、その後は再現しなくなってます。

      c.vm.network "private_network", ip: "192.168.0.20", virtualbox__intnet: "a.private"

今更ながらVagrantとてもいいんですけど、ネットワークで凝った設定が必要な場合はちょっと厳しいかなというのが今回の気づきでした。

参考にさせていただいたサイト

ありがとうございました。