Skip to content

Commit 95ead90

Browse files
committed
feat(accounts): 계정 거버넌스 — 서버/PC 로컬계정 × LDAP × 승인대장 접근권한 검토
- 수집: POST /ingest/accounts (osquery push, 토큰인증, 특권 자동산출) → schema/010 host_accounts 영속 - 교차검증(services/account_recon): 퇴사자 잔존·미등록 특권·미승인 sudo·휴면 자동 검출 - 승인대장(account_approvals): 허용 계정/sudo 등록(전역·호스트) → 이상검출 기준선(예외 승인=증적) - API: /accounts/overview·/host/{key}·/approvals(CRUD)·/overview.csv (admin·security) - UI: 🔑 계정 탭(거버넌스 카드+통합목록+필터+IP+승인대장+CSV) admin·security 전용 + 호스트 상세 모달 계정 섹션. i18n 한/영 - StateRepo load/save_host_accounts·account_approvals + 부팅 warm-load + write-through 영속 - ISMS-P 2.5.1/2.5.5/2.5.6, ISO A.5.16/A.5.18/A.8.2. README 한/영 + CHANGELOG v0.14.0 검증: 샘플 push→findings(orphan/sudo/dormant)·승인시 해제·per-host·CSV·재시작 유지·RBAC 403, 153 test pass
1 parent 989b58e commit 95ead90

15 files changed

