From 95e998119636f07c25f54e4678d2f90b5da952cb Mon Sep 17 00:00:00 2001 From: samwaf Date: Mon, 16 Jun 2025 08:28:25 +0800 Subject: [PATCH] feat:same port http jump https #36 #39 --- global/config.go | 1 + wafenginecore/wafengine.go | 86 ++++++---- .../wafhttpserver/redirecting_http_server.go | 147 ++++++++++++++++++ waftask/task_config.go | 4 + 4 files changed, 208 insertions(+), 30 deletions(-) create mode 100644 wafenginecore/wafhttpserver/redirecting_http_server.go diff --git a/global/config.go b/global/config.go index 07707b3..5a800e6 100644 --- a/global/config.go +++ b/global/config.go @@ -11,6 +11,7 @@ var ( GCONFIG_RECORD_KAFKA_URL string = "127.0.0.1:9092" //kafka url地址 GCONFIG_RECORD_KAFKA_TOPIC string = "samwaf_logs_topic" //kafka topic GCONFIG_RECORD_REDIRECT_HTTPS_CODE int64 = 301 //80跳转https的方式 + GCONFIG_ENABLE_HTTPS_REDIRECT int64 = 0 //是否启用HTTPS重定向服务器 0关闭 1开启 GCONFIG_RECORD_LOGIN_MAX_ERROR_TIME int64 = 3 //登录周期里错误最大次数 GCONFIG_RECORD_LOGIN_LIMIT_MINTUTES int64 = 1 //登录错误记录周期 单位分钟最小1 diff --git a/wafenginecore/wafengine.go b/wafenginecore/wafengine.go index 0a49258..50fce16 100644 --- a/wafenginecore/wafengine.go +++ b/wafenginecore/wafengine.go @@ -15,6 +15,7 @@ import ( "SamWaf/utils" "SamWaf/wafenginecore/loadbalance" "SamWaf/wafenginecore/wafhttpcore" + "SamWaf/wafenginecore/wafhttpserver" "SamWaf/wafenginecore/wafwebcache" "SamWaf/wafproxy" "bytes" @@ -1225,39 +1226,64 @@ func (waf *WafEngine) StartProxyServer(innruntime innerbean.ServerRunTime) { zlog.Warn("https recover ", e) } }() - svr := &http.Server{ - Addr: ":" + strconv.Itoa(innruntime.Port), - Handler: waf, - TLSConfig: &tls.Config{ - GetCertificate: waf.GetCertificateFunc, - }, - } - serclone := waf.ServerOnline[innruntime.Port] - serclone.Svr = svr - serclone.Status = 0 - waf.ServerOnline[innruntime.Port] = serclone - zlog.Info("启动HTTPS 服务器" + strconv.Itoa(innruntime.Port)) - err := svr.ListenAndServeTLS("", "") - if err == http.ErrServerClosed { - zlog.Error("[HTTPServer] https server has been close, cause:[%v]", err) - } else { - //TODO 记录如果https 端口被占用的情况 记录日志 且应该推送websocket - wafSysLog := model.WafSysLog{ - BaseOrm: baseorm.BaseOrm{ - Id: uuid.GenUUID(), - USER_CODE: global.GWAF_USER_CODE, - Tenant_ID: global.GWAF_TENANT_ID, - CREATE_TIME: customtype.JsonTime(time.Now()), - UPDATE_TIME: customtype.JsonTime(time.Now()), + var svr *http.Server + // 检查是否启用HTTPS重定向服务器 + if global.GCONFIG_ENABLE_HTTPS_REDIRECT == 1 { + // 使用新的重定向服务器 + redirectServer := &wafhttpserver.RedirectingHTTPSServer{ + Server: &http.Server{ + Addr: ":" + strconv.Itoa(innruntime.Port), + Handler: waf, + TLSConfig: &tls.Config{ + GetCertificate: waf.GetCertificateFunc, + }, }, - OpType: "系统运行错误", - OpContent: "HTTPS端口被占用: " + strconv.Itoa(innruntime.Port) + ",请检查", } - global.GQEQUE_LOG_DB.Enqueue(wafSysLog) - zlog.Error("[HTTPServer] https server start fail, cause:[%v]", err) - } - zlog.Info("server https shutdown") + svr = redirectServer.Server + serclone := waf.ServerOnline[innruntime.Port] + serclone.Svr = svr + serclone.Status = 0 + waf.ServerOnline[innruntime.Port] = serclone + zlog.Info("启动HTTPS重定向服务器" + strconv.Itoa(innruntime.Port)) + err := redirectServer.ListenAndServeTLS("", "") + if err == http.ErrServerClosed { + zlog.Error("[HTTPServer] https redirect server has been close, cause:[%v]", err) + } + } else { + svr = &http.Server{ + Addr: ":" + strconv.Itoa(innruntime.Port), + Handler: waf, + TLSConfig: &tls.Config{ + GetCertificate: waf.GetCertificateFunc, + }, + } + serclone := waf.ServerOnline[innruntime.Port] + serclone.Svr = svr + serclone.Status = 0 + waf.ServerOnline[innruntime.Port] = serclone + zlog.Info("启动HTTPS 服务器" + strconv.Itoa(innruntime.Port)) + err := svr.ListenAndServeTLS("", "") + if err == http.ErrServerClosed { + zlog.Error("[HTTPServer] https server has been close, cause:[%v]", err) + } else { + //TODO 记录如果https 端口被占用的情况 记录日志 且应该推送websocket + wafSysLog := model.WafSysLog{ + BaseOrm: baseorm.BaseOrm{ + Id: uuid.GenUUID(), + USER_CODE: global.GWAF_USER_CODE, + Tenant_ID: global.GWAF_TENANT_ID, + CREATE_TIME: customtype.JsonTime(time.Now()), + UPDATE_TIME: customtype.JsonTime(time.Now()), + }, + OpType: "系统运行错误", + OpContent: "HTTPS端口被占用: " + strconv.Itoa(innruntime.Port) + ",请检查", + } + global.GQEQUE_LOG_DB.Enqueue(wafSysLog) + zlog.Error("[HTTPServer] https server start fail, cause:[%v]", err) + } + zlog.Info("server https shutdown") + } } else { defer func() { e := recover() diff --git a/wafenginecore/wafhttpserver/redirecting_http_server.go b/wafenginecore/wafhttpserver/redirecting_http_server.go new file mode 100644 index 0000000..3dc77c7 --- /dev/null +++ b/wafenginecore/wafhttpserver/redirecting_http_server.go @@ -0,0 +1,147 @@ +package wafhttpserver + +import ( + "fmt" + "net" + "net/http" + "strings" +) + +type RedirectingHTTPSServer struct { + *http.Server + ExtConfig string +} + +func (s *RedirectingHTTPSServer) ListenAndServeTLS(certFile, keyFile string) error { + addr := s.Addr + if addr == "" { + addr = ":https" + } + + listener, err := net.Listen("tcp", addr) + if err != nil { + return err + } + + return s.ServeTLS(&redirectingListener{ + Listener: listener, + extConfig: s.ExtConfig, + }, certFile, keyFile) +} + +type redirectingListener struct { + net.Listener + extConfig string +} + +func (l *redirectingListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return nil, err + } + + return &redirectingConn{ + Conn: conn, + extConfig: l.extConfig, + }, nil +} + +type redirectingConn struct { + net.Conn + extConfig string + firstRead bool +} + +func (c *redirectingConn) Read(b []byte) (n int, err error) { + n, err = c.Conn.Read(b) + + // 只在第一次读取时检查 + if !c.firstRead && n > 0 { + c.firstRead = true + + // 检查是否为HTTP请求 + data := string(b[:n]) + if strings.HasPrefix(data, "GET ") || + strings.HasPrefix(data, "POST ") || + strings.HasPrefix(data, "PUT ") || + strings.HasPrefix(data, "HEAD ") || + strings.HasPrefix(data, "OPTIONS ") || + strings.HasPrefix(data, "DELETE ") { + + // 解析HTTP请求行和Host头 + lines := strings.Split(data, "\r\n") + if len(lines) > 0 { + // 解析请求行 (例如: "GET /path?query HTTP/1.1") + requestLine := lines[0] + parts := strings.Split(requestLine, " ") + + var requestPath string + var queryString string + if len(parts) >= 2 { + urlPart := parts[1] + if strings.Contains(urlPart, "?") { + urlParts := strings.SplitN(urlPart, "?", 2) + requestPath = urlParts[0] + queryString = urlParts[1] + } else { + requestPath = urlPart + } + } + + // 查找Host头 + var host string + for _, line := range lines[1:] { + if strings.HasPrefix(strings.ToLower(line), "host:") { + host = strings.TrimSpace(line[5:]) + break + } + } + + // 构建HTTPS重定向URL + var targetHttpsUrl string + if host != "" { + // 检查host是否包含端口 + if strings.Contains(host, ":") { + // 如果host包含端口,需要处理HTTPS端口 + hostParts := strings.Split(host, ":") + hostName := hostParts[0] + port := hostParts[1] + + // 如果是标准端口(80或443),不显示端口号 + if port == "80" || port == "443" { + targetHttpsUrl = fmt.Sprintf("https://%s%s", hostName, requestPath) + } else { + // 非标准端口,显示端口号 + targetHttpsUrl = fmt.Sprintf("https://%s:%s%s", hostName, port, requestPath) + } + } else { + // host不包含端口,使用默认HTTPS端口443(不显示端口号) + targetHttpsUrl = fmt.Sprintf("https://%s%s", host, requestPath) + } + + // 添加查询参数 + if queryString != "" { + targetHttpsUrl += "?" + queryString + } + } else { + return 0, fmt.Errorf("redirected to HTTPS not find host") + } + + // 发送重定向响应 + redirectResponse := fmt.Sprintf( + "HTTP/1.1 301 Moved Permanently\r\n"+ + "Location: %s\r\n"+ + "Connection: close\r\n"+ + "\r\n", + targetHttpsUrl, + ) + + c.Conn.Write([]byte(redirectResponse)) + c.Conn.Close() + return 0, fmt.Errorf("redirected to HTTPS") + } + } + } + + return n, err +} diff --git a/waftask/task_config.go b/waftask/task_config.go index ca5f37e..43bd570 100644 --- a/waftask/task_config.go +++ b/waftask/task_config.go @@ -88,6 +88,8 @@ func setConfigIntValue(name string, value int64, change int) { case "sslhttp_check": global.GCONFIG_RECORD_SSLHTTP_CHECK = value break + case "enable_https_redirect": + global.GCONFIG_ENABLE_HTTPS_REDIRECT = value default: zlog.Warn("Unknown config item:", name) } @@ -207,6 +209,8 @@ func TaskLoadSetting(initLoad bool) { updateConfigStringItem(initLoad, "kafka", "kafka_topic", global.GCONFIG_RECORD_KAFKA_TOPIC, "kafka topic", "string", "") updateConfigIntItem(initLoad, "system", "redirect_https_code", global.GCONFIG_RECORD_REDIRECT_HTTPS_CODE, "80重定向https时候跳转代码", "int", "") + updateConfigIntItem(initLoad, "system", "enable_https_redirect", global.GCONFIG_ENABLE_HTTPS_REDIRECT, "是否启用HTTPS重定向服务器(0关闭 1开启)", "int", "") + updateConfigIntItem(initLoad, "system", "login_max_error_time", global.GCONFIG_RECORD_LOGIN_MAX_ERROR_TIME, "登录周期里错误最大次数 请大于0 ", "int", "") updateConfigIntItem(initLoad, "system", "login_limit_mintutes", global.GCONFIG_RECORD_LOGIN_LIMIT_MINTUTES, "登录错误记录周期 单位分钟数,默认1分钟", "int", "") updateConfigIntItem(initLoad, "system", "enable_owasp", global.GCONFIG_RECORD_ENABLE_OWASP, "启动OWASP数据检测(1启动 0关闭)", "int", "")