Domain policy가 자정에 DOMAIN\svc_scada를 만료시켰다. 공장에는 아무도 없었다. 06:40에 주간 근무자가 본 상태는 이랬다. HMI는 떠 있고 값도 초록색, historian은 6시간이 비어 있고, 야간 batch report는 아예 생성되지 않았다.
HMI가 멀쩡했던 이유는 driver service가 몇 시간 전에 인증을 마치고 session을 붙잡고 있었기 때문이다. 자정에 죽은 건 다시 인증해야 하는 쪽뿐이다. Idle timeout 후 재접속하는 historian SQL connector, 02:00 report task, file share write. Service account 장애는 대체로 이 모양이다. 부분적으로, 조용히, 첫 한 시간은 엉뚱한 subsystem을 의심하면서.
Runtime 계정은 IT 뒷단 항목이 아니라 제어 시스템 설정이다. IEC 62443-3-3은 사람과 software를 아예 다른 요구사항으로 갈라 둔다. SR 1.1이 human user 식별·인증이고, SR 1.2가 software process와 device의 식별·인증이다. Service account가 들어가는 칸은 SR 1.2다. 그 credential을 어떻게 발급하고 폐기하고 바꾸는지는 SR 1.5(authenticator management)가 맡는다. 길이와 구성 규칙을 다루는 SR 1.7(strength of password-based authentication)은 human user 쪽에 초점이 있는데, 이 사실이 아래 이야기의 절반이다. 이걸 조직 차원의 정책과 절차로 묶는 요구는 IEC 62443-2-1에 있다. 어느 쪽도 교체 주기를 숫자로 못 박지 않는다.
달력이 아니라 사건이 교체를 부른다
분기마다 전부 바꾸는 정책은 사람이 머리로 외우는 비밀번호를 전제로 만들어졌다. 그 전제는 NIST SP 800-63B §5.1.1.2에서 이미 뒤집혔다. verifier는 memorized secret을 주기적으로 바꾸라고 요구하지 말아야 하며(SHOULD NOT), 침해 정황이 있으면 반드시(SHALL) 변경을 강제해야 한다. 이유는 단순하다. 주기 강제는 Plant2026! 다음에 Plant2027!을 낳는다.
이걸 service account에 그대로 붙여서 "그러니 안 바꿔도 된다"고 말하면 과한 인용이다. 조문이 다루는 건 사람이 외우는 secret이고, service account의 비밀번호는 아무도 외우지 않는다. 설정 파일과 SCM과 vault에 저장될 뿐이다. 다만 그 차이는 주기 교체 쪽에 불리하게 작용한다. 주기 교체가 줄여 주는 위험 — 추측, 재사용, 어깨너머 노출 — 은 사람이 본 적 없는 랜덤 문자열에는 거의 없고, 주기 교체가 만드는 위험 — 절반만 적용된 credential, 자정의 6시간 공백 — 은 그대로 남는다.
NERC CIP 대상이면 감사자가 펴는 건 CIP-007-6 R5이고, 15개월 주기를 요구하는 건 그중 Part 5.6이다. 조문은 범위를 "password-only authentication for interactive user access"로 한정한다. 사람이 로그인 화면 앞에 앉지 않는 service account는 문언상 여기 들어가지 않는다. 이 해석은 감사자와 entity마다 갈리니, 자기 해석을 근거와 함께 문서로 남겨 두는 편이 낫다. 구두로 설명하면 매번 처음부터 설명하게 된다.
교체를 실제로 부르는 건 사건이다.
- credential을 알던 사람이 떠났다. 통합업체 engineer, 퇴사자, 종료된 계약.
- 노출이 의심된다. 평문으로 발견된 설정 파일, 티켓에 첨부된 스크린샷, 메일로 돌아다닌 인수인계 문서.
- Vendor 인수인계. 시운전 계정을 그대로 운영으로 넘겼다면 넘기는 날이 교체일이다.
- 출처를 모르는 lockout, 또는 잦아들지 않는 4625. 조사 사유이자 교체 사유다.
- Project archive나 백업 media가 통제 밖으로 나갔다.
이 목록에 "분기가 끝났다"는 없다.
손대기 전에 credential이 사는 곳을 전부 찾는다
가장 위험한 말은 "그 계정 SCADA에서만 씁니다"이다. 2년 넘은 시스템에서 이 말이 사실이었던 적은 아직 없다.
| 숨어 있는 곳 | 실제 확인 방법 |
|---|---|
| Windows service | Service마다 sc.exe qc <service>, 또는 Win32_Service를 StartName으로 필터 |
| Scheduled task | schtasks /query /fo LIST /v — "Run As User"와 "Logon Mode"를 같이 본다 |
| IIS app pool | appcmd list apppool /@t:* — ProcessModel identity와 cert store 접근 권한 |
| ODBC | User DSN은 옛 profile의 HKCU\Software\ODBC\ODBC.INI에만 있다 |
| OPC Classic (DA) | dcomcnfg → 해당 server → Identity 탭, 그리고 OPCEnum launch/activation ACL |
| OPC UA client | Application instance certificate, private key ACL, endpoint user credential |
| SQL Server | Login, job step별 SQL Agent proxy, linked server, CREATE CREDENTIAL object |
| Connection string | Vendor 설정 파일, web.config, historian collector 설정 |
| File share | Share ACL과 NTFS ACL은 서로 다른 목록이다. 둘 다 본다 |
SCADA project 폴더와 vendor 설정 트리를 계정 이름으로 grep하는 편이, 받아 본 어떤 사용처 문서보다 많이 찾아낸다.
설정 안에 비밀번호가 박혀 있는 경우는 따로 취급해야 한다. HMI project 파일에 저장된 SQL login, historian collector 설정의 저장된 credential, OPC DA용 DCOM Identity — 이런 건 AD에서 계정 비밀번호만 바꿔서는 끝나지 않는다. Project를 열어 고치고, 다시 빌드하고, runtime node와 client마다 재배포해야 반영된다. Client 12대짜리 현장에서 3대를 빼먹으면 그 3대가 계정을 계속 잠근다. 그리고 옛 비밀번호는 project 백업과 version history 안에 그대로 살아 있다. 그 백업을 누가 읽을 수 있는지까지 정리해야 폐기가 끝난 것이다.
시운전에 쓴 계정으로 시험해야 한다
Least privilege는 맞는 방향이고, 과하게 조이면 증상이 일정하게 나타난다. HMI는 열리고 tag read도 되는데 가장자리 기능 하나가 안 된다. Trend export, alarm history insert, recipe download 같은 것들. SCADA stack에서 read 경로와 write 경로는 소유자가 다른 경우가 많다.
실제로 주는 권한은 이렇다.
SeServiceLogonRight("서비스로 로그온") 하나만. Interactive 권한 계열은 주지 않는다. Vendor가 이유를 문서로 대지 못하면 local logon도 없다.db_owner대신 stored procedure schema에EXECUTE. Historian collector가 시운전 후에 DDL이 필요한 경우는 거의 없다.- Report output folder에만 modify. 승인 절차가 다른 시스템에 있으면 recipe template은 read-only.
- Client certificate의 private key read 권한. Windows에서는 certificate store 화면에 보이는 권한이 아니라
C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys아래 key container의 ACL이다. 이거 하나로 반나절 쓰는 현장을 계속 본다. 갱신 주기까지 같이 잡으려면 OPC UA certificate 갱신 체크리스트를 본다.
그리고 그 계정으로 시운전한다. Domain admin으로 시험하면, 빠진 ACL을 startup 당일 고객 뒤에 세워 두고 찾게 된다.
더 나은 답은 교체 작업 자체를 없애는 것이다. Group managed service account(gMSA)는 AD가 비밀번호를 알아서 돌린다. 생성 시 ManagedPasswordIntervalInDays 기본값이 30일이고 생성 이후에는 바꿀 수 없으며, 사람은 그 값을 볼 일이 없다. Forest에 KDS root key가 있어야 하고 service가 gMSA를 지원해야 한다. SQL Server와 IIS는 된다. 지원하는 SCADA service도 늘고 있다. 비밀번호를 자기 설정 파일에 저장하는 제품은 안 되고 앞으로도 안 된다. 그런 제품은 위의 재배포 문제를 그대로 안고 가므로, 수동 교체를 전제로 계획을 잡는다.
교체는 티켓이 아니라 change window다
비밀번호 변경은 service를 재시작하고, database session을 끊고, OPC UA secure channel을 재협상하고, 예약 report를 중단시킨다. 다른 제어 시스템 변경과 똑같이 잡는다.
- 위 사용처 목록을 확정하고, 누가 무엇을 재시작할 수 있는지 정한다.
- 백업이 최신인지 확인한다. SCADA project, vendor 설정, certificate store. 복구가 실제로 되는지는 백업·복구 드릴 쪽 문제다.
- Rollback을 준비한다. 정책상 옛 비밀번호를 잠깐 되살릴 수 있다면, 그 명령을 미리 알아 둔다.
- 새 credential을 한 번에 전부 적용한다. 절반만 적용된 상태가 시작 안 한 것보다 나쁘다.
- 아래에서 위로 재시작한다. Database 접속 확인 → local license service → PLC driver, OPC UA client, gateway → historian interface와 buffer → alarm/event 처리 → HMI runtime과 web gateway → report와 export task.
- 동작을 시험하고, 그다음에 security log를 읽는다.
5번이 생각보다 중요하다. Driver service가 certificate를 못 읽고 있는 상태에서 HMI만 재시작하면, 나중에 20분 걸려 읽어야 할 log만 쌓인다. 그리고 리허설한 적 없는 절차라면 교대 5분 전에 바꾸지 않는다.
진짜 깨지는 지점은 profile에 묶인 상태다
DOMAIN\svc_scada_old를 DOMAIN\svc_scada_new로 바꾸면 %APPDATA%, %LOCALAPPDATA%, HKCU, 사용자별 certificate store가 전부 이동한다. 프로그램이 거기에 조용히 써 둔 것은 service 입장에서 전부 사라진 것이 된다.
- OPC UA client가 새 user store에 application instance certificate를 다시 만든다. Server는 이걸 거부한다. Thumbprint가 다른 신뢰되지 않은 인증서니 거부가 맞다.
- 더 나쁜 경우는 ApplicationUri까지 같이 바뀐 경우다. 새 certificate를 trust해도 해결되지 않는다. IEC 62541-6은 ApplicationUri가 certificate SubjectAltName의
uniformResourceIdentifier항목과 일치할 것을 요구하고, 불일치는BadCertificateUriInvalid로 돌아온다. 권한 문제처럼 전혀 안 보이는 메시지다. - Certificate는 새 store로 복사됐는데 private key ACL이 따라오지 않으면, handshake가 네트워크 장애처럼 보이는 형태로 실패한다.
- User DSN이 사라진다. 변경 전에 System DSN으로 옮긴다.
- Mapped drive는 logon session 상태다. Service는 이걸 안정적으로 보지 못한다. 항상 UNC path를 쓴다.
%APPDATA%아래 vendor license cache. 조용히 재활성화되는 제품도 있고 아예 시작을 거부하는 제품도 있다.
같은 계정의 비밀번호만 바꾸면 위 문제는 하나도 안 생긴다. 계정 자체를 바꾸면 전부 생긴다. 둘은 별개의 change window이고, 붙여서 하면 2시간짜리 작업이 밤샘 작업이 된다.
재시작 후에는 service 상태가 아니라 동작을 본다
Service 목록의 초록색 아이콘은 SCM이 process를 띄웠다는 뜻이다. 인증에 대해서는 아무것도 증명하지 않는다.
| 영역 | 무엇으로 확인하나 |
|---|---|
| Live data | 대표 PLC tag가 good quality이고 값이 변한다 |
| Write 경로 | 정해 둔 safe tag나 simulation point가 값을 받고 feedback까지 온다 |
| Alarm | 알람이 새로 뜨고, acknowledge되고, alarm history에 남는다 |
| Historian | 새 sample이 올바른 timestamp/quality로 들어오고 buffer가 빠진다 |
| Report | 수동 실행이 data query와 실제 폴더 output까지 끝난다 |
| File share | Recipe import/export, backup target, batch report drop folder |
| OPC UA | Secure endpoint가 새 trust prompt나 certificate error 없이 붙는다 |
| Security log | Window를 닫은 뒤 4625 / 4771이 반복되지 않는다 |
Write 시험은 safe tag나 simulation point로 한다. Credential 작업 중에 valve를 즉흥으로 움직여 보는 건 아니다.
반복해서 다시 만나는 유형
HMI는 초록인데 historian에 구멍이 있다. 이미 맺힌 session은 살아남고 재접속은 실패한다. Idle timeout이나 예약 재접속이 있는 쪽이 가장 먼저 죽고 가장 늦게 확인된다.
Report가 밤에만 실패한다. Task Scheduler는 비밀번호 사본을 Credential Manager vault에 따로 들고 있다. Service logon 비밀번호를 바꿔도 이건 갱신되지 않는다. Task마다 다시 입력해야 한다.
노트북에서는 되는데 runtime에서는 안 된다. 노트북 시험은 engineer 계정으로 한 것이다. runas /user:DOMAIN\svc_scada로 vendor diagnostic을 다시 돌리거나, 제품에 "service account로 연결 시험" 버튼이 있으면 그걸 쓴다.
출처를 알 수 없는 authentication failure. Member server의 4625는 process 이름을 알려 준다. Domain controller의 4771 / 4776은 호출한 컴퓨터를 알려 준다. 4625만 쫓으면, 다른 node에 남아 있던 scheduled task가 15분마다 계정을 잠그고 있는 상황을 놓친다.
다음 날 아침에는 SCADA log부터 보지 말고 DC에서 계정 lockout 상태와 badPwdCount를 본다. 놓친 사용처 하나는 장애로 드러나기 한참 전에 거기서 규칙적인 실패로 먼저 보이고, 결국 lockout threshold를 넘는 시점은 하필 교대 시간이다.