The Story

Tăng bảo mật, giảm workload truyền file bằng 2 chức năng này trong NGINX

Ngày đăng : 13 tháng 1 năm 2021

Xin chào các bạn,

Tôi là Reo, hiện là kỹ sư Rails đang tham gia dự án MFCBox của Money Forward (マネーフォワード クラウドBox)

MFCBox như cái tên của nó là một dự án về microservices dành cho lưu trữ. Tuy nhiên, sau khi đã cân nhắc về vấn đề bảo mật và giảm nhẹ workload của phương thức truyền file, chúng tôi đã quyết định sử dụng 2 chức năng Signature của AWS version 4 và X-Accel-Redirect có trong NGINX.

X-Accel-Redirect

Giải thích chính thức từ NGINX:

X-accel allows for internal redirection to a location determined by a header returned from a backend. This allows you to handle authentication, logging or whatever else you please in your backend and then have NGINX handle serving the contents from the redirected location to the end user, thus freeing up the backend to handle other requests.

https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/

Việc truyền dữ liệu thông thường chiếm một lượng lớn dung lượng bộ nhớ, khiến cho lượng workload của back-end service cũng tăng theo. Nhưng với NGINX, nội dung có thể được gửi một cách ưu việt hơn, có thể vừa làm giảm lượng workload của back-end services vừa nâng cao được tốc độ truyền tin.

X-Accel-Redirect là một cơ chế sử dụng response header từ back-end servers để có thể thực hiện các thao tác về redirect nội bộ.

Thông qua việc sử dụng X-Accel-Redirect, có thể truyền dữ liệu bằng NGINX, còn phần xác thực thì thực hiện ở back-end.

Về MFCBox:

  • Chỉ truyền file của user đã được xác thực.
  • Có thể truyền file từ đa số các service nội bộ trong công ty thông qua API.

Tuỳ vào nhu cầu sau này mà có thể sử dụng X-Accel-Redirect để vừa có thể truyền file tốc độ cao vừa giảm được workload cho servers.

Chúng tôi cũng đã cân nhắc về không sử dụng Pre-signed URLs có trong S3.

Tuy Pre-signed URL có thể giới hạn truy cập của file cho người nhận URL này, nhưng MFCBox được dùng để xử lý các tài liệu bảo mật như bảng báo giá, hoá đơn…, nếu URL này bị lộ ra ngoài, một user bất kỳ nào đó đều có thể xem được thông tin mật này. Đó là lý do tại sao chúng tôi không muốn sử dụng cơ chế này. 

AWS signature version 4

Thông qua việc sử dụng X-Accel-Redirect, NGINX có thể thay back-end server để truyền file. File này có thể lưu trên S3 nên NGINX cần phải lấy dữ liệu thông qua việc truy cập S3. Tất nhiên là phải có quyền truy cập S3.

Qua đó, chúng tôi pre-sign các HTTP Requests được gửi bởi NGINX tới S3, sau đó gom các signature date vào proxy header để NGINX có thể truy cập vào được S3.

Thông qua cách này, NGINX có thể truy cập vào S3 mà không cần phải thiết lập quyền xác nhận tại NGINX.

Về signature thì dùng Signature version 4 (SigV4)

Quy trình xử lý trên thực tế là thế nào, mọi người hãy xem sơ đồ dưới nhé:

Sample code

signer = Aws::Sigv4::Signer.new(

  service: ‘s3,

  region: region,

  credentials_provider: Aws::InstanceProfileCredentials.new

)

url = "https://#{bucket_name}.s3-#{region_name}.amazonaws.com/#{key}"

signature = signer.sign_request(

  http_method: ‘GET‘,

  url: url

)

%w[host x-amz-date x-amz-security-token x-amz-content-sha256 authorization].each do

  headers[_1] = signature.headers[_1]

end

headers[‘signed-url‘] = url

headers[‘X-Accel-Redirect‘] = "/files"

# Sử dụng AWS Ruby SDK.

# https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Sigv4/Signer.html

  1. Tạo instance của Signature bằng cách truyền thông tin xác thực của S3
  2. Chuẩn bị sẵn URL và phương thức HTTP mà mình muốn ký, thông qua việc truyền instance Signature mà có thể trả về thông tin xác nhận dùng cho việc set quyền truy cập vào URL và HTTP method này.
  3. Gom các thông tin xác nhận này vào response header, rồi truyền tới NGINX.

Để xác thực thông tin, có thể dùng AWS_ACCESS_KEY_ID và AWS_SECRET_ACCESS_KEY như là các argument cho signer, nhưng vì Pod đã tiếp nhận vai trò của IAM nên không cần phải định nghĩa thông tin xác nhận AWS security vè lâu dài.

Tham khảo thêm tại đây.

Thay vì thế, chúng ta cần hiển thị x-amz-security-token xác thực tạm thời thông tin trên request header.

Và việc hiển thị x-amz-security-token trên thông tin xác nhận tạm thời tại Request Header lại trở nên cần thiết.

Chi tiết hơn tại mục Note thứ 2 trên trang AWS

Nếu loại bỏ đi các phần cần thiết trong thiết lập của NGINX thì…

location = /files {

  internal;

  set $x_amz_date $upstream_http_x_amz_date;

  set $x_amz_security_token $upstream_http_x_amz_security_token;

  set $x_amz_content_sha256 $upstream_http_x_amz_content_sha256;

  set $authorization $upstream_http_authorization;

  set $signed_url $upstream_http_signed_url;

  proxy_set_header x-amz-date $x_amz_date;

  proxy_set_header x-amz-security-token $x_amz_security_token;

  proxy_set_header x-amz-content-sha256 $x_amz_content_sha256;

  proxy_set_header Authorization $authorization;

  proxy_pass $signed_url;

}

Như kết quả của đoạn code trên, khi lấy proxy giá trị của header trong NGINX thì phải thiết lập lại.

Ngoài ra, nếu mình để sẵn cái thiết lập của internal như ở dòng code thứ 2, thì request được nhận chỉ ở trường hợp redirect nội bộ. 

Lời kết 

Topic này có thể hơi không phổ biến, nhưng nếu các bạn có thể tìm thấy gì đó tham khảo được thì tốt.

Và như các bạn cũng biết, việc tự trang bị sẵn kiến thức cho mình là đều rất quan trọng.

Tôi đã hoàn toàn không biết là có sự tồn tại một loại kỹ thuật mang X-Accel-Redirect khi mới bắt đầu tham gia dự án PJ, cho đến khi các requirement được hoàn tất thì tôi mới mang vấn đề này bàn với team Infra, thì được team đưa ra đề xuất này. Lúc đó tôi cũng chưa quyết định sử dụng kỹ thuật này ngay lập tức, mà chỉ quyết định sau khi đã bàn bạc xong về các vấn đề như rủi ro bảo mật và performance như đã đề cập trong bài viết này.

Ở Money Forward, chúng tôi đa phần làm việc với quy mô là các team nhỏ, nhưng rất tích cực trong việc chia sẻ kiến thức với các team nhau, tôi nghĩ tại đây chúng tôi có một môi trường được tạo điều kiện rất tốt cho việc trao đổi ý kiến. 

Author: Reo Kuriyama

Translator: Michael