swanctl(strongswan)で作る自分用IPSec VPN

フリーWi-Fi下でのトンネル用途と、屋外でも自宅サーバに接続することを目的にIPSecサーバを構築した。
swanctlでの構築文献が少なかったので記録として残す。

この記事について

strongswanはかつてのパッケージだと ipsec コマンドや ipsec.conf による構成が主流だった。
本記事ではそれらを用いず、最新の swanctlswanctl.conf による構成を目指す。

当時 ipsec 時代の構築にあたっては下記リンクを参考にしていた。
https://www.digitalocean.com/community/tutorials/how-to-set-up-an-ikev2-vpn-server-with-strongswan-on-ubuntu-20-04

マイグレーションにあたっては下記公式ドキュメントを参考にした。
https://wiki.strongswan.org/projects/strongswan/wiki/Fromipsecconf

本記事の手順はコマンドベースで説明しているが、実環境では最終的にansibleを用いて構築した。
そのため若干の差異や誤りを含む可能性がある。

構成

下図のようにグローバルにあるVPSでVPNの通信を受け付け、自宅のVPNサーバ、屋外の移動端末からそれぞれVPSに向けて通信を確立する。
移動端末からグローバルに出る際にトンネリングしたり(赤矢印)、移動端末から自宅内のサーバにアクセスしたり(青矢印)することを想定した構成である。

VPNはIKEv2のIPSecを使用し、L2TPは用いずL3トンネリングで接続する。

ちなみに拠点間VPNのことをSite-to-Site VPNと呼ぶのは有名だが、移動端末と拠点を結ぶ(図の左側の)VPNは「Roadwarrior」と呼ぶらしい。
本記事のRoadwarrior VPNは、クライアント証明書(TLS)方式と、ID+password(EAP)方式の両方を受けられるように設定する。

IPアドレス

IPアドレスは下記の前提とする。実際の構築の際は適宜差し替える。

  • VPSグローバルアドレス:203.0.113.1
  • 自宅NWアドレス:192.168.1.0/24
    • ゲートウェイ:192.168.1.1
    • VPNサーバ:192.168.1.2
  • Roadwarriorクライアントアドレス帯:192.168.2.0/24
    • EAP接続クライアント:192.168.2.0/25
    • TLS接続クライアント:192.168.2.128/25

自宅NWについて

私の自宅のONU/HGWの自由度がかなり低く、

  • グローバル向けデフォゲ以外のルーティング設定ができない
  • ONUのみの動作モードはできない(ブリッジにしてルーティングを他機器に移譲することができない)
  • 当然IPSecもしゃべれない
  • MAP-E方式のため、グローバルアドレスが変化する可能性がある

という状態だったので、自宅内NWのleafにあたる機器(192.168.1.2/24)をNAT状態でVPNサーバにした。
この構成による課題は下記になる。

  • グローバルサーバから自宅内サーバ方向に接続することはできないため、VPN確立は常に自宅サーバから始動する必要がある(厳密なSite-to-Site VPNではない)
    • 本記事ではどちらのサーバを再起動しても自動でVPNが復旧するよう、自律再接続ができることを目標に構築した
  • グローバルから自宅に接続する際、1段階余計にNAPTを嚙ませる必要がある
    • 自宅NW(192.168.1.0/24)のデフォゲはHGW(192.168.1.1)に向いており、VPNセグメント(192.168.2.0/24)の通信だけVPNサーバ(192.168.1.2)に向けるのは骨が折れる
    • そのため、192.168.1.2がVPN通信を終端した後、192.168.1.2から同セグ内通信をするようにふるまうNAPTを実装する必要がある
  • (IKEv2はNAT処理を良しなにしてくれるので、パススルーの考慮は不要だった)

機器バージョン

  • VPS
    • Ubuntu Server 22.04→24.04
  • 自宅VPNサーバ
    • Ubuntu Server 22.04→24.04
  • 移動端末
    • Ubuntu Desktop 24.04
    • iOS 17→18

CA、サーバ証明書の作成

ipsec 時代と変わらないため、本記事では割愛する。
下記記事のStep2, Step3を参照されたい。
https://www.digitalocean.com/community/tutorials/how-to-set-up-an-ikev2-vpn-server-with-strongswan-on-ubuntu-20-04

