Files
license/README.md

341 lines
11 KiB
Markdown
Raw Normal View History

2026-07-31 16:59:55 +08:00
# Ops Licence 许可证管理系统
Ops Licence 是供内部人员使用的离线许可证签发管理系统。系统通过“平台名称 + Workspace”维护授权对象支持许可证签发、签发记录查询、续期和 `licence.key` 下载,并提供可复制到其他 Go 项目的本地验证 SDK。
系统不依赖目标平台调用在线接口。许可证由内部人员在管理页面生成后,手动交付给使用方;使用方通过 Go SDK 在本地完成真实性、授权对象、有效期和配额验证。
## 主要功能
- 授权对象:以“平台名称 + Workspace”作为唯一授权标识区分大小写。
- 直接签发:设置生效日期、到期日期和全部配额,生成并下载许可证文件。
- 续期:从原签发记录发起,生成一份新的许可证文件,原记录和原文件保持不变。
- 签发记录:查看签发类型、授权对象、有效期、配额、签发时间和前一份许可证。
- 再次下载:从历史签发记录重新下载当时生成的许可证文件。
- 本地验证Go SDK 不访问许可证管理系统,也不绑定具体机器。
- 简约 Web 管理界面:前端已经嵌入 Go 可执行文件,无需单独部署静态站点。
当前系统不包含登录、会话、在线激活、许可证吊销和机器绑定功能。
## 业务规则
### 授权对象
平台名称与 Workspace 共同组成一个授权对象,两者都不能为空。同一组合不能重复,并且按原始大小写精确匹配。
- 没有签发记录时:允许修改、删除。
- 存在任意签发记录后:禁止修改、删除。
### 日期
生效日期和到期日期均采用 `YYYY-MM-DD`,按照 `Asia/Shanghai` 自然日判断,首尾日期都包含在有效期内。到期日期不能早于生效日期。
### 配额
每次直接签发或续期都会保存当次全部配额的完整快照。这里的“全部配额快照”是指:签发记录会固定保存下面 11 个配额在当次签发时的数值,以后不会跟随其他记录变化。续期页面会预填上一份许可证的配额,但提交后会生成新的独立记录。
所有配额都是非负整数,`0` 表示该项未授权:
| 字段 | 含义 |
| --- | --- |
| `max_database` | 数据库数量上限 |
| `max_middleware` | 中间件数量上限 |
| `max_network_device` | 网络设备数量上限 |
| `max_security` | 安全设备数量上限 |
| `max_storage` | 存储设备数量上限 |
| `max_pc` | PC 数量上限 |
| `max_server` | 服务器数量上限 |
| `max_user` | 用户数量上限 |
| `max_role` | 角色数量上限 |
| `max_permission` | 权限数量上限 |
| `max_menu` | 菜单数量上限 |
## 快速启动
### 1. 准备 PostgreSQL
数据库名称必须精确为 `ops_licence`。服务首次启动时会自动执行内嵌迁移,创建本项目自己的数据表:
- `schema_migrations`
- `authorization_subjects`
- `signing_keys`
- `licence_issuances`
不需要手工建表,也不会使用其他项目的业务表。
### 2. 配置 YAML
本地配置文件为 `D:\work\license\etc\license.yaml`。配置只包含服务监听地址、数据库 DSN 和当前签发密钥文件:
```yaml
server:
listen_addr: "127.0.0.1:8080"
database:
dsn: "postgresql://username:password@127.0.0.1:5432/ops_licence?sslmode=disable"
signing:
private_key_file: "D:\\work\\license\\secrets\\signing-private.pem"
certificate_file: "D:\\work\\license\\secrets\\signing-certificate.json"
```
注意:
- `listen_addr` 必须使用回环 IP例如 `127.0.0.1:8080`
- DSN 必须显式包含用户名、密码、单一主机、端口、数据库名和 `sslmode`
- 支持的 `sslmode``disable``require``verify-ca``verify-full`
- 数据库用户名或密码包含特殊字符时,需要使用 URL 百分号编码。
- DSN 不要添加 `TimeZone` 等额外查询参数。
- 服务只读取 YAML不需要配置数据库环境变量运行用户已有的 `PG*` 数据库连接环境变量需要清除。
- 签发私钥和签发密钥凭证必须使用绝对路径,并且必须相互匹配。
### 3. 启动服务
在项目目录执行:
```powershell
.\bin\ops-licence.exe serve --config D:\work\license\etc\license.yaml
```
启动成功后访问:
```text
http://127.0.0.1:8080
```
## 页面操作
### 新建授权对象
1. 打开“授权对象”。
2. 点击“新建授权对象”。
3. 填写平台名称和 Workspace 后保存。
如果填写错误,只要该对象还没有签发记录,就可以从详情页修改或删除。
### 直接签发
1. 打开授权对象详情。
2. 点击“直接签发”。
3. 选择生效日期、到期日期并填写全部配额。
4. 点击“签发并下载”。
5. 将下载得到的 `{许可证ID}.licence.key` 文件手动交付给使用方。
签发成功后记录不可修改、不可删除。
### 续期
1. 打开“签发记录”。
2. 进入需要续期的记录详情。
3. 点击“续期”。
4. 确认或调整日期和配额。
5. 提交后下载新生成的 `{许可证ID}.licence.key`
续期不会覆盖旧记录,也不会让旧文件失效;新记录会保存其来源许可证 ID。
## 密钥管理
许可证采用两级 Ed25519 信任链:
1. 根私钥签发“签发密钥凭证”。
2. 签发私钥签发具体的许可证。
3. SDK 内置根公钥,先验证签发密钥凭证,再验证许可证签名。
根私钥不需要部署到许可证管理服务器。服务器运行时只需要当前签发私钥和对应的签发密钥凭证。
### 更换签发密钥
使用现有根私钥签发一组新的签发密钥和凭证:
```powershell
.\bin\ops-licence.exe keys issue `
--root-private D:\keys\root-private.pem `
--private-out D:\keys\signing-private-2026.pem `
--certificate-out D:\keys\signing-certificate-2026.json
```
输出文件不能预先存在。生成后修改 YAML 中的两个 `signing` 文件路径并重启服务。
更换签发密钥不需要重新生成或复制 SDK因为新签发密钥仍由同一个根私钥签发。只有主动更换根信任时才需要更新 SDK 内置根公钥并重新复制 SDK。
### 重新建立根信任
当前项目已经包含根公钥。下面的命令只用于明确决定更换整套根信任时,不属于日常签发密钥轮换:
```powershell
go run .\cmd\root-keygen `
--private-out D:\keys\new-root-private.pem `
--sdk-public-out D:\keys\root_public_key.go
```
生成后需要用新的 `root_public_key.go` 替换 SDK 中的根公钥文件,再用新根私钥签发新的签发密钥。更换根信任后必须重新分发 SDK。
## Linux 部署
项目提供了 Linux systemd 和 Nginx 配置模板:
- `deploy/ops-licence.service`
- `deploy/nginx.conf`
### 文件位置
建议按模板放置:
```text
/usr/local/bin/ops-licence
/etc/ops-licence/ops-licence.yaml
/etc/ops-licence/keys/signing-private.pem
/etc/ops-licence/keys/signing-certificate.json
/var/lib/ops-licence
```
Linux YAML 示例:
```yaml
server:
listen_addr: "127.0.0.1:8080"
database:
dsn: "postgresql://username:password@database-host:5432/ops_licence?sslmode=disable"
signing:
private_key_file: "/etc/ops-licence/keys/signing-private.pem"
certificate_file: "/etc/ops-licence/keys/signing-certificate.json"
```
### systemd
创建与模板一致的 `ops-licence` 系统用户和目录后,安装并启动服务:
```bash
sudo install -m 0755 dist/ops-licence /usr/local/bin/ops-licence
sudo install -m 0644 deploy/ops-licence.service /etc/systemd/system/ops-licence.service
sudo systemctl daemon-reload
sudo systemctl enable --now ops-licence
sudo systemctl status ops-licence
```
查看运行日志:
```bash
sudo journalctl -u ops-licence -f
```
### Nginx
`deploy/nginx.conf` 将 HTTPS 请求反向代理到 `127.0.0.1:8080`。部署时将其中证书路径替换为实际路径,并把配置放入 Nginx 的站点配置目录后重新加载 Nginx。
Go 可执行文件已经包含前端资源Nginx 只需要代理所有路径,不需要另外复制 `web/dist`
## Go SDK 接入
将整个 `sdk/licence` 目录复制到目标 Go 项目中,保持目录内所有文件完整。目标项目不需要连接许可证管理系统。
使用时传入许可证文件路径,以及目标程序实际使用的平台名称和 Workspace
```go
result, err := licence.VerifyFile(
"/etc/example/licence.key",
licence.Subject{
PlatformName: "平台名称",
Workspace: "workspace",
},
)
if err != nil {
return err
}
databaseLimit := result.Quotas.MaxDatabase
expiresOn := result.ExpiresOn
```
`VerifyFile` 会一次完成:
- 文件格式和大小检查。
- 根公钥到签发密钥的凭证验证。
- 许可证签名验证。
- 平台名称与 Workspace 精确匹配。
-`Asia/Shanghai` 日期检查是否生效、是否到期。
- 所有配额非负检查。
可以通过 `errors.Is` 区分主要验证结果:
```go
switch {
case errors.Is(err, licence.ErrNotYetValid):
// 许可证尚未生效
case errors.Is(err, licence.ErrExpired):
// 许可证已经到期
case errors.Is(err, licence.ErrSubjectMismatch):
// 平台名称或 Workspace 不匹配
case errors.Is(err, licence.ErrInvalidSignature):
// 许可证签名无效
case err != nil:
// 文件格式、签发密钥凭证或配额无效
}
```
复制后SDK 的 Go 导入路径以目标项目的模块路径为准。
## HTTP 接口
管理页面使用以下接口,接口不要求登录或会话:
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| `GET` | `/api/v1/dashboard` | 获取首页统计 |
| `GET` | `/api/v1/subjects` | 查询授权对象 |
| `POST` | `/api/v1/subjects` | 新建授权对象 |
| `GET` | `/api/v1/subjects/:id` | 获取授权对象详情 |
| `PUT` | `/api/v1/subjects/:id` | 修改未签发的授权对象 |
| `DELETE` | `/api/v1/subjects/:id` | 删除未签发的授权对象 |
| `POST` | `/api/v1/subjects/:id/licences` | 直接签发并下载许可证 |
| `GET` | `/api/v1/licences` | 查询签发记录 |
| `GET` | `/api/v1/licences/:id` | 获取签发记录详情 |
| `GET` | `/api/v1/licences/:id/download` | 再次下载许可证 |
| `POST` | `/api/v1/licences/:id/renew` | 续期并下载新许可证 |
## 构建
构建环境Go `1.25.1`、Node.js、pnpm。
前端构建结果会直接写入 `internal/webui/dist`
```powershell
Set-Location D:\work\license\web
pnpm.cmd install
pnpm.cmd build
```
前端有变化时,先构建前端,再构建 Go 程序。
Windows
```powershell
Set-Location D:\work\license
go build -buildvcs=false -o bin\ops-licence.exe .\cmd\ops-licence
```
Linux AMD64
```powershell
Set-Location D:\work\license
$env:GOOS = "linux"
$env:GOARCH = "amd64"
go build -buildvcs=false -o dist\ops-licence .\cmd\ops-licence
```
## 项目目录
```text
cmd/ops-licence 服务与签发密钥命令
cmd/root-keygen 根密钥生成工具
deploy Linux systemd 与 Nginx 模板
docs 设计、实施与使用文档
etc 本地 YAML 配置
internal 服务端实现、数据库迁移和内嵌前端
sdk/licence 可复制的 Go 本地验证 SDK
web Vue 管理界面源码
bin Windows 可执行文件
dist Linux 可执行文件
```