概要
本記事はCloudFrontの署名付きCookieを用いてS3上の特定のリソースへのアクセス制御を構築する手順の忘備録となります。
背景
- とあるWebページにおいて、利用しているOSSの問題により、本来ならページのURLを共有するところで表示しているページ内のリソースのURLが共有され、意図せずS3からリソースファイルがダウンロードされるケースが発生していました。
- 構成は下図のようなAWSのCloudFront + S3を用いた形となります。
今回は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
公開鍵の登録
キーグループの作成
ビヘイビアの作成
- 作成済みのディストリビューションに対して、アクセス制限を行うためのビヘイビアを作成します。
これにより署名付きCookieがない場合、Webページから対象の3Dモデルファイルへのアクセスが行えなくなります。
次に署名付きCookieを付与する仕組みを作成します。
現在WebページはCloudFrontの関数によりBasic認証を掛けており、認証後にAPIでCookieが設定されるよう下図のように追加の構築を行います。
APIの作成
- 署名付きCookieをセットするAPIを東京リージョン(ap-northeast-1)に構築します。
- CookieをセットするためにAPIは構築済みのWebサイトと同じドメインとする必要があります。
Lambda作成
- 今回はNode.js 18.xでaws-sdkの
getSignedCookies
を用いて署名付きCookieを取得します。- 参考:@aws-sdk/cloudfront-signer内Get signed cookies with a Policy
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)でパブリック証明書の発行を行います。
証明書をリクエスト
API Gateway作成
クッキーセットに際しWebページと同じドメインでAPIを呼べるようAPI Gatewayを作成します。
API
APIを作成
リソースを作成
メソッドを作成
上記のAPIをデプロイします。ステージ名は暫定で「test」としています。
カスタムドメイン名を作成
ドメイン名を追加
登録したカスタムドメインに作成済みのAPIとステージをマッピングします。
このとき、パスにはドメイン内でAPIを分岐するためのパスパターンを設定します。(e.g. api
)
このパスパターンで後ほどCloudFrontのビヘイビアに登録を行います。
CloudFrontの設定②
- API Gatewayにルーティングするためのオリジン登録します。
オリジンを作成
ビヘイビアの作成
APIの呼び出し
最後にWebページより作成したAPI(https://custom-domain/api/API名
)を呼び出し、ブラウザにCookieが設定されていることを確認します。
カスタムエラーの登録
現在の設定では署名付きCookieが設定されていない状態で対象のリソースにアクセスがあるとCloudFrontのエラーページが表示されます。
このままでも良いのですが、若干不親切なのでS3にエラーページのHTMLを追加してCloudFrontから403エラーの際に参照する形で設定します。
さいごに
今回は署名付きCookieで対応を行いましたが、署名付きCookieを設定した上で対象リソースのURLがわかればファイルをDLできてしまうという問題が残っています。HTMLからの参照は許可しつつファイルへの直接アクセスを制御する方法がないか引き続き検討を行っていこうと思います。