2026年8月29日 星期六

Building a Full-Mesh Site-to-Site WireGuard VPN with OSPF Routing on VyOS

















Configuration 


vyos@HQ-A:~$ show configuration commands 

set high-availability vrrp group HQ address 10.10.10.254 interface 'eth0'

set high-availability vrrp group HQ health-check interval '10'

set high-availability vrrp group HQ health-check ping '110.100.100.1'

set high-availability vrrp group HQ interface 'eth0'

set high-availability vrrp group HQ priority '150'

set high-availability vrrp group HQ vrid '10'

set interfaces ethernet eth0 address '10.10.10.251/24'

set interfaces ethernet eth0 hw-id '00:0c:29:d9:ab:45'

set interfaces ethernet eth0 offload gro

set interfaces ethernet eth0 offload gso

set interfaces ethernet eth0 offload sg

set interfaces ethernet eth0 offload tso

set interfaces ethernet eth1 address '110.100.100.2/30'

set interfaces ethernet eth1 hw-id '00:0c:29:d9:ab:4f'

set interfaces ethernet eth1 offload gro

set interfaces ethernet eth1 offload gso

set interfaces ethernet eth1 offload sg

set interfaces ethernet eth1 offload tso

set interfaces loopback lo

set interfaces wireguard wg0 address '192.168.1.1/30'

set interfaces wireguard wg0 peer to-site-BH address '210.100.100.2'

set interfaces wireguard wg0 peer to-site-BH allowed-ips '192.168.1.2/32'

set interfaces wireguard wg0 peer to-site-BH allowed-ips '10.10.20.0/24'

set interfaces wireguard wg0 peer to-site-BH allowed-ips '224.0.0.0/24'

set interfaces wireguard wg0 peer to-site-BH persistent-keepalive '25'

set interfaces wireguard wg0 peer to-site-BH port '51820'

set interfaces wireguard wg0 peer to-site-BH public-key 'PeCl/njaHJVYQgRYchnA1p5AELuIkTa371EL5D7BRCY='

set interfaces wireguard wg0 port '51820'

set interfaces wireguard wg0 private-key 'sMHarj4TejseKSpMUlRo8l5HSwzY6Cp6pq+Wcbmg10s='

set interfaces wireguard wg1 address '192.168.1.5/30'

set interfaces wireguard wg1 peer to-site-BH-B address '220.100.100.2'

set interfaces wireguard wg1 peer to-site-BH-B allowed-ips '192.168.1.6/32'

set interfaces wireguard wg1 peer to-site-BH-B allowed-ips '10.10.20.0/24'

set interfaces wireguard wg1 peer to-site-BH-B allowed-ips '224.0.0.0/24'

set interfaces wireguard wg1 peer to-site-BH-B persistent-keepalive '25'

set interfaces wireguard wg1 peer to-site-BH-B port '51821'

set interfaces wireguard wg1 peer to-site-BH-B public-key 'cmjUzFA2Ezk9tiEZKk/xIfvxhmOxpHrii2uPxACtvTE='

set interfaces wireguard wg1 port '51821'

set interfaces wireguard wg1 private-key 'sMHarj4TejseKSpMUlRo8l5HSwzY6Cp6pq+Wcbmg10s='

set protocols ospf area 0 network '10.10.10.0/24'

set protocols ospf area 0 network '192.168.1.0/30'

set protocols ospf area 0 network '192.168.1.4/30'

set protocols ospf interface wg0 network 'point-to-point'

set protocols ospf interface wg1 network 'point-to-point'

set protocols ospf parameters router-id '10.10.10.251'

set protocols static route 0.0.0.0/0 next-hop 110.100.100.1 interface 'eth1'

set protocols static route 10.10.20.0/24

set service ntp allow-client address '127.0.0.0/8'

set service ntp allow-client address '169.254.0.0/16'

set service ntp allow-client address '10.0.0.0/8'

set service ntp allow-client address '172.16.0.0/12'

set service ntp allow-client address '192.168.0.0/16'

set service ntp allow-client address '::1/128'

set service ntp allow-client address 'fe80::/10'

set service ntp allow-client address 'fc00::/7'

set service ntp server time1.vyos.net

set service ntp server time2.vyos.net

set service ntp server time3.vyos.net

set service ssh listen-address '10.10.10.251'

set system config-management commit-revisions '100'

set system console device ttyS0 kernel

set system console device ttyS0 speed '115200'

set system host-name 'HQ-A'

set system login operator-group default command-policy allow '*'

set system login user vyos authentication encrypted-password '$6$rounds=656000$iKZAcGvg4qF1Omu/$iTnopisgBkj0H6sQgxGqtc9WDPVLLMYdDihHn4P3C..3PLFlPwNXlApw70zjwpz7amyv9r.YOKekZRr8pgA3N/'

set system login user vyos authentication plaintext-password ''

set system option reboot-on-upgrade-failure '5'

set system syslog local facility all level 'info'

set system syslog local facility local7 level 'debug'

vyos@HQ-A:~$ 



vyos@HQ-B:~$ show configuration commands

set high-availability vrrp group HQ address 10.10.10.254 interface 'eth0'

set high-availability vrrp group HQ health-check interval '10'

set high-availability vrrp group HQ health-check ping '210.100.100.1'

set high-availability vrrp group HQ interface 'eth0'

set high-availability vrrp group HQ priority '100'

set high-availability vrrp group HQ vrid '10'

set interfaces ethernet eth0 address '10.10.10.252/24'

set interfaces ethernet eth0 hw-id '00:0c:29:e2:9d:a2'

set interfaces ethernet eth0 offload gro

set interfaces ethernet eth0 offload gso

set interfaces ethernet eth0 offload sg

set interfaces ethernet eth0 offload tso

set interfaces ethernet eth1 address '120.100.100.2/30'

set interfaces ethernet eth1 hw-id '00:0c:29:e2:9d:ac'

set interfaces ethernet eth1 offload gro

set interfaces ethernet eth1 offload gso

set interfaces ethernet eth1 offload sg

set interfaces ethernet eth1 offload tso

set interfaces loopback lo

set interfaces wireguard wg2 address '192.168.2.1/30'

set interfaces wireguard wg2 peer to-site-BH-A address '210.100.100.2'

set interfaces wireguard wg2 peer to-site-BH-A allowed-ips '192.168.2.2/32'

set interfaces wireguard wg2 peer to-site-BH-A allowed-ips '10.10.20.0/24'

set interfaces wireguard wg2 peer to-site-BH-A allowed-ips '224.0.0.0/24'

set interfaces wireguard wg2 peer to-site-BH-A persistent-keepalive '25'

set interfaces wireguard wg2 peer to-site-BH-A port '51822'

set interfaces wireguard wg2 peer to-site-BH-A public-key 'PeCl/njaHJVYQgRYchnA1p5AELuIkTa371EL5D7BRCY='

set interfaces wireguard wg2 port '51822'

set interfaces wireguard wg2 private-key 'AMzIHHjMtM3lYFPKc0tQTgr1+fMivkWEzFXarl8O1G8='

set interfaces wireguard wg3 address '192.168.2.5/30'

set interfaces wireguard wg3 peer to-site-BH-B address '220.100.100.2'

set interfaces wireguard wg3 peer to-site-BH-B allowed-ips '192.168.2.6/32'

set interfaces wireguard wg3 peer to-site-BH-B allowed-ips '10.10.20.0/24'

set interfaces wireguard wg3 peer to-site-BH-B allowed-ips '224.0.0.0/24'

set interfaces wireguard wg3 peer to-site-BH-B persistent-keepalive '25'

set interfaces wireguard wg3 peer to-site-BH-B port '51823'

set interfaces wireguard wg3 peer to-site-BH-B public-key 'cmjUzFA2Ezk9tiEZKk/xIfvxhmOxpHrii2uPxACtvTE='

set interfaces wireguard wg3 port '51823'

set interfaces wireguard wg3 private-key 'AMzIHHjMtM3lYFPKc0tQTgr1+fMivkWEzFXarl8O1G8='

