Serverless Framework から AWS Systems Manager パラメータストアを利用する
はじめに
Serverless Framework でアプリケーションを作成する際、秘密情報をリポジトリ内に管理せず外部で管理して取得したい場合があります。
そこで Serverless Framework では、AWS Systems Manager パラメータストアに対応し、管理したパラメータを取得して展開できます。
今回は Go のサンプルアプリケーションを元に、 AWS Systems Manager パラメータストアと連携してどのように値を取得するのか記載します。
環境
- macOS High Sierra Version 10.13.6
- Node.js v10.15.3
- npm 6.9.0
- Go 1.12.1
- Serverless Framework 1.39.1
- AWS CLI 1.16.120
- github.com/aws/aws-lambda-go v1.9.0
- github.com/aws/aws-sdk-go v1.18.6
Serverless Framework のインストール
Node.js・Go・AWS CLI がインストールされている前提で始めます。
インストールしていない方は各ランタイム、CLI のインストールから始めてください。
まずは、Serverless Framework をインストールします。
$ npm install -g serverless
すでにインストールされている方はアップデートしましょう。
$ npm update -g serverless
サンプルアプリケーションの作成
任意のディレクトリで以下のコマンドからサンプルアプリケーションを作成します。
sls
は、serverless
コマンドの省略形です。
$ sls create --template aws-go --path sample
以下のようなテンプレートプロジェクトが作成されます。
$ tree sample
sample
├── Makefile
├── hello
│ └── main.go
├── serverless.yml
└── world
└── main.go
作成したディレクトリ内で Go Modules を有効にします。
$ cd sample
$ export GO111MODULE=on
$ go mod init
$ go get github.com/aws/aws-lambda-go
サンプルアプリケーションを少し修正します。
serverless.yml
を開き、以下のコメントアウトを外して region を ap-northeast-1
に変更します。
provider:
name: aws
runtime: go1.x
# you can overwrite defaults here
-# stage: dev
-# region: us-east-1
+ stage: dev
+ region: ap-northeast-1
また、Makefile に undeploy
コマンドを追加しておきます。
-.PHONY: build clean deploy
+.PHONY: build clean deploy undeploy
build:
env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go
env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go
clean:
rm -rf ./bin
deploy: clean build
sls deploy --verbose
+
+undeploy:
+ sls remove --verbose
確認のためにサンプルアプリケーションをデプロイしてみます。
$ make deploy
デプロイ後以下のようなエンドポイントが払い出されます。
endpoints:
GET - https://odf7wg3qii.execute-api.ap-northeast-1.amazonaws.com/dev/hello
GET - https://odf7wg3qii.execute-api.ap-northeast-1.amazonaws.com/dev/world
それぞれのエンドポイントを確認すると以下のようなレスポンスが得られます。
$ curl https://odf7wg3qii.execute-api.ap-northeast-1.amazonaws.com/dev/hello
{"message":"Go Serverless v1.0! Your function executed successfully!"}
$ curl https://odf7wg3qii.execute-api.ap-northeast-1.amazonaws.com/dev/world
{"message":"Okay so your other function also executed successfully!"}
パラメータを取得して注入する
さて本題ですが、AWS からパラメータを取得する方法として大きく 2 つの方法があります。
AWS Systems Manager パラメータストアと AWS Secrets Manager です。
AWS Systems Manager パラメータストアは、無料でパラメータを管理するサービスです。AWS KMS の料金が別途掛かりますが、AWS KMS による暗号化にも対応しています。
AWS Secrets Manager は、有料ですが AWS KMS で暗号化し、RDS などの DB 接続情報を自動更新したい場合に特に有効なサービスです。
今回は AWS Systems Manager パラメータストアを使用します。
AWS Secrets Manager を用いる場合には、以下のドキュメントを参考にしてください。
Reference Variables using AWS Secrets Manager - Serverless Framework Documentation
パラメータストアに文字列として保存した値を取得する
まずは AWS Systems Manager パラメータストアへ暗号化せずに、パラメータを保存して取得します。
以下のコマンドで文字列のパラメータを作成します。
$ aws ssm put-parameter --name "MY_SECRET" \
--description "文字列パラメータ" \
--type "String" \
--value "String parameter"
作成した MY_SECRET
を環境変数から取得するように serverless.yml
を修正します。
provider:
name: aws
runtime: go1.x
# you can overwrite defaults here
stage: dev
region: ap-northeast-1
+ environment:
+ MY_SECRET: ${ssm:MY_SECRET}
environment
に環境変数名を指定し、${ssm:パラメータ名}
とすることでパラメータストアから取得できます。
hello の main 関数を修正して追記した環境変数を取得します。
package main
import (
"bytes"
"context"
"encoding/json"
+ "os"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
---------------------------------------------------------------------------
// Handler is our lambda handler invoked by the `lambda.Start` function call
func Handler(ctx context.Context) (Response, error) {
var buf bytes.Buffer
+ var mySecret = os.Getenv("MY_SECRET")
+
+ var parameter = "MY_SECRET: " + mySecret
+
body, err := json.Marshal(map[string]interface{}{
- "message": "Go Serverless v1.0! Your function executed successfully!",
+ "message": parameter,
})
if err != nil {
return Response{StatusCode: 404}, err
}
再度デプロイします。
$ make deploy
hello のエンドポイントを確認すると MY_SECRET
の環境変数にパラメータストアへ保存した値が取得できていることが分かります。
$ curl https://odf7wg3qii.execute-api.ap-northeast-1.amazonaws.com/dev/hello
{"message":"MY_SECRET: String parameter"}
AWS Lambda の画面からも MY_SECRET
の環境変数が展開されていることが分かります。
パラメータストアに安全な文字列として保存した値を取得する
次に AWS KMS で値を暗号化してパラメータストアから取得してみます。
まずは、AWS KMS の暗号化キーを作成します。
特に細かいポリシーは作成していませんが、必要に応じて指定してください。
$ aws kms create-key --description "Sample key"
作成した暗号化キーに分かりやすい名前を付けておきます。
KeyId には、aws kms create-key
で作成した際に表示された KeyId を指定します。
$ aws kms create-alias --alias-name alias/sample-key --target-key-id ${KeyId}
作成した暗号化キーを用いてパラメータを作成します。
$ aws ssm put-parameter --name "MY_SECRET2" \
--description "安全な文字列パラメータ" \
--type "SecureString" \
--value "Secure string parameter" \
--key-id "alias/sample-key"
作成した MY_SECRET2
を環境変数から取得するように serverless.yml
を修正します。
provider:
name: aws
runtime: go1.x
# you can overwrite defaults here
stage: dev
region: ap-northeast-1
environment:
MY_SECRET: ${ssm:MY_SECRET}
+ MY_SECRET2: ${ssm:MY_SECRET2~true}
~true
を付与することで暗号化したパラメータを復号化した状態で取得できます。
hello の main 関数を修正して追記した環境変数を取得します。
// Handler is our lambda handler invoked by the `lambda.Start` function call
func Handler(ctx context.Context) (Response, error) {
var buf bytes.Buffer
var mySecret = os.Getenv("MY_SECRET")
+ var mySecret2 = os.Getenv("MY_SECRET2")
- var parameter = "MY_SECRET: " + mySecret
+ var parameter = "MY_SECRET: " + mySecret + ", MY_SECRET2: " + mySecret2
body, err := json.Marshal(map[string]interface{}{
"message": parameter,
})
if err != nil {
return Response{StatusCode: 404}, err
}
再度デプロイします。
$ make deploy
再度 hello のエンドポイントを確認すると MY_SECRET2
の環境変数にパラメータストアへ暗号化して保存した値が取得できていることが分かります。
$ curl https://odf7wg3qii.execute-api.ap-northeast-1.amazonaws.com/dev/hello
{"message":"MY_SECRET: String parameter, MY_SECRET2: Secure string parameter"}
AWS Lambda の画面からも MY_SECRET2
の環境変数が展開されていることが分かります。
しかしここで課題に挙げるとすれば、AWS Lambda の画面上で暗号化したパラメータが平文として見えてしまうことです。
AWS Lambda の画面上での復号化について問題とする場合には次の方法で対策が可能です。
AWS Lambda 内からパラメータストアに安全な文字列として保存した値を取得する
AWS Lambda 内で復号化まで完結したい場合には、AWS Lambda 内から AWS SDK for Go を利用して復号化する必要があります。
serverless.yml
を修正します。
MY_SECRET2
は、AWS Lambda 内から復号化するため削除し、AWS SDK for Go から復号するためのロールを付与します。
対象のリソースは復号するパラメータのリソースに限定するとより良いですが、今回は全体で指定しています。
適宜調整してください。
provider:
name: aws
runtime: go1.x
# you can overwrite defaults here
stage: dev
region: ap-northeast-1
+ iamRoleStatements:
+ - Effect: "Allow"
+ Action:
+ - "ssm:GetParameter"
+ Resource: "*"
+ - Effect: "Allow"
+ Action:
+ - "kms:Decrypt"
+ Resource: "*"
environment:
MY_SECRET: ${ssm:MY_SECRET}
- MY_SECRET2: ${ssm:MY_SECRET2~true}
Go Modules 有効になった状態で AWS SDK for Go をインストールします。
$ go get github.com/aws/aws-sdk-go
AWS SDK for Go を利用してパラメータ取得するように hello の main 関数を修正します。
package main
import (
"bytes"
"context"
"encoding/json"
"os"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/session"
+ "github.com/aws/aws-sdk-go/service/ssm"
)
---------------------------------------------------------------------------
// Handler is our lambda handler invoked by the `lambda.Start` function call
func Handler(ctx context.Context) (Response, error) {
var buf bytes.Buffer
var mySecret = os.Getenv("MY_SECRET")
- var mySecret2 = os.Getenv("MY_SECRET2")
+
+ // Session の作成
+ sess, err := session.NewSession(&aws.Config{
+ Region: aws.String("ap-northeast-1")},
+ )
+
+ // KMS サービスクライアントの作成
+ svc := ssm.New(sess)
+
+ // 復号化してパラメータを取得
+ result, err := svc.GetParameter(&ssm.GetParameterInput{
+ Name: aws.String("MY_SECRET2"),
+ WithDecryption: aws.Bool(true),
+ })
+ if err != nil {
+ return Response{StatusCode: 500}, err
+ }
- var parameter = "MY_SECRET: " + mySecret + ", MY_SECRET2: " + mySecret2
+ var parameter = "MY_SECRET: " + mySecret + ", MY_SECRET2: " + *result.Parameter.Value
body, err := json.Marshal(map[string]interface{}{
"message": parameter,
})
if err != nil {
return Response{StatusCode: 404}, err
}
再度デプロイします。
$ make deploy
再度 hello のエンドポイントを確認すると MY_SECRET2
の環境変数を指定せずにパラメータストアへ暗号化して保存した値が取得できていることが分かります。
$ curl https://odf7wg3qii.execute-api.ap-northeast-1.amazonaws.com/dev/hello
{"message":"MY_SECRET: String parameter, MY_SECRET2: Secure string parameter"}
AWS Lambda の画面からも MY_SECRET
の環境変数のみが展開されていることが分かります。
お掃除
サンプルアプリケーションをアンデプロイします。
$ make undeploy
作成したパラメータを削除します。
$ aws ssm delete-parameter --name "MY_SECRET"
$ aws ssm delete-parameter --name "MY_SECRET2"
作成した暗号化キーを削除します。
暗号化キーはすぐには削除されず、削除の待機期間が設けられます。
待機期間は、7 から 30 日の範囲で指定できます。
今回は最短の 7 日を指定します。
KeyId には、暗号化キー作成した際に表示された KeyId を指定します。
分からない場合は、aws kms list-aliases
コマンドを実行して alias に対応する TargetKeyId
を確認してください。
$ aws kms schedule-key-deletion --key-id ${KeyId} --pending-window-in-days 7
さいごに
Serverless Framework から AWS Systems Manager パラメータストアを利用して管理したパラメータを取得できることが分かりました。
Serverless Framework が提供する機能を利用して、パラメータストアから必要に応じて AWS KMS と連携して暗号化したパラメータも取得できるのは非常に便利です。
そして、~true
を指定して復号化すると AWS Lambda から平文で見えてしまうという課題を挙げました。
試した方法では、内部でパラメータストアから直接復号化してしまうと環境変数として取得する意義が薄れると感じました。
今回は試していませんが、~true
を指定しなければ暗号化されたテキストが取得できるので、暗号化されたテキストを AWS SDK の AWS KMS の機能を利用して復号化する方が多少の自由度は上がる可能性があります。
参考
AWS の Parameter Store と Secrets Manager、結局どちらを使えばいいのか?比較 - Qiita
Lambda(Golang)から AWS KMS を復号化する方法 · TechTeco
Serverless Framework - Serverless - AWS Guide
kms — AWS CLI 1.16.128 Command Reference - Amazon.com
ssm — AWS CLI 1.16.128 Command Reference - Amazon.com
ssm - Amazon Web Services - Go SDK - AWS Documentation
カスタマーマスターキーを削除する - AWS Key Management Service
Author And Source
この問題について(Serverless Framework から AWS Systems Manager パラメータストアを利用する), 我々は、より多くの情報をここで見つけました https://qiita.com/takasp/items/60b201607cec57d55596著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .