403 lines
11 KiB
Go
403 lines
11 KiB
Go
package services
|
|
|
|
import (
|
|
models2 "cmii-uav-watchdog-common/models"
|
|
"cmii-uav-watchdog-common/totp_tier_one"
|
|
"cmii-uav-watchdog-common/totp_tier_two"
|
|
"cmii-uav-watchdog-common/utils"
|
|
"cmii-uav-watchdog-common/wdd_log"
|
|
"cmii-uav-watchdog/config"
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
// 单例相关变量
|
|
var (
|
|
authServiceInstance *AuthService
|
|
authServiceOnce sync.Once
|
|
authServiceMutex sync.Mutex
|
|
)
|
|
|
|
// AuthService 授权服务
|
|
type AuthService struct {
|
|
mu sync.RWMutex
|
|
heartBeatHostInfoMap map[string]models2.HostInfo // 主机信息集合
|
|
authorizationInfo *models2.AuthorizationStorage // 授权信息
|
|
totpService *TOTPService
|
|
initialized bool
|
|
}
|
|
|
|
// NewAuthService 创建授权服务(单例模式)
|
|
func NewAuthService() *AuthService {
|
|
// 使用sync.Once确保初始化逻辑只执行一次
|
|
authServiceOnce.Do(func() {
|
|
authServiceMutex.Lock()
|
|
defer authServiceMutex.Unlock()
|
|
|
|
// 如果实例已存在,直接返回
|
|
if authServiceInstance != nil {
|
|
return
|
|
}
|
|
|
|
// 创建新实例
|
|
service := &AuthService{
|
|
heartBeatHostInfoMap: make(map[string]models2.HostInfo),
|
|
totpService: NewTOTPService(),
|
|
initialized: false,
|
|
}
|
|
|
|
// 尝试从本地加载授权信息
|
|
service.loadAuthorizationInfo()
|
|
|
|
// 确保 authorizationInfo 不为 nil
|
|
if service.authorizationInfo == nil {
|
|
service.authorizationInfo = &models2.AuthorizationStorage{}
|
|
}
|
|
|
|
// 判断 项目级别的 TOTP密钥是否为空
|
|
// 若为空 则生成一个 二级TOTP密钥 然后持久化写入到授权文件中
|
|
if service.authorizationInfo.SecondTOTPSecret == "" {
|
|
secondTOTPSecret, err := totp_tier_two.GenerateTierTwoTOTPSecret()
|
|
if err != nil {
|
|
wdd_log.Error("生成二级TOTP密钥失败: %v", err)
|
|
return
|
|
}
|
|
service.authorizationInfo.SecondTOTPSecret = secondTOTPSecret
|
|
|
|
// 持久化写入到授权文件中
|
|
err = service.saveAuthorizationInfo()
|
|
if err != nil {
|
|
wdd_log.Error("持久化写入授权文件失败: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// 设置全局实例
|
|
authServiceInstance = service
|
|
})
|
|
|
|
return authServiceInstance
|
|
}
|
|
|
|
// AddHostInfo 添加主机信息
|
|
func (as *AuthService) AddHostInfo(hostInfo models2.HostInfo) {
|
|
as.mu.Lock()
|
|
defer as.mu.Unlock()
|
|
|
|
hostKey, err := totp_tier_one.EncryptHostInfo(hostInfo, as.totpService.tierOneSecret)
|
|
if err != nil {
|
|
wdd_log.Error("加密主机信息失败: %v", err)
|
|
return
|
|
}
|
|
|
|
as.heartBeatHostInfoMap[hostKey] = hostInfo
|
|
}
|
|
|
|
// GenerateAuthorizationFile 生成授权文件
|
|
func (as *AuthService) GenerateAuthorizationFile() (*models2.AuthorizationFile, error) {
|
|
as.mu.RLock()
|
|
defer as.mu.RUnlock()
|
|
|
|
// 检查是否有主机信息
|
|
if len(as.heartBeatHostInfoMap) == 0 {
|
|
return nil, errors.New("没有可用的主机信息")
|
|
}
|
|
|
|
// 加密主机信息
|
|
encryptedHostMap := make(map[string]models2.HostInfo)
|
|
for _, hostInfo := range as.heartBeatHostInfoMap {
|
|
encrypted, err := totp_tier_one.EncryptHostInfo(hostInfo, as.totpService.tierOneSecret)
|
|
if err != nil {
|
|
wdd_log.Error("加密主机信息失败: %v hostInfo NetworkInfo: %v", err, hostInfo.NetInfo)
|
|
}
|
|
encryptedHostMap[encrypted] = hostInfo
|
|
}
|
|
|
|
// 获取项目命名空间
|
|
projectNamespace := config.GetConfig().Project.ProjectNamespace
|
|
encryptedNamespace, err := totp_tier_one.Encrypt(projectNamespace, projectNamespace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 生成TOTP验证码
|
|
tierOneSecret := config.GetConfig().TierOneAuth.TierOneSecret
|
|
tierOneTOTPCode, err := totp_tier_one.GenerateTierOneTOTPCode(tierOneSecret)
|
|
if err != nil {
|
|
wdd_log.Error("生成TOTP验证码失败: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
// 创建授权文件
|
|
authFile := &models2.AuthorizationFile{
|
|
EncryptedHostMap: encryptedHostMap,
|
|
TOTPCode: tierOneTOTPCode,
|
|
CurrentTime: utils.CurentTimeString(),
|
|
ProjectNamespace: projectNamespace,
|
|
EncryptedNamespace: encryptedNamespace,
|
|
}
|
|
|
|
return authFile, nil
|
|
}
|
|
|
|
// ProcessAuthorizationCode 处理授权码
|
|
func (as *AuthService) ProcessAuthorizationCode(authCode models2.AuthorizationCode) error {
|
|
as.mu.Lock()
|
|
defer as.mu.Unlock()
|
|
|
|
// 验证TOTP
|
|
if !as.totpService.VerifyTierOneTOTP(authCode.TOTPCode) {
|
|
wdd_log.Error("无效的授权码: TOTP验证失败")
|
|
return errors.New("无效的授权码: TOTP验证失败")
|
|
}
|
|
|
|
// 解密项目命名空间
|
|
projectNamespace, err := totp_tier_one.Decrypt(authCode.EncryptedNamespace, authCode.ProjectNamespace)
|
|
if err != nil {
|
|
wdd_log.Error("解密项目命名空间失败: %v", err)
|
|
return err
|
|
}
|
|
|
|
// 验证项目命名空间
|
|
if projectNamespace != config.GetConfig().Project.ProjectNamespace {
|
|
wdd_log.Error("无效的授权码: 项目命名空间不匹配")
|
|
return errors.New("无效的授权码: 项目命名空间不匹配")
|
|
}
|
|
|
|
// 解密服务器信息
|
|
encryptedServerInfoMap := authCode.EncryptedHostMap
|
|
serverInfoMap := make(map[string]models2.HostInfo)
|
|
for encrypted, hostInfo := range encryptedServerInfoMap {
|
|
isOK, err := totp_tier_one.DecryptHostInfo(encrypted, hostInfo, as.totpService.tierOneSecret)
|
|
if !isOK || err != nil {
|
|
wdd_log.Error("解密服务器信息失败: %v hostInfo NetworkInfo: %v", err, hostInfo.NetInfo)
|
|
continue
|
|
}
|
|
// 将解密后的主机信息添加到serverInfoMap中
|
|
serverInfoMap[encrypted] = hostInfo
|
|
// 将解密后的主机信息添加到hostInfoSet中
|
|
as.heartBeatHostInfoMap[encrypted] = hostInfo
|
|
}
|
|
|
|
// 获取当前时间
|
|
now := utils.CurentTimeString()
|
|
|
|
// 准备初次授权时间
|
|
firstAuthTime := now
|
|
if as.initialized {
|
|
if as.authorizationInfo.FirstAuthTime != "" {
|
|
wdd_log.Debug("已存在初次授权时间: %s", as.authorizationInfo.FirstAuthTime)
|
|
firstAuthTime = as.authorizationInfo.FirstAuthTime
|
|
}
|
|
}
|
|
|
|
// 计算授权时间偏移
|
|
nowTime, err := utils.ParseTimeString(now)
|
|
if err != nil {
|
|
wdd_log.Error("解析当前时间失败: %v", err)
|
|
return err
|
|
}
|
|
firstAuthTimeTime, err := utils.ParseTimeString(firstAuthTime)
|
|
if err != nil {
|
|
wdd_log.Error("解析初次授权时间失败: %v", err)
|
|
return err
|
|
}
|
|
timeOffset := nowTime.Unix() - firstAuthTimeTime.Unix()
|
|
|
|
// 加密授权码
|
|
authCodeJSON, err := json.Marshal(authCode)
|
|
if err != nil {
|
|
wdd_log.Error("加密授权码失败: %v", err)
|
|
return err
|
|
}
|
|
|
|
// 加密授权码 全部加密
|
|
encryptedCode, err := totp_tier_one.Encrypt(string(authCodeJSON), as.totpService.tierOneSecret)
|
|
if err != nil {
|
|
wdd_log.Error("加密授权码失败: %v", err)
|
|
return err
|
|
}
|
|
|
|
// 保存授权信息
|
|
as.authorizationInfo = &models2.AuthorizationStorage{
|
|
EncryptedAuthrizationCode: encryptedCode,
|
|
FirstAuthTime: firstAuthTime,
|
|
TimeOffset: timeOffset,
|
|
AuthorizedHostMap: serverInfoMap,
|
|
SecondTOTPSecret: as.authorizationInfo.SecondTOTPSecret,
|
|
ProjectNamespace: projectNamespace,
|
|
EncryptedNamespace: authCode.EncryptedNamespace,
|
|
}
|
|
|
|
// 保存到文件
|
|
if err := as.saveAuthorizationInfo(); err != nil {
|
|
wdd_log.Error("保存授权信息失败: %v", err)
|
|
return err
|
|
}
|
|
|
|
// 设置初始化状态
|
|
as.initialized = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsHostAuthorized 检查主机是否已授权
|
|
func (as *AuthService) IsHostAuthorized(hostInfo models2.HostInfo) bool {
|
|
as.mu.RLock()
|
|
defer as.mu.RUnlock()
|
|
|
|
if !as.initialized {
|
|
return false
|
|
}
|
|
|
|
// 加密主机信息
|
|
encrypted, err := totp_tier_one.EncryptHostInfo(hostInfo, as.totpService.tierOneSecret)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// 检查是否在已授权列表中
|
|
_, ok := as.authorizationInfo.AuthorizedHostMap[encrypted]
|
|
return ok
|
|
}
|
|
|
|
// VerifyAuthorizationTime 验证授权时间有效性
|
|
func (as *AuthService) VerifyAuthorizationTime() {
|
|
as.mu.RLock()
|
|
defer as.mu.RUnlock()
|
|
|
|
if !as.initialized {
|
|
return
|
|
}
|
|
|
|
// 获取当前时间和存储的初次授权时间
|
|
now := utils.CurentTimeString()
|
|
storedOffset := as.authorizationInfo.TimeOffset
|
|
nowTime, err := utils.ParseTimeString(now)
|
|
if err != nil {
|
|
return
|
|
}
|
|
firstAuthTime, err := utils.ParseTimeString(as.authorizationInfo.FirstAuthTime)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// 计算实际时间偏移
|
|
actualOffset := nowTime.Unix() - firstAuthTime.Unix()
|
|
|
|
// 计算偏差
|
|
offsetDiff := actualOffset - storedOffset
|
|
|
|
// 获取允许的时间偏移
|
|
allowedOffset := config.GetConfig().TierOneAuth.TimeOffsetAllowed
|
|
|
|
// 检查偏差是否超过允许范围
|
|
if offsetDiff > allowedOffset || offsetDiff < -allowedOffset {
|
|
wdd_log.Warn("检测到时间篡改! 存储偏移: %d, 实际偏移: %d, 差值: %d",
|
|
storedOffset, actualOffset, offsetDiff)
|
|
// 这里可以添加更多的处理逻辑,例如发送警告、禁用授权等
|
|
}
|
|
}
|
|
|
|
// GetAuthorizationInfo 获取授权信息
|
|
func (as *AuthService) GetAuthorizationInfo() (models2.AuthorizationStorage, error) {
|
|
as.mu.RLock()
|
|
defer as.mu.RUnlock()
|
|
|
|
if !as.initialized {
|
|
return models2.AuthorizationStorage{}, errors.New("未授权")
|
|
}
|
|
|
|
// 解密授权码
|
|
decryptedCode, err := totp_tier_one.Decrypt(as.authorizationInfo.EncryptedAuthrizationCode, as.totpService.tierOneSecret)
|
|
if err != nil {
|
|
return models2.AuthorizationStorage{}, err
|
|
}
|
|
|
|
// 解析授权码
|
|
var authorizationInfo models2.AuthorizationStorage
|
|
if err := json.Unmarshal([]byte(decryptedCode), &authorizationInfo); err != nil {
|
|
return models2.AuthorizationStorage{}, err
|
|
}
|
|
|
|
return authorizationInfo, nil
|
|
}
|
|
|
|
// GetAllHostInfo 获取所有主机信息(心跳检测到的所有主机)
|
|
func (as *AuthService) GetAllHostInfo() map[string]models2.HostInfo {
|
|
as.mu.RLock()
|
|
defer as.mu.RUnlock()
|
|
|
|
// 返回主机信息集合的副本
|
|
result := make(map[string]models2.HostInfo, len(as.heartBeatHostInfoMap))
|
|
for k, v := range as.heartBeatHostInfoMap {
|
|
result[k] = v
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GetAllAuthorizedHosts 获取所有已授权的主机信息
|
|
func (as *AuthService) GetAllAuthorizedHosts() map[string]models2.HostInfo {
|
|
as.mu.RLock()
|
|
defer as.mu.RUnlock()
|
|
|
|
if !as.initialized || as.authorizationInfo == nil {
|
|
return make(map[string]models2.HostInfo)
|
|
}
|
|
|
|
// 返回已授权主机信息的副本
|
|
result := make(map[string]models2.HostInfo, len(as.authorizationInfo.AuthorizedHostMap))
|
|
for k, v := range as.authorizationInfo.AuthorizedHostMap {
|
|
result[k] = v
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// saveAuthorizationInfo 保存授权信息到文件
|
|
func (as *AuthService) saveAuthorizationInfo() error {
|
|
data, err := json.Marshal(as.authorizationInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(config.GetConfig().TierOneAuth.LocalAuthFilePath, data, 0600)
|
|
}
|
|
|
|
// loadAuthorizationInfo 从文件加载授权信息
|
|
func (as *AuthService) loadAuthorizationInfo() {
|
|
filePath := config.GetConfig().TierOneAuth.LocalAuthFilePath
|
|
|
|
// 检查文件是否存在
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
// 初始化一个空的授权信息
|
|
as.authorizationInfo = &models2.AuthorizationStorage{}
|
|
return
|
|
}
|
|
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
wdd_log.Error("读取授权文件失败: %v", err)
|
|
// 初始化一个空的授权信息
|
|
as.authorizationInfo = &models2.AuthorizationStorage{}
|
|
return
|
|
}
|
|
|
|
var authInfo *models2.AuthorizationStorage
|
|
if err := json.Unmarshal(data, &authInfo); err != nil {
|
|
wdd_log.Error("解析授权文件失败: %v", err)
|
|
// 初始化一个空的授权信息
|
|
as.authorizationInfo = &models2.AuthorizationStorage{}
|
|
return
|
|
}
|
|
|
|
if authInfo == nil {
|
|
authInfo = &models2.AuthorizationStorage{}
|
|
}
|
|
|
|
as.authorizationInfo = authInfo
|
|
as.initialized = true
|
|
}
|