FDE PulseViệc làm FDE đang mở 316Mới đăng 7 ngày qua 10Chủ đề nổi bật: Đào tạo kỹ năng FDE tại Đông Nam Á

Tờ báo của nghề Forward Deployed Engineer

Bách khoa

Demo chạy tốt ở nhà nhưng hỏng trong mạng khách: cách dò lỗi từ DNS, qua proxy, đến CORS và cookie

Cùng một đoạn code, nhưng mạng của ngân hàng hay nhà máy có thể trả về 407, 504 hoặc một dòng CORS đỏ trong console, và FDE nào biết lỗi nằm ở tầng nào thì sửa xong trước.

Đồ hoạBốn tầng cần dò khi tích hợp trong mạng khách
  1. 11. DNS: tên miền ra đâu?Chạy nslookup từ chính máy trong mạng khách, không dùng laptop của bạn
  2. 22. Kết nối: ra được không?HTTPS qua proxy phải mở tunnel bằng CONNECT; có proxy chỉ cho phép cổng 443
  3. 33. Mã HTTP: ai đang trả lời?407 do proxy trả, 401 do ứng dụng trả; 502 và 504 do gateway ở giữa trả
  4. 44. Trình duyệt: CORS, cookieXem console và tab Network; preflight không mang cookie, cần SameSite=None kèm Secure

Đi đúng thứ tự từ tên miền đến trình duyệt để biết request bị chặn ở đâu, trước khi đụng vào code.

Đồ hoạ: FDE Times

Tóm tắt nhanh

  • Hỏi theo thứ tự bốn tầng: tên miền phân giải ra đâu, kết nối có ra được không, ai trả mã HTTP, trình duyệt có cho phép không.
  • 407 do proxy trả về, 401 do ứng dụng trả về; 502 và 504 nghĩa là một gateway ở giữa không nhận được phản hồi đúng hoặc kịp lúc.
  • CORS do trình duyệt thực thi, nên curl chạy được chưa chứng minh gì; preflight OPTIONS không mang cookie và có thể bị một lớp bảo mật đòi cookie chặn lại.
Chia sẻLinkedInFacebookX

Tuần đầu ở site khách, bản tích hợp vốn chạy trơn tru trên laptop của bạn lần lượt trả về 407, rồi 504, rồi một dòng đỏ chữ “CORS” trong console. Code không đổi dòng nào. Cái thay đổi là mạng.

FDE gặp cảnh này thường xuyên, vì bạn không deploy vào một cloud sạch sẽ do mình làm chủ mà vào mạng của người khác. Ở đó có proxy doanh nghiệp, firewall, reverse proxy và cookie SSO, mỗi lớp lại do một đội khác quản lý. Người gỡ nút thắt nhanh thường không phải người thuộc nhiều mẹo.

Đó là người nói được lỗi nằm ở tầng nào trước khi bắt tay vào sửa.

Một request đi qua tay bao nhiêu bên?

HTTP là giao thức client-server: request do user-agent gửi đi, hoặc do một proxy gửi thay. Tài liệu MDN mô tả rằng giữa client và server có rất nhiều máy trung gian, gọi chung là proxy, làm nhiệm vụ gateway hoặc cache. Trước khi request đi được bước nào, DNS, hệ thống đặt tên phân cấp và phi tập trung, phải đổi tên miền thành địa chỉ.

Khi đọc sơ đồ mạng của khách, việc đầu tiên là tách hai loại proxy. Forward proxy đứng phía client và che danh tính của client, đó là proxy mà nhân viên ngân hàng đi qua để ra Internet. Reverse proxy đứng phía server và che danh tính của server, đó là lớp nằm trước API của bạn hoặc của chính khách.

Từ đó, bạn có một mô hình bốn tầng với bốn câu hỏi đi theo đúng thứ tự: tên miền có phân giải ra đúng chỗ từ bên trong mạng khách không, kết nối có ra được hay bị proxy và firewall chặn, mã HTTP trả về là gì và do ai trả.

Câu hỏi cuối dành cho trình duyệt: nó có cho trang đọc response và gửi cookie đi không?

Mã trạng thái cho biết ai đang trả lời bạn

