非VPC LambdaでIP制限されたS3バケットにアクセスしたい

2019.08.22

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

以下のように特定 IP アドレスのみにアクセス許可している S3 バケットに対して、非 VPC Lambda で GetObject したい場合にどうしたら良いでしょうか?

(前提としては各 IAM ロールで S3 への Allow 権限は与えています。)

IP 制限されたバケットポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<BUCKET-NAME>/*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "59.xx.xx.xx/32"
                }
            }
        }
    ]
}

VPC Lambda を使えばアクセス元が限定できるので、「VPC エンドポイント ID で許可」したり、「NATGW の EIP で許可」ということも可能ですが、VPC Lambda を使わずにという要件です。

VPC Lambda は必ずしも起動が遅いということはありませんが、今回は「ENI 作成あり」のコールドスタートの遅延が許容できない、そんな想定です。

VPC Lambda のコールドスタートについては、以下、大阪オフィス岩田の考察がとても参考になります。

VPC Lambdaのコールドスタートにお悩みの方へ捧ぐコールドスタート予防のハック Lambdaを定期実行するならメモリの割り当ては1600Mがオススメ?!

やってみる

aws:SourceArn で出来るだろうか?

最初は aws:SourceArn を使って、 ConditionAND 条件で出来ないか、と以下のようなバケットポリシーで試したのですが aws:SourceArn 指定は期待した動作にはなりませんでした。

注)これは動作しないバケットポリシーです

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<BUCKET-NAME>/*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "59.xx.xx.xx/32"
                },
                "ArnNotEquals": {
                    "aws:SourceArn": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:s3-get-test"
                }
            }
        }
    ]
}

このあたりの条件キーは公式ガイドを見ても、どのサービスが、どの条件キーを使えるのか一目では判断できないのがツライです。以下、aws:SourceArn の説明ですが

aws:SourceArn ARN 演算子で動作します。 ソースの Amazon リソースネーム (ARN) を使用して、リクエストのソースを確認します。 この条件キーは一部のサービスでのみ利用可能です。

俺は「その、一部のサービスとやらを知りたいんだよ!」と声をあげたくなりますね。どこかに良いまとめがあれば教えてください。

NotPrincipal を使う

一旦、動作が確認できた方法としては NotPrincipal で Lambda に割り当てている IAM ロールを Deny から除外しました。

lambda_execution_role は Lambda 関数 s3-get-objec に割り当てた IAM ロールになります。NotPrincipal を利用する場合のポイントは、該当の IAM ロール以外にも assumed-role なども含めて指定する必要があります。

特定の IAM ロールを Deny から除外するバケットポリシー

{
    "Version": "2008-10-17",
    "Id": "PolicyDemo",
    "Statement": [
        {
            "Effect": "Deny",
            "NotPrincipal": {
                "AWS": [
                    "arn:aws:iam::xxxxxxxxxxxx:role/lambda_execution_role",
                    "arn:aws:sts::xxxxxxxxxxxx:assumed-role/lambda_execution_role/s3-get-object"
                ]
            },
            "Action": "s3:GetObject",
            "Resource": [
                "arn:aws:s3:::<BUCKET-NAME>/*"
            ],
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "59.xx.xx.xx/32"
                }
            }
        }
    ]
}
通信元 GetObject
59.xx.xx.xx/32 成功
58.xx.xx.xx/32 失敗
Lambda(s3-get-object) 成功

この場合、該当の IAM ロールを使えば Lambda 以外からでも IP 制限を逃れることが出来てしまうので、「Lambda から実行された場合」を担保するには、IAM ロールの信頼関係などの編集権限は適切に管理されていることが前提になります。

さいごに

セキュリティに厳しい環境だと、IP 制限された S3 バケットはよくある構成だと思いますが、VPC Lambda のコールドスタート(ENI作成あり)時間が許容できない場合、このような方法で Lambda のみを IP 制限から解放するという案をご紹介しました。

これだけが解決方法ではないと思いますので、他にもっと良い方法などあれば教えてください!

以上!大阪オフィスの丸毛(@marumo1981)でした!