Compare commits

...

6 Commits

Author SHA1 Message Date
yusing
11e3a9231f fix(route): update path exclusion rules in webui_dev.yml
Add exclusion for all paths under /src/* and modify the websocket protocol header to 'vite-hmr' for improved compatibility with development environments.
2026-03-21 10:28:48 +08:00
yusing
213e4a5cdb feat(auth): add CSRF protection middleware
Implement Signed Double Submit Cookie pattern to prevent CSRF attacks.
Adds CSRF token generation, validation, and middleware for API endpoints.
Safe methods (GET/HEAD/OPTIONS) automatically receive CSRF cookies, while
unsafe methods require X-CSRF-Token header matching the cookie value with
valid HMAC signature. Includes same-origin exemption for login/callback
endpoints to support browser-based authentication flows.
2026-03-19 14:55:47 +08:00
yusing
a541d75bb5 fix(api/file): prevent path traversal in file API
Use os.OpenRoot to restrict file access to the application root,
preventing directory traversal attacks through the file download endpoint.

Also add test to verify path traversal attempts are blocked.
2026-03-19 10:50:58 +08:00
yusing
f67ef3c519 chore(deps): upgrade dependencies 2026-03-19 10:24:23 +08:00
yusing
3c84692b40 ci: add compat Docker image workflow
Add a new GitHub Actions workflow for building Docker images with the "compat" tag on the compat branch. Also update the existing nightly workflow to only run on the compat branch instead of all branches.
2026-03-11 11:51:07 +08:00
yusing
180135dcf9 fix(route): handle synthetic load balancer routes consistently
Synthetic load balancer routes were created with SchemeNone and a zero
proxy port, so the embedded Route logic treated them as excluded routes.
That caused them to be keyed like excluded routes instead of by alias,
which broke HTTP route lookup in reverse proxy load balancer tests.

Override Key and ShouldExclude for synthetic load balancer routes so
they stay addressable through the HTTP route pool while preserving the
existing behavior for normal backend routes.

Also guard addToLoadBalancer against a nil Homepage on an existing
linked route, and update the reverse proxy test to use the in-memory
test entrypoint rather than depending on real listener setup.
2026-03-11 11:47:13 +08:00
23 changed files with 962 additions and 151 deletions

View File

@@ -0,0 +1,25 @@
name: Docker Image CI (nightly)
on:
push:
branches:
- "*" # matches every branch that doesn't contain a '/'
- "*/*" # matches every branch containing a single '/'
- "**" # matches every branch
- "!dependabot/*"
- "!main" # excludes main
- "!compat" # excludes compat branch
jobs:
build-nightly:
uses: ./.github/workflows/docker-image.yml
with:
image_name: ${{ github.repository_owner }}/godoxy
tag: nightly
target: main
build-nightly-agent:
uses: ./.github/workflows/docker-image.yml
with:
image_name: ${{ github.repository_owner }}/godoxy-agent
tag: nightly
target: agent

View File

@@ -1,24 +1,20 @@
name: Docker Image CI (nightly)
name: Docker Image CI (compat)
on:
push:
branches:
- "*" # matches every branch that doesn't contain a '/'
- "*/*" # matches every branch containing a single '/'
- "**" # matches every branch
- "!dependabot/*"
- "!main" # excludes main
- "compat" # compat branch
jobs:
build-nightly:
build-compat:
uses: ./.github/workflows/docker-image.yml
with:
image_name: ${{ github.repository_owner }}/godoxy
tag: nightly
tag: compat
target: main
build-nightly-agent:
build-compat-agent:
uses: ./.github/workflows/docker-image.yml
with:
image_name: ${{ github.repository_owner }}/godoxy-agent
tag: nightly
tag: compat
target: agent

View File

