브라우저에서 쓰기 가능하다고 운전원도 쓸 수 있는 것은 아니다
UaExpert에서 setpoint가 잘 보인다. 연필 아이콘이 있고, 값을 입력하면 써진다. 그런데 HMI가 SCADA service account로 실제 운영에 들어가면 같은 노드가 write마다 BadUserAccessDenied(0x801F0000)를 돌려준다. 주소 공간은 아무것도 바뀌지 않았다. 바뀐 것은 계정(identity)이다.
OPC UA에는 대충 보면 비슷해 보이지만 서로 다른 네 가지 속성이 있다. OPC UA Part 3에 따르면 AccessLevel은 노드가 지원하는 동작을 나타내는 바이트 비트마스크다. CurrentRead가 bit 0(값 1), CurrentWrite가 bit 1(값 2)이므로, 쓰기 가능한 값 노드는 AccessLevel = 3으로 읽힌다. UserAccessLevel은 인코딩은 똑같지만 지금 세션 기준으로 걸러진 값이다. 엔지니어 계정으로 접속한 브라우저 도구는 3을 보고, 제한된 런타임 계정은 1을 본다. 바로 이 1비트 차이가 실패의 원인이고, 잘못된 계정으로 켠 브라우저 도구는 이 차이를 완전히 가린다.
WriteMask와 UserWriteMask는 전혀 다른 것이며(아래에서 설명), 둘 다 Value를 쓸 수 있는지와는 무관하다.
브라우저 도구에 연필 아이콘이 보였다는 이유만으로 write 경로를 승인하면 위험하다. 그 연필은 브라우징 세션의 UserAccessLevel을 반영할 뿐, 런타임의 권한이 아니다. 실제 운영에 쓸 endpoint, 보안 모드, 인증서, 계정, 역할로 다시 시험해야 한다. 그러지 않으면 잘못된 권한 집합을 시험하는 셈이다.
태그 매핑 옆에 접근 권한을 같이 남긴다
운전원이 쓸 수 있는 태그는 간단한 권한 기록을 같이 두는 편이 좋다. 별도 보안 문서처럼 크게 만들 필요는 없다. 대신 태그 목록이나 I/O checkout sheet에서 바로 볼 수 있어야 한다.
| 항목 | 남길 내용 | 필요한 이유 |
|---|---|---|
| NodeId와 BrowseName | HMI가 실제로 쓰는 원본 노드 | 비슷한 이름의 engineering 노드에 쓰는 실수를 막는다 |
| Data type | Boolean, Int16, Float, enum, structure | 형식 오류와 setpoint 포맷 문제를 줄인다 |
| AccessLevel | 서버가 제공하는 기본 읽기/쓰기 능력 | 해당 노드가 write 대상인지 확인한다 |
| UserAccessLevel | 런타임 계정 기준 권한 | 역할, 인증서, 계정 차이를 잡는다 |
| 범위와 단위 | engineering limit와 단위 | 프로토콜상 유효하지만 공정상 위험한 값을 막는다 |
| 예상 거부 사유 | 로컬 모드, 인터록, 레시피 잠금, bad quality | HMI에 쓸 수 있는 안내 문구가 된다 |
장비 업체가 command node와 feedback node를 같이 노출할 때 특히 중요하다. Pump101.StartCmd와 Pump101.Running은 주소 공간에서 가까이 있을 수 있다. 하지만 HMI가 써야 할 노드는 하나뿐이다.
WriteMask는 운전 조작 권한으로 보지 않는다
WriteMask를 보고 "이 태그 값에 write 가능"이라고 해석하는 경우가 있다. 아니다. OPC UA Part 3에서 WriteMask는 노드의 어떤 속성을 바꿀 수 있는지를 나타내는 32비트 맵이다. DisplayName, Description, AccessLevel 속성 자체 등이 대상이다. Value 속성에도 전용 비트가 있지만, 일반 Variable에서 Value의 쓰기 가능 여부는 WriteMask가 아니라 AccessLevel/UserAccessLevel의 bit 1(CurrentWrite)이 결정한다. 그래서 노드가 WriteMask = 0이어도 값 write는 얼마든지 받을 수 있다. 일반 SCADA 런타임에 WriteMask가 켜져 있을 일은 거의 없다. 켜져 있다면 누군가 운영 중에 태그 이름을 바꿀 수 있다는 뜻이다.
운전 조작에서는 아래 항목을 봐야 한다.
- 런타임 세션의 UserAccessLevel에
CurrentWrite가 있는가. - data type과 array 형태가 맞는가.
- engineering range check가 있는가.
- 서버의 role, certificate mapping 규칙이 맞는가.
- OPC UA write 후 PLC나 장비 상태가 명령을 받을 수 있는가.
HMI가 런타임에 metadata를 바꿔야 한다면 그것은 설정 관리 기능으로 따로 봐야 한다. 기동, 정지, 리셋, setpoint 입력은 metadata write가 없어도 동작해야 정상이다.
실제 운영 계정으로 시험한다
쓰기 실패의 상당수는 태그 문제가 아니라 identity 문제다. 엔지니어 계정으로는 OPC UA test client에서 잘 써진다. 그런데 SCADA service account는 권한이 없다. 인증서를 교체한 뒤 서버가 그 인증서를 다른 role로 매핑해서 갑자기 write가 막히기도 한다.
시운전 때는 작은 write test matrix를 만든다.
- SCADA 런타임에서 쓰는 계정, 인증서, endpoint URL, security policy로 접속한다.
- 대상 노드의
AccessLevel과UserAccessLevel을 읽는다. - 안전한 test value를 쓰거나 업체가 제공한 simulation command를 사용한다.
- OPC UA write 결과만 보지 말고 설비 feedback이 바뀌는지 확인한다.
- 권한이 없는 계정으로도 한 번 시도해서 거부되는지 본다.
- 서버 audit log에 성공과 실패가 어떻게 남는지 확인한다.
Setpoint라면 낮은 값, 정상 값, 높은 값, 범위 밖 값을 나눠서 본다. Command bit라면 클라이언트 연결이 끊긴 뒤 bit가 latch되어 남지 않는지도 봐야 한다.
프로토콜 거부와 공정 거부를 구분해서 보여준다
운전원은 HMI가 태그를 못 쓴 것인지, 장비가 명령을 받은 뒤 거부한 것인지 알아야 한다. 대응 방법이 다르다. Write 서비스(OPC UA Part 4, §5.10.4)가 돌려주는 status code가 어느 계층에서 거부했는지 알려주므로, 이 코드를 숨기지 말고 그대로 드러내야 한다.
| status code | 의심 위치 | HMI 문구 예 |
|---|---|---|
BadUserAccessDenied (0x801F0000) | OPC UA 사용자 또는 role | 쓰기 거부: HMI 계정 권한 없음 |
BadNotWritable (0x803B0000) | OPC UA 노드 접근 (CurrentWrite 없음) | 쓰기 거부: 읽기 전용 노드 |
BadTypeMismatch (0x80730000) | 클라이언트 값 형식 | 쓰기 거부: 값 형식 불일치 |
BadOutOfRange (0x803C0000) | 서버 범위 검증 | 쓰기 거부: 값 범위 초과 |
Good인데 feedback이 안 바뀜 | PLC 또는 장비 로직 | 명령 전송됨, 운전 feedback 없음 |
Good 후 status 태그에 reject code | 공정 조건 검증 | 명령 거부: 로컬 모드 |
이런 경우를 모두 Command failed로 뭉개면 원인 찾기가 느려진다. 위쪽 Bad* status code 묶음은 보통 엔지니어링 설정이나 보안 설정 문제다. Good이지만 효과가 없는 경우는 로컬 모드나 인터록처럼 운전원이 현장 패널에서 해결할 수 있는 정상적인 공정 동작일 때가 많다.
자주 나오는 실패 패턴
브라우징 계정과 런타임 계정이 다르다
관리자 계정으로 태그를 browse하고 import했다. 실제 런타임은 제한 계정을 쓴다. 읽기 태그는 전부 정상인데 setpoint만 배포 후 실패한다.
현장 확인: 실제 런타임 서버에서 실제 런타임 credential로 browse와 write를 한 번 수행한다.
서버 교체 후 role mapping이 초기화된다
OPC UA 서버를 업그레이드했는데 endpoint와 NodeId는 그대로다. 하지만 certificate-to-role mapping이 빠졌다. HMI는 접속하고 읽을 수 있지만 write는 access denied로 떨어진다.
현장 확인: backup/restore 항목에 인증서와 endpoint뿐 아니라 user, role mapping도 넣는다.
HMI가 feedback 노드에 쓴다
템플릿 바인딩이 잘못되어 command와 feedback을 바꿔 물었다. 깔끔하게 실패하면 다행이다. 어떤 장비는 simulation이나 override 노드가 따로 있어 더 위험하다.
현장 확인: command마다 NodeId를 업체 인터페이스 문서나 PLC export와 대조한다.
남겨야 할 시운전 증거
다음 엔지니어가 무엇을 시험했는지 알 수 있을 정도는 남겨야 한다.
- endpoint URL, security policy, message mode;
- client certificate thumbprint 또는 application URI;
- 시험에 쓴 runtime user 또는 role;
- 대상 NodeId, data type, engineering unit;
- 성공한 write 값과 실제 feedback;
- 거부 시험에 사용한 값과 returned status code;
- 운전원 화면에 표시된 HMI 메시지.
사람들이 빼먹는 시험은 부정(negative) 시험이다. 쓰면 안 되는 계정으로 로그인해서 setpoint를 시도하고, BadUserAccessDenied가 나오는지, audit 기록이 남는지 확인하는 것이다. 허가된 경우만 계속 증명하는 write 경로는 절반만 시험한 것이다. 문이 열린다는 것만 보였을 뿐, 잠긴다는 것은 보이지 않았다.