Windows10 + aws-vault + AWS CDK + Serverless Frameworkにて、最低限の権限を持つユーザでAWS Lambda + API GatewayなAPIを作ってみた

前回の記事で、 aws-vaultAWS CLIを連携させて使えるようになりました。
Windows + aws-vaultにて、AWSのアクセスキーを保護し、 AWS CLIを AssumeRole で使えるようにしてみた - メモ的な思考的な

 
今回は、 aws-vault + Serverless Frameworkを連携させ、AWS Lambda + API Gateway を使ったAPIを作成してみます。

また、Serverless Frameworkでデプロイする時のユーザについて、公式では

Search for and select AdministratorAccess ... Note that the above steps grant the Serverless Framework administrative access to your account. While this makes things simple when you are just starting out, we recommend that you create and use more fine grained permissions once you determine the scope of your serverless applications and move them into production.

https://www.serverless.com/framework/docs/providers/aws/guide/credentials/

と書かれています。

ただ、どのような権限があれば最低限となるのか分からなかったため、試してみることにします。

なお、手動で権限を与えるのは手間なので、今回は AWS CDK for TypeScript を使って必要なIAMまわりを作成し、最後にデプロイ用ユーザに手動で割り当てるだけにします*1

 
目次

 

環境

  • Windows10 (1909, 18363.1379)
  • aws-vault v6.2.0
  • Serverless Framework 2.25.2
    • Plugin 4.4.3
    • SDK 2.3.2
    • Components: 3.7.0
  • AWS CDK for TypeScript 1.90.1 (build 0aee440)
  • nvm-windows 1.1.7
    • WindowsにおけるNode.jsのバージョン管理ツール
  • Node.js 14.15.5

 
なお、前回の環境から引き続きで行っているため、

  • aws-vault はインストール済
  • aws-vault で AdministratorAccess にAssumeRoleして、AWSリソースを操作可能

な状況とします。

一方、nvm-windowsやNode.js、CDKは今回初めてインストールするものとします。

 

Node.js環境の構築

Serverless FrameworkやAWS CDKはNode.jsを使うため、Node.js環境を構築します。

 

nvm-windowsのインストール

Node.jsをそのままインストールしてもよいのですが、今後色々試すことを考えて複数バージョンを使えるようにしておきます。

Windowsではどのようなツールがあるかを調べたところ、以下にまとまっていました。
Windows における Node.js バージョン管理マネージャの選択(nvm-windows, nodist 等) - clock-up-blog

 
最近見かけるWindowsのパッケージマネージャの一つ Scoop のMainバケットには何が含まれているかを確認したところ、 nvm-windows は含まれているものの、nodist はありませんでした。そこで今回は nvm-windows を使うことにしました。
https://github.com/ScoopInstaller/Main/blob/master/bucket/nvm.json

 
次に nvm-windowsのインストールについてです。

Scoopを使ってインストールすることも考えましたが、今後 winget が登場するとまた変わるのかなと思いました。
https://github.com/microsoft/winget-cli

 
そこで今回は、Githubから nvm-windows をダウンロード・インストールしました。
https://github.com/coreybutler/nvm-windows

 

Node.jsのインストール

管理者権限でWindows Terminalを起動し、 nvm-windows を使ってNode.jsをインストールします。

まずは使えるNode.jsのバージョンを確認します。

>nvm list available

|   CURRENT    |     LTS      |  OLD STABLE  | OLD UNSTABLE |
|--------------|--------------|--------------|--------------|
|    15.9.0    |   14.15.5    |   0.12.18    |   0.11.16    |
|    15.8.0    |   14.15.4    |   0.12.17    |   0.11.15    |
...

This is a partial list. For a complete list, visit https://nodejs.org/download/release

 
バージョンリストより、Node.js LTSの最新をインストールします。

>nvm install 14.15.5

Downloading node.js version 14.15.5 (64-bit)...
Complete
Creating %USERPROFILE%\AppData\Roaming\nvm\temp

Downloading npm version 6.14.11... Complete
Installing npm v6.14.11...

Installation complete. If you want to use this version, type

nvm use 14.15.5

 
使用するNode.jsを有効化し、バージョンを確認します。

>nvm use 14.15.5
Now using node v14.15.5 (64-bit)

