Tạo S3 Presigned URL: Cấp Quyền Truy Cập Tạm Thời Vào File Private

Bạn có một file private trong S3 và cần cho phép người dùng tải xuống mà không cần cấp quyền IAM hay làm bucket thành public — đây là bài toán xuất hiện hàng ngày trong production. S3 Presigned URL là cơ chế chính xác để giải quyết vấn đề này: tạo một URL có chữ ký tạm thời, hết hạn sau khoảng thời gian bạn định nghĩa, không cần thay đổi bất kỳ bucket policy nào.

TL;DR — Tạo S3 Presigned URL

BướcHành độngKết quả
1Cấu hình IAM role/user có quyền s3:GetObjectCredential hợp lệ để ký URL
2Gọi generate_presigned_url với ExpiresIn=3600URL có chữ ký, hết hạn sau 1 giờ
3Trả URL về cho người dùngNgười dùng tải file trực tiếp từ S3
4Sau 3600 giây, URL vô hiệuRequest tiếp theo nhận 403 AccessDenied

S3 Presigned URL Hoạt Động Như Thế Nào

Presigned URL không phải là một token riêng biệt được S3 lưu trữ. Toàn bộ thông tin xác thực — identity, bucket, key, expiration, signature — được nhúng trực tiếp vào URL dưới dạng query parameters. Khi người dùng gửi request đến URL đó, S3 tự xác minh chữ ký bằng cách tái tính toán từ các tham số trong URL và so sánh với chữ ký đính kèm.

Điều này có nghĩa là: nếu credential dùng để ký URL bị thu hồi hoặc hết hạn trước thời điểm ExpiresIn, URL sẽ ngừng hoạt động ngay lập tức — dù thời gian hết hạn được chỉ định chưa đến. Đây là điểm quan trọng khi dùng temporary credentials từ IAM role (STS tokens).

sequenceDiagram participant App as Application Server participant SDK as AWS SDK participant User as Người dùng participant S3 as Amazon S3 App->>SDK: generate_presigned_url('get_object', ExpiresIn=3600) SDK-->>App: Presigned URL (SigV4 signed) App-->>User: Trả về Presigned URL User->>S3: HTTP GET với Presigned URL S3->>S3: Xác minh chữ ký & expiration S3-->>User: 200 OK + File content (hoặc 403)
  1. Application server gọi AWS SDK để tạo presigned URL — không có request nào đến S3 ở bước này.
  2. SDK dùng credential hiện tại (IAM user key hoặc STS token) để ký URL theo SigV4.
  3. Presigned URL được trả về cho application, sau đó chuyển cho người dùng.
  4. Người dùng gửi HTTP GET trực tiếp đến S3 — không qua application server.
  5. S3 xác minh chữ ký, kiểm tra expiration, sau đó trả về file hoặc từ chối.

Điều Kiện Tiên Quyết: IAM Permission

Identity dùng để tạo presigned URL phải có quyền s3:GetObject trên object đó. Nếu thiếu quyền này, URL vẫn được tạo thành công về mặt cú pháp — nhưng khi người dùng truy cập sẽ nhận 403 AccessDenied. Đây là lỗi phổ biến nhất khi mới triển khai.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example-bucket/private/*"
    }
  ]
}

Áp dụng policy này cho IAM role hoặc user mà application đang sử dụng. Không cần thêm quyền gì khác chỉ để tạo presigned URL.

Tạo S3 Presigned URL Với Python (boto3)

Đây là cách tạo presigned URL với thời hạn 1 giờ sử dụng AWS SDK for Python:

import boto3
from botocore.exceptions import ClientError

def generate_presigned_url(bucket_name, object_key, expiration=3600):
    s3_client = boto3.client('s3', region_name='us-east-1')
    try:
        url = s3_client.generate_presigned_url(
            'get_object',
            Params={
                'Bucket': bucket_name,
                'Key': object_key
            },
            ExpiresIn=expiration
        )
    except ClientError as e:
        print(f'Error generating presigned URL: {e}')
        return None
    return url

# Sử dụng
presigned_url = generate_presigned_url('example-bucket', 'private/report-2024.pdf')
print(presigned_url)

Tham số ExpiresIn nhận giá trị tính bằng giây. Với 1 giờ, dùng 3600. URL trả về có dạng HTTPS và chứa toàn bộ chữ ký trong query string.

Tạo S3 Presigned URL Với JavaScript (AWS SDK v3)

🔽 Xem code JavaScript (AWS SDK v3)
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const client = new S3Client({ region: 'us-east-1' });

async function generatePresignedUrl(bucketName, objectKey, expiresInSeconds = 3600) {
  const command = new GetObjectCommand({
    Bucket: bucketName,
    Key: objectKey,
  });

  try {
    const url = await getSignedUrl(client, command, { expiresIn: expiresInSeconds });
    return url;
  } catch (err) {
    console.error('Error generating presigned URL:', err);
    throw err;
  }
}

// Sử dụng
generatePresignedUrl('example-bucket', 'private/report-2024.pdf')
  .then(url => console.log(url));

SDK v3 dùng package riêng @aws-sdk/s3-request-presigner — không phải method trực tiếp trên S3Client. Nếu bạn đang dùng SDK v2, API khác: s3.getSignedUrlPromise('getObject', params).

Tạo Presigned URL Qua AWS CLI

Để test nhanh mà không cần viết code:

aws s3 presign s3://example-bucket/private/report-2024.pdf \
  --expires-in 3600 \
  --region us-east-1

CLI sẽ in ra URL đầy đủ. Dùng curl hoặc browser để kiểm tra ngay. Lưu ý: URL này dùng credential của AWS CLI profile hiện tại — nếu profile đó dùng STS session token, URL sẽ hết hạn khi session hết hạn, dù --expires-in chưa đến.

Lỗi Thường Gặp Khi Tạo S3 Presigned URL

Symptom: URL trả về 403 ngay lập tức

Lần đầu triển khai, nhóm hay nghĩ ngay đến bucket policy — kiểm tra đủ thứ, thêm s3:GetObject vào bucket policy, vẫn 403. Thực ra vấn đề nằm ở chỗ khác: identity tạo URL là Lambda execution role, nhưng role đó không có s3:GetObject trong IAM policy đính kèm. Bucket policy không thể grant quyền cho một identity không có permission ở IAM layer khi bucket không phải public.

Kiểm tra bằng CLI:

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/MyLambdaRole \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::example-bucket/private/report-2024.pdf \
  --region us-east-1

Nếu kết quả là implicitDeny, thêm s3:GetObject vào policy của role. Nếu là explicitDeny, có một Deny statement đang override — tìm trong SCP hoặc bucket policy.

Symptom: URL hết hạn sớm hơn ExpiresIn

Đây là hệ quả của việc dùng STS temporary credentials (IAM role, EC2 instance profile, Lambda execution role). Presigned URL hết hạn tại thời điểm sớm hơn giữa hai mốc: ExpiresIn và thời điểm hết hạn của STS session token. Nếu Lambda function chạy với session token còn 10 phút, URL tạo ra với ExpiresIn=3600 sẽ thực sự hết hạn sau 10 phút.

Presigned URL không phải là một object độc lập — nó là một lời hứa được ký bởi credential hiện tại. Khi credential hết hạn, lời hứa cũng vô hiệu, bất kể thời hạn ghi trên URL là bao lâu.

Để có URL với thời hạn dài và ổn định hơn, dùng IAM user long-term credentials để ký — nhưng cần cân nhắc kỹ về security implications của long-term keys trong production.

Symptom: URL hoạt động nhưng file bị download với tên sai

Thêm ResponseContentDisposition vào params để kiểm soát tên file khi download:

url = s3_client.generate_presigned_url(
    'get_object',
    Params={
        'Bucket': 'example-bucket',
        'Key': 'private/report-2024.pdf',
        'ResponseContentDisposition': 'attachment; filename="report-2024.pdf"'
    },
    ExpiresIn=3600
)

Kiểm Tra Presigned URL Đã Tạo

Sau khi tạo URL, verify bằng curl để xác nhận trước khi tích hợp vào application:

# Kiểm tra HTTP status code
curl -o /dev/null -s -w "%{http_code}\n" ""

# Download file thực sự
curl -L -o downloaded-file.pdf ""

Response 200 xác nhận URL hợp lệ. Response 403 với body XML sẽ chỉ rõ nguyên nhân — thường là AccessDenied hoặc RequestExpired.

Giới Hạn Và Lưu Ý Quan Trọng

Presigned URL cho phép bất kỳ ai có URL đó truy cập file — không có xác thực thêm ở phía S3. Nếu URL bị lộ (log, browser history, email forward), người nhận có thể dùng nó cho đến khi hết hạn. Thiết kế thời hạn ngắn nhất có thể phù hợp với use case.

Ngoài ra, presigned URL chỉ hoạt động với object-level operations. Bạn không thể tạo presigned URL để list bucket contents hay tạo bucket mới theo cách tương tự.

graph TD A["Tạo Presigned URL"] --> B{"Credential type?"} B -->|"IAM User
(long-term key)"| C["Hết hạn đúng ExpiresIn"] B -->|"IAM Role / Lambda
(STS token)"| D{"Session còn hạn?"} D -->|"Session > ExpiresIn"| E["Hết hạn đúng ExpiresIn"] D -->|"Session < ExpiresIn"| F["Hết hạn khi session expire"] F --> G["URL vô hiệu sớm hơn dự kiến"]
  1. Credential type ảnh hưởng trực tiếp đến độ tin cậy của expiration.
  2. IAM user key (long-term): URL hết hạn đúng theo ExpiresIn.
  3. STS/Role token (short-term): URL hết hạn tại min(ExpiresIn, session_expiry).
  4. Nếu session token hết hạn trước ExpiresIn, URL vô hiệu ngay lập tức.

Wrap-Up: Tạo S3 Presigned URL Trong Production

Tạo S3 Presigned URL là pattern đơn giản nhưng có vài điểm dễ sai trong production. Điểm cốt lõi: đảm bảo identity ký URL có s3:GetObject, chọn ExpiresIn phù hợp với use case, và hiểu rõ ràng rằng STS credentials ảnh hưởng đến thời hạn thực tế của URL.

Tài liệu tham khảo chính thức:

Glossary — Thuật Ngữ Chính

Thuật ngữGiải thích
Presigned URLURL chứa chữ ký SigV4 cho phép truy cập tạm thời vào S3 object mà không cần credential riêng
SigV4AWS Signature Version 4 — thuật toán ký request của AWS, dùng HMAC-SHA256
STS (Security Token Service)Dịch vụ AWS cấp temporary credentials có thời hạn cho IAM roles
ExpiresInTham số chỉ định thời gian hết hạn của presigned URL tính bằng giây
s3:GetObjectIAM action cho phép đọc/download object từ S3 bucket

Related Posts

Nhận xét

Bài đăng phổ biến từ blog này

EC2 Không Có Internet Trong Custom VPC: Cách Gắn Internet Gateway và Cập Nhật Route Table

RDS Multi-AZ: Lợi Ích Thực Sự Là Gì và Khi Nào Nên Bật?

Lỗi CORS trên API Gateway: Cách bật CORS và Lambda phải trả về header gì