Updates:
- Now with the ability to respond to cert rotation requests. When the device has been informed it needs to rotate certificates, simply set an additional (optional) attribute isRotation = True. This update is used in conjunction with a cert_rotation policy specified below. This solution relies on setting a cert_issuance date in the registry when the certificate is registered. This is handled by the provisioning template. Once the device is notified, it can process the rotation through setting the flag below.
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:
- Connect to IoTCore with stringent bootstrap credentials
- Obtain a unique private key and "production" certificate
- Present proof of ownership of the production credentials
- Prompt the execution of a provisioning template (custom provisioning logic)
- Rotate the certificates (decommission bootstrap, promote new cert)
- Test the rights of the newly acquired certificate.
-
Intended to be compatible with AWS Greengrass ... this solution depends on a python library (asyncio) which is only available w/ python 3.7 and above. Please ensure your solution has at least this version.
-
A .NET Core port of the reference client application is available within the dotnet-core folder - this does not currently support certificate rotation feature available on the Python version.
-
With any connection to IoT Core, you will require the addition of a root CA. We have included a root ca in the repo for convenience but we can't guarantee it will remain current. You can download/replace the contents from the latest contents here: https://www.amazontrust.com/repository/AmazonRootCA1.pem
-
It is recommended to use the general sample provisioning template below if you want the provisioning template to create a thing in IoT Core, Activate the cert, etc. Specifically, ensure the THING node attributes are included in YOUR template if you don't use it verbatim.
In order to run the client solution seamlessly you must configure dependencies in 2 dimensions: AWS Console / Edge Device
- Go to the IoT Core Service, and in the menu on the left select Secure and finally, Certificates.
- Select Create to create your common bootstrap certificates.
- Choose One Click Certificate Creation (This will create your bootstrap cert to be placed on all devices)
- Download and store certificates.
- ! Don't forget to download a root.ca.pem and select the button to ACTIVATE your certificate on the same screen.
- In console, select Onboard and then Fleet Provisioning Templates and finally, Create.
- Name your provisioning template (e.g. - birthing_template). Remember this name!
- Create or associate a basic IoT Role with this template. (at least - AWSIoTThingsRegistration)
- Select "Use the AWS IoT registry ..." to ensure the sample code works appropriately as it creates things here.
- Select Next
- Create or select the policy that you wish fully provisioned devices to have. (see sample open policy below)
- Select Next
- Enter a Thing name prefix (e.g. MyDevices_) and optionally type, groups or attributes for fully provisioned devices.
- Select Create Template
- Select the bootstrap certificate you created above and click the Attach Policy button.
- Ignore the section on Create IAM role to Provision devices, and select Enable template.
- Now select close to return to the console.
- Clone the aws-iot-fleet-provisioning repo to your edge device.
- Consider running the solution in a python virtual environment.
- Install python dependencies:
pip3 install -r requirements.txt
(requirements.txt located in solution root)
- Take your downloaded bootstrap credentials (including root.ca.pem) and securely store them on your device.
- 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
-
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.
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": "*"
},
{
"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/*"
]
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Publish",
"iot:Subscribe",
"iot:Connect",
"iot:Receive"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": [
"iot:GetThingShadow",
"iot:UpdateThingShadow",
"iot:DeleteThingShadow"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": [
"greengrass:*"
],
"Resource": [
"*"
]
}
]
}
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
{
"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"
}
}
}
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']
}
This library is licensed under the MIT-0 License. See the LICENSE file.