2021年03月にリリースされたAWS WAF v2のカスタムレスポンス機能を使って、APIから「メンテナンス中」の旨を返すようなメンテナンスモードを実現してみました。
AWS WAF v2のカスタムレスポンス機能とは
- WAFでリクエストをブロックするように設定した際に、httpステータスコードやレスポンスヘッダー・ボディを自由に定義して返せる機能
- 今まではステータスコードが403の固定で、レスポンスの構造も変えられなかった
前提
APIのメンテナンスモードとは
今回実現したい「APIのメンテナンスモード」というのは、Webやアプリなどのメンテナンス中に「メンテナンス中です」のようなページを表示するために、APIからのレスポンスを変更するためのものとして書いています。
あくまでAPI側での実現方法なので、フロント側の話は今回は触れません。
また今回はAPI側をスコープにしていますが、フロント側だけでメンテナンスページを表示したい場合は、CloudFrontのカスタムエラーレスポンスとWAFを組み合わせることで実現可能です。
CloudFrontのカスタムエラーレスポンスを使わず、WAFのカスタムレスポンスのみでもメンテナンスページを実現することができます。
対象リソース
今回はAWS WAFを使用するため、APIはAPI GatewayやAppsync、ELBなど、WAFがアタッチできるものに限ります。
特徴
RESTにもGraphQLにも対応できる
- RESTのメンテナンス時の特徴
- メンテナンスの際のステータスコードは503を使うことが多い
- メンテナンス用のヘッダーを定義して使うことも
→WAFのカスタムレスポンス機能で、ステータスコードを503にし、カスタムヘッダーを定義して実現する
※ステータスコードだけで実現することも可能ですが、503だけどメンテナンスモード時ではないときもメンテナンスモードとして扱ってしまうので、ちゃんとやるのであればヘッダーを使用した方が確実です。その場合、ステータスコードはハンドリングしなくて良いケースもあります。
- GraphQLのメンテナンス時の特徴
→WAFのカスタムレスポンス機能で、ステータスコードは200のまま、レスポンスボディを定義して実現する
APIのソースコード変更の必要がない
方法
AWS WAFのWebACLを作成し、APIリソースにアタッチすることでメンテナンスモードを実現します。
また、CloudFormation(ymlテンプレートファイル)を使用してみます。
※GitHubにもあります。
REST用
ステータスコードを503にし、カスタムヘッダーを定義
WAFWebACL: Type: AWS::WAFv2::WebACL Properties: Name: "Maintenance-WebACL" Scope: "REGIONAL" VisibilityConfig: CloudWatchMetricsEnabled: true MetricName: !Ref WAFWebACLMetricName SampledRequestsEnabled: true CustomResponseBodies: CustomResponseBodyKeyForRest: ContentType: APPLICATION_JSON Content: '{"error": {"errorType": "MaintenanceMode", "message": "Unable to access during the maintenance."}}' DefaultAction: Block: CustomResponse: ResponseCode: 503 CustomResponseBodyKey: CustomResponseBodyKeyForRest ResponseHeaders: - Name: custom-error-type Value: "MaintenanceMode"
Scope: "REGIONAL"
- VisibilityConfig
- 監視のための設定など
- 今回はあまり触れません
VisibilityConfig: CloudWatchMetricsEnabled: true MetricName: !Ref WAFWebACLMetricName SampledRequestsEnabled: true
- CustomResponseBodies
CustomResponseBodies: CustomResponseBodyKeyForRest: ContentType: APPLICATION_JSON Content: '{"error": {"errorType": "MaintenanceMode", "message": "Unable to access during the maintenance."}}'
- CustomResponse
DefaultAction: Block: CustomResponse: ResponseCode: 503 CustomResponseBodyKey: CustomResponseBodyKeyForRest ResponseHeaders: - Name: custom-error-type Value: MaintenanceMode
GraphQL用
ステータスコードは200のまま、レスポンスボディを定義
WAFWebACL: Type: AWS::WAFv2::WebACL Properties: Name: "Maintenance-WebACL" Scope: "REGIONAL" VisibilityConfig: CloudWatchMetricsEnabled: true MetricName: !Ref WAFWebACLMetricName SampledRequestsEnabled: true CustomResponseBodies: CustomResponseBodyKeyForGraphql: ContentType: APPLICATION_JSON Content: '{"errors": [{"errorType": "MaintenanceMode", "message": "Unable to access during the maintenance."}]}' DefaultAction: Block: CustomResponse: ResponseCode: 200 CustomResponseBodyKey: CustomResponseBodyKeyForGraphql
- CustomResponseBodies
- "errors"キーの中に配列を持たせ、その中でエラー内容(メンテナンス内容)を定義
- errors配列
- "errorType": "MaintenanceMode"
- "message": "Unable to access during the maintenance."
- errors配列
- "errors"キーの中に配列を持たせ、その中でエラー内容(メンテナンス内容)を定義
CustomResponseBodies: CustomResponseBodyKeyForGraphql: ContentType: APPLICATION_JSON Content: '{"errors": [{"errorType": "MaintenanceMode", "message": "Unable to access during the maintenance."}]}'
- CustomResponse
- ResponseCodeを200にする
- 先程定義した
CustomResponseBodyKeyForGraphql
を呼ぶ - RESTの方で定義した
ResponseHeaders
は記述しない(しても良い)
DefaultAction: Block: CustomResponse: ResponseCode: 200 CustomResponseBodyKey: CustomResponseBodyKeyForGraphql
WAF作成後
上記yamlをもとにCloudFormationでデプロイし、APIリソースにWAFをアタッチすると、APIへのアクセスがブロックされて定義したレスポンスが返るようになります。
※デプロイ方法やアタッチ方法は省略
$ curl -i ${url} #<- urlにはAPIのエンドポイントを入れる HTTP/2 503 content-type: application/json ...(省略) x-amzn-errortype: ForbiddenException custom-error-type: MaintenanceMode ...(省略) {"error": {"errorType": "MaintenanceMode", "message": "Unable to access during the maintenance."}}
あとはフロント側のjavascriptでレスポンスをハンドリングしてメンテナンスページに遷移させたり、CloudFrontのカスタムエラーレスポンス機能を使用したりすれば、メンテナンスページの表示までできるようになります。