Z Wang
Z Wang

Reputation: 156

How to add AWS IoT provisioning template in Cloudformation template / CDK

I am using Cloudformation template to create a stack including IoT fleet provisioning template and according to the document the IoT provisioning template body should be string type.

I have the IoT fleet provisioning template like this:

{
  "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": "mypolicy"
      },
      "Type": "AWS::IoT::Policy"
    },
    "thing": {
      "OverrideSettings": {
        "AttributePayload": "MERGE",
        "ThingGroups": "REPLACE",
        "ThingTypeName": "REPLACE"
      },
      "Properties": {
        "AttributePayload": {       
          "SerialNumber": {
            "Ref": "SerialNumber"
          }          
        },             
        "ThingName": {
          "Ref": "SerialNumber"
        }
      },
      "Type": "AWS::IoT::Thing"
    }
  }
}

The Cloudformation template is like this:

AWSTemplateFormatVersion: '2010-09-09'
Description: "Template to create iot"

Resources: 
  FleetProvisioningTemplate:
    Type: AWS::IoT::ProvisioningTemplate
    Properties: 
      Description: Fleet provisioning template
      Enabled: true      
      ProvisioningRoleArn: "arn:aws:iam::1234567890:role/IoT-role"      
      TemplateBody: String
      TemplateName: mytemplate

I tried to use the JSON string of the IoT provisioning template for the template body but it didn't work. My question is how I can create an IoT provisioning template using Cloudformation template?

update It turned out I can add the IoT provisioning template as a 'literal block'

AWSTemplateFormatVersion: '2010-09-09'
Description: "Template to create iot"

Resources: 
  FleetProvisioningTemplate:
    Type: AWS::IoT::ProvisioningTemplate
    Properties: 
      Description: Fleet provisioning template
      Enabled: true      
      ProvisioningRoleArn: "arn:aws:iam::1234567890:role/IoT-role"      
      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": "cto-full-function-dev"
              },
              "Type": "AWS::IoT::Policy"
            },
            "thing": {
              "OverrideSettings": {
                "AttributePayload": "MERGE",
                "ThingGroups": "DO_NOTHING",
                "ThingTypeName": "REPLACE"
              },
              "Properties": {
                "AttributePayload": {},
                "ThingGroups": [],
                "ThingName": {
                  "Ref": "SerialNumber"                  
                },
                "ThingTypeName": "cto"
              },
              "Type": "AWS::IoT::Thing"
            }
          }
        }

      TemplateName: mytemplate

But as soon as I added the PreProvisioningHook as the cloudformation document says, the template fails with invalid request error.

AWSTemplateFormatVersion: '2010-09-09'
Description: "Template to create iot"

Resources: 
  LambdaHook:
    Type: AWS::Lambda::Function
    ....
  FleetProvisioningTemplate:
    Type: AWS::IoT::ProvisioningTemplate
    Properties: 
      Description: Fleet provisioning template
      Enabled: true      
      ProvisioningRoleArn: "arn:aws:iam::1234567890:role/IoT-role"  
      PreProvisioningHook:               
        TargetArn: {
          "Fn::GetAtt": [
            "LambdaHook",
            "Arn"
          ]
        }
        PayloadVersion: "1.0"    
      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": "cto-full-function-dev"
              },
              "Type": "AWS::IoT::Policy"
            },
            "thing": {
              "OverrideSettings": {
                "AttributePayload": "MERGE",
                "ThingGroups": "DO_NOTHING",
                "ThingTypeName": "REPLACE"
              },
              "Properties": {
                "AttributePayload": {},
                "ThingGroups": [],
                "ThingName": {
                  "Ref": "SerialNumber"                  
                },
                "ThingTypeName": "cto"
              },
              "Type": "AWS::IoT::Thing"
            }
          }
        }

      TemplateName: mytemplate

I also asked question on here but no luck. Did any one have the same issue and fix it?

Upvotes: 5

Views: 1778

