LoginSignup
2
1

More than 3 years have passed since last update.

[AWS CloudFormation][AWS SAM] FleetProvisioningテンプレート定義

Last updated at Posted at 2020-11-01

構文

以下が公式ドキュメントに記載されている構文

Resources:
  ProvisioningTemplate: # 任意の名前
    Type: AWS::IoT::ProvisioningTemplate
    Properties: 
      Description: String # 説明
      Enabled: Boolean # フリートプロビジョニングテンプレート有効/無効
      PreProvisioningHook: # プロビジョニング時にフックするLambda
        ProvisioningHook
      ProvisioningRoleArn: String # [必須] デバイスをプロビジョニングする権限を持ったロールARN
      Tags: # フリートプロビジョニングテンプレートの管理に使用できるメタデータ
        Tags
      TemplateBody: String # [必須] フリートプロビジョニングテンプレートのJSON形式のコンテンツ(JSON)
      TemplateName: String # テンプレート名

以下、実際に値を入れたもの

Resources:
  ProvisioningTemplate:
    Type: AWS::IoT::ProvisioningTemplate
    Properties:
      Enabled: true
      ProvisioningRoleArn: !GetAtt ProvisioningRole.Arn
      TemplateName: !Sub '${AWS::StackName}_Template'
      TemplateBody: |
        {
          "Parameters": {
            "SerialNumber": {
              "Type": "String"
            },
            "AWS::IoT::Certificate::Id": {
              "Type": "String"
            }
          },
          "Resources": {
            "certificate": {
              "Properties": {
                "CertificateId": {
                  "Ref": "AWS::IoT::Certificate::Id"
                },
                "Status": "Active"
              },
              "Type": "AWS::IoT::Certificate"
            },
            "policy": {
              "Properties": {
                "PolicyName": "xxxxxxxxxx5D8828xxxxxxxxxx52CExxxxxxxxxx"
              },
              "Type": "AWS::IoT::Policy"
            },
            "thing": {
              "OverrideSettings": {
                "AttributePayload": "MERGE",
                "ThingGroups": "DO_NOTHING",
                "ThingTypeName": "REPLACE"
              },
              "Properties": {
                "AttributePayload": {},
                "ThingGroups": [],
                "ThingName": {
                  "Fn::Join": [
                    "",
                    [
                      "fleet-provisioning-test-",
                      {
                        "Ref": "SerialNumber"
                      }
                    ]
                  ]
                },
                "ThingTypeName": "fleet-provisioning-test"
              },
              "Type": "AWS::IoT::Thing"
            }
          }
        }

TemplateBodyのJSONはマネージメントコンソールでフリートプロビジョニングテンプレートを作成し、生成されたテンプレートのJSONをコピペしたもの

プロビジョニングロールの定義

ProvisioningRoleArnで参照しているロールは以下のように定義

  ProvisioningRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - iot.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration

ManagedPolicyArnsでAWS管理ポリシーのARNを指定
マネージメントコンソールでフリートプロビジョニングテンプレートを作成した場合に、自動で設定されるAWS管理ポリシーのARNを設定

Principalで権限を与える対象としてiotを指定

プロビジョニング前にフックするLambdaの設定

公式ドキュメントより以下のような構文になる

Resources:
  ProvisioningTemplate:
    Type: AWS::IoT::ProvisioningTemplate
    Properties: 
      PreProvisioningHook: # プロビジョニング時にフックするLambda
        TargetArn: String # フックするLambdaのARN
      ProvisioningRoleArn: String
      TemplateBody: String

以下、実際に値を入れたもの

Resources:
  ProvisioningTemplate:
    Type: AWS::IoT::ProvisioningTemplate
    Properties:
      Enabled: true
      PreProvisioningHook: # 追加
        TargetArn: !GetAtt FleetProvisioningHook.Arn
      ProvisioningRoleArn: !GetAtt ProvisioningRole.Arn
      TemplateName: !Sub '${AWS::StackName}_Template'
      TemplateBody: |
        ...

  FleetProvisioningHook: # Lambda関数定義
    Type: AWS::Serverless::Function
      ...

