การจำกัดให้ Web หรือ API เข้าใช้งานได้เฉพาะ IP ที่กำหนดเป็นวิธีควบคุมการเข้าถึงที่ใช้ได้ดีสำหรับ Internal API, Admin API, Webhook Endpoint, Monitoring Dashboard และระบบหลังบ้าน แต่เมื่อระบบอยู่หลัง Reverse Proxy, Load Balancer หรือ Kubernetes Ingress การตรวจ IP จะซับซ้อนขึ้น เพราะ Application อาจมองเห็น IP ของ Proxy แทน IP จริงของผู้ใช้

Workshop นี้จะแสดงการคอนฟิก NGINX Reverse Proxy + Trusted Proxy + IP Allowlisting พร้อม Docker Compose และการทดสอบด้วย curl

เป้าหมายสำคัญคือแยกให้ออกว่า Trusted Proxy ใช้กำหนดว่าเราจะเชื่อข้อมูล Client IP จาก Proxy ใด ส่วน IP Allowlist ใช้ตัดสินว่า Client IP ใดได้รับอนุญาตให้เข้าระบบ


#1. Architecture ของ Workshop

Client
  |
  | HTTP Request
  v
+------------------------+
| NGINX Reverse Proxy    |
| - Trusted Proxy        |
| - Resolve Client IP    |
| - IP Allowlist         |
+------------------------+
          |
          | Allowed only
          v
+------------------------+
| Demo API               |
| Python HTTP Server     |
+------------------------+

ในระบบ Production อาจมีหลายชั้น เช่น

Internet
   |
   v
Cloudflare / WAF
   |
   v
Load Balancer
   |
   v
NGINX / Ingress
   |
   v
Application / API

ยิ่งมี Proxy หลายชั้น ยิ่งต้องกำหนด Trust Boundary ให้ชัดเจน


#2. Trusted Proxy กับ IP Whitelisting ต่างกันอย่างไร

#Trusted Proxy

Trusted Proxy คือการกำหนดว่า Proxy หรือ Load Balancer ใดได้รับความเชื่อถือให้ส่งข้อมูล Client IP ผ่าน Header เช่น

X-Forwarded-For: 203.0.113.10
X-Forwarded-Proto: https
X-Forwarded-Host: api.example.com

หรือมาตรฐาน

Forwarded: for=203.0.113.10;proto=https

เราไม่ควรเชื่อ X-Forwarded-For จากทุก Request เพราะ Client สามารถสร้าง Header นี้เองได้

curl -H "X-Forwarded-For: 192.168.1.100" \
     https://api.example.com

#IP Whitelisting / Allowlisting

IP Allowlisting คือการอนุญาตเฉพาะ IP หรือ Network ที่กำหนด เช่น

203.0.113.10
198.51.100.0/24

Request จาก IP อื่นจะถูกปฏิเสธ เช่นด้วย HTTP

403 Forbidden

#3. เตรียม Project

สร้างโฟลเดอร์

mkdir trusted-proxy-lab
cd trusted-proxy-lab

โครงสร้างไฟล์:

trusted-proxy-lab/
├── docker-compose.yml
├── nginx/
│   └── default.conf
└── app/
    └── index.html

สร้าง directory:

mkdir nginx app

#4. สร้าง Demo Application

สร้างไฟล์

app/index.html

ใส่เนื้อหา:

<!DOCTYPE html>
<html>
<head>
    <title>Trusted Proxy Lab</title>
</head>
<body>
    <h1>Trusted Proxy / IP Allowlist</h1>
    <p>Request reached the backend successfully.</p>
</body>
</html>

Backend ใน Workshop ใช้ NGINX อีก container หนึ่งเพื่อให้ทดลองระบบได้ง่าย


#5. สร้าง Docker Compose

สร้าง docker-compose.yml

services:

  proxy:
    image: nginx:alpine
    container_name: trusted-proxy
    ports:
      - "8080:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app
    networks:
      - proxy-net

  app:
    image: nginx:alpine
    container_name: demo-api
    volumes:
      - ./app:/usr/share/nginx/html:ro
    networks:
      - proxy-net

networks:
  proxy-net:
    driver: bridge

Architecture:

Browser / curl
      |
      | :8080
      v
+-------------+
| proxy       |
| NGINX       |
+-------------+
      |
      v
+-------------+
| app         |
| NGINX       |
+-------------+

#6. คอนฟิก Reverse Proxy

สร้าง

nginx/default.conf

เริ่มต้นด้วย:

