Bläddra i källkod

feat: add sdk

xiaobing.wang 1 år sedan
förälder
incheckning
f01652de22

+ 103 - 0
sdk/ingress-nginx/README.md

@@ -0,0 +1,103 @@
+# ingress-nginx-safeline
+
+[Ingress-nginx](https://kubernetes.github.io/ingress-nginx/) plugin for Chaitin SafeLine Web Application Firewall (WAF). This plugin is used to protect your API from malicious requests. It can be used to block requests that contain malicious content in the request body, query parameters, headers, or URI.
+
+## Usage
+
+### Step 1: Install the plugin
+
+way 1: Build your own ingress-nginx/controller image with the plugin installed.
+
+```dockerfile
+FROM registry.k8s.io/ingress-nginx/controller:v1.10.1
+
+USER root
+
+RUN apk add --no-cache make gcc unzip wget
+
+# install luaroncks
+RUN wget https://luarocks.org/releases/luarocks-3.11.0.tar.gz && \
+    tar zxpf luarocks-3.11.0.tar.gz && \
+    cd luarocks-3.11.0 && \
+    ./configure && \
+    make && \
+    make install && \
+    cd .. && \
+    rm -rf luarocks-3.11.0 luarocks-3.11.0.tar.gz
+
+RUN luarocks install ingress-nginx-safeline && \
+    ln -s /usr/local/share/lua/5.1/safeline /etc/nginx/lua/plugins/safeline
+
+USER www-data
+```
+
+way 2: Use the chaitin ingress-nginx-controller image.
+
+replace image ingress-nginx-controller with `docker.io/chaitin/ingress-nginx-controller:v1.10.1` in your deployment.
+
+### Step 2: Configure the plugin
+
+use a ConfigMap to configure the plugin
+
+```yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: safeline
+  namespace: default
+data:
+  host: "YOUR_DETECTOR_HOST"
+  port: "YOUR_DETECTOR_PORT"
+```
+
+### Step 3: Configure the ingress-controller
+
+inject env `SAFELINE_HOST` and `SAFELINE_PORT` to the ingress-controller deployment
+
+```yaml
+...
+env:
+  - name: SAFELINE_HOST
+    valueFrom:
+      configMapKeyRef:
+        name: safeline
+        key: host
+  - name: SAFELINE_PORT
+    valueFrom:
+      configMapKeyRef:
+        name: safeline
+        key: port
+...            
+
+```
+
+### Step 3: Enable the plugin
+
+enable safeline plugin in configmap
+
+```yaml
+apiVersion: v1
+data:
+  allow-snippet-annotations: "false"
+  plugins: "safeline"
+kind: ConfigMap
+metadata:
+  name: ingress-nginx-controller
+  namespace: default
+```
+
+### Step 4: Set externalTrafficPolicy to Local
+by default, the ingress-nginx-controller service is of type LoadBalancer, which means the source IP of the request will be the IP of the LoadBalancer. If you want to get the real source IP, you can set the externalTrafficPolicy to Local.
+
+### Step 5: Test the plugin
+
+use a simple http sql injection test
+
+```bash
+curl -X POST http://localhost/ -d "select * from users where id=1 or 1=1" 
+```
+
+you should get a 403 response.
+```bash
+{"code": 403, "success":false, "message": "blocked by Chaitin SafeLine Web Application Firewall", "event_id": "b53eb5b95796475699c52a019abb8e6a"}
+```

+ 20 - 0
sdk/ingress-nginx/ingress-nginx-safeline-1.0.2-1.rockspec

@@ -0,0 +1,20 @@
+package = "ingress-nginx-safeline"
+version = "1.0.2-1"
+source = {
+   url = "git://github.com/xbingW/ingress-nginx-safeline.git"
+}
+description = {
+   summary = "Ingress-Nginx plugin for Chaitin SafeLine Web Application Firewall",
+   homepage = "https://github.com/xbingW/ingress-nginx-safeline",
+   license = "Apache License 2.0",
+   maintainer = "Xiaobing Wang <xiaobing.wang@chaitin.com>"
+}
+dependencies = {
+   "lua-resty-t1k"
+}
+build = {
+   type = "builtin",
+   modules = {
+       ["safeline.main"] = "lib/safeline/main.lua"
+   }
+}

+ 61 - 0
sdk/ingress-nginx/lib/safeline/main.lua

@@ -0,0 +1,61 @@
+local t1k = require "resty.t1k"
+local t1k_constants = require "resty.t1k.constants"
+
+local ngx = ngx
+local fmt = string.format
+
+local blocked_message = [[{"code": %s, "success":false, ]] ..
+        [["message": "blocked by Chaitin SafeLine Web Application Firewall", "event_id": "%s"}]]
+
+local _M = {}
+
+local mode = os.getenv("SAFELINE_MODE")
+local host = os.getenv("SAFELINE_HOST")
+local port = os.getenv("SAFELINE_PORT")
+local connect_timeout = os.getenv("SAFELINE_CONNECT_TIMEOUT")
+local send_timeout = os.getenv("SAFELINE_SEND_TIMEOUT")
+local read_timeout = os.getenv("SAFELINE_READ_TIMEOUT")
+local req_body_size = os.getenv("SAFELINE_REQ_BODY_SIZE")
+local keepalive_size = os.getenv("SAFELINE_KEEPALIVE_SIZE")
+local keepalive_timeout = os.getenv("SAFELINE_KEEPALIVE_TIMEOUT")
+local remote_addr = os.getenv("SAFELINE_REMOTE_ADDR")
+
+local function get_conf()
+    local t = {
+        mode = mode or "block",
+        host = host,
+        port = port,
+        connect_timeout = connect_timeout or 1000,
+        send_timeout = send_timeout or 1000,
+        read_timeout = read_timeout or 1000,
+        req_body_size = req_body_size or 1024,
+        keepalive_size = keepalive_size or 256,
+        keepalive_timeout = keepalive_timeout or 60000,
+        remote_addr = remote_addr
+    }
+    return t
+end
+
+function _M.rewrite()
+    local t = get_conf()
+    if not t.host then
+        ngx.log(ngx.ERR, "safeline host is required")
+        return
+    end
+    local ok, err, result = t1k.do_access(t, false)
+    if not ok then
+        ngx.log(ngx.ERR, "failed to detector req: ", err)
+        return
+    end
+    if result then
+        if result.action == t1k_constants.ACTION_BLOCKED then
+            local msg = fmt(blocked_message, result.status, result.event_id)
+            ngx.log(ngx.ERR, "blocked by safeline waf: ",msg)
+            ngx.status = tonumber(result.status,10)
+            ngx.say(msg)
+            return ngx.exit(ngx.HTTP_OK)
+        end
+    end
+end
+
+return _M

+ 34 - 0
sdk/kong/Readme.md

@@ -0,0 +1,34 @@
+# Kong Safeline Plugin
+Kong plugin for Chaitin SafeLine Web Application Firewall (WAF). This plugin is used to protect your API from malicious requests. It can be used to block requests that contain malicious content in the request body, query parameters, headers, or URI.
+
+## Installation
+To install the plugin, run the following command in your Kong server:
+
+```shell
+$ luarocks install kong-safeline
+```
+
+## Configuration
+You can add the plugin to your API by making the following request:
+
+```shell
+# if your detector is running on tcp port
+$ curl -X POST http://kong:8001/services/{name}/plugins \
+    --data "name=safeline" \
+    --data "config.host=your_detector_host" \
+    --data "config.port=your_detector_port"
+# if your detector is running on unix socket
+$ curl -X POST http://kong:8001/services/{name}/plugins \
+    --data "name=safeline" \
+    --data "config.host=unix:/path/to/your/unix/socket"
+```
+
+## Test
+You can test the plugin by sending a request to your API with malicious content. If the request is blocked, you will receive a `403 Forbidden` response.
+
+```shell
+$ curl -X POST http://kong:8000?1=1%20and%202=2
+
+# you will receive a 403 Forbidden response
+{"code": 403, "success":false, "message": "blocked by Chaitin SafeLine Web Application Firewall", "event_id": "8b41a021ea9541c89bb88f3773b4da24"}
+```

+ 21 - 0
sdk/kong/kong-safeline-1.0.0-1.rockspec

@@ -0,0 +1,21 @@
+package = "kong-safeline"
+version = "1.0.0-1"
+source = {
+   url = "git://github.com/xbingW/kong-safeline.git"
+}
+description = {
+   summary = "Kong plugin for Chaitin SafeLine Web Application Firewall",
+   homepage = "https://github.com/xbingW/kong-safeline",
+   license = "Apache License 2.0",
+   maintainer = "Xiaobing Wang <xiaobing.wang@chaitin.com>"
+}
+dependencies = {
+   "lua-resty-t1k"
+}
+build = {
+   type = "builtin",
+   modules = {
+      ["kong.plugins.safeline.handler"] = "kong/plugins/safeline/handler.lua",
+      ["kong.plugins.safeline.schema"] = "kong/plugins/safeline/schema.lua"
+   }
+}

+ 21 - 0
sdk/kong/kong-safeline-1.0.1-1.rockspec

@@ -0,0 +1,21 @@
+package = "kong-safeline"
+version = "1.0.1-1"
+source = {
+   url = "git://github.com/xbingW/kong-safeline.git"
+}
+description = {
+   summary = "Kong plugin for Chaitin SafeLine Web Application Firewall",
+   homepage = "https://github.com/xbingW/kong-safeline",
+   license = "Apache License 2.0",
+   maintainer = "Xiaobing Wang <xiaobing.wang@chaitin.com>"
+}
+dependencies = {
+   "lua-resty-t1k"
+}
+build = {
+   type = "builtin",
+   modules = {
+      ["kong.plugins.safeline.handler"] = "kong/plugins/safeline/handler.lua",
+      ["kong.plugins.safeline.schema"] = "kong/plugins/safeline/schema.lua"
+   }
+}

+ 47 - 0
sdk/kong/kong/plugins/safeline/handler.lua

@@ -0,0 +1,47 @@
+local t1k = require "resty.t1k"
+local t1k_constants = require "resty.t1k.constants"
+
+local fmt = string.format
+
+local SafelineHandler = {
+    VERSION = "0.0.1",
+    PRIORITY = 1000
+}
+
+local blocked_message = [[{"code": %s, "success":false, ]] ..
+                            [["message": "blocked by Chaitin SafeLine Web Application Firewall", "event_id": "%s"}]]
+
+local function get_conf(conf)
+    local t = {
+        mode = conf.mode,
+        host = conf.host,
+        port = conf.port,
+        connect_timeout = conf.connect_timeout,
+        send_timeout = conf.send_timeout,
+        read_timeout = conf.read_timeout,
+        req_body_size = conf.req_body_size,
+        keepalive_size = conf.keepalive_size,
+        keepalive_timeout = conf.keepalive_timeout,
+        remote_addr = conf.remote_addr
+    }
+    return t
+end
+
+function SafelineHandler:access(conf)
+    -- your custom code here
+    local t = get_conf(conf)
+    local ok, err, result = t1k.do_access(t, false)
+    if not ok then
+        kong.log.err("failed to detector req: ", err)
+    end
+    if result then
+        if result.action == t1k_constants.ACTION_BLOCKED then
+            local msg = fmt(blocked_message, result.status, result.event_id)
+            kong.log.debug("blocked by safeline: ",msg)
+            return kong.response.exit(result.status, msg)
+        end
+    end
+end
+
+
+return SafelineHandler

+ 73 - 0
sdk/kong/kong/plugins/safeline/schema.lua

@@ -0,0 +1,73 @@
+local typedefs = require "kong.db.schema.typedefs"
+
+return {
+    name = "kong-safeline",
+    fields = {{
+        consumer = typedefs.no_consumer
+    }, {
+        protocols = typedefs.protocols_http
+    }, {
+        config = {
+            type = "record",
+            fields = {{
+                host = {
+                    type = "string",
+                    required = true
+                }
+            }, {
+                port = {
+                    type = "number",
+                    required = false
+                }
+            }, {
+                mode = {
+                    type = "string",
+                    required = false,
+                    default = "block",
+                    one_of = {"monitor", "block", "off"}
+                }
+            }, {
+                connect_timeout = {
+                    type = "number",
+                    required = false,
+                    default = 1000
+                }
+            }, {
+                send_timeout = {
+                    type = "number",
+                    required = false,
+                    default = 1000
+                }
+            }, {
+                read_timeout = {
+                    type = "number",
+                    required = false,
+                    default = 1000
+                }
+            }, {
+                req_body_size = {
+                    type = "number",
+                    required = false,
+                    default = 1000
+                }
+            }, {
+                keepalive_size = {
+                    type = "number",
+                    required = false,
+                    default = 1000
+                }
+            }, {
+                keepalive_timeout = {
+                    type = "number",
+                    required = false,
+                    default = 1000
+                }
+            }, {
+                remote_addr = {
+                    type = "string",
+                    required = false
+                }
+            }}
+        }
+    }}
+}

+ 9 - 0
sdk/traefik/.traefik.yml

@@ -0,0 +1,9 @@
+displayName: Chaitin Safeline WAF
+type: middleware
+
+import: github.com/xbingW/traefik-safeline
+
+summary: 'Traefik plugin to proxy requests to safeline waf.t serves as a reverse proxy access to protect your website from network attacks that including OWASP attacks, zero-day attacks, web crawlers, vulnerability scanning, vulnerability exploit, http flood and so on.'
+
+testData:
+  addr: safeline-detector:8000

+ 62 - 0
sdk/traefik/README.md

@@ -0,0 +1,62 @@
+# Traefik Plugin Safeline
+
+This plugin is a middleware for Traefik that can be used to detect and block malicious requests which base on the [Safeline](https://waf.chaitin.com/) engine.
+
+## Usage
+
+For a plugin to be active for a given Traefik instance, it must be declared in the static configuration.
+
+Plugins are parsed and loaded exclusively during startup, which allows Traefik to check the integrity of the code and catch errors early on.
+If an error occurs during loading, the plugin is disabled.
+
+For security reasons, it is not possible to start a new plugin or modify an existing one while Traefik is running.
+
+Once loaded, middleware plugins behave exactly like statically compiled middlewares.
+Their instantiation and behavior are driven by the dynamic configuration.
+
+Plugin dependencies must be [vendored](https://golang.org/ref/mod#vendoring) for each plugin.
+Vendored packages should be included in the plugin's GitHub repository. ([Go modules](https://blog.golang.org/using-go-modules) are not supported.)
+
+### Configuration
+
+For each plugin, the Traefik static configuration must define the module name (as is usual for Go packages).
+
+The following declaration (given here in YAML) defines a plugin:
+
+```yaml
+# Static configuration
+
+experimental:
+  plugins:
+    safeline:
+      moduleName: github.com/xbingW/traefik-safeline
+      version: v1.0.0
+```
+
+Here is an example of a file provider dynamic configuration (given here in YAML), where the interesting part is the `http.middlewares` section:
+
+```yaml
+# Dynamic configuration
+
+http:
+  routers:
+    my-router:
+      rule: host(`demo.localhost`)
+      service: service-foo
+      entryPoints:
+        - web
+      middlewares:
+        - chaitin
+
+  services:
+   service-foo:
+      loadBalancer:
+        servers:
+          - url: http://127.0.0.1:5000
+  
+  middlewares:
+    chaitin:
+      plugin:
+        safeline:
+          addr: safeline-detector.safeline:8000
+```

+ 5 - 0
sdk/traefik/go.mod

@@ -0,0 +1,5 @@
+module github.com/xbingW/traefik-safeline
+
+go 1.17
+
+require github.com/xbingW/t1k v1.2.1

+ 75 - 0
sdk/traefik/safeline.go

@@ -0,0 +1,75 @@
+package traefik_safeline
+
+import (
+	"context"
+	"encoding/json"
+	"log"
+	"net/http"
+	"os"
+
+	"github.com/xbingW/t1k"
+)
+
+// Package example a example plugin.
+
+// Config the plugin configuration.
+type Config struct {
+	// Addr is the address for the detector
+	Addr string `yaml:"addr"`
+	// Get ip from header, if not set, get ip from remote addr
+	IpHeader string `yaml:"ipHeader"`
+	// When ip_header has multiple ip, use this to get the ip
+	//
+	//for example, X-Forwarded-For: ip1, ip2, ip3
+	// 	when ip_last_index is 0, the client ip is ip3
+	// 	when ip_last_index is 1, the client ip is ip2
+	// 	when ip_last_index is 2, the client ip is ip1
+	IPRightIndex uint `yaml:"ipRightIndex"`
+}
+
+// CreateConfig creates the default plugin configuration.
+func CreateConfig() *Config {
+	return &Config{
+		Addr:         "",
+		IpHeader:     "",
+		IPRightIndex: 0,
+	}
+}
+
+// Safeline a plugin.
+type Safeline struct {
+	next   http.Handler
+	name   string
+	config *Config
+	logger *log.Logger
+}
+
+// New created a new plugin.
+func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
+	return &Safeline{
+		next:   next,
+		name:   name,
+		config: config,
+		logger: log.New(os.Stdout, "safeline", log.LstdFlags),
+	}, nil
+}
+
+func (s *Safeline) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+	d := t1k.NewDetector(t1k.Config{
+		Addr:         s.config.Addr,
+		IpHeader:     s.config.IpHeader,
+		IPRightIndex: s.config.IPRightIndex,
+	})
+	resp, err := d.DetectorRequest(req)
+	if err != nil {
+		s.logger.Printf("Failed to detect request: %v", err)
+	}
+	if resp != nil && !resp.Allowed() {
+		rw.WriteHeader(resp.StatusCode())
+		if err := json.NewEncoder(rw).Encode(resp.BlockMessage()); err != nil {
+			s.logger.Printf("Failed to encode block message: %v", err)
+		}
+		return
+	}
+	s.next.ServeHTTP(rw, req)
+}

+ 4 - 0
sdk/traefik/vendor/github.com/xbingW/t1k/README.md

@@ -0,0 +1,4 @@
+# t1k-go
+
+## Getting started
+

+ 173 - 0
sdk/traefik/vendor/github.com/xbingW/t1k/detector.go

@@ -0,0 +1,173 @@
+package t1k
+
+import (
+	"bufio"
+	"fmt"
+	"net"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/xbingW/t1k/pkg/datetime"
+	"github.com/xbingW/t1k/pkg/rand"
+	"github.com/xbingW/t1k/pkg/t1k"
+)
+
+type Detector struct {
+	cfg Config
+}
+
+type Config struct {
+	Addr string `json:"addr"`
+	// Get ip from header, if not set, get ip from remote addr
+	IpHeader string `json:"ip_header"`
+	// When ip_header has multiple ip, use this to get ip from right
+	//
+	//for example, X-Forwarded-For: ip1, ip2, ip3
+	// 	when ip_last_index is 0, the client ip is ip3
+	// 	when ip_last_index is 1, the client ip is ip2
+	// 	when ip_last_index is 2, the client ip is ip1
+	IPRightIndex uint `json:"ip_right_index"`
+}
+
+func NewDetector(cfg Config) *Detector {
+	return &Detector{
+		cfg: cfg,
+	}
+}
+
+func (d *Detector) GetConn() (net.Conn, error) {
+	_, _, err := net.SplitHostPort(d.cfg.Addr)
+	if err != nil {
+		return nil, fmt.Errorf("detector add %s is invalid because %v", d.cfg.Addr, err)
+	}
+	return net.Dial("tcp", d.cfg.Addr)
+}
+
+func (d *Detector) DetectorRequestStr(req string) (*t1k.DetectorResponse, error) {
+	httpReq, err := http.ReadRequest(bufio.NewReader(strings.NewReader(req)))
+	if err != nil {
+		return nil, fmt.Errorf("read request failed: %v", err)
+	}
+	return d.DetectorRequest(httpReq)
+}
+
+func (d *Detector) DetectorRequest(req *http.Request) (*t1k.DetectorResponse, error) {
+	extra, err := d.GenerateExtra(req)
+	if err != nil {
+		return nil, fmt.Errorf("generate extra failed: %v", err)
+	}
+	dc := t1k.NewHttpDetector(req, extra)
+	conn, err := d.GetConn()
+	if err != nil {
+		return nil, fmt.Errorf("get conn failed: %v", err)
+	}
+	defer conn.Close()
+	return dc.DetectRequest(conn)
+}
+
+func (d *Detector) DetectorResponseStr(req string, resp string) (*t1k.DetectorResponse, error) {
+	httpReq, err := http.ReadRequest(bufio.NewReader(strings.NewReader(req)))
+	if err != nil {
+		return nil, fmt.Errorf("read request failed: %v", err)
+	}
+	httpResp, err := http.ReadResponse(bufio.NewReader(strings.NewReader(resp)), httpReq)
+	if err != nil {
+		return nil, fmt.Errorf("read response failed: %v", err)
+	}
+	extra, err := d.GenerateExtra(httpReq)
+	if err != nil {
+		return nil, fmt.Errorf("generate extra failed: %v", err)
+	}
+	conn, err := d.GetConn()
+	if err != nil {
+		return nil, fmt.Errorf("get conn failed: %v", err)
+	}
+	defer conn.Close()
+	return t1k.NewHttpDetector(httpReq, extra).SetResponse(httpResp).DetectResponse(conn)
+}
+
+func (d *Detector) DetectorResponse(req *http.Request, resp *http.Response) (*t1k.DetectorResponse, error) {
+	extra, err := d.GenerateExtra(req)
+	if err != nil {
+		return nil, fmt.Errorf("generate extra failed: %v", err)
+	}
+	conn, err := d.GetConn()
+	if err != nil {
+		return nil, fmt.Errorf("get conn failed: %v", err)
+	}
+	defer conn.Close()
+	return t1k.NewHttpDetector(req, extra).SetResponse(resp).DetectResponse(conn)
+}
+
+func (d *Detector) GenerateExtra(req *http.Request) (*t1k.HttpExtra, error) {
+	clientHost, err := d.getClientIP(req)
+	if err != nil {
+		return nil, err
+	}
+	serverHost, serverPort := req.Host, "80"
+	if hasPort(req.Host) {
+		serverHost, serverPort, err = net.SplitHostPort(req.Host)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return &t1k.HttpExtra{
+		UpstreamAddr:  "",
+		RemoteAddr:    clientHost,
+		RemotePort:    d.getClientPort(req),
+		LocalAddr:     serverHost,
+		LocalPort:     serverPort,
+		ServerName:    "",
+		Schema:        req.URL.Scheme,
+		ProxyName:     "",
+		UUID:          rand.String(32),
+		HasRspIfOK:    "y",
+		HasRspIfBlock: "n",
+		ReqBeginTime:  strconv.FormatInt(datetime.Now(), 10),
+		ReqEndTime:    "",
+		RspBeginTime:  strconv.FormatInt(datetime.Now(), 10),
+		RepEndTime:    "",
+	}, nil
+}
+
+func (d *Detector) getClientIP(req *http.Request) (string, error) {
+	if d.cfg.IpHeader != "" {
+		ips := req.Header.Get(d.cfg.IpHeader)
+		if ips != "" {
+			ipList := reverseStrSlice(strings.Split(ips, ","))
+			if len(ipList) > int(d.cfg.IPRightIndex) {
+				return strings.TrimSpace(ipList[d.cfg.IPRightIndex]), nil
+			}
+			return ipList[0], nil
+		}
+	}
+	if !hasPort(req.RemoteAddr) {
+		return req.RemoteAddr, nil
+	}
+	clientHost, _, err := net.SplitHostPort(req.RemoteAddr)
+	if err != nil {
+		return "", err
+	}
+	return clientHost, nil
+}
+
+func (d *Detector) getClientPort(req *http.Request) string {
+	_, clientPort, err := net.SplitHostPort(req.RemoteAddr)
+	if err != nil {
+		return ""
+	}
+	return clientPort
+}
+
+// has port check if host has port
+func hasPort(host string) bool {
+	return strings.LastIndex(host, ":") > strings.LastIndex(host, "]")
+}
+
+func reverseStrSlice(arr []string) []string {
+	if len(arr) == 0 {
+		return arr
+	}
+	return append(reverseStrSlice(arr[1:]), arr[0])
+}

+ 7 - 0
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/datetime/datetime.go

@@ -0,0 +1,7 @@
+package datetime
+
+import "time"
+
+func Now() int64 {
+	return time.Now().UnixNano() / 1e3
+}

+ 15 - 0
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/rand/str.go

@@ -0,0 +1,15 @@
+package rand
+
+import (
+	"math/rand"
+)
+
+const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789"
+
+func String(n int) string {
+	b := make([]byte, n)
+	for i := range b {
+		b[i] = letterBytes[rand.Intn(len(letterBytes))]
+	}
+	return string(b)
+}

+ 163 - 0
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/detector.go

@@ -0,0 +1,163 @@
+package t1k
+
+import (
+	"errors"
+	"io"
+	"log"
+	"net/http"
+	"regexp"
+	"strconv"
+)
+
+type ResultFlag string
+
+const (
+	ResultFlagAllowed ResultFlag = "."
+	ResultFlagBlocked ResultFlag = "?"
+)
+
+func (d ResultFlag) Byte() byte {
+	return d[0]
+}
+
+type DetectorResponse struct {
+	Head        byte
+	Body        []byte
+	Delay       []byte
+	ExtraHeader []byte
+	ExtraBody   []byte
+	Context     []byte
+	Cookie      []byte
+	WebLog      []byte
+	BotQuery    []byte
+	BotBody     []byte
+	Forward     []byte
+}
+
+func (r *DetectorResponse) Allowed() bool {
+	return r.Head == ResultFlagAllowed.Byte()
+}
+
+func (r *DetectorResponse) StatusCode() int {
+	str := string(r.Body)
+	if str == "" {
+		return http.StatusForbidden
+	}
+	code, err := strconv.Atoi(str)
+	if err != nil {
+		log.Printf("t1k convert status code failed: %v", err)
+		return http.StatusForbidden
+	}
+	return code
+}
+
+func (r *DetectorResponse) BlockMessage() map[string]interface{} {
+	return map[string]interface{}{
+		"status":   r.StatusCode(),
+		"success":  false,
+		"message":  "blocked by Chaitin SafeLine Web Application Firewall",
+		"event_id": r.EventID(),
+	}
+}
+
+func (r *DetectorResponse) EventID() string {
+	extra := string(r.ExtraBody)
+	if extra == "" {
+		return ""
+	}
+	// <!-- event_id: e1impksyjq0gl92le6odi0fnobi270cj -->
+	re, err := regexp.Compile(`<\!--\s*event_id:\s*([a-zA-Z0-9]+)\s*-->\s*`)
+	if err != nil {
+		log.Printf("t1k compile regexp failed: %v", err)
+		return ""
+	}
+	matches := re.FindStringSubmatch(extra)
+	if len(matches) < 2 {
+		log.Printf("t1k regexp not match event id: %s", extra)
+		return ""
+	}
+	return matches[1]
+}
+
+type HttpDetector struct {
+	extra *HttpExtra
+	req   Request
+	resp  Response
+}
+
+func NewHttpDetector(req *http.Request, extra *HttpExtra) *HttpDetector {
+	return &HttpDetector{
+		req:   NewHttpRequest(req, extra),
+		extra: extra,
+	}
+}
+
+func (d *HttpDetector) SetResponse(resp *http.Response) *HttpDetector {
+	d.resp = NewHttpResponse(d.req, resp, d.extra)
+	return d
+}
+
+func (d *HttpDetector) DetectRequest(socket io.ReadWriter) (*DetectorResponse, error) {
+	raw, err := d.req.Serialize()
+	if err != nil {
+		return nil, err
+	}
+	_, err = socket.Write(raw)
+	if err != nil {
+		return nil, err
+	}
+	return d.ReadResponse(socket)
+}
+
+func (d *HttpDetector) DetectResponse(socket io.ReadWriter) (*DetectorResponse, error) {
+	raw, err := d.resp.Serialize()
+	if err != nil {
+		return nil, err
+	}
+	_, err = socket.Write(raw)
+	if err != nil {
+		return nil, err
+	}
+	return d.ReadResponse(socket)
+}
+
+func (d *HttpDetector) ReadResponse(r io.Reader) (*DetectorResponse, error) {
+	res := &DetectorResponse{}
+	for {
+		p, err := ReadPacket(r)
+		if err != nil {
+			return nil, err
+		}
+		switch p.Tag().Strip() {
+		case TAG_HEADER:
+			if len(p.PayLoad()) != 1 {
+				return nil, errors.New("len(T1K_HEADER) != 1")
+			}
+			res.Head = p.PayLoad()[0]
+		case TAG_DELAY:
+			res.Delay = p.PayLoad()
+		case TAG_BODY:
+			res.Body = p.PayLoad()
+		case TAG_EXTRA_HEADER:
+			res.ExtraHeader = p.PayLoad()
+		case TAG_EXTRA_BODY:
+			res.ExtraBody = p.PayLoad()
+		case TAG_CONTEXT:
+			res.Context = p.PayLoad()
+		case TAG_COOKIE:
+			res.Cookie = p.PayLoad()
+		case TAG_WEB_LOG:
+			res.WebLog = p.PayLoad()
+		case TAG_BOT_QUERY:
+			res.BotQuery = p.PayLoad()
+		case TAG_BOT_BODY:
+			res.BotBody = p.PayLoad()
+		case TAG_FORWARD:
+			res.Forward = p.PayLoad()
+		}
+		if p.Last() {
+			break
+		}
+	}
+	return res, nil
+}

+ 75 - 0
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/extra.go

@@ -0,0 +1,75 @@
+package t1k
+
+import "fmt"
+
+type HttpExtra struct {
+	UpstreamAddr  string
+	RemoteAddr    string
+	RemotePort    string
+	LocalAddr     string
+	LocalPort     string
+	ServerName    string
+	Schema        string
+	ProxyName     string
+	UUID          string
+	HasRspIfOK    string
+	HasRspIfBlock string
+	ReqBeginTime  string
+	ReqEndTime    string
+	RspBeginTime  string
+	RepEndTime    string
+}
+
+func (h *HttpExtra) ReqSerialize() ([]byte, error) {
+	format := "UpstreamAddr:%s\n" +
+		"RemotePort:%s\n" +
+		"LocalPort:%s\n" +
+		"RemoteAddr:%s\n" +
+		"LocalAddr:%s\n" +
+		"ServerName:%s\n" +
+		"Schema:%s\n" +
+		"ProxyName:%s\n" +
+		"UUID:%s\n" +
+		"HasRspIfOK:%s\n" +
+		"HasRspIfBlock:%s\n" +
+		"ReqBeginTime:%s\n" +
+		"ReqEndTime:%s\n"
+	return []byte(fmt.Sprintf(
+		format,
+		h.UpstreamAddr,
+		h.RemotePort,
+		h.LocalPort,
+		h.RemoteAddr,
+		h.LocalAddr,
+		h.ServerName,
+		h.Schema,
+		h.ProxyName,
+		h.UUID,
+		h.HasRspIfOK,
+		h.HasRspIfBlock,
+		h.ReqBeginTime,
+		h.ReqEndTime,
+	)), nil
+}
+
+func (h *HttpExtra) RspSerialize() ([]byte, error) {
+	format := "Scheme:%s\n" +
+		"ProxyName:%s\n" +
+		"RemoteAddr:%s\n" +
+		"RemotePort:%s\n" +
+		"LocalAddr:%s\n" +
+		"LocalPort:%s\n" +
+		"UUID:%s\n" +
+		"RspBeginTime:%s\n"
+	return []byte(fmt.Sprintf(
+		format,
+		h.Schema,
+		h.ProxyName,
+		h.RemoteAddr,
+		h.RemotePort,
+		h.LocalAddr,
+		h.LocalPort,
+		h.UUID,
+		h.RspBeginTime,
+	)), nil
+}

+ 69 - 0
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/packet.go

@@ -0,0 +1,69 @@
+package t1k
+
+import (
+	"bytes"
+	"encoding/binary"
+	"io"
+)
+
+type Packet interface {
+	Serialize() []byte
+	Last() bool
+	Tag() Tag
+	PayLoad() []byte
+}
+
+type HttpPacket struct {
+	tag     Tag
+	payload []byte
+}
+
+func (p *HttpPacket) Last() bool {
+	return p.tag.Last()
+}
+
+func (p *HttpPacket) Tag() Tag {
+	return p.tag
+}
+
+func (p *HttpPacket) PayLoad() []byte {
+	return p.payload
+}
+
+func NewHttpPacket(tag Tag, payload []byte) Packet {
+	return &HttpPacket{
+		tag:     tag,
+		payload: payload,
+	}
+}
+
+func (p *HttpPacket) SizeBytes() []byte {
+	sizeBytes := make([]byte, 4)
+	binary.LittleEndian.PutUint32(sizeBytes, uint32(len(p.payload)))
+	return sizeBytes
+}
+
+func (p *HttpPacket) Serialize() []byte {
+	var buf bytes.Buffer
+	buf.WriteByte(byte(p.tag))
+	buf.Write(p.SizeBytes())
+	buf.Write(p.payload)
+	return buf.Bytes()
+}
+
+func ReadPacket(r io.Reader) (Packet, error) {
+	tag := make([]byte, 1)
+	if _, err := io.ReadFull(r, tag); err != nil {
+		return nil, err
+	}
+	sizeBytes := make([]byte, 4)
+	if _, err := io.ReadFull(r, sizeBytes); err != nil {
+		return nil, err
+	}
+	size := binary.LittleEndian.Uint32(sizeBytes)
+	payload := make([]byte, size)
+	if _, err := io.ReadFull(r, payload); err != nil {
+		return nil, err
+	}
+	return NewHttpPacket(Tag(tag[0]), payload), nil
+}

+ 122 - 0
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/req.go

@@ -0,0 +1,122 @@
+package t1k
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+)
+
+type Request interface {
+	Header() ([]byte, error)
+	Body() ([]byte, error)
+	Extra() ([]byte, error)
+	Serialize() ([]byte, error)
+}
+
+type HttpRequest struct {
+	extra *HttpExtra
+	req   *http.Request
+}
+
+func NewHttpRequest(req *http.Request, extra *HttpExtra) *HttpRequest {
+	return &HttpRequest{
+		req:   req,
+		extra: extra,
+	}
+}
+
+func NewHttpRequestRead(req string) *HttpRequest {
+	httpReq, _ := http.ReadRequest(bufio.NewReader(strings.NewReader(req)))
+	return &HttpRequest{
+		req: httpReq,
+	}
+}
+
+func (r *HttpRequest) Header() ([]byte, error) {
+	var buf bytes.Buffer
+	proto := r.req.Proto
+	startLine := fmt.Sprintf("%s %s %s\r\n", r.req.Method, r.req.URL.RequestURI(), proto)
+	_, err := buf.Write([]byte(startLine))
+	if err != nil {
+		return nil, err
+	}
+	_, err = buf.Write([]byte(fmt.Sprintf("Host: %s\r\n", r.req.Host)))
+	if err != nil {
+		return nil, err
+	}
+	err = r.req.Header.Write(&buf)
+	if err != nil {
+		return nil, err
+	}
+	_, err = buf.Write([]byte("\r\n"))
+	if err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
+func (r *HttpRequest) Body() ([]byte, error) {
+	var buf bytes.Buffer
+	_, err := buf.ReadFrom(r.req.Body)
+	if err != nil {
+		return nil, err
+	}
+	r.req.Body = io.NopCloser(bytes.NewReader(buf.Bytes()))
+	return buf.Bytes(), nil
+}
+
+func (r *HttpRequest) Extra() ([]byte, error) {
+	return r.extra.ReqSerialize()
+}
+
+func (r *HttpRequest) Version() []byte {
+	return []byte("Proto:3\n")
+}
+
+func (r *HttpRequest) Serialize() ([]byte, error) {
+	var buf bytes.Buffer
+	{
+		raw, err := r.Header()
+		if err != nil {
+			return nil, err
+		}
+		packet := NewHttpPacket(TAG_HEADER|MASK_FIRST, raw)
+		_, err = buf.Write(packet.Serialize())
+		if err != nil {
+			return nil, err
+		}
+	}
+	{
+		raw, err := r.Body()
+		if err != nil {
+			return nil, err
+		}
+		packet := NewHttpPacket(TAG_BODY, raw)
+		_, err = buf.Write(packet.Serialize())
+		if err != nil {
+			return nil, err
+		}
+	}
+	{
+		raw, err := r.Extra()
+		if err != nil {
+			return nil, err
+		}
+		packet := NewHttpPacket(TAG_EXTRA, raw)
+		_, err = buf.Write(packet.Serialize())
+		if err != nil {
+			return nil, err
+		}
+	}
+	{
+		packet := NewHttpPacket(TAG_VERSION|MASK_LAST, r.Version())
+		_, err := buf.Write(packet.Serialize())
+		if err != nil {
+			return nil, err
+		}
+	}
+	return buf.Bytes(), nil
+}

+ 127 - 0
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/res.go

@@ -0,0 +1,127 @@
+package t1k
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"net/http"
+)
+
+type Response interface {
+	RequestHeader() ([]byte, error)
+	RspHeader() ([]byte, error)
+	Body() ([]byte, error)
+	Extra() ([]byte, error)
+	Serialize() ([]byte, error)
+}
+
+type HttpResponse struct {
+	req   Request
+	extra *HttpExtra
+	rsp   *http.Response
+}
+
+func NewHttpResponse(req Request, rsp *http.Response, extra *HttpExtra) *HttpResponse {
+	return &HttpResponse{
+		req:   req,
+		rsp:   rsp,
+		extra: extra,
+	}
+}
+
+func (r *HttpResponse) RequestHeader() ([]byte, error) {
+	return r.req.Header()
+}
+
+func (r *HttpResponse) RspHeader() ([]byte, error) {
+	var buf bytes.Buffer
+	statusLine := fmt.Sprintf("HTTP/1.1 %s\n", r.rsp.Status)
+	_, err := buf.Write([]byte(statusLine))
+	if err != nil {
+		return nil, err
+	}
+	err = r.rsp.Header.Write(&buf)
+	if err != nil {
+		return nil, err
+	}
+	_, err = buf.Write([]byte("\r\n"))
+	if err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
+func (r *HttpResponse) Body() ([]byte, error) {
+	data, err := io.ReadAll(r.rsp.Body)
+	if err != nil {
+		return nil, err
+	}
+	r.rsp.Body = io.NopCloser(bytes.NewReader(data))
+	return data, nil
+}
+
+func (r *HttpResponse) Extra() ([]byte, error) {
+	return r.extra.RspSerialize()
+}
+
+func (r *HttpResponse) Version() []byte {
+	return []byte("Proto:3\n")
+
+}
+
+func (r *HttpResponse) Serialize() ([]byte, error) {
+	var buf bytes.Buffer
+	{
+		header, err := r.RequestHeader()
+		if err != nil {
+			return nil, err
+		}
+		packet := NewHttpPacket(TAG_HEADER|MASK_FIRST, header)
+		_, err = buf.Write(packet.Serialize())
+		if err != nil {
+			return nil, err
+		}
+	}
+	{
+		header, err := r.RspHeader()
+		if err != nil {
+			return nil, err
+		}
+		packet := NewHttpPacket(TAG_RSP_HEADER, header)
+		_, err = buf.Write(packet.Serialize())
+		if err != nil {
+			return nil, err
+		}
+	}
+	{
+		body, err := r.Body()
+		if err != nil {
+			return nil, err
+		}
+		packet := NewHttpPacket(TAG_RSP_BODY, body)
+		_, err = buf.Write(packet.Serialize())
+		if err != nil {
+			return nil, err
+		}
+	}
+	{
+		extra, err := r.Extra()
+		if err != nil {
+			return nil, err
+		}
+		packet := NewHttpPacket(TAG_RSP_EXTRA, extra)
+		_, err = buf.Write(packet.Serialize())
+		if err != nil {
+			return nil, err
+		}
+	}
+	{
+		version := r.Version()
+		packet := NewHttpPacket(TAG_VERSION|MASK_LAST, version)
+		_, err := buf.Write(packet.Serialize())
+		if err != nil {
+			return nil, err
+		}
+	}
+	return buf.Bytes(), nil
+}

+ 45 - 0
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/tag.go

@@ -0,0 +1,45 @@
+package t1k
+
+type Tag byte
+
+const (
+	TAG_HEADER       Tag = 0x01
+	TAG_BODY         Tag = 0x02
+	TAG_EXTRA        Tag = 0x03
+	TAG_RSP_HEADER   Tag = 0x11
+	TAG_RSP_BODY     Tag = 0x12
+	TAG_RSP_EXTRA    Tag = 0x13
+	TAG_VERSION      Tag = 0x20
+	TAG_ALOG         Tag = 0x21
+	TAG_STAT         Tag = 0x22
+	TAG_EXTRA_HEADER Tag = 0x23
+	TAG_EXTRA_BODY   Tag = 0x24
+	TAG_CONTEXT      Tag = 0x25
+	TAG_COOKIE       Tag = 0x26
+	TAG_WEB_LOG      Tag = 0x27
+	TAG_USER_DATA    Tag = 0x28
+	TAG_BOT_QUERY    Tag = 0x29
+	TAG_DELAY        Tag = 0x2b
+	TAG_FORWARD      Tag = 0x2c
+	TAG_BOT_BODY     Tag = 0x30
+
+	MASK_TAG   Tag = 0x3f
+	MASK_FIRST Tag = 0x40
+	MASK_LAST  Tag = 0x80
+)
+
+func (t Tag) Last() bool {
+	return t&MASK_LAST != 0
+}
+
+func (t Tag) First() bool {
+	return t&MASK_FIRST != 0
+}
+
+func (t Tag) Strip() Tag {
+	return t & MASK_TAG
+}
+
+func (t Tag) Byte() byte {
+	return byte(t)
+}

+ 6 - 0
sdk/traefik/vendor/modules.txt

@@ -0,0 +1,6 @@
+# github.com/xbingW/t1k v1.2.1
+## explicit; go 1.17
+github.com/xbingW/t1k
+github.com/xbingW/t1k/pkg/datetime
+github.com/xbingW/t1k/pkg/rand
+github.com/xbingW/t1k/pkg/t1k