>node -v
v14.15.5

 
これでNode.jsが使えるようになりました。

 

Serverless Frameworkのセットアップ

続いてServerless Frameworkをセットアップし、AdministratorAccess権限を持ったユーザでデプロイできるかを確認してみます。

 

インストール

Node.jsのグローバルにインストールします。

>npm install -g serverless

+ serverless@2.25.2

 

動作確認

Serverless Framework の aws-python3 テンプレートを使って、aws-vaultと組み合わせてデプロイできるかを確認してみます。
https://www.serverless.com/framework/docs/providers/aws/guide/services#creation

>serverless create --template aws-python3 --path hello
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "path\to\hello"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v2.25.2
 -------'

Serverless: Successfully generated boilerplate for template: "aws-python3"

 
デフォルトの serverless.yml では us-east-1 リージョンにデプロイされてしまうため、東京リージョンへと変更します。

provider:
  name: aws
  runtime: python3.8
  lambdaHashingVersion: 20201221
  region: ap-northeast-1  # 追加

 
念のためデプロイ前のLambdaの状態を aws-vault + AWS CLI にて確認してみましたが、何もありません。

>aws-vault exec admin -- aws lambda list-functions
{
    "Functions": []
}

 
Serverless Frameworkを使って、AdministratorAccess権限にてデプロイしてみます。

aws-vaultと組み合わせて使うので、 aws-vault exec admin -- <serverless frameworkのコマンド> という形で実行します。

>aws-vault exec admin -- serverless deploy -v
...
Serverless: Stack update finished...
Service Information
service: hello
stage: dev
region: ap-northeast-1
stack: hello-dev
resources: 6
api keys:
  None
endpoints:
  None
functions:
  hello: hello-dev-hello
layers:
  None

Stack Outputs
HelloLambdaFunctionQualifiedArn: arn:aws:lambda:ap-northeast-1:<account_id>:function:hello-dev-hello:1
ServerlessDeploymentBucketName: hello-dev-serverlessdeploymentbucket-xxx

 
Lambdaもできています。

>aws-vault exec admin -- aws lambda list-functions
Enter token for arn:aws:iam::<account_id>:mfa/gate: xxx
{
    "Functions": [
        {
            "FunctionName": "hello-dev-hello",
            "FunctionArn": "arn:aws:lambda:ap-northeast-1:<account_id>:function:hello-dev-hello",
            "Runtime": "python3.8",
            "Role": "arn:aws:iam::<account_id>:role/hello-dev-ap-northeast-1-lambdaRole",
            "Handler": "handler.hello",
            "CodeSize": 640,
            "Description": "",
            "Timeout": 6,
            "MemorySize": 1024,
            "LastModified": "2021-02-21T01:55:02.396+0000",
            "CodeSha256": "xxx",
            "Version": "$LATEST",
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "xxx",
            "PackageType": "Zip"
        }
    ]
}

 
続いて、Serverless Frameworkを使って削除してみます。

>av exec admin -- serverless remove -v
...
Serverless: Stack delete finished...

 
Lambdaもなくなっていました。

>aws-vault exec admin -- aws lambda list-functions
{
    "Functions": []
}

 

Serverless FrameworkでデプロイするためのIAMまわりを考える

ここまでは AdministratorAccess 権限でデプロイしてきました。

ただ、これだと権限が広すぎるため、必要最低限の権限を持つIAMまわりを考えてみます。

 

必要な権限について

今回は

をServerless Frameworkで自動生成するための権限を考えてみます。

 
Serverless Frameworkで最低限の権限でデプロイする方法を調べたところ、以下のissueや記事がありました。

 
今回は

  • IAMユーザはAssumeRoleする権限を所持
  • デプロイする権限やCloudFormationはAssumeRoleして使う

とするため、以下の2つのIAMロールを用意します。

  • aws-vault exec <profile> -- serverless deploy するためのIAMロール
  • デプロイするため上記ロールから、CloudFormationへPassRoleされた時のIAMロール
    • Serverless FrameworkはCloudFormationを使ってAWSリソースを構築するため

 
一方、AWS Lambdaから他のAWSリソースは使わないため、AWS Lambda用のロールはServerless Frameworkで自動生成されるものを使います。

