ちょっと話題の記事

Nginxを利用してCloudFront対応のWordPressを環境を最適化してみた

2019.05.23

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

はじめに

AWSチームのすずきです。

先日紹介させて頂いた、CloudFront、ELB、EC2を利用したWordPress環境。 ELBやEC2のパブリックIPアドレスを知り得た第三者により WordPressの実行環境が直接攻撃される事があった場合、DDoSなどの被害を受けやすいリスクがありました。

今回、この対策としてNginxをリバースプロキシとして導入し、 CloudFrontを経由と、正しい認証情報を持つ管理者のリクエストのみWordPress環境に中継、 他の不正なアクセスは遮断する方法を紹介させていただきます。

構成図

環境

  • OSは、Amazon Linux2のAMI (amzn2-ami-hvm-2.0.20190508-x86_64-gp2) を利用しました。
  • CloudFront、ELB、EC2の各リソースは、以下の環境を一部変更して利用します。

CloudFront対応のWordPress、Host設定でELB直接続も可能な環境を構築してみた

Nginx

インストール

AmazonLinux2のエクストラパッケージ「nginx1.12」、UserDataを利用してインストールしています。

amazon-linux-extras install -y nginx1.12

Amazon Linux 2にExtrasレポジトリからNginxをインストールする

Nginx 設定

「Nginx」の設定ファイル nginx.conf に反映した設定を紹介します。

バーチャルサーバ

プライベートポート TCP:50080 (管理者アクセス用)、50081(CloudFront用)、50082 (HTTP→HTTPS誘導用)のバーチャルサーバを用意しました。

デフォルト用

CloudFront は オリジン通信を HTTP/1.1 を利用、WordPressの管理者も HTTP/1.1 に対応しないブラウザを利用する事は無いものとして、 名前ベースのバーチャルホストに一致しないリクエスト、IP直指定などで届いたリクエストは接続を拒否する設定としました。

  # default_server
  server {
    listen 50080 default_server;
    location / {
      return 404;
    }
  }
  server {
    listen 50081 default_server;
    location / {
      return 404;
    }
 }
CloudFront用

CloudFront 用のバーチャルサーバは、指定したカスタムヘッダ「x_pre_shared_key」が一致する場合のみWordPressにリクエストを転送する設定としました。

  server {
    listen 50081;
    server_name wp.example.com;

    if ($http_x_pre_shared_key != "xxxxxxxx") {
      return 403
      break;
    }

    set $backend "127.0.0.1";
    location / {
      proxy_pass http://$backend;
      break;
    }
  }

CloudFront側は以下の「OriginCustomHeaders」設定を実施しています。

  CloudFrontDistributionElb:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
         -  OriginCustomHeaders:
              - HeaderName: X-pre-shared-key
                HeaderValue: !Ref 'CfNginxPreSharedkey'

今回、CloudFrontで付与したカスタムヘッダをNginxで判定していますが、 詳細は、同等の処理をAWS WAFや ELBのルールで実施している以下の記事をご確認ください。

AWS WAFを利用してCloudFrontのELBオリジンへ直接アクセスを制限してみた

CloudFront専用のALBをリクエストルーティングで設定してみた

管理者用

管理者用のバーチャルサーバは、BASIC認証を必須としました。

  server {
    listen 50080;
    server_name wp.example.com;

    set $backend "127.0.0.1";
    location / {
      auth_basic "private";
      auth_basic_user_file /etc/nginx/.htpasswd;
      proxy_pass http://$backend;
      break;
    }
  }

Basic認証で利用するパスワードファイル、今回のサンプルでは平文としています。

echo "${WPBasicAuthId}:{PLAIN}${WPBasicAuthPass}" > /etc/nginx/.htpasswd

実環境ではhtpasswdコマンドなどを利用して、適切に暗号化される事をおすすめします。

htpasswd -c /etc/nginx/.htpasswd <ID>
HTTPS誘導用

