IoT Core: MQTT, Policies, and Rules Engine

IoT Core

Device path: register Thing → create X.509 cert → IoT policy on cert → MQTT over TLS to data endpoint → topics → optional Rules → Lambda / DynamoDB / SQS / Timestream.

Cloud path: IAM principal calls iot-data API (Publish, UpdateThingShadow) - not the same auth as devices.

Use Data-ATS endpoint (…-ats.iot.<region>.amazonaws.com, port 8883). Thing name = MQTT client ID when policy scopes client/${iot:Connection.Thing.ThingName}.

PieceJob
ThingLogical device id in registry
CertificateDevice identity (TLS mutual auth)
IoT policyAllows Connect / Publish / Subscribe / Receive on ARNs
TopicMQTT routing (device/<thing>/telemetry)
RuleSQL on topic payload → action
ShadowJSON state document ($aws/things/<name>/shadow/...)
Env + data endpoint
export AWS_REGION=<region>
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

aws iot describe-endpoint --endpoint-type iot:Data-ATS --query endpointAddress --output text

Download Amazon Root CA 1 for devices (AmazonRootCA1.pem).


1. Thing + certificate + policy

Order matters: policy exists → cert created → attach policy to cert → attach cert to thing.

Duplicate thing name in same Region → ResourceAlreadyExistsException. Delete test things between tutorials or use unique names.

Create thing, cert, and attach
THING_NAME=my-sensor-01

aws iot create-thing --thing-name "$THING_NAME"

aws iot create-keys-and-certificate --set-as-active \
  --certificate-pem-outfile device.cert.pem \
  --public-key-outfile device.public.key \
  --private-key-outfile device.private.key \
  --query certificateArn --output text > cert-arn.txt

CERT_ARN=$(cat cert-arn.txt)

aws iot attach-thing-principal --thing-name "$THING_NAME" --principal "$CERT_ARN"
IoT policy (thing-scoped topics)

Replace <region> and <account>.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": "arn:aws:iot:<region>:<account>:client/${iot:Connection.Thing.ThingName}"
    },
    {
      "Effect": "Allow",
      "Action": ["iot:Publish", "iot:Receive"],
      "Resource": "arn:aws:iot:<region>:<account>:topic/device/${iot:Connection.Thing.ThingName}/*"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Subscribe",
      "Resource": "arn:aws:iot:<region>:<account>:topicfilter/device/${iot:Connection.Thing.ThingName}/*"
    }
  ]
}
aws iot create-policy --policy-name "${THING_NAME}-policy" --policy-document file://iot-policy.json
aws iot attach-policy --policy-name "${THING_NAME}-policy" --target "$CERT_ARN"

Policy on certificate (or Cognito identity), not on the thing object alone.


2. Publish telemetry (device)

Topic must match policy. QoS 0 or 1 (IoT Core supports QoS 0 and 1, not 2).

mosquitto_pub (quick test)
ENDPOINT=$(aws iot describe-endpoint --endpoint-type iot:Data-ATS --query endpointAddress --output text)

mosquitto_pub \
  --cafile AmazonRootCA1.pem \
  --cert device.cert.pem \
  --key device.private.key \
  -h "$ENDPOINT" -p 8883 -q 1 \
  -i "$THING_NAME" \
  -t "device/${THING_NAME}/telemetry" \
  -m '{"temp_c":22.5}'

Console: MQTT test client → subscribe device/# → watch messages.

Python (X.509) example

./content/dir/iot/mqtt-publish-telemetry.py

pip install awsiotsdk
python ./content/dir/iot/mqtt-publish-telemetry.py \
  --endpoint "$ENDPOINT" \
  --cert device.cert.pem --key device.private.key --ca AmazonRootCA1.pem \
  --thing-name "$THING_NAME"
Cloud publish (IAM, not device cert)

Backend or test from CLI with your IAM user/role:

aws iot-data publish \
  --topic "device/${THING_NAME}/telemetry" \
  --qos 1 \
  --payload '{"temp_c":23.0,"source":"cli"}' \
  --cli-binary-format raw-in-base64-out

Requires IAM iot:Publish on the topic ARN. Does not prove device cert wiring.


3. Rules engine → Lambda

Rule SQL selects fields from incoming messages. Rule execution role needs permission to invoke Lambda (and write to downstream resources).

Topic rule payload + create

rule-temp-lambda.json:

{
  "sql": "SELECT topic(2) AS thingName, temp_c, ts FROM 'device/+/telemetry' WHERE temp_c > 30",
  "description": "Hot readings to Lambda",
  "actions": [
    {
      "lambda": {
        "functionArn": "arn:aws:lambda:<region>:<account>:function:IotHotTelemetry"
      }
    }
  ],
  "ruleDisabled": false,
  "awsIotSqlVersion": "2016-03-23"
}
aws iot create-topic-rule --rule-name HotTelemetryToLambda --topic-rule-payload file://rule-temp-lambda.json

Lambda resource policy must allow iot.amazonaws.com to invoke. Rule errors → CloudWatch Logs log group AWSIotLogsV2 (enable logging in IoT settings if empty).

Rule → DynamoDB (pattern)
{
  "sql": "SELECT * FROM 'device/+/telemetry'",
  "actions": [
    {
      "dynamoDBv2": {
        "roleArn": "arn:aws:iam::<account>:role/IotRuleDynamoRole",
        "putItem": {
          "tableName": "DeviceTelemetry"
        }
      }
    }
  ]
}

Partition key in table must match what SQL exposes (often thingName from topic(n) or a field in JSON).


4. Device Shadow (optional)

Reported state from device; desired state from cloud. Classic shadow topics:

  • Update reported: $aws/things/<thingName>/shadow/update
  • Get: $aws/things/<thingName>/shadow/get

Policy must allow iot:Publish / iot:Subscribe on $aws/things/${iot:Connection.Thing.ThingName}/shadow/* if you use shadows with thing-scoped policies.

Shadow update payload
{
  "state": {
    "reported": {
      "temp_c": 22.5,
      "connected": true
    }
  }
}
aws iot-data update-thing-shadow \
  --thing-name "$THING_NAME" \
  --payload file://shadow-reported.json \
  --cli-binary-format raw-in-base64-out

5. Daily checks

SymptomFirst look
CONNECT fails / TLS handshakeWrong endpoint (use ATS); clock skew; CA file
Not authorized on connectPolicy not attached to cert; client ID ≠ thing name when policy uses ThingName variable
Not authorized on publishTopic string mismatch vs policy ARN (typo, missing level, wrong topic vs topicfilter)
Cert rejectedCert inactive or revoked; using cert from another account/Region
Rule never firesSQL topic filter vs actual topic; ruleDisabled; no message reaching broker
Lambda not invokedRule role + Lambda resource policy; check AWSIotLogsV2
Shadow access deniedShadow $aws/things/... ARNs missing in IoT policy
aws iot list-things
aws iot list-thing-principals --thing-name "$THING_NAME"
aws iot list-principal-policies --principal "$CERT_ARN"
aws iot get-topic-rule --rule-name HotTelemetryToLambda

Docs: troubleshoot MQTT · policy variables