IoT λ””λ°”μ΄μŠ€ 연결에 λŒ€ν•΄ μ§μ ‘μ μœΌλ‘œ λ‹΄λ‹Ήν–ˆλ˜ κ²½ν—˜μ€ μ—†μ§€λ§Œ IoT Core 컨트둀 ν”Œλ ˆμΈ μž‘μ—…μ— μ΄μ–΄μ„œ IoT λ””λ°”μ΄μŠ€μ— λŒ€ν•œ AWS IoT Device SDK for Java v2에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€. λŒ€λΆ€λΆ„μ˜ μ‹€λ¬΄μ—μ„œ IoT λ””λ°”μ΄μŠ€μ— λŒ€ν•œ SDKλŠ” C λ˜λŠ” 파이썬으둜 μž‘μ„±λœ 것을 ν™œμš©ν•˜κ² μ§€λ§Œ 이에 λŒ€ν•œ κ²½ν—˜μ€ μ—†μœΌλ©° C 와 νŒŒμ΄μ¬μ„ λ‹€λ£¨λŠ” κ°œλ°œμžλŠ” μ•„λ‹ˆλ―€λ‘œ μžλ°” SDK둜 확인해보도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€.

IoT λ””λ°”μ΄μŠ€ 데이터 μ—”λ“œν¬μΈνŠΈ

IoT λ””λ°”μ΄μŠ€μ—μ„œ X.509 ν΄λΌμ΄μ–ΈνŠΈ μΈμ¦μ„œμ™€ ν•¨κ»˜ MQTT ν”„λ‘œν† μ½œμ„ μ‚¬μš©ν•˜μ—¬ AWS IoT λ©”μ‹œμ§€ λΈŒλ‘œμ»€μ— μ—°κ²°ν•  λ•ŒλŠ” Data-ATS μ—”λ“œν¬μΈνŠΈλ₯Ό μ‚¬μš©ν•˜κ²Œ λ©λ‹ˆλ‹€. 이 μ—”λ“œν¬μΈνŠΈ μ£Όμ†ŒλŠ” AWS IoT Core의 DescribeEndpoint λͺ…λ ΉμœΌλ‘œ 확인할 수 μžˆμ—ˆμ§€λ§Œ μˆ˜μ‹œλ‘œ λ³€κ²½λ˜λŠ” 정보가 μ•„λ‹ˆλ―€λ‘œ μ›Ή μ½˜μ†”μ—μ„œ 확인해도 λ¬΄λ°©ν•˜λ―€λ‘œ IoT 연계 μ‹œμŠ€ν…œμ„ 톡해 호슀트 μ£Όμ†Œ, κ°œμΈν‚€ 파일, X.509 ν΄λΌμ΄μ–ΈνŠΈ μΈμ¦μ„œ 그리고 루트 CA μΈμ¦μ„œ νŒŒμΌμ„ λ‹€μš΄λ‘œλ“œ λ°›κ²Œ λ©λ‹ˆλ‹€.

λ©”μ‹œμ§€ 브둜컀 μ—°κ²° κΆŒν•œ

일반적으둜 ν΄λΌμ΄μ–ΈνŠΈ μ•„μ΄λ””λŠ” 사물 이름 κ·ΈλŒ€λ‘œ μ‚¬μš©ν•˜λ„λ‘ λ³΄μ•ˆ 정책을 μ μš©ν•©λ‹ˆλ‹€. λ”°λΌμ„œ, MQTT ν”„λ‘œν† μ½œμ„ μ‚¬μš©ν•  λ•Œ X.509 ν΄λΌμ΄μ–ΈνŠΈ μΈμ¦μ„œμ— λ“±λ‘λœ μ—°κ²° μ •μ±…μœΌλ‘œ μ •μ˜λœ ν΄λΌμ΄μ–ΈνŠΈ 아이디λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€. ν΄λΌμ΄μ–ΈνŠΈ 아이디λ₯Ό 사물 이름과 λ™μΌν•˜κ²Œ μ‚¬μš©ν•˜λŠ” μ΄μœ λŠ” MQTT λ©”μ‹œμ§€ λΈŒλ‘œμ»€μ— λ™μΌν•œ ν΄λΌμ΄μ–ΈνŠΈ μ•„μ΄λ””λ‘œ μ—°κ²°ν•  수 μ—†μœΌλ―€λ‘œ 이전 연결이 ν•΄μ œλ˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": "arn:aws:iot:ap-northeast-2:[account-id]:client/${iot:Connection.Thing.ThingName}"
    }
  ]
}

λ””λ°”μ΄μŠ€ μ—°κ²° μƒνƒœ

ν”Œλ¦Ώ μΈλ±μ‹±μ˜ 사물 연결을 ν™œμ„±ν™”ν•˜λ©΄ IoT Core μ—μ„œ IoT λ””λ°”μ΄μŠ€ μ—°κ²° μƒνƒœλ₯Ό λͺ¨λ‹ˆν„°λ§ν•˜κ²Œ λ©λ‹ˆλ‹€. ν”Œλ¦Ώ μΈλ±μ‹±μœΌλ‘œ 사물 μ—°κ²° μƒνƒœλ₯Ό ν™•μΈν•˜κΈ° μœ„ν•΄μ„œλŠ” MQTT ν΄λΌμ΄μ–ΈνŠΈ 아이디λ₯Ό 사물 이름과 λ™μΌν•˜κ²Œ μ„€μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€. μ•žμ„œ, λ©”μ‹œμ§€ 브둜컀 μ—°κ²° κΆŒν•œμ—μ„œ ν΄λΌμ΄μ–ΈνŠΈ 아이디λ₯Ό 사물 이름 κ·ΈλŒ€λ‘œ μ‚¬μš©ν•΄μ•Όν•˜λŠ” μ΄μœ κ°€ λ˜κΈ°λ„ ν•©λ‹ˆλ‹€. μ—°κ²° μƒνƒœμ— λŒ€ν•œ 상세 μ΄μœ λŠ” μ›Ή μ½˜μ†”μ˜ 사물 μ—°κ²°μ„± API ν…ŒμŠ€νŠΈ λ˜λŠ” 사물 ν™œλ™ 이λ ₯μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

IoT Device Java SDK v2 기반 μ—°κ²° μ½”λ“œ

AWS IoT Device SDK둜 IoT λ””λ°”μ΄μŠ€μ—μ„œ AWS λ©”μ‹œμ§€ 브둜컀둜 연결을 μˆ˜ν–‰ν•  수 있고 Java SDK v2 기반의 μƒ˜ν”Œ μ½”λ“œ μ€‘μ—μ„œ Direct MQTT with X509-based Mutual TLS Method λ₯Ό μ°Έκ³ ν•˜μ—¬ μž‘μ„±ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€. μƒ˜ν”Œ 예제 λ¬Έμ„œμ™€ λ‹€λ₯΄κ²Œ AwsIotMqtt5ClientBuilder μ—λŠ” newMtlsBuilderλΌλŠ” ν•¨μˆ˜κ°€ μ—†μœΌλ―€λ‘œ newDirectMqttBuilderWithMtlsFromMemory ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λ©΄ 되며 PEM ν˜•μ‹μ˜ λ¬Έμžμ—΄μ„ 읽기 μœ„ν•΄μ„œ Files.readAllBytes λŒ€μ‹ μ— JDK 11μ—μ„œ μΆ”κ°€λœ Files.readString 을 λŒ€μ‹  ν™œμš©ν–ˆμŠ΅λ‹ˆλ‹€.

IotDevice.java
Mqtt5Client client = null; try (IotClient iotClient = IotClient.builder() .credentialsProvider(DefaultCredentialsProvider.create()) .region(Region.AP_NORTHEAST_2) .build()) { DescribeEndpointResponse endpoint = iotClient.describeEndpoint(builder -> builder.endpointType("iot:Data-ATS").build()); String dataAtsEndpoint = endpoint.endpointAddress(); String certificatePem = getPem("certificate.pem"); String privateKey = getPem("privateKey.pem"); String caRoot = getPem("AmazonRootCA1.pem"); ConnectPacket.ConnectPacketBuilder connectProperties = new ConnectPacket.ConnectPacketBuilder() .withClientId("PC"); client = AwsIotMqtt5ClientBuilder .newDirectMqttBuilderWithMtlsFromMemory(dataAtsEndpoint, certificatePem, privateKey) .withCertificateAuthority(caRoot) .withConnectProperties(connectProperties) .build(); client.start(); Thread.sleep(Duration.ofMinutes(5)); } finally { if (client != null) { client.close(); } }
public static String getPem(String filename) {
    try {
        return Files.readString(Path.of(ClassLoader.getSystemResource(filename).toURI()), StandardCharsets.UTF_8);
    } catch (Exception e) {
        return "";
    }
}

μ—°κ²° μ‹€νŒ¨μ— λŒ€ν•œ 였λ₯˜ 원인에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄κΈ°

IoT λ””λ°”μ΄μŠ€μ˜ MQTT ν΄λΌμ΄μ–ΈνŠΈ 연결에 λŒ€ν•œ μ½”λ“œλ₯Ό μž‘μ„±ν•΄λ³΄λŠ” 경우 μ—°κ²° μ‹€νŒ¨μ— λŒ€ν•œ 였λ₯˜ 원인에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€. λͺ¨λ“  μ˜ˆμ™Έ μΌ€μ΄μŠ€λ₯Ό λ‹€λ£° 수 μ—†μ§€λ§Œ μ œκ°€ κ²½ν—˜ν–ˆλ˜ μΌ€μ΄μŠ€λ“€μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ 였λ₯˜ 원인을 미리 μ•Œμ•„λ‘λ©΄ μ‰½κ²Œ 원인을 μ°Ύμ•„κ°ˆ 수 μžˆλŠ”λ° 도움이 λ˜κΈ°λ„ ν•©λ‹ˆλ‹€.

LifecycleEvent: no lifecycle events found!
software.amazon.awssdk.crt.CrtRuntimeException: LifecycleEvent: no lifecycle events found! - error code: 38 (aws_last_error: AWS_ERROR_INVALID_STATE(38), An invalid state was encountered.) AWS_ERROR_INVALID_STATE(38)

Mqtt5Clientλ₯Ό 생성할 λ•ŒλŠ” Mqtt5ClientOptions.LifecycleEvents μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜μ—¬ 생애 주기에 λŒ€ν•œ λ™μž‘μ„ λΉŒλ”μ— 등둝해야 ν•©λ‹ˆλ‹€. κ·Έλ ‡μ§€ μ•ŠμœΌλ©΄ μœ„μ™€ 같이 라이프 사이클을 찾을 수 μ—†λ‹€λŠ” 였λ₯˜κ°€ λ°œμƒν•˜λ©° μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

CONNACK:Client is not authenticated/authorized to send the message
CONNACK:Client is not authenticated/authorized to send the message:30d84af7-ddab-fd2e-7931-2592832a22d5

μœ„ 였λ₯˜ λ©”μ‹œμ§€λŠ” onConnectionFailure 콜백 ν•¨μˆ˜λ‘œ μ „λ‹¬λ˜λŠ” 결과에 ν¬ν•¨λ˜λŠ” μ‚¬μœ μž…λ‹ˆλ‹€. 이 λ©”μ‹œμ§€λŠ” ν΄λΌμ΄μ–ΈνŠΈλ‘œ 인증할 수 μ—†λ‹€λŠ” κ²ƒμœΌλ‘œ ν΄λΌμ΄μ–ΈνŠΈ 아이디(사물 이름)에 μ—°κ²°λœ μΈμ¦μ„œμ— μ˜¬λ°”λ₯Έ 정책이 μ—°κ²°λ˜μ§€ μ•Šμ•˜μ„ λ•Œ λ°œμƒν•  수 μžˆμœΌλ―€λ‘œ iot:Connect μž‘μ—…μ— λŒ€ν•œ 정책이 μ—°κ²°λœ μƒνƒœκ°€ μ•„λ‹Œμ§€ 잘λͺ»λœ ν΄λΌμ΄μ–ΈνŠΈμ— λŒ€ν•œ κΆŒν•œμ„ κ°€μ§€λŠ”μ§€ 확인해보면 λ©λ‹ˆλ‹€. λ©”μ‹œμ§€ μ—°κ²° κΆŒν•œμ—μ„œ ν™•μΈν–ˆλ˜ 사물 이름을 ν΄λΌμ΄μ–ΈνŠΈ μ•„μ΄λ””λ‘œ μ—°κ²°ν•  수 μžˆλŠ” κΆŒν•œμ„ κ°€μ§€λ„λ‘ν•œ X.509 ν΄λΌμ΄μ–ΈνŠΈ μΈμ¦μ„œ 정책은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

ErrorCode: 5134

onConnectionFailure 콜백 κ²°κ³Όκ°€ ConnAckPacket 이 λΉ„μ–΄μžˆκ³  μ—λŸ¬ μ½”λ“œκ°€ 5134 둜 μ „λ‹¬λœλ‹€λ©΄ 사물에 μ—°κ²°λœ μΈμ¦μ„œκ°€ λΉ„ν™œμ„±ν™” μƒνƒœμΌ 수 μžˆμŠ΅λ‹ˆλ‹€. 이미 μ—°κ²°λœ ν΄λΌμ΄μ–ΈνŠΈμ— λŒ€ν•œ μΈμ¦μ„œλ₯Ό λΉ„ν™œμ„±ν™”ν•˜κ±°λ‚˜ μΈμ¦μ„œ 정책을 λ³€κ²½ν•˜λ”λΌλ„ 이미 μ—°κ²°λœ ν΄λΌμ΄μ–ΈνŠΈκ°€ μ—°κ²° ν•΄μ œλ˜μ§„ μ•ŠμœΌλ―€λ‘œ μ•Œμ•„λ‘λ©΄ 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€.