S3ObjectLambdaアクセスポイントのトラブル集（裏仕様）

# はじめに
S3ObjectLambdaアクセスポイントを使って出たトラブルとその対応をまとめました。（S3アクセスポイント含む）
* S3ObjectLambdaアクセスポイントポリシーで利用できないポリシー要素
* S3ObjectLambdaアクセスポイントポリシーで利用できないアクション
* S3アクセスポイントポリシーで利用できないアクション
* HTTPステータス302を返せない

# 前提構成
CloudFront（OAC）からS3ObjectLambdaをオリジンに指定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2741587/bb89c398-febc-d9f7-a211-54b95bf9fdf1.png)

# S3アクセスポイントとは
S3アクセスポイントは、S3バケットにアクセスするための追加の入口です。
これは、バケットにアタッチされたネットワークエンドポイントで、S3オブジェクトの操作（GetObjectやPutObjectなど）を実行するために使用します。
アクセスポイントには、専用のリソースベースポリシー「アクセスポイントポリシー」があるためアクセスポイント毎に専用のアクセスコントロールを行う事が出来ます。
これによりバケットポリシーでの大規模なアクセス管理を分割・簡素化することができ、セキュリティが向上します。

# S3ObjectLambdaアクセスポイントとは
S3ObjectLambdaアクセスポイントは、S3に対するGETリクエストをトリガーにLambda関数を起動し、データがアプリケーションに返されるときにデータの内容を変更および処理できる機能です。
これを使用するには、既存のS3アクセスポイントの前段にObjectLambdaアクセスポイントを追加します。これにより、データの取得と同時にデータの変換や処理が可能となります。
リクエストを行う際はバケット名ではなく、ObjectLambdaアクセスポイントを指定する必要があります。

# トラブル1：S3ObjectLambdaアクセスポイントポリシーで利用できないポリシー要素
- 事象
    - 最小権限の原則に従い、特定のアクセスのみを明示許可、それ以外のアクセスを明示拒否をS3ObjectLambdaアクセスポイントポリシーで設定しようとしたところ、エラー発生。
        ```yaml:アクセスポイントポリシー抜粋
        - Effect: Allow
          Principal: 
            Service: cloudfront.amazonaws.com
          Action:
            - s3-object-lambda:GetObject
          Resource: !Sub "arn:aws:s3-object-lambda:${AWS::Region}:${AWS::AccountId}:accesspoint/${ObjectLambdaAccessPoint}"
          Condition: 
            StringEquals: 
              aws:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${DistributionId}"
        - Effect: Deny
          Principal: 
            Service: cloudfront.amazonaws.com
          NotAction:
            - s3-object-lambda:GetObject
          Resource: !Sub "arn:aws:s3-object-lambda:${AWS::Region}:${AWS::AccountId}:accesspoint/${ObjectLambdaAccessPoint}"
          Condition: 
            StringEquals: 
              aws:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${DistributionId}"
        ```
    - CloudFormationで作成時のエラーメッセージ
        ```
        ObjectLambdaAccessPointPolicy CREATE_FAILED
        Resource handler returned message: "Policy document is malformed (Service: S3Control, Status Code: 400, Request ID: **REDACTED**, Extended Request ID: **REDACTED**)" (RequestToken: **REDACTED**, HandlerErrorCode: InvalidRequest)
        ```
    - マネジメントコンソールのポリシーエディタで設定した際のエラーメッセージ
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2741587/85896370-c923-8557-e698-d233c0bb1076.png)
    - どちらも「ポリシーが不正」とだけ。CloudTrailログも同様でした。
- 原因
    - s3objectlambdaアクセスポイントのアクセスポイントポリシーでは下記のポリシー要素が利用できないようです
        - NotAction
        - NotResource
        - NotPrincipal
    - 公式ドキュメントに記載は無くAWSサポートで確認（2023/11/14時点）
- 対応
    - NotActionを利用せず、GetObject以外の全てのアクションを明示的に列挙してDenyしました。

# トラブル2：S3ObjectLambdaアクセスポイントポリシーで利用できないアクション
- 事象
    - 上記トラブル1の対応で「GetObject以外の全てのアクション」をポリシーに全て列挙したところエラーが発生
    - エラーメッセージも同じ「Policy document is malformed」で原因わからず
