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:
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
pip3 install -r requirements.txt
(requirements.txt located in solution root)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.
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/*"
]
}
]
}
{
"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>"
]
}
]
}
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": {
}
}
{
"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
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.