← 전체 글
OPC UA/약 12분 읽기/ 조회

OPC UA에서 인증서를 신뢰한다고 write 권한이 생기지는 않는다

OPC UA가 인증서 신뢰, 사용자 identity, Part 18 role mapping을 어떻게 나누는지, 그리고 엉뚱한 session이 setpoint를 못 바꾸도록 write 권한을 시운전하는 방법.

OPC UASCADA네트워킹문제 해결프로젝트 노트

한 통합업체가 "보안된" OPC UA server를 넘겨준 적이 있다. Basic256Sha256 policy, 상호 인증서 신뢰, 모든 메시지 암호화까지 다 되어 있었다. 그런데 historian collector 계정이 mixer speed setpoint에 write가 됐다. "일단 접속되게" 하는 데서 멈추고 사용자 권한을 아무도 설정하지 않았기 때문이다. 채널은 끝에서 끝까지 암호화됐지만, 누가 무엇을 해도 되는지는 완전히 틀려 있었다.

이 간극이 문제의 전부다. OPC UA에서 security라는 한 단어 안에는 서로 다른 질문 세 개가 숨어 있고, 각각 스택의 다른 곳에서 답이 정해진다.

  • 어떤 application이 채널을 열 수 있는가 — application instance certificate (Part 4 OpenSecureChannel, GDS 기반 신뢰는 Part 12).
  • 이 session은 누구인가ActivateSession에 넘기는 user identity token (anonymous, username/password, X.509, issued token).
  • 이 session이 그 node에 무엇을 할 수 있는가 — role mapping과 RolePermissions / UserRolePermissions attribute (Part 18, Role-Based Security).

앞의 두 개를 통과해도 tag를 써도 되는 것은 아니다. 시운전에서 이 셋을 섞으면, 사실은 부여된 적 없는 role 문제를 하루 종일 "인증서 오류"라고 쫓게 된다.

User identity token은 ActivateSession에서 정해지니 신중히 고른다

User identity는 인증서와 별개이고, session을 activate할 때 제시한다. OPC UA는 token 종류 네 개를 정의하는데, 이 선택은 보안 강도보다 운영 방식의 문제에 가깝다.

Identity token쓰이는 자리나중에 무는 지점
AnonymousRead-only 진단, 격리된 데모Test client 때문에 켰다가 FAT 뒤에 남음
Username/passwordSCADA service account, engineering loginRotation, 그리고 모두가 opcuser 하나 공유
X.509 user certificate사용자와 session을 강하게 묶을 때추적·폐기할 인증서 수명이 하나 더 생김
Issued token (JWT/OAuth2)외부 identity provider 연동시간 오차, token TTL, server 지원 여부

플랜트 SCADA에서 나는 공유 계정보다 연동 경로별 named service account를 기본으로 쓴다. HMI runtime, historian collector, MES gateway, engineering workstation을 각각 분리한다. 설정에 몇 분 더 들지만, audit log가 HistorianCollector로 읽히느냐 전부 opcuser로 읽히느냐의 차이다. 후자면 audit trail은 아무것도 알려주지 못한다.

Role은 동작 기준으로 나누고 폴더를 믿지 않는다

편한 지름길은 주소 공간 폴더 단위로 권한을 주는 것이다. "Operator는 Area1 write 가능." 그런데 Area1 안에는 status tag, command tag, recipe 값, bypass bit, diagnostic control이 섞여 있고 위험도가 제각각이다. 폴더 단위 권한은 이 구분을 뭉갠다.

Part 18은 권한을 걸 well-known role을 준다 — Anonymous, AuthenticatedUser, Observer, Operator, Engineer, Supervisor, ConfigureAdmin, SecurityAdmin. 각 node는 RolePermissions(그리고 session별 UserRolePermissions)를 갖고, role마다 Browse/Read/Write/Call 가능 여부를 정한다. 아직 Part 18 node model을 완전히 구현하지 않은 server도 많아서 결국 vendor의 자체 role UI로 표현하게 되더라도, 설계는 이 모델 기준으로 하는 게 맞다. 중요한 건 폴더가 아니라 동작의 matrix다.

RoleBrowseReadSetpoint writeCommand writeAlarm ackMethod call
HistorianCollector가능가능불가불가불가불가
HmiOperator가능가능제한적제한적가능선택 항목만
Maintenance가능가능제한적제한적가능진단용만
MesGateway가능가능Recipe/download만직접 jog 불가불가선택 항목만
Engineering가능가능설정된 범위설정된 범위가능설정된 범위

Write와 method call은 둘 다 설비를 바꾼다. 그런데 한쪽만 잠그고 다른 쪽을 잊기 쉽다. HMI가 setpoint를 Write로 옮기고 MES가 recipe를 Call로 내려보낸다면, 두 경로 모두 role 검사 audit 기록이 필요하다. StartCycle, DownloadRecipe, ResetFaults 같은 method는 server나 equipment layer에서 role을 확인하지 않고 노출하면 안 된다. HMI에서 버튼을 숨기는 건 접근 제어가 아니다. NodeId만 알면 그 node는 여전히 써진다.

제어 동작에는 role만으로 부족하다

프로세스를 건드리는 동작에는 role 검사 하나로는 부족하다. 각 write나 method call마다 미리 정해 둔다.

  • 필요한 role,
  • 필요한 equipment mode 또는 ownership 상태 (올바른 role이라도 잘못된 시점이면 위험하다),
  • 허용 값 또는 argument 범위,
  • 기대하는 StatusCode와 error text,
  • 남길 audit 항목,
  • 실패 시 alarm을 올릴지 여부.

이 전부는 server나 equipment layer가 강제한다. Client는 무엇을 제시할지만 정한다.

