API Gateway HTTP API のアクセス制御が Lambda と IAM でできるようになりました!

記事タイトルとURLをコピーする

はじめに

こんにちは、技術1課の山中です。
前回 に引き続き今回も Amazon API Gateway に関するアップデートです。

aws.amazon.com

HTTP API におけるアクセス制御

これまでも JSON ウェブトークン (JWT) を利用することで HTTP API へのアクセス制御は実現できました。
今回のアップデートで、新たに AWS Lambda と IAM のオーソライザーに対応し、 HTTP API では以下 3 つのアクセス制御が可能となりました。

  • Lambda
  • JWT
  • IAM

本ブログでは HTTP API の AWS Lambda オーソライザーについて確認していきます。

HTTP API の Lambda オーソライザー

Lambda オーソライザーを利用すると Lambda 関数で HTTP API のアクセス制御を行うことが可能です。
API Gateway がクライアントからのリクエストを受け取ると Lambda 関数を呼び出し Lambda 関数からのレスポンスを使用してクライアントが API にアクセスするかどうか判断します。

f:id:swx-yamanaka:20200911075317p:plain

ペイロード形式のバージョン

Lambda オーソライザーでは、 API Gateway から Lambda へ送信するデータの形式とレスポンスの形式を ペイロード形式のバージョン として指定します。
ペイロードの形式のバージョンを指定すると API Gateway と Lambda 間でやり取りするデータ構造が決定します。
現在 1.02.0 のバージョンがあり、 REST API との互換性が必要な場合は 1.0 を指定する必要があります。
※ ペイロード形式のバージョンを指定しない場合は、最新バージョンが使用されます。

バージョン 1.0 のデータ構造

API Gateway → Lambda

{
    "version": "1.0",
    "type": "REQUEST",
    "methodArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request",
    "identitySource": "user1,123",
    "authorizationToken": "user1,123",
    "resource": "/request",
    "path": "/request",
    "httpMethod": "GET",
    "headers": {
        "X-AMZ-Date": "20170718T062915Z",
        "Accept": "*/*",
        "HeaderAuth1": "headerValue1",
        "CloudFront-Viewer-Country": "US",
        "CloudFront-Forwarded-Proto": "https",
        "CloudFront-Is-Tablet-Viewer": "false",
        "CloudFront-Is-Mobile-Viewer": "false",
        "User-Agent": "..."
    },
    "queryStringParameters": {
        "QueryString1": "queryValue1"
    },
    "pathParameters": {},
    "stageVariables": {
        "StageVar1": "stageValue1"
    },
    "requestContext": {
        "path": "/request",
        "accountId": "123456789012",
        "resourceId": "05c7jb",
        "stage": "test",
        "requestId": "...",
        "identity": {
            "apiKey": "...",
            "sourceIp": "..."
        },
        "resourcePath": "/request",
        "httpMethod": "GET",
        "apiId": "abcdef123"
    }
}

Lambda → API Gateway

{
  "principalId": "abcdef", // The principal user identification associated with the token sent by the client.
  "policyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": "execute-api:Invoke",
        "Effect": "Allow|Deny",
        "Resource": "arn:aws:execute-api:{regionId}:{accountId}:{apiId}/{stage}/{httpVerb}/[{resource}/[{child-resources}]]"
      }
    ]
  },
  "context": {
    "exampleKey": "exampleValue"
  }
}

バージョン 2.0 のデータ構造

API Gateway → Lambda

{
    "version": "2.0",
    "type": "REQUEST",
    "routeArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request",
    "identitySource": ["user1", "123"],
    "routeKey": "$default",
    "rawPath": "/my/path",
    "rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value",
    "cookies": [ "cookie1", "cookie2" ],
    "headers": {
        "Header1": "value1",
        "Header2": "value2"
    },
    "queryStringParameters": { "parameter1": "value1,value2", "parameter2": "value" },
    "requestContext": {
        "accountId": "123456789012",
        "apiId": "api-id",
        "domainName": "id.execute-api.us-east-1.amazonaws.com",
        "domainPrefix": "id",
        "http": {
          "method": "POST",
          "path": "/my/path",
          "protocol": "HTTP/1.1",
          "sourceIp": "IP",
          "userAgent": "agent"
        },
        "requestId": "id",
        "routeKey": "$default",
        "stage": "$default",
        "time": "12/Mar/2020:19:03:58 +0000",
        "timeEpoch": 1583348638390
    },
    "pathParameters": {"parameter1": "value1"},
    "stageVariables": {"stageVariable1": "value1", "stageVariable2": "value2"}
}

Lambda → API Gateway