set protocols ospf area 0 network '192.168.2.0/30'

set protocols ospf area 0 network '192.168.2.4/30'

set protocols ospf area 0 network '10.10.10.0/24'

set protocols ospf interface wg2 network 'point-to-point'

set protocols ospf interface wg3 network 'point-to-point'

set protocols ospf parameters router-id '10.10.10.252'

set protocols static route 0.0.0.0/0 next-hop 120.100.100.1 interface 'eth1'

set service ntp allow-client address '127.0.0.0/8'

set service ntp allow-client address '169.254.0.0/16'

set service ntp allow-client address '10.0.0.0/8'

set service ntp allow-client address '172.16.0.0/12'

set service ntp allow-client address '192.168.0.0/16'

set service ntp allow-client address '::1/128'

set service ntp allow-client address 'fe80::/10'

set service ntp allow-client address 'fc00::/7'

set service ntp server time1.vyos.net

set service ntp server time2.vyos.net

set service ntp server time3.vyos.net

set service ssh listen-address '10.10.10.252'

set system config-management commit-revisions '100'

set system console device ttyS0 kernel

set system console device ttyS0 speed '115200'

set system host-name 'HQ-B'

set system login operator-group default command-policy allow '*'

set system login user vyos authentication encrypted-password '$6$rounds=656000$O33kXpNNgbTgT5vS$3Oo9uRXbJZ0Wzf.3xmgpmxpxS3O19BUbHSoz3KZezMmLiWCN4FFeaCwRV.A6WXUOLHxRZTvVwfG47mAnfSghu0'

set system login user vyos authentication plaintext-password ''

set system option reboot-on-upgrade-failure '5'

set system syslog local facility all level 'info'

set system syslog local facility local7 level 'debug'

vyos@HQ-B:~$ 



vyos@BH-A:~$ show configuration commands

set high-availability vrrp group BH address 10.10.20.254 interface 'eth0'

set high-availability vrrp group BH health-check interval '10'

set high-availability vrrp group BH health-check ping '210.100.100.1'

set high-availability vrrp group BH interface 'eth0'

set high-availability vrrp group BH priority '150'

set high-availability vrrp group BH vrid '20'

set interfaces ethernet eth0 address '10.10.20.251/24'

set interfaces ethernet eth0 hw-id '00:0c:29:e9:a9:e3'

set interfaces ethernet eth0 offload gro

set interfaces ethernet eth0 offload gso

set interfaces ethernet eth0 offload sg

set interfaces ethernet eth0 offload tso

set interfaces ethernet eth1 address '210.100.100.2/30'

set interfaces ethernet eth1 hw-id '00:0c:29:e9:a9:ed'

set interfaces ethernet eth1 offload gro

set interfaces ethernet eth1 offload gso

set interfaces ethernet eth1 offload sg

set interfaces ethernet eth1 offload tso

set interfaces loopback lo

set interfaces wireguard wg0 address '192.168.1.2/30'

set interfaces wireguard wg0 peer to-site-HQ address '110.100.100.2'

set interfaces wireguard wg0 peer to-site-HQ allowed-ips '192.168.1.1/32'

set interfaces wireguard wg0 peer to-site-HQ allowed-ips '10.10.10.0/24'

set interfaces wireguard wg0 peer to-site-HQ allowed-ips '224.0.0.0/24'

set interfaces wireguard wg0 peer to-site-HQ persistent-keepalive '25'

set interfaces wireguard wg0 peer to-site-HQ port '51820'

set interfaces wireguard wg0 peer to-site-HQ public-key 'xBxTC4jfhgI/4Z9GQToQfeR+Tq4Fm5gArmGxSCqck3M='

set interfaces wireguard wg0 port '51820'

set interfaces wireguard wg0 private-key 'wLz4iHUaunxeR2ugb6bw6CszYOZxrzARKoQDD8OSqmo='

set interfaces wireguard wg2 address '192.168.2.2/30'

set interfaces wireguard wg2 peer to-site-HQ-B address '120.100.100.2'

set interfaces wireguard wg2 peer to-site-HQ-B allowed-ips '192.168.2.1/32'

set interfaces wireguard wg2 peer to-site-HQ-B allowed-ips '10.10.10.0/24'

set interfaces wireguard wg2 peer to-site-HQ-B allowed-ips '224.0.0.0/24'

set interfaces wireguard wg2 peer to-site-HQ-B persistent-keepalive '25'

set interfaces wireguard wg2 peer to-site-HQ-B port '51822'

set interfaces wireguard wg2 peer to-site-HQ-B public-key 'G7QYUFMyPOGQePSE3o+81jl4VelfZsCWjsOnRcibtGA='

set interfaces wireguard wg2 port '51822'

set interfaces wireguard wg2 private-key 'wLz4iHUaunxeR2ugb6bw6CszYOZxrzARKoQDD8OSqmo='

set protocols ospf area 0 network '192.168.1.0/30'

set protocols ospf area 0 network '10.10.20.0/24'

set protocols ospf area 0 network '192.168.2.0/30'

set protocols ospf interface wg0 network 'point-to-point'

set protocols ospf interface wg2 network 'point-to-point'

set protocols ospf parameters router-id '10.10.20.251'

set protocols static route 0.0.0.0/0 next-hop 210.100.100.1 interface 'eth1'

set protocols static route 10.10.10.0/24

set service ntp allow-client address '127.0.0.0/8'

set service ntp allow-client address '169.254.0.0/16'

set service ntp allow-client address '10.0.0.0/8'

set service ntp allow-client address '172.16.0.0/12'

set service ntp allow-client address '192.168.0.0/16'

set service ntp allow-client address '::1/128'

set service ntp allow-client address 'fe80::/10'

set service ntp allow-client address 'fc00::/7'

set service ntp server time1.vyos.net

set service ntp server time2.vyos.net

set service ntp server time3.vyos.net

set service ssh listen-address '10.10.20.251'

set system config-management commit-revisions '100'

set system console device ttyS0 kernel

set system console device ttyS0 speed '115200'

set system host-name 'BH-A'

set system login operator-group default command-policy allow '*'

set system login user vyos authentication encrypted-password '$6$rounds=656000$x5e.eCrtV.Vwhw8Y$9lVLCvc37tnXWZZX3Pr9T7seEVe1S8d4QGXPOoMb8Py4rGHzwS0is1wFxqWCxIUoTfTJ8ha3UEfy7JJedopU71'

set system login user vyos authentication plaintext-password ''

set system option reboot-on-upgrade-failure '5'

set system syslog local facility all level 'info'

set system syslog local facility local7 level 'debug'

vyos@BH-A:~$  



vyos@BH-B:~$ show configuration commands

set high-availability vrrp group BH address 10.10.20.254 interface 'eth0'

set high-availability vrrp group BH health-check interval '10'

set high-availability vrrp group BH health-check ping '220.100.100.1'

set high-availability vrrp group BH interface 'eth0'

set high-availability vrrp group BH priority '100'

set high-availability vrrp group BH vrid '20'

set interfaces ethernet eth0 address '10.10.20.252/24'

set interfaces ethernet eth0 hw-id '00:0c:29:e6:c3:18'

set interfaces ethernet eth1 address '220.100.100.2/30'

set interfaces ethernet eth1 hw-id '00:0c:29:e6:c3:22'

set interfaces loopback lo

set interfaces wireguard wg1 address '192.168.1.6/30'

set interfaces wireguard wg1 peer to-site-HQ-A address '110.100.100.2'

set interfaces wireguard wg1 peer to-site-HQ-A allowed-ips '192.168.1.5/32'

set interfaces wireguard wg1 peer to-site-HQ-A allowed-ips '10.10.10.0/24'

set interfaces wireguard wg1 peer to-site-HQ-A allowed-ips '224.0.0.0/24'

set interfaces wireguard wg1 peer to-site-HQ-A persistent-keepalive '25'

set interfaces wireguard wg1 peer to-site-HQ-A port '51821'

