mirror of
https://github.com/eolinker/apinto
synced 2025-09-26 21:01:19 +08:00
新增防重放插件
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
params_check "github.com/eolinker/apinto/drivers/plugins/params-check"
|
||||
params_check_v2 "github.com/eolinker/apinto/drivers/plugins/params-check-v2"
|
||||
"github.com/eolinker/apinto/drivers/plugins/prometheus"
|
||||
replay_attack_defender "github.com/eolinker/apinto/drivers/plugins/replay-attack-defender"
|
||||
request_file_parse "github.com/eolinker/apinto/drivers/plugins/request-file-parse"
|
||||
request_interception "github.com/eolinker/apinto/drivers/plugins/request-interception"
|
||||
response_file_parse "github.com/eolinker/apinto/drivers/plugins/response-file-parse"
|
||||
@@ -28,7 +29,7 @@ import (
|
||||
rsa_filter "github.com/eolinker/apinto/drivers/plugins/rsa-filter"
|
||||
script_handler "github.com/eolinker/apinto/drivers/plugins/script-handler"
|
||||
data_mask "github.com/eolinker/apinto/drivers/plugins/strategy/data-mask"
|
||||
|
||||
|
||||
access_log "github.com/eolinker/apinto/drivers/plugins/access-log"
|
||||
body_check "github.com/eolinker/apinto/drivers/plugins/body-check"
|
||||
circuit_breaker "github.com/eolinker/apinto/drivers/plugins/circuit-breaker"
|
||||
@@ -52,12 +53,12 @@ import (
|
||||
"github.com/eolinker/apinto/drivers/plugins/strategy/grey"
|
||||
"github.com/eolinker/apinto/drivers/plugins/strategy/limiting"
|
||||
"github.com/eolinker/apinto/drivers/plugins/strategy/visit"
|
||||
|
||||
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
func pluginRegister(extenderRegister eosc.IExtenderDriverRegister) {
|
||||
|
||||
|
||||
// 服务治理-策略相关插件
|
||||
limiting.Register(extenderRegister)
|
||||
cache.Register(extenderRegister)
|
||||
@@ -65,17 +66,17 @@ func pluginRegister(extenderRegister eosc.IExtenderDriverRegister) {
|
||||
visit.Register(extenderRegister)
|
||||
fuse.Register(extenderRegister)
|
||||
data_mask.Register(extenderRegister)
|
||||
|
||||
|
||||
// Dubbo协议相关插件
|
||||
dubbo2_proxy_rewrite.Register(extenderRegister)
|
||||
http_to_dubbo2.Register(extenderRegister)
|
||||
dubbo2_to_http.Register(extenderRegister)
|
||||
|
||||
|
||||
// gRPC协议相关插件
|
||||
http_to_grpc.Register(extenderRegister)
|
||||
grpc_to_http.Register(extenderRegister)
|
||||
grpc_proxy_rewrite.Register(extenderRegister)
|
||||
|
||||
|
||||
// 请求处理相关插件
|
||||
body_check.Register(extenderRegister)
|
||||
extra_params.Register(extenderRegister)
|
||||
@@ -89,7 +90,7 @@ func pluginRegister(extenderRegister eosc.IExtenderDriverRegister) {
|
||||
data_transform.Register(extenderRegister)
|
||||
request_interception.Register(extenderRegister)
|
||||
request_file_parse.Register(extenderRegister)
|
||||
|
||||
|
||||
// 响应处理插件
|
||||
response_rewrite.Register(extenderRegister)
|
||||
response_rewrite_v2.Register(extenderRegister)
|
||||
@@ -97,7 +98,7 @@ func pluginRegister(extenderRegister eosc.IExtenderDriverRegister) {
|
||||
gzip.Register(extenderRegister)
|
||||
response_file_parse.Register(extenderRegister)
|
||||
auto_redirect.Register(extenderRegister)
|
||||
|
||||
|
||||
// 安全相关插件
|
||||
ip_restriction.Register(extenderRegister)
|
||||
rate_limiting.Register(extenderRegister)
|
||||
@@ -109,23 +110,25 @@ func pluginRegister(extenderRegister eosc.IExtenderDriverRegister) {
|
||||
js_inject.Register(extenderRegister)
|
||||
acl.Register(extenderRegister)
|
||||
access_relational.Register(extenderRegister)
|
||||
replay_attack_defender.Register(extenderRegister)
|
||||
|
||||
// 可观测性(输出内容到第三方)
|
||||
access_log.Register(extenderRegister)
|
||||
prometheus.Register(extenderRegister)
|
||||
monitor.Register(extenderRegister)
|
||||
proxy_mirror.Register(extenderRegister)
|
||||
|
||||
|
||||
// 计数插件
|
||||
counter.Register(extenderRegister)
|
||||
|
||||
|
||||
// 鉴权插件
|
||||
oauth2.Register(extenderRegister)
|
||||
oauth2_introspection.Register(extenderRegister)
|
||||
|
||||
|
||||
// ai相关插件
|
||||
ai_prompt.Register(extenderRegister)
|
||||
ai_formatter.Register(extenderRegister)
|
||||
//ai_balance.Register(extenderRegister)
|
||||
|
||||
|
||||
script_handler.Register(extenderRegister)
|
||||
}
|
||||
|
12
drivers/plugins/replay-attack-defender/config.go
Normal file
12
drivers/plugins/replay-attack-defender/config.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package replay_attack_defender
|
||||
|
||||
import "github.com/eolinker/eosc"
|
||||
|
||||
type Config struct {
|
||||
NonceHeader string `json:"nonce_header" label:"API调用者生成的唯一UUID存放的请求头" default:"X-Ca-Nonce"`
|
||||
TimestampHeader string `json:"timestamp_header" label:"10位时间戳存放的请求头" default:"X-Ca-Timestamp"`
|
||||
SignHeader string `json:"sign_header" label:"防重放签名存放的请求头" default:"X-Ca-Signature"`
|
||||
ReplayAttackToken string `json:"replay_attack_token" label:"重放攻击防御令牌" default:"apinto"`
|
||||
TTL int `json:"ttl" label:"过期时间,单位:s" default:"600"`
|
||||
Cache eosc.RequireId `json:"cache" skill:"github.com/eolinker/apinto/resources.resources.ICache" required:"false" label:"缓存位置"`
|
||||
}
|
156
drivers/plugins/replay-attack-defender/executor.go
Normal file
156
drivers/plugins/replay-attack-defender/executor.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package replay_attack_defender
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/eolinker/apinto/drivers"
|
||||
"github.com/eolinker/apinto/resources"
|
||||
scope_manager "github.com/eolinker/apinto/scope-manager"
|
||||
"github.com/eolinker/apinto/utils"
|
||||
"github.com/eolinker/eosc"
|
||||
"github.com/eolinker/eosc/eocontext"
|
||||
http_service "github.com/eolinker/eosc/eocontext/http-context"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ http_service.HttpFilter = (*executor)(nil)
|
||||
var _ eocontext.IFilter = (*executor)(nil)
|
||||
var _ eosc.IWorker = (*executor)(nil)
|
||||
|
||||
type executor struct {
|
||||
drivers.WorkerBase
|
||||
signHeader string
|
||||
timestampHeader string
|
||||
nonceHeader string
|
||||
token string
|
||||
ttl time.Duration
|
||||
cache scope_manager.IProxyOutput[resources.ICache]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (e *executor) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) (err error) {
|
||||
return http_service.DoHttpFilter(e, ctx, next)
|
||||
}
|
||||
|
||||
func (e *executor) DoHttpFilter(ctx http_service.IHttpContext, next eocontext.IChain) (err error) {
|
||||
e.once.Do(func() {
|
||||
e.cache = scope_manager.Auto[resources.ICache]("", "redis")
|
||||
})
|
||||
timestamp, err := checkTimestamp(ctx.Request().Header().GetHeader(e.timestampHeader))
|
||||
if err != nil {
|
||||
ctx.Response().SetStatus(http.StatusBadRequest, "Bad Request")
|
||||
ctx.Response().SetBody([]byte(err.Error()))
|
||||
return err
|
||||
}
|
||||
|
||||
nonce := ctx.Request().Header().GetHeader(e.nonceHeader)
|
||||
if nonce == "" {
|
||||
ctx.Response().SetStatus(http.StatusBadRequest, "Bad Request")
|
||||
ctx.Response().SetBody([]byte("missing nonce"))
|
||||
return fmt.Errorf("missing nonce")
|
||||
}
|
||||
|
||||
sign := ctx.Request().Header().GetHeader(e.signHeader)
|
||||
if sign == "" {
|
||||
ctx.Response().SetStatus(http.StatusBadRequest, "Bad Request")
|
||||
ctx.Response().SetBody([]byte("missing sign"))
|
||||
return fmt.Errorf("missing sign")
|
||||
}
|
||||
signBefore := fmt.Sprintf("%d%s%s", timestamp, nonce, e.token)
|
||||
signAfter := utils.Md5(signBefore)
|
||||
if sign != signAfter {
|
||||
ctx.Response().SetStatus(http.StatusForbidden, "Forbidden")
|
||||
ctx.Response().SetBody([]byte("invalid sign"))
|
||||
return fmt.Errorf("invalid sign")
|
||||
}
|
||||
key := "apinto:replay-attack-defender:" + sign
|
||||
for _, c := range e.cache.List() {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
_, err = c.Get(ctx.Context(), key).Result()
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
c.SetNX(ctx.Context(), key, []byte(signBefore), e.ttl)
|
||||
break
|
||||
}
|
||||
if err.Error() != "redis: nil" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("replay attack detected, nonce: %s, sign: %s, timestamp: %d", nonce, sign, timestamp)
|
||||
ctx.Response().SetStatus(http.StatusForbidden, "Forbidden")
|
||||
ctx.Response().SetBody([]byte(err.Error()))
|
||||
return err
|
||||
}
|
||||
if next != nil {
|
||||
return next.DoChain(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *executor) Destroy() {
|
||||
return
|
||||
}
|
||||
|
||||
func (e *executor) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *executor) Reset(conf interface{}, workers map[eosc.RequireId]eosc.IWorker) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *executor) reset(conf *Config) error {
|
||||
|
||||
if conf.NonceHeader == "" {
|
||||
conf.NonceHeader = "X-Ca-Nonce"
|
||||
}
|
||||
if conf.SignHeader == "" {
|
||||
conf.SignHeader = "X-Ca-Signature"
|
||||
}
|
||||
if conf.TimestampHeader == "" {
|
||||
conf.TimestampHeader = "X-Ca-Timestamp"
|
||||
}
|
||||
if conf.TTL < 1 {
|
||||
conf.TTL = 600
|
||||
}
|
||||
e.signHeader = conf.SignHeader
|
||||
e.timestampHeader = conf.TimestampHeader
|
||||
e.nonceHeader = conf.NonceHeader
|
||||
e.token = conf.ReplayAttackToken
|
||||
e.ttl = time.Duration(conf.TTL) * time.Second
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *executor) Stop() error {
|
||||
e.Destroy()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *executor) CheckSkill(skill string) bool {
|
||||
return http_service.FilterSkillName == skill
|
||||
}
|
||||
|
||||
func checkTimestamp(timestamp string, ) (int64, error) {
|
||||
if timestamp == "" {
|
||||
return 0, fmt.Errorf("missing timestamp")
|
||||
}
|
||||
|
||||
ts, err := strconv.ParseInt(timestamp, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid timestamp %s, error: %v", timestamp, err)
|
||||
}
|
||||
now := time.Now()
|
||||
t := time.Unix(ts, 0)
|
||||
if now.Sub(t) > 10*time.Minute {
|
||||
return 0, fmt.Errorf("timestamp %d is too old, current time is %d", ts, now.Unix())
|
||||
} else if now.Sub(t) < -10*time.Minute {
|
||||
return 0, fmt.Errorf("timestamp %d is too new, current time is %d", ts, now.Unix())
|
||||
}
|
||||
return ts, nil
|
||||
}
|
30
drivers/plugins/replay-attack-defender/factory.go
Normal file
30
drivers/plugins/replay-attack-defender/factory.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package replay_attack_defender
|
||||
|
||||
import (
|
||||
"github.com/eolinker/apinto/drivers"
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "replay_attack_defender"
|
||||
)
|
||||
|
||||
func Register(register eosc.IExtenderDriverRegister) {
|
||||
register.RegisterExtenderDriver(Name, NewFactory())
|
||||
}
|
||||
|
||||
func NewFactory() eosc.IExtenderDriverFactory {
|
||||
return drivers.NewFactory[Config](Create)
|
||||
}
|
||||
|
||||
func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) {
|
||||
|
||||
w := &executor{
|
||||
WorkerBase: drivers.Worker(id, name),
|
||||
}
|
||||
err := w.reset(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w, nil
|
||||
}
|
Reference in New Issue
Block a user