← 전체 글
네트워킹/약 13분 읽기/ 조회

SCADA 방화벽 룰 시운전: 포트가 아니라 애플리케이션을 시험하라

SCADA, PLC, HMI, 히스토리언, OPC UA, Modbus TCP, MQTT 방화벽 룰을 데이터 흐름에서 시작해 시운전하는 법 — 커트오버 때 터지는 보조 서비스 경로를 잡고, 각 룰을 실제 애플리케이션 시험으로 증명하는 방법.

네트워킹SCADA문제 해결OPC UAModbus

FAT 내내 잘 돌던 화면이다. 운영망에서 운전자가 로그인하고 그래픽을 열면, 90초쯤 지나 태그 절반이 stale로 바뀐다. SCADA 설정은 아무도 건드리지 않았다. 바뀐 것은 이제 히스토리언 조회가 FAT 벤치에는 없던 방화벽을 지나간다는 점이고, 그 collector 포트의 return 경로가 룰에 없었을 뿐이다.

시운전 방화벽 문제는 대개 이런 모양이다. "방화벽이 다 막고 있다"가 아니라, 대체로 잘 동작하는 애플리케이션 뒤에서 보조 경로 하나가 빠진 것이다. 그래서 룰 세트는 일반 네트워크 잡무가 아니라 제어 시스템의 일부로 다뤄야 한다. 각 룰은 대화를 하나 그린다. 누가 누구와, 어떤 포트로, 어떤 공정 이유로 통신하는가. IEC 62443-3-3이 이것을 zone과 conduit이라고 부르는 데는 이유가 있다. 방화벽 룰은 담당자와 목적이 있는 conduit이거나, 아니면 나중에 애먼 곳이 욕먹을 기술 부채다.

포트보다 데이터 흐름을 먼저 적는다

포트 목록만 있으면 여섯 달 뒤에 해석이 안 된다. Allow 502는 PLC 열한 대 중 어느 것인지 알려주지 않는다. 흐름에서 시작하면 포트는 자연히 따라 나온다.

흐름방향실제로 열려야 하는 것
HMI → SCADA 서버워크스테이션 → 서버웹/thick client 포트, 그리고 인증 의존(AD, 로컬, 또는 SCADA 자체 서비스).
SCADA 드라이버 → PLC서버 → 컨트롤러Modbus TCP 502, DNP3 20000, EtherNet/IP 44818+2222, 또는 벤더 드라이버 포트.
SCADA → 히스토리언서버 → 히스토리언DB/API 포트 그리고 collector가 쓰는 store-and-forward callback.
OPC UA client → serverclient → serverendpoint 포트(기본 4840, 자주 재지정됨) 외에 DNS, NTP, 인증서 교환.
MQTT publisher → brokeredge → brokerTLS는 8883(1883 아님), 여기에 DNS와 두 번째 broker로 가는 bridge.
엔지니어링 PC → PLC워크스테이션 → 컨트롤러상시가 아니라 시간 제한·권한 제한.

각 행마다 source zone, destination zone, protocol, port, 룰을 소유한 시스템, 공정 이유를 남긴다. 주인 없는 룰은 아무도 감히 지우지 못해서 영원히 남는다.

client 시작은 기본값이지 보장이 아니다

산업용 프로토콜은 대부분 TCP를 client가 먼저 열고, stateful 방화벽이 return traffic을 알아서 처리한다. 그러다 드라이버가 callback을 열거나, license check가 밖으로 나가거나, discovery packet이 UDP로 나가는 순간, 깔끔한 단방향 룰로는 부족해진다.

OPC Classic(DA/DCOM)이 대표적인 함정이다. TCP 135에서 협상한 뒤 동적으로 할당된 상위 포트로 옮겨 가기 때문에, 135만 허용한 룰은 연결을 시작시켜 놓고 멈추게 만든다. DCOM 포트 범위를 고정하지 않으면 방화벽 너머로는 동작하지 않는다. 그러니 룰을 승인하기 전에 실제 패킷으로 확인한다.

  • 어느 쪽이 실제로 TCP 연결을 여는가 — 데이터시트를 믿지 말고 확인한다.
  • 이 룰에 방화벽이 stateful인가, 아니면 return 방향을 따로 열어야 하는가.
  • UDP discovery(OPC UA LDS, EtherNet/IP ListIdentity)가 설정 때만인가, 운영 중에도 도는가.
  • 이중화 쌍이 active 하나가 아니라 양쪽 물리 IP에서 통신을 시작하는가.
  • 방화벽 로그에 찍히는 주소가 node IP인가, virtual IP인가, NAT 변환 주소인가.

그리고 ping이 됐다고 룰을 승인하지 않는다. ICMP와 애플리케이션 포트는 무관하다. ping은 막혀도 Modbus는 잘 읽힐 수 있고, ping은 응답해도 502는 여전히 막혀 있을 수 있다.

커트오버가 죽는 곳은 보조 서비스다

애플리케이션 포트가 전부인 경우는 드물다. FAT에서는 되던 것이 격리된 운영 zone에서 실패한다면 보통 이 중 하나다 — SCADA 드라이버를 건드리기 전에 먼저 본다.

  • DNS(53). 호스트명 기반 OPC UA endpoint와 MQTT broker는 사무실 벤치에서는 풀리고, resolver 경로가 없는 zone에서는 실패한다. 증상은 포트가 아니라 이름 해석에서 나는 timeout이다.
  • NTP(123/UDP). 몇 분만 틀어져도 TLS handshake가 인증서를 아직 유효하지 않음으로 거부하고, 이벤트 순서가 뒤엉키고, 히스토리언 timestamp가 거짓말을 한다. 인증서 문제처럼 보이지만 시계 문제다.
  • AD / LDAP(88, 389, 636 + RPC). HMI 페이지는 SCADA 서버가 닿아서 뜨지만, 운전자 로그인은 도메인 컨트롤러가 안 닿아서 실패한다. conduit이 서로 다르다.
  • 인증서 서비스(OCSP/CRL, 보통 80/443). client가 revocation 확인을 하는데 CRL 배포 지점이 안 닿으면 TLS 연결이 멈추거나 끊긴다 — 간헐적으로, 그래서 더 나쁘다.
  • 라이선스 서버. FAT에서 검증되던 runtime 라이선스가 분리된 운영망에서 조용히 실패한다. 이건 항상 커트오버 새벽 2시에 나온다.

