---
name: csap-compliance
description: "GUARDiA CSAP/ISMS-P 공공기관 보안 준수 자동 점검 구현 스킬. 기존 compliance.py를 확장하여 공공기관 보안 체크리스트(100개 항목) 자동 점검, 증적 수집, Excel/HTML 보고서 생성을 구현한다. 다음 상황에서 반드시 사용: (1) 'CSAP', 'ISMS', '보안인증', '공공기관 보안점검' 구현 요청; (2) compliance.py CSAP 고도화 또는 core/csap_checker.py 작업; (3) 보안 점검 보고서, 준수율 대시보드 구현; (4) 증적 수집, 체크리스트 자동화; (5) 다시 실행, 업데이트, 보완 요청."
---

# CSAP 자동 점검 구현 스킬

## 구현 대상 파일
- `itsm/core/csap_checker.py` — CSAP 점검 엔진
- `itsm/routers/compliance.py` — 기존 파일에 CSAP 엔드포인트 추가

## DB 모델 (models.py에 추가)

```python
class CSAPCheckResult(Base):
    __tablename__ = "tb_csap_result"
    id             = Column(Integer, primary_key=True)
    scan_id        = Column(String(50), nullable=False, index=True)  # CSAP-YYYYMMDD-NNN
    inst_id        = Column(Integer, ForeignKey("tb_inst_meta.id"))
    item_id        = Column(String(20), nullable=False)    # M-01, T-15 등
    category       = Column(String(20))                    # 관리적 | 기술적 | 물리적 | 운영
    item_name      = Column(String(200))
    status         = Column(String(20))                    # PASS|FAIL|PARTIAL|MANUAL_REQUIRED|N_A
    severity       = Column(String(20))                    # HIGH|MEDIUM|LOW
    finding        = Column(Text)                          # 발견 사항
    evidence       = Column(JSON)                          # 자동 수집 증적 (마스킹 처리)
    recommendation = Column(Text)                          # 개선 권고
    scanned_at     = Column(DateTime, default=func.now())
```

## CSAP 점검 항목 구조 (core/csap_checker.py)

```python
CSAP_ITEMS = [
    # ── 관리적 보안 (M) ────────────────────────────────────────────────
    {"id":"M-01","cat":"관리적","sev":"HIGH","auto":False,
     "name":"정보보호 정책 수립","check":"policy_doc_uploaded"},
    {"id":"M-02","cat":"관리적","sev":"HIGH","auto":False,
     "name":"정보보호 조직 구성","check":"org_chart_uploaded"},
    {"id":"M-03","cat":"관리적","sev":"MEDIUM","auto":True,
     "name":"정보보호 교육 이력","check":"training_records_exist"},
    # ── 기술적 보안 (T) ────────────────────────────────────────────────
    {"id":"T-01","cat":"기술적","sev":"HIGH","auto":True,
     "name":"계정 잠금 정책 (5회 실패 시 잠금)","check":"account_lockout"},
    {"id":"T-02","cat":"기술적","sev":"HIGH","auto":True,
     "name":"패스워드 복잡도 정책 (8자 이상+특수문자)","check":"password_policy"},
    {"id":"T-03","cat":"기술적","sev":"HIGH","auto":True,
     "name":"불필요 서비스 비활성화","check":"unnecessary_services"},
    {"id":"T-04","cat":"기술적","sev":"HIGH","auto":True,
     "name":"SSH root 직접 로그인 차단","check":"ssh_root_disabled"},
    {"id":"T-05","cat":"기술적","sev":"HIGH","auto":True,
     "name":"보안 패치 최신화 (30일 이내)","check":"patch_currency"},
    {"id":"T-06","cat":"기술적","sev":"MEDIUM","auto":True,
     "name":"방화벽 룰 최소 권한 원칙","check":"fw_least_privilege"},
    {"id":"T-07","cat":"기술적","sev":"HIGH","auto":True,
     "name":"암호화 전송 (HTTPS/TLS 1.2 이상)","check":"tls_version"},
    {"id":"T-08","cat":"기술적","sev":"HIGH","auto":True,
     "name":"개인정보 암호화 저장","check":"pii_encryption"},
    # ── 운영 보안 (O) ─────────────────────────────────────────────────
    {"id":"O-01","cat":"운영","sev":"HIGH","auto":True,
     "name":"로그 보존 기간 (6개월 이상)","check":"log_retention"},
    {"id":"O-02","cat":"운영","sev":"HIGH","auto":True,
     "name":"백업 실시 및 무결성 검증","check":"backup_integrity"},
    {"id":"O-03","cat":"운영","sev":"MEDIUM","auto":True,
     "name":"변경 관리 프로세스 이행","check":"change_management"},
    # ── 물리적 보안 (P) ───────────────────────────────────────────────
    {"id":"P-01","cat":"물리적","sev":"MEDIUM","auto":False,
     "name":"출입 통제 시스템 운영","check":"physical_access"},
    {"id":"P-02","cat":"물리적","sev":"HIGH","auto":True,
     "name":"DR 사이트 운영 (RTO/RPO 충족)","check":"dr_test_passed"},
    # ... 총 100개 항목 (실제 구현 시 전체 목록 확장)
]
```

## 자동 점검 함수 패턴

```python
class CSAPChecker:
    async def check_ssh_root_disabled(self, db, inst_id: int) -> dict:
        """T-04: SSH root 로그인 차단 확인."""
        # 기관 서버 목록 조회 → 각 서버 SSH 접속 → /etc/ssh/sshd_config 확인
        # PermitRootLogin no 확인
        ...

    async def check_patch_currency(self, db, inst_id: int) -> dict:
        """T-05: 보안 패치 최신화 (30일 이내 패치 여부)."""
        # SSH → rpm -qa --last | head -20 또는 apt list --upgradable
        ...

    async def check_log_retention(self, db, inst_id: int) -> dict:
        """O-01: 로그 보존 6개월 이상."""
        # GUARDiA tb_audit_log 최오래된 레코드 날짜 확인
        from sqlalchemy import select, func
        oldest = await db.scalar(select(func.min(AuditLog.created_at)))
        ...

    async def check_backup_integrity(self, db, inst_id: int) -> dict:
        """O-02: 백업 무결성 (DR 테스트 최근 90일 이내 PASS)."""
        # tb_dr_test에서 최근 PASS 결과 확인
        ...

    async def check_dr_test_passed(self, db, inst_id: int) -> dict:
        """P-02: DR 테스트 이력."""
        # tb_dr_test에서 최근 1년 이내 PASS 확인
        ...

    def generate_scan_id(self) -> str:
        from datetime import datetime
        now = datetime.now()
        return f"CSAP-{now.strftime('%Y%m%d')}-{now.strftime('%H%M%S')}"
```

## 보고서 생성 패턴

```python
def generate_excel_report(self, results: list, inst_name: str) -> bytes:
    """openpyxl 기반 Excel 보고서 생성."""
    import openpyxl
    from openpyxl.styles import PatternFill, Font
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "CSAP 점검 결과"
    # 헤더: 항목ID, 카테고리, 항목명, 심각도, 결과, 발견사항, 개선권고
    # 결과별 색상: PASS=녹, FAIL=적, PARTIAL=황
    ...

def generate_html_report(self, results: list, scan_id: str) -> str:
    """HTML 점검 보고서 (인쇄 가능, 공문 첨부용)."""
    # 준수율 차트 (SVG inline), 항목별 상세 테이블
    ...
```

## compliance.py 추가 엔드포인트

```
POST /api/compliance/csap/scan              전체 자동 점검 (ADMIN 전용)
GET  /api/compliance/csap/items             점검 항목 목록 (category 필터)
GET  /api/compliance/csap/results           최근 점검 결과 요약 목록
GET  /api/compliance/csap/results/{scan_id} 배치 상세 결과
POST /api/compliance/csap/evidence/{item_id} 수동 증적 업로드
GET  /api/compliance/csap/report/html       HTML 보고서 (scan_id 쿼리)
GET  /api/compliance/csap/report/excel      Excel 보고서 (scan_id 쿼리)
GET  /api/compliance/csap/dashboard         기관별 준수율 대시보드
```

## 준수율 계산 공식
```
자동 점검 통과율 = (PASS + PARTIAL*0.5) / (전체 자동 항목) * 100
수동 항목 = MANUAL_REQUIRED로 표시, 별도 집계
전체 준수율 = (자동 통과 항목 수 + 수동 PASS 업로드 수) / 전체 100개 * 100
```