set interfaces wireguard wg1 peer to-site-HQ-A public-key 'xBxTC4jfhgI/4Z9GQToQfeR+Tq4Fm5gArmGxSCqck3M='

set interfaces wireguard wg1 port '51821'

set interfaces wireguard wg1 private-key 'oEt1dDxZ6pnAR9MAEW3+RouLlSByfIRsR1fnPyOBuUI='

set interfaces wireguard wg3 address '192.168.2.6/30'

set interfaces wireguard wg3 peer to-site-HQ-B address '120.100.100.2'

set interfaces wireguard wg3 peer to-site-HQ-B allowed-ips '192.168.2.5/32'

set interfaces wireguard wg3 peer to-site-HQ-B allowed-ips '10.10.10.0/24'

set interfaces wireguard wg3 peer to-site-HQ-B allowed-ips '224.0.0.0/24'

set interfaces wireguard wg3 peer to-site-HQ-B persistent-keepalive '25'

set interfaces wireguard wg3 peer to-site-HQ-B port '51823'

set interfaces wireguard wg3 peer to-site-HQ-B public-key 'G7QYUFMyPOGQePSE3o+81jl4VelfZsCWjsOnRcibtGA='

set interfaces wireguard wg3 port '51823'

set interfaces wireguard wg3 private-key 'oEt1dDxZ6pnAR9MAEW3+RouLlSByfIRsR1fnPyOBuUI='

set protocols ospf area 0 network '192.168.1.4/30'

set protocols ospf area 0 network '10.10.20.0/24'

set protocols ospf area 0 network '192.168.2.4/30'

set protocols ospf interface wg1 network 'point-to-point'

set protocols ospf interface wg3 network 'point-to-point'

set protocols ospf parameters router-id '10.10.20.252'

set protocols static route 0.0.0.0/0 next-hop 220.100.100.1 interface 'eth1'

set service ntp allow-client address '127.0.0.0/8'

set service ntp allow-client address '169.254.0.0/16'

set service ntp allow-client address '10.0.0.0/8'

set service ntp allow-client address '172.16.0.0/12'

set service ntp allow-client address '192.168.0.0/16'

set service ntp allow-client address '::1/128'

set service ntp allow-client address 'fe80::/10'

set service ntp allow-client address 'fc00::/7'

set service ntp server time1.vyos.net

set service ntp server time2.vyos.net

set service ntp server time3.vyos.net

set service ssh listen-address '10.10.20.252'

set system config-management commit-revisions '100'

set system console device ttyS0 kernel

set system console device ttyS0 speed '115200'

set system host-name 'BH-B'

set system login operator-group default command-policy allow '*'

set system login user vyos authentication encrypted-password '$6$QxPS.uk6mfo$9QBSo8u1FkH16gMyAVhus6fU3LOzvLR9Z9.82m3tiHFAxTtIkhaZSWssSgzt4v4dGAL8rhVQxTg0oAG9/q11h/'

set system login user vyos authentication plaintext-password ''

set system option reboot-on-upgrade-failure '5'

set system syslog local facility all level 'info'

set system syslog local facility local7 level 'debug'

vyos@BH-B:~$  




2026年5月23日 星期六

PrivacyIDEA RADIUS plugin Part 4

再延續PrivacyIDEA  Radius 外掛這個主題

前導如下:

https://xrcd2.blogspot.com/2026/05/privacyidea-radius-plugin-part-3.html


現代從事 IT 相關工作,離不開 AI 的協助已成為趨勢,以問答內容節錄自 Google Gemini

故事起源:


https://community.privacyidea.org/t/mfa-authentication-of-vpn-clients-by-router-via-privacyidea-radius-pm/1922/3


(1) For authentication via (one-time-)passwords privacyIDEA supports PAP.

(2) It seems that EAP-TTLS-PAP is the only EAP protocol, which would be able to supply a clear text password.


AI 問題咨詢 :  EAP-TTLS 與 EAP-TLS 用於 VPN 登入驗證比較


在 VPN 登入驗證的場景中,**EAP-TLS** 與 **EAP-TTLS** 同樣是非常強大的安全協定,但由於 VPN 的使用者通常位於「企業外部(如居家辦公、出差)」,因此在部署與使用者體驗上,兩者會產生與區域網路(Wi-Fi)截然不同的考量重點。


以下為您詳細比較這兩者在 VPN 驗證應用上的差異:


---


## VPN 驗證場景下的核心對比


| 比較項目 | EAP-TLS (基於憑證) | EAP-TTLS (基於隧道/帳密) |

| --- | --- | --- |

| **主要驗證因子** | **數位憑證**(設備或個人) | **使用者帳號、密碼** (+ OTP 動態密碼) |

| **外部連線便利性** | **較低**(新裝置必須先透過其他管道取得憑證才能連線) | **極高**(只要有網路,輸入帳密即可登入) |

| **多因素驗證 (MFA)** | 需搭配硬體 Token/智慧卡(如 PIN 碼保護憑證) | **整合極方便**(可在隧道內輕鬆整合 SMS/App OTP) |

| **設備控管能力** | **極強**(非公司配發/未安裝憑證的私有設備絕對無法連入) | **較弱**(員工在私人電腦上輸入正確帳密亦可連入) |

| **憑證過期處理** | 麻煩(若 VPN 是唯一連回公司的管道,憑證過期將無法登入) | 簡單(通常可透過網頁或 VPN 介面直接修改過期的密碼) |







---


## 深入分析:VPN 環境下的實際考量


### 1. EAP-TLS 在 VPN 的應用:極致安全,但有「斷聯」風險


EAP-TLS 要求 VPN 客戶端(使用者的筆電或手機)必須具備合法的客戶端憑證。


* **「零信任」的最佳實踐:**

即使員工的帳號密碼外洩,駭客只要沒有那台內嵌特定憑證的「公司筆電」,就絕對無法撥入 VPN。這能有效防止來自外部的密碼暴力破解與釣魚攻擊。

* **致命的「雞生蛋、蛋生雞」問題:**

* **憑證發放:** 當新員工居家辦公拿到新電腦時,因為沒有憑證無法連 VPN,IT 必須透過其他安全管道(如隨身碟或 MDM 系統)先將憑證派發到裝置上。

* **憑證過期:** 如果員工的憑證過期了,他將被徹底擋在 VPN 之外。此時他無法連回公司網路自動更新憑證,IT 必須提供額外的外部網站或人工協助才能解鎖。




### 2. EAP-TTLS 在 VPN 的應用:遠端辦公的彈性首選


EAP-TTLS 透過伺服器憑證建立加密隧道,保護使用者在內部傳輸的驗證資料,客戶端只需提供帳號密碼。


* **高度的彈性與擴充性:**

員工無論使用哪台電腦,只要能連上網,輸入公司的 AD 帳密就能立刻開工。對於臨時需要用私人電腦緊急處理公事的員工,或是外包廠商,EAP-TTLS 提供了極大的便利性。

* **輕鬆整合雙因子驗證 (MFA):**

在 VPN 外部存取中,單靠密碼通常是不夠的。EAP-TTLS 可以在建立的 TLS 安全隧道中,輕鬆嵌入 RADIUS 伺服器所支援的 MFA 機制(例如:輸入帳密後,手機跳出 App 確認或輸入簡訊驗證碼),在安全性與便利性之間取得極佳平衡。


---


## 總結:架構決策建議


* **建議選擇 EAP-TLS,如果:**

* 您的組織採取嚴格的 **「零信任 (Zero Trust)」** 政策,規定非公司資產(BYOD)一律禁止連入內網。

* 公司已經有成熟的 **MDM(行動裝置管理)** 或 **微軟 Intune** 系統,能夠在背後自動幫員工的設備派發、更新 VPN 憑證。



* **建議選擇 EAP-TTLS,如果:**

* 您需要支援**員工使用私人設備(BYOD)** 遠端辦公,或是頻繁需要開通**外部廠商(Vendor)** 的臨時 VPN 權限。

