How-To:Linux 架設 WireGuard 伺服器

操作環境:

  • 伺服端:RHEL 9.3
  • 客戶端:Fedora 38

 

WireGuard 是一款著重於高效能、實作簡化及低攻擊面的 VPN 方案,它是以 Linux 內核模組的形式運作,但另有以 Go 語言實作的跨平台版本(wireguard-go)。WireGuard 的設計其實並無伺服端(server)與客戶端(client)的分野,兩個端點(peer)可以用完全相等的設定方式來建立彼此之間的安全連線。不過本文流程還是會將其中一部主機(RHEL)視為伺服端,以服務的形式來管理 WireGuard;另一部主機(Fedora)則會表現得更像是一般的客戶端,並且透過伺服端來轉發連線。

 

在這樣的情境下,伺服器上的主要工程,是設定作業系統的防火牆、實作 NAT,然後為各個客戶端指定 VPN 的私有位址;客戶端則單純許多,除了一些較特別的應用情境外,基本上無須再設定防火牆。只要透過連線工具連接至 WireGuard 伺服器,並藉由所設定的內容來指定是全部、或者是特定的網段要透過 VPN 連線。

 

WireGuard 已合併至 Linux 內核 5.6 以後的版本,至於較舊的內核版本,則需要再安裝相應的模組(wireguard-linux-compat)。除了手動編譯模組的途徑外,許多 Linux 發行版可能也能夠使用編譯好的 kmod-wireguard 套件。或者是找尋有提供 wireguard-dkms 套件的套件來源,利用 DKMS 為當前使用的內核版本自動編譯 wireguard 模組。此外本文多數操作指令會需要 root 權限,可利用 sudo 或以其它途徑來達成,後續不再特別贅述。

 

1.1 安裝 WireGuard 工具程式

由於 RHEL 9 及 Fedora 38 所用的內核版本皆已內建 WireGuard 支援,因此僅需要再安裝 wireguard-tools 套件:

  • dnf install wireguard-tools

 

1.2 建立私鑰與公鑰

有點類似於 SSH 的概念,WireGuard 最基本的安全性及身份識別機制,是每一個端點都應該有自己一組獨特的私鑰及公鑰,私鑰寫入在本地端的設定檔裡,公鑰則寫入到目標端的設定檔裡。連線時一旦公私鑰是匹配的,即可順利建立 VPN 連線。可使用 wg 工具來產生私鑰及公鑰:

  • umask 077; wg genkey | tee privatekey | wg pubkey > publickey

 

前述指令可以分別在伺服端及客戶端上執行一次,或者由單一機器執行多次、產生多組公私鑰再分配出去。執行結果會產生 privatekey 及 publickey 這兩支文字檔,其內容就分別是私鑰及公鑰的字串。本文後續步驟會示範如何填入到設定檔裡,在完成填入後文字檔便無須保留,可自行刪除或另外找地方存放。

 

2.1 建立伺服端設定檔

於伺服端上執行以下步驟:

  • mkdir -p /etc/wireguard/
  • vim /etc/wireguard/wg0.conf

 

在 wg0.conf 裡寫入以下內容:

[Interface]
Address = 10.9.0.1/24
ListenPort = 25034
PrivateKey = <伺服端私鑰>

[Peer]
PublicKey = <客戶端公鑰>
AllowedIPs = 10.9.0.2/32

 

以上是將伺服端的私有位址指定為 10.9.0.1,監聽連接埠為 25034,以及客戶端私有位址為 10.9.0.2,這些都是可以自行決定的項目。此外也可以加入多個 [Peer] 區塊,並以不同的公鑰及私有位址來作為區隔。設定檔的名稱「wg0.conf」代表的就是名為 wg0 的 WireGuard 網路介面。所以也能夠以此類推,分別建立出 wg1、wg2 等多個不同的介面。

 

2.2 伺服端上的網路與防火牆設定

首先啟用封包轉遞,編輯 /etc/sysctl.conf,並加入以下內容:

net.ipv4.ip_forward = 1

 

然後使其生效:

  • sysctl -p

 

接著,使用 firewall-cmd 來啟用 IP 偽裝及放行 UDP 25034 連接埠:

  • firewall-cmd --permanent --zone=public --add-port=25034/udp
  • firewall-cmd --permanent --zone=public --add-masquerade
  • firewall-cmd --reload

 

