さくらの VPS (strongSwan) と拠点ネットワーク (RTX810) を IPSec でつなぐ　～swanctl と IPSec VTI 使用版～

# 1. 概要
Linux VPS (Virtual Private Server) と拠点ネットワークのルータ (YAMAHA RTX810) の間で IPSec トンネルによる拠点間 VPN を張ることで、インターネットに接続されたアクセス用端末から拠点ネットワーク内ホストに安全に接続したり、VPS への https アクセスを拠点ネットワーク内の http サーバに転送して代理応答させる構成を作ったので、やり方をまとめる。


![図1 : 概略](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216085/631efd58-111c-4c32-e2de-2b9e79daea6d.png)


なお、文中や図中で {{変数名やファイル名}} で示す部分は、各環境に応じて変数の中身やファイルの中身にて置き換えて読んでいただきたい。

# 2. 背景
- 拠点プライベートネットワーク内のホストにインターネットから安全にアクセスしたり、https でのサイト公開を行いたい。
- しかし、拠点のインターネット接続が、IPv4 接続性が動的 IP ベース（PPPoE）であったり、通信事業者側の NAT ベース（MAP-E や DS-Lite など）であった場合、直接任意ポートを開放し、外部から安定的に接続することには困難がある。
- そこで、固定 IP を持つ Linux VPS を使用して拠点ルータとの間に IPSec トンネルを張り、VPS を拠点プライベートネットワークの張り出し点として機能させることで、Linux VPS へのアクセスを適切に拠点プライベートネットワークに転送する構成としたい。
- Linux 上で動く IPSec の実装として strongSwan があるが、近年コンフィグの形式が新しい方式 (swanctl) へ移行しつつあるため、新たな方式での設定を行いたい。
- VPS 上のアプリケーション（例：nginx）からのアクセスを拠点プライベートネットワークに転送するには、トンネルインタフェースが明示的に存在し IP が付いている方が利便性が高い。よって、デフォルトのポリシーベースルーティング自動投入ではなく、IPSec VTI (Virtual Tunnel Interface) による仮想トンネルインタフェースの明示的な設置を行う。
- これら「strongSwan と YAMAHA ルータの対向」「swanctl による設定」「IPSec VTI の使用」の組み合わせを解説する記事が少ないと考えるため、自分用の環境構築メモをかねて記載することとした。

# 3. 目標とする状態

![図2. 構成図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216085/b8db1e97-aa4d-6066-d209-fb3e5fb8d1d7.png)

- Linux VPS（図の上部）に IPv4、IPv6 ともに固定 IP (それぞれ Global IPv4 A, Global IPv6 A とする) を割り当てる。（※ 筆者の環境では「さくらの VPS 512MB プラン」を使用した。）
- 拠点ルータ（図の右下）はインターネット接続に際してそれぞれ、「IPv4 : PPPoE による動的 IP 取得 (Global IPv4 B) 」、「IPv6 : IPv6 RA による半固定の /64 IP 取得（Global IPv6 B）」を用いる。（※ 筆者の環境では YAMAHA RTX810 を NTT 東日本フレッツ光ネクスト ONU に接続して使用しており、この状況となっている）
- 拠点ルータ配下のプライベートネットワーク（192.168.20.0/24）に拠点のサーバ群が収容されており、ssh や http などで待ち受けをしているものとする。（例：図右下の拠点サーバ）
- 拠点ネットワークにインターネット経由で安全に接続したい端末（図の左下「VPN 端末」）は、Wireguard を用いて Linux VPS に接続し、Wireguard 接続者用に確保された VPS 内プライベート IP （図中では 192.168.10.101/24 ）を得る。
- 外部から Linux VPS の https サーバにアクセスしてきた端末（図の下部「通常端末」）のリクエストは VPS の nginx で https 終端されたのち、拠点ネットワーク内のサーバ（192.168.20.11/24）に http でリバースプロキシされる。
- VPS と拠点ルータの間に IPv6 IPSec トンネルを張り、その両端にはトンネル用の IPv4 /30 IP を付与する。
- VPS には 192.168.20.0/24 (拠点内プライベート IP) 向かいのパケットが 10.0.0.2/30 へトンネル経由でルーティングされるように、拠点ルータには 192.168.10.0/24 (VPS 内プライベート IP) 向かいのパケットが 10.0.0.1/30 へトンネル経由でルーティングされるようにする。
- これらによって、Wireguard 接続者の IP (192.168.10.101/24) や VPS の nginx (10.0.0.1/24) から拠点ネットワーク内サーバ (192.168.20.11/24) へ通信が行えるようにする。

