mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-08-30 17:20:58 +08:00
1109 lines
39 KiB
Go
1109 lines
39 KiB
Go
package wafenginecore
|
||
|
||
import (
|
||
"SamWaf/customtype"
|
||
"SamWaf/global"
|
||
"SamWaf/innerbean"
|
||
"SamWaf/libinjection-go"
|
||
"SamWaf/model"
|
||
"SamWaf/model/baseorm"
|
||
"SamWaf/model/wafenginmodel"
|
||
"SamWaf/plugin"
|
||
"SamWaf/service/waf_service"
|
||
"SamWaf/utils"
|
||
"SamWaf/utils/zlog"
|
||
"SamWaf/wafbot"
|
||
"SamWaf/wafdefenserce"
|
||
"SamWaf/wafproxy"
|
||
"bufio"
|
||
"bytes"
|
||
"compress/flate"
|
||
"compress/gzip"
|
||
"context"
|
||
"crypto/tls"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"github.com/satori/go.uuid"
|
||
"go.uber.org/zap"
|
||
"golang.org/x/net/html/charset"
|
||
"golang.org/x/text/encoding"
|
||
"golang.org/x/text/encoding/unicode"
|
||
"golang.org/x/text/transform"
|
||
"golang.org/x/time/rate"
|
||
"io"
|
||
"net"
|
||
"net/http"
|
||
_ "net/http/pprof"
|
||
"net/url"
|
||
"strconv"
|
||
"strings"
|
||
"sync/atomic"
|
||
"time"
|
||
)
|
||
|
||
type WafEngine struct {
|
||
//主机情况(key:主机名+":"+端口,value : hostsafe信息里面有规则,ip信息等)
|
||
HostTarget map[string]*wafenginmodel.HostSafe
|
||
//主机和code的关系(key:主机code,value:主机名+":"+端口)
|
||
HostCode map[string]string
|
||
//服务在线情况(key:端口,value :服务情况)
|
||
ServerOnline map[int]innerbean.ServerRunTime
|
||
|
||
//所有证书情况 对应端口 可能多个端口都是https 443,或者其他非标准端口也要实现https证书
|
||
//嵌套结构 (key:端口 ,再往下是 下面的主机名,value 证书)
|
||
AllCertificate map[int]map[string]*tls.Certificate
|
||
EsHelper utils.EsHelper
|
||
|
||
EngineCurrentStatus int // 当前waf引擎状态
|
||
}
|
||
|
||
func (waf *WafEngine) Error() string {
|
||
fs := "HTTP: %d, HostCode: %d, Message: %s"
|
||
return fmt.Sprintf(fs)
|
||
}
|
||
func (waf *WafEngine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
atomic.AddUint64(&global.GWAF_RUNTIME_QPS, 1) // 原子增加计数器
|
||
host := r.Host
|
||
if !strings.Contains(host, ":") {
|
||
host = host + ":80"
|
||
}
|
||
|
||
defer func() {
|
||
e := recover()
|
||
if e != nil { // 捕获该协程的panic 111111
|
||
fmt.Println("11recover ", e)
|
||
}
|
||
}()
|
||
// 检查域名是否已经注册
|
||
if target, ok := waf.HostTarget[host]; ok {
|
||
// 取出客户IP
|
||
ipAndPort := strings.Split(r.RemoteAddr, ":")
|
||
_, ok := waf.ServerOnline[waf.HostTarget[host].Host.Remote_port]
|
||
//检测如果访问IP和远程IP是同一个IP,且远程端口在本地Server已存在则显示配置错误
|
||
if ipAndPort[0] == waf.HostTarget[host].Host.Remote_ip && ok == true {
|
||
resBytes := []byte("500: 配置有误" + host + " 当前IP和访问远端IP一样,且端口也一样,会造成循环问题")
|
||
w.Write(resBytes)
|
||
return
|
||
}
|
||
|
||
// 获取请求报文的内容长度
|
||
contentLength := r.ContentLength
|
||
|
||
//server_online[8081].Svr.Close()
|
||
var bodyByte []byte
|
||
|
||
// 拷贝一份request的Body ,控制不记录大文件的情况 ,先写死的
|
||
if r.Body != nil && r.Body != http.NoBody && contentLength < (global.GCONFIG_RECORD_MAX_BODY_LENGTH) {
|
||
bodyByte, _ = io.ReadAll(r.Body)
|
||
// 把刚刚读出来的再写进去,不然后面解析表单数据就解析不到了
|
||
r.Body = io.NopCloser(bytes.NewBuffer(bodyByte))
|
||
}
|
||
cookies, _ := json.Marshal(r.Cookies())
|
||
header := ""
|
||
for key, values := range r.Header {
|
||
for _, value := range values {
|
||
header += key + ": " + value + "\r\n"
|
||
}
|
||
}
|
||
|
||
region := utils.GetCountry(ipAndPort[0])
|
||
currentDay, _ := strconv.Atoi(time.Now().Format("20060102"))
|
||
|
||
//URL 解码
|
||
enEscapeUrl, _urlEscapeOk := url.QueryUnescape(r.RequestURI)
|
||
if _urlEscapeOk != nil {
|
||
enEscapeUrl = r.RequestURI
|
||
}
|
||
datetimeNow := time.Now()
|
||
weblogbean := innerbean.WebLog{
|
||
HOST: host,
|
||
URL: enEscapeUrl,
|
||
REFERER: r.Referer(),
|
||
USER_AGENT: r.UserAgent(),
|
||
METHOD: r.Method,
|
||
HEADER: string(header),
|
||
COUNTRY: region[0],
|
||
PROVINCE: region[2],
|
||
CITY: region[3],
|
||
SRC_IP: ipAndPort[0],
|
||
SRC_PORT: ipAndPort[1],
|
||
CREATE_TIME: datetimeNow.Format("2006-01-02 15:04:05"),
|
||
UNIX_ADD_TIME: datetimeNow.UnixNano() / 1e6,
|
||
CONTENT_LENGTH: contentLength,
|
||
COOKIES: string(cookies),
|
||
BODY: string(bodyByte),
|
||
REQ_UUID: uuid.NewV4().String(),
|
||
USER_CODE: global.GWAF_USER_CODE,
|
||
HOST_CODE: waf.HostTarget[host].Host.Code,
|
||
TenantId: global.GWAF_TENANT_ID,
|
||
RULE: "",
|
||
ACTION: "通过",
|
||
Day: currentDay,
|
||
POST_FORM: r.PostForm.Encode(),
|
||
TASK_FLAG: -1,
|
||
RISK_LEVEL: 0, //危险等级
|
||
GUEST_IDENTIFICATION: "正常访客", //访客身份识别
|
||
TimeSpent: 0,
|
||
}
|
||
|
||
formValues := url.Values{}
|
||
if strings.Contains(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") {
|
||
// 解码 x-www-form-urlencoded 数据
|
||
formValuest, err := url.ParseQuery(weblogbean.BODY)
|
||
if err != nil {
|
||
fmt.Println("解码失败:", err)
|
||
} else {
|
||
formValues = formValuest
|
||
}
|
||
}
|
||
|
||
r.Header.Add("waf_req_uuid", weblogbean.REQ_UUID)
|
||
|
||
if waf.HostTarget[host].Host.GUARD_STATUS == 1 {
|
||
var jumpGuardFlag = false
|
||
//ip白名单策略(局部)
|
||
if waf.HostTarget[host].IPWhiteLists != nil {
|
||
for i := 0; i < len(waf.HostTarget[host].IPWhiteLists); i++ {
|
||
if utils.CheckIPInCIDR(weblogbean.SRC_IP, waf.HostTarget[host].IPWhiteLists[i].Ip) {
|
||
jumpGuardFlag = true
|
||
break
|
||
}
|
||
}
|
||
}
|
||
//ip白名单策略(全局)
|
||
if waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].Host.GUARD_STATUS == 1 && waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].IPWhiteLists != nil {
|
||
for i := 0; i < len(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].IPWhiteLists); i++ {
|
||
if utils.CheckIPInCIDR(weblogbean.SRC_IP, waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].IPWhiteLists[i].Ip) {
|
||
jumpGuardFlag = true
|
||
break
|
||
}
|
||
}
|
||
}
|
||
//url白名单策略(局部)
|
||
if waf.HostTarget[host].UrlWhiteLists != nil {
|
||
for i := 0; i < len(waf.HostTarget[host].UrlWhiteLists); i++ {
|
||
if (waf.HostTarget[host].UrlWhiteLists[i].CompareType == "等于" && waf.HostTarget[host].UrlWhiteLists[i].Url == weblogbean.URL) ||
|
||
(waf.HostTarget[host].UrlWhiteLists[i].CompareType == "前缀匹配" && strings.HasPrefix(weblogbean.URL, waf.HostTarget[host].UrlWhiteLists[i].Url)) ||
|
||
(waf.HostTarget[host].UrlWhiteLists[i].CompareType == "后缀匹配" && strings.HasSuffix(weblogbean.URL, waf.HostTarget[host].UrlWhiteLists[i].Url)) ||
|
||
(waf.HostTarget[host].UrlWhiteLists[i].CompareType == "包含匹配" && strings.Contains(weblogbean.URL, waf.HostTarget[host].UrlWhiteLists[i].Url)) {
|
||
jumpGuardFlag = true
|
||
break
|
||
}
|
||
}
|
||
}
|
||
//url白名单策略(全局)
|
||
if waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].Host.GUARD_STATUS == 1 && waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlWhiteLists != nil {
|
||
for i := 0; i < len(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlWhiteLists); i++ {
|
||
if (waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlWhiteLists[i].CompareType == "等于" && waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlWhiteLists[i].Url == weblogbean.URL) ||
|
||
(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlWhiteLists[i].CompareType == "前缀匹配" && strings.HasPrefix(weblogbean.URL, waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlWhiteLists[i].Url)) ||
|
||
(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlWhiteLists[i].CompareType == "后缀匹配" && strings.HasSuffix(weblogbean.URL, waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlWhiteLists[i].Url)) ||
|
||
(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlWhiteLists[i].CompareType == "包含匹配" && strings.Contains(weblogbean.URL, waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlWhiteLists[i].Url)) {
|
||
jumpGuardFlag = true
|
||
break
|
||
}
|
||
}
|
||
}
|
||
//ip黑名单策略 (局部)
|
||
if waf.HostTarget[host].IPBlockLists != nil {
|
||
for i := 0; i < len(waf.HostTarget[host].IPBlockLists); i++ {
|
||
if utils.CheckIPInCIDR(weblogbean.SRC_IP, waf.HostTarget[host].IPBlockLists[i].Ip) {
|
||
weblogbean.RISK_LEVEL = 1
|
||
EchoErrorInfo(w, r, weblogbean, "IP黑名单", "您的访问被阻止了IP限制")
|
||
return
|
||
}
|
||
}
|
||
}
|
||
//ip黑名单策略(全局)
|
||
if waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].Host.GUARD_STATUS == 1 && waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].IPBlockLists != nil {
|
||
for i := 0; i < len(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].IPBlockLists); i++ {
|
||
if utils.CheckIPInCIDR(weblogbean.SRC_IP, waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].IPBlockLists[i].Ip) {
|
||
weblogbean.RISK_LEVEL = 1
|
||
EchoErrorInfo(w, r, weblogbean, "【全局】IP黑名单", "您的访问被阻止了IP限制")
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
//url黑名单策略-(局部) (待优化性能)
|
||
if waf.HostTarget[host].UrlBlockLists != nil {
|
||
for i := 0; i < len(waf.HostTarget[host].UrlBlockLists); i++ {
|
||
if (waf.HostTarget[host].UrlBlockLists[i].CompareType == "等于" && waf.HostTarget[host].UrlBlockLists[i].Url == weblogbean.URL) ||
|
||
(waf.HostTarget[host].UrlBlockLists[i].CompareType == "前缀匹配" && strings.HasPrefix(weblogbean.URL, waf.HostTarget[host].UrlBlockLists[i].Url)) ||
|
||
(waf.HostTarget[host].UrlBlockLists[i].CompareType == "后缀匹配" && strings.HasSuffix(weblogbean.URL, waf.HostTarget[host].UrlBlockLists[i].Url)) ||
|
||
(waf.HostTarget[host].UrlBlockLists[i].CompareType == "包含匹配" && strings.Contains(weblogbean.URL, waf.HostTarget[host].UrlBlockLists[i].Url)) {
|
||
weblogbean.RISK_LEVEL = 1
|
||
EchoErrorInfo(w, r, weblogbean, "URL黑名单", "您的访问被阻止了URL限制")
|
||
return
|
||
}
|
||
}
|
||
}
|
||
//url黑名单策略-(全局) (待优化性能)
|
||
if waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].Host.GUARD_STATUS == 1 && waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlBlockLists != nil {
|
||
for i := 0; i < len(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlBlockLists); i++ {
|
||
if (waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlBlockLists[i].CompareType == "等于" && waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlBlockLists[i].Url == weblogbean.URL) ||
|
||
(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlBlockLists[i].CompareType == "前缀匹配" && strings.HasPrefix(weblogbean.URL, waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlBlockLists[i].Url)) ||
|
||
(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlBlockLists[i].CompareType == "后缀匹配" && strings.HasSuffix(weblogbean.URL, waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlBlockLists[i].Url)) ||
|
||
(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlBlockLists[i].CompareType == "包含匹配" && strings.Contains(weblogbean.URL, waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].UrlBlockLists[i].Url)) {
|
||
weblogbean.RISK_LEVEL = 1
|
||
EchoErrorInfo(w, r, weblogbean, "【全局】URL黑名单", "您的访问被阻止了URL限制")
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
if jumpGuardFlag == false {
|
||
|
||
hostDefense := model.HostsDefense{
|
||
DEFENSE_BOT: 1,
|
||
DEFENSE_SQLI: 1,
|
||
DEFENSE_XSS: 1,
|
||
DEFENSE_SCAN: 1,
|
||
DEFENSE_RCE: 1,
|
||
}
|
||
err := json.Unmarshal([]byte(waf.HostTarget[host].Host.DEFENSE_JSON), &hostDefense)
|
||
if err != nil {
|
||
zlog.Error("解析defense json失败")
|
||
}
|
||
//检测爬虫bot
|
||
if hostDefense.DEFENSE_BOT == 1 {
|
||
isBot, isNormalBot, BotName := wafbot.DetermineNormalSearch(weblogbean.USER_AGENT, weblogbean.SRC_IP)
|
||
if isBot == true {
|
||
if isNormalBot {
|
||
weblogbean.GUEST_IDENTIFICATION = BotName
|
||
} else {
|
||
weblogbean.GUEST_IDENTIFICATION = BotName
|
||
weblogbean.RISK_LEVEL = 1
|
||
EchoErrorInfo(w, r, weblogbean, BotName, "请正确访问")
|
||
return
|
||
}
|
||
}
|
||
}
|
||
if hostDefense.DEFENSE_SQLI == 1 {
|
||
var sqlFlag = false
|
||
//检测sql注入
|
||
if libinjection.IsSQLiNotReturnPrint(weblogbean.URL) ||
|
||
libinjection.IsSQLiNotReturnPrint(weblogbean.BODY) ||
|
||
libinjection.IsSQLiNotReturnPrint(weblogbean.POST_FORM) {
|
||
sqlFlag = true
|
||
}
|
||
if sqlFlag == false {
|
||
for _, value := range formValues {
|
||
for _, v := range value {
|
||
if libinjection.IsSQLiNotReturnPrint(v) {
|
||
sqlFlag = true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if sqlFlag == true {
|
||
weblogbean.RISK_LEVEL = 2
|
||
EchoErrorInfo(w, r, weblogbean, "SQL注入", "请正确访问")
|
||
return
|
||
}
|
||
}
|
||
|
||
//检测xss注入
|
||
if hostDefense.DEFENSE_XSS == 1 {
|
||
var xssFlag = false
|
||
if libinjection.IsXSS(weblogbean.URL) ||
|
||
libinjection.IsXSS(weblogbean.POST_FORM) {
|
||
xssFlag = true
|
||
}
|
||
if xssFlag == false {
|
||
for _, value := range formValues {
|
||
for _, v := range value {
|
||
if libinjection.IsXSS(v) {
|
||
//xssFlag = true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if xssFlag == true {
|
||
weblogbean.RISK_LEVEL = 2
|
||
EchoErrorInfo(w, r, weblogbean, "XSS跨站注入", "请正确访问")
|
||
return
|
||
}
|
||
}
|
||
//检测扫描工具
|
||
if hostDefense.DEFENSE_SCAN == 1 {
|
||
var scanFlag = false
|
||
if libinjection.IsScan(weblogbean) {
|
||
scanFlag = true
|
||
}
|
||
if scanFlag == true {
|
||
weblogbean.RISK_LEVEL = 1
|
||
EchoErrorInfo(w, r, weblogbean, "扫描工具", "请正确访问")
|
||
return
|
||
}
|
||
}
|
||
//检测RCE
|
||
if hostDefense.DEFENSE_RCE == 1 {
|
||
isRce, RceName := wafdefenserce.DetermineRCE(weblogbean.URL, weblogbean.COOKIES, weblogbean.POST_FORM)
|
||
if isRce == true {
|
||
weblogbean.RISK_LEVEL = 3
|
||
EchoErrorInfo(w, r, weblogbean, "RCE:"+RceName, "请正确访问")
|
||
return
|
||
}
|
||
}
|
||
// cc 防护 (局部检测 )
|
||
if waf.HostTarget[host].PluginIpRateLimiter != nil {
|
||
limiter := waf.HostTarget[host].PluginIpRateLimiter.GetLimiter(weblogbean.SRC_IP)
|
||
if !limiter.Allow() {
|
||
weblogbean.RISK_LEVEL = 1
|
||
EchoErrorInfo(w, r, weblogbean, "触发IP频次访问限制1", "您的访问被阻止超量了1")
|
||
return
|
||
}
|
||
}
|
||
// cc 防护 (全局检测 )
|
||
if waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].Host.GUARD_STATUS == 1 && waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].PluginIpRateLimiter != nil {
|
||
limiter := waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].PluginIpRateLimiter.GetLimiter(weblogbean.SRC_IP)
|
||
if !limiter.Allow() {
|
||
weblogbean.RISK_LEVEL = 1
|
||
EchoErrorInfo(w, r, weblogbean, "【全局】触发IP频次访问限制", "您的访问被阻止超量了")
|
||
return
|
||
}
|
||
}
|
||
|
||
//规则判断 (局部)
|
||
if waf.HostTarget[host].Rule != nil {
|
||
if waf.HostTarget[host].Rule.KnowledgeBase != nil {
|
||
ruleMatchs, err := waf.HostTarget[host].Rule.Match("MF", &weblogbean)
|
||
if err == nil {
|
||
if len(ruleMatchs) > 0 {
|
||
rulestr := ""
|
||
for _, v := range ruleMatchs {
|
||
rulestr = rulestr + v.RuleDescription + ","
|
||
}
|
||
w.Header().Set("WAF", "SAMWAF DROP")
|
||
weblogbean.RISK_LEVEL = 1
|
||
EchoErrorInfo(w, r, weblogbean, rulestr, "您的访问被阻止触发规则")
|
||
return
|
||
}
|
||
} else {
|
||
zlog.Debug("规则 ", err)
|
||
}
|
||
}
|
||
}
|
||
//规则判断 (全局网站)
|
||
if waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].Host.GUARD_STATUS == 1 && waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].Rule != nil {
|
||
if waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].Rule.KnowledgeBase != nil {
|
||
ruleMatchs, err := waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].Rule.Match("MF", &weblogbean)
|
||
if err == nil {
|
||
if len(ruleMatchs) > 0 {
|
||
rulestr := ""
|
||
for _, v := range ruleMatchs {
|
||
rulestr = rulestr + v.RuleDescription + ","
|
||
}
|
||
w.Header().Set("WAF", "SAMWAF DROP")
|
||
weblogbean.RISK_LEVEL = 1
|
||
EchoErrorInfo(w, r, weblogbean, "【全局】"+rulestr, "您的访问被阻止触发规则")
|
||
return
|
||
}
|
||
} else {
|
||
zlog.Debug("规则 ", err)
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
//日志保存时候也是脱敏保存防止,数据库密码被破解,遭到敏感信息遭到泄露
|
||
if weblogbean.BODY != "" {
|
||
weblogbean.BODY = utils.DeSenText(weblogbean.BODY)
|
||
}
|
||
//global.GQEQUE_LOG_DB.PushBack(weblogbean)
|
||
remoteUrl, err := url.Parse(target.TargetHost)
|
||
if err != nil {
|
||
zlog.Debug("target parse fail:", zap.Any("", err))
|
||
return
|
||
}
|
||
// 在请求上下文中存储自定义数据
|
||
ctx := context.WithValue(r.Context(), "weblog", weblogbean)
|
||
|
||
// 直接从缓存取出
|
||
if waf.HostTarget[host].RevProxy != nil {
|
||
waf.HostTarget[host].RevProxy.ServeHTTP(w, r.WithContext(ctx))
|
||
} else {
|
||
transport := &http.Transport{
|
||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||
// 在这里可以自定义 DNS 解析过程
|
||
//priv_dns_host, priv_dns_port, err := net.SplitHostPort(addr)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// 使用解析后的 IP 地址进行连接
|
||
dialer := net.Dialer{
|
||
Timeout: 30 * time.Second,
|
||
KeepAlive: 30 * time.Second,
|
||
}
|
||
//通过自定义nameserver获取域名解析的IP
|
||
//ips, _ := dialer.Resolver.LookupHost(ctx, host)
|
||
//for _, s := range ips {
|
||
// log.Println(s)
|
||
//}
|
||
|
||
// 创建链接
|
||
if waf.HostTarget[host].Host.Remote_ip != "" {
|
||
conn, err := dialer.DialContext(ctx, network, waf.HostTarget[host].Host.Remote_ip+":"+strconv.Itoa(waf.HostTarget[host].Host.Remote_port))
|
||
if err == nil {
|
||
return conn, nil
|
||
}
|
||
}
|
||
return dialer.DialContext(ctx, network, addr)
|
||
},
|
||
}
|
||
proxy := wafproxy.NewSingleHostReverseProxy(remoteUrl)
|
||
proxy.Transport = transport
|
||
proxy.ModifyResponse = waf.modifyResponse()
|
||
proxy.ErrorHandler = errorHandler()
|
||
waf.HostTarget[host].RevProxy = proxy // 放入缓存
|
||
proxy.ServeHTTP(w, r.WithContext(ctx))
|
||
}
|
||
return
|
||
} else {
|
||
resBytes := []byte("403: Host forbidden " + host)
|
||
w.Write(resBytes)
|
||
// 获取请求报文的内容长度
|
||
contentLength := r.ContentLength
|
||
|
||
//server_online[8081].Svr.Close()
|
||
var bodyByte []byte
|
||
|
||
// 拷贝一份request的Body ,控制不记录大文件的情况 ,先写死的
|
||
if r.Body != nil && r.Body != http.NoBody && contentLength < (global.GCONFIG_RECORD_MAX_BODY_LENGTH) {
|
||
bodyByte, _ = io.ReadAll(r.Body)
|
||
// 把刚刚读出来的再写进去,不然后面解析表单数据就解析不到了
|
||
r.Body = io.NopCloser(bytes.NewBuffer(bodyByte))
|
||
}
|
||
cookies, _ := json.Marshal(r.Cookies())
|
||
header, _ := json.Marshal(r.Header)
|
||
// 取出客户IP
|
||
ipAndPort := strings.Split(r.RemoteAddr, ":")
|
||
region := utils.GetCountry(ipAndPort[0])
|
||
datetimeNow := time.Now()
|
||
|
||
currentDay, _ := strconv.Atoi(datetimeNow.Format("20060102"))
|
||
weblogbean := innerbean.WebLog{
|
||
HOST: r.Host,
|
||
URL: r.RequestURI,
|
||
REFERER: r.Referer(),
|
||
USER_AGENT: r.UserAgent(),
|
||
METHOD: r.Method,
|
||
HEADER: string(header),
|
||
COUNTRY: region[0],
|
||
PROVINCE: region[2],
|
||
CITY: region[3],
|
||
SRC_IP: ipAndPort[0],
|
||
SRC_PORT: ipAndPort[1],
|
||
CREATE_TIME: datetimeNow.Format("2006-01-02 15:04:05"),
|
||
UNIX_ADD_TIME: datetimeNow.UnixNano() / 1e6,
|
||
CONTENT_LENGTH: contentLength,
|
||
COOKIES: string(cookies),
|
||
BODY: string(bodyByte),
|
||
REQ_UUID: uuid.NewV4().String(),
|
||
USER_CODE: global.GWAF_USER_CODE,
|
||
HOST_CODE: "",
|
||
TenantId: global.GWAF_TENANT_ID,
|
||
RULE: "",
|
||
ACTION: "通过",
|
||
Day: currentDay,
|
||
STATUS: "禁止访问",
|
||
STATUS_CODE: 403,
|
||
TASK_FLAG: 1,
|
||
RISK_LEVEL: 1, //危险等级
|
||
GUEST_IDENTIFICATION: "未解析域名", //访客身份识别
|
||
TimeSpent: 0,
|
||
}
|
||
|
||
//记录响应body
|
||
weblogbean.RES_BODY = string(resBytes)
|
||
weblogbean.ACTION = "禁止"
|
||
global.GQEQUE_LOG_DB.PushBack(weblogbean)
|
||
}
|
||
}
|
||
func EchoErrorInfo(w http.ResponseWriter, r *http.Request, weblogbean innerbean.WebLog, ruleName string, blockInfo string) {
|
||
//通知信息
|
||
/* noticeStr := fmt.Sprintf("网站域名:%s 访问IP:%s 归属地区:%s 规则:%s 阻止信息:%s", weblogbean.HOST, weblogbean.SRC_IP, utils.GetCountry(weblogbean.SRC_IP), ruleName, blockInfo)
|
||
|
||
zlog.Debug(noticeStr)*/
|
||
//发送微信推送消息
|
||
global.GQEQUE_MESSAGE_DB.PushBack(innerbean.RuleMessageInfo{
|
||
BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "命中保护规则", Server: global.GWAF_CUSTOM_SERVER_NAME},
|
||
Domain: weblogbean.HOST,
|
||
RuleInfo: ruleName,
|
||
Ip: fmt.Sprintf("%s (%s)", weblogbean.SRC_IP, utils.GetCountry(weblogbean.SRC_IP)),
|
||
})
|
||
|
||
resBytes := []byte("<html><head><title>您的访问被阻止</title></head><body><center><h1>" + blockInfo + "</h1> <br> 访问识别码:<h3>" + weblogbean.REQ_UUID + "</h3></center></body> </html>")
|
||
w.Write(resBytes)
|
||
datetimeNow := time.Now()
|
||
weblogbean.TimeSpent = datetimeNow.UnixNano()/1e6 - weblogbean.UNIX_ADD_TIME
|
||
//记录响应body
|
||
weblogbean.RES_BODY = string(resBytes)
|
||
weblogbean.RULE = ruleName
|
||
weblogbean.ACTION = "阻止"
|
||
weblogbean.STATUS = "阻止访问"
|
||
weblogbean.STATUS_CODE = 403
|
||
weblogbean.TASK_FLAG = 1
|
||
weblogbean.GUEST_IDENTIFICATION = "可疑用户"
|
||
global.GQEQUE_LOG_DB.PushBack(weblogbean)
|
||
|
||
zlog.Debug(ruleName)
|
||
}
|
||
func errorHandler() func(http.ResponseWriter, *http.Request, error) {
|
||
return func(w http.ResponseWriter, req *http.Request, err error) {
|
||
zlog.Debug("Got error response:", zap.Any("err", err))
|
||
return
|
||
}
|
||
}
|
||
|
||
func (waf *WafEngine) modifyResponse() func(*http.Response) error {
|
||
return func(resp *http.Response) error {
|
||
resp.Header.Set("WAF", "SamWAF")
|
||
resp.Header.Set("Server", "SamWAFServer")
|
||
resp.Header.Set("X-Xss-Protection", "1; mode=block")
|
||
|
||
//X-Xss-Protection:
|
||
r := resp.Request
|
||
//开始准备req数据
|
||
//reqUUid := r.Header.Get("Waf_req_uuid")
|
||
|
||
if weblogfrist, ok := r.Context().Value("weblog").(innerbean.WebLog); ok {
|
||
fmt.Sprintf("weblogfrist: %v", weblogfrist)
|
||
|
||
weblogfrist.ACTION = "放行"
|
||
weblogfrist.STATUS = resp.Status
|
||
weblogfrist.STATUS_CODE = resp.StatusCode
|
||
|
||
host := resp.Request.Host
|
||
if !strings.Contains(host, ":") {
|
||
host = host + ":80"
|
||
}
|
||
/*if waf.HostTarget[host] != nil {
|
||
//weblogbean.HOST_CODE = waf.HostTarget[host].Host.Code
|
||
//TODO 理论上可以集成下来 等回头再看看
|
||
}*/
|
||
ldpFlag := false
|
||
//隐私保护(局部)
|
||
for i := 0; i < len(waf.HostTarget[host].LdpUrlLists); i++ {
|
||
if (waf.HostTarget[host].LdpUrlLists[i].CompareType == "等于" && waf.HostTarget[host].LdpUrlLists[i].Url == resp.Request.RequestURI) ||
|
||
(waf.HostTarget[host].LdpUrlLists[i].CompareType == "前缀匹配" && strings.HasPrefix(resp.Request.RequestURI, waf.HostTarget[host].LdpUrlLists[i].Url)) ||
|
||
(waf.HostTarget[host].LdpUrlLists[i].CompareType == "后缀匹配" && strings.HasSuffix(resp.Request.RequestURI, waf.HostTarget[host].LdpUrlLists[i].Url)) ||
|
||
(waf.HostTarget[host].LdpUrlLists[i].CompareType == "包含匹配" && strings.Contains(resp.Request.RequestURI, waf.HostTarget[host].LdpUrlLists[i].Url)) {
|
||
|
||
ldpFlag = true
|
||
break
|
||
}
|
||
}
|
||
//隐私保护(局部)
|
||
for i := 0; i < len(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists); i++ {
|
||
if (waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].CompareType == "等于" && waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].Url == resp.Request.RequestURI) ||
|
||
(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].CompareType == "前缀匹配" && strings.HasPrefix(resp.Request.RequestURI, waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].Url)) ||
|
||
(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].CompareType == "后缀匹配" && strings.HasSuffix(resp.Request.RequestURI, waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].Url)) ||
|
||
(waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].CompareType == "包含匹配" && strings.Contains(resp.Request.RequestURI, waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].LdpUrlLists[i].Url)) {
|
||
|
||
ldpFlag = true
|
||
break
|
||
}
|
||
}
|
||
if ldpFlag == true {
|
||
orgContentBytes, _ := waf.getOrgContent(resp)
|
||
newPayload := []byte("" + utils.DeSenText(string(orgContentBytes)))
|
||
finalCompressBytes, _ := waf.compressContent(resp, newPayload)
|
||
|
||
resp.Body = io.NopCloser(bytes.NewBuffer(finalCompressBytes))
|
||
// head 修改追加内容
|
||
resp.ContentLength = int64(len(finalCompressBytes))
|
||
resp.Header.Set("Content-Length", strconv.FormatInt(int64(len(finalCompressBytes)), 10))
|
||
}
|
||
//返回内容的类型
|
||
respContentType := resp.Header.Get("Content-Type")
|
||
respContentType = strings.Replace(respContentType, "; charset=utf-8", "", -1)
|
||
contentType := ""
|
||
//JS情况
|
||
if respContentType == "application/javascript" {
|
||
contentType = "js"
|
||
}
|
||
//CSS情况
|
||
if respContentType == "text/css" {
|
||
contentType = "css"
|
||
}
|
||
//图片情况
|
||
if respContentType == "image/jpeg" || respContentType == "image/png" ||
|
||
respContentType == "image/gif" || respContentType == "image/x-icon" {
|
||
contentType = "img"
|
||
}
|
||
//文本资源
|
||
if respContentType == "text/html" || respContentType == "application/html" ||
|
||
respContentType == "application/json" || respContentType == "application/xml" {
|
||
contentType = "text"
|
||
}
|
||
//记录静态日志
|
||
isStaticAssist := false
|
||
isText := false
|
||
switch contentType {
|
||
case "js":
|
||
isStaticAssist = true
|
||
break
|
||
case "css":
|
||
isStaticAssist = true
|
||
break
|
||
case "img":
|
||
isStaticAssist = true
|
||
break
|
||
case "text":
|
||
isText = true
|
||
break
|
||
default:
|
||
/*weblogbean.ACTION = "放行"
|
||
global.GQEQUE_LOG_DB.PushBack(weblogbean)*/
|
||
}
|
||
|
||
//记录响应body
|
||
if isText && resp.Body != nil && resp.Body != http.NoBody && global.GCONFIG_RECORD_RESP == 1 {
|
||
|
||
//编码转换,自动检测网页编码
|
||
orgContentBytes, _ := waf.getOrgContent(resp)
|
||
finalCompressBytes, _ := waf.compressContent(resp, orgContentBytes)
|
||
resp.Body = io.NopCloser(bytes.NewBuffer(finalCompressBytes))
|
||
|
||
// head 修改追加内容
|
||
resp.ContentLength = int64(len(finalCompressBytes))
|
||
resp.Header.Set("Content-Length", strconv.FormatInt(int64(len(finalCompressBytes)), 10))
|
||
if resp.ContentLength < global.GCONFIG_RECORD_MAX_RES_BODY_LENGTH {
|
||
weblogfrist.RES_BODY = string(orgContentBytes)
|
||
}
|
||
}
|
||
|
||
//TODO 如果是指定URL 或者 IP 不记录日志
|
||
if !isStaticAssist && !strings.Contains(weblogfrist.URL, "index.php/lttshop/task_scheduling/") {
|
||
datetimeNow := time.Now()
|
||
weblogfrist.TimeSpent = datetimeNow.UnixNano()/1e6 - weblogfrist.UNIX_ADD_TIME
|
||
weblogfrist.ACTION = "放行"
|
||
weblogfrist.STATUS = resp.Status
|
||
weblogfrist.STATUS_CODE = resp.StatusCode
|
||
weblogfrist.TASK_FLAG = 1
|
||
if global.GWAF_RUNTIME_RECORD_LOG_TYPE == "all" {
|
||
global.GQEQUE_LOG_DB.PushBack(weblogfrist)
|
||
} else if global.GWAF_RUNTIME_RECORD_LOG_TYPE == "abnormal" && weblogfrist.ACTION != "放行" {
|
||
global.GQEQUE_LOG_DB.PushBack(weblogfrist)
|
||
}
|
||
|
||
}
|
||
} else {
|
||
fmt.Println("weblog not found")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 返回内容前依据情况进行返回压缩数据
|
||
func (waf *WafEngine) compressContent(res *http.Response, inputBytes []byte) (respBytes []byte, err error) {
|
||
|
||
switch res.Header.Get("Content-Encoding") {
|
||
case "gzip":
|
||
respBytes, err = utils.GZipEncode(inputBytes)
|
||
case "deflate":
|
||
respBytes, err = utils.DeflateEncode(inputBytes)
|
||
default:
|
||
respBytes = inputBytes
|
||
}
|
||
return
|
||
}
|
||
|
||
// 获取原始内容
|
||
func (waf *WafEngine) getOrgContent(resp *http.Response) (cntBytes []byte, err error) {
|
||
var bodyReader io.Reader
|
||
switch resp.Header.Get("Content-Encoding") {
|
||
case "gzip":
|
||
bodyReader, err = gzip.NewReader(resp.Body)
|
||
case "deflate":
|
||
bodyReader = flate.NewReader(resp.Body)
|
||
default:
|
||
bodyReader = resp.Body
|
||
}
|
||
newBodyReader := bufio.NewReader(bodyReader)
|
||
//使用peek读取十分关键,只是偷看一下,不会移动读取位置,否则其他地方就没法读取了
|
||
var currentEncoding encoding.Encoding
|
||
bytes, err := newBodyReader.Peek(1024)
|
||
if err != nil {
|
||
currentEncoding = unicode.UTF8
|
||
} else {
|
||
currentEncoding, _, _ = charset.DetermineEncoding(bytes, "")
|
||
}
|
||
reader := transform.NewReader(newBodyReader, currentEncoding.NewDecoder())
|
||
resbodyByte, err := io.ReadAll(reader)
|
||
if err != nil {
|
||
zlog.Info("error", err)
|
||
return nil, err
|
||
}
|
||
return resbodyByte, nil
|
||
}
|
||
func (waf *WafEngine) StartWaf() {
|
||
|
||
waf.EngineCurrentStatus = 1
|
||
var hosts []model.Hosts
|
||
//是否有初始化全局保护
|
||
global.GWAF_LOCAL_DB.Where("global_host = ?", 1).Find(&hosts)
|
||
if hosts != nil && len(hosts) == 0 {
|
||
//初始化全局保护
|
||
var wafGlobalHost = &model.Hosts{
|
||
BaseOrm: baseorm.BaseOrm{
|
||
USER_CODE: global.GWAF_USER_CODE,
|
||
Tenant_ID: global.GWAF_TENANT_ID,
|
||
CREATE_TIME: customtype.JsonTime(time.Now()),
|
||
UPDATE_TIME: customtype.JsonTime(time.Now()),
|
||
},
|
||
Code: uuid.NewV4().String(),
|
||
Host: "全局网站",
|
||
Port: 0,
|
||
Ssl: 0,
|
||
GUARD_STATUS: 0,
|
||
REMOTE_SYSTEM: "",
|
||
REMOTE_APP: "",
|
||
Remote_host: "",
|
||
Remote_port: 0,
|
||
Certfile: "",
|
||
Keyfile: "",
|
||
REMARKS: "",
|
||
GLOBAL_HOST: 1,
|
||
}
|
||
global.GWAF_LOCAL_DB.Create(wafGlobalHost)
|
||
}
|
||
|
||
//初始化步骤[加载ip数据库]
|
||
// 从嵌入的文件中读取内容
|
||
|
||
//global.GCACHE_IP_CBUFF = main.Ip2regionBytes
|
||
|
||
//第一步 检测合法性并加入到全局
|
||
waf.LoadAllHost()
|
||
|
||
wafSysLog := &model.WafSysLog{
|
||
BaseOrm: baseorm.BaseOrm{
|
||
Id: uuid.NewV4().String(),
|
||
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: "WAF启动",
|
||
}
|
||
global.GQEQUE_LOG_DB.PushBack(wafSysLog)
|
||
|
||
waf.StartAllProxyServer()
|
||
}
|
||
|
||
// 关闭waf
|
||
func (waf *WafEngine) CloseWaf() {
|
||
defer func() {
|
||
e := recover()
|
||
if e != nil { // 捕获该协程的panic 111111
|
||
zlog.Debug("关闭 recover ", e)
|
||
}
|
||
}()
|
||
wafSysLog := &model.WafSysLog{
|
||
BaseOrm: baseorm.BaseOrm{
|
||
Id: uuid.NewV4().String(),
|
||
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: "WAF关闭",
|
||
}
|
||
global.GQEQUE_LOG_DB.PushBack(wafSysLog)
|
||
waf.EngineCurrentStatus = 0
|
||
|
||
waf.StopAllProxyServer()
|
||
//重置信息
|
||
waf.HostTarget = map[string]*wafenginmodel.HostSafe{}
|
||
waf.HostCode = map[string]string{}
|
||
waf.ServerOnline = map[int]innerbean.ServerRunTime{}
|
||
waf.AllCertificate = map[int]map[string]*tls.Certificate{}
|
||
|
||
}
|
||
|
||
// 加载全部host
|
||
func (waf *WafEngine) LoadAllHost() {
|
||
//重新查询
|
||
var hosts []model.Hosts
|
||
global.GWAF_LOCAL_DB.Find(&hosts)
|
||
for i := 0; i < len(hosts); i++ {
|
||
waf.LoadHost(hosts[i])
|
||
}
|
||
}
|
||
|
||
// 加载指定host
|
||
func (waf *WafEngine) LoadHost(inHost model.Hosts) innerbean.ServerRunTime {
|
||
|
||
//检测https
|
||
if inHost.Ssl == 1 {
|
||
cert, err := tls.X509KeyPair([]byte(inHost.Certfile), []byte(inHost.Keyfile))
|
||
if err != nil {
|
||
zlog.Warn("Cannot find %s cert & key file. Error is: %s\n", inHost.Host, err)
|
||
return innerbean.ServerRunTime{}
|
||
|
||
}
|
||
//all_certificate[hosts[i].Port][hosts[i].Host] = &cert
|
||
mm, ok := waf.AllCertificate[inHost.Port] //[hosts[i].Host]
|
||
if !ok {
|
||
mm = make(map[string]*tls.Certificate)
|
||
waf.AllCertificate[inHost.Port] = mm
|
||
}
|
||
waf.AllCertificate[inHost.Port][inHost.Host] = &cert
|
||
}
|
||
_, ok := waf.ServerOnline[inHost.Port]
|
||
if ok == false && inHost.GLOBAL_HOST == 0 {
|
||
waf.ServerOnline[inHost.Port] = innerbean.ServerRunTime{
|
||
ServerType: utils.GetServerByHosts(inHost),
|
||
Port: inHost.Port,
|
||
Status: 1,
|
||
}
|
||
}
|
||
//加载主机对于的规则
|
||
ruleHelper := &utils.RuleHelper{}
|
||
ruleHelper.InitRuleEngine()
|
||
//查询规则
|
||
var vcnt int
|
||
global.GWAF_LOCAL_DB.Model(&model.Rules{}).Where("host_code = ? and rule_status<>999",
|
||
inHost.Code).Select("sum(rule_version) as vcnt").Row().Scan(&vcnt)
|
||
zlog.Debug("主机host" + inHost.Code + " 版本" + strconv.Itoa(vcnt))
|
||
var ruleconfigs []model.Rules
|
||
if vcnt > 0 {
|
||
global.GWAF_LOCAL_DB.Where("host_code = ?and rule_status<>999", inHost.Code).Find(&ruleconfigs)
|
||
ruleHelper.LoadRules(ruleconfigs)
|
||
}
|
||
//查询ip限流(应该针对一个网址只有一个)
|
||
var anticcBean model.AntiCC
|
||
|
||
global.GWAF_LOCAL_DB.Where("host_code=? ", inHost.Code).Limit(1).Find(&anticcBean)
|
||
|
||
//初始化插件-ip计数器
|
||
var pluginIpRateLimiter *plugin.IPRateLimiter
|
||
if anticcBean.Id != "" {
|
||
pluginIpRateLimiter = plugin.NewIPRateLimiter(rate.Limit(anticcBean.Rate), anticcBean.Limit)
|
||
}
|
||
|
||
//查询ip白名单
|
||
var ipwhitelist []model.IPWhiteList
|
||
global.GWAF_LOCAL_DB.Where("host_code=? ", inHost.Code).Find(&ipwhitelist)
|
||
|
||
//查询url白名单
|
||
var urlwhitelist []model.URLWhiteList
|
||
global.GWAF_LOCAL_DB.Where("host_code=? ", inHost.Code).Find(&urlwhitelist)
|
||
|
||
//查询ip黑名单
|
||
var ipblocklist []model.IPBlockList
|
||
global.GWAF_LOCAL_DB.Where("host_code=? ", inHost.Code).Find(&ipblocklist)
|
||
|
||
//查询url白名单
|
||
var urlblocklist []model.URLBlockList
|
||
global.GWAF_LOCAL_DB.Where("host_code=? ", inHost.Code).Find(&urlblocklist)
|
||
|
||
//查询url隐私保护
|
||
var ldpurls []model.LDPUrl
|
||
global.GWAF_LOCAL_DB.Where("host_code=? ", inHost.Code).Find(&ldpurls)
|
||
|
||
//初始化主机host
|
||
hostsafe := &wafenginmodel.HostSafe{
|
||
RevProxy: nil,
|
||
Rule: ruleHelper,
|
||
TargetHost: inHost.Remote_host + ":" + strconv.Itoa(inHost.Remote_port),
|
||
RuleData: ruleconfigs,
|
||
RuleVersionSum: vcnt,
|
||
Host: inHost,
|
||
PluginIpRateLimiter: pluginIpRateLimiter,
|
||
IPWhiteLists: ipwhitelist,
|
||
UrlWhiteLists: urlwhitelist,
|
||
LdpUrlLists: ldpurls,
|
||
IPBlockLists: ipblocklist,
|
||
UrlBlockLists: urlblocklist,
|
||
}
|
||
hostsafe.Mux.Lock()
|
||
defer hostsafe.Mux.Unlock()
|
||
//赋值到白名单里面
|
||
waf.HostTarget[inHost.Host+":"+strconv.Itoa(inHost.Port)] = hostsafe
|
||
//赋值到对照表里面
|
||
waf.HostCode[inHost.Code] = inHost.Host + ":" + strconv.Itoa(inHost.Port)
|
||
return waf.ServerOnline[inHost.Port]
|
||
}
|
||
|
||
/*
|
||
*
|
||
移除主机
|
||
*/
|
||
func (waf *WafEngine) RemoveHost(host model.Hosts) {
|
||
/*//主机情况
|
||
HostTarget map[string]*wafenginmodel.HostSafe
|
||
//主机和code的关系
|
||
HostCode map[string]string
|
||
ServerOnline map[int]innerbean.ServerRunTime
|
||
|
||
//所有证书情况 对应端口 可能多个端口都是https 443,或者其他非标准端口也要实现https证书
|
||
AllCertificate map[int]map[string]*tls.Certificate*/
|
||
//1.如果这个port里面没有了主机 那可以直接停掉服务
|
||
//2.除了第一个情况之外的,把所有他的主机信息和关联信息都干掉
|
||
if waf_service.WafHostServiceApp.CheckPortExistApi(host.Port) == 0 {
|
||
zlog.Debug("准备移除这个端口下所有信息")
|
||
// a.移除证书数据
|
||
_, ok := waf.AllCertificate[host.Port]
|
||
if ok {
|
||
delete(waf.AllCertificate, host.Port)
|
||
}
|
||
//b.移除对照关系
|
||
delete(waf.HostCode, host.Code)
|
||
//c.移除主机保护信息
|
||
delete(waf.HostTarget, host.Host+":"+strconv.Itoa(host.Port))
|
||
//d.暂停服务 并 移除服务信息
|
||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||
defer cancel()
|
||
err := waf.ServerOnline[host.Port].Svr.Shutdown(ctx)
|
||
if err != nil {
|
||
zlog.Error("shutting down: " + err.Error())
|
||
} else {
|
||
zlog.Info("shutdown processed successfully port" + strconv.Itoa(host.Port))
|
||
}
|
||
delete(waf.ServerOnline, host.Port)
|
||
} else {
|
||
zlog.Debug("准备移除没用的主机信息")
|
||
// a.移除某个端口下的证书数据
|
||
_, ok := waf.AllCertificate[host.Port][host.Host]
|
||
if ok {
|
||
delete(waf.AllCertificate[host.Port], host.Host)
|
||
}
|
||
//b.移除对照关系
|
||
delete(waf.HostCode, host.Code)
|
||
//c.移除主机保护信息
|
||
delete(waf.HostTarget, host.Host+":"+strconv.Itoa(host.Port))
|
||
}
|
||
}
|
||
|
||
// 开启所有代理
|
||
func (waf *WafEngine) StartAllProxyServer() {
|
||
for _, v := range waf.ServerOnline {
|
||
waf.StartProxyServer(v)
|
||
}
|
||
}
|
||
func (waf *WafEngine) StartProxyServer(innruntime innerbean.ServerRunTime) {
|
||
if innruntime.Status == 0 {
|
||
//启动完成的就不进这里了
|
||
return
|
||
}
|
||
if innruntime.ServerType == "" {
|
||
//如果是空的就不进行了
|
||
return
|
||
}
|
||
go func(innruntime innerbean.ServerRunTime) {
|
||
|
||
if (innruntime.ServerType) == "https" {
|
||
|
||
defer func() {
|
||
e := recover()
|
||
if e != nil { // 捕获该协程的panic 111111
|
||
zlog.Warn("https recover ", e)
|
||
}
|
||
}()
|
||
svr := &http.Server{
|
||
Addr: ":" + strconv.Itoa(innruntime.Port),
|
||
Handler: waf,
|
||
TLSConfig: &tls.Config{
|
||
NameToCertificate: make(map[string]*tls.Certificate, 0),
|
||
},
|
||
}
|
||
serclone := waf.ServerOnline[innruntime.Port]
|
||
serclone.Svr = svr
|
||
serclone.Status = 0
|
||
waf.ServerOnline[innruntime.Port] = serclone
|
||
|
||
svr.TLSConfig.NameToCertificate = waf.AllCertificate[innruntime.Port]
|
||
svr.TLSConfig.GetCertificate = func(clientInfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||
if x509Cert, ok := svr.TLSConfig.NameToCertificate[clientInfo.ServerName]; ok {
|
||
return x509Cert, nil
|
||
}
|
||
return nil, errors.New("config error")
|
||
}
|
||
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.NewV4().String(),
|
||
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.PushBack(wafSysLog)
|
||
zlog.Error("[HTTPServer] https server start fail, cause:[%v]", err)
|
||
}
|
||
zlog.Info("server https shutdown")
|
||
|
||
} else {
|
||
defer func() {
|
||
e := recover()
|
||
if e != nil { // 捕获该协程的panic 111111
|
||
zlog.Warn("http recover ", e)
|
||
}
|
||
}()
|
||
svr := &http.Server{
|
||
Addr: ":" + strconv.Itoa(innruntime.Port),
|
||
Handler: waf,
|
||
}
|
||
serclone := waf.ServerOnline[innruntime.Port]
|
||
serclone.Svr = svr
|
||
serclone.Status = 0
|
||
waf.ServerOnline[innruntime.Port] = serclone
|
||
|
||
zlog.Info("启动HTTP 服务器" + strconv.Itoa(innruntime.Port))
|
||
err := svr.ListenAndServe()
|
||
if err == http.ErrServerClosed {
|
||
zlog.Warn("[HTTPServer] http server has been close, cause:[%v]", err)
|
||
} else {
|
||
//TODO 记录如果http 端口被占用的情况 记录日志 且应该推送websocket
|
||
wafSysLog := model.WafSysLog{
|
||
BaseOrm: baseorm.BaseOrm{
|
||
Id: uuid.NewV4().String(),
|
||
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: "HTTP端口被占用: " + strconv.Itoa(innruntime.Port) + ",请检查",
|
||
}
|
||
global.GQEQUE_LOG_DB.PushBack(wafSysLog)
|
||
zlog.Error("[HTTPServer] http server start fail, cause:[%v]", err)
|
||
}
|
||
zlog.Info("server http shutdown")
|
||
}
|
||
|
||
}(innruntime)
|
||
}
|
||
|
||
// 关闭所有代理服务
|
||
func (waf *WafEngine) StopAllProxyServer() {
|
||
for _, v := range waf.ServerOnline {
|
||
waf.StopProxyServer(v)
|
||
}
|
||
}
|
||
|
||
// 关闭指定代理服务
|
||
func (waf *WafEngine) StopProxyServer(v innerbean.ServerRunTime) {
|
||
if v.Svr != nil {
|
||
v.Svr.Close()
|
||
}
|
||
}
|