현재 사용법 (2026-09-10,
6fbaee7f): 플랫폼 운영 콘솔 · 기능 마스터. 아래는 작성 당시의 변경 설명이며 현재 전체 동작·재측정 결과를 뜻하지 않습니다.
system-admin — 로그인 비밀번호 필드 암호화 (2026-08-29)
시스템 관리 콘솔에서 로그인하거나 엔드포인트 자격증명을 등록할 때, 비밀번호가 요청 본문에 평문 대신 서버 공개키로 암호화된 값(passwordEnc)으로 전송됩니다. 화면에 보이는 동작은 바뀌지 않습니다.
직관: 콘솔 서버는 받은 비밀번호를 평문으로 복원할 수 있어야 하므로(아래 배경), 단방향 해시나 PAKE는 쓸 수 없습니다. 남는 선택지는 공개키 필드 암호화이고, 여기에 서버 발급 1회용 challenge를 결합해 암호문 재전송(replay)까지 막는 것이 이 변경의 전부입니다.
배경 / 지금 동작
콘솔 로그인은 비밀번호를 직접 검증하지 않습니다. server/index.ts의 로그인 라우트가 자격증명을 대상 엔드포인트(독립 인증 realm)의 account.login으로 재전달해 검증을 위임하고, 성공하면 같은 자격증명을 세션 매니저에 등록해 이후 멀티 엔드포인트 위임 호출에 씁니다. Endpoints 탭의 자격증명 등록 폼(routes/endpoints.ts)도 같은 구조입니다. 즉 서버는 평문 비밀번호가 반드시 필요하고, 이 제약이 방식 선택을 결정합니다 — 브라우저에서 해시해 보내면 엔드포인트에 로그인할 수 없습니다.
전송 자체는 TLS(HTTPS)가 이미 암호화합니다. 이번 변경이 추가로 막는 것은 TLS가 끝나는 지점들입니다: TLS 종단 장비(CloudFront 등)와 사내 SSL 인스펙션 프록시에서의 평문 노출, 그리고 엣지·서버의 실수 요청-바디 로깅입니다. XSS나 서버 자체 침해는 이 변경으로 막히지 않습니다 — TLS를 대체하는 것이 아니라 그 위에 얹는 심층 방어입니다.
데이터 흐름
변경 전
변경 후
서버 내부 이후 단계(엔드포인트 위임·vault)는 변경되지 않습니다 — 암호화 구간은 브라우저→콘솔 서버 한 홉입니다.
개념 흐름별 변경
서버가 공개키와 1회용 challenge 를 발급합니다. 신설 server/login-crypto.ts가 프로세스 기동 시 RSA-OAEP-2048 키쌍을 메모리에 생성하고, GET /api/auth/login-key(신설, public)가 공개키(SPKI base64)와 함께 난수 challenge 를 발급합니다. challenge 는 2분 TTL 의 인메모리 저장소에 들어가며, 복호화 시도 시 결과와 무관하게 즉시 폐기됩니다. 키를 ephemeral 로 둔 것은 키 관리를 없애고 유출 범위를 프로세스 수명으로 제한하기 위해서입니다 — 단일 replica 전제이며, 콘솔을 다중 replica 로 늘리면 env(PEM) 고정 키로 바꿔야 한다는 조건을 파일 주석에 남겼습니다.
복호화가 재전송 차단을 겸합니다. decryptPasswordEnc()는 암호문을 복호화해 { password, challenge }를 꺼낸 뒤, challenge 가 발급 저장소에 살아 있는지 확인하고 지웁니다. 같은 암호문을 다시 보내면 challenge 가 이미 소진돼 400으로 거부됩니다 — 이게 빠지면 "암호문이 로그에 남아도 재사용 불가"가 성립하지 않아 필드 암호화의 의미가 반감됩니다. timestamp 방식 대신 서버 발급 challenge 를 쓴 것은 클라이언트 시계 오차에 의존하지 않기 위해서입니다.
자격증명을 받는 두 라우트에 같은 처리를 넣습니다. 로그인(server/index.ts)과 엔드포인트 자격증명 등록(server/routes/endpoints.ts) 모두 passwordEnc가 오면 복호화해 기존 평문 변수에 채우고, 이후 로직(엔드포인트 검증·vault 등록)은 한 줄도 바뀌지 않습니다. 평문 password 필드도 계속 받습니다 — UI 는 항상 암호화해 보내지만, 스모크 스크립트 같은 비-브라우저 호출의 호환을 위해서입니다(전송은 TLS 가 보호).
프런트는 제출 직전에 암호화합니다. 신설 src/login-crypto.ts의 encryptLoginPassword()가 login-key 를 받아 WebCrypto(RSA-OAEP-SHA256)로 암호화하고, 로그인 폼(doLogin)과 엔드포인트 자격증명 폼(saveEndpointCredentials)이 이를 사용합니다. WebCrypto 는 secure context(https·localhost) 전용이라, 없는 환경에서는 평문 폴백 대신 명확한 에러 문구로 막습니다. RSA-OAEP-2048 평문 한도(190바이트) 초과도 암호화 전에 걸러냅니다. 두 폼의 catch 가 이런 준비-단계 에러 메시지를 그대로 화면에 보여주도록 함께 고쳤습니다.
검증
로컬 서버(:4198)에서 5개 시나리오를 실측했습니다: ① 실계정 암호화 로그인 200 + 토큰 발급, ② 같은 암호문 재전송 400(만료), ③ 틀린 비밀번호 401(복호화는 통과, 인증만 실패), ④ challenge 누락 위조 평문 400, ⑤ 평문 폴백 200. 이어 빌드된 UI 를 헤드리스 브라우저로 실로그인해 요청 본문에 identifier·passwordEnc만 실리고 평문 비밀번호가 없음을 확인했습니다. tsc(클라이언트·서버)·biome·vite build 모두 통과했습니다.