Show HN: Wag ระบบยืนยันตัวตนหลายปัจจัยและลงทะเบียนสำหรับ WireGuard
(github.com/NHAS)- Wag เป็นโปรเจกต์ที่เพิ่มการยืนยันตัวตนหลายปัจจัย การจำกัดเส้นทาง และการลงทะเบียนอุปกรณ์ให้กับ WireGuard โดยสามารถแยกเส้นทางที่ต้องใช้ MFA ออกจากเส้นทางสาธารณะที่เข้าถึงได้ตลอดเวลา
- มี API สำหรับลงทะเบียนไคลเอนต์ใหม่, ความพร้อมใช้งานสูง, การอัปเดตและการแจ้งเตือนผู้ใช้แบบเรียลไทม์ รวมถึงการผสานรวม MFA หลายแบบ เช่น Security Key, SSO, PAM และ TOTP
- การรันเซิร์ฟเวอร์ต้องเปิดใช้งาน IP forwarding และหากรันแบบแมนนวลต้องติดตั้ง
iptablesกับlibpamรวมถึงต้อง รันด้วย root เพื่อจัดการiptablesและอุปกรณ์ WireGuard - จัดการได้ผ่านเว็บ UI และ CLI โดย CLI มีคำสั่งย่อย
start,registration,devices,users,webadminสำหรับจัดการโทเค็นลงทะเบียน การล็อกอุปกรณ์ การรีเซ็ต MFA และบัญชีผู้ดูแลเว็บ - ข้อจำกัดคือรองรับ
AllowedIPเพียงรายการเดียวต่อไคลเอนต์ และโดยหลักรองรับเฉพาะ Linux ส่วน Windows อาจใช้งานได้หลังทำขั้นตอนบางอย่าง
ฟีเจอร์ของ WireGuard ที่ Wag เพิ่มเข้ามา
- Wag เพิ่ม MFA, การจำกัดเส้นทาง และการลงทะเบียนอุปกรณ์ให้กับ WireGuard
- สามารถกำหนดเส้นทางโดยแบ่งเป็นเส้นทางที่ต้องผ่านการยืนยัน MFA และ เส้นทางสาธารณะ ที่เข้าถึงได้ตลอดเวลา
- มี API ที่ใช้งานง่ายสำหรับลงทะเบียนไคลเอนต์ใหม่
- รองรับความพร้อมใช้งานสูง การอัปเดตผู้ใช้และการแจ้งเตือนแบบเรียลไทม์
- การผสานรวม MFA รวมวิธีต่อไปนี้
- Security Key
- SSO
- PAM
- TOTP
- เอกสารอยู่ที่ Documentation
เงื่อนไขการติดตั้งและการรัน
- ต้องเปิดใช้งาน forwarding บนเซิร์ฟเวอร์
- IPv4 ใช้การตั้งค่า
net.ipv4.ip_forward=1 - IPv6 ใช้การตั้งค่า
sysctlที่เกี่ยวข้อง เช่นnet.ipv6.conf.all.forwarding=1
- IPv4 ใช้การตั้งค่า
- ตัวอย่างการรัน Docker Compose ใช้อิมเมจ
wagvpn/wag:latest- ตัวอย่างพอร์ตหน้าจัดการคือ
4433/tcp - ตัวอย่างพอร์ตหน้าลงทะเบียนสาธารณะคือ
8081/tcp - ตัวอย่างพอร์ต WireGuard คือ
53230/udp - เชื่อมต่ออุปกรณ์
/dev/net/tunเข้ากับคอนเทนเนอร์
- ตัวอย่างพอร์ตหน้าจัดการคือ
- การติดตั้งแบบแมนนวลต้องใช้
iptablesและlibpam - Wag ต้องรันด้วย root เพื่อจัดการ
iptablesและ อุปกรณ์ WireGuard - ไบนารีรีลีสต้องใช้
glibc 2.31+ - การ build จากซอร์สต้องใช้
go1.23.1และnpm
วิธีจัดการ
- หลังเปิดใช้งาน UI สำหรับจัดการและตั้งค่า Wag แล้ว ระบบจะสร้างผู้ดูแลคนแรกและพิมพ์รหัสผ่านออกทาง STDOUT
- จากนั้นสามารถล็อกอินเข้าเว็บ UI เพื่อจัดการผู้ใช้ได้
- ผู้ใช้ root สามารถจัดการเซิร์ฟเวอร์ Wag ผ่าน CLI ได้
- รูปแบบ CLI คือ
wag subcommand [-options] - คำสั่งย่อยที่รองรับมีดังนี้
start: เริ่มเซิร์ฟเวอร์ Wag โดยไม่ทำให้เป็น daemonregistration: จัดการการสร้าง ลบ และดูรายการโทเค็นลงทะเบียนdevices: จัดการการดูรายการ ลบ ล็อก ปลดล็อกอุปกรณ์ WireGuard และดูเซสชัน MFA ที่ใช้งานอยู่users: จัดการ MFA ของผู้ใช้ ลบผู้ใช้ ล็อกบัญชี และรีเซ็ต MFAwebadmin: เพิ่ม ลบ ดูรายการ ล็อกและปลดล็อกบัญชีผู้ดูแลเว็บ UIversion,firewallก็รวมอยู่ในคำสั่งที่รองรับด้วย
โทเค็นลงทะเบียนและโฟลว์ MFA
- การลงทะเบียนอุปกรณ์ใหม่เริ่มจากสร้าง โทเค็นลงทะเบียน ด้วยคำสั่งอย่าง
wag registration -add -username tester - เมื่อนำโทเค็นที่สร้างขึ้นส่งไปยัง endpoint ลงทะเบียนสาธารณะ จะได้รับการตอบกลับเป็นการตั้งค่า WireGuard
- การตั้งค่าที่ส่งกลับมามีรายการอย่าง
Interface,PrivateKey,Address,Peer,Endpoint,PublicKey,AllowedIPs,PersistentKeepAlive - ผู้ใช้เชื่อมต่อไปยังที่อยู่ VPN ของเซิร์ฟเวอร์และกรอก โค้ด 2FA
- ระยะเวลาที่เซสชันคงอยู่ก่อนหมดอายุกำหนดในไฟล์ตั้งค่า
คอนโซลจัดการผ่านเว็บ
- หากต้องการล็อกอินเข้าคอนโซลจัดการ ต้องตั้งค่า
Webserver.Management.Enabledเป็นtrue - เพิ่มบัญชีผู้ดูแลเว็บในคอนโซลด้วย
sudo ./wag webadmin -add -username <your_username> -password <your-password-here> - จากนั้นเข้าที่อยู่ listen สำหรับการจัดการและกรอกข้อมูลรับรอง
- ตัวเว็บอินเทอร์เฟซเอง ไม่สามารถเพิ่มผู้ใช้ผู้ดูแล ได้
- แนะนำว่าไม่ควรเปิดเผยพอร์ทัลจัดการออกสู่ภายนอก และควรตั้ง
ListenAddressเป็น127.0.0.1หรือlocalhostแล้วเปิดให้เข้าถึงผ่าน SSH forwarding
รายการตั้งค่าหลัก
NumberProxiesระบุจำนวน reverse proxy ที่เชื่อถือได้ซึ่งอยู่หน้าไคลเอนต์ และทำให้ Wag parse IP ไคลเอนต์โดยอ้างอิงX-Forward-ForSocketคือ socket ควบคุมของ Wag และหากเปลี่ยนค่านี้จะสามารถรัน Wag หลายอินสแตนซ์บนเครื่องเดียวกันได้NATเปิดหรือปิด masquerading และเมื่อเปิดใช้งาน ทราฟฟิกทั้งหมดจะดูเหมือนเริ่มต้นจากเซิร์ฟเวอร์ VPNNATExcludeRangesระบุช่วง CIDR ที่จะยกเว้นจาก NAT เมื่อNAT=trueExposePortsเปิดเผยพอร์ตของเซิร์ฟเวอร์ VPN ให้ไคลเอนต์และเพิ่มกฎiptablesCheckUpdatesค่าเริ่มต้นปิดอยู่ และเมื่อเปิดใช้งาน UI จัดการจะแสดงการแจ้งเตือนเวอร์ชันใหม่ของ Wag และเข้าถึงapi.github.comAclsกำหนดกลุ่มและนโยบาย แต่จะมีผล เฉพาะตอนรันครั้งแรกเท่านั้น และระหว่าง runtime ให้แก้ไขผ่านเว็บ UIWebserverรวมการตั้งค่า endpoint ลงทะเบียนสาธารณะ พอร์ทัล MFA ของ tunnel และพอร์ทัลจัดการWireguardตั้งค่าชื่ออุปกรณ์ พอร์ต listen, private key, subnet ที่ VPN รับผิดชอบ, MTU และเซิร์ฟเวอร์ DNSClusteringรวมการตั้งค่าชื่อคลัสเตอร์ สถานะคลัสเตอร์ etcd ระดับ log, witness node, ตำแหน่งฐานข้อมูล และใบรับรองคลัสเตอร์
การทำงานของนโยบาย ACL
Policiesกำหนดเส้นทางที่ VPN จะ capture รวมถึงพอร์ตและโปรโตคอลที่จะผ่าน Wag- การใช้กฎอิงตาม ความยาว prefix ของ subnet โดย match ที่เฉพาะเจาะจงที่สุดจะเป็นตัวกำหนดระดับการเข้าถึงเส้นทาง
- ตัวอย่างเช่น หากกำหนด
/16เป็น MFA และกำหนด/32เฉพาะภายในนั้นเป็น Allow ค่า/32ที่เฉพาะเจาะจงกว่าจะมีลำดับความสำคัญ จึงเข้าถึงได้โดยไม่ต้องใช้ MFA - พฤติกรรมนี้เปลี่ยนใน
v6.0.0ก่อนหน้านั้นเส้นทาง MFA จะมีลำดับความสำคัญเสมอ - หากมีหลายนโยบายกำหนดบนเส้นทางเดียวกัน นโยบายจะถูกผสานกัน และกฎ MFA จะมีลำดับความสำคัญ
- ตั้งแต่เวอร์ชันที่ยังไม่รีลีส จะสามารถใช้กฎ
Denyเพื่อบล็อกการเข้าถึงเส้นทางได้ - เนื่องจากกฎที่เฉพาะเจาะจงที่สุดจะสร้าง “bucket” กฎใหม่ หากใน bucket
/32มีเพียง deny การเข้าถึงพอร์ตอื่นของ/32เดียวกันก็อาจไม่ได้รับอนุญาตด้วย
กฎพอร์ตและโปรโตคอล
- การเข้าถึงบริการสามารถกำหนดด้วยกฎพอร์ตและโปรโตคอลได้
- ประเภทกฎที่รองรับมี 3 แบบ
- Any: หากไม่มีกฎแยกต่างหากหรือใช้คีย์เวิร์ด
anyจะอนุญาตทุกการจับคู่บริการและพอร์ต - Single Service: อนุญาตพอร์ต TCP·UDP เฉพาะของโฮสต์ เช่น
192.168.1.1 22/tcp 53/udp - Ranges: ระบุช่วงพอร์ต เช่น
192.168.1.1 22-1024/tcp 23-53/any
- Any: หากไม่มีกฎแยกต่างหากหรือใช้คีย์เวิร์ด
- ช่วงพอร์ตต้องเขียนพอร์ตต่ำกว่าก่อน
- ICMP ไม่มีพอร์ต จึงระบุได้โดยไม่ต้องใส่พอร์ต เช่น
1.1.1.1 icmp
ข้อจำกัดและการพัฒนา
- Wag รองรับ
AllowedIPเพียงหนึ่งรายการต่อไคลเอนต์ - ข้อจำกัดนี้เหมาะกับโครงสร้างที่เชื่อมจากไคลเอนต์ไปยังเซิร์ฟเวอร์
- โดยหลักเป็น เฉพาะ Linux และ Windows อาจใช้งานได้หลังทำขั้นตอนบางอย่าง
- ในโหมดพัฒนา สามารถใช้ environment variable เพื่อตั้งค่า IP ของ request ที่เข้ามาทาง tunnel ให้เป็น IP ของไคลเอนต์ได้
- ตัวอย่างการทดสอบคือรัน
sudo go test -v .ในinternal/router - สำหรับการมีส่วนร่วมจากภายนอก แนะนำให้เขียน test เมื่อเป็นไปได้หากเพิ่มฟีเจอร์หรือแก้บั๊ก แล้วเปิด Pull Request
1 ความคิดเห็น
ความเห็นบน Hacker News
ดูเผิน ๆ ก็น่าสนใจ แต่มีบางจุดที่ติดใจ
จากตัวอย่าง
curl [http://public.server.address:8080/register_device?key=e83253...](<http://public.server.address/register_device/…;)และคำอธิบายว่า “บริการจะคืน response ที่ทำเป็น template ไว้ทั้งหมด” ดูเหมือนว่าใน ขั้นตอนลงทะเบียน เซิร์ฟเวอร์จะเป็นฝ่ายสร้าง private key แล้วส่งให้ไคลเอนต์ แทนที่ไคลเอนต์จะสร้าง private key เองแล้วส่ง public key ไปให้เซิร์ฟเวอร์อีกอย่าง ตัวอย่างเป็น HTTP ด้วย อย่างน้อยควรเปลี่ยนส่วนนั้นเพื่อไม่ให้คนคิดว่า HTTP ก็เป็นตัวเลือกที่โอเค
สงสัยด้วยว่าเมื่อเซสชันหมดอายุ ไคลเอนต์มีวิธีรู้เรื่องนี้หรือไม่ หรือว่าอย่างเซสชัน SSH จะหยุดค้างไปเฉย ๆ?
ผมเคยมองหาไคลเอนต์ WireGuard ที่ทำงานคล้ายการตรวจจับ captive portal ของ Wi‑Fi เป็นครั้งคราว โดยในอุดมคติคือเพิ่มบรรทัดเดียวอย่าง
persistentkeepaliveในไฟล์ตั้งค่า เพื่อดึง URL แล้วตรวจเป็นระยะ ๆ ถ้าได้OKก็ปกติ ถ้าไม่มี response ก็เป็นปัญหาเครือข่าย ถ้าได้ headerLocationก็เปิดเบราว์เซอร์ไปยังตำแหน่งนั้นเพื่อยืนยันเซสชันใหม่ เป็นต้นยังหาไคลเอนต์แบบนั้นไม่เจอ
pubkeyได้แบบเลือกใช้ ดังนั้นจึงไม่จำเป็นต้องพึ่งวิธีที่เซิร์ฟเวอร์สร้าง private key เอง เอกสารยังไม่พอเลยอาจทำให้สับสนได้ตอบคำถามสุดท้าย eBPF XDP ที่ผมใช้ทำได้แค่
PASS,DROP,REDIRECTดังนั้นจึงจัดการด้วยผลลัพธ์ที่ง่ายที่สุดคือPASS/DROPและการเชื่อมต่อก็จะค้างไปเฉย ๆอย่างไรก็ตาม ถ้าเพิ่มหน้าตรวจจับ captive portal เข้าไปในรายการ MFA ของ wag ก็สามารถตั้งค่าการตรวจจับเองได้ และหลังจากนั้นเบราว์เซอร์จะจัดการต่อให้
ผมไม่มีแผนจะทำฟีเจอร์ใน wag ที่ทำงานแบบ intercept หรือ proxy ถ้าทำแบบนั้น การจัดการตอน authentication หมดอายุหรือ logout จะง่ายขึ้นบ้าง แต่นั่นไม่ใช่ทิศทางของโครงการ
ผมเขียนไคลเอนต์ Go สำหรับ Mac ตัวหนึ่ง และใช้คำสั่ง
wgของ Brew เพื่อจัดการการสร้าง key ด้วย แต่มันยังหยาบและต้องใช้sudoถ้ามีแอป native ดี ๆ ที่ใช้สิทธิ์ด้านเครือข่ายก็คงดี แต่มันเกินความสามารถของผม
สงสัยว่าคุณได้จัดการหรือมีแผนจะจัดการปัญหา การจัดการเซสชัน แล้วหรือยัง
โดยแก่นแล้ว key ของ WireGuard ก็เหมือน session key ถาวร
ถ้าซอฟต์แวร์ที่ทำ transport layer ของ WireGuard เป็นโซลูชัน VPN server ที่เหมาะสม ก็ควร implement การจัดการเซสชันด้วย กล่าวคือผ่านช่องทางที่สองกับเซิร์ฟเวอร์เพื่อหมุนเวียน session key เป็นระยะ ๆ, ยุติเซสชัน, เปลี่ยน IP address, ตั้งค่า route ใหม่ และทำ authentication ซ้ำเมื่อจำเป็น
key ของ WireGuard ทำให้สื่อสารกับเซิร์ฟเวอร์ wag ได้ แต่เซสชันจริงถูกเก็บเป็น eBPF map ที่มีข้อมูลว่าผู้ใช้ผ่านการยืนยันตัวตนแล้วหรือไม่
ดังนั้นแม้มีคนขโมยข้อมูล private key ไป ก็ยังเข้าถึงเส้นทางที่ถูกจำกัดด้วย MFA ไม่ได้
สงสัยว่ามีการป้องกัน การ brute force โค้ด TOTP หรือไม่ เช่น rate limit หรือจำกัดจำนวนครั้งที่ลองใหม่
ผมไล่อ่านโค้ดเร็ว ๆ แต่ไม่เจอการจัดการแบบนั้น
สถานการณ์ที่นึกไว้คือมีคนเปิด UI สำหรับกรอก TOTP ในเบราว์เซอร์ เปิด developer tools แล้วลองยิงโค้ด TOTP ที่เป็นไปได้ทั้งหมดซ้ำ ๆ
โดยเฉพาะยังมีเจตนาให้ผู้ใช้ฉุกคิดด้วยว่าทำไมอุปกรณ์ถึงพยายามยืนยันตัวตนแบบฝืน ๆ เพราะสถานการณ์แบบนั้นอาจบ่งชี้ว่า endpoint ถูกเจาะ
ฟังดูคล้าย Headscale หรือ Tailscale มาก ดีที่เห็นทางเลือกสำหรับจัดการเครือข่าย WireGuard
สงสัยว่ามี เอกสารเปรียบเทียบ ที่ช่วยให้เข้าใจไหมว่าฟีเจอร์ทับซ้อนกันแค่ไหน, มีอะไรเพิ่มเข้ามา, ต่างกันอย่างไร และอะไรที่จะไม่ implement ต่อไปในอนาคต
ผมไม่ได้ใส่การเปรียบเทียบโดยตรงไว้ในเอกสาร และตอนนี้ไม่ใช่ทิศทางที่ผมจะไป โปรเจกต์นี้ตรงกับความต้องการของผมและสนุกพอสมควร
Wag เหมาะกับ โครงสร้างแบบ hub-and-spoke ที่ต้องการขอบเขตชัดเจน มากกว่า mesh แบบ Tailscale ที่ทุกอย่างถึงกันและกฎเป็นตัวกำหนด overlay
ทั้ง wag และ Tailscale เพิ่มการผสาน SSO และ 2FA โดยพฤตินัยเพื่อปกป้องผู้ใช้
ทั้งคู่มีวิธีลงทะเบียนและ web UI สำหรับบริหารจัดการ แต่ผมเป็นนักพัฒนาคนเดียวที่ไม่ชอบงาน web development ดังนั้น Tailscale น่าจะขัดเกลากว่ามาก
สิ่งที่จะไม่ implement แน่นอนคือฝั่ง intercept หรือ TLS proxy เพื่อ redirect ผู้ใช้หลัง session logout เหตุผลหลักคือ ณ ตอนนี้การทำสิ่งนั้นด้วย eBPF สำหรับผมยังหนักไปหน่อย และถ้าจะทำให้ใช้งานได้จริงก็คงต้องเขียนส่วนประกอบ DNAT/SNAT ซึ่งผมไม่อยากใช้
เป็น IPv4 เท่านั้น เหรอ ถ้าเป็นไซต์ที่เลือก WireGuard ก็น่าจะมีการตั้งค่าที่ทันสมัยกว่า และอาจใช้ ULA แบบ service ของตัวเองกันเยอะ
สงสัยว่าตอนพูดถึง ULA มีจุดไหนที่คิดไว้เป็นพิเศษหรือเปล่า