

# 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 を使用するには、`AuthType` パラメータの値として `AWS_IAM` を指定する必要があります。詳細については、「[Lambda 関数 URL を使用する](DownloadDistS3AndCustomOrigins.md#concept_lambda_function_url)」を参照してください。

### Lambda 関数 URL へのアクセス許可を CloudFront に付与する
<a name="oac-permission-to-access-lambda"></a>

CloudFront ディストリビューションで OAC を作成または設定する前に、Lambda 関数 URL へのアクセス許可が CloudFront にあることを確認します。これは、CloudFront ディストリビューションを作成した後で、ディストリビューション設定で Lambda 関数 URL に OAC を追加する前に行います。

**注記**  
Lambda 関数 URL の IAM ポリシーを更新するには、AWS Command Line Interface (AWS CLI) を使用する必要があります。現時点では、Lambda コンソールでの IAM ポリシーの編集はサポートされていません。

次の AWS CLI コマンドは、CloudFront サービスプリンシパル (`cloudfront.amazonaws.com`) に Lambda 関数 URL へのアクセスを許可します。ポリシーの `Condition` 要素は、Lambda 関数 URL を含む CloudFront ディストリビューションを対象とするリクエストに限り、Lambda へのアクセス許可を CloudFront に付与します。**これは、OAC を追加する Lambda 関数 URL オリジンを持つディストリビューションです。

**Example : OAC が有効になっている CloudFront ディストリビューションへの読み取り専用アクセスを許可するようにポリシーを更新する 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 コンソールで **[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 マネジメントコンソール、CloudFormation、AWS CLI、または CloudFront API を使用できます。

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

**OAC を作成するには**

1. AWS マネジメントコンソールにサインインし、[https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home) で CloudFront コンソールを開きます。

1. ナビゲーションペインで、[**オリジンアクセス**] を選択します。

1. [**コントロール設定を作成**] を選択します。

1. **[新しい OAC を作成]** フォームで、次の操作を実行します。

   1. OAC の**名前**とオプションの**説明**を入力します。

   1. **[署名動作]** は、デフォルト設定の **[署名リクエスト (推奨)]** のままにすることをお勧めします。詳細については、「[オリジンアクセスコントロールの詳細設定](#oac-advanced-settings-lambda)」を参照してください。

1. **[オリジンタイプ]** で、**[Lambda]** を選択します。

1. **[作成]** を選択します。
**ヒント**  
OAC を作成したら、**名前**を書き留めておきます。次の手順では、この操作を行う必要があります。

**ディストリビューションの Lambda 関数 URL にオリジンアクセスコントロールを追加するには**

1. CloudFront コンソール ([https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home)) を開きます。

1. OAC を追加する先の Lambda 関数 URL があるディストリビューションを選択し、**[オリジン]** タブを選択します。

1. OAC を追加する先の Lambda 関数 URL を選択し、**[編集]** を選択します。

1. オリジンの **[Protocol]** (プロトコル) として **[HTTPS only]** (HTTPS のみ) を選択します。

1. **[オリジンアクセスコントロール]** ドロップダウンから、使用する OAC 名を選択します。

1. **[Save changes]** (変更の保存) をクリックします。

ディストリビューションは、すべての CloudFront エッジロケーションへのデプロイを開始します。エッジロケーションは、新しい設定を受け取ると、Lambda 関数 URL に送信するすべてのリクエストに署名します。

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

CloudFormation で OAC を作成するには、`AWS::CloudFront::OriginAccessControl` リソースタイプを使用します。次の例は、OAC を作成するための CloudFormation テンプレート構文を YAML 形式で示しています。

```
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 を追加するために必要です。

**既存のディストリビューションの Lambda 関数 URL に OAC をアタッチするには (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 に OAC をアタッチできます。
+ 既存のディストリビューションにアタッチするには、[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) という名前の設定が含まれています。この設定では、次のオプションを使用できます。

**オリジンリクエストに常に署名する (推奨設定)**  
コンソールの [**Sign requests (recommended)**] (署名リクエスト (推奨))、または API、CLI、および CloudFormation の `always` という設定を使用することをお勧めします。この設定の場合、CloudFront は Lambda 関数 URL に送信するすべてのリクエストに常に署名します。

**オリジンリクエストに署名しない**  
この設定は、コンソールでは [**リクエストに署名しない**]、または API、CLI、およびCloudFormation では `never` です。この設定を使用して、この OAC を使用するすべてのディストリビューションですべてのオリジンの OAC をオフにします。OAC を使用するすべてのオリジンとディストリビューションから OAC を 1 つずつ削除する場合と比べて、この設定で時間と労力を節約できます。この設定の場合、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` ヘッダーを上書きしない**  
この設定は、コンソールでは [**認可ヘッダーを上書きしない**] で、API、CLI、および CloudFormation では `no-override` です。この設定は、対応するビューワーリクエストに `Authorization` ヘッダーに含まれていない場合にのみ、CloudFront がオリジンリクエストに署名するよう指定する場合に使用します。この設定では、ビューワーリクエストが存在するが、ビューワーリクエストに `Authorization` ヘッダーが含まれていないときにオリジンリクエストに署名する (独自の `Authorization` ヘッダーを追加) ときに、CloudFront はビューワーリクエストから `Authorization` ヘッダーを渡します。  
+ この設定を使用する場合は、CloudFront ディストリビューションの名前または CNAME の代わりに Lambda 関数 URL の署名バージョン 4 の署名を指定する必要があります。CloudFront がビューワーリクエストから Lambda 関数 URL に `Authorization` ヘッダーを転送すると、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 ヘッダーにペイロードハッシュを含めるという要件に対応
SigV4 認証を使用して安全な AWS のサービス アクセスを実現
マルチパートフォームデータを使用したファイルアップロードをサポート
リクエスト例外のエラー処理に対応

```
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)
```