Home

Awesome

aws-iot-elf

An AWS IoT python example client that strives to be Extremely Low Friction (ELF)

Overview

The AWS IoT ELF python example client demonstrates how one can Create Things, Send messages to Things, Subscribe to topics to receive messages from Things, and Clean up Things in the AWS IoT service using the AWS SDK for Python (aka. boto3). This example also demonstrates how to bring together boto3 and the standard AWS IoT Device client in a straightforward fashion.

Create Thing(s)

Once the getting started guide below has been completed, to create a single Thing in the AWS IoT service, simply type:

  python elf.py create

To create a given number of Things (eg. 3) in the AWS IoT service, type:

  python elf.py create 3

Send Messages

To send messages using previously created Things, type:

  python elf.py send

Subscribe to Topics

To receive messages from a topic, type:

  python elf.py subscribe

Clean Thing(s)

To clean up all previously created Things, type:

  python elf.py clean

Getting Started

To get this example working with Python 2.7.11+ on a flavor of UNIX or Mac OS. First ensure you have Python 2.7.11 on your machine by executing this command line command python --version. If you don't have Python locally, homebrew can help you get the latest Python on Mac OS; Python.org can start you off for Python on Linux/UNIX flavors. If you're starting out on Windows, follow the Windows Getting Started and return to this point after completion. Alternatively, ELF can be run as a Docker container; instructions for building an ELF Docker image can be found in the Docker Getting Started.

Now with a working Python and Git installation, clone this repo to your local machine.

  cd ~
  mkdir dev
  cd ~/dev
  git clone https://github.com/awslabs/aws-iot-elf.git

This will create a local folder ~/dev/aws-iot-elf.

To keep the AWS IoT ELF python dependencies separate, you probably want to install virtualenv. If you choose to install virtualenv then create a virtual environment:

  cd ~/dev/aws-iot-elf
  virtualenv venv

...and then activate that virtual environment

  source ~/dev/aws-iot-elf/venv/bin/activate

...or on Windows

  .\venv\Scripts\activate

Now install the AWS IoT ELF dependencies into your local environment using these commands:

  cd ~/dev/aws-iot-elf
  pip install -r requirements.txt

Next, install and configure the AWS CLI.