倘若伺服器的防火牆工具是使用 firewalld v1.0.0 以上版本(例如 RHEL 9 等發行版),那麼前述指令會不夠完善,一旦希望 WireGuard 各客戶端之間能夠彼此通訊將會遇到障礙。而一種可行的修正方式,是將 WireGuard 網路介面(例如 wg0)加入到 trusted 區域中:

  • firewall-cmd --permanent --zone=trusted --add-interface=wg0
  • firewall-cmd --reload

 

最後,可以透過以下指令的輸出結果,來確認封包轉遞及防火牆規則是否皆正確設定:

  • cat /proc/sys/net/ipv4/ip_forward
  • firewall-cmd --list-all-zones

 

2.3 以服務形式啟動 WireGuard

  • systemctl enable wg-quick@wg0
  • systemctl start wg-quick@wg0

 

此服務會背景執行 wg-quick 工具以啟動 WireGuard 網路介面。倘若未執行成功,可利用以下指令檢視錯誤訊息以進行進一步的排查:

  • systemctl status wg-quick@wg0 -l

 

3.1 建立客戶端設定檔

同樣是建立一支名為 wg0.conf 的檔案,但路徑不拘,合適的檔案權限則為 600。設定內容與先前的伺服端設定檔相呼應,如下所示:

[Interface]
Address = 10.9.0.2/24
PrivateKey = <客戶端私鑰>

[Peer]
PublicKey = <伺服端公鑰>
AllowedIPs = 0.0.0.0/0
Endpoint = <伺服端的公共 IP 位址或網域>:25034

 

在設定檔裡,AllowedIPs 決定了哪些網段會透過 VPN 連線,0.0.0.0/0 即表示是全部的 IPv4 位址。此外雖然本文範例皆是在 Linux 環境下,但倘若客戶端為 Windows 系統,則客戶端設定的 [Interface] 下應再加上 DNS 參數,來指定 DNS 伺服器位址。例如「DNS = 8.8.8.8, 8.8.4.4」,以防 Windows 客戶端透過 VPN 連線後出現無法解析域名的情形。

 

3.2 啟動或關閉客戶端 WireGuard 連線

執行 wg-quick 指令,並帶入如 wg0.conf 等設定檔的位置,便能夠啟動或關閉 WireGuard 連線:

  • wg-quick up ./wg0.conf
  • wg-quick down ./wg0.conf

 

若設定檔是位於當前目錄,則「./」是必要的,否則執行時會變成從 /etc/wireguard/ 目錄搜尋相關設定。而這也表示,以下指令的執行結果會是相同的:

  • wg-quick up wg0
  • wg-quick up /etc/wireguard/wg0.conf

 

4.1 連線狀態檢查

在前述的操作流程裡,伺服端與客戶端最終都會啟動一個名為 wg0 的網路介面,可以用 ifconfig 等工具來查看。或者是單純地執行 wg 指令,也會顯示當前的 WireGuard 狀態。由於在本文的範例裡,客戶端會透過 VPN 來對外連線,所以可以去找尋網路上能夠回傳客戶端 IP 位址的網站,藉此檢驗連線 IP 是否確實出現變化。此外也可以使用諸如 mtr 等指令,來檢視對外連線的路由節點。

 

5.1 與 IPv6 有關之設定

除了基本的 IPv4 連線外,倘若也希望能夠透過 VPN 來連接 IPv6 網際網路。那麼伺服器就需要具備 IPv6 網際網路的連線能力,至於客戶端本身是否可連接 IPv6 網際網路則不是必要的。因此同樣地,也需要在伺服器上啟用 IPv6 的封包轉遞及 IP 偽裝。首先編輯 /etc/sysctl.conf,加入以下設定;

net.ipv6.conf.all.forwarding = 1

 

然而因 Linux 具體設定 IPv6 網路的流程而異,對 IPv6 啟用封包轉遞(forwarding),有可能會與另一用於偵測 IPv6 預設路由的設定(accept_ra)衝突。所構成的結果,是有可能一旦啟用 IPv6 封包轉遞,反而會讓系統抓不到預設路由,而喪失對 IPv6 的連線能力。一種解決方案,是明確地對網卡介面(假設為 eth0)指定 accept_ra 設定值為 2,同樣是寫入到 /etc/sysctl.conf 裡:

net.ipv6.conf.eth0.accept_ra = 2

 

儲存後也是執行「sysctl -p」使其生效。接著則是啟用 IP 偽裝,同樣是透過 firewall-cmd 設定防火牆規則,但內容略為不同:

  • firewall-cmd --permanent --zone=public --add-rich-rule="rule family=ipv6 masquerade"
  • firewall-cmd --reload

 

完成前述設定後,延續先前的 WireGuard 設定檔範例,首先在伺服端的設定檔內,除了原先的 IPv4 私有位址外,再加入 IPv6 私有位址,如下所示:

[Interface]
Address = 10.9.0.1/24, fddb:65ec:174a::1/64
ListenPort = 25034
PrivateKey = <伺服端私鑰>

[Peer]
PublicKey = <客戶端公鑰>
AllowedIPs = 10.9.0.2/32, fddb:65ec:174a::2/128

 

接著則是客戶端的設定檔:

[Interface]
Address = 10.9.0.2/24, fddb:65ec:174a::2/128
PrivateKey = <客戶端私鑰>

[Peer]
PublicKey = <伺服端公鑰>
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = <伺服端的公共 IP 位址或網域>:25034

 

於 AllowedIPs 內新增的 ::/0,即表示所有的 IPv6 位址都會透過 VPN 連線。而在完成設定後,便可啟動 WireGuard 連線,檢驗客戶端的 IPv6 連線是否會透過 WireGuard 送出。此外,由於本文範例是在 WireGuard 內統一分配 IPv6 的私有位址,所以客戶端作業系統在預設情況下,倘若目標站點同時存在 IPv4 及 IPv6 位址,便會優先走 IPv4 連線。若是希望客戶端優先走 IPv6,則 WireGuard 設定內會需要為各個客戶端改分配 IPv6 公共位址。也就是在前述設定範例裡,客戶端的私有位址 fddb:65ec:174a::2/128 皆得替換為可供伺服器分配的 IPv6 公共位址。

 

5.2 更多的應用情境

倘若架設 VPN 的目的,並不在於讓客戶端透過 VPN 對外連線,而是希望多個客戶端藉由 VPN 伺服器為中介來彼此通訊。那麼在各個客戶端裡,只需要在設定檔的 AllowedIPs 內寫入 VPN 的私有網段即可:

AllowedIPs = 10.9.0.0/24

 

如果需要確保客戶端在長時間閒置下,也不會被網路設備斷開 VPN 連線,可以在客戶端設定檔加入 PersistentKeepalive 設定值:

AllowedIPs = 10.9.0.0/24

PersistentKeepalive = 25

 

除此之外,客戶端也可以指定要藉由 VPN 來連線至特定的 IP 位址,例如 8.8.8.8:

AllowedIPs = 10.9.0.0/24, 8.8.8.8/32

 

如果客戶端是希望連接 WireGuard 伺服器自身所在的區域網路(假設是 192.168.0.0/24),則客戶端設定檔內的 AllowedIPs 若不是 0.0.0.0/0,便再加上該區網網段,如下所示:

AllowedIPs = 10.9.0.0/24, 192.168.0.0/24

 

以及一種可能的情境,是希望透過 VPN 連接至某一客戶端所在的區域網路(例如 192.168.123.0/24)。那麼在此情況下,該客戶端的作業系統也會需要具備 NAT 的能力。所以得如同 WireGuard 伺服器,設定封包轉遞及 IP 偽裝。接著在 WireGuard 伺服器的設定檔內,會需要在該客戶端 [Peer] 的 AllowedIPs 裡,除了分配給客戶端的私有位址(例如 10.9.0.8)外,再加入該客戶端的區網網段:

AllowedIPs = 10.9.0.8/32, 192.168.123.0/24

 

完成前述設定後,WireGuard 伺服器本身應該就能夠接通客戶端的區網網段。若是有另一個客戶端也希望透過 VPN 接通此網段,那麼在這一個客戶端自己的設定檔裡,延續先前的概念,同樣會需要加入該區網網段:

AllowedIPs = 10.9.0.0/24, 192.168.123.0/24

 

5.3 其它參考資料