# 4. 作業

## 4.1 拠点ルータ側の作業

### 4.1.1 IPv4 インターネット接続の設定

下記リンク等を参考に IPv4 インターネット接続をセットアップする。

https://network.yamaha.com/setting/router_firewall/internet/internet_connect/opticalfiber_connection

この結果、拠点ネットワーク内装置はプライベート IPv4 アドレスを持ち、ルータが持つグローバル IPv4 アドレスを NAPT で共有してインターネット接続を行う構成となった。

### 4.1.2 IPv6 インターネット接続の設定

下記リンク等を参考に IPv6 インターネット接続をセットアップする。

https://network.yamaha.com/setting/router_firewall/flets/flets_other_service/ipv6_ipoe

この結果、拠点ネットワーク内装置は IPv4 に加え、ルータが中継した IPv6 RA を元に IPv6 アドレスを各々得て通信する構成となった。

※ 当システムでは IPv6 RA で網から配信される /64 アドレスが半固定（事業者側の変更がない限り同じ IP の割り当てが維持される）であることを期待した動作を行わせているが、一般的に何ら固定割り当ての保証がないものであることに留意されたい。

### 4.1.3 設定コンソール

IPSec 関連の高度な設定はコンソール経由で行う想定のため、必要なコンソール設定を投入する。一般的にはプライベートネットワーク側（LAN1）に限り ssh によるアクセスを許可する設定の利便性が高いと思われる。（参考：https://tkx.hatenablog.jp/entry/20140222/p1 ）

筆者が使用している設定を記載する。

```
administrator

console character ascii
console columns 200
console lines infinity
sshd host lan1

save
```

### 4.1.4 拠点ルータの IPSec 設定

下記設定を環境に合わせてパラメータ調整し投入する。
コマンドの大まかな意味についてはコメントに記載した。

```
administrator

# 使用する tunnel インターフェース番号（他に tunnel インターフェースが定義されている場合は適宜変更）
tunnel select 1
# tunnel インターフェースで利用する IPsec 設定の番号の定義（101 が使用済であれば適宜変更）
ipsec tunnel 101
# IPSec SA の暗号化方式設定
ipsec sa policy 101 1 esp aes-cbc sha-hmac
# rekey を RTX810 側から行わせるため、RTX 側には短めの有効期間、rekey タイミングはできるだけ早期に行うよう設定
ipsec ike duration ipsec-sa 1 300 rekey 75%
ipsec ike duration isakmp-sa 1 300 rekey 75%
# ISAKMP SA の暗号化方式設定
ipsec ike encryption 1 aes-cbc
ipsec ike group 1 modp1024
ipsec ike hash 1 sha
# キープアライブ設定
ipsec ike keepalive use 1 on dpd 20 5
# 互換性の設定
ipsec ike payload type 1 3
ipsec ike backward-compatibility 1 2
# PFS (Perfect Forward Security) の設定
ipsec ike pfs 1 on
# IKE フェーズ 1 用のプリシェアードキー
ipsec ike pre-shared-key 1 text {{pre_shared_key}}
# リモート側接続ポイントの IP
ipsec ike remote address 1 {{Global IPv6 A}}
# トンネル終端の IP
ip tunnel address 10.0.0.2/30
# トンネルの mss 設定
 ip tunnel tcp mss limit auto
# トンネリングインターフェースを有効化
tunnel enable 1
# IPsec SAの自動更新を有効化
ipsec auto refresh on
# 対向側のプライベートネットワークレンジへのルーティング定義
ip route 192.168.10.0/24 gateway tunnel 1
# WAN 側 v6 IP への IPSec 関連通信の着信を許可（フィルタ番号は空いているものから適宜使用）
ipv6 filter 101 pass * ra-prefix@lan2::1 udp * 500
ipv6 filter 102 pass * ra-prefix@lan2::1 udp * 4500
ipv6 filter 103 pass * ra-prefix@lan2::1 esp * *
ipv6 lan2 secure filter in 101 102 103 {既存のルールを追加}

save
```

