全力で怠けたい

怠けるために全力を尽くしたいブログ。

CloudFormation のスタックテンプレートから Secrets Manager のシークレットを動的に参照するメモ。

CloudFormation のスタックテンプレートから Secrets Manager のシークレットを動的に参照するメモ。

CloudFormation のスタックテンプレートから Secrets Manager のシークレットを動的に参照する

CloudFormation のスタックテンプレートからスタックを作っていると必ず一度は秘密情報の扱いをどうするか? みたいな事態に直面する。 ここでは秘密情報というのは DB のパスワードとか Web サービスのAPI キーみたいな「他人に漏れると金銭的損害を始めとしていろんな損害しかも大きな損害を被りそうなすごく秘匿性の高い情報」とかの意味で使うけど、普通に考えると DB のパスワードとか Web サービスの API キーとかを CloudFormation のスタックテンプレートの中に埋め込むのは明らかにまずいと思う。

そういう秘密情報を CloudFormation のスタックテンプレートに使うときは AWS Systems Manager の SecureString パラメータを使う方法と AWS Secrets Manager のシークレットを使う方法があるけどほとんどのケースは AWS Secrets Manager のシークレットを使う方法で事足りるので大概は AWS Secrets Manager のほうを使ってる。 => 一応、秘密情報はパラメータで渡すこともできるけどスタックを作るたびに指定するのは面倒くさい

そのへんのことを軽くメモしていく。

Secrets Manager のシークレットの登録

CloudFormation スタックテンプレートから参照するシークレットは事前に Secrets Manager に登録しておく。

$ aws secretsmanager create-secret --name=db-root-account --secret-string='{"username":"this-is-username","password":"this-is-password"}'
{
    "VersionId": "eaaac565-8481-4e22-8ae1-2e1ba125a233",
    "Name": "db-root-account",
    "ARN": "<ARN>"
}

このあと CloudFormation スタックテンプレートからバージョンを指定してシークレットを取得していくのでシークレットを更新しておく => VersionId のとこがシークレットを登録したときから変わる

$ aws secretsmanager update-secret --secret-id=db-root-account --secret-string='{"username":"this-is-new-username","password":"this-is-new-password"}'
{
    "VersionId": "5606eb1c-2892-4721-9936-324527c366b1",
    "Name": "db-root-account",
    "ARN": "<ARN>"
}

シークレットのバージョンを指定しないでシークレットを取得すると VersionStagesAWSCURRENT のシークレットを取得する。 VersionId のとこがシークレットを更新したときの出力が一致している。

$ aws secretsmanager get-secret-value --secret-id=db-root-account --version-id=5606eb1c-2892-4721-9936-324527c366b1
{
    "Name": "db-root-account",
    "VersionId": "5606eb1c-2892-4721-9936-324527c366b1",
    "SecretString": "{\"username\":\"this-is-new-username\",\"password\":\"this-is-new-password\"}",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": 1593245998.012,
    "ARN": "<ARN>"
}

シークレットを更新したときの VersionId を指定してシークレットを取得するとさきほどと同じシークレットを取得する。

$ aws secretsmanager get-secret-value --secret-id=db-root-account --version-id=5606eb1c-2892-4721-9936-324527c366b1
{
    "Name": "db-root-account",
    "VersionId": "5606eb1c-2892-4721-9936-324527c366b1",
    "SecretString": "{\"username\":\"this-is-new-username\",\"password\":\"this-is-new-password\"}",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": 1593245998.012,
    "ARN": "<ARN>"
}

シークレットを最初に登録したときの VersionId を指定してシークレットを取得すると最初に登録したシークレットを取得する。

$ aws secretsmanager get-secret-value --secret-id=db-root-account --version-id=eaaac565-8481-4e22-8ae1-2e1ba125a233
{
    "Name": "db-root-account",
    "VersionId": "eaaac565-8481-4e22-8ae1-2e1ba125a233",
    "SecretString": "{\"username\":\"this-is-username\",\"password\":\"this-is-password\"}",
    "VersionStages": [
        "AWSPREVIOUS"
    ],
    "CreatedDate": 1593245628.992,
    "ARN": "<ARN>"
}

CloudFormation のスタックテンプレートからシークレットを参照

CloudFormation スタックテンプレートから Secrets Manager に登録しているシークレットを動的に参照していく。作成するリソースはなんでもいいんだけどすぐに作れてお金がかからないセキュリティグループを作成していく。

まずバージョンは指定しないでシークレットを参照していく。 こんな感じのスタックテンプレートを書いて CloudFormation スタックを作成していく。

