""" 공공기관 정보화사업 필수 기능 체크리스트 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"
총 {status['total_items']}개 항목 | GUARDiA 구현율: {status['impl_rate']}% | 검증 완료: {status['verify_rate']}%
| ID | 분류 | 항목 | 법적 근거 | GUARDiA | 검증 | 비고 |
|---|
Copyright © 2026 GUARDiA All Rights Reserved.
""" return HTMLResponse(html)