When you configure the AWS CLI, the API Keys you install as the default profile or as a named profile should have at least the following privileges in an associated IAM policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ELFStmt20160531",
            "Effect": "Allow",
            "Action": [
                "iot:AttachPrincipalPolicy",
                "iot:AttachThingPrincipal",
                "iot:CreateKeysAndCertificate",
                "iot:CreatePolicy",
                "iot:CreateThing",
                "iot:CreateTopicRule",
                "iot:DeleteCertificate",
                "iot:DeletePolicy",
                "iot:DeleteThing",
                "iot:DeleteTopicRule",
                "iot:DescribeEndpoint",
                "iot:DetachPrincipalPolicy",
                "iot:DetachThingPrincipal",
                "iot:ReplaceTopicRule",
                "iot:UpdateCertificate",
                "iot:UpdateThing"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Now to Authenticate with AWS IoT using Server Authentication you will need to download the Verisign root CA and save it as a file aws-iot-rootCA.crt, or if you are on Linux / UNIX / MacOS simply execute this command in the same directory as elf.py.

  curl -o aws-iot-rootCA.crt https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem

Lastly, you will probably want to read through the Troubleshooting section at the bottom of these instructions, just in case you experience a bump.

To validate the AWS IoT ELF is setup correctly, execute python elf.py create and python elf.py clean. You should not see any errors.

Congratulations! The ELF and a minimal development environment are now configured on your machine.

Detailed Help

Defaults

The AWS IoT ELF uses the following defaults:

Create Thing(s)

Using the create command will invoke the create_things(cli) function with the given command line arguments.

To create a given number of Things (eg. 3) in the AWS IoT service in a specific region, type:

  python elf.py --region <region_name> create 3

This command results in three numbered things: thing_0, thing_1, and thing_2 being created in <region_name>.

To create a single Thing in the AWS IoT service using a different AWS CLI profile, type:

  python elf.py --profile <profile_name> create

To create a single Thing in the AWS IoT service in a specific region using a different AWS CLI profile, type:

  python elf.py --region <region_name> --profile <profile_name> create

Calling the create command with a --region and/or --profile CLI option means that the Things will be created in that region and will use the corresponding AWS CLI named profile API Key and Secret Key pair. Additional send, subscribe, and clean commands should use the same options. In this way the AWS IoT ELF will send messages to the same region and with the same profile used to create the Things in the first place. Once clean is called successfully, different --region and/or --profile option values can be used to orient the AWS IoT ELF differently.

When looking through the create_thing(cli) function, the core of the create command is shown in these lines of code:

...
    # generate a numbered thing name
    t_name = thing_name_template.format(i)
    # Create a Key and Certificate in the AWS IoT Service per Thing
    keys_cert = iot.create_keys_and_certificate(setAsActive=True)
    # Create a named Thing in the AWS IoT Service
    iot.create_thing(thingName=t_name)
    # Attach the previously created Certificate to the created Thing
    iot.attach_thing_principal(
        thingName=t_name, principal=keys_cert['certificateArn'])
...

Everything else around this code is to prepare for, or record the results of, the invocation of these functions. Note: The thing policy is created and attached in the send step.

Lastly, when you run the create command you can see in the example output below that a thing named thing_0 was created and associated with a certificate in region us-west-2.

$ python elf.py create
..iot-elf:INFO - Read ELF ID from config: 95bb3ad5-95d1-4c9a-b818-b23f92fcde30
..iot-elf:INFO - [create_things] ELF creating 1 thing
..iot-elf:INFO - Thing:'thing_0' associated with cert:'arn:aws:iot:us-west-2:EXAMPLE1261:cert/EXAMPLEEXAMPLEEXAMPLEb326b72e76c2f67ccd6f8ec15515a9bd28168b2cc42'
..iot-elf:INFO - Thing Name: thing_0 and PEM file: ~/dev/aws-iot-elf/misc/thing_0.pem
..iot-elf:INFO - Thing Name: thing_0 Public Key File: ~/dev/aws-iot-elf/misc/thing_0.pub
..iot-elf:INFO - Thing Name: thing_0 Private Key File: ~/dev/aws-iot-elf/misc/thing_0.prv
..iot-elf:INFO - Wrote 1 things to config file: ~/dev/aws-iot-elf/misc/things.json
..iot-elf:INFO - [create_things] ELF created 1 things in region:'us-west-2'.

Send Messages

Using the send command will invoke the send_messages(cli) function with the given command line arguments.

To send a specific message on a specific topic for a specified duration in another region, type:

  python elf.py --region <region_name> send --topic 'elf/example' --duration <num_seconds> 'Example ELF message'

To send a JSON payload as a message read from a file named example.json, type:

  python elf.py --region <region_name> send --json-message example.json --append-thing-name

...which will result in messages similar to the following, sent as shown in the example output:

...iot-elf:INFO - ELF thing_0 posting message:'{'some': 'thing', 'another': 'thing', 'ts': '1465257133.82'}' on topic: elf/thing_0
...iot-elf:INFO - ELF thing_0 posting message:'{'some': 'thing', 'another': 'thing', 'ts': '1465257134.82'}' on topic: elf/thing_0

To send a JSON payload as a thing_0 shadow update read from a file named example-shadow.json, type:

  python elf.py send --json-message example-shadow.json --topic '$aws/things/thing_0/shadow/update'

Note: The quotes around the --topic value are important, otherwise the $aws portion of the value will possibly be interpreted as a shell variable.

Subscribe to Topic(s)

Using the subscribe command will invoke the subscribe(cli) function with the given command line arguments.

To subscribe to messages at the default topic root of elf for a specified duration, type:

  python elf.py subscribe --duration <num_seconds>

To subscribe to messages at the default topic root of elf for a specified duration in another region, type:

  python elf.py --region <region_name> subscribe --duration <num_seconds>

To send messages from ELF Y to ELF X through the AWS IoT service, open two command line windows (aka. ELF X and ELF Y). In the ELF X window, type:

  python elf.py subscribe --duration 30 --append-thing-name

...and in the ELF Y window, type:

  python elf.py send --duration 15 --append-thing-name

...which, in a matter of seconds, will result in messages shown in the ELF X window similar to this example output:

...iot-elf:INFO - Received message: {"msg": "IoT ELF Hello", "ts": "1468604634.27"} from topic: elf/thing_0
...iot-elf:INFO - Received message: {"msg": "IoT ELF Hello", "ts": "1468604635.28"} from topic: elf/thing_0

Clean Thing(s)

Using the clean command will invoke the clean_up(cli) function with the given command line arguments. This will remove all resources that were created by ELF in the AWS IoT service and on the local file system.

To clean up all previously created resources, type:

  python elf.py clean

If you want to force only a clean up of the locally stored files, without cleaning the resources created in the AWS IoT service, type:

  python elf.py clean --only-local

When looking through the clean_up(cli) function, the core of the clean command is shown in these lines of code:

..snip..
    iot.detach_principal_policy(
        policyName=thing[policy_name_key],
        principal=thing['certificateArn']
    )
..snip..
    iot.delete_policy(
        policyName=thing[policy_name_key]
    )
..snip..
    iot.update_certificate(
        certificateId=thing['certificateId'],
        newStatus='INACTIVE'
    )
..snip..
    iot.detach_thing_principal(
        thingName=thing_name,
        principal=thing['certificateArn']
    )
..snip..
    iot.delete_certificate(certificateId=thing['certificateId'])
..snip..
    iot.delete_thing(thingName=thing_name)

All steps prior to delete_thing are required, in order to detach and clean up a fully functional and authorized Thing.

Help

For additional detailed help and configuration options, enter:

  python elf.py --help
..or..
  python elf.py create --help
  python elf.py send --help
  python elf.py subscribe --help
  python elf.py clean --help

Troubleshooting

Q: When I type in my command line I get a parameter parsing error. For example this command line gives an error:

$ python elf.py clean --region us-east-1
usage: elf.py [-h] [--region REGION] [--profile PROFILE_NAME]
              {create,send,clean} ...
elf.py: error: unrecognized arguments: --region us-east-1

A: This example error is caused when using the ELF Command Line because the order of the commands and options matters. The general structure of all ELF commands are: python elf.py [global-elf-options] <command> [command-specific-options]. The [global-elf-options] should be the same across commands. The [command-specific-options] are specific to any given command. You can learn more about the various options by getting detailed help on the command line.

Q: I see an UNKNOWN_PROTOCOL error similar to the following. Why?

File "..snip../python2.7/ssl.py", line 808, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: UNKNOWN_PROTOCOL] unknown protocol

