この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
CognitoやAuth0を使って、API GatewayのLambdaオーソライザーで「OK・NG」を判断することは多いと思います。 このとき、一部のAPIを公開したくなったので、試してみました。
以前はAWS SAMだけで完結していましたが、本記事では、OpenAPIを使っています。
おすすめの方
- AWS SAMでOpenAPIを使ってAPIを作りたい方
- AWS SAMでLambdaオーソライザーを作りたい方
- AWS SAMで一部のAPI(Lambda)にLambdaオーソライザーを適用したくない方
サーバーレスアプリを作成する
sam init
sam init \
--runtime python3.8 \
--name Lambda-Authorizer-Sample \
--app-template hello-world \
--package-type Zip
OpenAPI定義
Lambdaオーソライザーの設定もあります。
api.yaml
openapi: 3.0.1
info:
title: sample api
version: 1.0.0
paths:
/hello:
get:
tags:
- hello
responses:
200:
$ref: "#/components/responses/200"
403:
$ref: "#/components/responses/403"
500:
$ref: "#/components/responses/500"
security:
- MyLambdaAuthorizer: []
x-amazon-apigateway-integration:
type: aws_proxy
uri:
'Fn::Sub': >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunction.Arn}/invocations
httpMethod: POST
responses:
default:
statusCode: 200
passthroughBehavior: when_no_templates
contentHandling: CONVERT_TO_TEXT
/hello2:
get:
tags:
- hello
responses:
200:
$ref: "#/components/responses/200"
500:
$ref: "#/components/responses/500"
x-amazon-apigateway-integration:
type: aws_proxy
uri:
'Fn::Sub': >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorld2Function.Arn}/invocations
httpMethod: POST
responses:
default:
statusCode: 200
passthroughBehavior: when_no_templates
contentHandling: CONVERT_TO_TEXT
components:
securitySchemes:
MyLambdaAuthorizer:
type: apiKey
name: Authorization
in: header
x-amazon-apigateway-authtype: custom
x-amazon-apigateway-authorizer:
authorizerUri:
'Fn::Sub': >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AuthorizerFunction.Arn}/invocations
authorizerResultTtlInSeconds: 0
type: token
responses:
200:
description: Success
403:
description: User or client is not authorized.
500:
description: Internal Server Error
SAMテンプレート
Lambda自体は3つ作成します。
- Lambdaオーソライザー
GET /hello
用(Authorizerあり)GET /hello2
用(Authorizerなし)
また、API GatewayがLambdaを呼び出す権限(AWS::Lambda::Permission
)も作成します。
- Lambdaオーソライザー用のPermission
- 自分で作成する(★これ)
GET /hello
用のPermission- AWS SAMが自動で作成してくれる
GET /hello2
用のPermission- AWS SAMが自動で作成してくれる
これは、Lambda(Lambdaオーソライザー)にEvents
の定義がないため、AWS SAMがPermissionを作成できないからです。
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Lambda-Authorizer-Sample
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: dev
DefinitionBody:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: s3://cm-fujii.genki-deploy/Lambda-Authorizer-Sample-Stack/api.yaml
# Lambda Authorizer
AuthorizerFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: authorizer.lambda_handler
Runtime: python3.8
Timeout: 5
AuthorizerFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${AuthorizerFunction}
AuthorizerFunctionPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt AuthorizerFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MyApi}/authorizers/*
# API: GET /hello (Authorizerあり)
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.8
Timeout: 5
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
RestApiId: !Ref MyApi
HelloWorldFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}
# API: GET /hello2 (Authorizerなし)
HelloWorld2Function:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.8
Timeout: 5
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello2
Method: get
RestApiId: !Ref MyApi
HelloWorld2FunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${HelloWorld2Function}
Outputs:
HelloWorldApi:
Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/dev/hello"
Lambdaコード
Authorizer
authorization
がabc
ならOKとしています。
authorizer.py
def lambda_handler(event, context):
token = event['authorizationToken']
effect = 'Deny'
if token == 'abc':
effect = 'Allow'
return {
'principalId': '*',
'policyDocument': {
'Version': '2012-10-17',
'Statement': [
{
'Action': 'execute-api:Invoke',
'Effect': effect,
'Resource': event['methodArn']
}
]
}
}
GET /hello
用
GET /hello2
と共通です。
app.py
import json
def lambda_handler(event, context):
return {
'statusCode': 200,
'body': json.dumps({
'message': 'hello world',
}),
}
デプロイ
aws s3 cp \
api.yaml \
s3://cm-fujii.genki-deploy/Lambda-Authorizer-Sample-Stack/api.yaml
sam build
sam package \
--output-template-file packaged.yaml \
--s3-bucket cm-fujii.genki-deploy
sam deploy \
--template-file packaged.yaml \
--stack-name Lambda-Authorizer-Sample-Stack \
--s3-bucket cm-fujii.genki-deploy \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset
動作確認
APIエンドポイントを取得する
aws cloudformation describe-stacks \
--stack-name Lambda-Authorizer-Sample-Stack \
--query 'Stacks[].Outputs'
GET /hello
Authorizationあり(OK)
curl https://xxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello \
--header 'authorization: abc'
{"message": "hello world"}
Authorizationあり(NG)
curl https://xxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello \
--header 'authorization: xxx'
{"Message":"User is not authorized to access this resource with an explicit deny"}
Authorizationなし(NG)
curl https://xxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello
{"message":"Unauthorized"}
GET /hello2
Authorizationなし(OK)
curl https://xxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello2
{"message": "hello world"}
おまけ:すべてにLambdaオーソライザーを適用する場合
すべてのAPIにLambdaオーソライザーを適用する場合は、OpenAPI側ではなく、AWS SAMテンプレート側に定義を記載することも可能です。 ただし、Swagger等で確認したときにAuthorizerに関する情報は見れません。OpenAPI側にsecurityの記載が無いからです。
OpenAPI
api.yaml
openapi: 3.0.1
info:
title: sample api
version: 1.0.0
paths:
/hello:
get:
tags:
- "hello"
responses:
200:
$ref: "#/components/responses/200"
403:
$ref: "#/components/responses/403"
500:
$ref: "#/components/responses/500"
x-amazon-apigateway-integration:
type: "aws_proxy"
uri:
'Fn::Sub': >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunction.Arn}/invocations
httpMethod: "POST"
responses:
default:
statusCode: "200"
passthroughBehavior: "when_no_templates"
contentHandling: "CONVERT_TO_TEXT"
/hello2:
get:
tags:
- "hello"
responses:
200:
$ref: "#/components/responses/200"
500:
$ref: "#/components/responses/500"
x-amazon-apigateway-integration:
type: "aws_proxy"
uri:
'Fn::Sub': >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorld2Function.Arn}/invocations
httpMethod: "POST"
responses:
default:
statusCode: "200"
passthroughBehavior: "when_no_templates"
contentHandling: "CONVERT_TO_TEXT"
components:
responses:
200:
description: "Success"
403:
description: "User or client is not authorized."
500:
description: "Internal Server Error"
SAMテンプレート
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Lambda-Authorizer-Sample
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: dev
Auth:
DefaultAuthorizer: MyLambdaAuthorizer
Authorizers:
MyLambdaAuthorizer:
FunctionArn: !GetAtt AuthorizerFunction.Arn
Identity:
ReauthorizeEvery: 0
DefinitionBody:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: s3://cm-fujii.genki-deploy/Lambda-Authorizer-Sample-Stack/api.yaml
AuthorizerFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: authorizer.lambda_handler
Runtime: python3.8
Timeout: 5
AuthorizerFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${AuthorizerFunction}
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.8
Timeout: 5
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
RestApiId: !Ref MyApi
HelloWorldFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}
HelloWorld2Function:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.8
Timeout: 5
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello2
Method: get
RestApiId: !Ref MyApi
HelloWorld2FunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${HelloWorld2Function}
Outputs:
HelloWorldApi:
Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/dev/hello"
さいごに
「AWS SAM + OpenAPI」でAWS SAM側にLambdaオーソライザーの記述をしている状態から、一部だけオーソライザーを適用外にしたかったのですが、OpenAPI側にLambdaオーソライザーの記述を移動する部分で地味に苦労しました。
どなたかの参考になれば幸いです。