Sai lầm hay gặp nhất là coi mọi mã lỗi đều do backend trả về. Mã 407 trông giống 401, nhưng nghĩa là việc xác thực phải làm với proxy. Gặp 407 tức là proxy doanh nghiệp đòi đăng nhập, và ticket này phải gửi cho đội mạng chứ không phải đội ứng dụng.

Hai mã 502 và 504 cũng nói về tầng giữa: máy trả lời bạn đang làm gateway hoặc proxy, và nó nhận được phản hồi sai, hoặc không nhận kịp phản hồi, từ upstream. Cloudflare còn dùng thêm mã 520, một mã không có trong chuẩn.

Thấy 504, bạn nên đo xem timeout của gateway là bao lâu và API của mình mất bao lâu, rồi mới nghĩ đến chuyện tối ưu query.

Với HTTPS, client đứng sau một HTTP proxy phải mở tunnel bằng phương thức CONNECT. Không phải proxy nào cũng hỗ trợ CONNECT, và có proxy chỉ cho phép cổng 443.

Tầng kết nối còn một cái bẫy nữa: firewall và các middlebox nói chung rất khó cập nhật, có cái được cấu hình để chặn mọi lưu lượng mang extension lạ. QUIC và HTTP/3 chạy trên UDP vì thế có thể bị chặn trong mạng khách, nên client của bạn luôn cần đường lùi về HTTP qua TCP.

Triệu chứng Tầng nghi ngờ Kiểm tra đầu tiên
Tên miền không phân giải, hoặc trỏ sai IP Tầng 1: DNS Chạy nslookup từ chính máy trong mạng khách
CONNECT bị từ chối Tầng 2: kết nối qua proxy, firewall Cổng đích có phải 443 không, host đã nằm trong allowlist chưa
407 Tầng 3: mã HTTP, do forward proxy trả Hỏi đội mạng về cách xác thực với proxy
502, 504, 520 Tầng 3: mã HTTP, do gateway hoặc reverse proxy trả Timeout của gateway so với thời gian API xử lý
Lỗi CORS, cookie không được gửi Tầng 4: trình duyệt Console và tab Network trong DevTools: response của preflight, header Cookie, thuộc tính cookie (curl không tái hiện được tầng này)

Ví dụ: widget nhúng vào portal của một ngân hàng

Thử hình dung một trường hợp. Khách là một ngân hàng, có portal nội bộ tại portal.khachhang.vn. Bạn nhúng một widget từ app.congty-ban.com, và widget gọi API api.congty-ban.com bằng cookie phiên. Trên laptop mọi thứ đều chạy, nhưng ở máy nhân viên ngân hàng thì widget trắng trơn.

Hãy ngồi vào máy trong mạng khách và đi qua từng tầng bằng ba lệnh. Vì widget gửi POST với body JSON, lệnh giả lập preflight phải khai cả header Content-Type, giống hệt những gì trình duyệt sẽ gửi:

# 1. Từ trong mạng khách, tên miền phân giải ra đâu?
nslookup api.congty-ban.com

# 2. Đi qua proxy doanh nghiệp: xem dòng CONNECT và mã proxy trả về
curl -v -x http://proxy.khachhang.vn:8080 https://api.congty-ban.com/health

# 3. Giả lập preflight giống hệt trình duyệt
curl -v -X OPTIONS https://api.congty-ban.com/v1/data \
  -H "Origin: https://portal.khachhang.vn" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type"

Nếu lệnh 2 trả về 407, bạn đã biết cần nói chuyện với ai. Nếu lệnh 2 chạy được mà lệnh 3 nhận 403 từ một trang lỗi của firewall, bạn đã tìm ra thủ phạm.

MDN nhấn mạnh rằng preflight không bao giờ được mang theo credentials, vì vậy một lớp bảo mật đòi cookie ở mọi request sẽ chặn luôn OPTIONS. Khi đó trình duyệt báo lỗi CORS, dù backend của bạn không có lỗi gì.

Giả sử chẩn đoán dừng ở đúng chỗ đó, và tab Application còn cho thấy cookie phiên không khai SameSite. Gốc rễ khi ấy nằm ở hai nơi ngoài code: firewall chặn preflight vì thiếu cookie, còn cookie bị xử lý như Lax nên không đi theo request từ iframe.

Cách sửa là nhờ đội mạng cho OPTIONS tới api.congty-ban.com đi qua mà không đòi cookie, và đặt lại cookie với SameSite=None; Secure như ở phần dưới.

