CloudFront の OAC に Lambda 関数 URL を設定してみた

記事タイトルとURLをコピーする

こんにちは、末廣です。

先日の以下アップデートにて、Amazon CloudFront が Lambda 関数 URL オリジンのオリジンアクセスコントロール (OAC) をサポートするようになりました。

aws.amazon.com

そういえば関数 URL についてはこちらのブログにて説明、検証したなあと思い、続きのような感覚で検証しましたので合わせてご参考ください。

いったいどうなるのか

関数 URL を作成すると、 HTTP クライアントから URL に対して HTTP リクエストを送ることで Lambda 関数を実行することができるようになります。 この URL による Lambda の実行権限は IAM の認証、もしくは NONE(認証なし)から選択できます。つまり、誰でもアクセスできるパブリックな URL としてか、制限をかけたい場合は IAM を使ったリクエスト形式でのみ実行可能にする、といった権限の付与ができます。

このように、関数 URL には IAM のリソースベースのポリシーのみでしか制限がかけられないため、HTTP リクエストによる実行にて認証認可をもう少し細かく設定したいならば、API Gateway と統合する方法を選択するほうが有用でした。

docs.aws.amazon.com

docs.aws.amazon.com

ここで今回のアップデートにて、CloudFront の OAC を利用して、特定のディストリビューションのみから関数 URL オリジンを実行できるようになりました。これによって、意図しないユーザーが関数 URL へ直接アクセスするのをブロックし、CloudFront と WAF と統合するなど、より安全に HTTPリクエストに よる Lambda の実行が可能になりそうなので、実際に設定して確認してみます。

検証

手順としては公式ドキュメントを参考にしています。

docs.aws.amazon.com

関数 URL の作成

まず、実行したい Lambda 関数に URL を付与します。 この時、CloudFront からのみアクセスさせたいので、認証には AWS_IAM を選択しましょう。(ここで NONE を選んだ私はアクセスできなくなり、数時間ドツボにはまりました)

関数 URL の作成

CloudFront のオリジン設定

CloudFront のオリジンに 作成した関数 URL を指定してオリジンを追加します。 CloudFront の OAC については以下ブログで説明されておりますのでご参考ください。

blog.serverworks.co.jp

オリジンの作成

関数 URL はオリジンドメインからドロップダウンしても表示されないのでコピーペーストで入力する必要があります。 URL は HTTPS で発行されているのでプロトコルは HTTPS only を選択しましょう。

オリジンに関数 URL の入力

OAC の作成、設定

新しい OAC を作成し、Lambda の権限を更新します。署名について選択項目がありますが、Sign requests (recommended)を選択するようにしてください。手順 に以下のように記載がありますが、サインリクエストを無効化すると関数 URL が パブリックに実行できる状態でないと CloudFront からアクセスできなくなります。

To use this setting, the Lambda function URL must be publicly accessible. If you use this setting with a Lambda function URL that's not publicly accessible, CloudFront can't access the origin. The Lambda function URL returns errors to CloudFront and CloudFront passes those errors on to viewers. For more information, see the example Lambda policy for Policies and Permissions in Lambda in the AWS Lambda User Guide.

OAC の作成

そういえば OAC に S3 バケットを選択する時もバケットポリシーを更新するように言われましたね。

OAC に S3 バケットを選択した時

同じように Lambda 関数を選択した場合も忠告されます。

OAC に Lambda 関数を入力した時

権限の更新は現在 CLI からのみ実行可能なので以下コマンドを実行します。 account-id distribution-id lambda-function-nameをそれぞれ自分の環境に合わせて変更してください。

$ aws lambda add-permission \
--statement-id "AllowCloudFrontServicePrincipal" \
--action "lambda:InvokeFunctionUrl" \
--principal "cloudfront.amazonaws.com" \
--source-arn "arn:aws:cloudfront::<account-id>:distribution/<distribution-id>" \
--function-name <lambda-function-name>
補足:アクセス権限について

上で記載したコマンドを実行すると、Lambda ▶ Configuration ▶ Permissions のリソースベースポリシーに権限が追加されます。

Lambda リソースベースのポリシー

{
  "Version": "2012-10-17",
  "Id": "default",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipal",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "lambda:InvokeFunctionUrl",
      "Resource": "arn:aws:lambda:ap-northeast-1:xxx:function:xxx",
      "Condition": {
        "ArnLike": {
          "AWS:SourceArn": "arn:aws:cloudfront::xxx:distribution/xxx"
        }
      }
    }
  ]
}

