はじめに
AWS LambdaのFunction URLs(関数URL)は、Lambda単体でHTTPSのURLを発行し、HTTPリクエストをトリガーにLambdaを実行出来るようになる、非常に便利な機能です。
API Gatewayと統合せずともLambdaのみでWebAPIを構築出来るようになり、プロトタイピングやマイクロサービスに有用です。
関数URLの制限
ところで、関数URLの実行の認可は、IAMを用いた方法しかありませんでした(IAMロールベースの認可か、認可なししか無かった)。
Cloudfrontをリバースプロキシ的に前段に配置し、関数URLと繋ぐことで、ドメインを当てたりキャッシュを活用したり、便利な訳ですが、その際に上記が問題となります。というのは、CloudfrontからIAMベースのリクエストを行うには、Lambda@Edgeを利用するしかありませんでした(オリジンリクエストをIAMロールを付与したLambda@Edgeに任せる)。全てのリクエストでLambda@Edgeが実行されるとコストがかかりますし、せっかく関数URLはシンプルなのに、無用な複雑さを招いているようで、筋が良い感じがしません。
関数URLが発行するURLはランダム文字列なので、URLが漏れなければ安全、最悪漏れてもアプリケーション側でanonymousなリクエストにも耐えるゼロトラストな実装をしておけば、セキュリティ的な問題は起きないでしょうが、(URLがわかれば)誰でも好きなだけLambdaを実行出来てしまう、というのは怖いものです(関数URLにはWAFを設定出来ないこともある)。これを解決するにはAPI Gatewayを使うしかありませんでした。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-lambda.html
POST/PUTリクエストは追加の署名が必要らしいので、要注意。
CloudfrontのOACが関数URLをサポート
2024年4月11日にアナウンスされました。OAC(Origin Access Control)とは、Cloudfrontのディストリビューションからのみ、そのオリジンにアクセスを許可するような機能で、AWS S3では一般的に利用されていますね。これが関数URLに来たらしい、と。上記の問題全てが解決してしまう…!試してみます。
Lambda関数を作成する
大人気Hono.jsでさっくりと
npm create hono@latest oac
ランタイムはAWS Lambdaを選択しておきます。
以下のようなコードが生成されます。
import { Hono } from 'hono'
import { handle } from 'hono/aws-lambda'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
export const handler = handle(app)
npm run build
npm run zip
zipファイルが作成されます。
コンソールからLambdaを作成します。詳細設定で「関数URLを有効化」にチェックをつけておきましょう。動作確認するため、まずは認証タイプは「NONE」としておきます。
zipファイルをアップロードし、発行された関数URLにリクエストしてみましょう。
curl https://<randomurl-to-lambda-functionurl>.lambda-url.ap-northeast-1.on.aws/
Hello Hono!
関数URLの設定を変更して、認証タイプを「AWS IAM」として、再度リクエストしてみましょう。
curl https://<randomurl-to-lambda-functionurl>.ap-northeast-1.on.aws/
{"Message":"Forbidden"}
リクエスト出来なくなったことがわかります。
Cloudfrontと連携させてみる
ディストリビューションの作成
Cloudfrontディストリビューションを作成します。Origin Domainに、関数URLのドメインを入力すると、Cloudfrontから関数URLにリクエストを振り向けることが出来ます。
赤枠で示した箇所がOACの設定です。クリックしてみるとー
「オリジンタイプLambda」!?
設定してみましょう…。これでCloudfrontディストリビューションを作成します。
curl https://path-to-distribution.cloudfront.net/
{"Message":"Forbidden"}
この段階ではまだ繋がらない。
Lambda側の追加設定
作成したディストリビューションの設定画面を見ると、以下のような表示がありました。
S3のOACでも、Cloudfront側だけでなくS3のポリシーも変更する必要があるから、同じことですね。CLIのコマンドが書いてあるのでこれを実行すれば良さそう。ですがここはマネジメントコンソールだけで頑張ってみます。
Lambdaの「アクセス権限」設定には以下のような画面があります。ここが上記のCLIで設定する項目のようですね。
コマンド例を参考に入力します。
これでCloudfrontと関数URLにリクエストしてみましょう。
curl https://path-to-distribution.cloudfront.net/
Hello Hono!
curl https://path-to-functionurl.lambda-url.ap-northeast-1.on.aws/
{"Message":"Forbidden"}
Cloudfrontからのリクエストでのみ、関数が実行されています!!これは素晴らしい!
OACのポリシーを見直す
先ほど、Lambdaでポリシーステートメントを設定しましたが、見直してみます。
ポリシーを表示してみると…
このような記述になっています。CLIのコマンド例だと、ディストリビューションのARNを指定出来ていましたが、GUIからだと出来ていません(実際入力する箇所がなかった)。マネジメントコンソールには入力項目がなかったので未実装なのでしょう。現状だと、おそらく任意のCloudfrontディストリビューションから「AWS IAM」ベースのリクエストを受け入れるようになっています。
CLIで設定しなおす
関数URLのURLを知ったうえで、ディストリビューション側で明示的に設定が必要なのでリスクは極めて小さいでしょうが、リクエスト元のCloudfrontは限定しておくべきなので、ちゃんとCLIで設定しなおします。先ほどポリシーは一旦削除したうえで、以下を実行します。
aws lambda add-permission \
--statement-id "AllowCloudFrontServicePrincipal" \
--action "lambda:InvokeFunctionUrl" \
--principal "cloudfront.amazonaws.com" \
--source-arn "arn:aws:cloudfront::123456789012:distribution/ABCDEFGHIJKL" \
--function-name cf-oac-test
ポリシーを再確認するとー
ディストリビューションのARNが記述されていますね、これで一安心です。一応再実行してみてー
curl https://path-to-distribution.cloudfront.net/
Hello Hono!
curl https://path-to-functionurl.lambda-url.ap-northeast-1.on.aws/
{"Message":"Forbidden"}
無事にオリジンを保護出来ていることがわかります。今のところ、適切な設定はCLIを通じてのみ可能なようです。GUIはアップデート待ち。
終わりに
関数URLで唯一不安だったのが、オリジンを完全に保護出来ないという点でした。それが解決されたので、API Gateway + Lambdaという構成をとる必要性がこれまで以上に小さくなりました。また、AWS LambdaはHTTPを話すコンテナイメージを動かすことが出来るようになっているので、なんでもLambdaで動かすのが安い・速い・旨いかもしれませんね!