Sửa lỗi AWS S3 403 Forbidden: HeadObject bị chặn bởi Bucket Policy hoặc ACL

intermediate☁️ AWS2026-04-22| AWS S3, AWS CLI v2, Python boto3, mọi AWS SDK — tất cả các region

Error Message

An error occurred (403) when calling the HeadObject operation: Forbidden
#aws#s3#bucket-policy#acl#permissions#403

Lỗi Gặp Phải

Bạn chạy một lệnh S3 tưởng chừng bình thường và nhận được kết quả sau:

$ aws s3 cp s3://my-bucket/data/report.csv .
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden

Hoặc từ boto3:

botocore.exceptions.ClientError: An error occurred (403) when calling the HeadObject operation: Forbidden

Bucket vẫn tồn tại. Object vẫn tồn tại. Bạn có thể thấy nó trong console. Vậy mà SDK vẫn từ chối truy cập.

Nguyên Nhân

Quyền truy cập S3 không phải một cửa duy nhất — mà là bốn. IAM identity policy, bucket policy, ACL, và cài đặt block public access đều được đánh giá độc lập, và bất kỳ cái nào cũng có thể từ chối yêu cầu của bạn. Phần khó chịu là: thiếu Allow trông y hệt như một Deny tường minh từ góc nhìn của SDK.

Các nguyên nhân thường gặp:

  • Bucket policy có Deny tường minh cho IAM principal hoặc dải IP của bạn
  • IAM role/user của bạn thiếu quyền s3:GetObject hoặc s3:HeadObject
  • ACL của object được đặt là private và bucket owner enforcement đang tắt
  • Bucket nằm ở AWS account khác và cross-account trust chưa được cấu hình
  • S3 Block Public Access đang bật trong khi bucket policy cố cấp quyền public
  • Server-side encryption dùng KMS key mà role của bạn không có quyền giải mã

Cách Khắc Phục Từng Bước

Bước 1 — Xác nhận danh tính của bạn

Trước khi chỉnh sửa bất kỳ policy nào, hãy kiểm tra xem IAM principal nào đang thực sự thực hiện lệnh gọi:

aws sts get-caller-identity

Kết quả trả về:

{
    "UserId": "AROAXXXXXXXXXXXXXXXXX:session",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:role/my-app-role"
}

Lưu lại ARN đó. Bạn sẽ cần đối chiếu nó với các policy statement trong các bước tiếp theo.

Bước 2 — Kiểm tra bucket policy

aws s3api get-bucket-policy --bucket my-bucket | python3 -m json.tool

Tìm các statement có "Effect": "Deny" bao phủ ARN của bạn. Chú ý các điều kiện như aws:SourceIp hay aws:PrincipalOrgID — những điều kiện này có thể chặn bạn âm thầm dù principal trông có vẻ đúng.

Tìm thấy Deny bao phủ role của bạn? Hãy xóa nó đi hoặc thêm Allow tường minh phía trên:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowAppRole",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/my-app-role"
      },
      "Action": ["s3:GetObject", "s3:HeadObject"],
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}

Áp dụng policy:

aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json

Bước 3 — Kiểm tra IAM permissions

Bucket policy chưa phải toàn bộ câu chuyện. Với truy cập cùng account, chỉ cần bucket policy hoặc identity policy cho phép hành động là đủ. Truy cập cross-account nghiêm ngặt hơn — cả hai đều phải cho phép tường minh.

Chạy IAM Policy Simulator để xem chính xác điều gì đang xảy ra:

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/my-app-role \
  --action-names s3:GetObject s3:HeadObject \
  --resource-arns arn:aws:s3:::my-bucket/data/report.csv

Nhận được "EvalDecision": "implicitDeny" hoặc "explicitDeny"? Identity policy cần được cập nhật. Gắn thêm policy như sau:

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

Bước 4 — Kiểm tra ACL (nếu có)

ACL chỉ quan trọng khi Object Ownership chưa được đặt thành Bucket owner enforced. Khi ACL còn hoạt động, ACL cấp object có thể ghi đè mọi thứ khác:

aws s3api get-object-acl --bucket my-bucket --key data/report.csv

Object được upload từ account khác và ACL loại trừ bạn? Hãy đặt lại:

aws s3api put-object-acl \
  --bucket my-bucket \
  --key data/report.csv \
  --acl bucket-owner-full-control

Tốt hơn nữa, hãy loại bỏ ACL hoàn toàn để tránh rắc rối về sau:

aws s3api put-bucket-ownership-controls \
  --bucket my-bucket \
  --ownership-controls 'Rules=[{ObjectOwnership=BucketOwnerEnforced}]'

Bước 5 — Kiểm tra mã hóa KMS

KMS key do khách hàng quản lý thêm một lớp dễ bị bỏ qua. Chạy head-object để xem có key nào liên quan không:

aws s3api head-object --bucket my-bucket --key data/report.csv

Kết quả trả về có ServerSideEncryption: aws:kms cùng với SSEKMSKeyId? Thêm quyền KMS vào IAM policy:

{
  "Effect": "Allow",
  "Action": ["kms:Decrypt", "kms:GenerateDataKey"],
  "Resource": "arn:aws:kms:us-east-1:123456789012:key/your-key-id"
}

Xác Nhận Đã Sửa

Chạy kiểm tra nhanh trước khi kết luận đã xong:

# HeadObject — chỉ lấy metadata, nhanh hơn
aws s3api head-object --bucket my-bucket --key data/report.csv

# Tải file thực tế
aws s3 cp s3://my-bucket/data/report.csv /tmp/report.csv
echo $?  # 0 = thành công

Từ boto3:

import boto3
s3 = boto3.client('s3')
try:
    resp = s3.head_object(Bucket='my-bucket', Key='data/report.csv')
    print('OK:', resp['ContentLength'], 'bytes')
except Exception as e:
    print('Still failing:', e)

Trường Hợp Cross-Account

Bucket ở Account A, role ở Account B? Cả hai phía đều phải cho phép hành động một cách tường minh — không có cách nào khác. Bucket policy ở Account A cần khai báo trực tiếp external principal của bạn:

{
  "Principal": {
    "AWS": "arn:aws:iam::ACCOUNT_B_ID:role/cross-account-role"
  },
  "Action": ["s3:GetObject", "s3:HeadObject"],
  "Effect": "Allow",
  "Resource": "arn:aws:s3:::my-bucket/*"
}

Identity policy của Account B cũng cần cho phép s3:GetObject trên bucket đích. Thiếu một trong hai phía là bạn lại nhận 403.

Mẹo Hữu Ích

  • CloudTrail cho bạn biết policy nào đã kích hoạt. Lọc các sự kiện s3.amazonaws.com với errorCode: AccessDenied. Chi tiết sự kiện sẽ chỉ rõ policy nào gây ra từ chối — nhanh hơn nhiều so với đọc policy thủ công.
  • IAM Policy Simulator là công cụ tốt nhất trong trường hợp này. Chạy aws iam simulate-principal-policy trước và sau mỗi thay đổi để xác nhận sự khác biệt. Đừng đoán mò — hãy mô phỏng.
  • Bật S3 server access logging để có audit trail lâu dài — ai truy cập gì, khi nào, và từ IP nào.
  • Làm việc với EC2 instance profile gắn filesystem S3 qua s3fs? ToolCraft Unix Permissions Calculator hữu ích để kiểm tra quyền cấp file trên phía Linux. Chạy hoàn toàn trên trình duyệt — không có dữ liệu nào rời khỏi máy bạn.
  • Bỏ ACL cho các bucket mới. ACL là cơ chế cũ. Kết hợp ACL với bucket policy sinh ra đúng loại lỗi 403 này — khó truy vết, phiền phức để sửa. Chế độ bucket owner enforced kết hợp với bucket policy rõ ràng sẽ đơn giản và dễ kiểm tra hơn nhiều.

Related Error Notes