コンテンツにスキップ

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-ipIN_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_USEcanIpForward=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.secretAccessorcloud-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-vmjwnet-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 createfirewall-rules create

既存環境 kyoei-jwnet-gw(2026-09-07 実測)への差分適用

ルータ VM は作成済みで、jwnet-ipsec-subnet = 10.100.121.0/2410.100.121.2 にある。 旧 jwnet-subnet = 10.10.0.0/24 は残存し、Windows VM はその 10.10.0.2 にある。 以下は Cloud Shell で順に実施する。作成済み VM / VPC / サブネット / 固定 IP を再作成しない。

export PROJECT_ID='kyoei-jwnet-gw'

手順 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.shBiware 判定で失敗するのが正常で、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 の ipconfig10.100.121.x であることを確認する。 --private-network-ip と自動割当の動作は network-interfaces updateも参照する。

手順 4 — タグと TCP 5020 の FW を追加する

CREATE_IKE_FIREWALL=0 CREATE_CENTER_ROUTE=0 bash scripts/jwnet-ipsec/gcloud/configure-network.sh

タグ付与 2 件と、Windows 移設後の実 IP /32jwnet-ipsecjwnet-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 の証跡を採る

EGRESS_ADDRESS_NAME=jwnet-vpn-ip bash scripts/jwnet-ipsec/gcloud/check-egress.sh

全判定 OK(終了コード 0)の出力を V1 の証跡にする。Static external IP: 34.84.6.34、 ルータの networkIP: 10.100.121.2Biware 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.conflocal_addrs を §2 の実 IP へ変更し、 remote.id を設定票 / 窓口で確認する。%any で ID 不一致を回避しない。

4. Linux の転送と SNAT を永続化する

Debian 12 は iptablesnf_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 が無いため、sysctlsudo 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 600journalctl -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 も秘密そのものであり、catechoset -x を使わない。 installer は --no-log-http --verbosity=error を明示し、root の永続設定に core/log_http=true があっても秘密を含む HTTP 応答を記録させない。 空値・プレースホルダ・取得失敗は起動失敗になる。

/run は再起動で消えるため、毎回 Secret Manager から読み直す。サービスの再起動は 接続を切断するので接続テストの送受信中には行わない。既存 PSK をローカルで編集しない。 旧 /etc/ipsec.secrets に PSK 実値を残している場合だけ、上の Secret 読取成功と配置権限を確認した後に消す(値は表示しない)。

sudo shred -u /etc/ipsec.secrets

Secret の out-file 出力strongSwan 5.9.8 の secret / 0s 表記

6. strongSwan キーの意味と版を確認する

確認日 2026-09-06。一次資料は strongSwan 5.9 swanctl.confupstream 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 両方 psklocal / 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 renewal5.9 algorithm proposals

DPD の再送は strongswan.d/jwnet-retransmit.confcharon-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 ログに加えて次の完了行を確認する。

initiate completed successfully

この行は 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-256HMAC_SHA2_256_128PRF_HMAC_SHA2_256MODP_2048、ESP が AES_CBC-256HMAC_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 接続の失敗としない。

route print
Test-NetConnection 210.164.154.26 -Port 5020 -InformationLevel Detailed

期待は 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 apparmorname="/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.conflocal_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-systemdsystemd-journald を直接起動する。 strongSwan の journal 動作 に従って通常レベルのログを採取するが、GCP の本番 unit / Secret 取得経路は再現しない。 TCP listener は拡張 Z 手順や全銀認証を実装しないため、接続テスト合格には §8 の後の Biware 送受信・HR1 判定が引き続き必要である。