Tìm hiểu thuật toán chọn Server và Location của Nginx và một số thủ thật hay dùng cho security
Giới thiệu
Nginx
là một trong những web server nổi tiếng trên thế giới, nó có thể handle thành công nhiều kết nối cùng một lúc, đòng thời có thể dễ dàng hoạt động như một máy chủ độc lập, một mail server hay proxy server. Trong hướng dẫn dưới đây chúng ta có thể thảo luận thêm về việc Nginx
xử lý những request như thế nào, điều này giúp bạn dễ dàng phỏng đoán cũng như định hướng được yêu cầu sẽ được xử lý tại đâu.
Định nghĩa một Nginx
block
Nginx
phân chia các config thành các block khác nhau được tạo trong cùng một cấp, mỗi block sẽ được bắt đầu bởi:
server {
# config here
.....
}
Mỗi khi có request từ client gửi đến, Nginx
sẽ phân tích và xác định được block nào sẽ được sử dụng để xử lý yêu cầu đó. Và quá trình quyết định block nào xử lý chính là điều sẽ được thảo luận dưới đây.
Một block server là tập hợp con của file Nginx
config ở đó định nghĩa những virtual server được sử dụng để xử lý những yêu cầu thuộc một loại đã được xác định. Thông thường khi config server thì sẽ cần định nghĩa một vài server block, và quyết định block nào xử lý request nào dựa trên domain name, port hoặc IP của request.
Trong 1 server block lại có chứa nhiều location block, được xử dụng để xác định resource nào sẽ sử dụng cho request, và hoàn toàn có thể chia nhỏ theo cách mình mong muốn.
Cách Nginx
xác định server block nào sẽ handle request
Nginx
thực hiện điều này thông qua một hệ thống kiểm tra để quyết định ra được block nào phù hợp nhất được sử dụng bằng cách sử dụng 2 directive là listen
và server_name
, thứ tự ưu tiên là listen
rồi đến server_name
Directives: listen
Directive listen
có thể được set bởi các options sau:
- Một bộ IP / port
- Một mình IP address, khi đó sẽ được nghe dưới port mặc định là 80
- Một mình port number, khi đó nó sẽ lắng nghe được mọi interface trên cổng đó
- Cuối cùng có thể 1 path dẫn đến Unix socket
Tuỳ chọn cuối cùng thường được sử dụng để chuyển giữa yêu cầu giữa các servers khác nhau.
Khi cố gắng để xác định server block nào sẽ xử lý request, trước tiên Nginx sẽ cố gắng quyết định dựa trên tính đặc thù của directive listen
bằng các quy tắc sau:
- Một block không định nghĩa directive
listen
thì sẽ được gán với giá trị mặc định là0.0.0.0:80
. - Một block được định nghĩa listen only IP address
1.1.1.1
sẽ được gán port mặc định1.1.1.1::80
- Một block chỉ có port mà không có IP address
8080
sẽ được gán IP mặc định lắng nghe tất cả0.0.0.0:8080
Dựa vào những quy tắc trên Nginx
sẽ xác định được server block nào xử lý, nếu có nhiều server block matching thì nó sẽ dùng đến directive thứ 2 đẻ định nghĩa chính là server_name
.
Directives: server_name
Sau khi đã lọc được những block với directive listen
, Nginx
sẽ tiếp tục tìm đến server_name
để lấy được chính xác block nào xử lý dựa theo các nguyên tắc sau:
- Trước tiên, Nginx sẽ cố gắng tìm một server block có
server_name
trùng hoàn toàn với giá trị trongHost
của request, nếu có nhiều kết quả được tìm thấy thì nó sẽ lấy kết quả đầu tiên - Nếu không tìm được kết quả chính xác,
Nginx
sẽ tìm server block cóserver_name
khớp với ký tự đại diện trên đầu (leading wildcard) (được biểu thị bằng dấu * ở đầu trongserver_name
). Nếu nhiều block matches thì nó sẽ lấy block nào match với số kí tự nhiều nhất - Nếu với leading wildcard mà không tìm thấy kết quả phù hợp, nó sẽ sử dụng matches với trailing wildcard (kí tự * được đặt ở cuối
server_name
). Tương tự như leading wildcard => Nếu nhiều block matches sẽ lấy block matches với số kí tự nhiều nhất - Nếu như vẫn không matches thì
Nginx
sẽ xác định block bằng các biểu thức regex (nhữngserver_name
có~
ở đầu.)server_name
đầu tiên match regex vớiHost
của request sẽ được sử dụng. - Cuối cùng mà vẫn không tìm thấy được thì nó sẽ đẩy request vào block mặc định cho request đó.
Ví dụ
server {
listen 80;
server_name *.host.com;
...
}
server {
listen 80;
server_name ngocnt.host.com;
...
}
=> Khi chúng request bởi ngocnt.host.com
=> Thì block thứ 2 sẽ được lựa chọn.
server {
listen 80;
server_name www.host.*;
...
}
server {
listen 80;
server_name *.host.vn;
...
}
server {
listen 80;
server_name *.vn;
...
}
=> Khi chúng ta request www.host.vn
=> block thứ 2 cũng sẽ được lựa chọn.
server {
listen 80;
server_name ngocnt.host.com;
...
}
server {
listen 80;
server_name host.com;
...
}
server {
listen 80;
server_name www.host.*;
...
}
=> Khi chúng ra truy cập www.host.com
=> block thứ 3 sẽ được chọn.
server {
listen 80;
server_name host.com;
...
}
server {
listen 80;
server_name ~^(www|ngocnt).*\.host\.com$;
...
}
server {
listen 80;
server_name ~^(subdomain|www|ngocnt).*\.host\.com$;
...
}
=> Khi chúng ra truy cập www.host.com
=> block thứ 2 sẽ được chọn.
Blocks: location
Một location block sẽ được đặt trong server block với cú pháp
location optional_modifier location_match {
...
}
Trong đó các optional_modifier
sẽ có các lựa chọn sau:
- Nếu không có gì => Nó sẽ lấy phần đầu của request để matches
=
block này sẽ được coi là match nếu URI request match chính xác với location này~
block này được coi là match nếu URI request match regex với location này có phân biệt chữ hoa, chữ thường.~*
block này được coi là match nếu URI request match regex với location này không phân biệt chữ hoa, chữ thường.
Tương tự như bên block server, nó sẽ tìm những block với matches chính xác trước sau đó sẽ lựa chọn những regex matches để tìm được block phù hợp.
Dưới đây sẽ là một vài Cheat Sheet hay sử dụng liên quan đến security
Proxy Pass + Rewrite
location ~ <expr> {
rewrite /<path to strip>/(.*) /$1 break;
proxy_pass http://127.0.0.1:8080;
}
Proxy Pass + Host Header
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
}
Ẩn nginx version
serverserver_tokens: off;
Ẩn nginx server signature
more_set_headers "Server: Unknown";
** Keep only TLS 1.2 ( TLS 1.3 )**
ssl_protocols TLSv1.2
Force all connection over TLS
return 301 https //$host$request_uri
Cross-Site Scripting (XSS) header
add_header X-Xss-Protection "1; mode=block" always;
All rights reserved