거부는 이유까지 말하게 만든다

보안이 잘 된 시스템은 실패도 큰 소리로 해야 한다. Write가 튕겼을 때 role, mode, range, 잘못된 node, 인증서, 죽은 session을 구분할 수 있어야 하는데, OPC UA는 이미 그 구분에 맞는 StatusCode를 준다.

  • Bad_UserAccessDenied — role/권한 검사 실패.
  • Bad_NotWritable — 원래 쓸 수 없는 node.
  • Bad_OutOfRange — 값 범위 실패.
  • Bad_SecurityChecksFailed — authorization 이전에 이미 security/certificate 문제.
  • Bad_SessionClosed — 죽은 session, reconnect 필요.

HMI의 역할은 이 중 중요한 것을 operator가 조치할 수 있는 문장으로 바꾸는 것이다. Write failed는 쓸모없다. HmiViewer role은 Mixer M-101 Speed SP를 쓸 수 없습니다는 권한 문제임을 알려주고 tag를 짚어준다. Bad_UserAccessDeniedBad_SecurityChecksFailed냐 이 하나가 "role이 틀렸다"와 "인증서가 틀렸다"를 가르고, 둘의 조치는 완전히 다르다.

Audit event가 재구성해야 할 것

Write, method call, alarm acknowledge, 권한 실패는 몇 달 뒤에도 동작을 복원할 수 있을 만큼 남긴다.

  • server·source timestamp (가능하면),
  • session name, application URI, endpoint, client IP,
  • user identity mapped role,
  • NodeId, browse path, display name,
  • operation type (read/write/call/acknowledge/condition-refresh),
  • write라면 이전 값·요청 값·결과,
  • call이라면 argument와 result — secret 제외.

Password, token, private key material은 절대 log에 남기지 않는다. Audit trail은 동작을 설명해야지 secret 저장소가 되면 안 된다. 그리고 application name만 있고 session의 user identity가 없다면, audit이 답해야 할 바로 그 질문에 답 못 하는 log를 갖게 된다.

인증서 신뢰와 role mapping은 다르게 실패한다

User authentication은 application certificate trust를 대체하지 않는다. 둘은 함께 시운전한다. 가동 전에 확인한다. server가 client 인증서를 trust하는지, 반대도 되는지, endpoint URI 기준이 문서화됐는지, 만료일을 미리 아는지, rejected 인증서를 무조건 신뢰 목록에 넣지 않고 검토하는지, test와 production 인증서를 이유 없이 섞지 않는지. Backup/restore에는 certificate store와 필요한 private key가 포함돼야 한다. 안 그러면 server를 다시 세울 때 모든 client의 신뢰가 한 번에 날아간다.

Client가 "authentication failed"라고 하면 첫 수는 실패 지점을 시간축에 놓는 것이다. User login (인증서 신뢰, Bad_SecurityChecksFailed로 보임)에 실패했는지, (role mapping, Bad_UserAccessDenied로 보임)에 실패했는지. 이후 모든 게 여기에 달려 있다.

실제로 나오는 실패들

  • Historian collector가 operator 계정으로 돌면서 write 권한까지 조용히 물려받는다.
  • MES gateway가 recipe node는 browse하지만 download method를 Call하지 못한다.
  • HMI popup은 command 버튼을 숨겼지만 같은 user로 node write가 된다.
  • FAT 때 test client 때문에 켠 anonymous read가 아무도 안 꺼서 살아남는다.
  • Redundant HMI 한 대만 credential을 local에 저장해, rotation 뒤 그 한 대만 죽는다.
  • Audit log에 application name은 있고 session user identity가 없다.
  • Engineering tool이 예전 credential을 cache해서 실제 권한 시험을 가린다 — 현재 설정으로는 안 될 접근을 "확인"하게 된다.
  • Certificate 갱신을 role mapping 변경 탓으로 돌리거나, 반대로 돌린다.

이 거의 전부가 application trust, session authentication, authorization을 한 단어로 뭉친 데서 나온다.

다시 돌릴 수 있는 시운전 순서

Interface ready라고 말하기 전에 작은 반복 시험을 돌리고, 그대로 보관한다. 다음 인증서 갱신 뒤에 또 쓰게 된다.

  1. Trust 안 된 client 인증서로 접속, 거부(Bad_SecurityChecksFailed) 확인.
  2. 의도한 인증서를 trust하고 read-only user로 접속.
  3. 허용된 node의 browse·read 확인.
  4. Read-only user로 setpoint write 시도, Bad_UserAccessDenied 확인.
  5. 실제 HMI 또는 service account로 접속.
  6. 허용된 test setpoint를 쓰고 value·audit event·StatusCode 확인.
  7. 범위 밖 write 시도, 깨끗한 Bad_OutOfRange 확인.
  8. 필요한 role 없이 command/method call 시도, 거부 확인.
  9. Server log에 identity·role·node·operation·result가 남는지 확인.
  10. Test 계정 credential을 바꾸고 reconnect 동작 확인.

Turnover 기록에는 endpoint URL, security policy, identity 방식, role mapping, certificate thumbprint, 그리고 이 시험 결과를 남긴다. 인증서가 만료되거나 server를 교체하는 날, 이 한 장이 10분 수리와 반나절 추측을 가른다.

층을 헷갈리지 않게 하는 규칙 하나. Certificate는 어떤 application이 접속할 수 있는지 정하고, user identity와 role은 session이 무엇을 할 수 있는지 정하고, equipment logic은 지금 그 동작이 안전한지 정하고, audit event는 나중에 무슨 일이 있었는지 증명한다. Write가 거부됐을 때 이 넷 중 무엇인지 이미 알고 있다면, 이미 절반은 해결한 셈이다.