本記事では以下がすでに出来上がっているものとする。

  • CA証明書 ca-cert.pem
  • VPSのサーバ証明書 server-cert.pem
  • VPSのサーバ秘密鍵 server-key.pem
  • 自宅のサーバ証明書 server-cert.pem
  • 自宅のサーバ秘密鍵 server-key.pem

Ubuntuクライアントで使用可能なTLS接続用のクライアント証明書・秘密鍵もサーバ証明書と同じ要領で作成できる。
iOS対応の証明書作成(もしくはバンドル)については実施していないため記載しない。

グローバルサーバ(VPS)の設定

パッケージインストール

$ sudo apt install charon-systemd strongswan-swanctl

証明書等の配置

作成した証明書等を下記パスに配置する。
VPSにはVPSの証明書だけ設置すればよく、自宅サーバの証明書は不要である。

ファイル配置パス
CA証明書/etc/swanctl/x509ca/ca-cert.pem
サーバ証明書/etc/swanctl/x509/server-cert.pem
サーバ秘密鍵/etc/swanctl/private/server-key.pem

カーネルパラメータの設定

$ sudo vim /etc/sysctl.conf
# 以下を追記
net.ipv4.ip_forward=1
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.all.send_redirects=0
net.ipv4.ip_no_pmtu_disc=0

$ sudo sysctl -p

ufwの許可ルール設定

$ sudo ufw allow 500,4500/udp

XFRMインタフェースの作成

VPNサーバ以外の機器でルーティングをしっかり設定している場合は不要だが、今回の構成のようにVPNサーバ自身がルーティング処理をする場合には専用の論理インタフェースを作成する必要がある。
かつてはVTIというインタフェースが用いられていたようだが、現在のLinuxではXFRMというインタフェースを用いる。
詳細は下記を参照されたい。
https://docs.strongswan.org/docs/latest/features/routeBasedVpn.html

本構成では下記のように設定する。
I/F IDは、後ほどswanctlのコンフィグと紐づけるときに用いる。
(名前とI/F IDの数字ずれは、特に他意があるわけではない)
XFRMインタフェースそのものにIPアドレスのアサインは要らない。

  • xfrm0 (I/F ID:1) EAP接続クライアント用プール(192.168.2.0/25)
  • xfrm1 (I/F ID:2) TLS接続クライアント用プール(192.168.2.128/25)
  • xfrm2 (I/F ID:3) 自宅ネットワーク(192.168.1.0/24)

まずはxfrmインタフェース作成スクリプトを作成する
/usr/local/sbin/setup-xfrm.sh

#!/bin/bash
ip link add xfrm0 type xfrm if_id 1
ip link set xfrm0 up
ip route add 192.168.2.0/25 dev xfrm0

ip link add xfrm1 type xfrm if_id 2
ip link set xfrm1 up
ip route add 192.168.2.128/25 dev xfrm1

ip link add xfrm2 type xfrm if_id 3
ip link set xfrm2 up
ip route add 192.168.1.0/24 dev xfrm2
$ chmod +x /usr/local/sbin/setup-xfrm.sh

次に上記スクリプトをOS起動時にキックするよう、systemdに登録する
/etc/systemd/system/xfrm-setup.service

[Unit]
Description=Setup xfrm interface
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/setup-xfrm.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now xfrm-setup.service

iptablesの編集

Ubuntu Serverでは、ufwの設定ファイルからiptables設定を更新する。
コンフィグ内の eth0 は、マシンによってグローバルアドレスが割り振られているインタフェース名が異なるため、適宜差し替える。
/etc/ufw/before.rules

# 初期状態だとnatテーブルの行はないため新設する
*nat
-A POSTROUTING -s 192.168.2.0/24 -o eth0 -m policy --pol ipsec --dir out -j ACCEPT
# VPNクライアントからの通信は、VPNサーバからの通信としてNAPTしてフォワードする
-A POSTROUTING -s 192.168.2.0/24 -o eth0 -j MASQUERADE
COMMIT

# 初期状態だとmangleテーブルの行はないため新設する
*mangle
-A FORWARD --match policy --pol ipsec --dir in -s 192.168.2.0/24 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
COMMIT
# filterテーブルは初期ルールが書いてある。下記のルールに続き最後の2行を追記する
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
:ufw-not-local - [0:0]
# End required lines