なお、自動生成されるロール(<PROJECT>-<STAGE>-<REGION>-lambdaRole)は、以下のインラインポリシーを持って生成されます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup"
            ],
            "Resource": [
                "arn:aws:logs:<region>:<account_id>:log-group:/aws/lambda/<PROJECT>-<STAGE>*:*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:a<region>:<account_id>:log-group:/aws/lambda/<PROJECT>-<STAGE>*:*:*"
            ],
            "Effect": "Allow"
        }
    ]
}

 
次に、各IAMロールに必要な権限を考えてみます。

serverless deploy でできるリソースですが、

  • AWS
    • CloudFormationスタック
    • デプロイ中間ファイル配置用S3バケット
    • LambdaのIAMロール
    • Lambdaのロググループ
    • Lambdaのfunction

serverless deployで何が更新されるのか確認してみました(Serverless Framework / AWS) | DevelopersIO

の他、今回はAPI Gatewayまわりも作成されます。

そのため、各IAMロールで必要な権限を考えてみます*2

 

デプロイするための権限を持つIAMロール

デプロイするときは

  • CloudFormationを操作する権限
  • S3に対して中間ファイルを操作する権限
  • CloudFormationにPassRoleする権限

の3つがあれば良さそうです。

また、上記の各権限に対し、Serverless Frameworkで必要な Resource のみに制限します。

 

CloudFormationのための権限を持つIAMロール

実際のAWSリソースを生成するときは、AWSリソースごとの権限があれば良さそうです。

  • S3に対して中間ファイルを操作する権限
  • API Gatewayを操作する権限
  • ログ(CloudWatch)を操作する権限
  • AWS Lambdaを操作する権限

 
また、今回はLambdaの権限をServerless Frameworkで生成するため、以下の権限も必要です。

  • IAMを操作する権限
  • Lambda用ロールにPassRoleする権限

 
こちらも上記の各権限に対し、Serverless Frameworkで必要な Resource のみに制限します。

 

その他IAMまわりで必要なもの

デプロイするためのIAMロールですが、デプロイユーザがAssumeRoleできる必要があります。

そのため、

  1. デプロイするためのIAMロールに対してAssumeRoleできるIAMポリシーを作成
  2. 上記1.のポリシーを割り当てたIAMグループを作成
  3. 上記3のグループに、デプロイするユーザを所属させる

も行います。

 

AWS CDKでIAMまわりを作成する

上記で考えたIAMまわりを手動で作成してもよいのですが、

  • 後で同じようなことをしたくても、使い回しがきかない
  • IAMポリシーのResourceで対象を制限するときに、AWSアカウントIDや対象リソースをtypoしそう

という問題があります。

そこで、AWS CDK + TypeScriptにてIAMまわりを作成します。
@aws-cdk/aws-iam module · AWS CDK

なお、冒頭で書いたとおり、グループへの割当は手動のままとします。

 

AWS CDKまわりの環境構築

CDK本体をインストールします。

>npm install -g aws-cdk

+ aws-cdk@1.90.1

 
続いてCDK appを作成します。今回はTypeScriptで作成します。

なお、 cdk init は空のディレクトリでないと実行できないことから、IAMモジュールの追加は後で行います。

> cdk init app --language typescript
...
✅ All done!

 
最後に、IAMモジュールをインストールします。

> npm install -S @aws-cdk/aws-iam
...
+ @aws-cdk/aws-iam@1.90.1

 

CDK全体の構成

生成されたTypeScriptファイルは、以下の2つがありました。

  • bin/cdk.ts
  • lib/cdk-stack.ts

このうち、cdk.tsはcdk-stack.tsに含まれるクラス CdkStackインスタンス化しているだけなので、実装は cdk-stack.tsCdkStack クラスに行えば良さそうです。

 
まずは、CdkStack.ts に全体像を作ります。

今回は複数のリソースを作成することから、各リソースのまとまりごとにメソッドを3つに分割しておきます。

  • createRoleForCfn()
    • CloudFormation用のIAMロールを作成するメソッド
  • createRoleForDeployUser()
    • デプロイするユーザがAssumeRoleできるIAMロールを作成するメソッド
  • createGroupOfDeployUser()
    • デプロイするユーザが所属するIAMグループを作成するメソッド

 

import * as cdk from '@aws-cdk/core';
import IAM = require('@aws-cdk/aws-iam')
import { Effect } from '@aws-cdk/aws-iam';

// Serverless Frameworkのプロジェクトとステージ
const PROJECT = 'hello-sls'
const STAGE = '*'  // どのステージにも適用できるようにした(必要に応じて、ステージを分ける)

export class CdkStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const cfnRole = this.createRoleForCfn();
    const deployRole = this.createRoleForDeployUser(cfnRole);
    this.createGroupOfDeployUser(deployRole);
  }
}

 

CloudFormation用のIAMロールをCDKで実装

デプロイするためのIAMロールを作る際CloudFormation用IAMロールのARNが必要となるため、まずはCloudFormation用から作成します。

作成順は

  1. IAMポリシーのステートメント
  2. ステートメントをまとめたユーザ管理ポリシー
  3. ユーザ管理ポリシーを含めたIAMロール

となります。

 

IAMポリシーのステートメント群を作成

IAM.PolicyStatement クラスを使って、IAMポリシーのステートメントを1つずつ作成していきます。

AWSアカウントIDは this.account 、リージョンは this.region でそれぞれ参照できます。

また、resourcesで今回使うServerless Frameworkのリソースだけに絞ります。

なお、API Gatewayの場合、ResourceだけではServerless Frameworkのリソースのみに限定できなかったため、広めの権限を与えています。

// CFnでリソースを操作するロールを作成
createRoleForCfn(): IAM.Role {
  const statements = [];

  // Serverless FrameworkがLambda用Roleを作るときに、権限を渡してあげる
  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      'iam:PassRole'
    ],
    resources: [
      `arn:aws:iam::${this.account}:role/${PROJECT}-${STAGE}-${this.region}-lambdaRole`
    ]
  }));

  // CFnがS3からデータを取得できるようにする
  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      's3:*'
    ],
    resources: [
      `arn:aws:s3:::${PROJECT}-${STAGE}`,
      `arn:aws:s3:::${PROJECT}-${STAGE}/`,
    ]
  }));

  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      's3:ListAllMyBuckets',
      's3:CreateBucket',
    ],
    resources: [
      '*'
    ]
  }));

  // API Gatewayまわりの権限
  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      'apigateway:GET',
      'apigateway:PATCH',
      'apigateway:POST',
      'apigateway:PUT',
      'apigateway:DELETE'
    ],
    resources: [
      `arn:aws:apigateway:${this.region}::/restapis`,
      `arn:aws:apigateway:${this.region}::/restapis/*`
    ]
  }));

  // Lambdaがログを出力する先であるCloudWatchまわりの権限
  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      'logs:DescribeLogGroups',
    ],
    resources: [
      `arn:aws:logs:${this.region}:${this.account}:log-group::log-stream:*`
    ]
  }));

  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      'logs:CreateLogGroup',
      'logs:CreateLogStream',
      'logs:DeleteLogGroup',
      'logs:DeleteLogStream',
      'logs:DescribeLogStreams',
      'logs:FilterLogEvents'
    ],
    resources: [
      `arn:aws:logs:${this.region}:${this.account}:log-group:/aws/lambda/${PROJECT}-${STAGE}:log-stream:*`
    ]
  }));

  // Serverless FrameworkがLambda用ロールを扱えるようにするための権限
  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      "iam:GetRole",
      "iam:GetRolePolicy",
      "iam:CreateRole",
      "iam:DeleteRole",
      "iam:DeleteRolePolicy",
      "iam:PutRolePolicy"
    ],
    resources: [
      `arn:aws:iam::${this.account}:role/${PROJECT}-${STAGE}-${this.region}-lambdaRole`
    ]
  }));

  // Lambdaまわりの権限
  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      'lambda:GetFunction',
      'lambda:CreateFunction',
      'lambda:DeleteFunction',
      'lambda:UpdateFunctionConfiguration',
      'lambda:UpdateFunctionCode',
      'lambda:ListVersionsByFunction',
      'lambda:PublishVersion',
      'lambda:CreateAlias',
      'lambda:DeleteAlias',
      'lambda:UpdateAlias',
      'lambda:GetFunctionConfiguration',
      'lambda:AddPermission',
      'lambda:RemovePermission',
      'lambda:InvokeFunction'
    ],
    resources: [
      `arn:aws:lambda:${this.region}:${this.account}:function:${PROJECT}-${STAGE}`
    ]
  }));

  // 2. ステートメントをまとめたユーザ管理ポリシーを書く
  // 3. ユーザ管理ポリシーを含めたIAMロール
}

 

