• 作成:

AWS CDKでAmazon API GatewayにシンプルにIP制限をかける方法

API Gatewayに雑にIP制限したい

とあるOSSをHTTP経由で利用するDockerイメージを書いたのですが、よく分からないですがdocker buildに失敗するらしいです。私含めメンバー3人中2人は成功して、 LinuxとWindows(WSL, Hyper-VのVirtualBox)は成功して、 Macだと失敗するみたいですね。 Macのメモリが16GBしかないのが悪いのか何なのか…

まあ元々このコンポーネントはメモリとか結構食いそうなので、 AWSとかに置いて初期化しなくても利用できるようにする予定でした。向こうの環境だと失敗するのは別に良いでしょう、さっさとAWS Lambdaにデプロイしてしまいましょう。

それでAWS Lambdaは従量課金で、それなりにCPUバウンド(GPUも使った方が早いんだろうけどそこまでの速度は求められていない)な処理なので、外部にもノーガードで公開するのは流石に気が引けますね。なので社内VPNからしかアクセスできないようにIP制限を行いたいわけです。

セキュリティ的にはIP制限に頼るのは良くない(IPは偽装出来たりしますし、VPNに認証を依存するのも微妙)のですが、今回は私が書いたコードはFastAPIで50行以下のライブラリを呼び出すだけのコードで、機密情報など一切ない、いっそのこと別にOSSとして公開しても構わないレベルのものなので、もし不正侵入されても漏れるデータは殆ど無く(どのOSSを使ってるか分かるだけ?)、 Lambda関数の使う料金も天井が存在するのでリスクは低いと判断しました。 AWSのコストは毎日Slackで監視してますしね。

WAFはオーバーキルっぽい

まず適当にググって出てきた方法である、 AWS CDKでWAFv2を構築しIPアドレス制限を試してみた | DevelopersIO を参考にしてみたのですが、うまく行きませんでした。

CfnWebACLAssociationresourceArnに入れる値が導出できませんね。参考記事ではある程度固定のテンプレート文字列に入れてますが、そういうことはしたくない。

もうちょっと考えればARNを取得する方法分かるのかもしれませんが、 AWS CDK上ではWAFのハイレベルな抽象レイヤーは存在しませんし、 IP制限するだけでこれをちまちま定義していくのはだるい。他に方法があればそれを使いたい。

API Gatewayのリソースポリシー単体で制限できるようになっていたらしい

他に方法がありました。 IP制限だけならAPI Gatewayのリソース設定だけで行けるようですね。

API Gateway v2を諦める

CDKのドキュメントを読んだのですが、 policyを設定する所が見つかりませんでした。

頑張れば見つかるかもしれませんが、まだAWS CDK上ではAPI Gateway v2のハイレベル抽象レイヤーはまだexperimentalなので、そんなに頑張りたくないですね。

今回はWebSocketとか使う予定ないですし、おとなしくstableのAPI Gateway v1を使いましょう。

ポリシーを設定する

    // Lambdaは従量課金なのでVPN経由からしか使えないようにします
    const policy = new iam.PolicyDocument();
    const allowPolicyStatement = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
    });
    allowPolicyStatement.addAnyPrincipal();
    allowPolicyStatement.addActions("execute-api:Invoke");
    allowPolicyStatement.addResources("execute-api:/*/*/*");
    const denyPolicyStatement = new iam.PolicyStatement({
      effect: iam.Effect.DENY,
    });
    denyPolicyStatement.addAnyPrincipal();
    denyPolicyStatement.addActions("execute-api:Invoke");
    denyPolicyStatement.addResources("execute-api:/*/*/*");
    denyPolicyStatement.addCondition("NotIpAddress", {
      "aws:SourceIp": ["社内VPNのIPアドレス"],
    });
    policy.addStatements(allowPolicyStatement, denyPolicyStatement);

コンストラクタではなくてメソッドで色々設定する必要があって見栄えが悪いですが、これで動かせるようです。

これをapigateway.LambdaRestApiのpropsに渡してみます。

制限が働いているのを確認する

VPN接続が切れているのを確認してcurlでAPIのエンドポイントを叩いてみるとちゃんと拒否されました。

{
  "Message": "User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:ap-northeast-1:デプロイしたAPIのURL with an explicit deny"
}