0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

署名付きCookieを用いてリソースへの直接アクセスを制御する。

Posted at

概要

本記事はCloudFrontの署名付きCookieを用いてS3上の特定のリソースへのアクセス制御を構築する手順の忘備録となります。

背景

  • とあるWebページにおいて、利用しているOSSの問題により、本来ならページのURLを共有するところで表示しているページ内のリソースのURLが共有され、意図せずS3からリソースファイルがダウンロードされるケースが発生していました。
  • 構成は下図のようなAWSのCloudFront + S3を用いた形となります。
    【WebAR】署名付きCookieの対応について (5).png

今回はCloudFrontの署名付きCookieを用いて対策を行ったので設定までの流れを記事として残します。

署名付きCookieについて

署名付きCookieとは、CloudFrontのコンテンツアクセス制御機能の一つで、キーペアを用いてCookieに電子署名を行い、設定したポリシーによって特定のコンテンツへのアクセスを制限する仕組みです。これによりCookieが設定されたブラウザからのみ特定のリソースファイルにアクセスできるようになります。

構築

前提としまして、Route53で取得したドメインを用いてCloudFront + S3の構成でWebページを既に構築している状態から構築を進めます。

CloudFrontの設定①

キーペアの作成

$ openssl genrsa -out private_key.pem 2048
$ openssl rsa -pubout -in private_key.pem -out public_key.pem

公開鍵の登録

  • キー管理のパブリックキーに作成したpublic_key.pemの中身を登録します。
    スクリーンショット 2024-11-26 10.04.38.png

キーグループの作成

  • キー管理のキーグループに先ほど登録した公開鍵を含んだグループを作成します。
    スクリーンショット 2024-11-26 13.55.39.png