ステートメントをまとめたユーザ管理ポリシー

続いて、ステートメントをまとめたユーザ管理ポリシーを IAM.ManagedPolicy クラスを使って実装します。

statementesに、ステートメント群を指定します。

const cfnPolicy = new IAM.ManagedPolicy(
  this,
  'thinkAmiCfnPolicy',
  {
    managedPolicyName: 'thinkAmi-Serverless-CFn',
    statements: statements
  }
);

 

ユーザ管理ポリシーを含めたIAMロール

最後にIAMロールを作成します。

assumedBy にて、CloudFormationでしか使えないように指定します。なお、CloudFormationはAWSのサービスのため、 assumedBy で使うクラスは IAM.ServicePrincipal です。

return new IAM.Role(
  this,
  'thinkAmiCfnRole',
  {
    roleName: 'thinkAmi-Serverless-CFn-Role',
    assumedBy: new IAM.ServicePrincipal('cloudformation.amazonaws.com'),
    managedPolicies: [
      cfnPolicy
    ]
  }
);

 

デプロイするためのIAMロール

次に、デプロイするためのIAMロールを作成します。

作成順はCloudFormation用のIAMロールと同様です。

 

IAMポリシーのステートメント

CloudFormationで作成するAWSリソースに対する権限はCloudFormation用のIAMロールに任せるため、 iam:PassRole します。

あとは必要な権限を追加します。

// Serverless FrameworkでAWSリソースを扱うためのロールを作成
createRoleForDeployUser(cfnRole: IAM.Role): IAM.Role {
  const statements = []

  // CFnでのリソース作成をCFn用ロールにPassRoleする権限
  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      'iam:PassRole',
    ],
    resources: [
      cfnRole.roleArn
    ]
  }));

  // S3を使ってデプロイするための権限
  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      's3:*',
    ],
    resources: [
      `arn:aws:s3:::${PROJECT}-${STAGE}`,
      `arn:aws:s3:::${PROJECT}-${STAGE}/*`
    ]
  }));

  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      's3:ListAllMyBuckets',
      's3:CreateBucket'
    ],
    resources: [
      '*'
    ]
  }));

  // CFnを扱うための権限
  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      'cloudformation:CreateStack',
      'cloudformation:UpdateStack',
      'cloudformation:DeleteStack'
    ],
    resources: [
      `arn:aws:cloudformation:${this.region}:${this.account}:stack/${PROJECT}-${STAGE}/*`
    ]
  }));

  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      'cloudformation:Describe*',
      'cloudformation:List*',
      'cloudformation:Get*',
      'cloudformation:ValidateTemplate'
    ],
    resources: [
      '*'
    ]
  }));

  // Paramter Storeから値を取得する権限
  // serverless.yml中のdeploymentRoleにて指定するARNをParamter Storeに設定する
  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      'ssm:GetParameter'
    ],
    resources: [
      `arn:aws:ssm:${this.region}:${this.account}:parameter/hello-sls/CFn-Role`
    ]
  }));
// ...
}

 

ステートメントをまとめたユーザ管理ポリシー

書き方はCloudFormationのときと同じです。

// ユーザ管理ポリシーとして作成
const deployPolicy = new IAM.ManagedPolicy(
  this,
  'thinkAmiDeployPolicy',
  {
    managedPolicyName: 'thinkAmi-Serverless-Deploy',
    statements: statements
  }
);

 

ユーザ管理ポリシーを含めたIAMロール

同じようにIAMロールを作成します。

ただ、AssumeRoleの対象がアカウントIDであるため、 IAM.AccountPrincipal クラスに this.account を渡しています。

