Lambda에서 S3 호출 시 403 오류가 발생하는 이유와 해결 방법

Lambda 함수가 오류 없이 실행되는데 S3에서 403을 반환한다면, 대부분의 엔지니어는 즉시 실행 역할(Execution Role)을 의심한다. 맞는 방향이지만, S3의 403은 IAM 권한 하나만의 문제가 아니다. 버킷 정책, 퍼블릭 액세스 차단 설정, KMS 암호화, VPC 엔드포인트 정책까지 여러 레이어가 독립적으로 접근을 거부할 수 있고, 그 중 하나라도 Deny를 내리면 결과는 동일하게 403이다.

TL;DR — Lambda S3 403 빠른 진단표

원인 레이어증상확인 방법
실행 역할 IAM 권한 누락s3:GetObject 등 액션 없음IAM 정책 시뮬레이터 또는 CLI
버킷 정책 명시적 DenyIAM 권한 있어도 거부버킷 정책 직접 확인
S3 퍼블릭 액세스 차단퍼블릭 ACL 기반 접근 차단get-public-access-block
KMS 키 권한 누락SSE-KMS 버킷에서 403KMS 키 정책 확인
VPC 엔드포인트 정책VPC 내 Lambda에서만 403엔드포인트 정책 확인
S3 객체 ACL 불일치특정 객체만 403get-object-acl

Lambda S3 403 오류의 구조적 이해

S3의 접근 제어는 단일 정책이 아니라 여러 레이어의 평가 결과를 순서대로 합산한다. AWS는 이를 'Authorization Context'라고 부르며, 어느 레이어에서든 명시적 Deny가 발생하면 나머지 Allow는 무효화된다. Lambda 실행 역할에 s3:GetObject를 추가했는데도 403이 계속 나온다면, 상위 레이어 중 하나가 여전히 Deny를 내리고 있는 것이다.

