Files
license/README.md
2026-07-31 16:59:55 +08:00

341 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 可执行文件
```