AWS IoT Core is an amazing (and often overlooked) service as I said before when comparing IoT Core to API Gateway Websockets. To summarize, IoT Core manages it own connections, it has a powerful system of topics and rules, it scales well and, not unimportant, it's quite cheap.
However, IoT Core can also be intimidating and difficult to start with because of things, fleets, shadow devices and certificates. The good news is that those things are not at all mandatory to use this service. While maybe not obvious at first sight, there are ways to just connect some clients to IoT Core without first registering or managing them.
I will be posting a few articles to cover basic usage of IoT Core aimed at developers who are looking for an easy and flexible way of bi-directional communication between clients and server using websockets.
Part 1: Introduction & Permissions (this one!)
Part 2: Connect using a presigned url
Part 3: Connect using a custom authorizer
Part 4: Topic Rules
What is IoT Core?
Simply put, and maybe not giving enough credit, IoT Core is an MQTT broker. MQTT is a lightweight publish-subscribe protocol. The MQTT broker acts as the central hub between connected clients that can send and reach messages to each other. Messages are sent over topics. A topic is a hierarchical string separated by slashes, for instance sensor/temperature/room1. A client can send temperature updates to this topic every minute and another client might be subscribed to this topic and receives all temperatures that are sent to do something with it.
MQTT has many advanced options and use cases that I won't cover here. If you want to know more about them I recommend reading more here. For the rest of this series, it's enough to know that there is a central broker (IoT Core) and clients (ie: a web browser) that can publish and receive messages.
Topic Wildcards
In a topic, a + character can be used as a wildcard for one level while a # character can be used for a multi level wildcard. It is possible to have more than 1 single level wildcard, like sensor/+/+ however it's not possible to have more than 1 multi level wildcard (which makes sense).
Looking at the previous example, if a client is interested in temperatures of all rooms, it can subscribe to sensor/temperature/# where the # character is a wildcard in MQTT. This means that if a client wants the informations of all sensors, it can listen to sensor/#. It is also possible to subscribe to sensor/+/room1 to receive all messages (temperature and others) of room1.
Security
For simple use of IoT Core, there are 4 permissions that you need to know about. Applying least-privilege permissions obviously also applies to IoT Core. In case a client performs an action that is not allowed by it's policy, the client will be disconnected by the server.
It's important to realize that MQTT wildcards are treated as literal characters in IAM permissions. This means that you can use the ?, *, + and # in the IAM policy but the ? and * will be treated only as wildcard for the IAM policy itself while + and # are treated as wildcards for topics in IoT Core. I will explain this using several examples.
Permissions
iot:Connect
First, your client needs to be able to connect to the server. The client ID must be unique per region, otherwise the previously connected client with the same client ID will be disconnected.
Examples
arn:aws:iot:{region}:{account-id}:client/*
Allows to connect using any client ID.
arn:aws:iot:{region}:{account-id}:client/sensor-123
Allows to connect as sensor-123.
arn:aws:iot:{region}:{account-id}:client/sensor-???
Allows to connect using any client ID as long as it starts with sensor- and ends with 3 characters. This means that sensor-123, sensor-foo will work, but sensor, sensor-foobar and sensor-123456 won't work.
arn:aws:iot:{region}:{account-id}:client/sensor-*
Allows to connect using any client ID as long as it starts with sensor-.
arn:aws:iot:{region}:{account-id}:client/user-????????-????-????-????-????????????
Allows to connect using user-{uuid}.
iot:Subscribe
Before a client can receive messages, the client must first subscribe to one or multiple topics.
Examples
arn:aws:iot:{region}:{account-id}:topicfilter/updates
Allows to subscribe to updates.
arn:aws:iot:{region}:{account-id}:topicfilter/updates/sensor-???
Allows to subscribe to updates/sensor-123, updates/sensor-foo and other variants as long as the topic starts with updates/ and then has a second level starting with sensor- and then 3 characters. It won't accept updates/foo-123 or "updates/sensor-123/foo".
arn:aws:iot:{region}:{account-id}:topicfilter/updates/sensor-*
Allows to subscribe to updates/sensor-123, updates/sensor-123/foo, and/or "updates/sensor-123/foo/bar/baz" because the * allows for any level of depth in the topic.
arn:aws:iot:{region}:{account-id}:topicfilter/updates/sensor-*/???
Allows to subscribe to updates/sensor-123/foo, updates/sensor-12345678/bar and other variants as long as the topic starts with "updates/sensor-" and has a second level with exactly 3 characters. It won't accept updates/sensor-1 or updates/sensor-12345/#.
You can of course use the wildcards in MQTT too if you want.
arn:aws:iot:{region}:{account-id}:topicfilter/updates/+
Allows to subscribe to topic/updates/+ only as the + is treated as a literal in the policy.
arn:aws:iot:{region}:{account-id}:topicfilter/updates/#
Allows to subscribe to topic/updates/# only as the # is treated as a literal in the policy.
iot:Receive & iot:Publish
iot:Receive allows the client to receive messages over the topics that it is subscribed to while iot:Publish allows the client to publish messages that can be received by other clients and/or topic rules.
The iot:Receive & iot:Publish permissions have the same structure as iot:Subscribe with the only difference that it uses "topic" instead of "topicfilter". Usually the iot:Subscribe and iot:Receive will be the same as it doesn't make sense to allow receiving on topics the client isn't allowed to subscribe on and vice versa.
Examples
arn:aws:iot:{region}:{account-id}:topic/updates
arn:aws:iot:{region}:{account-id}:topic/updates/sensor-???
arn:aws:iot:{region}:{account-id}:topic/updates/sensor-*
arn:aws:iot:{region}:{account-id}:topic/updates/sensor-*/???
arn:aws:iot:{region}:{account-id}:topic/updates/+
arn:aws:iot:{region}:{account-id}:topic/updates/#
Creating a policy
Since the permissions and values have different formats, you can easily combine everything in a single policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Connect",
"iot:Subscribe",
"iot:Receive"
],
"Resource": [
"arn:aws:iot:{region}:{account-id}:client/sensor-*",
"arn:aws:iot:{region}:{account-id}:topicfilter/sensor-*/???",
"arn:aws:iot:{region}:{account-id}:topic/sensor-*/???"
]
}
]
}
In the next article I will explain how to connect to IoT Core using a presigned url.
Top comments (1)
I second, I have done crazy things with IoT core and able to create POCs with realtime communication in minutes. I guess many other services also offer that capability but I really like the simplicity of IoT core, once you really understand the content of this post 😅