# ここから下を追加
-A ufw-before-forward --match policy --pol ipsec --dir in --proto esp -s 192.168.2.0/24 -j ACCEPT
-A ufw-before-forward --match policy --pol ipsec --dir out --proto esp -d 192.168.2.0/24 -j ACCEPT

上記は ufw を再起動しないと反映されない。 sudo ufw disable && sudo ufw enable してもいいが、マシンごと再起動するのが確実。

swanctlコンフィグの作成

一部テンプレート表記 {{ var }} を用いている。同部分は書き換えが必須となる。
/etc/swanctl/conf.d/roadwarrior.conf

connections {
    rw-eap {
        local {
            # サーバ認証は公開鍵認証
            auth = pubkey
            certs = server-cert.pem
            id = {{ vpn_id }} # サーバ証明書のCNに設定したドメイン/IPアドレス
        }
        remote {
            auth = eap-mschapv2
            eap_id = %any
        }
        children {
            rw-eap {
                dpd_action = restart
                if_id_out = 1 # xfrm0のI/F ID
                if_id_in = 1 # xfrm0のI/F ID
                life_time = 0
                local_ts = 0.0.0.0/0
                remote_ts = 0.0.0.0/0
            }
        }
        dpd_delay = 60s
        encap = yes
        pools = pool-rw-eap
        rekey_time = 0
        send_cert = always
        send_certreq = no
        version = 2
    }
    rw-tls {
        local {
            auth = pubkey
            certs = server-cert.pem
            id = {{ vpn_id }} # サーバ証明書のCNに設定したドメイン/IPアドレス
        }
        remote {
            auth = pubkey
            cacerts = ca-cert.pem # クライアント証明書に署名したCA証明書
        }
        children {
            rw-tls {
                dpd_action = restart
                if_id_out = 2 # xfrm1のI/F ID
                if_id_in = 2 # xfrm1のI/F ID
                life_time = 0
                local_ts = 0.0.0.0/0
                remote_ts = 0.0.0.0/0
            }
        }
        dpd_delay = 60s
        encap = yes
        pools = pool-rw-tls
        rekey_time = 0
        send_cert = always
        send_certreq = no
        version = 2
    }
}

pools {
    pool-rw-eap {
            addrs = 192.168.2.0/25
            dns = {{ dns_ip }} # 自宅のDNSでも、8.8.8.8でもいい
    }
    pool-rw-tls {
            addrs = 192.168.2.128/25
            dns = {{ dns_ip }} # 自宅のDNSでも、8.8.8.8でもいい
    }
}

secrets {
    eap-{{ name }} { # eapユーザの分だけ作る
        id = {{ name }} # ユーザ名
        secret = {{ secret }} # パスワード
    }
}

/etc/swanctl/conf.d/home.conf

connections {
    home {
        local {
            auth = pubkey
            certs = server-cert.pem
            id = {{ vpn_id }} # サーバ証明書のCNに設定したドメイン/IPアドレス
        }
        remote {
            id = {{ vpn_home_id }} # 自宅サーバ証明書のCNに設定したドメイン/IPアドレス
            cacerts = ca-cert.pem

        }
        children {
            home {
                if_id_out = 3 # xfrm2のI/F ID
                if_id_in = 3 # xfrm2のI/F ID
                local_ts = 192.168.2.0/24 # 自宅への接続を許可するIP
                remote_ts = 192.168.1.0/24 # 自宅のIPセグメント
                start_action = trap
            }
        }
        dpd_delay = 60s
        encap = yes
        rekey_time = 0
        send_cert = always
        version = 2
    }
}

secrets {
        rsa {
            file = server-key.pem # サーバ秘密鍵。homeの方に書かなければいけないわけではないが、どこかに書いておく必要がある。
        }
}

最後にstrongswanを起動する。
swanctlをインストールしていると、 strongswan.servicestrongswan-swanctl.service のエイリアスになっているため、省略名で起動できる。

$ sudo systemctl enable --now strongswan
# すでに起動している場合はreloadをする
$ sudo systemctl reload strongswan

自宅VPNサーバの設定

