Files
jarca-ledger-mcp/docs/requirements/req-001_dashboard-administrator-authentication.md
T

447 lines
15 KiB
Markdown

---
{
"schema": "gitea-agent.requirement/v1",
"id": "REQ-001",
"title": "Dashboard 관리자 인증",
"revision": "2.0",
"status": "approved",
"depends_on": [],
"applies_to": ["dashboard"],
"language": "ko"
}
---
# REQ-001 — Dashboard 관리자 인증
## 1. 목적
Dashboard의 금융 기능 앞에 단일 fail-closed 관리자 인증 경계를 두고, 유효한 인증 cookie가 같은 browser profile의 새 연결에서 복원되도록 한다.
## 2. 배경
Issue #1의 관리자 인증을 Issue #6에서 persistent signed cookie와 명시적 logout으로 확장한다. 기존 요구사항의 정체성, 승인 범위, Issue #1과 구현 PR #3의 추적성을 유지하며 revision 2.0으로 갱신한다.
## 3. 범위
### 포함
- Dashboard 전 기능 앞의 인증 gate와 인증 전 DB 차단
- `JARCA_DASHBOARD_ADMIN_ID`, `JARCA_DASHBOARD_ADMIN_PASSWORD`, `JARCA_DASHBOARD_SIGNING_SECRET` 기반 server-only 검증
- constant-time credential 비교, 일반 오류, 실패당 최소 1초 지연
- same-origin HTTPS login, cleanup, logout route
- `Secure`/`HttpOnly`/`SameSite=Lax`/`Path=/` signed cookie와 24시간·30일 absolute TTL
- 고정된 비민감 role subject와 설정 credential에 묶인 서명 검증
- invalid, expired, tampered, malformed, configuration-failure cookie의 cleanup 및 matching deletion
- password, signing-secret, admin-ID 변경에 따른 기존 token 거부
- HTTPS, proxy, Kubernetes/Compose 운영 제약과 민감정보 비노출
### 제외
- 다중 사용자, RBAC, OAuth/OIDC, SSO, MFA, CAPTCHA, 계정 잠금
- 외부 인증 proxy로 책임 이전
- sliding expiration, DB-backed session/token registry, per-token revocation
- MCP endpoint 인증 변경, SQLite schema/data migration
- 다른 browser profile·기기 간 로그인 공유
## 4. 용어와 기준
- **role subject**: token의 `sub`에 고정 기록하는 비민감 값 `dashboard-admin`; 관리자 ID나 credential이 아니다.
- **server-only binding**: signing secret과 현재 설정 credential을 서버에서만 읽어 key를 파생하며 browser에 값을 보내지 않는 방식.
- **cleanup**: 인증 실패 cookie를 같은 origin의 cleanup route에서 matching name/path/security 속성으로 삭제하는 절차.
- **canonical 근거**: Issue #1, Issue #6, 구현 PR #3 및 #7.
## 5. 요구사항
### REQ-001.01 — 공통 인증 경계
모든 DB path, page dispatch, gateway, upload, import보다 먼저 설정과 cookie를 검증하고, 실패하면 로그인 화면만 표시한다.
**영향 범위**
- 인터페이스: `dashboard-auth-gate`
- 영속성: 없음
- 후속 사용 주체: Dashboard page와 gateway
- 마이그레이션: 없음
- 호환성: 인증 후 기존 page/query 동작 유지
- 배포 전제 조건: 유효한 세 환경변수
### REQ-001.02 — 설정과 server-only binding
세 환경변수만 사용하고 누락·빈 값·read error·짧은 signing secret이면 fail-closed한다. signing key는 서버에서 configured credential과 secret으로만 파생한다.
**영향 범위**
- 인터페이스: `dashboard-auth-config`, `dashboard-auth-cookie`
- 영속성: 없음
- 후속 사용 주체: 인증 verifier
- 마이그레이션: 없음
- 호환성: Streamlit secrets/fallback 미사용
- 배포 전제 조건: secret manager 주입
### REQ-001.03 — Credential과 민감정보 비노출
ID와 password는 constant-time 비교한다. credential, secret, signing material, token, raw cookie와 Set-Cookie 값은 UI, URL, log, 문서, session state에 기록하지 않는다.
**영향 범위**
- 인터페이스: login form, event log
- 영속성: 없음
- 후속 사용 주체: 운영자와 감사 도구
- 마이그레이션: 없음
- 호환성: 비민감 correlation ID만 유지
- 배포 전제 조건: redacted logging
### REQ-001.04 — Login 실패 계약
ID 또는 password가 틀리면 동일한 일반 오류와 최소 1초 지연을 적용하고 상태를 변경하지 않는다. 성공에는 지연을 적용하지 않는다.
**영향 범위**
- 인터페이스: `POST /api/auth/login`
- 영속성: 없음
- 후속 사용 주체: browser
- 마이그레이션: 없음
- 호환성: 기존 login form 유지
- 배포 전제 조건: 외부 rate limiting
### REQ-001.05 — Persistent token
성공 login은 기본 24시간, remember-me 선택 시 30일의 absolute TTL token을 발급한다. `iat`와 `exp`는 고정하며 활동·rerun·복원으로 연장하지 않는다. `sub`는 항상 `dashboard-admin`이다.
**영향 범위**
- 인터페이스: `dashboard-auth-cookie`
- 영속성: browser cookie만
- 후속 사용 주체: 새 Streamlit session
- 마이그레이션: 기존 session boolean에서 cookie로 전환
- 호환성: 같은 browser profile 복원
- 배포 전제 조건: HTTPS
### REQ-001.06 — Cookie 보안 속성
Cookie 이름은 `__Host-jarca_dashboard_auth`이며 `Secure`, `HttpOnly`, `SameSite=Lax`, `Path=/`, no `Domain`을 사용한다. 일반 요청은 재발급하지 않는다.
**영향 범위**
- 인터페이스: HTTP response와 browser
- 영속성: browser cookie
- 후속 사용 주체: browser/Streamlit handshake
- 마이그레이션: 없음
- 호환성: host-only cookie
- 배포 전제 조건: 공개 origin HTTPS
### REQ-001.07 — Invalid cookie cleanup
없음·malformed·oversized·tampered·expired·claim-invalid·rotation-invalid 또는 configuration-failure cookie는 보호 자원에 접근하기 전에 거부한다. 같은 origin cleanup route를 통해 동일 name/path/security 속성과 `Max-Age=0` deletion을 반환한 뒤 unauthenticated page를 표시한다.
**영향 범위**
- 인터페이스: `GET /api/auth/cleanup`, `dashboard-auth-gate`
- 영속성: 서버 session/registry 없음
- 후속 사용 주체: browser
- 마이그레이션: 없음
- 호환성: stale cookie가 남지 않음
- 배포 전제 조건: same-origin HTTPS
### REQ-001.08 — Login, cleanup, logout
Login과 logout은 trusted same-origin HTTPS POST만 허용하고 cleanup GET은 same-origin HTTPS `Referer`를 검증한다. logout은 명시적으로 matching deletion을 반환한다. cleanup과 logout은 금융 DB를 변경하지 않는다.
**영향 범위**
- 인터페이스: `POST /api/auth/login`, `GET /api/auth/cleanup`, `POST /api/auth/logout`
- 영속성: 없음
- 후속 사용 주체: browser
- 마이그레이션: 없음
- 호환성: root redirect 유지
- 배포 전제 조건: proxy가 Host/Origin/Proto 보존
### REQ-001.09 — Rotation과 무상태성
Token claim은 `v`, 고정 `sub`, `iat`, `exp`, `jti`만 허용한다. password, signing secret 또는 admin ID 변경은 기존 token을 거부한다. per-token revocation이나 DB session은 만들지 않는다.
**영향 범위**
- 인터페이스: token verifier
- 영속성: revocation/session 저장소 없음
- 후속 사용 주체: 운영 rotation 절차
- 마이그레이션: 없음
- 호환성: 전체 무효화는 rotation으로 수행
- 배포 전제 조건: 모든 replica 동일 설정
### REQ-001.10 — 보호 범위와 운영
인증 후 기존 page, query parameter, gateway와 MCP 동작을 유지한다. Compose/Kubernetes는 실제 secret을 저장하지 않으며 HTTPS, forwarded-header allowlist와 rate limit을 적용한다.
**영향 범위**
- 인터페이스: Dashboard 운영 진입점
- 영속성: 기존 SQLite만
- 후속 사용 주체: 운영 배포
- 마이그레이션: DB migration 없음
- 호환성: MCP endpoint는 별도 계약
- 배포 전제 조건: TLS와 제한된 proxy CIDR
### REQ-001.11 — 관찰 가능성
설정 오류, 인증 필요, token 수락, login 성공/실패/reject, cleanup, logout event와 비민감 correlation ID만 기록한다. 민감 값과 구체적 검증 원인은 기록하지 않는다.
**영향 범위**
- 인터페이스: event log
- 영속성: 로그에 비민감 메타데이터만
- 후속 사용 주체: 운영 관찰 도구
- 마이그레이션: 없음
- 호환성: 일반 오류 메시지 유지
- 배포 전제 조건: redaction 정책
## 6. 제약과 불변조건
- `sub`는 비민감 고정 role 값이며 credential에서 파생하거나 관리자 ID를 담지 않는다.
- credential, secret, token, cookie 값은 저장·출력·문서화하지 않는다.
- 인증 성공 전 DB/gateway/page dispatch는 실행하지 않는다.
- 만료는 absolute이며 활동으로 연장하지 않는다.
- invalid cookie cleanup과 explicit logout의 deletion은 발급 cookie와 matching scope를 사용한다.
- 서버는 per-token revocation과 DB-backed session을 유지하지 않는다.
## 7. 인수 기준
### REQ-001.01-AC01 — 인증 전 차단
**판정 기준**
모든 인증 거부 경로에서 DB, gateway, page dispatch가 실행되지 않는다.
**검증**
AppTest와 gate ordering 테스트에서 database access sentinel이 호출되지 않음을 확인한다.
**검증 분류**
- 수준: integration
- 백엔드: Streamlit AppTest
- 케이스: missing config, no cookie, invalid cookie
**상태 불변조건**
거부 시 금융 상태와 DB는 변경되지 않는다.
### REQ-001.02-AC01 — 설정 fail-closed
**판정 기준**
세 설정 중 하나라도 유효하지 않으면 일반 설정 오류로 인증을 거부한다.
**검증**
설정별 누락·빈 값·짧은 secret 테스트를 실행한다.
**검증 분류**
- 수준: unit/integration
- 백엔드: pytest
- 케이스: 각 환경변수 누락, 빈 값, short secret
**상태 불변조건**
cookie 발급과 보호 자원 접근이 없다.
### REQ-001.03-AC01 — 민감정보 비노출
**판정 기준**
credential, secret, token, cookie가 UI·URL·log·session state에 나타나지 않는다.
**검증**
로그·응답·session state와 문서를 redaction 검색한다.
**검증 분류**
- 수준: static/integration
- 백엔드: pytest, ruff, repository search
- 케이스: 성공·실패 login과 invalid cookie
**상태 불변조건**
비민감 correlation ID 외 인증 비밀 상태가 없다.
### REQ-001.04-AC01 — 실패 지연과 일반 오류
**판정 기준**
잘못된 ID와 잘못된 password가 같은 일반 오류를 반환하고 각각 최소 1초 지연된다.
**검증**
두 실패 경로의 elapsed time과 response를 측정한다.
**검증 분류**
- 수준: integration
- 백엔드: pytest/HTTP client
- 케이스: wrong ID, wrong password
**상태 불변조건**
cookie·DB·gateway 상태가 변하지 않는다.
### REQ-001.05-AC01 — Absolute TTL과 고정 subject
**판정 기준**
기본 24시간과 remember-me 30일 token의 `iat`, `exp`, `sub=dashboard-admin`이 고정된다.
**검증**
token claim, TTL, rerun/non-sliding 테스트를 실행한다.
**검증 분류**
- 수준: unit/integration
- 백엔드: pytest
- 케이스: normal, remember-me, activity, reconnect
**상태 불변조건**
복원은 token을 갱신하지 않는다.
### REQ-001.06-AC01 — Hardened cookie
**판정 기준**
발급 cookie가 이름·Secure·HttpOnly·SameSite·Path·Domain 계약을 모두 만족한다.
**검증**
실제 HTTP `Set-Cookie`를 파싱하고 속성을 검사한다.
**검증 분류**
- 수준: integration
- 백엔드: Starlette TestClient와 loopback HTTPS
- 케이스: login normal/remember
**상태 불변조건**
Domain attribute가 없고 평문 전송에서 발급되지 않는다.
### REQ-001.07-AC01 — Invalid cookie cleanup
**판정 기준**
invalid/expired/tampered/config-failure cookie는 보호 자원 전에 거부되고 same-origin cleanup route의 matching deletion 후 unauthenticated page가 표시된다.
**검증**
실제 browser/HTTP 흐름에서 각 invalid cookie와 cleanup response를 확인한다.
**검증 분류**
- 수준: integration/browser
- 백엔드: HTTPS probe와 Streamlit browser evidence
- 케이스: malformed, expired, tampered, wrong claims, config failure
**상태 불변조건**
cleanup은 DB를 변경하지 않으며 stale cookie가 남지 않는다.
### REQ-001.08-AC01 — Explicit logout
**판정 기준**
logout POST가 matching `Max-Age=0` deletion을 반환하고 다음 접근이 unauthenticated다.
**검증**
login 후 logout response와 fresh request를 확인한다.
**검증 분류**
- 수준: integration/browser
- 백엔드: HTTPS probe
- 케이스: same-origin logout, cross-origin rejection
**상태 불변조건**
금융 DB와 token registry는 변경되지 않는다.
### REQ-001.09-AC01 — Rotation과 무상태성
**판정 기준**
configured credential 변경은 기존 token을 거부하며 per-token revocation/DB session 구현이 없다.
**검증**
rotation 테스트와 source/schema/diff 검색을 실행한다.
**검증 분류**
- 수준: unit/static
- 백엔드: pytest와 repository search
- 케이스: password, signing secret, admin ID rotation
**상태 불변조건**
검증은 현재 설정과 서명만 사용한다.
### REQ-001.10-AC01 — 운영 제약
**판정 기준**
README와 Kubernetes 문서에 HTTPS, proxy allowlist, rate limit, secret 주입, rotation과 stateless logout이 기록된다.
**검증**
문서 검색과 deployment manifest 검토를 실행한다.
**검증 분류**
- 수준: documentation/static
- 백엔드: repository search
- 케이스: README, deploy/k8s/README.md
**상태 불변조건**
실제 credential·secret 값이 문서와 manifest에 없다.
### REQ-001.11-AC01 — 안전한 관찰성
**판정 기준**
허용 event와 correlation ID만 기록되고 민감 값·구체적 검증 원인은 기록되지 않는다.
**검증**
로그 테스트와 redaction 검색을 실행한다.
**검증 분류**
- 수준: unit/static
- 백엔드: pytest와 repository search
- 케이스: config, login, cleanup, logout success/failure
**상태 불변조건**
로그에서 credential·secret·token·cookie가 복원되지 않는다.
## 8. 오류 및 충돌 계약
동일 요청에서 설정 오류가 credential 오류보다 우선하며, 모든 인증 실패는 보호 자원 전에 종료된다. invalid cookie는 cleanup deletion을 시도하고, cleanup route가 거부되거나 실패해도 보호 자원은 열지 않는다. cross-origin/plain HTTP 요청은 403, malformed request는 400이다. 동시 login은 application lock 없이 독립 처리한다.
## 9. 데이터 및 인터페이스 계약
Token은 `v`, `sub=dashboard-admin`, `iat`, `exp`, `jti`와 서명만 가지며 browser cookie에만 저장된다. 세 환경변수는 server process에서만 읽는다. 상세 HTTP, cookie, cleanup, logout 계약은 `docs/interface-contract.md`에 정의한다.
## 10. 하위 호환성
Issue #1의 Dashboard-only 범위, 기존 page/query/gateway/MCP 동작과 구현 PR #3의 추적성을 유지한다. session-only 인증은 persistent cookie로 대체하며 SQLite schema와 MCP transport는 변경하지 않는다.
## 11. 추적성
- 근거 이슈: `#1`, `#6`
- 구현 PR: `#3`, `#7`
- 관련 설계 문서: `docs/interface-contract.md`, `docs/project-structure.md`, `docs/runtime-flow.md`, `README.md`, `deploy/k8s/README.md`
- 관련 요구사항: 없음