Skip to content

포털 API Key 인증 런북 (env 선언 vs 관리형) ​

포털(ProcessCenter·UiCenter·FileStore 등)이 PortalConsole API를 호출할 때 HTTP 401 {"error":"invalid_credentials"} 로 막히는 상황의 원인·진단·조치를 정리한다.

1. 인증 목록이 만들어지는 규칙 (핵심) ​

server/portalApiKeys.ts → resolveConfiguredPortalApiKeys()

키 종류인증 목록에 들어오는 조건
ENVIRONMENT환경변수 PORTAL_API_KEYS_JSON(프로세스 기동 시 로드)에 선언되어 있어야 한다. DB 행은 그 키의 keyId 로 scope·enabled override 만 제공한다.
MANAGEDDB portal_api_key_settings 행이 source='MANAGED' 이면 env와 무관하게 인증된다.

파생 규칙 두 가지가 사고를 만든다.

  1. DB에 source='ENVIRONMENT' 행만 있어도 인증되지 않는다. 그 행은 env 선언을 미러링한 값이라, env가 비어 있으면(= PORTAL_API_KEYS_JSON 없이 기동) 목록에서 빠진다. → 401이지만 서버 로그에 아무 흔적이 없다.
  2. scope는 override(DB) ?? env 우선순위다. .env 의 scope만 고쳐도 DB 행이 이기므로 반영되지 않는다. 둘 다 맞춰야 한다.

DB 폴백은 키 저장소에 연결 자체가 안 될 때만 env로 인증한다(경고 로그 [PortalAuth] 키 저장소에 연결할 수 없어 …).

2. 증상별 판정 ​

관측원인
401 {"error":"invalid_credentials"}키 미등록 / 비활성 / env 선언 키인데 PORTAL_API_KEYS_JSON 미로드
401 + HTML 본문PortalConsole 앱이 아니라 앞단(프록시·게이트웨이) 차단
401 {"error":"unauthorized"}Authorization 헤더가 앱에 도달하지 않음(빈 키 등)
403 {"error":"insufficient_scope","required":[…]}키는 유효, scope 부족 (예: POST /api/directory/v1/sync-status 는 directory:sync)
502/503/504 또는 연결 실패PortalConsole 미기동 또는 포털의 PORTAL_CONSOLE_ORIGIN 오설정
호출 포털이 AUTH_REJECTED: 상태로 재시도를 멈춤과거 401의 표시가 DB에 남아 스케줄러가 재시도를 미룸

3. 진단 절차 ​

bash
# (a) PortalConsole이 env 선언 키를 로드했는가 — 가장 먼저 확인
docker exec <portal-console> printenv PORTAL_API_KEYS_JSON | wc -c        # 0 이면 env 선언 키는 전부 401

# (b) 호출 포털이 실제로 보내는 주소/키 지문 (값은 노출하지 않는다)
docker exec <portal> printenv PORTAL_CONSOLE_ORIGIN PORTAL_CONSOLE_PORTAL_ID
docker exec <portal> sh -c 'printf %s "$PORTAL_CONSOLE_API_KEY" | sha256sum'

# (c) 포털 저장소의 전용 진단 도구 (권장) — ProcessCenter
docker exec <process-center> node scripts/directory-credential-doctor.mjs

# (d) PortalConsole DB의 실제 키
#   select key_id, portal_id, label, enabled, source, key_hash
#   from portal_api_key_settings where portal_id='<portal-id>';

Key ID는 pak_<24hex> 형식이고, key_hash = sha256(키 원문) hex 64자이다. (b)의 지문과 key_hash 를 그대로 대조하면 된다.

4. 조치 ​

