AWS CloudFormationでSecret Managerから文字列を引っ張ってくる手順

## はじめに
こんにちは。ネットアップ合同会社 Solutions Specialistの岩井です。皆さんは環境構築をする際、CloudFormationって使われていますか？岩井はよくお客様やパートナー様にハンズオンのイベントを開催していて、その時に使うラボ環境はCloudFormationを使って一気に作ったりします。去年京都で開催した防災訓練のラボ環境もCloudFormationを使って作りました。

https://qiita.com/buriki/items/5eae6542361a6098c066

CloudFormationのスクリプトを書く際、皆さんパスワードってどうしてますか？面倒くさがりの岩井は以前まで平文で突っ込んでいたんですが、セキュリティ防災訓練をする側が平文パスワードってどうなの？ってわけでCloudFormationにパスワードを入れる一番手軽な方法、**Secret Manager**をご紹介します。

## 今回のタスク
ハンズオンイベントでも検証でもよく使う、Amazon FSx for NetApp ONTAPを作っていきます。以前まで使っていたスクリプトはこんな感じ↓
```fsxn.yml
# ------------------------------------------------------------#
# FSxN
# ------------------------------------------------------------#
  MyFSxN01:
    Type: "AWS::FSx::FileSystem"
    Properties: 
      FileSystemType: "ONTAP"
      OntapConfiguration: 
        DeploymentType: "SINGLE_AZ_1"
        FsxAdminPassword: !Sub "anzen_na_password"
        ThroughputCapacity: 128        
      SecurityGroupIds: [!Ref FsxnSecurityGroup]
      StorageCapacity: 1024
      StorageType: "SSD"
      SubnetIds: 
        - !Sub "${PrivateSubnetID}"
      Tags: 
        - Key: "Name"
          Value: !Sub "iwai-fsxn-01"
```

見事に平文ですね。全然anzenじゃない。

## AWS Secret Managerとは
AWS Secrets Manager は、データベース認証情報、API キー、その他のシークレットのライフサイクルを通しての管理、取得、ローテーションができるサービスです。使いたい場所から(今回はCloudFormation)Secret ManagerのAPIを叩くことで、スクリプトやアプリに認証情報の平文を保存しなくてよいというメリットがあります。

https://aws.amazon.com/jp/secrets-manager/

試しにこんなシークレットを作るとします。passwordというキーに対して、オリジナルと同じ平文のパスワードを入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504005/e1a39ea7-80b6-2fbd-ccbb-bc108d52d244.png)

## CloudFormationでの実装 A-Part

公式ドキュメントを見ると、```Ref```でARNを指定すると、Secret Managerから文字列を引っ張ってこれそう。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-secret.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504005/e571eca9-bd4d-9c8b-03ea-462e8dd4be5a.png)

こんな感じに変更してみました。

```fsxn.yml
Parameters:
    FsxnAdminPassword:
    Type: String
    Default: !Ref '{{resolve:secretsmanager:ap-northeast-1:xxxxxxxxxxxxxxx:secret:iwai/handson-xxxxxx:password}}'

# ------------------------------------------------------------#
# FSxN
# ------------------------------------------------------------#
  MyFSxN01:
    Type: "AWS::FSx::FileSystem"
    Properties: 
      FileSystemType: "ONTAP"
      OntapConfiguration: 
        DeploymentType: "SINGLE_AZ_1"
        FsxAdminPassword: !Sub "${FsxnAdminPassword}"
        ThroughputCapacity: 128        
      SecurityGroupIds: [!Ref FsxnSecurityGroup]
      StorageCapacity: 1024
      StorageType: "SSD"
      SubnetIds: 
        - !Sub "${PrivateSubnetID}"
```

早速作ってみましたが、以下のエラーが、、、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504005/0bf9d141-0c71-2936-ca92-6e8c3acc46f3.png)

「Every Default member must be a string.」とあるので、パラメータのDefault部分はstringじゃないといけないらしいです。みなみにドキュメントをちゃんと読むと、```AWS::SecretsManager::Secret```を使うと、シークレットのARNを返してくれるそうです。紛らわしいですね（自分が悪い）。

## CloudFormationでの実装 B-Part

別の公式ドキュメント（CloudFormationからSecret Managerの値を引き出す方法について）を見てみます。A-Partで記した通り、パラメータではシークレットの値は取得できないので各リソースのスクリプト内で指定する必要があります。その際の指定フォーマットは以下です。

```
{{resolve:secretsmanager:secret-id:SecretString:json-key:version-stage:version-id}}
```

**secret-id**
シークレットの完全なARN。

**json-key**(オプション)
値を取得するkey&valueペアのキー名。指定しない場合は、シークレットテキスト全体を取得します。このセグメントには、コロン (:) を含めることはできません=Secret Managerでキー名を指定するときはコロン (:)含めないでください。そこはSecret Managerがキーの設定時に弾いてくれそうですが、親切設計ではないようですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504005/a87d8ee9-da90-205a-e380-57e6a217412f.png)


※version-stageとversion-idについては公式ドキュメントをご覧ください。

https://docs.aws.amazon.com/secretsmanager/latest/userguide/cfn-example_reference-secret.html

スクリプトの完成形がコチラ↓

```fsxn.yml
# ------------------------------------------------------------#
# FSxN
# ------------------------------------------------------------#
  MyFSxN01:
    Type: "AWS::FSx::FileSystem"
    Properties: 
      FileSystemType: "ONTAP"
      OntapConfiguration: 
        DeploymentType: "SINGLE_AZ_1"
        FsxAdminPassword: '{{resolve:secretsmanager:arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:iwai/handson-xxxxx:SecretString:password}}'
        ThroughputCapacity: 128        
      SecurityGroupIds: [!Ref FsxnSecurityGroup]
      StorageCapacity: 1024
      StorageType: "SSD"
      SubnetIds: 
        - !Sub "${PrivateSubnetID}"
```
:v:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504005/e3565b21-10e8-e4ee-aeba-340e9620743a.png)

# まとめ
毎度ながら一旦自分がスタックしたポイントも含めて、今回はCloudFormationからSecret Managerのシークレット値の取得方法をご紹介しました。正直同じような内容の記事は何個か見られましたが、自分がスタックした場所とは違う部分で皆さん躓いていたのでこの記事を書きました。正直言うと、一番スタックしたのは```{{resolve:secretsmanager:secret-id:SecretString:json-key:version-stage:version-id}}```を指定するときsecret-idを```arn:aws:```から始めなくて20分ぐらい画面とにらめっこしました。恥ずかしすぎて書くまでもないですね。