# light-vllm-security 使用教程

本文档面向**开发人员**和**实施人员**，涵盖：
- 厂商侧：密钥管理、模型加密、授权签发
- 客户侧：环境安装、模型部署、服务启动
- 运维：授权监控、续期
- 开发：构建、发布到 PyPI

---

## 目录

1. [概念说明](#1-概念说明)
2. [厂商操作](#2-厂商操作)
   - [2.1 环境准备](#21-环境准备)
   - [2.2 生成厂商密钥对（一次性）](#22-生成厂商密钥对一次性)
   - [2.3 加密模型](#23-加密模型)
   - [2.4 签发授权文件](#24-签发授权文件)
   - [2.5 向客户分发文件](#25-向客户分发文件)
3. [客户端操作](#3-客户端操作)
   - [3.1 安装依赖](#31-安装依赖)
   - [3.2 获取机器指纹](#32-获取机器指纹)
   - [3.3 部署模型文件](#33-部署模型文件)
   - [3.4 启动服务](#34-启动服务)
   - [3.5 授权过期处理](#35-授权过期处理)
4. [进阶场景](#4-进阶场景)
   - [4.1 集群部署（多机）](#41-集群部署多机)
   - [4.2 不绑定机器（测试/开发环境）](#42-不绑定机器测试开发环境)
   - [4.3 无到期时间的永久授权](#43-无到期时间的永久授权)
5. [构建与发布到 PyPI](#5-构建与发布到-pypi)
6. [文件说明速查](#6-文件说明速查)
7. [常见错误处理](#7-常见错误处理)

---

## 1. 概念说明

| 角色 | 职责 |
|------|------|
| **厂商** | 持有私钥，负责加密模型、签发授权文件、保管 `vendor_secret` |
| **客户** | 获得加密模型、公钥、授权文件、`vendor_secret`，在自己机器上运行服务 |

| 文件 | 持有方 | 说明 |
|------|--------|------|
| `vendor_private.pem` | 仅厂商 | RSA 私钥，用于签发授权，**绝不分发** |
| `vendor_public.pem` | 厂商 + 客户 | RSA 公钥，用于验证授权签名 |
| `vendor_secret.bin` | 厂商 + 客户 | 对称密钥，用于解密 DEK，通过安全渠道传递 |
| `.model_key` | 随加密模型分发 | 模型密钥元数据，含加密的 DEK |
| `*.lic` | 客户 | 授权文件，含签名、有效期、机器指纹 |
| `*.enc` | 客户 | 加密后的模型权重文件 |

**运行时解密流程：**

```
客户机器启动 light-vllm serve
  → 读取 *.lic 授权文件
  → RSA 验签（用 vendor_public.pem）
  → 校验有效期 + 机器指纹
  → 用 vendor_secret 解密 DEK（数据加密密钥）
  → 用 DEK 解密 *.enc 模型权重
  → 加载到 vllm 引擎
  → 后台线程每 24 小时检查一次授权有效期
```

---

## 2. 厂商操作

### 2.1 环境准备

在厂商的**内网安全机器**（不需要 GPU）上安装：

```bash
pip install light-vllm-security
```

验证安装：

```bash
vllm-encrypt-model --help
vllm-gen-license --help
```

---

### 2.2 生成厂商密钥对（一次性）

**整个公司只做一次，所有模型共用同一个密钥对。**

```bash
vllm-gen-license --gen-keypair --private-key vendor_private.pem --public-key vendor_public.pem
```

输出：
```
Generating RSA-2048 keypair...
  Private key: vendor_private.pem
  Public key:  vendor_public.pem

KEEP THE PRIVATE KEY SECURE!
Distribute the public key with encrypted models.
```

**安全要求：**

- `vendor_private.pem`：存入密钥管理系统（KMS）或离线保险箱，**绝不上传到代码仓库、不随模型分发**
- `vendor_public.pem`：随加密模型一起分发给客户，无需保密

---

### 2.3 加密模型

**加密 HuggingFace 格式模型目录：**

```bash
vllm-encrypt-model --input  ./llama-3-8b --output ./llama-3-8b-encrypted
```

输出示例：
```
Generated new DEK
Key ID: 3f8a2c1d...

Encrypting model directory: /path/to/llama-3-8b
Output directory: /path/to/llama-3-8b-encrypted

  Copying:    config.json
  Copying:    tokenizer.json
  Copying:    tokenizer_config.json
  Encrypting: model-00001-of-00002.safetensors -> model-00001-of-00002.safetensors.enc
  Encrypting: model-00002-of-00002.safetensors -> model-00002-of-00002.safetensors.enc

Encryption complete!
  Encrypted files: 2
  Key ID:          3f8a2c1d...
  Key file:        ./llama-3-8b-encrypted/.model_key
```

**加密单个 GGUF 文件：**

```bash
vllm-encrypt-model \
    --input  ./llama-3-8b-q4.gguf \
    --output ./llama-3-8b-q4.gguf.enc
```

**加密后目录结构：**

```
llama-3-8b-encrypted/
├── config.json                              # 明文（tokenizer 需要）
├── tokenizer.json                           # 明文
├── tokenizer_config.json                    # 明文
├── model-00001-of-00002.safetensors.enc     # 加密权重
├── model-00002-of-00002.safetensors.enc     # 加密权重
└── .model_key                               # 密钥元数据（含 DEK）
```

> **注意：** `.model_key` 文件在加密阶段生成，后续签发授权时需要读取其中的 `key_id`。

---

### 2.4 签发授权文件

每个客户签发一个独立的授权文件。

**场景一：绑定到客户单台机器（推荐生产环境）**

首先让客户在其部署机器上运行（见 [3.2 获取机器指纹](#32-获取机器指纹)），把指纹发给你，然后：

```bash
vllm-gen-license --model-dir ./llama-3-8b-encrypted --vendor-key vendor_private.pem --customer "Acme Corp" --fingerprint "240da3f55ff75ac6d1bda6bab06352e2a94a87e51ce71e6ee546c61117f57c79" --policy single --expire 2026-12-31 --out acme_corp.lic
```

输出示例：
```
Generating license...
  Customer:    Acme Corp
  Model:       ./llama-3-8b-encrypted
  Key ID:      3f8a2c1d...
  Policy:      single
  Fingerprints: 1
    - a1b2c3d4...
  Expires:     2026-12-31

Generated new vendor_secret and saved to: vendor_secret.bin
KEEP THIS FILE SECURE! It's required to decrypt models at runtime.

License saved to: acme_corp.lic
License ID: 550e8400-e29b-41d4-a716-446655440000
```

> **重要：** 首次为某个模型签发授权时，会自动生成 `vendor_secret.bin`。后续为**同一模型**签发新授权时，必须复用同一个 `vendor_secret.bin`，否则客户无法解密：
>
> ```bash
> vllm-gen-license \
>  --model-dir ./llama-3-8b-encrypted \
>  --vendor-key vendor_private.pem \
>  --vendor-secret @vendor_secret.bin \   # 复用已有的
>  --customer "Beta Corp" \
>  ...
>  
> # 后续续签（必须复用）
> vllm-gen-license --model-dir ./model-encrypted --vendor-key vendor_private.pem \
>     --vendor-secret @vendor_secret.bin ...
> ```

**场景二：绑定到集群（多台机器）**

```bash
vllm-gen-license \
    --model-dir ./llama-3-8b-encrypted \
    --vendor-key vendor_private.pem \
    --vendor-secret @vendor_secret.bin \
    --customer "BigCorp HA Cluster" \
    --fingerprint "fp_node1,fp_node2,fp_node3" \
    --policy cluster \
    --max-nodes 3 \
    --expire 2026-12-31 \
    --out bigcorp_cluster.lic
```

**场景三：无机器绑定（测试/演示）**

```bash
vllm-gen-license \
    --model-dir ./llama-3-8b-encrypted \
    --vendor-key vendor_private.pem \
    --vendor-secret @vendor_secret.bin \
    --customer "Demo" \
    --policy none \
    --expire 2025-03-31 \
    --out demo.lic
```

---

### 2.5 向客户分发文件

分发清单：

| 文件 | 传输方式 |
|------|---------|
| `llama-3-8b-encrypted/`（整个目录，含 `.model_key`） | 普通渠道（网盘、对象存储等） |
| `vendor_public.pem` | 普通渠道 |
| `acme_corp.lic` | 普通渠道 |
| `vendor_secret.bin` 的内容（hex 字符串） | **安全渠道**（加密邮件、企业 IM 私信、KMS） |

获取 `vendor_secret` 的 hex 值：

```bash
xxd -p vendor_secret.bin | tr -d '\n'
# 或
python3 -c "print(open('vendor_secret.bin','rb').read().hex())"
```

将这个 hex 字符串通过安全渠道告知客户。

---

## 3. 客户端操作

### 3.1 安装依赖

```bash
# 1. 安装 vllm（根据你的 CUDA 版本选择 和light-vllm-security对应支持版本）
pip install vllm==xxx

# 2. 安装 light-vllm-security
pip install light-vllm-security

# 验证安装
light-vllm --help
```

---

### 3.2 获取机器指纹

在**部署机器**上运行，将输出发送给厂商用于授权绑定：

```bash
vllm-get-fingerprint
# 输出：a1b2c3d4e5f6a1b2c3d4e5f6...（64位十六进制字符串）
```

查看详细组成（用于排查指纹变化问题）：

```bash
vllm-get-fingerprint --verbose
```

输出示例：
```
Machine Fingerprint Components:
  CPU ID:          GenuineIntel-...
  MAC Addresses:   aa:bb:cc:dd:ee:ff
  Hostname:        prod-server-01
  GPU UUIDs:       (none)
  Platform:        Linux

Fingerprint (no GPU):   a1b2c3d4...
Fingerprint (with GPU): e5f6a7b8...

Use this fingerprint: a1b2c3d4...
```

> **注意：** 默认不含 GPU UUID。如果机器 GPU 可能被替换，建议使用不含 GPU 的指纹（默认）。

---

### 3.3 部署模型文件

将厂商提供的文件放置到服务器：

```
/opt/models/
└── llama-3-8b-encrypted/
    ├── config.json
    ├── tokenizer.json
    ├── model-00001-of-00002.safetensors.enc
    ├── model-00002-of-00002.safetensors.enc
    ├── .model_key
    ├── vendor_public.pem          ← 放入模型目录（自动发现）
    └── model.lic                  ← 放入模型目录（自动发现，文件名必须是 model.lic）
```

> **自动发现规则：** 若 `vendor_public.pem` 和 `model.lic` 在模型目录下，无需额外指定路径。如果文件名或路径不同，通过 `--model-loader-extra-config` 指定（见下文）。

设置环境变量（保存 `vendor_secret`）：

```bash
# 推荐：写入 ~/.bashrc 或服务的 systemd EnvironmentFile
export VLLM_VENDOR_SECRET="厂商提供的hex字符串"
```

---

### 3.4 启动服务

**标准启动（推荐）：**

```bash
export VLLM_VENDOR_SECRET="a1b2c3d4..."

light-vllm serve /opt/models/llama-3-8b-encrypted \
    --load-format encrypted \
    --port 8000 \
    --gpu-memory-utilization 0.90 \
    --max-model-len 4096
```

**指定自定义文件路径：**

```bash
light-vllm serve /opt/models/llama-3-8b-encrypted \
    --load-format encrypted \
    --model-loader-extra-config '{
        "vendor_secret": "a1b2c3d4...",
        "license_path": "/etc/vllm/licenses/acme.lic",
        "vendor_pubkey_path": "/etc/vllm/vendor_public.pem"
    }' \
    --port 8000
```

**启动日志（正常）：**

```
================================================================================
🔐 MODEL SECURITY: Validating license: /opt/models/llama-3-8b-encrypted/model.lic
================================================================================
================================================================================
✅ LICENSE VALID - Model authorized for use
   License ID: 550e8400-e29b-41d4-a716-446655440000
   Expires: 2026-12-31
================================================================================
INFO: License monitor started (checking every 24 hours)
```

---

### 3.5 授权过期处理

**过期后 1-3 天（宽限期）**，每 24 小时打印一次警告，服务继续正常运行：

```
================================================================================
⚠️  LICENSE EXPIRED: License expired on 2026-12-31
   Grace period: 2 day(s) remaining before service stops.
   Please contact your vendor for a license renewal.
================================================================================
```

**过期超过 3 天**，服务自动终止：

```
================================================================================
🚫 LICENSE GRACE PERIOD ENDED: License expired on 2026-12-31
   Service is shutting down. Please renew your license.
================================================================================
```

**续期流程：**

1. 联系厂商，提供机器指纹（如有变化）
2. 厂商签发新的 `.lic` 文件
3. 替换模型目录下的 `model.lic`
4. 重启服务（`light-vllm serve ...`）

> **无需重新加密模型**，只需更换 `.lic` 文件。

---



### 3.6 Docker 服务部署

建议使用Docker 镜像，解决环境冲突问题。

`镜像名称:light290/light-vllm`

**部署命令**

```bash
docker run  --gpus all -p 8000:8000 -e VLLM_VENDOR_SECRET=753369782e1033dd81609be8eb5306ce39517e78a3ba33b3438582c4fe865a81 -v path/to/:/models light290/light-vllm:v0.8.5.post1 /models/Gemma3 --gpu-memory-utilization 0.95 --max-model-len 512 --enforce-eager --load-format encrypted --device cuda
```





## 4. 进阶场景

### 4.1 集群部署（多机）

在每台节点上获取指纹：

```bash
# node1
vllm-get-fingerprint
# → fp_node1

# node2
vllm-get-fingerprint
# → fp_node2
```

厂商签发集群授权：

```bash
vllm-gen-license \
    --model-dir ./llama-3-8b-encrypted \
    --vendor-key vendor_private.pem \
    --vendor-secret @vendor_secret.bin \
    --customer "BigCorp Cluster" \
    --fingerprint "fp_node1,fp_node2" \
    --policy cluster \
    --max-nodes 2 \
    --expire 2026-12-31 \
    --out bigcorp_cluster.lic
```

每台节点使用**同一个** `model.lic` 文件。

---

### 4.2 不绑定机器（测试/开发环境）

```bash
vllm-gen-license \
    --model-dir ./llama-3-8b-encrypted \
    --vendor-key vendor_private.pem \
    --vendor-secret @vendor_secret.bin \
    --customer "Internal Testing" \
    --policy none \
    --expire 2025-06-30 \
    --out test.lic
```

此授权可在任意机器上使用，适合内部测试，不建议用于生产分发。

---

### 4.3 无到期时间的永久授权

```bash
vllm-gen-license \
    --model-dir ./llama-3-8b-encrypted \
    --vendor-key vendor_private.pem \
    --vendor-secret @vendor_secret.bin \
    --customer "Perpetual License Customer" \
    --fingerprint "fp_customer" \
    --policy single \
    --out perpetual.lic
    # 不传 --expire，则永不过期
```

---

## 5. 构建与发布到 PyPI

### 5.1 前置工具

```bash
pip install build twine
```

### 5.2 更新版本号

编辑 `pyproject.toml`：

```toml
[project]
version = "0.1.0"   # 修改这里
```

### 5.3 构建

```bash
python -m build   # uv build
```

构建产物在 `dist/` 目录：

```
dist/
├── light_vllm_security-0.1.0-py3-none-any.whl
└── light_vllm_security-0.1.0.tar.gz
```

### 5.4 测试发布（TestPyPI）

```bash
# 上传到 TestPyPI
twine upload --repository testpypi dist/*

# 从 TestPyPI 安装测试
pip install --index-url https://test.pypi.org/simple/ light-vllm-security
```

### 5.5 正式发布到 PyPI

```bash
twine upload dist/*
```

首次需要在 [pypi.org](https://pypi.org) 注册账号，或使用 API Token：

```bash
# 使用 API Token（推荐，比密码更安全）
twine upload dist/* --username __token__ --password pypi-xxxxx...
```

或在 `~/.pypirc` 中配置：

```ini
[pypi]
username = __token__
password = pypi-xxxxx...
```

### 5.6 验证发布

```bash
pip install light-vllm-security==0.1.0
light-vllm --help
```

### 5.7 发布检查清单

发布前确认：

- [ ] `pyproject.toml` 版本号已更新
- [ ] `CHANGELOG` 或 Release Notes 已更新
- [ ] 在测试环境验证加密/解密/授权流程
- [ ] `vendor_private.pem` 未被打包进去（检查 `.gitignore` 和 `MANIFEST.in`）
- [ ] 运行 `python -m build` 无报错
- [ ] 先上传到 TestPyPI 验证安装流程

---

## 6. 文件说明速查

### CLI 命令

| 命令 | 使用方 | 功能 |
|------|--------|------|
| `vllm-gen-license --gen-keypair` | 厂商（一次性） | 生成 RSA 密钥对 |
| `vllm-encrypt-model` | 厂商 | 加密模型文件 |
| `vllm-gen-license` | 厂商 | 签发授权文件 |
| `vllm-get-fingerprint` | 客户 | 获取机器指纹 |
| `light-vllm serve` | 客户 | 启动加密模型服务 |

### 环境变量

| 变量 | 说明 |
|------|------|
| `VLLM_VENDOR_SECRET` | vendor_secret 的十六进制字符串，推荐通过此方式传递 |

### `--model-loader-extra-config` 参数

| 键 | 说明 | 默认 |
|----|------|------|
| `vendor_secret` | vendor_secret 的十六进制字符串 | 读取 `VLLM_VENDOR_SECRET` 环境变量 |
| `license_path` | 授权文件路径 | 自动发现 `<model_dir>/model.lic` |
| `vendor_pubkey_path` | 公钥文件路径 | 自动发现 `<model_dir>/vendor_public.pem` |

---

## 7. 常见错误处理

### `License expired`

```
ValueError: License expired on 2026-12-31.
```

**原因：** 授权已过期。
**解决：** 联系厂商续期，替换 `model.lic` 后重启服务。

---

### `License signature verification failed`

```
ValueError: License signature verification failed. License may be tampered.
```

**原因：** 授权文件损坏或被篡改，或使用了错误的 `vendor_public.pem`。
**解决：** 重新从厂商获取授权文件，确保 `vendor_public.pem` 与签发时的私钥匹配。

---

### `Machine fingerprint mismatch`

```
ValueError: Machine fingerprint mismatch. This license is not authorized for this machine.
```

**原因：** 服务器硬件发生变化（MAC 地址、CPU 等），或将授权文件复制到了未授权的机器。
**解决：** 在新机器上重新获取指纹，联系厂商签发新授权。

---

### `License key_id mismatch`

```
ValueError: License key_id mismatch. This license is not for this model.
```

**原因：** 授权文件与模型不对应，可能混用了不同版本模型的授权。
**解决：** 确认授权文件是针对当前模型签发的。

---

### `Cannot extract DEK: vendor_secret is required`

```
ValueError: Cannot extract DEK: vendor_secret is required for symmetric decryption mode.
```

**原因：** 未提供 `vendor_secret`。
**解决：** 设置环境变量 `export VLLM_VENDOR_SECRET=<hex>` 或在 `--model-loader-extra-config` 中传入。

---

### `invalid choice: 'encrypted'`

```
vllm serve: error: argument --load-format: invalid choice: 'encrypted'
```

**原因：** 使用了 `vllm serve` 而不是 `light-vllm serve`。
**解决：** 将命令替换为 `light-vllm serve`。

---

### `No module named 'vllm.model_security'`

**原因：** `light-vllm-security` 未安装或安装在了不同的 Python 环境中。
**解决：**

```bash
# 确认当前 Python 环境
which python
pip show light-vllm-security
pip install light-vllm-security
```