HTTP→HTTPS誘導を実現するため、リダイレクト専用のバーチャルサーバを用意しました。

 server {
   listen 50082 default_server;
   if ($scheme = http) {
      return 301 https://$host$request_uri;
      break;
   }
   location / {
     return 404;
   }

ELBとしてALBを利用する場合、ALBのルールを利用する事をお薦めします。

NLB設定

TargetGroup

Nginx で設定したポート指定のバーチャルサーバに対応するターゲットグループを用意しました。

  NlbTargetGroup1:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Port: 50080
      Protocol: TCP
      VpcId: !Ref 'VpcId'
  NlbTargetGroup2:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Port: 50081
      Protocol: TCP
      VpcId: !Ref 'VpcId'
  NlbTargetGroup3:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Port: 50082
      Protocol: TCP
      VpcId: !Ref 'VpcId'

Listener

HTTPS用のリスナーは、プロトコルとして「TLS」を指定。NLBを用いたTLS終端を実施しています。

管理者アクセス用

HTTPS(TCP:443) は管理者アクセス用として設定しました。

  NlbListenerHTTPS1:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref 'NlbTargetGroup1'
      LoadBalancerArn: !Ref 'NlbLoadBalancer'
      Port: '443'
      Protocol: TLS
      Certificates:
        - CertificateArn: !Ref 'AcmArnRegional'

CloudFront用

HTTPS(TCP:50443) はCloudFront用として設定しました。

  NlbListenerHTTPS2:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref 'NlbTargetGroup2'
      LoadBalancerArn: !Ref 'NlbLoadBalancer'
      Port: '50443'
      Protocol: TLS
      Certificates:
        - CertificateArn: !Ref 'AcmArnRegional'

HTTP用

HTTP(TCP:80)は、HTTPSリダイレクト用のターゲットグループを割り当てました。

  NlbListenerHTTP:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref 'NlbTargetGroup3'
      LoadBalancerArn: !Ref 'NlbLoadBalancer'
      Port: '80'
      Protocol: TCP

CloudFront設定

CloudFrontのオリジンの通信はHTTPS、利用ポート(HTTPSPort)はデフォルトの443から50443に変更しました。

  CloudFrontDistributionElb:
    Type: AWS::CloudFront::Distribution
    DependsOn: NlbLoadBalancer
    Properties:
      DistributionConfig:
        Origins:
          -  CustomOriginConfig:
              HTTPSPort: 50443
              OriginProtocolPolicy: https-only

ポートマッピング図

  • インターネットに公開するCloudFront、ELB(NLB)と、EC2(WordPress)は、HTTPSはTCP443、HTTPはTCP80のウェルノウンポートをそのまま利用します。
  • CloudFront、ELB(NLB)、EC2(Nginx)間通信はプライベートポートを利用しています。

その他Nginx設定

Nginx,アクセス制御以外以外で利用した機能を紹介します。

キャッシュ

  • ブラウザキャッシュの有効期間を宣言するexpires ヘッダと、ページキャッシュを設定しています。
                      location / {
                        expires 30m;
                        proxy_cache cache_static_file;
                        proxy_cache_key $scheme$host$uri$args$check_ua;
                        proxy_cache_valid 200 5m;
                        proxy_cache_valid any 1m;
                        proxy_pass http://$backend;
                        break;
                      }

今回、検証環境のコンテンツ更新が遅れる副作用を避けるため、ページキャッシュ、ブラウザキャッシュとも短い設定としました。 パス、ヘッダ情報などに応じた適切な設定を行う事で、大きなWeb配信性能の向上が期待できます。

HTTPS設定

サブドメインを含めて全てHTTPS化が完了した環境を想定し、HSTS (HTTP Strict Transport Security) ヘッダを宣言しています。

ブラウザにHTTPS利用を強制させる事で、意図せぬHTTP接続による平文通信の発生を回避します。

         add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';

コンテンツ圧縮設定

ページキャッシュの利用効率の改善と、CloudFrontが対応しない形式のファイルも圧縮を実施するため、NginxによるGZIP圧縮を設定しています。

                    gzip              on;
                    gzip_http_version 1.0;
                    gzip_types        text/plain
                      application/eot
                      application/font
                      application/font-sfnt
                      application/javascript
(略)
                      text/x-component
                      text/x-java-source
                      text/xml
                      text/x-script;
                    gzip_disable      "MSIE [1-6]\.";
                    gzip_disable      "Mozilla/4";
                    gzip_comp_level   2;
                    gzip_vary         on;
                    gzip_proxied      any;
                    gzip_buffers      4 8k;
  • gzip_proxiedanyを指定、デフォルトでは圧縮対象外となる プロキシ(CloudFront)を経由のリクエストもGZIP処理対象としています。

まとめ

リクエストヘッダなどを利用したアクセス制御を、WordPressが動作するhttpd、PHPで設定する事は可能ですが、 Nginxをリバースプロキシとして利用することで、不正アクセスの効率的な排除と、攻撃に晒される環境の隔離を実現できました。

WordPressの最適化について紹介させて頂きましたが、CloudFrontや Nginxを利用する事で、 多くのWeb配信システムを最適化できる可能性があります。

今回の環境、可用性などは特に考慮していませんが、 維持コスト、EC2(t3.small)と、ELB(NLB) のAWS費用は1日1〜1.5ドル。 CloudFrontは主にネットワーク転送量に応じて発生する従量課金(1GBあたり0.114ドル〜)で利用可能です。 CloudFrontやNginxを評価する機会がありましたら、ぜひお試し頂ければと思います。

テンプレート

今回の検証環境の展開に利用したCloudFormationテンプレートです。

事前に、 Route53 の HostedZone (利用するドメイン) と、CloudFront、ELB用のACM証明書のARNをご用意ください。

関連ブログ