#aws-iot #iot #aws #mqtt #mqtt-client #client-builder #mqtt5

gneiss-mqtt-aws

AWS IoT Core specific builders for asynchronous and threaded MQTT clients

2 unstable releases

new 0.3.0 Apr 29, 2024
0.2.0 Jan 14, 2024

#309 in WebSocket

Apache-2.0

1MB
18K SLoC

gneiss-mqtt-aws

A set of easy-to-use MQTT client builders for working with AWS IoT Core.

No-fuss configuration for:

Feedback is always welcome.

License

All projects in this space are licensed under the Apache 2.0 License.


lib.rs:

This crate provides a builder API for creating MQTT clients that connect to AWS IoT Core, an AWS-managed message broker that supports both MQTT5 and MQTT311. This crate depends on gneiss-mqtt, which contains the MQTT client implementations.

IoT Core supports three different ways to securely establish an MQTT connection:

  • MQTT over mTLS - provide an X509 certificate (registered with AWS IoT Core) and its associated private key
  • MQTT over Websockets - sign the websocket upgrade request with AWS credentials using the Sigv4 signing algorithm
  • MQTT with Custom Authentication - invoke an AWS Lambda with data fields passed via the MQTT username and password fields in the Connect packet

This crate's builder does all the dirty work for each of these connection methods, letting you just supply the minimal required data.

Usage

To use this crate, you'll first need to add it and gneiss-mqtt to your project's Cargo.toml, enabling a TLS implementation as well:

[dependencies]
gneiss-mqtt = { version = "0.2", features = [ "rustls" ] }
gneiss-mqtt-aws = { version = "0.2", features = [ "rustls" ] }

(Temporary) If your project does not include tokio, you will need to add it too:

[dependencies]
tokio = { version = "1", features = ["full"] }

Future releases will support other async runtimes as well as a client that runs in a background thread and does not need an async runtime. For now, tokio is required.

Example: Connect to AWS IoT Core via AWS IoT Custom Authentication (with tokio runtime)

Custom authentication is an AWS IoT Core specific way to perform authentication without using certificates or http request signing. Instead, an AWS Lambda is invoked to decide whether or not a connection is allowed. See the custom authentication documentation for step-by-step instructions in how to set up the AWS resources (authorizer, Lambda, etc...) to perform custom authentication.

Once the necessary AWS resources have been set up, you can easily create clients for each of the two supported custom authentication modes:

  • Unsigned Custom Authentication - Anyone can invoke the authorizer's lambda if they know its ARN. This is not recommended for production since it is not protected from external abuse that may run up your AWS bill.
  • Signed Custom Authentication - Your Lambda function will only be invoked (and billed) if the Connect packet includes the cryptographic signature (based on an IoT Core registered public key) of a controllable value. Recommended for production.

You must be careful with the encodings of authorizer, authorizer_signature, and authorizer_token_key_name. Because custom authentication is supported over HTTP, these values must be URI-safe. It is up to you to URI encode them if necessary. In general, authorizer and authorizer_token_key_name are fixed when you create the authorizer resource and so it is straightforward to determine if you need to encode them or not. authorizer_signature should always be URI encoded.

TODO: automatically encode authorizer_signature if it needs it.

MQTT Client Configuration

The above examples skip all client configuration in favor of defaults. There are many configuration details that may be of interest depending on your use case. These options are controlled by structures in the gneiss-mqtt crate, via the with_client_options and with_connect_options methods on the AwsClientBuilder. Further details can be found in the relevant sections of the gneiss-mqtt docs:

Additional Notes

See the gneiss-mqtt documentation for client usage details and guidance.

The intention is that this crate will eventually be as agnostic as possible of underlying implementation details (async runtimes, TLS/transport implementations, etc...) but at present it has hard dependencies on tokio, rustls, and some associated helper libraries. These will get feature-flag-gated before GA, allowing you to pare the implementation down to your exact connection needs. In Rust's current state, there is a fundamental tension between trying to be transport/runtime agnostic and trying to provide an easy-to-use interface for getting successful clients set up for the many different combinations expected by users.

Dependencies

~2–16MB
~214K SLoC