この記事は公開されてから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の各リソースは、以下の環境を一部変更して利用します。
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をご用意ください。