上記に加えて、定義したLambda関数を実行できる権限をIoTに与える必要があるため以下を追加

  LambdaAddPermission:
      Type: AWS::Lambda::Permission
      Properties:
        Action: lambda:InvokeFunction
        FunctionName: !GetAtt FleetProvisioningHook.Arn # LambdaのARN
        Principal: iot.amazonaws.com # iotからの実行を許可

プロビジョニングテンプレート(JSON)の内容変更

クレーム証明書の設定

(2020/11/05 追記)
下記"SerialNumber""AWS::IoT::Certificate::Id"はデバイスから受信する値のため"Default"は設定不要
(2020/11/05 追記終了)

以下のようにJSON内"AWS::IoT::Certificate::Id""Default"`に証明書IDを設定

          "Parameters": {
            "SerialNumber": {
              "Type": "String"
            },
            "AWS::IoT::Certificate::Id": {
              "Type": "String",
              "Default": "xxxxxxxxxx8d192xxxxxxfb22xxxxxxxxx5b8f6d148cdaafb5506xxxxxxxxxx"
            }
          },

プロビジョニングしたデバイスに付与するポリシーの設定

公式ドキュメント(CloudFormationのポリシー定義方法について)

1.既にAWSに作成済ポリシーを使用
以下のようにポリシーのARNを指定

            "policy": {
              "Properties": {
                "PolicyName": "xxxxxxxxxx5D8828xxxxxxxxxx52CExxxxxxxxxx"
              },
              "Type": "AWS::IoT::Policy"
            },

2.新規でポリシーを作成
以下のようにPolicyDocumentでポリシーを定義する

            "policy": {
              "Properties": {
                "PolicyDocument": {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Action": "iot:*",
                      "Resource": [
                        "arn:aws:iot:ap-northeast-1:36xxxxxxxxxx:client/*",
                        "arn:aws:iot:ap-northeast-1:36xxxxxxxxxx:topic/*",
                        "arn:aws:iot:ap-northeast-1:36xxxxxxxxxx:topicfilter/*"
                      ]
                    }
                  ]
                }
              },
              "Type": "AWS::IoT::Policy"
            },

リージョン、アカウントIDに変数を使用してみる(失敗)

前述のポリシー定義ではリージョン(ap-northeast-1)、アカウントID(36xxxxxxxxxx)が変わった場合にテンプレートの変更が必要となるため
以下のように設定してみたが、PolicyDocumentの構文異常でデバイス登録時に失敗

            "policy": {
              "Properties": {
                "PolicyDocument": {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Action": "iot:*",
                      "Resource": [
                        {"Fn::Sub" : "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:client/*"},
                        {"Fn::Sub" : "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/*"},
                        {"Fn::Sub" : "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/*"}
                      ]
                    }
                  ]
                }
              },

以下のようにParametersでリージョン、アカウントIDを定義した場合もParametersには文字列以外設定できないためエラー

          "Parameters": {
            "AccountID" : {
              "Type": "String",
              "Default": {"Fn::Sub" : "${AWS::AccountId}"}
            },
            "Region" : {
              "Type": "String",
              "Default": {"Fn::Sub" : "${AWS::Region}"}
            }
          },
          "Resources": {
            "policy": {
              "Properties": {
                "PolicyDocument": {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Action": "iot:*",
                      "Resource": [
                        {"Fn::Sub" : "arn:aws:iot:${Region}:${AccountID}:client/*"},
                        {"Fn::Sub" : "arn:aws:iot:${Region}:${AccountID}:topic/*"},
                        {"Fn::Sub" : "arn:aws:iot:${Region}:${AccountID}:topicfilter/*"}
                      ]
                    }
                  ]
                }
              },
              "Type": "AWS::IoT::Policy"
            }
          }

公式ドキュメントでも「組み込み関数の Fn::Sub が含まれています。この組み込み関数により、検証エラーが発生します。「すべての Default メンバーは文字列でなければなりません」。」と記載があり、この方法は不可

リージョン、アカウントIDに変数を使用してみる(成功)(2020/11/03 追加)

プロビジョニングテンプレートの内容(TemplateBody)をJSON形式文字列として、!Sub関数を使って変数を埋め込むことで対応することができた
これによって、リージョン、アカウントIDが入力不要になった

Resources:
  ProvisioningTemplate:
    Type: AWS::IoT::ProvisioningTemplate
    Properties:
      Enabled: true
      PreProvisioningHook:
        TargetArn: !GetAtt FleetProvisioningHook.Arn
      ProvisioningRoleArn: !GetAtt ProvisioningRole.Arn
      TemplateName: !Sub '${StackName}_ProvisioningTemplate'
      TemplateBody: !Sub |
        {
          "Parameters": {
            "SerialNumber": {
              "Type": "String"
            },
            "AWS::IoT::Certificate::Id": {
              "Type": "String"
            }
          },
          "Resources": {
            "certificate": {
              "Properties": {
                "CertificateId": {
                  "Ref": "AWS::IoT::Certificate::Id"
                },
                "Status": "Active"
              },
              "Type": "AWS::IoT::Certificate"
            },
            "policy": {
              "Properties": {
                "PolicyDocument": {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Action": "iot:*",
                      "Resource": [
                        "arn:aws:${AWS::Region}:${AWS::AccountId}:client/*",
                        "arn:aws:${AWS::Region}:${AWS::AccountId}:topic/*",
                        "arn:aws:${AWS::Region}:${AWS::AccountId}:topicfilter/*"
                      ]
                    }
                  ]
                }
              },
              "Type": "AWS::IoT::Policy"
            },
            ...

クレーム証明書にポリシーをアタッチする(2020/11/05 追記)

クレーム証明書の生成と、IoT Coreへの登録は手動で行うとして、
クレーム証明書に必要なポリシーのアタッチをCloudFormationで行う

手動でポリシーをアタッチした場合

以下のようにプロビジョニングテンプレートのアクションから「アクセス許可を編集」をクリック
1.png

以下のようにポリシーをアタッチする証明書を選択して、「ポリシーをアタッチ」をクリック
2.png

CloudFormationでポリシーをアタッチした場合

以下が、アタッチするポリシーと、ポリシーをアタッチするリソース定義

  CertificateAttachPolicy:
    Type: AWS::IoT::Policy
    Properties:
      PolicyName: !Sub '${StackName}_CertificateAttachPolicy'
      PolicyDocument: !Sub |
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "iot:Connect"
              ],
              "Resource": [
                "*"
              ]
            },
            {
              "Effect": "Allow",
              "Action": [
                "iot:Publish",
                "iot:Receive"
              ],
              "Resource": [
                "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/certificates/create/*",
                "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/provisioning-templates/${StackName}_ProvisioningTemplate/provision/*"
              ]
            },
            {
              "Effect": "Allow",
              "Action": [
                "iot:Subscribe"
              ],
              "Resource": [
                "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/$aws/certificates/create/*",
                "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/$aws/provisioning-templates/${StackName}_ProvisioningTemplate/provision/*"
              ]
            }
          ]
        }

  PolicyAttachment:
    Type: AWS::IoT::PolicyPrincipalAttachment
    Properties: 
      PolicyName: !Ref CertificateAttachPolicy
      Principal: !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:cert/${CertificateId}"

CertificateAttachPolicyがアタッチするポリシーリソース定義、
PolicyAttachmentがAWS IoT ポリシーをプリンシパル (X.509 証明書または別の証明書) にアタッチするためのリソース定義
PolicyAttachment内で使用している${CertificateId}は別途Parametersセクションで定義が必要

CloudFormation(AWS::IoT::PolicyPrincipalAttachment)を使用してアタッチした場合、
対象のスタックを削除した場合、自動的にデタッチされる

ポリシーのみCloudFormationで定義し、手動でポリシーをアタッチした場合、
アタッチされたポリシーがCloudFormationから操作できなくなりスタックの更新、削除でエラーとなるため注意

PolicyDocument${iot:Connection.Thing.ThingName}を使用する(2020/11/05 追記)

公式ドキュメントにあるように、以下のような${iot:Connection.Thing.ThingName}を使用したポリシー定義に変更する


{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action":["iot:Publish"],
        "Resource": ["arn:aws:iot:us-east-1:123456789012:topic/${iot:Connection.Thing.ThingName}"]
      },
      {
        "Effect": "Allow",
        "Action": ["iot:Connect"],
        "Resource": ["arn:aws:iot:us-east-1:123456789012:client/${iot:Connection.Thing.ThingName}"]
      }
    ]
}

前述した以下のプロビジョニングテンプレート定義内の"arn:aws:${AWS::Region}:${AWS::AccountId}:client/*"部分を変更する

      TemplateBody: !Sub |
        {
          "Parameters": {
            "SerialNumber": {
              "Type": "String"
            },
            "AWS::IoT::Certificate::Id": {
              "Type": "String"
            }
          },
          "Resources": {
            "certificate": {
              "Properties": {
                "CertificateId": {
                  "Ref": "AWS::IoT::Certificate::Id"
                },
                "Status": "Active"
              },
              "Type": "AWS::IoT::Certificate"
            },
            "policy": {
              "Properties": {
                "PolicyDocument": {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Action": "iot:*",
                      "Resource": [
                        "arn:aws:${AWS::Region}:${AWS::AccountId}:client/*", <= この"*"を変更する
                        "arn:aws:${AWS::Region}:${AWS::AccountId}:topic/*",
                        "arn:aws:${AWS::Region}:${AWS::AccountId}:topicfilter/*"
                      ]
                    }
                  ]
                }
              },
              "Type": "AWS::IoT::Policy"
            },
            ...

単純に
"arn:aws:${AWS::Region}:${AWS::AccountId}:client/${iot:Connection.Thing.ThingName}"
とした場合は、!Subを使用してるため、iot:Connection.Thing.ThingNameが見つからずエラーとなる

そのため、一旦以下のようにParameters${iot:Connection.Thing.ThingName}を文字列として定義してから、
"arn:aws:iot:${AWS::Region}:${AWS::AccountId}:client/${ConnectionThingName}"とする

Parameters:
  ConnectionThingName:
    Type: String
    Default: '${iot:Connection.Thing.ThingName}'

Resources:
  ProvisioningTemplate:
    Type: AWS::IoT::ProvisioningTemplate
    Properties:
      ...
      TemplateBody: !Sub |
        {
          "Parameters": {
            "SerialNumber": {
              "Type": "String"
            },
            "AWS::IoT::Certificate::Id": {
              "Type": "String"
            }
          },
          "Resources": {
            "certificate": {
              "Properties": {
                "CertificateId": {
                  "Ref": "AWS::IoT::Certificate::Id"
                },
                "Status": "Active"
              },
              "Type": "AWS::IoT::Certificate"
            },
            "policy": {
              "Properties": {
                "PolicyDocument": {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Action": "iot:*",
                      "Resource": [
                        "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:client/${ConnectionThingName}",
                        "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/*",
                        "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/*"
                      ]
                    }
                  ]
                }
              },
              "Type": "AWS::IoT::Policy"
            },

"arn:aws:iot:${AWS::Region}:${AWS::AccountId}:client/${ConnectionThingName}"
は、デプロイ後、以下のように変換される
"arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:client/${ConnectionThingName}"

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1