จำกัดการเข้าถึงเซิร์ฟเวอร์และวาง WAF ไว้หน้าแอปของคุณ

ตั้งแต่การ deploy ครั้งแรก erpc worker จะอนุญาต SSH จากที่อยู่ของคุณเท่านั้น และ drop การเชื่อมต่อขาเข้าอื่นทั้งหมด ส่วนตาราง [waf] จะวาง proxy สำหรับกรองไว้หน้า API ของคุณ
หน้านี้อธิบาย ERPC CLI v0.1.0-rc.4 ซึ่งเป็นเวอร์ชัน pre-release

ไฟร์วอลล์

การ deploy จะตั้งค่าไฟร์วอลล์ของเซิร์ฟเวอร์ก่อนที่แอปจะเริ่มทำงานครั้งแรก:
  • SSH ได้เฉพาะจาก firewall.ssh_allow และจากที่อยู่ต้นทางของการ deploy
  • การเชื่อมต่อขาเข้าอื่นทั้งหมดถูก drop ยกเว้น run.port จาก firewall.app_allow ส่วนแอปที่อยู่หลัง WAF จะเป็นพอร์ตของ WAF แทน และเฉพาะเมื่อ WAF บนเซิร์ฟเวอร์รันกฎที่ [waf] กำหนดในตอนนี้อยู่แล้วเท่านั้น (ด้านล่าง)
การเปลี่ยนแปลงแต่ละครั้งทำงานภายใต้ตัวจับเวลา rollback 120 วินาที และจะถูกคงไว้ก็ต่อเมื่อการเชื่อมต่อใหม่เข้ามาได้แล้วเท่านั้น การเปลี่ยนแปลงที่ผิดพลาดจึงไม่สามารถทำให้คุณถูกกันออกจากเซิร์ฟเวอร์ได้
อย่างไรก็ตาม allowlist จะยังคงอยู่แม้ที่อยู่ที่ใช้สร้างมันจะเปลี่ยนไปแล้ว: หากที่อยู่ IP ที่บ้านของคุณเปลี่ยน ทางกลับเข้าไปคือทีมสนับสนุนของ ERPC สำหรับเซิร์ฟเวอร์ของ ERPC หรือคอนโซลของผู้ให้บริการสำหรับเซิร์ฟเวอร์ของคุณเอง โปรดใส่ที่อยู่ที่สองที่คุณใช้เข้าถึงเซิร์ฟเวอร์ได้ (ออฟฟิศ หรือ VPN exit) ไว้ใน firewall.ssh_allow ก่อนที่จะไว้ใจที่อยู่ IP ที่บ้าน
หากที่อยู่ของคุณเปลี่ยน ให้เพิ่มที่อยู่คงที่ทุกที่อยู่ที่ผู้ดูแลระบบของคุณใช้:
bash
erpc worker firewall allow --to ssh 203.0.113.10 198.51.100.7
คำสั่งสิ่งที่ทำ
erpc worker firewall listแสดงว่าใครเข้าถึงเซิร์ฟเวอร์ได้บ้าง โดยแสดง erpc.toml คู่กับตารางของเซิร์ฟเวอร์
erpc worker firewall allow --to ssh|app <addresses>อนุญาตให้ที่อยู่เหล่านั้นเข้ามา และนำไปใช้ SSH อนุญาตได้จากที่อยู่เดี่ยวเท่านั้น ส่วนเครือข่ายจะรับได้เฉพาะเมื่อใช้ --to app
erpc worker firewall deny --to ssh|app <addresses>หยุดอนุญาตให้ที่อยู่เหล่านั้นเข้ามา
erpc worker firewall syncทำให้ไฟร์วอลล์ของเซิร์ฟเวอร์กลับมาเป็นไปตามที่ erpc.toml ระบุอีกครั้ง

วาง WAF ไว้หน้าแอปของคุณ

