×
採用サイトはこちら

CloudFormationで構築するCloudFront+S3+WAFで実現するメンテナンスページ切り替え

はじめに

Webサービスの運用において、リリース作業や障害対応などの理由で、特定のユーザーのみを許可して、他のユーザーにはメンテナンスページを表示したいというケースはよくあります。
このような場合、メンテナンスぺージを表示したいタイミングで、許可されていないユーザーをブロックして、メンテナンス用のページを表示することでメンテナンスページへの切り替えを実現します。

本記事ではCloudFormationを使用して、S3 + CloudFront + WAFによるメンテナンスページへの切り替え方法を紹介します。

目次

構成

本記事で作成する構成は下記となります。

各サービスの役割

  • WAF
    メンテナンス時のみ CloudFront にアタッチして IP 制限を有効化します。
  • CloudFront
    通常時は、すべてのユーザーにindex.html(通常ページ)を返します。
    メンテナンス時は、WAFにより許可していないユーザーにはメンテナンスページを表示するようにアクセス制御を行います。
    メンテナンス時は、許可したユーザの場合、WAFを通過し、index.html(通常ページ)を返します。
    一方で、許可されていないユーザーの場合、WAFによりブロックされ、403エラーをCloudFrontが検知して、カスタムエラーレスポンスによりmaintenance.html(メンテナンスページ)を返します。
  • S3バケット
    ユーザーに表示するための通常ページとメンテナンスページを配置します。
  • CloudFormation
    上記のリソースを作成します。
    IsMaintenanceでメンテナンスのON/OFFを切り替えられます。

環境構築手順

1. CloudFormation テンプレート作成

以下の maintenance-stack.yaml として下記を保存します。

AWSTemplateFormatVersion: "2010-09-09"
Description: CloudFront + WAF + S3 Maintenance Page

Parameters:
  IsMaintenance:
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"
    Description: Whether maintenance mode is enabled

  AllowedIp:
    Type: String
    Default: "123.456.789.123/32"
    Description: IP allowed during maintenance

Conditions:
  IsMaintenanceEnabled: !Equals 
    - !Ref IsMaintenance
    - "true"


Resources:
  ################################
  # S3 Bucket (Maintenance Page)
  ################################
  MaintenanceBucket:
    Type: AWS::S3::Bucket
    Properties:
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  ################################
  # CloudFront Origin Access Control
  ################################
  CloudFrontOAC:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Name: maintenance-oac
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4

  ################################
  # Bucket Policy for CloudFront
  ################################
  MaintenanceBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref MaintenanceBucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: cloudfront.amazonaws.com
            Action: s3:GetObject
            Resource: !Sub "${MaintenanceBucket.Arn}/*"
            Condition:
              StringEquals:
                AWS:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}"

  ################################
  # WAF IP Set
  ################################
  MaintenanceIpSet:
    Type: AWS::WAFv2::IPSet
    Properties:
      Name: maintenance-ipset
      Scope: CLOUDFRONT
      IPAddressVersion: IPV4
      Addresses:
        - !Ref AllowedIp

  ################################
  # WAF Web ACL
  ################################
  MaintenanceWebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: maintenance-webacl
      Scope: CLOUDFRONT
      DefaultAction: 
        Block: {}
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: maintenance-webacl
        SampledRequestsEnabled: true
      Rules:
        - Name: AllowSpecificIP
          Priority: 0
          Action:
            Allow: {}
          Statement:
            IPSetReferenceStatement:
              Arn: !GetAtt MaintenanceIpSet.Arn
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: allow-ip
            SampledRequestsEnabled: true


  ################################
  # CloudFront Distribution
  ################################
  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Enabled: true
        DefaultRootObject: index.html
        WebACLId: !If
          - IsMaintenanceEnabled
          - !GetAtt MaintenanceWebACL.Arn
          - !Ref AWS::NoValue
        Origins:
          - Id: S3Origin
            DomainName: !GetAtt MaintenanceBucket.RegionalDomainName
            OriginAccessControlId: !Ref CloudFrontOAC
            S3OriginConfig: {}
        DefaultCacheBehavior:
          TargetOriginId: S3Origin
          ViewerProtocolPolicy: redirect-to-https
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          ForwardedValues:
            QueryString: false
            Cookies:
              Forward: none
        CustomErrorResponses: !If
          - IsMaintenanceEnabled
          - 
            - ErrorCode: 403
              ResponseCode: 503
              ResponsePagePath: /maintenance.html
              ErrorCachingMinTTL: 0
          - !Ref AWS::NoValue

Outputs:
  CloudFrontDomainName:
    Value: !GetAtt CloudFrontDistribution.DomainName
    Description: CloudFront URL

2. 通常ページとメンテナンスページ用のhtmlファイルの準備

