mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-11-04 04:02:11 +08:00
Merge pull request #536 from samwafgo/feat_manage_https
feat:add manage panel
This commit is contained in:
@@ -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:
|
||||
// 通道已满,说明已经有重启请求在处理中
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
+6
-3
@@ -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相关*********/
|
||||
|
||||
@@ -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...")
|
||||
|
||||
@@ -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"` // 私钥内容
|
||||
}
|
||||
|
||||
@@ -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"` // 私钥内容
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+295
-11
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user