From d65fe4db6f29666bed6c122a5ae8e6f1bad6fd33 Mon Sep 17 00:00:00 2001 From: luorijun Date: Thu, 15 May 2025 15:56:20 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BD=91=E5=85=B3=E5=AE=9E=E7=8E=B0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=8E=A5=E5=8F=A3=E5=AE=89=E5=85=A8=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E4=B8=8E=E8=BE=B9=E7=BC=98=E8=8A=82=E7=82=B9=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E6=9D=83=E9=99=90=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++ cmd/gen/gen.go | 41 ---------- go.mod | 41 +--------- go.sum | 143 +++------------------------------ server/app/app.go | 12 +++ server/core/auth.go | 10 +++ server/core/consts.go | 6 ++ server/core/security.go | 74 +++++++++++++++++ server/{pkg => }/env/env.go | 54 ++++--------- server/fwd/analysis.go | 2 +- server/fwd/auth/auth.go | 107 ++++++------------------- server/fwd/auth/auth_test.go | 151 ----------------------------------- server/fwd/core/conn.go | 2 +- server/fwd/ctrl.go | 24 +++--- server/fwd/data.go | 2 +- server/fwd/fwd.go | 19 +---- server/fwd/http/http.go | 32 +++----- server/fwd/socks/socks.go | 32 ++++---- server/globals/redis.go | 27 +++++++ server/{pkg => }/log/logs.go | 2 +- server/pkg/orm/orm.go | 60 -------------- server/report/report.go | 91 ++++++++++----------- server/server.go | 74 +++++++---------- server/web/handlers/auth.go | 32 ++++++++ server/web/web.go | 2 +- 25 files changed, 353 insertions(+), 703 deletions(-) delete mode 100644 cmd/gen/gen.go create mode 100644 server/app/app.go create mode 100644 server/core/auth.go create mode 100644 server/core/consts.go create mode 100644 server/core/security.go rename server/{pkg => }/env/env.go (70%) delete mode 100644 server/fwd/auth/auth_test.go create mode 100644 server/globals/redis.go rename server/{pkg => }/log/logs.go (96%) delete mode 100644 server/pkg/orm/orm.go create mode 100644 server/web/handlers/auth.go diff --git a/README.md b/README.md index 020e896..ba167a5 100644 --- a/README.md +++ b/README.md @@ -67,3 +67,19 @@ server/fwd: 服务端核心代码 | tag(16) | status(1) | |---------|-----------------------| | 通道标识 | 目标地址连接结果,成功为 1,不成功为 0 | + +## 接口安全性 + +网关服务不强制使用 tls,因此非公开端口的请求(来自平台端的请求)都需要通过额外设计的加密方案进行请求内容验证 + +安全接口的请求体格式为: + +```json5 +{ + "content": "string", // base64 编码的请求体 + "nonce": "string", // 随机数值 + "timestamp": "number" // 时间戳,精确到毫秒 +} +``` + +其中,请求体的内容为经过 aes-gcm 加密后的数据,密钥为网关服务报告上线时,平台端返回的 secret diff --git a/cmd/gen/gen.go b/cmd/gen/gen.go deleted file mode 100644 index f06e273..0000000 --- a/cmd/gen/gen.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "gorm.io/driver/postgres" - "gorm.io/gen" - "gorm.io/gorm" - "gorm.io/gorm/schema" -) - -func main() { - - // 初始化 - db, _ := gorm.Open( - postgres.Open("host=localhost user=test password=test dbname=app port=5432 sslmode=disable TimeZone=Asia/Shanghai"), - &gorm.Config{ - NamingStrategy: schema.NamingStrategy{ - SingularTable: true, - }, - }, - ) - - g := gen.NewGenerator(gen.Config{ - OutPath: "server/repo/queries", - ModelPkgPath: "models", - Mode: gen.WithDefaultQuery | gen.WithoutContext, - }) - g.UseDB(db) - - common := []gen.ModelOpt{ - gen.FieldModify(func(field gen.Field) gen.Field { - if field.Type == "time.Time" { - field.Type = "orm.LocalDateTime" - } - return field - }), - } - - // 生成需要的模型 - g.GenerateModel("channel", common...) - g.GenerateModel("node", common...) -} diff --git a/go.mod b/go.mod index 130c553..f1fb11c 100644 --- a/go.mod +++ b/go.mod @@ -3,65 +3,30 @@ module proxy-server go 1.24 require ( - github.com/gin-gonic/gin v1.10.0 github.com/gofiber/fiber/v2 v2.52.6 github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 github.com/lmittmann/tint v1.0.7 github.com/mattn/go-colorable v0.1.14 + github.com/redis/go-redis/v9 v9.8.0 github.com/soheilhy/cmux v0.1.5 - gorm.io/driver/postgres v1.5.11 - gorm.io/gen v0.3.26 gorm.io/gorm v1.25.12 ) require ( github.com/andybalholm/brotli v1.1.0 // indirect - github.com/bytedance/sonic v1.12.8 // indirect - github.com/bytedance/sonic/loader v0.2.3 // indirect - github.com/cloudwego/base64x v0.1.5 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect - github.com/gin-contrib/sse v1.0.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.22.0 // indirect - github.com/go-sql-driver/mysql v1.7.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.5.5 // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect - github.com/klauspost/cpuid/v2 v2.2.9 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/arch v0.14.0 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.35.0 // indirect - golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/tools v0.28.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c // indirect - gorm.io/driver/mysql v1.4.4 // indirect - gorm.io/hints v1.1.0 // indirect - gorm.io/plugin/dbresolver v1.5.0 // indirect ) diff --git a/go.sum b/go.sum index 5a5795c..5598f53 100644 --- a/go.sum +++ b/go.sum @@ -1,77 +1,25 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs= -github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0= -github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= -github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= -github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= -github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= -github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= -github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= -github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= -github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -80,62 +28,24 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= -github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -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/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4= -golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -147,38 +57,5 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c h1:jWdr7cHgl8c/ua5vYbR2WhSp+NQmzhsj0xoY3foTzW8= -gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c/go.mod h1:SH2K9R+2RMjuX1CkCONrPwoe9JzVv2hkQvEu4bXGojE= -gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= -gorm.io/driver/mysql v1.4.4 h1:MX0K9Qvy0Na4o7qSC/YI7XxqUw5KDw01umqgID+svdQ= -gorm.io/driver/mysql v1.4.4/go.mod h1:BCg8cKI+R0j/rZRQxeKis/forqRwRSYOR8OM3Wo6hOM= -gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= -gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= -gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8= -gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= -gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= -gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= -gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= -gorm.io/gen v0.3.26 h1:sFf1j7vNStimPRRAtH4zz5NiHM+1dr6eA9aaRdplyhY= -gorm.io/gen v0.3.26/go.mod h1:a5lq5y3w4g5LMxBcw0wnO6tYUCdNutWODq5LrIt75LE= -gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= -gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= -gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= -gorm.io/hints v1.1.0 h1:Lp4z3rxREufSdxn4qmkK3TLDltrM10FLTHiuqwDPvXw= -gorm.io/hints v1.1.0/go.mod h1:lKQ0JjySsPBj3uslFzY3JhYDtqEwzm+G1hv8rWujB6Y= -gorm.io/plugin/dbresolver v1.5.0 h1:XVHLxh775eP0CqVh3vcfJtYqja3uFl5Wr3cKlY8jgDY= -gorm.io/plugin/dbresolver v1.5.0/go.mod h1:l4Cn87EHLEYuqUncpEeTC2tTJQkjngPSD+lo8hIvcT0= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/server/app/app.go b/server/app/app.go new file mode 100644 index 0000000..f6c8fce --- /dev/null +++ b/server/app/app.go @@ -0,0 +1,12 @@ +package app + +import "proxy-server/server/core" + +var ( + Id int32 + Name string + PlatformSecret string // 平台密钥,验证接收的请求是否属于平台 + + Assigns = make(map[uint16]int32) // 转发端口 -> 转发服务ID + Permits = make(map[uint16]core.Permit) // 转发端口 -> 权限配置 +) diff --git a/server/core/auth.go b/server/core/auth.go new file mode 100644 index 0000000..57c6eb0 --- /dev/null +++ b/server/core/auth.go @@ -0,0 +1,10 @@ +package core + +import "time" + +type Permit struct { + Expire time.Time `json:"expire"` + Whitelists []string `json:"whitelists"` + Username string `json:"username"` + Password string `json:"password"` +} diff --git a/server/core/consts.go b/server/core/consts.go new file mode 100644 index 0000000..4a67f5a --- /dev/null +++ b/server/core/consts.go @@ -0,0 +1,6 @@ +package core + +const ( + Version = 1 + RestoreMagic = 0x72 +) diff --git a/server/core/security.go b/server/core/security.go new file mode 100644 index 0000000..428b5be --- /dev/null +++ b/server/core/security.go @@ -0,0 +1,74 @@ +package core + +import ( + "context" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/json" + "fmt" + g "proxy-server/server/globals" + "time" +) + +type SecuredReq struct { + Content string `json:"content"` + Nonce string `json:"nonce"` + Timestamp int64 `json:"timestamp"` +} + +func Decrypt[T any](req *SecuredReq, secret string) (resp *T, err error) { + + // 解密请求 + block, err := aes.NewCipher([]byte(secret)) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + var nonce = []byte(req.Nonce) + + content, err := base64.StdEncoding.DecodeString(req.Content) + if err != nil { + return nil, err + } + + var aad = []byte(fmt.Sprintf("%s:%d", req.Nonce, req.Timestamp)) + + bytes, err := gcm.Open(nil, nonce, content, aad) + if err != nil { + return nil, err + } + + // 检查时间与 nonce 是否匹配 + var duration = time.Now().UnixMilli() - req.Timestamp + if duration > 1000*60*5 { // 5分钟 + return nil, fmt.Errorf("请求超时,当前时间:%d,接收时间:%d", time.Now().UnixMilli(), req.Timestamp) + } + + result, err := g.Redis.Exists(context.Background(), req.Nonce).Result() + if err != nil { + return nil, err + } + if result > 0 { + return nil, fmt.Errorf("请求已被使用,nonce:%s", req.Nonce) + } + + // 将 nonce 存入 redis,设置过期时间为 5 分钟 + err = g.Redis.Set(context.Background(), req.Nonce, 1, time.Minute*5).Err() + if err != nil { + return nil, err + } + + // 返回解密后的数据 + err = json.Unmarshal(bytes, &resp) + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/server/pkg/env/env.go b/server/env/env.go similarity index 70% rename from server/pkg/env/env.go rename to server/env/env.go index 3c78ee7..38160b5 100644 --- a/server/pkg/env/env.go +++ b/server/env/env.go @@ -18,12 +18,10 @@ var ( ClientId string ClientSecret string - DbHost string - DbPort uint16 = 5432 - DbDatabase string - DbUsername string - DbPassword string - DbTimezone = "Asia/Shanghai" + RedisHost = "localhost" + RedisPort = "6379" + RedisDb = 0 + RedisPass = "" EndpointOnline string EndpointOffline string @@ -83,46 +81,28 @@ func Init() { panic("环境变量 CLIENT_SECRET 未设置") } - value = os.Getenv("DB_HOST") + value = os.Getenv("REDIS_HOST") if value != "" { - DbHost = os.Getenv("DB_HOST") - } else { - panic("环境变量 DB_HOST 未设置") + RedisHost = value } - value = os.Getenv("DB_PORT") + value = os.Getenv("REDIS_PORT") if value != "" { - dbPort, err := strconv.Atoi(value) + RedisPort = value + } + + value = os.Getenv("REDIS_DB") + if value != "" { + redisDb, err := strconv.Atoi(value) if err != nil { - panic(fmt.Sprintf("环境变量 DB_PORT 格式错误: %v", err)) + panic(fmt.Sprintf("环境变量 REDIS_DB 格式错误: %v", err)) } - DbPort = uint16(dbPort) + RedisDb = redisDb } - value = os.Getenv("DB_DATABASE") + value = os.Getenv("REDIS_PASS") if value != "" { - DbDatabase = value - } else { - panic("环境变量 DB_DATABASE 未设置") - } - - value = os.Getenv("DB_USERNAME") - if value != "" { - DbUsername = value - } else { - panic("环境变量 DB_USERNAME 未设置") - } - - value = os.Getenv("DB_PASSWORD") - if value != "" { - DbPassword = value - } else { - panic("环境变量 DB_PASSWORD 未设置") - } - - value = os.Getenv("DB_TIMEZONE") - if value != "" { - DbTimezone = value + RedisPass = value } value = os.Getenv("ENDPOINT_ONLINE") diff --git a/server/fwd/analysis.go b/server/fwd/analysis.go index 1b177b0..aa06f08 100644 --- a/server/fwd/analysis.go +++ b/server/fwd/analysis.go @@ -22,7 +22,7 @@ func analysisAndLog(conn *core.Conn, reader io.Reader) error { } else { slog.Debug( "用户访问记录", - slog.Uint64("uid", uint64(conn.Auth.Payload.ID)), + slog.Int("uid", int(conn.Auth.Payload.ID)), slog.String("user", conn.RemoteAddr().String()), slog.String("proxy", conn.Protocol), slog.String("node", conn.LocalAddr().String()), diff --git a/server/fwd/auth/auth.go b/server/fwd/auth/auth.go index f679741..0c4034a 100644 --- a/server/fwd/auth/auth.go +++ b/server/fwd/auth/auth.go @@ -2,11 +2,9 @@ package auth import ( "fmt" - "log/slog" "net" + "proxy-server/server/app" "proxy-server/server/fwd/core" - "proxy-server/server/fwd/repo" - "proxy-server/server/pkg/orm" "strconv" "time" @@ -20,7 +18,7 @@ const ( Http = Protocol("http") ) -func CheckIp(conn net.Conn, proto Protocol) (*core.AuthContext, error) { +func Protect(conn net.Conn, proto Protocol, username, password *string) (*core.AuthContext, error) { // 获取用户地址 remoteAddr := conn.RemoteAddr().String() @@ -32,101 +30,48 @@ func CheckIp(conn net.Conn, proto Protocol) (*core.AuthContext, error) { // 获取服务端口 localAddr := conn.LocalAddr().String() _, _localPort, err := net.SplitHostPort(localAddr) - localPort, err := strconv.Atoi(_localPort) + localPort, err := strconv.ParseUint(_localPort, 10, 16) if err != nil { return nil, fmt.Errorf("noAuth 认证失败: %w", err) } - // 查询权限记录 - slog.Debug("用户 " + remoteHost + " 请求连接到 " + _localPort) - var channels []repo.Channel - err = orm.DB.Find(&channels, &repo.Channel{ - AuthIp: true, - UserAddr: remoteHost, - NodePort: localPort, - Protocol: string(proto), - }).Error - if err != nil { - return nil, errors.New("查询用户权限失败") - } - // 记录应该只有一条 - channel, err := orm.MaySingle(channels) - if err != nil { - return nil, errors.New("不在白名单内") + // 查找权限配置 + var permit, ok = app.Permits[uint16(localPort)] + if !ok { + return nil, errors.New("没有权限") } - // 检查是否需要密码认证 - if channel.AuthPass { - return nil, errors.New("需要密码认证") - } - - // 检查权限是否过期 - timeout := channel.Expiration.Sub(time.Now()).Seconds() - if timeout <= 0 { + // 检查是否过期 + if permit.Expire.Before(time.Now()) { return nil, errors.New("权限已过期") } - return &core.AuthContext{ - Timeout: timeout, - Payload: core.Payload{ - ID: channel.UserId, - }, - }, nil -} - -func CheckPass(conn net.Conn, proto Protocol, username, password string) (*core.AuthContext, error) { - - // 获取服务端口 - localAddr := conn.LocalAddr().String() - _, _localPort, err := net.SplitHostPort(localAddr) - localPort, err := strconv.Atoi(_localPort) - if err != nil { - return nil, fmt.Errorf("noAuth 认证失败: %w", err) - } - - // 查询权限记录 - var channel repo.Channel - err = orm.DB.Take(&channel, &repo.Channel{ - AuthPass: true, - Username: username, - NodePort: localPort, - Protocol: string(proto), - }).Error - if err != nil { - return nil, errors.New("用户不存在") - } - - // 检查密码 todo 哈希 - if channel.Password != password { - return nil, errors.New("密码错误") - } - - // 如果用户设置了双验证则检查 ip 是否在白名单中 - if channel.AuthIp { - - // 获取用户地址 - remoteAddr := conn.RemoteAddr().String() - remoteHost, _, err := net.SplitHostPort(remoteAddr) - if err != nil { - return nil, fmt.Errorf("无法获取连接信息: %w", err) + // 检查 IP 是否可用 + if len(permit.Whitelists) > 0 { + var found = false + for _, allowedHost := range permit.Whitelists { + var allowed = net.ParseIP(allowedHost) + var remote = net.ParseIP(remoteHost) + if remote.Equal(allowed) { + found = true + break + } } - - // 查询权限记录 - if channel.UserAddr != remoteHost { + if !found { return nil, errors.New("不在白名单内") } } - // 检查权限是否过期 - timeout := channel.Expiration.Sub(time.Now()).Seconds() - if timeout <= 0 { - return nil, errors.New("权限已过期") + if username != nil && password != nil { + if *username != permit.Username || *password != permit.Password { + return nil, errors.New("用户名或密码错误") + } } return &core.AuthContext{ - Timeout: timeout, + Timeout: time.Since(permit.Expire).Seconds(), Payload: core.Payload{ - ID: channel.UserId, + ID: app.Assigns[uint16(localPort)], }, }, nil } diff --git a/server/fwd/auth/auth_test.go b/server/fwd/auth/auth_test.go deleted file mode 100644 index 09607be..0000000 --- a/server/fwd/auth/auth_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package auth - -import ( - "net" - "proxy-server/server/fwd/core" - "proxy-server/server/pkg/orm" - "reflect" - "testing" - "time" -) - -type MockConn struct { - local net.Addr - remote net.Addr -} - -func (m MockConn) LocalAddr() net.Addr { - return m.local -} - -func (m MockConn) RemoteAddr() net.Addr { - return m.remote -} - -func (m MockConn) Read(b []byte) (n int, err error) { - return 0, nil -} - -func (m MockConn) Write(b []byte) (n int, err error) { - return 0, nil -} - -func (m MockConn) Close() error { - return nil -} - -func (m MockConn) SetDeadline(t time.Time) error { - return nil -} - -func (m MockConn) SetReadDeadline(t time.Time) error { - return nil -} - -func (m MockConn) SetWriteDeadline(t time.Time) error { - return nil -} - -func TestCheckIp(t *testing.T) { - - orm.InitForTest( - "host=localhost port=5432 user=proxy password=proxy dbname=app sslmode=disable TimeZone=Asia/Shanghai", - ) - - type args struct { - conn net.Conn - proto Protocol - } - tests := []struct { - name string - args args - want *core.AuthContext - wantErr bool - }{ - { - name: "test-ok", - args: args{ - conn: MockConn{ - remote: &net.TCPAddr{ - IP: []byte{127, 0, 0, 1}, - Port: 12345, - }, - local: &net.TCPAddr{ - IP: []byte{127, 0, 0, 1}, - Port: 20001, - }, - }, - proto: Http, - }, - want: &core.AuthContext{ - Payload: core.Payload{ID: 1}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := CheckIp(tt.args.conn, tt.args.proto) - if (err != nil) != tt.wantErr { - t.Errorf("CheckIp() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got.Payload, tt.want.Payload) || !reflect.DeepEqual(got.Meta, tt.want.Meta) { - t.Errorf("CheckIp() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCheckPass(t *testing.T) { - - orm.InitForTest( - "host=localhost port=5432 user=proxy password=proxy dbname=app sslmode=disable TimeZone=Asia/Shanghai", - ) - - type args struct { - conn net.Conn - username string - password string - proto Protocol - } - tests := []struct { - name string - args args - want *core.AuthContext - wantErr bool - }{ - { - name: "test-ok", - args: args{ - conn: MockConn{ - remote: &net.TCPAddr{ - IP: []byte{127, 0, 0, 1}, - Port: 12345, - }, - local: &net.TCPAddr{ - IP: []byte{127, 0, 0, 1}, - Port: 20001, - }, - }, - proto: Http, - username: "ip4http", - password: "asdf", - }, - want: &core.AuthContext{ - Payload: core.Payload{ID: 1}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := CheckPass(tt.args.conn, tt.args.proto, tt.args.username, tt.args.password) - if (err != nil) != tt.wantErr { - t.Errorf("CheckPass() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got.Payload, tt.want.Payload) || !reflect.DeepEqual(got.Meta, tt.want.Meta) { - t.Errorf("CheckPass() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/server/fwd/core/conn.go b/server/fwd/core/conn.go index 614c082..a1551b2 100644 --- a/server/fwd/core/conn.go +++ b/server/fwd/core/conn.go @@ -100,5 +100,5 @@ type AuthContext struct { } type Payload struct { - ID uint + ID int32 } diff --git a/server/fwd/ctrl.go b/server/fwd/ctrl.go index bd9ae73..b6c657f 100644 --- a/server/fwd/ctrl.go +++ b/server/fwd/ctrl.go @@ -9,10 +9,11 @@ import ( "log/slog" "net" "proxy-server/pkg/utils" + "proxy-server/server/app" + "proxy-server/server/env" "proxy-server/server/fwd/core" "proxy-server/server/fwd/dispatcher" "proxy-server/server/fwd/metrics" - "proxy-server/server/pkg/env" "proxy-server/server/report" "strconv" "strings" @@ -74,29 +75,26 @@ func (s *Service) processCtrlConn(conn net.Conn) error { if err != nil { return fmt.Errorf("读取客户端 ID 失败: %w", err) } - var clientId = int32(binary.BigEndian.Uint32(recv)) + var client = int32(binary.BigEndian.Uint32(recv)) // 分配端口 var minim uint16 = 20000 var maxim uint16 = 60000 - var fwdPort uint16 + var port uint16 for i := minim; i < maxim; i++ { - var _, ok = s.fwdPortMap[i] + var _, ok = app.Assigns[i] if !ok { - fwdPort = i - s.fwdPortMap[i] = clientId + port = i + app.Assigns[i] = client break } } - if fwdPort == 0 { + if port == 0 { return errors.New("没有可用的端口") } // 报告端口分配 - if s.Config.Id == nil || *s.Config.Id == 0 { - return errors.New("转发服务未成功注册,无法提供服务") - } - err = report.Assigned(s.ctx, *s.Config.Id, clientId, fwdPort) + err = report.Assigned(client, port) if err != nil { return fmt.Errorf("报告端口分配失败: %w", err) } @@ -108,8 +106,8 @@ func (s *Service) processCtrlConn(conn net.Conn) error { } // 启动转发服务 - slog.Info("监听转发端口", "port", fwdPort, "client", clientId) - proxy, err := dispatcher.New(fwdPort) + slog.Info("监听转发端口", "port", port, "client", client) + proxy, err := dispatcher.New(port) if err != nil { return err } diff --git a/server/fwd/data.go b/server/fwd/data.go index 9240b54..0513573 100644 --- a/server/fwd/data.go +++ b/server/fwd/data.go @@ -7,8 +7,8 @@ import ( "net" "proxy-server/pkg/utils" "proxy-server/server/debug" + "proxy-server/server/env" "proxy-server/server/fwd/metrics" - "proxy-server/server/pkg/env" "strconv" "sync" "time" diff --git a/server/fwd/fwd.go b/server/fwd/fwd.go index 9a63755..1d6758f 100644 --- a/server/fwd/fwd.go +++ b/server/fwd/fwd.go @@ -8,12 +8,7 @@ import ( "sync" ) -type Config struct { - Id *int32 -} - type Service struct { - Config *Config ctx context.Context cancel context.CancelFunc @@ -23,21 +18,13 @@ type Service struct { ctrlConnWg utils.CountWaitGroup dataConnWg utils.CountWaitGroup userConnWg utils.CountWaitGroup - - fwdPortMap map[uint16]int32 // 转发端口映射,key 为端口号,value 为边缘节点 ID } -func New(config *Config) *Service { - if config == nil { - config = &Config{} - } - +func New() *Service { ctx, cancel := context.WithCancel(context.Background()) return &Service{ - Config: config, - ctx: ctx, - cancel: cancel, - fwdPortMap: make(map[uint16]int32), + ctx: ctx, + cancel: cancel, } } diff --git a/server/fwd/http/http.go b/server/fwd/http/http.go index d161b78..098e1dd 100644 --- a/server/fwd/http/http.go +++ b/server/fwd/http/http.go @@ -49,18 +49,9 @@ func Process(ctx context.Context, conn net.Conn) (*core.Conn, error) { // 验证账号 authInfo := headers.Get("Proxy-Authorization") - var authCtx *core.AuthContext - var authErr error - if authInfo == "" { - authCtx, authErr = auth.CheckIp(conn, auth.Http) - if authErr != nil { - _, err := conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n")) - if err != nil { - return nil, fmt.Errorf("响应 407 失败: %v", err) - } - return nil, fmt.Errorf("验证账号失败: %v", authErr) - } - } else { + var username *string = nil + var password *string = nil + if authInfo != "" { authParts := strings.Split(authInfo, " ") if len(authParts) != 2 { return nil, errors.New("无效的 Proxy-Authorization") @@ -73,14 +64,17 @@ func Process(ctx context.Context, conn net.Conn) (*core.Conn, error) { return nil, fmt.Errorf("解码认证信息失败: %v", err) } authPair := strings.Split(string(authBytes), ":") - authCtx, authErr = auth.CheckPass(conn, auth.Http, authPair[0], authPair[1]) - if authErr != nil { - _, err := conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n")) - if err != nil { - return nil, fmt.Errorf("响应 407 失败: %v", err) - } - return nil, fmt.Errorf("验证账号失败: %v", authErr) + username = &authPair[0] + password = &authPair[1] + } + + authCtx, err := auth.Protect(conn, auth.Http, username, password) + if err != nil { + _, err = conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n")) + if err != nil { + return nil, fmt.Errorf("响应 407 失败: %v", err) } + return nil, fmt.Errorf("验证账号失败: %v", err) } // 获取 Host diff --git a/server/fwd/socks/socks.go b/server/fwd/socks/socks.go index be1bd6f..fa28dd0 100644 --- a/server/fwd/socks/socks.go +++ b/server/fwd/socks/socks.go @@ -104,10 +104,10 @@ func checkVersion(reader io.Reader) error { } // authenticate 执行认证流程 -func authenticate(ctx context.Context, reader *bufio.Reader, conn net.Conn) (*core.AuthContext, error) { +func authenticate(ctx context.Context, reader *bufio.Reader, conn net.Conn) (authContext *core.AuthContext, err error) { // 版本检查 - err := checkVersion(reader) + err = checkVersion(reader) if err != nil { return nil, err } @@ -122,7 +122,6 @@ func authenticate(ctx context.Context, reader *bufio.Reader, conn net.Conn) (*co return nil, err } - // 密码模式 if slices.Contains(methods, UserPassAuth) { _, err := conn.Write([]byte{Version, byte(UserPassAuth)}) if err != nil { @@ -167,7 +166,7 @@ func authenticate(ctx context.Context, reader *bufio.Reader, conn net.Conn) (*co password := string(passwordBuf) // 检查权限 - authContext, err := auth.CheckPass(conn, auth.Socks5, username, password) + authContext, err = auth.Protect(conn, auth.Socks5, &username, &password) if err != nil { return nil, fmt.Errorf("权限检查失败: %w", err) } @@ -179,30 +178,29 @@ func authenticate(ctx context.Context, reader *bufio.Reader, conn net.Conn) (*co } return authContext, nil - } - // 无认证 - if slices.Contains(methods, NoAuth) { + } else if slices.Contains(methods, NoAuth) { + _, err = conn.Write([]byte{Version, NoAuth}) if err != nil { return nil, fmt.Errorf("响应认证方式失败: %w", err) } - authCtx, err := auth.CheckIp(conn, auth.Socks5) + authContext, err = auth.Protect(conn, auth.Socks5, nil, nil) if err != nil { return nil, fmt.Errorf("权限检查失败: %w", err) } + return authContext, nil - return authCtx, nil + } else { + + _, err = conn.Write([]byte{Version, NoAcceptable}) + if err != nil { + return nil, err + } + + return nil, errors.New("没有适用的认证方式") } - - // 无适用的认证方式 - _, err = conn.Write([]byte{Version, NoAcceptable}) - if err != nil { - return nil, err - } - - return nil, errors.New("没有适用的认证方式") } type Request struct { diff --git a/server/globals/redis.go b/server/globals/redis.go new file mode 100644 index 0000000..f55b18a --- /dev/null +++ b/server/globals/redis.go @@ -0,0 +1,27 @@ +package globals + +import ( + "github.com/redis/go-redis/v9" + "log/slog" + "net" + "proxy-server/server/env" +) + +var Redis *redis.Client + +func InitRedis() { + Redis = redis.NewClient(&redis.Options{ + Addr: net.JoinHostPort(env.RedisHost, env.RedisPort), + DB: env.RedisDb, + Password: env.RedisPass, + }) +} + +func ExitRedis() { + if Redis != nil { + var err = Redis.Close() + if err != nil { + slog.Warn("关闭 Redis 连接失败", "err", err) + } + } +} diff --git a/server/pkg/log/logs.go b/server/log/logs.go similarity index 96% rename from server/pkg/log/logs.go rename to server/log/logs.go index 2e0f458..f46a0a8 100644 --- a/server/pkg/log/logs.go +++ b/server/log/logs.go @@ -3,7 +3,7 @@ package log import ( "log/slog" "os" - "proxy-server/server/pkg/env" + "proxy-server/server/env" "time" "github.com/lmittmann/tint" diff --git a/server/pkg/orm/orm.go b/server/pkg/orm/orm.go deleted file mode 100644 index 98a50de..0000000 --- a/server/pkg/orm/orm.go +++ /dev/null @@ -1,60 +0,0 @@ -package orm - -import ( - "fmt" - "log/slog" - "proxy-server/server/pkg/env" - - "errors" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" -) - -var DB *gorm.DB - -func Init() { - dsn := fmt.Sprintf( - "host=%s port=%d user=%s password=%s dbname=%s sslmode=disable TimeZone=%s", - env.DbHost, env.DbPort, env.DbUsername, env.DbPassword, env.DbDatabase, env.DbTimezone, - ) - - db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ - Logger: logger.Default, - }) - if err != nil { - panic(err) - } - - // 配置连接池 - sqlDb, err := db.DB() - if err != nil { - panic(err) - } - sqlDb.SetMaxIdleConns(10) - sqlDb.SetMaxOpenConns(100) - - DB = db -} - -func InitForTest(dsn string) { - db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ - Logger: logger.Default, - }) - if err != nil { - panic(err) - } - - DB = db -} - -func MaySingle[T any](results []T) (*T, error) { - rsLen := len(results) - if rsLen == 0 { - return nil, errors.New("记录为空") - } - if rsLen > 1 { - slog.Warn("记录不唯一", "ids") - } - return &results[0], nil -} diff --git a/server/report/report.go b/server/report/report.go index cc296d7..f913329 100644 --- a/server/report/report.go +++ b/server/report/report.go @@ -1,91 +1,88 @@ package report import ( - "context" "encoding/base64" "encoding/json" - "errors" + "fmt" "io" - "log/slog" "net/http" - "proxy-server/client/core" - "proxy-server/server/pkg/env" + "proxy-server/server/app" + "proxy-server/server/core" + "proxy-server/server/env" "strings" - "time" ) -func Online(ctx context.Context, name string) (id int32, err error) { +func Online(name string) (err error) { var resp string - resp, err = repeat(ctx, env.EndpointOnline, map[string]any{ + resp, err = call(env.EndpointOnline, map[string]any{ "name": name, "version": core.Version, }) if err != nil { - return 0, err + return err } var body struct { - Id int32 `json:"id"` + Id int32 `json:"id"` + Secret string `json:"secret"` } err = json.Unmarshal([]byte(resp), &body) if err != nil { - return 0, err + return err } - if body.Id == 0 { - return 0, errors.New("服务注册返回 ID 有误") - } else { - return body.Id, nil - } + app.Id = body.Id + app.PlatformSecret = body.Secret + + return nil } -func Offline(ctx context.Context, name string) (err error) { - _, err = repeat(ctx, env.EndpointOffline, map[string]any{ +func Offline(name string) (err error) { + _, err = call(env.EndpointOffline, map[string]any{ "name": name, "version": core.Version, }) return err } -func Assigned(ctx context.Context, id int32, edgeId int32, port uint16) (err error) { - _, err = repeat(ctx, env.EndpointAssigned, map[string]any{ - "proxy": id, +func Assigned(edgeId int32, port uint16) (err error) { + _, err = call(env.EndpointAssigned, map[string]any{ + "proxy": app.Id, "edge": edgeId, "port": port, }) return err } -func repeat(ctx context.Context, endpoint string, body any) (string, error) { +func call(endpoint string, body any) (string, error) { bodyStr, err := json.Marshal(body) if err != nil { panic(err) } - for { - req, err := http.NewRequest("POST", endpoint, strings.NewReader(string(bodyStr))) - if err != nil { - panic(err) - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Basic "+base64.RawURLEncoding.EncodeToString([]byte("proxy:proxy"))) - - resp, err := http.DefaultClient.Do(req) - if resp != nil && resp.StatusCode == http.StatusOK { - var body, err = io.ReadAll(resp.Body) - if err != nil { - return "", err - } - return string(body), nil - } - select { - case <-ctx.Done(): - return "", ctx.Err() - default: - } - - slog.Warn("服务调用失败,五秒后重试", "err", err) - time.Sleep(5 * time.Second) + req, err := http.NewRequest("POST", endpoint, strings.NewReader(string(bodyStr))) + if err != nil { + panic(err) } + + var auth = base64.RawURLEncoding.EncodeToString([]byte(env.ClientId + ":" + env.ClientSecret)) + var basic = fmt.Sprintf("Basic %s", auth) + req.Header.Set("Authorization", basic) + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("请求失败,状态码:%d", resp.StatusCode) + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(respBody), nil } diff --git a/server/server.go b/server/server.go index e09f214..6c5f983 100644 --- a/server/server.go +++ b/server/server.go @@ -2,15 +2,18 @@ package server import ( "context" + "fmt" "log/slog" "os" "os/signal" "proxy-server/pkg/utils" + "proxy-server/server/app" + "proxy-server/server/core" "proxy-server/server/debug" + "proxy-server/server/env" "proxy-server/server/fwd" - "proxy-server/server/pkg/env" - "proxy-server/server/pkg/log" - "proxy-server/server/pkg/orm" + g "proxy-server/server/globals" + "proxy-server/server/log" "proxy-server/server/report" "proxy-server/server/web" "sync" @@ -24,14 +27,7 @@ import ( _ "net/http/pprof" ) -const ( - Version = 1 - RestoreMagic = 0x72 -) - type server struct { - id int32 - name string } func New() *server { @@ -94,22 +90,15 @@ func (s *server) Run() (err error) { // 报告上线 slog.Debug("报告服务上线") - var reportErrCh = make(chan error, 1) - defer close(reportErrCh) - go func() { - id, err := report.Online(ctx, s.name) - if err != nil { - reportErrCh <- err - return - } - s.id = id - }() + err = report.Online(app.Name) + if err != nil { + return fmt.Errorf("服务上线失败: %w", err) + } // 等待退出信号 osQuit := make(chan os.Signal, 1) signal.Notify(osQuit, os.Interrupt, syscall.SIGTERM) - var reportErr error select { case <-osQuit: slog.Info("服务主动退出") @@ -117,26 +106,23 @@ func (s *server) Run() (err error) { slog.Warn("fwd 服务异常退出", "err", err) case err := <-apiQuit: slog.Warn("web 服务异常退出", "err", err) - case reportErr = <-reportErrCh: - slog.Warn("报告服务上线发生错误", "err", reportErr) } cancel() - // 报告下线 - if reportErr == nil { - slog.Debug("报告服务下线") - go func() { - err := report.Offline(ctx, s.name) - if err != nil { - slog.Error("报告服务下线发生错误", "err", err) - } - }() - } - - // 等待其它服务关闭 timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() + // 报告下线 + slog.Debug("报告服务下线") + err = report.Offline(app.Name) + if err != nil { + slog.Error("服务下线失败", "err", err) + } + + // 关闭 redis + g.ExitRedis() + + // 等待其它服务关闭 select { case <-utils.ChanWgWait(timeout, &wg): slog.Info("服务正常关闭") @@ -156,7 +142,7 @@ func (s *server) init() error { log.Init() env.Init() - orm.Init() + g.InitRedis() return nil } @@ -169,31 +155,29 @@ func (s *server) restore() error { return err } - if len(bytes) == 17 && bytes[0] == RestoreMagic { - s.name = uuid.UUID(bytes[1:]).String() - slog.Info("恢复服务名称", "name", s.name) + if len(bytes) == 17 && bytes[0] == core.RestoreMagic { + app.Name = uuid.UUID(bytes[1:]).String() + slog.Info("恢复服务名称", "name", app.Name) } else { var u = uuid.New() - s.name = u.String() + app.Name = u.String() bytes = make([]byte, 17) - bytes[0] = RestoreMagic + bytes[0] = core.RestoreMagic copy(bytes[1:], u[:]) err := os.WriteFile(file, bytes, 0644) if err != nil { return err } - slog.Info("生成服务名称", "name", s.name) + slog.Info("生成服务名称", "name", app.Name) } return nil } func (s *server) startFwd(ctx context.Context) error { - server := fwd.New(&fwd.Config{ - Id: &s.id, - }) + server := fwd.New() go func() { <-ctx.Done() server.Stop() diff --git a/server/web/handlers/auth.go b/server/web/handlers/auth.go new file mode 100644 index 0000000..d181435 --- /dev/null +++ b/server/web/handlers/auth.go @@ -0,0 +1,32 @@ +package handlers + +import ( + "github.com/gofiber/fiber/v2" + "proxy-server/server/app" + "proxy-server/server/core" +) + +type AuthReq struct { + Port uint16 `json:"port"` + core.Permit +} + +func Auth(ctx *fiber.Ctx) (err error) { + + // 安全验证 + var sec core.SecuredReq + if err := ctx.BodyParser(&sec); err != nil { + return err + } + + // 获取请求参数 + req, err := core.Decrypt[AuthReq](&sec, app.PlatformSecret) + if err != nil { + return err + } + + // 保存授权配置 + app.Permits[req.Port] = req.Permit + + return nil +} diff --git a/server/web/web.go b/server/web/web.go index f491606..b7e22dd 100644 --- a/server/web/web.go +++ b/server/web/web.go @@ -1,7 +1,7 @@ package web import ( - "proxy-server/server/pkg/env" + "proxy-server/server/env" "strconv" "github.com/gofiber/fiber/v2"