라이브 읽기 전용 데모
포털을 공개 라이브 데모로 운영할 수 있습니다. 누구나 시드된 데모 계정으로 로그인해 실제 프로젝트·스캔·취약점·라이선스·SBOM·보고서를 둘러볼 수 있지만, 모든 쓰기 작업은 비활성화되며 데이터셋은 매일 밤 깨끗한 상태로 초기화됩니다.
호스팅 데모 바로 써보기
**trusca-demo.duckdns.org**에서 운영 중인 데모를 바로 이용할 수 있습니다.
- 로그인: 시드된 데모 계정으로 접속합니다 —
dev@demo.trustedoss.dev(또는frontend-admin@demo.trustedoss.dev등*-admin@demo.trustedoss.dev계정), 비밀번호는DemoTest2026!입니다. 모든 시드 계정이 같은 비밀번호를 쓰고, 로그인 화면에는 자격 증명을 한 번에 채우는 버튼이 있습니다. - 둘러보기: 실제 프로젝트·컴포넌트·CVE·라이선스·SBOM·보고서를 볼 수 있습니다.
생성·수정·삭제는 모두
403으로 막힙니다. 읽기 전용이기 때문입니다. - SBOM 업로드: CycloneDX 파일(예: BomLens로
만든 SBOM)을 Demo Sandbox 프로젝트에 올리면 소스 없이도 TRUSCA가 라이선스와
CVE를 매칭해 보여줍니다. 여기서 10MB 이하 소스 스캔도 직접 돌릴 수 있습니다.
Demo Sandbox는
dev@demo.trustedoss.dev로 로그인해야 보입니다. - 주의: 데이터셋은 샌드박스를 포함해 매일 밤 지워지고 다시 시드됩니다. 올린 내용은 임시로 남고 다른 방문자에게도 보이므로, 사내·기밀 SBOM은 올리지 마세요.
이 문서의 나머지는 같은 구성 요소로 직접 읽기 전용 데모를 세우는 방법을 설명합니다.
두 가지 독립적인 구성 요소로 이루어집니다.
DEMO_READ_ONLY읽기 전용 모드 (모든 배포).- 일일 자동 리셋 (데모 호스트의 systemd 타이머가 리셋 스크립트를 백엔드 컨테이너 안에서 실행).
1. 읽기 전용 모드 (DEMO_READ_ONLY)
백엔드 환경 변수를 설정합니다.
DEMO_READ_ONLY=true
(허용되는 truthy 값: 1, true, yes, on — 대소문자 무관. 요청 시점에
읽으므로, 값을 바꿀 때 재빌드 없이 프로세스 재시작만 하면 됩니다.)
활성화되면 단일 미들웨어가 API 전체에 정책을 강제하므로, 개별 엔드포인트가 정책을 우회할 수 없습니다.
- 읽기는 항상 통과 —
GET·HEAD·OPTIONS(마지막은 CORS preflight 유지용). - 쓰기는 기본 차단 — 모든
POST·PUT·PATCH·DELETE(및 기타 메서드)는 허용 목록(allow-list) 에 없으면 거부됩니다. - 허용 목록은 데모에 꼭 필요한 인증 흐름뿐입니다:
POST /auth/login,POST /auth/refresh,POST /auth/logout. 그 외 — 회원가입, 비밀번호 재설정· 변경, 프로젝트 생성, 스캔 트리거, 승인, 설정, 웹훅, 파일 업로드 — 는 모두 차단됩니다.
차단된 요청은 Content-Type: application/problem+json 의 RFC 7807 403 을
받습니다.
{
"type": "urn:trustedoss:problem:demo-read-only",
"title": "Read-only demo",
"status": 403,
"detail": "This is a read-only live demo. Creating, updating, or deleting data is disabled. …",
"instance": "/v1/projects",
"demo_read_only": true
}
우회 방지
이 가드는 차단 목록이 아니라 허용 목록입니다. 나중에 추가되는 변이
엔드포인트는 별도 수정 없이 자동으로 차단됩니다. 허용 목록 비교 전에 경로를
정규화(역슬래시 변환, ./.. 세그먼트 해석, 끝 슬래시 제거)하므로
/v1/projects/../auth/login 같은 트래버설로 쓰기 경로를 허용 목록에 끼워 넣을 수
없습니다. HTTP 메서드는 대소문자 무관으로 비교하고 허용 목록은 (메서드, 경로)
쌍을 키로 쓰므로, 비정상 메서드가 허용된 경로에 편승할 수 없습니다.
프론트엔드 동작
SPA는 공개 GET /health 응답({"status":"ok","demo_read_only":true})에서
플래그를 읽어,
- 앱 상단에 슬림한 "읽기 전용 데모" 배너 를 표시하고,
- 쓰기 액션(예: "스캔", "프로젝트 등록" 버튼)을 비활성화하고 이유를 툴팁으로 안내합니다.
실제 경계는 미들웨어이며, UI 게이팅은 막다른 클릭을 줄이는 보조 수단입니다.
2. 일일 자동 리셋 (systemd 타이머)
Hetzner 데모 호스트는 리셋을 systemd 타이머로 실행합니다. 유닛 파일은
deploy/hetzner/ 에 있습니다.
-
trustedoss-demo-reset.service— 리셋 스크립트를 실행 중인 백엔드 컨테이너 안에서 실행하는oneshot유닛입니다.ExecStart=/usr/local/bin/docker-compose -f docker-compose.yml \exec -T -e APP_ENV=demo backend python -m scripts.reset_demo컨테이너 안에서 실행하면 HTTP
DEMO_READ_ONLY가드를 우회합니다(스크립트가 Postgres에 직접 접근). 스크립트 자체의APP_ENV허용 목록이 안전 경계입니다. -
trustedoss-demo-reset.timer— 서비스를 매일 03:17 UTC 에 실행합니다 (OnCalendar=*-*-* 03:17:00 UTC,Persistent=true이므로 호스트가 내려가 있어 놓친 리셋은 다음 부팅 때 한 번 실행됩니다).
리셋(apps/backend/scripts/reset_demo.py)은,
- 데모 데이터셋만 삭제 —
demo-org조직(FK cascade로 팀 → 프로젝트 → 스캔 → finding 제거)과 데모 사용자(demo-org 멤버십 기준 — demo-org 에만 속한 사용자만 삭제; cascade로 멤버십·알림 제거). 전체 truncate가 아니므로 다른 조직에도 속한 다른 테넌트의 데이터는 절대 건드리지 않습니다. - 멱등한
seed_demo._seed로 재시드 하므로 데이터셋 형태가 일반 시드와 단일 소스로 일치합니다. APP_ENV가dev또는demo가 아니면 실행을 거부합니다(프로덕션 데이터베이스에는 절대 실행되지 않음).
호스트에서 타이머를 설치하고 활성화합니다.
sudo cp deploy/hetzner/trustedoss-demo-reset.service /etc/systemd/system/
sudo cp deploy/hetzner/trustedoss-demo-reset.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now trustedoss-demo-reset.timer
공개 데모 자격 증명이 매일 밤 바뀌지 않도록 호스트 .env 의
DEMO_SUPER_ADMIN_PASSWORD 를 알려진 값으로 설정하세요. 미설정 시 재시드가 매일
밤 무작위 비밀번호를 생성하지만 평문을 로그에 출력하지 않으므로 새 자격 증명을
알 수 없습니다.
공개 데모가 어디서 운영되는지는 데모 SaaS 호스팅 을 참고하세요.
타이머를 기다리지 않고 즉시 리셋을 실행합니다.
sudo systemctl start trustedoss-demo-reset.service
# 또는 내부 명령을 직접 실행:
docker-compose -f docker-compose.yml exec -T -e APP_ENV=demo backend python -m scripts.reset_demo
로컬 읽기 전용 데모 (Docker Compose)
읽기 전용 모드는 모든 배포에서 동작합니다. 로컬 읽기 전용 인스턴스는 .env 에
DEMO_READ_ONLY=true 를 추가하고 백엔드를 재시작하면 됩니다. systemd 타이머는
데모 호스트용이며, 로컬에서는 깨끗한 데이터셋이 필요할 때마다 백엔드 컨테이너
안에서 리셋을 다시 실행하세요.
docker-compose -f docker-compose.dev.yml exec -e APP_ENV=demo backend \
python -m scripts.reset_demo