1つのLambda Functionを大量のAWSアカウントから使いたい

共有 Lambda Function を作ろうとしたら いろいろな上限に引っかかったときの話。
回避策はいろいろありますが特に結論的なものは出してないです。

## やりたいこと

ある AWS アカウント（Provider Account と仮称）にある Lamda Function を 100 以上ある AWS アカウント（Client Account と仮称）から叩きたい

## 案 1: SNS\*n -> Lambda Function

![sns_to_lambda.png](https://qiita-image-store.s3.amazonaws.com/0/135041/43417602-595e-bd46-5927-7e5eb47ec2df.png)

各アカウントの SNS から直に Lambda を呼んでみます。

クロスアカウントによる Lambda の呼び出しとなるため、トリガーとなる SNS に Subscribe するのは Provider Account から API/CLI を実行する必要がある。SNS を所有している Client Account からは Subscribe できない。

Provider Account から Subscribe してもらうために、まず Client Account の SNS のアクセスポリシーを変更する必要があります。

※全アカウントに対して処理しようと思ってたので CLI ではなく python で書いてます

```python
# Client Accont
client = boto3.client('sns')
client.add_permission(
    TopicArn=client_account_sns_topic_arn,
    Label=f'{provider_account_id}-access',
    AWSAccountId=[
        provider_account_id,
    ],
    ActionName=[
        'Subscribe',
        'Receive'
    ]
)
```

アクセスポリシーの変更ができたら Provider Account から Subscribe できます。

```python
# Provider Account
client = boto3.client('sns')
client.subscribe(
    TopicArn=client_account_sns_topic_arn,
    Protocol='lambda',
    Endpoint=provider_account_lambda_arn
)
```

最後に Client Account の SNS から Lambda を Invoke できるように権限を変更します。

```python
# Provider Account
client = boto3.client('lambda')
client.add_permission(
    FunctionName=provider_account_lambda_arn,
    StatementId=f'{client_account_id}-trriger',
    Action='lambda:InvokeFunction',
    Principal='sns.amazonaws.com',
    SourceArn=client_account_sns_topic_arn
)
```

あとはこれを必要アカウント分回せば設定が完了。
と簡単にいけばよかったんですが。

### Lambda の上限に引っかかる

アカウント増やしていったらエラーが起きました。

> An error occurred (PolicyLengthExceededException) when calling the AddPermission operation: The final policy size (20596) is bigger than the limit (20480).

[AWS Lambda Limits \- AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/limits.html)

> Function resource-based policy 20 KB

add_permission で設定できるリソースにサイズ上限がありました。
トピック名にもよりますが今回は `62個目` で 20KB を超えました。

## 案 2: SNS\*n -> SQS -> Lambda Function

![sns_to_sqs_to_lambda.png](https://qiita-image-store.s3.amazonaws.com/0/135041/f76d8c08-22c2-3d6f-f27a-46f966b5a4b9.png)

Lambda で直に受けるのはきついので間に SQS を用意しました。

各アカウントからの SQS SendMessage を許可するためアクセスポリシーを更新します。

```python
# Provider Account
client = boto3.client('sqs')
client.add_permission(
    QueueUrl=provider_sqs_url,
    Label="my-organization-accounts",
    AWSAccountIds=[
        client_account_id1, client_account_id2, client_account_id3, ...
    ],
    Actions=[
        'SendMessage',
    ]
)
```

あとは SQS を Lambda のトリガーとして登録すればいけると思ったんですが、また上限に引っかかりました。

### SQS の上限に引っかかる

[Amazon SQS Limits \- Amazon Simple Queue Service](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-limits.html)

> Principals 50

Principals に設定できるのが `50` までなので、50AWS アカウントまでしか 1 つの SQS で許可することができない。

## 案 3: any account & any resource -> Lambda Function

ここまでダメなら妥協して上限にかからないように横に並べたり、 `SNS*n -> API Gateway -> Lambda Function` として解決しようかと思ったんですが、そこはあまりやりたくないなという同じ想いをもった方がいました。

[Public & Cross\-account Functions on AWS Lambda – IOpipe Blog](https://read.iopipe.com/public-cross-account-functions-on-aws-lambda-bcc148303083)

Lambda アクセスポリシーの Principal に `*` を指定してすべてのリソースに権限を与える案。
まさに Public Functions。

```python
# Provider Account
client = boto3.client('lambda')
client .add_permission(
    FunctionName=provider_account_lambda_arn,
    StatementId='public-access',
    Action='lambda:InvokeFunction',
    Principal='*'
)
```

これで解決！といいたいところだけど、この設定は本当に大丈夫なのか？

### Public Functions にしたら危険なのでは？

- AWS アカウント ID
- Lambda Function 名

この 2 つがバレると Lambda を叩かれてしまいます。
ただ SNS Message の中身を validation していれば不正なデータ入力などはある程度防げます。

叩かれすぎてクラウド死になりそうなら Function 名の変更で凌ぐのが妥当でしょうか。
その場合 Client Account の Subscribe をやり直す必要があるため、そのあたりの処理を自動化できるようにしておいたほうがいいですね。

最悪バレても被害が起きそうにない処理であれば制限なしでもいいのかもしれません。

### 余談

そういえば CloudTrail のログ集約も似たような設計ですね。
Bucket Policy の Principal にアカウント ID や Arn を指定しているわけではなくサービス名しか指定していないので（一度 AWS 所有アカウントを中継？する形で来るため）、Bucket 名がバレたら誰でも CloudTrail ログを送ってくる可能性があります。
最初は気になってましたが今はもう気にしてないですし、そういうものとして受け入れるものなんでしょうかね。