以下のファイルを作成します。

  • index.html(通常ページ)
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>通常ページ</title>
</head>
<body>
  <h1>通常ページです</h1>
</body>
</html>
  • maintenance.html(メンテナンスページ)
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>メンテナンス中</title>
</head>
<body>
  <h1>現在メンテナンス中です</h1>
  <p>しばらくお待ちください。</p>
</body>
</html>

3. CloudFormationコードの適用

1.で作成したCloudFomrationテンプレートファイルによりAWS環境を構築します。

  • 適用リージョン:us-east-1
    ※WAF(Scope: CLOUDFRONT)はus-east-1でのみ作成可能というAWS仕様上の制約があるため、単一のスタックで構築する場合は、us-east-1の指定が必要となる。
  • スタック名:cloudfront-maintenance-stack ※任意の名称で作成してください
  • パラメータ:
    • IsMaintenance: “false”
    • AllowedIp: “123.456.789.123/32”
      ※許可したいIPアドレスを指定してください。

4. S3 バケットへファイルアップロード

CloudFormation 適用後に作成される S3 バケットに2.で作成したindex.html(通常ページ)と maintenance.html(メンテナンスページ)をアップロードします。

メンテナンスページの表示と切り替え

動作を確認するために必要な準備は完了しましたので、実際のメンテナンス画面の切り替えの動作を確認します。
CloudFormation のパラメータ IsMaintenanceの値を切り替えることでメンテナンスページの切り替えができます。

メンテナンスモードを有効にする(ON)

AWS Managementコンソール上やAWS CLIによりCloudFormationテンプレートのパラメータをIsMaintenanceをtrueに変更します。
AWS CLIで実行する場合は、下記のようなコマンドでcloudformationを更新できます。

aws cloudformation update-stack \
  --stack-name ${自身で作成したスタック名} \
  --use-previous-template \
  --parameters ParameterKey=IsMaintenance,ParameterValue=true \
               ParameterKey=AllowedIp,UsePreviousValue=true

上記を実行して、少し待ちますと、特定のIPアドレス以外のユーザからはメンテナンスページが表示されるようになりました。
ブラウザに入力するURLは、作成したCloudFrontのデフォルトドメインを指定しております。

メンテナンスモードを無効にする(OFF)

AWS Managementコンソール上やAWS CLIによりCloudFormationテンプレートのパラメータをIsMaintenanceをtrueに変更します。
AWS CLIで実行する場合は、下記のようなコマンドでcloudformationを更新できます。

aws cloudformation update-stack \
  --stack-name ${自身で作成したスタック名} \
  --use-previous-template \
  --parameters ParameterKey=IsMaintenance,ParameterValue=false \
               ParameterKey=AllowedIp,UsePreviousValue=true

上記を実行して、少し待ちますと、通常ページが表示されるようになりました。

まとめ

本記事では、CloudFormationを活用して S3 + CloudFront + WAF によるメンテナンスモードの切り替え方法を紹介しました。
本記事は構成の一例を紹介しました。本内容が設計や実装のご参考になりましたら幸いです。

最後に考慮が必要な項目をまとめさせて頂きます。

  • CloudFrontのキャッシュの影響
    WAFの付け替え後に、CloudFrontのキャッシュにより古いページが表示される場合があります。
    必要に応じて、CloudFrontのキャッシュの削除を行ってください。
  • CloudFrontのメンテナンスページの切り替え時の遅延
    CloudFormationでメンテナンスページのON・OFFを切り替えると、実行してから即時反映されるのではなく、少し時間をおいてから反映されますので、実際に試してみて動作を確認してください。
  • メンテナンスページでスタイルシート(CSS)や画像を使用する場合
    使用する必要がある場合は、対象のパスへのアクセス制限をしないようWAFのルールで設定が必要になります。
  • 容量が小さいメンテナンスページの場合
    WAFのカスタムレスポンスにおいて、4KB以下の場合、HTML形式で設定可能となりますので、あまりリッチなメンテナンス画面でない場合はWAFでメンテナンスページを完結することもできます。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

弊社FindConsulting在籍のエンジニアです。
主にPL/SEとして、AWS環境でのインフラ構築・運用に携わってきました。
オンプレミスからAWSへの移行案件や、AWS間の移行案件、AWS上でのWebサイト構築などのプロジェクトで、多様なAWSサービスを組み合わせたアーキテクチャの設計・実装を経験しています。
また、AWS CDKやCloudFormationによるIaC(Infrastructure as Code)や、CircleCIやCode Deployを用いたCI/CD環境の構築にも注力し、要件定義から運用まで一連の工程を幅広く担当しました。
これらの実務経験をもとに、本ブログではAWSを中心とした実践的なインフラ技術と開発・運用ノウハウを発信します。

目次