ビヘイビアの作成

  • 作成済みのディストリビューションに対して、アクセス制限を行うためのビヘイビアを作成します。
    • 例えばWebページで扱うmp4ファイルに対して署名付きCookieがないとアクセスできないよう制限を掛ける場合、/*.mp4がパスパターンとなります。
      screencapture-us-east-1-console-aws-amazon-cloudfront-v4-home-2024-11-26-10_17_36.png

これにより署名付きCookieがない場合、Webページから対象の3Dモデルファイルへのアクセスが行えなくなります。

次に署名付きCookieを付与する仕組みを作成します。
現在WebページはCloudFrontの関数によりBasic認証を掛けており、認証後にAPIでCookieが設定されるよう下図のように追加の構築を行います。

【WebAR】署名付きCookieの対応について (6).png

APIの作成

  • 署名付きCookieをセットするAPIを東京リージョン(ap-northeast-1)に構築します。
    • CookieをセットするためにAPIは構築済みのWebサイトと同じドメインとする必要があります。

Lambda作成

  • 今回はNode.js 18.xでaws-sdkのgetSignedCookiesを用いて署名付きCookieを取得します。
import { getSignedCookies } from "@aws-sdk/cloudfront-signer"; // ESM

const s3ObjectKey = process.env.S3_OBJECT_KEY;
const url = `${s3ObjectKey}`;
const privateKey = (process.env.PRIVATE_KEY).replace(/\\n/g, '\n');
const keyPairId = process.env.KEY_PAIR_ID;

const policy = {
  Statement: [
    {
      Resource: url,
      Condition: {
        DateLessThan: {
          'AWS:EpochTime': Math.floor((Date.now() + 24 * 60 * 60 * 1000) / 1000)
        },
      },
    },
  ],
};

const policyString = JSON.stringify(policy);

const cookies = getSignedCookies({
  keyPairId,
  privateKey,
  policy: policyString,
});

export const handler = async (event) => {
  // カスタムドメインを設定
  const origin = 'custom-domain';

  const cookieString = [
    `CloudFront-Key-Pair-Id=${cookies['CloudFront-Key-Pair-Id']}; Path=/; Secure; SameSite=Strict`,
    `CloudFront-Policy=${cookies['CloudFront-Policy']}; Path=/; Secure; SameSite=Strict`,
    `CloudFront-Signature=${cookies['CloudFront-Signature']}; Path=/; Secure; SameSite=Strict`
  ].join('; ');

  const response = {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': origin,
      'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
      'Access-Control-Allow-Credentials': 'true',
    },
    multiValueHeaders:{
      'Set-Cookie': [
        `CloudFront-Key-Pair-Id=${cookies['CloudFront-Key-Pair-Id']}; Path=/; Domain=${origin}; Secure; SameSite=None;`, 
        `CloudFront-Policy=${cookies['CloudFront-Policy']}; Path=/; Domain=${origin}; Secure; SameSite=None`,
        `CloudFront-Signature=${cookies['CloudFront-Signature']}; Path=/; Domain=${origin}; Secure; SameSite=None`,
      ],
    },
    body: JSON.stringify({
      message: 'Signed cookies have been set'
    })
  };
  
  return response;
};

環境変数

  • S3_OBJECT_KEY:CF経由のアクセス制御対象のファイルパス。(e.g. https://custom-domain/model/*)
  • PRIVATE_KEY:作成した秘密鍵の改行を\nに置換した文字列。
    • 秘密鍵は現在環境変数から取得していますが、SecretsManagerから取得するよう変更予定です。
  • KEY_PAIR_ID:CloudFrontに登録したパブリックキーのID。

AWS Certificate Manager 設定

  • API Gatewayで独自ドメインを利用するため、API Gatewayを構築するリージョン(今回はap-northeast-1)でパブリック証明書の発行を行います。

証明書をリクエスト

screencapture-ap-northeast-1-console-aws-amazon-acm-home-2024-11-26-14_19_04.png

API Gateway作成

クッキーセットに際しWebページと同じドメインでAPIを呼べるようAPI Gatewayを作成します。

API

APIを作成

screencapture-ap-northeast-1-console-aws-amazon-apigateway-main-create-rest-2024-11-26-14_39_09.png

リソースを作成

screencapture-ap-northeast-1-console-aws-amazon-apigateway-main-apis-dfp5veiugc-resources-5zkslzu70a-create-resource-2024-11-26-14_57_52.png

メソッドを作成

screencapture-ap-northeast-1-console-aws-amazon-apigateway-main-apis-dfp5veiugc-resources-5zkslzu70a-create-method-2024-11-26-14_58_25.png

上記のAPIをデプロイします。ステージ名は暫定で「test」としています。
スクリーンショット 2024-11-26 16.10.29.png

カスタムドメイン名を作成

ドメイン名を追加

screencapture-ap-northeast-1-console-aws-amazon-apigateway-main-publish-domain-names-create-2024-11-26-15_02_43.png

登録したカスタムドメインに作成済みのAPIとステージをマッピングします。
このとき、パスにはドメイン内でAPIを分岐するためのパスパターンを設定します。(e.g. api)
screencapture-ap-northeast-1-console-aws-amazon-apigateway-main-publish-domain-names-mappings-2024-11-26-15_08_46.png

このパスパターンで後ほどCloudFrontのビヘイビアに登録を行います。

CloudFrontの設定②

  • API Gatewayにルーティングするためのオリジン登録します。

オリジンを作成

screencapture-us-east-1-console-aws-amazon-cloudfront-v4-home-2024-11-26-15_17_21.png

ビヘイビアの作成

screencapture-us-east-1-console-aws-amazon-cloudfront-v4-home-2024-11-26-15_17_54.png

APIの呼び出し

最後にWebページより作成したAPI(https://custom-domain/api/API名)を呼び出し、ブラウザにCookieが設定されていることを確認します。
スクリーンショット 2024-11-26 15.29.36.png

カスタムエラーの登録

現在の設定では署名付きCookieが設定されていない状態で対象のリソースにアクセスがあるとCloudFrontのエラーページが表示されます。
スクリーンショット 2024-11-26 15.34.53.png

このままでも良いのですが、若干不親切なのでS3にエラーページのHTMLを追加してCloudFrontから403エラーの際に参照する形で設定します。
スクリーンショット 2024-11-26 15.32.15.png

カスタムエラー設定後のエラーページ
スクリーンショット 2024-11-26 15.38.35.png

さいごに

今回は署名付きCookieで対応を行いましたが、署名付きCookieを設定した上で対象リソースのURLがわかればファイルをDLできてしまうという問題が残っています。HTMLからの参照は許可しつつファイルへの直接アクセスを制御する方法がないか引き続き検討を行っていこうと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?