Nginxを利用してCloudFront対応のWordPressを環境を最適化してみた
はじめに
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の各リソースは、以下の環境を一部変更して利用します。
Nginx
インストール
AmazonLinux2のエクストラパッケージ「nginx1.12」、UserDataを利用してインストールしています。
amazon-linux-extras install -y nginx1.12
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のルールで実施している以下の記事をご確認ください。
管理者用
管理者用のバーチャルサーバは、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_proxied
でany
を指定、デフォルトでは圧縮対象外となる プロキシ(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をご用意ください。