0
0

More than 1 year has passed since last update.

kintone Webhook用のAPI をLambdaオーソライザーで保護する方法について

Last updated at Posted at 2022-10-13

API GatewayとLambdaを利用する時のAPIの保護の方法についての記事です。
kintone Webhookの利用に限ると、リクエストベースのLambdaオーソライザー(リクエストオーソライザー)が良かったので紹介します。

リクエストオーソライザーを選択する理由

2022-10現在、kintone Webhookではヘッダーを付けることができない。
使えるとすればリクエストパラメーターだけ。
なのでリクエストオーソライザーが合っている。(と個人的に思います)

環境

  • macOS
  • AWS SAM CLI, version 1.58.0
  • Node v16.13.1
  • kintone

API Gateway と Lambdaを作成する

SAMを利用してテンプレートから環境を作ります。

$ sam init
Which template source would you like to use?
	1 - AWS Quick Start Templates
What package type would you like to use?
	1 - Zip (artifact is a zip uploaded to S3)
Which runtime would you like to use?
	1 - nodejs14.x
AWS quick start application templates:
	2 - Hello World Example TypeScript

プロジェクトフォルダ配下にtemplate.yamlができるので編集します。

後で kintone Webhook からリクエストするので、メソッドはpostにします。

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  lambda-auth-test

  Sample SAM Template for lambda-auth-test
  
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      Auth:
        DefaultAuthorizer: MyLambdaRequestAuthorizer
        Authorizers:
          MyLambdaRequestAuthorizer:
            FunctionPayloadType: REQUEST
            FunctionArn: !GetAtt MyAuthFunction.Arn
            Identity:
              QueryStrings:
                - auth  

  MyAuthFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: authorizer.handler
      Runtime: nodejs14.x
      Architectures:
        - x86_64
    Metadata: # Manage esbuild properties
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: "es2020"
        # Sourcemap: true # Enabling source maps will create the required NODE_OPTIONS environment variables on your lambda function during sam build
        EntryPoints: 
        - authorizer.ts

  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs14.x
      Architectures:
        - x86_64
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            RestApiId: !Ref HelloWorldApi
            Path: /hello
            Method: post
    Metadata: # Manage esbuild properties
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: "es2020"
        # Sourcemap: true # Enabling source maps will create the required NODE_OPTIONS environment variables on your lambda function during sam build
        EntryPoints: 
        - app.ts

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${HelloWorldApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn
  MyAuthFunction:
    Description: "Hello World Lambda Auth Function ARN"
    Value: !GetAtt MyAuthFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World Auth function"
    Value: !GetAtt MyAuthFunctionRole.Arn

Lambdaオーソライザーの作成

名前はなんでも良いですが、オーソライザーを作成します。
今回はauthorizer.tsとしました。
若干TypeScript用に書き換えていますが、中身は下記のサンプルコードそのままです。

authorizer.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
  console.log('Received event:', JSON.stringify(event, null, 2));
  const headers = event.headers;
  const queryStringParameters = event.queryStringParameters;
  const pathParameters = event.pathParameters;
  const stageVariables = event.stageVariables;
      
  // Parse the input for the parameter values
  const tmp = event.methodArn.split(':');
  const apiGatewayArnTmp = tmp[5].split('/');
  const awsAccountId = tmp[4];
  const region = tmp[3];
  const restApiId = apiGatewayArnTmp[0];
  const stage = apiGatewayArnTmp[1];
  const method = apiGatewayArnTmp[2];
  let resource = '/'; // root resource
  if (apiGatewayArnTmp[3]) {
      resource += apiGatewayArnTmp[3];
  }
      
  const condition = { IpAddress: {}};
  let response: any;

  if (queryStringParameters.auth === "queryValue1") {
      response = generateAllow('me', event.methodArn);
      console.log(`Authorization SUCCESS: ${JSON.stringify(response, null, 2)}`);
  }  else {
      response = generateDeny('me', event.methodArn);
      console.log(`Authorization Error: ${JSON.stringify(response, null, 2)}`);
  }
    return response;
};

// Help function to generate an IAM policy
const generatePolicy = function(principalId: string, effect: string, resource: string) {
  // Required output:
  const authResponse: any = {};
  authResponse.principalId = principalId;
  if (effect && resource) {
      const policyDocument: any = {};
      policyDocument.Version = '2012-10-17'; // default version
      policyDocument.Statement = [];
      const statementOne: any = {};
      statementOne.Action = 'execute-api:Invoke'; // default action
      statementOne.Effect = effect;
      statementOne.Resource = resource;
      policyDocument.Statement[0] = statementOne;
      authResponse.policyDocument = policyDocument;
  }
  // Optional output with custom properties of the String, Number or Boolean type.
  authResponse.context = {
      "stringKey": "stringval",
      "numberKey": 123,
      "booleanKey": true
  };
  return authResponse;
}
   
const generateAllow = function(principalId: string, resource: string) {
  console.log(principalId);
  console.log(resource);
  return generatePolicy(principalId, 'Allow', resource);
}
   
const generateDeny = function(principalId: string, resource: string) {
  console.log(principalId);
  console.log(resource);
  return generatePolicy(principalId, 'Deny', resource);
}

ビルド&デプロイ

sam build して sam deploy --guided します。

kintone Webhook リクエスト

ビルドが終わると実行したコンソールにAPIのエンドポイントが表示されるので控えておきます。

適当なkintoneアプリを作ります。
Webhookを設定します。

Webhook URL に APIエンドポイントURLを設定して、認証用のパラメーターをキー=バリュー形式で設定します。

今回は ?auth=queryValue1 です。
こんな感じ。

image.png

レコード追加すると、WebhookリクエストがAPIに渡り、APIからオーソライザーが実行されて、認証が成功するとLambdaが呼び出されます。

kintone Webhookからのリクエストの内容は、オーソライザーとLambdaのCloudWatchログで確認できます。

参考:

AWS SAM リファレンス

API Gateway APIへのアクセスの制御

Use API Gateway Lambda authorizers

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0