├── .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 | 
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 |
--------------------------------------------------------------------------------