パッケージインストールカーネルパラメータの設定 までは、VPSサーバの設定と同じ。
証明書等の配置に関して、今回配置するのはVPSのサーバ証明書ではなく自宅サーバの証明書・秘密鍵であることに注意。

iptablesの編集

グローバルアドレス(203.0.113.1/32)は適宜グローバルサーバの実IPに書き換える

*nat
-A POSTROUTING -s 203.0.113.1/32 -o eth0  -m policy --pol ipsec --dir out -j ACCEPT
-A POSTROUTING -s 203.0.113.1/32 -o eth0  -j MASQUERADE

# 自宅サーバはVPNルータではないため、VPNサブネットから自宅サブネット内へ接続するときにNAPTをする必要がある(これをしないと戻りパケットが迷子になる)。
-A POSTROUTING -s 192.168.2.0/24 -d 192.168.1.0/24 -j MASQUERADE
COMMIT

*mangle
-A FORWARD --match policy --pol ipsec --dir in -s 203.0.113.1/32 -o eth0 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
COMMIT
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
:ufw-not-local - [0:0]
# End required lines

# ここから下を追加
-A ufw-before-forward --match policy --pol ipsec --dir in --proto esp -s 203.0.113.1/32 -j ACCEPT
-A ufw-before-forward --match policy --pol ipsec --dir out --proto esp -d 203.0.113.1/32 -j ACCEPT

-A FORWARD -s 192.168.2.0/24 -d 192.168.1.0/24 -j ACCEPT
# 下記はVPNセグメントへの戻りパケットのみを許可している。state設定が不要であれば上記行と同様に全ACCEPTをすることも可能(ただしルーティングは別途要検討)。
-A FORWARD -s 192.168.1.0/24 -d 192.168.2.0/24 -m state --state ESTABLISHED,RELATED -j ACCEPT

こちらも同様に ufw 再起動か、マシン再起動をする。

strongswanコンフィグの作成

/etc/swanctl/conf.d/tunnel.conf

connections {
    tunnel {
        local {
            auth = pubkey
            certs = server-cert.pem
            id = {{ vpn_home_id }} # 自宅サーバ証明書のCNに設定したドメイン/IPアドレス
        }
        remote {
            cacerts = ca-cert.pem
            id = {{ vpn_id }} # VPSサーバ証明書のCNに設定したドメイン/IPアドレス
        }
        children {
            tunnel {
                # 常に自宅サーバから接続を開始しなければいけないため、接続が切れた際に再接続するよう設定する
                dpd_action = start
                local_ts = 192.168.1.0/24
                remote_ts = 192.168.2.0/24
                start_action = start
                close_action = restart
            }
        }
        dpd_delay = 60s
        dpd_timeout = 120s
        encap = yes
        keyingtries = 0
        mobike = yes
        reauth_time = 10800s
        rekey_time = 0
        remote_addrs = 203.0.113.1 # サーバIP。FQDNでもOK
        send_cert = always
        version = 2
    }
}

secrets {
        rsa {
            file = server-key.pem # 自宅サーバ秘密鍵
        }
}

Systemdのoverrideコンフィグ作成

上記swanctlコンフィグでうまく接続してくれそうだが、マシンのブート時だけうまく接続されない事象を確認している。
これを解消するため、マシンブート時だけは強制的に swanctl コマンドから接続を確立するよう、systemdのユニットを作成した。
以下は strongswan-swanctl.service 定義のオーバーライドとして機能する。
/etc/systemd/system/strongswan-swanctl.service.d/override.conf

[Unit]
After=network-online.target
Wants=network-online.target

[Service]
ExecStartPost=/usr/sbin/swanctl --load-all --noprompt
ExecStartPost=/usr/sbin/swanctl --initiate --child tunnel # connection名の分だけ作成
Restart=on-failure
RestartSec=10

最後にstrongswanを起動する。

$ sudo systemctl enable --now strongswan
# すでに起動している場合はreloadをする
$ sudo systemctl reload strongswan

接続確認

下記のような出力を得られたら無事VPN確立が完了している。