## 4.2 Linux VPS 側の作業

### 4.2.1 基本的なセットアップ

- Ubuntu の最小動作要件 ( https://ubuntu.com/server/docs/installation ) はメモリ 1 GB からとなっており、標準の 22.04 amd64 server 向けイメージは 512MB の VPS では起動しない ( https://vps.sakura.ad.jp/news/vps-os-ubuntu22_04-kerneltrouble/ ) ことに注意されたい。

- 2022/11/04 現在、ubuntu 22.04 cloud-img ( https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img ) をカスタム OS としてインストールすることにより「さくらのVPS 512MB プラン」で動作することが確認できている。一方、保証外の動作であること、今後アップデートに伴って動作しなくなる可能性もあることに留意されたい。

- 「さくらの VPS」標準 OS を用いる場合、初期状態では IPv6 が netplan 上で無効化されているため、有効化手順 ( https://manual.sakura.ad.jp/vps/network/ipv6/ubuntu20.html ) などを参考に有効化する。

### 4.2.2 追加のネットワーク設定

- /etc/sysctl.conf の変更例

```
net.ipv4.ip_forward = 1  # wireguard プライベート IP <-> IPSec VTI との間の相互転送に必要
```

- ufw の設定例

```
sudo ufw allow 22/tcp    # ssh
sudo ufw allow 80/tcp    # http (let's encrypt 用)
sudo ufw allow 443/tcp   # https (nginx リバースプロキシ)
sudo ufw allow 500/udp   # strongswan
sudo ufw allow 51820/udp # wireguard
sudo ufw enable
```

- /etc/default/ufw

```
DEFAULT_FORWARD_POLICY="ACCEPT"  # wireguard プライベート IP <-> IPSec VTI との間の相互転送に必要
```

### 4.2.3 Wireguard の設定

参考リンク：

https://wiki.archlinux.jp/index.php/WireGuard

- インストール

```
$ sudo apt install -y wireguard
```

- サーバ側鍵ペア生成

```
$ wg genkey | tee ~/server.key | wg pubkey | tee ~/server.pub
```

- サーバ側 /etc/wireguard/wg0.conf

```
[Interface]
PrivateKey = {{server.key}}
ListenPort = 51820

[Peer]
PublicKey = {{client.key (4.3 節で生成)}}
AllowedIPs = 192.168.10.101
```

- 起動、有効化

```
$ sudo systemctl enable wg-quick@wg0
$ sudo systemctl start wg-quick@wg0
```

### 4.2.4 strongSwan の設定

- インストール

```
$ sudo apt install -y strongswan strongswan-swanctl
```

- /etc/strongswan.d/charon.conf 編集

```
install_routes = no

start-scripts {
    swanctl = /usr/sbin/swanctl -q  # swanctl による設定を有効化する
}
```

- /etc/swanctl/swanctl.conf 編集

参考：

https://docs.strongswan.org/docs/5.9/features/routeBasedVpn.html

```
connections {
    {{コネクション名を任意に設定 -- 以後 conn_name とする}} {
        version = 1
        local_addrs = {{Global IPv6 A}}
        remote_addrs = {{Global IPv6 B}}
        local_port = 500
        remote_port = 500
        proposals = aes128-sha1-modp1024
        dpd_delay = 20s
        dpd_timeout = 100s
        over_time = 100s   # ISAKMP SA の key 有効時間は over_time + rekey_time
        rekey_time = 900s  # ISAKMP SA 確立から 900 秒経過で rekey を開始する（基本的により有効期間を短くした RTX 側の方から始動することを期待する）
        local {
            auth = psk
        }
        remote {
            auth = psk
        }
        children {
            {{対向するセキュリティゲートウェイ名を任意に設定 -- 以後 gtw_name とする}} {
                esp_proposals = aes128-sha1-modp1024
                local_ts = 0.0.0.0/0
                remote_ts = 0.0.0.0/0
                mode = tunnel
                start_action = start
                close_action = start
                life_time = 1000s  # IPSec SA の key 有効時間は life_time そのまま
                rekey_time = 900s # IPSec SA 確立から 900 秒経過で rekey を開始する
                mark_in = {{0 以外の好きな数字指定}}
                mark_out = {{mark_in と同じもの指定}}
                updown = /etc/strongswan.d/updown.sh
            }
        }
    }
}
secrets {
    ike-1 {
        secret = "{{pre_shared_key}}"
    }
}
```

- /etc/strongswan.d/updown.sh

IPSec SA コネクション確立時・切断時に当スクリプトが呼ばれることにより、システムに IPSec VTI (仮想インターフェース) をセットアップして IP の割り当てやルーティングの設定が行われる。

※ハードコーディングな箇所がたくさんあり例としては適切でない・・・本来は swanctl.conf のパラメータから自動設定、IPv4 / v6 を自動判別して ip / ip -6 コマンドを切り替えたい。

```
#!/bin/bash

set -o nounset
set -o errexit

PLUTO_MARK_OUT_ARR=(${PLUTO_MARK_OUT//// })
PLUTO_MARK_IN_ARR=(${PLUTO_MARK_IN//// })

VTI_LOCALADDR=10.0.0.1/30
VTI_REMOTEADDR=10.0.0.2/30
VTI_DESTINATION=192.168.20.0/24

VTI_IF="vti"${PLUTO_MARK_IN_ARR}

echo ${PLUTO_VERB}
echo ${PLUTO_CONNECTION}

case "${PLUTO_VERB}" in
    up-client)
        # vti{{swanctl.conf で指定した mark_in}} の名前で仮想トンネルインタフェースを生成
        ip -6 tunnel add ${VTI_IF} local "${PLUTO_ME}" remote "${PLUTO_PEER}" mode vti6 key "${PLUTO_MARK_OUT%%/*}"
        sysctl -w net.ipv4.conf.${VTI_IF}.disable_policy=1 # IPSec ポリシーベースルーティング無効
        sysctl -w net.ipv4.conf.${VTI_IF}.rp_filter=0      # 戻り経路フィルタ無効
        ip addr add ${VTI_LOCALADDR} dev ${VTI_IF}         # トンネルインタフェースに IP を付ける
        ip link set ${VTI_IF} up mtu 1280                  # MTU はとりあえずの値
        ip route add ${VTI_DESTINATION} dev ${VTI_IF}      # 拠点プライベートネットワークへのルーティング定義
        ;;
    down-client)
        ip tunnel del "${VTI_IF}"
        iptables -t nat -D POSTROUTING -dst ${VTI_DESTINATION} -j SNAT --to-source ${VTI_LOCALADDR}
        ;;
esac
```

- 起動

```
sudo systemctl enable strongswan-starter.service
sudo systemctl start strongswan-starter.service
```

### 4.2.5 nginx からのリバースプロキシセットアップ

- 前提として、拠点サーバ側では http サーバが構築されているものとする (http://192.168.20.11:80/)

- Linux VPS では port 80 で let's encrypt の実在確認応答、port 443 にてリバースプロキシを行うものとする。セットアップには下記リンクを参考にした。

https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04-ja

- /etc/nginx/site-available/{{Linux VPS FQDN}} の内容編集

※ https://{{Linux VPS FQDN}}/ 以下を全て拠点内サーバの http://192.168.20.11/ に転送した例を示す

```
server {
        listen 80;
        server_name example.com;
        return 301 https://$host$request_uri;
}
server {
        listen [::]:443 ssl ipv6only=on; # managed by Certbot
        listen 443 ssl; # managed by Certbot
        ssl on;
        ssl_certificate /etc/letsencrypt/live/{{Linux VPS FQDN}}/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/{{Linux VPS FQDN}}/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

        root /var/www/{{Linux VPS FQDN}};
        index index.html index.htm index.nginx-debian.html;
        server_name {{Linux VPS FQDN}};

        location / {
                include proxy_params;
                proxy_pass http://192.168.20.11/;
        }
}

```

## 4.3 Wireguard 経由での接続用クライアントの設定


- 鍵生成（空のトンネルを追加.. とすると鍵が生成されるので、下記画像の {{client.pub}} と {{client.key}} を控える）

![wg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216085/e1a19270-0b7c-c936-4e85-8ce99013eb27.png)

- トンネルの設定を下記のように編集

```
[Interface]
PrivateKey = {{client.key}}
Address = 192.168.10.101/32
DNS = 8.8.8.8
MTU = 1280

[Peer]
PublicKey = {{server.pub}}
AllowedIPs = 192.168.20.0/24, 10.0.0.0/30
Endpoint = {{Global IPv4 A}}:51820
```

## 4.4 動作確認

## 4.4.1 IPSec 経路の動作確認

- Linux VPS から 10.0.0.2 に、拠点ルータから 10.0.0.1 に ping 疎通チェック

- Linux VPS 側での IPSec 状態の表示

※ ISAKMP SA が 1 本、IPSec SA が 1 本以上確立されていることを確認（rekey のタイミングでは古いコネクションを残したまま新しいコネクションが張られるため、一時的に本数が増える）

```
$ sudo ipsec statusall
Status of IKE charon daemon (strongSwan 5.9.5, Linux 5.15.0-52-generic, x86_64):
  uptime: 1 second, since Nov 15 02:51:41 2022
  malloc: sbrk 2125824, mmap 0, used 1259488, free 866336
  worker threads: 11 of 16 idle, 5/0/0/0 working, job queue: 0/0/0/0, scheduled: 7
  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 vici updown eap-mschapv2 xauth-generic counters
Listening IP addresses:
  {{Global IPv4 A}}
  {{Global IPv6 A}}
  10.0.0.1
Connections:
     {{conn_name}}:  {{Global IPv6 A}}...{{Global IPv6 B}}  IKEv1, dpddelay=20s
     {{conn_name}}:   local:  uses pre-shared key authentication
     {{conn_name}}:   remote: uses pre-shared key authentication
      {{gtw_name}}:   child:  0.0.0.0/0 === 0.0.0.0/0 TUNNEL, dpdaction=clear
Security Associations (1 up, 0 connecting):
  {{conn_name}}[1]: ESTABLISHED 1 second ago, {{Global IPv6 A}}[{{Global IPv6 A}}]...{{Global IPv6 B}}[{{Global IPv6 B}}]
  {{conn_name}}[1]: IKEv1 SPIs: d94c2737fec4e04d_i* d4f9ae846505142a_r, rekeying in 2 minutes
  {{conn_name}}[1]: IKE proposal: AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024
   {{gtw_name}}{1}:  INSTALLED, TUNNEL, reqid 1, ESP SPIs: c7b14614_i 94d163a4_o
   {{gtw_name}}{1}:  AES_CBC_128/HMAC_SHA1_96/MODP_1024, 0 bytes_i, 0 bytes_o, rekeying in 3 minutes
   {{gtw_name}}{1}:   0.0.0.0/0 === 0.0.0.0/0
```

- 拠点ルータ側での IPSec 状態の表示

※ ISAKMP SA が 1 本、IPSec SA が 1 本以上確立されていることを確認（rekey のタイミングでは古いコネクションを残したまま新しいコネクションが張られるため本数が増える）

```
> show ipsec sa
Total: isakmp:1 send:1 recv:1

sa   sgw isakmp connection   dir  life[s] remote-id
-----------------------------------------------------------------------------
1     1    -    isakmp       -    212     {{Global IPv6 A}}
2     1    1    tun[001]esp  send 160     {{Global IPv6 A}}
3     1    1    tun[001]esp  recv 160     {{Global IPv6 A}}
```

## 4.4.2  VPN 端末の動作確認

- 外部の wireguard 接続端末から 192.168.20.11 に ping, ssh 等できることを確認

- Linux VPS 側での接続クライアントの状態表示

※ peer の項目が有効になってデータ疎通がしているかチェック

```
$ sudo wg show
interface: wg0
  public key: {{server.pub}}
  private key: (hidden)
  listening port: 51820

peer: {{client.pub}}
  endpoint: {{Global IPv4 A}}:{{適宜ポート}}
  allowed ips: 192.168.10.101/32
  latest handshake: 4 seconds ago
  transfer: 616 B received, 344 B sent
```

## 4.4.3 https アクセスでの動作確認

- 外部のインターネット接続端末から `https://{{Global IPv4 A}}` にアクセスし、`http://192.168.20.11/` のサイトが見えることを確認

# 5. つまづき・やり残しポイントのメモ

## 5.1 IPSec rekey の始動順について

ISACMP SA、IPSec SA コネクションとも、ネゴシエーション時に決まった有効時間に対し規定の時間を消費すると鍵を再生成した新しいセッションを張り直す挙動となっている。

strongSwan と YAMAHA RTX シリーズとを IPSec 接続する場合、`なぜかrekeyに失敗してループに陥るためstrongSwan側からのIKE/IPsec SA rekeyは行わず、RTXから来るのをおとなしく待つ` という知見を紹介されている記事があった。

https://journal.teamgedoh.net/index.t.html/i/2791

筆者の環境でもテストしたところ、たしかに「StrongSwan 側から始動した rekey プロセスは正常終了せず、新しい IPSec SA コネクションが確立しないため、古い IPSec SA の有効時間切れとともに通信が途絶える」と思われる挙動が観測された。

とりあえずの対応として「RTX 側が StrongSwan よりも十分に速いタイミングで始動側として rekey を開始する」ように有効期間と rekey 発動時間を設定して安定動作を得たが、何が根本原因だったのかは十分に分析できていない。

# 6. 感想

一般的な PC 用 OS が動いているシステム上での VPN は昨今 Wireguard などが簡便でよいが、物理ルータを用いた VPN 接続を行いたい場合、今でも IPSec の需要は根強い。一方、機種の異なる物理ルータやソフトウェア実装との間で IPSec トンネルを張る事は相性問題の検証等で一筋縄ではいかないケースがある。

クラウドを活用する大規模組織であれば仮想ルータアプライアンスを使ったり、クラウド事業者が提供する VPN ゲートウェイを利用すると簡易に高信頼なシステム構築が可能だが、個人レベルでの使用等小規模な場合はコストに見合わない事もあって「Linux VPS 1つで実現できる（≒ソフトウェア実装と物理ルータとの直接対向）」構成の必要性があった。

今回「自分も一度やっておきたい」ということで構築にチャレンジし、個人レベルであれば十分活用できる手順を作ることができたと思う。