diff --git a/api/waf_vpconfig_api.go b/api/waf_vpconfig_api.go index f842278..e9e92c0 100644 --- a/api/waf_vpconfig_api.go +++ b/api/waf_vpconfig_api.go @@ -5,7 +5,16 @@ import ( "SamWaf/model/common/response" "SamWaf/model/request" response2 "SamWaf/model/response" + "SamWaf/utils" "SamWaf/wafconfig" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "fmt" + "os" + "path/filepath" + "time" + "github.com/gin-gonic/gin" ) @@ -42,3 +51,226 @@ func (w *WafVpConfigApi) GetIpWhitelistApi(c *gin.Context) { response.OkWithDetailed(resp, "获取IP白名单成功", c) } + +// UpdateSslEnableApi 更新SSL启用状态 +func (w *WafVpConfigApi) UpdateSslEnableApi(c *gin.Context) { + var req request.WafVpConfigSslEnableUpdateReq + err := c.ShouldBindJSON(&req) + if err == nil { + // 调用配置文件更新函数 + err = wafconfig.UpdateSslEnable(req.SslEnable) + if err != nil { + response.FailWithMessage("更新SSL启用状态失败: "+err.Error(), c) + } else { + response.OkWithMessage("更新SSL启用状态成功,需要重启管理端生效", c) + } + } else { + response.FailWithMessage("解析请求失败", c) + } +} + +// GetSslStatusApi 获取SSL状态 +func (w *WafVpConfigApi) GetSslStatusApi(c *gin.Context) { + // 获取SSL启用状态 + sslEnable := global.GWAF_SSL_ENABLE + + // 检查证书文件是否存在 + certPath := filepath.Join(utils.GetCurrentDir(), "data", "ssl", "manager", "domain.crt") + keyPath := filepath.Join(utils.GetCurrentDir(), "data", "ssl", "manager", "domain.key") + + hasCert := false + certExpireAt := "" + certDomain := "" + certContent := "" + keyContent := "" + + // 检查证书文件是否存在 + if _, err := os.Stat(certPath); err == nil { + if _, err := os.Stat(keyPath); err == nil { + hasCert = true + // 读取证书文件获取证书信息 + certInfo := getCertInfo(certPath) + certExpireAt = certInfo.ExpireAt + certDomain = certInfo.Domain + + // 读取证书内容 + if certData, err := os.ReadFile(certPath); err == nil { + certContent = string(certData) + } + + // 读取私钥内容 + if keyData, err := os.ReadFile(keyPath); err == nil { + keyContent = string(keyData) + } + } + } + + // 构造响应数据 + resp := response2.WafVpConfigSslStatusGetResp{ + SslEnable: sslEnable, + HasCert: hasCert, + CertExpireAt: certExpireAt, + CertDomain: certDomain, + CertContent: certContent, + KeyContent: keyContent, + } + + response.OkWithDetailed(resp, "获取SSL状态成功", c) +} + +// UploadSslCertApi 上传SSL证书 +func (w *WafVpConfigApi) UploadSslCertApi(c *gin.Context) { + var req request.WafVpConfigSslUploadReq + err := c.ShouldBindJSON(&req) + if err != nil { + response.FailWithMessage("解析请求失败", c) + return + } + + // 验证证书内容不为空 + if req.CertContent == "" || req.KeyContent == "" { + response.FailWithMessage("证书内容和私钥内容不能为空", c) + return + } + + // 校验证书可用性 + if err := validateCertificate(req.CertContent, req.KeyContent); err != nil { + response.FailWithMessage("证书校验失败: "+err.Error(), c) + return + } + + // 创建目录 + sslDir := filepath.Join(utils.GetCurrentDir(), "data", "ssl", "manager") + if err := os.MkdirAll(sslDir, 0755); err != nil { + response.FailWithMessage("创建证书目录失败: "+err.Error(), c) + return + } + + // 保存证书文件 + certPath := filepath.Join(sslDir, "domain.crt") + keyPath := filepath.Join(sslDir, "domain.key") + + // 写入证书文件 + if err := os.WriteFile(certPath, []byte(req.CertContent), 0644); err != nil { + response.FailWithMessage("保存证书文件失败: "+err.Error(), c) + return + } + + // 写入私钥文件 + if err := os.WriteFile(keyPath, []byte(req.KeyContent), 0600); err != nil { + // 如果私钥保存失败,删除证书文件 + os.Remove(certPath) + response.FailWithMessage("保存私钥文件失败: "+err.Error(), c) + return + } + + response.OkWithMessage("上传SSL证书成功,需要重启管理端生效", c) +} + +// validateCertificate 校验证书可用性 +func validateCertificate(certContent, keyContent string) error { + // 解析证书 + certBlock, _ := pem.Decode([]byte(certContent)) + if certBlock == nil { + return fmt.Errorf("无效的证书格式") + } + + cert, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + return fmt.Errorf("解析证书失败: %v", err) + } + + // 检查证书是否过期 + now := time.Now() + if now.Before(cert.NotBefore) { + return fmt.Errorf("证书尚未生效") + } + if now.After(cert.NotAfter) { + return fmt.Errorf("证书已过期") + } + + // 解析私钥 + keyBlock, _ := pem.Decode([]byte(keyContent)) + if keyBlock == nil { + return fmt.Errorf("无效的私钥格式") + } + + // 尝试加载证书和私钥配对 + _, err = tls.X509KeyPair([]byte(certContent), []byte(keyContent)) + if err != nil { + return fmt.Errorf("证书和私钥不匹配: %v", err) + } + + return nil +} + +// CertInfo 证书信息 +type CertInfo struct { + ExpireAt string // 过期时间 + Domain string // 域名 +} + +// getCertInfo 获取证书信息 +func getCertInfo(certPath string) CertInfo { + info := CertInfo{ + ExpireAt: "", + Domain: "", + } + + certData, err := os.ReadFile(certPath) + if err != nil { + return info + } + + certBlock, _ := pem.Decode(certData) + if certBlock == nil { + return info + } + + cert, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + return info + } + + // 获取过期时间 + info.ExpireAt = cert.NotAfter.Format("2006-01-02 15:04:05") + + // 获取域名信息 + if cert.Subject.CommonName != "" { + info.Domain = cert.Subject.CommonName + } + + // 如果有SAN(Subject Alternative Name),优先使用 + if len(cert.DNSNames) > 0 { + // 将所有域名用逗号连接 + info.Domain = "" + for i, dns := range cert.DNSNames { + if i > 0 { + info.Domain += ", " + } + info.Domain += dns + } + } + + return info +} + +// RestartManagerApi 重启管理端 +func (w *WafVpConfigApi) RestartManagerApi(c *gin.Context) { + response.OkWithMessage("管理端将在1秒后重启,请稍候5-10秒后重新访问", c) + + // 延迟后重启,给时间返回响应 + go func() { + time.Sleep(1 * time.Second) + + // 触发重启通道 + if global.GWAF_CHAN_MANAGER_RESTART != nil { + select { + case global.GWAF_CHAN_MANAGER_RESTART <- 1: + // 发送成功 + default: + // 通道已满,说明已经有重启请求在处理中 + } + } + }() +} diff --git a/conf/config.yml b/conf/config.yml index a3c36a2..389f28f 100644 --- a/conf/config.yml +++ b/conf/config.yml @@ -5,6 +5,7 @@ notice: isenable: false security: ip_whitelist: 0.0.0.0/0,::/0 + ssl_enable: true soft_id: SamWafCom user_code: 8ad2bca0-4fd0-46aa-afe6-833132a05eee zlog: diff --git a/global/global.go b/global/global.go index 1a19b7f..dad6348 100644 --- a/global/global.go +++ b/global/global.go @@ -9,13 +9,14 @@ import ( "SamWaf/wafnotify" "SamWaf/wafowasp" "SamWaf/wafsnowflake" + "strconv" + "sync/atomic" + "time" + "github.com/bytedance/godlp/dlpheader" "github.com/lionsoul2014/ip2region/binding/golang/xdb" "github.com/oschwald/geoip2-golang" "gorm.io/gorm" - "strconv" - "sync/atomic" - "time" ) const ( @@ -75,6 +76,7 @@ var ( //管理端访问控制 GWAF_IP_WHITELIST string = "0.0.0.0/0,::/0" //IP白名单 后台默认放行所有 + GWAF_SSL_ENABLE bool = false //是否启用SSL证书 //zlog 日志相关信息 GWAF_LOG_OUTPUT_FORMAT string = "console" //zlog输出格式 控制台格式console,json格式 @@ -106,6 +108,7 @@ var ( GWAF_CHAN_CLEAR_CC_IP = make(chan string, 10) //清除cc缓存信息IP GWAF_QUEUE_SHUTDOWN_SIGNAL chan struct{} = make(chan struct{}) // 队列关闭信号 GWAF_CHAN_CREATE_LOG_INDEX = make(chan string, 10) // 创建日志索引 + GWAF_CHAN_MANAGER_RESTART = make(chan int, 1) // 管理端重启信号 GWAF_SHUTDOWN_SIGNAL bool = false // 系统关闭信号 /*****CACHE相关*********/ diff --git a/main.go b/main.go index 8d3a3f4..83faa6d 100644 --- a/main.go +++ b/main.go @@ -704,7 +704,7 @@ func (m *wafSystenService) stopSamWaf() { zlog.Info("Shutdown SamWaf Cron finished") zlog.Info("Shutdown SamWaf WebManager...") - webmanager.CloseLocalServer() + webmanager.Shutdown() zlog.Info("Shutdown SamWaf WebManager finished") zlog.Info("Shutdown SamWaf IPDatabase...") diff --git a/model/request/waf_vpconfig_req.go b/model/request/waf_vpconfig_req.go index 64fce8a..d92f80b 100644 --- a/model/request/waf_vpconfig_req.go +++ b/model/request/waf_vpconfig_req.go @@ -4,3 +4,14 @@ package request type WafVpConfigIpWhitelistUpdateReq struct { IpWhitelist string `json:"ip_whitelist"` // IP白名单,多个IP用逗号分隔 } + +// WafVpConfigSslEnableUpdateReq SSL启用状态更新请求 +type WafVpConfigSslEnableUpdateReq struct { + SslEnable bool `json:"ssl_enable"` // 是否启用SSL +} + +// WafVpConfigSslUploadReq SSL证书上传请求 +type WafVpConfigSslUploadReq struct { + CertContent string `json:"cert_content"` // 证书内容 + KeyContent string `json:"key_content"` // 私钥内容 +} diff --git a/model/response/waf_vpconfig_resp.go b/model/response/waf_vpconfig_resp.go index 9d94009..892586b 100644 --- a/model/response/waf_vpconfig_resp.go +++ b/model/response/waf_vpconfig_resp.go @@ -4,3 +4,13 @@ package response type WafVpConfigIpWhitelistGetResp struct { IpWhitelist string `json:"ip_whitelist"` // IP白名单,多个IP用逗号分隔 } + +// WafVpConfigSslStatusGetResp SSL状态获取响应 +type WafVpConfigSslStatusGetResp struct { + SslEnable bool `json:"ssl_enable"` // 是否启用SSL + HasCert bool `json:"has_cert"` // 是否已上传证书 + CertExpireAt string `json:"cert_expire_at"` // 证书过期时间 + CertDomain string `json:"cert_domain"` // 证书域名 + CertContent string `json:"cert_content"` // 证书内容 + KeyContent string `json:"key_content"` // 私钥内容 +} diff --git a/router/waf_vpconfig_router.go b/router/waf_vpconfig_router.go index 43efe7d..79222f9 100644 --- a/router/waf_vpconfig_router.go +++ b/router/waf_vpconfig_router.go @@ -2,6 +2,7 @@ package router import ( "SamWaf/api" + "github.com/gin-gonic/gin" ) @@ -13,4 +14,8 @@ func (receiver *WafVpConfigRouter) InitWafVpConfigRouter(group *gin.RouterGroup) router := group.Group("") router.POST("/samwaf/vipconfig/updateIpWhitelist", wafVpConfigApi.UpdateIpWhitelistApi) router.GET("/samwaf/vipconfig/getIpWhitelist", wafVpConfigApi.GetIpWhitelistApi) + router.POST("/samwaf/vipconfig/updateSslEnable", wafVpConfigApi.UpdateSslEnableApi) + router.GET("/samwaf/vipconfig/getSslStatus", wafVpConfigApi.GetSslStatusApi) + router.POST("/samwaf/vipconfig/uploadSslCert", wafVpConfigApi.UploadSslCertApi) + router.POST("/samwaf/vipconfig/restartManager", wafVpConfigApi.RestartManagerApi) } diff --git a/wafconfig/wafconfig.go b/wafconfig/wafconfig.go index c1c688e..4b5f789 100644 --- a/wafconfig/wafconfig.go +++ b/wafconfig/wafconfig.go @@ -125,6 +125,14 @@ func LoadAndInitConfig() { config.Set("security.ip_whitelist", global.GWAF_IP_WHITELIST) configChanged = true } + + //配置和提取SSL启用状态 + if config.IsSet("security.ssl_enable") { + global.GWAF_SSL_ENABLE = config.GetBool("security.ssl_enable") + } else { + config.Set("security.ssl_enable", global.GWAF_SSL_ENABLE) + configChanged = true + } // 只有在配置发生变化时才写入文件 if configChanged { err := config.WriteConfig() @@ -190,3 +198,55 @@ func UpdateIpWhitelist(ipWhitelist string) error { return nil } + +// UpdateSslEnable 更新SSL启用状态配置 +func UpdateSslEnable(sslEnable bool) error { + // 格式化当前时间为指定格式 + currentTime := time.Now().Format("2006-01-02 15:04:05.000") + + // 判断配置目录是否存在 + configDir := utils.GetCurrentDir() + "/conf/" + if _, err := os.Stat(configDir); os.IsNotExist(err) { + if err := os.MkdirAll(configDir, os.ModePerm); err != nil { + fmt.Printf("%s\tERROR\t创建config目录失败:%v\n", currentTime, err) + return err + } + } + + config := viper.New() + config.AddConfigPath(configDir) // 文件所在目录 + config.SetConfigName("config") // 文件名 + config.SetConfigType("yml") // 文件类型 + + // 读取配置文件 + if err := config.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + fmt.Printf("%s\tWARN\t找不到配置文件..\n", currentTime) + config.Set("local_port", global.GWAF_LOCAL_SERVER_PORT) + err = config.SafeWriteConfig() + if err != nil { + return err + } + } else { + fmt.Printf("%s\tERROR\t配置文件出错..\n", currentTime) + return err + } + } + + // 更新SSL启用状态配置 + config.Set("security.ssl_enable", sslEnable) + + // 更新全局变量 + global.GWAF_SSL_ENABLE = sslEnable + + // 写入配置文件 + err := config.WriteConfig() + if err != nil { + fmt.Printf("%s\tERROR\twrite config failed:%v\n", currentTime, err) + return err + } + + fmt.Printf("%s\tINFO\tSSL enable config updated\n", currentTime) + + return nil +} diff --git a/wafmangeweb/localserver.go b/wafmangeweb/localserver.go index 28e2b47..5033f81 100644 --- a/wafmangeweb/localserver.go +++ b/wafmangeweb/localserver.go @@ -6,23 +6,31 @@ import ( "SamWaf/middleware" "SamWaf/router" "SamWaf/service/waf_service" + "SamWaf/utils" "SamWaf/wafmangeweb/static" "context" + "crypto/tls" "errors" "fmt" - "github.com/gin-contrib/pprof" - "github.com/gin-gonic/gin" "io" "log" + "net" "net/http" + "os" + "path/filepath" "strconv" "time" + + "github.com/gin-contrib/pprof" + "github.com/gin-gonic/gin" ) type WafWebManager struct { - HttpServer *http.Server - R *gin.Engine - LogName string + HttpServer *http.Server + R *gin.Engine + LogName string + restartWatcherStarted bool + isShuttingDown bool // 标记是否正在完全关闭(非重启) } func (web *WafWebManager) initRouter(r *gin.Engine) { @@ -152,15 +160,231 @@ func (web *WafWebManager) StartLocalServer() error { if err != nil { zlog.Info("加载token到cache错误", err.Error()) } - // 正式启动服务 - if err := web.HttpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - errMsg := fmt.Sprintf("启动管理界面失败: %s", err.Error()) - zlog.Error(web.LogName, errMsg) + + // 启动重启信号监听(只启动一次) + if !web.restartWatcherStarted { + web.restartWatcherStarted = true + go web.WatchRestartSignal() + } + + // 检查是否启用 SSL + if global.GWAF_SSL_ENABLE { + // 获取证书文件路径 + certPath := filepath.Join(utils.GetCurrentDir(), "data", "ssl", "manager", "domain.crt") + keyPath := filepath.Join(utils.GetCurrentDir(), "data", "ssl", "manager", "domain.key") + + // 检查证书文件是否存在 + if _, err := os.Stat(certPath); err != nil { + zlog.Warn(web.LogName, "SSL证书文件不存在,降级使用 HTTP: ", certPath) + // 降级到 HTTP + zlog.Info(web.LogName, "启动 HTTP 管理端(降级模式), port:", global.GWAF_LOCAL_SERVER_PORT) + if err := web.HttpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + errMsg := fmt.Sprintf("启动管理界面失败: %s", err.Error()) + zlog.Error(web.LogName, errMsg) + return err + } + return nil + } + + if _, err := os.Stat(keyPath); err != nil { + zlog.Warn(web.LogName, "SSL私钥文件不存在,降级使用 HTTP: ", keyPath) + // 降级到 HTTP + zlog.Info(web.LogName, "启动 HTTP 管理端(降级模式), port:", global.GWAF_LOCAL_SERVER_PORT) + if err := web.HttpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + errMsg := fmt.Sprintf("启动管理界面失败: %s", err.Error()) + zlog.Error(web.LogName, errMsg) + return err + } + return nil + } + + // 尝试加载证书 + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + zlog.Warn(web.LogName, "SSL证书加载失败,降级使用 HTTP: ", err.Error()) + // 降级到 HTTP + zlog.Info(web.LogName, "启动 HTTP 管理端(降级模式), port:", global.GWAF_LOCAL_SERVER_PORT) + if err := web.HttpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + errMsg := fmt.Sprintf("启动管理界面失败: %s", err.Error()) + zlog.Error(web.LogName, errMsg) + return err + } + return nil + } + + // 使用混合监听器,同时支持 HTTPS 和 HTTP(降级方案) + zlog.Info(web.LogName, "启动 HTTPS/HTTP 混合管理端, port:", global.GWAF_LOCAL_SERVER_PORT) + if err := web.listenAndServeHybrid(cert); err != nil && !errors.Is(err, http.ErrServerClosed) { + errMsg := fmt.Sprintf("启动管理界面失败: %s", err.Error()) + zlog.Error(web.LogName, errMsg) + return err + } + } else { + // 使用 HTTP 启动服务 + zlog.Info(web.LogName, "启动 HTTP 管理端, port:", global.GWAF_LOCAL_SERVER_PORT) + if err := web.HttpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + errMsg := fmt.Sprintf("启动管理界面失败: %s", err.Error()) + zlog.Error(web.LogName, errMsg) + return err + } + } + + return nil +} + +// listenAndServeHybrid 混合监听器,同时支持 HTTPS 和 HTTP +func (web *WafWebManager) listenAndServeHybrid(cert tls.Certificate) error { + addr := web.HttpServer.Addr + if addr == "" { + addr = ":http" + } + + // 创建基础监听器 + ln, err := net.Listen("tcp", addr) + if err != nil { return err } - zlog.Info(web.LogName, "本地 port:", global.GWAF_LOCAL_SERVER_PORT) - return nil + // 创建混合监听器 + hybridListener := &hybridListener{ + Listener: ln, + cert: cert, + } + + // 使用混合监听器启动服务 + return web.HttpServer.Serve(hybridListener) +} + +// hybridListener 混合监听器,可以同时处理 HTTPS 和 HTTP 连接 +type hybridListener struct { + net.Listener + cert tls.Certificate +} + +func (l *hybridListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return nil, err + } + + // 返回混合连接,用于判断是 HTTPS 还是 HTTP + return &hybridConn{ + Conn: conn, + cert: l.cert, + }, nil +} + +// hybridConn 混合连接,可以自动识别 HTTPS 和 HTTP +type hybridConn struct { + net.Conn + cert tls.Certificate + tlsConn *tls.Conn + firstRead bool + isTLS bool +} + +func (c *hybridConn) Read(b []byte) (n int, err error) { + // 第一次读取时判断是 TLS 还是普通 HTTP + if !c.firstRead { + c.firstRead = true + + // 读取第一个字节来判断协议 + firstByte := make([]byte, 1) + n, err := c.Conn.Read(firstByte) + if err != nil { + return 0, err + } + + // TLS 握手的第一个字节是 0x16 (22),表示 Handshake + if n > 0 && firstByte[0] == 0x16 { + // 这是 TLS 连接 + c.isTLS = true + + // 创建 TLS 配置 + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{c.cert}, + MinVersion: tls.VersionTLS12, + } + + // 创建 TLS 连接包装器 + // 将第一个字节放回,让 TLS 握手可以正常进行 + wrappedConn := &firstByteConn{ + Conn: c.Conn, + firstByte: firstByte[0], + firstRead: false, + } + + c.tlsConn = tls.Server(wrappedConn, tlsConfig) + + // 执行 TLS 握手 + if err := c.tlsConn.Handshake(); err != nil { + zlog.Warn("WafWebManager", "TLS握手失败,尝试降级到HTTP: ", err.Error()) + // TLS 握手失败,关闭连接 + c.Conn.Close() + return 0, err + } + + // 使用 TLS 连接读取数据 + return c.tlsConn.Read(b) + } else { + // 这是普通 HTTP 连接 + c.isTLS = false + zlog.Debug("WafWebManager", "检测到HTTP连接,使用降级模式") + + // 将第一个字节放到缓冲区 + if n > 0 { + b[0] = firstByte[0] + // 继续读取剩余数据 + if len(b) > 1 { + n2, err2 := c.Conn.Read(b[1:]) + return n + n2, err2 + } + return 1, nil + } + } + } + + // 后续读取 + if c.isTLS && c.tlsConn != nil { + return c.tlsConn.Read(b) + } + return c.Conn.Read(b) +} + +func (c *hybridConn) Write(b []byte) (n int, err error) { + if c.isTLS && c.tlsConn != nil { + return c.tlsConn.Write(b) + } + return c.Conn.Write(b) +} + +func (c *hybridConn) Close() error { + if c.tlsConn != nil { + c.tlsConn.Close() + } + return c.Conn.Close() +} + +// firstByteConn 用于将第一个字节放回连接 +type firstByteConn struct { + net.Conn + firstByte byte + firstRead bool +} + +func (c *firstByteConn) Read(b []byte) (n int, err error) { + if !c.firstRead { + c.firstRead = true + if len(b) > 0 { + b[0] = c.firstByte + if len(b) > 1 { + n2, err2 := c.Conn.Read(b[1:]) + return 1 + n2, err2 + } + return 1, nil + } + } + return c.Conn.Read(b) } /* @@ -187,3 +411,63 @@ func (web *WafWebManager) CloseLocalServer() { } } + +// WatchRestartSignal 监听重启信号 +func (web *WafWebManager) WatchRestartSignal() { + go func() { + // 使用 ticker 定期检查关闭信号 + shutdownCheckTicker := time.NewTicker(500 * time.Millisecond) + defer shutdownCheckTicker.Stop() + + for { + select { + case <-global.GWAF_CHAN_MANAGER_RESTART: + // 检查是否正在完全关闭系统 + if global.GWAF_SHUTDOWN_SIGNAL || web.isShuttingDown { + zlog.Warn(web.LogName, "System is shutting down, ignoring restart request") + continue + } + + zlog.Info(web.LogName, "Received manager restart signal, starting restart...") + + // 关闭当前服务器 + web.CloseLocalServer() + + // 等待服务完全关闭 + time.Sleep(2 * time.Second) + + // 再次检查是否收到关闭信号 + if global.GWAF_SHUTDOWN_SIGNAL || web.isShuttingDown { + zlog.Warn(web.LogName, "System is shutting down, canceling restart") + return + } + + // SSL配置已经通过API更新到全局变量,直接使用即可 + zlog.Info(web.LogName, "Current SSL configuration status: ", global.GWAF_SSL_ENABLE) + + // 重新启动服务器 + zlog.Info(web.LogName, "Restarting manager...") + go func() { + if err := web.StartLocalServer(); err != nil { + zlog.Error(web.LogName, "Failed to restart manager: ", err.Error()) + } + }() + + zlog.Info(web.LogName, "Manager restart completed") + + case <-shutdownCheckTicker.C: + // 定期检查系统关闭信号,如果系统正在关闭则退出goroutine + if global.GWAF_SHUTDOWN_SIGNAL || web.isShuttingDown { + zlog.Info(web.LogName, "Detected system shutdown signal, exiting restart watcher") + return + } + } + } + }() +} + +// Shutdown 完全关闭管理端 +func (web *WafWebManager) Shutdown() { + web.isShuttingDown = true + web.CloseLocalServer() +}