ちなみに関数 URL 作成時に認証に NONE を選択すると、以下のようなポリシーが作成されます。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "StatementId": "FunctionURLAllowPublicAccess",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "lambda:InvokeFunctionUrl",
      "Resource": "arn:aws:lambda:ap-northeast-1:xxx:function:lxxx",
      "Condition": {
        "StringEquals": {
          "lambda:FunctionUrlAuthType": "NONE"
        }
      }
    }
  ]
}

私は関数 URL の作成時に認証を NONE で作成し、パブリックに実行ができるか確認した後、このポリシーを削除し、CloudFront からのみアクセスが可能になったか確認しました。 その際、認証を IAM に戻しておらず、OAC によるポリシーが意味をなさない状態であり、CloudFront からも一向にアクセスできず詰まってしまいました。皆様検証する時は関数 URL の認証方法は IAM を指定するようご注意ください。

以上でオリジンの作成が完了です。

ビヘイビアの設定

オリジンに関数 URL を追加できたので CloudFront のビヘイビアを設定します。 オリジンを作成するときは関数 URL を入力する必要がありましたが、ビヘイビアの設定時はドロップダウンから選択可能です。

ビヘイビア設定のオリジン選択

キャッシュポリシーとオリジンリクエストポリシーにはマネージドに 関数 URL 用にあるものを選択します。 オリジンリクエストポリシーですが、AllViewerExceptHostHeader を必ず選択する必要があります。名の通りホストヘッダ以外ビューアリクエストに含むものであり、関数 URL は Host ヘッダが CloudFront ディストリビューションのドメイン名ではなく、オリジンのドメイン名を含む必要があるためです。

キャッシュポリシーとオリジンリクエストポリシーの設定

docs.aws.amazon.com

関数 URL へリクエスト

以上で設定が完了したので実際にアクセスしてみます。

CloudFront のドメインへアクセス

CloudFront のドメインへアクセス

無事Lambda からのレスポンスが確認できました。

Lambda 関数 URL へリクエスト

Lambda 関数 URL へリクエスト

OAC を設定し、CloudFront からのみ関数 URL は実行できるためアクセス拒否されていることが確認できました。

おまけ:Basic 認証と組み合わせてみる

アクセスにもう少し制限をかけるために、CloudFront Functions で Basic 認証を実装し、ユーザ & パスワードを入力したクライアントのみ Lambda 関数を実行できるようにしてみました。

スクリプトは Cloudflare のサンプルプログラムを参考にし、CloudFront にて利用できるように変更しています。

developers.cloudflare.com

function handler(event) {
  const request = event.request;
  const headers = request.headers;

  const BASIC_USER = "sue";
  const BASIC_PASS = "Passw0rd";

  if (typeof headers.authorization === "undefined") {
    return {
      statusCode: 401,
      statusDescription: "No Authorization",
      headers: {
        "www-authenticate": {
          value: 'Basic realm="my scope", charset="UTF-8"',
        },
      },
    };
  }

  const encoded = headers.authorization.value.split(" ")[1];
  const credentials = Buffer.from(encoded, "base64").toString();

  const index = credentials.indexOf(":");
  const user = credentials.substring(0, index);
  const pass = credentials.substring(index + 1);

  if (user != BASIC_USER || pass != BASIC_PASS) {
    return {
      statusCode: 401,
      statusDescription: "Unauthorized",
      headers: {
        "www-authenticate": {
          value: 'Basic realm="my scope", charset="UTF-8"',
        },
      },
    };
  }

  return event.request;
}

ビヘイビアのビューアリクエストに CloudFront Functions を設定します。

CloudFront Functions をビューアリクエストに設定

ユーザ、パスワードを入力後、Lambda からレスポンスが返ってくることが確認できました。

Basic 認証の様子

Basic 認証後

まとめ

本ブログでは CloudFront の OAC を使用し、特定のディストリビューションみからの関数 URL オリジンの実行を試してみました。

Lambda の前段に CloudFront が配置されることで、エッジロケーション、AWS Shield Standard の保護、WAF の組み合わせるようになり、より実用的に Lambda 関数を使えるようになりました。また、CloudFront Functions や Lambda@Edge を合わせて使うことで、認証方式を作成可能になったため、より柔軟なセキュリティ対策にもなりうると考えます。

末廣 満希(執筆記事の一覧)

2022年新卒入社です。ここに何かかっこいい一言を書くことができるエンジニアになれるように頑張ります。