เพิ่มตารางหนึ่งตารางใน erpc.toml:
toml
[waf]
port = 8080
จากนั้น erpc worker deploy จะรัน HTTP reverse proxy บนเซิร์ฟเวอร์ใน systemd unit ของตัวเอง (erpc-waf-<app>-<env>): ทำงานในฐานะ dynamic user ที่ไม่มีสิทธิ์พิเศษ ไม่มีคีย์ ไม่มี secret และไม่มีสิ่งใดของ worker และถูกจำกัดหน่วยความจำไว้ที่ 1 GiB WAF รอรับการเชื่อมต่อที่ waf.port อ่านคำขอแต่ละรายการก่อนที่แอปจะอ่าน ปฏิเสธสิ่งที่กฎของ WAF ระบุ และส่งต่อส่วนที่เหลือไปยังแอปที่ 127.0.0.1:<run.port>
  • แอปยังอยู่บน loopback พอร์ตของแอปเองจะไม่ถูกเปิดให้เข้าถึงจากภายนอกเลย erpc worker api และ health check เข้าถึงแอปผ่าน loopback โดยไม่ผ่าน WAF
  • ใครเชื่อมต่อได้ยังคงเป็นไปตามรายการของไฟร์วอลล์ และพอร์ตของ WAF เป็นที่เดียวที่รายการนั้นอนุญาตให้ทุกคนได้:
    sh
    erpc worker firewall allow --to app 0.0.0.0/0     # and ::/0
    เมื่อไม่มีใครอยู่ในรายการ WAF จะรอรับการเชื่อมต่อแต่ไม่มีใครได้รับอนุญาตให้เข้ามา และการ deploy จะแจ้งเรื่องนี้
  • พอร์ตจะเปิดเมื่อ WAF รอรับการเชื่อมต่อบนพอร์ตนั้นแล้ว ไม่เปิดก่อนหน้านั้นเลย การ deploy ที่ WAF ไม่ขึ้นมาทำงานจะล้มเหลวและแจ้งสาเหตุ โดยที่แอปยังคงทำงานอยู่
  • เมื่อคุณเปลี่ยน [waf] พอร์ตจะไม่เปิดให้ใครรายใหม่จนกว่าการ deploy จะนำกฎใหม่ไปใช้แล้ว
  • การนำ [waf] ออกจาก erpc.toml แล้ว deploy จะลบ WAF ออกจากเซิร์ฟเวอร์

การตั้งค่า WAF

