[PMS 완성] - core/si_report.py: 일/주/월 보고서 (Excel/HTML/PDF/DOCX/PPTX) - routers/si_report.py: daily|weekly|monthly + 메신저 발송 - routers/deliverables.py: 산출물 CRUD + 제출/검토 - si_issues.py: 이슈→SR 자동 연결 - scheduler.py: 일일 18:00 + 주간 금 17:00 자동 보고서 - models.py: Deliverable 모델 [준수성 자동 점검] - core/compliance_check.py: SC-8개/WA-7개/PI-6개 규칙 - routers/compliance.py: 스캔 + HTML/Excel 보고서 [JMeter 성능 테스트] - routers/jmeter.py: JTL 업로드 + 내장 부하 테스트 + 보고서 [공공기관 필수 기능] - routers/public_checklist.py: 행안부 기준 19개 항목 [UI/브랜드] - 로고(ziologo.png) + Copyright 2026 All Rights Reserved - Nifty 계층형 사이드바 (PMS 서브메뉴) - X-Powered-By + X-Copyright 응답 헤더 - manual/15_UI_Nifty_가이드.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
355 lines
15 KiB
Python
355 lines
15 KiB
Python
"""
|
|
공공기관 정보화사업 필수 기능 체크리스트 API
|
|
|
|
근거 법령/기준:
|
|
- 행안부 「정보시스템 구축·운영 지침」
|
|
- 행안부 「소프트웨어 개발보안 가이드」
|
|
- 「개인정보보호법」 및 시행령
|
|
- 「정보보안 관리체계(ISMS-P) 인증기준」
|
|
- 「장애인차별금지법」 (웹 접근성 의무)
|
|
- 「전자서명법」
|
|
|
|
엔드포인트:
|
|
GET /api/public/checklist — 공공기관 필수 기능 체크리스트
|
|
POST /api/public/checklist/{id}/check — 항목 완료 처리
|
|
GET /api/public/status — GUARDiA 공공기관 준비 현황
|
|
GET /api/public/report/html — 준비 현황 HTML 보고서
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from fastapi.responses import HTMLResponse
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from core.auth import get_current_user, require_admin_role
|
|
from database import get_db
|
|
from models import User
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/public", tags=["public_checklist"])
|
|
|
|
# 공공기관 필수 기능 체크리스트
|
|
PUBLIC_CHECKLIST = [
|
|
# ── 보안 ────────────────────────────────────────────────────────────────
|
|
{
|
|
"id": "SEC-001",
|
|
"category": "보안",
|
|
"law": "행안부 SW 개발보안 가이드",
|
|
"title": "시큐어코딩 적용",
|
|
"desc": "OWASP Top 10 및 행안부 47개 보안약점 제거",
|
|
"guardia_status": "구현됨",
|
|
"api": "POST /api/compliance/scan",
|
|
},
|
|
{
|
|
"id": "SEC-002",
|
|
"category": "보안",
|
|
"law": "ISMS-P 인증기준",
|
|
"title": "취약점 자동 스캔",
|
|
"desc": "서버 포트/CVE 취약점 정기 스캔 및 패치 추적",
|
|
"guardia_status": "구현됨",
|
|
"api": "POST /api/vuln/scan",
|
|
},
|
|
{
|
|
"id": "SEC-003",
|
|
"category": "보안",
|
|
"law": "개인정보보호법 제29조",
|
|
"title": "접근권한 관리 (RBAC)",
|
|
"desc": "역할 기반 최소권한 원칙, 퇴직자 계정 즉시 비활성화",
|
|
"guardia_status": "구현됨",
|
|
"api": "GET /api/auth/users",
|
|
},
|
|
{
|
|
"id": "SEC-004",
|
|
"category": "보안",
|
|
"law": "개인정보보호법 제25조",
|
|
"title": "감사 로그 (불변 로그)",
|
|
"desc": "모든 접근 및 변경 이력 해시체인 기록 (최소 1년 보관)",
|
|
"guardia_status": "구현됨",
|
|
"api": "GET /api/audit",
|
|
},
|
|
{
|
|
"id": "SEC-005",
|
|
"category": "보안",
|
|
"law": "전자서명법",
|
|
"title": "전자서명 / MFA",
|
|
"desc": "관리자 계정 다단계 인증 필수",
|
|
"guardia_status": "구현됨",
|
|
"api": "POST /api/auth/mfa/setup",
|
|
},
|
|
{
|
|
"id": "SEC-006",
|
|
"category": "보안",
|
|
"law": "ISMS-P",
|
|
"title": "특권 접근 관리 (PAM)",
|
|
"desc": "root/admin 계정 사용 제어, 세션 녹화, 명령 로깅",
|
|
"guardia_status": "구현됨",
|
|
"api": "GET /api/pam/sessions",
|
|
},
|
|
# ── 개인정보보호 ─────────────────────────────────────────────────────────
|
|
{
|
|
"id": "PI-001",
|
|
"category": "개인정보보호",
|
|
"law": "개인정보보호법 제30조",
|
|
"title": "개인정보처리방침 게시",
|
|
"desc": "처리 목적·보유기간·제3자 제공 현황 공개",
|
|
"guardia_status": "미구현",
|
|
"action": "/static/privacy-policy.html 페이지 생성 필요",
|
|
},
|
|
{
|
|
"id": "PI-002",
|
|
"category": "개인정보보호",
|
|
"law": "개인정보보호법 제28조",
|
|
"title": "개인정보 암호화",
|
|
"desc": "비밀번호, 주민번호, 바이오정보 AES-256 이상 암호화",
|
|
"guardia_status": "구현됨",
|
|
"api": "모델: os_pw_enc 컬럼 AES-256-GCM",
|
|
},
|
|
{
|
|
"id": "PI-003",
|
|
"category": "개인정보보호",
|
|
"law": "개인정보보호법 제31조",
|
|
"title": "개인정보 영향평가 (PIA)",
|
|
"desc": "10만명 이상 처리 시스템 PIA 의무 수행",
|
|
"guardia_status": "해당없음",
|
|
"action": "사용 규모에 따라 PIA 수행 여부 판단 필요",
|
|
},
|
|
# ── 접근성 ──────────────────────────────────────────────────────────────
|
|
{
|
|
"id": "ACC-001",
|
|
"category": "웹 접근성",
|
|
"law": "장애인차별금지법 제21조",
|
|
"title": "KWCAG 2.1 준수 (수준 AA)",
|
|
"desc": "대체 텍스트, 자막, 색상 대비, 키보드 접근성 등 4개 원칙",
|
|
"guardia_status": "부분구현",
|
|
"api": "POST /api/compliance/scan/file → WA- 규칙",
|
|
},
|
|
{
|
|
"id": "ACC-002",
|
|
"category": "웹 접근성",
|
|
"law": "장애인차별금지법",
|
|
"title": "접근성 인증 마크",
|
|
"desc": "한국웹접근성인증평가원(WA) 인증 취득",
|
|
"guardia_status": "미구현",
|
|
"action": "접근성 점검 완료 후 WA 인증 신청 필요",
|
|
},
|
|
# ── 성능/가용성 ──────────────────────────────────────────────────────────
|
|
{
|
|
"id": "PERF-001",
|
|
"category": "성능",
|
|
"law": "행안부 정보시스템 구축·운영 지침",
|
|
"title": "성능 테스트 수행",
|
|
"desc": "오픈 전 목표 부하의 120% 이상 성능 검증",
|
|
"guardia_status": "구현됨",
|
|
"api": "POST /api/perf/run 또는 POST /api/perf/upload/jtl",
|
|
},
|
|
{
|
|
"id": "PERF-002",
|
|
"category": "가용성",
|
|
"law": "행안부 운영 지침",
|
|
"title": "99.9% 가용성 (연간 8.7시간 이하 다운)",
|
|
"desc": "이중화 구성, 자동 장애조치, 헬스체크",
|
|
"guardia_status": "부분구현",
|
|
"action": "Nginx HA, DB 이중화 설정 필요",
|
|
},
|
|
# ── 형상관리/배포 ────────────────────────────────────────────────────────
|
|
{
|
|
"id": "CM-001",
|
|
"category": "형상관리",
|
|
"law": "행안부 정보화사업 관리지침",
|
|
"title": "소스코드 형상관리 (Git)",
|
|
"desc": "브랜치 전략, 코드 리뷰, 변경 이력 관리",
|
|
"guardia_status": "구현됨",
|
|
"api": "Gitea: http://localhost:3000",
|
|
},
|
|
{
|
|
"id": "CM-002",
|
|
"category": "CI/CD",
|
|
"law": "행안부 SW 품질관리 지침",
|
|
"title": "자동 빌드·테스트·배포 파이프라인",
|
|
"desc": "Jenkins CI/CD, SonarQube 품질 게이트",
|
|
"guardia_status": "구현됨",
|
|
"api": "POST /api/vibe/{id}/build",
|
|
},
|
|
# ── 문서화 ──────────────────────────────────────────────────────────────
|
|
{
|
|
"id": "DOC-001",
|
|
"category": "문서화",
|
|
"law": "행안부 정보화사업 관리지침",
|
|
"title": "산출물 목록 관리",
|
|
"desc": "분석서, 설계서, 시험계획서, 시험결과서, 운영매뉴얼 필수",
|
|
"guardia_status": "구현됨",
|
|
"api": "GET /api/si/projects/{id}/deliverables",
|
|
},
|
|
{
|
|
"id": "DOC-002",
|
|
"category": "문서화",
|
|
"law": "행안부 정보화사업 관리지침",
|
|
"title": "정기 보고서 제출 (주간/월간)",
|
|
"desc": "주간 업무 보고, 월간 실적 보고 PM에게 제출",
|
|
"guardia_status": "구현됨",
|
|
"api": "GET /api/si/projects/{id}/report/weekly",
|
|
},
|
|
# ── 운영이관 ────────────────────────────────────────────────────────────
|
|
{
|
|
"id": "OP-001",
|
|
"category": "운영이관",
|
|
"law": "행안부 운영 지침",
|
|
"title": "CMDB 등록 (자산 관리)",
|
|
"desc": "서버·SW·네트워크 자산 CMDB 등록 및 라이프사이클 관리",
|
|
"guardia_status": "구현됨",
|
|
"api": "GET /api/cmdb/cis",
|
|
},
|
|
{
|
|
"id": "OP-002",
|
|
"category": "운영이관",
|
|
"law": "행안부 운영 지침",
|
|
"title": "On-Call 체계 구축",
|
|
"desc": "24/7 장애 대응 담당자 지정, 에스컬레이션 체계",
|
|
"guardia_status": "구현됨",
|
|
"api": "GET /api/oncall/on-duty",
|
|
},
|
|
]
|
|
|
|
# 항목별 완료 상태 저장 (실제 운영 시 DB 테이블로 이전)
|
|
_check_status: dict[str, dict] = {}
|
|
|
|
|
|
class CheckUpdateRequest(BaseModel):
|
|
completed: bool
|
|
note: Optional[str] = None
|
|
evidence_url: Optional[str] = None
|
|
|
|
|
|
@router.get("/checklist")
|
|
async def get_checklist(
|
|
category: Optional[str] = None,
|
|
status: Optional[str] = None,
|
|
_u: User = Depends(get_current_user),
|
|
):
|
|
"""공공기관 정보화사업 필수 체크리스트."""
|
|
items = []
|
|
for item in PUBLIC_CHECKLIST:
|
|
if category and item["category"] != category:
|
|
continue
|
|
check = _check_status.get(item["id"], {})
|
|
merged = {**item, "completed": check.get("completed", False),
|
|
"note": check.get("note"), "evidence_url": check.get("evidence_url"),
|
|
"checked_by": check.get("checked_by"), "checked_at": check.get("checked_at")}
|
|
if status == "done" and not merged["completed"]:
|
|
continue
|
|
if status == "todo" and merged["completed"]:
|
|
continue
|
|
items.append(merged)
|
|
|
|
completed = sum(1 for i in items if i["completed"])
|
|
return {
|
|
"total": len(items),
|
|
"completed": completed,
|
|
"completion_rate": round(completed / len(items) * 100, 1) if items else 0,
|
|
"items": items,
|
|
}
|
|
|
|
|
|
@router.post("/checklist/{check_id}/check")
|
|
async def update_check(
|
|
check_id: str,
|
|
body: CheckUpdateRequest,
|
|
_u: User = Depends(get_current_user),
|
|
):
|
|
"""체크리스트 항목 완료 처리."""
|
|
item = next((i for i in PUBLIC_CHECKLIST if i["id"] == check_id), None)
|
|
if not item:
|
|
raise HTTPException(404, f"체크리스트 항목 {check_id}를 찾을 수 없습니다.")
|
|
|
|
_check_status[check_id] = {
|
|
"completed": body.completed,
|
|
"note": body.note,
|
|
"evidence_url": body.evidence_url,
|
|
"checked_by": _u.username,
|
|
"checked_at": datetime.utcnow().isoformat(),
|
|
}
|
|
return {"message": f"{item['title']} — {'완료' if body.completed else '미완료'} 처리"}
|
|
|
|
|
|
@router.get("/status")
|
|
async def public_status(_u: User = Depends(get_current_user)):
|
|
"""GUARDiA 공공기관 준비 현황 요약."""
|
|
items = PUBLIC_CHECKLIST
|
|
total = len(items)
|
|
impl_done = sum(1 for i in items if i.get("guardia_status") in ("구현됨", "해당없음"))
|
|
check_done= sum(1 for i in items if _check_status.get(i["id"], {}).get("completed"))
|
|
|
|
by_cat: dict = {}
|
|
for item in items:
|
|
cat = item["category"]
|
|
if cat not in by_cat:
|
|
by_cat[cat] = {"total": 0, "impl": 0, "checked": 0}
|
|
by_cat[cat]["total"] += 1
|
|
if item.get("guardia_status") in ("구현됨", "해당없음"):
|
|
by_cat[cat]["impl"] += 1
|
|
if _check_status.get(item["id"], {}).get("completed"):
|
|
by_cat[cat]["checked"] += 1
|
|
|
|
return {
|
|
"total_items": total,
|
|
"guardia_impl": impl_done,
|
|
"verified": check_done,
|
|
"impl_rate": round(impl_done / total * 100, 1),
|
|
"verify_rate": round(check_done / total * 100, 1),
|
|
"by_category": by_cat,
|
|
"action_items": [i["id"] + ": " + i.get("action","") for i in items if i.get("action") and not _check_status.get(i["id"],{}).get("completed")],
|
|
}
|
|
|
|
|
|
@router.get("/report/html", response_class=HTMLResponse)
|
|
async def public_report_html(_u: User = Depends(get_current_user)):
|
|
"""공공기관 준비 현황 HTML 보고서."""
|
|
rows = ""
|
|
for item in PUBLIC_CHECKLIST:
|
|
chk = _check_status.get(item["id"], {})
|
|
done = chk.get("completed", False)
|
|
gstatus = item.get("guardia_status", "—")
|
|
color = "#c6f6d5" if done else ("#fef9e7" if gstatus == "구현됨" else "#fed7d7")
|
|
gstatus_color = "green" if gstatus == "구현됨" else "orange"
|
|
check_mark = "✅ 검증완료" if done else "⬜ 미검증"
|
|
note_text = item.get("action", "") or item.get("api", "")
|
|
rows += (
|
|
f"<tr style='background:{color}'>"
|
|
f"<td>{item['id']}</td><td>{item['category']}</td>"
|
|
f"<td><b>{item['title']}</b><br><small>{item['desc']}</small></td>"
|
|
f"<td>{item['law']}</td>"
|
|
f"<td style='color:{gstatus_color}'>{gstatus}</td>"
|
|
f"<td>{check_mark}</td>"
|
|
f"<td><small>{note_text}</small></td>"
|
|
"</tr>"
|
|
)
|
|
|
|
status = await public_status(_u)
|
|
html = f"""<!DOCTYPE html><html lang="ko"><head><meta charset="UTF-8">
|
|
<title>공공기관 필수 기능 체크리스트</title>
|
|
<style>
|
|
body{{font-family:Arial,sans-serif;margin:30px;font-size:12px}}
|
|
h1{{color:#1a365d}} table{{border-collapse:collapse;width:100%}}
|
|
th{{background:#2d3748;color:#fff;padding:8px;text-align:left}}
|
|
td{{padding:6px 8px;border-bottom:1px solid #e2e8f0;vertical-align:top}}
|
|
.bar{{background:#e2e8f0;border-radius:4px;height:12px;overflow:hidden}}
|
|
.bar-fill{{background:#4299e1;height:100%;border-radius:4px}}
|
|
</style></head><body>
|
|
<h1>공공기관 정보화사업 필수 기능 체크리스트</h1>
|
|
<p>총 {status['total_items']}개 항목 | GUARDiA 구현율: <b>{status['impl_rate']}%</b> | 검증 완료: <b>{status['verify_rate']}%</b></p>
|
|
<div class="bar"><div class="bar-fill" style="width:{status['impl_rate']}%"></div></div>
|
|
<br>
|
|
<table>
|
|
<tr><th>ID</th><th>분류</th><th>항목</th><th>법적 근거</th><th>GUARDiA</th><th>검증</th><th>비고</th></tr>
|
|
{rows}
|
|
</table>
|
|
<p style="margin-top:20px;color:#718096;font-size:11px">
|
|
Copyright © 2026 GUARDiA All Rights Reserved.
|
|
</p></body></html>"""
|
|
return HTMLResponse(html)
|