版本封存

This commit is contained in:
zeaslity
2025-12-06 11:26:05 +08:00
parent 13949e1ba8
commit c0ae5e30c4
57 changed files with 2443 additions and 1428 deletions

2
.gitignore vendored
View File

@@ -18,3 +18,5 @@ build
.cursor/rules
cmii-uav-watchdog-project-设计结构

13
.run/agent-35-70.run.xml Normal file
View File

@@ -0,0 +1,13 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="agent-35-70" type="GoApplicationRunConfiguration" factoryName="Go Application">
<module name="cmii-uav-watchdog-project" />
<target name="wdd-dev-35.70" />
<working_directory value="$PROJECT_DIR$/cmii-uav-watchdog-agent" />
<kind value="PACKAGE" />
<package value="cmii-uav-watchdog-agent/cmd" />
<directory value="$PROJECT_DIR$" />
<filePath value="$PROJECT_DIR$/cmii-uav-watchdog-agent/cmd/start_up.go" />
<option name="build_on_remote_target" value="true" />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,47 @@
package main
import (
"os"
"sync"
"cmii-uav-watchdog-common/models"
)
var PodEnv = models.EnvInfo{}
func init() {
PodEnv = GetEnvInfo()
}
// GetEnvInfo 获取环境信息
// 单例模式, 如果已经初始化过, 则直接返回
func GetEnvInfo() models.EnvInfo {
var once sync.Once
if PodEnv.K8S_NAMESPACE != "" {
return PodEnv
}
once.Do(func() {
PodEnv = models.EnvInfo{
K8S_NAMESPACE: os.Getenv("K8S_NAMESPACE"),
APPLICATION_NAME: os.Getenv("APPLICATION_NAME"),
CUST_JAVA_OPTS: os.Getenv("CUST_JAVA_OPTS"),
BIZ_CONFIG_GROUP: os.Getenv("BIZ_CONFIG_GROUP"),
SYS_CONFIG_GROUP: os.Getenv("SYS_CONFIG_GROUP"),
IMAGE_NAME: os.Getenv("IMAGE_NAME"),
JAVA_VERSION: os.Getenv("JAVA_VERSION"),
GIT_COMMIT: os.Getenv("GIT_COMMIT"),
GIT_BRANCH: os.Getenv("GIT_BRANCH"),
NODE_NAME: os.Getenv("NODE_NAME"),
NODE_IP: os.Getenv("NODE_IP"),
POD_NAME: os.Getenv("POD_NAME"),
LIMIT_CPU: os.Getenv("LIMIT_CPU"),
LIMIT_MEMORY: os.Getenv("LIMIT_MEMORY"),
REQUEST_CPU: os.Getenv("REQUEST_CPU"),
REQUEST_MEMORY: os.Getenv("REQUEST_MEMORY"),
}
})
return PodEnv
}

View File

@@ -1 +1,70 @@
package cmd
package main
// import (
// "cmii-uav-watchdog-agent/host_info"
// "cmii-uav-watchdog-common/wdd_log"
// "net/http"
// "os"
// "os/signal"
// "syscall"
// "github.com/gin-gonic/gin"
// )
// func StartHostInfoGin() {
// // 创建一个默认的 Gin 路由
// var r = gin.Default() // 定义一个 GET 路由
// r.GET("/ping", func(c *gin.Context) {
// c.JSON(http.StatusOK, gin.H{"message": "pong"})
// })
// // 定义一个 POST 路由
// r.POST("/echo", func(c *gin.Context) {
// var json map[string]interface{}
// if err := c.ShouldBindJSON(&json); err == nil {
// c.JSON(http.StatusOK, json)
// } else {
// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
// }
// })
// r.GET("/cpu", func(c *gin.Context) {
// cpuInfo := host_info.GetCPUInfo() // 直接返回 CPU 信息
// c.JSON(http.StatusOK, cpuInfo)
// })
// r.GET("/memory", func(c *gin.Context) {
// memInfo := host_info.GetMemoryInfo() // 直接返回内存信息
// c.JSON(http.StatusOK, memInfo)
// })
// r.GET("/disk", func(c *gin.Context) {
// diskInfo := host_info.GetDiskInfo() // 直接返回磁盘信息
// c.JSON(http.StatusOK, diskInfo)
// })
// r.GET("/motherboard", func(c *gin.Context) {
// mbInfo := host_info.GetMotherboardInfo() // 直接返回主板信息
// c.JSON(http.StatusOK, mbInfo)
// })
// r.GET("/network", func(c *gin.Context) {
// networkInterfaces := host_info.GetNetworkInterfaces()
// c.JSON(http.StatusOK, networkInterfaces)
// })
// r.GET("/all", func(c *gin.Context) {
// allInfo := host_info.GetAllInfo()
// c.JSON(http.StatusOK, allInfo)
// })
// //r.GET("/phy", func(c *gin.Context) {
// // allInfo, _ := services.GetPVForLV()
// // c.JSON(http.StatusOK, allInfo)
// //})
// // 启动服务,监听在 8080 端口
// r.Run(":8098")
// // 等待终止信号
// sigs := make(chan os.Signal, 1)
// signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
// <-sigs
// wdd_log.Info("正在关闭服务...")
// }

View File

