

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 限制對 AWS Lambda 函數 URL 原始伺服器的存取
<a name="private-content-restricting-access-to-lambda"></a>

CloudFront 提供*原始存取控制* (OAC)，以限制對 Lambda 函數 URL 原始伺服器的存取。

**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` 方法，您的使用者必須運算內文的 SHA256，並在將請求傳送至 CloudFront 時，在 `x-amz-content-sha256` 標頭中包含請求內文的承載雜湊值。Lambda 不支援未簽署的承載。

**Topics**
+ [先決條件](#oac-prerequisites-lambda)
+ [授予 CloudFront 存取 Lambda 函數 URL 的許可](#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)。

### 授予 CloudFront 存取 Lambda 函數 URL 的許可
<a name="oac-permission-to-access-lambda"></a>

在您建立 OAC 或在 CloudFront 分佈中設定它之前，請確定 CloudFront 具有存取 Lambda 函數 URL 的許可。在建立 CloudFront 分佈之後，但在將 OAC 新增至分佈組態中的 Lambda 函數 URL 之前，請執行此作業。

**注意**  
若要更新 Lambda 函數 URL 的 IAM 政策，您必須使用 AWS Command Line Interface (AWS CLI)。目前不支援在 Lambda 主控台中編輯 IAM 政策。

下列 AWS CLI 命令會授予 CloudFront 服務主體 (`cloudfront.amazonaws.com`) 存取 Lambda 函數 URL 的權限。使用政策中的 `Condition` 元素，*僅*當請求代表包含 Lambda 函數 URL 的 CloudFront 分佈時，才允許 CloudFront 存取 Lambda。這是您要新增 OAC 的 Lambda 函數 URL 原始伺服器分佈。

**Example ： AWS CLI 命令更新政策，以允許啟用 OAC 之 CloudFront 分佈的唯讀存取**  
下列 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 管理主控台 ，並在 開啟 CloudFront 主控台[https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home)。

1. 於左側導覽窗格中，選擇 **Origin access** (原始存取)。

1. 選擇 **Create control setting** (建立控制設定)。

1. 在**建立新 OAC** 表單上，執行下列操作：

   1. 輸入 OAC 的**名稱**，(選用) 輸入**描述**。

   1. 於**簽署行為**中，建議您保留預設設定 (**簽署請求 (建議使用)**)。如需詳細資訊，請參閱[原始存取控制的進階設定](#oac-advanced-settings-lambda)。

1. 在**原始伺服器類型**中，選擇 **Lambda**。

1. 選擇**建立**。
**提示**  
建立 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. 在原始伺服器的**Protocol** (通訊協定) 選取 **HTTPS only** (僅限 HTTPS)。

1. 在**原始存取控制**下拉式功能表中，選擇您想要使用的 OAC 名稱。

1. 選擇**儲存變更**。

分佈開始部署至所有 CloudFront 邊緣節點。當邊緣節點接收到新組態時，其會簽署傳送至 Lambda 函數 URL 的所有請求。

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

若要使用 建立 OAC CloudFormation，請使用 `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` 值，您需要其將 OAC 新增至 CloudFront 分佈中的 Lambda 函數 URL。

**如要將 OAC 附加至現有分佈 (包含輸入檔案的 CLI) 中的 Lambda 函數 URL**

1. 使用下列命令來儲存您想要新增之 CloudFront 分佈的分佈組態。分佈必須有 Lambda 函數 URL 做為原始伺服器。

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

1. 開啟您剛才建立且命名為 `dist-config.yaml` 的檔案。編輯檔案，進行下列變更：
   + 於 `Origins` 物件中，將 OAC 的 ID 新增至名為 `OriginAccessControlId` 的欄位。
   + 從名為 `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 參考文件。

建立原始存取控制之後，您可以使用下列其中一個 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。此設定提供下列選項：

**永遠簽署原始請求 (建議設定)**  
我們建議使用此設定，於主控台中名為**Sign requests (recommended)** (簽署請求 (建議使用))，或於 API、CLI 和 CloudFormation中的 `always`。使用此設定時，CloudFront 一律會簽署傳送至 Lambda 函數 URL 的所有請求。

**絕不簽署原始伺服器請求**  
此設定於主控台中命名為 **Do not sign requests** (請勿簽署請求)，或 API、CLI 和 CloudFormation中的 `never`。使用此設定，關閉使用此 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`。如果您想要 CloudFront 僅於對應的檢視器請求不包含 `Authorization` 標題時簽署原始伺服器請求，請使用此設定。利用此設定，當檢視器請求存在時，CloudFront 會傳遞來自檢視器請求的 `Authorization` 標題，但在檢視器請求不包含 `Authorization` 標題時對原始伺服器請求進行簽名 (新增其自己的 `Authorization` 標題)。  
+ 如果您使用此設定，則必須為 Lambda 函數 URL 指定簽章第 4 版簽署，而不是 CloudFront 分佈的名稱或 CNAME。當 CloudFront 將 `Authorization` 標頭從檢視器請求轉送至 Lambda 函數 URL 時，Lambda 會根據 Lambda URL 網域的主機驗證簽署。如果簽署不是以 Lambda URL 網域為基礎，則簽署中的主機與 Lambda URL 原始伺服器所使用的主機不相符。這表示請求將會失敗，導致簽署驗證錯誤。
+ 如要從檢視器請求傳遞 `Authorization` 標題，您*必須*將 `Authorization` 標題新增至[快取政策](controlling-the-cache-key.md)中，適用於使用與此原始存取控制相關聯之 Lambda 函數 URL 的所有快取行為。

## 範本程式碼範例
<a name="example-template-code-lambda-oac"></a>

如果您的 CloudFront 原始伺服器是與 OAC 相關聯的 Lambda 函數 URL，您可以使用下列 Python 指令碼，透過 `POST` 方法將檔案上傳至 Lambda 函數。

此程式碼假設您已將預設簽署行為設定為**永遠簽署原始請求**的 OAC，且未選取**請勿覆寫授權標頭**設定。

此組態允許 OAC 使用 Lambda 主機名稱，以 Lambda 正確管理 SigV4 授權。承載是從 Lambda 函數 URL 授權的 IAM 身分使用 SigV4 簽署，該身分指定為 `IAM_AUTH` 類型。

範本示範如何處理來自用戶端 `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)
```