// デプロイするユーザ用ロールとして作成
return new IAM.Role(
  this,
  'thinkAmiDeployRole',
  {
    roleName: 'thinkAmi-Serverless-Deploy-Role',
    assumedBy: new IAM.AccountPrincipal(this.account),
    managedPolicies: [deployPolicy]
  }
);

 

デプロイするユーザが所属するグループを作成

デプロイ用IAMロールに対してAssumeRoleできるIAMポリシーをIAMグループに割り当てます。

// Deployするユーザが所属するグループを作成
// Serverless FrameworkでAWSリソースを扱うためのロールに対してAssumeRoleできる権限を、このグループに割り当てる
createGroupOfDeployUser(deployRole: IAM.Role) {
  const statements = [];

  // AssumeRoleする権限
  statements.push(new IAM.PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      'sts:AssumeRole'
    ],
    resources: [
      deployRole.roleArn
    ]
  }));

  // ユーザ管理ポリシーとして作成
  const assumePolicy = new IAM.ManagedPolicy(
    this,
    'thinkAmiAssumePolicy',
    {
      managedPolicyName: 'thinkAmi-Serverless-Assume-By-User',
      statements: statements
    }
  );

  // グループに割り当て
  new IAM.Group(
    this,
    'thinkAmiAssumeGroup',
    {
      groupName: 'thinkAmi-Serverless-Assume',
      managedPolicies:[assumePolicy]
    }
  );
}

 

CloudFormationテンプレートの確認

cdk synth を使うことで、CDKで実装した内容がCloudFormationテンプレートとして表示されます。

そのため、CDKでデプロイする前にCloudFormation用テンプレートを確認できます。

# cdk.json ファイルがある階層で実施
>cdk synth

 

CDKでのデプロイ

IAMまわりを作成するため、AdministratorAccess権限を持つIAMロールで実行します。

>aws-vault exec admin -- cdk deploy -v
...
 ✅  CdkStack

Stack ARN:
arn:aws:cloudformation:<region>:<account_id>:stack/CdkStack/xxx

 

手動での設定
ユーザをグループに割り当て

手動にて、 thinkAmi-Serverless-Assume グループへデプロイユーザを割り当てます。

 

aws-vault用にデプロイロール設定を追加

既存のgateユーザがデプロイロールを使えるようにするため、 .aws\config へ追記します。

[default]
region=ap-northeast-1
output=json

[profile gate]


# 以下を追加
[profile hello]
source_profile = gate
role_arn = <thinkAmi-Serverless-Deploy-RoleのARN>
role_session_name = thinkAmiHelloDeploy
mfa_serial = <gateユーザが持つMFAのARN>

 

Systems ManagerのParameter Storeにデプロイ用ロールのARNをセット

デプロイ用ロールのARNをserverless.ymlに設定する必要があるものの、ARNの値をハードコーディングしたくありません。

そこで今回は、Systems ManagerのParameter Storeに値を手動で設定します。
Secrets Management for AWS Powered Serverless Applications

名前 種類
/hello-sls/CFn-Role デプロイ用IAM RoleのARN SecureString

 
ここまでで、Serverless Frameworkでデプロイするのに必要な権限まわりの準備は終わりました。

 

Serverless Frameworkによる実装

続いて、Serverless Framewrokでの設定を行います。

 

serverless.yamlでの設定

Serverless FrameworkでデプロイするAWSリソースなどを serverless.yml に記載します。

 

serviceまわり

service名は hello-sls とします。

また、frameworkVersionはデフォルトの 2 のままとします。

 
なお、Serverless Frameworkの2系では、Parameter Storeから値が取得できなくてもデプロイが継続されてしまいます。

一方、今後リリースされる3系ではエラーになるようです。
https://www.serverless.com/framework/docs/deprecations/#PROVIDER_IAM_SETTINGS

そこで今回は、3系と同じくParameter Storeより値が取得できなければエラーとなるように設定します。

service: hello-sls
frameworkVersion: '2'

# Parameter Storeより値が取得できなければエラー
unresolvedVariablesNotificationMode: error

 

provider

通常設定するものの他、Serverless Framework 3系の機能を先取りするよう設定します。