Lines changed: 775 additions & 1 deletion

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@ All notable changes to this project are documented here.
44
Format follows [Keep a Changelog](https://keepachangelog.com/); this is an alpha project so
55
versions are `x.y.z-alpha.n`.
66

7+
## [v0.14.0-alpha.1] — 2026-07-09 — 계정 거버넌스 (접근권한 검토 · 서버/PC × LDAP)
8+
9+
서버·PC **로컬 계정**을 수집(osquery)해 **LDAP 디렉터리 × 승인 대장**과 대조 →
10+
퇴사자 잔존·미등록 특권·미승인 sudo·휴면 계정을 자동 검출. ISMS-P 2.5.1/2.5.5/2.5.6,
11+
ISO 27001:2022 A.5.16/A.5.18/A.8.2 접근권한 검토 증적.
12+
13+
### Added
14+
- **계정 인벤토리 인제스트** (`POST /ingest/accounts`, 토큰 인증) — osquery(Fleet) 스케줄
15+
쿼리 결과(`users`/`sudoers`/`logged_in_users`/`shadow`)를 호스트별로 push. 특권(uid 0·
16+
sudo·wheel/admin 그룹) 자동 산출. `schema/010`(`host_accounts`) 영속.
17+
- **교차검증 서비스** (`services/account_recon.py`) — 로컬 계정 × 디렉터리 × 승인 대장 대조:
18+
**퇴사자 잔존**(디렉터리 비활성 매칭) · **미등록 특권**(디렉터리에 없는 특권) · **미승인
19+
sudo** · **휴면**(로그인 90일↑, 설정 `accounts_dormant_days`).
20+
- **승인 대장**(`account_approvals`) — 허용 계정/sudo 등록(전역·호스트 범위). 등록된 건 이상
21+
검출에서 제외(예외 승인 근거=증적). `GET/POST/DELETE /accounts/approvals` (admin·security).
22+
- **🔑 계정 탭** (admin·security 전용) — 거버넌스 finding 카드 4종 + 통합 계정 목록(서버/PC,
23+
필터: 유형·이상·특권) + **IP 리스트** + 승인 대장 관리 + **CSV 증적 export**(`/accounts/overview.csv`).
24+
- **호스트 상세 계정 섹션** — '내 담당 서버' 상세 모달에 그 호스트의 로컬 계정·이상 표시(admin·security).
25+
726
## [v0.13.0-alpha.1] — 2026-07-08 — 관리자 콘솔 정리 (중복 제거 · 진입점 · 톤 통일)
827

928
### Changed

README.en.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ A one-line (`docker compose up -d`) **ISMS-P / ISO 27001 audit-evidence accumula
1818
- 🚀 **One-line start**`./scripts/mori-start-demo.sh``http://localhost:18000/ui` (`admin / 1234`, demo only)
1919
- 📖 **Install & operate guides** — new here? [**Getting Started (new users)**](docs/GETTING_STARTED.en.md); already running Zabbix/Wazuh/Fleet? [**Connect an existing stack**](docs/BROWNFIELD_CONNECT.en.md); single sign-on? [**LDAP integration**](docs/LDAP_INTEGRATION.en.md) (all KO/EN)
2020
- 🔑 **LDAP integration (optional)** — local accounts by default; **opt in** with `MORI_LDAP_ENABLED=true` and login goes through LDAP, and **approving a signup creates an LDAP account** so the same login works in **Grafana/Zabbix/Fleet** pointed at the same directory. **Manage LDAP users right from the admin console** (add/delete/reset password/change role). Point it at your existing corporate LDAP/AD by changing the URL. → [guide](docs/LDAP_INTEGRATION.en.md)
21+
- 🧑‍💻 **Account governance (access review, admin·security)** — collect server/PC **local accounts** via osquery (`POST /ingest/accounts`) and reconcile them against the **LDAP directory × approval list** → auto-detect **leaver-remains · unmanaged privileged · unapproved sudo · dormant** accounts. Dedicated **🔑 Accounts tab** (unified list + IP + approval list + CSV evidence) + per-host account section. ISMS-P 2.5.1·2.5.5·2.5.6 / ISO A.5.16·A.5.18·A.8.2. e.g. `osquery "SELECT * FROM users" → POST /ingest/accounts`
2122
- 📊 **Screens** — Unified dashboard · Alert Triage · Incidents · Assets / Vulnerabilities · **Risk Assessment Matrix** · Compliance PDCA + **Control catalog (ISMS-P 101 × ISO)** · 6 audit-evidence CSV/PDF reports
2223
- 🎯 **Risk Assessment (R-series)** — Per-CVE **Risk = Impact (asset importance H/M/L) × Likelihood (severity)** as a **score (1–9)** on a 3×3 matrix (score-forward), with treatment decision (mitigate/accept/transfer/avoid) · residual risk · review date. **Admin-only assessment-basis (provenance)** panel. A single **risk-acceptance threshold (DoA)** auto-classifies risks at or below it as **auto-acceptable**. Based on ISMS-P risk management / ISO 27001 6.1.2·8.8
2324
- 📚 **Control catalog (Phase 2)** — ISMS-P 101 + ISO 27001:2022 93 = **194 criteria** (KO/EN) as a tree in the **Compliance tab**. Edit each control's **implementation status (implemented/partial/not-implemented/N-A) · owner · plan · due date** → persisted to `control_status` (`schema/009`, survives restarts) + change history. One-click per-control **evidence-pack PDF**. admin·security only

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- 🚀 **한 줄 시작**`./scripts/mori-start-demo.sh``http://localhost:18000/ui` (`admin / 1234`, 데모 전용)
1919
- 📖 **설치·운영 가이드** — 처음이면 [**시작하기(신규 사용자)**](docs/GETTING_STARTED.md), 이미 Zabbix/Wazuh/Fleet를 운영 중이면 [**기존 스택 연결**](docs/BROWNFIELD_CONNECT.md), 계정 통합은 [**LDAP 통합**](docs/LDAP_INTEGRATION.md) (모두 한/영)
2020
- 🔑 **LDAP 통합 인증 (선택)** — 기본은 로컬 계정, **원하면** `MORI_LDAP_ENABLED=true`로 켜면 로그인이 LDAP으로 되고 **가입 승인 시 LDAP 계정이 생성**되어 같은 LDAP을 보는 **Grafana/Zabbix/Fleet에서도 같은 계정으로 로그인**. **어드민 콘솔에서 LDAP 사용자를 직접 관리**(추가·삭제·비번 재설정·역할 변경). 기존 사내 LDAP/AD도 URL만 바꿔 연결. → [가이드](docs/LDAP_INTEGRATION.md)
21+
- 🧑‍💻 **계정 거버넌스 (접근권한 검토, admin·security)** — 서버·PC **로컬 계정**을 osquery로 수집(`POST /ingest/accounts`)해 **LDAP 디렉터리 × 승인 대장**과 대조 → **퇴사자 잔존·미등록 특권·미승인 sudo·휴면 계정** 자동 검출. 전용 **🔑 계정 탭**(통합 목록+IP+승인 대장+CSV 증적) + 호스트 상세 계정 섹션. ISMS-P 2.5.1·2.5.5·2.5.6 / ISO A.5.16·A.5.18·A.8.2. 예: `osquery "SELECT * FROM users" → POST /ingest/accounts`
2122
- 📊 **화면** — 통합 대시보드 · Alert Triage · 인시던트 · 자산/취약점 · **위험성 평가 매트릭스** · Compliance PDCA + **통제 카탈로그(ISMS-P 101 × ISO)** · 6종 감사 증적 CSV/PDF
2223
- 🎯 **위험성 평가 (R-series)** — 취약점(CVE)별 **위험도 = 영향도(자산 중요도 상/중/하) × 발생가능성(심각도)** 를 3×3 매트릭스로 **점수(1~9)** 산정(라벨보다 점수 우선), 위험처리 결정(조치/수용/이관/회피)·잔여위험·재평가일 기록. **어드민 전용 산정 근거(provenance)** 패널. **위험 수용 기준(DoA)** — admin이 임계 점수를 입력하면 그 이하 위험은 **기본 수용가능**으로 자동 분류. ISMS-P 위험관리 / ISO 27001 6.1.2·8.8 기반
2324
- 📚 **통제 카탈로그 (Phase 2)** — ISMS-P 101 + ISO 27001:2022 93 = **194개 인증기준**(한/영) 트리를 **컴플라이언스 탭**에 표시. 통제별 **이행 상태(이행/부분이행/미이행/해당없음)·담당자·개선계획·기한을 편집**하면 `control_status`(`schema/009`)에 **영속(재시작 유지)** + 변경 이력 기록. 통제별 **증적 팩 PDF** 1클릭. admin·security 전용

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ services:
1717
- ./schema/007_controls.sql:/docker-entrypoint-initdb.d/007_controls.sql:ro
1818
- ./schema/008_settings.sql:/docker-entrypoint-initdb.d/008_settings.sql:ro
1919
- ./schema/009_control_status.sql:/docker-entrypoint-initdb.d/009_control_status.sql:ro
20+
- ./schema/010_accounts.sql:/docker-entrypoint-initdb.d/010_accounts.sql:ro
2021
healthcheck:
2122
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
2223
interval: 10s

schema/010_accounts.sql

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
-- 010_accounts.sql — 서버/PC 로컬 계정 인벤토리 + 승인 대장 (접근권한 거버넌스)
2+
--
3+
-- host_accounts: osquery(Fleet) push 로 수집한 호스트별 로컬 계정 인벤토리.
4+
-- account_approvals: 허용 계정/sudo 승인 대장 — 이상 검출의 기준선(여기 없는 것만 이상).
5+
-- 목적: 서버 로컬 계정 × LDAP 디렉터리 대조 → 퇴사자 잔존·미등록 특권·미승인 sudo·휴면 검출.
6+
-- ISMS-P 2.5.1/2.5.5/2.5.6, ISO 27001:2022 A.5.16/A.5.18/A.8.2 증적.
7+
8+
CREATE TABLE IF NOT EXISTS host_accounts (
9+
host_key TEXT NOT NULL, -- hostname (또는 host_id)
10+
username TEXT NOT NULL,
11+
host_type TEXT NOT NULL DEFAULT 'server', -- server | pc
12+
uid TEXT,
13+
gid TEXT,
14+
shell TEXT,
15+
home TEXT,
16+
groups JSONB NOT NULL DEFAULT '[]'::jsonb,
17+
is_privileged BOOLEAN NOT NULL DEFAULT false, -- uid 0 / wheel·admin·sudo 그룹
18+
is_sudo BOOLEAN NOT NULL DEFAULT false,
19+
disabled BOOLEAN NOT NULL DEFAULT false,
20+
last_login TIMESTAMPTZ,
21+
pwd_last_change DATE,
22+
source TEXT NOT NULL DEFAULT 'osquery',
23+
collected_at TIMESTAMPTZ NOT NULL DEFAULT now(),
24+
PRIMARY KEY (host_key, username)
25+
);
26+
27+
CREATE INDEX IF NOT EXISTS idx_host_accounts_priv ON host_accounts (is_privileged) WHERE is_privileged;
28+
29+
CREATE TABLE IF NOT EXISTS account_approvals (
30+
id TEXT PRIMARY KEY,
31+
scope TEXT NOT NULL DEFAULT 'global', -- global | host
32+
host_key TEXT, -- scope=host 일 때만
33+
username TEXT NOT NULL,
34+
kind TEXT NOT NULL DEFAULT 'account', -- account | sudo
35+
reason TEXT,
36+
approver TEXT,
37+
expires DATE,
38+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
39+
);
40+
41+
COMMENT ON TABLE host_accounts IS 'osquery push 로컬 계정 인벤토리(호스트별). 접근권한 검토 증적.';
42+
COMMENT ON TABLE account_approvals IS '허용 계정/sudo 승인 대장 — 이상 검출 기준선(예외 승인 근거=증적).';

scripts/mori-seed-sample-data.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ fi
6262
if [ -f "$PROJECT_ROOT/schema/009_control_status.sql" ]; then
6363
run_sql_file "$PROJECT_ROOT/schema/009_control_status.sql" >/dev/null 2>&1 || true
6464
fi
65+
if [ -f "$PROJECT_ROOT/schema/010_accounts.sql" ]; then
66+
run_sql_file "$PROJECT_ROOT/schema/010_accounts.sql" >/dev/null 2>&1 || true
67+
fi
6568

6669
# Fix legacy CHECK constraints (older DBs missing 'trivy' in source whitelists)
6770
run_sql_lenient "

src/mori_soc/api/i18n.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,42 @@ def _i18n_toggle_html(fixed: bool = True) -> str:
333333
"dash.tab.incidents": "📋 인시던트",
334334
"dash.tab.assets": "📡 자산 현황",
335335
"dash.tab.compliance": "✅ Compliance PDCA",
336+
"dash.tab.accounts": "🔑 Accounts",
337+
"dash.acc.title": "🔑 Account governance (access review)",
338+
"dash.acc.csv": "📥 CSV",
339+
"dash.acc.sub": "Cross-checks server/PC local accounts (osquery) × LDAP directory × approval list to find anomalies. ISMS-P 2.5.1·2.5.5·2.5.6 access-review evidence. osquery push: POST /ingest/accounts",
340+
"dash.acc.list_title": "Accounts (servers · PCs)",
341+
"dash.acc.search_ph": "Search account/host…",
342+
"dash.acc.f.alltype": "All types", "dash.acc.f.server": "Server", "dash.acc.f.pc": "PC",
343+
"dash.acc.f.allfind": "All", "dash.acc.f.flagged": "Flagged only", "dash.acc.f.priv": "Privileged only",
344+
"dash.acc.approve_title": "✅ Approval list (allowed accounts · sudo)",
345+
"dash.acc.approve_sub": "Accounts/sudo listed here are never flagged. The approval reason itself is evidence.",
346+
"dash.acc.appr_host_ph": "host (blank = global)", "dash.acc.appr_reason_ph": "Approval reason", "dash.acc.appr_add": "+ Approve",
347+
"dash.acc.ip_title": "🌐 IP list", "dash.acc.ip_sub": "IP and status of collected hosts.",
348+
"dash.acc.hosts": "hosts", "dash.acc.accounts": "accounts", "dash.acc.priv": "privileged", "dash.acc.dir": "directory",
349+
"dash.acc.find.leaver": "leaver remains", "dash.acc.find.orphan_priv": "unmanaged privileged", "dash.acc.find.unapproved_sudo": "unapproved sudo", "dash.acc.find.dormant": "dormant",
350+
"dash.acc.col.host": "Host", "dash.acc.col.user": "Account", "dash.acc.col.priv": "Priv", "dash.acc.col.dir": "Directory", "dash.acc.col.login": "Last login", "dash.acc.col.find": "Findings",
351+
"dash.acc.none": "No matching accounts. (before osquery push, or filtered)", "dash.acc.global": "global",
352+
"dash.acc.appr_none": "No approvals yet.", "dash.acc.appr_del": "Delete", "dash.acc.ip_none": "No hosts",
353+
"dash.acc.host_title": "Local accounts", "dash.acc.host_none": "No accounts collected (osquery push needed)",
354+
"dash.tab.accounts": "🔑 계정",
355+
"dash.acc.title": "🔑 계정 거버넌스 (접근권한 검토)",
356+
"dash.acc.csv": "📥 CSV",
357+
"dash.acc.sub": "서버·PC 로컬 계정(osquery) × LDAP 디렉터리 × 승인 대장을 대조해 이상 계정을 찾습니다. ISMS-P 2.5.1·2.5.5·2.5.6 접근권한 검토 증적. osquery push: POST /ingest/accounts",
358+
"dash.acc.list_title": "계정 목록 (서버 · PC)",
359+
"dash.acc.search_ph": "계정/호스트 검색…",
360+
"dash.acc.f.alltype": "전체 유형", "dash.acc.f.server": "서버", "dash.acc.f.pc": "PC",
361+
"dash.acc.f.allfind": "전체", "dash.acc.f.flagged": "이상만", "dash.acc.f.priv": "특권만",
362+
"dash.acc.approve_title": "✅ 승인 대장 (허용 계정 · sudo)",
363+
"dash.acc.approve_sub": "여기에 등록된 계정/sudo는 이상으로 잡지 않습니다. 예외 승인 근거 자체가 증적입니다.",
364+
"dash.acc.appr_host_ph": "host(비우면 전역)", "dash.acc.appr_reason_ph": "승인 사유", "dash.acc.appr_add": "+ 승인",
365+
"dash.acc.ip_title": "🌐 IP 리스트", "dash.acc.ip_sub": "수집된 호스트의 IP·상태입니다.",
366+
"dash.acc.hosts": "호스트", "dash.acc.accounts": "계정", "dash.acc.priv": "특권", "dash.acc.dir": "디렉터리",
367+
"dash.acc.find.leaver": "퇴사자 잔존", "dash.acc.find.orphan_priv": "미등록 특권", "dash.acc.find.unapproved_sudo": "미승인 sudo", "dash.acc.find.dormant": "휴면",
368+
"dash.acc.col.host": "호스트", "dash.acc.col.user": "계정", "dash.acc.col.priv": "특권", "dash.acc.col.dir": "디렉터리", "dash.acc.col.login": "최근 로그인", "dash.acc.col.find": "이상",
369+
"dash.acc.none": "해당 계정이 없습니다. (osquery push 전이거나 필터)", "dash.acc.global": "전역",
370+
"dash.acc.appr_none": "등록된 승인이 없습니다.", "dash.acc.appr_del": "삭제", "dash.acc.ip_none": "호스트 없음",
371+
"dash.acc.host_title": "로컬 계정", "dash.acc.host_none": "수집된 계정 없음 (osquery push 필요)",
336372
"dash.tab.guides": "📖 가이드 & 기준",
337373
"dash.bn.dashboard": "대시보드",
338374
"dash.bn.triage": "Triage",

0 commit comments

Comments
 (0)