はじめに
こんにちは。マイクロアドでインフラエンジニアをしているハダです。
3回目のブログ投稿です。
今回のブログは、オンプレの環境とGCPの接続にVPNを使用し、
オンプレ側で使用するVPNゲートウェイをstrongSwan
とFRRで構築した内容です。
www.strongswan.org
frrouting.org
一般的に、オンプレ側ではVPN対応RouterやVyOSなどのネットワークOSを使ってVPN接続することが多いです。
今回OSはUbuntu20.04を使いstrongSwanとFRRでGCPとVPN接続しました。
GCP側はHA VPN Gatewayを使うため、動的ルーティング(BGP)が必須となっています。1
そのため、オンプレ側ではFRRを使いBGP接続できるようにします。
なお、この記事の中ではGCP側のVPN設定の方法及びオンプレ側でのNAT設定等については書いていません。
オンプレとクラウドの接続方法
本題の前に、オンプレとクラウドを接続する方法を振り返っておきます。
クラウド事業者によりそれぞれのサービス名は異なりますが、主に以下2つの方法があります。
それぞれの接続方法については、メリット・デメリットありますがこの記事では省略します。
- 専用線による接続:オンプレ環境のあるデータセンターからクラウド事業者のアクセスポイントまで専用線を引いて接続する方法
- 拠点間VPNによる接続:オンプレ環境にVPNゲートウェイを準備し、VPNで接続する方法
専用線による接続
主なパブリッククラウドの接続サービス名称は、以下のようなものです。
- GCP Dedicated Interconnect
- AWS Direct Connect
- Azure Express Route
などです。
こちらのサービスは、クラウド事業者と直接回線を引いて接続する方法と
クラウド接続サービスを提供するプロバイダーを利用し、接続する方法があります。
どちらの場合も、オンプレ側に接続用の回線を準備する(引き込む)必要があります。
拠点間VPNによる接続
VPN接続サービスのサービス名称は、以下のようなものです。
- GCP Cloud VPN
- AWS VPN
- Azure VPN Gateway
などです。
今回はこちらの方法で接続し、その時オンプレ側で構築したVPNゲートウェイの構築方法についてです。
構成
今回のVPNゲートウェイは、Ubuntu20.04.4を使って構築しています。
VPN接続に必要なソフトウェアはstrongSwanです。
strongSwanとは
Linuxで使えるオープンソースのIPSecベースのVPNソフトウェアです。
IKEv1とv2に対応しています。
メジャーなLinuxディストリビューションには、バイナリパッケージが提供されています。
Ubuntuにインストールする場合は、標準リポジトリでパッケージが提供されていますので、 aptでインストールが可能です。
sudo apt install strongswan
接続イメージ
左側がオンプレ側、右側がクラウド側です。
後述するipsecの設定でleft,rightという項目が出てきますが、
この図の通りleftがオンプレ(設定している自分)側・rightがクラウド(相手)側として設定しています。
strongSwanがGCP側のHA VPN GatewayとIPsecでVPN Tunnelを構成します。
このTunnelを使って、オンプレ側のFRRとGCP側のCloud RouterがBGPセッションを張ってPeeringします。
設定方法
IPsecの設定
strongSwanのIPSec設定します。
ipsec.confの設定
設定ファイルは、/etc/ipsec.confです(使用するディストリビューションによりPathが異なります)
以下、ipsec.confの設定例です。
各パラメータについては、環境により適切に変更してください。
ipsec.confの例を見る
# ipsec.conf - strongSwan IPsec configuration file # basic configuration config setup # strictcrlpolicy=yes # uniqueids = no # Add connections here. ## default conn %default ikelifetime=60m keylife=20m rekeymargin=3m keyingtries=1 authby=secret keyexchange=ikev2 mobike=no type=tunnel leftsubnet=0.0.0.0/0,::/0 rightsubnet=0.0.0.0/0,::/0 ## GCP-VPN conn gcp-vpn-tunnel01 esp=aes128-sha1-modp1024,3des-sha1-modp1024 ikelifetime=600m keylife=180m rekeymargin=1m keyingtries=5 keyexchange=ikev2 left=[自HostIP] leftid=[自Site側のPublicIP] leftupdown=/etc/ipsec-vti.sh right=[対向のIPアドレス] auto=start mark=100 conn gcp-vpn-tunnel02 esp=aes128-sha1-modp1024,3des-sha1-modp1024 ikelifetime=600m keylife=180m rekeymargin=1m keyingtries=5 keyexchange=ikev2 left=[自HostIP] leftid=[自Site側のPublicIP] leftupdown=/etc/ipsec-vti.sh right=[対向のIPアドレス] auto=start mark=200
ipsec.secretsの作成
/etc/ipsec.secretsファイルを作成します。
このファイルには、自サイト側のPublic IP、対向側のPublic IP、事前共有キーを1行に記載するだけです。
ipsec.secretsの書き方を見る
[自サイト側public ip] [対向側IP1] : PSK "hogehogehogehogehogehogehogehoge" [自サイト側public ip] [対向側IP2] : PSK "hehehehehehehehehehehehehehehehe"
トンネルインタフェース作成用のスクリプト
IPSecコネクションが張れた場合にトンネルインタフェースを作成するスクリプトを設置します。
前述のipsec.conf内で、leftupdownのパラメータで指定している /etc/ipsec-vti.sh というファイル名で、
スクリプト設置します。(ファイル名は任意です)
作成したファイルには、実行権限を付与しておきます。
ipsec-vti.shのスクリプト例を見る
#!/bin/bash # # /etc/ipsec-vti.sh # IP=$(which ip) IPTABLES=$(which iptables) PLUTO_MARK_OUT_ARR=(${PLUTO_MARK_OUT//// }) PLUTO_MARK_IN_ARR=(${PLUTO_MARK_IN//// }) case "$PLUTO_CONNECTION" in gcp-vpn-tunnel01) # ipsec.conf内でconnに設定しているコネクション名を設定 VTI_INTERFACE=vti01 VTI_LOCALADDR=169.254.0.2/30 VTI_REMOTEADDR=169.254.0.1/30 ;; gcp-vpn-tunnel02) # ipsec.conf内でconnに設定しているコネクション名を設定 VTI_INTERFACE=vti02 VTI_LOCALADDR=169.254.0.6/30 VTI_REMOTEADDR=169.254.0.5/30 ;; esac # output parameters to /var/log/messages for debug logger "ipsec-vti.sh: =================================================" logger "ipsec-vti.sh: PLUTO_CONNECTION = ${PLUTO_CONNECTION}" logger "ipsec-vti.sh: PLUTO_VERB = ${PLUTO_VERB}" logger "ipsec-vti.sh: VTI_INTERFACE = ${VTI_INTERFACE}" logger "ipsec-vti.sh: PLUTO_ME = ${PLUTO_ME}" logger "ipsec-vti.sh: PLUTO_PEER = ${PLUTO_PEER}" logger "ipsec-vti.sh: PLUTO_MARK_IN_ARR[0] = ${PLUTO_MARK_IN_ARR[0]}" logger "ipsec-vti.sh: PLUTO_MARK_OUT_ARR[0] = ${PLUTO_MARK_OUT_ARR[0]}" logger "ipsec-vti.sh: PLUTO_MARK_IN = ${PLUTO_MARK_IN}" logger "ipsec-vti.sh: =================================================" case "${PLUTO_VERB}" in up-client) $IP link add ${VTI_INTERFACE} type vti local ${PLUTO_ME} remote ${PLUTO_PEER} okey ${PLUTO_MARK_OUT_ARR[0]} ikey ${PLUTO_MARK_IN_ARR[0]} sysctl -w net.ipv4.conf.${VTI_INTERFACE}.disable_policy=1 sysctl -w net.ipv4.conf.${VTI_INTERFACE}.rp_filter=2 || sysctl -w net.ipv4.conf.${VTI_INTERFACE}.rp_filter=0 $IP addr add ${VTI_LOCALADDR} remote ${VTI_REMOTEADDR} dev ${VTI_INTERFACE} $IP link set ${VTI_INTERFACE} up mtu 1436 $IPTABLES -t mangle -I FORWARD -o ${VTI_INTERFACE} -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu $IPTABLES -t mangle -I INPUT -p esp -s ${PLUTO_PEER} -d ${PLUTO_ME} -j MARK --set-xmark ${PLUTO_MARK_IN} $IP route flush table 220 ;; down-client) $IP link del ${VTI_INTERFACE} $IPTABLES -t mangle -D FORWARD -o ${VTI_INTERFACE} -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu $IPTABLES -t mangle -D INPUT -p esp -s ${PLUTO_PEER} -d ${PLUTO_ME} -j MARK --set-xmark ${PLUTO_MARK_IN} ;; esac # Enable IPv4 forwarding sysctl -w net.ipv4.ip_forward=1 sysctl -w net.ipv4.conf.eth0.disable_xfrm=1 sysctl -w net.ipv4.conf.eth0.disable_policy=1
スクリプト中の VTI_LOCALADDRとVTI_REMOTEADDR については169.254.0.0/16から/30で切り出したものを設定します。
ここで設定しているIPアドレスが、トンネルの両端に設定されるアドレスです。
後述のBGPの設定内で、このIPアドレスを使ってBGPのpeer設定します。
補足:AWSでは、この範囲のうち以下については予約されていて設定できません。
169.254.0.0/30 169.254.1.0/30 169.254.2.0/30 169.254.3.0/30 169.254.4.0/30 169.254.5.0/30 169.254.169.252/30
ipsec設定の反映
ここまでの設定ファイルの作成で、ipsecによるトンネルが作成できる状態になっています。
設定反映を反映し、トンネルが作成できるか確認します。
systemctl restart strongswan-starter.service
もしくは、以下のようにipsecコマンドを使います。
ipsec stop ipsec start
トンネル状態の確認
トンネルの状態を確認するには、ipsec statusallで確認できます。
Connections及びSecurity Associationsで設定したトンネルが作成されていればOKです。
ubuntu@vpngateway01:~$ sudo ipsec statusall Status of IKE charon daemon (strongSwan 5.8.2, Linux 5.4.0-107-generic, x86_64): uptime: 25 days, since Apr 21 10:54:49 2022 malloc: sbrk 3690496, mmap 0, used 1899216, free 1791280 worker threads: 11 of 16 idle, 5/0/0/0 working, job queue: 0/0/0/0, scheduled: 8 loaded plugins: charon aesni aes rc2 sha2 sha1 md5 mgf1 random nonce x509 revocation constraints pubkey pkcs1 pkcs7 pkcs8 pkcs12 pgp dnskey sshkey pem openssl fips-prf gmp agent xcbc hmac gcm drbg attr kernel-netlink resolve socket-default connmark stroke updown eap-mschapv2 xauth-generic counters Listening IP addresses: <自HostIPアドレス> 169.254.0.2 169.254.0.6 Connections: gcp-vpn-tunnel01: <自HostIPアドレス>...<対向PublicIP1> IKEv2 gcp-vpn-tunnel01: local: [<自SitePublicIP>] uses pre-shared key authentication gcp-vpn-tunnel01: remote: [<対向PublicIP1>] uses pre-shared key authentication gcp-vpn-tunnel01: child: 0.0.0.0/0 ::/0 === 0.0.0.0/0 ::/0 TUNNEL gcp-vpn-tunnel02: <自HostIPアドレス>...<対向PublicIP2> IKEv2 gcp-vpn-tunnel02: local: [<自SitePublicIP>] uses pre-shared key authentication gcp-vpn-tunnel02: remote: [<対向PublicIP2>] uses pre-shared key authentication gcp-vpn-tunnel02: child: 0.0.0.0/0 ::/0 === 0.0.0.0/0 ::/0 TUNNEL Security Associations (2 up, 0 connecting): gcp-vpn-tunnel02[308]: ESTABLISHED 21 seconds ago, <自HostIPアドレス>[<自SitePublicIP>]...<対向PublicIP2>[<対向PublicIP2>] gcp-vpn-tunnel02[308]: IKEv2 SPIs: b5c9ef394a38844e_i* 06cf833b057ad6c8_r, pre-shared key reauthentication in 9 hours gcp-vpn-tunnel02[308]: IKE proposal: AES_GCM_16_128/PRF_AES128_XCBC/MODP_2048 gcp-vpn-tunnel02{823}: INSTALLED, TUNNEL, reqid 119, ESP in UDP SPIs: c83c018d_i e93650b4_o gcp-vpn-tunnel02{823}: AES_CBC_128/HMAC_SHA1_96, 1180 bytes_i (17 pkts, 1s ago), 1490 bytes_o (21 pkts, 1s ago), rekeying in 2 hours gcp-vpn-tunnel02{823}: 0.0.0.0/0 === 0.0.0.0/0 gcp-vpn-tunnel01[307]: ESTABLISHED 4 hours ago, <自HostIPアドレス>[<自SitePublicIP>]...<対向PublicIP1>[<対向PublicIP1>] gcp-vpn-tunnel01[307]: IKEv2 SPIs: 0bc3c2c69af5a567_i* 66b5d8b2940de6a2_r, pre-shared key reauthentication in 5 hours gcp-vpn-tunnel01[307]: IKE proposal: AES_GCM_16_128/PRF_AES128_XCBC/MODP_2048 gcp-vpn-tunnel01{822}: INSTALLED, TUNNEL, reqid 118, ESP in UDP SPIs: c848ec62_i 13a9586e_o gcp-vpn-tunnel01{822}: AES_CBC_128/HMAC_SHA1_96/MODP_1024, 261905 bytes_i (4258 pkts, 0s ago), 260137 bytes_o (4224 pkts, 0s ago), rekeying in 71 minutes gcp-vpn-tunnel01{822}: 0.0.0.0/0 === 0.0.0.0/0
トンネルインタフェースの確認
ipコマンドを使って、トンネルインタフェースが作成されていることを確認します。
vti01とvti02のインタフェースが作成されていれば問題ありません。
ubuntu@vpngateway01:~$ ip a 〜〜〜省略〜〜〜 9: ip_vti0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000 link/ipip 0.0.0.0 brd 0.0.0.0 135: vti01@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1436 qdisc noqueue state UNKNOWN group default qlen 1000 link/ipip <自HostIPアドレス> peer <対向PublicIP1> inet 169.254.0.2 peer 169.254.0.1/30 scope global vti01 valid_lft forever preferred_lft forever inet6 fe80::5efe:ac1e:272f/64 scope link valid_lft forever preferred_lft forever 136: vti02@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1436 qdisc noqueue state UNKNOWN group default qlen 1000 link/ipip <自HostIPアドレス> peer <対向PublicIP2> inet 169.254.0.6 peer 169.254.0.5/30 scope global vti02 valid_lft forever preferred_lft forever inet6 fe80::5efe:ac1e:272f/64 scope link valid_lft forever preferred_lft forever
通信状況も確認します
PacketsやBytesが増えていてエラーが増え続けていなければ問題ありません。
ubuntu@vpngateway01:~$ ip -s tunnel show ip_vti0: ip/ip remote any local any ttl inherit nopmtudisc key 0 RX: Packets Bytes Errors CsumErrs OutOfSeq Mcasts 0 0 0 0 0 0 TX: Packets Bytes Errors DeadLoop NoRoute NoBufs 0 0 0 0 0 0 vti01: ip/ip remote <対向PublicIP1> local <自HostIPアドレス> ttl inherit nopmtudisc key 100 RX: Packets Bytes Errors CsumErrs OutOfSeq Mcasts 11719 720872 0 0 0 0 TX: Packets Bytes Errors DeadLoop NoRoute NoBufs 11648 717244 0 0 0 0 vti02: ip/ip remote <対向PublicIP2> local <自HostIPアドレス> ttl inherit nopmtudisc key 200 RX: Packets Bytes Errors CsumErrs OutOfSeq Mcasts 1351 83221 0 0 0 0 TX: Packets Bytes Errors DeadLoop NoRoute NoBufs 1313 81347 0 0 0 0
FRRの設定
FRRを使ってGCPのCloud RouterとBGP接続します。
マイクロアドではオンプレの環境はIP Closネットワークで構築しており、各サーバにFRRがインストールされBGPでToRと接続しています。
そのため、今回のVPNゲートウェイもすでにFRRがインストールされているため
BGP接続に関してはFRRにNeighborを追加し、必要な経路を広報するように修正して対応しました。
Neighborの設定
vtyshを使ってFRRの設定を変更します。
ubuntu@vpngateway01:~$ sudo vtysh vpngateway01# conf t vpngateway01(config)# router bgp [自AS番号] vpngateway01(config-router)# neighbor 169.254.0.1 remote-as [GCPに設定したAS番号] vpngateway01(config-router)# neighbor 169.254.0.5 remote-as [GCPに設定したAS番号] vpngateway01(config-router)# address-family ipv4 unicast vpngateway01(config-router-af)# neighbor 169.254.0.1 soft-reconfiguration inbound vpngateway01(config-router-af)# neighbor 169.254.0.5 soft-reconfiguration inbound
これだけで、Cloud RouterとBGP接続できます。
ただし、この状態だとオンプレ側にある経路をそのままCloud Routerに広報してしまうため、
必要な経路かつAggregateしてCloud Router側に広報するように設定します。
ip prefix-listとroute-mapを追加して、経路フィルターができるように追加します。
ここでは、192.168.[0|1|2].0/24のPREFIXを広報するようにします。
vpngateway01# conf t vpngateway01(config)# ip prefix-list SERVER_RANGE seq 10 permit 192.168.0.0/24 vpngateway01(config)# ip prefix-list SERVER_RANGE seq 11 permit 192.168.1.0/24 vpngateway01(config)# ip prefix-list SERVER_RANGE seq 12 permit 192.168.2.0/24 vpngateway01(config)# route-map AGGREGATE_ROUTES permit 10 vpngateway01(config-route-map)# match ip address prefix-list SERVER_RANGE
このあと、作成したrote-mapを今回接続しているNeighborに対してout方向でフィルタリングし、 Aggregateする設定も追加します。
vpngateway01# conf t vpngateway01(config)# router bgp [自AS番号] vpngateway01(config-router)# address-family ipv4 unicast vpngateway01(config-router-af)# aggregate-address 192.168.0.0/24 vpngateway01(config-router-af)# aggregate-address 192.168.1.0/24 vpngateway01(config-router-af)# aggregate-address 192.168.2.0/24 vpngateway01(config-router-af)# neighbor 169.254.0.1 route-map AGGREGATE_ROUTES out vpngateway01(config-router-af)# neighbor 169.254.0.5 route-map AGGREGATE_ROUTES out
ここまで設定すると、Cloud Router側に広報する経路が192.168.0.0/24〜192.168.2.0/24の3経路のみになります。
vpngateway01# show ip bgp summary 〜〜〜省略〜〜〜 Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt 169.254.0.1 4 [GCPに設定したAS番号] 748692 752463 0 0 0 07:45:32 1 3 169.254.0.5 4 [GCPに設定したAS番号] 706792 709111 0 0 0 03:26:22 1 3 〜〜〜省略〜〜〜
設定が完了したら、write memoryで設定ファイルに書きが出して保存しておきます。
vpngateway01# write memory Note: this version of vtysh never writes vtysh.conf Building Configuration... Integrated configuration saved to /etc/frr/frr.conf [OK]
まとめ
このあとGCP側にVMを作成して通信状況を確認することでVPNでの接続設定は完了です。
個人的には過去にVyattaと言われていた頃のVyOSでVPN接続したことはありましたが、
strongSwanでの接続もポイントさえ押さえれば意外と簡単に設定できました。
ただ、strongSwanではトンネルインタフェースを作るところまでしてくれないので、
スクリプトを別途用意して対応する必要があるなど注意点があることも事実です。
AWSでも同じ方法で接続が可能です。
ただしAWSで設定するカスタマーゲートウェイに設定できるAS番号が
4byteのプライベートAS番号が含まれていない点に注意が必要です。 2
参考にしたサイト
今回の構築にあたって、以下のサイトを参考にさせていただきました。
AWS Site-to-Site VPN with IPSec VPN (StrongSwan) and BGP (FRRouting)