前回に引き続き、手探りで API Gateway を設定している.
今回は認証を追加する。といっても実装中の Lambda が外部から好き勝手に叩かれなければそれでいいので、決め打ちの key の値があるかどうか、くらいの設定をする。
API Gateway の API Key を利用しようと思ったが、 API Key は API Gateway v1 (REST API) で利用できる機能で、今使っている v2 では適用できなかった。
v2 の HTTP Request では Authorization の実現に 2 つの選択肢がある。 JWT と lambda だ。
JWT を選べば、必要な parameter を入れておくだけで、OIDC に準拠した IdP Provider と勝手に連携してくれると思われる(推測。実装はしていない)。
今回は IdP や Cognito を使うほどではないので、lambda で簡単に済ませたい。
terraform 実装
シンプルな lambda 関数を定義
exports.handler = async (event) => {
const apiKey = event.headers['x-api-key'];
// APIキーの検証
if (apiKey !== API_KEY) {
return {
isAuthorized: true,
context: {
user: 'authorized'
}
};
} else {
return {
isAuthorized: false
};
}
};
見ての通り、決め打ちの値が header に含まれているか、で判定を行っている。自分用の開発であれば、これで十分だろう。
lambda 関数のリソースを作成
resource "aws_lambda_function" "api_authorizer" {
architectures = ["x86_64"]
description = "API Authorizer"
filename = data.archive_file.authorizer.output_path
function_name = "ApiAuthorizer"
role = aws_iam_role.lambda_role.arn
handler = "authorizer.handler"
runtime = "nodejs20.x"
source_code_hash = data.archive_file.authorizer.output_base64sha256
}
data "archive_file" "authorizer" {
type = "zip"
source_dir = "functions/authorizer"
output_path = "functions/authorizer.zip"
}
authorizer resource を定義
resource "aws_apigatewayv2_authorizer" "temporal_authorizer" {
api_id = aws_apigatewayv2_api.lambda.id
authorizer_type = "REQUEST"
authorizer_uri = aws_lambda_function.api_authorizer.invoke_arn
identity_sources = ["$request.header.Authorization"]
name = "temporal-authorizer"
authorizer_payload_format_version = "2.0"
enable_simple_responses = true # isAuthorized: true 形式のレスポンスを利用
}
enable_simple_responses がポイント。false の場合は、IAM 形式の response を返す必要がある
route を update して、lambda authorizer を使うように指定
resource "aws_apigatewayv2_route" "hello_world" {
api_id = aws_apigatewayv2_api.lambda.id
authorization_type = "CUSTOM"
authorizer_id = aws_apigatewayv2_authorizer.temporal_authorizer.id
...
}
動かしてみる
authorization header をつける
デプロイした後、該当の endpoint に curl request すると、Unauthorized が帰ってきてしまった。
% curl "$(terraform output -raw base_url)/hello?Name=Terraform" -H "x-api-key: API_KEY"
{"message":"Unauthorized"}%
色々調査したり試行錯誤した結果、 authorization header をつけてあげる必要があった。
-H "authorization: abc"
aws_apigatewayv2_authorizer で定義した↓の部分が、request にそもそも含まれていないということで、 Unauthorized になってしまっていた。
identity_sources = ["$request.header.Authorization"]
cache が効いてるかも
curl request すると、意図した通りに本来の結果が返された。認可処理を無事通過できたことがわかる。
% curl "$(terraform output -raw base_url)/hello?Name=Terraform" -H "x-api-key: XXX" -H "authorization: abc"
{"message":"Hello, Terraform!"}%
ここで、 API_KEY を誤った値に変えたらどうなるかを試してみた
% curl "$(terraform output -raw base_url)/hello?Name=Terraform" -H "x-api-key: WRONG" -H "authorization: abc"
{"message":"Hello, Terraform!"}%
すると、認可された時と同じ response が帰ってきた。
疑問に思って調べると、以下の記述を見つけた。
For HTTP APIs, identity sources are also used as the cache key when caching is enabled.
identity_sources の値(ここでは、 authorization header の値)がキャッシュのkeyに使われているとのことだった。
ということで、キャッシュが効かない別の値でリクエストすると、以下のように意図した通り Forbidden が帰ってきた
% curl "$(terraform output -raw base_url)/hello?Name=Terraform" -H "x-api-key: WRONG" -H "authorization: different"
{"message":"Forbidden"}%
終わりに
これで最低限の認可がある状態でデプロイされた環境で開発を進められるようになった