- 原因
    - 記載するアクションを減らして切り分けしたところ、s3objectlambdaのアクセスポイントポリシーではアクション「WriteGetObjectResponse」を記載できないことが判明
    - こちらも公式ドキュメントに記載は無くAWSサポートで確認（2023/11/14時点）
        - アイデンティティベースポリシーでのみ記載できそうな近しい記述はありました。
        - 公式ドキュメント：[S3ユーザーガイド](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/olap-policies.html#olap-execution-role)
        > Object Lambda アクセスポイントに GET リクエストが行われた場合、Lambda 関数には、S3 Object Lambda にデータを送信する許可が必要です。このアクセス許可は、Lambda 関数の実行ロールで s3-object-lambda:WriteGetObjectResponse 許可を有効にすることで提供されます。新しい実行ロールを使作成することも、既存のロールを更新することもできます。
- 対応
    - しょうがないのでWriteGetObjectResponseだけはポリシーに記載記載しませんでした。（暗黙拒否）

# トラブル3：S3アクセスポイントポリシーで利用できないアクション
- 事象
    - 最小権限の原則に従い、特定のアクセスのみを明示許可、それ以外のアクセスを明示拒否をS3アクセスポイントポリシーで設定しようとしたところ、エラー発生。
        ```yaml:アクセスポイントポリシー抜粋
        - Effect: Allow
          Principal: 
            AWS: !Split [',', !Sub '${AdministrativeRoleArn}']
          Action:
            - s3:CreateAccessPoint
            - s3:DeleteAccessPoint
            - s3:DeleteAccessPointPolicy
            - s3:GetAccessPointPolicy
            - s3:GetAccessPointPolicyStatus
            - s3:PutAccessPointPolicy
          Resource:
            - !Sub "arn:aws:s3:${AWS::Region}:${AWS::AccountId}:accesspoint/from-cloudfront-ap-${SystemName}"
            - !Sub "arn:aws:s3:${AWS::Region}:${AWS::AccountId}:accesspoint/from-cloudfront-ap-${SystemName}/object/*"
        ```
    - CloudFormationで作成時のエラーメッセージ
        ```
        Resource handler returned message: "Policy has invalid action (Service: S3Control, Status Code: 400, Request ID: **REDACTED**, Extended Request ID: **REDACTED**)" (RequestToken: **REDACTED**, HandlerErrorCode: GeneralServiceException)
        ```
- 原因
    - S3アクセスポイントはS3のオブジェクトに対するアクションのみ実行可能
        - 公式ドキュメント：[S3ユーザーガイド](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/access-points.html)
        >アクセスポイントは、オブジェクトに対するオペレーションの実行にのみ使用できます。アクセスポイントを使用して、バケットの変更や削除など、他の Amazon S3 オペレーションを実行することはできません。
    - 公式ドキュメント：[Amazon S3 で定義されるアクション](https://docs.aws.amazon.com/ja_jp/service-authorization/latest/reference/list_amazons3.html#amazons3-actions-as-permissions) のうち、リソースタイプが「object*」となっている33件のアクションのみが実行可能
   - 従って対象「accesspoint*」に対するアクションは定義できない
 - 対応
    - リソースタイプが「object*」となっている33件のアクションのみをアクセスポイントポリシーで定義

# トラブル4：HTTPステータス302を返せない
- 事象
    - S3ObjectLambdaアクセスポイントを使って、HTTPリダイレクトをしたく、lambdaでステータス「302」、レスポンスヘッダにlocationを設定しよう試みるも、エラー発生。
        ```Python:lambdaコード抜粋
            s3.write_get_object_response(
                Body=html,
                RequestRoute=request_route,
                RequestToken=request_token,
                StatusCode=302,
                ContentType='text/html',
                Headers={'Location': redirect_location}
            )
        ```
    - エラーメッセージ
        ```:lambdaログ
        [ERROR] ParamValidationError: Parameter validation failed:
        Unknown parameter in input: ""Headers"", must be one of: RequestRoute, RequestToken, Body, StatusCode, ErrorCode, ErrorMessage, AcceptRanges, CacheControl, ContentDisposition, ContentEncoding, ContentLanguage, ContentLength, ContentRange, ContentType, ChecksumCRC32, ChecksumCRC32C, ChecksumSHA1, ChecksumSHA256, DeleteMarker, ETag, Expires, Expiration, LastModified, MissingMeta, Metadata, ObjectLockMode, ObjectLockLegalHoldStatus, ObjectLockRetainUntilDate, PartsCount, ReplicationStatus, RequestCharged, Restore, ServerSideEncryption, SSECustomerAlgorithm, SSEKMSKeyId, SSECustomerKeyMD5, StorageClass, TagCount, VersionId, BucketKeyEnabled
        Traceback (most recent call last):
          File ""/var/task/index.py"", line 54, in lambda_handler
            s3.write_get_object_response(
          File ""/var/runtime/botocore/client.py"", line 530, in _api_call
            return self._make_api_call(operation_name, kwargs)
          File ""/var/runtime/botocore/client.py"", line 919, in _make_api_call
            request_dict = self._convert_to_request_dict(
          File ""/var/runtime/botocore/client.py"", line 990, in _convert_to_request_dict
            request_dict = self._serializer.serialize_to_request(
          File ""/var/runtime/botocore/validate.py"", line 381, in serialize_to_request
            raise ParamValidationError(report=report.generate_report())
      ```
- 原因
    - エラーメッセージは「Headers」がパラメータとして無効であることが直接原因。
        - （ちゃんとドキュメントを読みましょうということで反省）
    - S3ObjectLambdaアクセスポイントでレスポンスヘッダへ設定できる情報には制限がある
        - 自由レスポンスヘッダに設定できる関数は用意されていない
        - 返却できるステータスは限られており、「302」は返却できない。
        - サポートされるステータス：200/206/304/400/401/403/404/405/409/411/412/416/500/503 
        - 公式ドキュメント：[S3ユーザーガイド](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/write_get_object_response.html)
- 対応
    - S3ObjectLambdaアクセスポイントでのレスポンスヘッダ設定はあきらめ、下記で代替
        - 案1:Lambda@Edgeでレスポンスヘッダに設定する
        - 案2:CloudFront Functionsでレスポンスヘッダに設定する（こちらで対応しました）



# まとめ
今回は私が遭遇したトラブルのご紹介でした。
ドキュメントに記載が無くトライアンドエラーになるケースは多いかと思います。
アクセスポイントポリシーを詳細に設定するケースは少ないかと思いますが、もし同じようなエラーに遭遇したかたの参考になれれば幸いです。
追加で何か発生したら追記します。