Nginx phía trước, backend riêng tư phía sau VPN
Trình duyệt nói chuyện với Nginx, và Nginx mở kết nối thứ hai vào trong đường hầm. Định tuyến cả hai, xác minh cả hai chứng chỉ và giữ các chuyển hướng công khai luôn công khai.
Theo dõi một yêu cầu từ điện thoại trên tàu đến một backend chỉ tồn tại bên trong mạng riêng. Điện thoại phân giải một tên công khai, kết nối tới một địa chỉ công khai trên cổng 443 và hoàn tất bắt tay TLS bình thường. Nginx chấp nhận kết nối đó rồi mở kết nối thứ hai, hoàn toàn tách biệt, tới một địa chỉ mà nó chỉ tiếp cận được qua giao diện VPN. Không có gì thay đổi trên điện thoại, và điện thoại không bao giờ biết rằng có một đường hầm tham gia.
Theo dõi một yêu cầu qua hai kết nối
Chặng vào là một giao dịch HTTP công khai. Nó có tên công khai, chứng chỉ công khai và một đường phản hồi phải hoạt động với bất kỳ địa chỉ nào mà khách truy cập đang có tại thời điểm đó. Bất kỳ thay đổi nào làm xáo trộn đường đó đều phá hỏng toàn bộ khách truy cập cùng lúc. Đó là lý do chặng công khai là chặng bạn ít muốn thử nghiệm nhất.
Chặng ra là chặng riêng tư. Nginx trở thành ứng dụng khách, phân giải một tên nội bộ hoặc dùng địa chỉ cố định, và tiếp cận một dịch vụ vốn sẽ từ chối kết nối từ internet.
Public client -> frontend public address:443 (TLS, public name)
Frontend -> VPN private backend 10.40.0.20:8443 (TLS, private name)
Hãy giữ hai chặng tách biệt trong đầu và cấu hình sẽ trở nên hiển nhiên. Trộn chúng lại, bạn sẽ kết thúc bằng việc cố cho một ứng dụng khách công khai tham gia vào đường hầm, và phần lớn sự rối rắm của thiết kế này đến từ đó. Chặng riêng tư được phép mong manh. Chặng công khai thì không.
Trước khi xây bất cứ thứ gì trong số này, hãy xác nhận rằng việc công bố ứng dụng là có chủ đích. Một proxy hoạt động cũng là một sự phơi bày hoạt động, và mô hình xác thực phải là một quyết định chứ không phải một tác dụng phụ.
Tên của cổng vào phải sống sót qua đường hầm
Có một cái bẫy tinh vi trong việc phân giải tên. Nếu DNS riêng tư ghi đè đúng cái tên mà hồ sơ VPN dùng cho cổng vào của chính nó, thì một lần kết nối lại có thể kết thúc bằng việc cố tiếp cận cổng vào đó qua chính đường hầm mà nó phải thiết lập. Ứng dụng khách cần tên đó được phân giải trước khi đường hầm tồn tại. Khi nó chỉ được phân giải bên trong đường hầm, kết nối không bao giờ có thể được dựng lên.
Dùng các tên đã được phê duyệt riêng biệt cho cổng vào và backend, và giới hạn chính sách của trình phân giải để mỗi tên phân giải tới địa chỉ mà bên tiêu thụ của nó cần. Việc sửa tiêu đề Host của ứng dụng không giúp gì ở đây, vì ứng dụng khách VPN mới là bên thực hiện tra cứu.
Sau đó hãy nghĩ về phạm vi. Frontend cần một tuyến tới địa chỉ backend, và nó cũng phải tiếp tục trả lời các ứng dụng khách công khai. Một đường hầm đầy đủ trên máy chủ đó có thể thỏa yêu cầu thứ nhất và phá yêu cầu thứ hai.
Ở nơi chính sách cho phép, một đường hầm tới đích cụ thể là thiết kế sạch hơn. Ở nơi chính sách bắt buộc đường hầm đầy đủ, frontend thuộc về một bối cảnh mạng riêng thay vì bị vá bằng các tuyến tạm thời cho những khách truy cập đã biết.
Viết khối máy chủ sao cho cả hai chặng đều xác minh
Giả sử backend trình bày một chứng chỉ cho tên này: backend.corp.example và trả lời trên một địa chỉ riêng tư. Kết nối tới một địa chỉ trong khi xác minh một tên chính là mục đích của các tùy chọn SNI và tên của mô-đun proxy.
upstream private_app {
server 10.40.0.20:8443;
}
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /etc/nginx/tls/app-fullchain.pem;
ssl_certificate_key /etc/nginx/tls/app-key.pem;
location / {
proxy_pass https://private_app;
proxy_ssl_server_name on;
proxy_ssl_name backend.corp.example;
proxy_ssl_trusted_certificate /etc/nginx/trust/backend-ca.pem;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_set_header Host app.example.com;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
Mọi đường dẫn và địa chỉ ở trên đều là chỗ giữ chỗ. Phần quan trọng là những phần giữ cho việc xác minh luôn bật: một tên để kiểm tra, một gói tin cậy để kiểm tra dựa vào, và một độ sâu khớp với chuỗi đã được phê duyệt.
proxy_set_header Host app.example.com là có chủ đích. Backend nên được cấu hình để chấp nhận nguồn gốc công khai đó, và nó chỉ nên tin các tiêu đề được chuyển tiếp từ chính frontend này. Ghi đè X-Forwarded-For bằng địa chỉ của ứng dụng khách là đúng với một proxy và sai với một chuỗi, nên một triển khai nhiều proxy cần cách sắp xếp địa chỉ tin cậy của riêng nó.
Khiến nguồn gốc công khai cư xử như một nguồn gốc công khai
Ứng dụng nên phát ra URL tuyệt đối, chuyển hướng và cookie cho nguồn gốc công khai. Nếu nó chuyển hướng một khách truy cập tới backend.corp.example, thì khách truy cập đó bị gửi tới một tên mà họ không thể phân giải.
Hãy sửa điều đó qua thiết lập URL bên ngoài mà ứng dụng hỗ trợ và một chính sách chuyển hướng đã được rà soát, chứ không phải bằng cách viết lại tên máy chủ trong phần thân phản hồi. Việc thay thế chuỗi trên nội dung tùy ý sẽ phá vỡ các tải trọng đã ký và tạo ra những lỗi khó truy vết.
Cookie cũng đáng được chăm sóc như vậy. Một cookie giới hạn ở tên riêng tư sẽ không bao giờ được trình duyệt chỉ biết tên công khai gửi đi.
Cũng hãy xác nhận rằng Nginx thực sự có thể sở hữu socket công khai. Một dịch vụ web đang có trên 443 phải chuyển đi, hoặc frontend phải sống ở nơi khác, vì hai tiến trình không thể cùng ràng buộc một địa chỉ và cổng.
Kiểm tra từng chặng ngay tại nơi nó thực sự chạy
Hãy xác thực cấu hình bằng trình phân tích đã cài trước mọi lần tải lại, để một lỗi đánh máy không làm sập trình lắng nghe đang phục vụ.
Sau đó kiểm tra chặng backend ngay từ máy chủ frontend, trong cùng bối cảnh mạng mà Nginx dùng. Một lệnh ping xuyên đường hầm chứng minh khả năng tiếp cận và không chứng minh gì về TLS, phân quyền hay ứng dụng.
Cuối cùng hãy gửi một yêu cầu công khai mới tới tên máy chủ thật với xác minh chứng chỉ bình thường, và quan sát đường phản hồi chứ không chỉ phần thân phản hồi.
Giữ cả hai quan sát cùng với cấu hình đã triển khai, và ghi lại điều gì xảy ra khi đường hầm không khả dụng. Frontend nên trả về một lỗi upstream có kiểm soát, chứ không lặng lẽ chuyển sang một backend khác mà không ai ghi lại. Một 502 với dòng nhật ký rõ ràng là kết quả tốt. Một trang được lưu trong bộ nhớ đệm từ một nguồn gốc không mong muốn thì không phải.