Amazon CloudFrontでApacheのリダイレクトとユーザー制限(IP、Basic認証)を実現する #CloudFront Functions #AWS WAF #Lambda@Edge

AWS

2023.12.28

Topics

はじめに

こんにちは、フクナガです。

EC2で静的サイトをホスティングしている方は、費用面や可用性からマネージドサービスであるCloudFrontとS3による静的サイトホスティングに移行することを検討すると思います。その際、リダイレクト設定やIP制限、Basic認証などの「既存運用」が気がかりで中々移行に踏み切れない方も多いのではないでしょうか。

今回は、EC2内に構築されたApacheで実装されているいくつかの機能をCloudFrontに移し替える場合、どういう実装に変わるのかをお伝えすることで、皆様の移行へのハードルを下げることが出来ればと思い、記事を書きます。

登場するAWSリソース

(1) CloudFront

Amazon CloudFront は、高いパフォーマンス、セキュリティ、デベロッパーの利便性のために構築されたコンテンツ配信ネットワーク (CDN) サービスです。
Amazon CloudFront

(2) Lambda@Edge

CloudFrontにアタッチし、リクエストに対しての処理を実行するサービスです。使用した分だけ課金が発生します。

(3) CloudFront Functions

Lambda@Edgeと同様、CloudFrontにアタッチし、リクエストに対しての処理を実行するサービスです。使用した分だけ課金が発生します。

Lambda@EdgeとCloudFront Functionsの違いについては本記事では触れませんが、気になる方は下記を参照ください。
関数を使用してエッジでカスタマイズ

(4) AWS WAF

外部からの攻撃からウェブアプリケーションを保護するために利用するサービスです。CloudFrontやALBなどのAWSリソースに関連付けて利用することが可能です。

今回取り上げる実装

今回は下記4つの機能について取り上げて、CloudFrontに置き換えるとどうなるのか、ご紹介します。

  1. リダイレクト
  2. 証明書(HTTPS化)
  3. Basic認証
  4. IP制限

1. リダイレクト

長年サイトを運用していると「このURLを書き変えて、このコンテンツを表示する」などの、独自のリダイレクトを実装している場合があると思います。
例:
利用者アクセス先:https://sample.com/A.html
提供されるコンテンツ:https://sample.com/archive/pageA.html

[Apacheでの実装]
(1) http.confなどの設定ファイルに下記を追記

Redirect permanent /A.html https://sample.com/archive/pageA.html

※様々実装方法はありますが、上記を代表例として扱います

CloudFrontに移行する際は、「Lambda@Edge」「CloudFront Functions」を利用すると上記のようなリダイレクトを実装することが可能です。

注意点

Lambda@Edge、CloudFront Functionsはバージニア北部リージョンのみに対応しているリソースとなります。

Lambda@Edgeでの実装

(1) バージニア北部リージョン(us-east-1)でLambdaを作成する
今回は、Python 3.9での実装とします。
(2) Lambdaコードを作成し、「デプロイ」を押下する


import json
import re
import urllib

#ビューアーリクエストの受け取りと宣言
def lambda_handler(event, context):

    request = event['Records'][0]['cf']['request']
    req_uri = request['uri']
    
    uri_pat = '^/A.html$'
    
    if(re.fullmatch(uri_pat,req_uri)):
        response = {
            'status':'301',
            'statusDescription':'Moved Permanently',
            'headers': {
                'location':[{
                    'value': 'https://[リダイレクト先のURL]/archive/PageA.html'
                }]
            }
        }
        return response
        
    return request

(3) 対象のLambdaに紐づいているIAMロールの信頼関係を下記に変更する

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "lambda.amazonaws.com",
                    "edgelambda.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

(4) 「アクション」 > 「Lambda@Edgeへのデプロイ」を押下する

(5) 下記をそれぞれ入力し、「デプロイ」を押下する
・Distribution
対象のCloudFrontのDistribution
・Cache behavior
「*」を選択
・CloudFront Event
Viewer request
・Confirm deploy to Lambda@Edge
チェックを入れる

実際に検証したところ、/A.htmlへのアクセスが/archive/PageA.htmlへリダイレクトされることを確認できました。

CloudFront Functionsでの実装

(1) CloudFrontコンソールの「関数」を押下する

(2) 「関数を作成」を押下する

(3) 「名前」を入力し、「関数を作成」を押下する

(4) 関数コードを作成する


function handler(event) {
  var request = event.request;
  var host = request.headers.host.value;
  var uri = request.uri;
  var newurl = `https://${host}/index.html`

  if (uri === '/A.html') {
    newurl = `https://${host}/archive/PageA.html`;
  }
  else {
    return request;
  }

  var response = {
    statusCode: 301,
    statusDescription: 'Moved Permanently',
    headers:
      { "location": { "value": newurl } }
  }

  return response;
}

(5) 「発行」から「関数を発行」を押下する

(6) 「関連付けられているディストリビューション」から「関連付けを追加」を押下する

(7) 下記をそれぞれ入力し、「関連付けを追加」を押下する
・ディストリビューション
対象のCloudFrontのDistribution
・イベントタイプ
Viewer request
・キャッシュビヘイビア
「Default(*)」を選択

実際に検証したところ、/A.htmlへのアクセスが/archive/PageA.htmlへリダイレクトされることを確認できました。

2. 証明書(HTTPS化)

EC2でWebサイトをホストしている場合、サーバー内に証明書を配置し、Apacheの設定でHTTPS化を実装しているケースがあると思います。

[Apacheでの実装]
(1) 秘密鍵・中間証明書・サーバー証明書をサーバー内に配置する。
※今回の例では、「/etc/httpd/conf/」に配置しています。

(2) http.confなどの設定ファイルに下記を追記

SSLEngine on

SSLCertificateKeyFile /etc/httpd/conf/domain_name.key
SSLCertificateChainFile /etc/httpd/conf/domain_name.cer
SSLCertificateFile /etc/httpd/conf/domain_name.crt

CloudFrontに移行する際は、既存の証明書をAWS Certificate Manager(ACM)にインポートし、CloudFrontにアタッチすることで実装します。

ACMへの証明書インポート

(1) AWS Certificate Manager (ACM)コンソール左側メニュー「証明書をインポート」を押下する

(2) 「証明書本文」「証明書のプライベートキー」「証明書チェーン」を入力し、「次へ」を押下する

(3) インポートしようとしている証明書に問題がなければ「インポート」を押下する

3. Basic認証

「開発者のみアクセス可能」などユーザーを絞る運用をしている場合、ApacheによるBasic認証を実装しているケースがあると思います。

[Apacheでの実装]
(1) パスワード認証ファイルを下記コマンドで作成し、適切な権限に設定(詳細は省きます)

sudo htpasswd -c /etc/httpd/htpasswd ユーザー名

(2) http.confなどの設定ファイルに下記を追記

<Directory "/var/www/html">
  AuthType Basic
  AuthName "auth"
  AuthUserFile /etc/httpd/htpasswd
  Require valid-user
</Directory>

CloudFrontに移行する際は、リダイレクトと同じように「Lambda@Edge」「CloudFront Functions」を利用すると実装することが可能です。

Lambda@Edgeでの実装

Python 3.9での実装例となります


import json
import base64

accounts = [
    {
        "user": "admin",
        "pass": "password"
    }
    ]

def lambda_handler(event, context):
    request = event.get("Records")[0].get("cf").get("request")
    headers = request.get("headers")

    authorization_header = headers.get("authorization")

    if not check_authorization_header(authorization_header):
        return {
            'headers': {
                'www-authenticate': [
                    {
                        'key': 'WWW-Authenticate',
                        'value':'Basic'
                    }
                ]
            },
            'status': 401,
            'body': 'Unauthorized'
        }


    return request

def check_authorization_header(authorization_header: list) -> bool:
    if not authorization_header:
        return False

    for account in accounts:
        encoded_value = base64.b64encode("{}:{}".format(account.get("user"), account.get("pass")).encode('utf-8'))
        check_value = "Basic {}".format(encoded_value.decode(encoding='utf-8'))

        if authorization_header[0].get("value") == check_value:
            return True

    return False

CloudFront Functionsでの実装


function handler(event) {
    var request = event.request;
    var headers = request.headers;
    
    var authString = ["Basic YWRtaW46cGFzc3dvcmQ="]

    if (
        typeof headers.authorization === "undefined" ||
        !authString.includes(headers.authorization.value)
    ) {
        return {
        statusCode: 401,
        statusDescription: "Unauthorized",
        headers: { "www-authenticate": { value: "Basic" } }
        };
    }
    
  return request;
}


Basic認証用の文字列(上記コード内「authString」)は下記コマンドで出力

echo -n "admin:password" | base64

4. IP制限

対象のサイトの要件に、特定の環境からのみアクセス可能というものがある場合ApacheによるIP制限を実装しているケースがあると思います。

[Apacheでの実装]
(1) http.confなどの設定ファイルに下記を追記

Require all denied
Require ip xxx.xxx.xx.xx

CloudFrontに移行する際は、WAFをアタッチすることで実現が可能です。
※Lambda@EdgeやCloudFront Functionsでも実装は可能ですが、本記事では省略します

WAFでの実装

(1) WAF & Shield コンソールで「Web ACLs」を押下し、リージョンを「Global(CloudFront)」に設定、「Create web ACL」を押下する

(2) 「Name」に任意の値を入力し、それ以外はデフォルトで「Next」を押下する

(3) 「Default web ACL action for requests that don’t match any rules」の「Default action」を「Block」に設定し、「Next」を押下する

(4) 以降の項目は全てデフォルトで設定し、「Create web ACL」を押下する

(5) WAF & Shield コンソールで「IP sets」を押下し、リージョンを「Global(CloudFront)」に設定、「Create IP set」を押下する

(6) 「IP set name」に任意の値、「IP addresses」に許可したいIPの値を入力し、「Create IP set」を押下する

(7) 作成したWeb ACLを選択し、「Rules」タブを選択、「Add rules」>「Add my own rules and rule groups」を押下する

(8) 以下を入力し、「Add rule」を押下する
・Rule type
IP set
・Name
任意の値
・IP set
先ほどの工程で作成したIP set
・IP address to use as the originating address
Source IP address
・Action
Allow

(9) 「Save」を押下する

(10) 作成したWeb ACLを選択し、「Associated AWS resources」タブを選択、「Add AWS resources」を押下する

(11) IP制限対象のCloudFront Distributionを選択し、「Add」を押下する

許可対象のIPからはサイトが閲覧でき、外部IPからはアクセスできないことが確認できました。

まとめ

本記事では、EC2内に構築されたApacheで実装されているいくつかの機能をCloudFrontに移し替える場合、どういう実装に変わるのかをお伝えしました。
移行計画時には、既存の要件や運用をどの程度維持し、どの程度変更が必要なのか検討することになると思います。CloudFrontに対応した様々なマネージドサービスやCloudFront自身の機能を利用することで、既存運用を維持できるケースも多くあります。
移行検討時にこの記事が参考になってくれるととてもうれしいです!

フクナガ

インフラエンジニア歴5年のフクナガです。AWS(特にBedrockとCodePipeline)が得意分野。休日はベースを弾いてます。技術力と発信力を高め、Top Engineerを目指しています。

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら