방화벽 담당자는 생산 cell로 들어오는 inbound port를 열어 주지 않는다. 그리고 그게 맞다. 원래 SCADA client는 server의 opc.tcp port 4840으로 접속하는데, 이건 SCADA VLAN에서 cell 안으로 들어가는 inbound rule을 의미한다. 세그먼트를 나눈 공장이라면 정확히 없애고 싶어 하는 방향이다. Reverse connect는 그 논쟁을 하지 않고도 데이터를 받아 오는 방법이다.
핵심은 메시지 하나다. 보통은 client가 먼저 Hello를 보낸다. Reverse connect에서는 server가 outbound로 TCP socket을 열고 ReverseHello를 보낸다. 이 메시지는 OPC UA Part 6의 UA Connection Protocol에 정의돼 있고, server의 ServerUri와 EndpointUrl을 담는다. Listener socket에 앉아 있던 client가 평소처럼 Hello로 응답하면, 그 다음 secure channel과 session은 늘 하던 대로 진행된다. 보안이 바뀌는 건 하나도 없다. 첫 패킷을 누가 거는지만 뒤집혔고, 그 결과 방화벽 rule이 cell에서 나가는 outbound allow가 된다. 현장이 실제로 승인해 주는 방향이다.
이게 값을 하는 경우는 이렇다.
- Plant SCADA로 접속해야 하지만 장비 제작사 방화벽 뒤에 있는 OEM 장비 server.
- 일부러 세그먼트를 나눈 cell 안쪽의 skid나 package 장비.
- 생산 zone 안으로 절대 먼저 접속하면 안 되는 DMZ collector (IEC 62443의 zone-and-conduit 모델).
- NAT 때문에 client에서 보는 server 주소가 고정되지 않는 경우.
한 가지 못 하는 것: reverse connect가 주는 건 TCP socket뿐이다. Certificate trust, endpoint 선택, user 인증, 정상 subscription은 주지 않는다. 이전에 있던 모든 OPC UA 고장은 그대로 남는다. 바뀐 건 어느 쪽이 network에서 막히느냐뿐이다.
누가 listen하고 누가 접속하는지 먼저 적는다
"SCADA가 PLC와 통신한다"는 여기서 아무 쓸모가 없다. Reverse connect는 방향을 뒤집으니, 다른 걸 보기 전에 누가 listener socket을 쥐고 누가 그쪽으로 outbound 접속을 거는지부터 못 박아야 한다.
| 항목 | 예시 | 현장 확인 |
|---|---|---|
| Client listener | 10.40.8.21:4841 | SCADA 또는 collector host에서 port가 열려 있는지 확인. |
| Server outbound target | opc.tcp://10.40.8.21:4841 | Server 설정의 target 주소가 listener와 같은지 확인. |
| 방화벽 방향 | Cell network에서 SCADA network로 | Server source에서 listener destination으로 허용되는지 확인. |
| Application URI | urn:Line3:Press01:UaServer | Certificate identity가 예상한 application인지 확인. |
| Handoff 후 endpoint | Security policy와 mode | Client가 의도한 secure endpoint를 고르는지 확인. |
| 재접속 동작 | 10초 retry, log 제한 | Retry가 너무 빠르거나 log를 밀어내지 않는지 확인. |
이 표가 없으면 시운전 때 말이 엇갈린다. 방화벽 담당자는 outbound가 허용됐다고 하고, SCADA 담당자는 예전 방식의 server port로 계속 접속 시험을 할 수 있다.
Socket은 붙었는데 여전히 trust가 안 될 때
Certificate 검증(Part 4, application instance certificate)은 TCP 접속이 어느 방향에서 왔는지 신경 쓰지 않는다. 양쪽은 일반 접속과 똑같이 certificate을 주고받고 검사한다. 내가 가장 자주 받는 reverse connect 문의가 "server는 접속하는데 session이 안 뜬다"인데, 열에 아홉은 network는 멀쩡하고 한쪽이 certificate을 조용히 reject한 경우다.
실제로 자주 틀어지는 지점은 이렇다.
- Server cert이 client trust store에 없거나 client cert이 server 쪽에 없다. Reverse connect라고 양방향 검증이 사라지지 않는다.
- Certificate의 ApplicationUri가 endpoint description의
applicationUri와 안 맞는다. UA stack은 이걸 hard reject로 처리하고, 에러 문구는 대개 "BadSecurityChecksFailed"뿐이라 원인이 잘 안 보인다. - Client나 server를 교체하면 옛 cert이 rejected 폴더에 들어가고 새 cert은 trusted로 승격되지 않는다. Rejected 폴더 정리는 장애 후가 아니라 교체 작업의 일부로 한다.
- 만료. 시운전 때 발급한 3년짜리 cert이 아무도 안 보는 화요일에 만료된다. 만료일을 UPS 배터리와 같은 점검 목록에 올려 둔다.
Maintenance window가 있으면 한 번쯤 trust를 일부러 빼 보고, 진단이 certificate rejected로 뜨는지 확인하는 게 좋다. 운전자가 늘 "connection failed"만 본다면, 실제로 터진 날 두 시간짜리 전화 통화를 미리 예약해 둔 셈이다.
Client는 태연하게 엉뚱한 endpoint를 고른다
보통 server는 endpoint를 한 움큼 광고한다. SecurityPolicy#None, Basic256Sha256 위의 Sign과 SignAndEncrypt, 그리고 최신 Aes128Sha256RsaOaep / Aes256Sha256RsaPss 프로파일이 각각 anonymous와 username/password token policy와 곱해져 나온다. (Basic128Rsa15와 Basic256은 현행 spec에서 deprecated다. 그냥 광고만 남기지 말고 꺼라.) Reverse connect는 socket을 붙일 뿐, client가 의도한 endpoint에 착지하도록 보장하지 않는다.
내가 사인하기 전에 확인하는 것:
- 현장 정책이 허용하면
#None과 anonymous endpoint를 죽인다. 안 그러면 "임시" test 설정이 무보안으로 조용히 운영에 실린다. - 내가 의도한 게 아니라 client가 실제로 negotiate한 endpoint URL, security policy, message security mode, user token type을 적는다.
- Username token이 실패했을 때 client가 조용히 anonymous로 fallback하지 않는지 확인한다.
- Endpoint discovery가 reverse connection을 타는지, 아니면 별도 normal
GetEndpoints접속을 여는지 본다. 뒤엣것을 하는 stack도 있는데, 이러면 없애려던 inbound 방향이 되살아난다. - Client 재시작과 server 재시작을 따로 시험하며 각각 복구되는지 본다.
첫 연결은 되는데 browse가 죽으면 reverse connect를 탓하지 말자. Endpoint 선택 부근의 client log를 잡아 보면 거의 항상 user token policy나 certificate trust 문제다.
방화벽 rule은 좁게 — 그게 애초에 목적이었다
Reverse connect가 세그먼트를 조이려고 있는 거라면, 넓게 열어 둔 rule은 그 작업의 이유 자체를 버리는 셈이다. Rule은 다른 것과 똑같이 구체적이어야 한다.
- Source: 알려진 OPC UA server host 또는 cell subnet.
- Destination: SCADA client나 collector listener host.
- Destination port: 설정한 reverse connect listener port.
- Protocol: TCP.
- Logging: 시운전 중에는 최소한 enable.
- Owner: 임시가 아니라 설비명, 시스템명, 변경 ticket으로 식별.
피해야 할 형태는 "cell network → SCADA network, high port 전체"짜리 rule이다. 이건 세그먼트를 나눈 이유를 무너뜨리고, 이제 무엇이든 무엇과 통신할 수 있으니 다음 사람의 문제 추적도 더 어려워진다.
성공 1건, 실패 1건을 잡아 둔다
성공한 연결과 실패한 연결을 양쪽 timestamp까지 짧게 남겨 둔다. 새벽 3시에 연결이 끊겼을 때, 비교할 정상 log가 있으면 진단이 재발견이 아니라 5분짜리 diff가 된다.
남겨 둘 만한 증거는 다음과 같다.
- Server outbound 접속에 대한 firewall accept 또는 deny log.
- Client listener가 incoming reverse connection을 받은 log.
- Server의 reverse connect retry와 handoff log.
- Secure channel 생성 결과.
- Session 생성 결과와 user identity.
- Subscription 생성 결과와 monitored item 개수.
- 첫 good quality data의 timestamp.
여기서 clock sync는 생각보다 중요하다. 방화벽, SCADA server, 장비 gateway 시간이 몇 분만 어긋나도, 한 장비의 deny log와 다른 장비의 channel 에러를 맞추는 게 추측이 된다. 시작 전에 셋 다 같은 NTP source를 보게 한다.
실제로 마주치는 고장
| 증상 | 먼저 볼 것 |
|---|---|
| Server는 계속 retry하지만 client에는 아무 것도 안 보임 | Listener IP 오타, 방화벽 방향, NAT 주소, host firewall. |
| TCP 연결 직후 바로 끊김 | 제품 간 reverse connect handoff 호환성, UA stack 차이, certificate reject. |
| Secure channel 이후 session 실패 | User token policy, 계정/암호, role 권한, server session limit. |
| Browse는 되는데 값이 갱신 안 됨 | Subscription 설정, monitored item limit, namespace mismatch, source quality. |
| 수동 재시작 후에만 동작 | Reconnect timer, listener 시작 순서, stale connection 정리. |
| Test VLAN에서는 되고 운영망에서는 안 됨 | NAT, asymmetric route, firewall object 차이, DNS 차이. |
"Port 열렸다"는 아무것도 증명하지 못한다. Reverse connect는 순서대로 세 계층을 통과해야 한다. TCP path, secure channel trust, 그리고 good quality 데이터를 실제로 내보내는 subscription이 붙은 session이다. 1계층에 초록불이 들어와도 2·3계층에 대해서는 아무것도 알려 주지 않는다.
다음 사람에게 남길 것
위 내용은 다음 엔지니어가 장애 시계 아래에서 다시 발견해야 한다면 전부 무용지물이다. 인수인계에 남긴다.
- Listener host, port, service owner.
- Server의 reverse target 설정.
- Firewall rule ID와 변경 ticket.
- Certificate 위치와 갱신 담당.
- 예상 endpoint security policy와 user token type.
- 정상 reconnect 주기와 정상 log 문구.
- Live data 확인에 쓰는 test tag나 folder.
Reverse connect는 꼼수가 아니다. Inbound를 안 열어 주는 방화벽에 대한 정당한 답이다. 다만 접속 방향, trust, endpoint 선택이 시운전 엔지니어 한 사람의 기억이 아니라 설계 문서에 들어 있어야 안정적으로 유지된다.