JWNET 接続テスト — strongSwan 5.9 / swanctl¶
Debian 12 の専用ルータ VM に設定テンプレートを配置し、Biware から
210.164.154.26:5020 への接続を確認する手順。
GCP・Biware 手順書 Phase E の具体的な実装を本書が持つ。
テンプレートはリポジトリの scripts/jwnet-ipsec/。接続テスト専用であり、
実センターとの接続・再鍵交換・障害復旧・SNAT 往復は 未確認。
実行前に VM アドレスを照合する
元の構成メモ / Phase E:1 の「VM 内部 IP = 10.100.121.1」は GCP では使えない。
10.100.121.0/24 の .1 は予約済みの仮想 gateway である。
本書は NIC / IKE の外側 = 使用可能な VM アドレス(ルータ .2 は実測済み)、
IPsec 内側の SNAT = 設定票どおり .1 に分ける。
.1 を NIC・alias・loopback に割り当てず、local_addrs を VM の実 IP に合わせる。
2026-09-07 実測で kyoei-jwnet-gw のルータ NIC は .2。Windows は旧 10.10.0.2 に
あり通過対象外なので、§2 の差分適用で移設する。旧 Phase E の作成・ipsec・Windows 固定ルートの
コマンドを混在させず、IPsec 部分は本書の順序で実行する。
根拠: GCP の予約 IPv4 アドレス。
1. 設定票と未確認項目を照合する¶
非秘密値の正典は docs/superpowers/specs/2026-09-02-jwnet-edi-connection-test-config.md §1-3。
PSK・全銀パスワード・ファイルアクセスキー・センター確認コードの実値はリポジトリ、
ターミナル履歴、チャット、作業ログに書かない。PSK の雛形は
<PSK_FROM_SETTING_SHEET> のまま保ち、実値は既存の Secret Manager secret version から読む。
この手順では Secret の作成・実値の投入は行わない。
| 項目 | テンプレート / 判定 |
|---|---|
| peer / IKE / 認証 | 210.164.154.13 / IKEv2 / PSK |
| local IKE ID | @AG3b5kF62ZcalzsnVIYBrWxvdw.local(FQDN 型) |
| remote IKE ID | 210.164.154.13(IPv4 型)。2026-09-11 に PSK 認証成功で実証済み(暫定の確定)。不一致を %any で迂回しない |
| IKE / ESP | AES256-CBC・HMAC-SHA-256・DH14 / AES256-CBC・HMAC-SHA-256・PFS なし |
| TS / 内側 SNAT | local 10.100.121.0/24・remote 210.164.154.24/30 / 10.100.121.1 |
| VM 実アドレス | 2026-09-07 実測: ルータ 10.100.121.2。Windows は移設後 .10 を指定する(移設前は 10.10.0.2 で通過対象外)。自動割当の場合は移設後の実 IP に合わせる |
| 外部 IP・FW・IAM・egress | 2026-09-07 実測: 34.84.6.34 = jwnet-vpn-ip(IN_USE)、canIpForward=true。既存 IKE FW は §2 で再照合し、Windows → ルータの TCP 5020 FW は要作成。IAM / scope は充足(§2 手順 2 で専用 SA jwnet-ipsec-router + cloud-platform scope + 対象 PSK secret への secretmanager.secretAccessor を実施・§5 の Secret 読取成功で実証)。egress は未確認 |
| DPD のベンダー間対応 | 設定票どおり 30s / retry 3 / timeout 90s(Q2 回答: 設定票の値)。無通信 30s + 再送待ち 90s(最大約120s)の §6 構成を維持する。ベンダー間の実挙動は障害試験で実測する |
| センター疎通・rekey・復旧・SNAT | 2026-09-11 に IKE_SA・CHILD_SA 確立を実測(§7 の期待ログの形で確認)。rekey・復旧・SNAT 往復は引き続き期間内に観測する |
接続テスト期間は 2026-10-05〜10-18、運用時間は平日(土日祝を除く)9:00–17:00。 期間外はセンターが IKE / EDI 接続を受けない可能性がある。 期間終了後はこれらの設定値を使えない。本番用設定票は別途受領する。
2. GCP の VM・FW・ルート・固定 IP を用意する¶
Cloud Shell のリポジトリルートで実行する。コマンド中の <...> は非秘密識別子の置換箇所。
既存 VM / ルールがある場合は describe で比較して必要な差分だけを適用する。
新規環境は以下の従来手順、既存環境 kyoei-jwnet-gw は
差分適用へ進む。既存 VM に作成スクリプトを実行しない。
新規環境(従来の手順)¶
export PROJECT_ID='<GCP_PROJECT_ID>'
export ROUTER_SERVICE_ACCOUNT_EMAIL='<ROUTER_SERVICE_ACCOUNT_EMAIL>'
# 未作成の場合のみ。サブネット jwnet-subnet = 10.100.121.0/24 が前提。
# Phase B-1 の名前を使う場合は export SUBNET_NAME=jwnet-ipsec-subnet を先に設定する。
gcloud compute addresses create jwnet-ipsec-egress \
--project="$PROJECT_ID" --region=asia-northeast1
bash scripts/jwnet-ipsec/gcloud/create-router-vm.sh
# Windows の実 IP を確認する。configure-network.sh もこの値を取得して /32 に絞る。
gcloud compute instances describe jwnet-gw-vm --project="$PROJECT_ID" \
--zone=asia-northeast1-b --format='value(networkInterfaces[0].networkIP)'
bash scripts/jwnet-ipsec/gcloud/configure-network.sh
bash scripts/jwnet-ipsec/gcloud/check-egress.sh
既存環境で予約名が jwnet-ipsec-egress と違う場合(kyoei-jwnet-gw では jwnet-vpn-ip)は
export EGRESS_ADDRESS_NAME=jwnet-vpn-ip を先に設定する。
check-egress.sh は予約済み static 外部 IP と NIC の natIP の一致、予約の IN_USE、
canIpForward=true、内部 IP、Windows が 10.100.121.0/24 の使用可能範囲内にいることを確認する。
表示されたルータの内部 IP を local_addrs に転記する。
外部 IP は GCP が 1:1 NAT するため、Linux の local_addrs には入れない。
VM 作成スクリプトは予約リソースを describe し、address に IPv4 値を渡す。
取得失敗・空値・IPv4 以外は VM 作成前に中断する(空の address= による ephemeral IP を防ぐ)。
GCP の static 外部 IP の割り当て
VM サービスアカウントには 対象 PSK secret に限った roles/secretmanager.secretAccessor と
cloud-platform scope が必要。JSON サービスアカウント鍵は作らず、VM の認証を使う。
Cloud Shell のユーザー権限と VM のサービスアカウント権限は別である。
VM の Secret アクセス要件
configure-network.sh が作るのは次の設定。
- ルータへの ingress: 送信元
210.164.154.13/32の UDP 500/4500 のみをjwnet-ipsecタグへ許可。 - Windows から転送先ルータへの ingress: Windows 実 IP
/32から TCP 5020 を許可。 - VPC 静的ルート:
210.164.154.24/30→--next-hop-instance=jwnet-ipsec-vm。jwnet-biwareタグを Windows だけに付ける。ルータへ同じタグを付けない。
Windows は通常の GCP gateway に送り、VPC が転送先を選ぶため、Windows の
route -p add ... 10.100.121.1 は不要。既存の同宛先の手動ルートは route print
で確認し、今回の VPC 方式へ一本化する。ルータのデフォルト経路(peer 宛)をこのルートで変更しない。
egress は VPC の implied allow が前提。組織 / 階層 firewall、Windows Firewall、VM の host firewall
も確認し、Windows → /30 TCP 5020、ルータ → peer UDP 500/4500、Secret Manager の HTTPS / DNS
が通ることを確かめる。追加した allow より優先される deny は別途解消が必要。
根拠: GCP routes create、
firewall-rules create。
既存環境 kyoei-jwnet-gw(2026-09-07 実測)への差分適用¶
ルータ VM は作成済みで、jwnet-ipsec-subnet = 10.100.121.0/24 の 10.100.121.2 にある。
旧 jwnet-subnet = 10.10.0.0/24 は残存し、Windows VM はその 10.10.0.2 にある。
以下は Cloud Shell で順に実施する。作成済み VM / VPC / サブネット / 固定 IP を再作成しない。
手順 1 — 実測を読み取り専用で再確認する¶
gcloud compute networks subnets list --network=jwnet-vpc --project="$PROJECT_ID"
gcloud compute routes list --filter='network:jwnet-vpc' --project="$PROJECT_ID"
gcloud compute firewall-rules list --filter='network:jwnet-vpc' --project="$PROJECT_ID"
gcloud compute firewall-rules describe allow-jwnet-ipsec --project="$PROJECT_ID" --format='value(sourceRanges,allowed)'
gcloud compute routes describe to-jwnet-via-ipsec --project="$PROJECT_ID" --format='value(destRange,nextHopInstance,priority,tags)'
EGRESS_ADDRESS_NAME=jwnet-vpn-ip bash scripts/jwnet-ipsec/gcloud/check-egress.sh
allow-jwnet-ipsec は UDP 500/4500 と ESP を許可し、target-tag 無しで存在する。
source-ranges は Phase B-3 どおり 210.164.154.13/32 の見込みなので、上の describe で再確認する。
to-jwnet-via-ipsec は宛先 210.164.154.24/30、次ホップ jwnet-ipsec-vm、priority 100、タグ無しであることを照合する。
同等性を確認できなければ手順 4 で流用しない。
Windows 移設前は check-egress.sh が Biware 判定で失敗するのが正常で、V1 合格にはしない。
手順 2 — ルータ VM のサービスアカウントと scope を付け替える¶
現状は既定の Compute Engine SA で cloud-platform scope が無く、Secret Manager を読めない。
専用 SA が未作成の場合だけ最初の create 行を実行し、VM を停止してから付け替える。
gcloud iam service-accounts create jwnet-ipsec-router --display-name='JWNET IPsec router VM' --project="$PROJECT_ID"
gcloud compute instances stop jwnet-ipsec-vm --zone=asia-northeast1-b --project="$PROJECT_ID"
gcloud compute instances set-service-account jwnet-ipsec-vm --zone=asia-northeast1-b \
--service-account=jwnet-ipsec-router@kyoei-jwnet-gw.iam.gserviceaccount.com \
--scopes=cloud-platform --project="$PROJECT_ID"
gcloud compute instances start jwnet-ipsec-vm --zone=asia-northeast1-b --project="$PROJECT_ID"
停止は必須である(VM の SA / access scope 変更要件)。
対象 PSK secret に限った roles/secretmanager.secretAccessor をこの SA に付与し、§5 の Secret 読取要件を満たす。
Secret の作成・実値投入は本 runbook の対象外である。
ログ / 監視エージェントを使う場合は roles/logging.logWriter / roles/monitoring.metricWriter を任意で付ける。
手順 3 — Windows VM を通過対象サブネットへ移設する¶
NIC 移設の要件と制約を確認する。
- VM は停止していること。IPv4 のみを使っていること。
- インスタンスグループ / NEG に属していないこと。
- 移設先サブネットは VM と同一リージョン(
asia-northeast1)であること。 - NIC の MAC アドレスが変わる。Windows のネットワークプロファイルが変わり得るため、Biware に NIC 依存の設定が無いか移設後に確認する。
- 外部 IP は移設先の
compute.subnetworks.useExternalIp権限があり、組織ポリシーが外部 IP を禁止していなければ保持される。 --private-network-ipが拒否された場合はその引数を外して自動割当とし、describeで実 IP を読む。以降の FW は実 IP を使う(configure-network.shが取得する)。
gcloud compute instances stop jwnet-gw-vm --zone=asia-northeast1-b --project="$PROJECT_ID"
gcloud compute instances network-interfaces update jwnet-gw-vm --zone=asia-northeast1-b \
--network-interface=nic0 --network=jwnet-vpc --subnetwork=jwnet-ipsec-subnet \
--private-network-ip=10.100.121.10 --project="$PROJECT_ID"
--private-network-ip による指定が拒否された場合だけ、停止したまま次を実行する。
権限や移設要件のエラーは先に解消する。
gcloud compute instances network-interfaces update jwnet-gw-vm --zone=asia-northeast1-b \
--network-interface=nic0 --network=jwnet-vpc --subnetwork=jwnet-ipsec-subnet --project="$PROJECT_ID"
移設の完了を待ってから起動し、実 IP を確認する。
gcloud compute instances start jwnet-gw-vm --zone=asia-northeast1-b --project="$PROJECT_ID"
gcloud compute instances describe jwnet-gw-vm --zone=asia-northeast1-b --project="$PROJECT_ID" \
--format='value(networkInterfaces[0].networkIP)'
接続確認の前に、IAP 経由の RDP の前提を用意する(未作成なら作る・既存は再作成しない。手順 4 の configure-network.sh も同じタグを付けるが、その前に接続を確認するためここで付ける。タグの追加は冪等)。
gcloud compute instances add-tags jwnet-gw-vm --zone=asia-northeast1-b --tags=jwnet-biware --project="$PROJECT_ID"
gcloud compute firewall-rules describe allow-iap-rdp --project="$PROJECT_ID" --format='value(name)' 2>/dev/null \
|| gcloud compute firewall-rules create allow-iap-rdp --network=jwnet-vpc --direction=INGRESS --action=ALLOW \
--rules=tcp:3389 --source-ranges=35.235.240.0/20 --target-tags=jwnet-biware --project="$PROJECT_ID"
Windows App(IAP トンネル・GCP/Biware runbook 付録 A)で入れることと、Windows の ipconfig が 10.100.121.x であることを確認する。
--private-network-ip と自動割当の動作は
network-interfaces updateも参照する。
手順 4 — タグと TCP 5020 の FW を追加する¶
タグ付与 2 件と、Windows 移設後の実 IP /32 → jwnet-ipsec の jwnet-allow-biware-forward を作成する。
既存 allow-jwnet-ipsec / to-jwnet-via-ipsec を流用し、同等の IKE FW / ルートの重複作成を避ける。
既存ルートはタグ無し・priority 100 で全 VM に効くが、ルータ VM 自身の IKE / ESP は peer 210.164.154.13 宛で /30 ルートに当たらず、
ルータ自身の /30 宛平文は §4 で配置する jwnet-filter.rules の OUTPUT が遮断するため、その構成で実害はない。
設計のタグ限定に寄せるなら別途 --tags=jwnet-biware 付きルートへ置き換える(今回は実行しない)。
手順 5 — 接続テストに不要な口を閉じる¶
公開網からの RDP 許可は NAT の有無に関係なく外す(方針 2026-09-08: RDP は IAP トンネル経由のみ)。allow-iap-rdp 以外で
TCP 3389 を許可する FW(常設の allow-my-rdp、旧付録 A が作っていた一時 FW tmp-bootstrap-rdp など)を列挙し、出たものを外す。
source の実値は文書・報告・作業ログへ転記しない。
gcloud compute firewall-rules list --project="$PROJECT_ID" \
--filter='allowed[].ports:3389 AND NOT name=allow-iap-rdp' --format='value(name,sourceRanges)'
gcloud compute firewall-rules delete allow-my-rdp --project="$PROJECT_ID" # 一覧に出たものだけ実行する
gcloud compute firewall-rules delete tmp-bootstrap-rdp --project="$PROJECT_ID"
Windows App の接続は IAP トンネル(allow-iap-rdp・35.235.240.0/20 → 3389)経由で、公開 FW にも外部 IP にも依存しない。外した後に接続を確認する。
外部 IP は Cloud NAT を確認してから判断する。 2026-09-07 の実測には含まれておらず、存在は未確認である。
gcloud compute routers nats list --router=jwnet-router --region=asia-northeast1 --project="$PROJECT_ID"
gcloud compute routers nats describe jwnet-nat --router=jwnet-router --region=asia-northeast1 --project="$PROJECT_ID" --format='value(sourceSubnetworkIpRangesToNat)'
jwnet-nat が存在し、describe が ALL_SUBNETWORKS_ALL_IP_RANGES を返す場合だけ、Windows の外部 IP を外す。
最初に access config の名前を読み、次の <その名前> に転記する(外部 IP の実値は記録しない)。
gcloud compute instances describe jwnet-gw-vm --zone=asia-northeast1-b --project="$PROJECT_ID" \
--format='value(networkInterfaces[0].accessConfigs[0].name)'
gcloud compute instances delete-access-config jwnet-gw-vm --zone=asia-northeast1-b --project="$PROJECT_ID" --access-config-name='<その名前>'
NAT が無い場合や確認できない場合は外部 IP を残す(Windows Update などの外向き通信のため)。外部 IP が残っていても、上で公開 RDP 許可を外していれば受信経路は IAP だけになる。
手順 6 — V1 の証跡を採る¶
全判定 OK(終了コード 0)の出力を V1 の証跡にする。Static external IP: 34.84.6.34、
ルータの networkIP: 10.100.121.2、Biware networkIP: <移設後の実 IP> を確認し、§3 へ進む。
この判定だけで IAM / scope、egress、センター疎通が確認済みになるわけではない。
3. Debian 12 の版とテンプレートを配置する¶
Cloud Shell から非秘密のテンプレートだけを転送する。
gcloud compute scp --recurse scripts/jwnet-ipsec \
jwnet-ipsec-vm:~/jwnet-ipsec --project="$PROJECT_ID" \
--zone=asia-northeast1-b --tunnel-through-iap
gcloud compute ssh jwnet-ipsec-vm --project="$PROJECT_ID" \
--zone=asia-northeast1-b --tunnel-through-iap
以降は Linux VM。専用 VM の新規構築を前提とする。strongswan-starter / ipsec.conf の
デーモンと同時起動しない。既に導入済みなら systemctl status strongswan-starter
を確認し、旧方式を停止してから移行する。
既存 VM の旧方式(strongswan-starter / ipsec.conf)からの移行¶
2026-09-07 実測では Debian 12 bookworm に legacy の strongswan / strongswan-starter /
strongswan-charon / libcharon-extauth-plugins / libstrongswan-standard-plugins(5.9.8-5+deb12u5)が導入済みで、
strongswan / strongswan-starter はいずれも inactive。/etc/ipsec.conf は残存し、
strongswan-swanctl / charon-systemd、/etc/swanctl/conf.d、/opt/jwnet-ipsec は未導入である。
charon-systemd と同時起動しないよう、まず旧 unit を無効化・mask する。
sudo systemctl disable --now strongswan-starter
sudo systemctl mask strongswan-starter
sudo ip xfrm policy
sudo ip xfrm policy の出力が空であることを確認してから、次の apt-get install へ進む。
残っていれば旧接続の停止状態を調べる。ip xfrm state は鍵素材を出すので使わない。
/etc/ipsec.conf は残してよいが swanctl 方式では読み込まれない。
/etc/ipsec.secrets に PSK 実値が入っている場合は、値を表示せず、§5 の Secret 読取が成功した後に
sudo shred -u /etc/ipsec.secrets で消す。読取成功前には消さない。
swanctl 版の導入(新規・移行共通)¶
sudo apt-get update
sudo apt-get install -y strongswan-swanctl charon-systemd iptables-persistent python3 procps
sudo systemctl stop strongswan
dpkg-query -W strongswan-swanctl charon-systemd
iptables --version
gcloud version
sudo install -d -m 0755 /opt/jwnet-ipsec
sudo cp -R ~/jwnet-ipsec/. /opt/jwnet-ipsec/
sudo chown -R root:root /opt/jwnet-ipsec
sudo chmod 0755 /opt/jwnet-ipsec/install-secrets.sh
sudo install -d -m 0755 /etc/swanctl/conf.d /etc/strongswan.d
sudo install -m 0644 /opt/jwnet-ipsec/swanctl/swanctl.conf /etc/swanctl/swanctl.conf
sudo install -m 0644 /opt/jwnet-ipsec/swanctl/conf.d/jwnet.conf /etc/swanctl/conf.d/jwnet.conf
sudo install -m 0644 /opt/jwnet-ipsec/strongswan.d/jwnet-retransmit.conf /etc/strongswan.d/
対象は Debian bookworm の 5.9.8 系(Debian の security revision を含む)。
dpkg-query の版を記録し、5.9 以外なら自動的に適用せず同梱の man page と再照合する。
gcloud が無ければ Google Cloud CLI の Debian 導入手順
で導入する。root から VM サービスアカウントを利用できることも確認する。
既存の /etc/swanctl/conf.d/ に他の接続や secrets が無いこと、
/etc/strongswan.conf がトップレベルで include strongswan.d/*.conf を読むことを確認する。
他接続があれば専用 VM の前提を満たさないので分離する。
/etc/swanctl/conf.d/jwnet.conf の local_addrs を §2 の実 IP へ変更し、
remote.id を設定票 / 窓口で確認する。%any で ID 不一致を回避しない。
4. Linux の転送と SNAT を永続化する¶
Debian 12 は iptables が nf_tables backend(iptables-nft)を使う。
今回の iptables 形式をそのまま使える iptables-persistent / netfilter-persistent を選ぶ。
native nftables と二重に同じルールを管理しない。
Debian nftables / iptables-nft の既定
テンプレート投入前に旧 SNAT を整理する。2026-09-07 実測では source 条件無しの同じルールが 2 本重複していた。
sudo iptables -t nat -S POSTROUTING
# 下記の旧ルールが出る回数だけ -D を実行する(実測時は 2 回)。
sudo iptables -t nat -D POSTROUTING -d 210.164.154.24/30 -j SNAT --to-source 10.100.121.1
sudo iptables -t nat -S POSTROUTING
-d 210.164.154.24/30 -j SNAT --to-source 10.100.121.1(-s 無し)が消えたことを確認してから、
以下で -s 10.100.121.0/24 付きの jwnet-snat.rules を入れる。
非 root の PATH に /sbin が無いため、sysctl は sudo sysctl で実行する。
sudo install -m 0644 /opt/jwnet-ipsec/sysctl.d/99-ipfwd.conf /etc/sysctl.d/
sudo sysctl -p /etc/sysctl.d/99-ipfwd.conf
sudo iptables -t nat -S
sudo iptables -S FORWARD
sudo iptables -S OUTPUT
# 初回だけ。既存ルールを確認し、重複投入や先行する汎用 MASQUERADE を避ける。
sudo iptables-restore --test --noflush < /opt/jwnet-ipsec/iptables/jwnet-filter.rules
sudo iptables-restore --test --noflush < /opt/jwnet-ipsec/iptables/jwnet-snat.rules
sudo iptables-restore --noflush < /opt/jwnet-ipsec/iptables/jwnet-filter.rules
sudo iptables-restore --noflush < /opt/jwnet-ipsec/iptables/jwnet-snat.rules
sudo netfilter-persistent save
sudo systemctl enable netfilter-persistent
jwnet-filter.rules は SA のない /30 宛の平文を FORWARD / OUTPUT で拒否する。
jwnet-snat.rules は /24 → /30 のみを .1 に SNAT する。
元の source と NAT 後 source がともに local_ts に入る構成を保つ。
SNAT は内側パケットに働き、戻りは conntrack が Windows のアドレスへ逆変換する。
この GCP 構成での往復は未確認なので §8 で応答とカウンタを実測する。
通常の VPN 向け「IPsec パケットを NAT から除外する ACCEPT」をこの SNAT より前に入れると
要求を満たさなくなる。strongSwan の forwarding / NAT の注意
host firewall の FORWARD policy が DROP の場合、上記ルールだけでは許可されない。
既存ポリシーに Windows → /30 TCP 5020(outbound IPsec policy に一致)とその
ESTABLISHED 戻りの許可を追加する。全体を ACCEPT に変更して解決しない。
5. PSK を起動前に Secret Manager から読む¶
/etc/default/jwnet-ipsec を root 所有・0644 で作り、次の 非秘密識別子だけを記入する。
バージョンは検証対象を固定できる数値を使う。意図して追随させる場合だけ latest を指定する。
PROJECT_ID=<GCP_PROJECT_ID>
PSK_SECRET_ID=<SECRET_MANAGER_SECRET_NAME>
PSK_SECRET_VERSION=<SECRET_VERSION_NUMBER>
接続テスト用の実例: PSK_SECRET_ID=jwnet-ipsec-psk-test(接続テスト用・本番は別 secret。値は書かない)。
systemctl start strongswan の前に、swanctl の AppArmor 許可を入れる。
Debian 12 の /etc/apparmor.d/usr.sbin.swanctl は /etc/swanctl/** r と
/run/charon.vici rw だけを許可し、/run/jwnet-ipsec/jwnet.secrets を読めない。
読めない include は設定パーサが黙って飛ばすため、secrets 未ロードのまま
no shared key found で IKE_AUTH 前に失敗する(2026-09-11 実測)。
同梱の local 拡張(scripts/jwnet-ipsec/apparmor/local-usr.sbin.swanctl)の 2 行を
/etc/apparmor.d/local/usr.sbin.swanctl へ入れ、profile を再読込する。
既存ファイルがあれば上書きではなく、足りない行だけ追記する
(Debian は空の local ファイルを置くことがある)。
aa-status が /usr/sbin/swanctl の profile を出さない環境(AppArmor 無効)では
この手順を読み飛ばしてよい。
sudo install -d -m 0755 /etc/systemd/system/strongswan.service.d
sudo install -m 0644 /opt/jwnet-ipsec/systemd/jwnet-secrets.conf \
/etc/systemd/system/strongswan.service.d/jwnet-secrets.conf
sudo install -d -m 0755 /etc/apparmor.d/local
sudo touch /etc/apparmor.d/local/usr.sbin.swanctl
if [ -n "$(sudo tail -c 1 /etc/apparmor.d/local/usr.sbin.swanctl)" ]; then
printf '\n' | sudo tee -a /etc/apparmor.d/local/usr.sbin.swanctl > /dev/null
fi
for line in '/run/jwnet-ipsec/ r,' '/run/jwnet-ipsec/jwnet.secrets r,'; do
sudo grep -qxF -- "$line" /etc/apparmor.d/local/usr.sbin.swanctl 2>/dev/null \
|| printf '%s\n' "$line" | sudo tee -a /etc/apparmor.d/local/usr.sbin.swanctl > /dev/null
done
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.swanctl
sudo systemctl daemon-reload
sudo systemctl enable strongswan
sudo systemctl start strongswan
sudo stat -c '%U:%G %a %n' /run/jwnet-ipsec /run/jwnet-ipsec/jwnet.secrets
sudo journalctl -k --since '-10 minutes' | grep -i 'apparmor.*DENIED'
sudo swanctl --load-creds --noprompt
追記の前の tail -c 1 が最終改行を保証する。最終改行なしの既存ファイルへそのまま追記すると
既存行へ連結され、2 回目の grep -qxF が連結後の行を見つけられず同じ許可を重複追記するためである。
既に許可行が既存行へ連結されて 1 行になっている場合は、エディタで 2 行に直してから実行する。
期待値はディレクトリ root:root 700、ファイル root:root 600。
journalctl -k の DENIED 行は空であること(出ていれば AppArmor 拒否が残っている)、
swanctl --load-creds --noprompt には loaded ike secret 'ike-jwnet' が出ること。
ExecStartPre の installer が gcloud secrets versions access --out-file で一時ファイルに読み、
0s + Base64 の swanctl 表記へ変換して原子的に配置する。PSK のバイト列は変更しない。
設定票を登録した Secret に意図しない末尾改行がないことは登録側で確認する。
Base64 も秘密そのものであり、cat、echo、set -x を使わない。
installer は --no-log-http --verbosity=error を明示し、root の永続設定に
core/log_http=true があっても秘密を含む HTTP 応答を記録させない。
空値・プレースホルダ・取得失敗は起動失敗になる。
/run は再起動で消えるため、毎回 Secret Manager から読み直す。サービスの再起動は
接続を切断するので接続テストの送受信中には行わない。既存 PSK をローカルで編集しない。
旧 /etc/ipsec.secrets に PSK 実値を残している場合だけ、上の Secret 読取成功と配置権限を確認した後に消す(値は表示しない)。
Secret の out-file 出力、 strongSwan 5.9.8 の secret / 0s 表記
6. strongSwan キーの意味と版を確認する¶
確認日 2026-09-06。一次資料は
strongSwan 5.9 swanctl.conf、
upstream 5.9.8 の man 原稿、
Debian bookworm swanctl.conf(5)。
Web の 5.9 ページにも後続版の記述が入り得るため、使用する全キーを 5.9.8 タグの原稿にも照合する。
配置後は man swanctl.conf / man strongswan.conf と実際の dpkg-query の版を最終根拠とする。
5.9.8 のキー / 節(C = connections.jwnet、H = C.children.jwnet) |
採用値と意味 |
|---|---|
connections / 任意接続名 / children / 任意 child 名 |
両方 jwnet。--child jwnet は後者を指定 |
C.version / C.local_addrs / C.remote_addrs |
2 / 実 NIC IP / peer。IKE の外側 endpoint |
C.local.auth / C.remote.auth |
両方 psk。local / remote は認証ラウンドの節 |
C.local.id / C.remote.id |
前者は FQDN、後者は暫定 IPv4。IKE identity と通信 endpoint は別 |
C.proposals |
aes256-sha256-modp2048。AES-CBC 256、SHA2-256 integrity、DH group14。省略した PRF は SHA2-256 integrity から導出 |
H.esp_proposals / H.mode |
aes256-sha256 / tunnel。DH を含めず CHILD の独立 DH(PFS)を要求しない |
H.local_ts / H.remote_ts |
/24 / /30。port / protocol は TS で絞らず TCP 5020 の通過は FW 側で確認 |
C.encap / C.mobike |
yes で ESP-in-UDP を強制。no で固定接続に不要な MOBIKE を無効化。NAT-T は維持 |
C.dpd_delay |
30s。受信 IKE / ESP がなければ liveness check。IKEv2 に dpd_timeout は無効なので書かない |
C.keyingtries |
0。初回の再送シーケンスを再試行。認証等の恒久エラーまで無限に直せるわけではない |
C.reauth_time |
0s。再認証は無効、IKEv2 rekey で鍵を更新 |
C.rekey_time / C.over_time / C.rand_time |
25920s / 2880s / 0s。7h12m で開始し、48m の猶予を足した 8h で未完了なら終了 |
H.rekey_time / H.life_time / H.rand_time |
3240s / 3600s / 0s。54m で更新開始、1h で hard expiry |
H.start_action / H.close_action |
start / start。ロード時開始 / peer の明示 DELETE で再作成。後者は交渉失敗には作用しない |
H.dpd_action |
restart。DPD 失敗後に新しい IKE SA で CHILD を再交渉 |
secrets / ike-jwnet / id / secret |
ike 接頭辞の PSK 節、local ID への関連付け、雛形値。実行時は 0s 表記 |
include |
strongswan.conf と共通構文。接続と /run の秘密ファイルをトップレベルで読む |
@ は必須ではない。この名前は bare string でも IPv4 へ解析できず FQDN 型になるが、
@ で型を明示する。接頭辞自体は送信 ID に含まれず、ID の DNS 解決は行わない。
5.9 Identity Parsing
IKEv2 は SA lifetime を peer と交渉しない。ここでの 8h / 1h は 当方の期限であり、
peer が先に rekey すれば更新される。rekey_time=28800s とするだけでは over_time 分だけ
8h を超えるため、期限の 90% で開始する。乱数差引は 0s に固定して期限を明確にした。
同時 rekey の回避に既定乱数を使う場合は deadline の計算も再検証する。
PFS なしでも初回 CHILD は IKE の DH 由来の鍵を使うため、「DH が一切ない」という意味ではない。
5.9 SA renewal、
5.9 algorithm proposals
DPD の再送は strongswan.d/jwnet-retransmit.conf の charon-systemd 節で設定し、
専用デーモンの全 IKE request に作用する。単位接尾辞を付けず数値で指定する。
キー(charon-systemd. の後) |
値 | 意味 |
|---|---|---|
retransmit_timeout |
6.0 |
初回の応答待ち秒数 |
retransmit_base |
2.0 |
指数 backoff の底 |
retransmit_tries |
3 |
再送回数(最初の送信を含まない) |
retransmit_jitter |
0 |
乱数による待ち時間減少なし |
retransmit_limit |
0 |
算出した待ち時間の上限を無効化 |
6 × 2^n を初回待ち + 3 回の再送後待ちまで 4 区間加算し、6+12+24+48=90s。
DPD request の初回を t=0 とすると再送は 6 / 18 / 42s、断念は 90s。
無通信を検出する 30s はこの前にあるため、最後の受信からは最大約120sになる。
設定票の「retry 3 / timeout 90s」がこの定義かは 未確認であり、窓口確認と障害試験で確定する。
根拠: 5.9 再送の計算式、
5.9.8 charon オプション原稿、
5.9 strongswan.conf の派生 daemon 名前空間。
7. 設定ロードと SA を確認する¶
sudo swanctl --load-all --noprompt
sudo swanctl --list-conns
sudo swanctl --initiate --child jwnet
sudo swanctl --list-sas
sudo journalctl -u strongswan --since '-10 minutes' --no-pager
systemd の起動でも swanctl のロードが走り、start_action=start で自動開始する。
既に CHILD があれば重複開始は不要。手動の --initiate は初回確認 / 再試行用。
自動確立(start_action=start)は手動 --initiate を実行せず、journal の (a) と --list-sas の (b) で確認する。
成功の確認先は 2 つある。(a) daemon の認証成功・IKE/CHILD 確立ログは --initiate の stdout に中継され、
同じ本文を journalctl -u strongswan でも確認できる。小文字の established で確立を表す。
(b) --list-sas は大文字の状態語
(ESTABLISHED / INSTALLED)で現在の状態を表す。journal に大文字の ESTABLISHED は出ない。
--list-sas は PSK を表示しない。ip xfrm state は鍵素材を表示し得るので記録に使わない。
(a) daemon ログとして --initiate の stdout / journal で確認する行(2026-09-11 実測の形。
<...> は SPI・実 IP・実時刻・ID のプレースホルダで、実値は記録しない)。
以下は stdout の表示形式で、接頭辞は出力先によって異なる。
[IKE] initiating IKE_SA jwnet[4] to <peer>
[NET] sending packet: from <local>[500] to <peer>[500] (464 bytes)
[NET] received packet: from <peer>[500] to <local>[500] (619 bytes)
[CFG] selected proposal: IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048
[IKE] local host is behind NAT, sending keep alives
[IKE] authentication of '<local id>' (myself) with pre-shared key
[IKE] establishing CHILD_SA jwnet{1}
[NET] sending packet: from <local>[4500] to <peer>[4500] (288 bytes)
[IKE] authentication of '<peer id>' with pre-shared key successful
[IKE] IKE_SA jwnet[4] established between <local>[<local id>]...<peer>[<peer id>]
[CFG] selected proposal: ESP:AES_CBC_256/HMAC_SHA2_256_128/NO_EXT_SEQ
[IKE] CHILD_SA jwnet{1} established with SPIs <in>_i <out>_o and TS <local ts> === <remote ts>
確認する要素((a)): authentication of '<peer id>' with pre-shared key successful
の行があること / IKE_SA jwnet[4] established between ...(小文字 established)/
CHILD_SA jwnet{1} established with SPIs ... and TS ...(小文字 established)。
charon-systemd のサービス / journal
手動 --initiate の stdout では、上記の daemon ログに加えて次の完了行を確認する。
この行は swanctl クライアント自身が VICI の成功応答を受けて stdout に出力する。 daemon のログではないため、journal や自動確立の確認条件には含めない。 5.9.8 の swanctl --initiate 出力処理
(b) --list-sas で確認する状態(2026-09-11 実測の形。<...> は同上)。
jwnet: #4, ESTABLISHED, IKEv2, <spi>_i* <spi>_r
local '<local id>' @ <local>[4500]
remote '<peer id>' @ <peer>[4500]
AES_CBC-256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048
established Ns ago, rekeying in Ns
jwnet: #1, reqid 1, INSTALLED, TUNNEL-in-UDP, ESP:AES_CBC-256/HMAC_SHA2_256_128
installed Ns ago, rekeying in Ns, expires in Ns
in <spi>, N bytes, N packets
out <spi>, N bytes, N packets
local <local ts>
remote <remote ts>
確認する要素((b)): jwnet: #..., ESTABLISHED, IKEv2 と child の
INSTALLED, TUNNEL-in-UDP / IKE が AES_CBC-256・HMAC_SHA2_256_128・
PRF_HMAC_SHA2_256・MODP_2048、ESP が AES_CBC-256・HMAC_SHA2_256_128 /
TS が期待の <local ts> === <remote ts> / IKE rekey 25920s・lifetime 28800s・
CHILD rekey 3240s。NAT 環境では TUNNEL-in-UDP、NAT が無ければ TUNNEL になる
(本環境は GCP の 1:1 NAT 配下で local host is behind NAT が出るため前者)。
swanctl --list-sas
ゲートウェイ用の常駐公開は GCP・Biware runbook Phase E-2.7
本節の --list-sas 確認を常駐ゲートウェイの health probe から読める形で公開する
手順(scripts/gateway/ の配置・systemd unit・FW)は
GCP・Biware 手順書 Phase E-2.7 にある。
8. Windows から SNAT 往復を確認する¶
管理者 PowerShell で次を実行する。ping が通らないだけでは TCP 接続の失敗としない。
期待は TcpTestSucceeded : True。同時に Linux VM でカウンタを前後比較する。
sudo swanctl --list-sas
sudo iptables -t nat -L POSTROUTING -n -v
sudo iptables -L FORWARD -n -v
ip route get 210.164.154.13
ip route show table 220
sudo sysctl net.ipv4.ip_forward
SNAT ルールの hit(nat は接続の最初のパケットを数える)、CHILD の in/out byte の増加、
Windows の成功をそろえて記録する。センター側で観測した内側 source が .1 であることも
窓口と確認する。必要なら conntrack の tuple だけで逆変換を確認し、payload や鍵を採取しない。
TCP 接続だけでは拡張Z手順の認証成功にはならない。次に
Biware 手順書 Phase F
の送信・結果受信・HR1 判定へ進む。
9. 失敗の切り分け・復旧と証跡を残す¶
| 症状 | 確認するもの / 次の操作 |
|---|---|
| unit が起動しない | journalctl -u strongswan。Secret の IAM / scope / version / gcloud の有無、0600 の配置、include のパス。値を出力して調べない |
| ExecStartPre の installer が PERMISSION_DENIED / scope で失敗 | VM の SA と cloud-platform scope を確認し、§2 手順 2 で付け替える。対象 secret の roles/secretmanager.secretAccessor も照合する |
sysctl: command not found |
非 root の PATH に /sbin が無い。sudo sysctl で実行する |
NO_PROPOSAL_CHOSEN |
IKE と CHILD を区別して proposal を比較。IKE=DH14、ESP=DH なし。default や別の暗号を無根拠に足さない |
ID / AUTHENTICATION_FAILED |
local ID の型と値、remote ID(2026-09-11 に IPv4 210.164.154.13 で確定)、PSK の正しい version を照合。%any や認証省略で迂回しない |
no shared key found for '<local id>' - '<remote id>' かつ --load-creds が無出力 |
swanctl の AppArmor 拒否。journalctl -k \| grep apparmor で name="/run/jwnet-ipsec/jwnet.secrets" の DENIED を確認 → §5 の local 拡張を入れて apparmor_parser -r する |
| IKE 再送が続く | 期間・営業時間、peer .13、static 外部 IP、UDP500/4500、GCP / host FW を確認。NAT-T の UDP4500 が双方向で通るかを調べる |
| ESTABLISHED だが CHILD なし | ESP proposal・TS が /24 と /30 か、センターの TS narrowing をログで確認 |
journal に大文字の ESTABLISHED が出ない |
正常。journal(charon の IKE ログ)の確立行は小文字の established。大文字の状態語は --list-sas の出力にしか現れない。§7 の (a) と (b) を見分けて確認する |
| CHILD はあるが TCP が失敗 | VPC route の Windows タグ、転送先と canIpForward、Linux ip_forward、SNAT 順序、FORWARD、Windows egress を順に確認 |
| Windows から TCP が失敗し、ルータの FORWARD カウンタが増えない | Windows の networkIP が 10.100.121.0/24 内か check-egress.sh で確認する。旧 10.10.0.2 なら §2 手順 3 で移設する |
| 54分付近 / 7h12m付近に切れる | CHILD / IKE rekey をログと --list-sas で別々に確認。初回成功だけで PFS・rekey が合ったとは判定しない |
接続テストの送受信がない時間に、再起動後の Secret 再取得・SNAT 永続化・SA 自動開始を確認する。
次に管理下の peer 到達不能試験で再送3回と90sの断念、復旧後の再接続を journal の時刻で測る。
close_action=start は明示 DELETE にだけ作用し、keyingtries=0 も恒久的な認証エラーは救済しない。
失敗原因を直した後は swanctl --initiate --child jwnet で再試行する。
記録するのは日時・Debian / strongSwan 版・テンプレート commit・非秘密 IP / ID・SA 状態・ 提案・カウンタ・TCP 判定・再送/rekey の時刻・未確認項目の解消結果。 PSK・全銀認証値・鍵素材・通信 payload を証跡に混ぜない。
リポジトリ側の検証¶
pnpm --filter @workspace/scripts test -- jwnet-ipsec/templates.test.mjs
pnpm verify
TZ=UTC pnpm turbo run test
mkdocs build --strict
git diff --check
templates.test.mjs は有効な設定行の peer / ID / proposal / TS / SNAT と寿命、
PFS なし、秘密のプレースホルダを固定する。ESP に modp2048 を追加する変異と、
placeholder を非秘密のダミー実値に置換する変異は、それぞれテストの exit code が非0になることを
実測して戻す。秘密検出はラベル付き値のラチェットと gitleaks の併用であり、
任意の無ラベル文字列が秘密かどうかを判定できるものではない。
ローカル ループバック試験(接続テスト前)¶
scripts/jwnet-ipsec/loopback/run.sh は Docker に client / router / NAT / center を作り、
§3–4 のテンプレートを使って Linux XFRM の往復を検査する。router の jwnet.conf は
local_addrs をコンテナの実 NIC 172.30.121.2 へ置換する以外、全バイトを保持する。
内側 NIC は 10.100.121.2、SNAT の .1 は NIC / alias / loopback に割り当てない。
center の endpoint は .13、内側 TCP listener は .26:5020。
中間 NAT の .14 が center に見える送信元になる。
前提は Docker Desktop または Linux Docker(XFRM / ESP-in-UDP / iptables policy match 対応)、
Docker Compose v2 以降、Node 22 以降。初回は Debian 12 / strongSwan 5.9 系のイメージをビルドする。
Docker の /proc/sys が NET_ADMIN だけでは read-only のため、sysctl を逐語適用する router
だけ privileged で起動する。他 3 台は NET_ADMIN / NET_RAW。ホストのポートは公開しない。
Docker の internal bridge はサブネット外宛転送も落とすので通常の bridge を使い、
全コンテナの default route を撤去して、ハーネス内の明示ルートだけを残す。
ホストの経路・firewall は変更しない。固定 subnet を使うため同時実行はできない。
bash scripts/jwnet-ipsec/loopback/run.sh
# 失敗調査のため保持するときだけ(秘密はコンテナ tmpfs に残る)
bash scripts/jwnet-ipsec/loopback/run.sh --keep
bash scripts/jwnet-ipsec/loopback/down.sh
各項目は順に PASS / FAIL、最後に総合判定を出す。失敗は終了コード 1。
ビルド後の所要時間は約 4 分。既定で下表の rekey まで実行する。
--skip-rekey の場合はその項目を SKIP と表示する。
通常は成功・失敗・SIGINT/SIGTERM のいずれでもコンテナとネットワークを撤去する。
--keep または強制終了後は down.sh を実行する(pause 中なら unpause してから撤去)。
| PASS 項目 | 実測する内容 |
|---|---|
| load | swanctl load-all の成功、list-conns の jwnet、テンプレートの配置 |
| IKE_SA | ESTABLISHED / IKEv2、AES256-CBC / SHA256 / PRF-SHA256 / DH14、FQDN / IPv4 の期待 ID による相互認証 |
| CHILD_SA | INSTALLED / TUNNEL、ESP AES256-CBC / SHA256・DH なし、TS がちょうど /24 === /30 |
| NAT-T | UDP4500 と center が観測した NAT 後 endpoint、NAT 転送カウンタ / conntrack |
| SNAT | client の TCP echo 成功、listener の source .1、SNAT hit と CHILD in/out bytes 増加 |
| plaintext | terminate 後に client / router の TCP を REJECT、FORWARD / OUTPUT hit、NAT / center の /30 平文と listener 増加が 0 |
| DPD/recovery | center pause、実 journal の再送 6/18/42s・断念 90s(各 ±3s)、自動再接続で 2–5 が再成立 |
| rekey | runtime の後読み override のみで CHILD を 45s/120s に短縮し、70 秒の単一 TCP セッションを保ったまま自動更新 |
ランダム PSK を実行ごとに生成して両側へ stdin で渡し、/run tmpfs の 0600 ファイルにだけ置く。
設定票の実 PSK・全銀認証値は使わない。証跡に PSK・鍵素材・実電文は含めない。
router の retransmit / SNAT / filter / sysctl はテンプレートをそのまま適用する。
rekey の短縮値は最後の試験だけに使い、元のファイルへは書き戻さない。
静的回帰は scripts/jwnet-ipsec/loopback.test.mjs(置換の差分制限・鏡像設定・秘密検出・
実測値の判定)で、pnpm test は Docker を起動しない。特権 Docker が必要なため CI 登録はしない。
このハーネスが保証しないこと: 実センターの ID 型 / ACL / DPD 実装差 / 期間外の受付。
§1 の未確認項目は、この PASS だけでは解消しない。
GCP の実際の 1:1 NAT / VPC route / firewall / Windows、Secret Manager / IAM、
systemd unit の起動順・再起動永続化、MTU / 大容量転送、実時間の 1h / 8h 寿命、IKE rekey も別検査。
コンテナでは charon-systemd と systemd-journald を直接起動する。
strongSwan の journal 動作
に従って通常レベルのログを採取するが、GCP の本番 unit / Secret 取得経路は再現しない。
TCP listener は拡張 Z 手順や全銀認証を実装しないため、接続テスト合格には §8 の後の
Biware 送受信・HR1 判定が引き続き必要である。