mirror of
https://github.com/chaitin/SafeLine.git
synced 2025-11-26 03:45:08 +08:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d871e638b | ||
|
|
ad2aeb4cf9 | ||
|
|
8247d11dda | ||
|
|
63588b8f5e | ||
|
|
bf8d64c6b2 | ||
|
|
0b8b253efc | ||
|
|
24e2bac1bd | ||
|
|
8113dbb171 | ||
|
|
66c4e60255 | ||
|
|
eeae48affb | ||
|
|
75a5346e6f | ||
|
|
b6f694a376 | ||
|
|
9cab30cef1 | ||
|
|
8fd59844ea | ||
|
|
681e7f95f2 | ||
|
|
84c2db9ee3 | ||
|
|
c845231a1d | ||
|
|
91a26b8f93 | ||
|
|
24770cf5b4 | ||
|
|
ff4d6616fb | ||
|
|
a13d269c28 | ||
|
|
19db896a9a | ||
|
|
01c38c26d0 | ||
|
|
14fb45f648 | ||
|
|
f371d588a4 | ||
|
|
1317b3ed5a | ||
|
|
7fdf63310d | ||
|
|
1578ea6027 | ||
|
|
ec3dc96765 | ||
|
|
48e828cc03 | ||
|
|
78e71aae23 | ||
|
|
857e0c46ef | ||
|
|
8cee76ecbe | ||
|
|
6abcb6b69f | ||
|
|
075bd524a9 | ||
|
|
5a96c2d4d0 | ||
|
|
8de97ff483 | ||
|
|
b7449480b3 | ||
|
|
38f00302bf | ||
|
|
850fd440ff | ||
|
|
b243e2b51d | ||
|
|
34fc8fa8e5 | ||
|
|
6ceeae4d2b | ||
|
|
abd0427273 | ||
|
|
82f0c5d52e | ||
|
|
c7fa1efe5d | ||
|
|
be0571a67f | ||
|
|
a79d932801 | ||
|
|
8157ef050e | ||
|
|
04d1891891 | ||
|
|
1d7eeeba36 | ||
|
|
52f6e857df | ||
|
|
255fd4173d | ||
|
|
1a80a5ac07 | ||
|
|
6324dea166 | ||
|
|
695c438ec3 | ||
|
|
bb0b1187a0 | ||
|
|
ba84cc5380 | ||
|
|
e7f6a66083 | ||
|
|
9086cf52d4 | ||
|
|
b853673100 | ||
|
|
381f2cba28 | ||
|
|
37d37728ca | ||
|
|
a1f151eed6 | ||
|
|
0e88a09fcf | ||
|
|
644943fac1 | ||
|
|
41888dcff7 | ||
|
|
c0dfa51925 | ||
|
|
8d66f96228 | ||
|
|
b7a2e86c5e | ||
|
|
078ef2a3ce | ||
|
|
49deca7ce5 | ||
|
|
53a955b439 | ||
|
|
d4f31efdea | ||
|
|
c3f064fa4c | ||
|
|
233ac9547c | ||
|
|
5d73a333d0 | ||
|
|
e443cd47c0 | ||
|
|
2f61ebaf85 | ||
|
|
9fe9ff3ef1 | ||
|
|
a75123c86d | ||
|
|
599a6903a9 | ||
|
|
bf87488eff | ||
|
|
c2c3fe32fb | ||
|
|
a18cc16b33 | ||
|
|
3efdd3fff5 | ||
|
|
9d156c65ba | ||
|
|
8f575dc232 | ||
|
|
e44e88c136 | ||
|
|
3f8187ae6c | ||
|
|
de7307fbda | ||
|
|
2685813f4d | ||
|
|
78912d064e | ||
|
|
e1e801cec8 | ||
|
|
71b14ddcb0 | ||
|
|
f851033a7a | ||
|
|
82a2e4473a | ||
|
|
c6f5467000 | ||
|
|
2c7f0f94c5 | ||
|
|
e2672c93e2 |
41
.github/workflows/slmcp-docker.yml
vendored
Normal file
41
.github/workflows/slmcp-docker.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: MCP Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "v*"
|
||||
paths:
|
||||
- "mcp_server/**"
|
||||
- ".github/workflows/slmcp-docker.yml"
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERIO_USERNAME }}
|
||||
password: ${{ secrets.DOCKERIO_PASSWORD }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./mcp_server
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
chaitin/safeline-mcp:latest
|
||||
chaitin/safeline-mcp:${{ github.ref_name }}
|
||||
cache-from: type=registry,ref=chaitin/safeline-mcp:buildcache
|
||||
cache-to: type=registry,ref=chaitin/safeline-mcp:buildcache,mode=max
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,3 +5,6 @@
|
||||
*.tar.gz
|
||||
build.sh
|
||||
compose.yml
|
||||
__pycache__
|
||||
.cursor
|
||||
.vscode
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -3,3 +3,6 @@
|
||||
url = https://github.com/chaitin/blazehttp
|
||||
|
||||
|
||||
[submodule "sdk/traefik-safeline"]
|
||||
path = sdk/traefik-safeline
|
||||
url = https://github.com/chaitin/traefik-safeline
|
||||
|
||||
18
README.md
18
README.md
@@ -7,9 +7,9 @@
|
||||
</h4>
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://waf.chaitin.com/">🏠 Website</a> |
|
||||
<a target="_blank" href="https://docs.waf.chaitin.com/">📖 Docs</a> |
|
||||
<a target="_blank" href="https://demo.waf.chaitin.com:9443/">🔍 Live Demo</a> |
|
||||
<a target="_blank" href="https://ly.safepoint.cloud/laA8asp">🏠 Website</a> |
|
||||
<a target="_blank" href="https://ly.safepoint.cloud/w2AeHhb">📖 Docs</a> |
|
||||
<a target="_blank" href="https://ly.safepoint.cloud/hSMd4SH">🔍 Live Demo</a> |
|
||||
<a target="_blank" href="https://discord.gg/SVnZGzHFvn">🙋♂️ Discord</a> |
|
||||
<a target="_blank" href="/README_CN.md">中文版</a>
|
||||
</p>
|
||||
@@ -26,7 +26,7 @@ A web application firewall helps protect web apps by filtering and monitoring HT
|
||||
|
||||
By deploying a WAF in front of a web application, a shield is placed between the web application and the Internet. While a proxy server protects a client machine’s identity by using an intermediary, a WAF is a type of reverse-proxy, protecting the server from exposure by having clients pass through the WAF before reaching the server.
|
||||
|
||||
A WAF protects your web apps by filtering, monitoring, and blocking any malicious HTTP/S traffic traveling to the web application, and prevents any unauthorized data from leaving the app. It does this by adhering to a set of policies that help determine what traffic is malicious and what traffic is safe. Just as a proxy server acts as an intermediary to protect the identity of a client, a WAF operates in similar fashion but acting as an reverse proxy intermediary that protects the web app server from a potentially malicious client.
|
||||
A WAF protects your web apps by filtering, monitoring, and blocking any malicious HTTP/S traffic traveling to the web application, and prevents any unauthorized data from leaving the app. It does this by adhering to a set of policies that help determine what traffic is malicious and what traffic is safe. Just as a proxy server acts as an intermediary to protect the identity of a client, a WAF operates in similar fashion but acting as a reverse proxy intermediary that protects the web app server from a potentially malicious client.
|
||||
|
||||
its core capabilities include:
|
||||
|
||||
@@ -52,8 +52,8 @@ List of the main features as follows:
|
||||
- It defenses for all of web attacks, such as `SQL injection`, `XSS`, `code injection`, `os command injection`, `CRLF injection`, `XXE`, `SSRF`, `path traversal` and so on.
|
||||
- **`Rate Limiting`**
|
||||
- Defend your web apps against `DoS attacks`, `bruteforce attempts`, `traffic surges`, and other types of abuse by throttling traffic that exceeds defined limits.
|
||||
- **`Captcha Challenge`**
|
||||
- CAPTCHA challenges to protect your website from `bot attacks`, humen users will be allowed, crawlers and bots will be blocked.
|
||||
- **`Anti-Bot Challenge`**
|
||||
- Anti-Bot challenges to protect your website from `bot attacks`, humen users will be allowed, crawlers and bots will be blocked.
|
||||
- **`Authentication Challenge`**
|
||||
- When authentication challenge turned on, visitors need to enter the password, otherwise they will be blocked.
|
||||
- **`Dynamic Protection`**
|
||||
@@ -65,7 +65,7 @@ List of the main features as follows:
|
||||
| ----------------------------- | --------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| **`Block Web Attacks`** | <img src="./images/skeleton.png" width=270 /> | <img src="./images/blocked-for-attack-detected.png" width=270 /> |
|
||||
| **`Rate Limiting`** | <img src="./images/skeleton.png" width=270 /> | <img src="./images/blocked-for-access-too-fast.png" width=270 /> |
|
||||
| **`Captcha Challenge`** | <img src="./images/captcha-1.gif" width=270 /> | <img src="./images/captcha-2.gif" width=270 /> |
|
||||
| **`Anti-Bot Challenge`** | <img src="./images/captcha-1.gif" width=270 /> | <img src="./images/captcha-2.gif" width=270 /> |
|
||||
| **`Auth Challenge`** | <img src="./images/auth-1.gif" width=270 /> | <img src="./images/auth-2.gif" width=270 /> |
|
||||
| **`HTML Dynamic Protection`** | <img src="./images/dynamic-html-1.png" width=270 /> | <img src="./images/dynamic-html-2.png" width=270 /> |
|
||||
| **`JS Dynamic Protection`** | <img src="./images/dynamic-js-1.png" width=270 /> | <img src="./images/dynamic-js-2.png" width=270 /> |
|
||||
@@ -77,11 +77,11 @@ List of the main features as follows:
|
||||
|
||||
#### 📦 Installing
|
||||
|
||||
Information on how to install SafeLine can be found in the [Install Guide](https://docs.waf.chaitin.com/en/tutorials/install)
|
||||
Information on how to install SafeLine can be found in the [Install Guide](https://docs.waf.chaitin.com/en/GetStarted/Deploy)
|
||||
|
||||
#### ⚙️ Protecting Web Apps
|
||||
|
||||
to see [Configuration](https://docs.waf.chaitin.com/en/tutorials/Configuration)
|
||||
to see [Configuration](https://docs.waf.chaitin.com/en/GetStarted/AddApplication)
|
||||
|
||||
## 📋 More Informations
|
||||
|
||||
|
||||
70
compose.yaml
70
compose.yaml
@@ -14,7 +14,7 @@ services:
|
||||
postgres:
|
||||
container_name: safeline-pg
|
||||
restart: always
|
||||
image: ${IMAGE_PREFIX}/safeline-postgres:15.2
|
||||
image: ${IMAGE_PREFIX}/safeline-postgres${ARCH_SUFFIX}:15.2
|
||||
volumes:
|
||||
- ${SAFELINE_DIR}/resources/postgres/data:/var/lib/postgresql/data
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
@@ -30,11 +30,13 @@ services:
|
||||
mgt:
|
||||
container_name: safeline-mgt
|
||||
restart: always
|
||||
image: ${IMAGE_PREFIX}/safeline-mgt-g:${IMAGE_TAG:?image tag required}
|
||||
image: ${IMAGE_PREFIX}/safeline-mgt${REGION}${ARCH_SUFFIX}${RELEASE}:${IMAGE_TAG:?image tag required}
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ${SAFELINE_DIR}/resources/mgt:/app/data
|
||||
- ${SAFELINE_DIR}/logs/nginx:/app/log/nginx:z
|
||||
- ${SAFELINE_DIR}/resources/sock:/app/sock
|
||||
- /var/run:/app/run
|
||||
ports:
|
||||
- ${MGT_PORT:-9443}:1443
|
||||
healthcheck:
|
||||
@@ -45,6 +47,7 @@ services:
|
||||
- postgres
|
||||
- fvm
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "100m"
|
||||
max-file: "5"
|
||||
@@ -54,7 +57,7 @@ services:
|
||||
detect:
|
||||
container_name: safeline-detector
|
||||
restart: always
|
||||
image: ${IMAGE_PREFIX}/safeline-detector-g:${IMAGE_TAG}
|
||||
image: ${IMAGE_PREFIX}/safeline-detector${REGION}${ARCH_SUFFIX}${RELEASE}:${IMAGE_TAG}
|
||||
volumes:
|
||||
- ${SAFELINE_DIR}/resources/detector:/resources/detector
|
||||
- ${SAFELINE_DIR}/logs/detector:/logs/detector
|
||||
@@ -64,48 +67,32 @@ services:
|
||||
networks:
|
||||
safeline-ce:
|
||||
ipv4_address: ${SUBNET_PREFIX}.5
|
||||
mario:
|
||||
container_name: safeline-mario
|
||||
restart: always
|
||||
image: ${IMAGE_PREFIX}/safeline-mario-g:${IMAGE_TAG}
|
||||
volumes:
|
||||
- ${SAFELINE_DIR}/resources/mario:/resources/mario
|
||||
- ${SAFELINE_DIR}/logs/mario:/logs/mario
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
environment:
|
||||
- LOG_DIR=/logs/mario
|
||||
- GOGC=100
|
||||
- DATABASE_URL=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-pg/safeline-ce
|
||||
logging:
|
||||
options:
|
||||
max-size: "100m"
|
||||
max-file: "5"
|
||||
networks:
|
||||
safeline-ce:
|
||||
ipv4_address: ${SUBNET_PREFIX}.6
|
||||
tengine:
|
||||
container_name: safeline-tengine
|
||||
restart: always
|
||||
image: ${IMAGE_PREFIX}/safeline-tengine-g:${IMAGE_TAG}
|
||||
image: ${IMAGE_PREFIX}/safeline-tengine${REGION}${ARCH_SUFFIX}${RELEASE}:${IMAGE_TAG}
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /etc/resolv.conf:/etc/resolv.conf:ro
|
||||
- ${SAFELINE_DIR}/resources/nginx:/etc/nginx
|
||||
- ${SAFELINE_DIR}/resources/detector:/resources/detector
|
||||
- ${SAFELINE_DIR}/resources/chaos:/resources/chaos
|
||||
- ${SAFELINE_DIR}/logs/nginx:/var/log/nginx:z
|
||||
- ${SAFELINE_DIR}/resources/cache:/usr/local/nginx/cache
|
||||
- ${SAFELINE_DIR}/resources/sock:/app/sock
|
||||
environment:
|
||||
- TCD_MGT_API=https://${SUBNET_PREFIX}.4:1443/api/open/publish/server
|
||||
- TCD_SNSERVER=${SUBNET_PREFIX}.5:8000
|
||||
# deprecated
|
||||
- SNSERVER_ADDR=${SUBNET_PREFIX}.5:8000
|
||||
- CHAOS_ADDR=${SUBNET_PREFIX}.10
|
||||
ulimits:
|
||||
nofile: 131072
|
||||
network_mode: host
|
||||
luigi:
|
||||
container_name: safeline-luigi
|
||||
restart: always
|
||||
image: ${IMAGE_PREFIX}/safeline-luigi-g:${IMAGE_TAG}
|
||||
image: ${IMAGE_PREFIX}/safeline-luigi${REGION}${ARCH_SUFFIX}${RELEASE}:${IMAGE_TAG}
|
||||
environment:
|
||||
- MGT_IP=${SUBNET_PREFIX}.4
|
||||
- LUIGI_PG=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-pg/safeline-ce?sslmode=disable
|
||||
@@ -113,6 +100,7 @@ services:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ${SAFELINE_DIR}/resources/luigi:/app/data
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "100m"
|
||||
max-file: "5"
|
||||
@@ -125,49 +113,31 @@ services:
|
||||
fvm:
|
||||
container_name: safeline-fvm
|
||||
restart: always
|
||||
image: ${IMAGE_PREFIX}/safeline-fvm-g:${IMAGE_TAG}
|
||||
image: ${IMAGE_PREFIX}/safeline-fvm${REGION}${ARCH_SUFFIX}${RELEASE}:${IMAGE_TAG}
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "100m"
|
||||
max-file: "5"
|
||||
networks:
|
||||
safeline-ce:
|
||||
ipv4_address: ${SUBNET_PREFIX}.8
|
||||
bridge:
|
||||
container_name: safeline-bridge
|
||||
restart: always
|
||||
image: ${IMAGE_PREFIX}/safeline-bridge-g:${IMAGE_TAG}
|
||||
command:
|
||||
- /app/bridge
|
||||
- serve
|
||||
- -n
|
||||
- unix
|
||||
- -a
|
||||
- /app/run/safeline.sock
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /var/run:/app/run
|
||||
logging:
|
||||
options:
|
||||
max-size: "100m"
|
||||
max-file: "5"
|
||||
networks:
|
||||
safeline-ce:
|
||||
ipv4_address: ${SUBNET_PREFIX}.9
|
||||
depends_on:
|
||||
- mgt
|
||||
chaos:
|
||||
container_name: safeline-chaos
|
||||
restart: always
|
||||
image: ${IMAGE_PREFIX}/safeline-chaos-g:${IMAGE_TAG}
|
||||
image: ${IMAGE_PREFIX}/safeline-chaos${REGION}${ARCH_SUFFIX}${RELEASE}:${IMAGE_TAG}
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "100m"
|
||||
max-file: "10"
|
||||
environment:
|
||||
- DB_ADDR=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-pg/safeline-ce?sslmode=disable
|
||||
volumes:
|
||||
- ${SAFELINE_DIR}/resources/sock:/app/sock
|
||||
- ${SAFELINE_DIR}/resources/chaos:/app/chaos
|
||||
networks:
|
||||
safeline-ce:
|
||||
ipv4_address: ${SUBNET_PREFIX}.10
|
||||
ipv4_address: ${SUBNET_PREFIX}.10
|
||||
@@ -14,7 +14,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
)
|
||||
|
||||
28
mcp_server/Dockerfile
Normal file
28
mcp_server/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 go build -o mcp-server .
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
|
||||
COPY --from=builder /app/mcp-server .
|
||||
COPY --from=builder /app/config.yaml .
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
EXPOSE 5678
|
||||
|
||||
CMD ["./mcp-server"]
|
||||
256
mcp_server/README.md
Normal file
256
mcp_server/README.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# SafeLine MCP Server
|
||||
|
||||
SafeLine MCP Server is an implementation of the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) that provides complete management and control capabilities for SafeLine WAF.
|
||||
|
||||
[](docker-compose.yml)
|
||||
[](go.mod)
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Automated management and control of SafeLine WAF instances
|
||||
- WAF configuration and policy management through API
|
||||
- Building AI-based security protection tools and applications
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Install [Docker](https://www.docker.com/) (if running in container)
|
||||
2. Configure SafeLine API Token (obtained from SafeLine console)
|
||||
|
||||
## Features
|
||||
|
||||
- Complete MCP (Management Control Protocol) server implementation
|
||||
- Support for SafeLine WAF instance management and control
|
||||
- Flexible configuration system supporting file configuration and environment variables
|
||||
- Docker containerization support
|
||||
- Secure API communication
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Environment Variable | Description | Default Value | Required |
|
||||
|---------|------|--------|-----|
|
||||
| LISTEN_PORT | Service listening port | 5678 | No |
|
||||
| LISTEN_ADDRESS | Service listening address | 0.0.0.0 | No |
|
||||
| SAFELINE_SECRET | SSE server secret | - | No |
|
||||
| SAFELINE_ADDRESS | SafeLine API address | - | Yes |
|
||||
| SAFELINE_API_TOKEN | SafeLine API authentication token | - | Yes |
|
||||
|
||||
### Using Docker
|
||||
|
||||
#### Method 1: Using docker run
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name safeline-mcp \
|
||||
-p 5678:5678 \
|
||||
-e SAFELINE_API_TOKEN="your_api_token" \
|
||||
-e SAFELINE_ADDRESS="https://your.safeline.com" \
|
||||
-e LISTEN_PORT=5678 \
|
||||
-e LISTEN_ADDRESS="0.0.0.0" \
|
||||
chaitin/safeline-mcp:latest
|
||||
```
|
||||
|
||||
#### Method 2: Using docker-compose
|
||||
|
||||
```bash
|
||||
# 1. Clone repository
|
||||
git clone https://github.com/chaitin/safeline-mcp.git
|
||||
cd safeline-mcp
|
||||
|
||||
# 2. Edit docker-compose.yml to configure environment variables
|
||||
# Example docker-compose.yml:
|
||||
# version: '3'
|
||||
# services:
|
||||
# mcp:
|
||||
# image: chaitin/safeline-mcp:latest
|
||||
# container_name: safeline-mcp
|
||||
# ports:
|
||||
# - "5678:5678"
|
||||
# environment:
|
||||
# - SAFELINE_API_TOKEN=your_api_token
|
||||
# - SAFELINE_ADDRESS=https://your.safeline.com
|
||||
# - LISTEN_PORT=5678
|
||||
# - LISTEN_ADDRESS=0.0.0.0
|
||||
|
||||
# 3. Start service
|
||||
docker compose -f docker-compose.yml up -d
|
||||
```
|
||||
|
||||
#### Method 3: Using Go
|
||||
|
||||
```bash
|
||||
# 1. Clone repository
|
||||
git clone https://github.com/chaitin/SafeLine.git
|
||||
cd safeline-mcp
|
||||
|
||||
# 2. Install dependencies
|
||||
go mod download
|
||||
|
||||
# 3. Configure config.yaml
|
||||
cp config.yaml.example config.yaml
|
||||
# Edit config.yaml with necessary configurations
|
||||
|
||||
# 4. Run service
|
||||
go run main.go
|
||||
```
|
||||
|
||||
For more API details, please refer to the [API Documentation](https://demo.waf.chaitin.com:9443/swagger/index.html).
|
||||
|
||||
## Tools
|
||||
|
||||
### Application Management
|
||||
|
||||
- **create_application**
|
||||
|
||||
### Rule Management
|
||||
- **create_blacklist_rule**
|
||||
- **create_whitelist_rule**
|
||||
|
||||
### Analyze
|
||||
- **get_attack_events**
|
||||
|
||||
## Development Guide
|
||||
|
||||
The Go API in this project is currently under development, and APIs may change. If you have specific requirements, please submit an Issue for discussion.
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
internal/
|
||||
├── api/ # API implementation
|
||||
│ ├── app/ # Application-related APIs
|
||||
│ │ └── create_application.go
|
||||
│ └── rule/ # Rule-related APIs
|
||||
│ └── create_rule.go
|
||||
└── tools/ # MCP tool implementation
|
||||
├── app/ # Application-related tools
|
||||
│ └── create_application.go
|
||||
└── rule/ # Rule-related tools
|
||||
└── create_rule.go
|
||||
```
|
||||
|
||||
### Adding New Tools
|
||||
|
||||
1. **Create Tool File**
|
||||
- Create corresponding directory and file under `internal/tools`
|
||||
- File name should match tool name
|
||||
- Use separate file for each tool
|
||||
- Example: `internal/tools/app/create_application.go`
|
||||
|
||||
2. **Tool Implementation Template**
|
||||
```go
|
||||
package app
|
||||
|
||||
type ToolName struct{}
|
||||
|
||||
type ToolParams struct {
|
||||
// Parameter definitions
|
||||
Param1 string `json:"param1" desc:"parameter description" required:"true"`
|
||||
Param2 int `json:"param2" desc:"parameter description" required:"false"`
|
||||
}
|
||||
|
||||
type ToolResult struct {
|
||||
Field1 string `json:"field1"`
|
||||
}
|
||||
|
||||
func (t *ToolName) Name() string {
|
||||
return "tool_name"
|
||||
}
|
||||
|
||||
func (t *ToolName) Description() string {
|
||||
return "tool description"
|
||||
}
|
||||
|
||||
func (t *ToolName) Validate(params ToolParams) error {
|
||||
// Parameter validation logic
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ToolName) Execute(ctx context.Context, params ToolParams) (result ToolResult, err error) {
|
||||
// Tool execution logic
|
||||
return result, nil
|
||||
}
|
||||
```
|
||||
|
||||
3. **[Optional]Create API Implementation**
|
||||
|
||||
If you need to use some APIs that have not been implemented yet, you need to create corresponding files in the api directory for implementation
|
||||
- Create same directory structure under `internal/api`
|
||||
- File name should match tool func
|
||||
- Example: `internal/api/app/create_application.go`
|
||||
|
||||
**API Implementation Template**
|
||||
```go
|
||||
package app
|
||||
|
||||
type RequestType struct {
|
||||
// Request parameter definitions
|
||||
Param1 string `json:"param1"`
|
||||
Param2 int `json:"param2"`
|
||||
}
|
||||
|
||||
func APIName(ctx context.Context, req *RequestType) (ResultType, error) {
|
||||
if req == nil {
|
||||
return nil, errors.New("request is required")
|
||||
}
|
||||
|
||||
var resp api.Response[ResultType]
|
||||
err := api.Service().Post(ctx, "/api/path", req, &resp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to execute")
|
||||
}
|
||||
|
||||
if resp.Err != nil {
|
||||
return nil, errors.New(resp.Msg)
|
||||
}
|
||||
|
||||
return resp.Data, nil
|
||||
}
|
||||
```
|
||||
4. **Tool Registration (init.go)**
|
||||
|
||||
The tool registration file `internal/tools/init.go` is used to centrally manage all tool registrations
|
||||
- Register all tools uniformly in the `init()` function
|
||||
- Use the `AppendTool()` method for registration
|
||||
- Example:
|
||||
```go
|
||||
// Register create application tool
|
||||
AppendTool(&app.CreateApp{})
|
||||
|
||||
// Register create blacklist rule tool
|
||||
AppendTool(&rule.CreateBlacklistRule{})
|
||||
```
|
||||
|
||||
### Development Standards
|
||||
|
||||
1. **Naming Conventions**
|
||||
- Use lowercase letters and underscores for tool names
|
||||
- File names should match tool names
|
||||
|
||||
2. **Directory Organization**
|
||||
- Divide directories by functional modules (e.g., app, rule, etc.)
|
||||
- Maintain consistent structure between tools and api directories
|
||||
- Keep related functionality in the same directory
|
||||
|
||||
3. **Code Standards**
|
||||
- Follow Go standard code conventions
|
||||
- Add necessary parameter validation
|
||||
- Use unified error handling approach
|
||||
- Add appropriate logging
|
||||
|
||||
4. **Documentation Requirements**
|
||||
- Provide clear functional description in tool Description
|
||||
- Add detailed description for parameters
|
||||
- Update API toolkit documentation in README
|
||||
|
||||
### Example
|
||||
|
||||
Refer to the implementation of the `create_application` tool:
|
||||
- Tool implementation: `internal/tools/app/create_application.go`
|
||||
- API implementation: `internal/api/app/create_application.go`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
27
mcp_server/config.yaml
Normal file
27
mcp_server/config.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# Server Configuration
|
||||
server:
|
||||
name: "SafeLine MCP Server"
|
||||
version: "1.0.0"
|
||||
# Can be overridden by environment variable LISTEN_PORT
|
||||
port: 5678
|
||||
# Can be overridden by environment variable LISTEN_ADDRESS
|
||||
host: "0.0.0.0"
|
||||
# Can be overridden by environment variable SAFELINE_SECRET
|
||||
secret: "" # Secret for SSE server
|
||||
# Logger Configuration
|
||||
logger:
|
||||
level: "info" # Log level: debug, info, warn, error
|
||||
file_path: "" # Log file path
|
||||
console: true # Whether to output to console
|
||||
caller: false # Whether to record caller information
|
||||
development: true # Whether to use development mode
|
||||
|
||||
# API Configuration
|
||||
api:
|
||||
# Can be overridden by environment variable SAFELINE_ADDRESS
|
||||
base_url: "" # API service address
|
||||
# Can be overridden by environment variable SAFELINE_API_TOKEN
|
||||
token: "" # Authentication token
|
||||
timeout: 30 # Timeout in seconds
|
||||
debug: false # Whether to enable debug mode
|
||||
insecure_skip_verify: true # Whether to skip certificate verification
|
||||
15
mcp_server/docker-compose.yml
Normal file
15
mcp_server/docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
mcp_server:
|
||||
image: chaitin/safeline-mcp:latest
|
||||
container_name: mcp_server
|
||||
restart: always
|
||||
ports:
|
||||
- "5678:5678"
|
||||
environment:
|
||||
- SAFELINE_SECRET=your_secret_key # optional, if you want to use secret key to authenticate
|
||||
- SAFELINE_ADDRESS=https://your_safeline_ip:9443 # required, your SafeLine WAF address
|
||||
- SAFELINE_API_TOKEN=your_safeline_api_token # required, your SafeLine WAF api token
|
||||
- LISTEN_PORT=5678 # optional, default is 5678
|
||||
- LISTEN_ADDRESS=0.0.0.0 # optional, default is 0.0.0.0
|
||||
16
mcp_server/go.mod
Normal file
16
mcp_server/go.mod
Normal file
@@ -0,0 +1,16 @@
|
||||
module github.com/chaitin/SafeLine/mcp_server
|
||||
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/mark3labs/mcp-go v0.18.0
|
||||
go.uber.org/zap v1.27.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/mcuadros/go-defaults v1.2.0
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
)
|
||||
28
mcp_server/go.sum
Normal file
28
mcp_server/go.sum
Normal file
@@ -0,0 +1,28 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mark3labs/mcp-go v0.18.0 h1:YuhgIVjNlTG2ZOwmrkORWyPTp0dz1opPEqvsPtySXao=
|
||||
github.com/mark3labs/mcp-go v0.18.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
|
||||
github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc=
|
||||
github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
47
mcp_server/internal/api/analyze/get_event_list.go
Normal file
47
mcp_server/internal/api/analyze/get_event_list.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package analyze
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/api"
|
||||
)
|
||||
|
||||
type GetEventListRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
IP string `json:"ip"`
|
||||
Start int64 `json:"start"`
|
||||
End int64 `json:"end"`
|
||||
}
|
||||
|
||||
type GetEventListResponse struct {
|
||||
Nodes []Event `json:"nodes"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
ID uint `json:"id"`
|
||||
IP string `json:"ip"`
|
||||
Protocol int `json:"protocol"`
|
||||
Host string `json:"host"`
|
||||
DstPort uint64 `json:"dst_port"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
StartAt int64 `json:"start_at"`
|
||||
EndAt int64 `json:"end_at"`
|
||||
DenyCount int64 `json:"deny_count"`
|
||||
PassCount int64 `json:"pass_count"`
|
||||
Finished bool `json:"finished"`
|
||||
Country string `json:"country"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
}
|
||||
|
||||
func GetEventList(ctx context.Context, req *GetEventListRequest) (*GetEventListResponse, error) {
|
||||
var resp api.Response[GetEventListResponse]
|
||||
err := api.Service().Get(ctx, fmt.Sprintf("/api/open/events?page=%d&page_size=%d&ip=%s&start=%d&end=%d", req.Page, req.PageSize, req.IP, req.Start, req.End), &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp.Data, nil
|
||||
}
|
||||
34
mcp_server/internal/api/app/create_application.go
Normal file
34
mcp_server/internal/api/app/create_application.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/api"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/errors"
|
||||
)
|
||||
|
||||
type CreateAppRequest struct {
|
||||
ServerNames []string `json:"server_names"`
|
||||
Ports []string `json:"ports"`
|
||||
Upstreams []string `json:"upstreams"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
// CreateApp Create new website or app
|
||||
func CreateApp(ctx context.Context, req *CreateAppRequest) (int64, error) {
|
||||
if req == nil {
|
||||
return 0, errors.New("request is required")
|
||||
}
|
||||
|
||||
var resp api.Response[int64]
|
||||
err := api.Service().Post(ctx, "/api/open/site", req, &resp)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to create app")
|
||||
}
|
||||
|
||||
if resp.Err != nil {
|
||||
return 0, errors.New(resp.Msg)
|
||||
}
|
||||
|
||||
return resp.Data, nil
|
||||
}
|
||||
157
mcp_server/internal/api/client.go
Normal file
157
mcp_server/internal/api/client.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/errors"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
|
||||
)
|
||||
|
||||
// Client API client
|
||||
type Client struct {
|
||||
baseURL string
|
||||
httpClient *http.Client
|
||||
headers map[string]string
|
||||
}
|
||||
|
||||
// ClientOption Client configuration options
|
||||
type ClientOption func(*Client)
|
||||
|
||||
// WithTimeout Set timeout duration
|
||||
func WithTimeout(timeout time.Duration) ClientOption {
|
||||
return func(c *Client) {
|
||||
c.httpClient.Timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// WithHeader Set request header
|
||||
func WithHeader(key, value string) ClientOption {
|
||||
return func(c *Client) {
|
||||
c.headers[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// WithBaseURL Set base URL
|
||||
func WithBaseURL(baseURL string) ClientOption {
|
||||
return func(c *Client) {
|
||||
c.baseURL = baseURL
|
||||
}
|
||||
}
|
||||
|
||||
// WithInsecureSkipVerify Set whether to skip certificate verification
|
||||
func WithInsecureSkipVerify(skip bool) ClientOption {
|
||||
return func(c *Client) {
|
||||
if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
|
||||
if transport.TLSClientConfig == nil {
|
||||
transport.TLSClientConfig = &tls.Config{}
|
||||
}
|
||||
transport.TLSClientConfig.InsecureSkipVerify = skip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient Create new API client
|
||||
func NewClient(opts ...ClientOption) *Client {
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{},
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
httpClient: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
Transport: transport,
|
||||
},
|
||||
headers: make(map[string]string),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Request Send request
|
||||
func (c *Client) Request(ctx context.Context, method, path string, body interface{}, result interface{}) error {
|
||||
reqURL := fmt.Sprintf("%s%s", c.baseURL, path)
|
||||
|
||||
var bodyReader io.Reader
|
||||
if body != nil {
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal request body failed")
|
||||
}
|
||||
bodyReader = bytes.NewReader(bodyBytes)
|
||||
}
|
||||
logger.With("url", reqURL).Debug("request url")
|
||||
req, err := http.NewRequestWithContext(ctx, method, reqURL, bodyReader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create request failed")
|
||||
}
|
||||
|
||||
// Set common headers
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
for k, v := range c.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "send request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read response body
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read response body failed")
|
||||
}
|
||||
|
||||
// Check status code
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return errors.New(fmt.Sprintf("request failed with status %d: %s", resp.StatusCode, string(respBody)))
|
||||
}
|
||||
|
||||
// Parse response
|
||||
if result != nil {
|
||||
if err := json.Unmarshal(respBody, result); err == nil {
|
||||
return nil
|
||||
}
|
||||
var respData map[string]interface{}
|
||||
if err := json.Unmarshal(respBody, &respData); err != nil {
|
||||
return errors.Wrap(err, "unmarshal response failed")
|
||||
}
|
||||
if respData["err"] != nil || respData["msg"] != nil {
|
||||
return errors.New(respData["msg"].(string))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get Send GET request
|
||||
func (c *Client) Get(ctx context.Context, path string, result interface{}) error {
|
||||
return c.Request(ctx, http.MethodGet, path, nil, result)
|
||||
}
|
||||
|
||||
// Post Send POST request
|
||||
func (c *Client) Post(ctx context.Context, path string, body interface{}, result interface{}) error {
|
||||
return c.Request(ctx, http.MethodPost, path, body, result)
|
||||
}
|
||||
|
||||
// Put Send PUT request
|
||||
func (c *Client) Put(ctx context.Context, path string, body interface{}, result interface{}) error {
|
||||
return c.Request(ctx, http.MethodPut, path, body, result)
|
||||
}
|
||||
|
||||
// Delete Send DELETE request
|
||||
func (c *Client) Delete(ctx context.Context, path string, result interface{}) error {
|
||||
return c.Request(ctx, http.MethodDelete, path, nil, result)
|
||||
}
|
||||
11
mcp_server/internal/api/response.go
Normal file
11
mcp_server/internal/api/response.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package api
|
||||
|
||||
// Response Common API response structure
|
||||
type Response[T any] struct {
|
||||
// Response data
|
||||
Data T `json:"data"`
|
||||
// Error message
|
||||
Err any `json:"err"`
|
||||
// Prompt message
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
35
mcp_server/internal/api/rule/create_rule.go
Normal file
35
mcp_server/internal/api/rule/create_rule.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/api"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/errors"
|
||||
)
|
||||
|
||||
type CreateRuleRequest struct {
|
||||
Name string `json:"name"`
|
||||
IP []string `json:"ip"`
|
||||
IsEnabled bool `json:"is_enabled"`
|
||||
Pattern [][]api.Pattern `json:"pattern"`
|
||||
Action int `json:"action"`
|
||||
}
|
||||
|
||||
// CreateRule Create new rule
|
||||
func CreateRule(ctx context.Context, req *CreateRuleRequest) (int64, error) {
|
||||
if req == nil {
|
||||
return 0, errors.New("request is required")
|
||||
}
|
||||
|
||||
var resp api.Response[int64]
|
||||
err := api.Service().Post(ctx, "/api/open/policy", req, &resp)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to create policy rule")
|
||||
}
|
||||
|
||||
if resp.Err != nil {
|
||||
return 0, errors.New(resp.Msg)
|
||||
}
|
||||
|
||||
return resp.Data, nil
|
||||
}
|
||||
100
mcp_server/internal/api/service.go
Normal file
100
mcp_server/internal/api/service.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/config"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/errors"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
|
||||
)
|
||||
|
||||
// APIClient API client implementation
|
||||
type APIClient struct {
|
||||
client *Client
|
||||
config *config.APIConfig
|
||||
}
|
||||
|
||||
var (
|
||||
instance *APIClient
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// Init Initialize API service
|
||||
func Init(cfg *config.APIConfig) error {
|
||||
var err error
|
||||
once.Do(func() {
|
||||
instance, err = newAPIClient(cfg)
|
||||
if err != nil {
|
||||
logger.With("error", err).Error("failed to initialize API service")
|
||||
return
|
||||
}
|
||||
logger.Info("API service initialized successfully")
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Service Get API service instance
|
||||
func Service() *APIClient {
|
||||
if instance == nil {
|
||||
logger.Error("API service not initialized")
|
||||
panic("API service not initialized")
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
// newAPIClient Create new API client
|
||||
func newAPIClient(config *config.APIConfig) (*APIClient, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is required")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" {
|
||||
return nil, errors.New("base_url is required")
|
||||
}
|
||||
|
||||
timeout := 30
|
||||
if config.Timeout > 0 {
|
||||
timeout = config.Timeout
|
||||
}
|
||||
|
||||
opts := []ClientOption{
|
||||
WithBaseURL(config.BaseURL),
|
||||
WithTimeout(time.Duration(timeout) * time.Second),
|
||||
WithHeader("User-Agent", "SafeLine-MCP/1.0"),
|
||||
WithInsecureSkipVerify(config.InsecureSkipVerify),
|
||||
}
|
||||
|
||||
// If token is configured, add authentication header
|
||||
if config.Token != "" {
|
||||
opts = append(opts, WithHeader("X-SLCE-API-TOKEN", config.Token))
|
||||
}
|
||||
|
||||
client := NewClient(opts...)
|
||||
|
||||
return &APIClient{
|
||||
client: client,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Post Send POST request
|
||||
func (c *APIClient) Post(ctx context.Context, path string, body interface{}, result interface{}) error {
|
||||
return c.client.Request(ctx, "POST", path, body, result)
|
||||
}
|
||||
|
||||
// Get Send GET request
|
||||
func (c *APIClient) Get(ctx context.Context, path string, result interface{}) error {
|
||||
return c.client.Request(ctx, "GET", path, nil, result)
|
||||
}
|
||||
|
||||
// Put Send PUT request
|
||||
func (c *APIClient) Put(ctx context.Context, path string, body interface{}, result interface{}) error {
|
||||
return c.client.Request(ctx, "PUT", path, body, result)
|
||||
}
|
||||
|
||||
// Delete Send DELETE request
|
||||
func (c *APIClient) Delete(ctx context.Context, path string, result interface{}) error {
|
||||
return c.client.Request(ctx, "DELETE", path, nil, result)
|
||||
}
|
||||
50
mcp_server/internal/api/types.go
Normal file
50
mcp_server/internal/api/types.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package api
|
||||
|
||||
type PolicyRuleAction int
|
||||
|
||||
const (
|
||||
PolicyRuleActionAllow PolicyRuleAction = iota
|
||||
PolicyRuleActionDeny
|
||||
PolicyRuleActionMax
|
||||
)
|
||||
|
||||
type Key = string
|
||||
|
||||
const (
|
||||
KeySrcIP Key = "src_ip"
|
||||
KeyURI Key = "uri"
|
||||
KeyURINoQuery Key = "uri_no_query"
|
||||
KeyHost Key = "host"
|
||||
KeyMethod Key = "method"
|
||||
KeyReqHeader Key = "req_header"
|
||||
KeyReqBody Key = "req_body"
|
||||
KeyGetParam Key = "get_param"
|
||||
KeyPostParam Key = "post_param"
|
||||
)
|
||||
|
||||
type Op = string
|
||||
|
||||
const (
|
||||
OpEq Op = "eq" // equal
|
||||
OpNotEq Op = "not_eq" // not equal
|
||||
OpMatch Op = "match" // match
|
||||
OpCIDR Op = "cidr" // cidr
|
||||
OpHas Op = "has" // has
|
||||
OpNotHas Op = "not_has" // not has
|
||||
OpPrefix Op = "prefix" // prefix
|
||||
OpRe Op = "re" // regex
|
||||
OpIn Op = "in" // in
|
||||
OpNotIn Op = "not_in" // not in
|
||||
OpNotCIDR Op = "not_cidr" // not cidr
|
||||
OpExist Op = "exist" // exist
|
||||
OpNotExist Op = "not_exist" // not exist
|
||||
OpGeoEq Op = "geo_eq" // geo equal
|
||||
OpGeoNotEq Op = "geo_not_eq" // geo not equal
|
||||
)
|
||||
|
||||
type Pattern struct {
|
||||
K Key `json:"k"`
|
||||
Op Op `json:"op"`
|
||||
V []string `json:"v"`
|
||||
SubK string `json:"sub_k"`
|
||||
}
|
||||
118
mcp_server/internal/config/config.go
Normal file
118
mcp_server/internal/config/config.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config Global configuration structure
|
||||
type Config struct {
|
||||
Server *ServerConfig `yaml:"server"`
|
||||
Logger *LoggerConfig `yaml:"logger"`
|
||||
API *APIConfig `yaml:"api"`
|
||||
}
|
||||
|
||||
// APIConfig API configuration
|
||||
type APIConfig struct {
|
||||
// API base URL
|
||||
BaseURL string `yaml:"base_url"`
|
||||
// API token
|
||||
Token string `yaml:"token"`
|
||||
// API timeout
|
||||
Timeout int `yaml:"timeout"`
|
||||
// API debug mode
|
||||
Debug bool `yaml:"debug"`
|
||||
// API insecure skip verify
|
||||
InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
|
||||
}
|
||||
|
||||
// ServerConfig Server configuration
|
||||
type ServerConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Version string `yaml:"version"`
|
||||
Port int `yaml:"port"`
|
||||
Host string `yaml:"host"`
|
||||
Secret string `yaml:"secret"`
|
||||
}
|
||||
|
||||
// LoggerConfig Logger configuration
|
||||
type LoggerConfig struct {
|
||||
Level string `yaml:"level"`
|
||||
FilePath string `yaml:"file_path"`
|
||||
Console bool `yaml:"console"`
|
||||
Caller bool `yaml:"caller"`
|
||||
Development bool `yaml:"development"`
|
||||
}
|
||||
|
||||
var config *Config
|
||||
|
||||
// getEnvString Get string value from environment variable, return default value if not exists
|
||||
func getEnvString(key, defaultValue string) string {
|
||||
if value, exists := os.LookupEnv(key); exists {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// getEnvInt Get integer value from environment variable, return default value if not exists or cannot be parsed
|
||||
func getEnvInt(key string, defaultValue int) int {
|
||||
if value, exists := os.LookupEnv(key); exists {
|
||||
if intValue, err := strconv.Atoi(value); err == nil {
|
||||
return intValue
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// Load Load configuration file
|
||||
func Load(path string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read config file failed")
|
||||
}
|
||||
|
||||
config = &Config{}
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
return errors.Wrap(err, "unmarshal config failed")
|
||||
}
|
||||
|
||||
// Override configuration from environment variables
|
||||
if config.Server != nil {
|
||||
config.Server.Host = getEnvString("LISTEN_ADDRESS", config.Server.Host)
|
||||
config.Server.Port = getEnvInt("LISTEN_PORT", config.Server.Port)
|
||||
}
|
||||
|
||||
if config.API != nil {
|
||||
config.API.BaseURL = getEnvString("SAFELINE_ADDRESS", config.API.BaseURL)
|
||||
config.API.Token = getEnvString("SAFELINE_API_TOKEN", config.API.Token)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetServer Get server configuration
|
||||
func GetServer() *ServerConfig {
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
return config.Server
|
||||
}
|
||||
|
||||
// GetLogger Get logger configuration
|
||||
func GetLogger() *LoggerConfig {
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
return config.Logger
|
||||
}
|
||||
|
||||
// GetAPI Get API configuration
|
||||
func GetAPI() *APIConfig {
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
return config.API
|
||||
}
|
||||
45
mcp_server/internal/tools/analyze/get_atttack_events.go
Normal file
45
mcp_server/internal/tools/analyze/get_atttack_events.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package analyze
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/api/analyze"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
|
||||
)
|
||||
|
||||
type GetAttackEventsParams struct {
|
||||
IP string `json:"ip" desc:"ip" required:"false"`
|
||||
Page int `json:"page" desc:"page" required:"false" default:"1"`
|
||||
PageSize int `json:"page_size" desc:"page size" required:"false" default:"10"`
|
||||
Start int64 `json:"start" desc:"start unix timestamp in milliseconds" required:"false"`
|
||||
End int64 `json:"end" desc:"end unix timestamp in milliseconds" required:"false"`
|
||||
}
|
||||
|
||||
type GetAttackEvents struct{}
|
||||
|
||||
func (t *GetAttackEvents) Name() string {
|
||||
return "get_attack_events"
|
||||
}
|
||||
|
||||
func (t *GetAttackEvents) Description() string {
|
||||
return "get attack events"
|
||||
}
|
||||
|
||||
func (t *GetAttackEvents) Validate(params GetAttackEventsParams) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *GetAttackEvents) Execute(ctx context.Context, params GetAttackEventsParams) (analyze.GetEventListResponse, error) {
|
||||
resp, err := analyze.GetEventList(ctx, &analyze.GetEventListRequest{
|
||||
IP: params.IP,
|
||||
PageSize: params.PageSize,
|
||||
Page: params.Page,
|
||||
Start: params.Start,
|
||||
End: params.End,
|
||||
})
|
||||
if err != nil {
|
||||
return analyze.GetEventListResponse{}, err
|
||||
}
|
||||
logger.With("total", resp.Total).Info("get attack events")
|
||||
return *resp, nil
|
||||
}
|
||||
41
mcp_server/internal/tools/app/create_application.go
Normal file
41
mcp_server/internal/tools/app/create_application.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/api/app"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
|
||||
)
|
||||
|
||||
type CreateApp struct{}
|
||||
|
||||
type CreateAppParams struct {
|
||||
ServerNames []string `json:"server_names" desc:"domain list" required:"true"`
|
||||
Ports []string `json:"ports" desc:"port list" required:"true"`
|
||||
Upstreams []string `json:"upstreams" desc:"upstream list" required:"true"`
|
||||
}
|
||||
|
||||
func (t *CreateApp) Name() string {
|
||||
return "create_http_application"
|
||||
}
|
||||
|
||||
func (t *CreateApp) Description() string {
|
||||
return "create a new website or app"
|
||||
}
|
||||
|
||||
func (t *CreateApp) Validate(params CreateAppParams) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *CreateApp) Execute(ctx context.Context, params CreateAppParams) (int64, error) {
|
||||
id, err := app.CreateApp(ctx, &app.CreateAppRequest{
|
||||
ServerNames: params.ServerNames,
|
||||
Ports: params.Ports,
|
||||
Upstreams: params.Upstreams,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
logger.Info("create app success", logger.Int64("id", id))
|
||||
return id, nil
|
||||
}
|
||||
46
mcp_server/internal/tools/example.go
Normal file
46
mcp_server/internal/tools/example.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/errors"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
|
||||
)
|
||||
|
||||
type CalculateSum struct{}
|
||||
|
||||
func (t *CalculateSum) Name() string {
|
||||
return "calculate_sum"
|
||||
}
|
||||
|
||||
func (t *CalculateSum) Description() string {
|
||||
return "Add two numbers together"
|
||||
}
|
||||
|
||||
type MyToolInput struct {
|
||||
A int `json:"a" desc:"number a" required:"true"`
|
||||
B int `json:"b" desc:"number b" required:"true"`
|
||||
}
|
||||
|
||||
type MyToolOutput struct {
|
||||
C int `json:"c"`
|
||||
}
|
||||
|
||||
func (t *CalculateSum) Validate(params MyToolInput) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *CalculateSum) Execute(ctx context.Context, params MyToolInput) (MyToolOutput, error) {
|
||||
logger.With("a", params.A).
|
||||
With("b", params.B).
|
||||
Debug("Executing calculation")
|
||||
|
||||
result := MyToolOutput{
|
||||
C: params.A + params.B,
|
||||
}
|
||||
|
||||
logger.With("result", result.C).
|
||||
Debug("Calculation completed")
|
||||
|
||||
return result, errors.New("test error")
|
||||
}
|
||||
19
mcp_server/internal/tools/init.go
Normal file
19
mcp_server/internal/tools/init.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/tools/analyze"
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/tools/app"
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/tools/rule"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// app
|
||||
AppendTool(&app.CreateApp{})
|
||||
|
||||
// rule
|
||||
AppendTool(&rule.CreateBlacklistRule{})
|
||||
AppendTool(&rule.CreateWhitelistRule{})
|
||||
|
||||
// analyze
|
||||
AppendTool(&analyze.GetAttackEvents{})
|
||||
}
|
||||
66
mcp_server/internal/tools/rule/create_blacklist_rule.go
Normal file
66
mcp_server/internal/tools/rule/create_blacklist_rule.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/api"
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/api/rule"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
|
||||
)
|
||||
|
||||
type CreateBlacklistRule struct{}
|
||||
|
||||
type CreateBlacklistRuleParams struct {
|
||||
Name string `json:"name" desc:"name" required:"true"`
|
||||
IP []string `json:"ip" desc:"ip" required:"false"`
|
||||
URINoQuery []string `json:"uri_no_query" desc:"uri_no_query" required:"false"`
|
||||
}
|
||||
|
||||
func (t *CreateBlacklistRule) Name() string {
|
||||
return "create_blacklist_rule"
|
||||
}
|
||||
|
||||
func (t *CreateBlacklistRule) Description() string {
|
||||
return "create a new blacklist rule"
|
||||
}
|
||||
|
||||
func (t *CreateBlacklistRule) Validate(params CreateBlacklistRuleParams) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *CreateBlacklistRule) Execute(ctx context.Context, params CreateBlacklistRuleParams) (int64, error) {
|
||||
var pattern [][]api.Pattern
|
||||
if len(params.IP) > 0 {
|
||||
pattern = append(pattern, []api.Pattern{
|
||||
{
|
||||
K: api.KeySrcIP,
|
||||
Op: api.OpEq,
|
||||
V: params.IP,
|
||||
SubK: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(params.URINoQuery) > 0 {
|
||||
pattern = append(pattern, []api.Pattern{
|
||||
{
|
||||
K: api.KeyURINoQuery,
|
||||
Op: api.OpEq,
|
||||
V: params.URINoQuery,
|
||||
SubK: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
id, err := rule.CreateRule(ctx, &rule.CreateRuleRequest{
|
||||
Name: params.Name,
|
||||
IP: params.IP,
|
||||
IsEnabled: true,
|
||||
Action: int(api.PolicyRuleActionDeny),
|
||||
Pattern: pattern,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
logger.With("id", id).Info("create blacklist rule success")
|
||||
return id, nil
|
||||
}
|
||||
66
mcp_server/internal/tools/rule/create_whitelist_rule.go
Normal file
66
mcp_server/internal/tools/rule/create_whitelist_rule.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/api"
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/api/rule"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
|
||||
)
|
||||
|
||||
type CreateWhitelistRule struct{}
|
||||
|
||||
type CreateWhitelistRuleParams struct {
|
||||
Name string `json:"name" desc:"name" required:"true"`
|
||||
IP []string `json:"ip" desc:"ip" required:"false"`
|
||||
URINoQuery []string `json:"uri_no_query" desc:"uri_no_query" required:"false"`
|
||||
}
|
||||
|
||||
func (t *CreateWhitelistRule) Name() string {
|
||||
return "create_whitelist_rule"
|
||||
}
|
||||
|
||||
func (t *CreateWhitelistRule) Description() string {
|
||||
return "create a new whitelist rule"
|
||||
}
|
||||
|
||||
func (t *CreateWhitelistRule) Validate(params CreateWhitelistRuleParams) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *CreateWhitelistRule) Execute(ctx context.Context, params CreateWhitelistRuleParams) (int64, error) {
|
||||
var pattern [][]api.Pattern
|
||||
if len(params.IP) > 0 {
|
||||
pattern = append(pattern, []api.Pattern{
|
||||
{
|
||||
K: api.KeySrcIP,
|
||||
Op: api.OpEq,
|
||||
V: params.IP,
|
||||
SubK: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(params.URINoQuery) > 0 {
|
||||
pattern = append(pattern, []api.Pattern{
|
||||
{
|
||||
K: api.KeyURINoQuery,
|
||||
Op: api.OpEq,
|
||||
V: params.URINoQuery,
|
||||
SubK: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
id, err := rule.CreateRule(ctx, &rule.CreateRuleRequest{
|
||||
Name: params.Name,
|
||||
IP: params.IP,
|
||||
IsEnabled: true,
|
||||
Action: int(api.PolicyRuleActionAllow),
|
||||
Pattern: pattern,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
logger.With("id", id).Info("create whitelist rule success")
|
||||
return id, nil
|
||||
}
|
||||
41
mcp_server/internal/tools/tool.go
Normal file
41
mcp_server/internal/tools/tool.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/mcp"
|
||||
)
|
||||
|
||||
// By deferring the concretization of generic types to the Register method,
|
||||
// we avoid type inference issues.
|
||||
|
||||
// Each Tool is wrapped in a toolWrapper that knows its concrete type,
|
||||
// allowing correct passing of generic parameters during registration.
|
||||
type ToolWrapper interface {
|
||||
Register(s *mcp.MCPServer) error
|
||||
}
|
||||
|
||||
var (
|
||||
tools = []ToolWrapper{}
|
||||
)
|
||||
|
||||
func AppendTool[T any, R any](tool ...mcp.Tool[T, R]) {
|
||||
for _, t := range tool {
|
||||
tools = append(tools, &toolWrapper[T, R]{tool: t})
|
||||
}
|
||||
}
|
||||
|
||||
func Tools() []ToolWrapper {
|
||||
return tools
|
||||
}
|
||||
|
||||
type toolWrapper[T any, R any] struct {
|
||||
tool mcp.Tool[T, R]
|
||||
}
|
||||
|
||||
func (w *toolWrapper[T, R]) Register(s *mcp.MCPServer) error {
|
||||
logger.Info("Registering tool",
|
||||
logger.String("name", w.tool.Name()),
|
||||
logger.String("description", w.tool.Description()),
|
||||
)
|
||||
return mcp.RegisterTool(s, w.tool)
|
||||
}
|
||||
62
mcp_server/main.go
Normal file
62
mcp_server/main.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/api"
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/config"
|
||||
"github.com/chaitin/SafeLine/mcp_server/internal/tools"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/mcp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
configPath := flag.String("config", "config.yaml", "path to config file")
|
||||
flag.Parse()
|
||||
|
||||
if err := config.Load(*configPath); err != nil {
|
||||
panic(fmt.Errorf("failed to load config: %v", err))
|
||||
}
|
||||
|
||||
logConfig := config.GetLogger()
|
||||
if err := logger.Init(&logger.Config{
|
||||
Level: logConfig.Level,
|
||||
FilePath: logConfig.FilePath,
|
||||
Console: logConfig.Console,
|
||||
Caller: logConfig.Caller,
|
||||
Development: logConfig.Development,
|
||||
}); err != nil {
|
||||
panic(fmt.Errorf("failed to init logger: %v", err))
|
||||
}
|
||||
|
||||
logger.With("base_url", config.GetAPI().BaseURL).Info("Initializing API service...")
|
||||
if err := api.Init(config.GetAPI()); err != nil {
|
||||
panic(fmt.Errorf("failed to init API service: %v", err))
|
||||
}
|
||||
|
||||
logger.Info("Starting MCP Server...")
|
||||
serverConfig := config.GetServer()
|
||||
s := mcp.NewMCPServer(
|
||||
serverConfig.Name,
|
||||
serverConfig.Version,
|
||||
serverConfig.Secret,
|
||||
)
|
||||
|
||||
logger.Info("Registering tools...")
|
||||
for _, tool := range tools.Tools() {
|
||||
if err := tool.Register(s); err != nil {
|
||||
logger.With("error", err).
|
||||
Error("Failed to register tool")
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", serverConfig.Host, serverConfig.Port)
|
||||
logger.With("addr", addr).Info("Starting server")
|
||||
if err := s.Start(addr); err != nil {
|
||||
logger.With("error", err).
|
||||
Error("Server failed to start")
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
72
mcp_server/pkg/config/config.go
Normal file
72
mcp_server/pkg/config/config.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config Global configuration structure
|
||||
type Config struct {
|
||||
Server ServerConfig `yaml:"server"`
|
||||
Logger LoggerConfig `yaml:"logger"`
|
||||
}
|
||||
|
||||
// ServerConfig Server configuration
|
||||
type ServerConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Version string `yaml:"version"`
|
||||
Port int `yaml:"port"`
|
||||
Host string `yaml:"host"`
|
||||
Secret string `yaml:"secret"`
|
||||
}
|
||||
|
||||
// LoggerConfig Logger configuration
|
||||
type LoggerConfig struct {
|
||||
Level string `yaml:"level"`
|
||||
FilePath string `yaml:"file_path"`
|
||||
Console bool `yaml:"console"`
|
||||
Caller bool `yaml:"caller"`
|
||||
Development bool `yaml:"development"`
|
||||
}
|
||||
|
||||
var (
|
||||
globalConfig *Config
|
||||
)
|
||||
|
||||
// Load Load configuration from file
|
||||
func Load(filename string) error {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config := &Config{}
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
globalConfig = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get Get global configuration
|
||||
func Get() *Config {
|
||||
return globalConfig
|
||||
}
|
||||
|
||||
// GetServer Get server configuration
|
||||
func GetServer() ServerConfig {
|
||||
if globalConfig == nil {
|
||||
return ServerConfig{}
|
||||
}
|
||||
return globalConfig.Server
|
||||
}
|
||||
|
||||
// GetLogger Get logger configuration
|
||||
func GetLogger() LoggerConfig {
|
||||
if globalConfig == nil {
|
||||
return LoggerConfig{}
|
||||
}
|
||||
return globalConfig.Logger
|
||||
}
|
||||
136
mcp_server/pkg/errors/errors.go
Normal file
136
mcp_server/pkg/errors/errors.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
// Common errors
|
||||
ErrInternal = New("internal error")
|
||||
ErrInvalidParam = New("invalid parameter")
|
||||
ErrNotFound = New("resource not found")
|
||||
ErrUnauthorized = New("unauthorized")
|
||||
ErrForbidden = New("forbidden")
|
||||
ErrTimeout = New("timeout")
|
||||
)
|
||||
|
||||
// Error Custom error structure
|
||||
type Error struct {
|
||||
err error
|
||||
stack []string
|
||||
msg string
|
||||
location string
|
||||
}
|
||||
|
||||
// Error Implement error interface
|
||||
func (e *Error) Error() string {
|
||||
if e.msg != "" {
|
||||
return fmt.Sprintf("%s: %v (at %s)", e.msg, e.err, e.location)
|
||||
}
|
||||
return fmt.Sprintf("%v (at %s)", e.err, e.location)
|
||||
}
|
||||
|
||||
// Unwrap Return original error
|
||||
func (e *Error) Unwrap() error {
|
||||
if e.err == nil {
|
||||
return nil
|
||||
}
|
||||
if wrapped, ok := e.err.(*Error); ok {
|
||||
return wrapped.Unwrap()
|
||||
}
|
||||
return e.err
|
||||
}
|
||||
|
||||
// Stack Return error stack
|
||||
func (e *Error) Stack() []string {
|
||||
return e.stack
|
||||
}
|
||||
|
||||
// Location Return error location
|
||||
func (e *Error) Location() string {
|
||||
return e.location
|
||||
}
|
||||
|
||||
// getCallerLocation Get caller location
|
||||
func getCallerLocation(skip int) string {
|
||||
_, file, line, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
return "unknown"
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
|
||||
// WrapL Wrap error and print log
|
||||
func WrapL(err error, msg string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get stack trace information
|
||||
var stack []string
|
||||
for i := 1; i < 32; i++ {
|
||||
pc, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
break
|
||||
}
|
||||
name := fn.Name()
|
||||
if strings.Contains(name, "runtime.") {
|
||||
break
|
||||
}
|
||||
stack = append(stack, fmt.Sprintf("%s:%d", file, line))
|
||||
}
|
||||
|
||||
wrappedErr := &Error{
|
||||
err: err,
|
||||
stack: stack,
|
||||
msg: msg,
|
||||
location: getCallerLocation(2),
|
||||
}
|
||||
|
||||
// Print error information and stack using logger
|
||||
logger.With("error", err).
|
||||
With("location", wrappedErr.location).
|
||||
With("stack", strings.Join(stack, "\n")).
|
||||
Error(msg)
|
||||
|
||||
return wrappedErr
|
||||
}
|
||||
|
||||
// Is Check error type
|
||||
func Is(err, target error) bool {
|
||||
return errors.Is(err, target)
|
||||
}
|
||||
|
||||
// As Type assertion
|
||||
func As(err error, target interface{}) bool {
|
||||
return errors.As(err, target)
|
||||
}
|
||||
|
||||
// Wrap Wrap error without printing log
|
||||
func Wrap(err error, msg string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &Error{
|
||||
err: err,
|
||||
msg: msg,
|
||||
location: getCallerLocation(2),
|
||||
}
|
||||
}
|
||||
|
||||
// New Create new error
|
||||
func New(text string) error {
|
||||
return &Error{
|
||||
err: errors.New(text),
|
||||
location: getCallerLocation(2),
|
||||
}
|
||||
}
|
||||
49
mcp_server/pkg/logger/field.go
Normal file
49
mcp_server/pkg/logger/field.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// Field 日志字段
|
||||
type Field = zapcore.Field
|
||||
|
||||
// String 创建字符串字段
|
||||
func String(key string, val string) Field {
|
||||
return zap.String(key, val)
|
||||
}
|
||||
|
||||
// Int 创建整数字段
|
||||
func Int(key string, val int) Field {
|
||||
return zap.Int(key, val)
|
||||
}
|
||||
|
||||
// Int64 创建 int64 字段
|
||||
func Int64(key string, val int64) Field {
|
||||
return zap.Int64(key, val)
|
||||
}
|
||||
|
||||
// Float64 创建浮点数字段
|
||||
func Float64(key string, val float64) Field {
|
||||
return zap.Float64(key, val)
|
||||
}
|
||||
|
||||
// Bool 创建布尔字段
|
||||
func Bool(key string, val bool) Field {
|
||||
return zap.Bool(key, val)
|
||||
}
|
||||
|
||||
// Err 创建错误字段
|
||||
func Err(err error) Field {
|
||||
return zap.Error(err)
|
||||
}
|
||||
|
||||
// Any 创建任意类型字段
|
||||
func Any(key string, val interface{}) Field {
|
||||
return zap.Any(key, val)
|
||||
}
|
||||
|
||||
// Duration 创建时间段字段
|
||||
func Duration(key string, val float64) Field {
|
||||
return zap.Float64(key, val)
|
||||
}
|
||||
205
mcp_server/pkg/logger/logger.go
Normal file
205
mcp_server/pkg/logger/logger.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// Logger 封装 zap.Logger
|
||||
type Logger struct {
|
||||
zl *zap.Logger
|
||||
}
|
||||
|
||||
var (
|
||||
defaultLogger *Logger
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// Config 日志配置
|
||||
type Config struct {
|
||||
// 日志级别
|
||||
Level string `json:"level" yaml:"level"`
|
||||
// 日志文件路径
|
||||
FilePath string `json:"file_path" yaml:"file_path"`
|
||||
// 是否输出到控制台
|
||||
Console bool `json:"console" yaml:"console"`
|
||||
// 是否记录调用者信息
|
||||
Caller bool `json:"caller" yaml:"caller"`
|
||||
// 是否使用开发模式(更详细的日志)
|
||||
Development bool `json:"development" yaml:"development"`
|
||||
}
|
||||
|
||||
// 默认配置
|
||||
var defaultConfig = Config{
|
||||
Level: "info",
|
||||
FilePath: "logs/mcp.log",
|
||||
Console: true,
|
||||
Caller: true,
|
||||
Development: false,
|
||||
}
|
||||
|
||||
// Init 初始化日志系统
|
||||
func Init(cfg *Config) error {
|
||||
var err error
|
||||
once.Do(func() {
|
||||
if cfg == nil {
|
||||
cfg = &defaultConfig
|
||||
}
|
||||
|
||||
// 确保日志目录存在
|
||||
if cfg.FilePath != "" {
|
||||
dir := filepath.Dir(cfg.FilePath)
|
||||
if err = os.MkdirAll(dir, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 配置编码器
|
||||
encoderConfig := zapcore.EncoderConfig{
|
||||
TimeKey: "time",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
MessageKey: "msg",
|
||||
StacktraceKey: "stacktrace",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||
enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
|
||||
},
|
||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
}
|
||||
|
||||
// 设置日志级别
|
||||
var level zapcore.Level
|
||||
if err = level.UnmarshalText([]byte(cfg.Level)); err != nil {
|
||||
level = zapcore.InfoLevel
|
||||
}
|
||||
|
||||
// 创建Core
|
||||
var cores []zapcore.Core
|
||||
|
||||
// 文件输出
|
||||
if cfg.FilePath != "" {
|
||||
fileWriter, err := os.OpenFile(cfg.FilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cores = append(cores, zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderConfig),
|
||||
zapcore.AddSync(fileWriter),
|
||||
level,
|
||||
))
|
||||
}
|
||||
|
||||
// 控制台输出
|
||||
if cfg.Console {
|
||||
cores = append(cores, zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(encoderConfig),
|
||||
zapcore.AddSync(os.Stdout),
|
||||
level,
|
||||
))
|
||||
}
|
||||
|
||||
// 创建Logger
|
||||
core := zapcore.NewTee(cores...)
|
||||
zl := zap.New(core)
|
||||
|
||||
// 是否记录调用者信息
|
||||
if cfg.Caller {
|
||||
zl = zl.WithOptions(zap.AddCaller())
|
||||
}
|
||||
|
||||
// 是否使用开发模式
|
||||
if cfg.Development {
|
||||
zl = zl.WithOptions(zap.Development())
|
||||
}
|
||||
|
||||
defaultLogger = &Logger{zl: zl}
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetLogger 获取日志实例
|
||||
func GetLogger() *Logger {
|
||||
if defaultLogger == nil {
|
||||
Init(nil)
|
||||
}
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
// With 创建带有字段的新Logger
|
||||
func (l *Logger) With(key string, value interface{}) *Logger {
|
||||
return &Logger{zl: l.zl.With(Any(key, value))}
|
||||
}
|
||||
|
||||
// Debug level
|
||||
func (l *Logger) Debug(msg string, fields ...Field) {
|
||||
l.zl.Debug(msg, fields...)
|
||||
}
|
||||
|
||||
// Info level
|
||||
func (l *Logger) Info(msg string, fields ...Field) {
|
||||
l.zl.Info(msg, fields...)
|
||||
}
|
||||
|
||||
// Warn level
|
||||
func (l *Logger) Warn(msg string, fields ...Field) {
|
||||
l.zl.Warn(msg, fields...)
|
||||
}
|
||||
|
||||
// Error level
|
||||
func (l *Logger) Error(msg string, fields ...Field) {
|
||||
l.zl.Error(msg, fields...)
|
||||
}
|
||||
|
||||
// Fatal level
|
||||
func (l *Logger) Fatal(msg string, fields ...Field) {
|
||||
l.zl.Fatal(msg, fields...)
|
||||
}
|
||||
|
||||
// 全局函数
|
||||
|
||||
// With 创建带有字段的新Logger
|
||||
func With(key string, value interface{}) *Logger {
|
||||
l := GetLogger()
|
||||
// 为链式调用创建新的logger实例,并添加caller skip
|
||||
return &Logger{zl: l.zl.WithOptions(zap.AddCallerSkip(1)).With(Any(key, value))}
|
||||
}
|
||||
|
||||
// Debug level
|
||||
func Debug(msg string, fields ...Field) {
|
||||
l := GetLogger()
|
||||
l.zl.WithOptions(zap.AddCallerSkip(1)).Debug(msg, fields...)
|
||||
}
|
||||
|
||||
// Info level
|
||||
func Info(msg string, fields ...Field) {
|
||||
l := GetLogger()
|
||||
l.zl.WithOptions(zap.AddCallerSkip(1)).Info(msg, fields...)
|
||||
}
|
||||
|
||||
// Warn level
|
||||
func Warn(msg string, fields ...Field) {
|
||||
l := GetLogger()
|
||||
l.zl.WithOptions(zap.AddCallerSkip(1)).Warn(msg, fields...)
|
||||
}
|
||||
|
||||
// Error level
|
||||
func Error(msg string, fields ...Field) {
|
||||
l := GetLogger()
|
||||
l.zl.WithOptions(zap.AddCallerSkip(1)).Error(msg, fields...)
|
||||
}
|
||||
|
||||
// Fatal level
|
||||
func Fatal(msg string, fields ...Field) {
|
||||
l := GetLogger()
|
||||
l.zl.WithOptions(zap.AddCallerSkip(1)).Fatal(msg, fields...)
|
||||
}
|
||||
148
mcp_server/pkg/mcp/mcp.go
Normal file
148
mcp_server/pkg/mcp/mcp.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/errors"
|
||||
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"github.com/mcuadros/go-defaults"
|
||||
)
|
||||
|
||||
type Tool[T any, R any] interface {
|
||||
Name() string
|
||||
|
||||
Description() string
|
||||
|
||||
Execute(ctx context.Context, params T) (R, error)
|
||||
|
||||
Validate(params T) error
|
||||
}
|
||||
|
||||
type SSEServer struct {
|
||||
sse *server.SSEServer
|
||||
secret string
|
||||
}
|
||||
|
||||
func (s *SSEServer) Start(addr string) error {
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: s,
|
||||
}
|
||||
return srv.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *SSEServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if s.secret == "" {
|
||||
s.sse.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
messagePath := s.sse.CompleteMessagePath()
|
||||
if messagePath != "" && r.URL.Path == messagePath {
|
||||
secret := r.Header.Get("Secret")
|
||||
if secret != s.secret {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
s.sse.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
type MCPServer struct {
|
||||
server *server.MCPServer
|
||||
sse *SSEServer
|
||||
}
|
||||
|
||||
func NewMCPServer(name, version string, secret string) *MCPServer {
|
||||
s := server.NewMCPServer(
|
||||
name,
|
||||
version,
|
||||
server.WithLogging(),
|
||||
)
|
||||
return &MCPServer{
|
||||
server: s,
|
||||
sse: &SSEServer{sse: server.NewSSEServer(s), secret: secret},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MCPServer) Start(addr string) error {
|
||||
return s.sse.Start(addr)
|
||||
}
|
||||
|
||||
func handleToolCall[T any, R any](ctx context.Context, request mcp.CallToolRequest, tool Tool[T, R]) (result *mcp.CallToolResult, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("panic recovered: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
var raw []byte
|
||||
raw, err = json.Marshal(request.Params.Arguments)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "marshal arguments failed")
|
||||
}
|
||||
var params T
|
||||
defaults.SetDefaults(¶ms)
|
||||
if err = json.Unmarshal(raw, ¶ms); err != nil {
|
||||
return nil, errors.Wrap(err, "unmarshal parameters failed")
|
||||
}
|
||||
|
||||
if err = tool.Validate(params); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var execResult R
|
||||
execResult, err = tool.Execute(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := any(execResult)
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return mcp.NewToolResultText(v), nil
|
||||
case []byte:
|
||||
return mcp.NewToolResultText(string(v)), nil
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
return mcp.NewToolResultText(json.Number(fmt.Sprint(v)).String()), nil
|
||||
case bool:
|
||||
return mcp.NewToolResultText(strconv.FormatBool(v)), nil
|
||||
default:
|
||||
bytes, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid result type")
|
||||
}
|
||||
return mcp.NewToolResultText(string(bytes)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterTool[T any, R any](s *MCPServer, tool Tool[T, R]) error {
|
||||
var v T
|
||||
opts, err := SchemaToOptions(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, mcp.WithDescription(tool.Description()))
|
||||
t := mcp.NewTool(tool.Name(),
|
||||
opts...,
|
||||
)
|
||||
|
||||
s.server.AddTool(t, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
result, err := handleToolCall(ctx, request, tool)
|
||||
if err != nil {
|
||||
logger.With("error", err).Error("handle tool call failed")
|
||||
if wrapped, ok := err.(*errors.Error); ok {
|
||||
return nil, wrapped.Unwrap()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
169
mcp_server/pkg/mcp/schema.go
Normal file
169
mcp_server/pkg/mcp/schema.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
)
|
||||
|
||||
// SchemaToOptions Convert struct to MCP ToolOption list
|
||||
func SchemaToOptions(schema any) ([]mcp.ToolOption, error) {
|
||||
t := reflect.TypeOf(schema)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
var options []mcp.ToolOption
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
desc := field.Tag.Get("desc")
|
||||
required := field.Tag.Get("required") == "true"
|
||||
enumTag := field.Tag.Get("enum")
|
||||
defaultTag := field.Tag.Get("default")
|
||||
minTag := field.Tag.Get("min")
|
||||
maxTag := field.Tag.Get("max")
|
||||
opts := []mcp.PropertyOption{}
|
||||
|
||||
if desc != "" {
|
||||
opts = append(opts, mcp.Description(desc))
|
||||
}
|
||||
if required {
|
||||
opts = append(opts, mcp.Required())
|
||||
}
|
||||
if enumTag != "" && field.Type.Kind() == reflect.String {
|
||||
enumValues := strings.Split(enumTag, ",")
|
||||
opts = append(opts, mcp.Enum(enumValues...))
|
||||
}
|
||||
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:
|
||||
if defaultTag != "" {
|
||||
if defaultValue, err := strconv.Atoi(defaultTag); err == nil {
|
||||
opts = append(opts, mcp.DefaultNumber(float64(defaultValue)))
|
||||
}
|
||||
}
|
||||
if minTag != "" {
|
||||
if minValue, err := strconv.Atoi(minTag); err == nil {
|
||||
opts = append(opts, mcp.Min(float64(minValue)))
|
||||
}
|
||||
}
|
||||
if maxTag != "" {
|
||||
if maxValue, err := strconv.Atoi(maxTag); err == nil {
|
||||
opts = append(opts, mcp.Max(float64(maxValue)))
|
||||
}
|
||||
}
|
||||
options = append(options, mcp.WithNumber(jsonTag, opts...))
|
||||
case reflect.Bool:
|
||||
if defaultTag != "" {
|
||||
if defaultValue, err := strconv.ParseBool(defaultTag); err == nil {
|
||||
opts = append(opts, mcp.DefaultBool(defaultValue))
|
||||
}
|
||||
}
|
||||
options = append(options, mcp.WithBoolean(jsonTag, opts...))
|
||||
case reflect.String:
|
||||
if defaultTag != "" {
|
||||
opts = append(opts, mcp.DefaultString(defaultTag))
|
||||
}
|
||||
options = append(options, mcp.WithString(jsonTag, opts...))
|
||||
case reflect.Struct:
|
||||
subSchema := reflect.New(field.Type).Interface()
|
||||
subOptions, err := SchemaToOptions(subSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a temporary Tool to get JSON Schema of sub-struct
|
||||
tempTool := mcp.NewTool("temp", subOptions...)
|
||||
tempJSON, _ := tempTool.MarshalJSON()
|
||||
var tempMap map[string]any
|
||||
if err := json.Unmarshal(tempJSON, &tempMap); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract properties from temporary Tool
|
||||
if inputSchema, ok := tempMap["inputSchema"].(map[string]any); ok {
|
||||
if properties, ok := inputSchema["properties"].(map[string]any); ok {
|
||||
// Check if there are required fields
|
||||
if required, ok := inputSchema["required"].([]any); ok {
|
||||
// Add required field information to corresponding properties
|
||||
for _, req := range required {
|
||||
if reqStr, ok := req.(string); ok {
|
||||
if prop, ok := properties[reqStr].(map[string]any); ok {
|
||||
prop["required"] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
opts = append(opts, mcp.Properties(properties))
|
||||
}
|
||||
}
|
||||
options = append(options, mcp.WithObject(jsonTag, opts...))
|
||||
|
||||
case reflect.Slice:
|
||||
elemType := field.Type.Elem()
|
||||
var items map[string]any
|
||||
|
||||
switch elemType.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:
|
||||
items = map[string]any{
|
||||
"type": "number",
|
||||
}
|
||||
case reflect.Bool:
|
||||
items = map[string]any{
|
||||
"type": "boolean",
|
||||
}
|
||||
case reflect.String:
|
||||
items = map[string]any{
|
||||
"type": "string",
|
||||
}
|
||||
case reflect.Struct:
|
||||
subSchema := reflect.New(elemType).Interface()
|
||||
subOptions, err := SchemaToOptions(subSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a temporary Tool to get JSON Schema of sub-struct
|
||||
tempTool := mcp.NewTool("temp", subOptions...)
|
||||
tempJSON, _ := tempTool.MarshalJSON()
|
||||
var tempMap map[string]any
|
||||
if err := json.Unmarshal(tempJSON, &tempMap); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract properties from temporary Tool
|
||||
if inputSchema, ok := tempMap["inputSchema"].(map[string]any); ok {
|
||||
if properties, ok := inputSchema["properties"].(map[string]any); ok {
|
||||
// Check if there are required fields
|
||||
if required, ok := inputSchema["required"].([]any); ok {
|
||||
// Add required field information to corresponding properties
|
||||
for _, req := range required {
|
||||
if reqStr, ok := req.(string); ok {
|
||||
if prop, ok := properties[reqStr].(map[string]any); ok {
|
||||
prop["required"] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
items = map[string]any{
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
opts = append(opts, mcp.Items(items))
|
||||
options = append(options, mcp.WithArray(jsonTag, opts...))
|
||||
}
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
227
mcp_server/pkg/mcp/schema_test.go
Normal file
227
mcp_server/pkg/mcp/schema_test.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
)
|
||||
|
||||
func TestSchemaToOptions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args any
|
||||
want mcp.Tool
|
||||
}{
|
||||
{
|
||||
name: "test number",
|
||||
args: struct {
|
||||
A int `json:"a" desc:"number a" required:"true"`
|
||||
}{},
|
||||
want: mcp.NewTool("test number",
|
||||
mcp.WithNumber("a", mcp.Required(), mcp.Description("number a")),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test number int64",
|
||||
args: struct {
|
||||
A int64 `json:"a" desc:"number a" required:"true"`
|
||||
}{},
|
||||
want: mcp.NewTool("test number int64",
|
||||
mcp.WithNumber("a", mcp.Required(), mcp.Description("number a")),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test number float64",
|
||||
args: struct {
|
||||
A float64 `json:"a" desc:"number a" required:"true"`
|
||||
}{},
|
||||
want: mcp.NewTool("test number float64",
|
||||
mcp.WithNumber("a", mcp.Required(), mcp.Description("number a")),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test number default",
|
||||
args: struct {
|
||||
A int `json:"a" desc:"number a" required:"true" default:"10"`
|
||||
}{},
|
||||
want: mcp.NewTool("test number default",
|
||||
mcp.WithNumber("a", mcp.Required(), mcp.Description("number a"), mcp.DefaultNumber(10)),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test number min max",
|
||||
args: struct {
|
||||
A int `json:"a" desc:"number a" required:"true" min:"10" max:"20"`
|
||||
}{},
|
||||
want: mcp.NewTool("test number min max",
|
||||
mcp.WithNumber("a", mcp.Required(), mcp.Description("number a"), mcp.Min(10), mcp.Max(20)),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test number optional",
|
||||
args: struct {
|
||||
A int `json:"a" desc:"number a"`
|
||||
}{},
|
||||
want: mcp.NewTool("test number optional",
|
||||
mcp.WithNumber("a", mcp.Description("number a")),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test boolean",
|
||||
args: struct {
|
||||
A bool `json:"a" desc:"boolean a" required:"true"`
|
||||
}{},
|
||||
want: mcp.NewTool("test boolean",
|
||||
mcp.WithBoolean("a", mcp.Required(), mcp.Description("boolean a")),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test string",
|
||||
args: struct {
|
||||
A string `json:"a" desc:"string a" required:"true"`
|
||||
}{},
|
||||
want: mcp.NewTool("test string",
|
||||
mcp.WithString("a", mcp.Required(), mcp.Description("string a")),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test string default",
|
||||
args: struct {
|
||||
A string `json:"a" desc:"string a" required:"true" default:"hello"`
|
||||
}{},
|
||||
want: mcp.NewTool("test string default",
|
||||
mcp.WithString("a", mcp.Required(), mcp.Description("string a"), mcp.DefaultString("hello")),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test string enum",
|
||||
args: struct {
|
||||
A string `json:"a" desc:"string a" required:"true" enum:"1,2,3"`
|
||||
}{},
|
||||
want: mcp.NewTool("test string enum",
|
||||
mcp.WithString("a", mcp.Required(), mcp.Description("string a"), mcp.Enum("1", "2", "3")),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test object",
|
||||
args: struct {
|
||||
A struct {
|
||||
B int `json:"b" desc:"number b" required:"true"`
|
||||
} `json:"a" desc:"object a" required:"true"`
|
||||
}{},
|
||||
want: mcp.NewTool("test object",
|
||||
mcp.WithObject("a", mcp.Required(), mcp.Description("object a"),
|
||||
mcp.Properties(map[string]any{
|
||||
"b": map[string]any{
|
||||
"type": "number",
|
||||
"description": "number b",
|
||||
"required": true,
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test object optional",
|
||||
args: struct {
|
||||
A struct {
|
||||
B int `json:"b" desc:"number b" required:"true"`
|
||||
C int `json:"c" desc:"number c"`
|
||||
} `json:"a" desc:"object a" required:"true"`
|
||||
}{},
|
||||
want: mcp.NewTool("test object optional",
|
||||
mcp.WithObject("a", mcp.Required(), mcp.Description("object a"),
|
||||
mcp.Properties(map[string]any{
|
||||
"b": map[string]any{
|
||||
"type": "number",
|
||||
"description": "number b",
|
||||
"required": true,
|
||||
},
|
||||
"c": map[string]any{
|
||||
"type": "number",
|
||||
"description": "number c",
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test nested object",
|
||||
args: struct {
|
||||
A struct {
|
||||
B struct {
|
||||
C int `json:"c" desc:"number c" required:"true"`
|
||||
} `json:"b" desc:"object b" required:"true"`
|
||||
} `json:"a" desc:"object a" required:"true"`
|
||||
}{},
|
||||
want: mcp.NewTool("test nested object",
|
||||
mcp.WithObject("a", mcp.Required(), mcp.Description("object a"),
|
||||
mcp.Properties(map[string]any{
|
||||
"b": map[string]any{
|
||||
"type": "object",
|
||||
"description": "object b",
|
||||
"required": true,
|
||||
"properties": map[string]any{
|
||||
"c": map[string]any{
|
||||
"type": "number",
|
||||
"description": "number c",
|
||||
"required": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test array",
|
||||
args: struct {
|
||||
A []int `json:"a" desc:"array a" required:"true"`
|
||||
}{},
|
||||
want: mcp.NewTool("test array",
|
||||
mcp.WithArray("a", mcp.Required(), mcp.Description("array a"),
|
||||
mcp.Items(map[string]any{
|
||||
"type": "number",
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "test array of object",
|
||||
args: struct {
|
||||
A []struct {
|
||||
B int `json:"b" desc:"number b" required:"true"`
|
||||
} `json:"a" desc:"array of object a" required:"true"`
|
||||
}{},
|
||||
want: mcp.NewTool("test array of object",
|
||||
mcp.WithArray("a", mcp.Required(), mcp.Description("array of object a"),
|
||||
mcp.Items(map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"b": map[string]any{
|
||||
"type": "number",
|
||||
"description": "number b",
|
||||
"required": true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := SchemaToOptions(tt.args)
|
||||
if err != nil {
|
||||
t.Errorf("SchemaToOptions() error = %v", err)
|
||||
return
|
||||
}
|
||||
s1, _ := mcp.NewTool(tt.name, got...).MarshalJSON()
|
||||
s2, _ := tt.want.MarshalJSON()
|
||||
if !reflect.DeepEqual(s1, s2) {
|
||||
t.Errorf("\n got %v\n want %v", string(s1), string(s2))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
1567
scripts/manage.py
Normal file
1567
scripts/manage.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,42 @@
|
||||
|
||||
[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
|
||||
## Safeline Prepare
|
||||
The detection engine of the SafeLine provides services by default via Unix socket. We need to modify it to use TCP, so it can be called by the t1k plugin.
|
||||
|
||||
1.Navigate to the configuration directory of the SafeLine detection engine:
|
||||
```shell
|
||||
cd /data/safeline/resources/detector/
|
||||
```
|
||||
2.Open the `detector.yml` file in a text editor. Modify the bind configuration from Unix socket to TCP by adding the following settings:
|
||||
```yaml
|
||||
bind_addr: 0.0.0.0
|
||||
listen_port: 8000
|
||||
```
|
||||
These configuration values will override the default settings in the container, making the SafeLine engine listen on port 8000.
|
||||
|
||||
3.Next, map the container’s port 8000 to the host machine. First, navigate to the SafeLine installation directory:
|
||||
```shell
|
||||
cd /data/safeline
|
||||
```
|
||||
|
||||
4.Open the compose.yaml file in a text editor and add the ports field to the detector container to expose port 8000:
|
||||
```yaml
|
||||
...
|
||||
detect:
|
||||
ports:
|
||||
- 8000:8000
|
||||
...
|
||||
```
|
||||
|
||||
5.Save the changes and restart SafeLine with the following commands:
|
||||
```shell
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
```
|
||||
This will apply the changes and activate the new configuration.
|
||||
|
||||
## Plugin Usage
|
||||
|
||||
### Step 1: Install the plugin
|
||||
|
||||
@@ -67,7 +102,7 @@ env:
|
||||
configMapKeyRef:
|
||||
name: safeline
|
||||
key: port
|
||||
...
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
|
||||
20
sdk/ingress-nginx/ingress-nginx-safeline-1.0.4-1.rockspec
Normal file
20
sdk/ingress-nginx/ingress-nginx-safeline-1.0.4-1.rockspec
Normal file
@@ -0,0 +1,20 @@
|
||||
package = "ingress-nginx-safeline"
|
||||
version = "1.0.4-1"
|
||||
source = {
|
||||
url = "git://github.com/chaitin/ingress-nginx-safeline.git"
|
||||
}
|
||||
description = {
|
||||
summary = "Ingress-Nginx plugin for Chaitin SafeLine Web Application Firewall",
|
||||
homepage = "https://github.com/chaitin/ingress-nginx-safeline",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Xiaobing Wang <xiaobing.wang@chaitin.com>"
|
||||
}
|
||||
dependencies = {
|
||||
"lua-resty-t1k >= 1.1.5"
|
||||
}
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["safeline.main"] = "lib/safeline/main.lua"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,49 @@
|
||||
# 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
|
||||
## Safeline Prepare
|
||||
The detection engine of the SafeLine provides services by default via Unix socket. We need to modify it to use TCP, so it can be called by the t1k plugin.
|
||||
|
||||
1.Navigate to the configuration directory of the SafeLine detection engine:
|
||||
```shell
|
||||
cd /data/safeline/resources/detector/
|
||||
```
|
||||
2.Open the `detector.yml` file in a text editor. Modify the bind configuration from Unix socket to TCP by adding the following settings:
|
||||
```yaml
|
||||
bind_addr: 0.0.0.0
|
||||
listen_port: 8000
|
||||
```
|
||||
These configuration values will override the default settings in the container, making the SafeLine engine listen on port 8000.
|
||||
|
||||
3.Next, map the container’s port 8000 to the host machine. First, navigate to the SafeLine installation directory:
|
||||
```shell
|
||||
cd /data/safeline
|
||||
```
|
||||
|
||||
4.Open the compose.yaml file in a text editor and add the ports field to the detector container to expose port 8000:
|
||||
```yaml
|
||||
...
|
||||
detect:
|
||||
ports:
|
||||
- 8000:8000
|
||||
...
|
||||
```
|
||||
|
||||
5.Save the changes and restart SafeLine with the following commands:
|
||||
```shell
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
```
|
||||
This will apply the changes and activate the new configuration.
|
||||
|
||||
## Plugin Installation
|
||||
To install the plugin, run the following command in your Kong server:
|
||||
|
||||
```shell
|
||||
$ luarocks install kong-safeline
|
||||
```
|
||||
|
||||
## Configuration
|
||||
## Plugin Configuration
|
||||
You can add the plugin to your API by making the following request:
|
||||
|
||||
```shell
|
||||
|
||||
31
sdk/kong/kong-safeline-1.0.3-1.rockspec
Normal file
31
sdk/kong/kong-safeline-1.0.3-1.rockspec
Normal file
@@ -0,0 +1,31 @@
|
||||
package = "kong-safeline"
|
||||
version = "1.0.3-1"
|
||||
source = {
|
||||
url = "file://kong-safeline-1.0.3.tar.gz",
|
||||
}
|
||||
build = {
|
||||
type = "script",
|
||||
rockspec = {
|
||||
build = {
|
||||
"git clone https://github.com/chaitin/SafeLine.git",
|
||||
"cp -r sdk/kong .",
|
||||
"rm -rf SafeLine"
|
||||
}
|
||||
}
|
||||
}
|
||||
description = {
|
||||
summary = "Kong plugin for Chaitin SafeLine Web Application Firewall",
|
||||
homepage = "https://github.com/chaitin/SafeLine",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Xiaobing Wang <xiaobing.wang@chaitin.com>"
|
||||
}
|
||||
dependencies = {
|
||||
"lua-resty-t1k >= 1.1.5"
|
||||
}
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["kong.plugins.safeline.handler"] = "kong/plugins/safeline/handler.lua",
|
||||
["kong.plugins.safeline.schema"] = "kong/plugins/safeline/schema.lua"
|
||||
}
|
||||
}
|
||||
31
sdk/kong/kong-safeline-1.0.4-1.rockspec
Normal file
31
sdk/kong/kong-safeline-1.0.4-1.rockspec
Normal file
@@ -0,0 +1,31 @@
|
||||
package = "kong-safeline"
|
||||
version = "1.0.4-1"
|
||||
source = {
|
||||
url = "git://github.com/chaitin/SafeLine.git",
|
||||
}
|
||||
build = {
|
||||
type = "script",
|
||||
rockspec = {
|
||||
build = {
|
||||
"git clone https://github.com/chaitin/SafeLine.git",
|
||||
"cp -r sdk/kong .",
|
||||
"rm -rf SafeLine"
|
||||
}
|
||||
}
|
||||
}
|
||||
description = {
|
||||
summary = "Kong plugin for Chaitin SafeLine Web Application Firewall",
|
||||
homepage = "https://github.com/chaitin/SafeLine",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Xiaobing Wang <xiaobing.wang@chaitin.com>"
|
||||
}
|
||||
dependencies = {
|
||||
"lua-resty-t1k >= 1.1.5"
|
||||
}
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["kong.plugins.safeline.handler"] = "kong/plugins/safeline/handler.lua",
|
||||
["kong.plugins.safeline.schema"] = "kong/plugins/safeline/schema.lua"
|
||||
}
|
||||
}
|
||||
31
sdk/kong/kong-safeline-1.0.5-1.rockspec
Normal file
31
sdk/kong/kong-safeline-1.0.5-1.rockspec
Normal file
@@ -0,0 +1,31 @@
|
||||
package = "kong-safeline"
|
||||
version = "1.0.5-1"
|
||||
source = {
|
||||
url = "git://github.com/chaitin/SafeLine.git",
|
||||
}
|
||||
build = {
|
||||
type = "script",
|
||||
rockspec = {
|
||||
build = {
|
||||
"git clone https://github.com/chaitin/SafeLine.git",
|
||||
"cp -r sdk/kong .",
|
||||
"rm -rf SafeLine"
|
||||
}
|
||||
}
|
||||
}
|
||||
description = {
|
||||
summary = "Kong plugin for Chaitin SafeLine Web Application Firewall",
|
||||
homepage = "https://github.com/chaitin/SafeLine",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Xiaobing Wang <xiaobing.wang@chaitin.com>"
|
||||
}
|
||||
dependencies = {
|
||||
"lua-resty-t1k >= 1.1.5"
|
||||
}
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["kong.plugins.safeline.handler"] = "kong/plugins/safeline/handler.lua",
|
||||
["kong.plugins.safeline.schema"] = "kong/plugins/safeline/schema.lua"
|
||||
}
|
||||
}
|
||||
21
sdk/kong/kong-safeline-1.0.6-1.rockspec
Normal file
21
sdk/kong/kong-safeline-1.0.6-1.rockspec
Normal file
@@ -0,0 +1,21 @@
|
||||
package = "kong-safeline"
|
||||
version = "1.0.6-1"
|
||||
source = {
|
||||
url = "git://github.com/xbingW/kong-safeline.git",
|
||||
}
|
||||
description = {
|
||||
summary = "Kong plugin for Chaitin SafeLine Web Application Firewall",
|
||||
homepage = "https://github.com/chaitin/SafeLine",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Xiaobing Wang <xiaobing.wang@chaitin.com>"
|
||||
}
|
||||
dependencies = {
|
||||
"lua-resty-t1k >= 1.1.5"
|
||||
}
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["kong.plugins.safeline.handler"] = "kong/plugins/safeline/handler.lua",
|
||||
["kong.plugins.safeline.schema"] = "kong/plugins/safeline/schema.lua"
|
||||
}
|
||||
}
|
||||
21
sdk/kong/kong-safeline-1.0.7-1.rockspec
Normal file
21
sdk/kong/kong-safeline-1.0.7-1.rockspec
Normal file
@@ -0,0 +1,21 @@
|
||||
package = "kong-safeline"
|
||||
version = "1.0.7-1"
|
||||
source = {
|
||||
url = "git://github.com/chaitin/kong-safeline.git",
|
||||
}
|
||||
description = {
|
||||
summary = "Kong plugin for Chaitin SafeLine Web Application Firewall",
|
||||
homepage = "https://github.com/chaitin/SafeLine",
|
||||
license = "Apache License 2.0",
|
||||
maintainer = "Xiaobing Wang <xiaobing.wang@chaitin.com>"
|
||||
}
|
||||
dependencies = {
|
||||
"lua-resty-t1k >= 1.1.5"
|
||||
}
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["kong.plugins.safeline.handler"] = "kong/plugins/safeline/handler.lua",
|
||||
["kong.plugins.safeline.schema"] = "kong/plugins/safeline/schema.lua"
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,11 @@ function SafelineHandler:access(conf)
|
||||
if not ok then
|
||||
kong.log.err("failed to detector req: ", err)
|
||||
end
|
||||
if result then
|
||||
if result and result.status 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)
|
||||
return kong.response.exit(tonumber(result.status), msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
1
sdk/traefik-safeline
Submodule
1
sdk/traefik-safeline
Submodule
Submodule sdk/traefik-safeline added at bc2258ccc3
@@ -1,9 +0,0 @@
|
||||
displayName: Chaitin Safeline WAF
|
||||
type: middleware
|
||||
|
||||
import: github.com/chaitin/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
|
||||
@@ -1,62 +0,0 @@
|
||||
# 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/chaitin/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
|
||||
```
|
||||
@@ -1,7 +0,0 @@
|
||||
module github.com/chaitin/traefik-safeline
|
||||
|
||||
go 1.17
|
||||
|
||||
require golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
|
||||
require github.com/chaitin/t1k-go v1.5.0
|
||||
@@ -1,99 +0,0 @@
|
||||
package traefik_safeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
t1k "github.com/chaitin/t1k-go"
|
||||
)
|
||||
|
||||
// Package example a example plugin.
|
||||
|
||||
// Config the plugin configuration.
|
||||
type Config struct {
|
||||
// Addr is the address for the detector
|
||||
Addr string `yaml:"addr"`
|
||||
PoolSize int `yaml:"pool_size"`
|
||||
}
|
||||
|
||||
// CreateConfig creates the default plugin configuration.
|
||||
func CreateConfig() *Config {
|
||||
return &Config{
|
||||
Addr: "",
|
||||
PoolSize: 100,
|
||||
}
|
||||
}
|
||||
|
||||
// Safeline a plugin.
|
||||
type Safeline struct {
|
||||
next http.Handler
|
||||
server *t1k.Server
|
||||
name string
|
||||
config *Config
|
||||
logger *log.Logger
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// New created a new plugin.
|
||||
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
|
||||
logger := log.New(os.Stdout, "safeline", log.LstdFlags)
|
||||
logger.Printf("config: %+v", config)
|
||||
return &Safeline{
|
||||
next: next,
|
||||
name: name,
|
||||
config: config,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Safeline) initServer() error {
|
||||
if s.server != nil {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.server == nil {
|
||||
server, err := t1k.NewWithPoolSize(s.config.Addr, s.config.PoolSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.server = server
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Safeline) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
s.logger.Printf("panic: %s", r)
|
||||
}
|
||||
}()
|
||||
if err := s.initServer(); err != nil {
|
||||
s.logger.Printf("error in initServer: %s", err)
|
||||
s.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
rw.Header().Set("X-Chaitin-waf", "safeline")
|
||||
result, err := s.server.DetectHttpRequest(req)
|
||||
if err != nil {
|
||||
s.logger.Printf("error in detection: \n%+v\n", err)
|
||||
s.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
if result.Blocked() {
|
||||
rw.WriteHeader(result.StatusCode())
|
||||
msg := fmt.Sprintf(`{"code": %d, "success":false, "message": "blocked by Chaitin SafeLine Web Application Firewall", "event_id": "%s"}`,
|
||||
result.StatusCode(),
|
||||
result.EventID(),
|
||||
)
|
||||
_, _ = rw.Write([]byte(msg))
|
||||
return
|
||||
}
|
||||
s.next.ServeHTTP(rw, req)
|
||||
//rw.WriteHeader(http.StatusForbidden)
|
||||
//_, _ = rw.Write([]byte("Inject by safeline\n"))
|
||||
}
|
||||
201
sdk/traefik/vendor/github.com/chaitin/t1k-go/License
generated
vendored
201
sdk/traefik/vendor/github.com/chaitin/t1k-go/License
generated
vendored
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2023 Beijing Chaitin Technology Co., Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
5
sdk/traefik/vendor/github.com/chaitin/t1k-go/README.md
generated
vendored
5
sdk/traefik/vendor/github.com/chaitin/t1k-go/README.md
generated
vendored
@@ -1,5 +0,0 @@
|
||||
# t1k-go
|
||||
|
||||
Go implementation of the T1K protocol for [Chaitin/SafeLine](https://github.com/chaitin/safeline) Web Application Firewall.
|
||||
|
||||
|
||||
101
sdk/traefik/vendor/github.com/chaitin/t1k-go/conn.go
generated
vendored
101
sdk/traefik/vendor/github.com/chaitin/t1k-go/conn.go
generated
vendored
@@ -1,101 +0,0 @@
|
||||
package t1k
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/chaitin/t1k-go/detection"
|
||||
"github.com/chaitin/t1k-go/t1k"
|
||||
|
||||
"github.com/chaitin/t1k-go/misc"
|
||||
)
|
||||
|
||||
type conn struct {
|
||||
socket net.Conn
|
||||
server *Server
|
||||
failing bool
|
||||
}
|
||||
|
||||
func makeConn(socket net.Conn, server *Server) *conn {
|
||||
return &conn{
|
||||
socket: socket,
|
||||
server: server,
|
||||
failing: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) onErr(err error) {
|
||||
if err != nil {
|
||||
// re-open socket to recover from possible error state
|
||||
c.socket.Close()
|
||||
sock, errConnect := c.server.socketFactory()
|
||||
if errConnect != nil {
|
||||
c.failing = true
|
||||
return
|
||||
}
|
||||
c.socket = sock
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) Close() {
|
||||
if c.socket == nil {
|
||||
return
|
||||
}
|
||||
c.socket.Close()
|
||||
}
|
||||
|
||||
func (c *conn) DetectRequestInCtx(dc *detection.DetectionContext) (*detection.Result, error) {
|
||||
ret, err := DetectRequestInCtx(c.socket, dc)
|
||||
c.onErr(err)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (c *conn) DetectResponseInCtx(dc *detection.DetectionContext) (*detection.Result, error) {
|
||||
ret, err := DetectResponseInCtx(c.socket, dc)
|
||||
c.onErr(err)
|
||||
return ret, misc.ErrorWrap(err, "")
|
||||
}
|
||||
|
||||
func (c *conn) Detect(dc *detection.DetectionContext) (*detection.Result, *detection.Result, error) {
|
||||
retReq, retRsp, err := Detect(c.socket, dc)
|
||||
c.onErr(err)
|
||||
return retReq, retRsp, misc.ErrorWrap(err, "")
|
||||
}
|
||||
|
||||
func (c *conn) DetectHttpRequest(req *http.Request) (*detection.Result, error) {
|
||||
ret, err := DetectHttpRequest(c.socket, req)
|
||||
c.onErr(err)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (c *conn) DetectRequest(req detection.Request) (*detection.Result, error) {
|
||||
ret, err := DetectRequest(c.socket, req)
|
||||
c.onErr(err)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (c *conn) Heartbeat() {
|
||||
err := DoHeartbeat(c.socket)
|
||||
c.onErr(err)
|
||||
}
|
||||
|
||||
func (c *conn) WriteSection(sec t1k.Section) error {
|
||||
err := t1k.WriteSection(sec, c.socket)
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
|
||||
func (c *conn) ReadSection() (t1k.Section, error) {
|
||||
sec, err := t1k.ReadSection(c.socket)
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
return sec, nil
|
||||
}
|
||||
|
||||
func (c *conn) ReadFullSection() (t1k.Section, error) {
|
||||
sec, err := t1k.ReadFullSection(c.socket)
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
return sec, nil
|
||||
}
|
||||
253
sdk/traefik/vendor/github.com/chaitin/t1k-go/detect.go
generated
vendored
253
sdk/traefik/vendor/github.com/chaitin/t1k-go/detect.go
generated
vendored
@@ -1,253 +0,0 @@
|
||||
package t1k
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/chaitin/t1k-go/detection"
|
||||
"github.com/chaitin/t1k-go/t1k"
|
||||
|
||||
"github.com/chaitin/t1k-go/misc"
|
||||
)
|
||||
|
||||
func writeDetectionRequest(w io.Writer, req detection.Request) error {
|
||||
{
|
||||
data, err := req.Header()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sec := t1k.MakeSimpleSection(t1k.TAG_HEADER|t1k.MASK_FIRST, data)
|
||||
err = t1k.WriteSection(sec, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
{
|
||||
bodySize, bodyReadCloser, err := req.Body()
|
||||
if err == nil {
|
||||
defer bodyReadCloser.Close()
|
||||
sec := t1k.MakeReaderSection(t1k.TAG_BODY, bodySize, bodyReadCloser)
|
||||
err = t1k.WriteSection(sec, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
data, err := req.Extra()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sec := t1k.MakeSimpleSection(t1k.TAG_EXTRA, data)
|
||||
err = t1k.WriteSection(sec, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
{
|
||||
sec := t1k.MakeSimpleSection(t1k.TAG_VERSION|t1k.MASK_LAST, []byte("Proto:2\n"))
|
||||
err := t1k.WriteSection(sec, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeDetectionResponse(w io.Writer, rsp detection.Response) error {
|
||||
{
|
||||
data, err := rsp.RequestHeader()
|
||||
if err != nil {
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
sec := t1k.MakeSimpleSection(t1k.TAG_HEADER|t1k.MASK_FIRST, data)
|
||||
err = t1k.WriteSection(sec, w)
|
||||
if err != nil {
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
}
|
||||
{
|
||||
data, err := rsp.Header()
|
||||
if err != nil {
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
sec := t1k.MakeSimpleSection(t1k.TAG_RSP_HEADER, data)
|
||||
err = t1k.WriteSection(sec, w)
|
||||
if err != nil {
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
}
|
||||
{
|
||||
bodySize, bodyReadCloser, err := rsp.Body()
|
||||
if err == nil {
|
||||
defer bodyReadCloser.Close()
|
||||
sec := t1k.MakeReaderSection(t1k.TAG_RSP_BODY, bodySize, bodyReadCloser)
|
||||
err = t1k.WriteSection(sec, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
data, err := rsp.Extra()
|
||||
if err != nil {
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
sec := t1k.MakeSimpleSection(t1k.TAG_RSP_EXTRA, data)
|
||||
err = t1k.WriteSection(sec, w)
|
||||
if err != nil {
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
}
|
||||
{
|
||||
sec := t1k.MakeSimpleSection(t1k.TAG_VERSION, []byte("Proto:2\n"))
|
||||
err := t1k.WriteSection(sec, w)
|
||||
if err != nil {
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
}
|
||||
{
|
||||
data, err := rsp.T1KContext()
|
||||
if err != nil {
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
sec := t1k.MakeSimpleSection(t1k.TAG_CONTEXT|t1k.MASK_LAST, data)
|
||||
err = t1k.WriteSection(sec, w)
|
||||
if err != nil {
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readDetectionResult(r io.Reader) (*detection.Result, error) {
|
||||
var ret detection.Result
|
||||
parseSection := func(sec t1k.Section) error {
|
||||
var buf bytes.Buffer
|
||||
err := sec.WriteBody(&buf)
|
||||
if err != nil {
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
tag := sec.Header().Tag.Strip()
|
||||
switch tag {
|
||||
case t1k.TAG_HEADER:
|
||||
if len(buf.Bytes()) != 1 {
|
||||
return fmt.Errorf("len(T1K_HEADER) != 1")
|
||||
}
|
||||
ret.Head = buf.Bytes()[0]
|
||||
case t1k.TAG_BODY:
|
||||
ret.Body = buf.Bytes()
|
||||
case t1k.TAG_ALOG:
|
||||
ret.Alog = buf.Bytes()
|
||||
case t1k.TAG_EXTRA_HEADER:
|
||||
ret.ExtraHeader = buf.Bytes()
|
||||
case t1k.TAG_EXTRA_BODY:
|
||||
ret.ExtraBody = buf.Bytes()
|
||||
case t1k.TAG_CONTEXT:
|
||||
ret.T1KContext = buf.Bytes()
|
||||
case t1k.TAG_COOKIE:
|
||||
ret.Cookie = buf.Bytes()
|
||||
case t1k.TAG_WEB_LOG:
|
||||
ret.WebLog = buf.Bytes()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
sec, err := t1k.ReadFullSection(r)
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
if !sec.Header().Tag.IsFirst() {
|
||||
return nil, fmt.Errorf("first section IsFirst != true, middle of another msg or corrupt stream, with <%x>", sec.Header().Tag)
|
||||
}
|
||||
for {
|
||||
err = parseSection(sec)
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
if sec.Header().Tag.IsLast() {
|
||||
break
|
||||
}
|
||||
sec, err = t1k.ReadSection(r)
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func doDetectRequest(s io.ReadWriter, req detection.Request) (*detection.Result, error) {
|
||||
err := writeDetectionRequest(s, req)
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
ret, err := readDetectionResult(s)
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
ret.Objective = detection.RO_REQUEST
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func doDetectResponse(s io.ReadWriter, rsp detection.Response) (*detection.Result, error) {
|
||||
err := writeDetectionResponse(s, rsp)
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
ret, err := readDetectionResult(s)
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
ret.Objective = detection.RO_RESPONSE
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func DetectRequestInCtx(s io.ReadWriter, dc *detection.DetectionContext) (*detection.Result, error) {
|
||||
ret, err := doDetectRequest(s, dc.Request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dc.ProcessResult(ret)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func DetectResponseInCtx(s io.ReadWriter, dc *detection.DetectionContext) (*detection.Result, error) {
|
||||
ret, err := doDetectResponse(s, dc.Response)
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
dc.ProcessResult(ret)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func Detect(s io.ReadWriter, dc *detection.DetectionContext) (*detection.Result, *detection.Result, error) {
|
||||
var reqResult *detection.Result
|
||||
var rspResult *detection.Result
|
||||
if dc.Request != nil {
|
||||
ret, err := doDetectRequest(s, dc.Request)
|
||||
if err != nil {
|
||||
return nil, nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
reqResult = ret
|
||||
dc.ProcessResult(reqResult)
|
||||
}
|
||||
if dc.Response != nil {
|
||||
ret, err := doDetectResponse(s, dc.Response)
|
||||
if err != nil {
|
||||
return nil, nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
rspResult = ret
|
||||
dc.ProcessResult(rspResult)
|
||||
}
|
||||
return reqResult, rspResult, nil
|
||||
}
|
||||
|
||||
func DetectHttpRequest(s io.ReadWriter, req *http.Request) (*detection.Result, error) {
|
||||
dc, _ := detection.MakeContextWithRequest(req)
|
||||
return doDetectRequest(s, detection.MakeHttpRequestInCtx(req, dc))
|
||||
}
|
||||
|
||||
func DetectRequest(s io.ReadWriter, req detection.Request) (*detection.Result, error) {
|
||||
return doDetectRequest(s, req)
|
||||
}
|
||||
88
sdk/traefik/vendor/github.com/chaitin/t1k-go/detection/context.go
generated
vendored
88
sdk/traefik/vendor/github.com/chaitin/t1k-go/detection/context.go
generated
vendored
@@ -1,88 +0,0 @@
|
||||
package detection
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/chaitin/t1k-go/misc"
|
||||
)
|
||||
|
||||
type DetectionContext struct {
|
||||
UUID string
|
||||
Scheme string
|
||||
ProxyName string
|
||||
RemoteAddr string
|
||||
Protocol string
|
||||
RemotePort uint16
|
||||
LocalAddr string
|
||||
LocalPort uint16
|
||||
ReqBeginTime int64
|
||||
RspBeginTime int64
|
||||
|
||||
T1KContext []byte
|
||||
|
||||
Request Request
|
||||
Response Response
|
||||
}
|
||||
|
||||
func New() *DetectionContext {
|
||||
return &DetectionContext{
|
||||
UUID: misc.GenUUID(),
|
||||
Scheme: "http",
|
||||
ProxyName: "go-sdk",
|
||||
RemoteAddr: "127.0.0.1",
|
||||
RemotePort: 30001,
|
||||
LocalAddr: "127.0.0.1",
|
||||
LocalPort: 80,
|
||||
Protocol: "HTTP/1.1",
|
||||
}
|
||||
}
|
||||
|
||||
func MakeContextWithRequest(req *http.Request) (*DetectionContext, error) {
|
||||
if req == nil {
|
||||
return nil, errors.New("nil http.request or response")
|
||||
}
|
||||
wrapReq := &HttpRequest{
|
||||
req: req,
|
||||
}
|
||||
|
||||
// ignore GetRemoteIP error,not sure request record remote ip
|
||||
remoteIP, _ := wrapReq.GetRemoteIP()
|
||||
remotePort, _ := wrapReq.GetRemotePort()
|
||||
|
||||
localAddr, err := wrapReq.GetUpstreamAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localPort, err := wrapReq.GetUpstreamPort()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
if req.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
context := &DetectionContext{
|
||||
UUID: misc.GenUUID(),
|
||||
Scheme: scheme,
|
||||
ProxyName: "go-sdk",
|
||||
RemoteAddr: remoteIP,
|
||||
RemotePort: remotePort,
|
||||
LocalAddr: localAddr,
|
||||
LocalPort: localPort,
|
||||
ReqBeginTime: misc.Now(),
|
||||
Request: wrapReq,
|
||||
Protocol: req.Proto,
|
||||
}
|
||||
wrapReq.dc = context
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func (dc *DetectionContext) ProcessResult(r *Result) {
|
||||
if r.Objective == RO_REQUEST {
|
||||
dc.T1KContext = r.T1KContext
|
||||
}
|
||||
}
|
||||
93
sdk/traefik/vendor/github.com/chaitin/t1k-go/detection/extra.go
generated
vendored
93
sdk/traefik/vendor/github.com/chaitin/t1k-go/detection/extra.go
generated
vendored
@@ -1,93 +0,0 @@
|
||||
package detection
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/chaitin/t1k-go/misc"
|
||||
)
|
||||
|
||||
func MakeRequestExtra(
|
||||
scheme string,
|
||||
proxyName string,
|
||||
remoteAddr string,
|
||||
remotePort uint16,
|
||||
localAddr string,
|
||||
localPort uint16,
|
||||
uuid string,
|
||||
hasRspIfOK string,
|
||||
hasRspIfBlock string,
|
||||
reqBeginTime int64,
|
||||
) []byte {
|
||||
format := "Scheme:%s\n" +
|
||||
"ProxyName:%s\n" +
|
||||
"RemoteAddr:%s\n" +
|
||||
"RemotePort:%d\n" +
|
||||
"LocalAddr:%s\n" +
|
||||
"LocalPort:%d\n" +
|
||||
"UUID:%s\n" +
|
||||
"HasRspIfOK:%s\n" +
|
||||
"HasRspIfBlock:%s\n" +
|
||||
"ReqBeginTime:%d\n"
|
||||
|
||||
return []byte(fmt.Sprintf(
|
||||
format,
|
||||
scheme,
|
||||
proxyName,
|
||||
remoteAddr,
|
||||
remotePort,
|
||||
localAddr,
|
||||
localPort,
|
||||
uuid,
|
||||
hasRspIfOK,
|
||||
hasRspIfBlock,
|
||||
reqBeginTime,
|
||||
))
|
||||
}
|
||||
|
||||
func MakeResponseExtra(
|
||||
scheme string,
|
||||
proxyName string,
|
||||
remoteAddr string,
|
||||
remotePort uint16,
|
||||
localAddr string,
|
||||
localPort uint16,
|
||||
uuid string,
|
||||
rspBeginTime int64,
|
||||
) []byte {
|
||||
format := "Scheme:%s\n" +
|
||||
"ProxyName:%s\n" +
|
||||
"RemoteAddr:%s\n" +
|
||||
"RemotePort:%d\n" +
|
||||
"LocalAddr:%s\n" +
|
||||
"LocalPort:%d\n" +
|
||||
"UUID:%s\n" +
|
||||
"RspBeginTime:%d\n"
|
||||
|
||||
return []byte(fmt.Sprintf(
|
||||
format,
|
||||
scheme,
|
||||
proxyName,
|
||||
remoteAddr,
|
||||
remotePort,
|
||||
localAddr,
|
||||
localPort,
|
||||
uuid,
|
||||
rspBeginTime,
|
||||
))
|
||||
}
|
||||
|
||||
func PlaceholderRequestExtra(uuid string) []byte {
|
||||
return MakeRequestExtra("http", "go-sdk", "127.0.0.1", 30001, "127.0.0.1", 80, uuid, "n", "n", misc.Now())
|
||||
}
|
||||
|
||||
func GenRequestExtra(dc *DetectionContext) []byte {
|
||||
hasRsp := "u"
|
||||
if dc.Response != nil {
|
||||
hasRsp = "y"
|
||||
}
|
||||
return MakeRequestExtra(dc.Scheme, dc.ProxyName, dc.RemoteAddr, dc.RemotePort, dc.LocalAddr, dc.LocalPort, dc.UUID, hasRsp, hasRsp, dc.ReqBeginTime)
|
||||
}
|
||||
|
||||
func GenResponseExtra(dc *DetectionContext) []byte {
|
||||
return MakeResponseExtra(dc.Scheme, dc.ProxyName, dc.RemoteAddr, dc.RemotePort, dc.LocalAddr, dc.LocalPort, dc.UUID, dc.RspBeginTime)
|
||||
}
|
||||
132
sdk/traefik/vendor/github.com/chaitin/t1k-go/detection/request.go
generated
vendored
132
sdk/traefik/vendor/github.com/chaitin/t1k-go/detection/request.go
generated
vendored
@@ -1,132 +0,0 @@
|
||||
package detection
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/chaitin/t1k-go/misc"
|
||||
)
|
||||
|
||||
type Request interface {
|
||||
Header() ([]byte, error)
|
||||
Body() (uint32, io.ReadCloser, error)
|
||||
Extra() ([]byte, error)
|
||||
}
|
||||
|
||||
type HttpRequest struct {
|
||||
req *http.Request
|
||||
dc *DetectionContext // this is optional
|
||||
}
|
||||
|
||||
func MakeHttpRequest(req *http.Request) *HttpRequest {
|
||||
return &HttpRequest{
|
||||
req: req,
|
||||
}
|
||||
}
|
||||
|
||||
func MakeHttpRequestInCtx(req *http.Request, dc *DetectionContext) *HttpRequest {
|
||||
ret := &HttpRequest{
|
||||
req: req,
|
||||
dc: dc,
|
||||
}
|
||||
dc.Request = ret
|
||||
|
||||
if dc.ReqBeginTime == 0 {
|
||||
dc.ReqBeginTime = misc.Now()
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (r *HttpRequest) GetUpstreamAddress() (string, error) {
|
||||
if r.req.Host == "" {
|
||||
return "", errors.New("empty Host in request")
|
||||
}
|
||||
host, _, err := net.SplitHostPort(r.req.Host)
|
||||
if err != nil {
|
||||
return r.req.Host, nil // OK; there probably was no port
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
func (r *HttpRequest) GetUpstreamPort() (uint16, error) {
|
||||
_, port, err := net.SplitHostPort(r.req.Host)
|
||||
if err != nil {
|
||||
if r.req.TLS != nil {
|
||||
return 443, nil
|
||||
} else {
|
||||
return 80, nil
|
||||
}
|
||||
}
|
||||
if portNum, err := strconv.Atoi(port); err == nil {
|
||||
return uint16(portNum), nil
|
||||
}
|
||||
return 0, errors.New("wrong value of port")
|
||||
}
|
||||
|
||||
func (r *HttpRequest) GetRemoteIP() (string, error) {
|
||||
host, _, err := net.SplitHostPort(r.req.RemoteAddr)
|
||||
if err != nil {
|
||||
return r.req.RemoteAddr, nil
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
func (r *HttpRequest) GetRemotePort() (uint16, error) {
|
||||
_, port, _ := net.SplitHostPort(r.req.RemoteAddr)
|
||||
if portNum, err := strconv.Atoi(port); err == nil {
|
||||
return uint16(portNum), nil
|
||||
}
|
||||
return 0, errors.New("wrong value of port")
|
||||
}
|
||||
|
||||
func (r *HttpRequest) Header() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
proto := r.req.Proto
|
||||
if r.dc != nil {
|
||||
if r.dc.Protocol != "" {
|
||||
proto = r.dc.Protocol
|
||||
} else {
|
||||
r.dc.Protocol = 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() (uint32, io.ReadCloser, error) {
|
||||
bodyBytes, err := io.ReadAll(r.req.Body)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
r.req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return uint32(len(bodyBytes)), io.NopCloser(bytes.NewReader(bodyBytes)), nil
|
||||
}
|
||||
|
||||
func (r *HttpRequest) Extra() ([]byte, error) {
|
||||
if r.dc == nil {
|
||||
return PlaceholderRequestExtra(misc.GenUUID()), nil
|
||||
}
|
||||
return GenRequestExtra(r.dc), nil
|
||||
}
|
||||
72
sdk/traefik/vendor/github.com/chaitin/t1k-go/detection/response.go
generated
vendored
72
sdk/traefik/vendor/github.com/chaitin/t1k-go/detection/response.go
generated
vendored
@@ -1,72 +0,0 @@
|
||||
package detection
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/chaitin/t1k-go/misc"
|
||||
)
|
||||
|
||||
type Response interface {
|
||||
RequestHeader() ([]byte, error)
|
||||
Header() ([]byte, error)
|
||||
Body() (uint32, io.ReadCloser, error)
|
||||
Extra() ([]byte, error)
|
||||
T1KContext() ([]byte, error)
|
||||
}
|
||||
|
||||
type HttpResponse struct {
|
||||
rsp *http.Response
|
||||
dc *DetectionContext // this is a must-have
|
||||
}
|
||||
|
||||
func MakeHttpResponseInCtx(rsp *http.Response, dc *DetectionContext) *HttpResponse {
|
||||
ret := &HttpResponse{
|
||||
rsp: rsp,
|
||||
dc: dc,
|
||||
}
|
||||
dc.Response = ret
|
||||
dc.RspBeginTime = misc.Now()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (r *HttpResponse) RequestHeader() ([]byte, error) {
|
||||
return r.dc.Request.Header()
|
||||
}
|
||||
|
||||
func (r *HttpResponse) Header() ([]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() (uint32, io.ReadCloser, error) {
|
||||
bodyBytes, err := io.ReadAll(r.rsp.Body)
|
||||
if err != nil {
|
||||
return 0, nil, misc.ErrorWrapf(err, "get body size %d", len(bodyBytes))
|
||||
}
|
||||
r.rsp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return uint32(len(bodyBytes)), io.NopCloser(bytes.NewReader(bodyBytes)), nil
|
||||
}
|
||||
|
||||
func (r *HttpResponse) Extra() ([]byte, error) {
|
||||
return GenResponseExtra(r.dc), nil
|
||||
}
|
||||
|
||||
func (r *HttpResponse) T1KContext() ([]byte, error) {
|
||||
return r.dc.T1KContext, nil
|
||||
}
|
||||
76
sdk/traefik/vendor/github.com/chaitin/t1k-go/detection/result.go
generated
vendored
76
sdk/traefik/vendor/github.com/chaitin/t1k-go/detection/result.go
generated
vendored
@@ -1,76 +0,0 @@
|
||||
package detection
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ResultObjective int
|
||||
|
||||
const (
|
||||
RO_REQUEST ResultObjective = 0
|
||||
RO_RESPONSE ResultObjective = 1
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
Objective ResultObjective
|
||||
Head byte
|
||||
Body []byte
|
||||
Alog []byte
|
||||
ExtraHeader []byte
|
||||
ExtraBody []byte
|
||||
T1KContext []byte
|
||||
Cookie []byte
|
||||
WebLog []byte
|
||||
}
|
||||
|
||||
func (r *Result) Passed() bool {
|
||||
return r.Head == '.'
|
||||
}
|
||||
|
||||
func (r *Result) Blocked() bool {
|
||||
return !r.Passed()
|
||||
}
|
||||
|
||||
func (r *Result) 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 *Result) 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 *Result) 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]
|
||||
}
|
||||
17
sdk/traefik/vendor/github.com/chaitin/t1k-go/heartbeat.go
generated
vendored
17
sdk/traefik/vendor/github.com/chaitin/t1k-go/heartbeat.go
generated
vendored
@@ -1,17 +0,0 @@
|
||||
package t1k
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/chaitin/t1k-go/t1k"
|
||||
)
|
||||
|
||||
func DoHeartbeat(s io.ReadWriter) error {
|
||||
h := t1k.MakeHeader(t1k.MASK_FIRST|t1k.MASK_LAST, 0)
|
||||
_, err := s.Write(h.Serialize())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = readDetectionResult(s)
|
||||
return err
|
||||
}
|
||||
81
sdk/traefik/vendor/github.com/chaitin/t1k-go/misc/dump_hex.go
generated
vendored
81
sdk/traefik/vendor/github.com/chaitin/t1k-go/misc/dump_hex.go
generated
vendored
@@ -1,81 +0,0 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var asciiPrintableMap map[byte]bool
|
||||
|
||||
func init() {
|
||||
asciiPrintableMap = make(map[byte]bool)
|
||||
for i := 0; i < 256; i++ {
|
||||
asciiPrintableMap[byte(i)] = false
|
||||
}
|
||||
s := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ "
|
||||
for _, ch := range []byte(s) {
|
||||
asciiPrintableMap[ch] = true
|
||||
}
|
||||
}
|
||||
|
||||
func isAsciiPrintable(b byte) bool {
|
||||
return asciiPrintableMap[b]
|
||||
}
|
||||
|
||||
func DumpHex(w io.Writer, b []byte) error {
|
||||
lines := len(b) / 16
|
||||
for i := 0; i < lines; i += 1 {
|
||||
srep := ""
|
||||
for j := 0; j < 16; j += 1 {
|
||||
it := b[16*i+j]
|
||||
_, err := w.Write([]byte(fmt.Sprintf("%02x ", it)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isAsciiPrintable(it) {
|
||||
srep += string([]byte{it})
|
||||
} else {
|
||||
srep += "."
|
||||
}
|
||||
}
|
||||
_, err := w.Write([]byte("| " + srep + "\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
remain := len(b) - 16*lines
|
||||
srep := ""
|
||||
j := 0
|
||||
for ; j < remain; j += 1 {
|
||||
it := b[16*lines+j]
|
||||
_, err := w.Write([]byte(fmt.Sprintf("%02x ", it)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isAsciiPrintable(it) {
|
||||
srep += string([]byte{it})
|
||||
} else {
|
||||
srep += "."
|
||||
}
|
||||
}
|
||||
for ; j < 16; j++ {
|
||||
_, err := w.Write([]byte(" "))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srep += " "
|
||||
}
|
||||
_, err := w.Write([]byte("| " + srep + "\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PrintHex(b []byte) {
|
||||
err := DumpHex(os.Stdout, b)
|
||||
if err != nil {
|
||||
fmt.Printf("error in PrintHex() : %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
58
sdk/traefik/vendor/github.com/chaitin/t1k-go/misc/errors.go
generated
vendored
58
sdk/traefik/vendor/github.com/chaitin/t1k-go/misc/errors.go
generated
vendored
@@ -1,58 +0,0 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type wrapError struct {
|
||||
message string
|
||||
next error
|
||||
frame xerrors.Frame
|
||||
}
|
||||
|
||||
func (e *wrapError) Unwrap() error {
|
||||
return e.next
|
||||
}
|
||||
|
||||
func (e *wrapError) Error() string {
|
||||
if e.next == nil {
|
||||
return e.message
|
||||
}
|
||||
return fmt.Sprintf("%s: %v", e.message, e.next)
|
||||
}
|
||||
|
||||
func (e *wrapError) Format(f fmt.State, c rune) {
|
||||
xerrors.FormatError(e, f, c)
|
||||
}
|
||||
|
||||
func (e *wrapError) FormatError(p xerrors.Printer) error {
|
||||
p.Print(e.message)
|
||||
if p.Detail() {
|
||||
e.frame.Format(p)
|
||||
}
|
||||
return e.next
|
||||
}
|
||||
|
||||
func wrap(err error, message string, skip int) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &wrapError{
|
||||
message: message,
|
||||
next: err,
|
||||
frame: xerrors.Caller(skip),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns a error annotating `err` with `message` and the caller's frame.
|
||||
// Wrap returns nil if `err` is nil.
|
||||
func ErrorWrap(err error, message string) error {
|
||||
return wrap(err, message, 2)
|
||||
}
|
||||
|
||||
// Wrapf returns a error annotating `err` with `message` formatted and the caller's frame.
|
||||
func ErrorWrapf(err error, message string, args ...interface{}) error {
|
||||
return wrap(err, fmt.Sprintf(message, args...), 2)
|
||||
}
|
||||
17
sdk/traefik/vendor/github.com/chaitin/t1k-go/misc/gen_uuid.go
generated
vendored
17
sdk/traefik/vendor/github.com/chaitin/t1k-go/misc/gen_uuid.go
generated
vendored
@@ -1,17 +0,0 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
var (
|
||||
rng *MT19937 = NewMT19937()
|
||||
)
|
||||
|
||||
func GenUUID() string {
|
||||
u := make([]byte, 16)
|
||||
rng.RandBytes(u)
|
||||
u[6] = (u[6] | 0x40) & 0x4F
|
||||
u[8] = (u[8] | 0x80) & 0xBF
|
||||
return hex.EncodeToString(u)
|
||||
}
|
||||
102
sdk/traefik/vendor/github.com/chaitin/t1k-go/misc/mt19937.go
generated
vendored
102
sdk/traefik/vendor/github.com/chaitin/t1k-go/misc/mt19937.go
generated
vendored
@@ -1,102 +0,0 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
n = 312
|
||||
m = 156
|
||||
seedMask = 170298
|
||||
hiMask uint64 = 0xffffffff80000000
|
||||
loMask uint64 = 0x000000007fffffff
|
||||
matrixA uint64 = 0xB5026F5AA96619E9
|
||||
)
|
||||
|
||||
type MT19937 struct {
|
||||
state []uint64
|
||||
index int
|
||||
mut sync.Mutex
|
||||
}
|
||||
|
||||
func NewMT19937WithSeed(seed int64) *MT19937 {
|
||||
mt := &MT19937{
|
||||
state: make([]uint64, n),
|
||||
index: n,
|
||||
}
|
||||
|
||||
mt.mut.Lock()
|
||||
defer mt.mut.Unlock()
|
||||
|
||||
mt.state[0] = uint64(seed)
|
||||
for i := uint64(1); i < n; i++ {
|
||||
mt.state[i] = 6364136223846793005*(mt.state[i-1]^(mt.state[i-1]>>62)) + i
|
||||
}
|
||||
|
||||
return mt
|
||||
}
|
||||
|
||||
func NewMT19937() *MT19937 {
|
||||
var seed int64
|
||||
|
||||
b := make([]byte, 8)
|
||||
if _, err := rand.Read(b); err == nil {
|
||||
seed = int64(binary.LittleEndian.Uint64(b[:]))
|
||||
}
|
||||
|
||||
seed = seed ^ time.Now().UnixNano() ^ seedMask
|
||||
|
||||
return NewMT19937WithSeed(seed)
|
||||
}
|
||||
|
||||
func (mt *MT19937) Uint64() uint64 {
|
||||
mt.mut.Lock()
|
||||
defer mt.mut.Unlock()
|
||||
|
||||
x := mt.state
|
||||
if mt.index >= n {
|
||||
for i := 0; i < n-m; i++ {
|
||||
y := (x[i] & hiMask) | (x[i+1] & loMask)
|
||||
x[i] = x[i+m] ^ (y >> 1) ^ ((y & 1) * matrixA)
|
||||
}
|
||||
for i := n - m; i < n-1; i++ {
|
||||
y := (x[i] & hiMask) | (x[i+1] & loMask)
|
||||
x[i] = x[i+(m-n)] ^ (y >> 1) ^ ((y & 1) * matrixA)
|
||||
}
|
||||
y := (x[n-1] & hiMask) | (x[0] & loMask)
|
||||
x[n-1] = x[m-1] ^ (y >> 1) ^ ((y & 1) * matrixA)
|
||||
mt.index = 0
|
||||
}
|
||||
y := x[mt.index]
|
||||
y ^= (y >> 29) & 0x5555555555555555
|
||||
y ^= (y << 17) & 0x71D67FFFEDA60000
|
||||
y ^= (y << 37) & 0xFFF7EEE000000000
|
||||
y ^= (y >> 43)
|
||||
mt.index++
|
||||
return y
|
||||
}
|
||||
|
||||
func (mt *MT19937) RandBytes(p []byte) {
|
||||
for len(p) >= 8 {
|
||||
val := mt.Uint64()
|
||||
p[0] = byte(val)
|
||||
p[1] = byte(val >> 8)
|
||||
p[2] = byte(val >> 16)
|
||||
p[3] = byte(val >> 24)
|
||||
p[4] = byte(val >> 32)
|
||||
p[5] = byte(val >> 40)
|
||||
p[6] = byte(val >> 48)
|
||||
p[7] = byte(val >> 56)
|
||||
p = p[8:]
|
||||
}
|
||||
if len(p) > 0 {
|
||||
val := mt.Uint64()
|
||||
for i := 0; i < len(p); i++ {
|
||||
p[i] = byte(val)
|
||||
val >>= 8
|
||||
}
|
||||
}
|
||||
}
|
||||
9
sdk/traefik/vendor/github.com/chaitin/t1k-go/misc/now.go
generated
vendored
9
sdk/traefik/vendor/github.com/chaitin/t1k-go/misc/now.go
generated
vendored
@@ -1,9 +0,0 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func Now() int64 {
|
||||
return time.Now().UnixNano() / 1e3
|
||||
}
|
||||
184
sdk/traefik/vendor/github.com/chaitin/t1k-go/server.go
generated
vendored
184
sdk/traefik/vendor/github.com/chaitin/t1k-go/server.go
generated
vendored
@@ -1,184 +0,0 @@
|
||||
package t1k
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/chaitin/t1k-go/detection"
|
||||
|
||||
"github.com/chaitin/t1k-go/misc"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_POOL_SIZE = 8
|
||||
HEARTBEAT_INTERVAL = 20
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
socketFactory func() (net.Conn, error)
|
||||
poolCh chan *conn
|
||||
poolSize int
|
||||
count int
|
||||
closeCh chan struct{}
|
||||
logger *log.Logger
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (s *Server) newConn() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
sock, err := s.socketFactory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.count += 1
|
||||
s.poolCh <- makeConn(sock, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) GetConn() (*conn, error) {
|
||||
if s.count < s.poolSize {
|
||||
for i := 0; i < (s.poolSize - s.count); i++ {
|
||||
err := s.newConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
c := <-s.poolCh
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (s *Server) PutConn(c *conn) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if c.failing {
|
||||
s.count -= 1
|
||||
c.Close()
|
||||
} else {
|
||||
s.poolCh <- c
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) broadcastHeartbeat() {
|
||||
l := len(s.poolCh)
|
||||
for i := 0; i < l; i++ {
|
||||
select {
|
||||
case c := <-s.poolCh:
|
||||
c.Heartbeat()
|
||||
s.PutConn(c)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) runHeartbeatCo() {
|
||||
for {
|
||||
timer := time.NewTimer(HEARTBEAT_INTERVAL * time.Second)
|
||||
select {
|
||||
case <-s.closeCh:
|
||||
return
|
||||
case <-timer.C:
|
||||
}
|
||||
s.broadcastHeartbeat()
|
||||
}
|
||||
}
|
||||
|
||||
func NewFromSocketFactoryWithPoolSize(socketFactory func() (net.Conn, error), poolSize int) (*Server, error) {
|
||||
ret := &Server{
|
||||
socketFactory: socketFactory,
|
||||
poolCh: make(chan *conn, poolSize),
|
||||
poolSize: poolSize,
|
||||
closeCh: make(chan struct{}),
|
||||
logger: log.New(os.Stdout, "snserver", log.LstdFlags),
|
||||
mu: sync.Mutex{},
|
||||
}
|
||||
for i := 0; i < poolSize; i++ {
|
||||
err := ret.newConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
go ret.runHeartbeatCo()
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func NewFromSocketFactory(socketFactory func() (net.Conn, error)) (*Server, error) {
|
||||
return NewFromSocketFactoryWithPoolSize(socketFactory, DEFAULT_POOL_SIZE)
|
||||
}
|
||||
|
||||
func NewWithPoolSize(addr string, poolSize int) (*Server, error) {
|
||||
return NewFromSocketFactoryWithPoolSize(func() (net.Conn, error) {
|
||||
return net.Dial("tcp", addr)
|
||||
}, poolSize)
|
||||
}
|
||||
|
||||
func New(addr string) (*Server, error) {
|
||||
return NewWithPoolSize(addr, DEFAULT_POOL_SIZE)
|
||||
}
|
||||
|
||||
func (s *Server) DetectRequestInCtx(dc *detection.DetectionContext) (*detection.Result, error) {
|
||||
c, err := s.GetConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.PutConn(c)
|
||||
return c.DetectRequestInCtx(dc)
|
||||
}
|
||||
|
||||
func (s *Server) DetectResponseInCtx(dc *detection.DetectionContext) (*detection.Result, error) {
|
||||
c, err := s.GetConn()
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
defer s.PutConn(c)
|
||||
return c.DetectResponseInCtx(dc)
|
||||
}
|
||||
|
||||
func (s *Server) Detect(dc *detection.DetectionContext) (*detection.Result, *detection.Result, error) {
|
||||
c, err := s.GetConn()
|
||||
if err != nil {
|
||||
return nil, nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
|
||||
reqResult, rspResult, err := c.Detect(dc)
|
||||
if err == nil {
|
||||
s.PutConn(c)
|
||||
}
|
||||
return reqResult, rspResult, err
|
||||
}
|
||||
|
||||
func (s *Server) DetectHttpRequest(req *http.Request) (*detection.Result, error) {
|
||||
c, err := s.GetConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.PutConn(c)
|
||||
return c.DetectHttpRequest(req)
|
||||
}
|
||||
|
||||
func (s *Server) DetectRequest(req detection.Request) (*detection.Result, error) {
|
||||
c, err := s.GetConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.PutConn(c)
|
||||
return c.DetectRequest(req)
|
||||
}
|
||||
|
||||
// blocks until all pending detection is completed
|
||||
func (s *Server) Close() {
|
||||
close(s.closeCh)
|
||||
for i := 0; i < s.count; i++ {
|
||||
c, err := s.GetConn()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
35
sdk/traefik/vendor/github.com/chaitin/t1k-go/t1k/header.go
generated
vendored
35
sdk/traefik/vendor/github.com/chaitin/t1k-go/t1k/header.go
generated
vendored
@@ -1,35 +0,0 @@
|
||||
package t1k
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const (
|
||||
T1K_HEADER_SIZE uint64 = 5
|
||||
)
|
||||
|
||||
type Header struct {
|
||||
Tag Tag
|
||||
Size uint32
|
||||
}
|
||||
|
||||
func MakeHeader(tag Tag, size uint32) Header {
|
||||
return Header{
|
||||
Tag: tag,
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
|
||||
func (h Header) Serialize() []byte {
|
||||
b := make([]byte, 5)
|
||||
b[0] = byte(uint8(h.Tag))
|
||||
binary.LittleEndian.PutUint32(b[1:], h.Size)
|
||||
return b
|
||||
}
|
||||
|
||||
func DeserializeHeader(b []byte) Header {
|
||||
return MakeHeader(
|
||||
Tag(uint8(b[0])),
|
||||
binary.LittleEndian.Uint32(b[1:]),
|
||||
)
|
||||
}
|
||||
95
sdk/traefik/vendor/github.com/chaitin/t1k-go/t1k/section.go
generated
vendored
95
sdk/traefik/vendor/github.com/chaitin/t1k-go/t1k/section.go
generated
vendored
@@ -1,95 +0,0 @@
|
||||
package t1k
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/chaitin/t1k-go/misc"
|
||||
)
|
||||
|
||||
type Section interface {
|
||||
Header() Header
|
||||
WriteBody(io.Writer) error
|
||||
}
|
||||
|
||||
type SimpleSection struct {
|
||||
tag Tag
|
||||
body []byte
|
||||
}
|
||||
|
||||
func MakeSimpleSection(tag Tag, body []byte) *SimpleSection {
|
||||
return &SimpleSection{
|
||||
tag: tag,
|
||||
body: body,
|
||||
}
|
||||
}
|
||||
|
||||
func (msg *SimpleSection) Header() Header {
|
||||
return MakeHeader(msg.tag, uint32(len(msg.body)))
|
||||
}
|
||||
|
||||
func (msg *SimpleSection) WriteBody(w io.Writer) error {
|
||||
_, err := w.Write(msg.body)
|
||||
return err
|
||||
}
|
||||
|
||||
type ReaderSection struct {
|
||||
tag Tag
|
||||
size uint32
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func MakeReaderSection(tag Tag, size uint32, reader io.Reader) *ReaderSection {
|
||||
return &ReaderSection{
|
||||
tag: tag,
|
||||
size: size,
|
||||
reader: reader,
|
||||
}
|
||||
}
|
||||
|
||||
func (msg *ReaderSection) Header() Header {
|
||||
return MakeHeader(msg.tag, msg.size)
|
||||
}
|
||||
|
||||
func (msg *ReaderSection) WriteBody(w io.Writer) error {
|
||||
_, err := io.CopyN(w, msg.reader, int64(msg.size))
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
|
||||
func WriteSection(s Section, w io.Writer) error {
|
||||
h := s.Header()
|
||||
_, err := w.Write(h.Serialize())
|
||||
if err != nil {
|
||||
return misc.ErrorWrap(err, "")
|
||||
}
|
||||
return s.WriteBody(w)
|
||||
}
|
||||
|
||||
// returns a *ReaderSection, must call its WriteBody
|
||||
// before next call to r
|
||||
func ReadSection(r io.Reader) (Section, error) {
|
||||
bHeader := make([]byte, T1K_HEADER_SIZE)
|
||||
_, err := io.ReadFull(r, bHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := DeserializeHeader(bHeader)
|
||||
bodyReader := io.LimitReader(r, int64(h.Size))
|
||||
return MakeReaderSection(h.Tag, h.Size, bodyReader), nil
|
||||
}
|
||||
|
||||
// returns a *SimpleSection
|
||||
func ReadFullSection(r io.Reader) (Section, error) {
|
||||
bHeader := make([]byte, T1K_HEADER_SIZE)
|
||||
_, err := io.ReadFull(r, bHeader)
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
h := DeserializeHeader(bHeader)
|
||||
var buf bytes.Buffer
|
||||
_, err = io.CopyN(&buf, r, int64(h.Size))
|
||||
if err != nil {
|
||||
return nil, misc.ErrorWrap(err, "")
|
||||
}
|
||||
return MakeSimpleSection(h.Tag, buf.Bytes()), nil
|
||||
}
|
||||
39
sdk/traefik/vendor/github.com/chaitin/t1k-go/t1k/tag.go
generated
vendored
39
sdk/traefik/vendor/github.com/chaitin/t1k-go/t1k/tag.go
generated
vendored
@@ -1,39 +0,0 @@
|
||||
package t1k
|
||||
|
||||
type Tag uint8
|
||||
|
||||
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_BOT_BODY Tag = 0x30
|
||||
|
||||
MASK_TAG Tag = 0x3f
|
||||
MASK_FIRST Tag = 0x40
|
||||
MASK_LAST Tag = 0x80
|
||||
)
|
||||
|
||||
func (t Tag) IsFirst() bool {
|
||||
return !(0 == (t & MASK_FIRST))
|
||||
}
|
||||
|
||||
func (t Tag) IsLast() bool {
|
||||
return !(0 == (t & MASK_LAST))
|
||||
}
|
||||
|
||||
func (t Tag) Strip() Tag {
|
||||
return t & MASK_TAG
|
||||
}
|
||||
4
sdk/traefik/vendor/github.com/xbingW/t1k/README.md
generated
vendored
4
sdk/traefik/vendor/github.com/xbingW/t1k/README.md
generated
vendored
@@ -1,4 +0,0 @@
|
||||
# t1k-go
|
||||
|
||||
## Getting started
|
||||
|
||||
173
sdk/traefik/vendor/github.com/xbingW/t1k/detector.go
generated
vendored
173
sdk/traefik/vendor/github.com/xbingW/t1k/detector.go
generated
vendored
@@ -1,173 +0,0 @@
|
||||
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
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/datetime/datetime.go
generated
vendored
7
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/datetime/datetime.go
generated
vendored
@@ -1,7 +0,0 @@
|
||||
package datetime
|
||||
|
||||
import "time"
|
||||
|
||||
func Now() int64 {
|
||||
return time.Now().UnixNano() / 1e3
|
||||
}
|
||||
15
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/rand/str.go
generated
vendored
15
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/rand/str.go
generated
vendored
@@ -1,15 +0,0 @@
|
||||
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
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/detector.go
generated
vendored
163
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/detector.go
generated
vendored
@@ -1,163 +0,0 @@
|
||||
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
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/extra.go
generated
vendored
75
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/extra.go
generated
vendored
@@ -1,75 +0,0 @@
|
||||
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
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/packet.go
generated
vendored
69
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/packet.go
generated
vendored
@@ -1,69 +0,0 @@
|
||||
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
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/req.go
generated
vendored
122
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/req.go
generated
vendored
@@ -1,122 +0,0 @@
|
||||
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
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/res.go
generated
vendored
127
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/res.go
generated
vendored
@@ -1,127 +0,0 @@
|
||||
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
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/tag.go
generated
vendored
45
sdk/traefik/vendor/github.com/xbingW/t1k/pkg/t1k/tag.go
generated
vendored
@@ -1,45 +0,0 @@
|
||||
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)
|
||||
}
|
||||
27
sdk/traefik/vendor/golang.org/x/xerrors/LICENSE
generated
vendored
27
sdk/traefik/vendor/golang.org/x/xerrors/LICENSE
generated
vendored
@@ -1,27 +0,0 @@
|
||||
Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
22
sdk/traefik/vendor/golang.org/x/xerrors/PATENTS
generated
vendored
22
sdk/traefik/vendor/golang.org/x/xerrors/PATENTS
generated
vendored
@@ -1,22 +0,0 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
2
sdk/traefik/vendor/golang.org/x/xerrors/README
generated
vendored
2
sdk/traefik/vendor/golang.org/x/xerrors/README
generated
vendored
@@ -1,2 +0,0 @@
|
||||
This repository holds the transition packages for the new Go 1.13 error values.
|
||||
See golang.org/design/29934-error-values.
|
||||
193
sdk/traefik/vendor/golang.org/x/xerrors/adaptor.go
generated
vendored
193
sdk/traefik/vendor/golang.org/x/xerrors/adaptor.go
generated
vendored
@@ -1,193 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xerrors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// FormatError calls the FormatError method of f with an errors.Printer
|
||||
// configured according to s and verb, and writes the result to s.
|
||||
func FormatError(f Formatter, s fmt.State, verb rune) {
|
||||
// Assuming this function is only called from the Format method, and given
|
||||
// that FormatError takes precedence over Format, it cannot be called from
|
||||
// any package that supports errors.Formatter. It is therefore safe to
|
||||
// disregard that State may be a specific printer implementation and use one
|
||||
// of our choice instead.
|
||||
|
||||
// limitations: does not support printing error as Go struct.
|
||||
|
||||
var (
|
||||
sep = " " // separator before next error
|
||||
p = &state{State: s}
|
||||
direct = true
|
||||
)
|
||||
|
||||
var err error = f
|
||||
|
||||
switch verb {
|
||||
// Note that this switch must match the preference order
|
||||
// for ordinary string printing (%#v before %+v, and so on).
|
||||
|
||||
case 'v':
|
||||
if s.Flag('#') {
|
||||
if stringer, ok := err.(fmt.GoStringer); ok {
|
||||
io.WriteString(&p.buf, stringer.GoString())
|
||||
goto exit
|
||||
}
|
||||
// proceed as if it were %v
|
||||
} else if s.Flag('+') {
|
||||
p.printDetail = true
|
||||
sep = "\n - "
|
||||
}
|
||||
case 's':
|
||||
case 'q', 'x', 'X':
|
||||
// Use an intermediate buffer in the rare cases that precision,
|
||||
// truncation, or one of the alternative verbs (q, x, and X) are
|
||||
// specified.
|
||||
direct = false
|
||||
|
||||
default:
|
||||
p.buf.WriteString("%!")
|
||||
p.buf.WriteRune(verb)
|
||||
p.buf.WriteByte('(')
|
||||
switch {
|
||||
case err != nil:
|
||||
p.buf.WriteString(reflect.TypeOf(f).String())
|
||||
default:
|
||||
p.buf.WriteString("<nil>")
|
||||
}
|
||||
p.buf.WriteByte(')')
|
||||
io.Copy(s, &p.buf)
|
||||
return
|
||||
}
|
||||
|
||||
loop:
|
||||
for {
|
||||
switch v := err.(type) {
|
||||
case Formatter:
|
||||
err = v.FormatError((*printer)(p))
|
||||
case fmt.Formatter:
|
||||
v.Format(p, 'v')
|
||||
break loop
|
||||
default:
|
||||
io.WriteString(&p.buf, v.Error())
|
||||
break loop
|
||||
}
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if p.needColon || !p.printDetail {
|
||||
p.buf.WriteByte(':')
|
||||
p.needColon = false
|
||||
}
|
||||
p.buf.WriteString(sep)
|
||||
p.inDetail = false
|
||||
p.needNewline = false
|
||||
}
|
||||
|
||||
exit:
|
||||
width, okW := s.Width()
|
||||
prec, okP := s.Precision()
|
||||
|
||||
if !direct || (okW && width > 0) || okP {
|
||||
// Construct format string from State s.
|
||||
format := []byte{'%'}
|
||||
if s.Flag('-') {
|
||||
format = append(format, '-')
|
||||
}
|
||||
if s.Flag('+') {
|
||||
format = append(format, '+')
|
||||
}
|
||||
if s.Flag(' ') {
|
||||
format = append(format, ' ')
|
||||
}
|
||||
if okW {
|
||||
format = strconv.AppendInt(format, int64(width), 10)
|
||||
}
|
||||
if okP {
|
||||
format = append(format, '.')
|
||||
format = strconv.AppendInt(format, int64(prec), 10)
|
||||
}
|
||||
format = append(format, string(verb)...)
|
||||
fmt.Fprintf(s, string(format), p.buf.String())
|
||||
} else {
|
||||
io.Copy(s, &p.buf)
|
||||
}
|
||||
}
|
||||
|
||||
var detailSep = []byte("\n ")
|
||||
|
||||
// state tracks error printing state. It implements fmt.State.
|
||||
type state struct {
|
||||
fmt.State
|
||||
buf bytes.Buffer
|
||||
|
||||
printDetail bool
|
||||
inDetail bool
|
||||
needColon bool
|
||||
needNewline bool
|
||||
}
|
||||
|
||||
func (s *state) Write(b []byte) (n int, err error) {
|
||||
if s.printDetail {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if s.inDetail && s.needColon {
|
||||
s.needNewline = true
|
||||
if b[0] == '\n' {
|
||||
b = b[1:]
|
||||
}
|
||||
}
|
||||
k := 0
|
||||
for i, c := range b {
|
||||
if s.needNewline {
|
||||
if s.inDetail && s.needColon {
|
||||
s.buf.WriteByte(':')
|
||||
s.needColon = false
|
||||
}
|
||||
s.buf.Write(detailSep)
|
||||
s.needNewline = false
|
||||
}
|
||||
if c == '\n' {
|
||||
s.buf.Write(b[k:i])
|
||||
k = i + 1
|
||||
s.needNewline = true
|
||||
}
|
||||
}
|
||||
s.buf.Write(b[k:])
|
||||
if !s.inDetail {
|
||||
s.needColon = true
|
||||
}
|
||||
} else if !s.inDetail {
|
||||
s.buf.Write(b)
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// printer wraps a state to implement an xerrors.Printer.
|
||||
type printer state
|
||||
|
||||
func (s *printer) Print(args ...interface{}) {
|
||||
if !s.inDetail || s.printDetail {
|
||||
fmt.Fprint((*state)(s), args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *printer) Printf(format string, args ...interface{}) {
|
||||
if !s.inDetail || s.printDetail {
|
||||
fmt.Fprintf((*state)(s), format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *printer) Detail() bool {
|
||||
s.inDetail = true
|
||||
return s.printDetail
|
||||
}
|
||||
1
sdk/traefik/vendor/golang.org/x/xerrors/codereview.cfg
generated
vendored
1
sdk/traefik/vendor/golang.org/x/xerrors/codereview.cfg
generated
vendored
@@ -1 +0,0 @@
|
||||
issuerepo: golang/go
|
||||
23
sdk/traefik/vendor/golang.org/x/xerrors/doc.go
generated
vendored
23
sdk/traefik/vendor/golang.org/x/xerrors/doc.go
generated
vendored
@@ -1,23 +0,0 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package xerrors implements functions to manipulate errors.
|
||||
//
|
||||
// This package is based on the Go 2 proposal for error values:
|
||||
//
|
||||
// https://golang.org/design/29934-error-values
|
||||
//
|
||||
// These functions were incorporated into the standard library's errors package
|
||||
// in Go 1.13:
|
||||
// - Is
|
||||
// - As
|
||||
// - Unwrap
|
||||
//
|
||||
// Also, Errorf's %w verb was incorporated into fmt.Errorf.
|
||||
//
|
||||
// Use this package to get equivalent behavior in all supported Go versions.
|
||||
//
|
||||
// No other features of this package were included in Go 1.13, and at present
|
||||
// there are no plans to include any of them.
|
||||
package xerrors // import "golang.org/x/xerrors"
|
||||
33
sdk/traefik/vendor/golang.org/x/xerrors/errors.go
generated
vendored
33
sdk/traefik/vendor/golang.org/x/xerrors/errors.go
generated
vendored
@@ -1,33 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xerrors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// errorString is a trivial implementation of error.
|
||||
type errorString struct {
|
||||
s string
|
||||
frame Frame
|
||||
}
|
||||
|
||||
// New returns an error that formats as the given text.
|
||||
//
|
||||
// The returned error contains a Frame set to the caller's location and
|
||||
// implements Formatter to show this information when printed with details.
|
||||
func New(text string) error {
|
||||
return &errorString{text, Caller(1)}
|
||||
}
|
||||
|
||||
func (e *errorString) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) }
|
||||
|
||||
func (e *errorString) FormatError(p Printer) (next error) {
|
||||
p.Print(e.s)
|
||||
e.frame.Format(p)
|
||||
return nil
|
||||
}
|
||||
190
sdk/traefik/vendor/golang.org/x/xerrors/fmt.go
generated
vendored
190
sdk/traefik/vendor/golang.org/x/xerrors/fmt.go
generated
vendored
@@ -1,190 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xerrors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/xerrors/internal"
|
||||
)
|
||||
|
||||
const percentBangString = "%!"
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string as a
|
||||
// value that satisfies error.
|
||||
//
|
||||
// The returned error includes the file and line number of the caller when
|
||||
// formatted with additional detail enabled. If the last argument is an error
|
||||
// the returned error's Format method will return it if the format string ends
|
||||
// with ": %s", ": %v", or ": %w". If the last argument is an error and the
|
||||
// format string ends with ": %w", the returned error implements an Unwrap
|
||||
// method returning it.
|
||||
//
|
||||
// If the format specifier includes a %w verb with an error operand in a
|
||||
// position other than at the end, the returned error will still implement an
|
||||
// Unwrap method returning the operand, but the error's Format method will not
|
||||
// return the wrapped error.
|
||||
//
|
||||
// It is invalid to include more than one %w verb or to supply it with an
|
||||
// operand that does not implement the error interface. The %w verb is otherwise
|
||||
// a synonym for %v.
|
||||
//
|
||||
// Note that as of Go 1.13, the fmt.Errorf function will do error formatting,
|
||||
// but it will not capture a stack backtrace.
|
||||
func Errorf(format string, a ...interface{}) error {
|
||||
format = formatPlusW(format)
|
||||
// Support a ": %[wsv]" suffix, which works well with xerrors.Formatter.
|
||||
wrap := strings.HasSuffix(format, ": %w")
|
||||
idx, format2, ok := parsePercentW(format)
|
||||
percentWElsewhere := !wrap && idx >= 0
|
||||
if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) {
|
||||
err := errorAt(a, len(a)-1)
|
||||
if err == nil {
|
||||
return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)}
|
||||
}
|
||||
// TODO: this is not entirely correct. The error value could be
|
||||
// printed elsewhere in format if it mixes numbered with unnumbered
|
||||
// substitutions. With relatively small changes to doPrintf we can
|
||||
// have it optionally ignore extra arguments and pass the argument
|
||||
// list in its entirety.
|
||||
msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
|
||||
frame := Frame{}
|
||||
if internal.EnableTrace {
|
||||
frame = Caller(1)
|
||||
}
|
||||
if wrap {
|
||||
return &wrapError{msg, err, frame}
|
||||
}
|
||||
return &noWrapError{msg, err, frame}
|
||||
}
|
||||
// Support %w anywhere.
|
||||
// TODO: don't repeat the wrapped error's message when %w occurs in the middle.
|
||||
msg := fmt.Sprintf(format2, a...)
|
||||
if idx < 0 {
|
||||
return &noWrapError{msg, nil, Caller(1)}
|
||||
}
|
||||
err := errorAt(a, idx)
|
||||
if !ok || err == nil {
|
||||
// Too many %ws or argument of %w is not an error. Approximate the Go
|
||||
// 1.13 fmt.Errorf message.
|
||||
return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)}
|
||||
}
|
||||
frame := Frame{}
|
||||
if internal.EnableTrace {
|
||||
frame = Caller(1)
|
||||
}
|
||||
return &wrapError{msg, err, frame}
|
||||
}
|
||||
|
||||
func errorAt(args []interface{}, i int) error {
|
||||
if i < 0 || i >= len(args) {
|
||||
return nil
|
||||
}
|
||||
err, ok := args[i].(error)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// formatPlusW is used to avoid the vet check that will barf at %w.
|
||||
func formatPlusW(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// Return the index of the only %w in format, or -1 if none.
|
||||
// Also return a rewritten format string with %w replaced by %v, and
|
||||
// false if there is more than one %w.
|
||||
// TODO: handle "%[N]w".
|
||||
func parsePercentW(format string) (idx int, newFormat string, ok bool) {
|
||||
// Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go.
|
||||
idx = -1
|
||||
ok = true
|
||||
n := 0
|
||||
sz := 0
|
||||
var isW bool
|
||||
for i := 0; i < len(format); i += sz {
|
||||
if format[i] != '%' {
|
||||
sz = 1
|
||||
continue
|
||||
}
|
||||
// "%%" is not a format directive.
|
||||
if i+1 < len(format) && format[i+1] == '%' {
|
||||
sz = 2
|
||||
continue
|
||||
}
|
||||
sz, isW = parsePrintfVerb(format[i:])
|
||||
if isW {
|
||||
if idx >= 0 {
|
||||
ok = false
|
||||
} else {
|
||||
idx = n
|
||||
}
|
||||
// "Replace" the last character, the 'w', with a 'v'.
|
||||
p := i + sz - 1
|
||||
format = format[:p] + "v" + format[p+1:]
|
||||
}
|
||||
n++
|
||||
}
|
||||
return idx, format, ok
|
||||
}
|
||||
|
||||
// Parse the printf verb starting with a % at s[0].
|
||||
// Return how many bytes it occupies and whether the verb is 'w'.
|
||||
func parsePrintfVerb(s string) (int, bool) {
|
||||
// Assume only that the directive is a sequence of non-letters followed by a single letter.
|
||||
sz := 0
|
||||
var r rune
|
||||
for i := 1; i < len(s); i += sz {
|
||||
r, sz = utf8.DecodeRuneInString(s[i:])
|
||||
if unicode.IsLetter(r) {
|
||||
return i + sz, r == 'w'
|
||||
}
|
||||
}
|
||||
return len(s), false
|
||||
}
|
||||
|
||||
type noWrapError struct {
|
||||
msg string
|
||||
err error
|
||||
frame Frame
|
||||
}
|
||||
|
||||
func (e *noWrapError) Error() string {
|
||||
return fmt.Sprint(e)
|
||||
}
|
||||
|
||||
func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
|
||||
|
||||
func (e *noWrapError) FormatError(p Printer) (next error) {
|
||||
p.Print(e.msg)
|
||||
e.frame.Format(p)
|
||||
return e.err
|
||||
}
|
||||
|
||||
type wrapError struct {
|
||||
msg string
|
||||
err error
|
||||
frame Frame
|
||||
}
|
||||
|
||||
func (e *wrapError) Error() string {
|
||||
return fmt.Sprint(e)
|
||||
}
|
||||
|
||||
func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
|
||||
|
||||
func (e *wrapError) FormatError(p Printer) (next error) {
|
||||
p.Print(e.msg)
|
||||
e.frame.Format(p)
|
||||
return e.err
|
||||
}
|
||||
|
||||
func (e *wrapError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
34
sdk/traefik/vendor/golang.org/x/xerrors/format.go
generated
vendored
34
sdk/traefik/vendor/golang.org/x/xerrors/format.go
generated
vendored
@@ -1,34 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xerrors
|
||||
|
||||
// A Formatter formats error messages.
|
||||
type Formatter interface {
|
||||
error
|
||||
|
||||
// FormatError prints the receiver's first error and returns the next error in
|
||||
// the error chain, if any.
|
||||
FormatError(p Printer) (next error)
|
||||
}
|
||||
|
||||
// A Printer formats error messages.
|
||||
//
|
||||
// The most common implementation of Printer is the one provided by package fmt
|
||||
// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message
|
||||
// typically provide their own implementations.
|
||||
type Printer interface {
|
||||
// Print appends args to the message output.
|
||||
Print(args ...interface{})
|
||||
|
||||
// Printf writes a formatted string.
|
||||
Printf(format string, args ...interface{})
|
||||
|
||||
// Detail reports whether error detail is requested.
|
||||
// After the first call to Detail, all text written to the Printer
|
||||
// is formatted as additional detail, or ignored when
|
||||
// detail has not been requested.
|
||||
// If Detail returns false, the caller can avoid printing the detail at all.
|
||||
Detail() bool
|
||||
}
|
||||
56
sdk/traefik/vendor/golang.org/x/xerrors/frame.go
generated
vendored
56
sdk/traefik/vendor/golang.org/x/xerrors/frame.go
generated
vendored
@@ -1,56 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xerrors
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// A Frame contains part of a call stack.
|
||||
type Frame struct {
|
||||
// Make room for three PCs: the one we were asked for, what it called,
|
||||
// and possibly a PC for skipPleaseUseCallersFrames. See:
|
||||
// https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169
|
||||
frames [3]uintptr
|
||||
}
|
||||
|
||||
// Caller returns a Frame that describes a frame on the caller's stack.
|
||||
// The argument skip is the number of frames to skip over.
|
||||
// Caller(0) returns the frame for the caller of Caller.
|
||||
func Caller(skip int) Frame {
|
||||
var s Frame
|
||||
runtime.Callers(skip+1, s.frames[:])
|
||||
return s
|
||||
}
|
||||
|
||||
// location reports the file, line, and function of a frame.
|
||||
//
|
||||
// The returned function may be "" even if file and line are not.
|
||||
func (f Frame) location() (function, file string, line int) {
|
||||
frames := runtime.CallersFrames(f.frames[:])
|
||||
if _, ok := frames.Next(); !ok {
|
||||
return "", "", 0
|
||||
}
|
||||
fr, ok := frames.Next()
|
||||
if !ok {
|
||||
return "", "", 0
|
||||
}
|
||||
return fr.Function, fr.File, fr.Line
|
||||
}
|
||||
|
||||
// Format prints the stack as error detail.
|
||||
// It should be called from an error's Format implementation
|
||||
// after printing any other error detail.
|
||||
func (f Frame) Format(p Printer) {
|
||||
if p.Detail() {
|
||||
function, file, line := f.location()
|
||||
if function != "" {
|
||||
p.Printf("%s\n ", function)
|
||||
}
|
||||
if file != "" {
|
||||
p.Printf("%s:%d\n", file, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
8
sdk/traefik/vendor/golang.org/x/xerrors/internal/internal.go
generated
vendored
8
sdk/traefik/vendor/golang.org/x/xerrors/internal/internal.go
generated
vendored
@@ -1,8 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
// EnableTrace indicates whether stack information should be recorded in errors.
|
||||
var EnableTrace = true
|
||||
112
sdk/traefik/vendor/golang.org/x/xerrors/wrap.go
generated
vendored
112
sdk/traefik/vendor/golang.org/x/xerrors/wrap.go
generated
vendored
@@ -1,112 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xerrors
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// A Wrapper provides context around another error.
|
||||
type Wrapper interface {
|
||||
// Unwrap returns the next error in the error chain.
|
||||
// If there is no next error, Unwrap returns nil.
|
||||
Unwrap() error
|
||||
}
|
||||
|
||||
// Opaque returns an error with the same error formatting as err
|
||||
// but that does not match err and cannot be unwrapped.
|
||||
func Opaque(err error) error {
|
||||
return noWrapper{err}
|
||||
}
|
||||
|
||||
type noWrapper struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e noWrapper) FormatError(p Printer) (next error) {
|
||||
if f, ok := e.error.(Formatter); ok {
|
||||
return f.FormatError(p)
|
||||
}
|
||||
p.Print(e.error)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err implements
|
||||
// Unwrap. Otherwise, Unwrap returns nil.
|
||||
//
|
||||
// Deprecated: As of Go 1.13, use errors.Unwrap instead.
|
||||
func Unwrap(err error) error {
|
||||
u, ok := err.(Wrapper)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return u.Unwrap()
|
||||
}
|
||||
|
||||
// Is reports whether any error in err's chain matches target.
|
||||
//
|
||||
// An error is considered to match a target if it is equal to that target or if
|
||||
// it implements a method Is(error) bool such that Is(target) returns true.
|
||||
//
|
||||
// Deprecated: As of Go 1.13, use errors.Is instead.
|
||||
func Is(err, target error) bool {
|
||||
if target == nil {
|
||||
return err == target
|
||||
}
|
||||
|
||||
isComparable := reflect.TypeOf(target).Comparable()
|
||||
for {
|
||||
if isComparable && err == target {
|
||||
return true
|
||||
}
|
||||
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
|
||||
return true
|
||||
}
|
||||
// TODO: consider supporing target.Is(err). This would allow
|
||||
// user-definable predicates, but also may allow for coping with sloppy
|
||||
// APIs, thereby making it easier to get away with them.
|
||||
if err = Unwrap(err); err == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// As finds the first error in err's chain that matches the type to which target
|
||||
// points, and if so, sets the target to its value and returns true. An error
|
||||
// matches a type if it is assignable to the target type, or if it has a method
|
||||
// As(interface{}) bool such that As(target) returns true. As will panic if target
|
||||
// is not a non-nil pointer to a type which implements error or is of interface type.
|
||||
//
|
||||
// The As method should set the target to its value and return true if err
|
||||
// matches the type to which target points.
|
||||
//
|
||||
// Deprecated: As of Go 1.13, use errors.As instead.
|
||||
func As(err error, target interface{}) bool {
|
||||
if target == nil {
|
||||
panic("errors: target cannot be nil")
|
||||
}
|
||||
val := reflect.ValueOf(target)
|
||||
typ := val.Type()
|
||||
if typ.Kind() != reflect.Ptr || val.IsNil() {
|
||||
panic("errors: target must be a non-nil pointer")
|
||||
}
|
||||
if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) {
|
||||
panic("errors: *target must be interface or implement error")
|
||||
}
|
||||
targetType := typ.Elem()
|
||||
for err != nil {
|
||||
if reflect.TypeOf(err).AssignableTo(targetType) {
|
||||
val.Elem().Set(reflect.ValueOf(err))
|
||||
return true
|
||||
}
|
||||
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
|
||||
return true
|
||||
}
|
||||
err = Unwrap(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
10
sdk/traefik/vendor/modules.txt
vendored
10
sdk/traefik/vendor/modules.txt
vendored
@@ -1,10 +0,0 @@
|
||||
# github.com/chaitin/t1k-go v1.5.0
|
||||
## explicit; go 1.17
|
||||
github.com/chaitin/t1k-go
|
||||
github.com/chaitin/t1k-go/detection
|
||||
github.com/chaitin/t1k-go/misc
|
||||
github.com/chaitin/t1k-go/t1k
|
||||
# golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028
|
||||
## explicit; go 1.18
|
||||
golang.org/x/xerrors
|
||||
golang.org/x/xerrors/internal
|
||||
5
version.json
Normal file
5
version.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"latest_version": "v9.2.7",
|
||||
"rec_version": "v9.2.7",
|
||||
"lts_version": "v9.1.0-lts"
|
||||
}
|
||||
Reference in New Issue
Block a user