정책상 어떤 의존성을 계속 막는다면, 대체 설계를 의도적으로 한다 — 로컬 계정, 현장 NTP, offline/stapled 인증서 검증. 이걸 미리 고르는 것이 엔지니어링이다. 시운전 중에 막혀서 구멍을 뚫어 넘어가는 것은 아니다.

NAT는 지금 쫓는 그 주소를 숨긴다

공장 zone, 벤더 장비, 상위망 사이의 NAT는 피할 수 없을 때가 있고, 모든 packet capture를 모호하게 만든다. 문제를 일으키는 주소들:

  • OPC UA 서버가 GetEndpoints 응답에서 내부 endpoint URL을 광고한다. client는 NAT 주소로 닿았다가 닿지 않는 내부 주소를 받아 들고, handshake 후 session이 죽는다.
  • PLC 룰은 실제 주소로 썼는데 로그에는 변환 주소가 찍혀서, 마치 매칭된 룰이 없는 것처럼 보인다.
  • 히스토리언 collector가 장치 식별자로 NAT 주소를 기록해서, point 출처가 틀어진다.
  • failover가 active 노드를 룰 없는 IP로 옮겨서, 이중화가 "동작"하다가 시험하는 날 무너진다.

경로에 NAT가 있으면 실제 주소와 변환 주소를 모두 시운전 표에 넣는다. 그 한 칸이 양쪽에서 두 시간 캡처할 일을 한 번 보고 끝낼 일로 바꾼다.

telnet 말고 프로토콜로 증명한다

telnet host 502나 nmap의 open 포트는 TCP 3-way handshake가 끝난다는 것만 증명한다. 프로토콜이 동작한다는 증명은 아니다 — stateful 방화벽은 SYN을 통과시키고도 session을 망칠 수 있다. 애플리케이션 계층에서 시험하고 증거를 남긴다.

  • Modbus TCP: 알려진 register를 읽고 값 그리고 quality를 확인한다.
  • OPC UA: browse, live 태그 subscription, 범위에 있으면 허용된 write 하나까지 확인한다.
  • MQTT: 실제 broker listener로 TLS를 켠 채 publish와 subscribe를 한다 — 나중에 닫는 걸 잊을 plaintext 지름길 말고.
  • 히스토리언: 테스트 값을 저장하고 리포트 경로에서 다시 읽는다. 이 경로는 collector와 다른 conduit을 지나는 경우가 많다.
  • HMI: 운전자 권한으로 로그인해서 live data가 있는 그래픽을 연다.
  • 엔지니어링 접속: 승인된 정비 시간 안에서만 online 또는 download를 한다.

각 시험과 같은 timestamp의 방화벽 로그를 함께 챙긴다. 그 로그 한 줄이 의도한 룰로 통신했다는 증거다 — 누가 열어 두고 간 any-any startup 룰이 아니라.

실제로 맞닥뜨리는 일곱 가지 패턴

현장 troubleshooting은 몇 가지 원인으로 수렴한다. "되던" 경로가 이상해지면 거의 항상 이 중 하나다.

  1. 룰은 맞는데 source가 틀림 — 테스트 노트북은 허용, 운영 서버는 아님.
  2. 룰은 맞는데 방향이 틀림 — return은 허용, client 시작이 막힘(또는 callback이면 그 반대).
  3. primary만 — secondary IP, virtual IP, failover 경로에 자기 룰이 없음.
  4. 이름 해석 누락 — IP 직접은 되고 호스트명 endpoint는 안 됨.
  5. 시계 차단 — NTP 막혀서 TLS/OPC UA 인증서가 invalid로 읽힘.
  6. 임시 룰이 가림 — any-any 시운전 permit이 남아서 그 아래 빠진 세부 룰을 전부 숨김.
  7. 로그 꺼짐 — 어떤 룰이 허용·차단했는지 증명 못 해서 모든 분쟁이 추측이 됨.

위험한 건 6번이다. broad 룰을 정리하는 날 전까지는 전부 잘 동작하는 것처럼 보이기 때문이다. 그래서 턴오버 직전 마지막 두 작업은 이것이다. startup permit을 지운 모든 경로를 다시 시험하고, 로깅이 켜져 있는지 확인한다.

턴오버 패키지에 들어가야 하는 것

인수인계 전, 방화벽 패키지는 모든 룰에 대해 — 승인된 source, destination, protocol, port, 목적, 승인자; 주요 흐름마다의 애플리케이션 수준 시험 결과; 이중화 노드·virtual IP·게이트웨이·NAT 주소의 별도 표기; 보조 서비스 conduit(DNS, NTP, 인증, 인증서, 라이선스); 벤더·엔지니어링 임시 룰의 만료일; 생산 커트오버 중 변경에 대한 rollback 방법; 그리고 허용된 traffic과 예상한 deny를 함께 보여주는 로그 샘플을 담아야 한다. 마지막으로, 시운전 후 누가 룰을 요청·승인·삭제할 수 있는지 이름을 적는다 — 그게 모호해지는 순간부터 룰 세트는 썩기 시작하기 때문이다.