リソースベースポリシーで Lambda function URLs のアクセスを制御する

## はじめに
2022/4/6 から利用可能になった Lambda function URLs (関数 URL) はリクエストの認証タイプに `AWS_IAM` を選択できます。AWS_IAM では IAM エンティティのポリシーと AWS Lambda のリソースベースポリシーに基づいてリクエストが認証されます。

https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html

リソースベースポリシーによる制御パターンについていくつか検証してみました。

## 結論
### できたこと
* クロスアカウントの呼び出しを許可するポリシーステートメントの設定
* PrincipalOrgID を指定した、組織内からの呼び出しを許可するポリシーステートメントの設定
    * コンソールからは設定できないため、AWS CLI/SDK での AddPermission API の実行が必要

### できなかったこと
* [AWS グローバル条件コンテキストキー](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html) を使用した IP 制限などの細やかなアクセス制御
  * AWS Lambda のリソースベースポリシーは [AddPermission API](https://docs.aws.amazon.com/lambda/latest/dg/API_AddPermission.html) で設定するが、指定可能な要素が限られているため

## 検証の前提
今回は Lambda function URLs の 認証タイプは `AWS_IAM` に設定して検証しています。認証タイプに `AWS_IAM` を設定した場合、デフォルトではリソースベースポリシーは設定されません。同じアカウント内で `lambda:InvokeFunctionUrl` 権限を持つ IAM ユーザーまたはロールのみが関数 URL を呼び出せます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/27efaecf-2e5d-42aa-77c6-f1b60e69bea2.png)

AWS_IAM 認証によるリクエストを試すには curl 7.75.0 以降で利用可能な [--aws-sigv4 オプション](https://curl.se/docs/manpage.html#--aws-sigv4) がお手軽です。

```
$ curl -i https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws/ \
  -H "X-Amz-Security-Token: ${AWS_SESSION_TOKEN}" \
  --aws-sigv4 "aws:amz:ap-northeast-1:lambda" \
  --user "${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}"

HTTP/1.1 200 OK
Date: Fri, 15 Apr 2022 02:38:38 GMT
Content-Type: application/json
Content-Length: 20
Connection: keep-alive
x-amzn-RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
X-Amzn-Trace-Id: root=x-xxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx;sampled=0

"Hello from Lambda!"
```

### 参考: 認証タイプを `NONE` に設定した場合
以下のようなリソースベースポリシーが設定されます。このポリシーにより、関数 URL にパブリックにアクセスできる状態になります。

```json
    {
      "StatementId": "FunctionURLAllowPublicAccess",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "lambda:InvokeFunctionUrl",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:Lambda_Function_URLs_Policy_Test",
      "Condition": {
        "StringEquals": {
          "lambda:FunctionUrlAuthType": "NONE"
        }
      }
    }
```

## クロスアカウントでの呼び出しを許可する
コンソールの アクセス権限を追加 から関数 URL 用のポリシーステートメントを設定できます。Principal にクロスアカウントでの呼び出しを許可する IAM エンティティの ARN を入力します。許可するアクションは `lambda:InvokeFunctionUrl` 固定です。以下の例では アカウント ID `111111111111` に対して呼び出しを許可しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/04369d11-0b83-3439-7dc6-09877819dba2.png)


以下のようなポリシーが設定され、クロスアカウントでの呼び出しができます。

```json
    {
      "Sid": "x-account",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:root"
      },
      "Action": "lambda:InvokeFunctionUrl",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:Lambda_Function_URLs_Policy_Test",
      "Condition": {
        "StringEquals": {
          "lambda:FunctionUrlAuthType": "AWS_IAM"
        }
      }
    }
```

この方式の場合、許可する IAM エンティティごとにポリシーを追加する必要があります。

## 組織内からの呼び出しを許可する
PrincipalOrgID を指定し、組織内の AWS アカウントの任意の IAM エンティティからの呼び出しを許可する例です。
以下の理由からコンソールからは設定できませんでした。

* 関数 URL のステートメント作成 UI では `PrincipalOrgID` を指定できない
* AWS アカウントのステートメント作成 UI では、アクションに `lambda:InvokeFunctionUrl` を選択できない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/f4387155-29f0-b1d8-4396-3f2f7116cd67.png)

AWS CLI を使用して AddPermission API を叩いた場合は設定できました。

```
$ aws lambda add-permission \
  --function-name Lambda_Function_URLs_Policy_Test \
  --function-url-auth-type AWS_IAM \
  --statement-id PrincipalOrgIDExample \
  --principal "*" \
  --action lambda:InvokeFunctionUrl  
  --principal-org-id o-a1b2c3e4d5
```
以下のようなリソースベースポリシーがアタッチされます。

```json
    {
      "Sid": "PrincipalOrgIDExample",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "lambda:InvokeFunctionUrl",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:Lambda_Function_URLs_Policy_Test",
      "Condition": {
        "StringEquals": {
          "lambda:FunctionUrlAuthType": "AWS_IAM",
          "aws:PrincipalOrgID": "o-a1b2c3e4d5"
        }
      }
    }
```


## Condition 要素を指定した細やかな制御はできない
AWS Lambda のリソースベースポリシーは AddPermission API で設定しますが、前述したとおり指定可能な要素が限られています。そのため、例えば `"Condition": {"IpAddress": {"aws:SourceIp": "xxx.xxx.xxx.xxx/24"}}` のように IP アドレス制限などを追加することは現状できません。

仮に IP アドレス制限を実装したい場合は、関数 URL から渡ってくる Event データの中に含まれる sorceIp から独自に実装する必要があります。

```json
{
  "version": "2.0",
  "routeKey": "$default",
  "rawPath": "/my/path",
  "rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value",
  "cookies": [
    "cookie1",
    "cookie2"
  ],
  "headers": {
    "header1": "value1",
    "header2": "value1,value2"
  },
  "queryStringParameters": {
    "parameter1": "value1,value2",
    "parameter2": "value"
  },
  "requestContext": {
    "accountId": "123456789012",
    "apiId": "<urlid>",
    "authentication": null,
    "authorizer": {
        "iam": {
                "accessKey": "AKIA...",
                "accountId": "111122223333",
                "callerId": "AIDA...",
                "cognitoIdentity": null,
                "principalOrgId": null,
                "userArn": "arn:aws:iam::111122223333:user/example-user",
                "userId": "AIDA..."
        }
    },
    "domainName": "<url-id>.lambda-url.us-west-2.on.aws",
    "domainPrefix": "<url-id>",
    "http": {
      "method": "POST",
      "path": "/my/path",
      "protocol": "HTTP/1.1",
      "sourceIp": "123.123.123.123",
      "userAgent": "agent"
    },
    "requestId": "id",
    "routeKey": "$default",
    "stage": "$default",
    "time": "12/Mar/2020:19:03:58 +0000",
    "timeEpoch": 1583348638390
  },
  "body": "Hello from client!",
  "pathParameters": null,
  "isBase64Encoded": false,
  "stageVariables": null
}

```

こちらは @_kensh さんから Twiiter でコメントいただきました。ありがとうございます。

https://twitter.com/_kensh/status/1514190037189292033

## 参考
https://docs.aws.amazon.com/lambda/latest/dg/API_AddPermission.html

https://speakerdeck.com/_kensh/aws-lambda-function-urls

以上です。
参考になれば幸いです。