AWS SAMでAPI GatewayのIAM認証をしてみた

AWS SAMでAPIを作るとき、IAM認証をしてみました。
2022.02.22

API GatewayとLambdaで作るAPIがあります。本記事では、このAPIにIAM認証を適用してみました。 これらの仕組みをAWS SAMで作成します。

おすすめの方

  • AWS SAMでAPI GatewayのIAM認証をしたい方
  • AWS SAMでAPI(API Gateway)を作りたい方
  • AWS SAMでOpen APIを使いたい方

AWS SAMでAPIを作成する

sam init

sam init \
    --runtime python3.9 \
    --name Api-IAM-Sample \
    --app-template hello-world \
    --package-type Zip

OpenAPI定義

GET /helloを作成します。

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:
        - awsSigv4: []
      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

components:
  securitySchemes:
    awsSigv4:
      type: apiKey
      name: Authorization
      in: header
      x-amazon-apigateway-authtype: awsSigv4

  responses:
    200:
      description: Success
    403:
      description: User or client is not authorized.
    500:
      description: Internal Server Error

SAMテンプレート

次のリソースを定義しています。

  • IAMユーザ
  • IAMユーザ用のIAMポリシー
  • API Gateway
  • Lambda
  • Lambda用のロググループ

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Api-IAM-Sample

Parameters:
  StageName:
    Type: String
    Default: v1

Resources:
  # IAM認証で使うIAMユーザ(アクセスキーを使う)
  IamUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Sub api-gateway-user

  IamUserPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub api-gateway-user-policy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: execute-api:Invoke
            Resource: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${HelloWorldApi}/${StageName}/GET/hello
      Users:
        - !Ref IamUser

  HelloWorldApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref StageName
      Auth:
        DefaultAuthorizer: AWS_IAM
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: s3://cm-fujii.genki-deploy/Api-IAM-Sample-Stack/api.yaml


  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.9
      Timeout: 3
      Architectures:
        - x86_64
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
            RestApiId: !Ref HelloWorldApi


  HelloWorldFunctionLogGroup:
      Type: AWS::Logs::LogGroup
      Properties:
        LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}

Outputs:
  HelloWorldApi:
    Value: !Sub "https://${HelloWorldApi}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/hello"

Lambdaコード

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/Api-IAM-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 Api-IAM-Sample-Stack \
    --s3-bucket cm-fujii.genki-deploy \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

エンドポイント取得

$ aws cloudformation describe-stacks \
    --stack-name Api-IAM-Sample-Stack \
    --query 'Stacks[].Outputs'

[
    [
        {
            "OutputKey": "HelloWorldApi",
            "OutputValue": "https://xxx.execute-api.ap-northeast-1.amazonaws.com/v1/hello"
        }
    ]
]

APIにアクセスしてみる(失敗する)

SigV4署名ヘッダが無いので、アクセスに失敗します。

$ curl https://xxx.execute-api.ap-northeast-1.amazonaws.com/v1/hello
{"message":"Missing Authentication Token"}

SigV4署名ヘッダを使って、APIにアクセスする

アクセスキーを取得する

下記コマンドでアクセスキーを取得します。マネジメントコンソールから取得してもOKです。

aws iam create-access-key \
    --user-name api-gateway-user

APIアクセス用のスクリプトを作成する

Postmanを使えば簡単に動作確認できますが、今回は、Pythonを使ってアクセスしてみます。

次のライブラリを利用します。

pip install requests
pip install requests-aws4auth

スクリプト本体は次のコードです。さきほど取得したAWSアクセスキーを利用します。

sample.py

import requests
from requests_aws4auth import AWS4Auth

API_ENDPOINT = 'https://xxx.execute-api.ap-northeast-1.amazonaws.com/v1/hello'

auth = AWS4Auth(
    'your access key',
    'your secret key',
    'ap-northeast-1',
    'execute-api',
)

response = requests.get(API_ENDPOINT, auth=auth)

print(response.status_code)
print(response.json())

APIにアクセスする

成功しました!

$ python sample.py
200
{'message': 'hello world'}

参考