권장 — 관리형(MANAGED)으로 등록 (env 의존 제거) ​

  1. 호출 포털이 이미 쓰는 키를 버리지 않고 그 해시를 MANAGED로 등록한다(포털 env·재기동 불필요).
    • 관리자 화면: 포털 연동 설정 → 구간 「포털」 → ② 포털 호출 키 → 「기존 키 등록」 에 원문을 입력 (2026-10-15 용어 정리 이전 이름: 호출 인증 키 · 호출 키 등록)
    • 또는 registerManagedPortalApiKey() 와 동일한 형태로 DB에 등록 + 감사 로그(PORTAL_API_KEY_REGISTERED)
  2. scope를 요구사항에 맞춘다(읽기 directory:read, 동기화 상태 기록 directory:sync 등).
  3. 같은 key_hash 가 이미 있으면 관리자 화면 등록은 중복 검사로 거부된다(→ 새 시크릿을 쓰거나 기존 행을 정리).
  4. 키를 교체했다면 남길 키 하나만 남긴다 — ② 포털 호출 키 목록에서 실제로 쓰는 키를 열어 「이 키만 남기기」를 누른다(포털당 활성 포털 호출 키는 하나 — 2026-10-15 규칙). 자동 전달이 적용 확인(health 2xx)까지 끝난 경우에는 콘솔이 같은 규칙으로 자동 회전한다. 환경변수로 선언한 키라면 .env 에서도 빼야 다음 기동에 되살아나지 않는다.
sql
-- 관리자 화면 등록이 불가한 경우의 등록 예 (감사 로그 동반)
BEGIN;
INSERT INTO portal_api_key_settings
  (key_id, portal_id, label, key_hash, scopes, enabled, source, created_at, updated_at)
VALUES ('pak_<24hex>', '<portal-id>', '<표시 이름>', '<sha256 hex>',
        '["directory:read","directory:sync"]'::jsonb, true, 'MANAGED', now(), now());
INSERT INTO directory_audit_logs (id, actor_user_id, action, entity_type, entity_id, reason, before_data, after_data)
VALUES ('portal_key_<32hex>', NULL, 'PORTAL_API_KEY_REGISTERED', 'OPERATIONS', 'pak_<24hex>',
        '운영 조치: 환경변수 미선언 키를 관리형으로 등록', NULL, '{"portalId":"<portal-id>","source":"MANAGED"}');
COMMIT;

대안 — env 선언 방식을 유지할 때 ​

.env 의 PORTAL_API_KEYS_JSON 과 DB 행을 동시에 수정한 뒤 PortalConsole 을 재기동한다(기동 시 1회만 읽는다).

sql
update portal_api_key_settings set scopes = '["directory:read","directory:sync"]'::jsonb, updated_at = now()
where key_id = '<key-id>';

공통 — 호출 포털의 stale 인증거부 표시 정리 ​

sql
update directory_sync_state
set status='IDLE', "lastError"=NULL, "consecutiveFailures"=0, "lastAlertAt"=NULL;

그 뒤 포털 컨테이너를 재기동하면 부팅 시 즉시 1회 동기화한다(ProcessCenter startDirectorySyncScheduler()).

5. 확인된 사례 (2026-10-03) ​

환경원인조치
로컬 devPortalConsole을 PORTAL_API_KEYS_JSON 없이 기동 → source='ENVIRONMENT' 키가 인증 제외env 명시 로드 후 재기동 + DB scope 동시 수정
Coolify 배포PortalConsole 컨테이너에 PORTAL_API_KEYS_JSON 없음(count 0) + 호출 포털 키 해시가 DB에 없음(consecutiveFailures=496)해당 키를 MANAGED로 등록 → doctor 200 · 동기화 SUCCEEDED · 부서 미러 2,252건 적재

6. 재발 방지 체크리스트 ​

  • [ ] PortalConsole 기동에 PORTAL_API_KEYS_JSON(또는 systemd EnvironmentFile=)을 고정한다.
  • [ ] 신규 포털 연동은 MANAGED 등록을 기본으로 한다(env 선언은 호환용으로만).
  • [ ] 키·scope 변경 후에는 호출 포털 컨테이너 재기동을 반드시 포함한다(키는 시작 시 1회 읽음).
  • [ ] 포털에서 AUTH_REJECTED:/consecutiveFailures 경보가 오면 §3 진단 절차를 그대로 실행한다.