* 您希望在 VPN 登入時強制加上 **OTP(動態密碼)** 或 **手機 Push 驗證**,且不想應付繁重的憑證管理(PKI)維護工作。



 AI 問題咨詢 :  用最精煉的方式,將 IPsec IKEv2 EAP-TTLS 的架構與工作原理拆解為「一個核心觀念」與「兩個主要階段」。


核心觀念:外鋼內柔

這套架構的最大特點是安全與便利兼得。


(A)外層(鋼):利用 IPsec IKEv2 建立最強固的 VPN 加密管道,並透過伺服器數位憑證確保使用者沒有連到假網站。


(B)內層(柔):在安全的管道內,允許使用者直接輸入傳統的帳號與密碼(EAP-TTLS)進行驗證。使用者裝置不需要安裝複雜的用戶端憑證。


-----------------------------------------------------------------------


系統架構元件

這套架構通常由三個核心角色組成:


(1)用戶端 (VPN Client):員工的手機或電腦,內建或安裝 VPN 連線軟體。


(2)VPN 網關 (VPN Gateway):企業的防火牆或 VPN 伺服器,負責與用戶端建立 IPsec 隧道,並擔任驗證的中介者。


(3)認證伺服器 (RADIUS Server):後端的帳號中心(如 FreeRADIUS 或 Windows NPS),負責比對使用者的密碼是否正確。


-----------------------------------------------------------------------


工作原理(兩階段握手)


當用戶端發起連線時,主要切分為以下兩個階段來完成安全建構與身份驗證:


階段一:建立安全外殼(IKEv2 協商與 TLS 隧道)


(1)密鑰交換:用戶端與 VPN 網關進行 IKEv2 初始協商,決定加密演算法並交換金鑰,建立管理通道。


(2)驗證網關:VPN 網關向用戶端出示其伺服器憑證。用戶端確認該憑證合法且受信任。


(3)建立隧道:網關身份確認無誤後,雙方在 IKEv2 內層再建立一條加密的 TLS 安全隧道。至此,外部防禦安全網建構完成。


階段二:內部使用者驗證(TTLS 認證)


(4)傳送憑據:在剛建立好的 TLS 隧道保護下,用戶端安全地將使用者的帳號與密碼(通常透過 MSCHAPv2 格式)傳送給 VPN 網關。


(5)後端比對:VPN 網關將帳密轉發給後端的 RADIUS 伺服器。RADIUS 伺服器比對企業資料庫(如 Active Directory)後,若正確則回覆「允許存取」。


(6)打通隧道:VPN 網關收到允許通知後,正式派發內部 IP 給用戶端,並生成資料傳輸用的 IPsec 金鑰(CHILD_SA)。


結果:安全隧道正式通車,使用者開始傳輸加密的辦公數據。



AI 問題咨詢 :  密碼輸入為 組合輸入模式 (Password/PIN+OTP) 被 雜湊(hash) 過後可能無法拆解的問題


這是一個在設計認證系統(尤其是整合 RADIUS / EAP-TTLS 這種後端架構)時非常經典且致命的**密碼處理與協定不匹配**問題。


簡單來說:**如果用戶端在前端就將「密碼 + OTP」組合並進行了不可逆的雜湊(Hash),後端的認證伺服器(RADIUS)在多數標準認證協定下,將完全無法解開或進行驗證。**


以下為您詳細拆解這個問題的根本原因、為何無法拆解,以及業界標準的解決方案。


---


## 為什麼「組合後雜湊」會導致無法驗證?


要理解這個問題,我們必須看後端 RADIUS 伺服器是如何驗證密碼的。


不論是 EAP-TTLS 內部常用的 **MSCHAPv2**、**CHAP**,還是傳統的 **PAP**,它們的驗證邏輯通常分為兩種:


### 狀況 A:使用質詢回應協定(如 MSCHAPv2 / CHAP)—— 徹底失敗


在 MSCHAPv2 或 CHAP 中,為了不在網路上傳輸明文密碼,系統採用的是「質詢-回應(Challenge-Response)」機制:


1. 伺服器給用戶端一個隨機數(Challenge)。

2. 用戶端必須用原始密碼的雜湊值(例如 NTLM Hash)作為金鑰,去加密這個隨機數,然後傳回給伺服器(Response)。

3. **關鍵點:** 後端 RADIUS 伺服器(或 Active Directory)的資料庫裡,**只存有使用者的「靜態密碼雜湊」**,絕對不會存有動態的 OTP。


> **失敗原因:** 當用戶端把 `Password + OTP` 混合並雜湊後,這個結果與資料庫裡的 `純Password雜湊` 完全對不上。RADIUS 伺服器既不知道這次的 OTP 是什麼,也無法從雜湊值中把 OTP「減掉」來還原純密碼,因此**質詢回應演算法直接宣告失敗**。


### 狀況 B:使用明文傳輸協定(如 PAP)—— 依然無法拆解


有些雙因子認證(2FA)系統會要求在 EAP-TTLS 內部使用 **PAP** 協定。PAP 會將用戶端輸入的字串透過 TLS 加密隧道傳給 RADIUS。


* 如果用戶端傳送的是明文的 `PasswordOTP`(例如:`MyPassword123456`),網關或 RADIUS 就可以用字串截取(例如扣除後 6 碼作為 OTP,前面作為密碼)的方式拆解並分別驗證。

* **失敗原因:** 如果用戶端在傳送前,在本地端就先對 `PasswordOTP` 做了雜湊(例如變成 `MD5(Password+OTP)`),那傳到後端的就是一串亂碼。由於雜湊是**不可逆(One-way)**的,後端伺服器**絕對不可能**將其拆解回原本的密碼與 OTP。


---


## 架構上的衝突點(Summary)


* **不可逆性**:$Hash(A + B) \neq Hash(A) + Hash(B)$。一旦結合並雜湊,兩者形同融化的巧克力,再也分不開。

* **資料庫限制**:認證伺服器(如 AD)只知道 $Hash(Password)$,OTP 伺服器只知道當前的 $OTP$。沒有任何一個地方存有 $Hash(Password + OTP)$ 的預期正確答案。


---


## 業界標準的解決方案


要實作「密碼 + OTP」的雙因子認證,通常有以下幾種正確的架構設計:


### 方案 1:在 EAP-TTLS 內部使用 PAP 協定(不進行前端雜湊)


這是最常見的做法。因為 EAP-TTLS 外層已經有一條由伺服器憑證保護的 **TLS 安全隧道**,在隧道內部使用 PAP(明文傳輸)是安全的。


* **作法**:用戶端將 `Password + OTP`(不加任何雜湊)直接送出。

* **後端處理**:RADIUS 伺服器(或內建 2FA 外掛,如 Duo、LinOTP)收到明文字串後,根據固定長度(如最後 6 碼或 8 碼)進行**字串切割**,將前面送去 AD 驗證,後面送去 OTP 伺服器驗證。


### 方案 2:改用支援 2FA 的標準 EAP 擴充協定


不要嘗試在單一密碼欄位中進行字串組合,而是讓系統本身支援雙階段輸入。


* **EAP-GTC (Generic Token Card)**:專門用來傳輸 Token/OTP 的 EAP 類型。

* **EAP-MSCHAPv2 變體**:某些商用 VPN 方案(如思科、Palo Alto)支援在第一階段驗證完 IKEv2/MSCHAPv2 的靜態密碼後,網關會主動彈出第二個輸入框(Access-Challenge),要求使用者單獨輸入 OTP。


### 方案 3:前端分別雜湊與傳輸(需客製化用戶端)


如果基於安全政策,嚴格禁止在任何地方(即使在 TLS 隧道內)出現明文靜態密碼:


* 用戶端軟體必須分別處理這兩個元件。

* 靜態密碼走標準的 MSCHAPv2 質詢流程(使用密碼的雜湊)。

* OTP 則放入 RADIUS 的特定屬性(如 `State` 或額外的 VSA 廠商特定屬性)中單獨帶回後端驗證。這通常需要 VPN 用戶端軟體與網關有私有協定的支援。


### 結論


