Upload File lên AWS S3 và Phân Quyền Public/Private An Toàn (2026)
Tháng trước mình review hệ thống của một startup và phát hiện bucket S3 chứa toàn bộ hợp đồng khách hàng đang để Public — ai có link là đọc được, không cần đăng nhập. Lý do? Dev mới cấu hình sai Block Public Access vì nghĩ "chỉ cần set ACL là xong". S3 có nhiều lớp kiểm soát quyền truy cập chồng lên nhau, và nếu không hiểu rõ từng lớp, sai lầm xảy ra rất tự nhiên.
S3 Permissions Hoạt Động Như Thế Nào?
Hãy hình dung S3 như một tòa nhà văn phòng: Bucket là cả tòa nhà, Object là từng phòng bên trong. Bạn có thể kiểm soát ai được vào tòa nhà (Bucket Policy), ai được mở từng phòng (Object ACL), và có thể khóa toàn bộ cửa chính không cho ai vào dù có chìa khóa (Block Public Access).
AWS S3 có 4 lớp kiểm soát quyền truy cập:
- Block Public Access: Lớp bảo vệ cấp account/bucket — override mọi thứ bên dưới. Đây là "khóa master" an toàn nhất.
- Bucket Policy: JSON policy gắn vào bucket, kiểm soát quyền theo IP, VPC, điều kiện phức tạp.
- ACL (Access Control List): Quyền cấp object hoặc bucket, đang dần bị deprecate — AWS khuyến nghị dùng Bucket Policy thay thế.
- IAM Policy: Gắn vào User/Role, kiểm soát ai được làm gì với S3 từ phía AWS identity.
Hiểu 4 lớp này là chìa khóa để không bao giờ cấu hình sai quyền S3 nữa.
Khi Nào Cần Public, Khi Nào Giữ Private?
Trước khi upload, cần xác định rõ mục đích sử dụng file:
- Public (qua CloudFront): Ảnh sản phẩm, thumbnail blog, file CSS/JS của website tĩnh, video marketing. Không public trực tiếp bucket — luôn đặt CloudFront phía trước để cache + bảo vệ.
- Private + Presigned URL: File tải xuống của user (hóa đơn, báo cáo), ảnh avatar cá nhân, tài liệu nội bộ cần chia sẻ tạm thời. Presigned URL có thời hạn (1 giờ, 24 giờ) — hết hạn tự động hết quyền truy cập.
- Private hoàn toàn (VPC only): Database backup, log hệ thống, dữ liệu nhạy cảm chỉ backend nội bộ được đọc. Kết hợp với VPC Endpoint để không đi qua internet.
Hướng Dẫn Từng Bước: Upload và Phân Quyền S3
Bước 1 — Tạo bucket với Block Public Access bật mặc định
Khi tạo bucket mới, AWS bật Block Public Access theo mặc định từ 2023. Đừng tắt nó trừ khi bạn thực sự cần serve file public trực tiếp (hiếm gặp). Kiểm tra lại bằng AWS CLI:
aws s3api get-public-access-block --bucket ten-bucket-cua-banOutput cần thấy tất cả 4 trường đều là true.
Bước 2 — Upload file với AWS CLI
Upload file đơn giản:
aws s3 cp ./invoice.pdf s3://my-private-bucket/invoices/invoice_001.pdfUpload cả thư mục (ví dụ: build React app):
aws s3 sync ./dist s3://my-website-bucket/ --deleteTùy chọn --delete xóa file trên S3 không còn tồn tại local — hữu ích khi deploy website tĩnh.
Bước 3 — Bucket Policy cho website tĩnh (Public qua CloudFront)
Thay vì public bucket trực tiếp, tạo CloudFront Origin Access Control (OAC) và chỉ cho CloudFront đọc bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontOAC",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-website-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789:distribution/EXXXXX"
}
}
}
]
}Policy này chỉ cho phép đúng CloudFront distribution của bạn đọc file — không ai khác, dù biết URL S3 gốc cũng không truy cập được.
Bước 4 — Tạo Presigned URL cho file private
Khi user cần tải hóa đơn PDF riêng của họ, backend tạo presigned URL thay vì expose file trực tiếp:
import boto3
from datetime import datetime
s3 = boto3.client('s3', region_name='ap-southeast-1')
presigned_url = s3.generate_presigned_url(
'get_object',
Params={
'Bucket': 'my-private-bucket',
'Key': f'invoices/{user_id}/invoice_001.pdf'
},
ExpiresIn=3600 # Hết hạn sau 1 giờ
)
# Trả URL này về cho frontend — không expose AWS credentialsMẹo: Đặt ExpiresIn ngắn nhất có thể cho use case của bạn. Hóa đơn tải về một lần → 15 phút là đủ. Tránh dùng 7 ngày vì nếu link bị lộ, kẻ tấn công có cả tuần.
Sai Lầm Phổ Biến và Cách Tránh
1. Tắt Block Public Access "thử xem" — rồi quên bật lại. Giải pháp: dùng AWS Config Rule s3-bucket-public-read-prohibited để tự động cảnh báo khi bucket bị public.
2. Dùng ACL public-read cho từng object — thay vì Bucket Policy. ACL đang bị deprecate và khó audit. Chuyển sang Bucket Policy + CloudFront OAC cho mọi trường hợp public.
3. IAM User có quyền s3:* trên mọi bucket — vi phạm least privilege. Tạo IAM Role riêng cho từng service, chỉ cấp quyền đúng bucket và đúng action (s3:GetObject, s3:PutObject).
FAQ
Q: Dùng ACL hay Bucket Policy — cái nào tốt hơn?A: Bucket Policy. AWS đã disable Object ACL mặc định từ April 2023 cho bucket mới. Bucket Policy linh hoạt hơn, dễ audit hơn, và hỗ trợ điều kiện phức tạp (IP whitelist, MFA, VPC). Chỉ dùng ACL khi làm việc với bucket cũ chưa migrate.
Q: Presigned URL có an toàn không nếu bị lộ?A: An toàn hơn link public nhưng không phải 100%. Ai có URL đó trong thời hạn ExpiresIn đều truy cập được. Giải pháp: đặt ExpiresIn ngắn, log access với S3 Server Access Logging, và khi cần bảo mật cao hơn thì kết hợp CloudFront Signed URL với trusted key group.
Q: Upload file lớn (5GB+) có cần cách khác không?A: Cần dùng Multipart Upload — S3 tự động dùng khi bạn upload qua AWS CLI hoặc SDK với file trên 8MB. Với file >5GB, đây là bắt buộc. Nếu upload trực tiếp từ browser, dùng presigned URL multipart để không expose credentials phía client.
Làm Đúng Từ Đầu, Không Phải Vá Sau
Phần lớn sự cố rò rỉ dữ liệu S3 không phải do lỗ hổng AWS — mà do cấu hình sai của người dùng. Nguyên tắc đơn giản: mọi bucket đều private mặc định, chỉ mở public qua CloudFront khi thực sự cần, và dùng Presigned URL cho mọi file cần chia sẻ tạm thời. Tuần tới mình sẽ đào sâu vào EBS — ổ cứng SSD ảo cho EC2 — và tại sao đây là lựa chọn tốt hơn hẳn so với lưu file thẳng trên instance.
