amazon-web-servicesgoboto3aws-iotaws-iot-core

AWS GoLang SDK Version 2 and iot core Fleet Provisioning


I am trying to use the AWS GoLang SDK version 2 (https://github.com/aws/aws-sdk-go-v2) to implement Fleet Provisioning for a Linux device.

I have written the following code that successfully creates the generic claim credentials. Now I want to implement the Fleet Provisioning flow that happens on the device to exchange the generic claims for the unique credentials for each device. That flow is shown in the AWS Python SDK sample here --> https://github.com/aws/aws-iot-device-sdk-python-v2/blob/main/samples/fleetprovisioning.py

The python example does the work using MQTT. It looks like it connects to the IOT Core endpoint with the generic claims and then calls 'CreateKeysAndCertificate' and 'RegisterThing'. So far I do not see how I can accomplish the same thing with the GoLang SDK. There is a RegisterThing function, but I'm not sure how to 'connect' with my generic claims in GoLang in order to then call 'CreateKeysAndCertificate'. How do I accomplish this with the GoLang SDK?

    log.Println(color.InBlue("Creating AWS fleet provisioning generic claims ..."))

    // Load the Shared AWS Configuration (~/.aws/config)
    log.Println(color.InBlue("Loading AWS configuration file now ..."))
    cfg, cfgErr := config.LoadDefaultConfig(context.TODO(), config.WithSharedCredentialsFiles(
        []string{"config"},
    ))
    if cfgErr != nil {
        log.Fatal(color.InRed("Error: Failed to get configuration: "), cfgErr.Error())
    } else {
        log.Println(color.InGreen("Successfully read configuration ..."))
    }

    // Create an Amazon S3 service client
    client := iot.NewFromConfig(cfg)
    log.Println(color.InGreen("Successfully created AWS iot client ..."))

    // Create the thing group $THING_GROUP_NAME. Device created by fleet provisioning will be added to this group.
    // e.g. --> aws iot create-thing-group --thing-group-name $THING_GROUP_NAME
    createThingGroupParams := iot.CreateThingGroupInput{
        ThingGroupName:       aws.String(THING_GROUP_NAME),
        ParentGroupName:      nil,
        Tags:                 nil,
        ThingGroupProperties: nil,
    }

    createThingGroupOutput, createThingGroupErr := client.CreateThingGroup(context.TODO(), &createThingGroupParams)
    if createThingGroupErr != nil {
        log.Fatal(color.InRed("Error: Failed to create ThingGroup: "), createThingGroupErr.Error())
    } else {
        log.Println(color.InGreen("Create ThingGroup returned: "), createThingGroupOutput)
    }

    // Create the provisioning template
    jsonProvisioningTemplateString, jsonProvisioningTemplateErr := readJsonFileIntoString(FLEET_PROVISIONING_TEMPLATE)
    if jsonProvisioningTemplateErr != nil {
        log.Fatal(color.InRed("Error: Failed to get FleetProvisioningTemplate: "), jsonProvisioningTemplateErr.Error())
    } else {
        log.Println(color.InGreen("Successfully read FleetProvisioningTemplate: "), jsonProvisioningTemplateString)
    }
    createProvisioningTemplateParams := iot.CreateProvisioningTemplateInput{
        ProvisioningRoleArn: aws.String(ARN_IOT_PROVISIONING_ROLE),
        TemplateBody:        aws.String(jsonProvisioningTemplateString),
        TemplateName:        aws.String(FLEET_PROVISIONING_TEMPLATE_NAME),
        Description:         nil,
        Enabled:             true,
        PreProvisioningHook: nil,
        Tags:                nil,
        Type:                "",
    }

    createProvisioningTemplateOutput, createProvisioningTemplateErr := client.CreateProvisioningTemplate(context.TODO(), &createProvisioningTemplateParams)
    if createProvisioningTemplateErr != nil {
        log.Fatal(color.InRed("Error: Failed to create ProvisioningTemplate: "), createProvisioningTemplateErr.Error())
    } else {
        log.Println(color.InGreen("Create ProvisioningTemplate returned: "), createProvisioningTemplateOutput)
    }

    // Read back the template for proof
    describeProvisioningTemplateParams := iot.DescribeProvisioningTemplateInput{TemplateName: aws.String(FLEET_PROVISIONING_TEMPLATE_NAME)}
    describeProvisioningTemplateOutput, describeProvisioningTemplateErr := client.DescribeProvisioningTemplate(context.TODO(), &describeProvisioningTemplateParams)
    if describeProvisioningTemplateErr != nil {
        log.Fatal(color.InRed("Error: Failed to get Description of ProvisioningTemplate: "), describeProvisioningTemplateErr.Error())
    } else {
        log.Println(color.InGreen("Description of ProvisioningTemplate returned: "), describeProvisioningTemplateOutput)
    }

    // Create the claim certificate and key
    createKeysAndCertificateParams := iot.CreateKeysAndCertificateInput{SetAsActive: true}
    createKeysAndCertsOutput, createKeysAndCertsErr := client.CreateKeysAndCertificate(context.TODO(), &createKeysAndCertificateParams)
    if createKeysAndCertsErr != nil {
        log.Fatal(color.InRed("Error: Failed to Create KeysAndCertificate: "), createKeysAndCertsErr.Error())
    } else {
        log.Println(color.InGreen("Successfully Created KeysAndCertificate: "), createKeysAndCertsOutput)
    }

    // Get the certificate arn from the result of the previous command. The certificate arn is required to attach an IoT policy to it
    jsonPolicyString, jsonPolicyErr := readJsonFileIntoString(FLEET_PROVISIONING_POLICY)
    if jsonPolicyErr != nil {
        log.Fatal(color.InRed("Error: Failed to get Policy: "), jsonPolicyErr.Error())
    } else {
        log.Println(color.InGreen("Successfully read Policy: "), jsonPolicyString)
    }
    createPolicyParams := iot.CreatePolicyInput{
        PolicyDocument: aws.String(jsonPolicyString),
        PolicyName:     aws.String(FLEET_PROVISIONING_POLICY_NAME),
        Tags:           nil,
    }

    // Create the policy
    createPolicyOutput, createPolicyErr := client.CreatePolicy(context.TODO(), &createPolicyParams)
    if createPolicyErr != nil {
        log.Fatal(color.InRed("Error: Failed to Create Policy: "), createPolicyErr.Error())
    } else {
        log.Println(color.InGreen("Successfully Created Policy: "), createPolicyOutput)
    }

    // Attach the policy
    attachPolicyParams := iot.AttachPolicyInput{
        PolicyName: aws.String(FLEET_PROVISIONING_POLICY_NAME),
        Target:     createKeysAndCertsOutput.CertificateArn,
    }

    attachPolicyOutput, attachPolicyErr := client.AttachPolicy(context.TODO(), &attachPolicyParams)
    if attachPolicyErr != nil {
        log.Fatal(color.InRed("Error: Failed to Attach Policy: "), attachPolicyErr.Error())
    } else {
        log.Println(color.InGreen("Successfully Attach Policy: "), attachPolicyOutput)
    }

    // If all is well, saved the claims ( generic ) certificate and public/private keys.
    privKeyErr := writeJsonStringIntoFile(FLEET_PROVISIONING_GENERIC_CLAIM_PRIVATE, *createKeysAndCertsOutput.KeyPair.PrivateKey)
    if privKeyErr != nil {
        log.Fatal(color.InRed("Error: Failed to save Private Key: "), privKeyErr.Error())
    }

    pubKeyErr := writeJsonStringIntoFile(FLEET_PROVISIONING_GENERIC_CLAIM_PUBLIC, *createKeysAndCertsOutput.KeyPair.PublicKey)
    if pubKeyErr != nil {
        log.Fatal(color.InRed("Error: Failed to save Public Key: "), pubKeyErr.Error())
    }

    cerErr := writeJsonStringIntoFile(FLEET_PROVISIONING_GENERIC_CLAIM_CERT, *createKeysAndCertsOutput.CertificatePem)
    if cerErr != nil {
        log.Fatal(color.InRed("Error: Failed to save Certificate Key: "), cerErr.Error())
    }

    log.Println(color.InGreen("Success!"))

Solution

  • The python script you mentioned is making use of the fleet provisioning process. the python script connects using the claim credentials, then publishing to specific topic in order to receive final certificates.

    I'd suggest to look at this blog post: https://aws.amazon.com/blogs/iot/how-to-automate-onboarding-of-iot-devices-to-aws-iot-core-at-scale-with-fleet-provisioning/ It explains the flow.

    Fleet provisioning works with a template you defined. Then you have to make mqtt calls in order to register your device. Fleet provisioning will take care of creating the thing in AWS IoT as well as creating certificate and attach policy you defined.

    Also, please have a look at the detailed documentation. It describes the different MQTT message you have to send in order to work with AWS IoT Fleet Provisioning. https://docs.aws.amazon.com/iot/latest/developerguide/fleet-provision-api.html