Answers (3)

wzr1337
wzr1337

Reputation: 3767

in CDK you can opt to use a shorthand too:

preProvisioningHookLambda.grantInvoke(new iam.ServicePrincipal('iot.amazonaws.com')) // allow iot to invoke this function

This is the TS code I am using for everyones reference:

import * as cdk from '@aws-cdk/core';
import * as iam from '@aws-cdk/aws-iam';
import * as lambdaNodeJS from '@aws-cdk/aws-lambda-nodejs';
import * as iot from "@aws-cdk/aws-iot";

const props = {
  stage: 'development'
}


const PolicyName = "DevicePolicy";
const templateName = 'DeviceProvisioningTemplateV1';
const templateBody = {
  Parameters: {
    SerialNumber: {
      Type: "String"
    },
    ModelType: {
      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
      },
      Type: "AWS::IoT::Policy"
    },
    thing: {
      OverrideSettings: {
        AttributePayload: "MERGE",
        ThingGroups: "DO_NOTHING",
        ThingTypeName: "REPLACE"
      },
      Properties: {
        ThingGroups: [],
        ThingName: {
          Ref: "SerialNumber"
        }
      },
      Type: "AWS::IoT::Thing"
    }
  }
};
const preProvisioningHookLambda = new lambdaNodeJS.NodejsFunction(this, `provisioning-hook-lambda-${props?.stage}`, {
  entry: './src/lambda/provisioning/hook.ts',
  handler: 'handler',
  bundling: {
    externalModules: [
    ]
  },
  timeout: cdk.Duration.seconds(5)
});
preProvisioningHookLambda.grantInvoke(new iam.ServicePrincipal('iot.amazonaws.com')) // allow iot to invoke this function

// Give the AWS IoT service permission to create or update IoT resources such as things and certificates in your account when provisioning devices
const provisioningRole = new iam.Role(this, `provisioning-role-arn-${props?.stage}`, {
  assumedBy: new iam.ServicePrincipal('iot.amazonaws.com'),
});
provisioningRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSIoTThingsRegistration'));
new cdk.CfnOutput(this, 'provisioningRoleArn ', { value: provisioningRole.roleArn || 'undefined' });

const provisioningTemplate = new iot.CfnProvisioningTemplate(this, `provisioning-hook-template-${props?.stage}`, {
  provisioningRoleArn: provisioningRole.roleArn,
  templateBody: JSON.stringify(templateBody),
  enabled: true,
  templateName,
  preProvisioningHook: {
    payloadVersion: '2020-04-01',
    targetArn: preProvisioningHookLambda.functionArn,
  }
});

new cdk.CfnOutput(this, 'preProvisioningLambdaFunctionName ', { value: preProvisioningHookLambda.functionName || 'undefined' });
new cdk.CfnOutput(this, 'provisioningTemplateName ', { value: provisioningTemplate.templateName || 'undefined' });

Upvotes: 3

Arian Acosta
Arian Acosta

Reputation: 6817

Base on the answer by Z Wang, this is how you do it in the AWS CDK:

myLambda.addPermission('InvokePermission', {
  principal: new ServicePrincipal('iot.amazonaws.com'),
  action: 'lambda:InvokeFunction',
});

Upvotes: 2

Z Wang
Z Wang

Reputation: 156

I finally figured it out but want to share it in case someone is having the same question.

AWS IoT document doesn't mention this but if you want to add a PreProvisioningHook for your provisioning template, you need to give IoT access to the lambda, AKA PreProvisioningHook, so in the Cloudformation template, add something like this:

LambdaAddPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt PreProvisionHook.Arn 
      Principal: iot.amazonaws.com 

In the Provisioning Template resource, make sure you have this:

PreProvisioningHook:               
        PayloadVersion: '2020-04-01'
        TargetArn: {
          "Fn::GetAtt": [
            "PreProvisionHook",
            "Arn"
          ]
        }

Upvotes: 8

Related Questions