IoT, 백엔드, 프론트, 보안이 같은 DB 스키마를 기준으로 개발하기 위한 문서입니다.
스키마 변경 시 이 파일과 api.md를 함께 수정한 뒤 PR을 올립니다.
REST API · MQTT:
api.md· 배포:deployment.md
팀 역할:CONTRIBUTING.md
| 영역 | 주 담당 | 협업 |
|---|---|---|
| 스키마·ERD·마이그레이션 | 박소정 (Backend) | 전원 (해당 API/기능 담당자 리뷰) |
devices · WireGuard 필드 |
박소정 | 윤아령 (Collector) |
sbom · packages |
박소정 | 윤아령, 최지현 (Trivy 연동) |
cves · AI 피처·LLM 캐시 |
박소정 | 박대호 (ML·LLM), 최지현 (CVE 소스) |
network_scans |
박소정 | 최지현 |
관계 요약: customers → users · devices · settings · notifications · sbom → packages → package_cves ← cves · devices_cves · scan_history · network_scans · scan_schedule
플랫폼 관리자(제조사 담당자) 계정 정보를 저장합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
id |
bigint (PK) | 사용자 ID |
email |
varchar(255) | 이메일 |
password_hash |
varchar(255) | 해시된 비밀번호 |
customer_id |
bigint (FK, null 허용) | customers 참조 — 이 계정이 속한 고객사. /devices 등 기기 조회 범위를 결정(§15). NULL이면 기기 목록이 빈 배열 |
created_at |
timestamp | 생성 시각 |
updated_at |
timestamp | 수정 시각 |
관리자별 자동 스캔 주기, 알림 여부, 위험도 임계값 등 개인 설정을 저장합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
user_id |
bigint (PK/FK) | users 참조 |
auto_scan |
boolean | 자동 스캔 ON/OFF |
scan_interval |
integer | 스캔 주기 |
alert_enabled |
boolean | 알림 ON/OFF |
risk_threshold_critical |
integer | critical 위험도 기준 |
risk_threshold_high |
integer | high 위험도 기준 |
risk_threshold_medium |
integer | medium 위험도 기준 |
updated_at |
timestamp | 설정 수정 시각 |
2026-09-02부로 scan_interval(스캔 주기)은 실제 재스캔 스케줄러가 참조하는 값이 아니게 됐습니다 — 사용자별 settings.scan_interval은 화면에 남아있지만 무시되고, 실제 주기는 아래 scan_schedule(시스템 전체 공통 단일 설정)을 따릅니다.
Trivy·네트워크 주기 재스캔이 실제로 참조하는 시스템 전체 공통 설정. 항상 id=1 한 행만 존재합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
id |
integer (PK) | 항상 1 |
scan_interval |
integer | 재스캔 간격(시간) — 24=매일, 168=매주, 720=매월 |
updated_at |
timestamp | 마지막 변경 시각. 재스캔 주기 계산의 기준(앵커)으로도 쓰임 |
판매된 IoT 기기가 등록되는 고객사(기업) 또는 개인 고객 정보를 저장합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
id |
bigint (PK) | 고객사/개인 ID |
name |
varchar(255) | 이름 |
type |
varchar(20) | 고객 유형 (enterprise/individual) |
contact |
varchar(255) | 연락처 |
created_at |
timestamp | 생성 시각 |
플랫폼에 등록된 모든 IoT 기기의 기본 정보를 저장합니다. customer_id가 NULL이면 아직 판매되지 않은 기기입니다. 2026-08-28부로 이 값이 계정별 기기 조회 범위 제한에도 쓰입니다(§15) — 로그인한 관리자의 customer_id와 다르면 조회되지 않습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
device_id |
uuid (PK) | 전 세계 고유 식별자 |
customer_id |
bigint (FK, null 허용) | customers 참조 (미판매 기기는 NULL) |
name |
varchar(255) | 기기 이름 |
ip_address |
varchar(45) | IP 주소 |
model |
varchar(255) | 모델명 |
kernel_version |
varchar(255) | 커널 버전 |
arch |
varchar(50) | CPU 아키텍처 |
os_info |
text | OS 정보 |
created_at |
timestamp | 생성 시각 |
updated_at |
timestamp | 수정 시각 |
last_seen_at |
timestamp | 마지막 heartbeat |
deleted_at |
timestamp (null 허용) | 소프트 삭제 시각. NULL이면 정상 기기. DELETE /devices/{id}는 이 값만 채우고 실제 row는 지우지 않는다 |
wg_ip_address |
varchar(15) (null 허용) | WireGuard 가상 IP (WG_ENABLED 시 할당, 예: 10.100.0.x) |
wg_public_key |
varchar(64) (null 허용) | WireGuard 클라이언트 공개키 (기기당 1개) |
exposure_level |
varchar(20) (null 허용) | 네트워크 노출 구역 (internal / dmz / internet_facing) |
기기별로 실행된 스캔(Syft/Trivy/RustScan+Nmap+Nuclei)의 실행 시점과 성공/실패 상태를 기록합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
id |
bigint (PK) | 스캔 이력 ID |
device_id |
uuid (FK) | devices 참조 |
scan_type |
varchar(20) | 스캔 종류 |
triggered_by |
varchar(20) | 스캔 실행 주체 |
status |
varchar(20) | 스캔 상태 |
started_at |
timestamp | 스캔 시작 시각 |
finished_at |
timestamp | 스캔 종료 시각 |
result_summary |
text | 스캔 결과 요약 |
Syft로 추출한 기기별 소프트웨어 구성 명세(SBOM) 원본을 저장합니다.
전송: HTTPS -> AWS S3
| 필드 | 타입 | 설명 |
|---|---|---|
id |
bigint (PK) | SBOM ID |
device_id |
uuid (FK) | devices 참조 |
sbom_format |
varchar(20) | 예: cyclonedx-json, spdx-json |
sbom_version |
varchar(20) | SBOM 버전 |
tool_name |
varchar(100) | SBOM 생성 도구명 |
sbom_payload |
jsonb | Syft 출력 본문 |
hash |
varchar(64) | SBOM 해시값 |
collected_at |
timestamp | 수집 시각 |
created_at |
timestamp | 생성 시각 |
source_container |
varchar(255) | 기기가 여러 컨테이너로 구성된 경우 이 SBOM의 출처(Collector --scan-target 값). 레거시 데이터는 NULL |
SBOM에서 추출된 개별 오픈소스 패키지(라이브러리) 목록을 저장합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
id |
bigint (PK) | 패키지 ID |
device_id |
uuid (FK) | devices 참조 |
sbom_id |
bigint (FK) | sbom 참조 |
component_id |
text | 컴포넌트 식별자 |
package_name |
text | 패키지명 |
version |
text | 설치 버전 |
type |
varchar(50) | 패키지 유형 |
purl |
text | Package URL |
supplier |
text | 공급자 |
relationship |
varchar(100) | 의존 관계 |
license |
text | 라이선스 (2026-09-23부로 varchar(255)→text — syft가 SPDX 식별자 대신 라이선스 전문을 넣는 경우가 있어 INSERT 자체가 실패하던 버그 수정) |
NVD, GitHub Advisory 등에서 수집한 CVE(취약점) 원본 정보를 저장합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
id |
bigint (PK) | CVE 레코드 ID |
cve_id |
varchar(50) UNIQUE | CVE 번호 (예: CVE-2024-0001) |
title |
text | 취약점 제목 |
description |
text | 취약점 설명 |
severity |
varchar(20) | critical, high, medium, low |
cvss_score |
decimal(3,1) | CVSS 점수 |
cvss_vector |
varchar(255) | CVSS 벡터 |
published_date |
date | 공개 날짜 |
last_modified_date |
date | 최종 수정일 (Trivy LastModifiedDate) |
primary_url |
text | 참고 링크 (대표 1개) |
cwe_ids |
varchar(50)[] | CWE 식별자 목록 (Trivy CweIDs) |
references |
text[] | 참고 링크 전체 목록 (Trivy References) |
created_at |
timestamp | 생성 시각 |
updated_at |
timestamp | 수정 시각 |
attack_vector |
varchar(20) | AI 모델 입력 피처 — NVD CVSS v3 (NETWORK/ADJACENT_NETWORK/LOCAL/PHYSICAL) |
attack_complexity |
varchar(10) | AI 모델 입력 피처 — NVD CVSS v3 (LOW/HIGH) |
privileges_required |
varchar(10) | AI 모델 입력 피처 — NVD CVSS v3 (NONE/LOW/HIGH) |
user_interaction |
varchar(10) | AI 모델 입력 피처 — NVD CVSS v3 (NONE/REQUIRED) |
confidentiality_impact |
varchar(10) | AI 모델 입력 피처 — NVD CVSS v3 (NONE/LOW/HIGH) |
integrity_impact |
varchar(10) | AI 모델 입력 피처 — NVD CVSS v3 (NONE/LOW/HIGH) |
availability_impact |
varchar(10) | AI 모델 입력 피처 — NVD CVSS v3 (NONE/LOW/HIGH) |
epss_score |
numeric(8,5) | AI 모델 입력 피처 — EPSS(FIRST.org) 악용 가능성 점수(0~1), 매일 갱신됨 |
epss_percentile |
numeric(8,5) | AI 모델 입력 피처 — EPSS 백분위(0~1) |
has_exploit_ref |
boolean | 공개 Exploit 레퍼런스 존재 여부 — NVD references의 Exploit 태그 |
ref_count |
integer | NVD references 개수 |
config_count |
integer | NVD configurations 노드 수 |
nvd_cwe_id |
varchar(50) | NVD 공식 CWE(단일) — cwe_ids(Trivy, 배열)와 별개. AI 모델은 NVD 기준으로 학습돼 추론 시 이 값을 우선 사용 |
ai_features_updated_at |
timestamp | 위 AI 피처들을 NVD/EPSS API로 마지막으로 채운 시각. NULL이면 미채움 — 매일 자동 백필(AI_ENRICH_HOUR_UTC) 대상 |
llm_analysis |
jsonb | Groq LLM(gpt-oss-20b) 분석 텍스트 캐시 — {analysis_summary, attack_types, attack_scenarios, recommended_actions} |
llm_analysis_label |
varchar(10) | llm_analysis를 생성할 당시의 predicted_label — 위험도가 바뀌면 캐시 무효화 판단에 사용 |
llm_analysis_updated_at |
timestamp | llm_analysis 마지막 생성 시각 |
특정 패키지가 일반적으로 어떤 CVE에 영향을 받는지 매핑합니다 (기기와 무관한 범용 연관관계).
| 필드 | 타입 | 설명 |
|---|---|---|
id |
bigint (PK) | 패키지-CVE 연결 ID |
package_id |
bigint (FK) | packages 참조 |
cve_id |
bigint (FK) | cves 참조 |
affected_versions |
text | 영향받는 버전 |
fixed_versions |
text | 수정된 버전 |
created_at |
timestamp | 생성 시각 |
특정 기기에서 실제로 탐지된 CVE 발생 이력을 기록합니다. 어떤 스캔 실행(scan_history)에서 발견됐는지도 함께 남깁니다.
| 필드 | 타입 | 설명 |
|---|---|---|
id |
bigint (PK) | 기기-CVE 연결 ID |
device_id |
uuid (FK) | devices 참조 |
cve_id |
bigint (FK) | cves 참조 |
package_id |
bigint (FK) | packages 참조 |
scan_history_id |
bigint (FK) | scan_history 참조 |
installed_version |
varchar(255) | 설치 버전 |
installed_path |
text | 취약 파일 경로 (Trivy PkgPath, 언어별 패키지에서만 채워짐) |
status |
varchar(20) | 상태 |
detected_at |
timestamp | 탐지 시각 |
resolved_at |
timestamp | 해결 시각 |
source |
varchar(20) | 탐지 출처 |
notes |
text | 비고 |
RustScan(포트탐지) → Nmap(서비스식별) → Nuclei(취약점매칭)로 수행한 기기별 네트워크 포트·노출 스캔 결과를 저장합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
id |
bigint (PK) | 스캔 ID |
device_id |
uuid (FK) | devices 참조 |
scan_type |
varchar(20) | 스캔 종류 |
target |
varchar(255) | 스캔 대상 |
protocol |
varchar(20) | 프로토콜 |
service |
varchar(100) | 서비스명 |
port |
integer | 포트 |
nvt_name |
varchar(255) | Nuclei 템플릿 이름 |
severity |
varchar(20) | 심각도 |
cvss_score |
decimal(3,1) | CVSS 점수 |
description |
text | 설명 |
solution |
text | 해결 방법 |
cve_id |
varchar(50) | CVE 참조가 있는 발견물만 채워짐(null 가능) |
scanned_at |
timestamp | 스캔 시각 |
신규 취약점 발생 시 담당 관리자에게 발송되는 알림 내역을 저장합니다. (고객사에는 발송되지 않음)
2026-09-03부로 실제로 채워집니다 — 기기에서 CVE가 이전 스캔에는 없던 조합으로 처음 탐지되면(services/trivy_runner.py), 그 기기 소유 고객사의 계정들에게 알림 row가 생성됩니다. 재스캔으로 같은 CVE가 다시 잡히는 건 알림을 새로 만들지 않습니다. settings.alert_enabled가 명시적으로 false인 계정만 제외(설정 안 건드린 계정은 기본으로 받음) — 단, 이 값을 켜고 끄는 화면(Settings "알림 설정" 탭)은 PR #56에서 제거되어 현재는 코드로만 존재합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
id |
bigint (PK) | 알림 ID |
user_id |
bigint (FK) | users 참조 |
device_id |
uuid (FK) | devices 참조 |
title |
varchar(255) | 알림 제목 |
message |
text | 알림 내용 |
severity |
varchar(20) | 알림 위험도 |
is_read |
boolean default false | 읽음 여부 |
created_at |
timestamp | 생성 시각 |
