Files
zkcoi e9ca2f7d70 fix: rename manager module to git.zkcoi.com/zkcoi/Meshray-Manager
- go.mod module path matches repo zkcoi/Meshray-Manager (case-distinct from zkcoi/Meshray/core)
- rewrite internal imports meshray/{internal,web,pkg} -> Meshray-Manager/... (core refs kept)
- sync README.md / install.sh repo URLs; add CHANGELOG entry
2026-07-15 16:25:46 +08:00

204 lines
5.6 KiB
Go
Raw Permalink 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.
package service
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"time"
"git.zkcoi.com/zkcoi/Meshray-Manager/internal/model"
sqlite "git.zkcoi.com/zkcoi/Meshray-Manager/internal/store/sqlite"
"go.uber.org/zap"
"golang.org/x/crypto/ed25519"
"gorm.io/gorm"
)
// MeshSeedService MeshSeed 服务
type MeshSeedService struct {
store *sqlite.Store
logger *zap.Logger
signingKey ed25519.PrivateKey // Ed25519 签名密钥
issuerNodeID string // 签发节点 ID
}
// NewMeshSeedService 创建 MeshSeed 服务
func NewMeshSeedService(store *sqlite.Store, logger *zap.Logger, signingKey ed25519.PrivateKey, issuerNodeID string) *MeshSeedService {
return &MeshSeedService{
store: store,
logger: logger,
signingKey: signingKey,
issuerNodeID: issuerNodeID,
}
}
// GenerateMeshSeed 生成 MeshSeed
func (s *MeshSeedService) GenerateMeshSeed(networkID uint64, maxUses int, expiresAt time.Time, ddnsEnabled bool) (*model.MeshSeed, error) {
// 1. 验证网络是否存在
var network model.Network
if err := s.store.DB().First(&network, networkID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("网络不存在")
}
return nil, fmt.Errorf("查询网络失败:%w", err)
}
// 2. 生成随机 SeedID16 字节随机数)
seedBytes := make([]byte, 16)
if _, err := rand.Read(seedBytes); err != nil {
return nil, fmt.Errorf("生成随机数失败:%w", err)
}
seedID := base64.RawURLEncoding.EncodeToString(seedBytes)
// 3. 构建 JoinToken(包含网络信息)
joinTokenData := map[string]interface{}{
"seed_id": seedID,
"network_id": networkID,
"network_name": network.Name,
"subnet_ipv4": network.SubnetIPv4,
"mode": network.Mode,
"ddns_enabled": ddnsEnabled,
"expires_at": expiresAt.Unix(),
"max_uses": maxUses,
}
// 序列化为 JSON
tokenJSON, err := json.Marshal(joinTokenData)
if err != nil {
return nil, fmt.Errorf("序列化 Token 失败:%w", err)
}
// Base64 编码
joinToken := base64.StdEncoding.EncodeToString(tokenJSON)
// 4. Ed25519 签名
signature := ed25519.Sign(s.signingKey, []byte(joinToken))
signatureStr := base64.StdEncoding.EncodeToString(signature)
// 5. 创建 MeshSeed 记录
meshSeed := &model.MeshSeed{
SeedID: seedID,
NetworkID: networkID,
JoinToken: joinToken,
Signature: signatureStr,
IssuerNodeID: s.issuerNodeID,
MaxUses: maxUses,
UsedCount: 0,
ExpiresAt: expiresAt,
DDNSEnabled: ddnsEnabled,
UpdateVersion: 0,
Revoked: false,
}
if err := s.store.DB().Create(meshSeed).Error; err != nil {
return nil, fmt.Errorf("创建 MeshSeed 失败:%w", err)
}
s.logger.Info("MeshSeed 已生成",
zap.String("seed_id", seedID),
zap.Uint64("network_id", networkID),
zap.Int("max_uses", maxUses),
zap.Time("expires_at", expiresAt))
return meshSeed, nil
}
// VerifyMeshSeed 验证 MeshSeed
func (s *MeshSeedService) VerifyMeshSeed(joinToken, signature string) (*model.MeshSeed, error) {
// 1. 解码 JoinToken
tokenBytes, err := base64.StdEncoding.DecodeString(joinToken)
if err != nil {
return nil, fmt.Errorf("解码 Token 失败:%w", err)
}
// 2. 解码签名
sigBytes, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
return nil, fmt.Errorf("解码签名失败:%w", err)
}
// 3. 验证 Ed25519 签名
publicKey := s.signingKey.Public()
if !ed25519.Verify(publicKey.(ed25519.PublicKey), tokenBytes, sigBytes) {
return nil, fmt.Errorf("签名验证失败")
}
// 4. 解析 Token 内容
var tokenData map[string]interface{}
if err := json.Unmarshal(tokenBytes, &tokenData); err != nil {
return nil, fmt.Errorf("解析 Token 失败:%w", err)
}
seedID, ok := tokenData["seed_id"].(string)
if !ok {
return nil, fmt.Errorf("Token 格式错误")
}
// 5. 查询 MeshSeed
var meshSeed model.MeshSeed
if err := s.store.DB().Where("seed_id = ?", seedID).First(&meshSeed).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("MeshSeed 不存在")
}
return nil, fmt.Errorf("查询 MeshSeed 失败:%w", err)
}
// 6. 检查是否被吊销
if meshSeed.Revoked {
return nil, fmt.Errorf("MeshSeed 已被吊销")
}
// 7. 检查使用次数
if meshSeed.UsedCount >= meshSeed.MaxUses {
return nil, fmt.Errorf("MeshSeed 使用次数已用尽")
}
// 8. 检查过期时间
if time.Now().After(meshSeed.ExpiresAt) {
return nil, fmt.Errorf("MeshSeed 已过期")
}
return &meshSeed, nil
}
// IncrementUseCount 增加使用次数
func (s *MeshSeedService) IncrementUseCount(seedID string) error {
return s.store.DB().Transaction(func(tx *gorm.DB) error {
var meshSeed model.MeshSeed
if err := tx.Where("seed_id = ?", seedID).First(&meshSeed).Error; err != nil {
return err
}
return tx.Model(&meshSeed).UpdateColumn("used_count", meshSeed.UsedCount+1).Error
})
}
// RevokeMeshSeed 吊销 MeshSeed
func (s *MeshSeedService) RevokeMeshSeed(seedID string) error {
result := s.store.DB().Model(&model.MeshSeed{}).
Where("seed_id = ?", seedID).
Update("revoked", true)
if result.Error != nil {
return fmt.Errorf("吊销 MeshSeed 失败:%w", result.Error)
}
if result.RowsAffected == 0 {
return fmt.Errorf("MeshSeed 不存在")
}
s.logger.Info("MeshSeed 已吊销", zap.String("seed_id", seedID))
return nil
}
// ListMeshSeeds 获取网络的 MeshSeed 列表
func (s *MeshSeedService) ListMeshSeeds(networkID uint) ([]model.MeshSeed, error) {
var seeds []model.MeshSeed
err := s.store.DB().Where("network_id = ? AND revoked = ?", networkID, false).
Order("created_at DESC").
Find(&seeds).Error
return seeds, err
}