FreeBSD13とかにしたら、strongswanの設定がswanctlになり、色々変わったので
FreeBSD13でIPsecリモートアクセスサーバに設定例の備忘録書きました。
ふとFreeBSD 11-STABLEのGENERIC kernelを使ってみたらIPSEC_NAT_Tを有効にしていないのにNAT-Tが利用できたので調べてみたところ、2017/03/18に
IPSECが随分と手直しされてました。現在のkernelがこの変更を反映したものかどうか確かめるには、以下のkernel statが定義されているかどうか見れば良さそうです。
$ sysctl kern.features.ipsec_natt
kern.features.ipsec_natt: 1
この値が得られるkernelであれば、上記手直しが反映済みなので、下記kernelの再構築は必要ありません。そのままpackage準備に進んでください。
FreeBSD 11.0-RELEASEのGENERIC kernelでは標準で
IPSECが有効になりました。さて、これでIPsecの設定が簡単になると思ってみたら、いろいろあったのでメモ代わり。
IPsecを利用したVPNではL2TP/IPsecがよく利用されていますが、どうせ端末をIP的に繋ぐだけなら素のIPsecだけで利用した方がMTU的に効率が良さそうなので、L2TPは使わない構成にしています。鍵交換を自動化するためにIKEの実装を探したところ、OpenIKEDは現在FreeBSDにポーティングされていなかったため、代わりにstrongswanを使うことにしました。これでいわゆるダイヤルアップVPNを構築します。
ひとまずここでは基本的な接続の設定までを紹介するので、あとは自分で証明書認証にするとか、お気に入りのcipher suiteを指定するとかしてください。
FreeBSD11.0-RELEASEではIPSECが標準的にサポートされたため、ESPなど標準的なIPsecパケットをやり取りする分には特に特段の設定なくできます。ただ、ESPはNATと仲が悪いのでNAT背後のクライアントからIPsecダイヤルアップサーバに接続する際には、ESPパケットをUDPで包んでNATを越えて透過的に通信する方式が取られます。これをNAT traversal(NAT-T)やUDP_ENCAPと呼びます。なんとFreeBSD11.0-RELEASEでは、IPSECは有効になったものの、NAT-Tを利用するのに必要なIPSEC_NAT_Tがkernelで有効になっていません。クライアントがNATの背後から接続する可能性がある場合には、FreeBSD11.0-RELEASEでもこれまでと同様にkernelを作る必要があります。11-STABLEには随分と手直しが入っているため、そのうちこの手順は必要なくなるはず。
なお、NAT-Tが有効になっていない状態でNAT背後のクライアントから接続を受けると以下のようなログが見えます。まずはstrongswanの起動時に
charon: 00[KNL] unable to set UDP_ENCAP: Invalid argument
charon: 00[NET] enabling UDP decapsulation for IPv6 on port 4500 failed
charon: 00[KNL] unable to set UDP_ENCAP: Invalid argument
charon: 00[NET] enabling UDP decapsulation for IPv4 on port 4500 failed
と言われ、実際にNAT背後のクラアントが接続しに来た際には自動的にNATの検出が行われ、
charon: 15[IKE] remote host is behind NAT
鍵交換などはそのまま進む(IKEはUDPで包まれているため通信できる)ものの、IPsec SAをkernelに登録しようとするところでUDP_ENCAPが利用できないため失敗して、セッションが切断されます。残念。
charon: 15[KNL] error sending to PF_KEY socket: Invalid argument
charon: 15[KNL] unable to add SAD entry with SPI c70b0bb1
charon: 15[KNL] error sending to PF_KEY socket: Invalid argument
charon: 15[KNL] unable to add SAD entry with SPI 00f36cbd
charon: 15[IKE] unable to install inbound and outbound IPsec SA (SAD) in kernel
charon: 15[IKE] failed to establish CHILD_SA, keeping IKE_SA
そんなわけで、クラアントがNAT背後にいる可能性がある場合には、NAT_Tに対応したkernelを作りたいわけです。まずはkernel周りのsourceを持ってくる必要があります。src.tgzを手動で展開しても良いのですが、その後の修正も取り込もうとすると、
Appendix A. Obtaining FreeBSD - A.3. Using Subversionにある通り、svnを使うのが王道のようです。HTTPS経由でcheckoutする場合はTLS鍵の検証が自動的にできるようにroot CAを取り込んでおき、subversionもインストールしておきます。
# pkg install ca_root_nss
# pkg install subversion
準備が整ったところで、sourceを持ってきます。以下、/usr/src 以下が上書きされるので、そこに何か置いている人はご注意。
# svn checkout https://svn.freebsd.org/base/stable/11 /usr/src
# svn up /usr/src
僕はamd64 kernelを使っているので、設定ファイルは/usr/src/sys/amd64/conf に作ります。と言っても、FreeBSD11-STABLEではGENERIC kernelがNAT-Tに対応したので、そのままkernelを作れば大丈夫です。makeも問題無さそうだったので置き換え後、再起動しました。
# cd /usr/src
# make buildkernel
# make installkernel
# reboot
IKE実装としてstrongswanを導入します。これだけ。
# pkg install strongswan
ひとまず一番簡単な事前共有鍵認証(PSK)で。strongswanの設定はipsec.confと事前共有鍵を書いておくipsec.secretsだけです。設定ファイルは全て/usr/local/etcに置きます。同ディレクトリにサンプル設定もあります。以下の例ではクライアントとIPv6のユニキャスト空間(2000::/3)は全てIPsecトンネルを通過させる設定になっています。leftがサーバ側、rightがクライアント側です。
クライアントに割り当てるアドレス空間は/64を専用に割り当て、上流のルータからこのサーバに向けて静的にルーティングしています。サーバは上流のルータから受け取ったパケットがIPsec SAに合致した場合はESPに包んでフォワードし、それ以外の場合は破棄します。つまり、このサーバはIPv6のルータとして動作することになります。
# /usr/local/etc/ipsec.conf
config setup
conn vpn
leftid = <サーバのホスト名>
left = <サーバのホスト名>
leftauth = psk
leftsubnet = 2000::/3
lefthostaccess=yes
right = %any
rightauth = psk
rightsourceip = <クライアントに割り当てるアドレス空間>
auto = add
事前共有鍵は以下の書式で/usr/local/etc/ipsec.secretsに記載しておきます。
# /usr/local/etc/ipsec.secrets
: PSK "<適当な共通鍵>"
ルータ設定などは/etc/rc.conf。ルーティングループを起こさないようにdiscard経路を設定しています。FreeBSDではdisc0インタフェースを作り、そこに経路を向ければ良いみたい。
# /etc/rc.conf
ipv6_gateway_enable="YES"
cloned_interfaces="disc0"
ipv6_static_routes="discard"
ipv6_route_discard="<クライアントに割り当てるアドレス空間> -iface disc0"
strongswan_enable="YES"
FreeBSDがクライアントの場合は、ipsec.confでauto=startにすれば能動的にセッションを張りにいきます。
Mac OSXの場合は、新しいネットワークの追加でインタフェースを「VPN」、VPNタイプを「IKEv2」、サービス名は自分の識別用に適当に。その後、サーバアドレスを「サーバのホスト名」、リモートIDも上記のipsec.confで設定したように「サーバのホスト名」、ローカルIDは自分の識別用に適当に。認証設定は「なし」にした後、共有シークレットに上記のipsec.secretsで設定した「適当な共通鍵」を入力して完了。「適用」後接続で完了。OSXではipsec0インタフェースが作られるため、ここをtcpdumpすると暗号化前&復号化後のパケットが見れます。
サーバ側ではセッションが確立しても特に新たなインタフェースは見えず、経路情報にも変化がありません。以下のipsec関連のコマンドでSAが意図した状態になっているか確認しましょう。
# ipsec status
# setkey -D