- 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
204 lines
5.6 KiB
Go
204 lines
5.6 KiB
Go
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. 生成随机 SeedID(16 字节随机数)
|
||
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
|
||
}
|