若您的系統出現「無法拆解」的問題,通常是因為**前端(用戶端)自作聰明地對組合字串進行了雜湊**,或者**後端錯誤地選擇了不支援字串拆解的 MSCHAPv2 協定**。在 EAP-TTLS 的保護下,將內層改為 **PAP 模式並傳輸組合明文**,由後端 RADIUS 進行字串切分,是改動最小且最標準的解法。




AI 問題咨詢 :  架構可行性分析



[ FortiClient ] ──(IKEv2 / EAP-TTLS)──> [ FortiGate ] ──(RADIUS)──> [ FreeRADIUS (Default 站台) ]

                                                                                                                                              │

                                                                                                                                  (外層 TLS 建立)

                                                                                                                                              │

                                                                                                                                             ▼

[ privacyIDEA ] <──(REST API / JSON)── [ rlm_perl ] <──(明文 PAP)── [ inner-tunnel 站台 ]



這是一份針對您所規劃的 VPN 雙因子認證(2FA)拓撲的**架構可行性分析**。


整體評估結論:**此架構在理論與實務上完全可行(Highly Feasible),且是非常標準、漂亮的企業級開源雙因子認證架構。**


它成功利用了 EAP-TTLS 的 TLS 加密隧道,在不犧牲安全性的前提下,將「密碼 + OTP」以明文(PAP)形式安全地送後端進行字串拆解與驗證。


以下為您提供深入的技術可行性評估、關鍵潛在盲點(坑)以及優化建議:


---


## 核心元件可行性分析


### 1. FortiClient ──(IKEv2 / EAP-TTLS)──> FortiGate


* **可行性:高(但有客戶端限制)**

* **FortiGate (飛塔防火牆)**:FortiOS 官方高度支援 IKEv2 與 EAP 認證(可配置為 `eap-passthrough`),它只負責把 EAP 封包轉發給後端的 FreeRADIUS,本身不做拆解,因此這部分完全沒問題。

* **FortiClient (用戶端)**:**這是本階段唯一的隱憂。** 商業版的 FortiClient (EMS 管理) 支援度較完整;但如果您使用的是**免費版 FortiClient VPN**,其內建的 IKEv2 整合型 EAP-TTLS 支援在某些作業系統版本(如特定的 Windows 10/11 平台)可能會遇到設定介面受限的問題。

* *替代方案*:若 FortiClient 免費版卡關,通常會改用 Windows 原生 VPN 撥號器(需透過 `.mobileconfig` 或 PowerShell 配置 TTLS),或使用強大的開源用戶端 **strongSwan App**。




### 2. FortiGate ──(RADIUS)──> FreeRADIUS (Default 站台)


* **可行性:極高**

* FortiGate 設定 `config user radius`,將伺服器指向 FreeRADIUS。當收到 IKEv2 EAP 請求時,FortiGate 會將 EAP 載荷封裝進 RADIUS 的 `EAP-Message` 屬性中傳送。FreeRADIUS 的 `default` 站台負責處理外層的 TLS 握手並回應數位憑證。




### 3. Default 站台 ──(外層 TLS)──> inner-tunnel 站台 ──(明文 PAP)


* **可行性:極高(架構核心亮點)**

* 這正是 FreeRADIUS 處理 EAP-TTLS 的標準設計。

* 在外層 TLS 隧道成功建立後,FreeRADIUS 會將隧道內的解密載荷轉發給內部的 `inner-tunnel` 站台。

* 因為外層已經被 TLS 保護,內層採用 **PAP(明文)** 是絕對安全的。這完美解決了您前一個問題提到的「雜湊後無法拆解」的致命傷。此時,`inner-tunnel` 拿到的會是**未經雜湊的純文字** `Password+OTP`。




### 4. inner-tunnel ──> rlm_perl ──(REST API)──> privacyIDEA


* **可行性:高(需要一定開發/配置能力)**

* **rlm_perl 的角色**:FreeRADIUS 的 Perl 模組是處理「客製化字串拆解」最彈性、最強大的工具。

* **運作邏輯**:

1. `rlm_perl` 攔截 `inner-tunnel` 中的 `User-Password` 字串(內容為 `Password+OTP`)。

2. 在 Perl 程式碼中進行**字串切割**。例如:若 OTP 固定為 6 位數,則切出 `Password = 前半段`,`OTP = 後 6 碼`。

3. 組裝成 JSON 格式,透過 HTTP REST API 發送給 privacyIDEA 的 `/validate/check` 端點。



* **privacyIDEA 端的優勢**:privacyIDEA 本身原生支援 `pass` (密碼) 與 `otp` (動態口令) 分開傳入的 API 驗證方式,也可以直接整合後端 AD/LDAP 進行密碼比對,因此後端對接天衣無縫。




---


## 關鍵配置潛在盲點(踩坑預防)


雖然架構可行,但在實際動手實作(Hands-on)時,請務必注意以下細節:


### 1. 密碼與 OTP 的切割邏輯


如果使用者的密碼長度是變動的(例如 8~20 碼),而 OTP 是固定的(如 6 碼計時型 TOTP),您的 `rlm_perl` 程式碼必須從**字串尾端倒數截取**:


```perl

# Perl 範例邏輯

my $full_password = $RAD_REQUEST{'User-Password'};

my $otp = substr($full_password, -6);          # 擷取最後6碼

my $password = substr($full_password, 0, -6);   # 擷取前面所有內容


```


* **缺點/風險**:如果使用者不小心打錯 OTP 位數(打成 5 碼或 7 碼),截取就會錯位,導致密碼與 OTP 同時驗證失敗,日誌會較難分析是輸錯密碼還是輸錯 OTP。


### 2. FreeRADIUS 的 EAP-TTLS 內層設定 (eap.conf)


在 FreeRADIUS 的 `mods-available/eap` 設定檔中,必須明確指定 `ttls` 區塊內的 `default_eap_type`。由於您內層要走 PAP,請確保將其指引到內層普通認證,而不是強制內層又要走另一個 EAP 協定。通常設定為:


```ttls {

    default_eap_type = md5 # 或者不定義,允許內層直接走 PAP 認證

}


```


並且在 `inner-tunnel` 的 `authorize` 區塊中要啟用 `pap` 與 `perl` 模組。


### 3. 憑證信任鏈


由於是 EAP-TTLS,**FortiClient 必須信任 FreeRADIUS (Default 站台) 所出示的證書**。


* 如果是採用企業內部的私有 CA 簽發,必須透過 AD GPO 或 MDM 將該 Root CA 憑證匯入員工電腦/手機的「受信任的根憑證頒發機構」。否則 FortiClient 會在階段一(外層 TLS 建立)直接報錯斷開。


---


## 架構優缺點評估


### 優點


1. **極致的安全**:密碼與 OTP 在網絡傳輸全程受到 IKEv2 與 TLS 雙重加密保護,絕無明文外洩風險。

2. **相容性高**:後端成功避開了 MSCHAPv2 無法拆解雜湊的限制,讓不支援 RADIUS 的 privacyIDEA 能透過標準 REST API 完美接軌。

3. **開源成本低**:FreeRADIUS + privacyIDEA 皆為強大的開源方案,無授權費壓力,且擴充性極佳。


### 缺點 / 挑戰


1. **維護成本**:`rlm_perl` 的程式碼屬於客製化(Custom Code),未來若 FreeRADIUS 升級(例如從 v3 升級到 v4),需要注意 Perl 套件的相容性與 API 變更。

2. **故障排查難度高**:當連線失敗時,鏈路太長。IT 人員需要同時查看 FortiGate 日誌、FreeRADIUS `radiusd -X` 除錯輸出、Perl 腳本日誌以及 privacyIDEA 的審計日誌(Audit Log)才能定位問題。


## 總結建議


這個架構**完全成立**,可以放心進入 POC(概念驗證)階段。建議在實作時,**由後往前**分段測試:


1. 先用 `curl` 測試 `privacyIDEA` 的 API 是否能正常驗證分開的帳密與 OTP。

