0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

サーバーレスAPIを安全に!CloudFrontとLambdaとGitHub Actionsで作るセキュアな仕組み

Posted at

AWS CloudFrontとLambdaを組み合わせたAPIへのアクセス制限方法

本記事は、GitHubで公開しているアプリケーション「ai-mon」のバックエンドAPI(APIGateway + Lambda)に、CloudFront経由でのアクセスのみを許可するための仕組みを実装した際の技術的なメモです。具体的には、カスタムヘッダーを使ったアクセス制限について解説します。

下記の記事を参考にsamコマンドで実行する方法を整理しました。

なぜCloudFrontからのアクセスのみに制限するのか?

多くのWebアプリケーションでは、コンテンツ配信を最適化するためにCloudFront(CDN: Content Delivery Network)を導入します。これにより、ユーザーは高速なアクセスが可能になります。このアプリケーションもCloudFrontを介してAPIGatewayとLambdaで構成されたAPIにアクセスする設計です。

しかし、CloudFrontを経由させても、APIGatewayのエンドポイントURLは外部から直接アクセス可能です。エンドポイント自体は複雑な文字列ですが、アプリケーションのソースコードが公開されている場合、そのURLを知られてしまうリスクがあります。

別の認証機能がありますが、そちらはDBなどへのアクセスがあり不要なアクセスによって必要以上のリソースを消費することにつながります。
そうした必要以上のリソースの消費を防ぐための仕組みが必要になります。

どのようなアクセス制限を行なったか

今回は、CloudFrontからAPIGatewayにリクエストを送る際に、特定のカスタムヘッダーを付与し、Lambda側でそのヘッダーの値が正しいかを検証する手法を採用しました。
これにより、カスタムヘッダーを持たない直接的なアクセスをLambdaで拒否し、不要なリソース消費を防ぐことができます。この手法は、APIGatewayのAPIキーやリソースポリシーだけでは実現できない、より厳密なアクセス制限ができます。

どう構築するか

構築には、GitHub ActionsによるCI/CDとAWS SAM(Serverless Application Model)を利用しました。これにより、カスタムヘッダーのシークレット値を安全に管理し、デプロイプロセスを自動化できます。

さらにCloudFrontからのカスタムヘッダーの存在有無だけではなく、値自体も比較しチェックするようにする必要があります。さらにその値を秘匿化することと値のずれを発生しないようにすることが必要になります。
概念図はこちらです。

image.png

samコマンド&yamlファイルの記述

値はGithubのシークレットから取得して反映するので値を秘匿されます。

GithubActionの値の受け渡し

以下のコードで値を受け渡します。
CLOUD_FRONT_API_SECRET_VALUEで取得した値を、CLOUD_FRONT_API_SECRET_VALUEの変数に変更して受け渡しています。

- name: Deploy SAM Application
        env:
          CLOUD_FRONT_API_SECRET_VALUE: ${{ secrets.CLOUD_FRONT_API_SECRET_VALUE }}
        run: sam deploy --region ap-northeast-1 --stack-name スタック名 --no-confirm-changeset --no-fail-on-empty-changeset --config-file ./backend/src/lambda/samconfig.toml --parameter-overrides "CloudFrontSecretHeaderValue=${CLOUD_FRONT_API_SECRET_VALUE}"

Globals:
  Function:
    Timeout: 30
    Runtime: nodejs22.x
    LoggingConfig:
      LogFormat: JSON
    Environment:
      Variables:
        CLOUD_FRONT_API_SECRET_VALUE: !Ref CloudFrontSecretHeaderValue
        
Parameters:
  CloudFrontSecretHeaderValue:
    Type: String
    Description: "Secret value for CloudFront custom header"
------------------------------------------------------------------------
Cloudfront:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
          - Id: ApiGatewayOrigin
            DomainName: !Sub '${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com'
            OriginCustomHeaders:
              - HeaderName: X-CloudFront-Secret
                HeaderValue: !Ref CloudFrontSecretHeaderValue


LambdaはGlobalsで共通の変数として定義して、全ての関数に値を分配しました。

CloudFrontの確認

オリジンの設定の中に以下のようなカスタムヘッダーの設定があれば正しく連携できています。
image.png

Lambdaの設定

Lambda側の環境変数に設定されていれば正しく連携できています。

image.png

Lambdaの実装

実装の一部を抜粋しました。以下の実装で値のチェックを行いました。

/**
 * CloudFrontからのカスタムヘッダーと��境変数の値を比較してリクエストを検証します。
 * @param {object} headers - リクエストヘッダー
 * @returns {boolean} - ヘッダーの値が環境変数の値と一致する場合はtrue、それ以外はfalse
 */
export const validateCloudFrontSecret = (headers) => {
    const secretFromHeader = headers['X-CloudFront-Secret'];
    const secretFromEnv = process.env.CLOUD_FRONT_API_SECRET_VALUE;

    if (!secretFromHeader || !secretFromEnv) {
        console.error("Secret value is missing in header or environment variable.");
        return false;
    }

    return secretFromHeader === secretFromEnv;
};

まとめ

今回はAPIGatewayのアクセス制御を行うことで、不正なアクセスを防止する環境の構築をまとめました。

0
2
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
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?