This sample provides an end-to-end demo of using a single-use, short expiry registration code to provision devices by sideloading the registration code via a local web service running on the device. The device can then use that registration code to authenticate itself and initialize the provisioning process. This solves a common use-case of transferring trust from an authenticated user to a device so the device can provision itself asynchronously.
This sample also contains some useful code snippets/libraries to demo the following:
- CDK integration with Chalice to deploy API Gateway APIs, Lambda functions, DynamoDB tables, and S3 buckets
- Narrowly scoped IoT policies with policy variables and AWS IoT Credential Provider permissions
- AWS IoT Credential Provider API calls
- IAM Role with AWS IoT Credential Provider Policy Variables
- AWS SigV4 Authentication for API Gateway API calls
Below is a sequence diagram and overview diagram for this sample.
- AWS API Gateway
- AWS Lambda
- Amazon DynamoDB
- AWS Lambda
- AWS IoT Credential Provider
- AWS CDK
- AWS Chalice
300
- Admin-level permissions due to the creation of IAM resources
- Python or Docker installed
This project has 2 parts: The IoT Client and the Registration API.
The registration API portion includes a CDK application and a Chalice application. These correspond to a
infrastructure
and runtime
directory respectively. To run any CDK CLI commands, ensure you're in the
infrastructure
directory, and if you need to run any Chalice CLI commands, which you won't for this demo, ensure
you're in the runtime
directory.
The IoT client portion consists the client itself with the AWS IoT Python SDK, the aws_auth library for AWS SigV4 auth,
the requirements file with the required dependencies, and the Dockerfile. These are all stored under the client
directory.
First, you'll need to install the AWS CDK if you haven't already. The CDK requires Node.js and npm to run. See the Getting started with the AWS CDK for more details.
npm install -g aws-cdk
Next you'll need to install the dependencies for the CDK deployment.
There are two ways to do this. Either globally from the standard shell or using a virtual environment such as pipenv
The recommended way to install Python dependencies is with a virtual environment such as Pipenv. There's an included Pipfile with the repo that you can use to install all dependencies to run the client. First you'll need to make sure Pipenv is installed. https://pipenv.pypa.io/en/latest/install/
Then you can use pipenv to install all the Python dependencies.
pipenv install
Once all dependencies are installed, you'll need to activate the shell with pipenv shell
From the root directory, switch to the api
directory with cd api
and then run
pip install -r requirements.txt
Once the dependencies are installed, to work with the CDK and deploy your application, you'll need to change directories
to the infrastructure
directory.
-
If this is you're first time using the CDK you'll need to bootstrap your AWS account with the resouces the CDK needs.
cdk bootstrap
-
Now you're ready to deploy your application.
cdk deploy
Note: During deployment, the CDK will ask you to approve of the changes being created by CloudFormation, make sure to
type y
when prompted.
-
There will be two pieces of infrastructure left to manually provision that can not be deployed with CloudFormation, and that's the AWS IoT Credential Provider role alias and the AWS IoT Thing Types.
- The creation of the role alias can not be done via the console and must be done via a CLI command:
aws iot create-role-alias --role-alias <tenant> --role-arn <Role arn from CDK template>
You'll be using a fake "tenant" as the role alias name. In our case, that tenant name is
acme
. The role arn should come from theAWSIoTCredentialProviderRole
role that was deployed with the CloudFormation template.- You'll also need to go into the AWS IoT Console and create a Thing Type. This should be named
deviceTypeA
. These Thing Types do not need any configuration other than their names.
The IoT client can either be run as a Docker container or a native Python application. In both cases, you'll need to ensure the correct environment variables are configured. These are:
IOT_ENDPOINT
- PREFIX ONLY (everything before ".iot" or "-ats.iot"). This can be found under "Settings" in the IoT
console or by running aws iot describe-endpoint
in the AWS CLI
CREDENTIALS_ENDPOINT
- PREFIX ONLY (everything before ".credentials"). This can only be found by running
aws iot describe-endpoint --endpoint-type iot:CredentialProvider
in the AWS CLI
AWS_DEFAULT_REGION
- Eg. us-east-1
REG_API
- Endpoint of Registration API (eg. https://abc123.execute-api.us-east-1.amazonaws.com)
A sample Docker environment file has been included if you choose to run the client in Docker.
-
Download the AWS IoT CA server certificate from here and store it in the
client
directory. This will be used by the IoT client to trust the AWS IoT Core Device Gateway.wget -O client/AmazonRootCA1.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem
-
Make sure the correct environment variables mentioned above are configured. The exact commands to do this might vary slightly between operating systems and runtime environments. But generally in standard Linux/Unix shells it's accomplished by running
$ export <KEY>=<VALUE>
for each environment variable. -
With environment variables set, you're ready to start the client. The Python client requires no arguments passed to it.
python iot_client.py
-
For running with docker, you'll need to build the container locally using the included Dockerfile.
docker build -t <use any image tag name here> .
-
Make sure the previously mentioned environment file has the correct values set and then
docker run
with the following command:docker run -p 5000:5000 --env-file docker.env -it <build-image-tag>
Ensure all setup steps are complete, the API has been provisioned successfully, and the IoT client pre-requisites have been installed.
The overview of the demo steps are as follows:
- Start IoT client
- Make API call to get registration token
- Pass registration token to local IoT client
- The IoT client will then do the rest on its own including:
- Call the Registration API with the token to get a certificate
- Initialize the MQTT client with the certificate
- Call the AWS IoT Credentials Provider to receive AWS STS credentials
- Call the Registration API with a SigV4 authentication header derived from the STS credentials to receive an S3 pre-signed URL
- Upload sample data to the S3 bucket created in the CloudFormation stack using the S3 pre-signed URL (object key: acme//sample)
- Upload sample data to the same S3 bucket using the standard Boto3 SDK with the permissions provided by the AWS IoT Credentials Provider (object key: /sample)
-
Local IoT client should already be started by this point
-
You will need to make a "GET" request to the registration API to get a token. When this request is made, the API will generate a token as well as dummy metadata including "tenant", "location", "deviceType", and save it all to a DynamoDB table. The URL for the request is: /api/token
curl <REGISTRATION_API_ENDPOINT>/api/token
-
Copy the token out of the response from the previous request and use it to create a "POST" request to the local IoT client. Please note the token expires in 5 minutes. The URL for this request is
127.0.0.1:5000/regToken
. The body should be JSON and the structure is:{"registrationCode": "<value>"}
curl --request POST '127.0.0.1:5000/regToken' --header 'Content-Type: application/json' --data-raw '{"registrationCode": <token from previous request>}'
The registration token will then be used by the Registration API to authenticate the device and retrieve a certificate issued by AWS IoT Core. Once The IoT client has the certificate, and the device has been registered, it will complete the demo as previously described.
- You'll know everything was successful when the last output from the IoT client is
Successfully uploaded object to S3 via Boto
. Open the S3 bucket that was created in the CloudFormation template and confirm that the sample data is shown there with a key prefix of<tenant>/<thing_name>/sample_data_
.
- Delete all items out of DynamoDB
- Delete all objects out of S3
- Delete CloudFormation stack
- Delete AWS IoT Thing Types
- Delete AWS IoT Role Aliases
See CONTRIBUTING for more information.
This library is licensed under the MIT-0 License. See the LICENSE file.