Skip to content

Commit 62c465c

Browse files
Add Cognito connect sample (#407)
1 parent 5590d6e commit 62c465c

File tree

6 files changed

+252
-42
lines changed

6 files changed

+252
-42
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ env:
2323
CI_SAMPLES_FOLDER: "./aws-iot-device-sdk-python-v2/samples"
2424
CI_IOT_CONTAINERS_ROLE: ${{ secrets.AWS_CI_IOT_CONTAINERS }}
2525
CI_PUBSUB_ROLE: ${{ secrets.AWS_CI_PUBSUB_ROLE }}
26+
CI_COGNITO_ROLE: ${{ secrets.AWS_CI_COGNITO_ROLE }}
2627
CI_CUSTOM_AUTHORIZER_ROLE: ${{ secrets.AWS_CI_CUSTOM_AUTHORIZER_ROLE }}
2728
CI_SHADOW_ROLE: ${{ secrets.AWS_CI_SHADOW_ROLE }}
2829
CI_JOBS_ROLE: ${{ secrets.AWS_CI_JOBS_ROLE }}
@@ -244,6 +245,14 @@ jobs:
244245
export SOFTHSM2_CONF=/tmp/softhsm2.conf
245246
echo "directories.tokendir = /tmp/tokens" > /tmp/softhsm2.conf
246247
python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_pkcs11_connect_cfg.json
248+
- name: configure AWS credentials (Cognito)
249+
uses: aws-actions/configure-aws-credentials@v1
250+
with:
251+
role-to-assume: ${{ env.CI_COGNITO_ROLE }}
252+
aws-region: ${{ env.AWS_DEFAULT_REGION }}
253+
- name: run Cognito Connect sample
254+
run: |
255+
python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_cognito_connect_cfg.json
247256
- name: configure AWS credentials (MQTT5 samples)
248257
uses: aws-actions/configure-aws-credentials@v1
249258
with:
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"language": "Python",
3+
"sample_file": "./aws-iot-device-sdk-python-v2/samples/cognito_connect.py",
4+
"sample_region": "us-east-1",
5+
"sample_main_class": "",
6+
"arguments": [
7+
{
8+
"name": "--endpoint",
9+
"secret": "ci/endpoint"
10+
},
11+
{
12+
"name": "--signing_region",
13+
"data": "us-east-1"
14+
},
15+
{
16+
"name": "--cognito_identity",
17+
"secret": "ci/Cognito/identity_id"
18+
}
19+
]
20+
}

documents/MQTT5_Userguide.md