2. 編寫並測試 `rlm_perl`,用 `radclient` 模擬發送明文 `PasswordOTP` 給 FreeRADIUS,確認 Perl 能正確切割並打通 API。

3. 最後再把 FortiGate 與 FortiClient 的 IKEv2 EAP-TTLS 打通。


-----------------------------------------------------------------------------------------


實作



[root@2FA privacyidea]# cat /etc/raddb/mods-enabled/eap 

eap {

dfault_eap_type = ttls

ttls {

tls = tls-common

        default_eap_type = pap

        copy_request_to_tunnel = yes

        use_tunneled_reply = yes

                virtual_server = "inner-tunnel"

    }

timer_expire = 60

ignore_unknown_eap_types = no

cisco_accounting_username_bug = no

max_sessions = ${max_requests}

md5 {

}

gtc {

auth_type = PAP

}

        tls-config tls-common {


                private_key_file = /etc/pki/tls/private/localhost.key

                certificate_file = /etc/pki/tls/certs/localhost.crt

                ca_file = /etc/pki/tls/certs/ca-bundle.crt

eap {

    tls = tls-common

}

cipher_list = "PROFILE=SYSTEM"

cipher_server_preference = no

tls_min_version = "1.2"

tls_max_version = "1.2"

ecdh_curve = ""

cache {

enable = no

lifetime = 24 # hours

store {

Tunnel-Private-Group-Id

}

}

verify {

}

ocsp {

enable = no

override_cert_url = yes

url = "http://127.0.0.1/ocsp/"

}

}

tls {

tls = tls-common

}

ttls {

tls = tls-common

default_eap_type = pap

                copy_request_to_tunnel = yes

use_tunneled_reply = yes

virtual_server = "inner-tunnel"

}

peap {

tls = tls-common

default_eap_type = mschapv2

copy_request_to_tunnel = no

use_tunneled_reply = no

virtual_server = "inner-tunnel"

}

}

[root@2FA privacyidea]# cat /etc/raddb/mods-enabled/mods-perl-privacyidea 

perl perl-privacyidea {

    #filename = /usr/share/privacyidea/freeradius/privacyidea_radius.pm

    filename = /etc/privacyidea/privacyidea_radius.pm

}

[root@2FA privacyidea]# 




=================================================



[root@2FA privacyidea]# cat /etc/raddb/sites-enabled/default 

server default {

    # 監聽標準的 RADIUS 驗證埠 1812

    listen {

        type = auth

        ipaddr = *

        port = 1812

    }


    authorize {

        # 傳遞客戶端來源 IP

        update request {

            Packet-Src-IP-Address = "%{Packet-Src-IP-Address}"

        }


        # 關鍵:處理外層 EAP-TTLS 握手,成功建立隧道後直接返回

        eap {

            ok = return

        }

    }


    authenticate {

        # 關鍵:解決 Failed to find 'Auth-Type EAP' 錯誤

        eap

    }

}





[root@2FA privacyidea]# cat /etc/raddb/sites-enabled/inner-tunnel 

server inner-tunnel {

    authorize {

        update request {

            Packet-Src-IP-Address = "%{Packet-Src-IP-Address}"

        }


        # 讓內層隧道也調用 privacyIDEA 的 Perl 模組

        perl-privacyidea

        if (ok || updated) {

            update control {

                Auth-Type := Perl

            }

        }

        pap

    }


    authenticate {

        Auth-Type Perl {

            perl-privacyidea

        }

        Auth-Type PAP {

            pap

        }

    }

}

[root@2FA privacyidea]# 




=====================

測試與驗證


[root@2FA privacyidea]# cat  ttls-pap.conf



network={

        ssid="VPN-Test"

        key_mgmt=WPA-EAP

        eap=TTLS

        phase2="auth=PAP"

        

        # 測試帳號

        identity="vpn.sqluser"

        # 您的 privacyIDEA 密碼/PIN + OTP

        password="MyPassword1234568"

        

        # 為了測試方便,先不強求檢查伺服器憑證

        ca_cert="/etc/pki/tls/certs/localhost.crt"

}



EAP-TTLS  


[root@2FA privacyidea]# eapol_test -r 0 -t 5 -c ./ttls-pap.conf -a 192.168.100.20 -p 1812 -s Passw0rd  


  


原 Perl/perl-privacyidea


[root@2FA privacyidea]# radtest  vpn.sqluser MyPassword498750 192.168.100.20 0  Passw0rd 





======================


[root@2FA ~]# radiusd -X


====================


(0) Received Access-Request Id 0 from 192.168.100.20:33118 to 192.168.100.20:1812 length 136

(0)   Message-Authenticator = 0x02c9cbd24f9f6ce2dc8794036443337c

(0)   User-Name = "vpn.sqluser"

(0)   NAS-IP-Address = 127.0.0.1

(0)   Calling-Station-Id = "02-00-00-00-00-01"

(0)   Framed-MTU = 1400

(0)   NAS-Port-Type = Wireless-802.11

(0)   Service-Type = Framed-User

(0)   Connect-Info = "CONNECT 11Mbps 802.11b"

(0)   EAP-Message = 0x02d300100176706e2e73716c75736572

(0) # Executing section authorize from file /etc/raddb/sites-enabled/default

(0)   authorize {

(0)     update request {

(0)       EXPAND %{Packet-Src-IP-Address}

(0)          --> 192.168.100.20

(0)       Packet-Src-IP-Address = 192.168.100.20

(0)     } # update request = noop

(0) eap: Peer sent EAP Response (code 2) ID 211 length 16

(0) eap: EAP-Identity reply, returning 'ok' so we can short-circuit the rest of authorize

(0)     [eap] = ok

(0)   } # authorize = ok

(0) Found Auth-Type = eap

(0) # Executing group from file /etc/raddb/sites-enabled/default

(0)   authenticate {

(0) eap: Peer sent packet with method EAP Identity (1)

(0) eap: Calling submodule eap_md5 to process data

(0) eap_md5: Issuing MD5 Challenge

(0) eap: Sending EAP Request (code 1) ID 212 length 22

(0) eap: EAP session adding &reply:State = 0x5621f58e56f5f108

(0)     [eap] = handled

(0)   } # authenticate = handled

(0) Using Post-Auth-Type Challenge

(0) Post-Auth-Type sub-section not found.  Ignoring.

(0) Sent Access-Challenge Id 0 from 192.168.100.20:1812 to 192.168.100.20:33118 length 80

(0)   EAP-Message = 0x01d40016041020ea461a5b559d152dc802faf5f0f7ec

(0)   Message-Authenticator = 0x00000000000000000000000000000000

(0)   State = 0x5621f58e56f5f108f057f71af64d8cbe

(0) Finished request

Waking up in 4.9 seconds.

(1) Received Access-Request Id 1 from 192.168.100.20:33118 to 192.168.100.20:1812 length 144

(1)   Message-Authenticator = 0xd0fab63897b8e8e3829f5915746ede6e

(1)   User-Name = "vpn.sqluser"

(1)   NAS-IP-Address = 127.0.0.1

(1)   Calling-Station-Id = "02-00-00-00-00-01"

(1)   Framed-MTU = 1400

(1)   NAS-Port-Type = Wireless-802.11

(1)   Service-Type = Framed-User

(1)   Connect-Info = "CONNECT 11Mbps 802.11b"

(1)   EAP-Message = 0x02d400060315

(1)   State = 0x5621f58e56f5f108f057f71af64d8cbe

(1) session-state: No cached attributes

(1) # Executing section authorize from file /etc/raddb/sites-enabled/default

(1)   authorize {

(1)     update request {

(1)       EXPAND %{Packet-Src-IP-Address}

(1)          --> 192.168.100.20

(1)       Packet-Src-IP-Address = 192.168.100.20

(1)     } # update request = noop

(1) eap: Peer sent EAP Response (code 2) ID 212 length 6

(1) eap: No EAP Start, assuming it's an on-going EAP conversation

(1)     [eap] = updated

(1)   } # authorize = updated

(1) Found Auth-Type = eap

(1) # Executing group from file /etc/raddb/sites-enabled/default

