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 }