見出し画像

Amazon CloudFront KeyValueStoreをSAMを用いて作成してみた

カシオで、バックエンド開発に携わっている入社2年目の伊藤です。

現在担当しているプロジェクトで、AWSが提供するサービス「CloudFront」を使用していることから、このサービスに関する学習や調査を実施しています。
その過程で、2023年11月からCloudFrontがサポートするようになったサービス「KeyValueStore」に目が止まり、現在のプロジェクトに盛り込めないかと考えるようになりました。この記事では、CloudFrontをはじめとしたサービスの役割を紹介したうえで、新サービスKeyValueStoreと、これに紐付くCloudFront Functionsの実装内容をまとめました。
なるべく平易な形で、自分の中で噛み砕いて書いてみましたので、本質的な誤りなどはぜひご指摘いただけると幸いです。


CDNとCloudFront

AWSは、CDN(コンテンツ配信ネットワーク)というサービスを提供しています。CDNは、世界中のエッジロケーションに配置されたサーバーを利用してコンテンツを配信することが特徴で、ユーザーがコンテンツにアクセスする際に、最も近いエッジサーバーからコンテンツが提供されることから、高速な応答時間と低遅延を実現します。

なお、AWSが提供する代表的なCDNサービスとして、CloudFrontが挙げられます。CloudFrontは、ウェブサイトや動画、アプリケーションの静的・動的なコンテンツを配信するだけでなく、APIやストリーミングメディアなど、さまざまなコンテンツの配信に対応しています。

また、CloudFrontは、CloudFront Functionsというサービスを使用することでリクエストやレスポンスの変換、ルーティング、認証などの処理を関数コード(JavaScript)を用いてエッジ(エンドユーザーから最も近い物理的な場所やネットワークの境界)で行うことができます。

そして、このCloudFront Functionsは、KeyValueStoreと呼ばれるデータストアを2023年11月からサポートするようになりました。

KeyValueStore

KeyValueStoreは、CloudFront Functions内でのデータを共有するためのデータストアとなっています。主にキーと値のペアを格納し、CloudFront Functionsからアクセスすることができます。これにより、CloudFront Functionsのコード内で参照するキーと値を別の場所で安全に管理できるようになります。

以下に、KeyValueStoreのSAMを用いた作成方法を記します。

KeyValueStoreの作成方法

SAMテンプレート

KeyValueStoreを作成するためのSAMテンプレートは、以下の通りです。

Type: AWS::CloudFront::KeyValueStore
Properties:
  Comment: String
  ImportSource: 
    SourceArn: String
    SourceType: String
  Name: String
  • comment

    • KeyValueStoreに対するコメント

  • ImportSource

    • KeyValueStoreのインポートソース

  • Name(必須キー)

    • KeyValueStoreを識別する名前

以下、実装例となります。

CloudFrontKeyStore:
    Type: AWS::CloudFront::KeyValueStore
    Properties:
      Comment: "Authority management"
      Name: !Sub "${Product}-${System}-${Environment}-key-value-store"

【補足】ImportSourceキーの活用

今回は、KeyValueStoreでデータ管理しましたが、よりセキュアにデータを管理するためにS3にキーと値を保管し、KeyValueStoreでS3のデータをインポートし活用することも可能となっています。
この場合は、ImportSourceキーでSourceArn及びSourceType(許可される値:S3)を指定し、S3と紐付けします。

CloudFront Functionsとの紐付け方

SAMテンプレート

Cloud FrontFunctionsを作成するためのSAMテンプレートは、以下の通りです。

Type: AWS::CloudFront::Function
Properties:
  AutoPublish: Boolean
  FunctionCode: String
  FunctionConfig: 
    FunctionConfig
  FunctionMetadata: 
    FunctionMetadata
  Name: String
  • AutoPublish

    • LIVE関数の作成時に関数を自動的に公開するか否かを決定するフラグ

  • FunctionCode(必須キー)

    • リクエストやレスポンスを処理する関数コード(JavaScript)

  • FunctionConfig(必須キー)

    • CloudFront機能に関する設定情報

  • FunctionMetadata

    • CloudFront 関数に関するメタデータ

  • Name(必須キー)

    • 関数を識別する名前

KeyValueStoreと紐付くキー

この4つのキーのうちKeyValueStoreと紐付くキーは

  • FunctionConfig

  • FunctionCode
    となります。

FunctionConfigでは、KeyValueStoreARNと紐付けさせます。

FunctionConfig:
Comment: ""
KeyValueStoreAssociations: 
 - KeyValueStoreARN: !GetAtt CloudFrontKeyStore.Arn

FunctionCodeでは、KeyValueStoreに保存されているキー・値を呼び出して活用します。例として、リクエスト認証を実施するコードを以下に示します。