graph TD A["Lambda 함수 실행"] --> B["IAM 정책 평가
(실행 역할 + SCP)"] B --> C{"Allow?"} C -- "Deny" --> Z["403 Access Denied"] C -- "Allow" --> D["버킷 정책 평가"] D --> E{"Deny 구문?"} E -- "명시적 Deny" --> Z E -- "Allow 또는 없음" --> F["VPC 엔드포인트 정책 평가
(VPC Lambda인 경우)"] F --> G{"Allow?"} G -- "Deny" --> Z G -- "Allow" --> H["KMS 키 정책 평가
(SSE-KMS인 경우)"] H --> I{"Allow?"} I -- "Deny" --> Z I -- "Allow" --> J["객체 ACL 평가"] J --> K{"Allow?"} K -- "Deny" --> Z K -- "Allow" --> L["S3 객체 접근 성공"]
  1. IAM 평가: Lambda 실행 역할의 인라인/연결 정책에서 해당 액션이 Allow되어 있는지 확인한다.
  2. SCP 평가: AWS Organizations를 사용하는 경우, 계정 수준의 SCP가 S3 액션을 제한하고 있을 수 있다.
  3. 버킷 정책 평가: 버킷 정책의 Deny 구문은 IAM Allow를 덮어쓴다.
  4. VPC 엔드포인트 정책: Lambda가 VPC 내에서 실행되고 S3 VPC 엔드포인트를 경유한다면, 엔드포인트 정책도 평가 대상이다.
  5. ACL 및 객체 소유권: 버킷 소유자와 객체 업로더가 다를 경우 객체 ACL이 접근을 차단할 수 있다.

Lambda S3 403 오류 단계별 진단

1단계: Lambda 실행 역할에 S3 권한이 있는지 확인

가장 먼저 실행 역할에 필요한 IAM 권한이 연결되어 있는지 확인한다. S3 객체를 읽으려면 최소한 s3:GetObject가 필요하고, 버킷 목록 조회는 s3:ListBucket이 별도로 필요하다. 많은 경우 s3:ListBucket을 빠뜨려서 특정 경로 접근 시 403이 발생한다. s3:ListBucket은 버킷 ARN에, s3:GetObject는 객체 ARN(버킷ARN/*)에 각각 적용해야 한다는 점도 놓치기 쉽다.

# Lambda 함수의 실행 역할 이름 확인
aws lambda get-function-configuration \
  --function-name my-function \
  --query 'Role' \
  --output text

# 해당 역할에 연결된 정책 목록 확인
aws iam list-attached-role-policies \
  --role-name my-lambda-execution-role

# 인라인 정책도 별도로 확인
aws iam list-role-policies \
  --role-name my-lambda-execution-role

역할에 S3 관련 정책이 없다면 아래 최소 권한 정책을 연결한다.

🔽 최소 권한 S3 읽기 IAM 정책 예시 (클릭하여 펼치기)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowS3GetObject",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::my-example-bucket/*"
    },
    {
      "Sid": "AllowS3ListBucket",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket"
      ],
      "Resource": "arn:aws:s3:::my-example-bucket"
    }
  ]
}
# 위 정책을 파일로 저장 후 역할에 인라인 정책으로 추가
aws iam put-role-policy \
  --role-name my-lambda-execution-role \
  --policy-name S3ReadAccess \
  --policy-document file://s3-read-policy.json

2단계: IAM 정책 시뮬레이터로 실제 평가 결과 확인

정책을 추가했는데도 403이 계속 난다면, IAM 정책 시뮬레이터를 CLI로 실행해서 실제 평가 결과를 확인한다. 콘솔에서 정책이 붙어 있어 보여도, SCP나 Permission Boundary가 조용히 차단하고 있을 수 있다. 시뮬레이터는 이 레이어들을 한 번에 평가해준다.

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/my-lambda-execution-role \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::my-example-bucket/my-object.json

출력에서 EvalDecisionallowed가 아니라면, MatchedStatementsOrganizationsDecisionDetail 필드를 통해 어느 레이어가 차단하는지 확인할 수 있다.

3단계: 버킷 정책에 명시적 Deny가 있는지 확인

IAM 시뮬레이터가 'allowed'를 반환해도 버킷 정책의 명시적 Deny는 별도로 작동한다. IAM 시뮬레이터는 리소스 기반 정책(버킷 정책)을 완전히 반영하지 않을 수 있으므로, 버킷 정책을 직접 확인하는 것이 필수다.

aws s3api get-bucket-policy \
  --bucket my-example-bucket \
  --query 'Policy' \
  --output text

출력된 정책에서 "Effect": "Deny" 구문을 찾고, Principal이 Lambda 실행 역할을 포함하는지, Condition이 현재 요청 컨텍스트와 일치하는지 확인한다. 특히 aws:SourceVpc, aws:SourceIp, aws:PrincipalOrgID 조건이 Lambda 실행 환경과 맞지 않으면 의도치 않게 차단된다.

버킷 정책의 명시적 Deny는 IAM의 Allow를 완전히 무효화한다. 이 우선순위는 AWS IAM 평가 로직의 핵심 원칙이며, 예외가 없다.

4단계: SSE-KMS 암호화 버킷이라면 KMS 키 권한 확인

버킷이 SSE-KMS로 암호화되어 있다면, Lambda 실행 역할은 S3 권한 외에 KMS 키에 대한 kms:Decrypt(읽기 시) 또는 kms:GenerateDataKey(쓰기 시) 권한도 필요하다. 이 권한이 없으면 S3는 403을 반환한다. KMS 오류인지 S3 권한 오류인지 로그만 봐서는 구분이 안 되는 경우가 많다.

# 버킷의 기본 암호화 설정 확인
aws s3api get-bucket-encryption \
  --bucket my-example-bucket

# KMS 키 정책 확인 (KeyId는 위 명령 출력에서 확인)
aws kms get-key-policy \
  --key-id arn:aws:kms:us-east-1:123456789012:key/your-key-id \
  --policy-name default

KMS 키 정책에 Lambda 실행 역할이 포함되어 있지 않다면, 키 정책을 수정하거나 실행 역할의 IAM 정책에 KMS 권한을 추가해야 한다. KMS 키 정책은 IAM 정책과 독립적으로 평가되므로, 둘 다 Allow가 있어야 접근이 허용된다.

🔽 KMS 복호화 권한 IAM 정책 예시 (클릭하여 펼치기)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowKMSDecrypt",
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt",
        "kms:DescribeKey"
      ],
      "Resource": "arn:aws:kms:us-east-1:123456789012:key/your-key-id"
    }
  ]
}

5단계: VPC 내 Lambda라면 VPC 엔드포인트 정책 확인

Lambda가 VPC 내에서 실행되고 S3 VPC 엔드포인트(Gateway 타입)를 통해 S3에 접근하는 경우, 엔드포인트 정책이 추가 평가 레이어로 작동한다. 엔드포인트 정책이 기본값(Full Access)이 아니라 커스텀으로 설정되어 있다면, 해당 정책에서도 Lambda 역할의 접근이 허용되어야 한다. VPC 내 Lambda에서만 403이 발생하고 외부에서는 정상이라면 이 레이어를 먼저 의심해야 한다.

# VPC 엔드포인트 목록 확인
aws ec2 describe-vpc-endpoints \
  --filters 'Name=service-name,Values=com.amazonaws.us-east-1.s3' \
  --query 'VpcEndpoints[*].{Id:VpcEndpointId,Policy:PolicyDocument}' \
  --output json

출력된 PolicyDocumentnull이 아니고 커스텀 정책이 적용되어 있다면, 해당 정책에 Lambda 실행 역할과 대상 버킷이 포함되어 있는지 확인한다.

6단계: 객체 소유권 및 ACL 확인

버킷 소유자와 다른 계정에서 업로드된 객체는 기본적으로 업로더 계정이 소유권을 가진다. 이 경우 버킷 소유자 계정의 IAM 정책이 있어도 해당 객체에 접근할 수 없다. 특정 객체에서만 403이 발생하고 다른 객체는 정상이라면 이 시나리오를 확인해야 한다.

# 특정 객체의 ACL 확인
aws s3api get-object-acl \
  --bucket my-example-bucket \
  --key path/to/my-object.json

# 버킷의 객체 소유권 설정 확인
aws s3api get-bucket-ownership-controls \
  --bucket my-example-bucket

객체 소유권 문제라면, 버킷 수준에서 BucketOwnerEnforced를 설정하면 ACL이 비활성화되고 버킷 소유자가 모든 객체의 소유권을 갖게 된다. 단, 이 설정은 기존 ACL을 모두 무효화하므로 영향 범위를 먼저 검토해야 한다.

flowchart TD START(["Lambda S3 403 발생"]) --> Q1{"IAM 정책에
s3:GetObject 있음?"} Q1 -- "없음" --> FIX1["실행 역할에
S3 권한 추가"] Q1 -- "있음" --> Q2{"IAM 시뮬레이터
결과?"} Q2 -- "Deny" --> FIX2["SCP 또는
Permission Boundary 확인"] Q2 -- "Allow" --> Q3{"버킷 정책에
Deny 구문?"} Q3 -- "있음" --> FIX3["버킷 정책
Deny 조건 수정"] Q3 -- "없음" --> Q4{"SSE-KMS
암호화?"} Q4 -- "예" --> FIX4["KMS 키 정책에
역할 추가"] Q4 -- "아니오" --> Q5{"VPC 내
Lambda?"} Q5 -- "예" --> FIX5["VPC 엔드포인트
정책 확인"] Q5 -- "아니오" --> Q6{"특정 객체만
403?"} Q6 -- "예" --> FIX6["객체 ACL 및
소유권 확인"] Q6 -- "아니오" --> END["AWS Support 문의"]

실제 운영에서 마주친 패턴 — 잘못된 진단과 실제 원인

Lambda 실행 역할에 s3:GetObject를 추가했는데도 403이 계속 발생한 사례가 있었다. CloudWatch Logs에는 Python의 ClientError만 찍혔고, 오류 메시지는 단순히 'Access Denied'였다. 처음에는 정책 전파 지연을 의심해서 몇 분 기다렸지만 증상이 지속됐다.

실제 원인은 버킷 정책에 있었다. 해당 버킷에는 특정 VPC에서만 접근을 허용하는 조건부 Deny 구문이 있었다.

{
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:*",
  "Resource": [
    "arn:aws:s3:::my-example-bucket",
    "arn:aws:s3:::my-example-bucket/*"
  ],
  "Condition": {
    "StringNotEquals": {
      "aws:SourceVpc": "vpc-xxxxxxxx"
    }
  }
}

Lambda 함수는 다른 VPC에서 실행되고 있었고, 이 Deny 조건에 걸렸다. IAM 시뮬레이터는 'allowed'를 반환했는데, 이는 시뮬레이터가 버킷 정책의 VPC 조건을 실제 실행 컨텍스트로 평가하지 않기 때문이다. 버킷 정책을 직접 읽기 전까지는 원인을 찾을 수 없었다.

IAM 시뮬레이터가 'allowed'를 반환해도 버킷 정책은 별도로 확인해야 한다 — 이것이 이 사례의 핵심 교훈이다.

Lambda S3 403 진단 체크리스트 요약

순서확인 항목CLI 명령
1실행 역할에 s3:GetObject, s3:ListBucket 있는지iam list-attached-role-policies
2IAM 정책 시뮬레이터 평가 결과iam simulate-principal-policy
3버킷 정책 명시적 Deny 여부s3api get-bucket-policy
4SSE-KMS 여부 및 KMS 키 권한s3api get-bucket-encryption
5VPC 엔드포인트 정책 (VPC Lambda만)ec2 describe-vpc-endpoints
6객체 소유권 및 ACLs3api get-object-acl

마무리 및 다음 단계

Lambda에서 S3 403 오류를 해결할 때 실행 역할 IAM 권한은 출발점이지, 종착점이 아니다. 버킷 정책, KMS 키 정책, VPC 엔드포인트 정책, 객체 ACL까지 각 레이어를 순서대로 확인하는 것이 가장 빠른 진단 경로다. 특히 버킷 정책의 명시적 Deny는 IAM Allow를 완전히 무효화하므로, IAM 시뮬레이터 결과만 믿지 말고 버킷 정책을 직접 확인하는 습관이 중요하다.

핵심 용어 정리

용어설명
실행 역할 (Execution Role)Lambda 함수가 AWS 서비스를 호출할 때 사용하는 IAM 역할. 함수에 연결된 IAM 권한의 실질적 주체다.
명시적 Deny (Explicit Deny)IAM 또는 리소스 기반 정책에서 Effect가 Deny인 구문. 다른 모든 Allow보다 우선 적용된다.
SSE-KMSAWS KMS 키를 사용하는 S3 서버 측 암호화 방식. S3 권한 외에 KMS 키 권한도 별도로 필요하다.
VPC 엔드포인트 정책VPC 엔드포인트를 통한 AWS 서비스 접근을 제어하는 리소스 기반 정책. IAM 및 버킷 정책과 독립적으로 평가된다.
버킷 소유권 (Object Ownership)S3 객체의 소유권을 결정하는 버킷 수준 설정. 교차 계정 업로드 시 접근 제어에 영향을 준다.

Related Posts

댓글

이 블로그의 인기 게시물

EC2 SSH 연결 타임아웃 완전 해결 가이드: Security Group 인바운드 규칙부터 라우팅까지

S3 정적 웹사이트 호스팅 완전 가이드: HTML/CSS 사이트를 무료로 배포하는 법

EC2 SSH 연결 시간 초과: 확인해야 할 보안 그룹(Security Group) 규칙