@@ -29,7 +29,7 @@ require (
github.com/pion/transport/v3 v3.1.1
github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.11.1
github.com/yusing/godoxy v0.27.2
github.com/yusing/godoxy v0.27.4
github.com/yusing/godoxy/socketproxy v0.0.0-00010101000000-000000000000
github.com/yusing/goutils v0.7.0
)
@@ -37,7 +37,7 @@ require (
require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/gopkg v0.1.4 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
@@ -58,7 +58,7 @@ require (
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.30.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
@@ -93,8 +93,8 @@ require (
github.com/valyala/fasthttp v1.69.0 // indirect
github.com/yusing/ds v0.4.1 // indirect
github.com/yusing/gointernals v0.2.0 // indirect
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260228084019-4912690d409d // indirect
github.com/yusing/goutils/http/websocket v0.0.0-20260228084019-4912690d409d // indirect
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260310041503-e48e337bd10c // indirect
github.com/yusing/goutils/http/websocket v0.0.0-20260310041503-e48e337bd10c // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
@@ -103,10 +103,10 @@ require (
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
golang.org/x/arch v0.25.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/text v0.35.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,15 +1,15 @@
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
@@ -77,8 +77,8 @@ github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy0
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
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/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -115,8 +115,8 @@ github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/luthermonson/go-proxmox v0.4.0 h1:LKXpG9d64zTaQF79wV0kfOnnSwIcdG39m7sc4ga+XZs=
github.com/luthermonson/go-proxmox v0.4.0/go.mod h1:U6dAkJ+iiwaeb1g/LMWpWuWN4nmvWeXhmoMuYJMumS4=
github.com/magefile/mage v1.16.0 h1:2naaPmNwrMicCdLBCRDw288hcyClO9lmnm6FMpXyJ5I=
github.com/magefile/mage v1.16.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magefile/mage v1.16.1 h1:j5UwkdA48xTlGs0Hcm1Q3sSAcxBorntQjiewDNMsqlo=
github.com/magefile/mage v1.16.1/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
@@ -236,12 +236,12 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
@@ -254,12 +254,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -2,6 +2,6 @@ module github.com/yusing/godoxy/cmd/h2c_test_server
go 1.26.1
require golang.org/x/net v0.51.0
require golang.org/x/net v0.52.0
require golang.org/x/text v0.34.0 // indirect
require golang.org/x/text v0.35.0 // indirect

View File

@@ -1,4 +1,4 @@
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=

40
go.mod
View File

@@ -22,7 +22,7 @@ replace (
)
require (
github.com/PuerkitoBio/goquery v1.11.0 // parsing HTML for extract fav icon; modify_html middleware
github.com/PuerkitoBio/goquery v1.12.0 // parsing HTML for extract fav icon; modify_html middleware
github.com/cenkalti/backoff/v5 v5.0.3 // backoff for retrying operations
github.com/coreos/go-oidc/v3 v3.17.0 // oidc authentication
github.com/fsnotify/fsnotify v1.9.0 // file watcher
@@ -37,15 +37,15 @@ require (
github.com/puzpuzpuz/xsync/v4 v4.4.0 // lock free map for concurrent operations
github.com/rs/zerolog v1.34.0 // logging
github.com/vincent-petithory/dataurl v1.0.0 // data url for fav icon
golang.org/x/crypto v0.48.0 // encrypting password with bcrypt
golang.org/x/net v0.51.0 // HTTP header utilities
golang.org/x/crypto v0.49.0 // encrypting password with bcrypt
golang.org/x/net v0.52.0 // HTTP header utilities
golang.org/x/oauth2 v0.36.0 // oauth2 authentication
golang.org/x/sync v0.20.0 // errgroup and singleflight for concurrent operations
golang.org/x/time v0.15.0 // time utilities
)
require (
github.com/bytedance/gopkg v0.1.3 // xxhash64 for fast hash
github.com/bytedance/gopkg v0.1.4 // xxhash64 for fast hash
github.com/bytedance/sonic v1.15.0 // fast json parsing
github.com/docker/cli v29.3.0+incompatible // needs docker/cli/cli/connhelper connection helper for docker client
github.com/goccy/go-yaml v1.19.2 // yaml parsing for different config files
@@ -60,13 +60,13 @@ require (
github.com/stretchr/testify v1.11.1 // testing framework
github.com/valyala/fasthttp v1.69.0 // fast http for health check
github.com/yusing/ds v0.4.1 // data structures and algorithms
github.com/yusing/godoxy/agent v0.0.0-20260228194043-59238adb5b6e
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260228194043-59238adb5b6e
github.com/yusing/godoxy/agent v0.0.0-20260311035107-3c84692b40d7
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260311035107-3c84692b40d7
github.com/yusing/gointernals v0.2.0
github.com/yusing/goutils v0.7.0
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260228084019-4912690d409d
github.com/yusing/goutils/http/websocket v0.0.0-20260228084019-4912690d409d
github.com/yusing/goutils/server v0.0.0-20260228084019-4912690d409d
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260310041503-e48e337bd10c
github.com/yusing/goutils/http/websocket v0.0.0-20260310041503-e48e337bd10c
github.com/yusing/goutils/server v0.0.0-20260310041503-e48e337bd10c
)
require (
@@ -102,14 +102,14 @@ require (
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
github.com/googleapis/gax-go/v2 v2.19.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magefile/mage v1.16.0 // indirect
github.com/magefile/mage v1.16.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.72 // indirect
@@ -140,13 +140,13 @@ require (
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/atomic v1.11.0
go.uber.org/ratelimit v0.3.1 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/mod v0.34.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.42.0 // indirect
google.golang.org/api v0.270.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.2 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/tools v0.43.0 // indirect
google.golang.org/api v0.272.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/ini.v1 v1.67.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
@@ -169,7 +169,7 @@ require (
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/go-resty/resty/v2 v2.17.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/google/go-querystring v1.2.0 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
@@ -177,8 +177,8 @@ require (
github.com/linode/linodego v1.66.0 // indirect
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
github.com/nrdcg/goinwx v0.12.0 // indirect
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.0 // indirect
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.0 // indirect
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2 // indirect
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pion/dtls/v3 v3.1.2 // indirect
github.com/pion/logging v0.2.4 // indirect

68
go.sum
View File

@@ -29,8 +29,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 h1:4iB+IesclUX
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 h1:h/33OxYLqBk0BYmEbSUy7MlvgQR/m1w1/7OJFKoPL1I=
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q=
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
@@ -49,8 +49,8 @@ github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVk
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
@@ -130,8 +130,8 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
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/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -153,8 +153,8 @@ 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/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gotify/server/v2 v2.9.1 h1:wsQUCdYJ4ZvP7RIRKDLtAtmFQc3kxbrv3QqccO5RWzs=
@@ -197,8 +197,8 @@ github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/magefile/mage v1.16.0 h1:2naaPmNwrMicCdLBCRDw288hcyClO9lmnm6FMpXyJ5I=
github.com/magefile/mage v1.16.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magefile/mage v1.16.1 h1:j5UwkdA48xTlGs0Hcm1Q3sSAcxBorntQjiewDNMsqlo=
github.com/magefile/mage v1.16.1/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
@@ -227,10 +227,10 @@ github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.0 h1:r/RTdSUa7oUSTkDku6Bz8QTcYr5nhoiCURAgFZ0Z7EY=
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.0/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.0 h1:dia9msUR1RHXMn4RHiyOI/8Ud2v3EZyDoHGVRTaq3/I=
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.0/go.mod h1:xUUiSvfkzsJIhCtjNE15qvfdHORt+9C2uRRtbz10R4Q=
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2 h1:SlJJlU2lgrB8dB9UFopLUNaO+JxtzLK4UWHYY3e3gvo=
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2 h1:c8B4nduK77OvP6WnsfzfZgnK6uHhZETZGYnxNfu23Go=
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2/go.mod h1:twxIRVoHRrrMZp1A8Mh46CqCG/gyzLnImEb4dpzleIE=
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -365,15 +365,15 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -383,8 +383,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -434,8 +434,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -444,21 +444,21 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.270.0 h1:4rJZbIuWSTohczG9mG2ukSDdt9qKx4sSSHIydTN26L4=
google.golang.org/api v0.270.0/go.mod h1:5+H3/8DlXpQWrSz4RjGGwz5HfJAQSEI8Bc6JqQNH77U=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js=
google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

Submodule goutils updated: e48e337bd1...635feb302e

109
internal/api/csrf.go Normal file
View File

@@ -0,0 +1,109 @@
package api
import (
"net"
"net/http"
"net/url"
"strings"
"github.com/gin-gonic/gin"
"github.com/yusing/godoxy/internal/auth"
apitypes "github.com/yusing/goutils/apitypes"
)
// CSRFMiddleware implements the Signed Double Submit Cookie pattern.
//
// Safe methods (GET/HEAD/OPTIONS): ensure a signed CSRF cookie exists.
// Unsafe methods (POST/PUT/DELETE/PATCH): require X-CSRF-Token header
// matching the cookie value, with a valid HMAC signature.
func CSRFMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
switch c.Request.Method {
case http.MethodGet, http.MethodHead, http.MethodOptions:
ensureCSRFCookie(c)
c.Next()
return
}
if allowSameOriginAuthBootstrap(c.Request) {
ensureCSRFCookie(c)
c.Next()
return
}
cookie, err := c.Request.Cookie(auth.CSRFCookieName)
if err != nil {
// No cookie at all — issue one so the frontend can retry.
reissueCSRFCookie(c)
c.JSON(http.StatusForbidden, apitypes.Error("missing CSRF token"))
c.Abort()
return
}
cookieToken := canonicalCSRFToken(cookie.Value)
headerToken := canonicalCSRFToken(c.GetHeader(auth.CSRFHeaderName))
if headerToken == "" || cookieToken != headerToken || !auth.ValidateCSRFToken(cookieToken) {
// Stale or forged token — issue a fresh one so the
// frontend can read the new cookie and retry.
reissueCSRFCookie(c)
c.JSON(http.StatusForbidden, apitypes.Error("invalid CSRF token"))
c.Abort()
return
}
c.Next()
}
}
func ensureCSRFCookie(c *gin.Context) {
if _, err := c.Request.Cookie(auth.CSRFCookieName); err == nil {
return
}
reissueCSRFCookie(c)
}
func reissueCSRFCookie(c *gin.Context) {
token, err := auth.GenerateCSRFToken()
if err != nil {
return
}
auth.SetCSRFCookie(c.Writer, c.Request, token)
}
func allowSameOriginAuthBootstrap(r *http.Request) bool {
if r.Method != http.MethodPost {
return false
}
switch r.URL.Path {
case "/api/v1/auth/login", "/api/v1/auth/callback":
return requestSourceMatchesHost(r)
default:
return false
}
}
func requestSourceMatchesHost(r *http.Request) bool {
for _, header := range []string{"Origin", "Referer"} {
value := r.Header.Get(header)
if value == "" {
continue
}
u, err := url.Parse(value)
if err != nil || u.Host == "" {
return false
}
return normalizeHost(u.Hostname()) == normalizeHost(r.Host)
}
return false
}
func normalizeHost(host string) string {
host = strings.ToLower(host)
if h, _, err := net.SplitHostPort(host); err == nil {
return h
}
return host
}
func canonicalCSRFToken(token string) string {
return strings.Trim(strings.TrimSpace(token), "\"")
}

280
internal/api/csrf_test.go Normal file
View File

@@ -0,0 +1,280 @@
package api
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/yusing/godoxy/internal/auth"
autocert "github.com/yusing/godoxy/internal/autocert/types"
"github.com/yusing/godoxy/internal/common"
"github.com/yusing/goutils/task"
)
func TestAuthCheckIssuesCSRFCookie(t *testing.T) {
handler := newAuthenticatedHandler(t)
req := httptest.NewRequest(http.MethodHead, "/api/v1/auth/check", nil)
req.Host = "app.example.com"
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusFound, rec.Code)
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
require.NotNil(t, csrfCookie)
assert.NotEmpty(t, csrfCookie.Value)
assert.Empty(t, csrfCookie.Domain)
assert.Equal(t, "/", csrfCookie.Path)
assert.Equal(t, http.SameSiteStrictMode, csrfCookie.SameSite)
}
func TestUserPassCallbackAllowsSameOriginFormPostWithoutCSRFCookie(t *testing.T) {
handler := newAuthenticatedHandler(t)
req := newJSONRequest(t, http.MethodPost, "/api/v1/auth/callback", map[string]string{
"username": common.APIUser,
"password": common.APIPassword,
})
req.Host = "app.example.com"
req.Header.Set("Origin", "https://app.example.com")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
tokenCookie := findCookie(rec.Result().Cookies(), "godoxy_token")
require.NotNil(t, tokenCookie)
assert.NotEmpty(t, tokenCookie.Value)
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
require.NotNil(t, csrfCookie)
assert.NotEmpty(t, csrfCookie.Value)
}
func TestUserPassCallbackRejectsCrossOriginPostWithoutCSRFCookie(t *testing.T) {
handler := newAuthenticatedHandler(t)
req := newJSONRequest(t, http.MethodPost, "/api/v1/auth/callback", map[string]string{
"username": common.APIUser,
"password": common.APIPassword,
})
req.Host = "app.example.com"
req.Header.Set("Origin", "https://evil.example.com")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusForbidden, rec.Code)
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
require.NotNil(t, csrfCookie)
assert.NotEmpty(t, csrfCookie.Value)
}
func TestUserPassCallbackAcceptsValidCSRFCookie(t *testing.T) {
handler := newAuthenticatedHandler(t)
csrfCookie := issueCSRFCookie(t, handler)
req := newJSONRequest(t, http.MethodPost, "/api/v1/auth/callback", map[string]string{
"username": common.APIUser,
"password": common.APIPassword,
})
req.Host = "app.example.com"
req.AddCookie(csrfCookie)
req.Header.Set(auth.CSRFHeaderName, csrfCookie.Value)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
tokenCookie := findCookie(rec.Result().Cookies(), "godoxy_token")
require.NotNil(t, tokenCookie)
assert.NotEmpty(t, tokenCookie.Value)
}
func TestUnsafeRequestAcceptsQuotedCSRFCookieValue(t *testing.T) {
handler := newAuthenticatedHandler(t)
csrfCookie := issueCSRFCookie(t, handler)
sessionToken := issueSessionToken(t)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/logout", nil)
req.Host = "app.example.com"
req.Header.Set("Cookie", `godoxy_token=`+sessionToken+`; godoxy_csrf="`+csrfCookie.Value+`"`)
req.Header.Set(auth.CSRFHeaderName, csrfCookie.Value)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusFound, rec.Code)
}
func TestLogoutRequiresCSRFCookie(t *testing.T) {
handler := newAuthenticatedHandler(t)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/logout", nil)
req.Host = "app.example.com"
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusForbidden, rec.Code)
}
func TestLoginAllowsSameOriginPostWithoutCSRFCookie(t *testing.T) {
handler := newAuthenticatedHandler(t)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", nil)
req.Host = "app.example.com"
req.Header.Set("Origin", "https://app.example.com")
req.Header.Set("Accept", "text/html")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusFound, rec.Code)
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
require.NotNil(t, csrfCookie)
assert.NotEmpty(t, csrfCookie.Value)
}
func TestGetLogoutRouteStillAvailableForFrontend(t *testing.T) {
handler := newAuthenticatedHandler(t)
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/logout", nil)
req.Host = "app.example.com"
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusFound, rec.Code)
}
func TestCertRenewRejectsCrossOriginWebSocketRequest(t *testing.T) {
handler := newAuthenticatedHandler(t)
provider := &stubAutocertProvider{}
sessionToken := issueSessionToken(t)
req := httptest.NewRequest(http.MethodGet, "/api/v1/cert/renew", nil)
req.Host = "app.example.com"
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", "websocket")
req.Header.Set("Sec-WebSocket-Version", "13")
req.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
req.Header.Set("Origin", "https://evil.example.com")
req.AddCookie(&http.Cookie{Name: "godoxy_token", Value: sessionToken})
req = req.WithContext(context.WithValue(req.Context(), autocert.ContextKey{}, provider))
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusForbidden, rec.Code)
assert.Zero(t, provider.forceExpiryCalls)
}
func newAuthenticatedHandler(t *testing.T) *gin.Engine {
t.Helper()
gin.SetMode(gin.TestMode)
prevSecret := common.APIJWTSecret
prevUser := common.APIUser
prevPassword := common.APIPassword
prevDisableAuth := common.DebugDisableAuth
prevIssuerURL := common.OIDCIssuerURL
common.APIJWTSecret = []byte("0123456789abcdef0123456789abcdef")
common.APIUser = "username"
common.APIPassword = "password"
common.DebugDisableAuth = false
common.OIDCIssuerURL = ""
t.Cleanup(func() {
common.APIJWTSecret = prevSecret
common.APIUser = prevUser
common.APIPassword = prevPassword
common.DebugDisableAuth = prevDisableAuth
common.OIDCIssuerURL = prevIssuerURL
})
require.NoError(t, auth.Initialize())
return NewHandler(true)
}
func issueCSRFCookie(t *testing.T, handler http.Handler) *http.Cookie {
t.Helper()
req := httptest.NewRequest(http.MethodHead, "/api/v1/auth/check", nil)
req.Host = "app.example.com"
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
require.NotNil(t, csrfCookie)
return csrfCookie
}
func issueSessionToken(t *testing.T) string {
t.Helper()
userpass, ok := auth.GetDefaultAuth().(*auth.UserPassAuth)
require.True(t, ok)
token, err := userpass.NewToken()
require.NoError(t, err)
return token
}
func newJSONRequest(t *testing.T, method, target string, body any) *http.Request {
t.Helper()
encoded, err := json.Marshal(body)
require.NoError(t, err)
req := httptest.NewRequest(method, target, bytes.NewReader(encoded))
req.Header.Set("Content-Type", "application/json")
return req
}
func findCookie(cookies []*http.Cookie, name string) *http.Cookie {
for _, cookie := range cookies {
if cookie.Name == name {
return cookie
}
}
return nil
}
type stubAutocertProvider struct {
forceExpiryCalls int
}
func (p *stubAutocertProvider) GetCert(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return nil, nil
}
func (p *stubAutocertProvider) GetCertInfos() ([]autocert.CertInfo, error) {
return nil, nil
}
func (p *stubAutocertProvider) ScheduleRenewalAll(task.Parent) {}
func (p *stubAutocertProvider) ObtainCertAll() error {
return nil
}
func (p *stubAutocertProvider) ForceExpiryAll() bool {
p.forceExpiryCalls++
return true
}
func (p *stubAutocertProvider) WaitRenewalDone(context.Context) bool {
return true
}

View File

@@ -56,11 +56,11 @@ func NewHandler(requireAuth bool) *gin.Engine {
if auth.IsEnabled() && requireAuth {
v1Auth := r.Group("/api/v1/auth")
{
v1Auth.HEAD("/check", authApi.Check)
v1Auth.POST("/login", authApi.Login)
v1Auth.HEAD("/check", CSRFMiddleware(), authApi.Check)
v1Auth.POST("/login", CSRFMiddleware(), authApi.Login)
v1Auth.GET("/callback", authApi.Callback)
v1Auth.POST("/callback", authApi.Callback)
v1Auth.POST("/logout", authApi.Logout)
v1Auth.POST("/callback", CSRFMiddleware(), authApi.Callback)
v1Auth.POST("/logout", CSRFMiddleware(), authApi.Logout)
v1Auth.GET("/logout", authApi.Logout)
}
}
@@ -68,6 +68,7 @@ func NewHandler(requireAuth bool) *gin.Engine {
v1 := r.Group("/api/v1")
if auth.IsEnabled() && requireAuth {
v1.Use(AuthMiddleware())
v1.Use(CSRFMiddleware())
}
if common.APISkipOriginCheck {
v1.Use(SkipOriginCheckMiddleware())

View File

@@ -19,6 +19,7 @@ import (
// @Tags cert,websocket
// @Produce plain
// @Success 200 {object} apitypes.SuccessResponse
// @Failure 400 {object} apitypes.ErrorResponse
// @Failure 403 {object} apitypes.ErrorResponse
// @Failure 500 {object} apitypes.ErrorResponse
// @Router /cert/renew [get]

View File

@@ -1,6 +1,7 @@
package fileapi
import (
"io"
"net/http"
"os"
"path"
@@ -44,7 +45,14 @@ func Get(c *gin.Context) {
return
}
content, err := os.ReadFile(request.FileType.GetPath(request.Filename))
f, err := os.OpenInRoot(".", request.FileType.GetPath(request.Filename))
if err != nil {
c.Error(apitypes.InternalServerError(err, "failed to open root"))
return
}
defer f.Close()
content, err := io.ReadAll(f)
if err != nil {
c.Error(apitypes.InternalServerError(err, "failed to read file"))
return

View File

@@ -0,0 +1,68 @@
package fileapi_test
import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
api "github.com/yusing/godoxy/internal/api"
fileapi "github.com/yusing/godoxy/internal/api/v1/file"
"github.com/yusing/goutils/fs"
)
func TestGet_PathTraversalBlocked(t *testing.T) {
gin.SetMode(gin.TestMode)
files, err := fs.ListFiles("..", 1, false)
require.NoError(t, err)
require.Greater(t, len(files), 0, "no files found")
relativePath := files[0]
fileContent, err := os.ReadFile(relativePath)
require.NoError(t, err)
r := gin.New()
r.Use(api.ErrorHandler())
r.GET("/api/v1/file/content", fileapi.Get)
tests := []struct {
name string
filename string
queryEscaped bool
}{
{
name: "dotdot_traversal",
filename: relativePath,
},
{
name: "url_encoded_dotdot_traversal",
filename: relativePath,
queryEscaped: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filename := tt.filename
if tt.queryEscaped {
filename = url.QueryEscape(filename)
}
url := "/api/v1/file/content?type=config&filename=" + filename
req := httptest.NewRequest(http.MethodGet, url, nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
// "Blocked" means we should never successfully read the outside file.
assert.NotEqual(t, http.StatusOK, w.Code)
assert.NotEqual(t, fileContent, w.Body.String())
})
}
}

84
internal/auth/csrf.go Normal file
View File

@@ -0,0 +1,84 @@
package auth
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"net/http"
"strings"
"github.com/yusing/godoxy/internal/common"
"golang.org/x/crypto/hkdf"
)
const (
CSRFCookieName = "godoxy_csrf"
CSRFHKDFSalt = "godoxy-csrf"
CSRFHeaderName = "X-CSRF-Token"
csrfTokenLength = 32
)
// csrfSecret is derived from API_JWT_SECRET via HKDF for cryptographic
// separation from JWT signing. Falls back to an ephemeral random key
// for OIDC-only setups where no JWT secret is configured.
var csrfSecret = func() []byte {
if common.APIJWTSecret != nil {
return hkdf.Extract(sha256.New, common.APIJWTSecret, []byte(CSRFHKDFSalt))
}
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
panic("failed to generate CSRF secret: " + err.Error())
}
return b
}()
func GenerateCSRFToken() (string, error) {
nonce := make([]byte, csrfTokenLength)
if _, err := rand.Read(nonce); err != nil {
return "", err
}
nonceHex := hex.EncodeToString(nonce)
return nonceHex + "." + csrfSign(nonceHex), nil
}
// ValidateCSRFToken checks the HMAC signature embedded in the token.
// This prevents subdomain cookie-injection attacks where an attacker
// sets a forged CSRF cookie — they cannot produce a valid signature
// without the ephemeral secret.
func ValidateCSRFToken(token string) bool {
nonce, sig, ok := strings.Cut(token, ".")
if !ok || len(nonce) != csrfTokenLength*2 {
return false
}
return hmac.Equal([]byte(sig), []byte(csrfSign(nonce)))
}
func csrfSign(nonce string) string {
mac := hmac.New(sha256.New, csrfSecret)
mac.Write([]byte(nonce))
return hex.EncodeToString(mac.Sum(nil))
}
func SetCSRFCookie(w http.ResponseWriter, r *http.Request, token string) {
http.SetCookie(w, &http.Cookie{
Name: CSRFCookieName,
Value: token,
HttpOnly: false,
Secure: common.APIJWTSecure,
SameSite: http.SameSiteStrictMode,
Path: "/",
})
}
func ClearCSRFCookie(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: CSRFCookieName,
Value: "",
MaxAge: -1,
HttpOnly: false,
Secure: common.APIJWTSecure,
SameSite: http.SameSiteStrictMode,
Path: "/",
})
}

View File

@@ -6,7 +6,7 @@ replace github.com/yusing/godoxy => ../..
require (
github.com/go-acme/lego/v4 v4.32.0
github.com/yusing/godoxy v0.27.2
github.com/yusing/godoxy v0.27.4
)
require (
@@ -23,7 +23,7 @@ require (
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/gopkg v0.1.4 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
@@ -49,7 +49,7 @@ require (
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
github.com/googleapis/gax-go/v2 v2.19.0 // indirect
github.com/gotify/server/v2 v2.9.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
@@ -65,8 +65,8 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/nrdcg/goacmedns v0.2.0 // indirect
github.com/nrdcg/goinwx v0.12.0 // indirect
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.0 // indirect
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.0 // indirect
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2 // indirect
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2 // indirect
github.com/nrdcg/porkbun v0.4.0 // indirect
github.com/ovh/go-ovh v1.9.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
@@ -90,17 +90,17 @@ require (
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/ratelimit v0.3.1 // indirect
golang.org/x/arch v0.25.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/mod v0.34.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.42.0 // indirect
google.golang.org/api v0.270.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.2 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/tools v0.43.0 // indirect
google.golang.org/api v0.272.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/ini.v1 v1.67.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

View File

@@ -37,8 +37,8 @@ github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
@@ -105,8 +105,8 @@ 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/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
github.com/gotify/server/v2 v2.9.1 h1:wsQUCdYJ4ZvP7RIRKDLtAtmFQc3kxbrv3QqccO5RWzs=
github.com/gotify/server/v2 v2.9.1/go.mod h1:8scw0hiExomp4rJDrXBwRIcgQm7kv74P4Z4B+iM4l8w=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
@@ -150,10 +150,10 @@ github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.0 h1:r/RTdSUa7oUSTkDku6Bz8QTcYr5nhoiCURAgFZ0Z7EY=
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.0/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.0 h1:dia9msUR1RHXMn4RHiyOI/8Ud2v3EZyDoHGVRTaq3/I=
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.0/go.mod h1:xUUiSvfkzsJIhCtjNE15qvfdHORt+9C2uRRtbz10R4Q=
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2 h1:SlJJlU2lgrB8dB9UFopLUNaO+JxtzLK4UWHYY3e3gvo=
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2 h1:c8B4nduK77OvP6WnsfzfZgnK6uHhZETZGYnxNfu23Go=
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2/go.mod h1:twxIRVoHRrrMZp1A8Mh46CqCG/gyzLnImEb4dpzleIE=
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
@@ -223,12 +223,12 @@ go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
@@ -240,25 +240,25 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.270.0 h1:4rJZbIuWSTohczG9mG2ukSDdt9qKx4sSSHIydTN26L4=
google.golang.org/api v0.270.0/go.mod h1:5+H3/8DlXpQWrSz4RjGGwz5HfJAQSEI8Bc6JqQNH77U=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js=
google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -123,6 +123,24 @@ func (r *ReverseProxyRoute) ReverseProxy() *reverseproxy.ReverseProxy {
return r.rp
}
func (r *ReverseProxyRoute) isSyntheticLoadBalancerRoute() bool {
return r.loadBalancer != nil && r.rp == nil
}
func (r *ReverseProxyRoute) Key() string {
if r.isSyntheticLoadBalancerRoute() {
return r.Alias
}
return r.Route.Key()
}
func (r *ReverseProxyRoute) ShouldExclude() bool {
if r.isSyntheticLoadBalancerRoute() {
return false
}
return r.Route.ShouldExclude()
}
// Start implements task.TaskStarter.
func (r *ReverseProxyRoute) Start(parent task.Parent) error {
r.task = parent.Subtask("http."+r.Name(), false)
@@ -206,7 +224,7 @@ func (r *ReverseProxyRoute) addToLoadBalancer(parent task.Parent, ep entrypoint.
linked = l.(*ReverseProxyRoute) // it must be a reverse proxy route
lb = linked.loadBalancer
lb.UpdateConfigIfNeeded(cfg)
if linked.Homepage.Name == "" {
if linked.Homepage == nil || linked.Homepage.Name == "" {
linked.Homepage = r.Homepage
}
} else {

View File

@@ -1,16 +1,165 @@
package route
import (
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
"github.com/yusing/godoxy/internal/homepage"
route "github.com/yusing/godoxy/internal/route/types"
"github.com/yusing/godoxy/internal/types"
"github.com/yusing/goutils/task"
)
type testPool[T interface{ Key() string }] struct {
mu sync.RWMutex
items map[string]T
}
func newTestPool[T interface{ Key() string }]() *testPool[T] {
return &testPool[T]{items: make(map[string]T)}
}
func (p *testPool[T]) Get(alias string) (T, bool) {
p.mu.RLock()
defer p.mu.RUnlock()
v, ok := p.items[alias]
return v, ok
}
func (p *testPool[T]) Iter(yield func(alias string, r T) bool) {
p.mu.RLock()
defer p.mu.RUnlock()
for alias, r := range p.items {
if !yield(alias, r) {
return
}
}
}
func (p *testPool[T]) Size() int {
p.mu.RLock()
defer p.mu.RUnlock()
return len(p.items)
}
func (p *testPool[T]) Add(r T) {
p.mu.Lock()
defer p.mu.Unlock()
p.items[r.Key()] = r
}
func (p *testPool[T]) Del(r T) {
p.mu.Lock()
defer p.mu.Unlock()
delete(p.items, r.Key())
}
type testEntrypoint struct {
httpRoutes *testPool[types.HTTPRoute]
streamRoutes *testPool[types.StreamRoute]
excludedRoutes *testPool[types.Route]
}
func newTestEntrypoint() *testEntrypoint {
return &testEntrypoint{
httpRoutes: newTestPool[types.HTTPRoute](),
streamRoutes: newTestPool[types.StreamRoute](),
excludedRoutes: newTestPool[types.Route](),
}
}
func (ep *testEntrypoint) SupportProxyProtocol() bool { return false }
func (ep *testEntrypoint) DisablePoolsLog(bool) {}
func (ep *testEntrypoint) GetRoute(alias string) (types.Route, bool) {
if r, ok := ep.httpRoutes.Get(alias); ok {
return r, true
}
if r, ok := ep.streamRoutes.Get(alias); ok {
return r, true
}
if r, ok := ep.excludedRoutes.Get(alias); ok {
return r, true
}
return nil, false
}
func (ep *testEntrypoint) StartAddRoute(r types.Route) error {
if r.ShouldExclude() {
ep.excludedRoutes.Add(r)
return nil
}
switch rt := r.(type) {
case types.HTTPRoute:
ep.httpRoutes.Add(rt)
return nil
case types.StreamRoute:
ep.streamRoutes.Add(rt)
return nil
default:
return fmt.Errorf("unknown route type: %T", r)
}
}
func (ep *testEntrypoint) IterRoutes(yield func(r types.Route) bool) {
ep.httpRoutes.Iter(func(_ string, r types.HTTPRoute) bool {
return yield(r)
})
ep.streamRoutes.Iter(func(_ string, r types.StreamRoute) bool {
return yield(r)
})
ep.excludedRoutes.Iter(func(_ string, r types.Route) bool {
return yield(r)
})
}
func (ep *testEntrypoint) NumRoutes() int {
return ep.httpRoutes.Size() + ep.streamRoutes.Size() + ep.excludedRoutes.Size()
}
func (ep *testEntrypoint) RoutesByProvider() map[string][]types.Route {
return map[string][]types.Route{}
}
func (ep *testEntrypoint) HTTPRoutes() entrypoint.PoolLike[types.HTTPRoute] {
return ep.httpRoutes
}
func (ep *testEntrypoint) StreamRoutes() entrypoint.PoolLike[types.StreamRoute] {
return ep.streamRoutes
}
func (ep *testEntrypoint) ExcludedRoutes() entrypoint.RWPoolLike[types.Route] {
return ep.excludedRoutes
}
func (ep *testEntrypoint) GetHealthInfo() map[string]types.HealthInfo {
return nil
}
func (ep *testEntrypoint) GetHealthInfoWithoutDetail() map[string]types.HealthInfoWithoutDetail {
return nil
}
func (ep *testEntrypoint) GetHealthInfoSimple() map[string]types.HealthStatus {
return nil
}
func TestReverseProxyRoute(t *testing.T) {
t.Run("LinkToLoadBalancer", func(t *testing.T) {
testTask := task.GetTestTask(t)
entrypoint.SetCtx(testTask, newTestEntrypoint())
cfg := Route{
Alias: "test",
Scheme: route.SchemeHTTP,
@@ -36,4 +185,75 @@ func TestReverseProxyRoute(t *testing.T) {
require.NoError(t, err)
assert.NotNil(t, r2)
})
t.Run("LoadBalancerRoute", func(t *testing.T) {
testTask := task.GetTestTask(t)
entrypoint.SetCtx(testTask, newTestEntrypoint())
newServer := func() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
}
srv1 := newServer()
t.Cleanup(srv1.Close)
srv2 := newServer()
t.Cleanup(srv2.Close)
srv3 := newServer()
t.Cleanup(srv3.Close)
makeRoute := func(alias string, target *httptest.Server) *Route {
t.Helper()
targetURL, err := url.Parse(target.URL)
require.NoError(t, err)
host, portStr, err := net.SplitHostPort(targetURL.Host)
require.NoError(t, err)
port, err := strconv.Atoi(portStr)
require.NoError(t, err)
return &Route{
Alias: alias,
Scheme: route.SchemeHTTP,
Host: host,
Port: Port{Proxy: port},
Homepage: &homepage.ItemConfig{
Show: true,
},
LoadBalance: &types.LoadBalancerConfig{
Link: "lb-test",
},
HealthCheck: types.HealthCheckConfig{
Path: "/",
Interval: 2 * time.Second,
Timeout: time.Second,
UseGet: true,
},
}
}
_, err := NewStartedTestRoute(t, makeRoute("lb-1", srv1))
require.NoError(t, err)
_, err = NewStartedTestRoute(t, makeRoute("lb-2", srv2))
require.NoError(t, err)
_, err = NewStartedTestRoute(t, makeRoute("lb-3", srv3))
require.NoError(t, err)
ep := entrypoint.FromCtx(testTask.Context())
require.NotNil(t, ep)
lbRoute, ok := ep.HTTPRoutes().Get("lb-test")
require.True(t, ok)
lb, ok := lbRoute.(*ReverseProxyRoute)
require.True(t, ok)
require.False(t, lb.ShouldExclude())
require.NotNil(t, lb.loadBalancer)
require.NotNil(t, lb.HealthMonitor())
assert.Equal(t, route.SchemeNone, lb.Scheme)
assert.Empty(t, lb.Host)
assert.Zero(t, lb.Port.Proxy)
assert.Equal(t, "3/3 servers are healthy", lb.HealthMonitor().Detail())
})
}

View File

@@ -6,9 +6,10 @@
!path glob("/@tanstack-start/*")
!path glob("/@vite-plugin-pwa/*")
!path glob("/__tsd/*")
!path glob("/src/*")
!path /@react-refresh
!path /@vite/client
!path regex("/\?token=[a-zA-Z0-9-_]+")
!header Sec-Websocket-Protocol vite-hmr
!path glob("/@id/*")
!path glob("/api/v1/auth/*")
!path glob("/auth/*")

View File

@@ -7,7 +7,7 @@ replace github.com/yusing/goutils => ../goutils
require (
github.com/gorilla/mux v1.8.1
github.com/yusing/goutils v0.7.0
golang.org/x/net v0.51.0
golang.org/x/net v0.52.0
)
require (
@@ -18,5 +18,5 @@ require (
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
github.com/rs/zerolog v1.34.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/text v0.35.0 // indirect
)

View File

@@ -21,14 +21,14 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=