A: The Python installation in use does not support TLSv1_2 which is required by the AWS IoT service. Upgrade the Python installation to 2.7.11+.

Q: I seem to need to upgrade my openssl and python installations. Why?

A: A version of Python 2.7 ssl with support for Open SSL 1.0.1 is necessary to support the security posture (and specifically TLSv1_2) required by the AWS IoT service.

Q: When I try to send messages, I see a ResourceAlreadyExistsException similar to the following. What might be wrong?

...example...
botocore.exceptions.ClientError: An error occurred (ResourceAlreadyExistsException) when calling the
CreatePolicy operation: Policy cannot be created - name already exists (name=policy-thing_0)

A: In this example exception, for some reason the policy name policy-thing_0 already exists and is colliding with the new policy to be created and applied to the Thing. The old existing policy needs to be Detached and Deleted manually using the AWS CLI or AWS IoT Console.

Q: When I try to create, send, or clean, I see an AccessDeniedException similar to the following. What might be wrong?

...example...
botocore.exceptions.ClientError: An error occurred (AccessDeniedException) when calling the
CreateKeysAndCertificate operation: User: arn:aws:iam::XXXXXXYYYYYY:user/elf is not
authorized to perform: iot:CreateKeysAndCertificate

A: In this example exception, the user elf does not have enough privilege to perform the iot:CreateKeysAndCertificate action on the AWS IoT service. Make sure the privileges as described in the Getting Started section are associated with the user or --profile (and specifically the API keys) experiencing the exception.

Q: When I try to send messages using my recently created Things, I see a ResourceNotFoundException similar to the following. What might be wrong?

...example...
botocore.exceptions.ClientError: An error occurred (ResourceNotFoundException) when calling the
AttachPrincipalPolicy operation: The certificate given in the principal does not exist.

A: In this example exception, the certificate recorded into the AWS IoT ELF config file does not exist in the region. Most likely the create command was called with a --region option that is not the same as the --region used when calling the send command.

Related Resources