Bước cuối là xác nhận trên chính máy nhân viên ngân hàng, bằng trình duyệt chứ không phải curl. Trong tab Network, preflight OPTIONS phải trả về thành công kèm các header Access-Control-Allow-*, request POST thật phải mang header Cookie, và console không còn dòng đỏ nào.

CORS hoạt động bằng các HTTP header, qua đó server khai báo những origin nào được phép đọc dữ liệu từ trình duyệt. Còn HTTP thì vốn không giữ trạng thái, và cookie là thứ tạo ra phiên có trạng thái. Vấn đề là khi gọi fetch hoặc XHR sang origin khác, mặc định trình duyệt không gửi credentials, tức là không gửi cookie.

Cho widget ở ví dụ trên, phía client phải bật credentials một cách tường minh:

fetch('https://api.congty-ban.com/v1/data', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});

Phía server, response phải có Access-Control-Allow-Credentials: true thì request thật mới dùng được cookie. Nên ghi rõ origin của portal thay vì để rộng cửa:

Access-Control-Allow-Origin: https://portal.khachhang.vn
Access-Control-Allow-Credentials: true
Set-Cookie: session=abc123; SameSite=None; Secure; Domain=congty-ban.com

Dòng Set-Cookie là phần hay bị bỏ qua. Cookie không khai SameSite sẽ được xử lý như Lax, và trong ngữ cảnh iframe cross-site như portal ngân hàng, đó thường chính là lý do phiên “biến mất”. Muốn dùng SameSite=None thì bắt buộc phải kèm Secure.

Thuộc tính Domain quyết định server nào nhận cookie: đặt congty-ban.com thì cookie đến được cả server đó lẫn các subdomain như api và app.

Những lỗi khiến bạn mất một tuần

Lỗi đầu tiên là tin vào header X-Forwarded-For. Khi API nằm sau reverse proxy của khách, bạn sẽ muốn lấy IP thật từ header này để rate limit hoặc làm ACL. MDN nói rõ rằng mọi cách dùng liên quan đến bảo mật chỉ được dựa vào những IP do proxy đáng tin thêm vào.

Nếu lấy giá trị đầu tiên một cách ngây thơ, kẻ gửi header giả có thể vượt qua giới hạn của bạn.

Lỗi thứ hai là xin “mở firewall” một cách mơ hồ. Đội mạng của ngân hàng cần biết chính xác host, cổng, giao thức, và cả việc phương thức OPTIONS phải đi qua được. Một yêu cầu viết theo bảng bốn tầng ở trên sẽ được duyệt nhanh hơn nhiều so với câu “API bị lỗi, nhờ anh xem giúp”.

Lỗi thứ ba là thấy 504 thì sửa backend. Hãy xác định trước máy nào đang trả mã đó. Lỗi cuối cùng là mặc định client chạy được HTTP/3, trong khi firewall của khách có thể được cấu hình chặn lưu lượng lạ, và QUIC chạy trên UDP hoàn toàn có thể nằm trong số đó.

Với developer Việt Nam muốn chuyển sang làm FDE, đây là kỹ năng dễ chứng minh. Trong CV, đừng ghi chung chung “xử lý sự cố mạng”. Hãy kể một sự cố theo từng tầng: triệu chứng là gì, bạn khoanh vùng nó ở DNS, proxy, mã HTTP hay trình duyệt, và đã sửa ra sao.

Khi đọc JD có những cụm như “enterprise network”, “on-premise” hay “customer environment”, bạn cũng nên chuẩn bị sẵn một câu chuyện như thế cho vòng phỏng vấn.

Lần tới widget trắng trơn ở site khách, đừng mở code ra trước. Hãy mở terminal, đi lần lượt qua bốn tầng, rồi mang theo bằng chứng đến đúng bàn của đội cần xử lý.

9 nguồn
Đọc tiếp trên lộ trình · Chặng 2: Kỹ thuật rộngDebug máy chủ Linux của khách qua bastion: một dòng ssh -J và ba lệnh đầu tiên cần gõỞ site khách, máy chủ đang lỗi thường không có dashboard và không cho cài thêm công cụ. Thứ bạn chắc chắn có chỉ là một bastion, một shell và vài phút trước khi có người hỏi bạn đã tìm ra nguyên nhân chưa.