FunctionCode: !Sub |

 import cf from 'cloudfront';

 // KeyValueSrtoreのIdを取得
 const kvsId = '${CloudFrontKeyStore.Id}';

 // KeyValueSrtoreにIdを用いてアクセス
 const kvsHandle = cf.kvs(kvsId);

 async function handler(event) {

 // KeyValueSrtoreから"secretKey"を非同期に取得
  const secretKey = await kvsHandle.get("secretKey");

 // リクエストのヘッダー情報を取得
  const request = event.request;
  const headers = request.headers;

   // 認証処理
  if (!headers['x-secret-key'] || secretKey != headers['x-secret-key'].value) {
       const response = {
       statusCode: 401,
       statusDescription: 'Unauthorized'
     }
     return response;
   }
   return request;
 }

このリクエスト認証では、KeyValueStoreに保存されているキーの値とリクエストヘッダーに含まれるシークレットキーの値の一致を確認することで、リクエストの認証を行います。認証が成功した場合には、リクエストオブジェクトを返します。認証が失敗した場合には、401(Unauthorized)のレスポンスオブジェクトを返します。

このコードの詳細処理について、ステップ順に解説します。

1.cloudfrontライブラリからcfモジュールをインポートします

import cf from 'cloudfront';

2.${CloudFrontKeyStore.Id}の値をkvsId変数に代入しています。これは、実行時に実際のKey Value Store(KVS)IDで置き換えられる変数です。

 // KeyValueSrtoreのIdを取得
 const kvsId = '${CloudFrontKeyStore.Id}';

3.kvsIdを使用して、KeyValueStoreにアクセスするためのハンドルを作成します。

 // KeyValueSrtoreにIdを用いてアクセス
 const kvsHandle = cf.kvs(kvsId);

4.handler関数が定義されており、eventパラメータを受け取ります。

 async function handler(event) {

5.handler関数の内部では、kvsHandleを使用して非同期にKeyValueStoreから"secretKey"の値を取得します。

 // KeyValueSrtoreから"secretKey"を非同期に取得
  const secretKey = await kvsHandle.get("secretKey");

6.eventオブジェクトからリクエストヘッダーを取得します。

 // リクエストのヘッダー情報を取得
  const request = event.request;
  const headers = request.headers;

7.認証プロセスが行われます。リクエストヘッダーにx-secret-keyが存在し、かつその値がKeyValueStoreから取得した"secretKey"の値と一致しない場合、ステータスコードが401(Unauthorized)のレスポンスオブジェクトを返します。
認証が成功した場合、コードはrequestオブジェクトを返します。

   // 認証処理
  if (!headers['x-secret-key'] || secretKey != headers['x-secret-key'].value) {
       const response = {
       statusCode: 401,
       statusDescription: 'Unauthorized'
     }
     return response;
   }
   return request;
 }

実装例

上記の説明を踏まえて、KeyValueStoreとこれに紐づくCloudFrontFunctionsのSAMテンプレート例は以下のようになります。
※CloudFrontFunctionsとCloudFrontの紐付け実装例は、今回の記事のテーマ外のため割愛します。


CloudFrontKeyStore:
    Type: AWS::CloudFront::KeyValueStore
    Properties:
      Comment: "Authority management"
      Name: !Sub "${Product}-${System}-${Environment}-key-value-store"

CloudFrontFunction:
    Type: AWS::CloudFront::Function
    Properties:
      Name: !Sub "${Product}-${System}-${Environment}-cloudfront-function"
      FunctionConfig:
        Comment: ""
        KeyValueStoreAssociations: 
         - KeyValueStoreARN: !GetAtt CloudFrontKeyStore.Arn //CloudFrontKeyStore紐付け
        Runtime: cloudfront-js-2.0
      AutoPublish: true
      FunctionCode: !Sub |
        import cf from 'cloudfront';
        const kvsId = '${CloudFrontKeyStore.Id}';
        const kvsHandle = cf.kvs(kvsId);

        async function handler(event) {
            const secretKey = await kvsHandle.get("secretKey");
            
            const request = event.request;
            const headers = request.headers;

            if (!headers['x-secret-key'] || secretKey != headers['x-secret-key'].value) {
                const response = {
                statusCode: 401,
                statusDescription: 'Unauthorized'
              }
              return response;
            }
            return request;
        }

KeyValueStore活用の場面

今回の実装で、KeyValueStoreは、CloudFront Functionsのコード内で参照するキーと値を別の場所で安全に管理してくれることが改めて分かりました。このことから、機密情報を扱う処理(リクエスト認証処理など)をCloudFront Functionsで実施する際に、KeyValueStoreは重宝されるかと考えています。

以前は、CloudFrontFunctionsを使用する際にシークレットキーや機密情報を直接コード内に埋め込む必要があり、これには機密情報の漏洩や不正利用のリスクがありました。このような懸念点が解消されるのは大きなメリットかと考えます。

また、より安全な情報管理を実現するために、機密情報をS3バケットに保存し、KeyValueStoreを介してインポートする方法もあります。
この方法では、S3バケットへのアクセス権限を厳密に制御し、必要なユーザーだけが機密情報にアクセスできるようになります。この手法については、まだ試したことがないので、是非とも試して、セキュリティ強化につなげたいと考えています。


【カシオのソフトウェア採用についてはこちら】


この記事が参加している募集