$ sudo swanctl -l
tunnel: #24, ESTABLISHED, IKEv2, 2f99f99e927c7ac4_i* b9e8082623a716bd_r
  local  '<censor>' @ 192.168.1.2[4500]
  remote '<censor>' @ 203.0.113.1[4500]
  AES_CBC-128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256
  established 5781s ago, reauth in 4132s
  tunnel: #84, reqid 1, INSTALLED, TUNNEL-in-UDP, ESP:AES_GCM_16-128
    installed 2509s ago, rekeying in 852s, expires in 1451s
    in  c5f339f1,    712 bytes,    11 packets,  1900s ago
    out c0a7d9c0,   1842 bytes,    11 packets,  1900s ago
    local  192.168.1.0/24
    remote 192.168.2.0/24

移動端末の設定

iOS

iOSでのTLS接続は試していないため、EAP接続の前提とする。

  1. CA証明書をインストール
    • HTTPサーバを立てたり、メール添付などで転送したものを開く
    • iOSにインストールする場合は、証明書拡張子を.crtにするとよい
  2. 設定 > VPNとデバイス管理 > VPN > VPN構成を追加
  3. 下記の通り設定する
    • タイプ:IKEv2
    • 説明:任意
    • サーバ:グローバルサーバのFQDNかIPアドレス
    • リモートID:VPSサーバ証明書のCNに設定したドメイン/IPアドレス
    • ユーザ名・パスワード: roadwarrior.confsecrets に記載したもの

Ubuntu

EAP / TLS両方で接続を確認した。

  1. CA証明書をダウンロード
    • ファイルシステムのどこかに置いていればよい
  2. sudo apt install network-manager-strongswan
  3. 設定 > ネットワーク > VPN > 「+」 > IPSec/IKEv2(strongswan)
  4. 下記の通り設定する
    • 名前:任意
    • Address:グローバルサーバのFQDNかIPアドレス
    • Certrificate:CA証明書
    • Identity:サーバ証明書のCNに設定したドメイン/IPアドレス
    • Authentication:TLSなら「Certificate」、EAPなら「EAP」
    • TLSの場合:
      • Certificate:Certificate/private key
      • Certificate file:クライアント証明書(同じCAによって署名されたもの)
      • Private key:上記に対応するクライアント秘密鍵
    • EAPの場合:
      • Username・Password:roadwarrior.confsecrets に記載したもの
    • Optionsのうち以下を有効化
      • Request an inner IP address
      • Enforce UDP encapsulation

はまりポイント

strongswanのPoolは重複したIPを払い出すことがある

今回の設定で構築したVPNでは、同じ認証情報(証明書・EAP ID)を用いて複数機器から接続すると、同じPool IPが払い出されてしまい、通信が重複する。
1機器1IDを徹底するか、同じIDを割り当てた機器が同時に接続しないよう気を付ける必要がある。

また複数のconnectionで同じpoolを割り当ててしまうと、こちらもconnectionをまたいで同じIPアドレスが払い出される(こちらは独立したpoolを割り当てるようにコンフィグ設定した)。

特定条件下でMTU制約に引っ掛かり通信できなくなる

今回確認したのは下記事象である。

  • Ubuntu Desktopを使用(インタフェースMTUは1500)
  • あるフリーWi-Fi下からVPN接続
  • HTTPS、SSHといったパケット長が長いIPパケットだけ通らなくなる

切り分けたところ、当該Wi-FiネットワークのMTUが厳しく設定されており、VPNを使用する場合は端末のインタフェースMTUを下げなければいけないことが分かった。
実際に、下記コマンドを打った場合はpingが通ったが、MTUを1267以上にすると通らなかった。

$ ping -c 1 -M do -s 1266 8.8.8.8

対策として、NetworkManagerの設定をいじり、当該SSID接続時のMTUを調整した。
下記コマンドにより、普段はインタフェース標準のMTU(1500)が用いられるが、特定SSIDに接続した場合に限りMTUが上書きされる。

# 現在の接続プロファイルを確認
$ nmcli connection show

# 該当するWi-Fiプロファイルに対してMTUを設定
$ sudo nmcli connection modify "Wi-FiのSSID" 802-11-wireless.mtu 1266

# 設定を反映するために接続を再接続
$ sudo nmcli connection down "Wi-FiのSSID"
$ sudo nmcli connection up "Wi-FiのSSID"

コメント

タイトルとURLをコピーしました