guardia-docs/14_라이선스_키_발급_가이드.md
DESKTOP-TKLFCPRython 938b25f286 feat(itsm): G-1~G-12 확장 기능 + 하네스/봇/설치스크립트 구현
G-1: 메신저 Webhook Relay + _send_to_room 실제 httpx 호출 구현
G-2: POST /api/tasks/bulk SR 대량작업 엔드포인트 (최대 100건)
G-3: 라이선스 만료 알림 스케줄러 (매일 09:00 KST)
G-4: 체험판 upgrade_banner 필드 + license.py 배너 로직
G-5: core/auto_rca.py + incidents/problem auto-rca 엔드포인트
G-6: core/deploy_impact.py + vibe impact-analysis 엔드포인트
G-7: core/ticket_classifier.py + SR 생성 시 AI 분류 + ai-suggestion API
G-8: VulnPatchRecord 모델 + vuln_scan 패치추적 4개 엔드포인트
G-9: core/jira_sync.py + gateway Jira/Confluence 연동 엔드포인트
G-10: core/push_notify.py + routers/push.py + PushSubscription 모델
G-11: approvals 다중승인 (위임/서명/기한초과/마감연장)
G-12: alembic.ini + migrations/ + cicd/migrate_to_postgres.sh

하네스: guardia-orchestrator 확장기능 Phase 반영
봇명령어: /sr /status /license /bulk 슬래시 명령어 추가
설치스크립트: setup/ (Ubuntu, CentOS, RHEL, Windows) --test 옵션 포함

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:18:52 +09:00

648 lines
19 KiB
Markdown

# 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=<jwt-secret>
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 <JWT_TOKEN>` 헤더 필요.
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년 |