211 lines
5.1 KiB
Go
211 lines
5.1 KiB
Go
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||
//go:build plus
|
||
|
||
package nodes
|
||
|
||
import (
|
||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||
"github.com/TeaOSLab/EdgeNode/internal/uam"
|
||
"github.com/TeaOSLab/EdgeNode/internal/utils/agents"
|
||
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
|
||
"github.com/TeaOSLab/EdgeNode/internal/utils/ttlcache"
|
||
"github.com/iwind/TeaGo/Tea"
|
||
"github.com/iwind/TeaGo/types"
|
||
"net/http"
|
||
"strings"
|
||
)
|
||
|
||
var sharedUAMManager *uam.Manager
|
||
|
||
func init() {
|
||
if !teaconst.IsMain {
|
||
return
|
||
}
|
||
|
||
events.On(events.EventLoaded, func() {
|
||
if sharedUAMManager != nil {
|
||
return
|
||
}
|
||
manager, _ := uam.NewManager(nodeConfig().NodeId, nodeConfig().Secret)
|
||
if manager != nil {
|
||
sharedUAMManager = manager
|
||
}
|
||
})
|
||
|
||
events.On(events.EventReload, func() {
|
||
if sharedUAMManager != nil {
|
||
return
|
||
}
|
||
manager, _ := uam.NewManager(nodeConfig().NodeId, nodeConfig().Secret)
|
||
if manager != nil {
|
||
sharedUAMManager = manager
|
||
}
|
||
})
|
||
}
|
||
|
||
func (this *HTTPRequest) isUAMRequest() bool {
|
||
if this.web.UAM == nil || !this.web.UAM.IsOn || this.RawReq.Method != http.MethodPost {
|
||
return false
|
||
}
|
||
|
||
var cookiesString = this.RawReq.Header.Get("Cookie")
|
||
if len(cookiesString) == 0 {
|
||
return false
|
||
}
|
||
|
||
return strings.HasPrefix(cookiesString, uam.CookiePrevKey+"=") ||
|
||
strings.Contains(cookiesString, " "+uam.CookiePrevKey+"=")
|
||
}
|
||
|
||
// UAM
|
||
// TODO 需要检查是否为plus
|
||
func (this *HTTPRequest) doUAM() (block bool) {
|
||
var serverId int64
|
||
if this.ReqServer != nil {
|
||
serverId = this.ReqServer.Id
|
||
}
|
||
|
||
var uamConfig = this.web.UAM
|
||
if uamConfig == nil ||
|
||
!uamConfig.IsOn ||
|
||
!uamConfig.MatchURL(this.requestScheme()+"://"+this.ReqHost+this.Path()) ||
|
||
!uamConfig.MatchRequest(this.Format) {
|
||
return
|
||
}
|
||
|
||
var policy = this.nodeConfig.FindUAMPolicyWithClusterId(this.ReqServer.ClusterId)
|
||
if policy == nil {
|
||
policy = nodeconfigs.NewUAMPolicy()
|
||
}
|
||
if policy == nil || !policy.IsOn {
|
||
return
|
||
}
|
||
|
||
// 获取UAM管理器
|
||
var manager = sharedUAMManager
|
||
if manager == nil {
|
||
return false
|
||
}
|
||
|
||
// 忽略URL白名单
|
||
if this.RawReq.URL.Path == "/favicon.ico" || this.RawReq.URL.Path == "/favicon.png" {
|
||
return false
|
||
}
|
||
|
||
// 检查白名单
|
||
var remoteAddr = this.requestRemoteAddr(true)
|
||
|
||
// 检查UAM白名单
|
||
if uamConfig.AddToWhiteList && ttlcache.SharedInt64Cache.Read("UAM:WHITE:"+remoteAddr) != nil {
|
||
return false
|
||
}
|
||
|
||
// 检查是否为白名单直连
|
||
if !Tea.IsTesting() && this.nodeConfig.IPIsAutoAllowed(remoteAddr) {
|
||
return
|
||
}
|
||
|
||
// 是否在全局名单中
|
||
canGoNext, isInAllowedList, _ := iplibrary.AllowIP(remoteAddr, serverId)
|
||
if !canGoNext {
|
||
this.disableLog = true
|
||
this.Close()
|
||
return true
|
||
}
|
||
if isInAllowedList {
|
||
return false
|
||
}
|
||
|
||
// 如果是搜索引擎直接通过
|
||
var userAgent = this.RawReq.UserAgent()
|
||
if len(userAgent) == 0 {
|
||
// 如果User-Agent为空,则直接阻止
|
||
this.writer.WriteHeader(http.StatusForbidden)
|
||
|
||
// 增加失败次数
|
||
if manager.IncreaseFails(policy, remoteAddr, serverId) {
|
||
this.isAttack = true
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// 不管是否开启允许搜索引擎,这里都放行,避免收录了拦截的代码
|
||
if agents.SharedManager.ContainsIP(remoteAddr) {
|
||
return false
|
||
}
|
||
if policy.AllowSearchEngines {
|
||
if searchEngineRegex.MatchString(userAgent) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 如果是python之类的直接拦截
|
||
if policy.DenySpiders && spiderRegexp.MatchString(userAgent) {
|
||
this.writer.WriteHeader(http.StatusForbidden)
|
||
|
||
// 增加失败次数
|
||
if manager.IncreaseFails(policy, remoteAddr, serverId) {
|
||
this.isAttack = true
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// 检查预生成Key
|
||
var step = this.Header().Get("X-GE-UA-Step")
|
||
if step == uam.StepPrev {
|
||
if this.Method() != http.MethodPost {
|
||
this.writer.WriteHeader(http.StatusForbidden)
|
||
return true
|
||
}
|
||
if manager.CheckPrevKey(policy, uamConfig, this.RawReq, remoteAddr, this.writer) {
|
||
_, _ = this.writer.Write([]byte(`{"ok": true}`))
|
||
} else {
|
||
_, _ = this.writer.Write([]byte(`{"ok": false}`))
|
||
}
|
||
|
||
// 增加失败次数
|
||
if manager.IncreaseFails(policy, remoteAddr, serverId) {
|
||
this.isAttack = true
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// 检查是否启用QPS
|
||
if uamConfig.MinQPSPerIP > 0 && len(step) == 0 {
|
||
var avgQPS = counters.SharedCounter.IncreaseKey("UAM:"+remoteAddr, 60) / 60
|
||
if avgQPS <= 0 {
|
||
avgQPS = 1
|
||
}
|
||
if avgQPS < types.Uint32(uamConfig.MinQPSPerIP) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 检查Cookie中的Key
|
||
isVerified, isAttack, _ := manager.CheckKey(policy, this.RawReq, this.writer, remoteAddr, serverId, uamConfig.KeyLife)
|
||
if isVerified {
|
||
return false
|
||
}
|
||
this.isAttack = isAttack
|
||
|
||
// 检查是否已生成有效的prev key,如果已经生成,则表示当前请求是附带请求(比如favicon.ico),不再重新生成新的
|
||
// TODO 考虑这里的必要性
|
||
if manager.ExistsActivePreKey(this.RawReq) {
|
||
return true
|
||
}
|
||
|
||
// 显示加载页面
|
||
err := manager.LoadPage(policy, this.RawReq, this.Format, remoteAddr, this.writer)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
|
||
return true
|
||
}
|