From d38c4770ac309f9d6caa4612ae9dae765ddffcf2 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 6 Apr 2025 00:08:17 +0800 Subject: [PATCH 1/5] feat: Add VXLAN over WireGuard support with site automation - Enabled VXLAN over L3 via WireGuard tunnel; - Standardized Ansible role structure for `vpn-overlay`; - Integrated systemd service management via `vxlan-setup.service`; - Configured per-site VXLAN overlay in `config/sit/vpn-overlay.yaml`; --- .../vhosts/common/files/install-packages.sh | 2 +- .../vpn-overlay/vxlan/site/defaults/main.yml | 7 ++ .../vxlan/site/files/setup_sit_vxlan.sh | 76 +++++++++++++++++++ .../vpn-overlay/vxlan/site/tasks/main.yml | 54 +++++++++++++ .../site/templates/vxlan-setup.service.j2 | 12 +++ ansible/playbooks/vpn-overlay.yaml | 8 ++ config/{sites.yaml => sit/vpn-overlay.yaml} | 35 +++++---- 7 files changed, 175 insertions(+), 19 deletions(-) create mode 100644 ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/defaults/main.yml create mode 100644 ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh create mode 100644 ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/tasks/main.yml create mode 100644 ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/templates/vxlan-setup.service.j2 create mode 100644 ansible/playbooks/vpn-overlay.yaml rename config/{sites.yaml => sit/vpn-overlay.yaml} (56%) diff --git a/ansible/playbooks/roles/vhosts/common/files/install-packages.sh b/ansible/playbooks/roles/vhosts/common/files/install-packages.sh index 3ff7ff5b..e3967b6e 100644 --- a/ansible/playbooks/roles/vhosts/common/files/install-packages.sh +++ b/ansible/playbooks/roles/vhosts/common/files/install-packages.sh @@ -2,4 +2,4 @@ export DEBIAN_FRONTEND=noninteractive sudo apt-get update -sudo apt-get install -y vim inetutils-ping +sudo apt-get install -y vim iputils-ping rsync diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/defaults/main.yml b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/defaults/main.yml new file mode 100644 index 00000000..6d9fb4f3 --- /dev/null +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/defaults/main.yml @@ -0,0 +1,7 @@ +--- +# Default VXLAN settings for site nodes +vxlan_dev_if: wg0 +vxlan_vni: 100 +vxlan_cidr_suffix: 16 +overlay_config_path: config/sit/vpn-overlay.yaml + diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh new file mode 100644 index 00000000..b1dc3619 --- /dev/null +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# 安全增强版 VXLAN Overlay 脚本(支持 wg0 作为通道设备) +# 用法: ./setup_sit_vxlan.sh [cidr_suffix] [vxlan_id] [mtu] + +set -e + +DEV_IF="$1" +LOCAL_IP="$2" +REMOTE_IP="$3" +BRIDGE_IP="$4" +CIDR_SUFFIX="${5:-16}" +VNI="${6:-100}" +MTU="${7:-1400}" + +if [[ -z "$DEV_IF" || -z "$LOCAL_IP" || -z "$REMOTE_IP" || -z "$BRIDGE_IP" ]]; then + echo "Usage: $0 [cidr_suffix] [vxlan_id] [mtu]" + exit 1 +fi + +VXLAN_IF="vxlan${VNI}" +BR_IF="br0" +BRIDGE_CIDR="${BRIDGE_IP}/${CIDR_SUFFIX}" +SUBNET="$(echo "$BRIDGE_IP" | cut -d. -f1-2).0.0/${CIDR_SUFFIX}" + +# 自动判断 dev 是否能用于 VXLAN(需支持广播) +function is_vxlan_dev_usable() { + [[ -d "/sys/class/net/$1" ]] && grep -q "broadcast" "/sys/class/net/$1/flags" +} + +echo "🔍 检查 $DEV_IF 是否可用于 VXLAN..." +USE_DEV_PARAM=true +if ! is_vxlan_dev_usable "$DEV_IF"; then + echo "⚠️ $DEV_IF 不支持广播,将省略 dev 参数(通过路由走隧道)" + USE_DEV_PARAM=false +fi + +# 清理旧接口 +for iface in "$VXLAN_IF" "$BR_IF"; do + ip link show "$iface" &>/dev/null && ip link set "$iface" down && ip link del "$iface" +done + +# 创建 VXLAN 接口 +echo "🛠️ 创建 VXLAN 接口 $VXLAN_IF ..." +if $USE_DEV_PARAM; then + ip link add "$VXLAN_IF" type vxlan id "$VNI" dstport 4789 local "$LOCAL_IP" remote "$REMOTE_IP" dev "$DEV_IF" +else + ip link add "$VXLAN_IF" type vxlan id "$VNI" dstport 4789 local "$LOCAL_IP" remote "$REMOTE_IP" +fi +ip link set "$VXLAN_IF" mtu "$MTU" +ip link set "$VXLAN_IF" up + +# 创建 br0 桥 +ip link add "$BR_IF" type bridge +ip link set "$BR_IF" mtu "$MTU" +ip link set "$VXLAN_IF" master "$BR_IF" +ip link set "$BR_IF" up + +# 配置 IP +ip addr add "$BRIDGE_CIDR" dev "$BR_IF" + +# 启用转发 + SNAT(仅主机出网时) +sysctl -w net.ipv4.ip_forward=1 +iptables -t nat -C POSTROUTING -s "$SUBNET" -o "$DEV_IF" -j MASQUERADE 2>/dev/null || \ +iptables -t nat -A POSTROUTING -s "$SUBNET" -o "$DEV_IF" -j MASQUERADE + +# 尝试激活邻居学习 +REMOTE_BR_IP="172.16.0.$(( $(echo "$BRIDGE_IP" | awk -F. '{print $4}') == 2 ? 3 : 2 ))" +ping -c 1 "$REMOTE_BR_IP" || true + +# 展示结果 +echo "" +echo "✅ VXLAN Overlay 已建立" +echo " - bridge: $BR_IF ($BRIDGE_CIDR)" +echo " - vxlan: $VXLAN_IF ($LOCAL_IP ⇄ $REMOTE_IP, id=$VNI, mtu=$MTU)" +echo " - SNAT: $SUBNET → $DEV_IF" + diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/tasks/main.yml b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/tasks/main.yml new file mode 100644 index 00000000..691a3aaa --- /dev/null +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/tasks/main.yml @@ -0,0 +1,54 @@ +--- +- name: Load site overlay config + set_fact: + overlay_data: "{{ lookup('file', overlay_config_path) | from_yaml }}" + +- name: Select current site from config + set_fact: + current_site: "{{ overlay_data.sites | selectattr('name', 'equalto', inventory_hostname) | list | first }}" + +- name: Fail if no matching site found + fail: + msg: "当前主机 {{ inventory_hostname }} 未在 config 中定义" + when: current_site is not defined + +- name: Select first hub as default + set_fact: + selected_hub: "{{ overlay_data.hubs[0] }}" + +- name: Set VXLAN parameters + set_fact: + vxlan_dev_if: "{{ current_site.interface}}" + vxlan_local_ip: "{{ current_site.local_ip }}" + vxlan_remote_ip: "{{ current_site.remote_ip }}" + vxlan_br_ip: "{{ current_site.br_ip }}" + vxlan_cidr_suffix: "{{ overlay_data.vxlan_cidr_suffix | default(16) }}" + vxlan_vni: "{{ overlay_data.vxlan_id | default(100) }}" + +- name: 使用 rsync 分发 VXLAN 脚本 + synchronize: + src: "files/setup_sit_vxlan.sh" + dest: /tmp/setup_sit_vxlan.sh + mode: push + +- name: 移动脚本并赋权到 /usr/local/bin + shell: | + mv /tmp/setup_sit_vxlan.sh /usr/local/bin/setup_sit_vxlan.sh + chmod +x /usr/local/bin/setup_sit_vxlan.sh + become: true + +- name: Render systemd unit for VXLAN + template: + src: vxlan-setup.service.j2 + dest: /etc/systemd/system/vxlan-setup.service + mode: '0644' + +- name: Reload systemd daemon + systemd: + daemon_reload: true + +- name: Enable and start VXLAN overlay setup + systemd: + name: vxlan-setup + enabled: true + state: started diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/templates/vxlan-setup.service.j2 b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/templates/vxlan-setup.service.j2 new file mode 100644 index 00000000..d9313f02 --- /dev/null +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/templates/vxlan-setup.service.j2 @@ -0,0 +1,12 @@ +[Unit] +Description=VXLAN Overlay Setup for {{ inventory_hostname }} +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/setup_sit_vxlan.sh {{ vxlan_dev_if }} {{ vxlan_local_ip }} {{ vxlan_remote_ip }} {{ vxlan_br_ip }} {{ vxlan_cidr_suffix }} {{ vxlan_vni }} +RemainAfterExit=true + +[Install] +WantedBy=multi-user.target diff --git a/ansible/playbooks/vpn-overlay.yaml b/ansible/playbooks/vpn-overlay.yaml new file mode 100644 index 00000000..1a64ec7f --- /dev/null +++ b/ansible/playbooks/vpn-overlay.yaml @@ -0,0 +1,8 @@ +- name: Run infrastructure setup + hosts: all + become: yes + gather_facts: yes + vars: + overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml" + roles: + - vhosts/vpn-overlay/vxlan/site diff --git a/config/sites.yaml b/config/sit/vpn-overlay.yaml similarity index 56% rename from config/sites.yaml rename to config/sit/vpn-overlay.yaml index 89d96e65..1957ff1d 100644 --- a/config/sites.yaml +++ b/config/sit/vpn-overlay.yaml @@ -1,16 +1,16 @@ # 基础网络参数 -wg_network: 10.100.0.0/24 -bridge_network: 172.16.0.0/16 +wg_network: 172.16.0.0/24 +bridge_network: 10.253.0.0/16 vxlan_id: 100 hub_port: 51820 # 全局功能开关 features: - enable_gretap: true # 是否开启 gretap 桥接(站点接入 Hub) + enable_vxlan_between_sits: true # 是否开启 vxlan 桥接(站点接入 Hub) enable_vxlan_between_hubs: true # 是否开启 Hub 之间的 VXLAN Mesh - enable_vless: true # 是否通过 VLESS 中转 WG 流量 - enable_multi_hub: true # 是否支持多 Hub 架构(false 则为单 Hub star 架构) - only_wireguard: false # 若为 true,仅使用 WireGuard 点对点,忽略 gretap/vxlan + enable_vless: true # 是否通过 VLESS 中转 WG 流量 + enable_multi_hub: true # 是否支持多 Hub 架构(false 则为单 Hub star 架构) + only_wireguard: false # 若为 true,仅使用 WireGuard 点对点,忽略 gretap/vxlan # WireGuard Hub 节点配置 hubs: @@ -26,24 +26,23 @@ hubs: # 各个站点定义 sites: - - name: site_a - wg_ip: 10.100.0.2 - br_ip: 172.16.0.2 - hub: hub1 - public_ip: a.example.com + - name: master-1 + interface: ens5 + br_ip: 10.253.253.2 + local_ip: 10.255.0.200 + remote_ip: 10.255.0.100 vless: - enabled: true - endpoint: vless-a.example.com + enabled: false uuid: "11111111-1111-1111-1111-111111111111" transport: ws tls: true path: /wg - - name: site_b - wg_ip: 10.100.0.3 - br_ip: 172.16.0.3 - hub: hub1 - public_ip: b.example.com + - name: slave-1 + interface: ens5 + br_ip: 10.253.253.3 + local_ip: 10.255.0.100 + remote_ip: 10.255.0.200 vless: enabled: false # 此节点不通过 VLESS,直接访问 Hub From bfc960e4e91a6e1d8db24ddca0f32564f1e059e9 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 6 Apr 2025 12:28:58 +0800 Subject: [PATCH 2/5] feat(vxlan): support optional DNAT port mapping via EXPOSE_PORT --- .../vxlan/site/files/setup_sit_vxlan.sh | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh index b1dc3619..992620bf 100644 --- a/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh @@ -1,6 +1,6 @@ #!/bin/bash -# 安全增强版 VXLAN Overlay 脚本(支持 wg0 作为通道设备) -# 用法: ./setup_sit_vxlan.sh [cidr_suffix] [vxlan_id] [mtu] +# 安全增强版 VXLAN Overlay 脚本(支持 wg0 作为通道设备 + 可选公网 DNAT 映射) +# 用法: ./setup_sit_vxlan.sh [cidr_suffix] [vxlan_id] [mtu] [expose_port] set -e @@ -11,9 +11,10 @@ BRIDGE_IP="$4" CIDR_SUFFIX="${5:-16}" VNI="${6:-100}" MTU="${7:-1400}" +EXPOSE_PORT="${8:-443}" if [[ -z "$DEV_IF" || -z "$LOCAL_IP" || -z "$REMOTE_IP" || -z "$BRIDGE_IP" ]]; then - echo "Usage: $0 [cidr_suffix] [vxlan_id] [mtu]" + echo "Usage: $0 [cidr_suffix] [vxlan_id] [mtu] [expose_port]" exit 1 fi @@ -63,9 +64,13 @@ sysctl -w net.ipv4.ip_forward=1 iptables -t nat -C POSTROUTING -s "$SUBNET" -o "$DEV_IF" -j MASQUERADE 2>/dev/null || \ iptables -t nat -A POSTROUTING -s "$SUBNET" -o "$DEV_IF" -j MASQUERADE -# 尝试激活邻居学习 -REMOTE_BR_IP="172.16.0.$(( $(echo "$BRIDGE_IP" | awk -F. '{print $4}') == 2 ? 3 : 2 ))" -ping -c 1 "$REMOTE_BR_IP" || true +# ⚠️ 可选:仅在 EXPOSE_PORT 被定义时启用 DNAT 公网端口映射 +if [[ -n "$EXPOSE_PORT" ]]; then + echo "🌐 添加 DNAT 映射规则:公网:$EXPOSE_PORT → ${BRIDGE_IP}:443" + + iptables -t nat -C PREROUTING -p tcp -m tcp --dport "$EXPOSE_PORT" -j DNAT --to-destination "${BRIDGE_IP}:443" 2>/dev/null || \ + iptables -t nat -A PREROUTING -p tcp -m tcp --dport "$EXPOSE_PORT" -j DNAT --to-destination "${BRIDGE_IP}:443" +fi # 展示结果 echo "" @@ -73,4 +78,6 @@ echo "✅ VXLAN Overlay 已建立" echo " - bridge: $BR_IF ($BRIDGE_CIDR)" echo " - vxlan: $VXLAN_IF ($LOCAL_IP ⇄ $REMOTE_IP, id=$VNI, mtu=$MTU)" echo " - SNAT: $SUBNET → $DEV_IF" - +if [[ -n "$EXPOSE_PORT" ]]; then +echo " - DNAT: 公网:$EXPOSE_PORT → ${BRIDGE_IP}:443" +fi From bd8553f20e3035721fea3312a7964cec6726a5b1 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 6 Apr 2025 13:29:01 +0800 Subject: [PATCH 3/5] feat(vpn-overlay): add WireGuard Site role --- .../wireguard/site/defaults/main.yml | 3 ++ .../vpn-overlay/wireguard/site/tasks/main.yml | 38 +++++++++++++++++ .../wireguard/site/templates/wg0.conf.j2 | 10 +++++ ansible/playbooks/vpn-wireguard-site.yaml | 8 ++++ config/sit/vpn-keys.md | 17 ++++++++ config/sit/vpn-keys.yaml | 41 +++++++++++++++++++ config/sit/vpn-overlay.yaml | 2 + 7 files changed, 119 insertions(+) create mode 100644 ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/defaults/main.yml create mode 100644 ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/tasks/main.yml create mode 100644 ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/templates/wg0.conf.j2 create mode 100644 ansible/playbooks/vpn-wireguard-site.yaml create mode 100644 config/sit/vpn-keys.md create mode 100644 config/sit/vpn-keys.yaml diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/defaults/main.yml b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/defaults/main.yml new file mode 100644 index 00000000..6c5b4ae1 --- /dev/null +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/defaults/main.yml @@ -0,0 +1,3 @@ +wg_config_path: "/etc/wireguard/wg0.conf" +wg_interface: "wg0" +wg_port: "{{ overlay_data.hub_port | default(51820) }}" diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/tasks/main.yml b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/tasks/main.yml new file mode 100644 index 00000000..1df80855 --- /dev/null +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/tasks/main.yml @@ -0,0 +1,38 @@ +--- +- name: 加载 overlay 配置 + set_fact: + overlay_data: "{{ lookup('file', overlay_config_path) | from_yaml }}" + +- name: 加载密钥配置 + set_fact: + overlay_keys: "{{ lookup('file', overlay_keys_path) | from_yaml }}" + +- name: 提取当前节点信息(作为 current) + set_fact: + current_node: "{{ (overlay_data.sites + overlay_data.hubs) | selectattr('name', 'equalto', inventory_hostname) | list | first }}" + current_key: "{{ overlay_keys.keys | selectattr('name', 'equalto', inventory_hostname) | list | first }}" + +- name: 提取对端 peer 节点信息 + set_fact: + peer_node: "{{ (overlay_data.sites + overlay_data.hubs) | selectattr('name', 'equalto', current_node.wireguard_peer) | list | first }}" + peer_key: "{{ overlay_keys.keys | selectattr('name', 'equalto', current_node.wireguard_peer) | list | first }}" + +- name: 生成 WireGuard 参数 + set_fact: + current_wg_ip: "{{ current_node.wg_ip }}" + peer_allowed_ips: "{{ peer_node.wg_ip }}/32" + peer_endpoint: "{{ peer_node.public_ip }}:{{ overlay_data.hub_port | default(51820) }}" + +- name: 渲染 wg0.conf + template: + src: wg0.conf.j2 + dest: /etc/wireguard/wg0.conf + mode: '0600' + become: true + +- name: 启用并启动 wg-quick@wg0 + systemd: + name: wg-quick@wg0 + enabled: true + state: started + become: true diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/templates/wg0.conf.j2 b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/templates/wg0.conf.j2 new file mode 100644 index 00000000..d305f5b2 --- /dev/null +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/templates/wg0.conf.j2 @@ -0,0 +1,10 @@ +[Interface] +PrivateKey = {{ current_key.private_key }} +Address = {{ current_wg_ip }}/32 +ListenPort = {{ overlay_data.hub_port }} + +[Peer] +PublicKey = {{ peer_key.public_key }} +AllowedIPs = {{ peer_allowed_ips }} +Endpoint = {{ peer_endpoint }} +PersistentKeepalive = 25 diff --git a/ansible/playbooks/vpn-wireguard-site.yaml b/ansible/playbooks/vpn-wireguard-site.yaml new file mode 100644 index 00000000..084c8b80 --- /dev/null +++ b/ansible/playbooks/vpn-wireguard-site.yaml @@ -0,0 +1,8 @@ +- name: Setup WireGuard for site + hosts: overlay_sites + become: true + vars: + overlay_config_path: "config/sit/vpn-overlay.yaml" + overlay_keys_path: "config/sit/vpn-keys.yaml" + roles: + - vhosts/vpn-overlay/wireguard/site diff --git a/config/sit/vpn-keys.md b/config/sit/vpn-keys.md new file mode 100644 index 00000000..6aa00420 --- /dev/null +++ b/config/sit/vpn-keys.md @@ -0,0 +1,17 @@ +只加密 private_key 字段 +1. 原始 vpn-keys.yaml +yaml +keys: + - name: master-1 + private_key: + public_key: +2. 使用 ansible-vault encrypt_string 加密 private_key +bash +ansible-vault encrypt_string 'my-secret-private-key' --name 'private_key' +示例输出(加密后是 YAML 结构): + +yaml +private_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 62326432376162336462343864333933356363373235623262306463326432363737623732613763 + 3962613662616565393463343030653733623066626137610a313465323462623261303031323337 diff --git a/config/sit/vpn-keys.yaml b/config/sit/vpn-keys.yaml new file mode 100644 index 00000000..1a34e287 --- /dev/null +++ b/config/sit/vpn-keys.yaml @@ -0,0 +1,41 @@ +keys: + - name: master-1 + private_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 35363663306235633966383634393535366462633039383137316163653166643832376264653965 + 3163363463663535363734343135643061653834353134610a393465636431346635643231613632 + 66393139656238333637353132663937633632396230313438656135353066383234353139343666 + 6230626332326437310a626639363063613062616239623734333131633865393063343030353133 + 32323465393434343634646238636531646230356365343264656364333132316636353338353133 + 6261663131646237316635306438613938396639383439653665 + public_key: + + - name: slave-1 + private_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 64393432666564616632613366663264326338643931653966653961643362306562643537663636 + 6434343630366165623261623363376333306339333164360a353037383261616335623561386538 + 64633762323939656633313433306330333533373434323664393965373038623431653539633561 + 3461626530613863640a353931346639653961303136303763303432653532636438636532613936 + 64313264643465303663646135383537623930626137656264323433373536326530613131363134 + 6334613964626261323838616534623639616133303564653063 + public_key: + + - name: site_c + private_key: + public_key: + + - name: hub1 + private_key: + public_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 38336537383061383333643431643261343739323864316235303366623930633366336139386636 + 6162336232336533636134353863386233303631626363360a376533336664636661373933623230 + 34333765346661383335663034393561646436333135613838373438396336633061396533613061 + 3031326364353036630a373862396266653961346334663139626633313362656131663163383563 + 34376231306239636536313830333962323934343035333263643234363363396164626366353061 + 3833613132373666303563623863373735396566666239316536 + + - name: hub2 + private_key: + public_key: diff --git a/config/sit/vpn-overlay.yaml b/config/sit/vpn-overlay.yaml index 1957ff1d..b070cc36 100644 --- a/config/sit/vpn-overlay.yaml +++ b/config/sit/vpn-overlay.yaml @@ -28,6 +28,7 @@ hubs: sites: - name: master-1 interface: ens5 + wireguard_peer: hub-1 br_ip: 10.253.253.2 local_ip: 10.255.0.200 remote_ip: 10.255.0.100 @@ -40,6 +41,7 @@ sites: - name: slave-1 interface: ens5 + wireguard_peer: hub-1 br_ip: 10.253.253.3 local_ip: 10.255.0.100 remote_ip: 10.255.0.200 From 31c915eb1f195ec5d26eed73f9a6434bd1bda844 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 6 Apr 2025 13:36:28 +0800 Subject: [PATCH 4/5] feat(config): add ignore_files support to config loader --- iac_modules/pulumi/utils/config_loader.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/iac_modules/pulumi/utils/config_loader.py b/iac_modules/pulumi/utils/config_loader.py index b622be6a..006705e7 100644 --- a/iac_modules/pulumi/utils/config_loader.py +++ b/iac_modules/pulumi/utils/config_loader.py @@ -14,22 +14,29 @@ def deep_merge(dict1, dict2): result[k] = v return result -def load_merged_config(config_dir=None): +def load_merged_config(config_dir=None, ignore_files=None): config_dir = config_dir or os.environ.get("CONFIG_PATH", "config") + ignore_files = set(ignore_files or ['vpn-keys.yaml']) # 默认忽略敏感 key 文件 if not os.path.isdir(config_dir): raise FileNotFoundError(f"❌ 配置目录不存在: {config_dir}") merged = {} - files = sorted(glob.glob(os.path.join(config_dir, "*.yaml")) + glob.glob(os.path.join(config_dir, "*.yml"))) + files = sorted( + glob.glob(os.path.join(config_dir, "*.yaml")) + glob.glob(os.path.join(config_dir, "*.yml")) + ) if not files: raise FileNotFoundError(f"⚠️ 未找到任何 YAML 配置文件于: {config_dir}") for file in files: + filename = os.path.basename(file) + if filename in ignore_files: + continue + with open(file) as f: part = yaml.safe_load(f) or {} merged = deep_merge(merged, part) - merged["__config_path__"] = config_dir # 可选调试字段 + merged["__config_path__"] = config_dir return merged From 5be8955ff5ea93a1c2ca023f60a2fd1bd998ff7c Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 6 Apr 2025 17:50:32 +0800 Subject: [PATCH 5/5] feat: enhance WireGuard VPN Overlay deployment and DNAT support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Fixed wg0.conf.j2 template variables - Refactored wireguard/site logic to support dynamic peer/key selection - Updated defaults, playbook entry, and install script for consistency Tested: ✅ NodePort 443 + DNAT verified from master/slave nodes and external curl --- .../vhosts/common/files/install-packages.sh | 2 +- .../wireguard/site/defaults/main.yml | 9 +++- .../vpn-overlay/wireguard/site/tasks/main.yml | 44 ++++++++++++++----- .../wireguard/site/templates/wg0.conf.j2 | 10 +++-- ansible/playbooks/vpn-wireguard-site.yaml | 6 +-- config/sit/vpn-keys.yaml | 11 ++++- config/sit/vpn-overlay.yaml | 14 +++--- scripts/run.sh | 8 ++-- 8 files changed, 72 insertions(+), 32 deletions(-) diff --git a/ansible/playbooks/roles/vhosts/common/files/install-packages.sh b/ansible/playbooks/roles/vhosts/common/files/install-packages.sh index e3967b6e..982db402 100644 --- a/ansible/playbooks/roles/vhosts/common/files/install-packages.sh +++ b/ansible/playbooks/roles/vhosts/common/files/install-packages.sh @@ -2,4 +2,4 @@ export DEBIAN_FRONTEND=noninteractive sudo apt-get update -sudo apt-get install -y vim iputils-ping rsync +sudo apt-get install -y vim iputils-ping rsync wireguard-tools diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/defaults/main.yml b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/defaults/main.yml index 6c5b4ae1..d61f1bac 100644 --- a/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/defaults/main.yml +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/defaults/main.yml @@ -1,3 +1,8 @@ -wg_config_path: "/etc/wireguard/wg0.conf" wg_interface: "wg0" -wg_port: "{{ overlay_data.hub_port | default(51820) }}" + +# 默认配置文件路径,可在 playbook 中覆盖 +overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml" +overlay_keys_path: "{{ playbook_dir }}/../../config/sit/vpn-keys.yaml" + +# WireGuard 默认端口(若 overlay_data.hub_port 未定义) +wg_port: "51820" diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/tasks/main.yml b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/tasks/main.yml index 1df80855..eeff3664 100644 --- a/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/tasks/main.yml +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/tasks/main.yml @@ -1,27 +1,47 @@ --- -- name: 加载 overlay 配置 +- name: 加载 overlay 配置(标准 YAML) set_fact: overlay_data: "{{ lookup('file', overlay_config_path) | from_yaml }}" -- name: 加载密钥配置 - set_fact: - overlay_keys: "{{ lookup('file', overlay_keys_path) | from_yaml }}" +- name: 加载密钥配置(支持 !vault) + include_vars: + file: "{{ overlay_keys_path }}" + name: overlay_keys - name: 提取当前节点信息(作为 current) set_fact: - current_node: "{{ (overlay_data.sites + overlay_data.hubs) | selectattr('name', 'equalto', inventory_hostname) | list | first }}" - current_key: "{{ overlay_keys.keys | selectattr('name', 'equalto', inventory_hostname) | list | first }}" + current_node: >- + {{ (overlay_data.sites + overlay_data.hubs) + | selectattr('name', 'equalto', inventory_hostname) + | list | first }} + current_key: >- + {{ overlay_keys['keys'] + | selectattr('name', 'equalto', inventory_hostname) + | list | first }} -- name: 提取对端 peer 节点信息 +- name: 提取对端 peer 节点列表 set_fact: - peer_node: "{{ (overlay_data.sites + overlay_data.hubs) | selectattr('name', 'equalto', current_node.wireguard_peer) | list | first }}" - peer_key: "{{ overlay_keys.keys | selectattr('name', 'equalto', current_node.wireguard_peer) | list | first }}" + peer_node_list: "{{ (overlay_data.sites + overlay_data.hubs) | selectattr('name', 'equalto', current_node.wireguard_peer) | list }}" + peer_key_list: "{{ overlay_keys['keys'] | selectattr('name', 'equalto', current_node.wireguard_peer) | list }}" -- name: 生成 WireGuard 参数 +- name: 校验 wireguard_peer 是否匹配成功 + fail: + msg: "未找到对端节点 '{{ current_node.wireguard_peer }}' 或其密钥" + when: peer_node_list | length == 0 or peer_key_list | length == 0 + +- name: 设置对端节点与密钥 set_fact: + peer_nodes: "{{ peer_node_list }}" + peer_keys: "{{ peer_key_list }}" + +- name: 提取最终配置变量(私钥、公钥、端口等) + set_fact: + wg_port: "{{ wg_port }}" current_wg_ip: "{{ current_node.wg_ip }}" - peer_allowed_ips: "{{ peer_node.wg_ip }}/32" - peer_endpoint: "{{ peer_node.public_ip }}:{{ overlay_data.hub_port | default(51820) }}" + current_private_key: "{{ current_key.private_key }}" + current_allowed_ips: "{{ current_node.allowed_ips }}" + peer_public_key: "{{ peer_keys[0].public_key }}" + peer_endpoint: "{{ peer_nodes[0].public_ip }}:{{ overlay_data.hub_port | default(wg_port) }}" - name: 渲染 wg0.conf template: diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/templates/wg0.conf.j2 b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/templates/wg0.conf.j2 index d305f5b2..a065f004 100644 --- a/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/templates/wg0.conf.j2 +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/wireguard/site/templates/wg0.conf.j2 @@ -1,10 +1,12 @@ [Interface] -PrivateKey = {{ current_key.private_key }} +PrivateKey = {{ current_private_key }} Address = {{ current_wg_ip }}/32 -ListenPort = {{ overlay_data.hub_port }} +ListenPort = {{ wg_port }} +DNS = 8.8.8.8 +MTU = 1400 [Peer] -PublicKey = {{ peer_key.public_key }} -AllowedIPs = {{ peer_allowed_ips }} +PublicKey = {{ peer_public_key }} +AllowedIPs = {{ current_allowed_ips }} Endpoint = {{ peer_endpoint }} PersistentKeepalive = 25 diff --git a/ansible/playbooks/vpn-wireguard-site.yaml b/ansible/playbooks/vpn-wireguard-site.yaml index 084c8b80..cf5bdafa 100644 --- a/ansible/playbooks/vpn-wireguard-site.yaml +++ b/ansible/playbooks/vpn-wireguard-site.yaml @@ -1,8 +1,8 @@ - name: Setup WireGuard for site - hosts: overlay_sites + hosts: all become: true vars: - overlay_config_path: "config/sit/vpn-overlay.yaml" - overlay_keys_path: "config/sit/vpn-keys.yaml" + overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml" + overlay_keys_path: "{{ playbook_dir }}/../../config/sit/vpn-keys.yaml" roles: - vhosts/vpn-overlay/wireguard/site diff --git a/config/sit/vpn-keys.yaml b/config/sit/vpn-keys.yaml index 1a34e287..82955714 100644 --- a/config/sit/vpn-keys.yaml +++ b/config/sit/vpn-keys.yaml @@ -25,8 +25,15 @@ keys: private_key: public_key: - - name: hub1 - private_key: + - name: hub-1 + private_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 33643635306332303761356562383035353333373234393132313162613834323963313635326562 + 3932356235303234356561623762393862666438386565310a376235306238343139386532336162 + 65623164666665353435653432396530303634666438656566656466643866366139613961363631 + 6363306631393038320a613163313338313237383837303966356333303737643331616433396430 + 33316331333766613438356462313130326433363961316162313761616561616466363939613033 + 3837623938376434656434386135333739613939653133373733 public_key: !vault | $ANSIBLE_VAULT;1.1;AES256 38336537383061383333643431643261343739323864316235303366623930633366336139386636 diff --git a/config/sit/vpn-overlay.yaml b/config/sit/vpn-overlay.yaml index b070cc36..01f4dc19 100644 --- a/config/sit/vpn-overlay.yaml +++ b/config/sit/vpn-overlay.yaml @@ -14,10 +14,10 @@ features: # WireGuard Hub 节点配置 hubs: - - name: hub1 - wg_ip: 10.100.0.1 + - name: hub-1 + wg_ip: 10.255.0.1 br_ip: 172.16.0.1 - public_ip: hub1.example.com + public_ip: global-proxy.onwalk.net - name: hub2 wg_ip: 10.100.1.1 @@ -28,8 +28,10 @@ hubs: sites: - name: master-1 interface: ens5 + allowed_ips: "10.255.0.0/16,10.253.0.0/16" wireguard_peer: hub-1 - br_ip: 10.253.253.2 + wg_ip: 10.255.0.200 + br_ip: 172.16.0.2 local_ip: 10.255.0.200 remote_ip: 10.255.0.100 vless: @@ -42,7 +44,9 @@ sites: - name: slave-1 interface: ens5 wireguard_peer: hub-1 - br_ip: 10.253.253.3 + allowed_ips: "10.255.0.0/16,10.253.0.0/16" + br_ip: 172.16.0.2 + wg_ip: 10.255.0.100 local_ip: 10.255.0.100 remote_ip: 10.255.0.200 vless: diff --git a/scripts/run.sh b/scripts/run.sh index a42311f4..18e8809d 100644 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -262,12 +262,14 @@ pulumi_run() { # ========== 执行 Ansible ========== run_ansible() { - if [ ! -f scripts/inventory.py ]; then - echo "❌ 未找到 scripts/inventory.py" + if [ ! -f scripts/dynamic_inventory.py ]; then + echo "❌ 未找到 scripts/dynamic_inventory.py" exit 1 fi echo "🧪 执行 Ansible Playbook" - ansible-playbook -i scripts/inventory.py "$ANSIBLE_DIR/playbooks/setup.yml" + ansible-playbook -i scripts/dynamic_inventory.py ansible/playbooks/common_setup.yml -D + ansible-playbook -i scripts/dynamic_inventory.py ansible/playbooks/vpn-wireguard-site.yaml -D -l slave-1,master-1 + ansible-playbook -i scripts/dynamic_inventory.py ansible/playbooks/vpn-overlay.yaml -D -l slave-1,master-1 } # ========== 分发 ==========