---
title: "จำกัดการเข้าถึงเซิร์ฟเวอร์และวาง WAF ไว้หน้าแอปของคุณ"
description: "erpc worker จำกัดการเข้าถึงเซิร์ฟเวอร์ตั้งแต่การ deploy ครั้งแรก และตารางเดียวใน erpc.toml จะวาง WAF ไว้หน้า API สาธารณะ สิ่งที่ทำและสิ่งที่ไม่ทำ"
canonical: "https://erpc.global/th/doc/erpc-cli/worker-firewall-and-waf/"
locale: "th"
---

# จำกัดการเข้าถึงเซิร์ฟเวอร์และวาง 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]` กำหนดในตอนนี้อยู่แล้วเท่านั้น ([ด้านล่าง](https://erpc.global/th/doc/erpc-cli/worker-firewall-and-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 | สิ่งที่ทำ | สิ่งที่ได้รับเมื่อถูกปฏิเสธ |
| --- | --- | --- |
| `methods` | method ที่ส่งต่อ เขียนเป็นตัวพิมพ์ใหญ่ (สูงสุด 16 รายการ และไม่มี `CONNECT` เด็ดขาด) | 405 พร้อม `Allow` |
| `allow_paths` | path ที่ส่งต่อ หากไม่ระบุเลยหมายถึงทุก path รูปแบบหนึ่งรายการคือ path เดียว หรือทุกอย่างภายใต้ prefix เมื่อลงท้ายด้วย `*`: `/api/*` คือ `/api` และทุกอย่างที่อยู่ใต้มัน เปรียบเทียบกับ path ที่ผ่านการ percent-decode หนึ่งครั้งแล้ว โดยแยกตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ | 404 |
| `deny_paths` | path ที่ถูกปฏิเสธ เขียนแบบเดียวกัน และถูกตรวจก่อน | 403 |
| `max_body_bytes` | ขนาด body ของคำขอที่ใหญ่ที่สุด ได้สูงสุด 64 MiB | 413 |
| `max_header_bytes` | ขนาดรวมที่ใหญ่ที่สุดของ request line และ header ตั้งแต่ 1024 ถึง 65536 | 431 |
| `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 ถึง 600 | 504 ส่วน 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](https://github.com/elsoul/erpc-cli/blob/v0.1.0-rc.4/docs/worker.md) มีกฎทั้งหมด รวมถึงกรณีที่การเปลี่ยนแปลง `app_allow` จะถูกพักไว้จนถึงการ deploy ครั้งถัดไป