Lines changed: 85 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* [MQTT over Websockets with Sigv4 authentication](#mqtt-over-websockets-with-sigv4-authentication)
1515
* [Direct MQTT with Custom Authentication](#direct-mqtt-with-custom-authentication)
1616
* [Direct MQTT with PKCS11 Method](#direct-mqtt-with-pkcs11-method)
17+
* [MQTT over Websockets with Cognito authentication](#mqtt-over-websockets-with-cognito-authentication)
1718
* [HTTP Proxy](#http-proxy)
1819
* [Client Lifecycle Management](#client-lifecycle-management)
1920
* [Lifecycle Events](#lifecycle-events)
@@ -52,7 +53,7 @@ SDK MQTT5 support comes from a separate client implementation. In doing so, we
5253
* [IoT Core specific validation](https://awslabs.github.io/aws-crt-python/api/mqtt5.html#awscrt.mqtt5.ExtendedValidationAndFlowControlOptions) - will validate and fail operations that break IoT Core specific restrictions
5354
* [IoT Core specific flow control](https://awslabs.github.io/aws-crt-python/api/mqtt5.html#awscrt.mqtt5.ExtendedValidationAndFlowControlOptions) - will apply flow control to honor IoT Core specific per-connection limits and quotas
5455
* [Flexible queue control](https://awslabs.github.io/aws-crt-python/api/mqtt5.html#awscrt.mqtt5.ClientOperationQueueBehaviorType) - provides a number of options to control what happens to incomplete operations on a disconnection event
55-
* A [new API](https://awslabs.github.io/aws-crt-python/api/mqtt5.html#awscrt.mqtt5.Client) has been added to query the internal state of the client's operation queue. This API allows the user to make more informed flow control decisions before submitting operatons to the client.
56+
* A [new API](https://awslabs.github.io/aws-crt-python/api/mqtt5.html#awscrt.mqtt5.Client) has been added to query the internal state of the client's operation queue. This API allows the user to make more informed flow control decisions before submitting operations to the client.
5657
* Data can no longer back up on the socket. At most one frame of data is ever pending-write on the socket.
5758
* The MQTT5 client has a single message-received callback. Per-subscription callbacks are not supported.
5859

@@ -77,7 +78,8 @@ We strongly recommend using the AwsIotMqtt5ClientConfigBuilder class to configur
7778
All lifecycle events and the callback for publishes received by the MQTT5 Client should be added to the builder on creation of the Client. A full list of accepted arguments can be found in the API guide.
7879
#### **Direct MQTT with X509-based mutual TLS**
7980
For X509 based mutual TLS, you can create a client where the certificate and private key are configured by path:
80-
```
81+
82+
```python
8183
# X.509 based certificate file
8284
certificate_file_path = "<certificate file path>"
8385
# PKCS#1 or PKCS#8 PEM encoded private key file
@@ -87,7 +89,7 @@ For X509 based mutual TLS, you can create a client where the certificate and pri
8789

8890
# Create an MQTT5 Client using mqtt5_client_builder
8991
client = mqtt5_client_builder.mtls_from_path(
90-
endpoint = <account-specific endpoint>,
92+
endpoint = "<account-specific endpoint>",
9193
cert_filepath=certificate_file_path,
9294
pri_key_filepath=private_key_filePath))
9395
```
@@ -98,18 +100,20 @@ will sign the websocket upgrade request made by the client while connecting. Th
98100
the SDK is capable of resolving credentials in a variety of environments according to a chain of priorities:
99101

100102
```Environment -> Profile (local file system) -> STS Web Identity -> IMDS (ec2) or ECS```
103+
101104
If the default credentials provider chain and built-in AWS region extraction logic are sufficient, you do not need to specify
102105
any additional configuration:
103-
```
106+
107+
```python
104108
# The signing region. e.x.: 'us-east-1'
105-
signing_region = <signing region>
106-
credentials_provider = auth.AwsCredentialsProvider.new_dfault_chain()
109+
signing_region = "<signing region>"
110+
credentials_provider = auth.AwsCredentialsProvider.new_default_chain()
107111

108112
# other builder configurations can be added using **kwargs in the builder
109113

110114
# Create an MQTT5 Client using mqtt5_client_builder
111115
client = mqtt5_client_builder.websockets_with_default_aws_signing(
112-
endpoint = <account-specific endpoint>,
116+
endpoint = "<account-specific endpoint>",
113117
region = signing_region,
114118
credentials_provider=credentials_provider))
115119
```
@@ -118,37 +122,41 @@ any additional configuration:
118122
AWS IoT Core Custom Authentication allows you to use a lambda to gate access to IoT Core resources. For this authentication method,
119123
you must supply an additional configuration structure containing fields relevant to AWS IoT Core Custom Authentication.
120124
If your custom authenticator does not use signing, you don't specify anything related to the token signature:
121-
```
125+
126+
```python
122127
# other builder configurations can be added using **kwargs in the builder
123128

124129
client = mqtt5_client_builder.direct_with_custom_authorizer(
125-
endpoint = <account-specific endpoint>,
126-
auth_authorizer_name = <Name of your custom authorizer>,
127-
auth_username = <Value of the username field that should be passed to the authorizer's lambda>,
128-
auth_password =<Binary data value of the password field that should be passed to the authorizer's lambda>)
130+
endpoint = "<account-specific endpoint>",
131+
auth_authorizer_name = "<Name of your custom authorizer>",
132+
auth_username = "<Value of the username field that should be passed to the authorizer's lambda>",
133+
auth_password = <Binary data value of the password field to be passed to the authorizer lambda>)
129134
```
135+
130136
If your custom authorizer uses signing, you must specify the three signed token properties as well. The token signature must be the URI-encoding of the base64 encoding of the digital signature of the token value via the private key associated with the public key that was registered with the custom authorizer. It is your responsibility to URI-encode the token signature.
131-
```
137+
138+
```python
132139
# other builder configurations can be added using **kwargs in the builder
133140

134141
client = mqtt5_client_builder.direct_with_custom_authorizer(
135-
endpoint = <account-specific endpoint>,
136-
auth_authorizer_name = <Name of your custom authorizer>,
137-
auth_username = <Value of the username field that should be passed to the authorizer's lambda>,
138-
auth_password =<Binary data value of the password field that should be passed to the authorizer's lambda>,
139-
auth_authorizer_signature=<The signature of the custom authorizer>)
142+
endpoint = "<account-specific endpoint>",
143+
auth_authorizer_name = "<Name of your custom authorizer>",
144+
auth_username = "<Value of the username field that should be passed to the authorizer's lambda>",
145+
auth_password = <Binary data value of the password field to be passed to the authorizer lambda>,
146+
auth_authorizer_signature= "<The signature of the custom authorizer>")
140147
```
148+
141149
In both cases, the builder will construct a final CONNECT packet username field value for you based on the values configured. Do not add the token-signing fields to the value of the username that you assign within the custom authentication config structure. Similarly, do not add any custom authentication related values to the username in the CONNECT configuration optionally attached to the client configuration. The builder will do everything for you.
142150

143151
#### **Direct MQTT with PKCS11 Method**
144152

145153
A MQTT5 direct connection can be made using a PKCS11 device rather than using a PEM encoded private key, the private key for mutual TLS is stored on a PKCS#11 compatible smart card or Hardware Security Module (HSM). To create a MQTT5 builder configured for this connection, see the following code:
146154

147-
```
155+
```python
148156
# other builder configurations can be added using **kwargs in the builder
149157

150158
pkcs11_lib = io.Pkcs11Lib(
151-
file=<Path to PKCS11 library>,
159+
file="<Path to PKCS11 library>",
152160
behavior=io.Pkcs11Lib.InitializeFinalizeBehavior.STRICT)
153161

154162
client = mqtt5_client_builder.mtls_with_pkcs11(
@@ -158,38 +166,68 @@ A MQTT5 direct connection can be made using a PKCS11 device rather than using a
158166
token_label=pkcs11_token_label,
159167
priave_key_label=pkcs11_private_key_label,
160168
cert_filepath=pkcs11_cert_filepath,
161-
endpoint = <account-specific endpoint>)
169+
endpoint = "<account-specific endpoint>")
162170
```
163171

164172
**Note**: Currently, TLS integration with PKCS#11 is only available on Unix devices.
165173

174+
#### **MQTT over Websockets with Cognito authentication**
175+
176+
A MQTT5 websocket connection can be made using Cognito to authenticate rather than the AWS credentials located on the device or via key and certificate. Instead, Cognito can authenticate the connection using a valid Cognito identity ID. This requires a valid Cognito identity ID, which can be retrieved from a Cognito identity pool. A Cognito identity pool can be created from the AWS console.
177+
178+
To create a MQTT5 builder configured for this connection, see the following code:
179+
180+
```python
181+
# The signing region. e.x.: 'us-east-1'
182+
signing_region = "<signing region>"
183+
184+
# See https://docs.aws.amazon.com/general/latest/gr/cognito_identity.html for Cognito endpoints
185+
cognito_endpoint = "cognito-identity." + signing_region + ".amazonaws.com"
186+
cognito_identity_id = "<Cognito identity ID>"
187+
credentials_provider = auth.AwsCredentialsProvider.new_cognito(
188+
endpoint=cognito_endpoint,
189+
identity=cognito_identity_id,
190+
tls_ctx=io.ClientTlsContext(TlsContextOptions()))
191+
192+
# other builder configurations can be added using **kwargs in the builder
193+
194+
# Create an MQTT5 Client using mqtt5_client_builder
195+
client = mqtt5_client_builder.websockets_with_default_aws_signing(
196+
endpoint = "<account-specific endpoint>",
197+
region = signing_region,
198+
credentials_provider=credentials_provider))
199+
```
200+
201+
**Note**: A Cognito identity ID is different from a Cognito identity pool ID and trying to connect with a Cognito identity pool ID will not work. If you are unable to connect, make sure you are passing a Cognito identity ID rather than a Cognito identity pool ID.
202+
166203
#### **HTTP Proxy**
167204
No matter what your connection transport or authentication method is, you may connect through an HTTP proxy
168205
by adding the http_proxy_options keyword argument to the builder:
169-
```
206+
207+
```python
170208
http_proxy_options = http.HttpProxyOptions(
171-
host_name = <proxy host>,
209+
host_name = "<proxy host>",
172210
port = <proxy port>)
173211

174212
# Create an MQTT5 Client using mqtt5_client_builder with proxy options as keyword argument
175213
client = mqtt5_client_builder.mtls_from_path(
176-
endpoint = <account-specific endpoint>,
177-
cert_filepath = <certificate file path>,
178-
pri_key_filepath = <private key file path>,
214+
endpoint = "<account-specific endpoint>",
215+
cert_filepath = "<certificate file path>",
216+
pri_key_filepath = "<private key file path>",
179217
http_proxy_options = http_proxy_options))
180218
```
219+
181220
SDK Proxy support also includes support for basic authentication and TLS-to-proxy. SDK proxy support does not include any additional
182221
proxy authentication methods (kerberos, NTLM, etc...) nor does it include non-HTTP proxies (SOCKS5, for example).
183222

184223
## **Client lifecycle management**
185224
Once created, an MQTT5 client's configuration is immutable. Invoking start() on the client will put it into an active state where it
186225
recurrently establishes a connection to the configured remote endpoint. Reconnecting continues until you invoke stop().
187226

188-
```
189-
# Create an MQTT5 Client
190-
227+
```python
228+
# Create an MQTT5 Client
191229
client_options = mqtt5.ClientOptions(
192-
host_name = <endpoint to connect to>,
230+
host_name = "<endpoint to connect to>",
193231
port = <port to use>)
194232

195233
# Other options in client options can be set but once Client is initialized configuration is immutable
@@ -198,16 +236,15 @@ recurrently establishes a connection to the configured remote endpoint. Reconne
198236

199237
client = mqtt5.Client(client_options)
200238

201-
202-
# Use the client
239+
# Use the client
203240
client.start();
204241
...
205242
```
206243

207244
Invoking stop() breaks the current connection (if any) and moves the client into an idle state.
208245

209-
```
210-
// Shutdown
246+
```python
247+
# Shutdown
211248
client.stop();
212249

213250
```
@@ -227,32 +264,37 @@ Emitted when a connection attempt fails at any point between DNS resolution and
227264
Emitted when the client's network connection is shut down, either by a local action, event, or a remote close or reset. Only emitted after a ConnectionSuccess event: a network connection that is shut down during the connecting process manifests as a ConnectionFailure event. A Disconnect event will always include an error code. If the Disconnect event is due to the receipt of a server-sent DISCONNECT packet, the packet will be included with the event data.
228265

229266
#### **Stopped**
230-
Emitted once the client has shutdown any associated network connection and entered an idle state where it will no longer attempt to reconnect. Only emitted after an invocation of stop() on the client. A stopped client may always be started again.
267+
Emitted once the client has shutdown any associated network connection and entered an idle state where it will no longer attempt to reconnect. Only emitted after an invocation of `stop()` on the client. A stopped client may always be started again.
231268

232269
## **Client Operations**
233270
There are four basic MQTT operations you can perform with the MQTT5 client.
234271

235272
### Subscribe
236273
The Subscribe operation takes a description of the SUBSCRIBE packet you wish to send and returns a future that resolves successfully with the corresponding SUBACK returned by the broker; the future result raises an exception if anything goes wrong before the SUBACK is received.
237-
```
274+
275+
```python
238276
subscribe_future = client.subscribe(subscribe_packet = mqtt5.SubscribePacket(
239277
subscriptions = [mqtt5.Subscription(
240278
topic_filter = "hello/world/qos1",
241279
qos = mqtt5.QoS.AT_LEAST_ONCE)]))
242280

243281
suback = subscribe_future.result()
244282
```
283+
245284
### Unsubscribe
246285
The Unsubscribe operation takes a description of the UNSUBSCRIBE packet you wish to send and returns a future that resolves successfully with the corresponding UNSUBACK returned by the broker; the future result raises an exception if anything goes wrong before the UNSUBACK is received.
247-
```
286+
287+
```python
248288
unsubscribe_future = client.unsubscribe(unsubscribe_packet = mqtt5.UnsubscribePacket(
249289
topic_filters=["hello/world/qos1"]))
250290

251291
unsuback = unsubscribe_future.result()
252292
```
293+
253294
### Publish
254295
The Publish operation takes a description of the PUBLISH packet you wish to send and returns a future of polymorphic value. If the PUBLISH was a QoS 0 publish, then the future result is an empty PUBACK packet with all members set to None and is completed as soon as the packet has been written to the socket. If the PUBLISH was a QoS 1 publish, then the future result is a PUBACK packet value and is completed as soon as the PUBACK is received from the broker. If the operation fails for any reason before these respective completion events, the future result raises an exception.
255-
```
296+
297+
```python
256298
publish_future = client.publish(mqtt5.PublishPacket(
257299
topic = "hello/world/qos1",
258300
payload = "This is the payload of a QoS 1 publish",
@@ -261,9 +303,11 @@ The Publish operation takes a description of the PUBLISH packet you wish to send
261303
# on success, the result of publish_future will be a PubackPacket
262304
puback = publish_future.result()
263305
```
306+
264307
### Disconnect
265-
The stop() API supports a DISCONNECT packet as an optional parameter. If supplied, the DISCONNECT packet will be sent to the server prior to closing the socket. There is no future returned by a call to stop() but you may listen for the 'stopped' event on the client.
266-
```
308+
The `stop()` API supports a DISCONNECT packet as an optional parameter. If supplied, the DISCONNECT packet will be sent to the server prior to closing the socket. There is no future returned by a call to `stop()` but you may listen for the 'stopped' event on the client.
309+
310+
```python
267311
client.stop(mqtt5.DisconnectPacket(
268312
reason_code = mqtt5.DisconnectReasonCode.NORMAL_DISCONNECTION,
269313
session_expiry_interval_sec = 3600))
@@ -277,4 +321,4 @@ Below are some best practices for the MQTT5 client that are recommended to follo
277321
* Use the minimum QoS you can get away with for the lowest latency and bandwidth costs. For example, if you are sending data consistently multiple times per second and do not have to have a guarantee the server got each and every publish, using QoS 0 may be ideal compared to QoS 1. Of course, this heavily depends on your use case but generally it is recommended to use the lowest QoS possible.
278322
* If you are getting unexpected disconnects when trying to connect to AWS IoT Core, make sure to check your IoT Core Thing’s policy and permissions to make sure your device is has the permissions it needs to connect!
279323
* For **Publish**, **Subscribe**, and **Unsubscribe**, you can check the reason codes in the returned Future to see if the operation actually succeeded.
280-
* You MUST NOT perform blocking operations on any callback, or you will cause a deadlock. For example: in the on_publish_received callback, do not send a publish, and then wait for the future to complete within the callback. The Client cannot do work until your callback returs, so the thread will be stuck.
324+
* You MUST NOT perform blocking operations on any callback, or you will cause a deadlock. For example: in the `on_publish_received` callback, do not send a publish, and then wait for the future to complete within the callback. The Client cannot do work until your callback returns, so the thread will be stuck.

0 commit comments

Comments
 (0)