LoginSignup
7
2

More than 1 year has passed since last update.

AWS SAMテンプレートのサンプルをベースにLambda Authorizerを組み込む

Posted at

LambdaやDynamoDBを使ったサーバレスアーキテクチャを構築するうえでとても便利なIaaC(Infrastructure as a Code)ツールであるSAM(Serverless Application Model)を使って、Lambda Authorizerを使った認証付きAPIを作成したいと思います。
今回はトークンベースの認証とします。
SAMには、いくつかのテンプレートが用意されています。その中でも最もシンプルなhello-worldテンプレートからスタートしてみます。

sam init

上記のコマンドでプロジェクトの作成を開始します。
今回は、AWS Quick Start Templatesを選び、Zipでデプロイする方式で、ランタイムはnodejs14.x、使用するテンプレートはHello World Exampleとしました。
ちなみに、ここでとってくるSAMのtemplateは、以下のリポジトリからクローンされます。
https://github.com/aws/aws-sam-cli-app-templates

結果として、次のように表示されました。

    -----------------------
    Generating application:
    -----------------------
    Name: authorizerDemo
    Runtime: nodejs14.x
    Dependency Manager: npm
    Application Template: hello-world
    Output Directory: .

    Next steps can be found in the README file at ./authorizerDemo/README.md

これでアプリケーションを開発していくためのベースのテンプレートからスタートできます。
ちょっとディレクトリ構成をいじります。

src <- 新規追加
 hello-world <- もともとあったソースをsrcディレクトリ以下に移動
 auth <- 新規追加

authディレクトリに移動して、npm initします。
auth.jsを作成します。これがユーザの資格情報をチェックするコードになりますが、ここでは、簡単な例として、tokenに、mySecretという文字列が入っているとき、Allowとし、それ以外はDenyとするコードにします。

exports.lambdaHandler = async(event, context) => {
    const token = event.authorizationToken;
    // このAPIについて拒否したり許可したりしたいので、このAPIを指定するためのリソースARNを取得
    const resource = event.methodArn;

    // tokenがmySecretであるならば、許可、そうでないなら拒否
    if (token == 'mySecret') {
        console.log('allow');
        return {
            principalId: token,
            policyDocument: {
                Version: "2012-10-17",
                Statement: [{
                    Action: "execute-api:Invoke",
                    Effect: "Allow",
                    Resource: resource,
                }, ],
            },
        };
    }
    else {
        console.log('deny')
        return {
            principalId: token,
            policyDocument: {
                Version: "2012-10-17",
                Statement: [{
                    Action: "execute-api:Invoke",
                    Effect: "Deny",
                    Resource: resource,
                }, ],
            },
        };

    }

};

template.yamlを、次のように変更します。
myApiは、API Gatewayを定義します。ここで、Authorizerとして、authFunctionを指定しています。
authFunctionとして、前述のauth.jsが呼ばれます。
また、必須ではないのですが、StageNameをパラメタとして定義して、ほかで使いまわせるようにします。

Parameters:
  StageName:
    Type: "String"
    Default: "dev"

Resources:

  myApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref StageName # 上で決めたStageNameを参照します
      Auth:
        DefaultAuthorizer: authFunction
        Authorizers:
          authFunction:
            FunctionArn: !GetAtt authFunction.Arn

  authFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/auth/
      Handler: auth.lambdaHandler
      Runtime: nodejs14.x

既存のHello-world関数に認証をつけたいと思いますので、以下のようにHello-world関数の定義で、LambdaAuthorizerを呼び出すように変更します。

HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs14.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            RestApiId: !Ref myApi # <- ここで、Authorizerを呼ぶように設定しています
            Path: /hello
            Method: get

Outputも設定しておきます。
HelloWorldApiのエンドポイントで、上記のmyApiのStageNameを参照するように書き換えています。

Outputs:
  ApiWithLambdaAuth:
    Description: "API Gateway endpoint URL"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/hello/"

ここまでできたら、あとは、デプロイするだけです。
template.yamlが見えるパスで、以下のコマンドで、ビルドしてデプロイします。

sam build
sam deploy --guided

guidedの対話シェルでは、以下のように入力をしました。ここでsamconfig.tomlファイルを作成しているのですが、このファイルとtemplate.yamlファイルが見えているパスで、--guidedをつけずに、sam deployすると、自動的にtomlファイルを読み込んでデプロイしてくれるようになります。便利ですので、せっかくなので作成するようにしましょう。

     Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]: lambda-auth-demo
        AWS Region [ap-northeast-1]:   
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [y/N]: y
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]: Y
        Save arguments to configuration file [Y/n]: Y
        SAM configuration file [samconfig.toml]: 
        SAM configuration environment [default]:

ところが、デプロイは途中でこけてしまいます。以下のエラーが出力されるのですが、何が起きているのでしょうか。

Error: Failed to create changeset for the stack: lambda-auth-demo, 
ex: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state:
 For expression "Status" we matched expected path: "FAILED" Status: FAILED. 
Reason: Unresolved resource dependencies [ServerlessRestApi] in the Outputs block of the template

Unresolved resource dependencies [ServerlessRestApi] in the Outputs block of the templateは、ServerlessRestApiというリソースが解決されません、ということなのですが、この、ServerlessRestApiというのは、API Gatewayをテンプレートのなかで明示的に指定しなかったときに、SAMによって暗黙的に生成されるAPI Gatewayのリソース名です。
もともとのテンプレートでは、API Gatewayのリソースを明示的に定義していないので、これが使えるのですが、いま、Authorizerを設定するために、API Gatewayのリソースを明示的に定義したので、使えなくなっています。
そこで、自分で定義したmyApiに変えてあげる必要があります。初めてデプロイしたときにはまったので、共有しておきます。
ただ、Outputsは、SAMが作成したエンドポイントを単に出力するだけなので、最悪なくても、動作に問題はないと思います。その場合は、自分でコンソール上でAPIゲートウェイを見に行くか、aws cloudformation --describe-stacksなどを用いてエンドポイントを確認します。

Outputs:
  ApiWithLambdaAuth:
    Description: "API Gateway endpoint URL"
    Value: !Sub "https://${myApi}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/hello/"

さて、これでデプロイできるはずです。やってみましょう。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                                                                                                      
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 ApiWithLambdaAuth                                                                                                                                                                        
Description         API Gateway endpoint URL                                                                                                                                                                 
Value               https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello/                                                                                                                   
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - lambda-auth-demo in ap-northeast-1

成功したみたいです!
動作確認をしてみましょう。

1.headerなし
$ curl https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello/ 
{"message":"Unauthorized"}
2.不正なトークン
$ curl https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello/ -H "Authorization: mySecrets"
{"Message":"User is not authorized to access this resource with an explicit deny"}
3.正当なトークン
$ curl https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello/ -H "Authorization: mySecret"
{"message":"hello world"}

1では401(Unauthorized)、2では403(Forbidden)が返り、3で200(OK)が返ります。
これで期待通りに動作していることが確認できました。

7
2
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
7
2