server {
    listen 80;

    location / {

        proxy_pass http://app:80;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        proxy_set_header X-Forwarded-For
                         $proxy_add_x_forwarded_for;

        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

เริ่มระบบ:

docker compose up -d

ตรวจสอบ:

docker compose ps

ทดลอง:

curl http://localhost:8080

ควรได้รับ HTML จาก backend


#7. เพิ่ม IP Allowlist

แก้ nginx/default.conf

server {
    listen 80;

    location / {

        allow 127.0.0.1;
        allow 192.168.1.0/24;

        deny all;

        proxy_pass http://app:80;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        proxy_set_header X-Forwarded-For
                         $proxy_add_x_forwarded_for;

        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

หลักการทำงานคือ

Request
   |
   v
Source IP
   |
   +---- อยู่ใน Allowlist ----> Proxy ไป Backend
   |
   +---- ไม่อยู่ -------------> 403 Forbidden

หลังแก้ config:

docker compose restart proxy

หรือ reload:

docker exec trusted-proxy nginx -s reload

#8. ตรวจสอบ Configuration

ตรวจ syntax ก่อน reload:

docker exec trusted-proxy nginx -t

ถ้าถูกต้องจะได้ข้อความลักษณะ

syntax is ok
test is successful

#9. ทดสอบ IP Whitelisting

ทดลอง:

curl -i http://localhost:8080

ถ้า IP ไม่อยู่ใน Allowlist จะได้รับ

HTTP/1.1 403 Forbidden

หากอยู่ใน Allowlist จะได้รับ

HTTP/1.1 200 OK

#10. ปัญหาเมื่อมี Proxy อีกชั้น

สมมติ Production Architecture:

Client
203.0.113.10
      |
      v
Load Balancer
10.0.0.5
      |
      v
NGINX
10.0.0.10
      |
      v
API

NGINX อาจเห็น

10.0.0.5

เป็น $remote_addr

ทั้งที่ Client จริงคือ

203.0.113.10

Load Balancer จึงมักส่ง

X-Forwarded-For: 203.0.113.10

แต่ NGINX ไม่ควรเชื่อ Header นี้โดยไม่มีการกำหนด Trusted Proxy


#11. กำหนด Trusted Proxy ใน NGINX

ตัวอย่าง:

set_real_ip_from 10.0.0.5;

real_ip_header X-Forwarded-For;
real_ip_recursive on;

ถ้า Load Balancer อยู่ใน subnet:

set_real_ip_from 10.0.0.0/24;

real_ip_header X-Forwarded-For;
real_ip_recursive on;

จากนั้น NGINX สามารถ resolve Client IP ตาม trust chain ที่กำหนด

ตัวอย่าง configuration:

server {

    listen 80;

    set_real_ip_from 10.0.0.0/24;

    real_ip_header X-Forwarded-For;
    real_ip_recursive on;

    location /api/ {

        allow 203.0.113.10;
        allow 198.51.100.0/24;

        deny all;

        proxy_pass http://app:80;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        proxy_set_header X-Forwarded-For
                         $proxy_add_x_forwarded_for;

        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Production ควรกำหนดเฉพาะ IP/CIDR ของ Proxy ที่เชื่อถือได้จริง ไม่ควรกำหนด network กว้างเกินความจำเป็น


#12. ทำไมไม่ควรเชื่อ X-Forwarded-For โดยตรง

สมมติ Application ใช้ logic:

client_ip = request.headers["X-Forwarded-For"]

ผู้โจมตีอาจส่ง

curl \
  -H "X-Forwarded-For: 203.0.113.10" \
  https://api.example.com

ถ้า Application เชื่อ Header โดยตรง ระบบ IP Allowlist อาจถูก bypass ได้

หลักการที่ถูกต้องคือ

Request
   |
   v
ตรวจ Source/Peer IP
   |
   v
มาจาก Trusted Proxy ?
   |
   +---- No ----> ไม่เชื่อ Forwarded Header
   |
   +---- Yes
          |
          v
   Resolve Client IP
          |
          v
      IP Allowlist

#13. Logging เพื่อ Debug Client IP

เพิ่ม log format:

log_format proxy_debug
    '$remote_addr '
    'xff="$http_x_forwarded_for" '
    'realip="$realip_remote_addr" '
    '"$request" $status';

access_log /var/log/nginx/access.log proxy_debug;

ดู log:

docker logs -f trusted-proxy

หรือ

docker exec trusted-proxy \
    tail -f /var/log/nginx/access.log

ข้อมูลเหล่านี้ช่วยตรวจสอบว่า NGINX กำลังใช้ IP ใดในการตัดสินใจ


#14. จำกัดเฉพาะ Admin API

ไม่จำเป็นต้อง Allowlist ทั้งระบบ

ตัวอย่าง:

server {

    listen 80;

    location / {
        proxy_pass http://app:80;
    }

    location /admin/ {

        allow 203.0.113.10;
        allow 198.51.100.0/24;

        deny all;

        proxy_pass http://app:80;
    }
}

ผลลัพธ์:

/                 Public
/api/              Public
/admin/            IP Restricted

แนวทางนี้เหมาะกับ

  • Admin Dashboard
  • Internal API
  • Database Management UI
  • Prometheus
  • Grafana
  • CI/CD Dashboard
  • Internal Webhook
  • Operations Endpoint

#15. IP Allowlist ไม่ควรแทน Authentication

Architecture ที่แข็งแรงควรเป็น:

Internet
   |
   v
WAF / Firewall
   |
   v
Load Balancer
   |
   v
Trusted Proxy
   |
   v
IP Allowlist
   |
   v
Authentication
   |
   v
Authorization
   |
   v
Application

IP Allowlist เป็นเพียงหนึ่ง security control ไม่ควรใช้แทน

JWT
OAuth 2.0
API Key
mTLS
Session Authentication
RBAC

#16. ตัวอย่าง Production Configuration

server {

    listen 443 ssl;
    server_name api.example.com;

    # Trusted Load Balancer
    set_real_ip_from 10.10.0.0/24;

    real_ip_header X-Forwarded-For;
    real_ip_recursive on;

    location /internal-api/ {

        # Office
        allow 203.0.113.10;

        # VPN Network
        allow 198.51.100.0/24;

        deny all;

        proxy_pass http://app:8000;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        proxy_set_header X-Forwarded-For
                         $proxy_add_x_forwarded_for;

        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

ควรเปลี่ยน IP/CIDR ตัวอย่างทั้งหมดให้ตรงกับ Infrastructure จริงก่อนใช้งาน


#17. Kubernetes และ Cloud Environment

เมื่อใช้ Kubernetes เส้นทาง Request อาจเป็น

Client
   |
   v
Cloud Load Balancer
   |
   v
Ingress Controller
   |
   v
Service
   |
   v
Pod

จึงต้องพิจารณาพร้อมกันทั้ง

  • Cloud Load Balancer
  • Ingress Controller
  • Service
  • externalTrafficPolicy
  • Forwarded Headers
  • Application Framework
  • Trusted Proxy Configuration

หากกำหนดผิด Application อาจเห็น IP ของ Ingress หรือ Node แทน Client IP


#18. Checklist ก่อนขึ้น Production

ตรวจสอบอย่างน้อยดังนี้:

  • [ ] ระบุ Proxy/Load Balancer ทุกชั้นใน request path แล้ว
  • [ ] กำหนด Trusted Proxy ด้วย IP/CIDR ที่เฉพาะเจาะจง
  • [ ] ไม่เชื่อ X-Forwarded-For จาก Internet โดยตรง
  • [ ] ตรวจสอบ Client IP ที่ NGINX resolve ได้จริง
  • [ ] ใช้ CIDR อย่างถูกต้อง
  • [ ] ใช้ deny all หลังรายการ allow
  • [ ] จำกัด Allowlist เฉพาะ endpoint ที่จำเป็น
  • [ ] เปิด Access Log สำหรับตรวจสอบ
  • [ ] ทดสอบทั้ง IP ที่อนุญาตและไม่อนุญาต
  • [ ] ทดสอบ spoofed X-Forwarded-For
  • [ ] ใช้ Authentication และ Authorization เพิ่มเติม
  • [ ] จำกัด Network/Firewall/Security Group อีกชั้นเมื่อทำได้

#19. สรุป

Trusted Proxy และ IP Allowlisting เป็นคนละกลไก แต่ทำงานร่วมกันอย่างใกล้ชิด

Trusted Proxy
     |
     v
"เราจะเชื่อใครว่าเป็นผู้บอก Client IP"
     |
     v
Resolve Real Client IP
     |
     v
IP Allowlist
     |
     v
"Client IP นี้ได้รับอนุญาตหรือไม่"

หัวใจสำคัญคือ อย่าใช้ Forwarded Header เป็นข้อมูลสำหรับ Authorization จนกว่าจะกำหนด Trust Boundary ของ Proxy อย่างถูกต้อง

สำหรับ Production แนวทางที่เหมาะสมคือใช้หลายชั้นร่วมกัน:

Firewall / WAF
      +
Trusted Proxy
      +
IP Allowlist
      +
Authentication
      +
Authorization

แนวทางนี้ช่วยลด Attack Surface และเหมาะสำหรับการป้องกัน Internal API หรือ Endpoint ที่ไม่ควรถูกเปิดให้ Internet เข้าถึงโดยตรง