バージョン 2.0 の場合、 Lambda 関数からは単純なブール値もしくは、 IAM ポリシー構文を返すことが可能です。
これはオーソライザーの設定で指定できます。 (Simple もしくは AM Policy ) それぞれのレスポンスモードによるデータ構造は以下のとおりです。

Simple

{
  "isAuthorized": true/false,
  "context": {
    "exampleKey": "exampleValue"
  }
}

IAM Policy

{
  "principalId": "abcdef", // The principal user identification associated with the token sent by the client.
  "policyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": "execute-api:Invoke",
        "Effect": "Allow|Deny",
        "Resource": "arn:aws:execute-api:{regionId}:{accountId}:{apiId}/{stage}/{httpVerb}/[{resource}/[{child-resources}]]"
      }
    ]
  },
  "context": {
    "exampleKey": "exampleValue"
  }
}

試してみる

今回は、 前回 のブログで作成した HTTP API に Lambda オーソライザーを利用してアクセス制御を行ってみようと思います。 また、ペイロード形式は最新の 2.0 で Simple と IAM Policy 両方のレスポンスモードを試してみます。

レスポンスモード = Simple

まずはシンプルなレスポンスモードを指定して、オーソライザーを試していきます。

Lambda 関数の作成

オーソライザー用の Lambda 関数を用意します。
ペイロード形式のバージョンとレスポンスモードに合わせて、 http-api-authorizer-ver2-simple という Lambda 関数を Python 3.8 で作成します。 f:id:swx-yamanaka:20200910110308p:plain コードに以下を入力し、保存できたら完成です。

def lambda_handler(event, context):
    response = {
        'isAuthorized': False,
        'context': {
            'stringKey': 'value',
            'numberKey': 1,
            'booleanKey': True,
            'arrayKey': ['value1', 'value2'],
            'mapKey': {'value1': 'value2'}
        }
    }
    
    if event['headers']['authorization'] == 'secretToken':
        response = {
            'isAuthorized': True,
            'context': {
                'stringKey': 'value',
                'numberKey': 1,
                'booleanKey': True,
                'arrayKey': ['value1', 'value2'],
                'mapKey': {'value1': 'value2'}
            }
        }
 
    return response

context についてはオプションですが、コンテキストプロパティを入力することで HTTP API のアクセスログをカスタマイズすることができます。
例えば、ログの設定を以下のようにすると、 f:id:swx-yamanaka:20200911073352p:plain CloudWatch Logs にはこのようにログが出力されます。 f:id:swx-yamanaka:20200911073525p:plain HTTP API のアクセスログについては、以下を参照ください。 docs.aws.amazon.com

オーソライザーの作成

HTTP API の画面から[認可] - [オーソライザーを管理] を選択して新しいオーソライザーを作成していきます。
f:id:swx-yamanaka:20200910110822p:plain オーソライザーの作成画面で、オーソライザータイプ Lambda を選択後、 http-api-authorizer-ver2-simple を指定してください。
ペイロード形式のバージョンは 2.0 、レスポンスモードは Simple としています。 f:id:swx-yamanaka:20200910111129p:plain これで作成は完了です。 f:id:swx-yamanaka:20200910111244p:plain

オーソライザーのアタッチ

作成したオーソライザーをルートにアタッチします。
オーソライザーを管理タグの横にある オーソライザーをルートにアタッチ タグを選択します。 f:id:swx-yamanaka:20200910111430p:plain まだ何もアタッチされていないので、作成したオーソライザーを選び オーソライザーをアタッチ ボタンをクリック。 f:id:swx-yamanaka:20200910111619p:plain これでオーソライザーの設定は完了です。 f:id:swx-yamanaka:20200910111718p:plain

実行

さっそく今回アタッチしたオーソライザーを試してみましょう。 まずは HTTP API に対して誤ったトークンを指定し GET リクエストを送信してみます。

$ curl -X GET https://swdld8wdl6.execute-api.ap-northeast-1.amazonaws.com/appconfig -H "Authorization: invalidSecretToken"
{"message":"Forbidden"}

誤ったトークンを指定しているため、エラーが返されデータの取得に失敗しました!
続いて、 Lambda で指定したトークン secretToken をヘッダにつけてリクエストを送信してみます。

$ curl -X GET https://swdld8wdl6.execute-api.ap-northeast-1.amazonaws.com/appconfig -H "Authorization: secretToken"
- PLCRz5JqTKzfn5rWKSfEAceiJWeHAxHThd #30分でわかる AWS UPDATE! - サービスカット版 -
- PLCRz5JqTKzfkU-mN5u13OC6OzuXrhhqc2 #30分でわかる AWS UPDATE!
- PLCRz5JqTKzfkEmHYlklTY7vHY0vbXMQSE #はじめてのAWS
- PLCRz5JqTKzfkAOEjtq2eniDfTCpl4-go3 #初級ハンズオン
- PLCRz5JqTKzfmHYSSSQhs2Y-qPFU201s3p #毎日AWS

