aws / aws-iot-device-sdk-java-v2

Next generation AWS IoT Client SDK for Java using the AWS Common Runtime
Apache License 2.0
119 stars 75 forks source link

AWS MQTT3 Websockets Connection Closed Unexpectedly #586

Closed magx2 closed 6 months ago

magx2 commented 6 months ago

Describe the bug

I'm trying to connect to AWS MQTT3 via websockets with authentication from Cognito, but I'm getting Caused by: software.amazon.awssdk.crt.mqtt.MqttException: The connection was closed unexpectedly.. What might be the problem?

This is my method for getting GetCredentialsForIdentityResponse

    public GetCredentialsForIdentityResponse getCredentialsForIdentity(AuthenticationResultType accessToken, String identityId) {
        try (var client = CognitoIdentityClient.builder()
                .region(Region.of(region))
                .build()) {
            return client.getCredentialsForIdentity(
                    GetCredentialsForIdentityRequest.builder()
                            .identityId(identityId)
                            .logins(
                                    Map.of(
                                            "cognito-idp.eu-central-1.amazonaws.com/eu-central-1_XGRz3CgoY",
                                            accessToken.idToken()))
                            .build());
        }
    }

After this I'm using GetCredentialsForIdentityResponse to obtain MqttClientConnection:

public MqttClientConnection buildConnection(String prefix,
                                            String region,
                                            String clientId,
                                            GetCredentialsForIdentityResponse credentialsForIdentity)  {
    try (var builder = AwsIotMqttConnectionBuilder.newMtlsBuilderFromPath(null, null)) {
        MqttClientConnectionEvents callbacks = new MqttClientConnectionEvents() {
            @Override
            public void onConnectionInterrupted(int errorCode) {
                log.error("Connection interrupted: " + errorCode + ": " + CRT.awsErrorString(errorCode));
            }

            @Override
            public void onConnectionResumed(boolean sessionPresent) {
                log.error("Connection resumed: " + (sessionPresent ? "existing session" : "clean session"));
            }
        };
        return builder.withEndpoint("%s-ats.iot.%s.amazonaws.com".formatted(prefix, region))
                .withWebsockets(true)
                .withConnectionEventCallbacks(callbacks)
                .withWebsocketSigningRegion(region)
                .withClientId(clientId)
                .withWebsocketCredentialsProvider(
                        new StaticCredentialsProvider.StaticCredentialsProviderBuilder()
                                .withAccessKeyId(  credentialsForIdentity.credentials().accessKeyId().getBytes(UTF_8))
                                .withSecretAccessKey(credentialsForIdentity.credentials().secretKey().getBytes(UTF_8))
                                .withSessionToken( credentialsForIdentity.credentials().sessionToken().getBytes(UTF_8))
                                .build())
                .build();
    }
}

and then when I'm trying to connect I'm getting previously mentioned error:

MqttClientConnection connection) throws ExecutionException, InterruptedException {
    try (connection) {
        CompletableFuture<Boolean> connected = connection.connect();
        boolean sessionPresent = connected.get(); // <--- here!
    }
}

Expected Behavior

Connect to MQTT via websockets

Current Behavior

Caused by: software.amazon.awssdk.crt.mqtt.MqttException: The connection was closed unexpectedly

Reproduction Steps

Repo: https://github.com/magx2/salus/blob/master/app/src/main/java/pl/grzeslowski/WsMqtt.java#L21

Possible Solution

No response

Additional Information/Context

No response

SDK version used

software.amazon.awssdk.iotdevicesdk:aws-iot-device-sdk:1.20.7

Environment details (OS name and version, etc.)

Windows 11

bretambrose commented 6 months ago

The most likely issue is a permission problem (missing or mismatched iot connect permission) on the cognito-sourced credentials.

Beyond that, once it's working, you don't want to stuff session credentials in a static provider. You could use https://github.com/awslabs/aws-crt-java/blob/main/src/main/java/software/amazon/awssdk/crt/auth/credentials/DelegateCredentialsProvider.java to adapt the cognito sourcing or you could use the CRT's cognito provider: https://github.com/awslabs/aws-crt-java/blob/main/src/main/java/software/amazon/awssdk/crt/auth/credentials/CognitoCredentialsProvider.java

bretambrose commented 6 months ago

Also, if you enable and attach SDK logs, we may be able to get a more detailed diagnosis.

magx2 commented 6 months ago

I think it might not be connected with permissions. Look at this code - I'm taking credentials from cognito and I'm querying HTTP shadow endpoint (https://a24u3z7zzwrtdl-ats.iot.eu-central-1.amazonaws.com/things/%s/shadow) for thing and it works

BTW I'm aweare of CognitoCredentialsProvider, I even tried to use it but I'm getting 400 from cognito....

magx2 commented 6 months ago

Also, if you enable and attach SDK logs, we may be able to get a more detailed diagnosis.

I'm attaching trace and debug log files. Those are runs of the same program, I just didn't know if you prefer trace or debug

issues-586-Trace.log issues-586-Debug.log

magx2 commented 6 months ago

Another thing:

when I'm using CognitoCredentialsProvider I'm getting: Caused by: software.amazon.awssdk.crt.mqtt.MqttException: Attempt to sign an http request without credentials

Look at line 427 in logs: [DEBUG] [2024-05-25T11:15:27Z] [00006300] [http-stream] - id=000001D9DE8D1690: Client request complete, response status: 400 (Bad Request).. Is my cognito badly configured?

issues-586-cognito-Trace.log

bretambrose commented 6 months ago

I think it might not be connected with permissions. Look at this code - I'm taking credentials from cognito and I'm querying HTTP shadow endpoint (https://a24u3z7zzwrtdl-ats.iot.eu-central-1.amazonaws.com/things/%s/shadow) for thing and it works

BTW I'm aweare of CognitoCredentialsProvider, I even tried to use it but I'm getting 400 from cognito....

The permissions needed to perform an http request to the shadow service and the permissions needed to use IoT Core's mqtt broker are very different.

I looked at the logs. The connection is successfully established. The signed websocket handshake upgrade succeeds. The remote hosts closes the connection immediately after the CONNECT packet was sent. I know of no other possible explanation then a permissions problem.

What is the policy associated with the cognito query? Have you tried loosening it to allow everything as a sanity check?

magx2 commented 6 months ago

I'm not an owner of this MQTT endpoint so I cannot loosen anything :(.

I know the other team uses flutter/dart to connect to AWS IoT and they are attaching policy. Is it possible with MQTT?

  print(await Cognito.initialize());
  if (!await Cognito.isSignedIn()) {
    print(await Cognito.signIn(dotenv.env['USERNAME'], dotenv.env['PASSWORD']));
  }
  print(await Cognito.getIdentityId());

  var device = AWSIotDevice(
    endpoint: dotenv.env['ENDPOINT'],
    clientId: dotenv.env['CLIENT_ID'],
  );

  await device.attachPolicy(
    identityId: await Cognito.getIdentityId(),
    policyName: dotenv.env['POLICY_NAME'],
  );

  await device.connect();
bretambrose commented 6 months ago

The permissions associated with the sourced credentials come from your cognito identity pool configuration, which is a resource under your control

magx2 commented 6 months ago

Thanks for the help! Now everything is clear :)!

github-actions[bot] commented 6 months ago

This issue is now closed. Comments on closed issues are hard for our team to see. If you need more assistance, please open a new issue that references this one.