1
votes

I am having problems connecting to AWS IOT from a python script using Cognito credentials. I am using the AWS IOT SDK in python as well as the boto3 package. Here is what I am doing:

First, I set up a Cognito User Pool with a couple of users who have a username and password to login. I also set up a Cognito Identity Pool with my Cognito User Pool as the one and only Authentication Provider. I do not provide access to unauthenticated identities. The Identity Pool has an Auth Role I will just call "MyAuthRole" and when I go to IAM, that role has two policies attached: One is the default policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*",
                "cognito-identity:*"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

and the second is a policy for IOT access:

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

Next I have python code to use my AWS IAM account credentials (access key and secret key) to get a temporary auth token like this:

auth_data = { 'USERNAME':username , 'PASSWORD':password }
provider_client=boto3.client('cognito-idp', region_name=region)
resp = provider_client.admin_initiate_auth(UserPoolId=user_pool_id, AuthFlow='ADMIN_NO_SRP_AUTH', AuthParameters=auth_data, ClientId=client_id)
id_token=resp['AuthenticationResult']['IdToken']

Finally I try to use this token to connect to AWS IOT, using this function:

def _get_aws_cognito_temp_credentials(aws_access_key_id=None,aws_secret_access_key=None,
                region_name='us-west-2',account_id=None,user_pool_id=None,
                identity_pool_id=None,id_token=None):
    boto3.setup_default_session(aws_access_key_id=aws_access_key_id,
                aws_secret_access_key=aws_secret_access_key,
                region_name = region_name)
    identity_client = boto3.client('cognito-identity', region_name=region_name)
    loginkey = "cognito-idp.%s.amazonaws.com/%s" % (region_name,user_pool_id)
    print("loginkey is %s" % loginkey)
    loginsdict={
        loginkey: id_token
    }
    identity_response = identity_client.get_id(AccountId=account_id,
                IdentityPoolId=identity_pool_id,
                Logins=loginsdict)
    identity_id = identity_response['IdentityId']
    #
    # Get the identity's credentials
    #
    credentials_response = identity_client.get_credentials_for_identity(
                IdentityId=identity_id,
                Logins=loginsdict)
    credentials = credentials_response['Credentials']
    access_key_id = credentials['AccessKeyId']
    secret_key = credentials['SecretKey']
    service = 'execute-api'
    session_token = credentials['SessionToken']
    expiration = credentials['Expiration']
    return access_key_id,secret_key,session_token,expiration

Finally I create the AWS IOT client and try to connect like this:

myAWSIoTMQTTClient = AWSIoTMQTTClient(clientId, useWebsocket=True)
myAWSIoTMQTTClient.configureEndpoint(host, port)
myAWSIoTMQTTClient.configureIAMCredentials(temp_access_key_id,
                        temp_secret_key,
                        temp_session_token)
myAWSIoTMQTTClient.configureAutoReconnectBackoffTime(1, 32, 20)
myAWSIoTMQTTClient.configureOfflinePublishQueueing(-1)
myAWSIoTMQTTClient.configureDrainingFrequency(2)
myAWSIoTMQTTClient.configureConnectDisconnectTimeout(10)
myAWSIoTMQTTClient.configureMQTTOperationTimeout(5)
log.info("create_aws_iot_client", pre_connect=True)
myAWSIoTMQTTClient.connect()
log.info("create_aws_iot_client", post_connect=True, myAWSIoTMQTTClient=myAWSIoTMQTTClient)

The problem is that it gets to pre_connect and then just hangs and eventually times out. The error message I get is this:

AWSIoTPythonSDK.exception.AWSIoTExceptions.connectTimeoutException

I also read somewhere that there may be other policies that somehow have to be attached:

"According to the Policies for HTTP and WebSocketClients documentation, in order to authenticate an Amazon Cognito identity to publish MQTT messages over HTTP, you must specify two policies. The first policy must be attached to an Amazon Cognito identity pool role. This is the managed policy AWSIoTDataAccess that was defined earlier in the IdentityPoolAuthRole.

The second policy must be attached to an Amazon Cognito user using the AWS IoT AttachPrincipalPolicy API."

However, I have no clue how to achieve the above in python or using the AWS console.

How do I fix this issue?

1

1 Answers

1
votes

You are right that the step you've missed is using the AttachPrincipalPolicy API (which is now depreciated and has been replaced with Iot::AttachPolicy.)

To do this:

  • Create an IoT policy (IoT Core > Secure > Policies > Create)

  • Give the policy the permissions you want any user attached to that policy to have, from your code as shared that would mean just copying the second IAM policy. Although you'll want to lock that down in production!

  • Using the AWS CLI you can attach this policy to your cognito user using:

    aws iot attach-policy --policy-name <iot-policy-name> --target <cognito-identity-id>

There is a significantly more involved AWS example at aws-samples/aws-iot-chat-example, however it is in JavaScript. I wasn't able to find an equivalent in Python, however the Cognito/IAM/IoT configuration steps and required API calls will remain the same whatever the language.