【小ネタ】AWS CloundFormationのYAMLテンプレート内のJSONで変数を使う

CloudFormationを使って自作のテンプレートを作りました。変数を使う際にYAMLの中にJSONが入っていると書式を注意しなければならないことがありハマってしまったのでまとめました。 調子に乗ってもうちょっとCFnと仲良くなれるようになりたいと思います。
2020.04.10

こんにちはCX事業本部のさかじです。
初めてAWS CloudFormationを使って自作のテンプレートを作りました。変数を使う際にYAMLの中にJSONが入っていると書式を注意しなければならないことがありハマってしまったのでまとめました。

はじめに

よく見る以下のサンプルポリシーを改善して「決められたアカウントIDからのデバイスのみpublishを許可するポリシー」を作成したいと思います。

変更前のポリシー

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish",
        "iot:Subscribe",
        "iot:Connect",
        "iot:Receive"
      ],
      "Resource": "*"
    }
  ]
}

変更後のポリシー

開発者ガイド>パブリッシュ/サブスクライブポリシーの例参考に作成

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect"
      ],
      "Resource": [
        "arn:aws:iot:ap-northeast-1:123456789012:client/testThing"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish"
      ],
      "Resource": [
        "arn:aws:iot:ap-northeast-1:123456789012:topic/testThing/*"
      ]
    }
  ]
}

CFnのテンプレート

AWS::IoT::Policyのドキュメントを確認しながらテンプレートを作ってみます

Syntax(YAML)

Type: AWS::IoT::Policy
Properties: 
  PolicyDocument: Json
  PolicyName: String

作ってみた

AWSTemplateFormatVersion: '2010-09-09'
Description: Create IoT Publish policy.

Resources:
  IoTShadowPolicy:
      Type: AWS::IoT::Policy 
      Properties: 
          PolicyDocument: { "Version": "2012-10-17", 
              "Statement": [ 
                  {
                      "Effect": "Allow",
                      "Action": [
                        "iot:Connect"
                      ],
                      "Resource": [
                          "arn:aws:iot:iot:ap-northeast-1:123456789012:client/*"
                      ]
                  },
                  {
                      "Effect": "Allow",
                      "Action": [
                        "iot:Publish"
                      ],
                      "Resource": [
                          "arn:aws:iot:ap-northeast-1:123456789012:topic/testThing/*"
                      ]
                  }
              ] } 
          PolicyName: "IoTPubulishPolicyTest1"

問題点

このままですと別のAWSアカウントや別のリージョンへCFnのテンプレートを持っていても、適切な環境へ書き換えるためには毎回テンプレートを書き換える必要があります。
そこで変数を使い、リージョンやアカウントIDの変更にも対応できるようにします。

Fn::Subのドキュメントを確認して変数を使えるようにします。

  • 参考 : マッピングなしの Fn::Sub

JSON

{ "Fn::Sub" : "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:client/*" }

YAML

!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:client/*'

ここで疑問が出てきました。IoTのポリシーの書式(YAML)で書いてありますが、PolicyDocumentはJSON objectとされています。JSONで記入する必要がありますので以下のように変数を使えるようなテンプレートが出来上がります。

AWSTemplateFormatVersion: '2010-09-09'
Description: Create IoT Device Shadow policy.

Resources:
  IoTShadowPolicy:
      Type: AWS::IoT::Policy Properties: 
          PolicyDocument: { "Version": "2012-10-17", 
              "Statement": [ 
                  {
                      "Effect": "Allow",
                      "Action": [
                        "iot:Connect"
                      ],
                      "Resource": [
                        "Fn::Sub" : "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:client/*"
                      ]
                  },
                  {
                      "Effect": "Allow",
                      "Action": [
                        "iot:Publish"
                      ],
                      "Resource": [
                        "Fn::Sub" : "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/testThing/*"
                      ]
                  }
              ] } 

          PolicyName: "IoTPubulishPolicyTest2"

参考サイト

最後に

YAMLの中にJSONが入っていてどうやって変数を入れるのか?と悩んでいたところ、単純な結果でしたが解決してよかったです。調子に乗ってもうちょっとCFnと仲良くなれるようになりたいと思います。