aws-samples / aws-iot-fleet-provisioning

Edge Reference Client to demonstrate emerging functionality to manage fleet provisioning for IoTCore.
MIT No Attribution
43 stars 29 forks source link

Device Fleet Provisioning with AWS IoTCore

Updates:

provisioner.get_official_certs(callback, isRotation=True)

It can often be difficult to manage the secure provisioning of myriad IoT devices in the field. This process can often involve invasive workflow measures, qualified personnel, secure handling of sensitive information, and management of dispensed credentials. Through IoT Core, AWS Fleet Provisioning provides a service oriented, api approach to managing credentials. To learn more about these rich capabilities, read here: https://docs.aws.amazon.com/iot/latest/developerguide/iot-provision.html

To aid in the adoption and utilization of the functionality mentioned above, this repo provides a reference client to illustrate how device(s) might interact with the provided services to deliver the desired experience. Specifically, the client demonstrates how a common "bootstrap" certificate (placed on n devices) can, upon a first-run experience:

  1. Connect to IoTCore with stringent bootstrap credentials
  2. Obtain a unique private key and "production" certificate
  3. Present proof of ownership of the production credentials
  4. Prompt the execution of a provisioning template (custom provisioning logic)
  5. Rotate the certificates (decommission bootstrap, promote new cert)
  6. Test the rights of the newly acquired certificate.

Dependencies of the solution

In order to run the client solution seamlessly you must configure dependencies in 2 dimensions: AWS Console / Edge Device

On the AWS Console:

Create a common bootstrap certificate.

  1. Go to the IoT Core Service, and in the menu on the left select Secure and finally, Certificates.
  2. Select Create to create your common bootstrap certificates.
  3. Choose One Click Certificate Creation (This will create your bootstrap cert to be placed on all devices)
  4. Download and store certificates.
  5. ! Don't forget to download a root.ca.pem and select the button to ACTIVATE your certificate on the same screen.

Create Provisioning Template / Attach Policies

  1. In console, select Onboard and then Fleet Provisioning Templates and finally, Create.
  2. Name your provisioning template (e.g. - birthing_template). Remember this name!
  3. Create or associate a basic IoT Role with this template. (at least - AWSIoTThingsRegistration)
  4. Select "Use the AWS IoT registry ..." to ensure the sample code works appropriately as it creates things here.
  5. Select Next
  6. Create or select the policy that you wish fully provisioned devices to have. (see sample open policy below)
  7. Select Next
  8. Enter a Thing name prefix (e.g. MyDevices_) and optionally type, groups or attributes for fully provisioned devices.
  9. Select Create Template
  10. Select the bootstrap certificate you created above and click the Attach Policy button.
  11. Ignore the section on Create IAM role to Provision devices, and select Enable template.
  12. Now select close to return to the console.

On the Edge device

Basic python hygiene

  1. Clone the aws-iot-fleet-provisioning repo to your edge device.
  2. Consider running the solution in a python virtual environment.
  3. Install python dependencies: pip3 install -r requirements.txt (requirements.txt located in solution root)

Solution setup

  1. Take your downloaded bootstrap credentials (including root.ca.pem) and securely store them on your device.
  2. Find config.ini within the solution and configure the below parameters:
    SECURE_CERT_PATH = PATH/TO/YOUR/CERTS
    ROOT_CERT = root.ca.pem
    CLAIM_CERT = xxxxxxxxxx-certificate.pem.crt
    SECURE_KEY = xxxxxxxxxx-private.pem.key
    IOT_ENDPOINT = xxxxxxxxxx-ats.iot.us-east-1.amazonaws.com
    PRODUCTION_TEMPLATE = my_template (e.g. - birthing_template)
    CERT_ROTATION_TEMPLATE = my_certrotation_template

Run solution (may need to use sudo if storing certificates in a protected dir)

  1. python3 main.py

##### CONNECTING WITH PROVISIONING CLAIM CERT #####
##### SUCCESS. SAVING KEYS TO DEVICE! #####
##### CREATING THING ACTIVATING CERT #####
##### CERT ACTIVATED AND THING birth_1234567-abcde-fghij-klmno-1234567abc-TLS350 CREATED #####
##### CONNECTING WITH OFFICIAL CERT #####
##### ACTIVATED AND TESTED CREDENTIALS (xxxxxxxxxx-private.pem.key, xxxxxxxxxx-certificate.pem.crt). #####
##### FILES SAVED TO PATH/TO/YOUR/CERTS #####

If the solution runs without error, you should notice the new certificates saved in the same directory as the bootstrap certs. You will also notice the creation of THINGS in the IoT Registry that are activated. As this solution is only meant to demo the solution, each subsequent run will use the original bootstrap cert to request new credentials, and therefore also create another thing. Thing names are created based on a hardcoded GUID-Like string (name however you'd like), alternatively, a randomly generated serial number is also shown (commented out) in the code.

See below for examples of necessary artifacts as part of this solution:

Sample "birth_policy" applied to a bootstrap certificate with permissions limited only to provisioning api's.

Note: If using the fleet provisioning feature in the console, this policy will be applied to the certificate automatically. Also, if you intend to copy/paste the below policy note the arn's and change the region/account number as appropriate.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect"
      ],
      "Resource": "<PUT DESIRED RESOURCE ARN(s) HERE.'*' may be used to showcase features but not recommended for real implementations>"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish",
        "iot:Receive"
      ],
      "Resource": [
        "arn:aws:iot:us-east-1:XXXXXXXXXXXX:topic/$aws/certificates/create/*",
        "arn:aws:iot:us-east-1:XXXXXXXXXXXX:topic/$aws/provisioning-templates/birthing_template/provision/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Subscribe",
      "Resource": [
        "arn:aws:iot:us-east-1:XXXXXXXXXXXX:topicfilter/$aws/certificates/create/*",
        "arn:aws:iot:us-east-1:XXXXXXXXXXXX:topicfilter/$aws/provisioning-templates/birthing_template/provision/*"
      ]
    }
  ]
}

