この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
おはようございます、加藤です。AWS CDKを使っている際に、外部APIのURLやシークレットをハードコートせずに与えたい場合があります。 この様な要望に答える為に、CDKにはAWS Systems Manager パラメータストアやAWS Secrets Managerに保存された値を利用する仕組みがあります。 やや癖のある仕様なので、具体的にどの様に使用する事が適切か解説します。
- Node.js: 12.14.1
- TypeScript: 3.7.5
- AWS CDK: 1.27.0
パラメータストア
パラメータストアから値をCDKに取り込む為のメソッドは3種類用意されています。なぜ3種類あるかと言うと、取得するタイミング及びタイプごとにメソッドが用意されているからです。 また、メソッドごとにCloudFormationのどの機能を使って実現しているかが異なります。実現する為の機能が違うのでメソッドごとに制約も異なります。
メソッド名 | 取得するタイミング | タイプ | 実現する為のCFnの機能 |
---|---|---|---|
valueFromLookup | テンプレート生成時 | String | テンプレートに転記 |
valueForStringParameter | デプロイ時 | String | SSM パラメータタイプ |
valueForSecureStringParameter | デプロイ時 | SecureString | ssm-secureの動的参照 |
valueFromLookup
このメソッドを使うと生成するテンプレートにパラメータストアに格納されていたStringを転記します。テンプレートに転記する為、SecureStringに対して使用する事ができません。SecureStringに対して使用できてしまうと、CloudFormationに対する読み取り権限があれば誰でもテンプレートを閲覧する事で内容を確認できてしまいます。Stringに対してパラメータストアの階層構造などで権限管理している場合、その管理から外れて閲覧できるユーザーが発生しうるので注意が必要です。 パラメータのバージョンはlatestに強制されます。 実は、SecureStringに対して使用する事は出来ますが、暗号化されたままの文字列で取り出されるので使い道はありません。
import { Construct, RemovalPolicy, Stack, StackProps, Tag } from '@aws-cdk/core';
import { Bucket } from '@aws-cdk/aws-s3';
import { StringParameter } from '@aws-cdk/aws-ssm';
export class CdkGetValueStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
new Bucket(this, 'Bucket', {removalPolicy: RemovalPolicy.DESTROY});
const plain = StringParameter.valueFromLookup(this, '/prj/env/plain');
const secure = StringParameter.valueFromLookup(this, '/prj/env/secure');
Tag.add(this, 'plain', plain, {includeResourceTypes: ['AWS::S3::Bucket']});
Tag.add(this, 'secure', secure, {includeResourceTypes: ['AWS::S3::Bucket']});
}
}
Resources:
Bucket83908E77:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: plain
Value: Plaintext
- Key: secure
Value: AQICAHjGxLVKJmKpVvWvXVpkAL4oQRmwTG0n2vcsGfl/k9eW+gErdD0rqL4FZyySRLTgD5VJAAAAaDBmBgkqhkiG9w0BBwagWTBXAgEAMFIGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMsKXPrjVWaa9EABdaAgEQgCVtebX5csafsTWvHAOZlDxWJ6iV5dSEE193sxGH4TOF2lwE1Em3
valueForStringParameter
このメソッドを使うとSSM パラメータタイプのAWS::SSM::Parameter::Value
を使用しパラメータストアに格納されたStringを任意の箇所で使用する事ができます。
今回は省略していますが、第3引数でパラメータのバージョンを指定する事が可能です。指定しない場合はlatestを参照します。
import { Construct, RemovalPolicy, Stack, StackProps, Tag } from '@aws-cdk/core';
import { Bucket } from '@aws-cdk/aws-s3';
import { StringParameter } from '@aws-cdk/aws-ssm';
export class CdkGetValueStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
new Bucket(this, 'Bucket', {removalPolicy: RemovalPolicy.DESTROY});
const plain = StringParameter.valueForStringParameter(this, '/prj/env/plain');
Tag.add(this, 'plain', plain, {includeResourceTypes: ['AWS::S3::Bucket']});
}
}
Parameters:
SsmParameterValueprjenvplainC96584B6F00A464EAD1953AFF4B05118Parameter:
Type: AWS::SSM::Parameter::Value
Default: /prj/env/plain
Resources:
Bucket83908E77:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: plain
Value:
Ref: SsmParameterValueprjenvplainC96584B6F00A464EAD1953AFF4B05118Parameter
valueForSecureStringParameter
このメソッドを使うとssm-secureの動的参照を使用しパラメータストアに格納されたSecureStringを下記の特定の箇所で使用する事ができます。 valueForStringParameterと異なりパラメータのバージョンの指定が必須でlatestは指定できません。 使用可能な箇所が限られており、シークレットだけど下記には該当しないという事象が容易に発生しそうです。個人的にはこれの使用は避けたいです。
リソース | プロパティタイプ | プロパティ |
---|---|---|
AWS::DirectoryService::MicrosoftAD | Password | |
AWS::DirectoryService::SimpleAD | Password | |
AWS::ElastiCache::ReplicationGroup | AuthToken | |
AWS::IAM::User | LoginProfile | Password |
AWS::KinesisFirehose::DeliveryStream | RedshiftDestinationConfiguration | Password |
AWS::OpsWorks::App | 送信元 | Password|AWS::OpsWorks::Stack|CustomCookbooksSource|Password |
AWS::OpsWorks::Stack | RdsDbInstances | DbPassword |
AWS::RDS::DBCluster | MasterUserPassword | |
AWS::RDS::DBInstance | MasterUserPassword | |
AWS::Redshift::Cluster | MasterUserPassword |
引用元: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/dynamic-references.html#template-parameters-dynamic-patterns-resources
import { Construct, SecretValue, Stack, StackProps } from '@aws-cdk/core';
import { User } from '@aws-cdk/aws-iam';
import { StringParameter } from '@aws-cdk/aws-ssm';
export class CdkGetValueStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const secure = StringParameter.valueForSecureStringParameter(this, '/prj/env/secure', 1);
new User(this, 'User', {
password: new SecretValue(secure)
});
}
}
Resources:
User00B015A1:
Type: AWS::IAM::User
Properties:
LoginProfile:
Password: "{{resolve:ssm-secure:/prj/env/secure:1}}"
Secrets Manager
Secrets Managerから値をCDKに取り込む為のメソッドとして、fromSecretAttributes
が用意されています。
このメソッドを使うとsecretsmanagerの動的参照を使用しシークレットを任意の箇所で使用する事ができます。
Secrets Managerは、文字列またはJSON文字列を格納できます。このメソッドは取り出す場合、文字列またはJSON文字列のキー指定で取り出す事ができます。
import { Construct, RemovalPolicy, Stack, StackProps, Tag } from '@aws-cdk/core';
import { Bucket } from '@aws-cdk/aws-s3';
import { Secret } from '@aws-cdk/aws-secretsmanager';
export class CdkGetValueStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const secret = Secret.fromSecretAttributes(this, 'Secret', {
secretArn: `arn:aws:secretsmanager:${this.region}:${this.account}:secret:secretmanager-nsUGO2`
});
const secretJson = Secret.fromSecretAttributes(this, 'SecretJson', {
secretArn: `arn:aws:secretsmanager:${this.region}:${this.account}:secret:secretmanagerJson-dQYcXL`
});
new Bucket(this, 'Buckets', {
removalPolicy: RemovalPolicy.DESTROY,
bucketName: `${secret.secretValue.toString()}-${this.account}`
});
new Bucket(this, 'Bucket', {
removalPolicy: RemovalPolicy.DESTROY,
bucketName: `${secretJson.secretValueFromJson('key').toString()}-${this.account}`
});
}
}
Resources:
BucketsF2AD3CC3:
Type: AWS::S3::Bucket
Properties:
BucketName: "{{resolve:secretsmanager:arn:aws:secretsmanager:${region}:${account}:secret:secretmanager-nsUGO2:SecretString:::}}-${account}"
Bucket83908E77:
Type: AWS::S3::Bucket
Properties:
BucketName: "{{resolve:secretsmanager:arn:aws:secretsmanager:${region}:${account}:secret:secretmanagerJson-dQYcXL:SecretString:key::}}-${account}"
まとめ
紹介してパラメータの取り込み方法を表にまとめました。
サービス名 | メソッド名 | 取得するタイミング | タイプ | 実現する為のCFnの機能 | 利用先 |
---|---|---|---|---|---|
SSM パラメータストア | valueFromLookup | テンプレート生成時 | String | テンプレートに転記 | 全て |
SSM パラメータストア | valueForStringParameter | デプロイ時 | String | SSM パラメータタイプ | 全て |
SSM パラメータストア | valueForSecureStringParameter | デプロイ時 | SecureString | ssm-secureの動的参照 | 限定 |
Secrets Manager | fromSecretAttributes | デプロイ時 | SecureString, SecureStringJson | secretsmanagerの動的参照 | 全て |
Secureが付いていない暗号化されていないから安全ではないという事ではありません。 シークレットをStringとしてパラメータに格納している場合でも、IAMが適切に管理されていれば開発者はシークレットにアクセスする事ができません。
考察
個人的な考察です。
ローテーションやJSON形式など、Secrets Managerの方がリッチに使えるので、パラメータストアと比べるとSecrets Managerを積極的に利用したい。
Secrets Managerはアクセスのコストがパラメータストアと比較して高いのがネックですが、今回の様なデプロイ時のみのアクセスであれば、まず問題無い。
パラメータストアの階層構造を使いたい場合はvalueForStringParameter
を使い、RDSのシークレットなどもStringで格納してIAMで制御すれば良い。
パラメータの格納は、READMEに手順を作り、初回構築時にAWSマネジメントコンソールかAWS CLIから行えば良さそう。 個人的感想をまとめます。
- Secrets Managerに全てのパラメータを格納して
fromSecretAttributes
で取り出す - パラメータストアを使いたい場合は全てStringで格納し
valueForStringParameter
で取り出す - 可能なら方法は併用せず1つだけ使う
あとがき
AWS BlackBelt CDKが面白かったし、CDK MeetUpも良かったのでブログを書きました。 最近、コードが書けるようになったので、CDK使っていて、足りなかった所をコントリビュートしているけど、レビューが当然英語で口語なので、Google翻訳がいい感じに訳してくれず辛いです。。。