diff --git a/infrastructure/oci/README.md b/infrastructure/oci/README.md index f42b931..13e336d 100644 --- a/infrastructure/oci/README.md +++ b/infrastructure/oci/README.md @@ -1,13 +1,113 @@ -# OCI infrastructure recovery +# OCI 云上基础设施 -The original Terraform source is currently unavailable. The likely authoritative -state remains in OCI Object Storage. Reconstruct configuration here only after -taking an encrypted independent state backup. +`terraform/` 是独立 Terraform 根模块,从 OCI Object Storage 中的现有 state 恢复。 +Terraform 管理云 API 资源;实例内的软件、Kubernetes 和操作系统配置不在该 state 中。 -Safety requirements: +## 资源与来源 -- preserve the existing state lineage and serial; -- reproduce the current VM and public-network design first; -- reach a zero-change plan before any apply; -- protect the instance and boot volume from destruction; -- treat migration to a private subnet as a separate reviewed change. +- Region:`ap-osaka-1`,compartment 为 tenancy 根。 +- State:namespace `axckv9ylwqxr`,bucket `oci-k8s-free-tier-tfstate`,key `terraform.tfstate`。 +- 恢复源:2026-08-15 12:26:24 UTC 对象,25,261 字节,serial `249`, + lineage `c945c6c4-ee01-d7b7-23f5-37207ea60609`,Terraform `1.15.8`。 +- 7 个受管资源保留原地址:`oci_core_instance.vm`、`oci_core_vcn.vcn`、 + `oci_core_subnet.public`、`oci_core_internet_gateway.igw`、`oci_core_route_table.public`、 + `oci_core_security_list.public`、`oci_limits_quota.free_tier_quota`。 +- 2 个数据源:`oci_core_images.ubuntu`、`oci_identity_availability_domains.ads`;保留 4 个原输出。 +- VM:`homelab-vm`,A1 Flex,2 OCPU / 12 GB RAM / 100 GB 启动盘, + 私网 `10.0.0.124`,恢复时公网 `129.225.138.179`。 +- VCN `10.0.0.0/16`,公共子网 `10.0.0.0/24`,默认路由经 Internet Gateway; + 入站保留 TCP 22、UDP 41641、ICMP type 3/code 4,出站全部允许。 +- 配额语句保留 A1 4 核 / 24 GB、10 个卷、200 GB 总存储限制;这些语句不是费用保证。 +- Bucket 自身不在 state 内,不由此根模块管理。 + +## 恢复设计 + +原变量、模块意图、provider 精确版本和生命周期规则无法从 state 完整恢复。 +本次选择并锁定 `oracle/oci 9.1.0`,提交 lockfile;这不是声称找回了原 provider 版本。 +资源间的 VCN、路由表、安全列表、子网和 DHCP 引用已重建。 +启动镜像固定为现有 image OCID,避免数据源选中更新镜像导致 VM 替换。 +新增 `prevent_destroy` 保护现有 VM;没有用 `ignore_changes` 掩盖配置差异。 + +Provider 使用本机 `~/.oci/config` 的 `DEFAULT` profile,可用变量覆盖 profile 和 region。 +metadata 经敏感变量传入,只保存在忽略的 `terraform.tfvars.json`,不进入版本库。 +State、plan、metadata 变量与 `.terraform/` 都不得提交;plan JSON 同样可能含敏感数据。 +保留原目录的恢复要求:保留 lineage/serial,先复现当前 VM 和公共网络设计, +实际基础设施变更前达到严格零变更,并保护实例及启动盘;迁移私有子网须单独评审。 +已用既有 GPG 加密子密钥 `5A6A04D1B216C64E` 创建独立加密源备份 +`.recovery/source.tfstate.gpg`;本地原始副本的 lineage/serial 保持不变。 + +## 日常维护 + +旧 Terraform/CI 已由维护者确认停用,当前仓库已接管原 OCI Object Storage state。 +`versions.tf` 使用 OCI backend,直接连接原对象,未迁移本地恢复副本。 +认证沿用本机 OCI CLI 的 `DEFAULT` profile;不要在配置中写密钥。 + +```bash +cd infrastructure/oci/terraform +terraform init +terraform validate +terraform plan -input=false -out=change.tfplan +# 核对计划后执行: +terraform apply change.tfplan +``` + +新 checkout 需从受限 state 副本提取 metadata 至被忽略的 `terraform.tfvars.json`。 +`prepare-local-state.py` 可执行这一步,同时保留 `.recovery/terraform.tfstate` 备份; +当前 backend 使用远端对象,`.recovery/` 中的副本不再参与日常 plan/apply。 +下载前设置 `umask 077`,并使用 GPG 加密源备份;不要把本地副本上传覆盖远端。 + +## 恢复验证记录 + +Terraform 1.15.8、OCI provider 9.1.0 验证通过。 +恢复计划唯一更新是 VM metadata 的敏感标记,plan JSON 中 before/after 值相同。 +经授权 apply 后,本地完整刷新 plan 达到 `No changes`。 +旧 state 在 provider 刷新后补充 VM shape/VNIC、subnet IPv4 CIDR、route type 字段, +这些读回差异没有产生基础设施修改计划。 +接管时重新连接原远端对象,因此新增实例计划也包含原 VM 的同一敏感标记归一化。 + +## AMD 实例 + +`oci_core_instance.amd` 配置为 `homelab-amd`,`VM.Standard.E2.1.Micro`,1 GB RAM, +Ubuntu 24.04 x86_64,50 GB / 10 VPU 启动盘。复用现有公共子网和 SSH 公钥, +没有复制 A1 实例的其他初始化内容,也不部署 Kubernetes。 +镜像固定为 `Canonical-Ubuntu-24.04-2026.08.25-0`,实例具有 `prevent_destroy` 保护。 + +创建前 API 确认大阪为 home region、机型计费类型为 `ALWAYS_FREE`,AMD 配额剩余 2 台。 +存储盘点只有 A1 的 100 GB 启动盘;已核对两个启动盘合计 150 GB,200 GB 免费额度内剩余 50 GB。 +免费额度跨启动盘和块存储共享;后续新增资源仍需重新核对实际占用。 + +实例已创建并确认 `RUNNING`,私网 `10.0.0.158`,公网 `129.225.176.134`。 +登录命令:`ssh ubuntu@129.225.176.134`(本次未验证 SSH 登录)。 +远端 state 已保存;创建后完整刷新 plan 为 `No changes`,退出码 0。 + +参考:[Always Free 资源](https://docs.oracle.com/en-us/iaas/Content/FreeTier/freetier_topic-Always_Free_Resources.htm)、 +[OCI provider 认证](https://docs.oracle.com/en-us/iaas/Content/dev/terraform/configuring.htm)、 +[OCI backend 配置](https://developer.hashicorp.com/terraform/language/backend/oci)。 + +## DNS 登录入口 + +- ARM:`ssh ubuntu@oci-arm.ddupan.top` +- AMD:`ssh ubuntu@oci-amd.ddupan.top` + +公网 A 记录声明位于 `../dns/records.yml`,在 Cloudflare 上关闭代理,TTL 300 秒。 +当前通过 DNS API 管理,未加入 OCI Terraform state;公网 IP 变化时需同步记录。 + +## WireGuard/BGP 与 DN42 + +家中端点已从 laptop 迁移到 VyOS `192.168.10.2`,AMD 与 VyOS 同属 AS4242421811, +通过独立 WireGuard 接口建立双栈 iBGP;laptop 保留原有 NEC BGP 和 OSPF,按路由经 VyOS 转发。 +Ansible 配置与运行方法见 [ansible/README.md](ansible/README.md)。 + +注册前缀 `172.21.111.160/27`、`fdd0:98df:15b0::/48` 已在内部路由中准备: +VyOS `.161` / `::1`,AMD `.162` / `::2`,使用 loopback /32、/128。 +首个外部 DN42 peer 已接入 RoutedBits Osaka(AS4242420207),AMD 使用独立 `wg-dn42-1` +和单 IPv6 MP-BGP 会话承载双栈;详见 [Ansible runbook](ansible/README.md)。 +外部明细留在 AMD,`172.20.0.0/14`、`fd00::/8` 汇总经 iBGP 下发 VyOS;IPv4 /14 再经 OSPF 下发 LAN。 +LAN 的 DN42 IPv6 /64 地址由 VyOS SLAAC 下发,fd00::/8 通过 RA RIO 分发,不通告 IPv6 默认路由。内部家中/OCI 业务路由不得向外部 DN42 邻居通告。 + +Terraform 管理 AMD NSG、VNIC 转发与 VCN 回程;Ansible 管理路由器/主机及 ARM 的 Tailscale +回程例外。Docker 与 DN42 的 /14 地址重叠已迁出,见 [Docker runbook](../docker/README.md)。 + +VyOS 的 `wg42` 主 IPv4 已改为注册的 `172.21.111.161/32`,内部 BGP 改为 +单 link-local IPv6 会话承载双栈。三个 LAN 私网到 DN42 /14 由 VyOS 定向 masquerade, +排除本 AS /27;OCI 业务保持原源地址。IPv6 不做 NAT,由 `ansible/dn42-ra.yml` 管理三个 LAN 的 SLAAC 与专用路由通告。 diff --git a/infrastructure/oci/ansible/README.md b/infrastructure/oci/ansible/README.md new file mode 100644 index 0000000..66a1ade --- /dev/null +++ b/infrastructure/oci/ansible/README.md @@ -0,0 +1,232 @@ +# VyOS ↔ OCI:WireGuard 与 DN42 内部 BGP + +站点端已从 laptop 迁移到 VyOS `192.168.10.2`。VyOS 位于双层 NAT 后,主动连接 +`oci-amd.ddupan.top:51820`,keepalive 25 秒;LAN 访问 DN42 时在 VyOS 做定向 masquerade。 +主机配置由 Ansible 管理;OCI NSG、VNIC 转发和 VCN 回程路由由旁边的 Terraform 管理。 + +| 节点 | 接口 | 传输 IPv4 | 传输 IPv6 | ASN | +|---|---|---|---|---| +| VyOS | wg42 | 172.21.111.161/32 | fe80::1811:1/64(BGP) | 4242421811 | +| AMD | wg-oci | 10.255.254.1/30(历史传输地址) | fe80::1811:2/64(BGP) | 4242421811 | + +AMD 显式设置 `fe80::1811:2/64`;VyOS BGP 使用固定 `fe80::1811:1/64`,双方 AllowedIPs 包含 +`fe80::/64`。FRR 在 ULA 建邻时仍需接口具有 link-local 下一跳地址。 + +MTU 1380,Linux 使用 `Table = off`,WireGuard AllowedIPs 用于选 peer 与源地址校验, +站点路由由 iBGP 安装。内外部均使用单条 IPv6 link-local 会话承载双 AFI,并启用 extended-nexthop。 +内部旧 IPv4/ULA BGP 邻居已退役;ULA 传输地址保留用于路由下一跳,不再用于建邻。 +VyOS 10.2.4 通过 `OCI-MP-IN` 的 `ipv6-next-hop prefer-global` 优先采用通告中的 ULA 下一跳, +避免 link-local NHT 显示 overlay index unresolved、BGP 已建邻但路由未安装。 + +## 地址与通告边界 + +已注册 `172.21.111.160/27`、`fdd0:98df:15b0::/48`: + +| 节点 | 路由器 IPv4 | 路由器 IPv6 | +|---|---|---| +| VyOS(IPv4 在 wg42,IPv6 在 lo) | 172.21.111.161/32 | fdd0:98df:15b0::1/128 | +| AMD loopback | 172.21.111.162/32 | fdd0:98df:15b0::2/128 | + +VyOS 为注册的 /27、/48 建立 distance 254 的 blackhole 聚合路由,保证精确前缀存在, +避免未分配地址落入默认路由。已分配的本地地址及 AMD 的 /32、/128 优先于聚合。 + +内部通告严格过滤: + +- VyOS → AMD:`192.168.10.0/24`、`10.60.0.0/24`、`10.61.0.0/24` 和注册 /27、/48。 +- AMD → VyOS:`10.0.0.0/24`、AMD 的注册 /32、/128,以及 DN42 汇总 `172.20.0.0/14`、`fd00::/8`。 +- VyOS 向 LAN OSPF 只重分发 OCI /24、DN42 /14 和注册 IPv4 /27,使用精确 route-map、E1 metric。 + 既有直连 LAN/SDN 的 OSPF area 声明保持不变,不使用泛化的 redistribute connected。 + +**首个外部 peer 为 RoutedBits Osaka(AS4242420207),由 `dn42.yml` 单独管理。** 外部邻居使用独立的 import/export +过滤,只对外通告注册 /27、/48;禁止把上述内部业务前缀的过滤器复用到外部邻居。 +入口使用 DN42 指南的保留地址、互联网络与前缀长度规则(IPv6 /44–/64), +并优先拒收本 AS、LAN、OCI 前缀;尚未配置注册表 ROA 校验。外部明细留在 AMD,汇总通过 iBGP 下发 VyOS;IPv4 汇总再经 OSPF 下发 LAN。 + +每个外部 WireGuard peer 使用独立接口,BGP 可复用本机 loopback 地址。 +单接口多个 WireGuard peer 要求可明确区分的 AllowedIPs;多家 peer 都提供同一 DN42 路由范围时, +使用独立接口让 BGP 决定出口,避免相同 AllowedIPs 抢占 peer。 + +## 执行 + +```bash +cd infrastructure/oci/ansible +export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" +ansible-playbook site.yml --check --diff +ansible-playbook site.yml +ansible-playbook site.yml # 复跑应 changed=0 +``` + +`vyos.yml` 先在路由器本机生成密钥并保存,再交换公钥并增量应用 VyOS set 命令。 +VyOS native config 含私钥,因此相关模块使用 `no_log`,不把配置备份到 Git 或打印出来。 +AMD 私钥在 `/etc/wireguard/wg-oci.key`(0600),由本机 PostUp 加载,不返回控制机。 +首次 check mode 无法生成私钥,因此会跳过依赖不存在公钥的 Linux 配置渲染。 + +`retire-laptop.yml` 是迁移收尾:先检查 VyOS 邻居,再停止 laptop 的 wg-oci, +删除试验邻居、三个新增 network 语句及专用防火墙链,保留 laptop 原 AS65001 ↔ NEC AS65000 +会话、原有 VPN 路由和 OSPF。旧私钥保留在 laptop 受限文件中,隧道和防火墙单元已禁用。 + +Linux 端 FRR 通过 `vtysh -f` 应用独立配置片段,另行 `write memory` 持久化; +文件变更时重建受管邻居,未变更的重跑不重置会话。移除前缀时还须显式删除已退出管理的 +`network` 语句,不能只追加 set 命令,也不能清空整份 BGP 配置。 + +## OCI 回程与 Tailscale + +VCN 虚拟路由器不参与主机间 iBGP;三个家中业务前缀的静态回程指向 AMD Private IP OCID。 +首次启用时必须先将 VNIC `skip_source_dest_check` 设为 true,OCI 才接受该私有 IP 为路由目标。 +当前 Terraform 的 route table → subnet → instance 依赖使首次引导需要先设置该标志。 +重建 AMD 后须重新查询并更新 `amd_router_private_ip_ocid`。 + +ARM 接受 Tailscale 家中子网路由,table 52 原本抢走回程。 +Ansible 在 ARM 设置 priority 5101–5103、仅匹配这三个目的前缀的 `lookup main` 规则, +让这些流量使用 OCI 网关→AMD;其他 Tailscale 地址保持原路径。 +AMD 的 Zebra route-map 仅为本机发起的 BGP 业务流量选择 `10.0.0.158` 源地址,不改写转发源地址。 + +## Docker 地址冲突 + +DN42 使用 `172.20.0.0/14`。原 laptop Docker 的四个 /16 已清除,默认池改为 `172.28.0.0/16`, +详见 [Docker runbook](../../docker/README.md)。最长前缀匹配决定路由,但不能解决两套网络实际 +地址重叠;不能只检查本 AS 注册的 /27 而忽略其他 DN42 注册前缀。 + +## 验证与停用 + +```bash +# VyOS operational mode +show interfaces wireguard wg42 summary +show bgp summary +show ip route 172.21.111.162/32 +show ipv6 route fdd0:98df:15b0::2/128 +# AMD +sudo wg show wg-oci latest-handshakes +sudo vtysh -c 'show bgp summary' +ping 192.168.10.4 +ping -I 172.21.111.162 172.21.111.161 +ping -6 -I fdd0:98df:15b0::2 fdd0:98df:15b0::1 +``` + +业务网段测试应使用业务源地址;DN42 loopback 测试使用注册地址。 +LAN 已通过 `dn42-ra.yml` 部署 DN42 SLAAC 与专用 RIO,不发布 IPv6 默认路由;OCI VCN 保持原配置。 +停用或回滚须同时处理 BGP、WireGuard、OSPF 重分发、VCN 回程和 ARM 例外规则, +不能仅停止隧道后留下静态回程指向不可达节点。 + +参考:[WireGuard](https://www.wireguard.com/quickstart/)、 +[VyOS WireGuard](https://docs.vyos.io/en/1.5/configuration/interfaces/wireguard.html)、 +[DN42 入门](https://www.dn42.dev/howto/Getting-Started)。 + +## 迁移验收 + +2026-09-14: + +- VyOS IPv4 iBGP 收到 OCI /24、AMD /32,通告三个家中前缀与注册 /27;IPv6 会话双方各收到一条前缀。 +- 两端 DN42 loopback IPv4、IPv6 互通;IPv6 本次采样约 3.1 ms。 +- laptop 的 `10.0.0.0/24` 与 `172.21.111.160/27` 经 OSPF 指向 `192.168.10.2`,不再使用旧隧道。 +- PVE1 可访问 ARM 私网和 AMD 的 DN42 IPv4;AMD 可访问 PVE1、`10.60.0.1`、`10.61.0.1`。 +- laptop 旧 WireGuard 和专用防火墙 service 已停止/禁用,原 NEC BGP 会话保留。 + +LAN 网关可能返回 ICMP Redirect,提示客户端将 VyOS 作为同网段下一跳;这是既有 LAN +拓扑的正常结果,没有为此修改客户端或网关的 redirect 策略。 + +## AMD 外部 DN42 首个接口 + +2026-09-15 已通过 `ansible-playbook dn42.yml` 准备独立监听: + +- 接口:`wg-dn42-1`,UDP endpoint:`oci-amd.ddupan.top:51821`(`129.225.176.134:51821`)。 +- 公钥:`YQ/X3QmNocnr0u4aUm5qhcV328StSNtg+ULd9AKCdhQ=`。 +- 私钥仅保存在 AMD `/etc/wireguard/wg-dn42-1.key`,由 root 受限目录保护,不返回控制机。 +- `wg-quick@wg-dn42-1` 开机启动;主机入站规则随接口启停,OCI NSG 规则由 Terraform 管理。 +- Link-local:`fe80::1811:2/64`,本机作用域为 `%wg-dn42-1`。首个 peer 使用单条 IPv6 BGP 会话承载双栈(MP-BGP + RFC 8950 extended next hop)。 +- 对端:AS4242420207,`router.osa1.routedbits.com:51811`,link-local `fe80::207`。 +- 对端公钥:`96PwUEGi/ijdmKO+IjuZ+J6DeykuTRukZD5atajfeH4=`。 +- WireGuard 使用 `Table = off`;AllowedIPs 为 `fe80::/64, 172.20.0.0/14, 10.0.0.0/8, 172.31.0.0/16, fd00::/8`,keepalive 25 秒。 +- 双 AFI 共用 IPv6 TCP 会话并启用 extended-nexthop;每 AFI maximum-prefix 10000,出口只允许注册 /27 和 /48。 +- 本机发起 DN42 流量使用注册地址作为 preferred source,不做 NAT。 +- Ubuntu 自带 FRR 8.4.4 不满足 [DN42 FRR 指南](https://dn42.dev/howto/frr) 的 link-local 版本要求。 + `tasks/frr-dn42.yml` 使用官方 frr-10.7 软件源,固定 10.7.1;升级前配置仅在 AMD `/var/backups/frr-before-dn42` 备份。 + +原 `wg-oci:51820` 继续承载与 VyOS 的内部互联。 + +2026-09-15 接入验收:WireGuard 握手与 `fe80::207%wg-dn42-1` 连通;FRR 10.7.1 +会话 Established,双方已协商 IPv4/IPv6 AFI 与 extended nexthop。采样接收 IPv4 1174、IPv6 1273 条, +对外仅通告 `172.21.111.160/27`、`fdd0:98df:15b0::/48`。AMD 无需显式指定源地址, +即可访问对端 `172.20.19.78`、`fdb1:e72a:343d::f`,各 3/3 回复,约 1 ms; +IPv4 内核路由下一跳为 `via inet6 fe80::207 dev wg-dn42-1`。内部 VyOS 双栈 BGP 会话已恢复。 + +## 向 LAN 分发 DN42 汇总 + +`site.yml` 在 AMD 生成 `172.20.0.0/14`、`fd00::/8` 的 BGP aggregate,并通过内部精确 +prefix-list 通告 VyOS。不使用全局 `summary-only`,避免抑制对外通告的注册 /27、/48; +对外出口过滤仍只允许这两个注册前缀。AMD 保留外部明细与汇总丢弃路径,无匹配明细的流量 +在 AMD 丢弃。汇总只要仍有覆盖的 BGP 明细(包括本 AS 注册前缀)就可能存在,不能作为外部 +peer 在线状态指示。自己的 /27、/48 更具体,继续指向本地站点。 + +VyOS 将 IPv4 /14 通过既有 OSPF E1 分发到 LAN;IPv6 /8 通过 RA 的 RIO 分发至接受该选项的客户端。 +LAN 的 DN42 IPv6 地址通过 SLAAC 自动分配。 +当前 LAN IPv4 客户端访问 DN42 /14 由 VyOS 定向 masquerade 到 `172.21.111.161`; +IPv6 使用 SLAAC 分配的注册地址直接路由。 +AMD 只放行注册前缀在内部 `wg-oci` 与外部 `wg-dn42-1` 之间转发。 + +2026-09-15 汇总验收:VyOS 双栈 BGP 分别收到 /14、/8,下一跳为 AMD; +laptop 的 `172.20.0.0/14` 为 OSPF 路由,经 `192.168.10.2 dev br0`。 +使用 VyOS 注册的 /32、/128 作为源,经 AMD 访问 RoutedBits 的双栈地址各 3/3 回复, +约 4–5 ms。`site.yml` 执行成功,ARM 无变更;LAN 地址配置保持原状。 + +## DN42 DNS 转发 + +`ansible-playbook dn42.yml dn42-dns.yml` 管理入口路由和 VyOS DNS。LAN 的 Blocky +(`192.168.10.127`) 将 `.dn42`、172.20–23 的 IPv4 反向区和 `d.f.ip6.arpa` +转给 VyOS `192.168.10.2:53`。VyOS 仅接受三个内部 LAN 网段,使用注册地址 +`172.21.111.161` / `fdd0:98df:15b0::1` 发起递归转发,不做 NAT。 + +| 上游 | IPv4 | IPv6 | +|---|---|---| +| a0.recursive-servers.dn42 | 172.20.0.53 | fd42:d42:d42:54::1 | +| a3.recursive-servers.dn42 | 172.23.0.53 | fd42:d42:d42:53::1 | + +两个上游的双栈地址均配置,递归请求设置 RD;转发域配置 NTA,避免使用公网根信任链 +验证 DN42 私有命名空间。本地转发器不声明已完成 DN42 DNSSEC 信任锚验证。 +IPv4 anycast /32 需要允许四个 `172.2x.0.0/24` 中的 /28–/32,不能只保留 /14 的 /21–/29。 +新增互联网络明细仅由 AMD 接收;LAN 汇总仍是既定 /14、通过 RIO 通告的 IPv6 /8。 +NEC 备用 DNS 与 k3s CoreDNS 本次未修改。 + +DNS 验收(2026-09-15):从 VyOS 用注册地址直查 a0/a3 的四个双栈地址均获得回复。 +LAN 查询 Blocky 可得到 a0 的 A、a3 的 AAAA;AD 与公网域名正常,Blocky healthy。 +VyOS DNS playbook 复跑 changed=0;AMD 已应用完整入口规则。 + +## VyOS LAN 到 DN42 masquerade + +规则 18100 排除本 AS `172.21.111.160/27`;18110、18120、18130 分别匹配三个 LAN +源网段,目的仅 `172.20.0.0/14` 且出口 `wg42`,translation 为 `masquerade`。 +为使 masquerade 选中注册地址,`172.21.111.161/32` 从 lo 移到 wg42,并移除 +`10.255.254.2/30`;不能在 wg42 仍以传输私网地址为主 IPv4 时直接启用 masquerade。 +现有 OCI 业务互联继续保留原源地址,DNS 转发使用的 `172.21.111.161` 保持可用。 +AMD 仍仅允许 DN42 注册源前缀进入外部隧道,不在 AMD 做第二次 NAT。 + +2026-09-15 masquerade 验收:laptop `192.168.10.127` 到 RoutedBits `172.20.19.78` +3/3 回复约 4.7 ms,VyOS NAT 表显示转换为 `172.21.111.161`;到 ARM `10.0.0.124` +3/3 回复约 4.2 ms,NAT 表确认保留 `192.168.10.127`。 +内部 link-local 单会话双 AFI 已建立;外部 peer 保持独立接口和精确出口。 + +## LAN DN42 IPv6 RA(不发布默认路由) + +执行 `ansible-playbook dn42-ra.yml`,为三个 LAN 启用 SLAAC: + +| LAN | VyOS 接口 | 前缀 | 路由器地址 | +|---|---|---|---| +| 192.168.10.0/24 | eth0 | fdd0:98df:15b0:10::/64 | fdd0:98df:15b0:10::1 | +| 10.60.0.0/24 | eth1 | fdd0:98df:15b0:60::/64 | fdd0:98df:15b0:60::1 | +| 10.61.0.0/24 | eth2 | fdd0:98df:15b0:61::/64 | fdd0:98df:15b0:61::1 | + +Router Lifetime 为 **0**,不发布 `::/0`;PIO 开启 on-link 与 autonomous 标志, +preferred lifetime 14400 秒、valid lifetime 86400 秒。RIO 只包含 `fd00::/8`, +有效期 180 秒;RA 周期 10–30 秒。不发布 RDNSS、DNSSL、DHCPv6 标志或链路 MTU, +保留客户端现有 DNS 和公网出口。IPv6 经 VyOS→AMD→DN42 直接路由,不做 NAT66。 +现有 /48 对外通告及内部回程已覆盖这三个 /64,无须泄漏每个 LAN 的明细到外部。 + +客户端须支持并接受 RIO。当前 laptop 的 br0 `accept_ra=0`、`forwarding=1`, +不会自动配置;本次保留其网络设置。Linux 路由主机如需接受 RA,需要在自身网络管理 +配置中显式启用,并允许至少 /8 的 RIO(`accept_ra_rt_info_max_plen`)。 +关闭 RA 时注意 PIO 的有效期;不要只删路由器接口地址而留下仍有效的客户端地址。 + +2026-09-15 RA 抓包验收:主 LAN 收到不带标签的 `:10::/64`;VLAN 100/110 +分别携带 `:60::/64`、`:61::/64`。三者 Router Lifetime 均为 0,PIO 为 onlink/auto, +RIO 为 fd00::/8、180 秒,没有 DNS 或默认路由通告。`dn42-ra.yml` 复跑 changed=0。 +同一 LAN 上的临时 Linux 测试客户端自动获得地址与 RIO,无 IPv6 默认路由;测试后自动删除。 diff --git a/infrastructure/oci/ansible/ansible.cfg b/infrastructure/oci/ansible/ansible.cfg new file mode 100644 index 0000000..89a5697 --- /dev/null +++ b/infrastructure/oci/ansible/ansible.cfg @@ -0,0 +1,8 @@ +[defaults] +inventory = inventory/hosts.yml +roles_path = roles +local_tmp = /tmp/ansible-oci +host_key_checking = True +interpreter_python = auto_silent +[ssh_connection] +ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=accept-new diff --git a/infrastructure/oci/ansible/dn42-dns.yml b/infrastructure/oci/ansible/dn42-dns.yml new file mode 100644 index 0000000..cd6d511 --- /dev/null +++ b/infrastructure/oci/ansible/dn42-dns.yml @@ -0,0 +1,23 @@ +--- +- name: 配置 VyOS DN42 DNS 转发 + hosts: site_routers + gather_facts: false + vars: + dn42_dns_zones: + - dn42 + - 20.172.in-addr.arpa + - 21.172.in-addr.arpa + - 22.172.in-addr.arpa + - 23.172.in-addr.arpa + - d.f.ip6.arpa + dn42_dns_servers: + - 172.20.0.53 + - 172.23.0.53 + - fd42:d42:d42:54::1 + - fd42:d42:d42:53::1 + tasks: + - name: 配置受限监听、注册地址源与条件转发 + vyos.vyos.vyos_config: + lines: "{{ lookup('template', 'templates/vyos-dn42-dns.conf.j2').splitlines() | reject('equalto', '') | list }}" + save: true + comment: Ansible DN42 DNS forwarding diff --git a/infrastructure/oci/ansible/dn42-ra.yml b/infrastructure/oci/ansible/dn42-ra.yml new file mode 100644 index 0000000..86d0f49 --- /dev/null +++ b/infrastructure/oci/ansible/dn42-ra.yml @@ -0,0 +1,21 @@ +--- +- name: 在 LAN 通告 DN42 SLAAC 地址与专用路由 + hosts: site_routers + gather_facts: false + vars: + dn42_ra_lans: + - interface: eth0 + prefix: fdd0:98df:15b0:10::/64 + address: fdd0:98df:15b0:10::1/64 + - interface: eth1 + prefix: fdd0:98df:15b0:60::/64 + address: fdd0:98df:15b0:60::1/64 + - interface: eth2 + prefix: fdd0:98df:15b0:61::/64 + address: fdd0:98df:15b0:61::1/64 + tasks: + - name: 配置接口地址、SLAAC 与 RIO,不通告默认路由或 DNS + vyos.vyos.vyos_config: + lines: "{{ lookup('template', 'templates/vyos-dn42-ra.conf.j2').splitlines() | reject('equalto', '') | list }}" + save: true + comment: Ansible DN42 SLAAC without default route diff --git a/infrastructure/oci/ansible/dn42.yml b/infrastructure/oci/ansible/dn42.yml new file mode 100644 index 0000000..f5fd593 --- /dev/null +++ b/infrastructure/oci/ansible/dn42.yml @@ -0,0 +1,98 @@ +--- +# 首个外部 peer:RoutedBits Osaka,IPv6 link-local MP-BGP。 +- name: 准备 AMD DN42 WireGuard 监听 + hosts: oci_amd + become: true + vars: + dn42_interface: wg-dn42-1 + dn42_port: 51821 + dn42_linklocal: fe80::1811:2/64 + dn42_peer_linklocal: fe80::207 + dn42_peer_asn: 4242420207 + dn42_peer_endpoint: router.osa1.routedbits.com:51811 + dn42_peer_public_key: 96PwUEGi/ijdmKO+IjuZ+J6DeykuTRukZD5atajfeH4= + tasks: + - name: 准备 FRR link-local 支持 + ansible.builtin.import_tasks: tasks/frr-dn42.yml + - name: 创建受限密钥目录 + ansible.builtin.file: + path: /etc/wireguard + state: directory + owner: root + group: root + mode: '0700' + - name: 在 AMD 本机生成独立私钥 + ansible.builtin.shell: umask 077; wg genkey > /etc/wireguard/{{ dn42_interface }}.key + args: + creates: /etc/wireguard/{{ dn42_interface }}.key + no_log: true + - name: 写入监听配置 + ansible.builtin.copy: + dest: /etc/wireguard/{{ dn42_interface }}.conf + owner: root + group: root + mode: '0600' + content: | + # Ansible 管理;首个 peer 使用 IPv6 link-local MP-BGP + extended next hop。 + [Interface] + Address = {{ dn42_linklocal }} + ListenPort = {{ dn42_port }} + MTU = 1380 + Table = off + PostUp = wg set %i private-key /etc/wireguard/{{ dn42_interface }}.key + PostUp = iptables -w -C INPUT -p udp --dport {{ dn42_port }} -j ACCEPT 2>/dev/null || iptables -w -I INPUT 1 -p udp --dport {{ dn42_port }} -j ACCEPT + PostDown = iptables -w -D INPUT -p udp --dport {{ dn42_port }} -j ACCEPT + + [Peer] + PublicKey = {{ dn42_peer_public_key }} + Endpoint = {{ dn42_peer_endpoint }} + AllowedIPs = fe80::/64, 172.20.0.0/14, 10.0.0.0/8, 172.31.0.0/16, fd00::/8 + PersistentKeepalive = 25 + notify: 重启 DN42 接口 + - name: 启用 DN42 监听 + ansible.builtin.systemd_service: + name: wg-quick@{{ dn42_interface }} + enabled: true + state: started + - name: 应用配置 + ansible.builtin.meta: flush_handlers + - name: 写入外部 BGP 配置片段 + ansible.builtin.template: + src: templates/dn42-bgp.conf.j2 + dest: /etc/frr/dn42-routedbits.vtysh + owner: root + group: frr + mode: '0640' + register: dn42_bgp_config + changed_when: dn42_bgp_config.changed or ('(deleted)' in frr_running.stdout) + notify: 应用 DN42 BGP + - name: 应用 BGP 配置 + ansible.builtin.meta: flush_handlers + - name: 读取公开信息 + ansible.builtin.command: wg show {{ dn42_interface }} {{ item }} + loop: + - public-key + - listen-port + changed_when: false + register: dn42_public_info + - name: 显示公钥和端口 + ansible.builtin.debug: + msg: '{{ dn42_public_info.results | map(attribute="stdout") | list }}' + handlers: + - name: 重启 FRR + ansible.builtin.systemd_service: + name: frr + state: restarted + when: not ansible_check_mode + - name: 重启 DN42 接口 + ansible.builtin.systemd_service: + name: wg-quick@{{ dn42_interface }} + state: restarted + when: not ansible_check_mode + - name: 应用 DN42 BGP + ansible.builtin.command: vtysh -f /etc/frr/dn42-routedbits.vtysh + notify: 保存 FRR 配置 + when: not ansible_check_mode + - name: 保存 FRR 配置 + ansible.builtin.command: vtysh -c 'write memory' + when: not ansible_check_mode diff --git a/infrastructure/oci/ansible/group_vars/all.yml b/infrastructure/oci/ansible/group_vars/all.yml new file mode 100644 index 0000000..6c2f7b8 --- /dev/null +++ b/infrastructure/oci/ansible/group_vars/all.yml @@ -0,0 +1,9 @@ +--- +wg_interface: wg-oci +wg_port: 51820 +wg_mtu: 1380 +wg_home_prefixes: [192.168.10.0/24, 10.60.0.0/24, 10.61.0.0/24] +wg_cloud_prefixes: [10.0.0.0/24] +dn42_asn: 4242421811 +dn42_ipv4: 172.21.111.160/27 +dn42_ipv6: fdd0:98df:15b0::/48 diff --git a/infrastructure/oci/ansible/host_vars/laptop.yml b/infrastructure/oci/ansible/host_vars/laptop.yml new file mode 100644 index 0000000..02e8a5c --- /dev/null +++ b/infrastructure/oci/ansible/host_vars/laptop.yml @@ -0,0 +1,12 @@ +--- +wg_address: 10.255.254.2/30 +wg_peer_address: 10.255.254.1 +wg_peer_host: oci_amd +wg_endpoint: oci-amd.ddupan.top:51820 +wg_keepalive: 25 +wg_lan_interface: br0 +bgp_asn: 65001 +bgp_peer_asn: 4242421811 +bgp_router_id: 192.168.10.127 +bgp_export: '{{ wg_home_prefixes }}' +bgp_import: '{{ wg_cloud_prefixes }}' diff --git a/infrastructure/oci/ansible/host_vars/oci_amd.yml b/infrastructure/oci/ansible/host_vars/oci_amd.yml new file mode 100644 index 0000000..a7c1cd8 --- /dev/null +++ b/infrastructure/oci/ansible/host_vars/oci_amd.yml @@ -0,0 +1,34 @@ +--- +wg_address: 10.255.254.1/30 +wg_peer_address: 172.21.111.161 +wg_peer_host: vyos_rtr +wg_endpoint: '' +wg_keepalive: 0 +wg_lan_interface: ens3 +bgp_asn: 4242421811 +bgp_peer_asn: 4242421811 +bgp_router_id: 10.0.0.158 +bgp_export: +- 10.0.0.0/24 +- 172.21.111.162/32 +bgp_import: +- 192.168.10.0/24 +- 10.60.0.0/24 +- 10.61.0.0/24 +- 172.21.111.160/27 +wg_ipv6_address: fdd0:98df:15b0:ffff::1/64 +wg_peer_ipv6: fdd0:98df:15b0:ffff::2 +bgp_export6: +- fdd0:98df:15b0::2/128 +bgp_import6: +- fdd0:98df:15b0::/48 +wg_linklocal_address: fe80::1811:2/64 + +# 仅向内部邻居通告汇总;外部 peer 保持精确出口过滤。 +bgp_summary: [172.20.0.0/14] +bgp_summary6: [fd00::/8] +dn42_external_interface: wg-dn42-1 + +# 内部与外部统一使用 link-local 单会话双 AFI;节点地址仍在 loopback。 +bgp_transport_peer: fe80::1811:1 +bgp_retired_peers: [10.255.254.2, "fdd0:98df:15b0:ffff::2"] diff --git a/infrastructure/oci/ansible/inventory/hosts.yml b/infrastructure/oci/ansible/inventory/hosts.yml new file mode 100644 index 0000000..a94deed --- /dev/null +++ b/infrastructure/oci/ansible/inventory/hosts.yml @@ -0,0 +1,26 @@ +--- +all: + children: + wireguard_sites: + hosts: + oci_amd: + ansible_host: oci-amd.ddupan.top + ansible_user: ubuntu + oci_routed_hosts: + hosts: + oci_arm: + ansible_host: oci-arm.ddupan.top + ansible_user: ubuntu + retired_wireguard_sites: + hosts: + laptop: + ansible_connection: local + ansible_python_interpreter: /usr/bin/python3 + site_routers: + hosts: + vyos_rtr: + ansible_host: 192.168.10.2 + ansible_user: vyos + ansible_connection: ansible.netcommon.network_cli + ansible_network_os: vyos.vyos.vyos + ansible_ssh_private_key_file: ~/.ssh/id_ed25519 diff --git a/infrastructure/oci/ansible/retire-laptop.yml b/infrastructure/oci/ansible/retire-laptop.yml new file mode 100644 index 0000000..6ea20e5 --- /dev/null +++ b/infrastructure/oci/ansible/retire-laptop.yml @@ -0,0 +1,92 @@ +--- +- name: 确认路由器内部 BGP 已建立 + hosts: site_routers + gather_facts: false + tasks: + - name: 确认到 AMD 的邻居 + vyos.vyos.vyos_command: + commands: show bgp neighbors 10.255.254.1 + register: migration_bgp + changed_when: false + failed_when: "'BGP state = Established' not in migration_bgp.stdout[0]" + +- name: 退役 laptop 的 WireGuard 试验端点 + hosts: retired_wireguard_sites + become: true + tasks: + - name: 停止并禁用旧隧道 + ansible.builtin.systemd_service: + name: wg-quick@wg-oci + state: stopped + enabled: false + + - name: 检查原 BGP 试验邻居是否存在 + ansible.builtin.command: vtysh -c 'show running-config' + register: laptop_frr + changed_when: false + + - name: 只移除本次试验添加的 BGP 节点,保留 NEC 邻居和 OSPF + ansible.builtin.command: + argv: + - vtysh + - -c + - configure terminal + - -c + - router bgp 65001 + - -c + - no neighbor 10.255.254.1 + - -c + - address-family ipv4 unicast + - -c + - no network 192.168.10.0/24 + - -c + - no network 10.60.0.0/24 + - -c + - no network 10.61.0.0/24 + - -c + - exit-address-family + - -c + - exit + - -c + - no ip protocol bgp route-map OCI-WG-SOURCE + - -c + - no route-map OCI-WG-SOURCE + - -c + - no ip prefix-list OCI-WG-IN + - -c + - no ip prefix-list OCI-WG-OUT + - -c + - end + - -c + - write memory + when: "'neighbor 10.255.254.1 remote-as' in laptop_frr.stdout" + + - name: 停止并禁用旧防火墙启动单元 + ansible.builtin.systemd_service: + name: oci-wg-firewall + state: stopped + enabled: false + + - name: 只删除旧隧道专用防火墙链 + ansible.builtin.shell: | + set -eu + changed=0 + for pair in INPUT:OCI-WG-IN FORWARD:OCI-WG-FWD; do + parent=${pair%%:*}; chain=${pair#*:} + if iptables -w -nL "$chain" >/dev/null 2>&1; then + while iptables -w -C "$parent" -j "$chain" 2>/dev/null; do + iptables -w -D "$parent" -j "$chain" + done + iptables -w -F "$chain" + iptables -w -X "$chain" + changed=1 + fi + done + echo "$changed" + register: retired_chains + changed_when: retired_chains.stdout == '1' + + - name: 移除旧的 BGP 配置片段,避免误用 + ansible.builtin.file: + path: /etc/frr/oci-wireguard.vtysh + state: absent diff --git a/infrastructure/oci/ansible/roles/wireguard_bgp/handlers/main.yml b/infrastructure/oci/ansible/roles/wireguard_bgp/handlers/main.yml new file mode 100644 index 0000000..d6ad7d7 --- /dev/null +++ b/infrastructure/oci/ansible/roles/wireguard_bgp/handlers/main.yml @@ -0,0 +1,20 @@ +--- +- name: 重载 FRR daemons + ansible.builtin.systemd_service: + name: frr + state: reloaded + when: not ansible_check_mode +- name: 刷新专用防火墙 + ansible.builtin.systemd_service: + name: oci-wg-firewall + daemon_reload: true + state: restarted + when: not ansible_check_mode +- name: 重启 WireGuard + ansible.builtin.systemd_service: + name: wg-quick@{{ wg_interface }} + state: restarted + when: not ansible_check_mode +- name: 应用 BGP 增量配置 + ansible.builtin.include_tasks: apply-bgp.yml + when: not ansible_check_mode diff --git a/infrastructure/oci/ansible/roles/wireguard_bgp/tasks/apply-bgp.yml b/infrastructure/oci/ansible/roles/wireguard_bgp/tasks/apply-bgp.yml new file mode 100644 index 0000000..0430d20 --- /dev/null +++ b/infrastructure/oci/ansible/roles/wireguard_bgp/tasks/apply-bgp.yml @@ -0,0 +1,8 @@ +--- +- name: 应用受管 FRR 配置片段 + ansible.builtin.command: vtysh -f /etc/frr/oci-wireguard.vtysh + register: bgp_apply + failed_when: bgp_apply.rc != 0 or '% Unknown' in bgp_apply.stdout or '% Unknown' in bgp_apply.stderr + +- name: 保存完整运行配置以便重启恢复 + ansible.builtin.command: vtysh -c 'write memory' diff --git a/infrastructure/oci/ansible/roles/wireguard_bgp/tasks/dn42-loopback.yml b/infrastructure/oci/ansible/roles/wireguard_bgp/tasks/dn42-loopback.yml new file mode 100644 index 0000000..cf3210f --- /dev/null +++ b/infrastructure/oci/ansible/roles/wireguard_bgp/tasks/dn42-loopback.yml @@ -0,0 +1,32 @@ +--- +- name: 安装 AMD DN42 loopback 地址启动单元 + ansible.builtin.copy: + dest: /etc/systemd/system/dn42-loopback.service + mode: '0644' + content: | + [Unit] + Description=DN42 router loopback addresses + Before=frr.service + [Service] + Type=oneshot + ExecStart=/usr/sbin/ip address replace 172.21.111.162/32 dev lo + ExecStart=/usr/sbin/ip -6 address replace fdd0:98df:15b0::2/128 dev lo + RemainAfterExit=yes + [Install] + WantedBy=multi-user.target + register: dn42_loopback_unit + +- name: 启用 AMD DN42 loopback + ansible.builtin.systemd_service: + name: dn42-loopback + daemon_reload: true + enabled: true + state: "{{ 'restarted' if dn42_loopback_unit.changed else 'started' }}" + when: not ansible_check_mode + +- name: 开启路由器 IPv6 转发 + ansible.posix.sysctl: + name: net.ipv6.conf.all.forwarding + value: '1' + sysctl_file: /etc/sysctl.d/90-dn42-forwarding.conf + sysctl_set: true diff --git a/infrastructure/oci/ansible/roles/wireguard_bgp/tasks/main.yml b/infrastructure/oci/ansible/roles/wireguard_bgp/tasks/main.yml new file mode 100644 index 0000000..8741e1c --- /dev/null +++ b/infrastructure/oci/ansible/roles/wireguard_bgp/tasks/main.yml @@ -0,0 +1,163 @@ +--- +- name: 安装 WireGuard 与 FRR + ansible.builtin.apt: + name: [wireguard-tools, frr, iptables] + state: present + update_cache: true + cache_valid_time: 3600 + register: packages + retries: 3 + delay: 5 + until: packages is succeeded + +- name: 创建 WireGuard 受限目录 + ansible.builtin.file: + path: /etc/wireguard + state: directory + owner: root + group: root + mode: '0700' + +- name: 在站点本机生成私钥,永不复制到控制机 + ansible.builtin.shell: 'umask 077; wg genkey > /etc/wireguard/{{ wg_interface }}.key' + args: + creates: '/etc/wireguard/{{ wg_interface }}.key' + no_log: true + +- name: 检查现有私钥 + ansible.builtin.stat: + path: '/etc/wireguard/{{ wg_interface }}.key' + register: wg_key_file + +- name: 读取可交换的公钥 + ansible.builtin.shell: 'wg pubkey < /etc/wireguard/{{ wg_interface }}.key' + register: wg_public_key + changed_when: false + check_mode: false + when: wg_key_file.stat.exists + +- name: 启用 BGP daemon,保留其他协议 + ansible.builtin.lineinfile: + path: /etc/frr/daemons + regexp: '^bgpd=' + line: bgpd=yes + notify: 重载 FRR daemons + when: not ansible_check_mode or not packages.changed + +- name: 确保 FRR 运行 + ansible.builtin.systemd_service: + name: frr + enabled: true + state: started + when: not ansible_check_mode + +- name: 配置 DN42 路由器地址 + ansible.builtin.include_tasks: dn42-loopback.yml + when: bgp_export6 is defined + +- name: 开启 IPv4 转发 + ansible.posix.sysctl: + name: net.ipv4.ip_forward + value: '1' + sysctl_file: /etc/sysctl.d/90-oci-wireguard.conf + sysctl_set: true + +- name: 安装专用防火墙规则脚本 + ansible.builtin.template: + src: firewall.sh.j2 + dest: /usr/local/sbin/oci-wg-firewall + owner: root + group: root + mode: '0755' + notify: 刷新专用防火墙 + +- name: 安装防火墙启动单元 + ansible.builtin.copy: + dest: /etc/systemd/system/oci-wg-firewall.service + mode: '0644' + content: | + [Unit] + Description=OCI WireGuard scoped forwarding (no NAT) + After=network-pre.target + Before=wg-quick@{{ wg_interface }}.service + [Service] + Type=oneshot + ExecStart=/usr/local/sbin/oci-wg-firewall + RemainAfterExit=yes + [Install] + WantedBy=multi-user.target + notify: 刷新专用防火墙 + +- name: 启动防火墙规则 + ansible.builtin.systemd_service: + name: oci-wg-firewall + daemon_reload: true + enabled: true + state: started + when: not ansible_check_mode + +- name: 写入只含公钥的隧道配置 + ansible.builtin.template: + src: wg.conf.j2 + dest: '/etc/wireguard/{{ wg_interface }}.conf' + owner: root + group: root + mode: '0600' + when: wg_key_file.stat.exists and (hostvars[wg_peer_host].wg_key_file | default({"stat":{"exists":false}})).stat.exists + notify: 重启 WireGuard + +- name: 启动 WireGuard + ansible.builtin.systemd_service: + name: 'wg-quick@{{ wg_interface }}' + enabled: true + state: started + when: not ansible_check_mode + +- name: 写入受管 BGP 增量配置 + ansible.builtin.template: + src: bgp.conf.j2 + dest: /etc/frr/oci-wireguard.vtysh + owner: frr + group: frr + mode: '0640' + notify: 应用 BGP 增量配置 + when: not ansible_check_mode or not packages.changed + +- name: 先完成配置变更再验证 + ansible.builtin.meta: flush_handlers + +- name: 检查 WireGuard 对端握手 + ansible.builtin.command: 'wg show {{ wg_interface }} latest-handshakes' + changed_when: false + register: wg_handshake + retries: 12 + delay: 5 + until: wg_handshake.stdout.split() | length == 2 and (wg_handshake.stdout.split()[-1] | int) > 0 + when: not ansible_check_mode + +- name: 验证隧道互通 + ansible.builtin.command: "{{ ('ping -6 -I ' ~ wg_interface ~ ' -c 3 -W 3 ' ~ bgp_transport_peer) if bgp_transport_peer is defined else ('ping -I ' ~ wg_interface ~ ' -c 3 -W 3 ' ~ wg_peer_address) }}" + changed_when: false + when: not ansible_check_mode + +- name: 验证 BGP 建邻并收到预期前缀数量 + ansible.builtin.command: vtysh -c 'show bgp summary json' + changed_when: false + register: bgp_summary + retries: 12 + delay: 5 + until: >- + (bgp_summary.stdout | from_json).get('ipv4Unicast', {}).get('peers', {}).get(bgp_transport_peer | default(wg_peer_address), {}).get('state') == 'Established' + and (bgp_summary.stdout | from_json).get('ipv4Unicast', {}).get('peers', {}).get(bgp_transport_peer | default(wg_peer_address), {}).get('pfxRcd', 0) == bgp_import | length + when: not ansible_check_mode + +- name: 验证 IPv6 iBGP 建邻与前缀 + ansible.builtin.command: vtysh -c 'show bgp summary json' + changed_when: false + register: bgp_summary6 + retries: 12 + delay: 5 + until: >- + (bgp_summary6.stdout | from_json).get('ipv6Unicast', {}).get('peers', {}).get(bgp_transport_peer | default(wg_peer_ipv6), {}).get('state') == 'Established' + and (bgp_summary6.stdout | from_json).get('ipv6Unicast', {}).get('peers', {}).get(bgp_transport_peer | default(wg_peer_ipv6), {}).get('pfxRcd', 0) == bgp_import6 | length + when: not ansible_check_mode and bgp_import6 is defined diff --git a/infrastructure/oci/ansible/roles/wireguard_bgp/templates/bgp.conf.j2 b/infrastructure/oci/ansible/roles/wireguard_bgp/templates/bgp.conf.j2 new file mode 100644 index 0000000..371c558 --- /dev/null +++ b/infrastructure/oci/ansible/roles/wireguard_bgp/templates/bgp.conf.j2 @@ -0,0 +1,80 @@ +{% set v4_peer = bgp_transport_peer | default(wg_peer_address) %} +{% set v6_peer = bgp_transport_peer | default(wg_peer_ipv6 | default("")) %} +! vtysh -f 使用配置模式,write memory 由独立 handler 执行。 +! 只重建本角色拥有的邻居和过滤器,保留既有 BGP/OSPF。 +router bgp {{ bgp_asn }} + no neighbor {{ v4_peer }} +{% for peer in bgp_retired_peers | default([]) %} + no neighbor {{ peer }} +{% endfor %} +exit +no ip prefix-list OCI-WG-IN +no ip prefix-list OCI-WG-OUT +{% for prefix in bgp_import %} +ip prefix-list OCI-WG-IN seq {{ loop.index * 10 }} permit {{ prefix }} +{% endfor %} +{% for prefix in bgp_export + (bgp_summary | default([])) %} +ip prefix-list OCI-WG-OUT seq {{ loop.index * 10 }} permit {{ prefix }} +{% endfor %} +router bgp {{ bgp_asn }} + bgp router-id {{ bgp_router_id }} + neighbor {{ v4_peer }} remote-as {{ bgp_peer_asn }} + neighbor {{ v4_peer }} description OCI-WireGuard + neighbor {{ v4_peer }} update-source {{ wg_interface }} +{% if bgp_transport_peer is defined %} + neighbor {{ v4_peer }} interface {{ wg_interface }} + neighbor {{ v4_peer }} capability extended-nexthop +{% endif %} + address-family ipv4 unicast +{% for prefix in bgp_summary | default([]) %} + aggregate-address {{ prefix }} +{% endfor %} +{% for prefix in bgp_export %} + network {{ prefix }} +{% endfor %} + neighbor {{ v4_peer }} activate + neighbor {{ v4_peer }} prefix-list OCI-WG-IN in + neighbor {{ v4_peer }} prefix-list OCI-WG-OUT out + neighbor {{ v4_peer }} maximum-prefix {{ bgp_import | length }} + exit-address-family +exit +! 仅为这些 BGP 路由选择本机业务 IP;转发报文源地址保持不变,不是 NAT。 +route-map OCI-WG-SOURCE permit 10 + match ip address prefix-list OCI-WG-IN + set src {{ bgp_router_id }} +exit +route-map OCI-WG-SOURCE permit 100 +exit +ip protocol bgp route-map OCI-WG-SOURCE + +{% if bgp_export6 is defined %} +no ipv6 prefix-list OCI-WG6-IN +no ipv6 prefix-list OCI-WG6-OUT +{% for prefix in bgp_import6 %} +ipv6 prefix-list OCI-WG6-IN seq {{ loop.index * 10 }} permit {{ prefix }} +{% endfor %} +{% for prefix in bgp_export6 + (bgp_summary6 | default([])) %} +ipv6 prefix-list OCI-WG6-OUT seq {{ loop.index * 10 }} permit {{ prefix }} +{% endfor %} +router bgp {{ bgp_asn }} + neighbor {{ v6_peer }} remote-as {{ bgp_peer_asn }} + neighbor {{ v6_peer }} update-source {{ wg_interface }} +{% if bgp_transport_peer is not defined %} + address-family ipv4 unicast + no neighbor {{ v6_peer }} activate + exit-address-family +{% endif %} + address-family ipv6 unicast +{% for prefix in bgp_summary6 | default([]) %} + aggregate-address {{ prefix }} +{% endfor %} +{% for prefix in bgp_export6 %} + network {{ prefix }} +{% endfor %} + neighbor {{ v6_peer }} activate + neighbor {{ v6_peer }} prefix-list OCI-WG6-IN in + neighbor {{ v6_peer }} prefix-list OCI-WG6-OUT out + neighbor {{ v6_peer }} maximum-prefix {{ bgp_import6 | length }} + exit-address-family +exit +{% endif %} diff --git a/infrastructure/oci/ansible/roles/wireguard_bgp/templates/firewall.sh.j2 b/infrastructure/oci/ansible/roles/wireguard_bgp/templates/firewall.sh.j2 new file mode 100644 index 0000000..8f90f0a --- /dev/null +++ b/infrastructure/oci/ansible/roles/wireguard_bgp/templates/firewall.sh.j2 @@ -0,0 +1,43 @@ +#!/bin/sh +set -eu +# 仅重建专用链,不保存/覆盖 Docker、k3s、Tailscale 或 OCI 的其他动态规则。 +for chain in OCI-WG-IN OCI-WG-FWD; do + iptables -w -nL "$chain" >/dev/null 2>&1 || iptables -w -N "$chain" + iptables -w -F "$chain" +done +iptables -w -A OCI-WG-IN -p udp --dport {{ wg_port }} -j ACCEPT +{% for prefix in [wg_peer_address ~ '/32'] + bgp_import %} +iptables -w -A OCI-WG-IN -i {{ wg_interface }} -s {{ prefix }} -j ACCEPT +{% endfor %} +{% for local_prefix in bgp_export %} +{% for remote_prefix in bgp_import %} +iptables -w -A OCI-WG-FWD -i {{ wg_interface }} -o {{ wg_lan_interface }} -s {{ remote_prefix }} -d {{ local_prefix }} -j ACCEPT +iptables -w -A OCI-WG-FWD -i {{ wg_lan_interface }} -o {{ wg_interface }} -s {{ local_prefix }} -d {{ remote_prefix }} -j ACCEPT +{% endfor %} +{% endfor %} +{% if dn42_external_interface is defined %} +# 仅允许注册地址在内部隧道与外部 DN42 之间转发,保持无 NAT。 +iptables -w -A OCI-WG-FWD -i {{ wg_interface }} -o {{ dn42_external_interface }} -s {{ dn42_ipv4 }} -d 172.20.0.0/14 -j ACCEPT +iptables -w -A OCI-WG-FWD -i {{ dn42_external_interface }} -o {{ wg_interface }} -s 172.20.0.0/14 -d {{ dn42_ipv4 }} -j ACCEPT +{% endif %} +iptables -w -A OCI-WG-FWD -i {{ wg_interface }} -j DROP +iptables -w -A OCI-WG-FWD -o {{ wg_interface }} -j DROP +iptables -w -C INPUT -j OCI-WG-IN 2>/dev/null || iptables -w -I INPUT 1 -j OCI-WG-IN +iptables -w -C FORWARD -j OCI-WG-FWD 2>/dev/null || iptables -w -I FORWARD 1 -j OCI-WG-FWD + +{% if bgp_import6 is defined %} +ip6tables -w -nL OCI-WG6-IN >/dev/null 2>&1 || ip6tables -w -N OCI-WG6-IN +ip6tables -w -F OCI-WG6-IN +ip6tables -w -A OCI-WG6-IN -i {{ wg_interface }} -s {{ dn42_ipv6 }} -j ACCEPT +ip6tables -w -C INPUT -j OCI-WG6-IN 2>/dev/null || ip6tables -w -I INPUT 1 -j OCI-WG6-IN +{% endif %} + +{% if dn42_external_interface is defined %} +ip6tables -w -nL OCI-DN42-FWD >/dev/null 2>&1 || ip6tables -w -N OCI-DN42-FWD +ip6tables -w -F OCI-DN42-FWD +ip6tables -w -A OCI-DN42-FWD -i {{ wg_interface }} -o {{ dn42_external_interface }} -s {{ dn42_ipv6 }} -d fd00::/8 -j ACCEPT +ip6tables -w -A OCI-DN42-FWD -i {{ dn42_external_interface }} -o {{ wg_interface }} -s fd00::/8 -d {{ dn42_ipv6 }} -j ACCEPT +ip6tables -w -A OCI-DN42-FWD -i {{ dn42_external_interface }} -j DROP +ip6tables -w -A OCI-DN42-FWD -o {{ dn42_external_interface }} -j DROP +ip6tables -w -C FORWARD -j OCI-DN42-FWD 2>/dev/null || ip6tables -w -I FORWARD 1 -j OCI-DN42-FWD +{% endif %} diff --git a/infrastructure/oci/ansible/roles/wireguard_bgp/templates/wg.conf.j2 b/infrastructure/oci/ansible/roles/wireguard_bgp/templates/wg.conf.j2 new file mode 100644 index 0000000..e522028 --- /dev/null +++ b/infrastructure/oci/ansible/roles/wireguard_bgp/templates/wg.conf.j2 @@ -0,0 +1,16 @@ +[Interface] +Address = {{ wg_address }}{% if wg_ipv6_address is defined %}, {{ wg_ipv6_address }}, {{ wg_linklocal_address }}{% endif %} + +ListenPort = {{ wg_port }} +MTU = {{ wg_mtu }} +Table = off +# 私钥在本机生成和读取;配置模板与控制机不保存私钥。 +PostUp = wg set %i private-key /etc/wireguard/{{ wg_interface }}.key + +[Peer] +PublicKey = {{ hostvars[wg_peer_host].wg_public_key.stdout }} +AllowedIPs = {{ ([wg_peer_address ~ '/32'] + bgp_import + (bgp_import6 | default([])) + (['fe80::/64'] if bgp_import6 is defined else [])) | join(', ') }} +{% if wg_endpoint %} +Endpoint = {{ wg_endpoint }} +{% endif %} +PersistentKeepalive = {{ wg_keepalive }} diff --git a/infrastructure/oci/ansible/site.yml b/infrastructure/oci/ansible/site.yml new file mode 100644 index 0000000..1f2316c --- /dev/null +++ b/infrastructure/oci/ansible/site.yml @@ -0,0 +1,52 @@ +--- +- import_playbook: vyos.yml + +- name: 配置无 NAT 的 WireGuard 与 BGP 站点互联 + hosts: wireguard_sites + become: true + strategy: linear + roles: + - wireguard_bgp + +- name: 为 OCI ARM 设置经 AMD 的站点回程,保留其余 Tailscale 路由 + hosts: oci_routed_hosts + become: true + tasks: + - name: 安装三个目标网段的策略路由脚本 + ansible.builtin.template: + src: templates/arm-routes.sh.j2 + dest: /usr/local/sbin/oci-site-return-routes + mode: '0755' + notify: 更新 OCI ARM 回程 + + - name: 安装回程策略启动单元 + ansible.builtin.copy: + dest: /etc/systemd/system/oci-site-return-routes.service + mode: '0644' + content: | + [Unit] + Description=Route homelab prefixes through OCI AMD rather than Tailscale + After=network-online.target + Wants=network-online.target + [Service] + Type=oneshot + ExecStart=/usr/local/sbin/oci-site-return-routes + RemainAfterExit=yes + [Install] + WantedBy=multi-user.target + notify: 更新 OCI ARM 回程 + + - name: 启用回程策略 + ansible.builtin.systemd_service: + name: oci-site-return-routes + daemon_reload: true + enabled: true + state: started + when: not ansible_check_mode + handlers: + - name: 更新 OCI ARM 回程 + ansible.builtin.systemd_service: + name: oci-site-return-routes + daemon_reload: true + state: restarted + when: not ansible_check_mode diff --git a/infrastructure/oci/ansible/tasks/frr-dn42.yml b/infrastructure/oci/ansible/tasks/frr-dn42.yml new file mode 100644 index 0000000..a18f6ed --- /dev/null +++ b/infrastructure/oci/ansible/tasks/frr-dn42.yml @@ -0,0 +1,38 @@ +--- +- name: 备份升级前 FRR 配置(仅远端) + ansible.builtin.command: cp -a /etc/frr /var/backups/frr-before-dn42 + args: + creates: /var/backups/frr-before-dn42 +- name: 安装 FRR 官方仓库签名密钥 + ansible.builtin.get_url: + url: https://deb.frrouting.org/frr/keys.gpg + dest: /usr/share/keyrings/frrouting.gpg + mode: '0644' +- name: 配置 FRR 10.7 官方仓库 + ansible.builtin.copy: + dest: /etc/apt/sources.list.d/frr.list + mode: '0644' + content: | + deb [signed-by=/usr/share/keyrings/frrouting.gpg] https://deb.frrouting.org/frr noble frr-10.7 + register: frr_repo +- name: 安装支持 link-local peering 的固定 FRR 版本 + ansible.builtin.apt: + name: + - frr=10.7.1-0~ubuntu24.04.1 + - frr-pythontools=10.7.1-0~ubuntu24.04.1 + update_cache: true + cache_valid_time: '{{ 0 if frr_repo.changed else 3600 }}' + dpkg_options: force-confdef,force-confold + register: frr_package + notify: 重启 FRR +- name: 检查 FRR 后台进程是否仍引用旧二进制 + ansible.builtin.shell: readlink /proc/$(cat /run/frr/bgpd.pid)/exe + changed_when: false + register: frr_running +- name: 调度切换已升级的 FRR 进程 + ansible.builtin.debug: + msg: 检查进程是否需要重启以使用已安装的 10.7.1 + changed_when: "'(deleted)' in frr_running.stdout" + notify: 重启 FRR +- name: 完成 FRR 升级 + ansible.builtin.meta: flush_handlers diff --git a/infrastructure/oci/ansible/templates/arm-routes.sh.j2 b/infrastructure/oci/ansible/templates/arm-routes.sh.j2 new file mode 100644 index 0000000..29bc0e4 --- /dev/null +++ b/infrastructure/oci/ansible/templates/arm-routes.sh.j2 @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +# 5101..5103 保留给此互联,优先于 Tailscale 的 52 表(规则 5270)。 +# 主表仍使用 OCI 网关;VCN 路由表再将这三个前缀交给 AMD。 +{% for prefix in wg_home_prefixes %} +if ! ip -4 rule show | grep -F 'to {{ prefix }} lookup main' >/dev/null; then + ip -4 rule add priority {{ 5100 + loop.index }} to {{ prefix }} lookup main +fi +{% endfor %} diff --git a/infrastructure/oci/ansible/templates/dn42-bgp.conf.j2 b/infrastructure/oci/ansible/templates/dn42-bgp.conf.j2 new file mode 100644 index 0000000..e82c522 --- /dev/null +++ b/infrastructure/oci/ansible/templates/dn42-bgp.conf.j2 @@ -0,0 +1,62 @@ +! Ansible 管理:首个 DN42 peer,单 IPv6 会话双 AFI。 +! 精确出口与地址范围入口;尚未接入注册表 ROA 校验。 +no ip prefix-list DN42-RB-IN +ip prefix-list DN42-RB-IN seq 1 deny 172.22.166.0/24 le 32 +ip prefix-list DN42-RB-IN seq 5 deny 172.21.111.160/27 le 32 +ip prefix-list DN42-RB-IN seq 10 deny 10.0.0.0/24 le 32 +ip prefix-list DN42-RB-IN seq 20 deny 10.60.0.0/24 le 32 +ip prefix-list DN42-RB-IN seq 30 deny 10.61.0.0/24 le 32 +ip prefix-list DN42-RB-IN seq 40 deny 192.168.10.0/24 le 32 +ip prefix-list DN42-RB-IN seq 1001 permit 172.20.0.0/24 ge 28 le 32 +ip prefix-list DN42-RB-IN seq 1002 permit 172.21.0.0/24 ge 28 le 32 +ip prefix-list DN42-RB-IN seq 1003 permit 172.22.0.0/24 ge 28 le 32 +ip prefix-list DN42-RB-IN seq 1004 permit 172.23.0.0/24 ge 28 le 32 +ip prefix-list DN42-RB-IN seq 1100 permit 172.20.0.0/14 ge 21 le 29 +ip prefix-list DN42-RB-IN seq 2001 permit 10.100.0.0/14 le 32 +ip prefix-list DN42-RB-IN seq 2002 permit 10.127.0.0/16 le 32 +ip prefix-list DN42-RB-IN seq 2003 permit 10.0.0.0/8 ge 15 le 24 +ip prefix-list DN42-RB-IN seq 3001 permit 172.31.0.0/16 le 32 +ip prefix-list DN42-RB-IN seq 9999 deny 0.0.0.0/0 le 32 +ip prefix-list DN42-RB-OUT seq 10 permit 172.21.111.160/27 +no ipv6 prefix-list DN42-RB6-IN +ipv6 prefix-list DN42-RB6-IN seq 5 deny fdd0:98df:15b0::/48 le 128 +ipv6 prefix-list DN42-RB6-IN seq 1001 permit fd00::/8 ge 44 le 64 +ipv6 prefix-list DN42-RB6-IN seq 9999 deny ::/0 le 128 +ipv6 prefix-list DN42-RB6-OUT seq 10 permit fdd0:98df:15b0::/48 +interface {{ dn42_interface }} +exit +router bgp 4242421811 + no neighbor {{ dn42_peer_linklocal }} + neighbor {{ dn42_peer_linklocal }} remote-as {{ dn42_peer_asn }} + neighbor {{ dn42_peer_linklocal }} shutdown + neighbor {{ dn42_peer_linklocal }} interface {{ dn42_interface }} + neighbor {{ dn42_peer_linklocal }} update-source {{ dn42_interface }} + neighbor {{ dn42_peer_linklocal }} description RoutedBits-OSA1 + neighbor {{ dn42_peer_linklocal }} capability extended-nexthop + neighbor {{ dn42_peer_linklocal }} timers connect 10 + address-family ipv4 unicast + neighbor {{ dn42_peer_linklocal }} activate + neighbor {{ dn42_peer_linklocal }} prefix-list DN42-RB-IN in + neighbor {{ dn42_peer_linklocal }} prefix-list DN42-RB-OUT out + neighbor {{ dn42_peer_linklocal }} maximum-prefix 10000 + exit-address-family + address-family ipv6 unicast + neighbor {{ dn42_peer_linklocal }} activate + neighbor {{ dn42_peer_linklocal }} prefix-list DN42-RB6-IN in + neighbor {{ dn42_peer_linklocal }} prefix-list DN42-RB6-OUT out + neighbor {{ dn42_peer_linklocal }} maximum-prefix 10000 + exit-address-family + no neighbor {{ dn42_peer_linklocal }} shutdown +exit +! 保留既有业务源地址规则,在其后为 DN42 路由选择注册地址;不是 NAT。 +route-map OCI-WG-SOURCE permit 20 + match ip address prefix-list DN42-RB-IN + set src 172.21.111.162 +exit +route-map DN42-SOURCE6 permit 10 + match ipv6 address prefix-list DN42-RB6-IN + set src fdd0:98df:15b0::2 +exit +route-map DN42-SOURCE6 permit 100 +exit +ipv6 protocol bgp route-map DN42-SOURCE6 diff --git a/infrastructure/oci/ansible/templates/vyos-dn42-dns.conf.j2 b/infrastructure/oci/ansible/templates/vyos-dn42-dns.conf.j2 new file mode 100644 index 0000000..e61de31 --- /dev/null +++ b/infrastructure/oci/ansible/templates/vyos-dn42-dns.conf.j2 @@ -0,0 +1,14 @@ +set service dns forwarding listen-address '192.168.10.2' +set service dns forwarding source-address '172.21.111.161' +set service dns forwarding source-address 'fdd0:98df:15b0::1' +set service dns forwarding no-serve-rfc1918 +{% for prefix in wg_home_prefixes %} +set service dns forwarding allow-from '{{ prefix }}' +{% endfor %} +{% for zone in dn42_dns_zones %} +set service dns forwarding domain {{ zone }} recursion-desired +set service dns forwarding domain {{ zone }} addnta +{% for server in dn42_dns_servers %} +set service dns forwarding domain {{ zone }} name-server '{{ server }}' +{% endfor %} +{% endfor %} diff --git a/infrastructure/oci/ansible/templates/vyos-dn42-ra.conf.j2 b/infrastructure/oci/ansible/templates/vyos-dn42-ra.conf.j2 new file mode 100644 index 0000000..41127be --- /dev/null +++ b/infrastructure/oci/ansible/templates/vyos-dn42-ra.conf.j2 @@ -0,0 +1,10 @@ +{% for lan in dn42_ra_lans %} +set interfaces ethernet {{ lan.interface }} address '{{ lan.address }}' +set service router-advert interface {{ lan.interface }} default-lifetime '0' +set service router-advert interface {{ lan.interface }} interval min '10' +set service router-advert interface {{ lan.interface }} interval max '30' +set service router-advert interface {{ lan.interface }} prefix {{ lan.prefix }} preferred-lifetime '14400' +set service router-advert interface {{ lan.interface }} prefix {{ lan.prefix }} valid-lifetime '86400' +set service router-advert interface {{ lan.interface }} route fd00::/8 route-preference 'medium' +set service router-advert interface {{ lan.interface }} route fd00::/8 valid-lifetime '180' +{% endfor %} diff --git a/infrastructure/oci/ansible/templates/vyos-key-bootstrap.vbash.j2 b/infrastructure/oci/ansible/templates/vyos-key-bootstrap.vbash.j2 new file mode 100644 index 0000000..2cb5bf3 --- /dev/null +++ b/infrastructure/oci/ansible/templates/vyos-key-bootstrap.vbash.j2 @@ -0,0 +1,12 @@ +source /opt/vyatta/etc/functions/script-template +if /bin/cli-shell-api existsActive interfaces wireguard wg42 private-key; then + echo KEY_EXISTS + builtin exit 0 +fi +configure +set interfaces wireguard wg42 address '10.255.254.2/30' +set interfaces wireguard wg42 private-key "$(wg genkey)" +commit || builtin exit 1 +save || builtin exit 1 +exit +echo KEY_CREATED diff --git a/infrastructure/oci/ansible/templates/vyos-site.conf.j2 b/infrastructure/oci/ansible/templates/vyos-site.conf.j2 new file mode 100644 index 0000000..f1f11a3 --- /dev/null +++ b/infrastructure/oci/ansible/templates/vyos-site.conf.j2 @@ -0,0 +1,84 @@ +set interfaces wireguard wg42 description 'OCI AMD / DN42 internal transport' +set interfaces wireguard wg42 address '172.21.111.161/32' +set interfaces wireguard wg42 address 'fdd0:98df:15b0:ffff::2/64' +set interfaces wireguard wg42 mtu '1380' +set interfaces wireguard wg42 port '51820' +set interfaces wireguard wg42 peer amd address '129.225.176.134' +set interfaces wireguard wg42 peer amd port '51820' +set interfaces wireguard wg42 peer amd public-key '{{ hostvars.oci_amd.wg_public_key.stdout | trim }}' +set interfaces wireguard wg42 peer amd persistent-keepalive '25' +set interfaces wireguard wg42 peer amd allowed-ips '10.255.254.1/32' +set interfaces wireguard wg42 peer amd allowed-ips '10.0.0.0/24' +set interfaces wireguard wg42 peer amd allowed-ips '172.21.111.162/32' +set interfaces wireguard wg42 peer amd allowed-ips 'fdd0:98df:15b0::/48' +set interfaces loopback lo address 'fdd0:98df:15b0::1/128' +set protocols static route 172.21.111.160/27 blackhole distance '254' +set protocols static route6 fdd0:98df:15b0::/48 blackhole distance '254' +set protocols bgp system-as '{{ dn42_asn }}' +set protocols bgp parameters router-id '172.21.111.161' +{% for p in ['10.0.0.0/24', '172.21.111.162/32', '172.20.0.0/14'] %} +set policy prefix-list OCI-SITE-IN rule {{ loop.index * 10 }} action 'permit' +set policy prefix-list OCI-SITE-IN rule {{ loop.index * 10 }} prefix '{{ p }}' +{% endfor %} +{% for p in wg_home_prefixes + [dn42_ipv4] %} +set protocols bgp address-family ipv4-unicast network '{{ p }}' +set policy prefix-list OCI-SITE-OUT rule {{ loop.index * 10 }} action 'permit' +set policy prefix-list OCI-SITE-OUT rule {{ loop.index * 10 }} prefix '{{ p }}' +{% endfor %} +set protocols bgp address-family ipv6-unicast network '{{ dn42_ipv6 }}' +set policy prefix-list6 OCI-SITE6-IN rule 10 action 'permit' +set policy prefix-list6 OCI-SITE6-IN rule 10 prefix 'fdd0:98df:15b0::2/128' +set policy prefix-list6 OCI-SITE6-OUT rule 10 action 'permit' +set policy prefix-list6 OCI-SITE6-OUT rule 10 prefix '{{ dn42_ipv6 }}' +set policy prefix-list OCI-TO-OSPF rule 10 action 'permit' +set policy prefix-list OCI-TO-OSPF rule 10 prefix '10.0.0.0/24' +set policy route-map OCI-TO-OSPF rule 10 action 'permit' +set policy route-map OCI-TO-OSPF rule 10 match ip address prefix-list 'OCI-TO-OSPF' +set protocols ospf redistribute bgp route-map 'OCI-TO-OSPF' +set protocols ospf redistribute bgp metric-type '1' +set policy prefix-list DN42-TO-OSPF rule 10 action 'permit' +set policy prefix-list DN42-TO-OSPF rule 10 prefix '{{ dn42_ipv4 }}' +set policy route-map DN42-TO-OSPF rule 10 action 'permit' +set policy route-map DN42-TO-OSPF rule 10 match ip address prefix-list 'DN42-TO-OSPF' +set protocols ospf redistribute static route-map 'DN42-TO-OSPF' +set protocols ospf redistribute static metric-type '1' +set interfaces wireguard wg42 peer amd allowed-ips 'fe80::/64' + +set interfaces wireguard wg42 peer amd allowed-ips '172.20.0.0/14' +set interfaces wireguard wg42 peer amd allowed-ips 'fd00::/8' +set policy prefix-list6 OCI-SITE6-IN rule 20 action 'permit' +set policy prefix-list6 OCI-SITE6-IN rule 20 prefix 'fd00::/8' +set policy prefix-list OCI-TO-OSPF rule 20 action 'permit' +set policy prefix-list OCI-TO-OSPF rule 20 prefix '172.20.0.0/14' + +set interfaces wireguard wg42 address 'fe80::1811:1/64' +set protocols bgp neighbor fe80::1811:2 remote-as '{{ dn42_asn }}' +set protocols bgp neighbor fe80::1811:2 description 'OCI AMD internal MP-BGP' +set protocols bgp neighbor fe80::1811:2 interface source-interface 'wg42' +set protocols bgp neighbor fe80::1811:2 update-source 'fe80::1811:1' +set protocols bgp neighbor fe80::1811:2 capability extended-nexthop +set protocols bgp neighbor fe80::1811:2 address-family ipv4-unicast prefix-list import 'OCI-SITE-IN' +set protocols bgp neighbor fe80::1811:2 address-family ipv4-unicast prefix-list export 'OCI-SITE-OUT' +set protocols bgp neighbor fe80::1811:2 address-family ipv6-unicast prefix-list import 'OCI-SITE6-IN' +set protocols bgp neighbor fe80::1811:2 address-family ipv6-unicast prefix-list export 'OCI-SITE6-OUT' + +{% for path in vyos_retired_paths %} +{% if vyos_retired_present.results[loop.index0].rc == 0 %} +delete {{ path }} +{% endif %} +{% endfor %} +set nat source rule 18100 description 'Exclude local DN42 allocation' +set nat source rule 18100 outbound-interface name 'wg42' +set nat source rule 18100 destination address '172.21.111.160/27' +set nat source rule 18100 exclude +{% for prefix in wg_home_prefixes %} +set nat source rule {{ 18100 + loop.index * 10 }} description 'LAN to DN42 masquerade' +set nat source rule {{ 18100 + loop.index * 10 }} outbound-interface name 'wg42' +set nat source rule {{ 18100 + loop.index * 10 }} source address '{{ prefix }}' +set nat source rule {{ 18100 + loop.index * 10 }} destination address '172.20.0.0/14' +set nat source rule {{ 18100 + loop.index * 10 }} translation address 'masquerade' +{% endfor %} +set policy route-map OCI-MP-IN rule 10 action 'permit' +set policy route-map OCI-MP-IN rule 10 set ipv6-next-hop prefer-global +set protocols bgp neighbor fe80::1811:2 address-family ipv4-unicast route-map import 'OCI-MP-IN' +set protocols bgp neighbor fe80::1811:2 address-family ipv6-unicast route-map import 'OCI-MP-IN' diff --git a/infrastructure/oci/ansible/vyos.yml b/infrastructure/oci/ansible/vyos.yml new file mode 100644 index 0000000..6b7a8eb --- /dev/null +++ b/infrastructure/oci/ansible/vyos.yml @@ -0,0 +1,84 @@ +--- +- name: 读取 AMD 的现有 WireGuard 公钥 + hosts: oci_amd + become: true + gather_facts: false + tasks: + - name: 仅交换公钥 + ansible.builtin.shell: wg pubkey < /etc/wireguard/wg-oci.key + register: wg_public_key + changed_when: false + check_mode: false + +- name: 配置 VyOS 站点与 DN42 内部路由 + hosts: site_routers + gather_facts: false + vars: + vyos_retired_paths: + - interfaces wireguard wg42 address 10.255.254.2/30 + - interfaces loopback lo address 172.21.111.161/32 + - protocols bgp neighbor 10.255.254.1 + - protocols bgp neighbor fdd0:98df:15b0:ffff::1 + tasks: + - name: 检查 VyOS 是否已有 WireGuard 私钥,仅返回存在状态 + ansible.builtin.command: + argv: [ssh, -o, BatchMode=yes, -o, ConnectTimeout=10, 'vyos@192.168.10.2', /bin/cli-shell-api, existsActive, interfaces, wireguard, wg42, private-key] + delegate_to: localhost + vars: + ansible_connection: local + ansible_python_interpreter: /usr/bin/python3 + register: vyos_key_present + changed_when: false + failed_when: vyos_key_present.rc not in [0, 1] + check_mode: false + + - name: 首次在 VyOS 本机生成并保存私钥,控制机不接收私钥 + ansible.builtin.command: + argv: [ssh, -o, BatchMode=yes, -o, ConnectTimeout=10, 'vyos@192.168.10.2', /bin/vbash, -s] + stdin: "{{ lookup('template', 'templates/vyos-key-bootstrap.vbash.j2') }}" + delegate_to: localhost + vars: + ansible_connection: local + ansible_python_interpreter: /usr/bin/python3 + no_log: true + register: vyos_key_bootstrap + changed_when: true + when: not ansible_check_mode and vyos_key_present.rc == 1 + + - name: 读取 VyOS WireGuard 公钥 + vyos.vyos.vyos_command: + commands: show interfaces wireguard wg42 public-key + register: vyos_wg_public + changed_when: false + when: not ansible_check_mode + + - name: 准備 Linux 端需要的公钥事实 + ansible.builtin.set_fact: + wg_public_key: + stdout: '{{ vyos_wg_public.stdout[0] | trim }}' + wg_key_file: + stat: + exists: true + when: not ansible_check_mode + + - name: 检查待退役的旧地址和 BGP 邻居是否存在 + ansible.builtin.command: + cmd: 'ssh -o BatchMode=yes vyos@192.168.10.2 /bin/cli-shell-api existsActive {{ item }}' + loop: '{{ vyos_retired_paths }}' + delegate_to: localhost + vars: + ansible_connection: local + ansible_python_interpreter: /usr/bin/python3 + register: vyos_retired_present + changed_when: false + failed_when: vyos_retired_present.rc not in [0, 1] + check_mode: false + + - name: 应用 VyOS 双栈 WireGuard、iBGP 与精确路由过滤 + vyos.vyos.vyos_config: + lines: "{{ lookup('template', 'templates/vyos-site.conf.j2').splitlines() | map('trim') | reject('equalto', '') | list }}" + save: true + comment: Ansible OCI WireGuard DN42 preparation + register: vyos_site_config + changed_when: (vyos_site_config.commands | default([]) | length) > 0 + no_log: true diff --git a/infrastructure/oci/terraform/.gitignore b/infrastructure/oci/terraform/.gitignore new file mode 100644 index 0000000..a353cef --- /dev/null +++ b/infrastructure/oci/terraform/.gitignore @@ -0,0 +1,9 @@ +.terraform/ +*.tfstate* +*.tfplan +*.tfvars +*.tfvars.json +*.auto.tfvars* +crash.log +crash.*.log +.recovery/ diff --git a/infrastructure/oci/terraform/.terraform.lock.hcl b/infrastructure/oci/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..400cbf2 --- /dev/null +++ b/infrastructure/oci/terraform/.terraform.lock.hcl @@ -0,0 +1,23 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/oracle/oci" { + version = "9.1.0" + hashes = [ + "h1:luO69Wv8J0GKbkd+hr2UbouenTZBAPhyAxTQacJAeGU=", + "zh:0343d74d10e8c0b803874a303bea5b6619e76762ace8fdafb1bae6d867c3ece0", + "zh:077ac3c561c1b9af97c9325900b65825279537a8f85074adcf3f11af151e387e", + "zh:2571fc3f274fb081aae27d7b9fd150e3ab39309fa15241bf48b8877b8f8d8529", + "zh:26e16906006b6cb3022a06abc21884f08e1b35696bb7259b92d4aaf0e8056a17", + "zh:3882078acbfddb13a5b64d37afcded15ca56d3381f46314e4869e68dfc7f2759", + "zh:3d9b6c5e1362abbb7469cd096e68a7c1bf642959621784b6b096f0fae97dcf89", + "zh:462ef1bb079e78aadd50377fde647144c8bb0da324c314e997f4d15af81991f4", + "zh:510f88ab1592960c73fc004de8329ceff26803d470e22522623aefb99e917163", + "zh:573a7139600f21d5ae1e733317948b6f352a5434d7d06e0d3910211f79d4f286", + "zh:647b750d4fa8d982cbeef73d2beeec1dc7076ee32ee8833d0ff634e2f7ccdad0", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:b1539e73622d3ee4fe11c951c11063341b8b302a167a9bad6712bb0e075ccdb7", + "zh:d4f1012d1a62bdc69230d7e3adc5f3bcb78a755adb858d7e6cd6af0200255828", + "zh:fa11dc03a714e8f7056bb5ff92646f1f34dea74339650b5fafdd923a5bcc5864", + ] +} diff --git a/infrastructure/oci/terraform/amd.tf b/infrastructure/oci/terraform/amd.tf new file mode 100644 index 0000000..72697b0 --- /dev/null +++ b/infrastructure/oci/terraform/amd.tf @@ -0,0 +1,45 @@ +# E2 Micro 使用独立的 x86_64 镜像;固定版本以免镜像目录更新触发重建。 +resource "oci_core_instance" "amd" { + availability_domain = oci_core_instance.vm.availability_domain + compartment_id = var.compartment_ocid + display_name = "homelab-amd" + shape = "VM.Standard.E2.1.Micro" + + create_vnic_details { + subnet_id = oci_core_subnet.public.id + skip_source_dest_check = true + nsg_ids = [oci_core_network_security_group.wireguard.id] + assign_public_ip = true + hostname_label = "homelab-amd" + display_name = "homelab-amd" + } + + # 仅复用登录公钥,不复制原实例可能新增的 user_data 等初始化内容。 + metadata = { + ssh_authorized_keys = var.instance_metadata["ssh_authorized_keys"] + } + + source_details { + source_type = "image" + source_id = "ocid1.image.oc1.ap-osaka-1.aaaaaaaap3ki4u45x7acf3q3upe6sudoe4l3ogin55dmgp3gye6lvhhjg6mq" + boot_volume_size_in_gbs = 50 + boot_volume_vpus_per_gb = 10 + } + + lifecycle { + prevent_destroy = true + } +} + +output "amd_instance_id" { + value = oci_core_instance.amd.id +} +output "amd_private_ip" { + value = oci_core_instance.amd.private_ip +} +output "amd_public_ip" { + value = oci_core_instance.amd.public_ip +} +output "amd_ssh_command" { + value = "ssh ubuntu@${oci_core_instance.amd.public_ip}" +} diff --git a/infrastructure/oci/terraform/backend.oci.hcl.example b/infrastructure/oci/terraform/backend.oci.hcl.example new file mode 100644 index 0000000..36799fc --- /dev/null +++ b/infrastructure/oci/terraform/backend.oci.hcl.example @@ -0,0 +1,8 @@ +# versions.tf 已配置相同的 OCI backend;此文件保留为参数参考。 +# 只连接已有对象,禁止把恢复副本迁移覆盖远端。 +bucket = "oci-k8s-free-tier-tfstate" +namespace = "axckv9ylwqxr" +key = "terraform.tfstate" +region = "ap-osaka-1" +auth = "APIKey" +config_file_profile = "DEFAULT" diff --git a/infrastructure/oci/terraform/compute.tf b/infrastructure/oci/terraform/compute.tf new file mode 100644 index 0000000..140ac18 --- /dev/null +++ b/infrastructure/oci/terraform/compute.tf @@ -0,0 +1,53 @@ +resource "oci_core_instance" "vm" { + agent_config { + are_all_plugins_disabled = false + is_management_disabled = false + is_monitoring_disabled = false + } + availability_config { + is_live_migration_preferred = false + recovery_action = "RESTORE_INSTANCE" + } + availability_domain = "diPX:AP-OSAKA-1-AD-1" + compartment_id = var.compartment_ocid + create_vnic_details { + assign_private_dns_record = false + assign_public_ip = "true" + display_name = "homelab-vm" + hostname_label = "homelab-vm" + private_ip = "10.0.0.124" + skip_source_dest_check = false + subnet_id = oci_core_subnet.public.id + } + display_name = "homelab-vm" + fault_domain = "FAULT-DOMAIN-1" + instance_options { + are_legacy_imds_endpoints_disabled = false + } + launch_options { + boot_volume_type = "PARAVIRTUALIZED" + firmware = "UEFI_64" + is_consistent_volume_naming_enabled = true + is_pv_encryption_in_transit_enabled = false + network_type = "PARAVIRTUALIZED" + remote_data_volume_type = "PARAVIRTUALIZED" + } + metadata = var.instance_metadata + shape = "VM.Standard.A1.Flex" + shape_config { + memory_in_gbs = 12 + ocpus = 2 + } + source_details { + boot_volume_size_in_gbs = "100" + boot_volume_vpus_per_gb = "10" + source_id = var.image_ocid + source_type = "image" + } + + # 原生命周期配置不可恢复;保护现有 VM,避免重建丢失系统盘数据。 + lifecycle { + prevent_destroy = true + } +} + diff --git a/infrastructure/oci/terraform/data.tf b/infrastructure/oci/terraform/data.tf new file mode 100644 index 0000000..29c3f01 --- /dev/null +++ b/infrastructure/oci/terraform/data.tf @@ -0,0 +1,13 @@ +data "oci_core_images" "ubuntu" { + compartment_id = var.compartment_ocid + operating_system = "Canonical Ubuntu" + operating_system_version = "24.04" + shape = "VM.Standard.A1.Flex" + sort_by = "TIMECREATED" + sort_order = "DESC" +} + +data "oci_identity_availability_domains" "ads" { + compartment_id = var.compartment_ocid +} + diff --git a/infrastructure/oci/terraform/network.tf b/infrastructure/oci/terraform/network.tf new file mode 100644 index 0000000..dd26b2d --- /dev/null +++ b/infrastructure/oci/terraform/network.tf @@ -0,0 +1,103 @@ +resource "oci_core_internet_gateway" "igw" { + compartment_id = var.compartment_ocid + display_name = "homelab-igw" + enabled = true + vcn_id = oci_core_vcn.vcn.id +} + +resource "oci_core_route_table" "public" { + compartment_id = var.compartment_ocid + display_name = "homelab-public-rt" + route_rules { + destination = "0.0.0.0/0" + destination_type = "CIDR_BLOCK" + network_entity_id = oci_core_internet_gateway.igw.id + } + dynamic "route_rules" { + for_each = toset(local.wireguard_home_prefixes) + content { + destination = route_rules.value + destination_type = "CIDR_BLOCK" + network_entity_id = var.amd_router_private_ip_ocid + } + } + vcn_id = oci_core_vcn.vcn.id +} + +resource "oci_core_security_list" "public" { + compartment_id = var.compartment_ocid + display_name = "homelab-public-sl" + egress_security_rules { + description = "" + destination = "0.0.0.0/0" + destination_type = "CIDR_BLOCK" + protocol = "all" + stateless = false + } + ingress_security_rules { + description = "" + icmp_options { + code = 4 + type = 3 + } + protocol = "1" + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + stateless = false + } + ingress_security_rules { + description = "" + protocol = "17" + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + stateless = false + udp_options { + max = 41641 + min = 41641 + } + } + ingress_security_rules { + description = "" + protocol = "6" + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + stateless = false + tcp_options { + max = 22 + min = 22 + } + } + # 内层源地址保持家中网段;OCI 安全列表须允许这些已路由的流量。 + dynamic "ingress_security_rules" { + for_each = toset(local.wireguard_home_prefixes) + content { + protocol = "all" + source = ingress_security_rules.value + source_type = "CIDR_BLOCK" + stateless = false + description = "WireGuard routed homelab traffic" + } + } + vcn_id = oci_core_vcn.vcn.id +} + +resource "oci_core_subnet" "public" { + cidr_block = "10.0.0.0/24" + compartment_id = var.compartment_ocid + dhcp_options_id = oci_core_vcn.vcn.default_dhcp_options_id + display_name = "homelab-public-subnet" + dns_label = "public" + prohibit_public_ip_on_vnic = false + route_table_id = oci_core_route_table.public.id + security_list_ids = [oci_core_security_list.public.id] + vcn_id = oci_core_vcn.vcn.id +} + +resource "oci_core_vcn" "vcn" { + cidr_blocks = ["10.0.0.0/16"] + compartment_id = var.compartment_ocid + display_name = "homelab-vcn" + dns_label = "homelab" + is_ipv6enabled = false +} + diff --git a/infrastructure/oci/terraform/outputs.tf b/infrastructure/oci/terraform/outputs.tf new file mode 100644 index 0000000..03f7ef8 --- /dev/null +++ b/infrastructure/oci/terraform/outputs.tf @@ -0,0 +1,16 @@ +output "instance_id" { + value = oci_core_instance.vm.id +} + +output "instance_private_ip" { + value = oci_core_instance.vm.private_ip +} + +output "instance_public_ip" { + value = oci_core_instance.vm.public_ip +} + +output "ssh_command" { + value = "ssh ubuntu@${oci_core_instance.vm.public_ip}" +} + diff --git a/infrastructure/oci/terraform/prepare-local-state.py b/infrastructure/oci/terraform/prepare-local-state.py new file mode 100644 index 0000000..533c777 --- /dev/null +++ b/infrastructure/oci/terraform/prepare-local-state.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""从已下载的 state 初始化本地恢复环境,不输出 metadata,也不覆盖现有文件。""" +import json +import os +from pathlib import Path +import sys + +os.umask(0o077) +source = Path(sys.argv[1]) +raw = source.read_bytes() +state = json.loads(raw) +vm = next(r for r in state['resources'] if r['mode'] == 'managed' + and r['type'] == 'oci_core_instance' and r['name'] == 'vm') +root = Path(__file__).resolve().parent +local = root / '.recovery' / 'terraform.tfstate' +variables = root / 'terraform.tfvars.json' +if local.exists() or variables.exists(): + sys.exit('本地 state 或 tfvars 已存在;请先核对并自行备份,脚本不会覆盖。') +local.parent.mkdir(mode=0o700, exist_ok=True) +with local.open('xb') as f: + f.write(raw) +with variables.open('x') as f: + json.dump({'instance_metadata': vm['instances'][0]['attributes']['metadata']}, f, indent=2) + f.write('\n') +print('已准备本地 state 和 metadata 变量;未修改远端。') diff --git a/infrastructure/oci/terraform/providers.tf b/infrastructure/oci/terraform/providers.tf new file mode 100644 index 0000000..2cc1957 --- /dev/null +++ b/infrastructure/oci/terraform/providers.tf @@ -0,0 +1,4 @@ +provider "oci" { + region = var.region + config_file_profile = var.oci_config_profile +} diff --git a/infrastructure/oci/terraform/quota.tf b/infrastructure/oci/terraform/quota.tf new file mode 100644 index 0000000..2c1fc77 --- /dev/null +++ b/infrastructure/oci/terraform/quota.tf @@ -0,0 +1,7 @@ +resource "oci_limits_quota" "free_tier_quota" { + compartment_id = var.compartment_ocid + description = "Enforce Always Free tier compliance for compute and storage." + name = "always-free-tier-quota" + statements = ["set compute-core quota standard-a1-core-count to 4 in tenancy", "set compute-memory quota standard-a1-memory-count to 24 in tenancy", "set block-storage quota volume-count to 10 in tenancy", "set block-storage quota total-storage-gb to 200 in tenancy"] +} + diff --git a/infrastructure/oci/terraform/variables.tf b/infrastructure/oci/terraform/variables.tf new file mode 100644 index 0000000..d192283 --- /dev/null +++ b/infrastructure/oci/terraform/variables.tf @@ -0,0 +1,22 @@ +variable "region" { + type = string + default = "ap-osaka-1" +} +variable "oci_config_profile" { + type = string + default = "DEFAULT" +} +variable "compartment_ocid" { + type = string + default = "ocid1.tenancy.oc1..aaaaaaaalncmdinfnljot5oh7rqleavphhsa6jpyurfu4luztcct3tp5gsfa" +} +# 固定现有启动镜像;查询最新 Ubuntu 镜像不应触发现有实例重建。 +variable "image_ocid" { + type = string + default = "ocid1.image.oc1.ap-osaka-1.aaaaaaaayniglvbm5xyligxib2nk3jz3zendq6ywckp4gppteii4tg7wzhua" +} +variable "instance_metadata" { + description = "从受限 state 副本提取的原实例 metadata;仅存于忽略的本地 tfvars。" + type = map(string) + sensitive = true +} diff --git a/infrastructure/oci/terraform/versions.tf b/infrastructure/oci/terraform/versions.tf new file mode 100644 index 0000000..0799c4d --- /dev/null +++ b/infrastructure/oci/terraform/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.15.8, < 2.0.0" + required_providers { + oci = { + source = "oracle/oci" + version = "= 9.1.0" + } + } + # 已停用旧执行流程,当前根模块直接管理既有对象;不迁移本地恢复副本。 + backend "oci" { + bucket = "oci-k8s-free-tier-tfstate" + namespace = "axckv9ylwqxr" + key = "terraform.tfstate" + region = "ap-osaka-1" + auth = "APIKey" + config_file_profile = "DEFAULT" + } +} diff --git a/infrastructure/oci/terraform/wireguard.tf b/infrastructure/oci/terraform/wireguard.tf new file mode 100644 index 0000000..c33a5d0 --- /dev/null +++ b/infrastructure/oci/terraform/wireguard.tf @@ -0,0 +1,48 @@ +locals { + wireguard_home_prefixes = ["192.168.10.0/24", "10.60.0.0/24", "10.61.0.0/24"] +} + +# route table -> subnet -> instance 已存在依赖,不能反向引用实例的私有 IP。 +# 此值从现有 AMD VNIC 核对;重建实例后必须重新查询,禁止继续使用旧 OCID。 +variable "amd_router_private_ip_ocid" { + type = string + default = "ocid1.privateip.oc1.ap-osaka-1.abvwsljrhxq2cw46zhjyrnn5zbz4rrynjbzlynkz6ubhiedadr72zcaideaq" +} + +resource "oci_core_network_security_group" "wireguard" { + compartment_id = var.compartment_ocid + vcn_id = oci_core_vcn.vcn.id + display_name = "homelab-amd-wireguard" +} + +resource "oci_core_network_security_group_security_rule" "wireguard" { + network_security_group_id = oci_core_network_security_group.wireguard.id + direction = "INGRESS" + protocol = "17" + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + stateless = false + description = "WireGuard NAT traversal; peer public key authenticates clients" + udp_options { + destination_port_range { + min = 51820 + max = 51820 + } + } +} + +resource "oci_core_network_security_group_security_rule" "dn42_first_peer" { + network_security_group_id = oci_core_network_security_group.wireguard.id + direction = "INGRESS" + protocol = "17" + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + stateless = false + description = "DN42 first external WireGuard peer; public key authentication" + udp_options { + destination_port_range { + min = 51821 + max = 51821 + } + } +}