diff --git a/backend/src/main/resources/db/migration/V026__data_retention_policy_menu.sql b/backend/src/main/resources/db/migration/V026__data_retention_policy_menu.sql new file mode 100644 index 0000000..000534c --- /dev/null +++ b/backend/src/main/resources/db/migration/V026__data_retention_policy_menu.sql @@ -0,0 +1,28 @@ +-- ============================================================ +-- V026: 데이터 보관기간 및 파기 정책 (DAR-10) 메뉴 추가 +-- 시스템관리 > 감사·보안 서브그룹 +-- ============================================================ + +-- 1. 권한 트리 노드 등록 +INSERT INTO kcg.auth_perm_tree(rsrc_cd, parent_cd, rsrc_nm, rsrc_level, sort_ord) +VALUES ('admin:data-retention', 'admin', '데이터 보관·파기', 1, 57) +ON CONFLICT (rsrc_cd) DO NOTHING; + +-- 2. 메뉴 메타데이터 갱신 +UPDATE kcg.auth_perm_tree +SET url_path = '/admin/data-retention', + label_key = 'nav.dataRetentionPolicy', + component_key = 'features/admin/DataRetentionPolicy', + nav_group = 'admin', + nav_sub_group = '감사·보안', + nav_sort = 2000, + labels = '{"ko":"데이터 보관·파기","en":"Data Retention"}' +WHERE rsrc_cd = 'admin:data-retention'; + +-- 3. ADMIN 역할에 전체 권한 부여 +INSERT INTO kcg.auth_perm(role_sn, rsrc_cd, oper_cd, grant_yn) +SELECT r.role_sn, 'admin:data-retention', op.oper_cd, 'Y' +FROM kcg.auth_role r +CROSS JOIN (VALUES ('READ'), ('CREATE'), ('UPDATE'), ('DELETE'), ('EXPORT')) AS op(oper_cd) +WHERE r.role_cd = 'ADMIN' +ON CONFLICT (role_sn, rsrc_cd, oper_cd) DO NOTHING; diff --git a/frontend/src/app/componentRegistry.ts b/frontend/src/app/componentRegistry.ts index caed5d4..d712205 100644 --- a/frontend/src/app/componentRegistry.ts +++ b/frontend/src/app/componentRegistry.ts @@ -122,6 +122,9 @@ export const COMPONENT_REGISTRY: Record = { 'features/admin/AIAgentSecurityPage': lazy(() => import('@features/admin').then((m) => ({ default: m.AIAgentSecurityPage })), ), + 'features/admin/DataRetentionPolicy': lazy(() => + import('@features/admin').then((m) => ({ default: m.DataRetentionPolicy })), + ), // ── 모선 워크플로우 ── 'features/parent-inference/ParentReview': lazy(() => import('@features/parent-inference/ParentReview').then((m) => ({ diff --git a/frontend/src/features/admin/DataRetentionPolicy.tsx b/frontend/src/features/admin/DataRetentionPolicy.tsx new file mode 100644 index 0000000..3858b00 --- /dev/null +++ b/frontend/src/features/admin/DataRetentionPolicy.tsx @@ -0,0 +1,432 @@ +import { useState } from 'react'; +import { Card, CardContent } from '@shared/components/ui/card'; +import { Badge } from '@shared/components/ui/badge'; +import { PageContainer, PageHeader } from '@shared/components/layout'; +import { getStatusIntent } from '@shared/constants/statusIntent'; +import { + Database, Clock, Trash2, ShieldCheck, FileText, AlertTriangle, + CheckCircle, Archive, CalendarClock, UserCheck, Search, + ChevronRight, Lock, Eye, Settings, +} from 'lucide-react'; + +/* + * DAR-10: 데이터 보관기간 및 파기 정책 + * + * 법령 내 규정 및 유관기관 협의에 따라 데이터 유형별 보관기간 및 + * 파기 절차를 수립하고 체계적으로 관리하는 정책 페이지. + * + * ① 보관 현황 ② 유형별 보관기간 ③ 파기 절차 ④ 예외·연장 ⑤ 파기 감사 대장 + */ + +type Tab = 'overview' | 'retention' | 'disposal' | 'exception' | 'audit'; + +// ─── 보관 현황 KPI ────────────────── +const RETENTION_KPI = [ + { label: '관리 데이터 유형', value: '6종', icon: Database, color: '#3b82f6' }, + { label: '보관기간 초과', value: '0건', icon: Clock, color: '#10b981' }, + { label: '파기 대기', value: '3건', icon: Trash2, color: '#f59e0b' }, + { label: '보존 연장 중', value: '1건', icon: ShieldCheck, color: '#8b5cf6' }, + { label: '금월 파기 완료', value: '12건', icon: CheckCircle, color: '#06b6d4' }, +]; + +// ─── 유형별 보관기간 기준표 ────────────────── +const RETENTION_TABLE = [ + { type: '선박 위치 로그 (AIS)', category: '운항 데이터', basis: '해사안전법 시행규칙 제42조', period: '5년', format: 'PostgreSQL + TimescaleDB', volume: '약 2.1TB/년', status: '정상' }, + { type: '단속 자료', category: '법집행 기록', basis: '해양경비법 제18조, 공공기록물법', period: '10년 (영구 가능)', format: 'PostgreSQL + 파일 스토리지', volume: '약 150GB/년', status: '정상' }, + { type: '수사 관련 자료', category: '수사 기록', basis: '형사소송법 제198조, 수사기록 보존규칙', period: '영구 (종결 후 30년)', format: '암호화 스토리지', volume: '약 50GB/년', status: '정상' }, + { type: 'AI 학습용 임시 데이터', category: 'AI 학습 데이터', basis: '개인정보보호법 제21조', period: '학습 완료 후 90일', format: 'S3 + DVC', volume: '약 500GB/주기', status: '정상' }, + { type: 'CCTV·영상 증거', category: '영상 데이터', basis: '개인정보보호법 제25조', period: '30일 (증거 채택 시 영구)', format: 'NAS + HLS', volume: '약 3TB/월', status: '주의' }, + { type: '시스템 접근 로그', category: '감사 로그', basis: '정보통신망법 제45조, 전자금융감독규정', period: '5년', format: 'Elasticsearch', volume: '약 80GB/년', status: '정상' }, +]; + +// ─── 파기 방식 정의 ────────────────── +const DISPOSAL_METHODS = [ + { method: '완전 삭제 (Secure Erase)', desc: 'DoD 5220.22-M 기준 3회 덮어쓰기 후 삭제', target: 'DB 레코드, 파일', encryption: '해당 없음', recovery: '복구 불가능', status: '적용' }, + { method: '암호화 키 폐기', desc: '암호화된 데이터의 복호화 키를 영구 폐기하여 접근 차단', target: '암호화 스토리지', encryption: 'AES-256 키 폐기', recovery: '복구 불가능', status: '적용' }, + { method: '논리적 삭제 + 만료', desc: 'soft-delete 마킹 후 보관기간 만료 시 물리 삭제 전환', target: '운영 DB', encryption: '-', recovery: '만료 전 복구 가능', status: '적용' }, + { method: '물리적 파기', desc: '디가우저(Degausser) 또는 물리적 파쇄로 매체 파기', target: '이동식 매체, 하드디스크', encryption: '해당 없음', recovery: '복구 불가능', status: '적용' }, +]; + +// ─── 파기 승인 절차 ────────────────── +const DISPOSAL_WORKFLOW = [ + { phase: '① 파기 대상 선별', actions: ['보관기간 만료 데이터 자동 탐색', '유형별 파기 대상 목록 생성', '백업 데이터 포함 여부 확인'], responsible: '시스템 자동', icon: Search }, + { phase: '② 파기 신청', actions: ['파기 대상 목록 검토 및 승인 요청', '수사·소송 보존 연장 대상 제외 확인', '파기 방식 지정 (완전삭제/키폐기/물리파기)'], responsible: '데이터 관리자', icon: FileText }, + { phase: '③ 승인 및 집행', actions: ['보안담당관 파기 승인', '파기 실행 (이중 확인 절차)', '백업 데이터 동시 파기'], responsible: '보안담당관', icon: UserCheck }, + { phase: '④ 결과 기록', actions: ['파기 결과 로그 자동 기록', '파기 대장 등록 (대상·일시·담당자·방식)', '감사 보고서 생성 및 보관'], responsible: '시스템 자동', icon: Archive }, +]; + +// ─── 보존 연장 예외 현황 ────────────────── +const EXCEPTIONS = [ + { id: 'EXC-2026-001', dataType: '단속 자료 #2024-1892', reason: '수사 진행 중 (인천해경)', originalExpiry: '2026-03-15', extendedTo: '수사 종결 시까지', approver: '수사과장', status: '연장 중' }, + { id: 'EXC-2026-002', dataType: 'AIS 로그 (2021-Q2)', reason: '재판 증거 제출 (서울중앙지법)', originalExpiry: '2026-06-30', extendedTo: '판결 확정 시까지', approver: '법무담당관', status: '연장 중' }, + { id: 'EXC-2025-015', dataType: 'CCTV 영상 #V-2025-0342', reason: '감사원 감사 대상', originalExpiry: '2025-12-01', extendedTo: '2026-06-30', approver: '감사담당관', status: '해제 예정' }, +]; + +const EXCEPTION_RULES = [ + { rule: '수사·소송 보존', desc: '수사 개시 또는 소송 진행 중인 데이터는 종결 시까지 파기 유예', authority: '수사과장 / 법무담당관' }, + { rule: '감사 보존', desc: '내부·외부 감사 대상 데이터는 감사 완료 후 6개월까지 보존 연장', authority: '감사담당관' }, + { rule: '재난·사고 보존', desc: '해양 사고 관련 데이터는 사고 조사 종결 시까지 보존', authority: '안전관리관' }, + { rule: '정보공개 청구', desc: '정보공개 청구 접수된 데이터는 처리 완료 시까지 보존', authority: '정보공개담당관' }, +]; + +// ─── 파기 감사 대장 ────────────────── +const DISPOSAL_AUDIT_LOG = [ + { id: 'DSP-2026-012', date: '2026-04-10', target: 'AI 학습 임시 데이터 (배치 #B-0392)', type: 'AI 학습 데이터', method: '완전 삭제', volume: '48.2GB', operator: '정해진', approver: '김영수', result: '완료' }, + { id: 'DSP-2026-011', date: '2026-04-08', target: 'CCTV 영상 2026-03월분 (미채택)', type: '영상 데이터', method: '완전 삭제', volume: '2.8TB', operator: '시스템', approver: '김영수', result: '완료' }, + { id: 'DSP-2026-010', date: '2026-04-05', target: '시스템 접근 로그 (2021-Q1)', type: '감사 로그', method: '완전 삭제', volume: '12.5GB', operator: '시스템', approver: '김영수', result: '완료' }, + { id: 'DSP-2026-009', date: '2026-04-03', target: 'AI 학습 임시 데이터 (배치 #B-0391)', type: 'AI 학습 데이터', method: '암호화 키 폐기', volume: '51.7GB', operator: '정해진', approver: '김영수', result: '완료' }, + { id: 'DSP-2026-008', date: '2026-04-01', target: 'AIS 위치 로그 (2021-03)', type: '운항 데이터', method: '완전 삭제', volume: '180GB', operator: '시스템', approver: '이상호', result: '완료' }, + { id: 'DSP-2026-007', date: '2026-03-28', target: '이동식 매체 (USB-0021~0025)', type: '물리 매체', method: '물리적 파기', volume: '5개', operator: '박민수', approver: '김영수', result: '완료' }, +]; + +// ─── 보관 구조 요약 ────────────────── +const STORAGE_ARCHITECTURE = [ + { tier: '운영 스토리지', desc: '실시간 조회·분석 대상 (최근 1년)', tech: 'PostgreSQL + TimescaleDB', encryption: 'TDE (AES-256)', backup: '일일 증분 + 주간 전체', icon: Database }, + { tier: '아카이브 스토리지', desc: '장기 보관 대상 (1~10년)', tech: 'S3 Compatible (Glacier 등급)', encryption: 'SSE-KMS', backup: '월간 무결성 검증', icon: Archive }, + { tier: '백업 스토리지', desc: '재해 복구용 (이중화)', tech: '원격지 NAS + 테이프', encryption: 'AES-256', backup: '분기별 복구 테스트', icon: Lock }, + { tier: '파기 대기 영역', desc: 'soft-delete 후 파기 승인 대기', tech: '격리 스토리지 (접근 제한)', encryption: 'AES-256', backup: '미백업 (파기 예정)', icon: Trash2 }, +]; + +export function DataRetentionPolicy() { + const [tab, setTab] = useState('overview'); + + return ( + + + + {/* 탭 */} +
+ {([ + { key: 'overview' as Tab, icon: Eye, label: '보관 현황' }, + { key: 'retention' as Tab, icon: CalendarClock, label: '유형별 보관기간' }, + { key: 'disposal' as Tab, icon: Trash2, label: '파기 절차' }, + { key: 'exception' as Tab, icon: ShieldCheck, label: '예외·연장' }, + { key: 'audit' as Tab, icon: FileText, label: '파기 감사 대장' }, + ]).map(t => ( + + ))} +
+ + {/* ── ① 보관 현황 ── */} + {tab === 'overview' && ( +
+
+ {RETENTION_KPI.map(k => ( +
+ +
+
{k.value}
+
{k.label}
+
+
+ ))} +
+ + {/* 보관 구조 */} + +
+ + 전체 보관 구조 (4-Tier) +
+
+ {STORAGE_ARCHITECTURE.map(s => ( +
+
+ + {s.tier} +
+

{s.desc}

+
+ {[ + ['기술', s.tech], + ['암호화', s.encryption], + ['백업', s.backup], + ].map(([k, v]) => ( +
+ {k} + {v} +
+ ))} +
+
+ ))} +
+
+ + {/* 정책 준수 현황 */} +
+ +
정책 준수 점검
+
+ {[ + ['유형별 보관기간 기준표', '6/6종 수립 완료', '완료'], + ['파기 방식 정의', '4/4 방식 적용', '완료'], + ['파기 승인 절차', '4단계 절차 운영 중', '완료'], + ['보존 연장 예외 관리', '3건 관리 중 (1건 해제 예정)', '정상'], + ['백업 데이터 동시 파기', '파기 시 백업 포함 확인', '완료'], + ['파기 감사 대장', '12건 기록 (금월)', '완료'], + ['CCTV 30일 보관 준수', '미채택 영상 30일 초과 1건', '주의'], + ].map(([k, v, s]) => ( +
+ {s === '완료' || s === '정상' ? : } + {k} + {v} +
+ ))} +
+
+ +
최근 파기 이력
+
+ {DISPOSAL_AUDIT_LOG.slice(0, 6).map(d => ( +
+ {d.date} + {d.target} + {d.method} + {d.volume} +
+ ))} +
+
+
+
+ )} + + {/* ── ② 유형별 보관기간 ── */} + {tab === 'retention' && ( +
+ +
+ + 데이터 유형별 보관기간 기준표 + {RETENTION_TABLE.length}종 관리 +
+ + + + + + + + + + + + + + {RETENTION_TABLE.map(r => ( + + + + + + + + + + ))} + +
데이터 유형분류법적 근거보관기간저장 형식연간 용량상태
{r.type}{r.category}{r.basis}{r.period}{r.format}{r.volume}{r.status}
+
+ + +
관련 법령 요약
+
+ {[ + { law: '해사안전법 시행규칙', article: '제42조', content: 'AIS 장치 기록 보존 의무 (5년)' }, + { law: '해양경비법', article: '제18조', content: '단속 기록 작성·보존 의무' }, + { law: '공공기록물 관리에 관한 법률', article: '제19조', content: '기록물 보존기간 준수 의무' }, + { law: '개인정보보호법', article: '제21조', content: '목적 달성 후 지체 없이 파기' }, + { law: '정보통신망법', article: '제45조', content: '접속기록 5년 보관 의무' }, + { law: '형사소송법', article: '제198조', content: '수사기록 보존 의무' }, + ].map(l => ( +
+
{l.law}
+ {l.article} +
{l.content}
+
+ ))} +
+
+
+ )} + + {/* ── ③ 파기 절차 ── */} + {tab === 'disposal' && ( +
+ {/* 파기 승인 워크플로우 */} + +
+ + 파기 승인 절차 (4단계) +
+
+ {DISPOSAL_WORKFLOW.map((s, i) => ( +
+ {i < DISPOSAL_WORKFLOW.length - 1 && ( + + )} +
+
+ + {s.phase} +
+
{s.responsible}
+
    + {s.actions.map(a => ( +
  • + + {a} +
  • + ))} +
+
+
+ ))} +
+
+ + {/* 파기 방식 */} + +
+ + 파기 방식 정의 +
+ + + + + + + + + + + + + {DISPOSAL_METHODS.map(m => ( + + + + + + + + + ))} + +
파기 방식설명적용 대상암호화복구 가능성상태
{m.method}{m.desc}{m.target}{m.encryption}{m.recovery}{m.status}
+
+
+ )} + + {/* ── ④ 예외·연장 ── */} + {tab === 'exception' && ( +
+ +
+ + 보존 연장 예외 현황 + {EXCEPTIONS.filter(e => e.status === '연장 중').length}건 연장 중 +
+ + + + + + + + + + + + + + {EXCEPTIONS.map(e => ( + + + + + + + + + + ))} + +
예외 ID대상 데이터사유원래 만료연장 기한승인자상태
{e.id}{e.dataType}{e.reason}{e.originalExpiry}{e.extendedTo}{e.approver}{e.status}
+
+ + +
+ + 보존 연장 사유 유형 +
+
+ {EXCEPTION_RULES.map(r => ( +
+ +
+
{r.rule}
+
{r.desc}
+
+ {r.authority} +
+ ))} +
+
+
+ )} + + {/* ── ⑤ 파기 감사 대장 ── */} + {tab === 'audit' && ( + +
+ + 파기 감사 대장 + {DISPOSAL_AUDIT_LOG.length}건 +
+ + + + + + + + + + + + + + + + {DISPOSAL_AUDIT_LOG.map(d => ( + + + + + + + + + + + + ))} + +
파기 ID일시파기 대상유형방식용량수행자승인자결과
{d.id}{d.date}{d.target}{d.type}{d.method}{d.volume}{d.operator}{d.approver}{d.result}
+
+ )} +
+ ); +} diff --git a/frontend/src/features/admin/index.ts b/frontend/src/features/admin/index.ts index 9eb51ae..d7386dd 100644 --- a/frontend/src/features/admin/index.ts +++ b/frontend/src/features/admin/index.ts @@ -5,3 +5,4 @@ export { AdminPanel } from './AdminPanel'; export { DataHub } from './DataHub'; export { AISecurityPage } from './AISecurityPage'; export { AIAgentSecurityPage } from './AIAgentSecurityPage'; +export { DataRetentionPolicy } from './DataRetentionPolicy'; diff --git a/frontend/src/lib/i18n/locales/en/common.json b/frontend/src/lib/i18n/locales/en/common.json index 1fce46f..9ec2062 100644 --- a/frontend/src/lib/i18n/locales/en/common.json +++ b/frontend/src/lib/i18n/locales/en/common.json @@ -36,7 +36,8 @@ "accessLogs": "Access Logs", "loginHistory": "Login History", "aiSecurity": "AI Security", - "aiAgentSecurity": "AI Agent Security" + "aiAgentSecurity": "AI Agent Security", + "dataRetentionPolicy": "Data Retention" }, "status": { "active": "Active", diff --git a/frontend/src/lib/i18n/locales/ko/common.json b/frontend/src/lib/i18n/locales/ko/common.json index f8b7de0..2ac6039 100644 --- a/frontend/src/lib/i18n/locales/ko/common.json +++ b/frontend/src/lib/i18n/locales/ko/common.json @@ -36,7 +36,8 @@ "accessLogs": "접근 이력", "loginHistory": "로그인 이력", "aiSecurity": "AI 보안", - "aiAgentSecurity": "AI Agent 보안" + "aiAgentSecurity": "AI Agent 보안", + "dataRetentionPolicy": "데이터 보관·파기" }, "status": { "active": "활성",