API GatewayでIAM認証を行う
今回はAWSサーバーレスリソースを構築した中で、ハマった箇所や工夫した箇所を全6回に分けてご紹介します。
第1回は全体構成とAPI GatewayでIAM認証を行った時にハマった箇所の話をします。
対象読者
AWSサーバーレスリソースを作りたい方
CloudFormationを使っている方
SAMを使っている方
全体構成
全体構成は以下の通りです。
ユーザーは一般と管理者に分かれていて、一つのCognitoユーザープール内の一般グループと管理者グループにそれぞれ所属している
Cognitoアプリクライアントを使用して、認証前ユーザーがログインやパスワード変更をできるようにする
Cognito IDプールを使用して、Cognitoユーザープールによって認証されたユーザーに、自身が所属するグループに紐づくIAMロールの一時的認証情報を渡す
フロントエンドコンテンツはS3をオリジンとして、CloudFrontが配信している
APIは一般用と管理者用の二つがあり、一般ユーザーは一般用のみを呼び出すことがき、管理者ユーザーは一般用と管理者用を呼び出すことができる
一般用API GatewayはCognitoオーソライザーを使用している
CognitoオーソライザーはCognitoユーザープールによって認証されたユーザーに、IDトークンを発行している
一般用APIはCloudFrontが配信している
管理者用API GatewayはIAM認証を使用している
管理者用APIはAPI Gatewayが配信している
今回のテーマ
上記構成図の赤枠内の管理者用API GatewayにIAM認証をかけて、管理者ユーザーのみが管理者用APIを呼び出せるようにします。
なぜ管理者用APIをCloudFrontで配信しなかったか
API GatewayのIAM認証は一時的認証情報を使用した呼び出しになります。
そのため、Cognitoユーザープールによって発行されたIDトークンを、Authorizationリクエストヘッダに付与するCognitoオーソライザーとはリクエスト内容が異なります。具体的には一時的認証情報を使用して、Hostリクエストヘッダ等に署名をするのですが、CloudFrontがHostヘッダをオリジンに転送する際に、Hostヘッダは書き換えても、署名までは書き換えないため、API Gatewayは不正な署名として扱ってしまうのです。 これを回避するためには、
CloudFrontとAPI Gatewayに同じカスタムドメインを当てる
CloudFrontにLambda Edgeを設置して署名を書き換える
の二通りの方法があります。今回は工数の都合上、管理者用APIを直接API Gatewayから配信することにしました。
参考資料
署名バージョン 4 の署名プロセス
https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-4.html
WebSocket API Gateway の前にCloudFrontを置く
https://dev.classmethod.jp/articles/cloudfront-in-front-on-websocket-api-gateway/
Does API Gateway behind CloudFront not support AWS_IAM authentication?
https://stackoverflow.com/questions/48815143/does-api-gateway-behind-cloudfront-not-support-aws-iam-authentication
ハマった箇所
まず、API GatewayでCognitoオーソライザーとIAM認証を併用して、CloudFrontで一般用と管理者用APIを配信する方法の調査から始め、「なぜ管理者用APIをCloudFrontで配信しなかったか」に行きつくまでに時間がかかりました。 どうやらこのままではCloudFrontで配信ができないらしい、と分かった後、どうやって署名付きリクエストを送るのか?ということにも少し時間がかかりました。
解決方法はこちら
[Python] Boto3以外でV4署名リクエストを行う
https://dev.classmethod.jp/articles/python-v4-signature-without-boto3/
さて、無事にAPI Gatewayにリクエストを送ることができたのですが、無慈悲な500エラーが返り、しかもAPI Gatewayの後段のLambdaにはリクエストが届いていないことが判明しました。
{"status":500, "message":"Internal Server Error"}
原因は、Cognitoユーザープールの管理者グループに紐づくIAMロールに、管理者用APIのLambda起動権限がないことでした。SAMを使用してAPI GatewayとLambdaを構築する場合、Lambdaパーミッションを明示的に記載せずに、API GatewayからLambdaを起動できるのですが、IAM認証の場合はユーザーにAPI Gateway起動権限の他、Lambda起動権限が必要でした。API Gatewayからは403が返ってきても良さそうなものが、500で返ってきてたので何が起きているのか直ぐには分かりませんでした。
以降、今回のテーマに該当するSAMとCloudFormationの記載となります。今回のポイントとなる箇所に ★★★ポイント★★★ を付けています。
SAMテンプレート
管理者用API GatewayとLambdaの箇所を抜粋します。
API GatewayのCloudWatch Logsの設定にもハマったので、次回以降のテーマとして扱い、その箇所のテンプレートも掲載します。
#===============================
# Globals Selection
#===============================
Globals:
#===============================
# API Gateway
#===============================
Api:
CacheClusterEnabled: false
EndpointConfiguration:
Type: EDGE
MethodSettings:
- CachingEnabled: false
DataTraceEnabled: true
HttpMethod: '*'
LoggingLevel: INFO
MetricsEnabled: true
ResourcePath: '/*'
ThrottlingBurstLimit: 1000
ThrottlingRateLimit: 1000
TracingEnabled: true
#===============================
# Lambda
#===============================
Function:
Runtime: go1.x
Timeout: 27
VpcConfig:
SubnetIds:
- <サブネットID>
- <サブネットID>
SecurityGroupIds:
- <セキュリティグループID>
EventInvokeConfig:
MaximumRetryAttempts: 0
KmsKeyArn: <KMS ARN>
MemorySize: 256
Tracing: Active
Resources:
#===============================
# API Gateway for Admin
#===============================
AdminApiResource:
Type: AWS::Serverless::Api
Properties:
Auth:
AddDefaultAuthorizerToCorsPreflight: false
ApiKeyRequired: false
DefaultAuthorizer: AWS_IAM # ★★★ポイント★★★
Cors:
AllowMethods: "'*'"
AllowHeaders: "'*'"
AllowOrigin: "'*'"
AllowCredentials: true # ★★★ポイント★★★
GatewayResponses:
DEFAULT_4XX:
ResponseParameters:
Headers:
Access-Control-Allow-Origin: "'*'"
Access-Control-Allow-Headers: "'*'"
Access-Control-Allow-Methods: "'*'"
ResponseTemplates:
'application/json': '{"message":$context.error.messageString}'
DEFAULT_5XX:
ResponseParameters:
Headers:
Access-Control-Allow-Origin: "'*'"
Access-Control-Allow-Headers: "'*'"
Access-Control-Allow-Methods: "'*'"
ResponseTemplates:
'application/json': '{"message":$context.error.messageString}'
Name: !Sub ${ServiceName}-${Env}-admin
StageName: api
#===============================
# Lambda for Admin
#===============================
GetTech4All:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${ServiceName}-${Env}-get_tech4all
CodeUri: src/get_tech4all/
Handler: get_tech4all
Role: !ImportValue LambdaBackendRoleArn
Events:
CatchAll:
Type: Api
Properties:
Path: /tech4all
Method: GET
RestApiId: !Ref AdminApiResource
#==============================
# Outputs
#==============================
Outputs:
#=======================================
# Admin API Gateway ID
#=======================================
AdminApiResourceId:
Value: !Ref AdminApiResource
Export:
Name: AdminApiResourceId
参考資料
ApiAuth
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-property-api-apiauth.html
CorsConfiguration
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-property-api-corsconfiguration.html
CloudFormationテンプレート
SAMテンプレートにはAPI GatewayとLambdaのみを記載し、それ以外のAWSリソースは全てCloudFormationに記載しています。今回は、Lambda用のIAMロールとCognitoユーザープールの管理者グループに紐づくIAMロールの箇所を抜粋しています。
Cognitoにまつわる箇所は次回以降に掲載します。
Resources:
#===============================
# Lambda Backend IAM Role
#===============================
LambdaBackendRole:
Type: AWS::IAM::Role
UpdateReplacePolicy: Retain
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: lambda.amazonaws.com
RoleName: !Sub ${ServiceName}-${Env}-lambda-backend-role
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess
- arn:aws:iam::aws:policy/service-role/AWSLambdaENIManagementAccess
#===============================
# Cognito Authenticated Admin IAM Role
#===============================
CognitoAuthenticatedAdminRole:
Type: AWS::IAM::Role
UpdateReplacePolicy: Retain
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action: sts:AssumeRoleWithWebIdentity
Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Condition:
StringEquals:
cognito-identity.amazonaws.com:aud: !Ref IdentityPool
ForAnyValue:StringLike:
cognito-identity.amazonaws.com:amr: authenticated
RoleName: !Sub ${ServiceName}-${Env}-cognito-authenticated-admin-role
Policies:
- PolicyName: !Sub ${ServiceName}-${Env}-cognito-authenticated-admin-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: Cognito
Effect: Allow
Action:
- cognito-identity:GetCredentialsForIdentity
- cognito-identity:GetId
Resource: "*"
- Sid: APIGateway
Effect: Allow
Action: execute-api:Invoke # ★★★ポイント★★★
Resource:
!Join
- ''
- - !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}
- ':'
- !ImportValue AdminApiResourceId
- '/api/GET/tech4all'
- Sid: Lambda
Effect: Allow
Action: lambda:InvokeFunction # ★★★ポイント★★★
Resource: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ServiceName}-${Env}-get_tech4all
#==============================
# Outputs
#==============================
Outputs:
#===============================
# LambdaBackend IAM Role
#===============================
LambdaBackendRoleArn:
Value: !GetAtt LambdaBackendRole.Arn
Export:
Name: LambdaBackendRoleArn
この記事が気に入ったらサポートをしてみませんか?