콘텐츠로 이동
무료 평가 시작

TLS 브로커에 연결

PDComm 시뮬레이터는 평문 TCP로만 브로커에 연결합니다. 이 튜토리얼에서는 시뮬레이터 옆에 로컬 브로커(edge)를 두고, 로컬 브로커가 TLS와 클라이언트 인증서로 원격 브로커(site)에 데이터를 넘기게 합니다.

simulator ── plain TCP ──▶ edge broker ══ TLS bridge ══▶ site broker ══ TLS ══▶ operator
(publisher) 127.0.0.1:1883 (edge.conf) cert CN=pdcomm-edge 127.0.0.1:8883 cert CN=operator
  • site 브로커는 8883번 포트에서 TLS만 받고, 모든 클라이언트에게 튜토리얼 CA가 발급한 인증서를 요구합니다.
  • edge 브로커는 루프백 주소의 1883번 포트에서 평문을 받고, opcua/# 토픽을 site로 브리지합니다.
  • 운영자(operator)는 자신의 인증서로 site에 연결해 데이터를 받습니다.

실습에서는 두 브로커를 한 컴퓨터에서 실행합니다. 실제 구성에서는 site 주소와 인증서의 이름만 바뀝니다.

  • Mosquitto 2.x와 mosquitto_sub
  • OpenSSL 명령행 도구. 인증서를 만드는 명령은 OpenSSL 3과 macOS 기본 LibreSSL에서 모두 동작합니다. 8단계의 openssl s_client 옵션은 OpenSSL 3 기준입니다.
  • PDComm OPC UA 시뮬레이터 패키지와 OPC UA PubSub over MQTT의 publisher-scenario.json
  • 터미널 창 4개. 1883번과 8883번 포트를 쓰는 다른 브로커가 없어야 합니다.
  1. 작업 디렉터리를 만들고 파일 다섯 개를 저장합니다.

    터미널 창
    mkdir mqtt-tls
    cd mqtt-tls
    cp ../mqtt-lab/publisher-scenario.json .

    인증서 확장 정보입니다. 브로커 인증서의 subjectAltName에는 클라이언트가 연결하는 이름과 주소를 넣습니다.

    server.ext
    # X.509 extensions for the broker certificate. The subjectAltName entries
    # must match the host name or address that clients and bridges connect to.
    basicConstraints = critical, CA:FALSE
    keyUsage = critical, digitalSignature, keyEncipherment
    extendedKeyUsage = serverAuth
    subjectAltName = DNS:localhost, IP:127.0.0.1
    client.ext
    # X.509 extensions for client certificates (the edge bridge and operators).
    basicConstraints = critical, CA:FALSE
    keyUsage = critical, digitalSignature, keyEncipherment
    extendedKeyUsage = clientAuth

    site 브로커 설정입니다.

    site.conf
    # Site broker: MQTT over TLS on port 8883. Every client must present a
    # certificate issued by the tutorial CA; its common name becomes the user name.
    listener 8883 127.0.0.1
    cafile ca.crt
    certfile site.crt
    keyfile site.key
    require_certificate true
    use_identity_as_username true
    allow_anonymous false
    persistence false
    log_dest stdout
    설정 뜻
    cafile, certfile, keyfile 클라이언트 인증서를 검증할 CA, 브로커 인증서, 브로커 개인 키
    require_certificate true 인증서를 내지 않는 클라이언트는 TLS 핸드셰이크에서 끊습니다.
    use_identity_as_username true 클라이언트 인증서의 CN을 사용자 이름으로 씁니다.
    allow_anonymous false 사용자 이름이 없는 연결을 거부합니다.

    edge 브로커 설정입니다.

    edge.conf
    # Edge broker: plain TCP for local clients such as the PDComm simulator,
    # and a TLS bridge that forwards their OPC UA topics to the site broker.
    listener 1883 127.0.0.1
    allow_anonymous true
    persistence false
    log_dest stdout
    connection site
    address 127.0.0.1:8883
    bridge_protocol_version mqttv50
    bridge_cafile ca.crt
    bridge_certfile edge.crt
    bridge_keyfile edge.key
    remote_clientid pdcomm-edge-bridge
    topic opcua/# out 1
    설정 뜻
    connection site site라는 이름의 브리지를 시작합니다.
    address 127.0.0.1:8883 브리지가 연결할 site 브로커 주소. 인증서의 subjectAltName에 있어야 합니다.
    bridge_protocol_version mqttv50 MQTT 5.0으로 브리지해 Content Type과 사용자 속성을 그대로 전달합니다.
    bridge_cafile, bridge_certfile, bridge_keyfile site 인증서를 검증할 CA와 브리지의 클라이언트 인증서, 개인 키
    topic opcua/# out 1 로컬의 opcua/# 메시지를 site로 QoS 1로 보냅니다(out).
  2. 튜토리얼 CA와 인증서 세 개를 만듭니다.

    터미널 창
    openssl req -x509 -new -newkey rsa:2048 -nodes -sha256 -days 365 \
    -subj "/CN=PDComm Tutorial CA" -keyout ca.key -out ca.crt
    openssl req -new -newkey rsa:2048 -nodes -subj "/CN=localhost" -keyout site.key -out site.csr
    openssl x509 -req -in site.csr -CA ca.crt -CAkey ca.key -CAcreateserial -sha256 -days 365 \
    -extfile server.ext -out site.crt
    openssl req -new -newkey rsa:2048 -nodes -subj "/CN=pdcomm-edge" -keyout edge.key -out edge.csr
    openssl x509 -req -in edge.csr -CA ca.crt -CAkey ca.key -CAcreateserial -sha256 -days 365 \
    -extfile client.ext -out edge.crt
    openssl req -new -newkey rsa:2048 -nodes -subj "/CN=operator" -keyout operator.key -out operator.csr
    openssl x509 -req -in operator.csr -CA ca.crt -CAkey ca.key -CAcreateserial -sha256 -days 365 \
    -extfile client.ext -out operator.crt
    chmod 600 *.key

    인증서가 CA로 이어지는지 확인합니다.

    터미널 창
    openssl verify -CAfile ca.crt site.crt edge.crt operator.crt
    site.crt: OK
    edge.crt: OK
    operator.crt: OK
  3. 네 터미널 모두 mqtt-tls 디렉터리에서 작업합니다. 첫 번째 터미널에서 site 브로커를 시작합니다.

    터미널 창
    mosquitto -c site.conf
    Config loaded from site.conf.
    Opening ipv4 listen socket on port 8883.
    mosquitto version 2.1.2 running
  4. 두 번째 터미널에서 운영자 인증서로 site에 연결해 구독합니다.

    터미널 창
    mosquitto_sub -h 127.0.0.1 -p 8883 --cafile ca.crt --cert operator.crt --key operator.key \
    -V 5 -t 'opcua/#' -F '%t qos=%q retain=%r content-type=%C user-properties=%P %l bytes'

    site 로그에 TLS 1.3 연결과 인증서에서 가져온 사용자 이름이 보입니다.

    New client connected from 127.0.0.1:53292 as auto-A75E2731-… (p5, c1, k60, u'operator').
    Client auto-A75E2731-… negotiated TLSv1.3 cipher TLS_AES_256_GCM_SHA384
  5. 세 번째 터미널에서 edge 브로커를 시작합니다.

    터미널 창
    mosquitto -c edge.conf
    Opening ipv4 listen socket on port 1883.
    Connecting bridge site (127.0.0.1:8883)
    mosquitto version 2.1.2 running

    site 로그에 브리지 연결이 나타납니다. 사용자 이름은 edge 인증서의 CN인 pdcomm-edge입니다.

    New client connected from 127.0.0.1:53293 as pdcomm-edge-bridge (p5, c0, k60, u'pdcomm-edge').
    Client pdcomm-edge-bridge negotiated TLSv1.3 cipher TLS_AES_256_GCM_SHA384
  6. 네 번째 터미널에서 시뮬레이터 발행자를 실행합니다. 시뮬레이터는 edge의 127.0.0.1:1883에 평문으로 연결합니다.

    터미널 창
    "$PDCOMM_APPS/bin/pdcomm-opcua-headless" --scenario publisher-scenario.json

    PDCOMM_APPS가 없는 새 터미널이라면 먼저 PDCOMM_APPS=<시뮬레이터 패키지 디렉터리>를 실행합니다. 시나리오는 종료 코드 0으로 끝납니다. 두 번째 터미널의 운영자에게 두 메시지가 TLS로 도착합니다.

    opcua/uadp/data/100/line1 qos=0 retain=0 content-type=application/opcua+uadp user-properties=UAMessageType:ua-data 52 bytes
    opcua/uadp/data/100/line1 qos=0 retain=0 content-type=application/opcua+uadp user-properties=UAMessageType:ua-data 52 bytes

    Content Type과 UAMessageType이 그대로 남아 있으므로 site 쪽 OPC UA 구독자도 이 메시지를 받을 수 있습니다.

  7. 인증서 없는 연결이 거부되는지 확인합니다. 네 번째 터미널에서 실행합니다.

    터미널 창
    mosquitto_sub -h 127.0.0.1 -p 8883 --cafile ca.crt -t 'opcua/#' -C 1 -W 3
    Error: The connection was lost

    site 로그에는 이유가 남습니다.

    OpenSSL Error while trying to get the error[0]: error:0A0000C7:SSL routines::peer did not return a certificate
    Client 127.0.0.1 [127.0.0.1:53299] disconnected: Protocol error.
  8. openssl s_client로 TLS 구성을 MQTT와 따로 확인할 수도 있습니다.

    터미널 창
    openssl s_client -connect 127.0.0.1:8883 -CAfile ca.crt -cert operator.crt -key operator.key \
    -verify_ip 127.0.0.1 -verify_return_error -brief < /dev/null

    출력의 주요 줄은 다음과 같습니다.

    CONNECTION ESTABLISHED
    Protocol version: TLSv1.3
    Ciphersuite: TLS_AES_256_GCM_SHA384
    Peer certificate: CN=localhost

    -verify_ip는 인증서의 subjectAltName에 그 IP 주소가 있는지 검사합니다. 호스트 이름으로 연결한다면 -verify_hostname <이름>을 씁니다.

  1. 두 번째 터미널의 mosquitto_sub를 Ctrl+C로 끝냅니다.
  2. 세 번째와 첫 번째 터미널의 브로커를 Ctrl+C로 끝냅니다.
  3. 시험이 끝나면 mqtt-tls 디렉터리를 지웁니다. 특히 ca.key는 다른 곳에 남기지 않습니다.
출력 원인 해결
Error: The connection was lost와 site 로그의 peer did not return a certificate 클라이언트가 인증서를 내지 않았습니다. --cert와 --key를 줍니다.
Error: Protocol error와 site 로그의 tlsv1 alert unknown ca 클라이언트가 site 인증서를 신뢰하지 않습니다. --cafile ca.crt를 줍니다.
edge 로그의 certificate verify failed와 Bridge site next backoff will be … ms 브리지가 site 인증서를 검증하지 못했습니다. bridge_cafile과 address가 인증서와 맞는지 확인합니다.
시뮬레이터의 MQTT broker connection failed: MQTT transport I/O: ConnectionReset 시뮬레이터를 TLS 포트(8883)에 연결했습니다. 시뮬레이터는 edge의 평문 포트(1883)에 연결합니다.

메시지별 원인은 TLS 문제에 더 자세히 정리했습니다.