Amazon API Gateway を利用してHTTPメソッドを変換してみた

こんにちは、AWS事業本部の荒平(@0Air)です。

最近、一部のHTTPメソッドが使えないため違うメソッドに変換できないか、との相談を受けました。
様々な方式が挙げられますが、本エントリでは、API Gatewayを用いてHTTPメソッドの変換を実施してみたので紹介します。

構成図

今回作成した構成はこちらです。
クライアントはAPI Gatewayにアクセスし、ALBを通じてFargateへリクエストを投げます。

実施手順

(1) 下準備

リクエストを受け付けるためのコンテナを作成します。今回はFlask + ECS on Fargateの構成としました。
既にリクエスト送信先の環境がある場合はスキップしてください。

サンプルのファイル構成は以下の通りです。

  • apps.py (Flaskアプリ)
  • requirements.txt (pipインストール用)
  • Dockerfile

apps.py

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'])
def handle_request():
    return jsonify({
        'method': request.method,
        'headers': dict(request.headers),
        'body': request.get_json(silent=True)
    }), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)

requirements.txt

Flask==2.3.2

Dockerfile

FROM amd64/python:3.8-alpine

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

COPY . .

CMD [ "python", "/app/apps.py" ]

Dockerイメージをビルドして、ECRへプッシュします。(以下はコマンド例)

aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin xxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -t {tag-name} . --platform amd64
docker tag {tag-name}:latest xxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/{repository-name}:latest
docker push xxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/{repository-name}:latest

次に、ECS on FargateをALB込みのサービスとして起動します。
ここでは詳細は割愛しますが、以下の記事を参考に、作成したDockerイメージを起動します。

ALB経由でサービスにアクセスできることを確認します。
ALBを経由しているため、X-Forwarded-For にて自端末のIPが表示されます。また、HTTPメソッドは、Webブラウザからアクセスした場合はGETが表示されました。

curl -X PUT {target}コマンドでPUTを投げてみると、しっかりPUTとして受け付けていることが分かります。

(2) API Gatewayの設定

AWSコンソールにて、API Gatewayを表示し、REST APIを[構築]します。

プロトコルは[REST]を選択し、[新しいAPI]にて適当なAPI名を入力します。
[APIの作成]から次の画面に進みます。

[アクション]から、[メソッドの作成]をクリックしてプルダウンから受け付けるHTTPメソッドを選択します。

今回の例では、POSTPATCH に変換するため、受け付けるHTTPメソッドとしてPOSTを選択しました。

統合タイプを[HTTP]として、HTTPメソッドには変換後(この例ではPATCH)を選択します。
エンドポイントURLには、今回ALBのDNS名を入力しました。

(3) テストの実行

設定したメソッドをクリックすると、以下の概念図のような画面が表示されます。
[テスト]ボタンを押して、リクエストを発出してみます。

レスポンス内容とログが表示されます。
Flaskのサービスが"method":"PATCH" と返していますので、PATCHとして認識されていることが分かりました。

(4) APIのデプロイ

動作が確認できたので、作成したAPIをデプロイします。
[アクション]から、[APIのデプロイ]をクリックします。

適当なステージに[デプロイ]します。今回は新規に"dev"ステージとしました。

デプロイすると呼び出し用のURLが払い出されるので、これをコピーします。

curl -X POST {target}コマンドでPOSTを投げてみると、PATCHとして受け付けていることが分かり、正しい挙動であることが確認できました。

IPアドレス制限を実装する (Optional)

ここまでで紹介した手順は、ALBがインターネットに公開され(0.0.0.0/0)、機微なシステムには不向きでした。
API GatewayとALBの間にNLBを追加し、ALBのセキュリティグループを変更することで、IPアドレスの制限を実装します。

以下の記事が参考になります。

構成図 (NLB追加)

(1) NLBの作成

公式ドキュメントを参考に、プライベートサブネットへNetwork Load Balancerを作成します。

但し、登録するターゲットグループには利用するALBを登録します。

また、ALBのセキュリティグループはNLBのIPレンジを許可するように変更します。(検証では、インターネットからのアクセス 0.0.0.0/0許可も同時に削除)

(2) VPCリンクの作成

API Gatewayの左ペインから、VPCリンクを表示し、[Create]をクリックします。

[REST API の VPCリンク]を選択し、名前と作成したNLBを選択します。

VPCリンクの作成は2〜4分掛かります。

(3) APIの編集

続いて、既存APIの設定を変更します。
統合タイプは[VPCリンク]を指定し、作成したVPCリンクを選択します。

確認画面で[OK]をクリックし、APIをデプロイすると切り替わります。

例のごとく、curl -X POST {target}コマンドでPOSTを投げてみると、PATCHとして受け付けていることが分かり、正しい挙動であることが確認できました。

また、X-Forwarded-ForはNLBのPrivate IPアドレスとなっています。

(4) API Gatewayへのアクセス制限

ここまでALBへのアクセス制限を行いましたが、API Gatewayには無制限でアクセスできる状態なので、リソースポリシーによる送信元IPアドレス制限を設定します。

以下の記事が参考になります。

リソースポリシー画面にて、例示されている[IP範囲の拒否リスト]をクリックすると、テンプレートが展開されます。

適宜アクセスを許可するIPアドレスなどを入力して、許可されないIPアドレスからのアクセスは無効にします。

検証では以下のようなポリシーを作成しました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:ap-northeast-1:000000000000:{api-id}/*/*/*"
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:ap-northeast-1:000000000000:{api-id}/*/*/*",
            "Condition":{
                "NotIpAddress": {
                    "aws:SourceIp": [
                        "{myIPaddress}/32"
                    ]
                }
            }
        }
    ]
}

API Gatewayにアクセスする端末のIPアドレスにより制御できることが確認できました。
また、上記ドキュメントではソース VPC または VPC エンドポイントに基づいて制御する例も記載されています。

おわりに

API GatewayによるHTTPメソッドの変換を試してみました。
触ったことがない領域でしたが、簡単にリクエストを編集できるので非常に手軽だと感じました。

このエントリが誰かの助けになれば幸いです。

それでは、AWS事業本部 コンサルティング部の荒平(@0Air)がお送りしました!

参考

API Gatewayのスループットが気になる場合: