IoT, 백엔드, 프론트가 같은 API·통신 인터페이스를 기준으로 개발하기 위한 문서입니다.
스키마가 바뀔 때는 database.md와 함께 수정한 뒤 PR을 올립니다.
이 문서는 통신 계약(MQTT · 인증 · REST)만 다룹니다. DB 테이블(§1–§12)은 database.md — 아래 본문은 §13부터입니다.
팀 분담 전체: CONTRIBUTING.md
| 영역 | 주 담당 | 협업 |
|---|---|---|
§13 MQTT devices/register |
윤아령 (IoT) | 강유진 (Mosquitto·인증서), 최지현 (mTLS) |
§14 JWT · /auth/* |
박소정 (Backend) | 박대호 (프론트 로그인) |
| §15 REST (기기·CVE·설정·대시보드) | 박소정 (Backend) | 박대호 (프론트 연동) |
§15 스캔 (/scans, /network-scans) |
박소정 (Backend) | 최지현 (Trivy·네트워크 스캔) |
§15 AI 분석 (/cves/{id}/analysis) |
박소정 (Backend API) | 박대호 (ML·LLM 모델·UI 스펙) |
| SBOM ingest · S3 upload | 박소정 (Backend) | 윤아령 (Collector) |
WireGuard /devices/{id}/vpn/join |
박소정 (Backend) | 윤아령 (Collector), 강유진 (EC2·SG) |
| 섹션 | 내용 | 주 담당 |
|---|---|---|
| §13 | MQTT 기기 등록 | 윤아령 |
| §14 | 인증 (JWT) | 박소정 |
| §15 | REST API 엔드포인트 | 박소정 |
| §16 | 변경 이력 | — |
상세 OpenAPI는 추후
shared/openapi.yaml로 이전할 수 있습니다.
DB 테이블 정의:database.md· 아키텍처·연결 정보:architecture.md· 배포:deployment.md
인프라 (현재 운영): EC218.183.189.134· DBproject5· APIhttp://18.183.189.134:8000· MQTTmqtts://18.183.189.134:8883(TLS 1.3 mTLS) · topicdevices/register
담당: 윤아령 (IoT) · 협업: 강유진 (브로커·인증서), 최지현 (mTLS)
기기가 브로커에 접속해 등록 전용 토픽으로 JSON을 발행하면, FastAPI가 동일 토픽을 구독해 devices upsert 및 last_seen_at 갱신을 수행합니다.
SBOM은 MQTT가 아닌 HTTPS로 별도 전송합니다 (§6).
전송: TLS 1.3 mTLS, 포트 8883 (mqtts://)
| 항목 | 값 |
|---|---|
| Broker URL | mqtts://<EC2_HOST>:8883 |
| TLS | 최소 TLS 1.3 |
| 인증 | mTLS — CA 서명 클라이언트 인증서 필수 (collector-client) |
| 인증서 배포 | Pi: ca.crt + collector-client.crt/key (CA private key는 Pi에 두지 않음) |
인증서 생성: infra/scripts/generate_mqtt_certs.sh (MQTT_SERVER_IP에 EC2 IP 포함 권장)
| Topic | QoS | 방향 | 설명 |
|---|---|---|---|
devices/register |
1 | Collector → Broker → Backend | 기기 메타데이터 등록·heartbeat |
{
"device_id": "550e8400-e29b-41d4-a716-446655440000",
"customer_id": 1,
"name": "doorlock-rpi-001",
"ip_address": "192.168.0.10",
"model": "Raspberry Pi 4",
"kernel_version": "6.6.31-v8+",
"arch": "aarch64",
"os_info": "Debian GNU/Linux 12 (bookworm)"
}| 필드 | 필수 | 설명 |
|---|---|---|
device_id |
필수 | UUID 권장. 문자열이어도 백엔드가 결정적 UUID5로 변환 |
customer_id |
선택 | 고객사. 없으면 DEFAULT_DEVICE_CUSTOMER_ID(기본 1)부터 시작해 customers에 없는 번호를 +1씩 찾아 할당(기존 기기는 유지). legacy user_id도 동일 의미로 허용 |
name, ip_address, model, kernel_version, arch, os_info |
선택 | devices 테이블 메타데이터 |
created_at,updated_at,last_seen_at은 서버가 기록합니다.
- Backend는 기동 시
devices/register구독 device_id로devicesINSERT 또는 UPDATE (last_seen_at갱신)
담당: 박소정 (Backend) · 협업: 박대호 (Frontend)
- 방식: JWT (Bearer)
- 로그인:
POST /auth/login—OAuth2PasswordRequestForm(username=email,password), 응답{ access_token, token_type } - 회원가입:
POST /auth/register—{ email, password } - 내 정보:
GET /auth/me(Bearer 필요) - 토큰 갱신 엔드포인트:
(TBD)— 현재 만료(30분) 시 재로그인 필요
담당: 박소정 (Backend) · 스캔 API 협업: 최지현 · AI 분석·프론트: 박대호
Base URL: 버전 프리픽스 없이 루트에 바로 노출 (http://<HOST>:8000/devices 등). 지금은 프로젝트 초기 단계라 /api/v1 같은 버전 프리픽스를 붙이지 않기로 했고, 필요해지면 라우터 prefix만 바꾸면 되므로 나중에 추가해도 무비용입니다.
2026-08-27부로 읽기(GET) 엔드포인트도 전부 관리자 JWT(Bearer)가 필요합니다 — 대시보드가 로그인 없이도 조회 가능해야 한다는 초기 방침을 프론트 로그인 플로우 완성 이후 재검토해 강화했습니다(§16, §TODO 참고). Collector가 호출하는 /ingest/sbom, /api/v1/sbom/upload/*는 JWT 대신 기기별 API 키(TOFU, 아래 참고)로 인증합니다. 생성/수정/삭제는 계속 관리자 JWT(Bearer)가 필요합니다. 상세 요청/응답 필드는 각 라우터의 Pydantic 스키마(backend/app/schemas/) 참고.
2026-08-28부로 기기(devices)를 다루는 모든 엔드포인트가 호출한 관리자 계정의 customer_id로 자동 범위 제한됩니다(계정별 기기 격리) — 로그인한 관리자와 다른 고객사 소속 기기는 존재해도 404로 응답합니다. 대상: GET/PATCH/DELETE /devices/{id}, /devices/{id}/cves*, /devices/{id}/risk-summary, /devices/{id}/scans*, /scans/{scan_id}, /devices/{id}/network-scans, /devices/{id}/report, /dashboard/*, /cves/{id}/devices, /cves/top. 관리자 계정에 customer_id가 없으면(NULL) 기기 목록·/cves/top은 빈 배열을 반환합니다. CVE 카탈로그 자체(/cves, /cves/{id})는 기기와 무관해 범위 제한 대상이 아닙니다.
| 메서드 | 경로 | 설명 | 인증 |
|---|---|---|---|
| POST | /auth/register |
관리자 회원가입 | - |
| POST | /auth/login |
로그인, JWT 발급 | - |
| GET | /auth/me |
내 정보 조회 | JWT |
| PATCH | /auth/me |
이메일 변경 | JWT |
| POST | /auth/me/password |
비밀번호 변경 (current_password, new_password) |
JWT |
| GET | /settings |
내 설정 조회 — 저장된 값이 없으면 기본값(auto_scan=true, scan_interval=24, risk_threshold_*=9/7/4) 반환 |
JWT |
| PATCH | /settings |
설정 변경분만 저장 (첫 저장 시 행 생성) | JWT |
| GET | /scan-schedule |
스캔 주기 조회 — 시스템 전체 공통(§2.1), 저장된 값 없으면 기본값(scan_interval=24) 반환 |
JWT |
| PUT | /scan-schedule |
스캔 주기 변경 (scan_interval은 24/168/720만 허용) — 저장 즉시 재스캔 스케줄러가 새 주기로 재계산 |
JWT |
| GET | /devices |
기기 목록/검색 (name, model, ip_address, package, cve, skip, limit) — 항상 호출자 소속 고객사 기기만 (자유 customer_id 쿼리 파라미터는 2026-08-28 제거) |
JWT |
| GET | /devices/{device_id} |
기기 상세 (패키지 수, 위험도 포함) | JWT |
| POST | /devices |
기기 수동 등록 (Settings "기기 등록" 모달). customer_id 미지정 시 호출자 소속 고객사로 자동 지정 |
JWT |
| PATCH | /devices/{device_id} |
기기 정보 수정 | JWT |
| DELETE | /devices/{device_id} |
기기 삭제 — 소프트 삭제(deleted_at만 설정). 스캔 이력(scan_history/devices_cves/packages/sbom)은 FK 보존을 위해 지우지 않으며, 목록/상세/스캔 관련 API와 자동 재스캔 대상에서 제외된다 |
JWT |
| GET | /cves |
CVE 목록/검색 (q, severity) |
JWT |
| GET | /cves/top |
미해결 CVE 중 위험도 Top-N (대시보드 "긴급 CVE Top5") — 호출자 소속 고객사 기기 기준 | JWT |
| GET | /cves/{cve_id} |
CVE 상세 (CWE, 참고 링크 전체 목록 포함) | JWT |
| GET | /cves/{cve_id}/devices |
이 CVE에 현재 걸려있는 기기 목록 (기기별 최신 탐지 1건만) | JWT |
| GET | /devices/{device_id}/cves |
기기별 탐지된 CVE 이력 (status 필터). 같은 기기를 여러 번 스캔하면 같은 CVE가 스캔 횟수만큼 중복될 수 있음 (이력으로서는 정상) |
JWT |
| GET | /devices/{device_id}/risk-summary |
기기별 정제된 현재 위험 상태 — CVE별 최신 탐지 1건 + 심각도별 개수·최고 CVSS 점수 + 출처 컨테이너(source_container, 기기가 여러 컨테이너로 구성된 경우 어느 것 CVE인지). AI 위험도 융합(Trivy+네트워크 스캔)이나 프론트 상세페이지에 넘길 때는 /cves가 아니라 이 엔드포인트를 쓴다 |
JWT |
| GET | /devices/{device_id}/cves/{cve_id}/analysis |
기기 1대 x CVE 1건의 상세 진단 — CVE 기본정보+탐지정보+AI 분석(services/ai_analysis.py, 2-Stage 앙상블 모델 + Groq LLM 실연동, CVE별 분석 텍스트 DB 캐싱)을 합쳐서 반환. risk_score/patch_score는 0 |
JWT |
| PATCH | /devices/{device_id}/cves/{id} |
기기-CVE 상태 변경 (해결 처리 등) | JWT |
| GET | /devices/{device_id}/report |
기기 취약점 진단 결과 PDF 다운로드 (report_type=customer|audit, 기본값 customer). 활성 CVE 전체를 모아 심각도별 요약+CVE별 상세로 생성 |
JWT |
| GET | /devices/{device_id}/scans |
스캔 이력 목록 | JWT |
| POST | /devices/{device_id}/scans |
스캔 요청 — scan_history에 status=pending으로 즉시 기록 후 응답, 실제 Syft/Trivy 실행은 백그라운드로 진행되어 완료되면 같은 행이 completed/failed로 갱신된다. sbom_id를 생략하면 기기의 최신 SBOM을 자동으로 찾아 스캔 |
JWT |
| GET | /scans/{scan_id} |
스캔 이력 상세 — 폴링해서 status 변화를 확인 |
JWT |
| GET | /devices/{device_id}/network-scans |
네트워크 스캔 결과 — RustScan(포트탐지)→Nmap(서비스식별)→Nuclei(취약점매칭) (severity 필터) |
JWT |
| POST | /devices/{device_id}/network-scans |
네트워크 스캔 요청 — scan_history에 scan_type=network, status=pending으로 즉시 기록 후 응답, 실제 RustScan→Nmap→Nuclei 실행은 백그라운드로 진행되어 완료되면 같은 행이 completed/failed로 갱신된다 |
JWT |
| GET | /dashboard/vulnerability-trend |
대시보드 일자별 취약점 그래프 — 최근 N일(days, 기본 7) 탐지/해결 건수 |
JWT |
| GET | /dashboard/vulnerability-by-device |
대시보드 기기별 누적 취약점 그래프 — 미해결 취약점 많은 기기 상위 N개(limit, 기본 6) + 전체 미해결 합계(total_unresolved, 대시보드 "미해결 CVE 합계" 카드용) |
JWT |
| GET | /dashboard/scan-status |
대시보드 "최근 24시간 스캔 상태" 카드용 — 최근 24시간 내 가장 최근 스캔의 status(success/failed/running/none)와 scanned_at |
JWT |
| GET | /notifications |
내 알림 목록 (unread_only, limit 기본 20) — total/unread_count/items 반환 |
JWT |
| PATCH | /notifications/{id}/read |
알림 1건 읽음 처리 | JWT |
| PATCH | /notifications/read-all |
내 안 읽은 알림 전체 읽음 처리 | JWT |
| POST | /ingest/sbom |
SBOM 업로드. device_id가 없으면 최소 정보로 선등록 후 저장. sbom_format=cyclonedx-json이면 components[]를 packages로 파싱, 그 외 포맷은 원본만 저장. source_container(optional) — 기기가 여러 컨테이너로 구성된 경우 이 SBOM의 출처. 스캔은 자동으로 트리거되지 않음 — 저장 후 별도로 POST /devices/{device_id}/scans 호출 필요 |
기기 API 키(아래 참고) |
/ingest/sbom, /api/v1/sbom/upload/* 인증 — 기기별 API 키 (Trust-On-First-Use): Collector는 JWT를 쓸 수 없어서, 대신 요청 헤더 X-Device-Api-Key로 인증합니다(backend/app/services/device_registration.py의 verify_or_bind_device_api_key). 헤더가 없으면 기존처럼 통과합니다(구버전 Collector와 하위 호환). 헤더가 있으면 그 기기(device_id)가 처음 보내는 값을 그대로 등록하고, 이후 요청부터는 그 값과 일치해야만 통과합니다 — 한 번 등록되면 그 키를 모르는 제3자는 같은 device_id로 가짜 SBOM을 올릴 수 없습니다. Collector가 실제로 키를 만들어서 보내야 보호가 시작됩니다 — 지금은 Collector 쪽에서 아직 X-Device-Api-Key를 안 보내므로, 기기별로 임의의 랜덤 문자열을 한 번 만들어 로컬에 저장하고 매 요청에 그대로 실어 보내는 부분이 남아있습니다 — 담당: 박소정 · 윤아령 · 최지현
아래 이력의
§번호는 작성 시점 기준입니다. 이 문서와database.md가 하나의 파일이던 시절에 매겨진 것으로,§1–§12는 현재database.md의 테이블 섹션에 해당합니다.
| 날짜 | 작성자 | 내용 |
|---|---|---|
| 2026-05-27 | 박소정 | 초안 템플릿 생성 |
| 2026-06-24 | 박소정 | ER 다이어그램 기반 DB 스키마 섹션 추가 (users~network_scans) |
| 2026-06-26 | 박소정 | 최종 ERD 기준 스키마 수정 (scan_history, package_cves, notifications 추가) |
| 2026-06-30 | 박소정 | devices_cves에 scan_history_id 필드 추가 |
| 2026-07-11 | 박소정 | customers 테이블 추가, devices.user_id → customer_id 변경, 각 테이블 역할 설명 추가 |
| 2026-07-11 | 윤아령 | MQTT devices/register 기기 등록 계약 추가 |
| 2026-07-22 | 윤아령 | MQTT 등록에서 SBOM 분리 — SBOM은 HTTPS(§6), MQTT는 기기 메타데이터만 |
| 2026-07-23 | 윤아령 | MQTT TLS 1.3 mTLS, 포트 8883 (mqtts://) 전환 |
| 2026-08-02 | 박소정 | REST API(§15) 추가 — auth/devices/cves/scan/ingest 라우터 구현, base URL 미버전화 결정 |
| 2026-08-06 | 최지현 | §8 cves에 cwe_ids/references/last_modified_date, §10 devices_cves에 installed_path 추가. §15에 GET /devices/{id}/risk-summary, GET /cves/{id}/devices 추가. POST /devices/{id}/scans Trivy 백그라운드 실행 |
| 2026-08-19 | 박소정 | §15에 GET /devices/{id}/report(취약점 PDF 리포트, customer/audit) 추가 |
| 2026-08-27 | 박소정 | §15 GET 엔드포인트 전체 JWT 인증 필수화(무인증 방침 폐기, TODO 항목 해결). §2 설정 API(GET/PATCH /settings), 기기 상세 CVE 분석 API(GET /devices/{id}/cves/{cve_id}/analysis), 대시보드 그래프 API 2종, 프로필 수정/비밀번호 변경 API(PATCH·POST /auth/me*), 기기 검색 package/cve 필터 추가 |
| 2026-08-28 | 박소정 | 계정별 기기 격리(멀티테넌시) 추가 — §1 users에 customer_id 추가, §4 devices 설명 보강, §15 기기 관련 엔드포인트 전체를 호출자 customer_id로 스코핑(타 고객사 기기는 404). GET /devices 자유 customer_id 쿼리 파라미터 제거, POST /devices customer_id 자동 지정으로 변경 |
| 2026-08-29 | 박소정 | §15 GET /dashboard/scan-status 신규 추가, GET /dashboard/vulnerability-by-device에 total_unresolved 필드 추가(대시보드 하드코딩 값 실데이터 연결). 기기 리포트 PDF 출력·삭제 버튼을 목록에서 상세페이지로 이동(팀 피드백 반영) |
| 2026-09-02 | 박소정 | §4 devices에 deleted_at 추가, §15 DELETE /devices/{id}를 소프트 삭제로 변경 — 스캔 이력이 있는 기기 삭제 시 발생하던 500 에러 수정 |
| 2026-09-01 | 최지현 | 네트워크 스캔(RustScan+Nmap+Nuclei) — §11 network_scans, §15 POST /devices/{id}/network-scans |
| 2026-09-02 | 최지현 | §2.1 scan_schedule, §15 GET/PUT /scan-schedule — Trivy·네트워크 재스캔 스케줄러 연동 |
| 2026-09-03 | 박소정 | §8 cves AI 피처·LLM 필드, §15 CVE analysis 실연동(협업: 박대호 ML·LLM), notifications API |
| 2026-09-04 | 최지현 | §6 sbom source_container, Trivy 컨테이너별 재스캔 |
-
/ingest/sbom·/api/v1/sbom/upload/*인증 — 백엔드는 기기별 API 키(TOFU, §15 참고)로 구현 완료 - Collector가 기기별 API 키를 생성·저장하고
X-Device-Api-Key헤더로 실제로 보내는 부분 — 박소정 · 윤아령 · 최지현