Amazon Web Services ブログ

Amazon CloudFront と AWS Lambda@Edge による署名付き Cookie ベースの認証: パート 2 – 認可

この 2 部構成のブログシリーズでは、メールアドレスとドメイン名を使用してユーザー認証を行う方法を学習します。この方法では、静的な Web サイトへの資格情報を使用しないユーザーアクセスを制限します。

このブログシリーズの第 2 部では、認可メカニズムの実装方法を学習します。前回のブログ記事では、認証メカニズムの実装方法を学習しました。ブログ記事の第 1 部を読んだ場合は、序文をスキップして、前提条件を読んでください。

このソリューションでは、Amazon Simple Storage Service (S3) にコンテンツを保存し、Amazon CloudFront 経由でコンテンツを配信します。AWS Lambda@Edge を介してエンドユーザーへの認証を処理します。AWS Lambda@Edge および Amazon Cognito を使用したログインベースの認証については、このブログを参照してください。

この代替認証ソリューションには、次の 4 つの利点があります。

  • 制限されたコンテンツにアクセスするユーザーに、資格情報を使用しない認証を提供する
  • ユーザー情報に関するデータベース管理は不要
  • ユーザーはサインアップする必要がない
  • このソリューションはサーバレスであり、サーバまたはクラスタのプロビジョニング、パッチ適用、オペレーティングシステムのメンテナンス、キャパシティプロビジョニングなどのインフラストラクチャ管理タスクが不要

このソリューションは General Data Protection Regulation (GDPR) に準拠しています。GDPR への準拠はユーザーデータに関連付けされており、このソリューションではユーザーのメールで処理されますが、この情報は保存されません。

AWS は、お客様のアクティビティに適用される GDPR 要件への準拠を支援するサービスとリソースをお客様に提供することをお約束します。それでも、責任共有モデルにおいてAWS で実行されるアプリケーションが GDPR に準拠しているかどうかは、お客様の責任となります。

ソリューションの概要

下の図 1 は、次に示す認証と認可のワークフローを示しています。

  1. ユーザがログインページに移動すると、ユーザの会社のメールを要求する簡単なフォームが表示される
  2. ユーザーは会社のメールアドレスを入力し、「ログイン」をクリックする
  3. フォームを送信すると API コールが呼び出される。API コールは AWS Lambda@Edge 関数に委任され、ユーザーが送信した E メールアドレスドメインをチェックする
  4. 許可されたドメインと一致した場合、この関数は署名付き URL を生成し、Amazon Simple Email Service を使用してユーザーの E メールに送信する
  5. リンクをクリックすると、ユーザは署名付き URL を使って制限付きコンテンツにルーティングされる
  6. 有効な署名付き URL を指定すると、Amazon CloudFront は署名付き Cookie を返す AWS Lambda@Edge 関数を呼び出す。ブラウザは、制限付きコンテンツにユーザーをリダイレクトする
  7. 署名付き Cookie は後続のリクエストで使用され、Amazon S3 に保存されているファイルへのアクセス権を付与する。これにより、入口となるコンテンツを通じてシームレスなブラウジングエクスペリエンスが可能になる

図 1 : 認証と認可のワークフロー

ウォークスルー

Amazon CloudFront と AWS Lambda@Edge を使用して資格情報を使用せずにコンテンツを保護するには、3 つのステップがあります。

  1. Amazon CloudFront でコンテンツを制限する (コンテンツへのアクセス)
  2. ドメインのチェックと署名付き URL の生成のための AWS Lambda@Edge 関数を作成する (認証)
  3. 署名付き Cookie を生成するために、署名付き URL にアクセスして呼び出されるAWS Lambda@Edge 関数を作成する(認可)

コードスニペットは、ブログ記事を通じて提供されています。

このブログ記事では、ステップ 3 の実装方法について説明します。

前提条件

このウォークスルーを開始するには、まず、このブログシリーズの第 1 部の認証メカニズムを実装する必要があります。このブログの残りの部分では、署名付き URL と署名付き Cookie を使用した認可メカニズムに焦点を当てます。

認可メカニズム

署名付き URL は、セキュリティで保護されたコンテンツにアクセスするのに役立ちます。署名付き URL は長くて判読できず、1 時間後に有効期限が切れます。セキュリティで保護されたコンテンツに繰り返しアクセスできる方法が必要です。したがって、Cookieを使用することになります。Cookie は、特定のサイトのログイン情報を保存します。サイトにアクセスするたびに認証を行う必要はありません。セキュリティ上の理由から、Cookie に署名する必要があります。悪意のある人物がこの Cookie を使用して制限されたコンテンツにアクセスすることはできません。

認可のために、ビューワーリクエストはロジックを呼び出し、リクエストヘッダーに署名付き Cookie を設定します。Amazon CloudFront ディストリビューションで /auth パスパターンのビヘイビアを作成します。このビヘイビアにより、AWS Lambda@Edge 関数がリクエストごとに呼び出されなくなります。

署名付き URL が https://dxxxxxxxxxxxxx.cloudfront.net/auth?<signed URL information>などのパスを指している場合、Amazon CloudFront はセキュリティで保護されたパスがアクセスされていることを認識します。URL が正しい認証情報を提供することが確認されると、Amazon CloudFront はリクエストの処理を許可します。/auth ビヘイビアには、リクエストの内容を評価する AWS Lambda@Edge 関数がアタッチされています。AWS Lambda@Edge 関数では、署名付き URL で送信されたリクエストのレスポンスヘッダーに、署名付き Cookie とリダイレクションステータスが含まれます。ブラウザは、restricted-content.html などの制限されたコンテンツにリクエストをリダイレクトします。ブラウザは、後続のリクエストのために署名付き Cookie を保存します。認可されたリクエストでは AWS Lambda@Edge 関数が呼び出されることを理解しておくことが重要です。署名付き URL または署名付き Cookie が必要です。どちらも利用できない場合、Amazon CloudFront はアクセス拒否メッセージと HTTP エラーコード 403 (Forbidden) で応答します。以下の図 2 は、認可フローを示しています。

図 2: 制限付きコンテンツへの認可フロー

AWS Lambda@Edge 関数のロジックは次のようになります。

const AWS = require('aws-sdk');
const ssm = new AWS.SSM({ region: '<your-region>' });

// Either defined as a constant or retrieved from AWS Systems Manager Parameter Store
const SIGNING_URL = '<your-cloudfront-url>';

const cache = {}

const loadParameter = async(key, WithDecryption = false) => {
    const { Parameter } = await ssm.getParameter({ Name: key, WithDecryption: WithDecryption }).promise();
    return Parameter.Value;
};

const policyString = JSON.stringify({
    'Statement': [{
        'Resource': `http*://${SIGNING_URL}/*`,
        'Condition': {
            'DateLessThan': { 'AWS:EpochTime': getExpiryTime() }
        }
    }]
});

function getSignedCookie(publicKey, privateKey) {
    const cloudFront = new AWS.CloudFront.Signer(publicKey, privateKey);
    const options = { policy: policyString };
    return cloudFront.getSignedCookie(options);
}

function getExpirationTime() {
    const date = new Date();
    return new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59);
}

function getExpiryTime() {
    return Math.floor(getExpirationTime().getTime() / 1000);
}

exports.handler = async(event) => {
    if (cache.publicKey == null) cache.publicKey = loadParameter('publicKey');
    if (cache.privateKey == null) cache.privateKey = loadParameter('privateKey', true);

    const { publicKey, privateKey } = cache;

    const signedCookie = getSignedCookie(publicKey, privateKey);

    return {
        status: '302',
        statusDescription: 'Found',
        headers: {
            location: [{
                key: 'Location',
                value: `https://${SIGNING_URL}/restricted-content.html`,
            }],
            'cache-control': [{
                key: "Cache-Control",
                value: "no-cache, no-store, must-revalidate"
            }],
            'set-cookie': [{
                key: "Set-Cookie",
                value: `CloudFront-Policy=${signedCookie['CloudFront-Policy']};Domain=${SIGNING_URL};Path=/;Expires=${getExpirationTime().toUTCString()};Secure;HttpOnly;SameSite=Lax`
            }, {
                key: "Set-Cookie",
                value: `CloudFront-Key-Pair-Id=${signedCookie['CloudFront-Key-Pair-Id']};Domain=${SIGNING_URL};Path=/;Expires=${getExpirationTime().toUTCString()};Secure;HttpOnly;SameSite=Lax`
            }, {
                key: "Set-Cookie",
                value: `CloudFront-Signature=${signedCookie['CloudFront-Signature']};Domain=${SIGNING_URL};Path=/;Expires=${getExpirationTime().toUTCString()};Secure;HttpOnly;SameSite=Lax`
            }]
        },
    };
};

コードを確認したら、署名付き Cookie に既定ポリシーとカスタムポリシーのどちらを使用するかを決定する必要があります。デモンストレーションの目的で、カスタムポリシーを使用します。ドキュメントには、既定ポリシーとカスタムポリシーの比較が記載されています。前提条件として、信頼されたキーグループを使用して Amazon CloudFront を定義する必要があります。これは、新しい Amazon CloudFront ディストリビューションをセットアップしたときに行われました。署名付き Cookie の詳細については、このドキュメントをお読みください。

SIGNING_URL 定数は、クッキーがアクセスする URL を定義します。プロトコル仕様、クエリ文字列パラメーター、有効期限の日時の設定など、リソースを指定するための追加オプションを使用できます。オプションの詳細については、ドキュメントを参照してください。

AWS Lambda@Edge 関数は、署名付き URL からリクエストが行われた場合か、またはリクエストのヘッダーに署名付き Cookie が示された場合に呼び出されます。/auth パスのリクエストは署名付き URL から送信されるため、リクエストは AWS Lambda@Edge 関数によって処理されます。AWS Lambda@Edge 関数は署名付き Cookie を作成し、レスポンスのヘッダーとして渡します。

getSignedCookie メソッドは、カスタムポリシーを引数として受け取り、CloudFrontポリシー、CloudFront署名、CloudFrontキーペア ID の 3 つの値を持つ署名付き Cookie を返します。CloudFront-Policy には、base64 でエンコードされた JSON のポリシーステートメントが含まれています。ポリシーステートメントを作成するときは、次の 2 つの必須フィールドを定義する必要があります。

  • ベース URL (リソース)
  • Unix 時間形式で指定された URL の有効期限切れ日時 (DateLessThan)

このデモでは、必須フィールドのみを使用します。

Amazon CloudFront 固有の Cookie 値は、Set-Cookie ヘッダー配下のレスポンスヘッダーで設定する必要があります。これらの 3 つの値は、Set-Cookie ヘッダーの value セクションの最初のパラメーターである必要があります。そうしないと、ブラウザがクッキーを読み取ることができず、後続のリクエストでは利用できなくなります。このブログ記事では、ドメイン、パス、有効期限、セキュア、HttpOnly、および SameSite の Cookie 属性を使用します。公式ドキュメントでは、すべての Set-Cookie レスポンスヘッダーについて説明しています。変更されたレスポンスは AWS Lambda@Edge 関数の戻り値です。

status フィールドを 302 に設定し、location ヘッダーを追加すると、ブラウザはリクエストを特定の制限されたコンテンツにリダイレクトするように強制します。ブラウザはレスポンスを受信し、後続のリクエストには署名付き Cookie を使用します。
署名された Cookie は、ブラウザのキャッシュと Cookie を削除しない限り、残りの 1 か月間有効です。制限されたコンテンツにはアクセスできなくなり、上記のメカニズムで再認証する必要があります。AWS Lambda@Edge 関数をエッジで利用できるようにするには、その関数をディストリビューションにロールアウトする必要があります。Amazon CloudFront では、番号付きのAWS Lambda 関数のみをエッジに公開します。

アクションメニューで、AWS Lambda@Edge 関数を Amazon CloudFront ディストリビューションにデプロイします。ビューワーのリクエストが行われるたびに、新しい Amazon CloudFront トリガーを作成する必要があります。正しい Amazon CloudFront ディストリビューションを選択し、チェックボックスをオンにして関数の新しいバージョンを作成します。次に、下の図 3 に示すように、[ Deploy ] ボタンを押します。

図 3 : AWS Lambda@Edge を Amazon CloudFront ディストリビューションにデプロイする

数分後にディストリビューションが更新され、AWS Lambda@Edge 関数がディストリビューションにデプロイされます。認証されたユーザに署名付き Cookie を提供する新しいメカニズムが利用可能になりました。

データ保護

セキュリティは AWS の最優先事項です。このブログ記事では、権限のないユーザーが制限されたコンテンツにアクセスすることを防ぐ方法の 1 つについて概説しました。機密性の高いデータのために、AWS と AWS のパートナーは、ネットワークセキュリティ、設定管理、アクセスコントロール、データ暗号化など、セキュリティ目標の達成を支援する数百ものツールや機能を提供しています。AWS CloudTrail を使用して Amazon S3 バケットや Amazon Simple Email Service による E メールの送信などの AWS リソースに対するアクセスやその他のオペレーションを監査します。AWS は、お客様の活動に適用される GDPR 要件への準拠を支援するサービスとリソースをお客様に提供することに尽力しています。

クリーンアップ

今後の課金を発生させないように、Amazon CloudFront ディストリビューション、Amazon S3 バケット、および AWS Lambda 関数を削除してください。

おわりに

このブログ記事では、静的な Web サイトを保護するための認可について説明しました。署名付き URL や署名付き Cookie などの機能を活用して、匿名ユーザーが制限されたコンテンツにアクセスできないようにすることで、ユーザーを管理せずにユーザーを認証する方法を学習しました。

2 部構成のブログシリーズでは、パスワードやトークンを入力せずに制限されたコンテンツにアクセスするための代替手段を提供します。セキュリティソリューションは、制限されるコンテンツの種類に常に依存します。私たちのセキュリティサービスにより、保管中および転送中の機密データを常に暗号化します。

JSON ウェブトークンまたは Amazon CloudFront ヘッダーをフェイルオーバーに活用したい場合は、以下のブログ投稿をご覧ください。

ご質問やご提案がございましたら、コメントを残してください。

Aleksandar Tolev
Aleksandar Tolev は、アマゾンウェブサービスのソリューションアーキテクトマネージャーで、製造業と自動車のお客様に情熱を注いでいます。技術的なガイダンスと信頼できるアドバイザーとして、自動車業界のお客様のクラウドジャーニーを支援しています。AWS に入社する前は、自動車 OEM のクラウドアーキテクトとして働いていました。

Marco Staufer
Marco Staufer は、アマゾンウェブサービスのグローバルアカウント担当者で自動車業界のお客様向けに活動しています。

翻訳はパートナーサクセスソリューションアーキテクト小林が担当しました。原文はこちらをご覧ください。