(1)   authenticate {

(1) eap: Removing EAP session with state 0x5621f58e56f5f108

(1) eap: Previous EAP request found for state 0x5621f58e56f5f108, released from the list

(1) eap: Peer sent packet with method EAP NAK (3)

(1) eap: Found mutually acceptable type TTLS (21)

(1) eap: Calling submodule eap_ttls to process data

(1) eap_ttls: (TLS) TTLS -Initiating new session

(1) eap: Sending EAP Request (code 1) ID 213 length 6

(1) eap: EAP session adding &reply:State = 0x5621f58e57f4e008

(1)     [eap] = handled

(1)   } # authenticate = handled

(1) Using Post-Auth-Type Challenge

(1) Post-Auth-Type sub-section not found.  Ignoring.

(1) session-state: Saving cached attributes

(1)   Framed-MTU = 984

(1) Sent Access-Challenge Id 1 from 192.168.100.20:1812 to 192.168.100.20:33118 length 64



=======================



# eapol_test -r 0 -t 5 -c ./ttls-pap.conf -a 192.168.100.20 -p 1812 -s Passw0rd  



RADIUS packet matching with station

MS-MPPE-Send-Key (sign) - hexdump(len=32): 4a e1 06 d2 fb c8 14 ca 38 d2 d1 fb 96 80 eb e0 4b 68 4a 68 ac 91 e0 07 d8 fa ae f5 e6 7e a0 d4

MS-MPPE-Recv-Key (crypt) - hexdump(len=32): 7f 65 f8 85 97 3a ab 83 f2 88 ff bd 5e 42 5e 35 ef 1b c4 2e 63 7a 77 1d 5a cb 0f 31 76 07 26 83

decapsulated EAP packet (code=3 id=92 len=4) from RADIUS server: EAP Success

EAPOL: Received EAP-Packet frame

EAPOL: SUPP_BE entering state REQUEST

EAPOL: getSuppRsp

EAP: EAP entering state RECEIVED

EAP: Received EAP-Success

EAP: Status notification: completion (param=success)

EAP: EAP entering state SUCCESS

CTRL-EVENT-EAP-SUCCESS EAP authentication completed successfully

EAPOL: IEEE 802.1X for plaintext connection; no EAPOL-Key frames required

WPA: EAPOL processing complete

Cancelling authentication timeout

State: DISCONNECTED -> COMPLETED

EAPOL: SUPP_PAE entering state AUTHENTICATED

EAPOL: SUPP_BE entering state RECEIVE

EAPOL: SUPP_BE entering state SUCCESS

EAPOL: SUPP_BE entering state IDLE

eapol_sm_cb: result=1

EAPOL: Successfully fetched key (len=32)

PMK from EAPOL - hexdump(len=32): 7f 65 f8 85 97 3a ab 83 f2 88 ff bd 5e 42 5e 35 ef 1b c4 2e 63 7a 77 1d 5a cb 0f 31 76 07 26 83

No EAP-Key-Name received from server

WPA: Clear old PMK and PTK

EAP: deinitialize previously used EAP method (21, TTLS) at EAP deinit

MPPE keys OK: 1  mismatch: 0

SUCCESS






 

PrivacyIDEA RADIUS plugin Part 3

再延續PrivacyIDEA  Radius 外掛這個主題

前導如下:

https://xrcd2.blogspot.com/2026/05/privacyidea-radius-plugin-part-2.html



privacyIDEA 帳號的取得方式,常見的有 

(1) Create a new Passwd Resolver (/etc/passwd)

(2) Create a new LDAP Resolver (如 Windows AD(Active Directory) )

(3) Create a new SQL Resolver


先建立 Resolvers 解析器 ,再建 Realm 領域 與其建立連結.


如要建立(或取得)一個 privacyIDEA 的使用者,它可以來自(1) privacyIDEA Linux 本機上的使用者.

可自行參考 https://xrcd2.blogspot.com/2025/04/privacyidea.html

或 (2) LDAP 上的帳號 可自行參考  https://xrcd2.blogspot.com/2025/04/privacyidea-2fardp-test.html 


如(1)透過本機的/etc/passwd的作法,密碼只能透過設定 PIN 碼取代之,因為 privacyIDEA 無權去讀解寫/etc/shadow


[root@2FA src]# ls -la /etc/shadow

---------- 1 root root 1327 May 23 19:45 /etc/shadow

[root@2FA src]# ls -la /etc/passwd

-rw-r--r-- 1 root root 2955 May 23 19:45 /etc/passwd

[root@2FA src]# 


(2) LDAP Resolver 對 privacyIDEA 而言它,簡單的說他只負責驗證,使用者跟Token/OTP的配對關連及登入驗證部份.


如上所述,如需更簡單的管理 privacyIDEA 上的使用者,透過 MySQL 是一個不錯的選擇.


使用情境假設. privacyIDEA 用於 VPN 及 Windows RDP 登入控管.那使用者在[遠端連回公司辦公]的安控,就是有二段完全獨立的雙因子驗證 (2FA) ,如第一階段 VPN 的部份可以用 privacyIDEA SQL 身份及OTP,第二階段 RDP 的部份,可以用 privacyIDE Windows AD 身份及OTP.這樣應該更安全了吧.


以下筆記為建立  privacyIDEA SQL 使用者帳戶的小筆記


[root@2FA src]# mysql

Welcome to the MariaDB monitor.  Commands end with ; or \g.

Your MariaDB connection id is 184

Server version: 10.11.15-MariaDB MariaDB Server


Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.


Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


MariaDB [(none)]> CREATE DATABASE vpn_users;

Query OK, 1 row affected (0.005 sec)


MariaDB [(none)]> CREATE USER 'pi_resolver'@'localhost' IDENTIFIED BY 'Passw0rd';

Query OK, 0 rows affected (0.003 sec)


MariaDB [(none)]> GRANT ALL PRIVILEGES ON vpn_users.* TO 'pi_resolver'@'localhost';

Query OK, 0 rows affected (0.001 sec)


MariaDB [(none)]> FLUSH PRIVILEGES;

Query OK, 0 rows affected (0.001 sec)


MariaDB [(none)]> use vpn_users;

Database changed