มีเพียง port ที่จำเป็น นี่คือ key ทั้งหมดพร้อมค่าเริ่มต้น:
toml
[waf]
port = 8080          # the public port, 1024 or above; the app keeps [run] port
methods = ["GET", "POST", "PUT", "DELETE"]
allow_paths = []     # every path
deny_paths = []
max_body_bytes = 1048576
max_header_bytes = 16384
rate = { per_second = 50, burst = 100 }
max_connections_per_address = 32
ban = { after = 100, window_secs = 60, for_secs = 600 }
block_probes = true
header_timeout_secs = 10
body_timeout_secs = 30
upstream_timeout_secs = 30
new_connections = { per_second = 50, burst = 100 }
Keyสิ่งที่ทำสิ่งที่ได้รับเมื่อถูกปฏิเสธ
methodsmethod ที่ส่งต่อ เขียนเป็นตัวพิมพ์ใหญ่ (สูงสุด 16 รายการ และไม่มี CONNECT เด็ดขาด)405 พร้อม Allow
allow_pathspath ที่ส่งต่อ หากไม่ระบุเลยหมายถึงทุก path รูปแบบหนึ่งรายการคือ path เดียว หรือทุกอย่างภายใต้ prefix เมื่อลงท้ายด้วย *: /api/* คือ /api และทุกอย่างที่อยู่ใต้มัน เปรียบเทียบกับ path ที่ผ่านการ percent-decode หนึ่งครั้งแล้ว โดยแยกตัวพิมพ์เล็กและตัวพิมพ์ใหญ่404
deny_pathspath ที่ถูกปฏิเสธ เขียนแบบเดียวกัน และถูกตรวจก่อน403
max_body_bytesขนาด body ของคำขอที่ใหญ่ที่สุด ได้สูงสุด 64 MiB413
max_header_bytesขนาดรวมที่ใหญ่ที่สุดของ request line และ header ตั้งแต่ 1024 ถึง 65536431
rateจำนวนคำขอต่อวินาทีจากที่อยู่เดียว และจำนวนคำขอพร้อมกันที่ยอมให้ได้ก่อนที่ rate จะมีผล429 พร้อม Retry-After
max_connections_per_addressจำนวนการเชื่อมต่อที่ที่อยู่หนึ่งถือไว้พร้อมกันได้การเชื่อมต่อถูกปิดโดยไม่ตอบกลับ
banหลังจากมีคำขอที่ถูกบล็อกครบ after ครั้งภายใน window_secs ที่อยู่นั้นจะถูกปฏิเสธเป็นเวลา for_secs (สูงสุด 30 วัน)การเชื่อมต่อของที่อยู่นั้นถูกปิดโดยไม่ตอบกลับ
block_probesปฏิเสธสิ่งที่เครื่องมือสแกนร้องขอ (/.env, /.git, /wp-login.php และอื่น ๆ ทำนองเดียวกัน)403
header_timeout_secsเวลาที่ client มีในการส่งส่วนหัวของคำขอ ตั้งแต่ 1 ถึง 120 และยังเป็นเวลาที่รอ client ที่หยุดกลางคันระหว่างส่ง body หรือหยุดรับคำตอบด้วย408 สำหรับส่วนหัว, 400 สำหรับ body ที่หยุดกลางคัน, การเชื่อมต่อถูกปิดสำหรับคำตอบที่ไม่ถูกรับ
body_timeout_secsเวลารวมทั้งหมดที่ body ของคำขอใช้ได้ ตั้งแต่ 1 ถึง 600 โดยไม่นับเวลาที่ WAF รอแอป408
upstream_timeout_secsเวลาที่แอปมีในการรับการเชื่อมต่อและเริ่มตอบกลับ ตั้งแต่ 1 ถึง 600504 ส่วน 502 เมื่อแอปไม่ได้รอรับการเชื่อมต่อ
new_connectionsขีดจำกัดของไฟร์วอลล์เองสำหรับการเชื่อมต่อ TCP ใหม่ต่อวินาทีจากที่อยู่เดียวไปยังพอร์ตของ WAF ซึ่ง nftables จะ drop ก่อนที่ proxy จะเห็น ขณะที่ชุดที่อยู่ (set) ของ nftables เต็ม ที่อยู่ที่ไม่อยู่ในชุดนั้นจะไม่ถูกนับ ขีดจำกัดนี้จึงไม่มีผลกับที่อยู่นั้นจนกว่าชุดจะมีที่ว่างอีกครั้ง แต่ขีดจำกัดของ proxy เอง (rate, max_connections_per_address, ban) ยังคงมีผลแพ็กเก็ตถูก drop
rate, max_connections_per_address, ban และ new_connections ปิดได้ด้วย false (ban = false) และ key ที่ไม่ได้ระบุจะใช้ค่าเริ่มต้น erpc worker check จะบอกว่าตาราง [waf] จะทำอะไร และมีอะไรผิด ก่อนการ deploy ใด ๆ

สิ่งที่ WAF ทำโดยไม่ต้องตั้งค่า

  • คำขอที่ไม่สามารถอ่านเป็นคำขอ HTTP/1 หนึ่งรายการที่ไม่กำกวมจะได้รับ 400 และ path ที่ไม่อยู่ในรูปแบบปกติ (normal form) ก็เช่นกัน
  • คำขอเปลี่ยนโปรโตคอล (Upgrade) จะได้รับ 501
  • การปฏิเสธทุกครั้งถูกนับรวมในเกณฑ์การแบน และการปฏิเสธแต่ละครั้งจะปิดการเชื่อมต่อนั้น
  • ที่อยู่หนึ่งหมายถึงที่อยู่ IPv4 หนึ่งที่อยู่ หรือ IPv6 หนึ่ง /64
  • WAF ไม่ส่งต่อ header ของการเชื่อมต่อระหว่าง client กับ proxy (Connection และ header ที่ header นั้นระบุ, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, Proxy-Connection, TE, Trailer), Expect, header ที่ชื่อมีขีดล่าง หรือ Forwarded, X-Real-IP และ X-Forwarded-Proto ที่ client ส่งมาเอง WAF ตั้ง X-Real-IP เป็นที่อยู่ต้นทางของการเชื่อมต่อ และเพิ่มที่อยู่นั้นต่อท้าย X-Forwarded-For มีเพียงรายการสุดท้ายของ X-Forwarded-For เท่านั้นที่ proxy รับรอง

ดูว่า WAF ทำอะไร

คำสั่งแสดง
erpc worker statusพอร์ตของ WAF ปลายทางที่ส่งต่อไป เริ่มทำงานตั้งแต่เมื่อใด และจำนวนนับต่าง ๆ ตั้งแต่นั้น
erpc worker firewall listใครเข้าถึงพอร์ตของ WAF ได้บ้าง และสิ่งที่ถูกบล็อก แยกตามกฎ
erpc worker logs --wafคำขอที่ถูกบล็อกแต่ละรายการ: กฎ รหัสสถานะ ที่อยู่ของ client, method และ 200 ไบต์แรกของ target
คำขอที่ถูกส่งต่อจะไม่ทิ้งบรรทัด log ไว้ และจะไม่มีการบันทึก header หรือ body ลง log เลย ส่วน query ของคำขอที่ถูกบล็อกจะถูกบันทึกเท่าที่ 200 ไบต์ครอบคลุม สิ่งที่ client ส่งมาไม่สามารถเขียนลงบนเทอร์มินัลของคุณได้ เพราะ WAF เขียน journal เป็น ASCII ที่พิมพ์ได้

สิ่งที่ WAF ไม่ทำ

  • ไม่มี TLS WAF ใช้ HTTP/1.1 แบบไม่เข้ารหัสบนพอร์ตของตัวเอง
  • ไม่รองรับ HTTP/2: WAF ตอบกลับด้วย 400
  • ไม่รองรับ WebSocket และ Upgrade แบบอื่นใด: 501
  • ไม่ดูข้อมูลภายใน body body จะถูกวัดขนาด แต่ไม่ถูกอ่านเลย รายการ probe นั้นสั้น และเป็นเพียงตัวกรองสัญญาณรบกวน ไม่ใช่การรับประกัน แอปยังคงตรวจสอบ input ของตัวเอง
  • ไม่มีการยืนยันตัวตน ใครเรียกแอปได้เป็นเรื่องที่แอปต้องตัดสินใจเอง และเป็นไปตามรายการของไฟร์วอลล์
  • ที่อยู่หนึ่งคือ client หนึ่งราย client ที่อยู่หลัง NAT, proxy หรือ CDN เดียวกันจะใช้ rate และการแบนร่วมกัน ส่วน client ที่มีหลายที่อยู่จะมี rate เท่ากับจำนวนที่อยู่
  • การแบนและจำนวนนับจะถูกลืมเมื่อ WAF รีสตาร์ท
  • WAF จำที่อยู่ได้สูงสุด 8192 ที่อยู่ และถือการเชื่อมต่อพร้อมกันได้สูงสุด 1024 รายการ
  • WAF รอรับการเชื่อมต่อบน IPv4 เท่านั้น รายการ IPv6 ใน app_allow จึงยังเข้าถึงสิ่งใดไม่ได้
  • path ถูกเปรียบเทียบตามไบต์ที่ได้หลังถอดรหัส แอปที่อ่าน path อย่างหลวมกว่า (ไม่แยกตัวพิมพ์เล็กพิมพ์ใหญ่ พารามิเตอร์ ; หรือถอดรหัสสองครั้ง) อาจถูกเข้าถึงได้ภายใต้ชื่อที่ deny_paths ไม่ครอบคลุม ส่วน allow_paths ไม่มีช่องโหว่นี้ เพราะสิ่งที่ไม่อยู่ในรายการจะไม่ถูกส่งต่อ
  • client ที่อ่านช้าจะได้รับคำตอบจนจบ client ที่ยังคงรับคำตอบอยู่ ไม่ว่าจะช้าเพียงใด จะได้รับคำตอบจนจบ
  • flood อาจตัดการอัปโหลดขนาดใหญ่ได้ WAF ทำงานบน thread เดียว flood จึงอาจตัดการอัปโหลดขนาดใหญ่ด้วย 408 ได้ แอปที่รับ body ขนาดใหญ่ให้เพิ่ม body_timeout_secs ไปพร้อมกับ max_body_bytes
คู่มือ worker มีกฎทั้งหมด รวมถึงกรณีที่การเปลี่ยนแปลง app_allow จะถูกพักไว้จนถึงการ deploy ครั้งถัดไป