@@ -1,16 +1,20 @@
package cmd
package main
import (
"cmii-uav-watchdog-common/wdd_log"
"flag"
"log"
"os"
"os/exec"
"os/signal"
"strings"
"sync"
"syscall"
"time"
)
// 是否是Debug模式
var DebugMode = false
var (
businessProgramType = flag.String("business-program-type", "", "Type of business program (java or python)")
businessProgramPath = flag.String("business-program-path", "", "Path to the business program file")
@@ -19,45 +23,221 @@ var (
mu sync.Mutex
)
// java 启动参数
var (
podName = "unknown"
imageName = "unknown"
imageVersion = "unknown"
workpath = "/cmii"
custJavaOpts = "-Xms200m -Xmx1500m -Djava.awt.headless=true -Dlog4j2.formatMsgNoLookups=true "
envJvmTimezone = "Asia/Shanghai"
k8sNamespace = "default"
applicationName = "app"
nacosRegistry = "helm-nacos:8848"
nacosDiscoveryIp = "127.0.0.1"
nacosDiscoveryPort = "8080"
nacosUsername = "nacos"
nacosPassword = "nacos"
bizConfigGroup = "wdd-biz"
sysConfigGroup = "wdd-sys"
)
// 初始化
func init() {
// 获取环境变量 特殊设置才能开启DEBUG
debugMode := os.Getenv("CMII_DEBUG_MODE")
if debugMode == "WDD_DEBUG" {
DebugMode = true
}
wdd_log.Info("DebugMode 是否开启: %v", DebugMode)
}
// 初始化配置并打印配置信息
func initConfig() {
// 获取环境变量
imageName = getEnvOrDefault("IMAGE_NAME", imageName)
if imageName != "unknown" {
parts := strings.Split(imageName, ":")
if len(parts) > 1 {
imageVersion = parts[len(parts)-1]
}
}
workpath = getEnvOrDefault("WORKPATH", workpath)
custJavaOpts = getEnvOrDefault("CUST_JAVA_OPTS", custJavaOpts)
envJvmTimezone = getEnvOrDefault("ENV_JVM_TIMEZONE", envJvmTimezone)
k8sNamespace = getEnvOrDefault("K8S_NAMESPACE", k8sNamespace)
applicationName = getEnvOrDefault("APPLICATION_NAME", applicationName)
nacosRegistry = getEnvOrDefault("NACOS_REGISTRY", nacosRegistry)
nacosDiscoveryIp = getEnvOrDefault("NACOS_DISCOVERY_IP", nacosDiscoveryIp)
nacosDiscoveryPort = getEnvOrDefault("NACOS_DISCOVERY_PORT", nacosDiscoveryPort)
nacosUsername = getEnvOrDefault("NACOS_USERNAME", nacosUsername)
nacosPassword = getEnvOrDefault("NACOS_PASSWORD", nacosPassword)
// 设置默认配置组
defaultConfigGroup := "default"
if imageVersion != "unknown" {
parts := strings.Split(imageVersion, "-")
if len(parts) > 0 {
defaultConfigGroup = parts[0]
}
}
bizConfigGroup = getEnvOrDefault("BIZ_CONFIG_GROUP", bizConfigGroup)
if bizConfigGroup == "" {
wdd_log.Info("[CONTAINER] As BIZ_CONFIG_GROUP is null, it set default value [%s]", defaultConfigGroup)
bizConfigGroup = defaultConfigGroup
os.Setenv("BIZ_CONFIG_GROUP", bizConfigGroup)
}
sysConfigGroup = getEnvOrDefault("SYS_CONFIG_GROUP", sysConfigGroup)
if sysConfigGroup == "" {
wdd_log.Info("[CONTAINER] As SYS_CONFIG_GROUP is null, it set default value [%s]", defaultConfigGroup)
sysConfigGroup = defaultConfigGroup
os.Setenv("SYS_CONFIG_GROUP", sysConfigGroup)
}
// 打印配置信息
wdd_log.Info("[CONTAINER] %s image is running ...", imageName)
wdd_log.Info("[CONTAINER] IMAGE_VERSION is %s", imageVersion)
wdd_log.Info("[CONTAINER] WORKPATH is %s", workpath)
wdd_log.Info("[CONTAINER] CUST_JAVA_OPTS is %s", custJavaOpts)
wdd_log.Info("[CONTAINER] JVM_TIMEZONE is %s", envJvmTimezone)
wdd_log.Info("[CONTAINER] K8S_NAMESPACE is %s", k8sNamespace)
wdd_log.Info("[CONTAINER] APPLICATION_NAME is %s", applicationName)
wdd_log.Info("[CONTAINER] NACOS_REGISTRY is %s", nacosRegistry)
wdd_log.Info("[CONTAINER] NACOS_DISCOVERY_IP is %s", nacosDiscoveryIp)
wdd_log.Info("[CONTAINER] NACOS_DISCOVERY_PORT is %s", nacosDiscoveryPort)
wdd_log.Info("[CONTAINER] NACOS_USERNAME is %s", nacosUsername)
wdd_log.Info("[CONTAINER] NACOS_PASSWORD is %s", nacosPassword)
wdd_log.Info("[CONTAINER] BIZ_CONFIG_GROUP is %s", bizConfigGroup)
wdd_log.Info("[CONTAINER] SYS_CONFIG_GROUP is %s", sysConfigGroup)
wdd_log.Info("[CONTAINER] starting...")
}
// 获取环境变量,如果为空则返回默认值
func getEnvOrDefault(key, defaultValue string) string {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
return value
}
func startBusinessProcess(programType, programPath string) *exec.Cmd {
var cmd *exec.Cmd
switch programType {
case "java":
cmd = exec.Command("java", "-jar", programPath)
// 初始化配置
initConfig()
// 构建命令参数列表
args := []string{}
// 添加Java选项
if custJavaOpts != "" {
// 分割CUST_JAVA_OPTS中的多个参数
for _, opt := range splitArgs(custJavaOpts) {
if opt != "" {
args = append(args, opt)
}
}
}
// 添加主JAR文件
args = append(args, "-jar", programPath)
// 添加其他参数
args = append(args, []string{
"--user.timezone=" + envJvmTimezone,
"-Dfile.encoding=UTF-8",
"--spring.main.allow-bean-definition-overriding=true",
"--spring.application.name=" + applicationName,
"--spring.cloud.nacos.username=" + nacosUsername,
"--spring.cloud.nacos.password=" + nacosPassword,
"--spring.cloud.nacos.config.server-addr=" + nacosRegistry,
"--spring.cloud.nacos.config.extension-configs[0].data-id=" + applicationName + ".yml",
"--spring.cloud.nacos.config.extension-configs[0].group=" + bizConfigGroup,
"--spring.cloud.nacos.config.extension-configs[0].refresh=true",
"--spring.cloud.nacos.config.shared-configs[0].data-id=cmii-backend-system.yml",
"--spring.cloud.nacos.config.shared-configs[0].group=" + sysConfigGroup,
"--spring.cloud.nacos.config.shared-configs[0].refresh=true",
"--spring.cloud.nacos.discovery.server-addr=" + nacosRegistry,
"--spring.cloud.nacos.discovery.ip=" + nacosDiscoveryIp,
"--spring.cloud.nacos.discovery.port=" + nacosDiscoveryPort,
}...)
wdd_log.Info("[CONTAINER] java args: %v", args)
cmd = exec.Command("java", args...)
case "python":
cmd = exec.Command("python", programPath)
default:
log.Fatalf("Unsupported business program type: %s", programType)
wdd_log.Error("不支持的业务程序类型: %s", programType)
}
return cmd
}
// 分割命令行参数
func splitArgs(s string) []string {
var args []string
var inQuote bool
var current string
var quoteChar rune
for _, r := range s {
switch {
case (r == '"' || r == '\'') && !inQuote:
inQuote = true
quoteChar = r
case r == quoteChar && inQuote:
inQuote = false
quoteChar = 0
case r == ' ' && !inQuote:
if current != "" {
args = append(args, current)
current = ""
}
default:
current += string(r)
}
}
if current != "" {
args = append(args, current)
}
return args
}
func main() {
// 解析命令行参数
flag.Parse()
if *businessProgramType == "" || *businessProgramPath == "" {
log.Fatal("Missing required flags: -business-program-type and -business-program-path must be specified")
wdd_log.Error("缺少必要的参数: -business-program-type -business-program-path 必须指定")
}
// 信号处理
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
// StartHeartbeatDetection(signalChan)
go func() {
for sig := range signalChan {
log.Printf("Received signal: %v", sig)
wdd_log.Info("接收到信号: %v", sig)
mu.Lock()
stopRequested = true
if currentCmd != nil && currentCmd.Process != nil {
// 发送 SIGTERM 给业务进程
if err := currentCmd.Process.Signal(syscall.SIGTERM); err != nil {
log.Printf("Failed to send SIGTERM to process: %v", err)
wdd_log.Error("向进程发送SIGTERM信号失败: %v", err)
}
// 等待 10 秒后强制杀死进程
time.AfterFunc(10*time.Second, func() {
mu.Lock()
defer mu.Unlock()
if currentCmd != nil && currentCmd.Process != nil {
log.Println("Graceful shutdown timeout, sending SIGKILL")
wdd_log.Warn("优雅关闭超时,发送SIGKILL信号")
currentCmd.Process.Kill()
}
})
@@ -66,12 +246,19 @@ func main() {
}
}()
// 授权检测
go func() {
for {
StartHeartbeatDetection(signalChan)
}
}()
// 主循环
for {
mu.Lock()
if stopRequested {
mu.Unlock()
log.Println("Shutting down due to stop request")
wdd_log.Info("收到停止请求,正在关闭")
os.Exit(0)
}
mu.Unlock()
@@ -82,11 +269,16 @@ func main() {
// 启动业务进程
if err := cmd.Start(); err != nil {
log.Printf("Failed to start business process: %v", err)
wdd_log.Error("启动业务进程失败: %v", err)
time.Sleep(5 * time.Second)
continue
}
// 业务进程启动成功
if *businessProgramType == "java" {
wdd_log.Info("[CONTAINER] 程序启动成功!")
}
mu.Lock()
currentCmd = cmd
mu.Unlock()
@@ -98,21 +290,21 @@ func main() {
mu.Unlock()
if err != nil {
log.Printf("Business process exited with error: %v", err)
wdd_log.Error("业务进程异常退出: %v", err)
} else {
log.Println("Business process exited normally")
wdd_log.Info("业务进程正常退出")
}
mu.Lock()
if stopRequested {
mu.Unlock()
log.Println("Shutting down due to stop request")
wdd_log.Info("收到停止请求,正在关闭")
os.Exit(0)
}
mu.Unlock()
// 等待 5 秒后重启
log.Println("Restarting business process in 5 seconds...")
wdd_log.Info("5秒后重启业务进程...")
time.Sleep(5 * time.Second)
}
}

View File

@@ -1,76 +1,95 @@
package cmd
package main
import (
"cmii-uav-watchdog-agent/host_info"
"cmii-uav-watchdog-agent/rpc"
"cmii-uav-watchdog-agent/totp"
"cmii-uav-watchdog-common/models"
"fmt"
"log"
"cmii-uav-watchdog-common/totp_tier_two"
"cmii-uav-watchdog-common/wdd_log"
"os"
"os/signal"
"syscall"
"time"
)
const (
var (
// 最大重试次数
maxRetryCount = 5
maxRetryCount = 12
// 默认心跳检测间隔
defaultHeartbeatInterval = 30 * time.Second
defaultHeartbeatInterval = 2 * time.Hour
// 检测失败后的等待间隔
failWaitInterval = 5 * time.Second
// 环境变量名称
appNameEnv = "APP_NAME"
failWaitInterval = 1 * time.Hour
)
// 启动心跳检测
func StartHeartbeatDetection() {
log.Println("启动心跳检测任务...")
var tierTwoTotpSecret = ""
// StartHeartbeatDetection 启动心跳检测
func StartHeartbeatDetection(signalChan chan os.Signal) {
wdd_log.Info("启动心跳检测任务...")
// variable
var err error
// 如果Debug模式那么使用环境变量赋值
if DebugMode {
// 如果从在环境变量 那么使用环境变量赋值
heartbeatInterval := os.Getenv("WATCHDOG_AGENT_HEARTBEAT_INTERVAL")
if heartbeatInterval != "" {
defaultHeartbeatInterval, err = time.ParseDuration(heartbeatInterval)
if err != nil {
wdd_log.Error("无法解析环境变量: %v", err)
}
wdd_log.Info("已更新心跳检测间隔 => %s", defaultHeartbeatInterval)
}
failWaitIntervalEnv := os.Getenv("WATCHDOG_AGENT_FAIL_WAIT_INTERVAL")
if failWaitIntervalEnv != "" {
failWaitInterval, err = time.ParseDuration(failWaitIntervalEnv)
if err != nil {
wdd_log.Error("无法解析环境变量: %v", err)
}
wdd_log.Info("已更新心跳检测失败等待间隔 => %s", failWaitInterval)
}
//
}
// 创建RPC客户端
client := rpc.NewClient(nil)
heartbeatURL := os.Getenv("WATCHDOG_HEARTBEAT_URL")
client := rpc.NewClient(nil, heartbeatURL)
// 监听终止信号
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
wdd_log.Info("心跳检测URL: %s", client.GetHeartbeatURL())
// 失败计数器
failCount := 0
failCount := 1
// 心跳检测循环
for {
select {
case <-signalChan:
log.Println("收到终止信号,停止心跳检测")
wdd_log.Info("收到终止信号,停止心跳检测")
return
default:
// 尝试发送心跳请求
authorized, err := sendHeartbeat(client)
if err != nil {
log.Printf("心跳检测失败: %v", err)
wdd_log.Error("第 %d 次心跳检测失败: %v", failCount, err)
failCount++
} else if !authorized {
log.Println("未获得授权")
wdd_log.Warn("第 %d 次心跳检测未获得授权", failCount)
failCount++
} else {
// 检测成功,重置失败计数
failCount = 0
log.Println("心跳检测成功,已获得授权")
failCount = 1
wdd_log.Info("第 %d 次心跳检测成功,已获得授权", failCount)
}
// 检查是否达到最大失败次数
if failCount >= maxRetryCount {
log.Printf("心跳检测连续失败 %d 次,发送终止信号", failCount)
// 发送终止信号给start_up.go
process, err := os.FindProcess(os.Getpid())
if err == nil {
process.Signal(syscall.SIGTERM)
}
wdd_log.Fatal("心跳检测连续失败 %d 次,发送终止信号", failCount)
signalChan <- syscall.SIGTERM
return
}
@@ -88,8 +107,9 @@ func StartHeartbeatDetection() {
// 发送心跳请求
func sendHeartbeat(client *rpc.Client) (bool, error) {
// 1. 获取主机信息
hostInfoData := services.GetAllInfo()
hostInfoData := host_info.GetAllInfo()
hostInfo := models.HostInfo{
SystemInfo: hostInfoData.SystemInfo,
CPUInfo: hostInfoData.CPUInfo,
@@ -98,26 +118,21 @@ func sendHeartbeat(client *rpc.Client) (bool, error) {
NetInfo: hostInfoData.NetInfo,
}
// 2. 获取应用名称
appName := os.Getenv(appNameEnv)
if appName == "" {
appName = "unknown-app"
log.Printf("警告: 环境变量 %s 未设置,使用默认值: %s", appNameEnv, appName)
}
// 获取环境信息
envInfo := GetEnvInfo()
// 构建心跳请求
request := &models.HeartbeatRequest{
HostInfo: hostInfo,
Timestamp: time.Now().Unix(),
AppName: appName,
EnvInfo: envInfo,
}
// 3. 如果已有TOTP密钥则生成TOTP验证码
totpSecret := totp.GetTOTPSecret()
if totpSecret != "" {
totpCode, err := totp.GenerateTOTPCode()
if tierTwoTotpSecret != "" {
totpCode, err := totp_tier_two.GenerateTierTwoTOTPCode(tierTwoTotpSecret)
if err != nil {
log.Printf("生成TOTP验证码失败: %v", err)
wdd_log.Error("生成TOTP验证码失败: %v", err)
} else {
request.TOTPCode = totpCode
}
@@ -126,20 +141,24 @@ func sendHeartbeat(client *rpc.Client) (bool, error) {
// 4. 发送心跳请求
response, err := client.SendHeartbeatWithRetry(request, 10*time.Second)
if err != nil {
return false, fmt.Errorf("发送心跳请求失败: %w", err)
return false, err
}
if response == nil {
return false, err
}
// 5. 处理响应
if response.SecondTOTPSecret != "" {
// 存储TOTP密钥
totp.SetTOTPSecret(response.SecondTOTPSecret)
log.Println("已更新TOTP密钥")
tierTwoTotpSecret = response.SecondTOTPSecret
wdd_log.Info("已更新TOTP密钥 => %s", tierTwoTotpSecret)
}
// 6. 如果有TOTP验证码进行验证
if response.TOTPCode != "" && totpSecret != "" {
if !totp.ValidateTOTPCode(response.TOTPCode) {
log.Println("TOTP验证码验证失败")
if response.TOTPCode != "" && tierTwoTotpSecret != "" {
if !totp_tier_two.VerifyTierTwoTOTPCode(response.TOTPCode, tierTwoTotpSecret) {
wdd_log.Warn("TOTP验证码验证失败")
return false, nil
}
}

View File

@@ -2,38 +2,10 @@ module cmii-uav-watchdog-agent
go 1.24
require (
cmii-uav-watchdog-common v0.0.0
github.com/gin-gonic/gin v1.10.0
)
require cmii-uav-watchdog-common v0.0.0
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require cmii-uav-watchdog-otp v0.0.0 // indirect
replace cmii-uav-watchdog-common => ../cmii-uav-watchdog-common
replace cmii-uav-watchdog-otp => ../cmii-uav-watchdog-otp

View File

@@ -1,89 +0,0 @@
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@@ -1,10 +1,13 @@
package services
package host_info
import (
"bufio"
"cmii-uav-watchdog-common/models"
"fmt"
"cmii-uav-watchdog-common/wdd_log"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
)
@@ -13,151 +16,190 @@ func NewCPUInfo() models.CPUInfo {
return models.CPUInfo{
ModelName: "unknown",
Cores: 0,
Architecture: "unknown",
UUID: "unknown",
Architecture: "amd64",
Flags: []string{},
Hypervisor: "unknown",
Virtualization: "unknown",
}
}
const (
exePath = "/proc/self/exe"
elfMagic = 0x7f
elfMagicString = "ELF"
eMachineOffset = 18
)
// parseLine 解析单行 CPU 信息
func parseLine(line string, cpuInfo *models.CPUInfo) {
if strings.HasPrefix(line, "model name") {
cpuInfo.ModelName = strings.TrimSpace(strings.Split(line, ":")[1])
}
}
// getCPUCores 通过 processor 行获取 CPU 核心数
func getCPUCores() (int, error) {
cores := 0
file, err := os.Open("/proc/cpuinfo")
if err != nil {
return cores, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "processor") {
cores++
}
}
if err := scanner.Err(); err != nil {
return cores, err
}
return cores, nil
}
// getCPUUUID 获取 CPU UUID
func getCPUUUID() (string, error) {
data, err := os.ReadFile("/sys/class/dmi/id/product_uuid")
if err != nil {
return "unknown", err
}
return strings.TrimSpace(string(data)), nil
}
func getCPUArchitecture() string {
cpuarch := "unknown"
// 打开当前进程的执行文件
file, err := os.Open(exePath)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Error opening %s: %v\n", exePath, err)
return cpuarch
}
defer file.Close()
// 读取前64个字节以获取 ELF 头部信息
header := make([]byte, 64)
if _, err := file.Read(header); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Error reading header: %v\n", err)
return cpuarch
}
// 检查 ELF 文件标识
if header[0] != elfMagic || string(header[1:4]) != elfMagicString {
_, _ = fmt.Fprintln(os.Stderr, "File is not an ELF file")
return cpuarch
}
// 获取架构信息
arch := header[eMachineOffset] // e_machine 字段的偏移量
switch arch {
case 0x02: // EM_386
cpuarch = "x86(32-bit)"
case 0x03: // EM_X86_64
cpuarch = "x86_64(64-bit)"
case 0x28: // EM_ARM
cpuarch = "ARM"
case 0x2A: // EM_ARM64
cpuarch = "ARM64"
case 0x08: // EM_MIPS
cpuarch = "MIPS"
default:
cpuarch = "unknown architecture"
}
return cpuarch
}
// GetCPUInfo 获取CPU信息
// 返回CPU信息结构体
func GetCPUInfo() models.CPUInfo {
cpuInfo := NewCPUInfo()
// 读取 /proc/cpuinfo
file, err := os.Open("/proc/cpuinfo")
if err != nil {
fmt.Println("Error opening /proc/cpuinfo:", err)
return cpuInfo // 返回默认值
}
defer func() {
if err := file.Close(); err != nil {
fmt.Println("Error closing /proc/cpuinfo:", err)
}
}()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
parseLine(scanner.Text(), &cpuInfo)
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading /proc/cpuinfo:", err)
return cpuInfo // 返回默认值
}
// 获取 CPU 核心数
cores, err := getCPUCores()
if err != nil {
fmt.Println("Error getting CPU cores:", err)
} else {
cpuInfo.Cores = cores
}
// 获取 CPU UUID
uuid, err := getCPUUUID()
if err != nil {
fmt.Println("Error getting CPU UUID:", err)
} else {
cpuInfo.UUID = uuid
}
cpuInfo.Architecture = getCPUArchitecture()
wdd_log.Debug("开始获取CPU信息")
// 首先尝试从/proc/cpuinfo文件中读取信息
cpuInfo, err := getCPUInfoFromProc()
if err == nil {
wdd_log.Debug("成功从/proc/cpuinfo获取CPU信息")
return cpuInfo
}
/*
CPU模型名称: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
核心数量: 4
架构信息: x86_64
// 如果从文件获取失败尝试使用lscpu命令
wdd_log.Warn("从/proc/cpuinfo获取CPU信息失败: %s尝试使用lscpu命令", err.Error())
cpuInfo, err = getCPUInfoFromLscpu()
if err == nil {
wdd_log.Debug("成功从lscpu命令获取CPU信息")
return cpuInfo
}
*/
// 如果都失败使用runtime包获取基本信息
wdd_log.Warn("从lscpu获取CPU信息失败: %s使用runtime包获取基本信息", err.Error())
return getFallbackCPUInfo()
}
// getCPUInfoFromProc 从/proc/cpuinfo文件中读取CPU信息
// 返回CPU信息结构体和可能的错误
func getCPUInfoFromProc() (models.CPUInfo, error) {
wdd_log.Debug("尝试从/proc/cpuinfo读取CPU信息")
// 创建默认的CPU信息结构体
cpuInfo := NewCPUInfo()
// 打开/proc/cpuinfo文件
file, err := os.Open("/proc/cpuinfo")
if err != nil {
return cpuInfo, err
}
defer file.Close()
// 使用scanner读取文件
scanner := bufio.NewScanner(file)
// 处理的CPU核心数量
coreCount := 1
var flags []string
// 逐行读取并解析
for scanner.Scan() {
line := scanner.Text()
parts := strings.Split(line, ":")
if len(parts) < 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "processor":
// 计数处理器核心数量
coreCount++
case "model name":
// 获取CPU型号名称
cpuInfo.ModelName = value
// 提取频率信息(如果包含)
if strings.Contains(value, "@") {
freqParts := strings.Split(value, "@")
if len(freqParts) > 1 {
cpuInfo.Frequency = strings.TrimSpace(freqParts[1])
}
}
case "flags":
// 获取CPU标志
flags = strings.Fields(value)
case "cpu cores":
// 获取每个物理CPU的核心数
cores, err := strconv.Atoi(value)
if err == nil && cores > 0 {
cpuInfo.Cores = cores
}
}
}
// 如果没有从cpu cores字段获取到核心数使用处理器计数
if cpuInfo.Cores == 0 && coreCount > 0 {
cpuInfo.Cores = coreCount
}
// 设置CPU标志
cpuInfo.Flags = flags
// 检测虚拟化相关信息
for _, flag := range flags {
if flag == "vmx" {
cpuInfo.Virtualization = "vmx"
} else if flag == "svm" {
cpuInfo.Virtualization = "svm"
} else if flag == "hypervisor" {
cpuInfo.Hypervisor = "present"
}
}
// 设置架构
cpuInfo.Architecture = runtime.GOARCH
return cpuInfo, nil
}
// getCPUInfoFromLscpu 使用lscpu命令获取CPU信息
// 返回CPU信息结构体和可能的错误
func getCPUInfoFromLscpu() (models.CPUInfo, error) {
wdd_log.Debug("尝试使用lscpu命令获取CPU信息")
// 创建默认的CPU信息结构体
cpuInfo := NewCPUInfo()
// 执行lscpu命令
cmd := exec.Command("lscpu")
output, err := cmd.Output()
if err != nil {
return cpuInfo, err
}
// 解析输出
lines := strings.Split(string(output), "\n")
for _, line := range lines {
parts := strings.Split(line, ":")
if len(parts) < 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "Model name":
cpuInfo.ModelName = value
// 提取频率信息(如果包含)
if strings.Contains(value, "@") {
freqParts := strings.Split(value, "@")
if len(freqParts) > 1 {
cpuInfo.Frequency = strings.TrimSpace(freqParts[1])
}
}
case "Architecture":
cpuInfo.Architecture = value
case "CPU(s)":
cores, err := strconv.Atoi(value)
if err == nil {
cpuInfo.Cores = cores
}
case "Flags":
cpuInfo.Flags = strings.Fields(value)
case "Hypervisor vendor":
cpuInfo.Hypervisor = value
case "Virtualization":
cpuInfo.Virtualization = value
case "CPU MHz":
if cpuInfo.Frequency == "" {
cpuInfo.Frequency = value + " MHz"
}
}
}
return cpuInfo, nil
}
// getFallbackCPUInfo 使用runtime包获取基本CPU信息
// 返回CPU信息结构体
func getFallbackCPUInfo() models.CPUInfo {
wdd_log.Debug("使用runtime包获取基本CPU信息")
cpuInfo := NewCPUInfo()
cpuInfo.Cores = runtime.NumCPU()
cpuInfo.Architecture = runtime.GOARCH
return cpuInfo
}

View File

@@ -1,8 +1,8 @@
package services
package host_info
import (
"cmii-uav-watchdog-common/models"
"fmt"
"cmii-uav-watchdog-common/wdd_log"
"strconv"
"strings"
"syscall"
@@ -68,7 +68,7 @@ func GetDiskInfo() []models.DiskInfo {
// Get disk usage information using stat
var stat syscall.Statfs_t
if err := syscall.Statfs(mountPoint, &stat); err != nil {
fmt.Printf("Warning: error getting statfs for %s: %v\n", mountPoint, err)
wdd_log.Warn("获取 %s 的statfs信息失败: %v", mountPoint, err)
continue
}
@@ -85,7 +85,8 @@ func GetDiskInfo() []models.DiskInfo {
// Calculate percentage used
usePercent := "0%"
if total > 0 {
usePercent = fmt.Sprintf("%.1f%%", float64(used)/float64(total)*100)
percentage := float64(used) / float64(total) * 100
usePercent = strconv.FormatFloat(percentage, 'f', 1, 64) + "%"
}
// Determine the physical device and its size

View File

@@ -1,4 +1,4 @@
package services
package host_info
import "cmii-uav-watchdog-common/models"

View File

@@ -1,8 +1,8 @@
package services
package host_info
import (
"cmii-uav-watchdog-common/models"
"fmt"
"cmii-uav-watchdog-common/wdd_log"
"os"
"strings"
)
@@ -19,7 +19,7 @@ var DefaultMotherboardInfo = models.MotherboardInfo{
func readFileWithWarning(path string) ([]byte, error) {
value, err := os.ReadFile(path)
if err != nil {
fmt.Printf("Warning: unable to read file %s: %v\n", path, err)
wdd_log.Warn("无法读取文件 %s: %v", path, err)
return nil, err
}
return value, nil

View File

@@ -1,8 +1,8 @@
package services
package host_info
import (
"cmii-uav-watchdog-common/models"
"fmt"
"cmii-uav-watchdog-common/wdd_log"
"os"
"strconv"
"strings"
@@ -26,7 +26,7 @@ func GetMemoryInfo() models.MemoryInfo {
memInfo := NewMemoryInfo()
data, err := os.ReadFile("/proc/meminfo")
if err != nil {
fmt.Println("Error reading /proc/meminfo:", err)
wdd_log.Error("读取 /proc/meminfo 失败: %v", err)
return memInfo
}
@@ -39,7 +39,7 @@ func GetMemoryInfo() models.MemoryInfo {
key := fields[0]
value, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
fmt.Printf("Error parsing value for %s: %v\n", key, err)
wdd_log.Error("解析 %s 的值失败: %v", key, err)
continue
}

View File

@@ -1,8 +1,8 @@
package services
package host_info
import (
"cmii-uav-watchdog-common/models"
"fmt"
"cmii-uav-watchdog-common/wdd_log"
"net"
"strings"
)
@@ -25,7 +25,7 @@ func GetNetworkInterfaces() []models.NetworkInterfaceInfo {
// 获取所有网络接口
ifaces, err := net.Interfaces()
if err != nil {
fmt.Println("Error getting network interfaces:", err)
wdd_log.Error("获取网络接口失败: %v", err)
return []models.NetworkInterfaceInfo{{Name: "unknown", MACAddress: "00:00:00:00:00:00", IPAddresses: []string{}}}
}
@@ -40,7 +40,7 @@ func GetNetworkInterfaces() []models.NetworkInterfaceInfo {
// 获取 MAC 地址
macAddress, err := getMACAddress(iface)
if err != nil {
fmt.Println("Error getting MAC address for", iface.Name, ":", err)
wdd_log.Error("获取网卡 %s 的MAC地址失败: %v", iface.Name, err)
continue
}
@@ -48,7 +48,7 @@ func GetNetworkInterfaces() []models.NetworkInterfaceInfo {
var ipAddresses []string
addrs, err := iface.Addrs()
if err != nil {
fmt.Println("Error getting addresses for", iface.Name, ":", err)
wdd_log.Error("获取网卡 %s 的IP地址失败: %v", iface.Name, err)
continue
}

View File

@@ -1,8 +1,8 @@
package services
package host_info
import (
"cmii-uav-watchdog-common/models"
"log"
"cmii-uav-watchdog-common/wdd_log"
"os"
"strings"
)
@@ -16,10 +16,6 @@ func NewOSInfo() models.OSInfo {
IDLike: "unknown",
VersionID: "unknown",
PrettyName: "unknown",
HomeURL: "unknown",
SupportURL: "unknown",
BugReportURL: "unknown",
PrivacyURL: "unknown",
}
}
@@ -41,6 +37,14 @@ func GetSystemInfo() models.SystemInfo {
if machineID != "" {
sysInfo.MachineID = machineID
}
// 获取 machine serial
serialID := getMachineSerial()
if serialID != "" {
sysInfo.MachineSerial = serialID
}
// 获取操作系统版本
sysInfo.OS = getOSVersion()
@@ -50,12 +54,26 @@ func GetSystemInfo() models.SystemInfo {
sysInfo.KernelVersion = kernelVersion
}
// 获取节点名称
sysInfo.NodeName = "unknown"
nodeName := os.Getenv("NODE_NAME")
if nodeName != "" {
sysInfo.NodeName = nodeName
}
// 获取节点IP
sysInfo.NodeIP = "unknown"
ip := os.Getenv("NODE_IP")
if ip != "" {
sysInfo.NodeIP = ip
}
return sysInfo
}
func getOSVersion() models.OSInfo {
data, err := os.ReadFile("/etc/os-release")
if err != nil {
log.Printf("Error reading /etc/os-release: %v", err)
wdd_log.Error("读取 /etc/os-release 失败: %v", err)
return NewOSInfo() // 返回默认值
}
@@ -88,25 +106,27 @@ func getOSVersion() models.OSInfo {
osInfo.VersionID = value
case "PRETTY_NAME":
osInfo.PrettyName = value
case "HOME_URL":
osInfo.HomeURL = value
case "SUPPORT_URL":
osInfo.SupportURL = value
case "BUG_REPORT_URL":
osInfo.BugReportURL = value
case "PRIVACY_URL":
osInfo.PrivacyURL = value
}
}
return osInfo
}
// getMachineID 从 /etc/machine-id 文件中获取机器 ID
// getMachineID 从 /sys/devices/virtual/dmi/id/product_uuid 文件中获取机器 ID
func getMachineID() string {
data, err := os.ReadFile("/etc/machine-id")
data, err := os.ReadFile("/sys/devices/virtual/dmi/id/product_uuid")
if err != nil {
log.Printf("Error reading /etc/machine-id: %v", err)
wdd_log.Error("读取 /sys/devices/virtual/dmi/id/product_uuid 失败: %v", err)
return ""
}
return strings.TrimSpace(string(data)) // 去除多余空格
}
// getMachineID 从 /sys/devices/virtual/dmi/id/product_uuid 文件中获取机器 ID
func getMachineSerial() string {
data, err := os.ReadFile("/sys/devices/virtual/dmi/id/product_serial")
if err != nil {
wdd_log.Error("读取 /sys/devices/virtual/dmi/id/product_serial 失败: %v", err)
return ""
}
return strings.TrimSpace(string(data)) // 去除多余空格
@@ -116,8 +136,8 @@ func getMachineID() string {
func getKernelVersion() string {
data, err := os.ReadFile("/proc/version")
if err != nil {
log.Printf("Error reading /proc/version: %v", err)
wdd_log.Error("读取 /proc/version 失败: %v", err)
return ""
}
return string(data)
return strings.TrimSpace(string(data))
}

View File

@@ -1,69 +0,0 @@
package main
import (
"cmii-uav-watchdog-agent/host_info"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"os"
"os/signal"
"syscall"
)
func main() {
// 创建一个默认的 Gin 路由
var r = gin.Default() // 定义一个 GET 路由
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "pong"})
})
// 定义一个 POST 路由
r.POST("/echo", func(c *gin.Context) {
var json map[string]interface{}
if err := c.ShouldBindJSON(&json); err == nil {
c.JSON(http.StatusOK, json)
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
r.GET("/cpu", func(c *gin.Context) {
cpuInfo := services.GetCPUInfo() // 直接返回 CPU 信息
c.JSON(http.StatusOK, cpuInfo)
})
r.GET("/memory", func(c *gin.Context) {
memInfo := services.GetMemoryInfo() // 直接返回内存信息
c.JSON(http.StatusOK, memInfo)
})
r.GET("/disk", func(c *gin.Context) {
diskInfo := services.GetDiskInfo() // 直接返回磁盘信息
c.JSON(http.StatusOK, diskInfo)
})
r.GET("/motherboard", func(c *gin.Context) {
mbInfo := services.GetMotherboardInfo() // 直接返回主板信息
c.JSON(http.StatusOK, mbInfo)
})
r.GET("/network", func(c *gin.Context) {
networkInterfaces := services.GetNetworkInterfaces()
c.JSON(http.StatusOK, networkInterfaces)
})
r.GET("/all", func(c *gin.Context) {
allInfo := services.GetAllInfo()
c.JSON(http.StatusOK, allInfo)
})
//r.GET("/phy", func(c *gin.Context) {
// allInfo, _ := services.GetPVForLV()
// c.JSON(http.StatusOK, allInfo)
//})
// 启动服务,监听在 8080 端口
r.Run(":8098")
// 等待终止信号
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
fmt.Println("Shutting down service...")
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
"time"
"cmii-uav-watchdog-common/models"
@@ -23,7 +24,8 @@ var (
)
const (
DefaultHeartbeatURL = "http://cmii-uav-watchdog/heartbeat"
DefaultHeartbeatURL = "http://cmii-uav-watchdog:8080/api/heartbeat"
// DefaultHeartbeatURL = "http://192.168.35.70:8990/api/heartbeat"
)
// ClientOptions HTTP客户端配置选项
@@ -50,6 +52,7 @@ func DefaultClientOptions() *ClientOptions {
type Client struct {
httpClient *http.Client
options *ClientOptions
heartbeatURL string // 心跳请求的URL地址
}
// NewClient 创建一个新的HTTP客户端
@@ -58,7 +61,7 @@ type Client struct {
//
// 返回:
// - *Client: HTTP客户端实例
func NewClient(options *ClientOptions) *Client {
func NewClient(options *ClientOptions, heartbeatURL string) *Client {
if options == nil {
options = DefaultClientOptions()
}
@@ -75,12 +78,24 @@ func NewClient(options *ClientOptions) *Client {
Transport: transport,
}
// 解析heartbeatURL
heartbeatURL = strings.TrimSpace(heartbeatURL)
if heartbeatURL == "" {
heartbeatURL = DefaultHeartbeatURL
}
return &Client{
httpClient: httpClient,
options: options,
heartbeatURL: heartbeatURL,
}
}
// GetHeartbeatURL 获取心跳请求的URL地址
func (c *Client) GetHeartbeatURL() string {
return c.heartbeatURL
}
// SendHeartbeat 发送心跳请求并处理响应
// 参数:
// - ctx: 上下文,用于取消请求
@@ -114,7 +129,7 @@ func (c *Client) SendHeartbeat(ctx context.Context, request *models.HeartbeatReq
}
// 创建HTTP请求
req, err := http.NewRequestWithContext(ctx, http.MethodPost, DefaultHeartbeatURL, bytes.NewBuffer(requestBody))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.heartbeatURL, bytes.NewBuffer(requestBody))
if err != nil {
lastError = fmt.Errorf("创建请求失败: %w", err)
continue
@@ -140,9 +155,37 @@ func (c *Client) SendHeartbeat(ctx context.Context, request *models.HeartbeatReq
// 确保响应体被关闭
defer resp.Body.Close()
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
lastError = fmt.Errorf("%w: 状态码 %d", ErrInvalidStatusCode, resp.StatusCode)
// 使用switch检查HTTP状态码
switch resp.StatusCode {
case http.StatusOK:
// 状态码为200继续处理响应
case http.StatusBadRequest:
lastError = fmt.Errorf("%w: 状态码 %d请求无效", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue
case http.StatusUnauthorized:
lastError = fmt.Errorf("%w: 状态码 %d未授权", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue
case http.StatusForbidden:
lastError = fmt.Errorf("%w: 状态码 %d禁止访问", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue
case http.StatusNotFound:
lastError = fmt.Errorf("%w: 状态码 %d未找到资源", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue
case http.StatusInternalServerError:
lastError = fmt.Errorf("%w: 状态码 %d服务器内部错误", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue
default:
lastError = fmt.Errorf("%w: 状态码 %d未知错误", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue

View File

@@ -1,105 +0,0 @@
package totp
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base32"
"encoding/binary"
"fmt"
"strings"
"sync"
"time"
)
// TOTPConfig TOTP配置
type TOTPConfig struct {
Secret string // TOTP密钥
Digits int // TOTP验证码长度
TimeStep time.Duration // TOTP时间步长
Algorithm string // TOTP算法
}
var (
defaultConfig = TOTPConfig{
Secret: "",
Digits: 6,
TimeStep: 30 * time.Second,
Algorithm: "SHA1",
}
mu sync.RWMutex
)
// SetTOTPSecret 设置TOTP密钥
func SetTOTPSecret(secret string) {
mu.Lock()
defer mu.Unlock()
defaultConfig.Secret = secret
}
// GetTOTPSecret 获取TOTP密钥
func GetTOTPSecret() string {
mu.RLock()
defer mu.RUnlock()
return defaultConfig.Secret
}
// GenerateTOTPCode 生成TOTP验证码
func GenerateTOTPCode() (string, error) {
mu.RLock()
config := defaultConfig
mu.RUnlock()
if config.Secret == "" {
return "", fmt.Errorf("TOTP密钥未设置")
}
// 确保密钥是Base32编码的
secret := strings.ToUpper(config.Secret)
secret = strings.ReplaceAll(secret, " ", "")
secretBytes, err := base32.StdEncoding.DecodeString(secret)
if err != nil {
return "", fmt.Errorf("解码TOTP密钥失败: %w", err)
}
// 获取当前时间戳并转换为TOTP时间计数器
timeCounter := uint64(time.Now().Unix()) / uint64(config.TimeStep.Seconds())
// 将时间计数器转换为字节数组
timeBytes := make([]byte, 8)
binary.BigEndian.PutUint64(timeBytes, timeCounter)
// 使用HMAC-SHA1计算TOTP值
h := hmac.New(sha1.New, secretBytes)
h.Write(timeBytes)
hash := h.Sum(nil)
// 根据RFC 6238我们使用哈希的最后一个字节的低4位作为偏移
offset := hash[len(hash)-1] & 0x0f
// 从哈希中提取4字节并转换为整数
binary := binary.BigEndian.Uint32(hash[offset : offset+4])
// 屏蔽最高位并获取指定位数的数字
totp := binary & 0x7fffffff % uint32(pow10(config.Digits))
// 将数字格式化为字符串,填充前导零
return fmt.Sprintf("%0*d", config.Digits, totp), nil
}
// ValidateTOTPCode 验证TOTP验证码
func ValidateTOTPCode(code string) bool {
generatedCode, err := GenerateTOTPCode()
if err != nil {
return false
}
return code == generatedCode
}
// pow10 计算10的n次方
func pow10(n int) int {
result := 1
for i := 0; i < n; i++ {
result *= 10
}
return result
}

View File

@@ -0,0 +1,37 @@
package main
import (
"cmii-uav-watchdog-center/config"
"cmii-uav-watchdog-center/router"
"cmii-uav-watchdog-common/wdd_log"
"os"
"path/filepath"
"strconv"
)
func main() {
// 获取执行目录
execDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
wdd_log.Fatal("获取执行目录失败: %v", err)
}
// 配置文件路径
configPath := filepath.Join(execDir, "config.json")
// 加载配置
config, err := config.LoadConfig(configPath)
if err != nil {
wdd_log.Fatal("加载配置失败: %v", err)
}
// 设置路由
r := router.SetupRouter()
// 启动服务器
port := config.Server.Port
wdd_log.Info("服务器正在监听端口: %d", port)
if err := r.Run(":" + strconv.Itoa(port)); err != nil {
wdd_log.Error("启动服务器失败: %v", err)
}
}

View File

@@ -0,0 +1,11 @@
package config
// Config 配置模型
type Config struct {
Server ServerConfig `json:"server"` // 服务器配置
}
// ServerConfig 服务器配置
type ServerConfig struct {
Port int `json:"port"` // 服务器端口
}

View File

@@ -0,0 +1,74 @@
package config
import (
"cmii-uav-watchdog-common/wdd_log"
"encoding/json"
"os"
"sync"
)
var (
config *Config
configOnce sync.Once
)
// LoadConfig 加载配置
func LoadConfig(configPath string) (*Config, error) {
configOnce.Do(func() {
// 读取配置文件
data, err := os.ReadFile(configPath)
if err != nil {
// 如果配置文件不存在,则创建默认配置
if os.IsNotExist(err) {
config = createDefaultConfig()
// 保存默认配置
saveConfig(configPath, config)
return
}
wdd_log.Error("读取配置文件失败: %v", err)
return
}
// 解析配置
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
wdd_log.Error("解析配置文件失败: %v", err)
return
}
config = &cfg
})
return config, nil
}
// GetConfig 获取配置
func GetConfig() *Config {
if config == nil {
// 如果配置未初始化,则加载默认配置
config = createDefaultConfig()
}
return config
}
// 创建默认配置
func createDefaultConfig() *Config {
return &Config{
Server: ServerConfig{
Port: 8080,
},
}
}
// 保存配置
func saveConfig(configPath string, cfg *Config) {
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
wdd_log.Error("序列化配置失败: %v", err)
return
}
if err := os.WriteFile(configPath, data, 0644); err != nil {
wdd_log.Error("保存配置文件失败: %v", err)
}
}

View File

@@ -0,0 +1,38 @@
package controllers
import (
"cmii-uav-watchdog-center/services"
"cmii-uav-watchdog-common/models"
"net/http"
"github.com/gin-gonic/gin"
)
// ProcessAuthorizationFile 处理授权文件
// @Summary 处理授权文件
// @Description 接收来自cmii-uav-watchdog的授权文件解码并生成授权码
// @Accept json
// @Produce json
// @Param authFile body models.AuthorizationFile true "授权文件"
// @Success 200 {object} models.AuthorizationCode "授权码"
// @Failure 400 {object} map[string]string "请求错误"
// @Failure 401 {object} map[string]string "未授权"
// @Router /api/v1/auth/process [post]
func ProcessAuthorizationFile(c *gin.Context) {
var authFile models.AuthorizationFile
// 解析请求体
if err := c.ShouldBindJSON(&authFile); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求体"})
return
}
authCode, err := services.ProcessAuthorizationFile(authFile)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成授权码失败"})
return
}
// 返回授权码
c.JSON(http.StatusOK, authCode)
}

View File

@@ -0,0 +1,45 @@
package controllers
import (
"cmii-uav-watchdog-center/services"
"cmii-uav-watchdog-common/models"
"cmii-uav-watchdog-common/wdd_log"
"net/http"
"github.com/gin-gonic/gin"
)
// GetProjectList 获取项目列表
func GetProjectList(c *gin.Context) {
// 获取项目列表
projectList, err := services.GetProjectList()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, projectList)
}
// CreateProject 创建项目
func CreateProject(c *gin.Context) {
// 绑定 Project
var project models.Project
if err := c.ShouldBindJSON(&project); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 创建项目
authFilePath, err := services.CreateProject(&project)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
wdd_log.Info("创建项目成功: %s", authFilePath)
c.JSON(http.StatusOK, project)
}

View File

@@ -1,3 +1,40 @@
module cmii-uav-watchdog-center
go 1.23
go 1.24
require (
cmii-uav-watchdog-common v0.0.0
cmii-uav-watchdog-otp v0.0.0
github.com/gin-gonic/gin v1.9.1
)
require (
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace cmii-uav-watchdog-common => ../cmii-uav-watchdog-common
replace cmii-uav-watchdog-otp => ../cmii-uav-watchdog-otp

View File

@@ -0,0 +1,86 @@
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@@ -0,0 +1,36 @@
package router
import (
"cmii-uav-watchdog-center/controllers"
"github.com/gin-gonic/gin"
)
// SetupRouter 设置路由
func SetupRouter() *gin.Engine {
// 创建一个默认的路由引擎
r := gin.Default()
// API路由组
apiV1 := r.Group("/api")
{
// 授权相关路由
auth := apiV1.Group("/auth")
{
// 处理授权文件
auth.POST("/process", controllers.ProcessAuthorizationFile)
}
// 项目相关路由
project := apiV1.Group("/project")
{
// 获取项目列表
project.GET("/list", controllers.GetProjectList)
// 创建项目
project.POST("/create", controllers.CreateProject)
}
}
return r
}

View File

@@ -0,0 +1,64 @@
package services
import (
"cmii-uav-watchdog-common/models"
"cmii-uav-watchdog-common/totp_tier_one"
"cmii-uav-watchdog-common/utils"
"cmii-uav-watchdog-common/wdd_log"
)
func ProcessAuthorizationFile(authFile models.AuthorizationFile) (models.AuthorizationCode, error) {
// 解密项目NameSpace
projectNamespace, err := totp_tier_one.Decrypt(authFile.EncryptedNamespace, authFile.ProjectNamespace)
if err != nil {
wdd_log.Error("解密项目Name失败: %v", err)
return models.AuthorizationCode{}, err
}
// 获取项目信息
projectInfo, err := GetProjectInfo(projectNamespace)
if err != nil {
wdd_log.Error("获取项目信息失败: %v", err)
return models.AuthorizationCode{}, err
}
// 验证TOTP验证码
if !totp_tier_one.VerifyTierOneTOTPCode(authFile.TOTPCode, projectInfo.TierOneSecret) {
wdd_log.Warn("TOTP验证失败: %s", authFile.TOTPCode)
return models.AuthorizationCode{}, err
}
// 处理加密的主机信息
// 一级TOTP密钥
key := projectInfo.TierOneSecret
// 注意:由于我们无法解密主机信息(我们只需要验证其有效性),所以直接使用
authorizedHostMap := make(map[string]models.HostInfo)
for encryptedInfo, hostInfo := range authFile.EncryptedHostMap {
// 使用 DecryptHostInfo 验证主机信息是否被篡改
isOK, err := totp_tier_one.DecryptHostInfo(encryptedInfo, hostInfo, key)
if !isOK || err != nil {
wdd_log.Error("主机信息验证失败: %v", err)
continue
}
// 验证通过,将主机信息添加到授权列表
authorizedHostMap[encryptedInfo] = hostInfo
}
// 生成新的TOTP验证码
newTOTPCode, err := totp_tier_one.GenerateTierOneTOTPCode(projectInfo.TierOneSecret)
if err != nil {
wdd_log.Error("生成TOTP验证码失败: %v", err)
return models.AuthorizationCode{}, err
}
return models.AuthorizationCode{
TOTPCode: newTOTPCode,
CurrentTime: utils.CurentTimeString(),
EncryptedHostMap: authorizedHostMap,
ProjectNamespace: projectNamespace,
EncryptedNamespace: authFile.EncryptedNamespace,
}, nil
}

View File

@@ -0,0 +1,101 @@
package services
import (
"cmii-uav-watchdog-common/models"
"cmii-uav-watchdog-common/utils"
"cmii-uav-watchdog-common/wdd_log"
"encoding/json"
"fmt"
"os"
)
const (
ProjectFilePath = "C:\\Users\\wddsh\\Documents\\IdeaProjects\\cmii-uav-watchdog-project\\cmii-uav-watchdog-center\\project_file\\"
)
func CreateProject(project *models.Project) (string, error) {
// 生成TierOneSecret
secret, err := GenerateTierOneTOTPSecret()
if err != nil {
wdd_log.Error("生成TierOneSecret失败: %v", err)
return "", err
}
project.TierOneSecret = secret
project.CreateTime = utils.CurentTimeString()
// 存储 project到file中
project.AuthFilePath = fmt.Sprintf("%s%s-%s.json", ProjectFilePath, project.Namespace, project.Name)
// 存储到file中
jsonData, err := json.Marshal(project)
if err != nil {
wdd_log.Error("序列化失败: %v", err)
return "", err
}
// 写入文件
err = os.WriteFile(project.AuthFilePath, jsonData, 0644)
if err != nil {
wdd_log.Error("写入文件失败: %v", err)
return "", err
}
return project.AuthFilePath, nil
}
// GetProjectList 获取项目列表
func GetProjectList() ([]models.Project, error) {
// 获取项目列表
projectFileList, err := os.ReadDir(ProjectFilePath)
if err != nil {
wdd_log.Error("读取项目列表失败: %v", err)
return nil, err
}
projectList := make([]models.Project, 0)
// 遍历项目列表
for _, projectFilePath := range projectFileList {
// 读取项目文件
projectFile, err := os.ReadFile(fmt.Sprintf("%s%s", ProjectFilePath, projectFilePath.Name()))
if err != nil {
wdd_log.Error("读取项目文件失败: %v", err)
return nil, err
}
// 反序列化
var project models.Project
err = json.Unmarshal(projectFile, &project)
if err != nil {
wdd_log.Error("反序列化失败: %v", err)
return nil, err
}
// 添加到项目列表
projectList = append(projectList, project)
}
return projectList, nil
}
// GetProjectInfo 获取项目信息
func GetProjectInfo(projectNamespace string) (models.Project, error) {
// 获取项目列表
projectList, err := GetProjectList()
if err != nil {
wdd_log.Error("获取项目列表失败: %v", err)
return models.Project{}, err
}
// 遍历项目列表
for _, project := range projectList {
if project.Namespace == projectNamespace {
return project, nil
}
}
return models.Project{}, fmt.Errorf("项目不存在")
}

View File

@@ -0,0 +1,18 @@
package services
import (
"cmii-uav-watchdog-common/totp_tier_one"
"cmii-uav-watchdog-otp/totp"
"log"
)
// GenerateTierOneTOTPSecret 生成一级TOTP密钥 只能center调用
func GenerateTierOneTOTPSecret() (string, error) {
secret, err := totp.Generate(totp_tier_one.TierOneTOTPSecretOpts)
if err != nil {
log.Printf("生成TOTP密钥失败: %v", err)
return "", err
}
return secret.Secret(), nil
}

View File

@@ -1,3 +1,7 @@
module cmii-uav-watchdog-common
go 1.24
require cmii-uav-watchdog-otp v0.0.0
replace cmii-uav-watchdog-otp => ../cmii-uav-watchdog-otp

View File

@@ -1,21 +0,0 @@
package main
import (
"fmt"
)
//TIP <p>To run your code, right-click the code and select <b>Run</b>.</p> <p>Alternatively, click
// the <icon src="AllIcons.Actions.Execute"/> icon in the gutter and select the <b>Run</b> menu item from here.</p>
func main() {
//TIP <p>Press <shortcut actionId="ShowIntentionActions"/> when your caret is at the underlined text
// to see how GoLand suggests fixing the warning.</p><p>Alternatively, if available, click the lightbulb to view possible fixes.</p>
s := "gopher"
fmt.Println("Hello and welcome, %s!", s)
for i := 1; i <= 5; i++ {
//TIP <p>To start your debugging session, right-click your code in the editor and select the Debug option.</p> <p>We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
// for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.</p>
fmt.Println("i =", 100/i)
}
}

View File

@@ -1,28 +1,32 @@
package models
import "time"
// AuthorizationFile 授权文件模型
type AuthorizationFile struct {
EncryptedHosts []string `json:"encrypted_hosts"` // 加密后的主机信息列表
EncryptedHostMap map[string]HostInfo `json:"encrypted_host_map"` // 加密后的主机信息列表
TOTPCode string `json:"totp_code"` // TOTP验证码
CurrentTime time.Time `json:"current_time"` // 当前系统时间
FirstAuthTime time.Time `json:"first_auth_time"` // 初次授权时间
CurrentTime string `json:"current_time"` // 当前系统时间
FirstAuthTime string `json:"first_auth_time"` // 初次授权时间
TimeOffset int64 `json:"time_offset"` // 授权时间偏移
ProjectNamespace string `json:"project_namespace"` // 项目命名空间
EncryptedNamespace string `json:"encrypted_namespace"` // 加密后的项目命名空间 防止信息篡改
}
// AuthorizationCode 授权码模型
type AuthorizationCode struct {
TOTPCode string `json:"totp_code"` // TOTP验证码
CurrentTime time.Time `json:"current_time"` // 当前系统时间
EncryptedHosts []string `json:"encrypted_hosts"` // 授权主机的加密字符串列表
CurrentTime string `json:"current_time"` // 当前系统时间
EncryptedHostMap map[string]HostInfo `json:"encrypted_host_map"` // 加密后的主机信息列表 防止信息篡改
ProjectNamespace string `json:"project_namespace"` // 项目命名空间
EncryptedNamespace string `json:"encrypted_namespace"` // 加密后的项目命名空间 防止信息篡改
}
// AuthorizationStorage 授权存储信息
type AuthorizationStorage struct {
EncryptedCode string `json:"encrypted_code"` // 加密后的授权码
FirstAuthTime time.Time `json:"first_auth_time"` // 初次授权时间
EncryptedAuthrizationCode string `json:"encrypted_authrization_code"` // 加密后的授权码,防止信息篡改
FirstAuthTime string `json:"first_auth_time"` // 初次授权时间
TimeOffset int64 `json:"time_offset"` // 授权时间偏移
AuthorizedHosts []string `json:"authorized_hosts"` // 已授权主机列表
AuthorizedHostMap map[string]HostInfo `json:"authorized_host_map"` // 已授权主机列表
SecondTOTPSecret string `json:"second_totp_secret"` // 第二级的totp密钥
ProjectNamespace string `json:"project_namespace"` // 项目命名空间
EncryptedNamespace string `json:"encrypted_namespace"` // 加密后的项目命名空间 防止信息篡改
}

View File

@@ -5,7 +5,10 @@ type CPUInfo struct {
ModelName string `json:"model_name"`
Cores int `json:"cores"`
Architecture string `json:"architecture"`
UUID string `json:"uuid"`
Flags []string `json:"flags"`
Hypervisor string `json:"hypervisor"`
Virtualization string `json:"virtualization"`
Frequency string `json:"frequency"`
}
// MemoryInfo holds the memory information.
@@ -49,9 +52,12 @@ type MotherboardInfo struct {
}
type SystemInfo struct {
MachineID string `json:"machine_id"` // 唯一标识符
MachineID string `json:"machine_id"` // 主机唯一标识符
MachineSerial string `json:"machine_serial"` // 主机序列号
OS OSInfo `json:"os"` // 操作系统
KernelVersion string `json:"kernel_version"` // 内核版本
NodeName string `json:"node_name"` // 节点名称
NodeIP string `json:"node_ip"` // 节点IP
}
type OSInfo struct {
@@ -61,10 +67,6 @@ type OSInfo struct {
IDLike string `json:"id_like"`
VersionID string `json:"version_id"`
PrettyName string `json:"pretty_name"`
HomeURL string `json:"home_url"`
SupportURL string `json:"support_url"`
BugReportURL string `json:"bug_report_url"`
PrivacyURL string `json:"privacy_url"`
}
// HostInfo 主机信息模型
@@ -77,12 +79,32 @@ type HostInfo struct {
MotherboardInfo MotherboardInfo `json:"motherboard_info"`
}
// EnvInfo 环境信息
type EnvInfo struct {
K8S_NAMESPACE string `json:"k8s_namespace"` // 环境名称
APPLICATION_NAME string `json:"application_name"` // 应用名称
CUST_JAVA_OPTS string `json:"cust_java_opts"` // 自定义java参数
BIZ_CONFIG_GROUP string `json:"biz_config_group"` // 业务配置组
SYS_CONFIG_GROUP string `json:"sys_config_group"` // 系统配置组
IMAGE_NAME string `json:"image_name"` // 镜像名称
JAVA_VERSION string `json:"java_version"` // java版本
GIT_COMMIT string `json:"git_commit"` // git commit
GIT_BRANCH string `json:"git_branch"` // git branch
NODE_NAME string `json:"node_name"` // 节点名称
NODE_IP string `json:"node_ip"` // 节点ip
POD_NAME string `json:"pod_name"` // pod名称
LIMIT_CPU string `json:"limit_cpu"` // 限制cpu
LIMIT_MEMORY string `json:"limit_memory"` // 限制内存
REQUEST_CPU string `json:"request_cpu"` // 请求cpu
REQUEST_MEMORY string `json:"request_memory"` // 请求内存
}
// HeartbeatRequest 心跳请求
type HeartbeatRequest struct {
HostInfo HostInfo `json:"host_info"` // 主机信息
EnvInfo EnvInfo `json:"env_info"` // 环境信息
Timestamp int64 `json:"timestamp"` // 时间戳
TOTPCode string `json:"totp_code"` // TOTP验证码
AppName string `json:"app_name"` // 应用名称
}
// HeartbeatResponse 心跳响应

View File

@@ -0,0 +1,22 @@
package models
// Project 项目信息
type Project struct {
Name string `json:"name" binding:"required"`
Namespace string `json:"namespace" binding:"required"`
Province string `json:"province"`
City string `json:"city"`
Domain string `json:"domain"`
BusinessMan string `json:"business_man"`
MarketMan string `json:"market_man" `
TierOneSecret string `json:"tier_one_secret" `
TierTwoSecret string `json:"tier_two_secret"`
AuthTime string `json:"auth_time"`
AuthFilePath string `json:"auth_file_path"`
CreateTime string `json:"create_time"`
}
// ProjectPO 项目持久化对象
type ProjectPO struct {
Project
}

View File

@@ -0,0 +1,169 @@
package totp_tier_one
import (
"cmii-uav-watchdog-common/models"
"cmii-uav-watchdog-common/wdd_log"
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"strconv"
"strings"
)
// Encrypt 加密字符串
// 保证使用相同的plaintext和key进行加密在不同时间、不同机器上输出的加密字符串都是固定的
func Encrypt(plaintext string, key string) (encryptedPlaintext string, encryptError error) {
// 创建hash
hasher := sha256.New()
hasher.Write([]byte(key))
keyBytes := hasher.Sum(nil)
// 创建cipher
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", err
}
// 创建gcm
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// 生成确定性nonce - 使用plaintext和key的组合生成确保对相同输入产生相同nonce
nonceHasher := sha256.New()
nonceHasher.Write([]byte(key + "fixed_salt_for_deterministic_result" + plaintext[:min(10, len(plaintext))]))
nonceSource := nonceHasher.Sum(nil)
// 创建合适大小的nonce
nonce := nonceSource[:gcm.NonceSize()]
// 加密
ciphertext := gcm.Seal(nil, nonce, []byte(plaintext), nil)
// 将nonce和密文连接起来
result := append(nonce, ciphertext...)
// 编码为base64
return base64.StdEncoding.EncodeToString(result), nil
}
// Decrypt 解密字符串
// 可以使用加密字符串和key在不同时间、不同机器上解密出原始plaintext
func Decrypt(encrypted string, key string) (decryptedPlaintext string, decryptError error) {
// 解码base64
data, err := base64.StdEncoding.DecodeString(encrypted)
if err != nil {
return "", err
}
// 创建hash
hasher := sha256.New()
hasher.Write([]byte(key))
keyBytes := hasher.Sum(nil)
// 创建cipher
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", err
}
// 创建gcm
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// 检查长度
if len(data) < gcm.NonceSize() {
return "", errors.New("密文太短")
}
// 分离nonce和密文
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
// 解密
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
// min 返回两个整数中较小的一个
func min(a, b int) int {
if a < b {
return a
}
return b
}
// EncryptHostInfo 加密主机信息
func EncryptHostInfo(hostInfo models.HostInfo, key string) (encryptedHostInfo string, encryptError error) {
// 生成主机唯一标识作为加密密钥
info := generateHostUniqueID(hostInfo)
// 加密主机信息
encrypted, err := Encrypt(info, key)
if err != nil {
return "", err
}
return encrypted, nil
}
// DecryptHostInfo 解密主机信息
func DecryptHostInfo(encrypted string, hostInfo models.HostInfo, key string) (decryptedOK bool, decryptError error) {
// 解密主机信息
decryptedStr, err := Decrypt(encrypted, key)
if err != nil {
wdd_log.Error("[DecryptHostInfo] - Decrypt失败: %v", err)
return false, err
}
// wdd_log.Info("[DecryptHostInfo] - Decrypt成功: %s", decryptedStr)
// 生成主机唯一标识作为加密密钥
hostUniqueID := generateHostUniqueID(hostInfo)
if hostUniqueID != decryptedStr {
wdd_log.Error("[DecryptHostInfo] - 主机信息篡改")
return false, errors.New("主机信息篡改")
}
return true, nil
}
// generateHostUniqueID 生成主机唯一标识作为加密密钥
func generateHostUniqueID(hostInfo models.HostInfo) string {
// hostinfo.SystemInfo.MachineID
machineID := hostInfo.SystemInfo.MachineID
// hostinfo.SystemInfo.KernelVersion
kernelVersion := hostInfo.SystemInfo.KernelVersion
// 将CPU信息中的ModelName Cores Hypervisor 拼接起来
cpuCores := hostInfo.CPUInfo.Cores
cpuHypervisor := hostInfo.CPUInfo.Hypervisor
cpuModelName := hostInfo.CPUInfo.ModelName
cpuInfo := fmt.Sprintf("%s-%d-%s", cpuModelName, cpuCores, cpuHypervisor)
// 不能使用 mac 地址,因为 每个Pod 的 mac 地址会变化
// hostinfo.SystemInfo.MachineSerial
machineSerial := hostInfo.SystemInfo.MachineSerial
// 将memory中的total 和 used 拼接起来
memoryTotal := hostInfo.MemoryInfo.Total
// 将上述的全部信息拼接起来
info := strings.Join([]string{machineID, kernelVersion, cpuInfo, machineSerial, strconv.FormatUint(memoryTotal, 10)}, "==")
return info
}

View File

@@ -0,0 +1,218 @@
package totp_tier_one
import (
"cmii-uav-watchdog-common/models"
"fmt"
"testing"
)
// TestEncryptDecrypt 测试加密和解密功能
func TestEncryptDecrypt(t *testing.T) {
tests := []struct {
name string
plaintext string
key string
}{
{
name: "正常文本加密解密",
plaintext: "这是一段测试文本",
key: "测试密钥",
},
{
name: "空文本加密解密",
plaintext: "",
key: "测试密钥",
},
{
name: "特殊字符加密解密",
plaintext: "!@#$%^&*()_+{}|:<>?",
key: "!@#$%^&*()_+{}|:<>?",
},
{
name: "长文本加密解密",
plaintext: "这是一段非常长的测试文本,用于测试加密和解密功能是否能够正确处理长文本。这是一段非常长的测试文本,用于测试加密和解密功能是否能够正确处理长文本。",
key: "测试密钥",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 加密
encrypted, err := Encrypt(tt.plaintext, tt.key)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
// 确保加密后的文本与原始文本不同
if tt.plaintext != "" && encrypted == tt.plaintext {
t.Errorf("加密后的文本与原始文本相同,可能未正确加密")
}
// 解密
decrypted, err := Decrypt(encrypted, tt.key)
if err != nil {
t.Fatalf("解密失败: %v", err)
}
// 确保解密后的文本与原始文本相同
if decrypted != tt.plaintext {
t.Errorf("解密后的文本与原始文本不同,期望:%s实际%s", tt.plaintext, decrypted)
}
})
}
}
// TestDecryptWithWrongKey 测试使用错误密钥解密
func TestDecryptWithWrongKey(t *testing.T) {
plaintext := "这是一段测试文本"
correctKey := "正确密钥"
wrongKey := "错误密钥"
// 使用正确密钥加密
encrypted, err := Encrypt(plaintext, correctKey)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
// 使用错误密钥解密,期望失败
_, err = Decrypt(encrypted, wrongKey)
fmt.Println("解密失败的 error :", err)
if err == nil {
t.Errorf("使用错误密钥解密应该失败,但成功了")
}
}
// TestDecryptInvalidData 测试解密无效的数据
func TestDecryptInvalidData(t *testing.T) {
invalidEncrypted := "这不是有效的加密数据"
key := "测试密钥"
_, err := Decrypt(invalidEncrypted, key)
if err == nil {
t.Errorf("解密无效数据应该失败,但成功了")
}
}
// createMockHostInfo 创建模拟的主机信息用于测试
func createMockHostInfo() models.HostInfo {
return models.HostInfo{
SystemInfo: models.SystemInfo{
MachineID: "test-machine-id",
KernelVersion: "5.10.0-test",
OS: models.OSInfo{
Name: "TestOS",
Version: "1.0",
ID: "test",
IDLike: "test",
VersionID: "1.0",
PrettyName: "Test OS 1.0",
},
},
CPUInfo: models.CPUInfo{
ModelName: "Test CPU",
Cores: 4,
Architecture: "x86_64",
Flags: []string{},
Hypervisor: "",
Virtualization: "",
Frequency: "",
},
DiskInfo: []models.DiskInfo{
{
Device: "/dev/sda1",
Filesystem: "ext4",
Type: "ext4",
Size: 1000000,
Used: 500000,
Available: 500000,
UsePercent: "50%",
MountPoint: "/",
PhysicalDevice: "/dev/sda",
PhysicalSize: 2000000,
},
},
MemoryInfo: models.MemoryInfo{
Total: 8000000,
Free: 4000000,
Available: 4000000,
Used: 4000000,
Buffers: 1000000,
Cached: 1000000,
Shared: 500000,
},
NetInfo: []models.NetworkInterfaceInfo{
{
Name: "eth0",
MACAddress: "00:11:22:33:44:55",
IPAddresses: []string{"192.168.1.100", "fe80::1234"},
},
},
MotherboardInfo: models.MotherboardInfo{
Manufacturer: "Test Manufacturer",
Product: "Test Product",
Version: "1.0",
Serial: "TEST123456",
},
}
}
// TestEncryptDecryptHostInfo 测试主机信息的加密和解密
func TestEncryptDecryptHostInfo(t *testing.T) {
hostInfo := createMockHostInfo()
key := "test-key"
// 加密主机信息
encrypted, err := EncryptHostInfo(hostInfo, key)
fmt.Println("加密后的主机信息:", encrypted)
if err != nil {
t.Fatalf("加密主机信息失败: %v", err)
}
// 确保加密后的文本不为空
if encrypted == "" {
t.Errorf("加密后的主机信息为空")
}
// 解密主机信息
decrypted, err := DecryptHostInfo(encrypted, hostInfo, key)
if err != nil {
t.Fatalf("解密主机信息失败: %v", err)
}
if !decrypted {
t.Errorf("解密后的主机信息与原始主机信息不同")
}
}
// TestEncryptDecryptHostInfoWithModifiedInfo 测试使用修改过的主机信息解密
func TestEncryptDecryptHostInfoWithModifiedInfo(t *testing.T) {
originalHostInfo := createMockHostInfo()
key := "test-key"
// 加密主机信息
encrypted, err := EncryptHostInfo(originalHostInfo, key)
if err != nil {
t.Fatalf("加密主机信息失败: %v", err)
}
t.Logf("加密后的主机信息: %s", encrypted)
// 创建修改过的主机信息
modifiedHostInfo := originalHostInfo
// 加密修改过的主机信息
encryptedModified, err := EncryptHostInfo(modifiedHostInfo, key)
if err != nil {
t.Fatalf("加密修改过的主机信息失败: %v", err)
}
t.Logf("加密修改后的主机信息: %s", encryptedModified)
// 使用修改过的主机信息尝试解密,期望失败
isOK, err := DecryptHostInfo(encryptedModified, originalHostInfo, key)
fmt.Println("解密后的主机信息 error :", err)
if isOK {
t.Errorf("解密后的主机信息与修改后的主机信息相同, 不应该相同")
}
}

View File

@@ -0,0 +1,39 @@
package totp_tier_one
import (
otp "cmii-uav-watchdog-otp"
"cmii-uav-watchdog-otp/totp"
"time"
)
var TierOneTOTPSecretOpts = totp.GenerateOpts{
SecretSize: 64,
Issuer: "cmii-uav-watchdog-center",
AccountName: "cmii-uav-watchdog-center",
Period: 30 * 2 * 30, // 30分钟
Digits: otp.DigitsEight,
Algorithm: otp.AlgorithmSHA256,
}
// GenerateTierOneTOTPCode 生成一级TOTP验证码
func GenerateTierOneTOTPCode(secret string) (string, error) {
validateOpts := totp.ValidateOpts{}
validateOpts.ConvertToValidateOpts(TierOneTOTPSecretOpts)
code, err := totp.GenerateCodeCustom(secret, time.Now(), validateOpts)
if err != nil {
return "", err
}
return code, nil
}
// VerifyTierOneTOTPCode 验证一级TOTP验证码
func VerifyTierOneTOTPCode(code string, secret string) bool {
validateOpts := totp.ValidateOpts{}
validateOpts.ConvertToValidateOpts(TierOneTOTPSecretOpts)
valid, err := totp.ValidateCustom(code, secret, time.Now(), validateOpts)
if err != nil {
return false
}
return valid
}

View File

@@ -0,0 +1,54 @@
package totp_tier_two
import (
"cmii-uav-watchdog-common/utils"
"cmii-uav-watchdog-common/wdd_log"
otp "cmii-uav-watchdog-otp"
"cmii-uav-watchdog-otp/totp"
)
var TierTwoTOTPSecretOpts = totp.GenerateOpts{
SecretSize: 32,
Issuer: "cmii-uav-watchdog",
AccountName: "cmii-uav-watchdog",
Period: 30,
Secret: []byte{},
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
Rand: nil,
}
// GenerateTierTwoTOTPSecret 生成二级TOTP密钥
func GenerateTierTwoTOTPSecret() (string, error) {
secret, err := totp.Generate(TierTwoTOTPSecretOpts)
if err != nil {
wdd_log.Error("生成TOTP密钥失败: %v", err)
return "", err
}
wdd_log.Info("生成TOTP密钥成功: %s", secret.Secret())
return secret.Secret(), nil
}
// GenerateTierTwoTOTPCode 生成二级TOTP验证码
func GenerateTierTwoTOTPCode(secret string) (string, error) {
validateOpts := totp.ValidateOpts{}
validateOpts.ConvertToValidateOpts(TierTwoTOTPSecretOpts)
code, err := totp.GenerateCodeCustom(secret, utils.CurentTime(), validateOpts)
if err != nil {
wdd_log.Error("TierTwo TOTP验证码生成失败: %v", err)
return "", err
}
return code, nil
}
// VerifyTierTwoTOTPCode 验证二级TOTP验证码
func VerifyTierTwoTOTPCode(code string, secret string) bool {
validateOpts := totp.ValidateOpts{}
validateOpts.ConvertToValidateOpts(TierTwoTOTPSecretOpts)
valid, err := totp.ValidateCustom(code, secret, utils.CurentTime(), validateOpts)
if err != nil {
wdd_log.Error("TierTwo TOTP验证失败: %v", err)
return false
}
return valid
}

View File

@@ -0,0 +1,36 @@
package utils
import (
"time"
)
var CST = time.FixedZone("CST", 8*60*60)
// CurentTimeString 获取当前时间字符串 东八区时间
func CurentTimeString() string {
return time.Now().In(CST).Format("2006-01-02 15:04:05")
}
// CurentTime 获取当前时间 东八区时间
func CurentTime() time.Time {
return time.Now().In(CST)
}
// CurentTimeUnix 获取当前时间戳 东八区时间
func CurentTimeUnix() int64 {
return CurentTime().Unix()
}
// ParseTimeString 解析时间字符串 东八区时间
func ParseTimeString(timeString string) (time.Time, error) {
return time.ParseInLocation("2006-01-02 15:04:05", timeString, CST)
}
// ParseTimeUnix 解析时间戳 东八区时间
func ParseTimeUnix(unix int64) (time.Time, error) {
return time.Unix(unix, 0).In(CST), nil
}

View File

@@ -0,0 +1,150 @@
package wdd_log
import (
"cmii-uav-watchdog-common/utils"
"fmt"
"sync"
)
const (
colorReset = "\033[0m"
colorRed = "\033[31m"
colorGreen = "\033[32m"
colorYellow = "\033[33m"
colorBlue = "\033[34m"
colorPurple = "\033[35m"
colorCyan = "\033[36m"
colorWhite = "\033[37m"
)
type LogLevel int
const (
LevelDebug LogLevel = iota
LevelInfo
LevelWarn
LevelError
LevelFatal
LogPrefix = "cmii-uav-watchdog"
)
var logLevelColors = map[LogLevel]string{
LevelDebug: colorCyan,
LevelInfo: colorGreen,
LevelWarn: colorYellow,
LevelError: colorRed,
LevelFatal: colorPurple,
}
var logLevelNames = map[LogLevel]string{
LevelDebug: "DEBUG",
LevelInfo: "INFO",
LevelWarn: "WARN",
LevelError: "ERROR",
LevelFatal: "FATAL",
}
// Logger 日志单例结构体
type Logger struct {
// EnableDebug 是否启用Debug日志
EnableDebug bool
}
var (
instance *Logger
once sync.Once
)
// GetInstance 获取Logger单例实例
// 返回Logger的单例对象指针
func GetInstance() *Logger {
once.Do(func() {
instance = &Logger{
EnableDebug: false, // 默认不启用Debug日志
}
})
return instance
}
// SetEnableDebug 设置是否启用Debug日志
// enable: true表示启用Debug日志false表示禁用
func (l *Logger) SetEnableDebug(enable bool) {
l.EnableDebug = enable
}
// Log 记录指定级别的日志
// level: 日志级别
// format: 日志格式
// args: 格式化参数
func (l *Logger) Log(level LogLevel, format string, args ...interface{}) {
// Debug级别日志在未启用时不输出
if level == LevelDebug && !l.EnableDebug {
return
}
now := utils.CurentTimeString()
color := logLevelColors[level]
levelName := logLevelNames[level]
message := fmt.Sprintf(format, args...)
fmt.Printf("[%s] %s %s%s%s %s\n", LogPrefix, now, color, levelName, colorReset, message)
}
// Debug 记录Debug级别日志
// format: 日志格式
// args: 格式化参数
func (l *Logger) Debug(format string, args ...interface{}) {
l.Log(LevelDebug, format, args...)
}
// Info 记录Info级别日志
// format: 日志格式
// args: 格式化参数
func (l *Logger) Info(format string, args ...interface{}) {
l.Log(LevelInfo, format, args...)
}
// Warn 记录Warn级别日志
// format: 日志格式
// args: 格式化参数
func (l *Logger) Warn(format string, args ...interface{}) {
l.Log(LevelWarn, format, args...)
}
// Error 记录Error级别日志
// format: 日志格式
// args: 格式化参数
func (l *Logger) Error(format string, args ...interface{}) {
l.Log(LevelError, format, args...)
}
// Fatal 记录Fatal级别日志
// format: 日志格式
// args: 格式化参数
func (l *Logger) Fatal(format string, args ...interface{}) {
l.Log(LevelFatal, format, args...)
}
// 为了兼容原有代码,保留全局函数,但内部调用单例实例
func Log(level LogLevel, format string, args ...interface{}) {
GetInstance().Log(level, format, args...)
}
func Debug(format string, args ...interface{}) {
GetInstance().Debug(format, args...)
}
func Info(format string, args ...interface{}) {
GetInstance().Info(format, args...)
}
func Warn(format string, args ...interface{}) {
GetInstance().Warn(format, args...)
}
func Error(format string, args ...interface{}) {
GetInstance().Error(format, args...)
}
func Fatal(format string, args ...interface{}) {
GetInstance().Fatal(format, args...)
}

View File

@@ -34,7 +34,7 @@ import (
"strings"
)
const debug = true
const debug = false
// Validate a HOTP passcode given a counter and secret.
// This is a shortcut for ValidateCustom, with parameters that

View File

@@ -1,24 +1,42 @@
package config
import (
"cmii-uav-watchdog-common/wdd_log"
"os"
"sync"
"gopkg.in/yaml.v3"
)
const (
localAuthFilePath = "/cmii/cmii-uav-watchdog/auth_code.json"
configFilePath = "/cmii/cmii-uav-watchdog/config.yaml"
)
// Config 配置结构体
type Config struct {
Server struct {
Port string `mapstructure:"port"`
Debug bool `mapstructure:"debug"`
} `mapstructure:"server"`
Auth struct {
Secret string `mapstructure:"secret"` // TOTP密钥
AuthFilePath string `mapstructure:"auth_file"` // 授权文件路径
TimeOffsetAllowed int64 `mapstructure:"time_offset_allowed"` // 允许的时间偏移(秒)
} `mapstructure:"auth"`
TierOneAuth struct {
TierOneSecret string `mapstructure:"tier_one_secret" yaml:"tier_one_secret"` // TOTP密钥
TimeOffsetAllowed int64 `mapstructure:"time_offset_allowed" yaml:"time_offset_allowed"` // 允许的时间偏移(秒)
LocalAuthFilePath string `mapstructure:"local_auth_file_path" yaml:"local_auth_file_path"` // 本地授权文件路径
} `mapstructure:"tier_one_auth" yaml:"tier_one_auth"`
WatchdogCenter struct {
URL string `mapstructure:"url"` // 一级授权中心地址
} `mapstructure:"watchdog_center"`
URL string `mapstructure:"url" yaml:"url"` // 一级授权中心地址
} `mapstructure:"watchdog_center" yaml:"watchdog_center"`
Project struct {
ProjectNamespace string `mapstructure:"project_namespace" yaml:"project_namespace"` // 项目命名空间
} `mapstructure:"project" yaml:"project"`
TierTwoAuth struct {
TierTwoSecret string `mapstructure:"tier_two_secret" yaml:"tier_two_secret"` // 二级授权密钥
} `mapstructure:"tier_two_auth" yaml:"tier_two_auth"`
}
var (
@@ -27,22 +45,56 @@ var (
)
// LoadConfig 加载配置
func LoadConfig(path string) error {
once.Do(func() {
// 设置默认配置
config.Server.Port = "8080"
config.Auth.Secret = "default-secret-key-please-change-in-production"
config.Auth.AuthFilePath = "./auth-storage.json"
config.Auth.TimeOffsetAllowed = 3600 // 默认允许1小时的时间偏移
config.WatchdogCenter.URL = "http://localhost:8081"
// 单例模式, 如果已经初始化过, 则直接返回
// 如果config文件不存在,报错 无法启动
// 不使用viper,用最简单的方式读取配置文件,解析其中的配置
// 如果解析失败,无法得到config 则报错 无法启动
func LoadConfig() error {
var err error
once.Do(func() {
// 读取配置文件
data, readErr := os.ReadFile(configFilePath)
if readErr != nil {
wdd_log.Fatal("无法读取配置文件: %v", readErr)
err = readErr
return
}
// 解析配置文件
parseErr := yaml.Unmarshal(data, &config)
if parseErr != nil {
wdd_log.Fatal("无法解析配置文件: %v", parseErr)
err = parseErr
return
}
// 设置本地授权文件保存地点
config.TierOneAuth.LocalAuthFilePath = localAuthFilePath
wdd_log.Info("[Config] - 本地授权文件保存地点: %s", config.TierOneAuth.LocalAuthFilePath)
// 初始化日志系统
InitLogger()
// 这里可以添加从文件加载配置的逻辑比如使用标准库读取JSON或YAML文件
// 但目前使用默认值替代
})
return nil
return err
}
// GetConfig 获取配置
func GetConfig() *Config {
return &config
}
// InitLogger 初始化日志系统
// 从环境变量CMII_UAV_WATCHDOG_DEBUG读取配置决定是否开启debug日志
func InitLogger() {
// 获取Logger单例实例
logger := wdd_log.GetInstance()
// 设置是否启用Debug日志
logger.SetEnableDebug(config.Server.Debug)
// 记录日志初始化信息
logger.Info("日志系统初始化完成Debug日志状态: %v", config.Server.Debug)
}

View File

@@ -1,10 +1,16 @@
server:
port: "8080"
port: "8080" # 服务器端口
debug: true
auth:
secret: "your-secret-key-for-totp"
auth_file: "./data/auth.json"
time_offset_allowed: 3600 # 允许的时间偏移,单位秒
tier_one_auth:
tier_one_secret: "NK537TIWSUOFIS7SYCUJ6A7FPOGFVM3UH67TJRX3IYQAHKZXK2X7SBAA6JOXZVSV3U6K5YZUX7Q6TWOPK6YCRU6MIML33ZJFBN55I2Q" # TOTP密钥
time_offset_allowed: 30 # 允许的时间偏移(秒)
watchdog_center:
url: "http://center-service:8080/api"
url: "https://watchdog-center.example.com" # 一级授权中心地址
project:
project_namespace: "uavcloud-devflight" # 项目命名空间
tier_two_auth:
tier_two_secret: "your_tier_two_secret_here" # 二级授权密钥

View File

@@ -2,7 +2,7 @@ package controllers
import (
models2 "cmii-uav-watchdog-common/models"
"cmii-uav-watchdog/models"
"cmii-uav-watchdog-common/wdd_log"
"cmii-uav-watchdog/services"
"net/http"
@@ -26,59 +26,52 @@ func (ac *AuthController) GenerateAuthFile(c *gin.Context) {
// 生成授权文件
authFile, err := ac.authService.GenerateAuthorizationFile()
if err != nil {
c.JSON(http.StatusInternalServerError, models.Response{
Code: 500,
Message: "生成授权文件失败",
Data: nil,
})
c.JSON(http.StatusInternalServerError, authFile)
return
}
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "生成授权文件成功",
Data: authFile,
})
c.JSON(http.StatusOK, authFile)
}
// ReceiveAuthCode 接收授权码
func (ac *AuthController) ReceiveAuthCode(c *gin.Context) {
var authCode models2.AuthorizationCode
if err := c.ShouldBindJSON(&authCode); err != nil {
c.JSON(http.StatusBadRequest, models.Response{
Code: 400,
Message: "无效的请求参数",
Data: nil,
})
c.JSON(http.StatusBadRequest, authCode)
return
}
// 处理授权码
err := ac.authService.ProcessAuthorizationCode(authCode)
if err != nil {
c.JSON(http.StatusInternalServerError, models.Response{
Code: 500,
Message: "处理授权码失败: " + err.Error(),
Data: nil,
})
wdd_log.Error("处理授权码失败: %v", err)
c.JSON(http.StatusInternalServerError, authCode)
return
}
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "处理授权码成功",
Data: nil,
})
c.JSON(http.StatusOK, authCode)
}
// NotifyAuthInfo 通知授权信息
func (ac *AuthController) NotifyAuthInfo(c *gin.Context) {
// 获取授权信息
authInfo := ac.authService.GetAuthorizationInfo()
authInfo, err := ac.authService.GetAuthorizationInfo()
if err != nil {
c.JSON(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "获取授权信息成功",
Data: authInfo,
c.JSON(http.StatusOK, authInfo)
}
// GetAllAuthorizedHosts 获取所有已授权的主机信息
func (ac *AuthController) GetAllAuthorizedHosts(c *gin.Context) {
// 获取所有已授权的主机信息
authorizedHosts := ac.authService.GetAllAuthorizedHosts()
// 返回已授权主机信息
c.JSON(http.StatusOK, gin.H{
"authorized_host_count": len(authorizedHosts),
"authorized_hosts": authorizedHosts,
})
}

View File

@@ -0,0 +1,24 @@
package controllers
import (
"cmii-uav-watchdog/services"
"github.com/gin-gonic/gin"
)
// CMIIController CMII控制器
type CMIIController struct {
cmiiService *services.CMIIService
}
// NewCMIIController 创建CMII控制器
func NewCMIIController() *CMIIController {
return &CMIIController{
cmiiService: services.NewCMIIService(),
}
}
// HandleHostInfo 处理主机信息
func (cc *CMIIController) HandleHostInfo(c *gin.Context) {
cc.cmiiService.HandleHostInfo(c)
}

View File

@@ -2,7 +2,7 @@ package controllers
import (
models2 "cmii-uav-watchdog-common/models"
"cmii-uav-watchdog/models"
"cmii-uav-watchdog-common/wdd_log"
"cmii-uav-watchdog/services"
"net/http"
@@ -25,28 +25,36 @@ func NewHeartbeatController() *HeartbeatController {
func (hc *HeartbeatController) HandleHeartbeat(c *gin.Context) {
var req models2.HeartbeatRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.Response{
Code: 400,
Message: "无效的请求参数",
Data: nil,
})
c.JSON(http.StatusBadRequest, req)
return
}
// 处理心跳
resp, err := hc.heartbeatService.ProcessHeartbeat(req)
if err != nil {
c.JSON(http.StatusInternalServerError, models.Response{
Code: 500,
Message: "处理心跳失败: " + err.Error(),
Data: nil,
c.JSON(http.StatusInternalServerError, resp)
return
}
c.JSON(http.StatusOK, resp)
}
// GetAllHeartbeatHosts 获取所有心跳主机信息
func (hc *HeartbeatController) GetAllHeartbeatHosts(c *gin.Context) {
// 获取所有主机信息
hostInfoMap, err := hc.heartbeatService.GetAllHostInfo()
if err != nil {
wdd_log.Error("获取所有心跳主机信息失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"error": "获取所有心跳主机信息失败",
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "处理心跳成功",
Data: resp,
// 返回主机信息
c.JSON(http.StatusOK, gin.H{
"host_count": len(hostInfoMap),
"hosts": hostInfoMap,
})
}

View File

@@ -2,15 +2,15 @@ module cmii-uav-watchdog
go 1.24
toolchain go1.24.1
toolchain go1.24.0
require (
cmii-uav-watchdog-common v0.0.0
cmii-uav-watchdog-otp v0.0.0
github.com/gin-gonic/gin v1.9.1
)
require (
cmii-uav-watchdog-otp v0.0.0 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect

View File

@@ -1,80 +1,18 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -85,95 +23,19 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -181,35 +43,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -217,341 +56,31 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@@ -1,26 +1,25 @@
package main
import (
"cmii-uav-watchdog-common/wdd_log"
"cmii-uav-watchdog/config"
"cmii-uav-watchdog/routes"
"cmii-uav-watchdog/services"
"log"
"time"
)
func main() {
// 初始化配置信息
err := config.LoadConfig("./config/config.yaml")
err := config.LoadConfig()
if err != nil {
log.Fatalf("加载配置文件失败: %v", err)
return
panic(err)
}
// 初始化授权服务(使用单例模式)
authService := services.NewAuthService()
if authService == nil {
log.Fatalf("初始化授权服务失败")
wdd_log.Fatal("初始化授权服务失败")
return
}
@@ -40,11 +39,11 @@ func main() {
// 启动服务
port := config.GetConfig().Server.Port
if port == "" {
port = "8080"
port = "8990"
}
log.Printf("服务启动在 :%s", port)
wdd_log.Info("服务启动在 :%s", port)
if err := r.Run(":" + port); err != nil {
log.Fatalf("服务启动失败: %v", err)
wdd_log.Error("服务启动失败: %v", err)
}
}

View File

@@ -1,8 +0,0 @@
package models
// Response 通用响应模型
type Response struct {
Code int `json:"code"` // 状态码
Message string `json:"message"` // 响应消息
Data interface{} `json:"data"` // 响应数据
}

View File

@@ -21,9 +21,10 @@ func SetupRouter() *gin.Engine {
auth := api.Group("/authorization")
{
authController := controllers.NewAuthController()
auth.POST("/generate", authController.GenerateAuthFile) // 授权文件生成
auth.POST("/code", authController.ReceiveAuthCode) // 授权码接收
auth.POST("/notify", authController.NotifyAuthInfo) // 授权信息通知
auth.GET("/generate", authController.GenerateAuthFile) // 授权文件生成
auth.POST("/auth", authController.ReceiveAuthCode) // 授权码接收
auth.POST("/info", authController.NotifyAuthInfo) // 授权信息通知
auth.GET("/hosts", authController.GetAllAuthorizedHosts) // 获取所有已授权主机信息
}
// 心跳检测路由
@@ -31,6 +32,14 @@ func SetupRouter() *gin.Engine {
{
heartbeatController := controllers.NewHeartbeatController()
heartbeat.POST("", heartbeatController.HandleHeartbeat) // 心跳检测
heartbeat.GET("/hosts", heartbeatController.GetAllHeartbeatHosts) // 获取所有心跳主机信息
}
// 暴露CMII的接口
cmii := api.Group("/cmii")
{
cmiiController := controllers.NewCMIIController()
cmii.POST("/host/info/all", cmiiController.HandleHostInfo) // 处理主机信息
}
}

View File

@@ -2,14 +2,15 @@ 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"
"cmii-uav-watchdog/utils"
"encoding/json"
"errors"
"log"
"os"
"sync"
"time"
)
// 单例相关变量
@@ -22,8 +23,8 @@ var (
// AuthService 授权服务
type AuthService struct {
mu sync.RWMutex
hostInfoSet map[string]models2.HostInfo // 主机信息集合
authorizationInfo models2.AuthorizationStorage // 授权信息
heartBeatHostInfoMap map[string]models2.HostInfo // 主机信息集合
authorizationInfo *models2.AuthorizationStorage // 授权信息
totpService *TOTPService
initialized bool
}
@@ -42,7 +43,7 @@ func NewAuthService() *AuthService {
// 创建新实例
service := &AuthService{
hostInfoSet: make(map[string]models2.HostInfo),
heartBeatHostInfoMap: make(map[string]models2.HostInfo),
totpService: NewTOTPService(),
initialized: false,
}
@@ -50,12 +51,17 @@ func NewAuthService() *AuthService {
// 尝试从本地加载授权信息
service.loadAuthorizationInfo()
// 确保 authorizationInfo 不为 nil
if service.authorizationInfo == nil {
service.authorizationInfo = &models2.AuthorizationStorage{}
}
// 判断 项目级别的 TOTP密钥是否为空
// 若为空 则生成一个 二级TOTP密钥 然后持久化写入到授权文件中
if service.authorizationInfo.SecondTOTPSecret == "" {
secondTOTPSecret, err := service.totpService.GenerateTierTwoTOTPSecret()
secondTOTPSecret, err := totp_tier_two.GenerateTierTwoTOTPSecret()
if err != nil {
log.Printf("生成二级TOTP密钥失败: %v", err)
wdd_log.Error("生成二级TOTP密钥失败: %v", err)
return
}
service.authorizationInfo.SecondTOTPSecret = secondTOTPSecret
@@ -63,7 +69,7 @@ func NewAuthService() *AuthService {
// 持久化写入到授权文件中
err = service.saveAuthorizationInfo()
if err != nil {
log.Printf("持久化写入授权文件失败: %v", err)
wdd_log.Error("持久化写入授权文件失败: %v", err)
return
}
}
@@ -80,8 +86,13 @@ func (as *AuthService) AddHostInfo(hostInfo models2.HostInfo) {
as.mu.Lock()
defer as.mu.Unlock()
hostKey := utils.GenerateHostKey(hostInfo)
as.hostInfoSet[hostKey] = hostInfo
hostKey, err := totp_tier_one.EncryptHostInfo(hostInfo, as.totpService.tierOneSecret)
if err != nil {
wdd_log.Error("加密主机信息失败: %v", err)
return
}
as.heartBeatHostInfoMap[hostKey] = hostInfo
}
// GenerateAuthorizationFile 生成授权文件
@@ -90,96 +101,143 @@ func (as *AuthService) GenerateAuthorizationFile() (*models2.AuthorizationFile,
defer as.mu.RUnlock()
// 检查是否有主机信息
if len(as.hostInfoSet) == 0 {
if len(as.heartBeatHostInfoMap) == 0 {
return nil, errors.New("没有可用的主机信息")
}
// 生成TOTP验证码
totpCode, err := as.totpService.GenerateTierTwoTOTPCode(as.authorizationInfo.SecondTOTPSecret)
if err != nil {
return nil, err
}
// 获取当前时间
now := time.Now()
// 准备初次授权时间
firstAuthTime := now
if as.initialized {
firstAuthTime = as.authorizationInfo.FirstAuthTime
}
// 计算时间偏移
timeOffset := now.Unix() - firstAuthTime.Unix()
// 加密主机信息
encryptedHosts := make([]string, 0)
for _, hostInfo := range as.hostInfoSet {
encrypted, err := utils.EncryptHostInfo(hostInfo)
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
}
encryptedHosts = append(encryptedHosts, encrypted)
// 生成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{
EncryptedHosts: encryptedHosts,
TOTPCode: totpCode,
CurrentTime: now,
FirstAuthTime: firstAuthTime,
TimeOffset: timeOffset,
EncryptedHostMap: encryptedHostMap,
TOTPCode: tierOneTOTPCode,
CurrentTime: utils.CurentTimeString(),
ProjectNamespace: projectNamespace,
EncryptedNamespace: encryptedNamespace,
}
return authFile, nil
}
// ProcessAuthorizationCode 处理授权码
func (as *AuthService) ProcessAuthorizationCode(code models2.AuthorizationCode) error {
func (as *AuthService) ProcessAuthorizationCode(authCode models2.AuthorizationCode) error {
as.mu.Lock()
defer as.mu.Unlock()
// 验证TOTP
if !as.totpService.VerifyTierTwoTOTPCode(code.TOTPCode, as.authorizationInfo.SecondTOTPSecret) {
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 := time.Now()
now := utils.CurentTimeString()
// 准备初次授权时间
firstAuthTime := now
if as.initialized {
if as.authorizationInfo.FirstAuthTime != "" {
wdd_log.Debug("已存在初次授权时间: %s", as.authorizationInfo.FirstAuthTime)
firstAuthTime = as.authorizationInfo.FirstAuthTime
}
}
// 计算授权时间偏移
timeOffset := now.Unix() - code.CurrentTime.Unix()
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(code)
authCodeJSON, err := json.Marshal(authCode)
if err != nil {
wdd_log.Error("加密授权码失败: %v", err)
return err
}
encryptedCode, err := utils.Encrypt(string(authCodeJSON), config.GetConfig().Auth.Secret)
// 加密授权码 全部加密
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{
EncryptedCode: encryptedCode,
as.authorizationInfo = &models2.AuthorizationStorage{
EncryptedAuthrizationCode: encryptedCode,
FirstAuthTime: firstAuthTime,
TimeOffset: timeOffset,
AuthorizedHosts: code.EncryptedHosts,
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
@@ -195,19 +253,14 @@ func (as *AuthService) IsHostAuthorized(hostInfo models2.HostInfo) bool {
}
// 加密主机信息
encrypted, err := utils.EncryptHostInfo(hostInfo)
encrypted, err := totp_tier_one.EncryptHostInfo(hostInfo, as.totpService.tierOneSecret)
if err != nil {
return false
}
// 检查是否在已授权列表中
for _, host := range as.authorizationInfo.AuthorizedHosts {
if host == encrypted {
return true
}
}
return false
_, ok := as.authorizationInfo.AuthorizedHostMap[encrypted]
return ok
}
// VerifyAuthorizationTime 验证授权时间有效性
@@ -220,43 +273,87 @@ func (as *AuthService) VerifyAuthorizationTime() {
}
// 获取当前时间和存储的初次授权时间
now := time.Now()
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 := now.Unix() - as.authorizationInfo.FirstAuthTime.Unix()
actualOffset := nowTime.Unix() - firstAuthTime.Unix()
// 计算偏差
offsetDiff := actualOffset - storedOffset
// 获取允许的时间偏移
allowedOffset := config.GetConfig().Auth.TimeOffsetAllowed
allowedOffset := config.GetConfig().TierOneAuth.TimeOffsetAllowed
// 检查偏差是否超过允许范围
if offsetDiff > allowedOffset || offsetDiff < -allowedOffset {
log.Printf("检测到时间篡改! 存储偏移: %d, 实际偏移: %d, 差值: %d",
wdd_log.Warn("检测到时间篡改! 存储偏移: %d, 实际偏移: %d, 差值: %d",
storedOffset, actualOffset, offsetDiff)
// 这里可以添加更多的处理逻辑,例如发送警告、禁用授权等
}
}
// GetAuthorizationInfo 获取授权信息
func (as *AuthService) GetAuthorizationInfo() interface{} {
func (as *AuthService) GetAuthorizationInfo() (models2.AuthorizationStorage, error) {
as.mu.RLock()
defer as.mu.RUnlock()
if !as.initialized {
return map[string]interface{}{
"authorized": false,
"message": "未授权",
}
return models2.AuthorizationStorage{}, errors.New("未授权")
}
return map[string]interface{}{
"authorized": true,
"authorized_hosts": len(as.authorizationInfo.AuthorizedHosts),
"first_auth_time": as.authorizationInfo.FirstAuthTime,
// 解密授权码
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 保存授权信息到文件
@@ -266,30 +363,40 @@ func (as *AuthService) saveAuthorizationInfo() error {
return err
}
return os.WriteFile(config.GetConfig().Auth.AuthFilePath, data, 0600)
return os.WriteFile(config.GetConfig().TierOneAuth.LocalAuthFilePath, data, 0600)
}
// loadAuthorizationInfo 从文件加载授权信息
func (as *AuthService) loadAuthorizationInfo() {
filePath := config.GetConfig().Auth.AuthFilePath
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 {
log.Printf("读取授权文件失败: %v", err)
wdd_log.Error("读取授权文件失败: %v", err)
// 初始化一个空的授权信息
as.authorizationInfo = &models2.AuthorizationStorage{}
return
}
var authInfo models2.AuthorizationStorage
var authInfo *models2.AuthorizationStorage
if err := json.Unmarshal(data, &authInfo); err != nil {
log.Printf("解析授权文件失败: %v", err)
wdd_log.Error("解析授权文件失败: %v", err)
// 初始化一个空的授权信息
as.authorizationInfo = &models2.AuthorizationStorage{}
return
}
if authInfo == nil {
authInfo = &models2.AuthorizationStorage{}
}
as.authorizationInfo = authInfo
as.initialized = true
}

View File

@@ -0,0 +1,36 @@
package services
import (
"cmii-uav-watchdog-common/models"
"net/http"
"github.com/gin-gonic/gin"
)
// CMIIService CMII服务
type CMIIService struct {
authService *AuthService
}
// NewCMIIService 创建CMII服务
func NewCMIIService() *CMIIService {
return &CMIIService{
authService: NewAuthService(),
}
}
// HandleHostInfo 处理主机信息
func (cs *CMIIService) HandleHostInfo(c *gin.Context) {
// 获取主机信息
hostInfo := cs.authService.GetAllHostInfo()
// 返回单纯的主机信息
hostInfoList := make([]models.HostInfo, 0)
for _, host := range hostInfo {
hostInfoList = append(hostInfoList, host)
}
// 返回主机信息
c.JSON(http.StatusOK, hostInfoList)
}

View File

@@ -2,8 +2,10 @@ package services
import (
"cmii-uav-watchdog-common/models"
"cmii-uav-watchdog-common/totp_tier_two"
"cmii-uav-watchdog-common/utils"
"cmii-uav-watchdog-common/wdd_log"
"errors"
"log"
"time"
)
@@ -30,6 +32,7 @@ func (hs *HeartbeatService) ProcessHeartbeat(req models.HeartbeatRequest) (*mode
secondTOTPSecret := hs.authService.authorizationInfo.SecondTOTPSecret
if secondTOTPSecret == "" {
wdd_log.Error("二级TOTP密钥为空")
return nil, errors.New("二级TOTP密钥为空")
}
@@ -47,12 +50,13 @@ func (hs *HeartbeatService) ProcessHeartbeat(req models.HeartbeatRequest) (*mode
}
// 检查totp验证码是否有效
if !hs.totpService.VerifyTierTwoTOTPCode(req.TOTPCode, secondTOTPSecret) {
if !totp_tier_two.VerifyTierTwoTOTPCode(req.TOTPCode, secondTOTPSecret) {
// 解析认证主机的相关信息
// 计算 请求时间与当前时间的时间差
utils.CurentTimeUnix()
diff := time.Now().Unix() - req.Timestamp
log.Printf("心跳请求时间与当前时间的时间差: %d", diff)
wdd_log.Error("TOTP验证失败心跳请求时间与当前时间的时间差: %d", diff)
return nil, errors.New("无效的TOTP验证码请检查系统时间是否正确")
}
@@ -61,7 +65,7 @@ func (hs *HeartbeatService) ProcessHeartbeat(req models.HeartbeatRequest) (*mode
authorized := hs.authService.IsHostAuthorized(req.HostInfo)
// 生成TOTP验证码
totpCode, err := hs.totpService.GenerateTierTwoTOTPCode(secondTOTPSecret)
totpCode, err := totp_tier_two.GenerateTierTwoTOTPCode(secondTOTPSecret)
if err != nil {
return nil, err
}
@@ -82,3 +86,16 @@ func (hs *HeartbeatService) isTimestampValid(timestamp int64) bool {
// 允许5分钟的时间偏差
return diff < 300 && diff > -300
}
// GetAllHostInfo 获取所有主机信息
func (hs *HeartbeatService) GetAllHostInfo() (map[string]models.HostInfo, error) {
// 调用AuthService的方法获取所有主机信息
hostInfoMap := hs.authService.GetAllHostInfo()
// 如果没有主机信息返回一个空的map
if len(hostInfoMap) == 0 {
return make(map[string]models.HostInfo), nil
}
return hostInfoMap, nil
}

View File

@@ -1,41 +1,31 @@
package services
import (
"cmii-uav-watchdog-common/totp_tier_one"
"cmii-uav-watchdog/config"
"log"
"time"
otp "cmii-uav-watchdog-otp"
"cmii-uav-watchdog-otp/totp"
)
var tierTwoTOTPSecretOpts = totp.GenerateOpts{
SecretSize: 32,
Issuer: "cmii-uav-watchdog",
AccountName: "cmii-uav-watchdog",
Period: 30,
Secret: []byte{},
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
Rand: nil,
}
// TOTPService TOTP服务
type TOTPService struct {
secret string
tierOneSecret string
}
// NewTOTPService 创建TOTP服务
func NewTOTPService() *TOTPService {
secret := config.GetConfig().TierOneAuth.TierOneSecret
if secret == "" {
panic("TierOne TOTP tierOneSecret is not set ! can not start the service!")
}
return &TOTPService{
secret: config.GetConfig().Auth.Secret,
tierOneSecret: secret,
}
}
// GenerateTierOneTOTP 生成一级TOTP验证码
func (ts *TOTPService) GenerateTierOneTOTP() (string, error) {
// 使用当前时间生成TOTP
code, err := totp.GenerateCode(ts.secret, time.Now())
code, err := totp_tier_one.GenerateTierOneTOTPCode(ts.tierOneSecret)
if err != nil {
return "", err
}
@@ -46,41 +36,7 @@ func (ts *TOTPService) GenerateTierOneTOTP() (string, error) {
// VerifyTierOneTOTP 验证一级TOTP验证码
func (ts *TOTPService) VerifyTierOneTOTP(code string) bool {
// 验证TOTP
valid := totp.Validate(code, ts.secret)
if !valid {
return false
}
valid := totp_tier_one.VerifyTierOneTOTPCode(code, ts.tierOneSecret)
return true
}
// GenerateTierTwoTOTPSecret 生成二级TOTP密钥
func (ts *TOTPService) GenerateTierTwoTOTPSecret() (string, error) {
secret, err := totp.Generate(tierTwoTOTPSecretOpts)
if err != nil {
log.Printf("生成TOTP密钥失败: %v", err)
return "", err
}
return secret.Secret(), nil
}
// GenerateTierTwoTOTPCode 生成二级TOTP验证码
func (ts *TOTPService) GenerateTierTwoTOTPCode(secret string) (string, error) {
code, err := totp.GenerateCode(secret, time.Now())
if err != nil {
return "", err
}
return code, nil
}
// VerifyTierTwoTOTPCode 验证二级TOTP验证码
func (ts *TOTPService) VerifyTierTwoTOTPCode(code string, secret string) bool {
validateOpts := totp.ValidateOpts{}
validateOpts.ConvertToValidateOpts(tierTwoTOTPSecretOpts)
valid, err := totp.ValidateCustom(code, secret, time.Now(), validateOpts)
if err != nil {
return false
}
return valid
}

View File

@@ -1,113 +0,0 @@
package utils
import (
"cmii-uav-watchdog-common/models"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"io"
)
// Encrypt 加密字符串
func Encrypt(plaintext string, key string) (string, error) {
// 创建hash
hasher := sha256.New()
hasher.Write([]byte(key))
keyBytes := hasher.Sum(nil)
// 创建cipher
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", err
}
// 创建gcm
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// 创建nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
// 加密
ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
// 编码为base64
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// Decrypt 解密字符串
func Decrypt(encrypted string, key string) (string, error) {
// 解码base64
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
if err != nil {
return "", err
}
// 创建hash
hasher := sha256.New()
hasher.Write([]byte(key))
keyBytes := hasher.Sum(nil)
// 创建cipher
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", err
}
// 创建gcm
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// 检查长度
if len(ciphertext) < gcm.NonceSize() {
return "", errors.New("密文太短")
}
// 分离nonce和密文
nonce, ciphertext := ciphertext[:gcm.NonceSize()], ciphertext[gcm.NonceSize():]
// 解密
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
// EncryptHostInfo 加密主机信息
func EncryptHostInfo(hostInfo models.HostInfo) (string, error) {
// 序列化主机信息
data, err := json.Marshal(hostInfo)
if err != nil {
return "", err
}
// 计算SHA256作为统一标识
hasher := sha256.New()
hasher.Write(data)
hash := base64.StdEncoding.EncodeToString(hasher.Sum(nil))
return hash, nil
}
// GenerateHostKey 生成主机唯一标识
func GenerateHostKey(hostInfo models.HostInfo) string {
// 序列化主机信息
key := hostInfo.UUID
if key == "" {
key = hostInfo.MAC
}
return key
}

View File

@@ -1 +0,0 @@
package cmii_uav_watchdog