無事取得することができました!きちんとオーソライザーが機能していることがわかりますね!

レスポンスモード = IAM Policy

続いて IAM Policy のレスポンスモードを試します。

Lambda 関数の作成

先ほどと同様にオーソライザー用の Lambda 関数 http-api-authorizer-ver2-iampolicy を作成します。

def lambda_handler(event, context):
    if event['headers']['authorization'] == 'secretToken':
        print('allowd')
        return {
            'principalId': 'abcdef',
            'policyDocument': {
                'Version': '2012-10-17',
                'Statement': [
                    {
                        'Action': 'execute-api:Invoke',
                        'Effect': 'Allow',
                        'Resource': event['routeArn']
                    }
                ]
            },
            'context': {
                'stringKey': 'value',
                'numberKey': 1,
                'booleanKey': True,
                'arrayKey': ['value1', 'value2'],
                'mapKey': {'value1': 'value2'}
            }
        }
 
    print('denied')
    return {
        'principalId': 'abcdef',
        'policyDocument': {
            'Version': '2012-10-17',
            'Statement': [
                {
                    'Action': 'execute-api:Invoke',
                    'Effect': 'Deny',
                    'Resource': event['routeArn']
                }
            ]
        },
        'context': {
            'stringKey': 'value',
            'numberKey': 1,
            'booleanKey': True,
            'arrayKey': ['value1', 'value2'],
            'mapKey': {'value1': 'value2'}
        }
    }

f:id:swx-yamanaka:20200911065902p:plain

オーソライザーの作成

まずは、先ほどアタッチしたオーソライザーをデタッチします。 f:id:swx-yamanaka:20200911070054p:plain デタッチできたら、新しいオーソライザーを作成します。 ここでは Lambda 関数に http-api-authorizer-ver2-iampolicy を指定します。 f:id:swx-yamanaka:20200911070255p:plain これで作成は完了です。 f:id:swx-yamanaka:20200911070424p:plain

オーソライザーのアタッチ

作成したオーソライザーをルートにアタッチします。
f:id:swx-yamanaka:20200911070524p:plain これでオーソライザーの設定は完了です。 f:id:swx-yamanaka:20200911070555p:plain

実行

設定が終わったので、こちらも試してみましょう。 まずは間違ったトークン invalidSecretToken を指定して GET リクエストを送信します。

$ curl -X GET https://swdld8wdl6.execute-api.ap-northeast-1.amazonaws.com/appconfig -H "Authorization: invalidSecretToken"
{"message":"Forbidden"}

先ほどと同様、間違ったトークンを指定しているため、エラーが返されデータの取得に失敗しました。 Lambda 関数 http-api-authorizer-ver2-iampolicy の CloudWatch Logs を確認してみると、トークンが異なるため denied が出力されています。 f:id:swx-yamanaka:20200911071455p:plain 続いて、 Lambda で指定したトークン secretToken をヘッダにつけてリクエストを送信してみます。

$ curl -X GET https://swdld8wdl6.execute-api.ap-northeast-1.amazonaws.com/appconfig -H "Authorization: secretToken"
- PLCRz5JqTKzfn5rWKSfEAceiJWeHAxHThd #30分でわかる AWS UPDATE! - サービスカット版 -
- PLCRz5JqTKzfkU-mN5u13OC6OzuXrhhqc2 #30分でわかる AWS UPDATE!
- PLCRz5JqTKzfkEmHYlklTY7vHY0vbXMQSE #はじめてのAWS
- PLCRz5JqTKzfkAOEjtq2eniDfTCpl4-go3 #初級ハンズオン
- PLCRz5JqTKzfmHYSSSQhs2Y-qPFU201s3p #毎日AWS

無事取得することができました!CloudWatch Logs を確認してみると、 allowd が出力されています。 f:id:swx-yamanaka:20200911071622p:plain こちらでもきちんとオーソライザーが機能していることがわかりますね!

さいごに

HTTP API のオーソライザーに IAM と Lambda がそれぞれ加わることでこれまで以上により柔軟なアクセス制御ができそうです!

参考

山中 大志(記事一覧)

クラウドインテグレーション部・技術1課

ビールと味噌カツをこよなく愛するエンジニアです。

AWSをみなさんにより使っていただけるような情報を発信していきたいと考えてます。