MariaDB [vpn_users]> CREATE TABLE pi_users (

    ->     id INT AUTO_INCREMENT PRIMARY KEY,

    ->     username VARCHAR(64) NOT NULL UNIQUE,

    ->     password_hash VARCHAR(255),

    ->     email VARCHAR(100),

    ->     mobile VARCHAR(20),

    ->     description VARCHAR(255)

    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Query OK, 0 rows affected (0.005 sec)


MariaDB [vpn_users]> INSERT INTO pi_users (username, email, mobile) VALUES ('testuser', 'testuser@vlab.tw', '0912345678');

Query OK, 1 row affected (0.001 sec)



MariaDB [vpn_users]>  SELECT * FROM pi_users\G;

*************************** 1. row ***************************

           id: 1

     username: testuser

password_hash: NULL

        email: testuser@vlab.tw

       mobile: 0912345678

  description: NULL

1 row in set (0.000 sec)


ERROR: No query specified


MariaDB [vpn_users]> UPDATE pi_users SET description = 'TEST User' WHERE id = 1;

Query OK, 1 row affected (0.001 sec)

Rows matched: 1  Changed: 1  Warnings: 0


MariaDB [vpn_users]>  SELECT * FROM pi_users\G;

*************************** 1. row ***************************

           id: 1

     username: testuser

password_hash: NULL

        email: testuser@vlab.tw

       mobile: 0912345678

  description: TEST User

1 row in set (0.000 sec)


ERROR: No query specified


MariaDB [vpn_users]> 



=======================


==>

 {"userid":"id","username":"username","password":"password_hash","email":"email","mobile":"mobile","description":"description"}


======================


CREATE TABLE pi_users (

    id INT AUTO_INCREMENT PRIMARY KEY,

    username VARCHAR(64) NOT NULL UNIQUE,

    password_hash VARCHAR(255),

    email VARCHAR(100),

    mobile VARCHAR(20),

    description VARCHAR(255)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;



==========================


[root@2FA ~]# vi   /etc/privacyidea/rlm_perl.ini

[Default]

URL = https://localhost/validate/check


#REALM = defrealm

#RESCONF = deflocal


REALM = mysql 

RESCONF = SQL


===================================


MariaDB [(none)]> use vpn_users;

Reading table information for completion of table and column names

You can turn off this feature to get a quicker startup with -A


Database changed

MariaDB [vpn_users]>  SELECT * FROM pi_users\G;

*************************** 1. row ***************************

           id: 1

     username: testuser

password_hash: NULL

        email: testuser@vlab.tw

       mobile: 0912345678

  description: TEST User

*************************** 2. row ***************************

           id: 2

     username: vpn.sqluser

password_hash: {SSHA256}p5yVX9/7OX0TrPdHlQhrm4rPZyi5F/8KbnPPPP/qFqsTAuA8

        email: vpn.sqluser@vlab.tw

       mobile: 0939000000

  description: VPN User in SQL

2 rows in set (0.000 sec)


ERROR: No query specified


MariaDB [vpn_users]> 


======================================

 


























PrivacyIDEA RADIUS plugin Part 2

 再延續PrivacyIDEA  Radius 外掛這個主題

前導如下:

https://xrcd2.blogspot.com/2025/09/privacyidea-radius-plugin-installation.html


整合 FreeRADIUS 與 FortiGate 進行 OTP 登入,主要有兩種常見的實作方式。

一種是將 OTP 與密碼(PIN碼)組合在同一個欄位輸入(Password/PIN+OTP),

另一種是透過挑戰-回應(Challenge-Response)機制分成兩個步驟輸入。


驗證模式選擇,根據不同的後端整合,可以選擇以下方式:


(1)組合輸入模式 (Password/PIN+OTP):

   使用者在 FortiClient 或登入介面的「密碼」欄位直接輸入 密碼(PIN碼) + 6位OTP(例如 MyPassword123456)(PIN 9999123456)。

    FreeRADIUS 接收到請求後,會自動拆分並分別驗證密碼((PIN碼) 與 OTP 碼。

    優點:設定簡單,無需處理複雜的 RADIUS 挑戰屬性。

(2)挑戰-回應模式 (Challenge-Response):

    使用者先輸入帳號密碼,FortiGate 發送第一個 RADIUS 請求。

    FreeRADIUS 回傳 Access-Challenge,FortiGate 隨即彈出第二個視窗要求輸入驗證碼。

    優點:使用者體驗較直覺,但需確保 FreeRADIUS 與 FortiGate 之間的通訊協定(如 PAP 或 MS-CHAPv2)支援此流程。


(1) 組合輸入模式    測試 


 (1)  Audit  


(2)挑戰-回應模式規則建立




(2)挑戰-回應模式 測試


(2) Audit 



Demo 





2026年4月30日 星期四

Lab : Setting up a FortiGate VPN Client (OPNsense) with OTP

 

 使用軟體 opnsense

 https://opnsense.org/


先前導讀相關 Blog  

https://xrcd2.blogspot.com/2025/05/pfsense-fortigate.html

https://xrcd2.blogspot.com/2025/04/freeradius-google-authenticator-2fa.html


下載 ISO 用它開機後,使用 帳/密:  installer / opnsense 進入安裝模式,如有使用二張網卡,預設 WAN port 會使用 DHCP 取得IP/GW/DNS等相關資訊,LAN port IP 預設則為 192..168.1.1/24 可以透過 browser 連入 OPNsense,可使用 帳/密:  root / opnsense 登入OPNsense管理頁面.

接下來可以安裝  os-freeradius / os-net-snmp  等外掛,在安裝及設定完成後可以移除不使用的 WAN port ,以下這台OPNsense僅做 RADIUS 伺服器使用.


以下運用假設情境為 FortiGate VPN Client 透過 RADIUS 做 OTP 登入認證.


運作機制說明:使用者輸入密碼再加上 Google Authenticator APP 產生的 6 碼動態驗證碼(TOTP 協定)。[OTP 碼直接在密碼後面接者輸入即可.]

FortiGate 收到後將驗證請求轉發至後端的 RADIUS 伺服器(FreeRADIUS ).RADIUS 伺服器與綁定 Google Authenticator (OTP) 演算法的驗證比對後回傳驗證成功.


實驗簡要架構說明:


假設情境:

FortiGate VPN Client ===>(密碼加OTP)===> Fortigate Firewall ===> RADIUS (OPNsense Plugin) ===>  OPNsense 登入認證 (user id/pwd/otp) 


重點一.必需將 OPNsense \System\  Access \ Servers 新增一個 OTP-Server 

          Type 為 Local + Timebased One Time Password


重點二. 必需將 OPNsense \System\  Access \ Users \ 新增一個使用者並

         設定上密碼 及使用 OTP (這些資訊是用來提供 VPN 登入認證使用) 


重點三. 不使用 OPNsense \ Services \ FreeRADIUS \ Users 這個功能.


實驗證明: 

還真的不行,但OTP用來做  OPNsense 登入應該是可以的.

它的 FreeRADIUS 僅支援一般的帳密,不支援 OTP 


還是只能走回老路用   pfSense

FortiGate VPN Client ===>(密碼加OTP)===> Fortigate Firewall ===> RADIUS (pfSense Plugin) ===>  pfSense 登入認證 (user id/pwd/otp) 



OTP Demo










2026年4月12日 星期日

守護網路安全的瑞士刀:Security Onion 簡介

 

守護網路安全的瑞士刀:Security Onion 簡介

在網路安全的世界裡,如果你想要監控流量、偵測威脅並進行入侵分析,通常需要安裝一大堆工具。但 Security Onion (SO) 的出現,讓這一切變得簡單許多。

什麼是 Security Onion?

Security Onion 是一個自由且開源的 Linux 發行版,專門用於 威脅狩獵 (Threat Hunting)企業安全監控 (Enterprise Security Monitoring, ESM) 以及 日誌管理

簡單來說,它就像是一個「安全工具大禮包」,幫你把業界最強大的開源安全軟體全部整合在一起,並預先配置好,讓你開箱即用。


Security Onion 的核心三部曲

Security Onion 的強大來自於它整合了三種主要功能:

  1. 全數據包捕獲 (Full Packet Capture): 就像錄影機一樣,記錄下網路上發生的一切細節。

  2. 入侵偵測系統 (IDS): 包含主機端 (HIDS) 與網路端 (NIDS),當發現可疑行為時會立即發出警報。

  3. 分析工具 (Analysis Tools): 提供直觀的圖表與介面,讓分析師能快速從海量數據中抓出「害群之馬」。


內建的神級工具陣容

Security Onion 整合了多款重量級工具,形成了一個完整的防禦體系:

  • 流量分析: Zeek (舊稱 Bro) 與 Suricata。

  • 日誌處理: 著名的 ELK Stack (Elasticsearch, Logstash, Kibana),現在也支持 Elastic Fleet

  • 分析介面: * Security Onion Console (SOC): 自家的管理中心。

    • CyberChef: 萬能的資料轉換工具。

    • TheHive: 專為事件響應設計的案例管理平台。


為什麼你應該嘗試 Security Onion?

1. 節省大量的部署時間

手動整合 Elasticsearch、Suricata、Zeek 等工具是一個地獄級的任務。Security Onion 透過簡單的安裝程序,幾分鐘內就能幫你搭建好一個專業級的 SOC 中心。

2. 強大的可視化

透過 Kibana 儀表板,你可以一眼看出當前網路的流量分佈、哪些 IP 正在嘗試攻擊你的伺服器,甚至追蹤惡意程式的連線行為。

3. 學習與實戰的絕佳平台

如果你是資安新手,Security Onion 是學習「藍隊 (Blue Teaming)」技能的最佳起點。它能讓你真實體驗到分析師在處理警報時的完整工作流。


以上內容撰寫: gemini ai

線上手冊: https://docs.securityonion.net/