Sample Policy for fully provisioned devices - aptly named 'full_citizen_role'

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish",
        "iot:Subscribe",
        "iot:Connect",
        "iot:Receive"
      ],
      "Resource": [
        "<PUT DESIRED RESOURCE ARN(s) HERE.'*' may be used to showcase features but not recommended for real implementations>"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:GetThingShadow",
        "iot:UpdateThingShadow",
        "iot:DeleteThingShadow"
      ],
      "Resource": [
        "<PUT DESIRED RESOURCE ARN(s) HERE.'*' may be used to showcase features but not recommended for real implementations>"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "greengrass:*"
      ],
      "Resource": [
        "<PUT DESIRED RESOURCE ARN(s) HERE.'*' may be used to showcase features but not recommended for real implementations>"
      ]
    }
  ]
}

Sample provisioning hook where you validate the request before activating a certificate

import json
from datetime import date

provision_response = {
    'allowProvisioning': False,
    "parameterOverrides": {"CertDate": date.today().strftime("%m/%d/%y")}
}

def handler(event, context):

    ########################
    ## Stringent validation against internal API's/DB etc to validate the request before proceeding
    ##
    ## if event['parameters']['SerialNumber'] = "approved by company CSO":
    ##     provision_response["allowProvisioning"] = True
    #####################

    return provision_response

Sample provisioning template JSON

{
  "Parameters": {
    "CertDate": {
      "Type": "String"
    },
    "deviceId": {
      "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": "fleetprov_prod_template"
      },
      "Type": "AWS::IoT::Policy"
    },
    "thing": {
      "OverrideSettings": {
        "AttributePayload": "MERGE",
        "ThingGroups": "DO_NOTHING",
        "ThingTypeName": "REPLACE"
      },
      "Properties": {
        "AttributePayload": {
          "cert_issuance": {
            "Ref": "CertDate"
          }
        },
        "ThingGroups": [],
        "ThingName": {
          "Ref": "deviceId"
        }
      },
      "Type": "AWS::IoT::Thing"
    }
  },
  "DeviceConfiguration": {
  }
}

Sample Cert Rotation Provisioning Template. Used to activate a new AWS IoT Certificate, and update the cert_issuance attribute in the registry.

{
  "Parameters": {
    "SerialNumber": {
      "Type": "String"
    },
    "CertDate": {
      "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": "fleetprov_prod_template"
      },
      "Type": "AWS::IoT::Policy"
    },
    "thing": {
      "OverrideSettings": {
        "AttributePayload": "REPLACE",
        "ThingGroups": "REPLACE",
        "ThingTypeName": "REPLACE"
      },
      "Properties": {
        "AttributePayload": {
          "cert_issuance": {
            "Ref": "CertDate"
          }
        },
        "ThingGroups": [],
        "ThingName": {
          "Ref": "SerialNumber"
        }
      },
      "Type": "AWS::IoT::Thing"
    }
  }
}

Sample AWS Lambda function used as a provisioning hook for cert rotation requests.

import json
import boto3
from datetime import date, timedelta

client = boto3.client('iot')
endpoint = boto3.client('iot-data')

#used to validate device actually needs a new cert
CERT_ROTATION_DAYS = 360

#validation check date for registry query
target_date = date.today()-timedelta(days=CERT_ROTATION_DAYS)
target_date = target_date.strftime("%Y%m%d")

#Set up payload with new cert issuance date
provision_response = {'allowProvisioning': False, "parameterOverrides": {
    "CertDate": date.today().strftime("%Y%m%d")}}

def handler(event, context):

    # Future log Cloudwatch logs
    print("Received event: " + json.dumps(event, indent=2))

    thing_name = event['parameters']['SerialNumber']
    response = client.describe_thing(
    thingName=thing_name)

    try:
      #Cross reference ID of requester with entry in registry to ensure device needs a rotation.
      if int(response['attributes']['cert_issuance']) < int(target_date):
        provision_response["allowProvisioning"] = True
    except:
      provision_response["allowProvisioning"] = False

    return provision_response

Sample Lambda used by Cloudwatch as a monitoring agent to notify devices when they're due for a cert rotation

import json
import boto3
from datetime import date, timedelta

client = boto3.client('iot')
endpoint = boto3.client('iot-data')

#Set Cert Rotation Interval
CERT_ROTATION_DAYS = 360

#Check for certificate expiry due in next 2 weeks.
target_date = date.today()-timedelta(days=CERT_ROTATION_DAYS)

#Convert to numeric format
target_date = target_date.strftime("%Y%m%d")

def lambda_handler(event, context):

  response = client.search_index(
    queryString='attributes.cert_issuance<{}'.format(target_date),
    maxResults=100)

  for thing in response['things']:
    endpoint.publish(
      topic='cmd/{}'.format(thing['thingName']),
      payload='{"msg":"rotate_cert"}'
      )

  return {
    'things': response['things']
  }

License

This library is licensed under the MIT-0 License. See the LICENSE file.