

# AWS Lambda 함수 URL 오리진에 대한 액세스 제한
<a name="private-content-restricting-access-to-lambda"></a>

CloudFront는 Lambda 함수 URL 오리진에 대한 액세스를 제한하기 위한 오리진 액세스 제어(OAC)를 제공합니다.**

**Topics**
+ [새 OAC 생성](#create-oac-overview-lambda)
+ [오리진 액세스 제어를 위한 고급 설정](#oac-advanced-settings-lambda)
+ [예제 템플릿 코드](#example-template-code-lambda-oac)

## 새 OAC 생성
<a name="create-oac-overview-lambda"></a>

다음 주제에 설명된 단계를 완료하여 CloudFront에서 새 OAC를 설정합니다.

**중요**  
Lambda 함수 URL과 함께 `PUT` 또는 `POST` 메서드를 사용하는 경우 사용자가 요청을 CloudFront에 전송할 때 본문의 SHA256을 컴퓨팅하고 `x-amz-content-sha256` 헤더에 있는 요청 본문의 페이로드 해시 값을 포함해야 합니다. Lambda는 서명되지 않은 페이로드를 지원하지 않습니다.

**Topics**
+ [사전 조건](#oac-prerequisites-lambda)
+ [Lambda 함수 URL에 액세스할 수 있는 CloudFront 권한 부여](#oac-permission-to-access-lambda)
+ [OAC 생성](#create-oac-lambda)

### 사전 조건
<a name="oac-prerequisites-lambda"></a>

OAC를 생성하고 설정하기 전에 Lambda 함수 URL을 오리진으로 사용하는 CloudFront 배포가 있어야 합니다. OAC를 사용하려면 `AWS_IAM`을 `AuthType` 파라미터의 값으로 지정해야 합니다. 자세한 내용은 [Lambda 함수 URL 사용](DownloadDistS3AndCustomOrigins.md#concept_lambda_function_url) 섹션을 참조하세요.

### Lambda 함수 URL에 액세스할 수 있는 CloudFront 권한 부여
<a name="oac-permission-to-access-lambda"></a>

OAC를 만들거나 CloudFront 배포에서 설정하기 전에 CloudFront에 Lambda 함수 URL에 액세스할 수 있는 권한이 있는지 확인합니다. 이 작업은 CloudFront 배포를 생성한 후 배포 구성의 Lambda 함수 URL에 OAC를 추가하기 전에 수행해야 합니다.

**참고**  
Lambda 함수 URL에 대한 IAM 정책을 업데이트하려면 AWS Command Line Interface(AWS CLI)을 사용해야 합니다. Lambda 콘솔에서의 IAM 정책 편집은 현재 지원되지 않습니다.

다음 AWS CLI 명령은 Lambda 함수 URL에 대한 CloudFront 서비스 보안 주체(`cloudfront.amazonaws.com`) 액세스 권한을 부여합니다. 이 정책의 `Condition` 요소는 요청이 Lambda 함수 URL을 포함하는 CloudFront 배포를 대신하는 경우에만 CloudFront가 Lambda에 액세스할 수 있도록 허용합니다.** OAC를 추가하려는 Lambda 함수 URL 오리진이 있는 배포입니다.

**Example : CloudFront 배포에서 OAC에 대한 읽기 전용 액세스를 허용하도록 정책을 업데이트하는 AWS CLI 명령입니다.**  
다음 AWS CLI 명령을 실행하면 CloudFront 배포(`E1PDK09ESKHJWT`)가 Lambda *`FUNCTION_URL_NAME`*에 액세스할 수 있습니다.

```
aws lambda add-permission \
--statement-id "AllowCloudFrontServicePrincipal" \
--action "lambda:InvokeFunctionUrl" \
--principal "cloudfront.amazonaws.com" \
--source-arn "arn:aws:cloudfront::123456789012:distribution/E1PDK09ESKHJWT" \
--function-name FUNCTION_URL_NAME
```

```
aws lambda add-permission \
--statement-id "AllowCloudFrontServicePrincipalInvokeFunction" \
--action "lambda:InvokeFunction" \
--principal "cloudfront.amazonaws.com" \
--source-arn "arn:aws:cloudfront::123456789012:distribution/E1PDK09ESKHJWT" \
--function-name FUNCTION_URL_NAME
```

**참고**  
배포를 생성했는데 Lambda 함수 URL에 대한 권한이 없는 경우 CloudFront 콘솔에서 **Copy CLI 명령**을 선택한 다음 명령줄 터미널에서 이 명령을 입력할 수 있습니다. 자세한 정보는 AWS Lambda 개발자 안내서의 [AWS 서비스에 대한 액세스 부여](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html#permissions-resource-serviceinvoke)를 참조합니다.**

### OAC 생성
<a name="create-oac-lambda"></a>

OAC를 생성하려면 AWS Management Console, CloudFormation, AWS CLI 또는 CloudFront API를 사용할 수 있습니다.

------
#### [ Console ]

**OAC를 만들려면**

1. AWS Management Console에 로그인한 다음 [https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home)에서 CloudFront 콘솔을 엽니다.

1. 탐색 창에서 **Origin access**(원본 액세스)를 선택합니다.

1. **Create control setting**(제어 설정 생성)을 선택합니다.

1. **새로운 OAC 생성** 양식에서 다음을 수행합니다.

   1. OAC의 **이름**을 입력하고 선택적으로 **설명**을 입력합니다.

   1. **서명 동작**의 경우 기본 설정인 **요청 서명(권장)**을 유지하는 것이 좋습니다. 자세한 내용은 [오리진 액세스 제어를 위한 고급 설정](#oac-advanced-settings-lambda) 섹션을 참조하세요.

1. **오리진 유형**에 대해 **Lambda**를 선택합니다.

1. **생성(Create)**을 선택합니다.
**작은 정보**  
OAC를 생성한 후 **이름**을 기록해 둡니다. 다음 절차에서 필요합니다.

**배포의 Lambda 함수 URL에 오리진 액세스 제어를 추가하려는 경우**

1. [https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home)에서 CloudFront 콘솔을 엽니다

1. OAC를 추가하려는 Lambda 함수 URL이 있는 배포를 선택한 다음 **오리진** 탭을 선택합니다.

1. OAC를 추가할 Lambda 함수 URL을 선택한 다음 **편집**을 선택합니다.

1. 오리진의 **프로토콜**에 **HTTPS만 해당**을 선택합니다.

1. **오리진 액세스 제어** 드롭다운 메뉴에서 사용할 OAC를 선택합니다.

1. **변경 사항 저장**를 선택합니다.

모든 CloudFront 엣지 로케이션에 배포가 시작됩니다. 엣지 로케이션은 새 구성을 수신하면 Lambda 함수 URL로 보내는 모든 요청에 서명합니다.

------
#### [ CloudFormation ]

CloudFormation에 OAC를 생성하려면 `AWS::CloudFront::OriginAccessControl` 리소스 유형을 사용합니다. 다음 예는 OAC를 생성하기 위한 YAML 형식의 CloudFormation 템플릿 구문을 보여줍니다.

```
Type: AWS::CloudFront::OriginAccessControl
Properties: 
  OriginAccessControlConfig: 
      Description: An optional description for the origin access control
      Name: ExampleOAC
      OriginAccessControlOriginType: lambda
      SigningBehavior: always
      SigningProtocol: sigv4
```

자세한 내용은 AWS CloudFormation 사용 설명서의 [AWS::CloudFront::OriginAccessControl](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-originaccesscontrol.html)을 참조하세요.**

------
#### [ CLI ]

AWS Command Line Interface(AWS CLI)를 사용하여 오리진 액세스 제어를 생성하려면 **aws cloudfront create-origin-access-control** 명령을 사용합니다. 각 개별 파라미터를 명령줄 입력으로 지정하는 대신 입력 파일을 사용하여 명령의 입력 파라미터를 제공할 수 있습니다.

**오리진 액세스 제어를 생성하려면(입력 파일과 CLI)**

1. 다음 명령을 사용하여 이름이 `origin-access-control.yaml`인 파일을 생성합니다. 이 파일에는 **create-origin-access-control** 명령에 대한 모든 입력 파라미터가 들어 있습니다.

   ```
   aws cloudfront create-origin-access-control --generate-cli-skeleton yaml-input > origin-access-control.yaml
   ```

1. 방금 생성한 `origin-access-control.yaml` 파일을 엽니다. 파일을 편집하여 OAC의 이름, 설명(선택 사항)을 추가하고 `SigningBehavior`를 `always`로 변경합니다. 그런 다음 파일을 저장합니다.

   다른 OAC 설정에 대한 자세한 내용은 [오리진 액세스 제어를 위한 고급 설정](#oac-advanced-settings-lambda) 섹션을 참조하세요.

1. 다음 명령으로 `origin-access-control.yaml` 파일의 입력 파라미터를 사용하여 오리진 액세스 제어를 만듭니다.

   ```
   aws cloudfront create-origin-access-control --cli-input-yaml file://origin-access-control.yaml
   ```

   명령 출력에 있는 `Id` 값을 기록해 둡니다. CloudFront 배포의 Lambda 함수 URL에 OAC를 추가하려면 필요합니다.

**OAC를 기존 배포의 Lambda 함수 URL에 연결하려는 경우(입력 파일과 CLI)**

1. 다음 명령을 사용하여 OAC를 추가할 CloudFront 배포에 대한 배포 구성을 저장합니다. 배포에 오리진과 함께 Lambda 함수 URL이 있어야 합니다.

   ```
   aws cloudfront get-distribution-config --id <CloudFront distribution ID> --output yaml > dist-config.yaml
   ```

1. 방금 생성한 `dist-config.yaml`이라는 파일을 엽니다. 파일을 편집하여 다음과 같이 변경합니다.
   + `Origins` 객체에서 `OriginAccessControlId`라는 필드에 OAC의 ID를 추가합니다.
   + `OriginAccessIdentity`라는 필드에서 값을 제거합니다(있는 경우).
   + `ETag` 필드의 이름을 `IfMatch`로 바꾸지만 필드 값은 변경하지 마세요.

   완료되면 파일을 저장합니다.

1. 오리진 액세스 제어를 사용하도록 배포를 업데이트하려면 다음 명령을 사용합니다.

   ```
   aws cloudfront update-distribution --id <CloudFront distribution ID> --cli-input-yaml file://dist-config.yaml
   ```

모든 CloudFront 엣지 로케이션에 배포가 시작됩니다. 엣지 로케이션은 새 구성을 수신하면 Lambda 함수 URL로 보내는 모든 요청에 서명합니다.

------
#### [ API ]

CloudFront API를 사용하여 OAC를 생성하려면 [CreateOriginAccessControl](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_CreateOriginAccessControl.html)을 사용합니다. 이 API 호출에서 지정하는 필드에 대한 자세한 내용은 AWS SDK 또는 기타 API 클라이언트에 대한 API 참조 설명서를 참조하세요.

OAC를 생성한 후 다음 API 호출 중 하나를 사용하여 배포의 Lambda 함수 URL에 연결할 수 있습니다.
+ 기존 배포에 연결하려면 [UpdateDistribution](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_UpdateDistribution.html)을 사용합니다.
+ 새 배포에 연결하려면 [CreateDistribution](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_CreateDistribution.html)을 사용합니다.

이 두 API 호출에 대해 오리진 내부의 `OriginAccessControlId` 필드에 OAC ID를 제공합니다. 이러한 API 호출에서 지정하는 다른 필드에 대한 자세한 내용은 AWS SDK 또는 기타 API 클라이언트에 대한 API 참조 설명서를 참조합니다.

------

## 오리진 액세스 제어를 위한 고급 설정
<a name="oac-advanced-settings-lambda"></a>

CloudFront OAC 기능에는 특정 사용 사례만을 위한 고급 설정이 포함되어 있습니다. 고급 설정이 특별히 필요한 경우가 아니면 권장 설정을 사용하세요.

OAC에는 **서명 동작**(콘솔) 또는 `SigningBehavior`(API, CLI 및 CloudFormation)라는 설정이 포함되어 있습니다. 이 설정은 다음 옵션을 제공합니다.

**항상 오리진 요청에 서명(권장 설정)**  
이 설정의 이름은 콘솔에서 **서명 요청(권장)**이고 API, CLI, CloudFormation에서 `always`이며, 이 설정을 사용하는 것이 좋습니다. 이 설정을 사용하면 CloudFront가 항상 Lambda 함수 URL으로 보내는 모든 요청에 서명합니다.

**오리진 요청 서명 안 함**  
이 설정의 이름은 콘솔에서 **Do not sign requests**(요청 서명 안 함)이고 API, CLI, CloudFormation에서 `never`입니다. 이 OAC를 사용하는 모든 배포의 모든 오리진에 대한 OAC를 끄려면 이 설정을 사용합니다. OAC를 사용하는 모든 오리진 및 배포에서 OAC를 하나씩 제거하는 것과 비교하여 시간과 노력을 절약할 수 있습니다. 이 설정을 사용하면 CloudFront가 Lambda 함수 URL로 보내는 모든 요청에 서명하지 않습니다.  
이 설정을 사용하려면 Lambda 함수 URL이 공개적으로 액세스할 수 있어야 합니다. 공개적으로 액세스할 수 없는 Lambda 함수 URL에 이 설정을 사용하는 경우 CloudFront가 오리진에 액세스할 수 없습니다. Lambda 함수 URL은 CloudFront에 오류를 반환하고 CloudFront 해당 오류를 뷰어에 전달합니다. 자세한 내용은 **AWS Lambda 사용 설명서의 [Lambda 함수 URL에 대한 보안 및 인증 모델](https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html)을 참조하세요.

**뷰어(클라이언트) `Authorization` 헤더 재정의 안 함**  
이 설정의 이름은 콘솔에서 **Do not override authorization header**(승인 헤더 재정의 안 함)이고 API, CLI, CloudFormation에서 `no-override`입니다. 해당 뷰어 요청에 `Authorization` 헤더가 포함되지 않은 경우에만 CloudFront에서 오리진 요청에 서명하도록 하려면 이 설정을 사용합니다. 이 설정을 사용하면 CloudFront에서는 뷰어 요청에 `Authorization` 헤더가 있는 경우 이를 전달하지만 뷰어 요청에 `Authorization` 헤더가 포함되어 있지 않으면 오리진 요청에 서명합니다(자체 `Authorization` 헤더 추가).  
+ 이 설정을 사용하는 경우, Lambda 함수 URL에 대해 CloudFront 배포의 이름 또는 CNAME 대신 Signature Version 4 서명을 지정해야 합니다. CloudFront가 뷰어 요청의 `Authorization` 헤더를 Lambda 함수 URL로 전달하면 Lambda는 Lambda URL 도메인의 호스트에 대해 서명을 검증합니다. 서명이 Lambda URL 도메인을 기반으로 하지 않는 경우 서명의 호스트는 Lambda URL 오리진에서 사용하는 호스트와 일치하지 않습니다. 따라서 요청이 실패하여 서명 검증 오류가 발생합니다.
+ 뷰어 요청에서 `Authorization` 헤더를 전달하려면 이 오리진 액세스 제어와 연결된 Lambda 함수 URL을 사용하는 모든 캐시 동작에 대한 `Authorization` 헤더를 [캐시 정책](controlling-the-cache-key.md)에 반드시** 추가해야 합니다.

## 예제 템플릿 코드
<a name="example-template-code-lambda-oac"></a>

CloudFront 오리진이 OAC와 연결된 Lambda 함수 URL인 경우 다음 Python 스크립트를 사용하여 `POST` 메서드로 Lambda 함수에 파일을 업로드할 수 있습니다.

이 코드는 기본 서명 동작이 **오리진 요청에 항상 서명**으로 설정된 OAC를 구성했고 **승인 헤더 재정의 안 함** 설정을 선택하지 않았다고 가정합니다.

이 구성을 통해 OAC는 Lambda 호스트 이름을 사용하여 Lambda로 SigV4 인증을 올바르게 관리할 수 있습니다. 페이로드는 `IAM_AUTH` 유형으로 지정된 Lambda 함수 URL에 대해 승인된 IAM 자격 증명의 SigV4를 사용하여 서명됩니다.

템플릿은 클라이언트 측의 `POST` 요청에 대해 x-amz-content-sha256 헤더에서 서명된 페이로드 해시 값을 처리하는 방법을 보여줍니다. 특히 이 템플릿은 양식 데이터 페이로드를 관리하도록 설계되었습니다. 템플릿은 CloudFront를 통해 Lambda 함수 URL에 대한 보안 파일 업로드를 활성화하고 AWS 인증 메커니즘을 사용하여 승인된 요청만 Lambda 함수에 액세스할 수 있도록 합니다.

**코드에 다음 기능이 포함됩니다.**  
x-amz-content-sha256 헤더에 페이로드 해시를 포함하기 위한 요구 사항을 충족합니다.
보안 AWS 서비스 액세스를 위해 SigV4 인증 사용
멀티파트 양식 데이터를 사용하여 파일 업로드 지원
요청 예외에 대한 오류 처리 포함

```
import boto3
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
import requests
import hashlib
import os


def calculate_body_hash(body):
    return hashlib.sha256(body).hexdigest()


def sign_request(request, credentials, region, service):
    sigv4 = SigV4Auth(credentials, service, region)
    sigv4.add_auth(request)


def upload_file_to_lambda(cloudfront_url, file_path, region):
    # AWS credentials
    session = boto3.Session()
    credentials = session.get_credentials()

    # Prepare the multipart form-data
    boundary = "------------------------boundary"

    # Read file content
    with open(file_path, 'rb') as file:
        file_content = file.read()

    # Get the filename from the path
    filename = os.path.basename(file_path)

    # Prepare the multipart body
    body = (
        f'--{boundary}\r\n'
        f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'
        f'Content-Type: application/octet-stream\r\n\r\n'
    ).encode('utf-8')
    body += file_content
    body += f'\r\n--{boundary}--\r\n'.encode('utf-8')

    # Calculate SHA256 hash of the entire body
    body_hash = calculate_body_hash(body)

    # Prepare headers
    headers = {
        'Content-Type': f'multipart/form-data; boundary={boundary}',
        'x-amz-content-sha256': body_hash
    }

    # Create the request
    request = AWSRequest(
        method='POST',
        url=cloudfront_url,
        data=body,
        headers=headers
    )

    # Sign the request
    sign_request(request, credentials, region, 'lambda')

    # Get the signed headers
    signed_headers = dict(request.headers)

    # Print request headers before sending
    print("Request Headers:")
    for header, value in signed_headers.items():
        print(f"{header}: {value}")

    try:
        # Send POST request with signed headers
        response = requests.post(
            cloudfront_url,
            data=body,
            headers=signed_headers
        )

        # Print response status and content
        print(f"\nStatus code: {response.status_code}")
        print("Response:", response.text)

        # Print response headers
        print("\nResponse Headers:")
        for header, value in response.headers.items():
            print(f"{header}: {value}")

    except requests.exceptions.RequestException as e:
        print(f"An error occurred: {e}")


# Usage
cloudfront_url = "https://d111111abcdef8.cloudfront.net"
file_path = r"filepath"
region = "us-east-1"  # example: "us-west-2"

upload_file_to_lambda(cloudfront_url, file_path, region)
```