まずはAPI Gatewayの名前を <service>-<stage> とするよう、 apiGateway.shouldStartNameWithService を設定します。
https://www.serverless.com/framework/docs/deprecations/#AWS_API_GATEWAY_NAME_STARTING_WITH_SERVICE

 
また、Serverless FrameworkでCloudFormationを実行するRoleの指定について、今までは provider.cfnRole だったものが今後は provider.iam.deploymentRole になるため、その変更も行います。
https://www.serverless.com/framework/docs/deprecations/#AWS_API_GATEWAY_NAME_STARTING_WITH_SERVICE

 
なお、 deploymentRole の値は Parameter Storeから取得するように指定します。
Secrets Management for AWS Powered Serverless Applications

今回のParamter Storeは SecureString としたため、serverless.ymlで指定する場合は末尾に ~true を付与します。  

provider:
  name: aws
  runtime: python3.8
  lambdaHashingVersion: 20201221
  region: ap-northeast-1
  stage: development

  apiGateway:
    shouldStartNameWithService: true

  iam:
    deploymentRole: ${ssm:/hello-sls/CFn-Role~true}

 

functions

ここからはAPI GatewayとLambdaの設定を行います。

handler.pyhello 関数をLambdaで実行するための設定を行います。

functions:
  hello:
    handler: handler.hello

 
また、API Gatewayも作成します。今回は以下の内容でAPI Gatewayを作成します。

項目
パス /req
メソッド GET
統合リクエス Lambda
マッピングテンプレート - リクエスト本文のパススルー リクエストの Content-Type ヘッダーに一致するテンプレートがない場合
マッピングテンプレート 定義なし

 
serverless.ymlでは以下の通りになります。

なお、 template では2つのContent-Typeに null を設定しています。何も設定しないとデフォルトのテンプレートが設定されてしまうためです。
https://www.serverless.com/framework/docs/providers/aws/events/apigateway#custom-request-templates

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: req
          method: get
          integration: lambda
          request:
            passThrough: WHEN_NO_MATCH
            template:
              application/json: null
              application/x-www-form-urlencoded: null

 
詳細は公式ドキュメントにも記載があります。
Serverless Framework - AWS Lambda Events - API Gateway

 
以上で serverless.yml の設定は完了です。

 

Lambdaの実装

serverless.yml に設定した handler.py を実装します。

今回は動作確認が取れれば良いので、生成されたLambdaをそのまま使います。

import json

def hello(event, context):
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

    # Use this code if you don't use the http event with the LAMBDA-PROXY
    # integration
    """
    return {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "event": event
    }
    """

 

デプロイと動作確認

デプロイ

ここまでで準備ができたため、Serverless Frameworkによるデプロイを行います。

デプロイが成功すると、Service Informationが表示されます。

>aws-vault exec hello -- serverless deploy -v

Enter token for arn:aws:iam::<account_id>:mfa/<user>: xxx
...
Serverless: Stack update finished...
Service Information
service: hello-sls
stage: development
region: ap-northeast-1
stack: hello-sls-development
resources: 11
api keys:
  None
endpoints:
  GET - https://path.to.ap-northeast-1.amazonaws.com/development/req
functions:
  hello: hello-sls-development-hello
layers:
  None

Stack Outputs
HelloLambdaFunctionQualifiedArn: arn:aws:lambda:<region>:<account_id>:function:hello-sls-development-hello:8
ServiceEndpoint: https://path.to.ap-northeast-1.amazonaws.com/development
ServerlessDeploymentBucketName: hello-sls-development-serverlessdeploymentbucket-xxx

 

動作確認

ここまでで Lambda + API GatewayAPIができました。

curl でアクセスしてみたところ、APIが動作するのを確認できました。

>curl -i https://path.to.ap-northeast-1.amazonaws.com/development/req

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 121
...
{"statusCode": 200, "body": "{\"message\": \"Go Serverless v1.0! Your function executed successfully!\", \"input\": {}}"}

 

ソースコード

Githubに上げました。
https://github.com/thinkAmi-sandbox/sls_hello_with_cdk_by_min_perm

*1:割り当てるところまでやってしまうとユーザ間での使い回しが面倒なため、一部手動を残しました

*2:と、さくっと書きましたが、実際にはトライアンドエラーでした。「serverless deploy」で失敗したら、CloudFormationのログを確認し、どの権限が不足しているかを確認しながら進めました