WireGuardを使用したVPNを構築する

2025-03-08

WireGuard

WireGuardは、現代的な暗号技術で低遅延・高スループットを実現した、次世代の軽量なOSS VPNプロトコルです。

最新の暗号アルゴリズムを採用していながら低遅延であり、またコードベースが小さいためカーネルで動作する軽量な点が特徴としてあります。

この記事ではWireGuardのホストサーバをAWSのEC2で作ります。シェルスクリプト1つで世界中のリージョンにサーバを立てれるようにしました。またソースコードはAIと作りました。

準備

IAMからEC2インスタンスを作るユーザーとそれにアタッチするロールを作成します。

EC2インスタンスにアタッチするロールvpn_launch_roleにはAmazonSSMManagedInstanceCoreを付与します。SSMで手元と通信してコンフィグのダウンロードとハートビードを送ります

実行ユーザーにはAmazonEC2FullAccess, AmazonSSMFullAccessとロールvpn_launch_roleをパスするためのインラインポリシーをアタッチします。

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::829282863407:role/vpn_launch_role"
}
]
}

実行ユーザを実行するCLIにaws configureで設定しましょう。

実行

適当にコピーとかして実行します。Bashで作ってます。 引数にリージョン名を追加してリージョンを指定します。

実行するとカレントディレクトリにvpn-{region}.confファイルが作成されます。それをクライアントアプリに投げ込めば接続できます!

:::message インスタンス消すの忘れないでねー :::

# generated by Gemini
#!/bin/bash
# --- 設定項目 ---
REGION=$1
SG_NAME="WireguardSG"
ROLE_NAME="vpn_launch_role"
INSTANCE_TYPE="t3.nano"
# リージョン指定の確認
if [ -z "$REGION" ]; then
echo "Usage: ./vpn.sh <region-name>"
echo "Example: ./vpn.sh us-west-2"
exit 1
fi
echo "=================================================="
echo " Wireguard All-in-One Deployer: ${REGION}"
echo "=================================================="
# 1. セキュリティグループの確認と自動作成
echo "[1/5] Checking Security Group in ${REGION}..."
SG_ID=$(aws ec2 describe-security-groups \
--region "$REGION" \
--filters "Name=group-name,Values=$SG_NAME" \
--query 'SecurityGroups[0].GroupId' \
--output text 2>/dev/null)
if [ "$SG_ID" == "None" ] || [ -z "$SG_ID" ]; then
echo "Creating new Security Group: ${SG_NAME}..."
SG_ID=$(aws ec2 create-security-group \
--group-name "$SG_NAME" \
--description "Wireguard VPN Group" \
--region "$REGION" \
--query 'GroupId' \
--output text)
# UDP 51820番ポートを解放
aws ec2 authorize-security-group-ingress \
--group-id "$SG_ID" \
--protocol udp \
--port 51820 \
--cidr 0.0.0.0/0 \
--region "$REGION"
echo "Security Group created: ${SG_ID}"
else
echo "Using existing Security Group: ${SG_ID}"
fi
# 2. EC2インスタンスの起動
echo "[2/5] Launching EC2 instance with Docker..."
USER_DATA=$(cat <<EOF
#!/bin/bash
dnf update -y
dnf install -y docker
systemctl start docker
systemctl enable docker
docker run -d \
--name=wireguard \
--cap-add=NET_ADMIN \
--cap-add=SYS_MODULE \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Asia/Tokyo \
-e SERVERURL=auto \
-e PEERS=1 \
-e PEERDNS=auto \
-p 51820:51820/udp \
-v /config:/config \
--restart unless-stopped \
linuxserver/wireguard
EOF
)
INSTANCE_ID=$(aws ec2 run-instances \
--region "$REGION" \
--image-id resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
--count 1 \
--instance-type "$INSTANCE_TYPE" \
--security-group-ids "$SG_ID" \
--iam-instance-profile Name="$ROLE_NAME" \
--user-data "$USER_DATA" \
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=Wireguard-$REGION}]" \
--query 'Instances[0].InstanceId' \
--output text)
if [ -z "$INSTANCE_ID" ] || [ "$INSTANCE_ID" == "None" ]; then
echo "ERROR: Failed to launch instance. Check your IAM permissions (PassRole etc.)."
exit 1
fi
echo "Instance ID: ${INSTANCE_ID}"
# 3. SSM AgentがOnlineになるのを待機
echo "[3/5] Waiting for SSM Agent to come online..."
while true; do
# インスタンスがSSM一覧に現れ、かつPingStatusがOnlineになるのを待つ
STATUS=$(aws ssm describe-instance-information \
--region "$REGION" \
--filters "Key=InstanceIds,Values=$INSTANCE_ID" \
--query "InstanceInformationList[0].PingStatus" \
--output text 2>/dev/null)
if [ "$STATUS" == "Online" ]; then
echo -e "\nSSM Agent is Online."
break
fi
printf "."
sleep 10
done
# 4. 設定ファイルの生成待ちと取得コマンド発行
echo "[4/5] Retrieving Wireguard config (waiting for generation)..."
# コンテナ内でファイルができるまでループし、できたらcatするスクリプトをSSMで送る
COMMAND_ID=$(aws ssm send-command \
--region "$REGION" \
--instance-ids "$INSTANCE_ID" \
--document-name "AWS-RunShellScript" \
--parameters 'commands=["for i in {1..30}; do if [ -f /config/peer1/peer1.conf ]; then docker exec wireguard cat /config/peer1/peer1.conf && exit 0; fi; sleep 5; done; echo \"Timeout waiting for config\"; exit 1"]' \
--query "Command.CommandId" \
--output text)
# AWS側でコマンドが完了するのを待機
aws ssm wait command-executed \
--region "$REGION" \
--command-id "$COMMAND_ID" \
--instance-id "$INSTANCE_ID"
# 5. ローカルファイルへの保存
echo "[5/5] Downloading configuration file..."
OUT_FILE="vpn-${REGION}.conf"
aws ssm list-command-invocations \
--region "$REGION" \
--command-id "$COMMAND_ID" \
--details \
--query "CommandInvocations[0].CommandPlugins[0].Output" \
--output text > "$OUT_FILE"
# ファイルが空でないか、"None"などのエラーメッセージでないか確認
if [ -s "$OUT_FILE" ] && ! grep -q "Timeout" "$OUT_FILE"; then
echo "=================================================="
echo " SUCCESS!"
echo " Region: ${REGION}"
echo " Config: ${OUT_FILE}"
echo "=================================================="
else
echo "ERROR: Config retrieval failed. The file is empty or timed out."
rm -f "$OUT_FILE"
fi

あとがき

AIに作らせたものって創造性が無くてあまり好きではないのですが(特に絵)、こうして実用的なものができた時、思わず共有したくなります。 大方ブラックボックス化してしまっていますが、何とかAWS CLIができるようにしたいなーと所感。

本当はNetflixでジブリが見たかったんですけど、もう少し何か対策が必要そうです。 とりあえず学校からマイクラできて神✨✨