前回、VPCとDebianの間で構築したIPsec VPNを、冗長化をなんとか保ったままNATをやめる方法を提案します。
あえて”提案”と書いたのは、実際自分はNATしたくないためこちらで利用しますが、こ汚い設計になるため自信もって推奨できないからです。
NATを外すための設定
前回の続きなので、まずVPN上のNATを無効にします。
1 2 |
# コメントアウトとか # iptables -t nat -A POSTROUTING -d 10.100.0.0/16 -j MASQUERADE |
次に、ipsec-toolsに設定を加えます。
元のTunnel用設定は確定しているので、ファイルを分けておきます。
1 2 |
# 名前順に実行されるようにリネーム mv /etc/ipsec-tools.d/vpc.conf /etc/ipsec-tools.d/10-vpc-tunnels.conf |
Customer Subnet <=> EC2 Subnetを許可します。
/etc/ipsec-tools.d/20-vpc-private.conf
1 2 |
spdadd 172.30.4.0/22 10.100.0.0/16 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.16/require; spdadd 10.100.0.0/16 172.30.4.0/22 any -P in ipsec esp/tunnel/27.0.1.16-172.30.4.11/require; |
そして、QuaggaにCustomerSubnetを書くことで、EC2側のルーティングに追加し、EC2 => Customerに返ってこれるようにします。
/etc/quagga/bgpd.conf
1 2 |
! この部分をコメントアウトしてたら有効にする network 172.30.4.11/22 |
そして setkey と quagga を再起動します。
1 2 |
service setkey restart service quagga restart |
これで、Customer ClientからPingを打った時に、EC2側で tcpdump icmp していると、今までsrcが 169.254.252.2 or 169.254.252.6 だったのが 172.30.4.20 になり、返りのルートも vgw となっているため見事往復できることになります。もちろん、VPNからの通信も変わりなくできる状態です。
NATなしでは正当な冗長化ができない理由
変更した設定には1つ不備に見える部分があります。setkeyです。Tunnel 1つ分の設定しかないので本来こう、したいところです…
/etc/ipsec-tools.d/20-vpc-private.conf
1 2 3 4 5 6 7 |
# Tunnel1 spdadd 172.30.4.0/22 10.100.0.0/16 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.16/require; spdadd 10.100.0.0/16 172.30.4.0/22 any -P in ipsec esp/tunnel/27.0.1.16-172.30.4.11/require; # Tunnel2 spdadd 172.30.4.0/22 10.100.0.0/16 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.144/require; spdadd 10.100.0.0/16 172.30.4.0/22 any -P in ipsec esp/tunnel/27.0.1.144-172.30.4.11/require; |
…が、ダメッ… です。左側のSubnetのSrc/Dstになっている部分は、同じ条件で複数設定できず後者がエラーになるためです。そうなると当然、フェイルオーバーした時に許可していない方のトンネルを使おうとすると、通信ができなくなる、という単純な罠が待ち受けています。
この問題、AWS業界では2011年頃には既に知られていて、SPD の設定を動的に、もしくは複数設定できない、という理由からLinuxで冗長化するのは不可能だと結論が出ていて今なお変わらないようです。
対応案としては、2つ見つかりました。
完全にイケてねー状態です。
で、それぞれの対応案を工夫する形で外道式を考えてみることにしました。
片方しかspdaddできない問題を解決する
Private間用のsetkeyの設定を、initスクリプトが実行しない形で用意します。中身は、設定中のTunnelを消してもう片方のTunnelを追加するだけのものです。
/etc/ipsec-tools.d/20-vpc-private.standby1
1 2 3 4 5 6 7 |
# delete Tunnel2 spddelete 172.30.4.0/22 10.100.0.0/16 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.144/require; spddelete 10.100.0.0/16 172.30.4.0/22 any -P in ipsec esp/tunnel/27.0.1.144-172.30.4.11/require; # add Tunnel1 spdadd 172.30.4.0/22 10.100.0.0/16 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.16/require; spdadd 10.100.0.0/16 172.30.4.0/22 any -P in ipsec esp/tunnel/27.0.1.16-172.30.4.11/require; |
/etc/ipsec-tools.d/20-vpc-private.standby2
1 2 3 4 5 6 7 |
# delete Tunnel1 spddelete 172.30.4.0/22 10.100.0.0/16 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.16/require; spddelete 10.100.0.0/16 172.30.4.0/22 any -P in ipsec esp/tunnel/27.0.1.16-172.30.4.11/require; # add Tunnel2 spdadd 172.30.4.0/22 10.100.0.0/16 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.144/require; spdadd 10.100.0.0/16 172.30.4.0/22 any -P in ipsec esp/tunnel/27.0.1.144-172.30.4.11/require; |
そして各TunnelへのPingを監視しておいて、到達しなくなったら自動的に切り替えるという作戦です。
1 2 3 4 5 |
# Tunnel2 から 1 に切り替える時 setkey -f /etc/ipsec-tools.d/20-vpc-private.standby1 # Tunnel1 から 2 に切り替える時 setkey -f /etc/ipsec-tools.d/20-vpc-private.standby2 |
一見上手くいきそうだったのですが、問題が1つありました。
Customer => EC2 と EC2 => Customer の通信において、
Customer => EC2 は Quagga が、
EC2 => Customer は VPC VPN G/W が
どちらのTunnelを使うか決定しており、その組み合わせは4種類あることになります。
往復が同じTunnelの場合もあるし、異なる場合もありどちらも動きます。
これ、Quaggaの選択状況は把握できるのですが、VPC側の選択状況は取得できません。
Tunnelを2本とも利用できる状態において、setkeyとquaggaの設定を、VPC側が選択しているTunnelと同じにしなくてはいけないのですが、色々調べてみても唯一の切り替え判断は Client <=> EC2 の疎通ができているか、なので、それをVPNサーバで行うわけにもいきません。
何度も切り替えたり、iptablesで遮断してTunnelDownを偽装してみると全然思い通りに動かなく、setkey切り替え作戦は失敗に終わりました。
片系のTunnelしか利用せず丸ごと切り替える
経路に問題があるなら片方しか選べないようにすればえぇんや!と半ばキレ気味に、racoon, setkey, quagga(bgpd) の設定を2つ用意し、monit監視でTunnelダウン検知時に設定ファイルごと切り替えて再起動する、という荒技を行うことにしました。
こんな感じです。
1 2 3 4 5 6 |
# Now Tunnel1 Customer VPN (169.254.252.2) ==========> (169.254.252.1) VPC VPN G/W ^^^ ping check [monit] <if failed> # Standby Tunnel2 overwrite conf & restart Customer VPN (169.254.252.6) ----------> (169.254.252.5) VPC VPN G/W |
racoon
VPN1の場合はこの設定。VPN2はG/WとTunnelSegmentを変えるだけです。ここで注意すべきは、鍵ファイルは分けずに2件書いて共通して使う必要があるということです。
/etc/racoon/racoon.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
log notify; path pre_shared_key "/etc/racoon/aws-vpc.txt"; remote 27.0.1.16 { exchange_mode main; lifetime time 28800 seconds; initial_contact on; proposal { encryption_algorithm aes128; hash_algorithm sha1; authentication_method pre_shared_key; dh_group 2; } } sainfo address 169.254.252.2/30 any address 169.254.252.1/30 any { pfs_group 2; lifetime time 3600 seconds; encryption_algorithm aes128; authentication_algorithm hmac_sha1; compression_algorithm deflate; } |
ipsec-tools
Tunnel部分は共通設定となります。/etc/ipsec-tools.d/10-vpc-tunnels.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/usr/sbin/setkey -f flush; spdflush; # Tunnel1 spdadd 169.254.252.2/30 169.254.252.1/30 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.16/require; spdadd 169.254.252.1/30 169.254.252.2/30 any -P in ipsec esp/tunnel/27.0.1.16-172.30.4.11/require; spdadd 169.254.252.2/30 10.100.0.0/16 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.16/require; spdadd 10.100.0.0/16 169.254.252.2/30 any -P in ipsec esp/tunnel/27.0.1.16-172.30.4.11/require; # Tunnel2 spdadd 169.254.252.6/30 169.254.252.5/30 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.144/require; spdadd 169.254.252.5/30 169.254.252.6/30 any -P in ipsec esp/tunnel/27.0.1.144-172.30.4.11/require; spdadd 169.254.252.6/30 10.100.0.0/16 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.144/require; spdadd 10.100.0.0/16 169.254.252.6/30 any -P in ipsec esp/tunnel/27.0.1.144-172.30.4.11/require; |
PrivateSubnet間の設定は分けておきます。これはVPN1用です。
/etc/ipsec-tools.d/20-vpc-private.conf
1 2 3 4 5 |
#!/usr/sbin/setkey -f # VPN1 Between Local and VPC spdadd 172.30.4.0/22 10.100.0.0/16 any -P out ipsec esp/tunnel/172.30.4.11-27.0.1.16/require; spdadd 10.100.0.0/16 172.30.4.0/22 any -P in ipsec esp/tunnel/27.0.1.16-172.30.4.11/require; |
quagga
/etc/quagga/zebra.conf に変更はありません。BGPのVPN1用はこうです。VPN2は networkのTunnelSubnetと、neighborのTunnelAddressを変えるだけです。
/etc/quagga/bgpd.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
hostname gedowfather-debian7-01 password QuaggaPassword enable password QuaggaPassword ! log file /var/log/quagga/bgpd.log !debug bgp events !debug bgp zebra debug bgp updates ! router bgp 65000 bgp router-id 172.30.4.11 network 169.254.252.2/30 ! Routing for VPC to CUSTOMER (see Route Tables on VPC Console) ! if CustomerVPN forward using NAT, this is unnecessary. network 172.30.4.11/22 ! ! aws tunnel #2 neighbor neighbor 169.254.252.1 remote-as 10124 ! line vty |
monit
そして監視用にmonitを入れます。
1 |
apt-get install monit |
監視設定はヘルスチェックスクリプトと、Tunnel切り替えスクリプトを使います。
/etc/monit/conf.d/aws-vpc.cfg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
set daemon 10 with start delay 5 set mailserver localhost set alert root@localhost not on {INSTANCE} check program aws-vpc with path /usr/local/bin/aws-vpc-healthcheck.sh if status != 0 for 10 cycles then exec "/usr/local/bin/aws-vpc-switch.sh" every 3 cycles check process racoon with pidfile /var/run/racoon.pid start program = "/etc/init.d/racoon start" stop program = "/etc/init.d/racoon stop" check process quagga with pidfile /var/run/quagga/zebra.pid start program = "/etc/init.d/quagga start" stop program = "/etc/init.d/quagga stop" |
monit用ヘルスチェックスクリプト
現行片VPNの、VPC側のTunnelAddressにPingが飛ばない場合、エラーと判定します。/usr/local/bin/aws-vpc-healthcheck.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#!/bin/bash # # AWS VPC VPN healthcheck script. # # Error is that local can't ping to current vpc tunnel. # # Config T1_OUT_VPC_GW=27.0.1.16 T2_OUT_VPC_GW=27.0.1.144 T1_IN_VPC_ADDR=169.254.252.1 T2_IN_VPC_ADDR=169.254.252.5 # Check setkey OUT_VPN_NUMBER=0 SETKEY_ROUTE=`/usr/sbin/setkey -D -P | grep -m1 -A5 "172.30.4.0/22" | grep -m1 "esp/tunnel"` echo "$SETKEY_ROUTE" | grep $T1_OUT_VPC_GW > /dev/null 2>&1 [ $? -eq 0 ] && OUT_VPN_NUMBER=1 echo "$SETKEY_ROUTE" | grep $T2_OUT_VPC_GW > /dev/null 2>&1 [ $? -eq 0 ] && OUT_VPN_NUMBER=2 [ $OUT_VPN_NUMBER -eq 0 ] && echo -n "Not set private route yet." >& 2 && exit 1 # Check ping IN_VPC_ADDR=`eval echo '$T'$OUT_VPN_NUMBER'_IN_VPC_ADDR'` ping -c 1 -w 1 $IN_VPC_ADDR > /dev/null 2>&1 [ $? -eq 0 ] && exit 0 # error echo -n "VPN $OUT_VPN_NUMBER tunnel is disconnected." >& 2 exit 1 |
monit用Tunnel切り替えスクリプト
実行すると、現行片VPNではないもう1つのVPNの設定に切り替えてrestartします。/usr/local/bin/aws-vpc-switch.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#!/bin/bash # # Config IPSEC_PRIVATE_CONF="/etc/ipsec-tools.d/20-vpc-private.conf" VPN1_DIR="/usr/local/etc/aws-vpc/vpn1/" VPN2_DIR="/usr/local/etc/aws-vpc/vpn2/" CONF1=$VPN1_DIR"20-vpc-private.conf" CONF2=$VPN2_DIR"20-vpc-private.conf" # Check current conf VPN_NUMBER=1 if [ -r "$IPSEC_PRIVATE_CONF" ];then diff $IPSEC_PRIVATE_CONF $CONF1 > /dev/null 2>&1 [ $? -eq 0 ] && VPN_NUMBER=2 fi # Overwrite CONF_DIR=`eval echo '$VPN'$VPN_NUMBER'_DIR'` cp $CONF_DIR'racoon.conf' /etc/racoon/ cp $CONF_DIR'20-vpc-private.conf' /etc/ipsec-tools.d/ cp $CONF_DIR'bgpd.conf' /etc/quagga/ # Restart Services service setkey restart service racoon restart service quagga restart service monit restart exit 0 |
スクリプトを見たらわかりますが、VPN1,2それぞれの上書き用設定は、ここでは /usr/local/etc/aws-vpc/vpn1, 2 に置いておき、切替時に本来のパスへ cp しているだけになります。
用意できたら、1回切り替えスクリプトを実行して、monit を起動しておけば、ひたすら片肺飛行を繰り返してくれる、なかなかグロい構成になっています。
1 2 |
/usr/local/bin/aws-vpc-switch.sh service monit restart |
問題点
この設定には1つ問題があります。本来すべき2Tunnelの設定をしていないせいか、切替時になかなかracoonがTunnel確立をできず、長いと1分以上かかる場合もあります。それゆえに、監視間隔時間の調整が大切になります。
そしてこの切替時間は、非NAT化のために犠牲にできる長さなのか、という話になります。それについては用途の問題で、仮にSSH接続だけなら問題ない範囲と捉えますし、重要なレプリケーションなどをするならNAT型にする、というかおとなしくハードウェアにした方がよいと思います。
完全切替型の構築スクリプト
お世辞にも綺麗な構成とは言えませんが、それでも構築スクリプトを置いておきます。せっかく調べたから書きましたが、いやー汚いですねコレ。
もしかしたら microインスタンスでOpenVPNの方がマシかもしれません。
相変わらず自分のネットワーク系のセンスの無さというか、撤退や妥協が下手というか、身にしみた案件でした。