5

There have been similar questions to this before, and I have searched plenty, although the answers I have found haven't helped me solve the problem.

I am trying to connect to AWS's MQTT broker for my IoT device, I am using MQTT over Web Sockets through AWS Amplify's PubSub library. The authentication is also handled through Amplify (and yes, the user is successfully authenticated). When I try to connect it is immediately closed with the error message:

errorCode: 8, errorMessage: AMQJS0008I Socket closed.

I have setup both an IAM role for the identity pool, and attached an IoT policy to the authenticated user (with the identity ID retrieved from Amplify).

Command used for attaching policy:

aws iot attach-principal-policy --policy-name "Air-RME-Users" --principal "ap-northeast-1:4f76a019-9f84-44f0-a23d-48357210016c"

Any help is appreciated, thanks!

CloudWatch log for IoT:

{
    "timestamp": "2018-07-05 04:00:42.998",
    "logLevel": "ERROR",
    "traceId": "<removed>",
    "accountId": "<removed>",
    "status": "Failure",
    "eventType": "Connect",
    "protocol": "MQTT",
    "clientId": "4e900ea4-8f05-4022-98a8-c1b26280b2a2",
    "principalId": "<removed>:CognitoIdentityCredentials",
    "sourceIp": "<removed>",
    "sourcePort": 54641,
    "reason": "AUTHORIZATION_FAILURE"
}

Cognito Identity pool policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iot:Connect",
                "iot:Publish",
                "iot:Subscribe",
                "iot:Receive",
                "iot:GetThingShadow",
                "iot:UpdateThingShadow",
                "iot:DeleteThingShadow"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

IoT Policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:*",
      "Resource": "arn:aws:iot:ap-northeast-1:<removed>:topic/*"
    }
  ]
}

Test code:

import Amplify from 'aws-amplify';
import {AWSIoTProvider} from 'aws-amplify/lib/PubSub/Providers';

require('babel-polyfill');

global.app = function () {
  let user = null;
  const onBtn = document.getElementById("on");
  const offBtn = document.getElementById("off");

  Amplify.configure({
    Auth: {
      // REQUIRED only for Federated Authentication - Amazon Cognito Identity Pool ID
      identityPoolId: 'ap-northeast-1:<removed>',

      // REQUIRED - Amazon Cognito Region
      region: 'ap-northeast-1',

      // OPTIONAL - Amazon Cognito User Pool ID
      userPoolId: 'ap-northeast-1_<removed>',

      // OPTIONAL - Amazon Cognito Web Client ID (26-char alphanumeric string)
      userPoolWebClientId: '<removed>',

      // OPTIONAL - Enforce user authentication prior to accessing AWS resources or not
      mandatorySignIn: true,
    }
  });

  Amplify.addPluggable(new AWSIoTProvider({
    aws_pubsub_region: 'ap-northeast-1',
    aws_pubsub_endpoint: 'wss://<removed>.iot.ap-northeast-1.amazonaws.com/mqtt',
  }));

  process();

  async function process() {

    await Amplify.Auth.signIn("test@test.com", "Test1234567")
      .then(user => {
        console.log("ログインできました。")
        onBtn.style.visibility = "visible";
        offBtn.style.visibility = "visible";
        console.log(user);
        if (user.challengeName === "NEW_PASSWORD_REQUIRED") {
          const currentPassword = "Test123456";
          const newPassword = "Test1234567"

          user.completeNewPasswordChallenge(newPassword)
            .then(() => {
              // winning
            }).catch(error => {
            console.log(error);
          });
        }
      })
      .catch(err => {
        alert("ログインできませんでした。");
        console.log(err);
      });

    Amplify.Auth.currentCredentials().then((info) => {
      const cognitoIdentityId = info._identityId;
      console.log(cognitoIdentityId);
    });

    Amplify.PubSub.subscribe('$aws/things/Air-RME-test/shadow/get/accepted').subscribe({
      next: data => console.log('Message received', data),
      error: error => console.error(error),
      close: () => console.log('Done'),
    });
    Amplify.PubSub.publish('$aws/things/Air-RME-test/shadow/get', '');
  }
};

Edit:

Adding the following policy to the authenticated cognito identity role, before attaching the policy solved my problem. There was also a problem with the arn I was using, so I will be using the * wildcard in the meanwhile. Will update again, once I have gotten it working.

Policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iot:AttachPrincipalPolicy"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
Kristoffer
  • 53
  • 1
  • 5
  • You don't need to attach the IoT policy to the authenticated user. Just one policy to the Cognito and iotprinicple policy to the user. Please look at the principle policy here https://stackoverflow.com/questions/50848195/forbiddenerror-403-when-getshadow-updateshadow-called-in-aws-iot/51055811#51055811 – Aarth Tandel Jul 05 '18 at 05:57
  • @AarthTandel Thank you for the link! Adding the `iot:AttachPrincipalPolicy` to the Cognito Auth pool's IAM role, and then attaching the policy to the user worked. Attaching the policy for the user is however required for an authenticated user. I tried not attaching, and it didn't work. – Kristoffer Jul 05 '18 at 06:29
  • if worked then I will add the answer – Aarth Tandel Jul 05 '18 at 10:20

1 Answers1

4

So there is one policy missing. That policy has to attach to each user that is iot:AttachPrincipalPolicy

{
"Version": "2012-10-17",
"Statement": [
    {
        "Effect": "Allow",
        "Action": [
            "iot:AttachPrincipalPolicy"
        ],
        "Resource": [
            "*"
        ]
    }
] }

Currently, there are two ways one is through CLI and another way is through AWS Lambda Function:

'use strict';

console.log('Loading function');
var AWS = require('aws-sdk');

exports.handler = (event, context, callback) => {
    console.log('Received event:', JSON.stringify(event, null, 2))
    event.Records.forEach((record) => {
        console.log(record.eventName);
        if (record.eventName == "INSERT") {
            console.log('DynamoDB Record:', JSON.stringify(record));
            console.log('DynamoDB Record:', record.dynamodb['Keys']['UserId']['S']);
            var user = record.dynamodb['Keys']['UserId']['S'];
            const iotMgmt = new AWS.Iot();
            return new Promise(function(resolve, reject) {
                let params = {
                    policyName: "basic",
                    principal: user
                };
                iotMgmt.attachPrincipalPolicy(params, (err, res) => {
                    console.log("Attaching IoT policy to " + user);
                    if (err) {
                        console.error(err);
                        reject(err);
                    }
                    else {
                        resolve();
                    }
                });
            });
        }

    });
};
Aarth Tandel
  • 1,001
  • 12
  • 32
  • Thank you for this code snippet! Now in 2021, isn't there any other out of the box method to attach the IoT policies to the identities? This is really kind of weird method to do things. – Mohammed Noureldin May 23 '21 at 12:59
  • Check out my [last post](https://stackoverflow.com/a/76573345/13215097) on this matter. – Lucas Meier Jun 28 '23 at 12:59