# GUARDiA ITSM — 라이선스 키 발급 및 적용 가이드 > **문서번호**: GUARDIA-LIC-001 > **버전**: 1.0 > **작성일**: 2026-05-28 > **작성자**: GUARDiA 개발팀 > **보안등급**: 내부용 (대외비) — 마스터 키 및 생성 절차는 벤더 내부 한정 --- ## 목차 1. [라이선스 시스템 개요](#1-라이선스-시스템-개요) 2. [에디션별 제한 및 기능](#2-에디션별-제한-및-기능) 3. [마스터 키 생성 및 보관](#3-마스터-키-생성-및-보관) 4. [라이선스 키 발급 (벤더 전용)](#4-라이선스-키-발급-벤더-전용) 5. [라이선스 키 고객 적용 절차](#5-라이선스-키-고객-적용-절차) 6. [라이선스 갱신 절차](#6-라이선스-갱신-절차) 7. [라이선스 상태 모니터링](#7-라이선스-상태-모니터링) 8. [CLI 레퍼런스](#8-cli-레퍼런스) 9. [API 레퍼런스](#9-api-레퍼런스) 10. [문제 해결](#10-문제-해결) --- ## 1. 라이선스 시스템 개요 ### 1.1 보안 설계 GUARDiA 라이선스는 **완전 오프라인 검증** 방식으로, 외부 인터넷 연결 없이 검증된다. ``` 라이선스 키 구조: GRD-{base64url(iv[12B] + ciphertext + gcm_tag[16B] + hmac_sig[8B])} 암호화: AES-256-GCM (iv 12B + GCM tag 16B) 서명: HMAC-SHA256 (앞 8B prefix — 위변조 즉시 탐지) 키 파생: SHA-256(master_key + "guardia-aes-v1") → AES 키 SHA-256(master_key + "guardia-license-hmac-v1") → HMAC 키 ``` **검증 흐름:** ``` 고객 서버가 키 수신 │ ▼ HMAC 서명 검증 (위변조 즉시 차단) │ ▼ AES-256-GCM 복호화 (마스터 키 불일치 시 실패) │ ▼ JSON 페이로드 파싱 (만료일, 에디션, 고객명 등) │ ▼ 만료일 비교 → valid: true / expired: true ``` ### 1.2 라이선스 페이로드 구성 ```json { "license_id": "GRD-A1B2C3", "edition": "ENTERPRISE", "customer": "서울특별시청", "issued_at": "2026-05-28T00:00:00+00:00", "expires_at": "2027-05-28T00:00:00+00:00", "limits": { "max_institutions": -1, "max_users": -1, "max_servers": -1, "features": ["MFA", "LDAP", "PAM", "AI_AGENTS", "VULN_SCAN", "CICD", "ANALYTICS", "FINOPS"] } } ``` --- ## 2. 에디션별 제한 및 기능 | 에디션 | 기관 수 | 사용자 수 | 서버 수 | 활성화 기능 | 대상 | |--------|--------|---------|--------|-----------|------| | **COMMUNITY** | 1 | 10 | 20 | MFA | 소규모 단일 기관, 평가판 | | **STANDARD** | 50 | 200 | 500 | MFA, LDAP, PAM, AI_AGENTS | 중규모 기관·지자체 | | **ENTERPRISE** | 무제한(-1) | 무제한(-1) | 무제한(-1) | 전체 기능 | 광역단체, 대규모 멀티테넌트 | ### 기능별 에디션 요구사항 | 기능 코드 | 기능명 | 최소 에디션 | |----------|-------|-----------| | `MFA` | 2단계 인증 (TOTP/OTP) | COMMUNITY | | `LDAP` | LDAP/AD 디렉토리 연동 | STANDARD | | `PAM` | 특권 접근 관리 | STANDARD | | `AI_AGENTS` | AI 에이전트 오케스트레이션 | STANDARD | | `VULN_SCAN` | 보안 취약점 자동 스캔 | ENTERPRISE | | `CICD` | CI/CD Jenkins 연동 | ENTERPRISE | | `ANALYTICS` | 고급 분석 대시보드 | ENTERPRISE | | `FINOPS` | 비용 분석 (FinOps) | ENTERPRISE | --- ## 3. 마스터 키 생성 및 보관 > **경고**: 마스터 키는 절대 외부 유출 금지. 분실 시 기존 발급 라이선스 모두 무효화됨. ### 3.1 마스터 키 생성 마스터 키는 GUARDiA 벤더 내부 시스템에서 **1회 생성**하여 안전하게 보관한다. ```bash # 방법 1: Python (권장) python3 -c "import secrets; print(secrets.token_hex(32))" # 방법 2: OpenSSL openssl rand -hex 32 # 결과 예시 (64자리 hex = 32바이트): # a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607 ``` ### 3.2 마스터 키 보관 정책 | 보관 방법 | 설명 | |---------|------| | **운영 서버** | 환경변수 `GUARDIA_LICENSE_KEY` — systemd 서비스 파일 또는 .env (권한 600) | | **비상 백업** | 오프라인 금고 (USB 암호화 드라이브) | | **접근 권한** | 벤더 최고 관리자 2인 이상 알 고리즘 — 단독 접근 금지 | ### 3.3 .env 파일 설정 (운영 서버) ```ini # C:\GUARDiA\itsm\.env (권한: 소유자 읽기/쓰기만) GUARDIA_LICENSE_KEY=a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607 ``` ```bash # Linux: 파일 권한 보호 chmod 600 /opt/guardia/itsm/.env chown guardia:guardia /opt/guardia/itsm/.env ``` --- ## 4. 라이선스 키 발급 (벤더 전용) > 이 절차는 GUARDiA 벤더(개발사) 내부 운영자만 실행한다. ### 4.1 발급 전 확인 사항 ``` □ 고객명 (정식 기관명, 계약서 표기 명칭과 동일) □ 에디션 (COMMUNITY / STANDARD / ENTERPRISE) □ 유효 기간 (일수) 예: 365일(1년), 730일(2년) □ 커스텀 한도 여부 (특수 계약 시: max_institutions, max_users, max_servers 별도 지정) □ GUARDIA_LICENSE_KEY 환경변수 설정 확인 ``` ### 4.2 CLI를 이용한 발급 ```bash # 발급 서버(벤더 내부 PC)에서 실행 cd C:\GUARDiA\itsm # 방법 1: 환경변수에 마스터 키 설정 후 발급 $env:GUARDIA_LICENSE_KEY = "a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607" python -m core.license --customer "서울특별시청" --edition ENTERPRISE --days 365 # 방법 2: --key 인수로 직접 지정 (환경변수 없어도 가능) python -m core.license \ --customer "인천광역시청" \ --edition STANDARD \ --days 730 \ --key a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607 # 커스텀 라이선스 ID 지정 (선택) python -m core.license --customer "부산광역시청" --edition ENTERPRISE --days 365 --lid GRD-BUSAN2026 ``` ### 4.3 발급 결과 예시 ``` ============================================================ GUARDiA 라이선스 키 생성 완료 ============================================================ 고객명 : 서울특별시청 에디션 : ENTERPRISE 라이선스ID: GRD-A1B2C3 발급일시 : 2026-05-28T00:00:00+00:00 만료일시 : 2027-05-28T00:00:00+00:00 유효기간 : 365일 ============================================================ 라이선스 키: GRD-eyJsaWNlbnNlX2lkIjoiR1JELUExQjJDMyIsImVkaXRpb24iOiJFTlRFUlBSSVNFIn0... ``` ### 4.4 Python 코드를 이용한 발급 (자동화) ```python from datetime import datetime, timedelta, timezone from core.license import generate_license_key, validate_license, LicenseEdition MASTER_KEY = "a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607" # 1년짜리 ENTERPRISE 라이선스 생성 lic_key = generate_license_key( customer = "서울특별시청", edition = LicenseEdition.ENTERPRISE, expires_at = datetime.now(timezone.utc) + timedelta(days=365), master_key_hex = MASTER_KEY, ) print(f"키: {lic_key}") # 검증 status = validate_license(lic_key, master_key_hex=MASTER_KEY) print(f"유효: {status['valid']}, 남은 일수: {status['days_remaining']}") ``` ### 4.5 커스텀 한도 지정 (특수 계약) ```python custom_limits = { "max_institutions": 10, # 기관 10개 "max_users": 100, # 사용자 100명 "max_servers": 200, # 서버 200대 "features": ["MFA", "LDAP", "PAM"], } lic_key = generate_license_key( customer = "경기도청", edition = LicenseEdition.STANDARD, expires_at = datetime.now(timezone.utc) + timedelta(days=365), custom_limits = custom_limits, master_key_hex = MASTER_KEY, ) ``` --- ## 5. 라이선스 키 고객 적용 절차 ### 5.1 방법 A: 웹 UI (권장) 1. ITSM 관리자 계정으로 로그인 2. 사이드바 **🔏 라이선스 관리** 클릭 → `/license` 3. **라이선스 키 등록/갱신** 섹션에 `GRD-...` 키 붙여넣기 4. **라이선스 등록** 버튼 클릭 5. 상단 배너에서 에디션·만료일 확인 ``` [상태 배너 예시] ✅ ENTERPRISE 라이선스 활성 — 365일 남음 (서울특별시청) ``` ### 5.2 방법 B: API (자동화/스크립트) ```bash # 1. 관리자 로그인 후 토큰 획득 TOKEN=$(curl -s -X POST http://localhost:8001/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"Admin!1234"}' \ | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") # 2. 라이선스 등록 curl -X POST http://localhost:8001/api/license/activate \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "license_key": "GRD-eyJsaWNlbnNlX2lkIjoiR1JELUExQjJDMyI..." }' # 성공 응답 예시: # { # "message": "ENTERPRISE 라이선스가 활성화되었습니다 (365일 남음).", # "license_id": "GRD-A1B2C3", # "edition": "ENTERPRISE", # "customer": "서울특별시청", # "expires_at": "2027-05-28T00:00:00+00:00", # "days_remaining": 365, # "valid": true # } ``` ### 5.3 방법 C: .env 파일 사전 구성 (설치 시점 적용) 배포 자동화 파이프라인에서 설치 시점에 라이선스를 미리 구성할 때: ```ini # C:\GUARDiA\itsm\.env SECRET_KEY= GUARDIA_LICENSE_KEY=<마스터_키_64자리_hex> ``` 서버 기동 시 ITSM이 자동으로 라이선스 상태를 출력한다: ``` [LICENSE] ENTERPRISE 라이선스 활성 (365일 남음) — 서울특별시청 ``` 단, `.env`에 마스터 키만 있고 DB에 라이선스 레코드가 없으면 활성화는 안 된다. **DB에 키를 등록하는 것은 API 호출(방법 A/B) 필수.** --- ## 6. 라이선스 갱신 절차 ### 6.1 갱신 타이밍 | 시점 | 시스템 반응 | 권장 조치 | |------|-----------|---------| | 만료 30일 전 | 웹 UI 황색 배너 경고, 서버 시작 시 경고 로그 | 갱신 라이선스 발급 준비 | | 만료 7일 전 | 매일 경고 | 즉시 갱신 신청 | | 만료 당일 | `expired: true`, 기능 제한 시작 | 긴급 갱신 | | 만료 후 | 기존 데이터 보존, 신규 등록만 차단 | 갱신 라이선스 즉시 적용 | > **중요**: 만료 후에도 기존 등록된 기관·서버·사용자 데이터는 삭제되지 않는다. > 신규 생성(기관 추가, 서버 추가)만 한도 초과 시 차단된다. ### 6.2 갱신 라이선스 발급 동일 CLI로 새 키를 발급하면 된다. 라이선스 ID는 새로 자동 생성된다: ```bash python -m core.license \ --customer "서울특별시청" \ --edition ENTERPRISE \ --days 365 \ --key <마스터_키> # 기존 라이선스는 자동 비활성화되고 새 라이선스로 교체된다. ``` ### 6.3 갱신 적용 **웹 UI**: `/license` → 새 `GRD-...` 키 입력 → **라이선스 등록** (기존 활성 라이선스는 자동으로 비활성화됨) **API**: ```bash curl -X POST http://localhost:8001/api/license/activate \ -H "Authorization: Bearer $TOKEN" \ -d '{"license_key": "GRD-<새_키>"}' ``` --- ## 7. 라이선스 상태 모니터링 ### 7.1 웹 UI 상태 배너 `/license` 페이지의 상단 배너로 즉시 확인: | 배너 색상 | 의미 | |---------|------| | 초록 | 정상 활성 | | 노랑 | 만료 30일 이내 경고 | | 빨강 | 만료됨 | | 회색 | 라이선스 미등록 (Community 제한 모드) | ### 7.2 사용량 현황 `/license` 페이지 **사용량 현황** 섹션에서 기관/사용자/서버 현재 수 vs 한도를 확인할 수 있다: ``` 기관 [██████████░] 48/50 사용자 [████░░░░░░░] 80/200 서버 [███░░░░░░░░] 150/500 ``` ### 7.3 API로 상태 확인 ```bash curl http://localhost:8001/api/license/status \ -H "Authorization: Bearer $TOKEN" ``` ```json { "activated": true, "valid": true, "expired": false, "expiry_warning": false, "license_id": "GRD-A1B2C3", "edition": "ENTERPRISE", "customer": "서울특별시청", "issued_at": "2026-05-28T00:00:00+00:00", "expires_at": "2027-05-28T00:00:00+00:00", "days_remaining": 365, "limits": { "max_institutions": -1, "max_users": -1, "max_servers": -1, "features": ["MFA","LDAP","PAM","AI_AGENTS","VULN_SCAN","CICD","ANALYTICS","FINOPS"] }, "message": "ENTERPRISE 라이선스 활성 (365일 남음)" } ``` ### 7.4 서버 로그 모니터링 ```bash # 시스템 기동 시 라이선스 상태 로그 확인 journalctl -u guardia-itsm -n 50 | grep LICENSE # 정상: # [LICENSE] ENTERPRISE 라이선스 활성 (365일 남음) — 서울특별시청 # 경고: # [LICENSE] 경고: 라이선스 만료 25일 남음 # 만료: # [LICENSE] 경고: 라이선스가 만료되었습니다. 갱신이 필요합니다. # 미설정: # [LICENSE] GUARDIA_LICENSE_KEY 미설정 — Community 모드로 실행됩니다. ``` --- ## 8. CLI 레퍼런스 ``` usage: python -m core.license [-h] --customer CUSTOMER --edition {COMMUNITY,STANDARD,ENTERPRISE} --days DAYS [--lid LID] [--key KEY] GUARDiA 라이선스 키 생성 도구 필수 인수: --customer 고객/기관 이름 (예: "서울특별시청") --edition 라이선스 에디션: COMMUNITY | STANDARD | ENTERPRISE --days 유효 기간 (일, 예: 365) 선택 인수: --lid 라이선스 ID 직접 지정 (기본: GRD-{6자리랜덤hex}) --key 마스터 키 hex 64자리 (기본: GUARDIA_LICENSE_KEY 환경변수) -h, --help 도움말 출력 ``` ### 사용 예시 ```bash # ENTERPRISE 1년 python -m core.license --customer "서울특별시청" --edition ENTERPRISE --days 365 # STANDARD 2년 python -m core.license --customer "경기도청" --edition STANDARD --days 730 # COMMUNITY 30일 평가판 python -m core.license --customer "테스트기관" --edition COMMUNITY --days 30 # 마스터 키 직접 지정 (환경변수 없을 때) python -m core.license --customer "부산광역시청" --edition ENTERPRISE --days 365 \ --key a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607 # 커스텀 라이선스 ID python -m core.license --customer "인천광역시청" --edition STANDARD --days 365 \ --lid GRD-INCHEON26 ``` --- ## 9. API 레퍼런스 모든 API는 `Authorization: Bearer ` 헤더 필요. ADMIN 전용 API는 ADMIN 역할 계정만 호출 가능. ### GET /api/license/status 현재 라이선스 상태 조회 (로그인한 모든 사용자 접근 가능). **응답:** ```json { "activated": true, "valid": true, "expired": false, "expiry_warning": false, "license_id": "GRD-A1B2C3", "edition": "ENTERPRISE", "customer": "서울특별시청", "issued_at": "2026-05-28T00:00:00+00:00", "expires_at": "2027-05-28T00:00:00+00:00", "days_remaining": 365, "limits": { ... }, "message": "ENTERPRISE 라이선스 활성 (365일 남음)" } ``` --- ### POST /api/license/activate 라이선스 키 등록 및 활성화 **(ADMIN 전용)**. **요청 본문:** ```json { "license_key": "GRD-eyJ..." } ``` **응답 (성공):** ```json { "message": "ENTERPRISE 라이선스가 활성화되었습니다 (365일 남음).", "license_id": "GRD-A1B2C3", "edition": "ENTERPRISE", "customer": "서울특별시청", "expires_at": "2027-05-28T00:00:00+00:00", "days_remaining": 365, "valid": true } ``` **오류:** | 코드 | 원인 | |------|------| | 400 | 키 형식 오류, 서명 불일치, 복호화 실패 | | 403 | ADMIN 권한 없음 | | 500 | GUARDIA_LICENSE_KEY 환경변수 미설정 | --- ### POST /api/license/verify 등록 없이 키 검증만 수행 **(ADMIN 전용)**. **요청 본문:** `{ "license_key": "GRD-eyJ..." }` **응답:** 라이선스 페이로드 전체 반환 (status와 동일 구조) --- ### DELETE /api/license/ 활성 라이선스 비활성화 **(ADMIN 전용)**. 시스템이 Community 제한 모드로 전환된다. **응답:** `{ "message": "라이선스가 비활성화되었습니다..." }` --- ### GET /api/license/history 등록 이력 전체 조회 **(ADMIN 전용)**. **응답:** ```json [ { "id": 2, "license_id": "GRD-A1B2C3", "edition": "ENTERPRISE", "customer": "서울특별시청", "issued_at": "2026-05-28T00:00:00", "expires_at": "2027-05-28T00:00:00", "is_active": true, "activated_by": "admin", "activated_at": "2026-05-28T09:00:00" }, { "id": 1, "license_id": "GRD-OLD001", "edition": "STANDARD", "is_active": false, ... } ] ``` --- ## 10. 문제 해결 ### 10.1 "GUARDIA_LICENSE_KEY 환경변수가 설정되지 않았습니다" ``` 원인: 서버 .env 파일에 GUARDIA_LICENSE_KEY가 없거나 서비스가 .env를 읽지 못함 해결: 1. .env 파일에 키 추가: GUARDIA_LICENSE_KEY=<64자리 hex> 2. 서비스 재시작: systemctl restart guardia-itsm (Linux) Restart-Service GUARDiA-ITSM (Windows) ``` ### 10.2 "라이선스 서명 검증 실패 — 위변조 또는 잘못된 키" ``` 원인 1: 키가 다른 마스터 키로 생성됨 (고객사 서버의 GUARDIA_LICENSE_KEY와 발급 시 키가 다름) 원인 2: 키 문자열이 복사 중 잘림 해결: 1. 키를 처음부터 끝까지 정확히 복사했는지 확인 (앞뒤 공백 없이) 2. 발급 시 사용한 마스터 키 == 운영 서버의 GUARDIA_LICENSE_KEY 확인 3. 다른 환경에서 발급된 키라면 해당 환경의 마스터 키로 서버를 재설정 ``` ### 10.3 "라이선스 복호화 실패 — 마스터 키 불일치" ``` 원인: HMAC 통과했으나 AES 복호화 실패 — 마스터 키 파생 문자가 다름 해결: 마스터 키가 정확히 64자리 hex인지 확인 python3 -c "print(len('YOUR_KEY'))" # 반드시 64 출력 ``` ### 10.4 "라이선스 한도 초과: ENTERPRISE 에디션으로 업그레이드하세요" ``` 원인: 현재 등록된 기관/서버/사용자 수가 에디션 한도를 초과함 해결: 방법 1: 상위 에디션 라이선스 발급 후 적용 방법 2: 불필요한 기관/서버를 삭제하여 한도 이하로 줄임 ``` ### 10.5 만료된 라이선스 등록 시도 ``` 동작: 만료된 키도 DB에 등록 가능하나 valid: false, expired: true 반환됨 등록 시 경고 메시지: "경고: 만료된 라이선스입니다 (만료일: ...). 갱신이 필요합니다." 해결: 새 라이선스를 발급받아 즉시 교체 등록 ``` ### 10.6 라이선스 캐시 갱신 안 됨 (변경 내용 미반영) ``` 원인: 인메모리 캐시(TTL 1시간)가 남아있어 이전 상태를 반환 해결: 라이선스를 activate/deactivate하면 캐시가 자동 무효화됨 긴급 시 ITSM 서비스 재시작으로 강제 캐시 초기화 ``` --- ## 부록 A: 라이선스 발급 체크리스트 (벤더용) ``` 고객사 발급 전 체크리스트: □ 계약서에 명시된 기관명과 동일한지 확인 □ 에디션이 계약 내용과 일치하는지 확인 □ 유효 기간이 계약 기간과 일치하는지 확인 □ 마스터 키가 해당 고객사 서버에 적용된 키와 동일한지 확인 □ 발급된 키를 암호화된 채널(이메일 암호화, 보안 메신저)로 전달 □ 발급 이력 대장에 기록 (날짜, 고객명, 에디션, 만료일, 발급자) ``` ## 부록 B: 라이선스 발급 이력 대장 양식 | 번호 | 발급일 | 고객명 | 에디션 | 라이선스 ID | 만료일 | 발급자 | 비고 | |-----|-------|-------|--------|-----------|-------|--------|------| | 001 | 2026-05-28 | 서울특별시청 | ENTERPRISE | GRD-A1B2C3 | 2027-05-28 | 홍길동 | 신규 | | 002 | 2026-05-28 | 경기도청 | STANDARD | GRD-D4E5F6 | 2028-05-28 | 홍길동 | 신규 2년 |