AWSTemplateFormatVersion: "2010-09-09"
Resources:
  HogeSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: dynamic refernces
      GroupName: hoge-security-group
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          Description: '{{resolve:secretsmanager:db-root-account:SecretString:username::}}'
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          Description: '{{resolve:secretsmanager:db-root-account:SecretString:password::}}'
      VpcId: <VPC ID>

CLI でセキュリティグループの定義を見てみると更新したあとのシークレットが取得しているのが分かる。

$ aws ec2 describe-security-groups --group-names=hoge-security-group | jq '.SecurityGroups[].IpPermissions[].IpRanges[].Description'
"this-is-new-username"
"this-is-new-password"

次はシークレットのバージョンを指定してシークレットを参照していく。 シークレットを最初に登録したときの VersionIdeaaac565-8481-4e22-8ae1-2e1ba125a233 を Description のとこに追加して CloudFormation スタックを作成していく。

AWSTemplateFormatVersion: "2010-09-09"
Resources:
  HogeSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: dynamic refernces
      GroupName: hoge-security-group
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          Description: '{{resolve:secretsmanager:db-root-account:SecretString:username::eaaac565-8481-4e22-8ae1-2e1ba125a233}}'
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          Description: '{{resolve:secretsmanager:db-root-account:SecretString:password::eaaac565-8481-4e22-8ae1-2e1ba125a233}}'
      VpcId: <VPC ID>

CLI でセキュリティグループの定義を見てみると最初に登録したシークレットが取得しているのが分かる。

$ aws ec2 describe-security-groups --group-names=hoge-security-group | jq '.SecurityGroups[].IpPermissions[].IpRanges[].Description'
"this-is-username"
"this-is-password"

CloudFormation スタックテンプレートからはこんな感じで Secrets Manager に登録しているシークレットを取得できる。

気にしておきたいこと

シークレットのリファレンスパターン

CloudFormation スタックテンプレートから Secrets Manager に登録しているシークレットを参照するときは {{resolve:secretsmanager:secret-id:secret-string:json-key:version-stage:version-id}} の形式で参照していく。 resolve:secretsmanager の部分は固定で、それ以外の部分はそれぞれの意味と使い方を書いていく。

secret-id

secret-id のとこは Secrets Manager に登録しているシークレットの ID を指定する。シークレットの完全な ARN を指定して別の AWS アカウントのシークレットを参照することもできる。

さきほどの例は db-root-account を指定した。

secret-string

SecretString 固定。

json-key

値を取得するシークレットのキー名を指定する。 CloudFormation は json-key を省略するとシークレットテキスト全体を取得する。

さきほどの例は usernamepassword を指定してシークレットのユーザー名とパスワードがそれぞれセキュリティグループの ingress ルールの Description に埋め込んだやつ。 もしさきほどの例で json-key を省略すると {"username":"this-is-new-username","password":"this-is-new-password"} がセキュリティグループの ingress ルールの Description に埋め込む感じ。

version-stage

version-stage を指定して取得するシークレットのバージョンを指定できる。 version-stage は AWSCURRENT とか AWSPREVIOUS とかがある。

version-stage と verson-id はどちらか1つしか指定できないので version-stage を指定するときは version-id は指定できず、version-stage も version-id も指定しないときは AWSCURRENT の version-stage を指定したのと同じになる。

さきほどの1つめの CloudFormation スタックテンプレートは version-stage も version-id も指定しないので AWSCURRENT の version-stage を指定したのと同じつまり更新後のシークレット this-is-new-usernamethis-is-new-password を取得している。

version-id

version-id を指定して取得するシークレットのバージョンを指定できる。 version-stage と verson-id はどちらか1つしか指定できないので version-stage を指定するときは version-id は指定できず、version-stage も version-id も指定しないときは AWSCURRENT の version-stage を指定したのと同じになる。

さきほどの2つめの CloudFormation スタックテンプレートは最初にシークレットを登録したときの version-id を指定しているので最初に登録したときのシークレット this-is-usernamethis-is-password を取得している。

必要なアクセス許可

Secrets Manager に格納されているシークレットを取得するには取得するシークレットに対して GetSecretValue を呼び出すためのアクセス権を持っている必要がある。

動的参照の制限

CloudFormation テンプレートスタックから Secrets Manager に登録しているシークレットを取得するとき固有の制限ではなくて動的参照に共通している制限だけどいくつか制限があるので書いておく。

  • スタックテンプレートは動的参照を最大で60個までしか含めない
  • AWS::Include とか AWS::Serverless などのトランスフォームの場合、AWS CloudFormation はトランスフォームを呼び出す前には動的な参照を解決せず、動的参照のリテラル文字列をトランスフォームに渡す
  • カスタムリソースは Secrets Manager に登録しているシークレットは動的参照できない
  • CloudFormation は最終値としてバックスラッシュを含む動的な参照は解決できない

参考ページ