Server에 ping이 된다. opc.tcp://opcua-prod-01:4840이 port에 응답한다. Server process도 올라와 있고 tag도 다 잡혀 있다. 그런데 client는 붙지 않는다. Socket은 열렸는데 address space를 browse하기 전 어딘가에서 session이 죽는다.
이럴 때는 sampling interval이나 subscription을 보지 마라. Session은 거기까지 가지도 못했다. 실패한 것은 identity다. Client가 endpoint URL, server certificate, ApplicationUri를 자기가 기대한 값과 비교했는데 그중 하나가 안 맞은 것이다. 네트워크 문제처럼 보이지만 사실은 certificate와 이름 문제다.
Socket은 검사보다 먼저 열린다
OPC UA client가 하는 두 단계를 사람들이 뭉뚱그린다. 먼저 endpoint URL로 TCP를 열고 GetEndpoints(OPC UA Part 4)를 부른다. 이건 port만 닿으면 성공한다. ping과 telnet이 사람을 속이는 이유가 이것이다. 그다음에 endpoint를 골라 SecureChannel을 열려고 하는데, certificate와 URI 검사는 여기서 일어난다. Socket이 멀쩡하다는 사실은 SecureChannel이 살아남을지에 대해 아무것도 말해 주지 않는다.
GetEndpoints는 EndpointDescription 목록을 돌려준다. 각 항목에는 endpoint URL, server의 ApplicationDescription(applicationUri 포함), server certificate 원본, securityPolicyUri, securityMode가 들어 있다. 이 목록을 저장해 둬라. 시운전에서 가장 흔한 실수는 server가 실제로 advertise하는 값이 아니라 client 설정 화면에 입력한 주소를 믿는 것이다. 그리고 server는 자기 실제 이름을 알려 주지 않으면 opc.tcp://localhost:4840이나 bench laptop hostname을 태연하게 advertise한다.
Status code가 알려 주는 것
Client log는 대개 고장을 정확히 짚어 준다. 중요한 세 가지를 외워 둬라.
BadCertificateHostNameInvalid— 접속한 host(예:10.20.30.40)가 server certificate의 Subject Alternative Name에 없다. OPC UA Part 4에 따르면 endpoint host는dNSName또는iPAddressSAN 항목으로 들어 있어야 한다. IP만 든 cert에 DNS로 붙거나 그 반대면 이게 뜬다.BadCertificateUriInvalid— certificate의 ApplicationUri가 endpoint ApplicationDescription의applicationUri와 안 맞는다. ApplicationUri는 cert 안에uniformResourceIdentifierSAN 항목으로 들어 있고, 스펙은 둘이 글자 하나까지 같기를 요구한다. Vendor firmware upgrade가 URI를 새로 만들면서 옛 cert를 재사용하면 여기 걸린다.BadSecurityChecksFailed— 나머지 전부. Chain 검증 실패, cert 만료, 또는 발급 CA가 client trust list에 없음.
이 셋 중 어느 것도 protocol bug가 아니다. Security layer가 IEC 62443이 원하는 그대로, 정체를 확인할 수 없는 endpoint에 붙기를 거부하는 것이다. 특히 ApplicationUri 검사는 같은 IP를 차지한 사칭 server에 client가 속아서 붙는 일을 막으려고 존재한다.
Identity와 네트워크를 가르는 신호
질문 하나면 대부분 빨리 갈린다. DNS 이름으로 붙을 때와 raw IP로 붙을 때 증상이 달라지는가? IP는 되고 DNS는 안 되면(또는 반대면) certificate SAN이 지금 쓰는 경로를 안 덮는 것이다. 둘 다 SecureChannel 단계에서 똑같이 실패하면 ApplicationUri나 trust store를 봐라.
두 번째 유용한 구분. SecurityPolicy#None으로 anonymous browse는 되는데 Basic256Sha256에 Sign이나 SignAndEncrypt를 걸면 실패하는가? 그렇다면 transport는 멀쩡하고 문제는 certificate identity로 좁혀진다. None endpoint는 signed endpoint가 강제하는 검사를 건너뛴다.
Certificate validation을 꺼서 "방향만 확인"하는 건 bench에서 5분이면 괜찮다. 하지만 production에서 꺼 두는 건 고친 게 아니다. 잘못 입력됐거나 위조된 endpoint가 조용히 trust되는 걸 막는 장치를 뗀 것이다.
실제로 어디서 오고, 무엇을 해야 하나
| 무엇이 틀렸나 | 어떻게 확인하나 | 조치 |
|---|---|---|
| Cert 발급 후 server rename | SAN에 예전 hostname만 있음 | 최종 hostname과 ApplicationUri로 cert 재발급 |
| Client는 IP, cert는 DNS SAN만 | Client URL과 cert SAN 목록 비교 | DNS로 붙거나, 정책이 허용하면 iPAddress SAN 추가 |
| Firmware upgrade에서 ApplicationUri 변경 | Endpoint list의 applicationUri와 cert URI SAN 대조 | Server URI와 cert를 맞추고 모든 client에서 re-trust |
| Redundant pair가 cert 하나 공유 | Primary와 standby cert를 따로 확인 | 두 host를 덮는 cert를 발급하거나 host별로 하나씩 |
| 폐기한 cert가 아직 trust됨 | Trust store에 비슷한 이름 cert가 둘 | 옛것 삭제, active thumbprint 기록 |
Endpoint가 localhost를 advertise | GetEndpoints가 opc.tcp://localhost:4840 반환 | Client 배포 전에 server bind/advertise hostname 수정 |
이 중 어느 것도 Basic128Rsa15나 None으로 낮춰서 해결하지 않는다. 그 policy들은 이유가 있어서 deprecated고, 거기 손대는 순간 15분짜리 이름 정리가 상시 audit 지적으로 바뀐다.
DNS와 NAT: cert 만들기 전에 정해라
OPC UA certificate는 이름에 대해 발급되지, 그 장비로 가는 모든 경로에 대해 발급되지 않는다. 그래서 순서가 중요하다. DNS record를 먼저 확정하고, 그 이름으로 certificate를 만들고, 그다음 server가 advertise하는 URL에 client를 맞춘다. 반대로 하면 누군가 새 경로를 발견할 때마다 cert를 재발급하게 된다.
몇 가지는 양보하지 않는 편이다.
- Server당 canonical endpoint 이름 하나.
opcua-prod-01.local, 짧은 hostname, raw IP를 client마다 섞으면 cert가 셋 다 덮어야 하고 항상 하나가 빠진다. - Server stack과 cert 계획이 명시적으로 감당하지 않는 한 OPC UA 앞에 load balancer나 reverse proxy를 두지 않는다. Client는 자기가 건 이름을 검증하는데 proxy는 대개 SAN에 없다.
- NAT가 불가피하면 client가 inside name, outside name, translated IP 중 무엇으로 붙는지 적어 둔다. 그 한 문장이 다음 담당자의 오후를 살린다.
- 임시 service laptop에는 별도 endpoint profile을 준다. 다음 주면 사라질 bench 장비 맞추자고 production certificate를 계속 고쳐 쓰지 마라.
실제로 돌릴 상태로 시험해라
시운전을 통과하는데 아무도 안 건드려서 살아남는 고장이 둘 있다.
Certificate import 후 server를 재시작하고 다시 붙어 봐라. Cert를 startup에서만 load하거나 re-bind하는 stack이 있어서, import 직후엔 잘 붙던 client가 다음 날 service가 튀면 실패한다.
그리고 redundant pair에서는 active뿐 아니라 standby 경로도 시험하라. 전형적인 누락은 primary는 완벽히 검증됐는데 standby가 primary hostname만 든 복사 certificate를 지니고 있는 경우다. Failover하는 날까지 안 보이는데, 그날이 바로 debug할 여유가 없는 날이다.