diff options
Diffstat (limited to 'vpn')
| -rw-r--r-- | vpn/.gitignore | 2 | ||||
| -rw-r--r-- | vpn/Dockerfile | 7 | ||||
| -rw-r--r-- | vpn/deploy.sh | 11 | ||||
| -rw-r--r-- | vpn/docker-compose.yml | 14 | ||||
| -rw-r--r-- | vpn/entrypoint.sh | 54 | ||||
| -rw-r--r-- | vpn/install.sh | 59 | ||||
| -rw-r--r-- | vpn/renew-cert.sh | 18 | ||||
| -rw-r--r-- | vpn/strongswan.conf | 15 | ||||
| -rw-r--r-- | vpn/swanctl.conf.tpl | 41 |
9 files changed, 221 insertions, 0 deletions
diff --git a/vpn/.gitignore b/vpn/.gitignore new file mode 100644 index 0000000..b68b01a --- /dev/null +++ b/vpn/.gitignore @@ -0,0 +1,2 @@ +certs/ +users.conf diff --git a/vpn/Dockerfile b/vpn/Dockerfile new file mode 100644 index 0000000..86644be --- /dev/null +++ b/vpn/Dockerfile @@ -0,0 +1,7 @@ +FROM alpine:3.21 +RUN apk add --no-cache strongswan iptables iproute2 gettext +COPY swanctl.conf.tpl /etc/swanctl/swanctl.conf.tpl +COPY strongswan.conf /etc/strongswan.conf +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/vpn/deploy.sh b/vpn/deploy.sh new file mode 100644 index 0000000..0005d9a --- /dev/null +++ b/vpn/deploy.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" + +# 初期化前はスキップ(install.sh を先に実行する必要がある) +if [ ! -f users.conf ] || [ ! -f certs/server.crt ]; then + echo "VPN: 未初期化のためスキップ (install.sh を実行してください)" + exit 0 +fi + +docker compose up -d --build diff --git a/vpn/docker-compose.yml b/vpn/docker-compose.yml new file mode 100644 index 0000000..83ff270 --- /dev/null +++ b/vpn/docker-compose.yml @@ -0,0 +1,14 @@ +services: + vpn: + build: . + restart: unless-stopped + network_mode: host + cap_add: + - NET_ADMIN + - SYS_MODULE + volumes: + - ./certs/server.crt:/etc/swanctl/x509/server.crt:ro + - ./certs/server.key:/etc/swanctl/private/server.key:ro + - ./users.conf:/etc/strongswan/users.conf:ro + environment: + - VPN_DOMAIN=vpn.yyamashita.com diff --git a/vpn/entrypoint.sh b/vpn/entrypoint.sh new file mode 100644 index 0000000..4efe500 --- /dev/null +++ b/vpn/entrypoint.sh @@ -0,0 +1,54 @@ +#!/bin/sh +set -e + +# IP フォワーディング有効化 +sysctl -w net.ipv4.ip_forward=1 +sysctl -w net.ipv6.conf.all.forwarding=1 + +# VPN クライアントのトラフィックを MASQUERADE +IFACE=$(ip route show default | awk '/default/ {print $5}' | head -1) +iptables -t nat -C POSTROUTING -s 10.10.0.0/24 -o "${IFACE}" -j MASQUERADE 2>/dev/null || \ + iptables -t nat -A POSTROUTING -s 10.10.0.0/24 -o "${IFACE}" -j MASQUERADE +iptables -C FORWARD -s 10.10.0.0/24 -j ACCEPT 2>/dev/null || \ + iptables -A FORWARD -s 10.10.0.0/24 -j ACCEPT +iptables -C FORWARD -d 10.10.0.0/24 -j ACCEPT 2>/dev/null || \ + iptables -A FORWARD -d 10.10.0.0/24 -j ACCEPT + +# users.conf から EAP シークレットセクションを生成 +VPN_SECRETS="" +while IFS=' ' read -r user pass || [ -n "$user" ]; do + [ -z "$user" ] && continue + case "$user" in \#*) continue ;; esac + VPN_SECRETS="${VPN_SECRETS} + eap-${user} { + id = ${user} + secret = \"${pass}\" + }" +done < /etc/strongswan/users.conf + +export VPN_SECRETS +export VPN_DOMAIN="${VPN_DOMAIN:-vpn.yyamashita.com}" + +# テンプレートから swanctl.conf を生成 +envsubst < /etc/swanctl/swanctl.conf.tpl > /etc/swanctl/swanctl.conf + +# charon (IKE デーモン) 起動 +ipsec start + +# 初期化待ち +sleep 2 + +# 設定ロード +swanctl --load-conns +swanctl --load-creds --noprompt +swanctl --load-pools + +echo "VPN ready. Domain: ${VPN_DOMAIN}" + +# シグナルハンドラ +trap 'ipsec stop; exit 0' TERM INT + +# charon が生きている間ループ +while ipsec status > /dev/null 2>&1; do + sleep 10 +done diff --git a/vpn/install.sh b/vpn/install.sh new file mode 100644 index 0000000..9a2f33d --- /dev/null +++ b/vpn/install.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" + +DOMAIN="vpn.yyamashita.com" +CADDY_DIR="/app/infra/caddy" +CERT_DIR="./certs" +CADDY_CERT_BASE="/data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}/${DOMAIN}" + +echo "=== VPN Install: ${DOMAIN} ===" + +# users.conf チェック +if [ ! -f users.conf ]; then + echo "" + echo "Error: users.conf が見つかりません。先に作成してください:" + echo " echo 'username password' > /app/infra/vpn/users.conf" + echo " chmod 600 /app/infra/vpn/users.conf" + echo "" + echo "複数ユーザーは1行ずつ追加:" + echo " user1 pass1" + echo " user2 pass2" + exit 1 +fi + +mkdir -p "$CERT_DIR" + +# Caddy コンテナから証明書を取得 +echo "Caddy から証明書を取得中..." +if ! docker compose -f "$CADDY_DIR/docker-compose.yml" exec -T caddy \ + cat "${CADDY_CERT_BASE}.crt" > "$CERT_DIR/server.crt"; then + echo "" + echo "Error: 証明書の取得に失敗しました。" + echo "Caddyfile に ${DOMAIN} が追加されているか確認してください。" + echo "追加後 'git push origin master' してから再実行してください。" + exit 1 +fi + +docker compose -f "$CADDY_DIR/docker-compose.yml" exec -T caddy \ + cat "${CADDY_CERT_BASE}.key" > "$CERT_DIR/server.key" + +chmod 600 "$CERT_DIR/server.key" +echo "証明書を $CERT_DIR/ に保存しました。" + +# VPN コンテナ起動 +echo "VPN サービスを起動中..." +docker compose up -d --build + +echo "" +echo "=== VPN 起動完了 ===" +echo "" +echo "クライアント接続情報:" +echo " サーバー: ${DOMAIN}" +echo " 種別: IKEv2" +echo " 認証: EAP (ユーザー名/パスワード)" +echo "" +echo "iOS/macOS: 設定 > VPN > VPN 構成を追加 > IKEv2" +echo "Android (Quest/FireTV): strongSwan VPN Client アプリをインストール" +echo "" +echo "証明書の更新: bash /app/infra/vpn/renew-cert.sh" diff --git a/vpn/renew-cert.sh b/vpn/renew-cert.sh new file mode 100644 index 0000000..50f8784 --- /dev/null +++ b/vpn/renew-cert.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" + +DOMAIN="vpn.yyamashita.com" +CADDY_DIR="/app/infra/caddy" +CADDY_CERT_BASE="/data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}/${DOMAIN}" + +echo "証明書を更新中..." + +docker compose -f "$CADDY_DIR/docker-compose.yml" exec -T caddy \ + cat "${CADDY_CERT_BASE}.crt" > certs/server.crt +docker compose -f "$CADDY_DIR/docker-compose.yml" exec -T caddy \ + cat "${CADDY_CERT_BASE}.key" > certs/server.key +chmod 600 certs/server.key + +docker compose exec vpn swanctl --load-creds --noprompt +echo "完了。証明書を更新してリロードしました。" diff --git a/vpn/strongswan.conf b/vpn/strongswan.conf new file mode 100644 index 0000000..b42f8c0 --- /dev/null +++ b/vpn/strongswan.conf @@ -0,0 +1,15 @@ +charon { + load_modular = yes + plugins { + include strongswan.d/charon/*.conf + } + filelog { + stderr { + default = 1 + ike = 2 + knl = 3 + } + } + send_vendor_id = no +} +include strongswan.d/*.conf diff --git a/vpn/swanctl.conf.tpl b/vpn/swanctl.conf.tpl new file mode 100644 index 0000000..f3a9e2e --- /dev/null +++ b/vpn/swanctl.conf.tpl @@ -0,0 +1,41 @@ +connections { + ikev2-eap { + version = 2 + # iOS/macOS/Android 互換の暗号スイート + proposals = aes256gcm16-prfsha256-ecp256,aes256-sha256-ecp256,aes256gcm16-prfsha384-ecp384,aes256-sha256-modp2048,aes256-sha1-modp2048 + + local { + auth = pubkey + certs = server.crt + id = ${VPN_DOMAIN} + } + + remote { + auth = eap-mschapv2 + eap_id = %any + } + + children { + vpn { + local_ts = 0.0.0.0/0 + esp_proposals = aes256gcm16,aes256-sha256,aes256-sha1 + dpd_action = clear + } + } + + pools = pool4 + send_certreq = no + dpd_delay = 30s + } +} + +pools { + pool4 { + addrs = 10.10.0.0/24 + dns = 1.1.1.1,8.8.8.8 + } +} + +secrets { +${VPN_SECRETS} +} |
