356 lines
16 KiB
Python
356 lines
16 KiB
Python
"""
|
|
GUARDiA GS인증 (Good Software 인증) 요건 자동 점검·보고
|
|
GS 1등급/2등급 인증을 위한 요건 체크리스트 자동 점검 및 보고서 생성
|
|
TTA(한국정보통신기술협회) 기준 준수
|
|
"""
|
|
import json
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List, Optional
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from pydantic import BaseModel
|
|
|
|
router = APIRouter(prefix="/api/gs-cert", tags=["GS Certification"])
|
|
|
|
|
|
# ── GS인증 요건 체크리스트 ───────────────────────────────────────────────────
|
|
# TTA GS 인증 기준 (SW 시험 기준 V4.0)
|
|
|
|
GS_CHECKLIST = {
|
|
"기능성": {
|
|
"weight": 25,
|
|
"items": [
|
|
{"id": "F-01", "name": "기능 완전성", "desc": "명세된 모든 기능이 구현됨", "required": True},
|
|
{"id": "F-02", "name": "기능 정확성", "desc": "올바른 결과와 필요한 정밀도 달성", "required": True},
|
|
{"id": "F-03", "name": "기능 적절성", "desc": "특정 사용자 목표 달성에 적절한 기능 제공", "required": True},
|
|
{"id": "F-04", "name": "기능 준수성", "desc": "관련 표준/규약/규정 준수", "required": False},
|
|
{"id": "F-05", "name": "상호운용성", "desc": "다른 시스템과의 상호작용 가능", "required": False},
|
|
]
|
|
},
|
|
"신뢰성": {
|
|
"weight": 20,
|
|
"items": [
|
|
{"id": "R-01", "name": "성숙성", "desc": "소프트웨어 결함에 의한 고장 회피", "required": True},
|
|
{"id": "R-02", "name": "결함 허용성", "desc": "결함 발생 시에도 일정 성능 수준 유지", "required": True},
|
|
{"id": "R-03", "name": "회복성", "desc": "고장 후 데이터 복구 및 성능 재확립 능력", "required": True},
|
|
{"id": "R-04", "name": "가용성", "desc": "요구된 기간 동안 운영·접근 가능 상태 유지", "required": False},
|
|
]
|
|
},
|
|
"사용성": {
|
|
"weight": 15,
|
|
"items": [
|
|
{"id": "U-01", "name": "이해용이성", "desc": "특정 작업에 적합한지 쉽게 이해 가능", "required": True},
|
|
{"id": "U-02", "name": "학습용이성", "desc": "기능 학습이 용이", "required": True},
|
|
{"id": "U-03", "name": "운영용이성", "desc": "사용자 제어 및 운영이 용이", "required": True},
|
|
{"id": "U-04", "name": "접근성", "desc": "장애인 등 다양한 사용자가 접근 가능 (WCAG 2.1 AA)", "required": True},
|
|
{"id": "U-05", "name": "UI 심미성", "desc": "사용자에게 즐겁고 만족스러운 상호작용 제공", "required": False},
|
|
]
|
|
},
|
|
"효율성": {
|
|
"weight": 15,
|
|
"items": [
|
|
{"id": "E-01", "name": "시간 효율성", "desc": "적절한 응답·처리 속도 (95% 응답 < 3초)", "required": True},
|
|
{"id": "E-02", "name": "자원 효율성", "desc": "CPU/메모리/디스크 자원 적절 사용", "required": True},
|
|
{"id": "E-03", "name": "용량 만족성", "desc": "최대 한계 처리 능력이 요건 충족", "required": False},
|
|
]
|
|
},
|
|
"유지보수성": {
|
|
"weight": 10,
|
|
"items": [
|
|
{"id": "M-01", "name": "분석성", "desc": "결함 진단·수정 위치 식별 용이", "required": True},
|
|
{"id": "M-02", "name": "변경성", "desc": "명세된 변경이 용이하게 이뤄짐", "required": True},
|
|
{"id": "M-03", "name": "안정성", "desc": "변경에 의한 예상치 못한 영향 최소화", "required": True},
|
|
{"id": "M-04", "name": "시험성", "desc": "변경된 소프트웨어 검증 용이", "required": True},
|
|
{"id": "M-05", "name": "모듈성", "desc": "한 컴포넌트 변경이 다른 컴포넌트에 영향 최소", "required": False},
|
|
]
|
|
},
|
|
"이식성": {
|
|
"weight": 10,
|
|
"items": [
|
|
{"id": "P-01", "name": "적응성", "desc": "다른 환경(OS/HW)에 적응 가능", "required": False},
|
|
{"id": "P-02", "name": "설치성", "desc": "지정 환경에 성공적으로 설치 가능", "required": True},
|
|
{"id": "P-03", "name": "공존성", "desc": "동일 환경 다른 소프트웨어와 공존 가능", "required": False},
|
|
]
|
|
},
|
|
"보안성": {
|
|
"weight": 5,
|
|
"items": [
|
|
{"id": "S-01", "name": "기밀성", "desc": "인가된 사용자만 접근 (JWT/RBAC)", "required": True},
|
|
{"id": "S-02", "name": "무결성", "desc": "데이터 불법 접근 및 변경 방지", "required": True},
|
|
{"id": "S-03", "name": "부인방지성", "desc": "행위 발생 사실 부인 불가 (감사로그)", "required": True},
|
|
{"id": "S-04", "name": "책임추적성", "desc": "사용자 행위 추적 가능", "required": True},
|
|
{"id": "S-05", "name": "인증성", "desc": "사용자/시스템 신원 확인 가능", "required": True},
|
|
{"id": "S-06", "name": "취약점 제거", "desc": "알려진 보안 취약점 (OWASP Top 10) 제거", "required": True},
|
|
]
|
|
},
|
|
}
|
|
|
|
# ITSM 현재 구현 상태 (자동 점검 기준)
|
|
ITSM_IMPLEMENTATION = {
|
|
"F-01": True, "F-02": True, "F-03": True, "F-04": True, "F-05": True,
|
|
"R-01": True, "R-02": True, "R-03": True, "R-04": True,
|
|
"U-01": True, "U-02": True, "U-03": True, "U-04": False, "U-05": True,
|
|
"E-01": True, "E-02": True, "E-03": False,
|
|
"M-01": True, "M-02": True, "M-03": True, "M-04": True, "M-05": True,
|
|
"P-01": True, "P-02": True, "P-03": True,
|
|
"S-01": True, "S-02": True, "S-03": True, "S-04": True, "S-05": True, "S-06": True,
|
|
}
|
|
|
|
# GS인증 추가 필수 서류/산출물
|
|
REQUIRED_DOCUMENTS = [
|
|
{"id": "DOC-01", "name": "소프트웨어 요구사항 명세서(SRS)", "status": "완료", "path": "docs/srs.md"},
|
|
{"id": "DOC-02", "name": "소프트웨어 설계 명세서", "status": "완료", "path": "docs/design.md"},
|
|
{"id": "DOC-03", "name": "테스트 케이스 명세서", "status": "완료", "path": "docs/test_cases.md"},
|
|
{"id": "DOC-04", "name": "사용자 매뉴얼", "status": "완료", "path": "workspace/guardia-docs/"},
|
|
{"id": "DOC-05", "name": "설치 가이드", "status": "완료", "path": "docs/install.md"},
|
|
{"id": "DOC-06", "name": "운영자 매뉴얼", "status": "완료", "path": "workspace/guardia-docs/"},
|
|
{"id": "DOC-07", "name": "접근성 준수 확인서", "status": "미완료","path": ""},
|
|
{"id": "DOC-08", "name": "보안 취약점 점검 결과서", "status": "완료", "path": "docs/security_audit.md"},
|
|
{"id": "DOC-09", "name": "성능 시험 결과서", "status": "미완료","path": ""},
|
|
{"id": "DOC-10", "name": "오픈소스 사용 신고서", "status": "완료", "path": "docs/opensource.md"},
|
|
]
|
|
|
|
_overrides: Dict[str, bool] = {} # 수동 업데이트
|
|
|
|
|
|
# ── Pydantic 모델 ──────────────────────────────────────────────────────────
|
|
|
|
class ItemUpdate(BaseModel):
|
|
item_id: str
|
|
status: bool
|
|
note: Optional[str] = None
|
|
|
|
class AuditReport(BaseModel):
|
|
grade: str # "1등급" | "2등급" | "미달"
|
|
score: float
|
|
passed: int
|
|
failed: int
|
|
required_failed: List[str]
|
|
by_category: Dict[str, Any]
|
|
documents: List[Dict]
|
|
recommendations: List[str]
|
|
generated_at: str
|
|
|
|
|
|
# ── 점검 로직 ─────────────────────────────────────────────────────────────
|
|
|
|
def _run_audit() -> AuditReport:
|
|
impl = {**ITSM_IMPLEMENTATION, **_overrides}
|
|
|
|
total_score = 0.0
|
|
passed = 0
|
|
failed = 0
|
|
required_failed: List[str] = []
|
|
by_category: Dict[str, Any] = {}
|
|
recommendations: List[str] = []
|
|
|
|
for cat_name, cat_data in GS_CHECKLIST.items():
|
|
weight = cat_data["weight"]
|
|
items = cat_data["items"]
|
|
cat_passed = 0
|
|
cat_failed_items = []
|
|
|
|
for item in items:
|
|
iid = item["id"]
|
|
ok = impl.get(iid, False)
|
|
if ok:
|
|
cat_passed += 1
|
|
passed += 1
|
|
else:
|
|
failed += 1
|
|
cat_failed_items.append(iid)
|
|
if item["required"]:
|
|
required_failed.append(f"{iid}: {item['name']}")
|
|
recommendations.append(f"[{iid}] {item['desc']} — 즉시 구현 필요")
|
|
else:
|
|
recommendations.append(f"[{iid}] {item['desc']} — 권고 사항")
|
|
|
|
cat_score = (cat_passed / len(items)) * weight
|
|
total_score += cat_score
|
|
by_category[cat_name] = {
|
|
"score": round(cat_score, 2),
|
|
"weight": weight,
|
|
"passed": cat_passed,
|
|
"total": len(items),
|
|
"failed": cat_failed_items,
|
|
}
|
|
|
|
# 등급 판정 (TTA 기준: 필수 항목 전체 통과 + 총점 80+ = 1등급, 70+ = 2등급)
|
|
if not required_failed and total_score >= 80:
|
|
grade = "GS 1등급"
|
|
elif len(required_failed) <= 2 and total_score >= 70:
|
|
grade = "GS 2등급"
|
|
else:
|
|
grade = "미달 (재심사 필요)"
|
|
|
|
return AuditReport(
|
|
grade=grade,
|
|
score=round(total_score, 2),
|
|
passed=passed,
|
|
failed=failed,
|
|
required_failed=required_failed,
|
|
by_category=by_category,
|
|
documents=REQUIRED_DOCUMENTS,
|
|
recommendations=recommendations[:20],
|
|
generated_at=datetime.utcnow().isoformat(),
|
|
)
|
|
|
|
|
|
# ── API 엔드포인트 ─────────────────────────────────────────────────────────
|
|
|
|
@router.get("/checklist")
|
|
async def get_checklist():
|
|
"""GS인증 체크리스트 전체 조회."""
|
|
impl = {**ITSM_IMPLEMENTATION, **_overrides}
|
|
result = {}
|
|
for cat, data in GS_CHECKLIST.items():
|
|
result[cat] = {
|
|
"weight": data["weight"],
|
|
"items": [
|
|
{**item, "passed": impl.get(item["id"], False)}
|
|
for item in data["items"]
|
|
]
|
|
}
|
|
return {"checklist": result, "categories": list(GS_CHECKLIST.keys())}
|
|
|
|
|
|
@router.get("/audit")
|
|
async def run_audit():
|
|
"""GS인증 자동 점검 실행 — 등급 판정."""
|
|
return _run_audit().model_dump()
|
|
|
|
|
|
@router.get("/audit/summary")
|
|
async def audit_summary():
|
|
"""점검 결과 요약 (경량)."""
|
|
report = _run_audit()
|
|
return {
|
|
"grade": report.grade,
|
|
"score": report.score,
|
|
"passed": report.passed,
|
|
"failed": report.failed,
|
|
"required_failed": len(report.required_failed),
|
|
"generated_at": report.generated_at,
|
|
}
|
|
|
|
|
|
@router.patch("/checklist/item")
|
|
async def update_item(req: ItemUpdate):
|
|
"""체크리스트 항목 수동 업데이트."""
|
|
_overrides[req.item_id] = req.status
|
|
return {
|
|
"updated": True,
|
|
"item_id": req.item_id,
|
|
"status": req.status,
|
|
"note": req.note,
|
|
}
|
|
|
|
|
|
@router.get("/documents")
|
|
async def get_documents():
|
|
"""GS인증 필수 산출물 목록."""
|
|
completed = sum(1 for d in REQUIRED_DOCUMENTS if d["status"] == "완료")
|
|
return {
|
|
"documents": REQUIRED_DOCUMENTS,
|
|
"total": len(REQUIRED_DOCUMENTS),
|
|
"completed": completed,
|
|
"missing": len(REQUIRED_DOCUMENTS) - completed,
|
|
}
|
|
|
|
|
|
@router.get("/report/json")
|
|
async def export_report_json():
|
|
"""GS인증 점검 보고서 JSON 내보내기."""
|
|
report = _run_audit()
|
|
return {
|
|
"report_type": "GS_CERTIFICATION_AUDIT",
|
|
"system": "GUARDiA ITSM v2.1.0",
|
|
"standard": "TTA GS 인증 기준 SW 시험 V4.0",
|
|
"result": report.model_dump(),
|
|
}
|
|
|
|
|
|
@router.get("/report/markdown")
|
|
async def export_report_markdown():
|
|
"""GS인증 점검 보고서 Markdown 내보내기."""
|
|
report = _run_audit()
|
|
lines = [
|
|
"# GUARDiA ITSM GS인증 점검 보고서",
|
|
f"생성일시: {report.generated_at}",
|
|
f"판정 등급: **{report.grade}**",
|
|
f"총점: {report.score}/100",
|
|
f"통과: {report.passed}개 / 실패: {report.failed}개",
|
|
"",
|
|
"## 카테고리별 결과",
|
|
"| 카테고리 | 가중치 | 통과 | 총수 | 점수 |",
|
|
"|---------|--------|------|------|------|",
|
|
]
|
|
for cat, data in report.by_category.items():
|
|
lines.append(f"| {cat} | {data['weight']}% | {data['passed']} | {data['total']} | {data['score']} |")
|
|
|
|
if report.required_failed:
|
|
lines += ["", "## 필수 미통과 항목", ""]
|
|
for item in report.required_failed:
|
|
lines.append(f"- {item}")
|
|
|
|
if report.recommendations:
|
|
lines += ["", "## 개선 권고사항", ""]
|
|
for rec in report.recommendations[:10]:
|
|
lines.append(f"- {rec}")
|
|
|
|
lines += ["", "## 필수 산출물", "| ID | 문서명 | 상태 |", "|-----|--------|------|"]
|
|
for doc in report.documents:
|
|
lines.append(f"| {doc['id']} | {doc['name']} | {doc['status']} |")
|
|
|
|
return {"markdown": "\n".join(lines)}
|
|
|
|
|
|
@router.get("/requirements")
|
|
async def list_requirements():
|
|
"""GS인증 추가 요건 (공공기관 납품 기준)."""
|
|
return {
|
|
"requirements": [
|
|
{"category": "법적 요건", "items": [
|
|
"저작권 등록 또는 소프트웨어 사업자 신고",
|
|
"오픈소스 라이선스 적합성 검토",
|
|
"개인정보보호법 준수 (개인정보 처리 시)",
|
|
]},
|
|
{"category": "TTA 시험", "items": [
|
|
"TTA 공인 시험기관 기능 시험",
|
|
"성능 시험 (부하, 스트레스)",
|
|
"보안 취약점 시험 (OWASP Top 10)",
|
|
"접근성 시험 (KWCAG 2.1)",
|
|
]},
|
|
{"category": "공공기관 특화", "items": [
|
|
"행정정보시스템 연동 가이드 준수",
|
|
"GS 인증서 취득 후 3년 유효",
|
|
"나라장터 등록 가능 (GS 1·2등급)",
|
|
"공공기관 정보화사업 입찰 자격",
|
|
]},
|
|
]
|
|
}
|
|
|
|
|
|
@router.get("/score/breakdown")
|
|
async def score_breakdown():
|
|
"""카테고리별 점수 분해 — 개선 우선순위 확인."""
|
|
report = _run_audit()
|
|
breakdowns = []
|
|
for cat, data in report.by_category.items():
|
|
max_possible = data["weight"]
|
|
current = data["score"]
|
|
gap = round(max_possible - current, 2)
|
|
breakdowns.append({
|
|
"category": cat,
|
|
"current": current,
|
|
"maximum": max_possible,
|
|
"gap": gap,
|
|
"priority": "높음" if gap > 5 else ("중간" if gap > 2 else "낮음"),
|
|
})
|
|
breakdowns.sort(key=lambda x: x["gap"], reverse=True)
|
|
return {
|
|
"total_score": report.score,
|
|
"grade": report.grade,
|
|
"breakdown": breakdowns,
|
|
"quick_wins": [b for b in breakdowns if b["gap"] > 0 and b["priority"] != "낮음"],
|
|
}
|