├── .github └── PULL_REQUEST_TEMPLATE.md ├── AWS_IoT_Device_Management_Workshop.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── bin ├── bulk-result.py ├── clean-up.py ├── create-root-ca-bundle.sh ├── fleet-indexing.py ├── mk-bulk.sh └── mk-prov.sh ├── cfn └── cfn-iot-dm-ws.json ├── dm-ws.tar ├── img └── AWS_IoT_Core.png ├── job-agent ├── job-agent.py └── job-document.json ├── lambda ├── lambda_function.py └── lambda_function.zip ├── mk-dm-ws-tar.sh └── templateBody.json /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /AWS_IoT_Device_Management_Workshop.md: -------------------------------------------------------------------------------- 1 | ![AWS_IoT_Core.png](img/AWS_IoT_Core.png) 2 | 3 | 4 | ## Updated version 5 | 6 | Please note: This is an older version of the AWS IoT Device Management workshop. 7 | 8 | You can find an updated version at [https://iot-device-management.workshop.aws/en/](https://iot-device-management.workshop.aws/en/). 9 | 10 | ## AWS IoT Device Management Workshop 11 | This workshop will guide you through the features of [AWS IoT Device Management](https://aws.amazon.com/iot-device-management/). 12 | 13 | The workshop is designed to be conducted on an EC2 instance which will be provisioned through AWS CloudFormation. Amazon Linux is used as operating system and the standard user is named *ec2-user*. 14 | 15 | We do provide some scripts that you will use in the workshop. They are copied automatically into the directory *~/bin* of the user *ec2-user* on your EC2 instance. 16 | 17 | You will also create a Lambda function during the workshop and the code can be found in *~/lambda*. 18 | 19 | For several exercises the aws command line interface (awscli) is used. It is installed and configured automatically on the EC2 Instance. 20 | 21 | ## Workshop Agenda 22 | 23 | * [Prerequisites](#Prerequisites) 24 | * [Launch an EC2 Instance with CloudFormation](#Launch_EC2_Instance_with_CloudFormation) 25 | * [Enable Logging for AWS IoT](#Enable_Logging_IoT) 26 | * [Registry Events](#Registry_Events) 27 | * [Device Provisioning with the API](#Device_Provisioning_with_the_API) 28 | * [Single Device Provisioning](#Single_Device_Provisioning) 29 | * [Bulk Device Provisioning](#Bulk_Device_Provisioning) 30 | * [JITP - Just-in-Time Provisioning](#JITP) 31 | * [JITR - Just-in-Time Registration](#JITR) 32 | * [IoT Jobs](#IoT_Jobs) 33 | * [Fleet Indexing](#Fleet_Indexing) 34 | * [Thing Groups](#Thing_Groups) 35 | * [Fine-Grained Logging](#Fine-Grained_Logging) 36 | * [Clean Up](#Cleanup) 37 | 38 | 39 | [[Top](#Top)] 40 | ## Prerequisites 41 | To conduct the workshop you will need the following tools/setup/knowledge: 42 | 43 | * AWS Account 44 | * Secure shell (ssh) to login into the EC2 instance 45 | * Mac OS/Linux: command lines tools are installed by default 46 | * Windows 47 | * Putty: ssh client: 48 | * Manual connect (ssh) to an EC2 instance from Windows with Putty: 49 | * A ssh key-pair to be able to log into the EC2 instance 50 | * a ssh key-pair can be generated or imported in the AWS console under *EC2 -> Key Pairs* 51 | * Basic knowledge howto move around on a linux system and edit files 52 | 53 | ### Recommendation 54 | Don't use a production account to conduct this workshop to not mess up things accidentally. Use an AWS region where you do not have provisioned any IoT resources to avoid conflicts during creating or deleting resources. 55 | 56 | 57 | 58 | [[Top](#Top)] 59 | ## Launch an EC2 Instance with AWS CloudFormation 60 | 61 | For the exercises in this workshop an EC2 instance will be used. We do provide a [CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html) stack to create an EC2 instance and other AWS resources required for this workshop. Simply click the AWS region where you want to launch your stack. It is recommended to choose the closest region. 62 | 63 | By choosing one of the links below you will be automatically redirected to the CloudFormation section of the AWS Console where your stack will be launched. 64 | 65 | #### Prior to launch the CloudFormation stack you need to have: 66 | 67 | * **An ssh key pair to log into the EC2 instance. If you don't have an ssh key pair you can create one in the** 68 | 69 | => *EC2 console -> Key pairs* 70 | 71 | The CloudFormation Stack creates the following resources: 72 | 73 | * **S3 Bucket** required for Bulk Provisioning 74 | * **IoT Policy** for provisioning scenarios 75 | * **VPC with public subnet + Security Group** for an EC2 instance 76 | * **EC2 instance** where you do your work 77 | * **Instance Profile** for your EC2 78 | * **IAM Role** required for provisioning scenarios 79 | 80 | --- 81 | 82 | #### Prepare to launch the CloudFormation stack 83 | To bootstrap the EC2 instance with the scripts for this workshop a tar file has to be pulled onto the instance during launch. The CloudFormation template is designed to get the tar file from a static S3 website. 84 | 85 | ##### Create an S3 bucket 86 | 87 | **Go to the S3 console** 88 | 89 | * \+ Create bucket 90 | * Bucket name: **enter a bucket name** (will be referred to as CFN\_S3\_BUCKET) 91 | * Next 92 | * Next 93 | * Next 94 | * Create bucket 95 | * Choose the bucket that you just have created 96 | * Properties 97 | * Static website hosting 98 | * Use this bucket to host a website 99 | * Index document: **index.html** 100 | * Error document: **error.html** 101 | * Take a note of your website URL. It has the format **http://\.s3-website.\.amazonaws.com** 102 | * Save 103 | 104 | ##### Modify the CloudFormation template 105 | 106 | * CloudFormation template: [**cfn/cfn-iot-dm-ws.json**](cfn/cfn-iot-dm-ws.json) 107 | * Replace the string **YOUR\_STATIC\_S3\_WEBSITE** in the template with the URL of your static S3 website 108 | 109 | 110 | ##### Upload the tar file to S3 111 | 112 | * Upload the file **dm-ws.tar** to the **CFN\_S3\_BUCKET** 113 | * Make the file publicly readable. This can be done in the properties section of the file in the S3 console. 114 | 115 | #### Launch the CloudFormation stack 116 | 117 | Choose one of the following regions to launch the CloudFormation stack. The link will guide you directly to the AWS CloudFormation console 118 | 119 | * [Launch CloudFormation stack in eu-central-1](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new) (Frankfurt) 120 | * [Launch CloudFormation stack in eu-west-1](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new) (Ireland) 121 | * [Launch CloudFormation stack in eu-west-2](https://console.aws.amazon.com/cloudformation/home?region=eu-west-2#/stacks/new) (London) 122 | * [Launch CloudFormation stack in us-east-1](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new) (N. Virginia) 123 | * [Launch CloudFormation stack in ap-south-1](https://console.aws.amazon.com/cloudformation/home?region=ap-south-1#/stacks/new) (Mumbai) 124 | * [Launch CloudFormation stack in us-west-2](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new) (Oregon) 125 | * [Launch CloudFormation stack in ap-southeast-2](https://console.aws.amazon.com/cloudformation/home?region=ap-southeast-2#/stacks/new) (Sydney) 126 | * [Launch CloudFormation stack in ap-northeast-1](https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/new) (Tokyo) 127 | 128 | After you have been redirected to the AWS CloudFormation console take the following steps to launch you stack: 129 | 130 | 1. Choose a template 131 | 2. Upload a template to Amazon S3 132 | 3. Select your modified CloudFormation template 133 | 4. Next 134 | 5. Stack name: **IoTDeviceManagementWS** 135 | 6. Parameters 136 | 7. Select SSHKeyName 137 | 8. Next 138 | 9. Next 139 | 10. Capabilities 140 | 11. Check "I acknowledge that AWS CloudFormation might create IAM resources." at the bottom of the page 141 | 12. Create 142 | 13. If the stack does not appear immediately hit the refresh button in the upper right corner 143 | 14. Click on **IoTDeviceManagementWS** 144 | 15. Wait until the complete stack has been created 145 | 146 | Creating the stack should take round about 5mins. 147 | 148 | If encounter any error(s) during stack creation look for the root cause in the Events section of the CloudFormation console. The most common error is to run into service limits. 149 | 150 | In the **Outputs** section for your stack in the CloudFormation console you find several values for resources that has been created. These values will be used during the workshop: 151 | 152 | * Name of an S3 bucket 153 | * Name of an IoT policy 154 | * ARN of a role that is used for device provisioning 155 | * SSH login information 156 | * ARN of a role required for a Lambda function 157 | 158 | Please note: for the ease of use in this workshop an open IoT policy is used. Don't 159 | use such an IoT policy in production environments. Always create your IoT policies 160 | according the the least privilege principle. 161 | 162 | You can go back at any time to the Outputs section to get these values. 163 | 164 | * ssh into your instance 165 | * you can find the hostname in the outputs section of the CloudFormation console 166 | 167 | * You should find at least the directories/files in the home directory of the ec2-user: 168 | * bin 169 | * CA 170 | * job-agent 171 | * lambda 172 | * templateBody.json 173 | 174 | 175 | 176 | [[Top](#Top)] 177 | ## Enable Logging for AWS IoT 178 | 179 | AWS IoT sends progress events about each message as it passes from your devices through the message broker and the rules engine. To view these logs, you must configure AWS IoT to generate the logs used by CloudWatch. 180 | 181 | To enable [AWS IoT logging](https://docs.aws.amazon.com/iot/latest/developerguide/cloud-watch-logs.html), you must create an IAM role, register the role with AWS IoT, and then configure AWS IoT logging. 182 | 183 | Go to the AWS IAM console 184 | 185 | 1. Roles 186 | 2. Create role 187 | 3. AWS service 188 | 4. IoT 189 | 5. Next: Permissions 190 | 6. At least the following managed policy should already be chosen: AWSIoTLogging 191 | 7. Next: Review 192 | 4. Role name: AWSIoTAccessServices 193 | 5. Create Role 194 | 195 | Go to the AWS IoT console 196 | 197 | 1. Get started (only if no resources are provisioned) 198 | 2. Settings 199 | 3. Logs (if DISABLED) -> Edit 200 | 4. Change "Level of verbosity" to "Warnings" 201 | 5. Set role -> Select "AWSIoTAccessServices" 202 | 6. Update 203 | 204 | 205 | The log files from AWS IoT are send to **Amazon CloudWatch**. The AWS console can be used to look at these logs. 206 | 207 | 208 | 209 | [[Top](#Top)] 210 | ## Registry Events 211 | The [Registry publishes event messages](https://docs.aws.amazon.com/iot/latest/developerguide/registry-events.html) when things, thing types, and thing groups are created, updated, or deleted. 212 | Each event causes a single event message to be published by the service. Messages are published over MQTT with a JSON payload. The content of the payload depends on the type of event. 213 | 214 | The device registry posts messages to 215 | 216 | $aws/events/# 217 | 218 | for several actions, e.g. when things are created/updated/deleted. 219 | 220 | **In this exercise** you will enable IoT Events. You will use IoT Events in the following exercises in this workshop. 221 | 222 | 223 | #### Get event configuration 224 | With the following command you can determine which IoT Events are enabled/disabled 225 | 226 | aws iot describe-event-configurations 227 | 228 | #### Enable all events 229 | Use the following command to enable all events: 230 | 231 | aws iot update-event-configurations --cli-input-json \ 232 | '{ 233 | "eventConfigurations": { 234 | "THING_TYPE": { 235 | "Enabled": true 236 | }, 237 | "JOB_EXECUTION": { 238 | "Enabled": true 239 | }, 240 | "THING_GROUP_HIERARCHY": { 241 | "Enabled": true 242 | }, 243 | "CERTIFICATE": { 244 | "Enabled": true 245 | }, 246 | "THING_TYPE_ASSOCIATION": { 247 | "Enabled": true 248 | }, 249 | "THING_GROUP_MEMBERSHIP": { 250 | "Enabled": true 251 | }, 252 | "CA_CERTIFICATE": { 253 | "Enabled": true 254 | }, 255 | "THING": { 256 | "Enabled": true 257 | }, 258 | "JOB": { 259 | "Enabled": true 260 | }, 261 | "POLICY": { 262 | "Enabled": true 263 | }, 264 | "THING_GROUP": { 265 | "Enabled": true 266 | } 267 | } 268 | }' 269 | 270 | 271 | 272 | [[Top](#Top)] 273 | ## Device Provisioning with the API 274 | **In this exercise** you will execute all the required commands to create and register a device with AWS IoT. Furthermore you will experience how registry events work. 275 | 276 | A device that should be able to communicate with AWS IoT needs to have a X.509 certificate which is registered with AWS IoT as well as a IoT policy. X.509 certificates are used for authentication and the IoT policy for authorisation. 277 | 278 | Several API calls are required to provision a device. The output for some commands is stored in files in /tmp because values from it are required for further steps in the provisioning chain. 279 | 280 | In the previous section you have enable IoT Events. To see the messages that the device registry publishes subscribe to the related topic hierarchy. Use the builtin MQTT client in the AWS console to subscribe to topics. 281 | 282 | * Go to the AWS IoT Console 283 | 284 | 1. Test 285 | 2. Subscribe to a topic 286 | 3. Subscription topic: **$aws/events/#** 287 | 4. Subscribe to topic 288 | 289 | 290 | ### Get root CA certificates 291 | 292 | Server certificates allow your devices to verify that they're communicating with AWS IoT and not another server impersonating AWS IoT: 293 | 294 | A shell script is provided on the EC2 instance to download the CA certificates. 295 | 296 | * ssh into your EC2 instance and run the following command: 297 | 298 | create-root-ca-bundle.sh 299 | 300 | The CA certificates are stored as **$HOME/root.ca.bundle.pem** 301 | 302 | ### Create a Device 303 | 304 | Use API calls to provision a device. A device is provisioned in AWS IoT when it has been created in the device registry, a device certificate has been registered and attached to the device, an IoT policy has been attached to the device. 305 | 306 | * ssh into your EC2 instance: 307 | 308 | # putting your thing name into variable makes the next steps easier 309 | THING_NAME=my-first-thing 310 | 311 | # create a thing in the thing registry 312 | aws iot create-thing --thing-name $THING_NAME 313 | 314 | * Go to the AWS IoT console: 315 | You should see a message that was posted to 316 | 317 | $aws/events/thing/$THING_NAME/created 318 | 319 | * Go to your EC2 instance: 320 | 321 | # create key and certificate for your device and active the device 322 | aws iot create-keys-and-certificate --set-as-active \ 323 | --public-key-outfile $THING_NAME.public.key \ 324 | --private-key-outfile $THING_NAME.private.key \ 325 | --certificate-pem-outfile $THING_NAME.certificate.pem > /tmp/create_cert_and_keys_response 326 | 327 | # look at the output from the previous command 328 | cat /tmp/create_cert_and_keys_response 329 | 330 | # output values from the previous call needed in further steps 331 | CERTIFICATE_ARN=$(jq -r ".certificateArn" /tmp/create_cert_and_keys_response) 332 | CERTIFICATE_ID=$(jq -r ".certificateId" /tmp/create_cert_and_keys_response) 333 | echo $CERTIFICATE_ARN 334 | echo $CERTIFICATE_ID 335 | 336 | # create an IoT policy 337 | POLICY_NAME=${THING_NAME}_Policy 338 | aws iot create-policy --policy-name $POLICY_NAME \ 339 | --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action": "iot:*","Resource":"*"}]}' 340 | 341 | # attach the policy to your certificate 342 | aws iot attach-policy --policy-name $POLICY_NAME \ 343 | --target $CERTIFICATE_ARN 344 | 345 | # attach the certificate to your thing 346 | aws iot attach-thing-principal --thing-name $THING_NAME \ 347 | --principal $CERTIFICATE_ARN 348 | 349 | * List things in the device registry. Don't trust the AWS IoT console. It might not be aware of every update made through the API 350 | 351 | aws iot list-things 352 | 353 | ### Update the Device 354 | To update the device you just created add an attribute. Also this modification of the device causes the device registry to publish an event message. 355 | 356 | * Update the device 357 | 358 | aws iot update-thing --thing-name $THING_NAME --attribute-payload '{"attributes": {"type": "ws-device"}}' 359 | 360 | * Look at your device 361 | 362 | aws iot list-things 363 | 364 | * Go to the AWS IoT console: 365 | You should see a message that was posted to 366 | 367 | $aws/events/thing/$THING_NAME/updated 368 | 369 | ### Publish a message with you newly created device 370 | After your device has been provisioned you will publish a message to AWS IoT. To publish a message the command *mosquitto_pub* is used. This command requires the iot endpoint to talk to. 371 | 372 | 373 | * Subscribe to the topic **iot/ws** 374 | 375 | * Go to your EC2 instance: 376 | 377 | # the iot endpoint has been already assigned to the shell variable $IOT_ENDPOINT but you can retrieve it with the following command: 378 | aws iot describe-endpoint 379 | 380 | # during setup of the EC2 instance the iot endpoint has already been assigned to the shell variable IOT_ENDPOINT 381 | 382 | # publish a message 383 | mosquitto_pub --cafile ~/root.ca.bundle.pem \ 384 | --cert $THING_NAME.certificate.pem \ 385 | --key $THING_NAME.private.key -h $IOT_ENDPOINT -p 8883 \ 386 | -q 0 -t iot/ws -i $THING_NAME --tls-version tlsv1.2 \ 387 | -m "{\"prov\": \"first\", \"date\": \"$(date)\"}" -d 388 | 389 | * Go to the AWS IoT Console and check if a message has arrived 390 | 391 | 392 | 393 | [[Top](#Top)] 394 | ## Single Device Provisioning 395 | **In this exercise** you will provision a single device with the [*register-thing*](https://docs.aws.amazon.com/iot/latest/developerguide/programmatic-provisioning.html) API call. In this provisioning example the thing will be assigned to a thing group and a thing type will be assigned. Group and type must exist before the provisioning is started. 396 | 397 | Provision a thing with a single API call (register-thing) and a provisioning template. A key for the device and a CSR are also required to provision the device. 398 | 399 | You will find a provisioning template in your home directory. The filename is *templateBody.json* 400 | 401 | Create keys, CSR and input parameter with the script *mk-prov.sh* for a single device. The output is applied through the Parameters section in the provisioning template to the register-thing API. 402 | 403 | The device that will be created will also be put into a thing group and a thing type will be assigned. This is done through the provisioning template. However group and type must **exist before** the provisioning process starts. 404 | 405 | 406 | * Create a thing group in AWS IoT 407 | 408 | aws iot create-thing-group --thing-group-name bulk-group 409 | 410 | * Create a thing type in AWS IoT 411 | 412 | aws iot create-thing-type --thing-type-name bulk-type 413 | 414 | * Create key, CSR and parameters: 415 | 416 | # set you thing name 417 | THING_NAME=my-second-thing 418 | 419 | mk-prov.sh $THING_NAME 420 | 421 | * Provision the thing. Copy the output between the curly brackets including the curly brackets and replace it in the following command in the --parameters section 422 | 423 | aws iot register-thing --template-body file://templateBody.json --parameters '[OUTPUT_FROM_THE_PREVIOUS_COMMAND]' 424 | 425 | * As output from the previous command you receive the certificate for your thing. Copy the certificate pem from the output and save it into a file with the following command 426 | 427 | echo -e [CERTIFICATE_PEM] > $THING_NAME.crt 428 | 429 | * Go to the AWS IoT Console: 430 | 431 | 1. Manage 432 | 2. Things 433 | 3. Click on you thing 434 | 4. Security 435 | 5. Click the certificate 436 | 6. Policies 437 | 7. Click the Policy name 438 | 439 | 440 | * Go to the AWS IoT Console: 441 | 442 | 1. Subscribe to the topic: iot/ws 443 | 444 | * Go to your EC2 instance: 445 | 446 | # publish a message to AWS IoT 447 | mosquitto_pub --cafile ~/root.ca.bundle.pem \ 448 | --cert $THING_NAME.crt --key $THING_NAME.key \ 449 | -h $IOT_ENDPOINT -p 8883 -q 0 -t iot/ws \ 450 | -i $THING_NAME --tls-version tlsv1.2 \ 451 | -m "{\"prov\": \"second\", \"date\": \"$(date)\"}" -d 452 | 453 | * Go to the AWS IoT Console and check if a message has arrived 454 | 455 | 456 | 457 | [[Top](#Top)] 458 | ## Bulk Device Provisioning 459 | **In this exercise** you will bulk provision multiple things with one API call: [*start-thing-registration-task*](https://docs.aws.amazon.com/iot/latest/developerguide/bulk-provisioning.html) 460 | 461 | The API call **start-thing-registration-task** can be used to provision things in bulk. To provision things in bulk you need the same parameters as with single device provisioning with the **register-thing** API call. But you will put multiple parameters into file which then must be stored in an Amazon S3 bucket. 462 | 463 | An IAM role is also required to allow AWS IoT to access the S3 bucket and provision devices in your account. The role was created already through CloudFormation. You can find the required role ARN in the outputs section of the CloudFormation stack. 464 | 465 | The parameters file which you need to store in the Amazon S3 bucket contains the values used to replace the parameters in the template. The file must be a newline-delimited JSON file. Each line contains all of the parameter values for provisioning a single device. 466 | 467 | You can create keys, CSR and input parameter with the script *mk-bulk.sh* for multiple devices. The script mk-bulk.sh will create a directory and put all the keys, CSRs and a file **bulk.json** into this directory. 468 | 469 | To create a bulk provisioning the required S3 bucket must exist in the **same region** where the devices should be provisioned. This bucket was already created for the workshop and can be found in the shell variable **S3_BUCKET** 470 | 471 | 472 | * Create keys and CSRs: 473 | 474 | THING_NAME=bulky 475 | 476 | # number of things to create 477 | NUM_THINGS=20 478 | 479 | mk-bulk.sh $THING_NAME $NUM_THINGS 480 | 481 | 482 | * Keys, CSRs and the file **bulk.json** are created in a directory with the naming-scheme *$THING_NAME-YYYY-mm-dd_H-M-S* 483 | 484 | * Copy the file *bulk.json* to your S3 bucket and verify that it was copied. The name of your S3 bucket has been copied during the setup of the workshop to the shell variable *S3_BUCKET* 485 | 486 | # cd to the directory where keys/CSRs where created 487 | cd $THING_NAME-YYYY-mm-dd_H-M-S 488 | 489 | # copy bulk.json to S3 490 | aws s3 cp bulk.json s3://$S3_BUCKET/ 491 | 492 | # verify that the file was copied 493 | aws s3 ls s3://$S3_BUCKET/ 494 | 495 | * Create a bulk thing registration task. The name of your role has been copied during the setup of the workshop to the shell variable *$ARN\_IOT\_PROVISIONING\_ROLE* 496 | 497 | aws iot start-thing-registration-task \ 498 | --template-body file://~/templateBody.json \ 499 | --input-file-bucket $S3_BUCKET \ 500 | --input-file-key bulk.json --role-arn $ARN_IOT_PROVISIONING_ROLE 501 | 502 | * If the command is successful you'll get back a task-id. You can verify the state of the task for ERRORS or RESULTS with the following commands: 503 | 504 | aws iot list-thing-registration-task-reports \ 505 | --report-type ERRORS --task-id [YOUR_TASK_ID] 506 | 507 | aws iot list-thing-registration-task-reports \ 508 | --report-type RESULTS --task-id [YOUR_TASK_ID] 509 | 510 | * If you get output from the report-type RESULTS from the command above you can download the output for this command from a URL. The output will be stored in the file **results.json** 511 | 512 | wget -O results.json $(aws iot list-thing-registration-task-reports --task-id [YOUR_TASK_ID] --report-type RESULTS | jq -r '.resourceLinks[]') 513 | 514 | * If you encounter errors use the following command to download the error messages. They are stored in the file **errors.json**. Examine the messages and solve the root cause. 515 | 516 | wget -O errors.json $(aws iot list-thing-registration-task-reports --task-id [YOUR_TASK_ID] --report-type ERRORS | jq -r '.resourceLinks[]') 517 | 518 | * Take a look at the file **results.json** 519 | * Write all the certificates from the file results.json to files for the related thing. Do it on your own or use a python script that we have prepared 520 | 521 | bulk-result.py results.json 522 | 523 | * Verify that the certificates have been written 524 | 525 | ls -l 526 | 527 | * List the things in the AWS IoT device registry 528 | 529 | aws iot list-things 530 | 531 | * Now try to publish with one of the things that you created. Subscribe to the topic "iot/ws" before 532 | 533 | # replace XX with a number of the things you created 534 | THING_NAME=bulkyXX 535 | 536 | mosquitto_pub --cafile ~/root.ca.bundle.pem \ 537 | --cert $THING_NAME.crt --key $THING_NAME.key \ 538 | -h $IOT_ENDPOINT -p 8883 -q 0 -t iot/ws \ 539 | -i $THING_NAME --tls-version tlsv1.2 \ 540 | -m "{\"prov\": \"bulk\", \"date\": \"$(date)\"}" -d 541 | 542 | * **Exercise:** publish with all the things that you bulk provisioned 543 | 544 | 545 | 546 | [[Top](#Top)] 547 | ## JITP - Just-in-Time Provisioning 548 | You can have your devices provisioned when they first attempt to connect to AWS IoT. [Just-in-time provisioning](https://docs.aws.amazon.com/iot/latest/developerguide/jit-provisioning.html) (JITP) settings are made on CA certificates. You must enable automatic registration and associate a provisioning template with the CA certificate used to sign the device certificate you are using to provision the device. 549 | 550 | For JITP you need to bring your own CA. 551 | 552 | **In this exercise** you will create your own CA, register it with AWS IoT, prepare the CA for JITP and register a new device simply by connecting to AWS IoT. 553 | 554 | * Change to the directory "CA" 555 | 556 | cd ~/CA 557 | 558 | * Generate a key pair for your own CA 559 | 560 | openssl genrsa -out rootCA.key 2048 561 | 562 | * Use the private key from the key pair to generate a CA certificate 563 | 564 | openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem 565 | 566 | * You will be prompted for some information. 567 | 568 | Country Name (2 letter code) [XX]:DE 569 | State or Province Name (full name) []:Berlin 570 | Locality Name (e.g., city) [Default City]:Berlin 571 | Organization Name (e.g., company) [Default Company Ltd]:IoT 572 | Organisational Unit Name (e.g., section) []:Workshop 573 | Common Name (e.g., your name or your server's hostname) []:IoT Workshop CA 574 | Email Address []: 575 | 576 | * Register CA certificate 577 | * Get a registration code from AWS IoT. This code will be used as the Common Name of the private key verification certificate 578 | 579 | aws iot get-registration-code 580 | 581 | * Generate a key pair for the private key verification certificate 582 | 583 | openssl genrsa -out verificationCert.key 2048 584 | 585 | * Create a CSR for the private key verification certificate. Set the **Common Name** to the registration code that you received above 586 | 587 | openssl req -new -key verificationCert.key -out verificationCert.csr 588 | 589 | * You will be prompted for some information. As common name set the registration code that you got from the "aws iot get-registration-code" command 590 | 591 | Country Name (2 letter code) [XX]: 592 | State or Province Name (full name) []: 593 | Locality Name (for example, city) []: 594 | Organization Name (for example, company) []: 595 | Organisational Unit Name (for example, section) []: 596 | Common Name (e.g. server FQDN or YOUR name) []:XXXXXXXXXXXXYOURREGISTRATIONCODEXXXXXX 597 | Email Address []: 598 | 599 | * Create a certificate from the CSR 600 | 601 | openssl x509 -req -in verificationCert.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out verificationCert.pem -days 500 -sha256 602 | 603 | * Register the CA certificate with AWS IoT by using the certificate that you just created 604 | 605 | aws iot register-ca-certificate --ca-certificate file://rootCA.pem --verification-cert file://verificationCert.pem 606 | 607 | * Verify that the CA certificate has been registered successfully. You received the ca certificate id in the output from the command above 608 | 609 | # assign the certificate id to a shell variable (for convenience) 610 | CA_CERTIFICATE_ID=[YOUR_CA_CERTIFICATE_ID] 611 | 612 | # verify registration 613 | aws iot describe-ca-certificate --certificate-id $CA_CERTIFICATE_ID 614 | 615 | * Activate the CA certificate. The certificate-id was in the output of the previous command. 616 | 617 | aws iot update-ca-certificate --new-status ACTIVE --certificate-id $CA_CERTIFICATE_ID 618 | 619 | * Enable JITP for your CA by attaching a provisioning template to the CA. The required certificate id was already used in the previous command. The role arn required for JITP has been stored in the shell variable *$ARN\_IOT\_PROVISIONING\_ROLE* 620 | 621 | # store the template body in a shell 622 | TB="{ \\\"Parameters\\\" : { \\\"AWS::IoT::Certificate::Id\\\" : { \\\"Type\\\" : \\\"String\\\" }, \\\"AWS::IoT::Certificate::CommonName\\\" : { \\\"Type\\\" : \\\"String\\\" }, \\\"AWS::IoT::Certificate::Country\\\" : { \\\"Type\\\" : \\\"String\\\" } }, \\\"Resources\\\" : { \\\"thing\\\" : { \\\"Type\\\" : \\\"AWS::IoT::Thing\\\", \\\"Properties\\\" : { \\\"ThingName\\\" : {\\\"Ref\\\" : \\\"AWS::IoT::Certificate::CommonName\\\"}, \\\"AttributePayload\\\" : { \\\"Country\\\" : {\\\"Ref\\\" : \\\"AWS::IoT::Certificate::Country\\\"} }, \\\"ThingGroups\\\" : [\\\"bulk-group\\\"] } }, \\\"certificate\\\" : { \\\"Type\\\" : \\\"AWS::IoT::Certificate\\\", \\\"Properties\\\" : { \\\"CertificateId\\\": { \\\"Ref\\\": \\\"AWS::IoT::Certificate::Id\\\" }, \\\"Status\\\" : \\\"ACTIVE\\\" } }, \\\"policy\\\" : { \\\"Type\\\" : \\\"AWS::IoT::Policy\\\", \\\"Properties\\\" : { \\\"PolicyName\\\": \\\"$IOT_POLICY\\\" } } } }" 623 | 624 | # verify the content of $TB 625 | echo $TB 626 | 627 | # attach the provisioning template (stored in the variable $TB) to the CA certificate 628 | aws iot update-ca-certificate --certificate-id $CA_CERTIFICATE_ID \ 629 | --no-remove-auto-registration \ 630 | --new-auto-registration-status ENABLE \ 631 | --registration-config "{\"templateBody\": \"$TB\",\"roleArn\": \"$ARN_IOT_PROVISIONING_ROLE\"}" 632 | 633 | 634 | * Get information about your CA certificate and verify that the provisioning template has been attached 635 | 636 | aws iot describe-ca-certificate --certificate-id $CA_CERTIFICATE_ID 637 | 638 | * Create a device certificate 639 | 640 | Name for your device cert 641 | 642 | deviceCert=deviceJITPCert 643 | 644 | Generate a key pair. 645 | 646 | openssl genrsa -out $deviceCert.key 2048 647 | 648 | Create a CSR for the device certificate. 649 | 650 | openssl req -new -key $deviceCert.key -out $deviceCert.csr 651 | 652 | You will be prompted for some information, as shown here. 653 | **Fill in at least Country Name and Common Name as these values from the certificate are used to provision your device.** 654 | 655 | Country Name (2 letter code) [XX]: 656 | State or Province Name (full name) []: 657 | Locality Name (for example, city) []: 658 | Organization Name (for example, company) []: 659 | Organisational Unit Name (for example, section) []: 660 | Common Name (e.g. server FQDN or YOUR name) []: my-jitp-device 661 | Email Address []: 662 | 663 | Please enter the following 'extra' attributes 664 | to be sent with your certificate request 665 | A challenge password []: 666 | An optional company name []: 667 | 668 | Create a device certificate from the CSR. 669 | 670 | # issue certificate 671 | openssl x509 -req -in $deviceCert.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out $deviceCert.pem -days 500 -sha256 672 | 673 | # put device cert and cert of your CA in one file 674 | cat $deviceCert.pem rootCA.pem > ${deviceCert}AndCACert.crt 675 | 676 | * In the AWS IoT console subscribe to **$aws/events/#** 677 | * Auto register your device by publishing a message 678 | 679 | # publish 680 | mosquitto_pub --cafile ~/root.ca.bundle.pem \ 681 | --cert ${deviceCert}AndCACert.crt --key $deviceCert.key \ 682 | -h $IOT_ENDPOINT -p 8883 -q 1 -t ji/tp \ 683 | -i $deviceCert --tls-version tlsv1.2 -m '{"let-me": "in"}' -d 684 | 685 | * The command will produce an error message like: 686 | 687 | Client [device_name] sending CONNECT 688 | Error: The connection was lost. 689 | 690 | * This is a **normal behaviour** as AWS IoT disconnects the client when the automatic registration of the device has been started. 691 | 692 | 693 | * Verify if the device was created either in the AWS IoT Console or on the command line 694 | 695 | aws iot list-things 696 | 697 | * Subscribe to **ji/tp** and publish again the message above. The device should be now provisioned and ready to operate 698 | 699 | # publish 700 | mosquitto_pub --cafile ~/root.ca.bundle.pem \ 701 | --cert ${deviceCert}AndCACert.crt --key $deviceCert.key \ 702 | -h $IOT_ENDPOINT -p 8883 -q 1 -t ji/tp -i $deviceCert \ 703 | --tls-version tlsv1.2 -m '{"let-me": "in"}' -d 704 | 705 | 706 | 707 | 708 | [[Top](#Top)] 709 | ## JITR - Just-in-Time Registration 710 | When you connect to AWS IoT with the device certificate for the first time, the service will detect an unknown certificate signed by a registered CA and will auto-register the device certificate. On successful registration AWS IoT will publish a registration message on a reserved MQTT topic and disconnect the client. 711 | 712 | This MQTT registration event will trigger an AWS Lambda function through an [IoT topic rule](https://docs.aws.amazon.com/iot/latest/developerguide/iot-rules.html). The Lambda function will complete the provisioning of the device. After these steps, your device will be ready to work with AWS IoT. 713 | 714 | **In this exercise** you will setup JITR with your own CA and an AWS Lambda function. When creating the device the lambda function will generate a thing name which starts with *jitr-* followed by the certificate id. The thing name will look similar to *jitr-12345435e8912702c16da3b3f8b91d085f7c0028d91ae7be53a1e7aa8160b3db* 715 | 716 | * You should still have stored the certificate id in the shell variable *$CA_CERTIFICATE_ID*. **If not** store the certificate id into that variable 717 | 718 | CA_CERTIFICATE_ID=[YOUR_CA_CERTIFICATE_ID] 719 | 720 | * First we need to remove JITP from the CA. 721 | 722 | aws iot update-ca-certificate --certificate-id $CA_CERTIFICATE_ID \ 723 | --remove-auto-registration \ 724 | --new-auto-registration-status DISABLE 725 | 726 | * Verify that the provision template an role arn have been removed from the CA 727 | 728 | aws iot describe-ca-certificate --certificate-id $CA_CERTIFICATE_ID 729 | 730 | * Enable Just-in-Time Registration (JITR) 731 | 732 | aws iot update-ca-certificate --certificate-id $CA_CERTIFICATE_ID \ 733 | --new-auto-registration-status ENABLE 734 | 735 | * Verify that the auto registration status is set to ENABLE 736 | 737 | aws iot describe-ca-certificate --certificate-id $CA_CERTIFICATE_ID 738 | 739 | 740 | * Create a Lambda function. The role that is required to create the Lambda function was already created for you and copied to the shell variable *$ARN\_LAMBDA\_ROLE* 741 | 742 | # change to the directory where the lambda is stored 743 | cd ~/lambda 744 | 745 | # create the lambda function 746 | aws lambda create-function \ 747 | --region $AWS_REGION \ 748 | --function-name jitr \ 749 | --zip-file fileb://lambda_function.zip \ 750 | --role $ARN_LAMBDA_ROLE \ 751 | --handler lambda_function.lambda_handler \ 752 | --runtime python2.7 \ 753 | --timeout 30 \ 754 | --memory-size 128 755 | 756 | # verify that the lambda has been created 757 | aws lambda list-functions 758 | 759 | * Create an IoT topic rule. With this rule the Lambda function that you just created is called when a device certificate is auto-registered 760 | 761 | # to create the topic rule we need to have the arn of the Lambda function 762 | # Store the Lambda function arn in a shell variable 763 | ARN_LAMBDA=$(aws lambda get-function --function-name jitr | jq -r '.Configuration.FunctionArn') 764 | 765 | # verify the variable content 766 | echo $ARN_LAMBDA 767 | 768 | # create the IoT topic rule 769 | aws iot create-topic-rule --rule-name JITRRule \ 770 | --topic-rule-payload "{ 771 | \"sql\": \"SELECT * FROM '\$aws/events/certificates/registered/#' WHERE certificateStatus = \\\"PENDING_ACTIVATION\\\"\", 772 | \"description\": \"Rule for JITR\", 773 | \"actions\": [ 774 | { 775 | \"lambda\": { 776 | \"functionArn\": \"$ARN_LAMBDA\" 777 | } 778 | } 779 | ] 780 | }" 781 | 782 | * Verify that the topic rule has been created 783 | 784 | aws iot get-topic-rule --rule-name JITRRule 785 | 786 | * Add a permission to the lambda to allow the AWS IoT to invoke the function. For adding the permissions we need the topic rule arn and will store it into a shell variable 787 | 788 | # get you AWS account id 789 | ACCOUNT_ID=$(aws sts get-caller-identity | jq -r '.Account') 790 | 791 | # verify that the variable has been set 792 | echo $ACCOUNT_ID 793 | 794 | # store topic rule arn 795 | ARN_TOPIC_RULE=$(aws iot get-topic-rule --rule-name JITRRule | jq -r '.ruleArn') 796 | 797 | # verify that the variable has been set 798 | echo $ARN_TOPIC_RULE 799 | 800 | # add permissions to the Lambda 801 | aws lambda add-permission --function-name jitr \ 802 | --region $AWS_REGION --principal iot.amazonaws.com \ 803 | --source-arn $ARN_TOPIC_RULE --source-account $ACCOUNT_ID \ 804 | --statement-id Id-123 --action "lambda:InvokeFunction" 805 | 806 | # verify the permissions of the function 807 | aws lambda get-policy --function-name jitr 808 | 809 | 810 | * Create device certificate in the same way you did for JITP but use a different name for the deviceCert 811 | 812 | Go to the certs directory 813 | 814 | cd ~/CA 815 | 816 | Name for your device cert 817 | 818 | deviceCert=deviceJITRCert 819 | 820 | Create a thing key, certificate and connect to AWS IoT like you did in the JITP section of this workshop. 821 | 822 | Connect to AWS IoT to initiate the JITR process 823 | 824 | mosquitto_pub --cafile ~/root.ca.bundle.pem \ 825 | --cert ${deviceCert}AndCACert.crt --key $deviceCert.key \ 826 | -h $IOT_ENDPOINT -p 8883 -q 1 -t ji/tr -i $deviceCert \ 827 | --tls-version tlsv1.2 -m '{"let-me": "in"}' -d 828 | 829 | 830 | * To verify if JITR was successful you should: 831 | * Have a look in the device registry (hint: aws iot list-things) 832 | * Look at the CloudWatch logs for the lambda and AWS IoT: **AWS CloudWatch Console -> Logs -> /aws/lambda/jitr** 833 | 834 | 835 | 836 | [[Top](#Top)] 837 | ## IoT Jobs 838 | [AWS IoT Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) is a service that allows you to define a set of remote operations that are sent to and executed on one or more devices connected to AWS IoT. 839 | 840 | #### Job Topics 841 | AWS IoT provides an MQTT API for jobs and uses the following topics: 842 | 843 | * $aws/things/\/jobs/notify (or $aws/things/\/jobs/notify-next) 844 | * $aws/things/\/jobs/get/accepted 845 | * $aws/things/\/jobs/get/rejected 846 | * $aws/things/\/jobs/jobId/get/accepted 847 | * $aws/things/\/jobs/jobId/get/rejected 848 | 849 | **In this exercise** you will create a job which will be gathered by a sample job agent. The job agent then acts based on the contents of the job document. The job agent uses the MQTT API to retrieve jobs and also to report the result of the executed job. 850 | You will find the job agent as *~/job-agent/job-agent.py* 851 | 852 | 853 | * **Provision a device** with one of the options that you have learned in the **previous exercises** and put the root.ca.bundle.crt, device key and device cert into the directory *~/job-agent/*. E.g: 854 | 855 | cd ~/job-agent/ 856 | THING_NAME=job-agent 857 | aws iot create-thing --thing-name $THING_NAME 858 | aws iot create-keys-and-certificate --set-as-active \ 859 | --public-key-outfile $THING_NAME.public.key \ 860 | --private-key-outfile $THING_NAME.private.key \ 861 | --certificate-pem-outfile $THING_NAME.certificate.pem > /tmp/create_cert_and_keys_response 862 | CERTIFICATE_ARN=$(jq -r ".certificateArn" /tmp/create_cert_and_keys_response) 863 | CERTIFICATE_ID=$(jq -r ".certificateId" /tmp/create_cert_and_keys_response) 864 | POLICY_NAME=${THING_NAME}_Policy 865 | aws iot create-policy --policy-name $POLICY_NAME \ 866 | --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action": "iot:*","Resource":"*"}]}' 867 | aws iot attach-policy --policy-name $POLICY_NAME \ 868 | --target $CERTIFICATE_ARN 869 | aws iot attach-thing-principal --thing-name $THING_NAME \ 870 | --principal $CERTIFICATE_ARN 871 | 872 | 873 | * List things in the device registry. Don't trust the AWS IoT console. It might not be aware of every update made through the API 874 | 875 | aws iot describe-thing --thing-name $THING_NAME 876 | 877 | 878 | * **Start the sample job agent**. To start the job agent you need to provide on the command line the client-id, iot-endpoint, ca certificate, device key and certificate 879 | 880 | # when you have used the commands above to create the device 881 | ./job-agent.py -c $THING_NAME -i $IOT_ENDPOINT \ 882 | --cacert ~/root.ca.bundle.pem --cert $THING_NAME.certificate.pem \ 883 | --key $THING_NAME.private.key 884 | 885 | Go to the AWS IoT Console 886 | 887 | 1. Test 888 | 2. Subscribe to a topic 889 | 3. Subscription topic: **$aws/events/#** 890 | 4. Subscribe 891 | 5. Subscribe to a topic 892 | 6. Subscription topic: **$aws/things/#** 893 | 7. Subscribe 894 | 8. Subscribe to a topic 895 | 9. Subscription topic: **sys/info** 896 | 10. Subscribe 897 | 898 | 899 | #### Sample job document 900 | The following sample job document was already copied to your EC2 instance to *~/job-agent/job-document.json* 901 | 902 | { 903 | "operation": "sys-info", 904 | "sys-info": "uptime", 905 | "topic": "sys/info" 906 | } 907 | 908 | The intention of the job document is to instruct the job agent to get the uptime from the system that is running on and report it to AWS IoT Core to the topic *sys/info* 909 | 910 | * ssh into the EC2 instance in an additional session. The job agent running in another session should not be interrupted. 911 | * Copy the job document to your S3 Bucket 912 | 913 | cd ~/job-agent/ 914 | # copy the document 915 | aws s3 cp job-document.json s3://$S3_BUCKET/ 916 | 917 | # verify that the document has been copied 918 | aws s3 ls s3://$S3_BUCKET/ 919 | 920 | * Create a job. The job id that you choose must be unique. It is not possible to reuse a job id. 921 | 922 | # retrieve the arn for the job device, 923 | # e.g. through list-things, describe-thing, "Manage" in the AWS IoT console 924 | JOB_THING_ARN=[ARN_OF_YOUR_JOB_THING] 925 | 926 | # define a unique job id 927 | JOB_ID=[an_id_e.g. 468] 928 | 929 | # finally create the job 930 | aws iot create-job --job-id $JOB_ID \ 931 | --targets $JOB_THING_ARN \ 932 | --document-source https://s3.amazonaws.com/$S3_BUCKET/job-document.json \ 933 | --presigned-url-config "{\"roleArn\":\"$ARN_IOT_PROVISIONING_ROLE\", \"expiresInSec\":3600}" 934 | 935 | * You should receive an output similar to the following: 936 | 937 | { 938 | "jobArn": "arn:aws:iot:[AWS_REGION]:[AWS_ACCOUNT_ID]:job/$JOB_ID", 939 | "jobId": "$JOB_ID" 940 | } 941 | 942 | * Watch the output of the job-agent.py in the other session 943 | * As long as there are no jobs present you should find a log line which contains: 944 | * **callback\_jobs\_get\_accepted - NO jobs available** 945 | * After you have created your job you should see more activity in the logs. When a job is available you should find a log line containing: 946 | * **callback\_jobs\_get\_accepted - JOBS AVAILABLE** 947 | * Feel free to create more jobs 948 | 949 | 950 | 951 | [[Top](#Top)] 952 | ## Fleet Indexing 953 | [Fleet Indexing](https://docs.aws.amazon.com/iot/latest/developerguide/iot-indexing.html) is a managed service that allows you to index and search your registry and shadow data in the cloud. After your fleet index is set up, the service manages the indexing of all your registry and shadow updates. You can use a simple query language based on the popular open source search engine, Apache Lucene, to search across this data. 954 | 955 | **In this exercise** you will enable fleet indexing for the registry and the shadow and then perform searches in the registry. 956 | 957 | Let's assume you manage a building with a temperature sensor per room. You want to find out in which of the rooms the temperature is above 20° because that might be an indicator that the air condition is not working properly. 958 | 959 | We will simulate temperature sensors by adding reported temperatures to the device shadows and adding also a room number attribute to devices. We will use the devices that you have created in the bulk provisioning exercise. 960 | 961 | * Enable fleet indexing for the registry and shadow 962 | 963 | aws iot update-indexing-configuration \ 964 | --thing-indexing-configuration thingIndexingMode=REGISTRY_AND_SHADOW 965 | 966 | * Verify if fleet indexing is enabled for both registry and shadow 967 | 968 | aws iot get-indexing-configuration 969 | 970 | * Add reported temperature to the device shadow and room number to the device. We provide a small script that modifies all things matching the thing name *"name\*"*. Use the devices that you have created in the bulk provisioning exercise. We assume that you have used the thing name *bulky* in the earlier bulk provisioning exercise. If another name has been used, change the name in the following command accordingly. 971 | 972 | # add temperature and room number 973 | # to all devices matching the name "bulky*" 974 | # if you have named the devices in the bulk provisioning section 975 | # differently, choose the appropriate basename 976 | fleet-indexing.py -b bulky 977 | 978 | If you get an **error message** containing *Creation of index AWS_Things is in progress* it means that the indexing process has not finished yet. Wait some minutes and try again. 979 | 980 | * Verify that the modification was successful. Every device from the bulk provisioning exercise should have an attribute *room\_number* and a reported temperature in the *shadow* 981 | 982 | aws iot search-index --query-string "thingName:bulky*" 983 | 984 | * Find all device in the registry which have a room number and where the temperature is greater than 20° 985 | 986 | aws iot search-index \ 987 | --query-string "shadow.reported.temperature>20 AND attributes.room_number:*" 988 | 989 | * You can find the shadow document for a device also in the AWS IoT Console 990 | 991 | 1. Go to the AWS IoT Console 992 | 2. Manage 993 | 3. Things 994 | 4. Click on a thing 995 | 5. Shadow 996 | 6. Find the shadow state in the shadow document 997 | 998 | 999 | 1000 | [[Top](#Top)] 1001 | ## Thing Groups 1002 | [Thing groups](https://docs.aws.amazon.com/iot/latest/developerguide/thing-groups.html) allow you to manage several things at once by categorizing them into groups. 1003 | 1004 | **In this exercise** you will learn how you can authorise devices by attaching an IoT policy to a thing group instead to a device certificate. The policy attached to the thing group will make use of policy variables so that a device is only allowed to publish to: 1005 | 1006 | telemetry/building/one/${iot:ClientId} 1007 | 1008 | Create a thing group policy, attach the policy to the thing group, create a device and add the device to the thing group. 1009 | 1010 | 1011 | * Go to your EC2 instance: 1012 | 1013 | # create a thing group 1014 | THING_GROUP_NAME=building-one 1015 | aws iot create-thing-group \ 1016 | --thing-group-name $THING_GROUP_NAME > /tmp/create_group_response 1017 | GROUP_ARN=$(jq -r ".thingGroupArn" /tmp/create_group_response) 1018 | 1019 | # get your AWS account id. It is required for the IoT policy in this example 1020 | ACCOUNT_ID=$(aws sts get-caller-identity | jq -r '.Account') 1021 | 1022 | # create IoT policy 1023 | POLICY_NAME=SmartBuilding_Policy 1024 | aws iot create-policy --policy-name $POLICY_NAME \ 1025 | --policy-document "{\ 1026 | \"Version\": \"2012-10-17\",\ 1027 | \"Statement\": [{\ 1028 | \"Effect\": \"Allow\", 1029 | \"Action\": [\"iot:Connect\"],\ 1030 | \"Resource\": [\ 1031 | \"arn:aws:iot:$AWS_REGION:$ACCOUNT_ID:client/\${iot:ClientId}\"\ 1032 | ]\ 1033 | },\ 1034 | {\ 1035 | \"Effect\": \"Allow\",\ 1036 | \"Action\": [\"iot:Publish\"],\ 1037 | \"Resource\": [\ 1038 | \"arn:aws:iot:$AWS_REGION:$ACCOUNT_ID:topic/telemetry/building/one/\${iot:ClientId}\"\ 1039 | ]\ 1040 | }]\ 1041 | }" 1042 | 1043 | # attach policy to thing group 1044 | aws iot attach-policy \ 1045 | --target $GROUP_ARN \ 1046 | --policy-name $POLICY_NAME 1047 | 1048 | # verify that the policy was attached to the thing group 1049 | aws iot list-attached-policies --target $GROUP_ARN 1050 | 1051 | * Create a thing as well as key and certificate. Choose any method that you have learned earlier. **But DO NOT attach a policy to the device certificate** 1052 | 1053 | cd 1054 | THING_NAME=group-member 1055 | aws iot create-thing --thing-name $THING_NAME 1056 | aws iot create-keys-and-certificate --set-as-active \ 1057 | --public-key-outfile $THING_NAME.public.key \ 1058 | --private-key-outfile $THING_NAME.private.key \ 1059 | --certificate-pem-outfile $THING_NAME.certificate.pem > /tmp/create_cert_and_keys_response 1060 | CERTIFICATE_ARN=$(jq -r ".certificateArn" /tmp/create_cert_and_keys_response) 1061 | CERTIFICATE_ID=$(jq -r ".certificateId" /tmp/create_cert_and_keys_response) 1062 | aws iot attach-thing-principal --thing-name $THING_NAME \ 1063 | --principal $CERTIFICATE_ARN 1064 | 1065 | * Add your thing to the thing group 1066 | 1067 | aws iot add-thing-to-thing-group \ 1068 | --thing-name $THING_NAME \ 1069 | --thing-group-name $THING_GROUP_NAME 1070 | 1071 | # list things in your group 1072 | aws iot list-things-in-thing-group \ 1073 | --thing-group-name $THING_GROUP_NAME 1074 | 1075 | * Subscribe in the AWS IoT console to **telemetry/building/one/#** 1076 | * Publish a message to the topic *telemetry/building/one/$THING_NAME*. You should see that the message is arriving. 1077 | 1078 | mosquitto_pub --cafile ~/root.ca.bundle.pem \ 1079 | --cert $THING_NAME.certificate.pem \ 1080 | --key $THING_NAME.private.key -h $IOT_ENDPOINT -p 8883 \ 1081 | -q 0 -t telemetry/building/one/$THING_NAME -i $THING_NAME --tls-version tlsv1.2 \ 1082 | -m "{\"group\": \"test\", \"date\": \"$(date)\"}" -d 1083 | 1084 | * Publish a message to another topic, this messages should not arrive. 1085 | 1086 | mosquitto_pub --cafile ~/root.ca.bundle.pem \ 1087 | --cert $THING_NAME.certificate.pem \ 1088 | --key $THING_NAME.private.key -h $IOT_ENDPOINT -p 8883 \ 1089 | -q 0 -t telemetry/building/one/${THING_NAME}_foo -i $THING_NAME --tls-version tlsv1.2 \ 1090 | -m "{\"group\": \"test\", \"date\": \"$(date)\"}" -d 1091 | 1092 | 1093 | [[Top](#Top)] 1094 | ## Fine-Grained Logging 1095 | [Fine-grained logging](https://docs.aws.amazon.com/iot/latest/developerguide/cloud-watch-logs.html) allows you to specify a logging level for a target. A target is defined by a resource type and a resource name. Currently, AWS IoT supports thing groups as targets. Fine-grained logging allows you to set a logging level for a specific thing group. Fine-grained logs are stored in log group *AWSIotLogsV2*. 1096 | 1097 | **In this exercise** you will learn how to set the log level for a thing group that is different from the global log level. The global log level has been set to WARN. For a particular thing group you will set a more verbose log level. Then you will create some log entries by publishing messages with the device you used in the previous section. Then you will use different filters to search the logs. 1098 | 1099 | 1100 | * Use the thing group *building-one* that you have created earlier 1101 | 1102 | THING_GROUP_NAME=building-one 1103 | 1104 | * Get the current logging configuration 1105 | 1106 | aws iot get-v2-logging-options 1107 | 1108 | * Set the logging level to DEBUG for the thing group 1109 | 1110 | aws iot set-v2-logging-level --log-level DEBUG \ 1111 | --log-target "{\"targetType\": \"THING_GROUP\", \"targetName\": \"$THING_GROUP_NAME\"}" 1112 | 1113 | # verify the logging level 1114 | aws iot list-v2-logging-levels 1115 | 1116 | * Publish messages like you did in the previous section to a topic where you are allowed to publish and also to topics where you don't have permissions to publish 1117 | * Wait some minutes because it can take a small amount of time until logs are delivered to CloudWatch 1118 | * Now let's search in the logs for events in the last hour 1119 | 1120 | # calculate the milliseconds since the epoch minus 1h 1121 | starttime=$(($(($(date '+%s') - 3600)) * 1000)) 1122 | 1123 | # search all logs in the last hour without any filters 1124 | aws logs filter-log-events --log-group-name AWSIotLogsV2 \ 1125 | --start-time $starttime 1126 | 1127 | # search all logs where the log level is INFO 1128 | aws logs filter-log-events --log-group-name AWSIotLogsV2 \ 1129 | --start-time $starttime --filter-pattern "{$.logLevel = INFO}" 1130 | 1131 | # search all logs where the log level is ERROR 1132 | aws logs filter-log-events --log-group-name AWSIotLogsV2 \ 1133 | --start-time $starttime --filter-pattern "{$.logLevel = ERROR}" 1134 | 1135 | # search for log entries from your thing used 1136 | aws logs filter-log-events --log-group-name AWSIotLogsV2 \ 1137 | --start-time $starttime --filter-pattern "{$.clientId = $THING_NAME}" 1138 | 1139 | 1140 | 1141 | [[Top](#Top)] 1142 | ## Clean Up 1143 | With the following steps you can delete the resources created during the workshop. 1144 | 1145 | ### On your EC2 instance issue the following commands: 1146 | 1147 | #### Disable all events 1148 | Use the following command to disable all events: 1149 | 1150 | aws iot update-event-configurations --cli-input-json \ 1151 | '{ 1152 | "eventConfigurations": { 1153 | "THING_TYPE": { 1154 | "Enabled": false 1155 | }, 1156 | "JOB_EXECUTION": { 1157 | "Enabled": false 1158 | }, 1159 | "THING_GROUP_HIERARCHY": { 1160 | "Enabled": false 1161 | }, 1162 | "CERTIFICATE": { 1163 | "Enabled": false 1164 | }, 1165 | "THING_TYPE_ASSOCIATION": { 1166 | "Enabled": false 1167 | }, 1168 | "THING_GROUP_MEMBERSHIP": { 1169 | "Enabled": false 1170 | }, 1171 | "CA_CERTIFICATE": { 1172 | "Enabled": false 1173 | }, 1174 | "THING": { 1175 | "Enabled": false 1176 | }, 1177 | "JOB": { 1178 | "Enabled": false 1179 | }, 1180 | "POLICY": { 1181 | "Enabled": false 1182 | }, 1183 | "THING_GROUP": { 1184 | "Enabled": false 1185 | } 1186 | } 1187 | }' 1188 | 1189 | #### Delete the resources 1190 | Deleting the resources that where created during the workshop is a semi-automated process. We start with deprecating the thing type. 1191 | 1192 | * First action is to deprecate the thing type as it will take round about 5 minutes to finish 1193 | 1194 | # deprecate the thing type 1195 | aws iot deprecate-thing-type --thing-type-name bulk-type 1196 | 1197 | 1198 | * The things that you have created can be deleted by a script. **WARNING** this script uses the prefixes for devices in the workshop, *bulky* for the bulk provisioned devices and *jitr-* for device provisioned through JITR. 1199 | 1200 | **IF YOU HAVE ANY DEVICES CREATED OUTSIDE OF THIS WORKSHOP WHO'S NAMES START WITH "bulky" or "jitr-" they will be also deleted. In case of doubt delete the resources manually!** 1201 | 1202 | clean-up.py 1203 | 1204 | * Delete the remaining resources manually 1205 | 1206 | # disable indexing 1207 | aws iot update-indexing-configuration \ 1208 | --thing-indexing-configuration thingIndexingMode=OFF 1209 | 1210 | # delete the IoT topic rule 1211 | aws iot delete-topic-rule --rule-name JITRRule 1212 | 1213 | # delete the lambda function 1214 | aws lambda delete-function --function-name jitr 1215 | 1216 | # empty your S3 bucket. If the bucket is not empty deleting the CloudFormation stack will fail 1217 | aws s3 rm s3://$S3_BUCKET --include "*" --recursive 1218 | 1219 | # delete thing group 1220 | aws iot delete-thing-group --thing-group-name bulk-group 1221 | 1222 | # delete the smart building policy 1223 | aws iot delete-policy --policy-name SmartBuilding_Policy 1224 | 1225 | # get CA certificate id 1226 | aws iot list-ca-certificates 1227 | 1228 | # inactivate CA 1229 | aws iot update-ca-certificate \ 1230 | --new-status INACTIVE --certificate-id [YOUR_CA_CERTIFICATE_ID] 1231 | 1232 | # delete CA 1233 | aws iot delete-ca-certificate \ 1234 | --certificate-id [YOUR_CA_CERTIFICATE_ID] 1235 | 1236 | # deprecation of thing-type needs 5min. If this command is not successful, wait and try again 1237 | aws iot delete-thing-type --thing-type-name bulk-type 1238 | 1239 | 1240 | * If there are any remaining resources like things, certificates, policies, etc. delete them manually 1241 | 1242 | * Detach all remaining certificates from the IoT policy *$IOT_POLICY* 1243 | 1244 | * Go to the AWS CloudFormation console 1245 | 1246 | Delete the stack IoTDeviceManagementWS 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws-samples/aws-iot-device-management-workshop/issues), or [recently closed](https://github.com/aws-samples/aws-iot-device-management-workshop/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-samples/aws-iot-device-management-workshop/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws-samples/aws-iot-device-management-workshop/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AWS Iot Device Management Workshop 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Repository Archived - Please Note 2 | 3 | This repository has been archived and is now in read-only mode. The reason for this archival is that the content is currently being updated in a new workshop. 4 | 5 | ## Important Information: 6 | 7 | 1. **New Workshop Location**: The updated version of this workshop can be found at: https://iot-device-management.workshop.aws/en/ 8 | 9 | 2. **Read-Only Status**: As this repository is archived: 10 | - No new issues or pull requests can be opened 11 | - Existing issues and pull requests are now read-only 12 | - The code, wiki, and all other repository content are read-only 13 | - No new collaborators or teams can be added 14 | - The repository can still be forked and starred 15 | 16 | 3. **Limited Functionality**: 17 | - You cannot make any changes to this repository 18 | - Commits, branches, tags, and releases are all read-only 19 | - Comments and reactions on issues, pull requests, and commits are disabled 20 | 21 | 4. **Searchability**: This repository will still appear in search results and can be referenced 22 | 23 | We encourage you to visit the new workshop link for the most up-to-date content and interactive experience. Thank you for your understanding and continued interest in AWS IoT Device Management. 24 | 25 | ---- 26 | 27 | ## AWS IoT Device Management Workshop 28 | 29 | [AWS IoT Device Management](https://aws.amazon.com/iot-device-management/) makes it easy to securely onboard, organize, monitor, and remotely manage IoT devices at scale. With this workshop your will learn hands-on the features from AWS IoT Device Management like several onboarding options, jobs, fleet indexing, thing groups and fine grained logging. 30 | 31 | 32 | ## Files/Directories for the Workshop 33 | 34 | * [AWS\_IoT\_Device\_Management\_Workshop.md](AWS_IoT_Device_Management_Workshop.md): Workshop instructions. 35 | * bin, job-agent, lambda: Directories containing scripts that are copied onto an Amazon EC2 instance 36 | * cfn: Directory for CloudFormation template 37 | * dm-ws.tar: tar file that is used to bootstrap an EC2 instance 38 | * mk-dm-ws-tar.sh: Shell script to create dm-ws.tar. In case you change something use this script to create a new tar file 39 | * templateBody.json: template for IoT provisioning options 40 | 41 | ## License 42 | 43 | This library is licensed under the Apache 2.0 License. 44 | -------------------------------------------------------------------------------- /bin/bulk-result.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). 6 | # You may not use this file except in compliance with the License. 7 | # A copy of the License is located at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | import json 17 | import os 18 | import sys 19 | 20 | 21 | def process_line(line): 22 | d = json.loads(line) 23 | crt = d["response"]["CertificatePem"] 24 | thing = d["response"]["ResourceArns"]["thing"].split('/')[1] 25 | print("creating file {}.crt for thing {}".format(thing, thing)) 26 | file = open(thing + ".crt", "w") 27 | file.write(crt) 28 | file.close() 29 | 30 | def process_results(file): 31 | try: 32 | with open(file) as f: 33 | for line in f: 34 | process_line(line) 35 | f.close() 36 | except Exception as e: 37 | print("error opening file {}: {}".format(file,e)) 38 | return None 39 | 40 | def main(argv): 41 | if len(argv) == 0: 42 | print("usage: {} ".format(os.path.basename(__file__))) 43 | sys.exit(1) 44 | 45 | process_results(argv[0]) 46 | 47 | if __name__ == "__main__": 48 | main(sys.argv[1:]) 49 | -------------------------------------------------------------------------------- /bin/clean-up.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). 6 | # You may not use this file except in compliance with the License. 7 | # A copy of the License is located at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | # 17 | # clean-up.py 18 | # 19 | # cleans up after workshop 20 | 21 | 22 | import boto3 23 | import os 24 | import random 25 | import sys 26 | import time 27 | 28 | ####################################################################### 29 | basenames = ['bulky', 'jitr-'] 30 | thing_names = ['my-first-thing', 'my-second-thing', 'job-agent', 'group-member', 'my-jitp-device'] 31 | thing_groups = ['building-one', 'bulk-group'] 32 | ####################################################################### 33 | 34 | def delete_thing(thing_name): 35 | global policy_names 36 | print(" DELETING {}".format(thing_name)) 37 | 38 | try: 39 | r_principals = c_iot.list_thing_principals(thingName=thing_name) 40 | except Exception as e: 41 | print("ERROR listing thing principals: {}".format(e)) 42 | r_principals = {'principals': []} 43 | 44 | #print("r_principals: {}".format(r_principals)) 45 | for arn in r_principals['principals']: 46 | cert_id = arn.split('/')[1] 47 | print(" arn: {} cert_id: {}".format(arn, cert_id)) 48 | 49 | r_detach_thing = c_iot.detach_thing_principal(thingName=thing_name,principal=arn) 50 | print(" DETACH THING: {}".format(r_detach_thing)) 51 | 52 | r_upd_cert = c_iot.update_certificate(certificateId=cert_id,newStatus='INACTIVE') 53 | print(" INACTIVE: {}".format(r_upd_cert)) 54 | 55 | r_policies = c_iot.list_principal_policies(principal=arn) 56 | #print(" r_policies: {}".format(r_policies)) 57 | 58 | for pol in r_policies['policies']: 59 | pol_name = pol['policyName'] 60 | print(" pol_name: {}".format(pol_name)) 61 | policy_names[pol_name] = 1 62 | r_detach_pol = c_iot.detach_policy(policyName=pol_name,target=arn) 63 | print(" DETACH POL: {}".format(r_detach_pol)) 64 | 65 | r_del_cert = c_iot.delete_certificate(certificateId=cert_id,forceDelete=True) 66 | print(" DEL CERT: {}".format(r_del_cert)) 67 | 68 | r_del_thing = c_iot.delete_thing(thingName=thing_name) 69 | print(" DELETE THING: {}\n".format(r_del_thing)) 70 | 71 | c_iot = boto3.client('iot') 72 | c_iot_data = boto3.client('iot-data') 73 | 74 | for basename in basenames: 75 | print("BASENAME: {}".format(basename)) 76 | 77 | query_string = "thingName:" + basename + "*" 78 | print("query_string: {}".format(query_string)) 79 | 80 | # first shot 81 | response = c_iot.search_index( 82 | queryString=query_string, 83 | ) 84 | 85 | print("response:\n{}".format(response)) 86 | for thing in response["things"]: 87 | thing_names.append(thing["thingName"]) 88 | 89 | while 'nextToken' in response: 90 | next_token = response['nextToken'] 91 | print("next token: {}".format(next_token)) 92 | response = c_iot.search_index( 93 | queryString=query_string, 94 | nextToken=next_token 95 | ) 96 | print("response:\n{}".format(response)) 97 | for thing in response["things"]: 98 | thing_names.append(thing["thingName"]) 99 | 100 | #print("END WHILE") 101 | print("--------------------------------------\n") 102 | print("number of things to delete: {}\n".format(len(thing_names))) 103 | print("thing names to be DELETED:\n{}\n".format(thing_names)) 104 | print("--------------------------------------\n") 105 | 106 | raw_input("THE DEVICES IN THE LIST ABOVE WILL BE DELETED INCLUDING CERTIFICATES AND POLICIES\n== press to continue, to abort!\n") 107 | #sys.exit() 108 | 109 | policy_names = {} 110 | 111 | for thing_name in thing_names: 112 | print("THING NAME: {}".format(thing_name)) 113 | delete_thing(thing_name) 114 | time.sleep(0.5) # avoid to run into api throttling 115 | 116 | for thing_group in thing_groups: 117 | print(thing_group) 118 | r_del_grp = c_iot.delete_thing_group(thingGroupName=thing_group) 119 | print("DELETE THING GROUP: {}".format(r_del_grp)) 120 | 121 | print("detaching targets from policy {}".format(os.environ['IOT_POLICY'])) 122 | r_targets_pol = c_iot.list_targets_for_policy(policyName=os.environ['IOT_POLICY'],pageSize=250) 123 | print(r_targets_pol) 124 | for arn in r_targets_pol['targets']: 125 | print("DETACH: {}".format(arn)) 126 | r_detach_pol = c_iot.detach_policy(policyName=os.environ['IOT_POLICY'],target=arn) 127 | print("r_detach_pol: {}\n".format(r_detach_pol)) 128 | 129 | for p in policy_names: 130 | if p == os.environ['IOT_POLICY']: 131 | continue 132 | print("DELETE policy: {}".format(p)) 133 | try: 134 | r_del_pol = c_iot.delete_policy(policyName=p) 135 | print("r_del_pol: {}".format(r_del_pol)) 136 | except Exception as e: 137 | print("ERROR: {}".format(e)) 138 | -------------------------------------------------------------------------------- /bin/create-root-ca-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). 6 | # You may not use this file except in compliance with the License. 7 | # A copy of the License is located at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | # create-root-ca-bundle.sh 17 | # Get the CA certificates which could be used to sign 18 | # AWS IoT server certificates 19 | # See also: https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html 20 | 21 | ROOT_CA_FILE=$HOME/root.ca.bundle.pem 22 | cp /dev/null $ROOT_CA_FILE 23 | 24 | for ca in \ 25 | https://www.amazontrust.com/repository/AmazonRootCA1.pem \ 26 | https://www.amazontrust.com/repository/AmazonRootCA2.pem \ 27 | https://www.amazontrust.com/repository/AmazonRootCA3.pem \ 28 | https://www.amazontrust.com/repository/AmazonRootCA4.pem \ 29 | https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem; do 30 | 31 | echo "getting CA: $ca" 32 | wget -O - $ca >> $ROOT_CA_FILE 33 | 34 | done 35 | 36 | echo "Stored CA certificates in $ROOT_CA_FILE" 37 | -------------------------------------------------------------------------------- /bin/fleet-indexing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). 6 | # You may not use this file except in compliance with the License. 7 | # A copy of the License is located at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | # 17 | # fleet-indexing.py 18 | # 19 | # Adds a reported temperature to device shadow matching the "devicename*" 20 | 21 | 22 | import argparse 23 | import boto3 24 | import json 25 | import random 26 | import time 27 | 28 | parser = argparse.ArgumentParser(description='Add shadow reported.reported.temperature to the things matching basename*') 29 | parser.add_argument("-b", action="store", required=True, dest="thing_base_name", 30 | help="Basename of the things to which the shadow document should be added.") 31 | 32 | args = parser.parse_args() 33 | thing_base_name = args.thing_base_name 34 | 35 | room_number = 100 36 | 37 | def shadow_doc(): 38 | temp = random.randint(15,30) 39 | return { 40 | "state": { 41 | "reported" : { 42 | "temperature" : temp 43 | } 44 | } 45 | } 46 | 47 | 48 | c_iot = boto3.client('iot') 49 | c_iot_data = boto3.client('iot-data') 50 | 51 | query_string = "thingName:" + thing_base_name + "*" 52 | print("query_string: {}".format(query_string)) 53 | response = c_iot.search_index( 54 | # indexName='string', 55 | queryString=query_string, 56 | # nextToken='string', 57 | # maxResults=123, 58 | # queryVersion='string' 59 | ) 60 | 61 | 62 | print("response:\n{}".format(response)) 63 | 64 | for thing in response["things"]: 65 | thing_name = thing["thingName"] 66 | shadow_document = json.dumps(shadow_doc(), indent=4) 67 | print("updating shadow for thing name: {}".format(thing_name)) 68 | print("shadow document: {}".format(shadow_document)) 69 | response2 = c_iot_data.update_thing_shadow( 70 | thingName=thing_name, 71 | payload=shadow_document 72 | ) 73 | print(response2) 74 | print("adding room number {} to thing attributes".format(room_number)) 75 | response3 = c_iot.update_thing( 76 | thingName=thing_name, 77 | attributePayload={ 78 | 'attributes': { 79 | 'room_number': str(room_number) 80 | }, 81 | 'merge': True 82 | }, 83 | removeThingType=False 84 | ) 85 | print(response3) 86 | room_number += 1 87 | 88 | time.sleep(0.5) 89 | -------------------------------------------------------------------------------- /bin/mk-bulk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). 6 | # You may not use this file except in compliance with the License. 7 | # A copy of the License is located at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | set -e 17 | 18 | if [ -z $1 ] || [ -z $2 ]; then 19 | echo "usage: $0 " 20 | exit 1 21 | fi 22 | 23 | thing_name=$1 24 | num_things=$2 25 | 26 | date_time=$(date "+%Y-%m-%d_%H-%M-%S") 27 | 28 | out_dir=$thing_name-$date_time 29 | mkdir $out_dir || exit 1 30 | 31 | 32 | for i in $(seq 1 $num_things) ; do 33 | openssl req -new -newkey rsa:2048 -nodes -keyout $out_dir/$thing_name$i.key -out $out_dir/$thing_name$i.csr -subj "/C=DE/ST=Berlin/L=Berlin/O=AWS/CN=Big Orchestra" 34 | 35 | one_line_csr=$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' $out_dir/$thing_name$i.csr) 36 | 37 | echo "{\"ThingName\": \"$thing_name$i\", \"SerialNumber\": \"$i\", \"CSR\": \"$one_line_csr\"}" >> $out_dir/bulk.json 38 | done 39 | 40 | echo "output written to $out_dir/bulk.json" 41 | -------------------------------------------------------------------------------- /bin/mk-prov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). 6 | # You may not use this file except in compliance with the License. 7 | # A copy of the License is located at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | # creates paramter for provision-thing 17 | 18 | if [ -z $1 ]; then 19 | echo "usage: $0 " 20 | exit 1 21 | fi 22 | 23 | thing_name=$1 24 | 25 | openssl req -new -newkey rsa:2048 -nodes -keyout $thing_name.key -out $thing_name.csr -subj "/C=DE/ST=Berlin/L=Berlin/O=AWS/CN=Big Orchestra" 26 | 27 | one_line_csr=$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' $thing_name.csr) 28 | 29 | echo "{\"ThingName\": \"$thing_name\", \"SerialNumber\": \"$RANDOM\", \"CSR\": \"$one_line_csr\"}" 30 | -------------------------------------------------------------------------------- /cfn/cfn-iot-dm-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion" : "2010-09-09", 3 | 4 | "Description" : "AWS CloudFormation template for an IoT Device Management WS. Creates an EC2 instance and bootstraps the instance. AMI: amzn-ami-hvm-2017.09.0.20170930-x86_64-gp2", 5 | 6 | "Parameters" : { 7 | "SSHKeyName": { 8 | "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instance", 9 | "Type": "AWS::EC2::KeyPair::KeyName", 10 | "ConstraintDescription" : "Can contain only ASCII characters.", 11 | "AllowedPattern" : ".+" 12 | }, 13 | "InstanceType" : { 14 | "Description" : "EC2 instance type", 15 | "Type" : "String", 16 | "Default" : "t2.micro", 17 | "AllowedValues" : [ "t2.micro", "t2.small", "t2.medium", "t2.large" ], 18 | "ConstraintDescription" : "Must be a valid EC2 instance type" 19 | } 20 | }, 21 | 22 | "Mappings" : { 23 | "AWSRegion2AMI" : { 24 | "us-east-1" : { "AMI" : "ami-8c1be5f6" }, 25 | "us-west-2" : { "AMI" : "ami-e689729e" }, 26 | "eu-central-1" : { "AMI" : "ami-c7ee5ca8" }, 27 | "eu-west-1" : { "AMI" : "ami-acd005d5" }, 28 | "eu-west-2" : { "AMI" : "ami-1a7f6d7e" }, 29 | "ap-southeast-2" : { "AMI" : "ami-8536d6e7" }, 30 | "ap-northeast-1" : { "AMI" : "ami-2a69be4c" }, 31 | "ap-south-1" : { "AMI" : "ami-4fc58420" } 32 | } 33 | }, 34 | 35 | "Resources" : { 36 | 37 | "VPC" : { 38 | "Type" : "AWS::EC2::VPC", 39 | "Properties" : { 40 | "CidrBlock" : "192.168.128.0/24", 41 | "EnableDnsSupport" : "true", 42 | "EnableDnsHostnames" : "true", 43 | "Tags" : [ 44 | { "Key" : "CFN Stack", "Value" : { "Ref" : "AWS::StackName" } }, 45 | { "Key" : "Name", "Value" : "VPC Device Management WS 192.168.128.0/24" } 46 | ] 47 | } 48 | }, 49 | 50 | "PubSubnet" : { 51 | "Type" : "AWS::EC2::Subnet", 52 | "Properties" : { 53 | "VpcId" : { "Ref" : "VPC" }, 54 | "AvailabilityZone" : {"Fn::Join": ["", [{"Ref": "AWS::Region"}, "a" ]]}, 55 | "CidrBlock" : "192.168.128.0/25", 56 | "MapPublicIpOnLaunch" : "true", 57 | "Tags" : [ 58 | { "Key" : "CFN Stack", "Value" : { "Ref" : "AWS::StackName" } }, 59 | { "Key" : "Name", "Value" : "Public Subnet Device Management WS 192.168.128.0/25" } 60 | ] 61 | } 62 | }, 63 | 64 | "InternetGateway" : { 65 | "Type" : "AWS::EC2::InternetGateway", 66 | "Properties" : { 67 | "Tags" : [ 68 | { "Key" : "CFN Stack", "Value" : { "Ref" : "AWS::StackName" } }, 69 | { "Key" : "Name", "Value" : "Inet GW" } 70 | ] 71 | } 72 | }, 73 | 74 | "GatewayToInternet" : { 75 | "Type" : "AWS::EC2::VPCGatewayAttachment", 76 | "Properties" : { 77 | "VpcId" : { "Ref" : "VPC" }, 78 | "InternetGatewayId" : { "Ref" : "InternetGateway" } 79 | } 80 | }, 81 | 82 | "PublicRouteTable" : { 83 | "Type" : "AWS::EC2::RouteTable", 84 | "DependsOn": "GatewayToInternet", 85 | "Properties" : { 86 | "VpcId" : { "Ref" : "VPC" }, 87 | "Tags" : [ 88 | { "Key" : "CFN Stack", "Value" : { "Ref" : "AWS::StackName" } }, 89 | { "Key" : "Name", "Value" : "PublicRouteTable" } 90 | ] 91 | } 92 | }, 93 | 94 | "PublicRoute" : { 95 | "Type" : "AWS::EC2::Route", 96 | "Properties" : { 97 | "RouteTableId" : { "Ref" : "PublicRouteTable" }, 98 | "DestinationCidrBlock" : "0.0.0.0/0", 99 | "GatewayId" : { "Ref" : "InternetGateway" } 100 | } 101 | }, 102 | 103 | "PubSubnetRTAssoc" : { 104 | "Type" : "AWS::EC2::SubnetRouteTableAssociation", 105 | "Properties" : { 106 | "SubnetId" : { "Ref" : "PubSubnet" }, 107 | "RouteTableId" : { "Ref" : "PublicRouteTable" } 108 | } 109 | }, 110 | 111 | "DMWSS3Bucket" : { 112 | "Type" : "AWS::S3::Bucket" 113 | }, 114 | 115 | "DMWSIoTPolicy": { 116 | "Type": "AWS::IoT::Policy", 117 | "Properties": { 118 | "PolicyDocument": { 119 | "Version": "2012-10-17", 120 | "Statement": [ 121 | { 122 | "Effect": "Allow", 123 | "Action": [ 124 | "iot:*" 125 | ], 126 | "Resource": [ 127 | "*" 128 | ] 129 | } 130 | ] 131 | } 132 | } 133 | }, 134 | 135 | "DMWSRegLambdaJITRRole": { 136 | "Type": "AWS::IAM::Role", 137 | "Properties": { 138 | "AssumeRolePolicyDocument": { 139 | "Statement": [ { 140 | "Effect": "Allow", 141 | "Principal": { 142 | "Service": [ "lambda.amazonaws.com" ] 143 | }, 144 | "Action": [ "sts:AssumeRole" ] 145 | } ] 146 | }, 147 | "Policies": [ { 148 | "PolicyName": {"Fn::Join": ["", ["DMWSRegLambdaJITRPolicy-", {"Ref": "AWS::Region"} ]]}, 149 | "PolicyDocument": { 150 | "Version":"2012-10-17", 151 | "Statement":[ 152 | { 153 | "Effect":"Allow", 154 | "Action":[ 155 | "logs:CreateLogGroup", 156 | "logs:CreateLogStream", 157 | "logs:PutLogEvents" 158 | ], 159 | "Resource":"arn:aws:logs:*:*:*" 160 | }, 161 | { 162 | "Effect":"Allow", 163 | "Action":[ 164 | "iot:CreateThing", 165 | "iot:UpdateCertificate", 166 | "iot:CreatePolicy", 167 | "iot:AttachPolicy", 168 | "iot:DescribeCertificate", 169 | "iot:AttachThingPrincipal" 170 | ], 171 | "Resource":"*" 172 | } 173 | ] 174 | } 175 | } 176 | ], 177 | "Path": "/" 178 | } 179 | }, 180 | 181 | "DMWSIoTServiceRole": { 182 | "Type": "AWS::IAM::Role", 183 | "Properties": { 184 | "AssumeRolePolicyDocument": { 185 | "Statement": [ { 186 | "Effect": "Allow", 187 | "Principal": { 188 | "Service": [ "iot.amazonaws.com" ] 189 | }, 190 | "Action": [ "sts:AssumeRole" ] 191 | } ] 192 | }, 193 | "ManagedPolicyArns": [ 194 | "arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration", 195 | "arn:aws:iam::aws:policy/service-role/AWSIoTLogging", 196 | "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" 197 | ], 198 | "Path": "/" 199 | } 200 | }, 201 | 202 | "DMWSEC2Role": { 203 | "Type": "AWS::IAM::Role", 204 | "Properties": { 205 | "AssumeRolePolicyDocument": { 206 | "Statement": [ { 207 | "Effect": "Allow", 208 | "Principal": { 209 | "Service": [ "ec2.amazonaws.com" ] 210 | }, 211 | "Action": [ "sts:AssumeRole" ] 212 | } ] 213 | }, 214 | "Path": "/" 215 | } 216 | }, 217 | 218 | "DMWSEC2Policy" : { 219 | "Type" : "AWS::IAM::Policy", 220 | "Properties" : { 221 | "PolicyName" : {"Fn::Join": ["", ["DMWSEC2Policy-", {"Ref": "AWS::Region"} ]]}, 222 | "PolicyDocument" : { 223 | "Statement" : [ { 224 | "Effect" : "Allow", 225 | "Action" : [ 226 | "iot:*", 227 | "s3:*", 228 | "iam:PassRole", 229 | "lambda:CreateFunction", 230 | "lambda:GetFunction", 231 | "lambda:ListFunctions", 232 | "lambda:DeleteFunction", 233 | "lambda:AddPermission", 234 | "lambda:GetPolicy", 235 | "logs:FilterLogEvents", 236 | "dynamodb:PutItem", 237 | "dynamodb:GetItem", 238 | "dynamodb:Scan" 239 | ], 240 | "Resource" : "*" 241 | }] 242 | }, 243 | "Roles" : [ { "Ref" : "DMWSEC2Role" } ] 244 | } 245 | }, 246 | 247 | "DMWSInstanceProfile": { 248 | "Type": "AWS::IAM::InstanceProfile", 249 | "Properties": { 250 | "Path": "/", 251 | "Roles": [ { 252 | "Ref": "DMWSEC2Role" 253 | } ] 254 | } 255 | }, 256 | 257 | "DMWSSecurityGroup" : { 258 | "Type" : "AWS::EC2::SecurityGroup", 259 | "Properties" : { 260 | "VpcId" : { "Ref" : "VPC" }, 261 | "GroupDescription" : "Enable access to port 22", 262 | "Tags" : [ { "Key" : "Name", "Value" : "Prov WS Security Group" } ], 263 | "SecurityGroupIngress" : [ 264 | {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "0.0.0.0/0"} 265 | ] 266 | } 267 | }, 268 | 269 | "DMWSEC2Instance": { 270 | "Type": "AWS::EC2::Instance", 271 | "Properties": { 272 | "InstanceType" : { "Ref" : "InstanceType" }, 273 | "ImageId" : { "Fn::FindInMap" : [ "AWSRegion2AMI", { "Ref" : "AWS::Region" }, "AMI" ]}, 274 | "KeyName" : { "Ref" : "SSHKeyName" }, 275 | "SubnetId" : { "Ref" : "PubSubnet" }, 276 | "IamInstanceProfile" : { "Ref" : "DMWSInstanceProfile" }, 277 | "SecurityGroupIds" : [ {"Ref" : "DMWSSecurityGroup"} ], 278 | "Tags" : [ { "Key" : "Name", "Value" : "Device Management WS" } ], 279 | "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ 280 | "#!/bin/bash -v\n", 281 | "echo LANG=en_US.utf-8 >> /etc/environment\n", 282 | "echo LC_ALL=en_US.UTF-8 >> /etc/environment\n", 283 | "\n", 284 | "if ! uname -r |grep amzn ; then\n", 285 | " exit 1\n", 286 | "fi\n", 287 | "\n", 288 | "wget YOUR_STATIC_S3_WEBSITE/dm-ws.tar\n", 289 | "\n", 290 | "tar xf dm-ws.tar -C /home/ec2-user/\n", 291 | "wget http://download.opensuse.org/repositories/home:/oojah:/mqtt/CentOS_CentOS-7/home:oojah:mqtt.repo -O /etc/yum.repos.d/mqtt.repo\n", 292 | "\n", 293 | "sleep 10\n", 294 | "\n", 295 | "PATH=$PATH:/usr/local/bin\n", 296 | "pip install --upgrade pip\n", 297 | "hash -r\n", 298 | "pip install AWSIoTPythonSDK\n", 299 | "pip install urllib3\n", 300 | "pip install --upgrade awscli\n", 301 | "pip install geopy\n", 302 | "pip install boto3\n", 303 | "pip install pyOpenSSL\n", 304 | "S3_BUCKET=", { "Ref" : "DMWSS3Bucket" }, "\n", 305 | "echo \"export S3_BUCKET=$S3_BUCKET\" >> /home/ec2-user/.bash_profile\n", 306 | "echo \"complete -C '/usr/local/bin/aws_completer' aws\" >> /home/ec2-user/.bash_profile\n", 307 | "REGION=", { "Ref" : "AWS::Region" }, "\n", 308 | "mkdir /home/ec2-user/CA\n", 309 | "mkdir /home/ec2-user/.aws\n", 310 | "echo '[default]' > /home/ec2-user/.aws/config\n", 311 | "echo 'output = json' >> /home/ec2-user/.aws/config\n", 312 | "echo \"region = $REGION\" >> /home/ec2-user/.aws/config\n", 313 | "chmod 400 /home/ec2-user/.aws/config\n", 314 | "rm -f /tmp/*\n", 315 | "yum -y install telnet jq strace git tree\n", 316 | "yum -y install mosquitto-clients\n", 317 | "ARN_LAMBDA_ROLE=", { "Fn::GetAtt" : ["DMWSRegLambdaJITRRole", "Arn"] }, "\n", 318 | "ARN_IOT_PROVISIONING_ROLE=", { "Fn::GetAtt" : ["DMWSIoTServiceRole", "Arn"] }, "\n", 319 | "IOT_ENDPOINT=$(aws iot describe-endpoint --region $REGION | jq -r '.endpointAddress')\n", 320 | "IOT_POLICY=", { "Ref": "DMWSIoTPolicy" }, "\n", 321 | "echo \"export IOT_ENDPOINT=$IOT_ENDPOINT\" >> /home/ec2-user/.bash_profile\n", 322 | "echo \"export IOT_POLICY=$IOT_POLICY\" >> /home/ec2-user/.bash_profile\n", 323 | "echo \"export AWS_REGION=$REGION\" >> /home/ec2-user/.bash_profile\n", 324 | "echo \"export ARN_LAMBDA_ROLE=$ARN_LAMBDA_ROLE\" >> /home/ec2-user/.bash_profile\n", 325 | "echo \"export ARN_IOT_PROVISIONING_ROLE=$ARN_IOT_PROVISIONING_ROLE\" >> /home/ec2-user/.bash_profile\n", 326 | "chown -R ec2-user:ec2-user /home/ec2-user/\n", 327 | "\n", 328 | "exit 0\n" 329 | ]]}} 330 | } 331 | } 332 | }, 333 | 334 | "Outputs" : { 335 | "SSHLogin" : { 336 | "Description" : "SSH login string", 337 | "Value" : { "Fn::Join" : ["", ["ssh -i ", { "Ref" : "SSHKeyName" }, " ec2-user@", { "Fn::GetAtt" : [ "DMWSEC2Instance", "PublicDnsName" ]}]] } 338 | }, 339 | "S3Bucket" : { 340 | "Description" : "Name of the S3 Bucket for the Device Management workshop", 341 | "Value" : { "Ref" : "DMWSS3Bucket" } 342 | }, 343 | "IoTPolicy" : { 344 | "Description" : "Name of the IoT policy for JITP", 345 | "Value" : { "Ref": "DMWSIoTPolicy" } 346 | }, 347 | "ArnIoTProvRole" : { 348 | "Description" : "Role Arn for IoT device provisiong", 349 | "Value" : { "Fn::GetAtt" : ["DMWSIoTServiceRole", "Arn"] } 350 | }, 351 | "ArnLambdaRole" : { 352 | "Description" : "Role Arn for the JITR Lambda function", 353 | "Value" : { "Fn::GetAtt" : ["DMWSRegLambdaJITRRole", "Arn"] } 354 | } 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /dm-ws.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-iot-device-management-workshop/dcac8117bce75c037d091014550da23575c72dc7/dm-ws.tar -------------------------------------------------------------------------------- /img/AWS_IoT_Core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-iot-device-management-workshop/dcac8117bce75c037d091014550da23575c72dc7/img/AWS_IoT_Core.png -------------------------------------------------------------------------------- /job-agent/job-agent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # coding: utf-8 4 | 5 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"). 8 | # You may not use this file except in compliance with the License. 9 | # A copy of the License is located at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # or in the "license" file accompanying this file. This file is distributed 14 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 15 | # express or implied. See the License for the specific language governing 16 | # permissions and limitations under the License. 17 | 18 | # IoT Job Agent 19 | # 20 | # Sample agent which handles IoT jobs. 21 | # 22 | 23 | # Libraries, Logging 24 | import argparse 25 | import AWSIoTPythonSDK 26 | import json 27 | import logging 28 | import time 29 | import sys 30 | import uuid 31 | from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient 32 | from datetime import timedelta 33 | 34 | # Configure logging 35 | logger = logging.getLogger("AWSIoTPythonSDK.core") 36 | #logger.setLevel(logging.DEBUG) 37 | logger.setLevel(logging.INFO) 38 | streamHandler = logging.StreamHandler() 39 | formatter = logging.Formatter("[%(asctime)s - %(levelname)s - %(filename)s:%(lineno)s - %(funcName)s - %(message)s") 40 | #formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 41 | streamHandler.setFormatter(formatter) 42 | logger.addHandler(streamHandler) 43 | 44 | # 45 | # parse command line args 46 | # 47 | parser = argparse.ArgumentParser(description='Sample job agent for AWS IoT Device Management') 48 | parser.add_argument("-c", "--client-id", action="store", required=True, dest="client_id", 49 | help="Name of your device. The client-id is also used in the job topics.") 50 | parser.add_argument("-i", "--iot-endpoint", action="store", required=True, dest="iot_endpoint", 51 | help="AWS IoT Endpoint (host) where the job agent connects to.") 52 | parser.add_argument("--cacert", action="store", required=True, dest="root_ca_cert", 53 | help="CA that signed the server certificate from AWS IoT Core.") 54 | parser.add_argument("--cert", action="store", required=True, dest="device_cert", 55 | help="Device certificate.") 56 | parser.add_argument("--key", action="store", required=True, dest="device_key", 57 | help="Device private key.") 58 | 59 | args = parser.parse_args() 60 | client_id = args.client_id 61 | iot_endpoint = args.iot_endpoint 62 | root_ca_cert = args.root_ca_cert 63 | device_cert = args.device_cert 64 | device_key = args.device_key 65 | 66 | def callback_jobs_get_rejected(client, userdata, message): 67 | #logger.info("client: {}".format(client)) 68 | #logger.info("userdata: {}".format(userdata)) 69 | logger.info("message on topic: {}".format(message.topic)) 70 | logger.info(message.payload + "\n") 71 | 72 | def callback_jobs_get_accepted(client, userdata, message): 73 | logger.info("message on topic: {}".format(message.topic)) 74 | logger.info(message.payload) 75 | 76 | payload = json.loads(message.payload) 77 | 78 | if "queuedJobs" in payload: 79 | time.sleep(2) 80 | for job in payload["queuedJobs"]: 81 | logger.info("job: {}".format(job)) 82 | time.sleep(2) 83 | 84 | if len(payload["queuedJobs"]) >= 1: 85 | logger.info("JOBS AVAILABLE") 86 | logger.info("found {} queued jobs".format(payload["queuedJobs"])) 87 | time.sleep(5) 88 | client_token = str(uuid.uuid4()) 89 | job_id = payload["queuedJobs"][0]["jobId"] 90 | logger.info("taking job with job_id: {}".format(job_id)) 91 | time.sleep(5) 92 | message = { "clientToken": client_token } 93 | topic = '$aws/things/' + client_id + '/jobs/' + job_id + '/get' 94 | logger.info("DescribeJobExecution: publish: topic: {} message: {}".format(topic, message)) 95 | myAWSIoTMQTTClient.publish(topic, json.dumps(message), 0) 96 | time.sleep(2) 97 | else: 98 | logger.info("NO jobs available") 99 | time.sleep(1) 100 | 101 | def callback_job_id_get_accepted(client, userdata, message): 102 | #logger.info("client: {}".format(client)) 103 | #logger.info("userdata: {}".format(userdata)) 104 | logger.info("message on topic: {}".format(message.topic)) 105 | logger.info(message.payload) 106 | 107 | payload = json.loads(message.payload) 108 | 109 | if "execution" in payload: 110 | if "status" in payload["execution"] and payload["execution"]["status"] == "QUEUED": 111 | if "jobDocument" in payload["execution"]: 112 | logger.info("found job document, calling job document processor") 113 | time.sleep(5) 114 | process_job_document(payload["execution"]["jobId"], payload["execution"]["jobDocument"]) 115 | else: 116 | logger.warn("job in status {} will not be processed".format(payload["execution"]["status"])) 117 | time.sleep(1) 118 | 119 | 120 | def callback_jobs_notify_next(client, userdata, message): 121 | #logger.info("client: {}".format(client)) 122 | #logger.info("userdata: {}".format(userdata)) 123 | logger.info("message on topic: {}".format(message.topic)) 124 | logger.info(message.payload) 125 | 126 | def callback_job_id_update(client, userdata, message): 127 | logger.info("message on topic: {}".format(message.topic)) 128 | logger.info(message.payload) 129 | 130 | def ack_callback_subscribe(mid, data): 131 | logger.info("mid: {} data: {}".format(mid, data)) 132 | 133 | def ack_callback_unsubscribe(mid): 134 | logger.info("mid: {}".format(mid)) 135 | 136 | def process_job_document(job_id, job_document): 137 | logger.info("job_id: {} job_document: {}".format(job_id, job_document)) 138 | time.sleep(2) 139 | 140 | # subscribe to update topix for a certain job_id 141 | topic_update_job = '$aws/things/' + client_id + '/jobs/' + str(job_id) + '/update/#' 142 | logger.info("UpdateJobExecution: receive responses: subcribeAsync to topic {}".format(topic_update_job)) 143 | packet_id = myAWSIoTMQTTClient.subscribeAsync(topic_update_job, 0, ackCallback=ack_callback_subscribe, messageCallback=callback_job_id_update) 144 | logger.info("packet_id: {}".format(packet_id)) 145 | time.sleep(2) 146 | 147 | # update_job_execution 148 | topic_job_exec_update = '$aws/things/' + client_id + '/jobs/' + str(job_id) + '/update' 149 | client_token = str(uuid.uuid4()) 150 | message = { 151 | "status": "IN_PROGRESS", 152 | "clientToken": client_token 153 | } 154 | logger.info("UpdateJobExecution: topic: {} message: {}".format(topic_job_exec_update, message)) 155 | myAWSIoTMQTTClient.publish(topic_job_exec_update, json.dumps(message), 0) 156 | time.sleep(2) 157 | 158 | upt_msg = { "thing-id": client_id, "uptime": uptime() } 159 | logger.info("PUBLISH UPTIME - topic: {} message: {}".format(job_document["topic"], upt_msg)) 160 | myAWSIoTMQTTClient.publish(job_document["topic"], json.dumps(upt_msg), 0) 161 | time.sleep(5) 162 | 163 | message = { 164 | "status": "SUCCEEDED", 165 | "clientToken": client_token 166 | } 167 | logger.info("UpdateJobExecution: topic: {} message: {}".format(topic_job_exec_update, message)) 168 | myAWSIoTMQTTClient.publish(topic_job_exec_update, json.dumps(message), 0) 169 | time.sleep(2) 170 | 171 | packet_id = myAWSIoTMQTTClient.unsubscribeAsync(topic_update_job, ackCallback=ack_callback_unsubscribe) 172 | logger.info("packet_id: {}".format(packet_id)) 173 | time.sleep(2) 174 | 175 | # function to get system uptime 176 | def uptime(): 177 | with open('/proc/uptime', 'r') as file: 178 | return str(timedelta(seconds = float(file.readline().split()[0]))) 179 | 180 | 181 | # Connect to AWS IoT 182 | logger.info("connecting to endpoint {} with client_id {}".format(iot_endpoint, client_id)) 183 | 184 | # Init AWSIoTMQTTClient 185 | myAWSIoTMQTTClient = None 186 | myAWSIoTMQTTClient = AWSIoTMQTTClient(client_id) 187 | myAWSIoTMQTTClient.configureEndpoint(iot_endpoint, 8883) 188 | myAWSIoTMQTTClient.configureCredentials(root_ca_cert, device_key, device_cert) 189 | 190 | # AWSIoTMQTTClient connection configuration 191 | myAWSIoTMQTTClient.configureAutoReconnectBackoffTime(1, 32, 20) 192 | myAWSIoTMQTTClient.configureOfflinePublishQueueing(-1) # Infinite offline Publish queueing 193 | myAWSIoTMQTTClient.configureDrainingFrequency(2) # Draining: 2 Hz 194 | myAWSIoTMQTTClient.configureConnectDisconnectTimeout(10) # 10 sec 195 | myAWSIoTMQTTClient.configureMQTTOperationTimeout(5) # 5 sec 196 | 197 | # Connect and subscribe to AWS IoT 198 | myAWSIoTMQTTClient.connect() 199 | 200 | 201 | # Subscribe to the Job Topics 202 | 203 | get_jobs_accepted_topic = '$aws/things/' + client_id + '/jobs/get/accepted' 204 | get_jobs_rejected_topic = '$aws/things/' + client_id + '/jobs/get/rejected' 205 | get_job_id_accepted_topic = '$aws/things/' + client_id + '/jobs/+/get/accepted' 206 | jobs_notify_next_topic = '$aws/things/' + client_id + '/jobs/notify-next' 207 | 208 | logger.info("GetPendingJobExecutions - subscribe: topics: {}, {}, {}, {}".format( 209 | get_jobs_accepted_topic, get_jobs_rejected_topic, get_job_id_accepted_topic, jobs_notify_next_topic)) 210 | 211 | try: 212 | myAWSIoTMQTTClient.subscribe(get_jobs_accepted_topic, 0, callback_jobs_get_accepted) 213 | myAWSIoTMQTTClient.subscribe(get_jobs_rejected_topic, 0, callback_jobs_get_rejected) 214 | myAWSIoTMQTTClient.subscribe(get_job_id_accepted_topic, 0, callback_job_id_get_accepted) 215 | myAWSIoTMQTTClient.subscribe(jobs_notify_next_topic, 0, callback_jobs_notify_next) 216 | time.sleep(2) 217 | logger.info("done subscribing") 218 | except Exception as e: 219 | logger.error("failed to subscribe to topics: {}".format(e)) 220 | 221 | 222 | 223 | # endless loop to get pending jobs 224 | while True: 225 | client_token = str(uuid.uuid4()) 226 | get_pending_jobs_execution_topic = '$aws/things/' + client_id + '/jobs/get' 227 | get_pending_jobs_execution_message = { "clientToken": client_token } 228 | 229 | try: 230 | logger.info("GetPendingJobExecutions: topic: {} message: {}".format(get_pending_jobs_execution_topic, get_pending_jobs_execution_message)) 231 | myAWSIoTMQTTClient.publish(get_pending_jobs_execution_topic, json.dumps(get_pending_jobs_execution_message), 0) 232 | time.sleep(2) 233 | except Exception as e: 234 | logger.error("GetPendingJobExecutions - publish failed: {}".format(e)) 235 | 236 | time.sleep(15) 237 | 238 | 239 | myAWSIoTMQTTClient.unsubscribe(get_jobs_accepted_topic) 240 | myAWSIoTMQTTClient.unsubscribe(get_jobs_rejected_topic) 241 | myAWSIoTMQTTClient.unsubscribe(jobs_notify_next_topic) 242 | 243 | myAWSIoTMQTTClient.disconnect() 244 | -------------------------------------------------------------------------------- /job-agent/job-document.json: -------------------------------------------------------------------------------- 1 | { 2 | "operation": "sys-info", 3 | "sys-info": "uptime", 4 | "topic": "sys/info" 5 | } 6 | -------------------------------------------------------------------------------- /lambda/lambda_function.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). 4 | # You may not use this file except in compliance with the License. 5 | # A copy of the License is located at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | # 15 | # lambda function for just-in-time registration 16 | # 17 | 18 | #required libraries 19 | import boto3 20 | import logging 21 | import os 22 | 23 | # configure logging 24 | logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s') 25 | logger = logging.getLogger() 26 | logger.setLevel(logging.INFO) 27 | 28 | def create_thing(c_iot, thing_name): 29 | try: 30 | response = c_iot.create_thing(thingName='jitr-' + thing_name) 31 | logger.info("create_thing: response: {}".format(response)) 32 | except Exception as e: 33 | logger.error("create_thing: {}".format(e)) 34 | 35 | def create_iot_policy(c_iot, policy_name): 36 | try: 37 | response = c_iot.create_policy( 38 | policyName=policy_name, 39 | policyDocument='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action": "iot:*","Resource":"*"}]}' 40 | ) 41 | logger.info("create_iot_policy: response: {}".format(response)) 42 | except Exception as e: 43 | logger.error("create_iot_policy: {}".format(e)) 44 | 45 | def activate_certificate(c_iot, certificate_id): 46 | try: 47 | response = c_iot.update_certificate( 48 | certificateId=certificate_id, 49 | newStatus='ACTIVE' 50 | ) 51 | logger.info("activate_cert: response: {}".format(response)) 52 | except Exception as e: 53 | logger.error("activate_certificate: {}".format(e)) 54 | 55 | def attach_policy(c_iot, certificate_id): 56 | try: 57 | response = c_iot.describe_certificate( 58 | certificateId=certificate_id 59 | ) 60 | #logger.info("describe_certificate: response: {}".format(response)) 61 | certificate_arn = response['certificateDescription']['certificateArn'] 62 | logger.info("certificate_arn: {}".format(certificate_arn)) 63 | 64 | response = c_iot.attach_thing_principal( 65 | thingName='jitr-' + certificate_id, 66 | principal=certificate_arn 67 | ) 68 | logger.info("attach_thing_principal: response: {}".format(response)) 69 | 70 | response = c_iot.attach_policy( 71 | policyName=certificate_id, 72 | target=certificate_arn 73 | ) 74 | logger.info("attach_policy: response: {}".format(response)) 75 | except Exception as e: 76 | logger.error("attach_policy: {}".format(e)) 77 | 78 | def lambda_handler(event, context): 79 | logger.info("event:\n" + str(event)) 80 | 81 | region = os.environ["AWS_REGION"] 82 | logger.info("region: {}".format(region)) 83 | 84 | ca_certificate_id = event['caCertificateId'] 85 | certificate_id = event['certificateId'] 86 | certificate_status = event['certificateStatus'] 87 | 88 | logger.info("ca_certificate_id: " + ca_certificate_id) 89 | logger.info("certificate_id: " + certificate_id) 90 | logger.info("certificate_status: " + certificate_status) 91 | 92 | c_iot = boto3.client('iot') 93 | create_thing(c_iot, certificate_id) 94 | create_iot_policy(c_iot, certificate_id) 95 | activate_certificate(c_iot, certificate_id) 96 | attach_policy(c_iot, certificate_id) 97 | 98 | 99 | #for k in os.environ.keys(): 100 | # logger.info("{}: {}".format(k, os.environ[k])) 101 | 102 | return True 103 | -------------------------------------------------------------------------------- /lambda/lambda_function.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-iot-device-management-workshop/dcac8117bce75c037d091014550da23575c72dc7/lambda/lambda_function.zip -------------------------------------------------------------------------------- /mk-dm-ws-tar.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). 6 | # You may not use this file except in compliance with the License. 7 | # A copy of the License is located at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | # mk-dm-ws-tar.sh 17 | # create tar file which is used to bootstrap an EC2 instance 18 | 19 | echo "create lambda zip" 20 | cd lambda && zip lambda_function.zip lambda_function.py && cd .. 21 | 22 | echo "create dm-ws.tar" 23 | tar cvf dm-ws.tar templateBody.json \ 24 | bin/ lambda/ \ 25 | job-agent/job-agent.py job-agent/job-document.json 26 | 27 | -------------------------------------------------------------------------------- /templateBody.json: -------------------------------------------------------------------------------- 1 | { 2 | "Parameters" : { 3 | "ThingName" : { 4 | "Type" : "String" 5 | }, 6 | "SerialNumber" : { 7 | "Type" : "String" 8 | }, 9 | "Location" : { 10 | "Type" : "String", 11 | "Default" : "WA" 12 | }, 13 | "CSR" : { 14 | "Type" : "String" 15 | } 16 | }, 17 | "Resources" : { 18 | "thing" : { 19 | "Type" : "AWS::IoT::Thing", 20 | "Properties" : { 21 | "ThingName" : {"Ref" : "ThingName"}, 22 | "AttributePayload" : { "version" : "v1", "serialNumber" : {"Ref" : "SerialNumber"}}, 23 | "ThingTypeName" : "bulk-type", 24 | "ThingGroups" : ["bulk-group"] 25 | } 26 | }, 27 | "certificate" : { 28 | "Type" : "AWS::IoT::Certificate", 29 | "Properties" : { 30 | "CertificateSigningRequest": {"Ref" : "CSR"}, 31 | "Status" : "ACTIVE" 32 | } 33 | }, 34 | "policy" : { 35 | "Type" : "AWS::IoT::Policy", 36 | "Properties" : { 37 | "PolicyDocument": "{\"Version\": \"2012-10-17\",\"Statement\": [{\"Effect\": \"Allow\",\"Action\": [\"iot:*\"],\"Resource\": [\"*\"]}]}" 38 | } 39 | } 40 | } 41 | } 42 | --------------------------------------------------------------------------------