mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-13 12:09:42 +02:00
Compare commits
23 Commits
v0.27.1
...
feat/inbou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b082d6dc77 | ||
|
|
2a6ad90b72 | ||
|
|
bb5725afe5 | ||
|
|
c7f9c2889b | ||
|
|
2a3823091d | ||
|
|
6cafbcf669 | ||
|
|
3de80bf9b1 | ||
|
|
e0cba8f415 | ||
|
|
361189118d | ||
|
|
5e461842dc | ||
|
|
41d0d28ca8 | ||
|
|
1c091bbfee | ||
|
|
8e670f15e5 | ||
|
|
11e3a9231f | ||
|
|
213e4a5cdb | ||
|
|
a541d75bb5 | ||
|
|
f67ef3c519 | ||
|
|
3c84692b40 | ||
|
|
180135dcf9 | ||
|
|
291fe67c31 | ||
|
|
93263eedbf | ||
|
|
41de86de75 | ||
|
|
59238adb5b |
@@ -58,8 +58,12 @@ GODOXY_API_ADDR=127.0.0.1:8888
|
||||
|
||||
# Local API listening address (unauthenticated, optional)
|
||||
# Useful for local development, debugging or automation
|
||||
# Must bind to loopback only (localhost / 127.0.0.1 / ::1) unless GODOXY_LOCAL_API_ALLOW_NON_LOOPBACK is true
|
||||
GODOXY_LOCAL_API_ADDR=
|
||||
|
||||
# WARNING: exposing local API to either LAN or WAN is dangerous, do not enable this unless you know what you're doing
|
||||
GODOXY_LOCAL_API_ALLOW_NON_LOOPBACK=false
|
||||
|
||||
# Metrics
|
||||
GODOXY_METRICS_DISABLE_CPU=false
|
||||
GODOXY_METRICS_DISABLE_MEMORY=false
|
||||
@@ -76,4 +80,4 @@ DOCKER_SOCKET=/var/run/docker.sock
|
||||
SOCKET_PROXY_LISTEN_ADDR=127.0.0.1:2375
|
||||
|
||||
# Debug mode
|
||||
GODOXY_DEBUG=false
|
||||
GODOXY_DEBUG=false
|
||||
|
||||
25
.github/workflows/docker-image-compat.yml
vendored
Normal file
25
.github/workflows/docker-image-compat.yml
vendored
Normal 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
|
||||
16
.github/workflows/docker-image-nightly.yml
vendored
16
.github/workflows/docker-image-nightly.yml
vendored
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Stage 1: deps
|
||||
FROM golang:1.26.0-alpine AS deps
|
||||
FROM golang:1.26.2-alpine AS deps
|
||||
HEALTHCHECK NONE
|
||||
|
||||
# package version does not matter
|
||||
|
||||
4
Makefile
4
Makefile
@@ -198,8 +198,8 @@ gen-swagger:
|
||||
|
||||
gen-api-types: gen-swagger
|
||||
# --disable-throw-on-error
|
||||
bunx --bun swagger-typescript-api generate --sort-types --generate-union-enums --axios --add-readonly --route-types \
|
||||
--responses -o ${WEBUI_DIR}/src/lib -n api.ts -p internal/api/v1/docs/swagger.json
|
||||
bunx --bun swagger-typescript-api generate --sort-types --generate-union-enums --add-readonly --route-types \
|
||||
--responses -o ${WEBUI_DIR}/src/lib -n api.ts -p internal/api/v1/docs/swagger.json
|
||||
|
||||
.PHONY: gen-cli build-cli update-wiki
|
||||
|
||||
|
||||
61
agent/go.mod
61
agent/go.mod
@@ -1,10 +1,14 @@
|
||||
module github.com/yusing/godoxy/agent
|
||||
|
||||
go 1.26.0
|
||||
go 1.26.2
|
||||
|
||||
exclude (
|
||||
github.com/moby/moby/api v1.53.0 // allow older daemon versions
|
||||
github.com/moby/moby/api v1.54.0 // allow older daemon versions
|
||||
github.com/moby/moby/api v1.54.1 // allow older daemon versions
|
||||
github.com/moby/moby/client v0.2.2 // allow older daemon versions
|
||||
github.com/moby/moby/client v0.3.0 // allow older daemon versions
|
||||
github.com/moby/moby/client v0.4.0 // allow older daemon versions
|
||||
)
|
||||
|
||||
replace (
|
||||
@@ -21,51 +25,51 @@ exclude github.com/containerd/nerdctl/mod/tigron v0.0.0
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.15.0
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/pion/dtls/v3 v3.1.2
|
||||
github.com/pion/transport/v3 v3.1.1
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/rs/zerolog v1.35.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/yusing/godoxy v0.26.0
|
||||
github.com/yusing/godoxy v0.27.5
|
||||
github.com/yusing/godoxy/socketproxy v0.0.0-00010101000000-000000000000
|
||||
github.com/yusing/goutils v0.7.0
|
||||
)
|
||||
|
||||
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/sonic/loader v0.5.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/cli v29.2.1+incompatible // indirect
|
||||
github.com/docker/cli v29.4.0+incompatible // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.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.30.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.2 // 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
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-isatty v0.0.21 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/moby/api v1.52.0 // indirect
|
||||
github.com/moby/moby/client v0.2.1 // indirect
|
||||
@@ -73,7 +77,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
|
||||
github.com/pion/logging v0.2.4 // indirect
|
||||
github.com/pion/transport/v4 v4.0.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
@@ -81,29 +85,30 @@ require (
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.26.1 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.69.0 // indirect
|
||||
github.com/valyala/fasthttp v1.70.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-20260223150038-3be815cb6e3b // indirect
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260223150038-3be815cb6e3b // indirect
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260408062000-d93e9de0157f // indirect
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260408062000-d93e9de0157f // 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
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
|
||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
golang.org/x/arch v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
golang.org/x/arch v0.26.0 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
180
agent/go.sum
180
agent/go.sum
@@ -1,19 +1,19 @@
|
||||
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/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
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.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||
github.com/andybalholm/brotli v1.2.1/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=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
||||
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@@ -24,21 +24,20 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A=
|
||||
github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/diskfs/go-diskfs v1.7.0 h1:vonWmt5CMowXwUc79jWyGrf2DIMeoOjkLlMnQYGVOs8=
|
||||
github.com/diskfs/go-diskfs v1.7.0/go.mod h1:LhQyXqOugWFRahYUSw47NyZJPezFzB9UELwhpszLP/k=
|
||||
github.com/diskfs/go-diskfs v1.9.1 h1:g/UCTC5jZFomhtH4DyF9fG1eRHGgDIjSd1hSjEErXn0=
|
||||
github.com/diskfs/go-diskfs v1.9.1/go.mod h1:rW9+4MPN1tbMpQqRZlcM3YQsh3Ucc+Q1k1iIqzzmZcg=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg=
|
||||
github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v29.4.0+incompatible h1:+IjXULMetlvWJiuSI0Nbor36lcJ5BTcVpUmB21KBoVM=
|
||||
github.com/docker/cli v29.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -51,14 +50,14 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-acme/lego/v4 v4.32.0 h1:z7Ss7aa1noabhKj+DBzhNCO2SM96xhE3b0ucVW3x8Tc=
|
||||
github.com/go-acme/lego/v4 v4.32.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
||||
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||
github.com/go-acme/lego/v4 v4.33.0 h1:2KrRKieG+VczT4zvVXKAY7Tp/S3XLvh/QImofBALRAM=
|
||||
github.com/go-acme/lego/v4 v4.33.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -73,15 +72,14 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
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.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||
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=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -93,14 +91,14 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
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.0 h1:2zRCl28wkq0oc6YNbyJS2n0dDOOVvOS3Oez5AG2ij54=
|
||||
github.com/gotify/server/v2 v2.9.0/go.mod h1:249wwlUqHTr0QsiKARGtFVqds0pNLIMjYLinHyMACdQ=
|
||||
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/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -111,19 +109,16 @@ 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/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
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/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.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak=
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/luthermonson/go-proxmox v0.4.1 h1:1WnUBHzCQEa5goHuzewkApi6LKtQcFB8/tXTtS2D5w8=
|
||||
github.com/luthermonson/go-proxmox v0.4.1/go.mod h1:U6dAkJ+iiwaeb1g/LMWpWuWN4nmvWeXhmoMuYJMumS4=
|
||||
github.com/magefile/mage v1.17.1 h1:F1d2lnLSlbQDM0Plq6Ac4NtaHxkxTK8t5nrMY9SkoNA=
|
||||
github.com/magefile/mage v1.17.1/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
@@ -143,8 +138,8 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
|
||||
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=
|
||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
@@ -155,7 +150,6 @@ github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k
|
||||
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
|
||||
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
|
||||
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -169,15 +163,14 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA
|
||||
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
|
||||
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.1 h1:RMOq8XqzfuGx1X0TEIlS9OXbbFmqLY2/wJppghz66YY=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.1/go.mod h1:DQYYve14WgCRN/XnKeHl4266jXK0DgYkYXkfZ4Fp98k=
|
||||
github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI=
|
||||
github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
||||
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/slog-common v0.22.0 h1:WyPxYRg/c5xUmxZJbtd0QgysHlLBhRA+MngKdJieHxE=
|
||||
github.com/samber/slog-common v0.22.0/go.mod h1:d/6OaSlzdkl9PFpfRLgn8FwY1OW6EFmPtBpsHX4MrU0=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.2 h1:DIFzfzDTxHeRyGlfg/D7b2by7VVzcsBTybRPrzjWF4c=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.2/go.mod h1:2q6cYK2OcN6YfQE/WyCnUtigc+yYf3ozqGsGmRwZR6I=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
@@ -202,8 +195,8 @@ github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
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.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
|
||||
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
||||
github.com/valyala/fasthttp v1.70.0 h1:LAhMGcWk13QZWm85+eg8ZBNbrq5mnkWFGbHMUJHIdXA=
|
||||
github.com/valyala/fasthttp v1.70.0/go.mod h1:oDZEHHkJ/Buyklg6uURmYs19442zFSnCIfX3j1FY3pE=
|
||||
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
|
||||
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
@@ -214,50 +207,49 @@ github.com/yusing/gointernals v0.2.0 h1:jyWB3kdUPkuU6s0r8QY/sS5h2WNBF4Kfisly8dtS
|
||||
github.com/yusing/gointernals v0.2.0/go.mod h1:xGzNbPGMm5Z8kG0t4JYISMscw+gMQlgghkLxlgRZv5Y=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
|
||||
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
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.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
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.26.0 h1:jZ6dpec5haP/fUv1kLCbuJy6dnRrfX6iVK08lZBFpk4=
|
||||
golang.org/x/arch v0.26.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
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=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
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/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
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/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
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.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
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=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26.0-alpine AS builder
|
||||
FROM golang:1.26.2-alpine AS builder
|
||||
|
||||
HEALTHCHECK NONE
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module github.com/yusing/godoxy/cmd/bench_server
|
||||
|
||||
go 1.26.0
|
||||
go 1.26.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/yusing/godoxy/cli
|
||||
|
||||
go 1.26.0
|
||||
go 1.26.2
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26.0-alpine AS builder
|
||||
FROM golang:1.26.2-alpine AS builder
|
||||
|
||||
HEALTHCHECK NONE
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module github.com/yusing/godoxy/cmd/h2c_test_server
|
||||
|
||||
go 1.26.0
|
||||
go 1.26.2
|
||||
|
||||
require golang.org/x/net v0.50.0
|
||||
require golang.org/x/net v0.53.0
|
||||
|
||||
require golang.org/x/text v0.34.0 // indirect
|
||||
require golang.org/x/text v0.36.0 // indirect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
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.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
|
||||
@@ -19,6 +19,17 @@
|
||||
|
||||
# 3. other providers, see https://docs.godoxy.dev/DNS-01-Providers
|
||||
|
||||
# Inbound mTLS profiles (optional)
|
||||
#
|
||||
# Reusable named profiles for inbound HTTPS client-certificate validation.
|
||||
# A profile must trust either the system CA store, one or more CA files, or both.
|
||||
#
|
||||
# inbound_mtls_profiles:
|
||||
# corp:
|
||||
# use_system_cas: true
|
||||
# ca_files:
|
||||
# - /app/certs/corp-ca.pem
|
||||
|
||||
# Access Control
|
||||
# When enabled, it will be applied globally at connection level,
|
||||
# all incoming connections (web, tcp and udp) will be checked against the ACL rules.
|
||||
@@ -52,6 +63,9 @@ entrypoint:
|
||||
# Note that HTTP/3 with proxy protocol is not supported yet.
|
||||
support_proxy_protocol: false
|
||||
|
||||
# To relay the client address to a TCP upstream (UDP relay is not supported yet)
|
||||
relay_proxy_protocol_header: false
|
||||
|
||||
# Below define an example of middleware config
|
||||
# 1. set security headers
|
||||
# 2. block non local IP connections
|
||||
@@ -146,6 +160,11 @@ providers:
|
||||
# secret: aaaa-bbbb-cccc-dddd
|
||||
# no_tls_verify: true
|
||||
|
||||
# To relay the downstream client address to a TCP upstream, set
|
||||
# `relay_proxy_protocol_header: true` on that specific TCP route in route
|
||||
# configuration (for example, see providers.example.yml). UDP relay is not
|
||||
# supported yet.
|
||||
|
||||
# Match domains
|
||||
# See https://docs.godoxy.dev/Certificates-and-domain-matching
|
||||
#
|
||||
|
||||
122
go.mod
122
go.mod
@@ -1,10 +1,14 @@
|
||||
module github.com/yusing/godoxy
|
||||
|
||||
go 1.26.0
|
||||
go 1.26.2
|
||||
|
||||
exclude (
|
||||
github.com/moby/moby/api v1.53.0 // allow older daemon versions
|
||||
github.com/moby/moby/api v1.54.0 // allow older daemon versions
|
||||
github.com/moby/moby/api v1.54.1 // allow older daemon versions
|
||||
github.com/moby/moby/client v0.2.2 // allow older daemon versions
|
||||
github.com/moby/moby/client v0.3.0 // allow older daemon versions
|
||||
github.com/moby/moby/client v0.4.0 // allow older daemon versions
|
||||
)
|
||||
|
||||
replace (
|
||||
@@ -20,70 +24,70 @@ 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/coreos/go-oidc/v3 v3.18.0 // oidc authentication
|
||||
github.com/fsnotify/fsnotify v1.9.0 // file watcher
|
||||
github.com/gin-gonic/gin v1.11.0 // api server
|
||||
github.com/go-acme/lego/v4 v4.32.0 // acme client
|
||||
github.com/go-playground/validator/v10 v10.30.1 // validator
|
||||
github.com/gin-gonic/gin v1.12.0 // api server
|
||||
github.com/go-acme/lego/v4 v4.33.0 // acme client
|
||||
github.com/go-playground/validator/v10 v10.30.2 // validator
|
||||
github.com/gobwas/glob v0.2.3 // glob matcher for route rules
|
||||
github.com/gorilla/websocket v1.5.3 // websocket for API and agent
|
||||
github.com/gotify/server/v2 v2.9.0 // reference the Message struct for json response
|
||||
github.com/gotify/server/v2 v2.9.1 // reference the Message struct for json response
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // fuzzy search for searching icons and filtering metrics
|
||||
github.com/pires/go-proxyproto v0.11.0 // proxy protocol support
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 // lock free map for concurrent operations
|
||||
github.com/rs/zerolog v1.34.0 // logging
|
||||
github.com/rs/zerolog v1.35.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.50.0 // HTTP header utilities
|
||||
golang.org/x/oauth2 v0.35.0 // oauth2 authentication
|
||||
golang.org/x/sync v0.19.0 // errgroup and singleflight for concurrent operations
|
||||
golang.org/x/time v0.14.0 // time utilities
|
||||
golang.org/x/crypto v0.50.0 // encrypting password with bcrypt
|
||||
golang.org/x/net v0.53.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.2.1+incompatible // needs docker/cli/cli/connhelper connection helper for docker client
|
||||
github.com/docker/cli v29.4.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
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // jwt authentication
|
||||
github.com/luthermonson/go-proxmox v0.4.0 // proxmox API client
|
||||
github.com/luthermonson/go-proxmox v0.4.1 // proxmox API client
|
||||
github.com/moby/moby/api v1.52.0 // docker API
|
||||
github.com/moby/moby/client v0.2.1 // docker client
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 // maxminddb for geoip database
|
||||
github.com/quic-go/quic-go v0.59.0 // http3 support
|
||||
github.com/shirou/gopsutil/v4 v4.26.1 // system information
|
||||
github.com/shirou/gopsutil/v4 v4.26.3 // system information
|
||||
github.com/spf13/afero v1.15.0 // afero for file system operations
|
||||
github.com/stretchr/testify v1.11.1 // testing framework
|
||||
github.com/valyala/fasthttp v1.69.0 // fast http for health check
|
||||
github.com/valyala/fasthttp v1.70.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-20260224071728-0eba04510480
|
||||
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260224071728-0eba04510480
|
||||
github.com/yusing/godoxy/agent v0.0.0-20260409084515-361189118df3
|
||||
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260409084515-361189118df3
|
||||
github.com/yusing/gointernals v0.2.0
|
||||
github.com/yusing/goutils v0.7.0
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260223150038-3be815cb6e3b
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260223150038-3be815cb6e3b
|
||||
github.com/yusing/goutils/server v0.0.0-20260223150038-3be815cb6e3b
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260408062000-d93e9de0157f
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260408062000-d93e9de0157f
|
||||
github.com/yusing/goutils/server v0.0.0-20260408062000-d93e9de0157f
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.18.2 // indirect
|
||||
cloud.google.com/go/auth v0.20.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/buger/goterm v1.0.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/diskfs/go-diskfs v1.7.0 // indirect
|
||||
github.com/diskfs/go-diskfs v1.9.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/djherbis/times v1.6.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0
|
||||
@@ -91,7 +95,7 @@ require (
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
@@ -99,17 +103,17 @@ require (
|
||||
github.com/gofrs/flock v0.13.0 // indirect
|
||||
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.12 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.21.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.15.0 // indirect
|
||||
github.com/magefile/mage v1.17.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-isatty v0.0.21 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
@@ -120,31 +124,31 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/ovh/go-ovh v1.9.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/samber/lo v1.52.0 // indirect
|
||||
github.com/samber/slog-common v0.20.0 // indirect
|
||||
github.com/samber/slog-zerolog/v2 v2.9.1 // indirect
|
||||
github.com/samber/lo v1.53.0 // indirect
|
||||
github.com/samber/slog-common v0.22.0 // indirect
|
||||
github.com/samber/slog-zerolog/v2 v2.9.2 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/sony/gobreaker v1.0.0 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0
|
||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.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/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
google.golang.org/api v0.268.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260223185530-2f722ef697dc // indirect
|
||||
google.golang.org/grpc v1.79.1 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
google.golang.org/api v0.275.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
||||
google.golang.org/grpc v1.80.0 // 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
|
||||
@@ -153,30 +157,30 @@ require (
|
||||
|
||||
require (
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
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/compress v1.18.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/linode/linodego v1.65.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
||||
github.com/linode/linodego v1.67.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
|
||||
github.com/nrdcg/goinwx v0.12.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.111.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.111.0 // 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
|
||||
@@ -188,9 +192,9 @@ require (
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/vultr/govultr/v3 v3.27.0 // indirect
|
||||
github.com/vultr/govultr/v3 v3.30.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/arch v0.24.0 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||
golang.org/x/arch v0.26.0 // indirect
|
||||
)
|
||||
|
||||
239
go.sum
239
go.sum
@@ -1,5 +1,5 @@
|
||||
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
||||
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
||||
cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
|
||||
cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
@@ -11,8 +11,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpz
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
|
||||
@@ -25,18 +25,18 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1 h1:edShSHV3DV90+kt+CMaEXEzR9QF7wFrPJxVGz2blMIU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1/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=
|
||||
github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||
github.com/andybalholm/brotli v1.2.1/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/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
@@ -49,12 +49,12 @@ 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=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
||||
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@@ -65,19 +65,18 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/diskfs/go-diskfs v1.7.0 h1:vonWmt5CMowXwUc79jWyGrf2DIMeoOjkLlMnQYGVOs8=
|
||||
github.com/diskfs/go-diskfs v1.7.0/go.mod h1:LhQyXqOugWFRahYUSw47NyZJPezFzB9UELwhpszLP/k=
|
||||
github.com/diskfs/go-diskfs v1.9.1 h1:g/UCTC5jZFomhtH4DyF9fG1eRHGgDIjSd1hSjEErXn0=
|
||||
github.com/diskfs/go-diskfs v1.9.1/go.mod h1:rW9+4MPN1tbMpQqRZlcM3YQsh3Ucc+Q1k1iIqzzmZcg=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg=
|
||||
github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v29.4.0+incompatible h1:+IjXULMetlvWJiuSI0Nbor36lcJ5BTcVpUmB21KBoVM=
|
||||
github.com/docker/cli v29.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -96,14 +95,14 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-acme/lego/v4 v4.32.0 h1:z7Ss7aa1noabhKj+DBzhNCO2SM96xhE3b0ucVW3x8Tc=
|
||||
github.com/go-acme/lego/v4 v4.32.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
||||
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||
github.com/go-acme/lego/v4 v4.33.0 h1:2KrRKieG+VczT4zvVXKAY7Tp/S3XLvh/QImofBALRAM=
|
||||
github.com/go-acme/lego/v4 v4.33.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -120,8 +119,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
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.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
||||
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
@@ -130,11 +129,10 @@ 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=
|
||||
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
@@ -151,14 +149,14 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
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.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12/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/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.21.0 h1:h45NjjzEO3faG9Lg/cFrBh2PgegVVgzqKzuZl/wMbiI=
|
||||
github.com/googleapis/gax-go/v2 v2.21.0/go.mod h1:But/NJU6TnZsrLai/xBAQLLz+Hc7fHZJt/hsCz3Fih4=
|
||||
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.0 h1:2zRCl28wkq0oc6YNbyJS2n0dDOOVvOS3Oez5AG2ij54=
|
||||
github.com/gotify/server/v2 v2.9.0/go.mod h1:249wwlUqHTr0QsiKARGtFVqds0pNLIMjYLinHyMACdQ=
|
||||
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/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
@@ -177,8 +175,8 @@ github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0=
|
||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
|
||||
@@ -191,21 +189,18 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
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/linode/linodego v1.65.0 h1:SdsuGD8VSsPWeShXpE7ihl5vec+fD3MgwhnfYC/rj7k=
|
||||
github.com/linode/linodego v1.65.0/go.mod h1:tOFiTErdjkbVnV+4S0+NmIE9dqqZUEM2HsJaGu8wMh8=
|
||||
github.com/linode/linodego v1.67.0 h1:pomhFuuCCJI4N6emtB9027h1yXHY2/MIT0hwHEFwvq4=
|
||||
github.com/linode/linodego v1.67.0/go.mod h1:+9mbdu0P3WMRCl0QbVfiFavR+Iel7TCRDJk3nInyx14=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
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.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak=
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/magefile/mage v1.17.1 h1:F1d2lnLSlbQDM0Plq6Ac4NtaHxkxTK8t5nrMY9SkoNA=
|
||||
github.com/magefile/mage v1.17.1/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI=
|
||||
github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
@@ -227,10 +222,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.108.2 h1:OWijzl3nHUApvTivl+3+78dbBwmyEHOnb+W9m6ixGbk=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 h1:9LsjN/zaIN7H8JE61NHpbWhxF0UGY96+kMlk3g8OvGU=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2/go.mod h1:32vZH06TuwZSn+IDMO1qcDvC2vHVlzUALCwXGWPA+dc=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.111.0 h1:i7tfJOtD7VRw94QwLFh2apHwKCQ+i+8SAEgrcWDKM90=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.111.0/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.111.0 h1:4OCyuewA2SntKr1cagj6OHWLbwLxCfR7fNe3QRoFT/c=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.111.0/go.mod h1:kQSJmotDqt21aMIn0k8dSZiOPSWEscfjXAYaPNsXyh4=
|
||||
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=
|
||||
@@ -241,8 +236,8 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
|
||||
github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
|
||||
@@ -255,9 +250,8 @@ github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG6
|
||||
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
|
||||
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/pkg/xattr v0.4.12 h1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM=
|
||||
github.com/pkg/xattr v0.4.12/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -273,15 +267,14 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA
|
||||
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
|
||||
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.1 h1:RMOq8XqzfuGx1X0TEIlS9OXbbFmqLY2/wJppghz66YY=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.1/go.mod h1:DQYYve14WgCRN/XnKeHl4266jXK0DgYkYXkfZ4Fp98k=
|
||||
github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI=
|
||||
github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
||||
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/slog-common v0.22.0 h1:WyPxYRg/c5xUmxZJbtd0QgysHlLBhRA+MngKdJieHxE=
|
||||
github.com/samber/slog-common v0.22.0/go.mod h1:d/6OaSlzdkl9PFpfRLgn8FwY1OW6EFmPtBpsHX4MrU0=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.2 h1:DIFzfzDTxHeRyGlfg/D7b2by7VVzcsBTybRPrzjWF4c=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.2/go.mod h1:2q6cYK2OcN6YfQE/WyCnUtigc+yYf3ozqGsGmRwZR6I=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
@@ -316,12 +309,12 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
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.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
|
||||
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
||||
github.com/valyala/fasthttp v1.70.0 h1:LAhMGcWk13QZWm85+eg8ZBNbrq5mnkWFGbHMUJHIdXA=
|
||||
github.com/valyala/fasthttp v1.70.0/go.mod h1:oDZEHHkJ/Buyklg6uURmYs19442zFSnCIfX3j1FY3pE=
|
||||
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
|
||||
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
|
||||
github.com/vultr/govultr/v3 v3.27.0 h1:J8etMyu/Jh5+idMsu2YZpOWmDXXHeW4VZnkYXmJYHx8=
|
||||
github.com/vultr/govultr/v3 v3.27.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY=
|
||||
github.com/vultr/govultr/v3 v3.30.0 h1:kTeDJ+5or6g4CQJmD6Kmz4R63B18poNZ8RP87r9LZdg=
|
||||
github.com/vultr/govultr/v3 v3.30.0/go.mod h1:2zyUw9yADQaGwKnwDesmIOlBNLrm7edsCfWHFJpWKf8=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
@@ -333,45 +326,47 @@ github.com/yusing/gointernals v0.2.0 h1:jyWB3kdUPkuU6s0r8QY/sS5h2WNBF4Kfisly8dtS
|
||||
github.com/yusing/gointernals v0.2.0/go.mod h1:xGzNbPGMm5Z8kG0t4JYISMscw+gMQlgghkLxlgRZv5Y=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
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.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
|
||||
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/arch v0.26.0 h1:jZ6dpec5haP/fUv1kLCbuJy6dnRrfX6iVK08lZBFpk4=
|
||||
golang.org/x/arch v0.26.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
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.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
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=
|
||||
@@ -381,10 +376,10 @@ 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.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
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=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -392,8 +387,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -403,17 +398,15 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -432,31 +425,31 @@ 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/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
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.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
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.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
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.268.0 h1:hgA3aS4lt9rpF5RCCkX0Q2l7DvHgvlb53y4T4u6iKkA=
|
||||
google.golang.org/api v0.268.0/go.mod h1:HXMyMH496wz+dAJwD/GkAPLd3ZL33Kh0zEG32eNvy9w=
|
||||
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-20260223185530-2f722ef697dc h1:51Wupg8spF+5FC6D+iMKbOddFjMckETnNnEiZ+HX37s=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260223185530-2f722ef697dc/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/api v0.275.0 h1:vfY5d9vFVJeWEZT65QDd9hbndr7FyZ2+6mIzGAh71NI=
|
||||
google.golang.org/api v0.275.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
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=
|
||||
|
||||
2
goutils
2
goutils
Submodule goutils updated: 4912690d40...8090ca8a21
109
internal/api/csrf.go
Normal file
109
internal/api/csrf.go
Normal 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
280
internal/api/csrf_test.go
Normal 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
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -115,7 +115,7 @@ No dedicated metrics exposed by handlers. Request metrics collected by middlewar
|
||||
|
||||
- All endpoints (except `/api/v1/version`) require authentication
|
||||
- Input validation using Gin binding tags
|
||||
- Path traversal prevention in file operations
|
||||
- File read/write handlers are rooted per file type (`config/` or `config/middlewares/`) to prevent traversal into sibling paths
|
||||
- WebSocket connections use same auth middleware as HTTP
|
||||
|
||||
## Failure Modes and Recovery
|
||||
@@ -195,5 +195,3 @@ func listContainers() ([]Container, error) {
|
||||
```bash
|
||||
curl http://localhost:8888/health
|
||||
```
|
||||
|
||||
)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 := request.FileType.OpenFile(request.Filename, os.O_RDONLY, 0)
|
||||
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
|
||||
@@ -65,9 +73,18 @@ func GetFileType(file string) FileType {
|
||||
return FileTypeProvider
|
||||
}
|
||||
|
||||
func (t FileType) GetPath(filename string) string {
|
||||
func (t FileType) RootPath() string {
|
||||
if t == FileTypeMiddleware {
|
||||
return path.Join(common.MiddlewareComposeBasePath, filename)
|
||||
return common.MiddlewareComposeBasePath
|
||||
}
|
||||
return path.Join(common.ConfigBasePath, filename)
|
||||
return common.ConfigBasePath
|
||||
}
|
||||
|
||||
func (t FileType) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
root, err := os.OpenRoot(t.RootPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer root.Close()
|
||||
return root.OpenFile(filename, flag, perm)
|
||||
}
|
||||
|
||||
101
internal/api/v1/file/get_test.go
Normal file
101
internal/api/v1/file/get_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package fileapi_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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"
|
||||
)
|
||||
|
||||
func setupFileAPITestRoot(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
oldWD, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
root := t.TempDir()
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(root, "config", "middlewares"), 0o755))
|
||||
require.NoError(t, os.Chdir(root))
|
||||
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, os.Chdir(oldWD))
|
||||
})
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
func newFileContentRouter() *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
r := gin.New()
|
||||
r.Use(api.ErrorHandler())
|
||||
r.GET("/api/v1/file/content", fileapi.Get)
|
||||
r.PUT("/api/v1/file/content", fileapi.Set)
|
||||
return r
|
||||
}
|
||||
|
||||
func TestGet_PathTraversalBlocked(t *testing.T) {
|
||||
root := setupFileAPITestRoot(t)
|
||||
|
||||
const (
|
||||
insideFilename = "providers.yml"
|
||||
insideContent = "app: inside\n"
|
||||
outsideContent = "app: outside\n"
|
||||
)
|
||||
|
||||
require.NoError(t, os.WriteFile(filepath.Join(root, "config", insideFilename), []byte(insideContent), 0o644))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(root, "secret.yml"), []byte(outsideContent), 0o644))
|
||||
|
||||
r := newFileContentRouter()
|
||||
|
||||
t.Run("read_in_root_file", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/file/content?type=config&filename="+insideFilename, nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, insideContent, w.Body.String())
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
queryEscaped bool
|
||||
}{
|
||||
{
|
||||
name: "dotdot_traversal_to_sibling_file",
|
||||
filename: "../secret.yml",
|
||||
},
|
||||
{
|
||||
name: "url_encoded_dotdot_traversal_to_sibling_file",
|
||||
filename: "../secret.yml",
|
||||
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, outsideContent, w.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,14 @@ func Set(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(request.FileType.GetPath(request.Filename), content, 0o644)
|
||||
f, err := request.FileType.OpenFile(request.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to open file"))
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(content)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to write file"))
|
||||
return
|
||||
|
||||
87
internal/api/v1/file/set_test.go
Normal file
87
internal/api/v1/file/set_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package fileapi_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const validProviderYAML = `app:
|
||||
host: attacker.com
|
||||
port: 443
|
||||
scheme: https
|
||||
`
|
||||
|
||||
func TestSet_PathTraversalBlocked(t *testing.T) {
|
||||
root := setupFileAPITestRoot(t)
|
||||
r := newFileContentRouter()
|
||||
|
||||
t.Run("write_in_root_file", func(t *testing.T) {
|
||||
req := httptest.NewRequest(
|
||||
http.MethodPut,
|
||||
"/api/v1/file/content?type=provider&filename=providers.yml",
|
||||
strings.NewReader(validProviderYAML),
|
||||
)
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
content, err := os.ReadFile(filepath.Join(root, "config", "providers.yml"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, validProviderYAML, string(content))
|
||||
})
|
||||
|
||||
const originalContent = "do not overwrite\n"
|
||||
require.NoError(t, os.WriteFile(filepath.Join(root, "secret.yml"), []byte(originalContent), 0o644))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
queryEscaped bool
|
||||
}{
|
||||
{
|
||||
name: "dotdot_traversal_to_sibling_file",
|
||||
filename: "../secret.yml",
|
||||
},
|
||||
{
|
||||
name: "url_encoded_dotdot_traversal_to_sibling_file",
|
||||
filename: "../secret.yml",
|
||||
queryEscaped: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filename := tt.filename
|
||||
if tt.queryEscaped {
|
||||
filename = url.QueryEscape(filename)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodPut,
|
||||
"/api/v1/file/content?type=provider&filename="+filename,
|
||||
strings.NewReader(validProviderYAML),
|
||||
)
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.NotEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
content, err := os.ReadFile(filepath.Join(root, "secret.yml"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, originalContent, string(content))
|
||||
})
|
||||
}
|
||||
}
|
||||
84
internal/auth/csrf.go
Normal file
84
internal/auth/csrf.go
Normal 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: "/",
|
||||
})
|
||||
}
|
||||
@@ -36,6 +36,7 @@ var (
|
||||
LocalAPIHTTPHost,
|
||||
LocalAPIHTTPPort,
|
||||
LocalAPIHTTPURL = env.GetAddrEnv("LOCAL_API_ADDR", "", "http")
|
||||
LocalAPIAllowNonLoopback = env.GetEnvBool("LOCAL_API_ALLOW_NON_LOOPBACK", false)
|
||||
|
||||
APIJWTSecure = env.GetEnvBool("API_JWT_SECURE", true)
|
||||
APIJWTSecret = decodeJWTKey(env.GetEnvString("API_JWT_SECRET", ""))
|
||||
|
||||
@@ -30,6 +30,7 @@ type Config struct {
|
||||
ACL *acl.Config
|
||||
AutoCert *autocert.Config
|
||||
Entrypoint entrypoint.Config
|
||||
InboundMTLSProfiles map[string]types.InboundMTLSProfile
|
||||
Providers Providers
|
||||
MatchDomains []string
|
||||
Homepage homepage.Config
|
||||
@@ -71,6 +72,8 @@ type State interface {
|
||||
}
|
||||
```
|
||||
|
||||
`StartAPIServers` starts the authenticated API from `common.APIHTTPAddr` and, when `LOCAL_API_ADDR` is set, an additional **unauthenticated** local listener from `common.LocalAPIHTTPAddr`. That address is validated for loopback binds (with optional DNS resolution); non-loopback requires `LOCAL_API_ALLOW_NON_LOOPBACK` and logs a warning. See [`internal/api/v1/README.md`](../api/v1/README.md#configuration-surface).
|
||||
|
||||
### Exported functions
|
||||
|
||||
```go
|
||||
|
||||
94
internal/config/inbound_mtls_validation_test.go
Normal file
94
internal/config/inbound_mtls_validation_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
entrypointtypes "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
routeimpl "github.com/yusing/godoxy/internal/route"
|
||||
route "github.com/yusing/godoxy/internal/route/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/goutils/server"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
func TestRouteValidateInboundMTLSProfile(t *testing.T) {
|
||||
prev := config.WorkingState.Load()
|
||||
t.Cleanup(func() {
|
||||
if prev != nil {
|
||||
config.WorkingState.Store(prev)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("rejects unknown profile", func(t *testing.T) {
|
||||
state := &stubState{cfg: &config.Config{
|
||||
InboundMTLSProfiles: map[string]types.InboundMTLSProfile{
|
||||
"known": {UseSystemCAs: true},
|
||||
},
|
||||
}}
|
||||
config.WorkingState.Store(state)
|
||||
|
||||
r := &routeimpl.Route{
|
||||
Alias: "test",
|
||||
Scheme: route.SchemeHTTP,
|
||||
Host: "example.com",
|
||||
Port: route.Port{Proxy: 80},
|
||||
InboundMTLSProfile: "missing",
|
||||
}
|
||||
err := r.Validate()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, `inbound mTLS profile "missing" not found`)
|
||||
})
|
||||
|
||||
t.Run("rejects route profile when global profile configured", func(t *testing.T) {
|
||||
state := &stubState{cfg: &config.Config{
|
||||
InboundMTLSProfiles: map[string]types.InboundMTLSProfile{
|
||||
"corp": {UseSystemCAs: true},
|
||||
},
|
||||
}}
|
||||
state.cfg.Entrypoint.InboundMTLSProfile = "corp"
|
||||
config.WorkingState.Store(state)
|
||||
|
||||
r := &routeimpl.Route{
|
||||
Alias: "test",
|
||||
Scheme: route.SchemeHTTP,
|
||||
Host: "example.com",
|
||||
Port: route.Port{Proxy: 80},
|
||||
InboundMTLSProfile: "corp",
|
||||
}
|
||||
err := r.Validate()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "route inbound_mtls_profile is not supported")
|
||||
})
|
||||
}
|
||||
|
||||
type stubState struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func (s *stubState) InitFromFile(string) error { return nil }
|
||||
func (s *stubState) Init([]byte) error { return nil }
|
||||
func (s *stubState) Task() *task.Task { return nil }
|
||||
func (s *stubState) Context() context.Context { return context.Background() }
|
||||
func (s *stubState) Value() *config.Config { return s.cfg }
|
||||
func (s *stubState) Entrypoint() entrypointtypes.Entrypoint { return nil }
|
||||
func (s *stubState) ShortLinkMatcher() config.ShortLinkMatcher { return nil }
|
||||
func (s *stubState) AutoCertProvider() server.CertProvider { return nil }
|
||||
func (s *stubState) LoadOrStoreProvider(string, types.RouteProvider) (types.RouteProvider, bool) {
|
||||
return nil, false
|
||||
}
|
||||
func (s *stubState) DeleteProvider(string) { /* no-op: test stub */ }
|
||||
func (s *stubState) IterProviders() iter.Seq2[string, types.RouteProvider] {
|
||||
// no-op: returns empty iterator
|
||||
return func(func(string, types.RouteProvider) bool) {}
|
||||
}
|
||||
func (s *stubState) NumProviders() int { return 0 } // no-op: test stub
|
||||
func (s *stubState) StartProviders() error { return nil } // no-op: test stub
|
||||
func (s *stubState) FlushTmpLog() { /* no-op: test stub */ }
|
||||
func (s *stubState) StartAPIServers() { /* no-op: test stub */ }
|
||||
func (s *stubState) StartMetrics() { /* no-op: test stub */ }
|
||||
|
||||
var _ config.State = (*stubState)(nil)
|
||||
77
internal/config/local_api_test.go
Normal file
77
internal/config/local_api_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestValidateLocalAPIAddr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
addr string
|
||||
allowNonLoopback bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "localhost",
|
||||
addr: "localhost:8888",
|
||||
},
|
||||
{
|
||||
name: "ipv4_loopback",
|
||||
addr: "127.0.0.1:8888",
|
||||
},
|
||||
{
|
||||
name: "ipv6_loopback",
|
||||
addr: "[::1]:8888",
|
||||
},
|
||||
{
|
||||
name: "all_interfaces",
|
||||
addr: ":8888",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "all_interfaces_allowed",
|
||||
addr: ":8888",
|
||||
allowNonLoopback: true,
|
||||
},
|
||||
{
|
||||
name: "ipv4_unspecified",
|
||||
addr: "0.0.0.0:8888",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ipv4_unspecified_allowed",
|
||||
addr: "0.0.0.0:8888",
|
||||
allowNonLoopback: true,
|
||||
},
|
||||
{
|
||||
name: "lan_ip",
|
||||
addr: "192.168.1.10:8888",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "lan_ip_allowed",
|
||||
addr: "192.168.1.10:8888",
|
||||
allowNonLoopback: true,
|
||||
},
|
||||
{
|
||||
name: "hostname_not_loopback",
|
||||
addr: "godoxy.internal:8888",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "hostname_not_loopback_allowed",
|
||||
addr: "godoxy.internal:8888",
|
||||
allowNonLoopback: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validateLocalAPIAddr(tt.addr, tt.allowNonLoopback)
|
||||
if tt.wantErr && err == nil {
|
||||
t.Fatalf("expected error for %q", tt.addr)
|
||||
}
|
||||
if !tt.wantErr && err != nil {
|
||||
t.Fatalf("unexpected error for %q: %v", tt.addr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"iter"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -216,6 +218,15 @@ func (state *state) StartAPIServers() {
|
||||
|
||||
// Local API Handler is used for unauthenticated access.
|
||||
if common.LocalAPIHTTPAddr != "" {
|
||||
if err := validateLocalAPIAddr(common.LocalAPIHTTPAddr, common.LocalAPIAllowNonLoopback); err != nil {
|
||||
log.Err(err).Str("addr", common.LocalAPIHTTPAddr).Msg("refusing to start local API server")
|
||||
return
|
||||
}
|
||||
if common.LocalAPIAllowNonLoopback && !isLoopbackLocalAPIHost(common.LocalAPIHTTPAddr) {
|
||||
log.Warn().
|
||||
Str("addr", common.LocalAPIHTTPAddr).
|
||||
Msg("local API server is allowed to bind to non-loopback addresses")
|
||||
}
|
||||
_, err := server.StartServer(state.task.Subtask("local_api_server", false), server.Options{
|
||||
Name: "local_api",
|
||||
HTTPAddr: common.LocalAPIHTTPAddr,
|
||||
@@ -227,6 +238,51 @@ func (state *state) StartAPIServers() {
|
||||
}
|
||||
}
|
||||
|
||||
func validateLocalAPIAddr(addr string, allowNonLoopback bool) error {
|
||||
if isLoopbackLocalAPIHost(addr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if allowNonLoopback {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch strings.ToLower(host) {
|
||||
case "localhost":
|
||||
return nil
|
||||
case "":
|
||||
return errors.New("local API address must bind to a loopback host, not all interfaces")
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("local API address must use a loopback host: %w", err)
|
||||
}
|
||||
if !ip.IsLoopback() {
|
||||
return fmt.Errorf("local API address must bind to a loopback host, got %q", host)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isLoopbackLocalAPIHost(addr string) bool {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.EqualFold(host, "localhost") {
|
||||
return true
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
return err == nil && ip.IsLoopback()
|
||||
}
|
||||
|
||||
func (state *state) StartMetrics() {
|
||||
systeminfo.Poller.Start(state.task)
|
||||
uptime.Poller.Start(state.task)
|
||||
@@ -268,6 +324,7 @@ func (state *state) initEntrypoint() error {
|
||||
errs := gperr.NewBuilder("entrypoint error")
|
||||
errs.Add(state.entrypoint.SetMiddlewares(epCfg.Middlewares))
|
||||
errs.Add(state.entrypoint.SetAccessLogger(state.task, epCfg.AccessLog))
|
||||
errs.Add(state.entrypoint.SetInboundMTLSProfiles(state.Config.InboundMTLSProfiles))
|
||||
return errs.Error()
|
||||
}
|
||||
|
||||
|
||||
@@ -19,14 +19,15 @@ import (
|
||||
|
||||
type (
|
||||
Config struct {
|
||||
ACL *acl.Config `json:"acl"`
|
||||
AutoCert *autocert.Config `json:"autocert"`
|
||||
Entrypoint entrypoint.Config `json:"entrypoint"`
|
||||
Providers Providers `json:"providers"`
|
||||
MatchDomains []string `json:"match_domains" validate:"domain_name"`
|
||||
Homepage homepage.Config `json:"homepage"`
|
||||
Defaults Defaults `json:"defaults"`
|
||||
TimeoutShutdown int `json:"timeout_shutdown" validate:"gte=0"`
|
||||
ACL *acl.Config `json:"acl"`
|
||||
AutoCert *autocert.Config `json:"autocert"`
|
||||
Entrypoint entrypoint.Config `json:"entrypoint"`
|
||||
InboundMTLSProfiles map[string]types.InboundMTLSProfile `json:"inbound_mtls_profiles" yaml:"inbound_mtls_profiles,omitempty"`
|
||||
Providers Providers `json:"providers"`
|
||||
MatchDomains []string `json:"match_domains" validate:"domain_name"`
|
||||
Homepage homepage.Config `json:"homepage"`
|
||||
Defaults Defaults `json:"defaults"`
|
||||
TimeoutShutdown int `json:"timeout_shutdown" validate:"gte=0"`
|
||||
}
|
||||
Defaults struct {
|
||||
HealthCheck types.HealthCheckConfig `json:"healthcheck"`
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
module github.com/yusing/godoxy/internal/dnsproviders
|
||||
|
||||
go 1.26.0
|
||||
go 1.26.2
|
||||
|
||||
replace github.com/yusing/godoxy => ../..
|
||||
|
||||
require (
|
||||
github.com/go-acme/lego/v4 v4.32.0
|
||||
github.com/yusing/godoxy v0.26.0
|
||||
github.com/go-acme/lego/v4 v4.33.0
|
||||
github.com/yusing/godoxy v0.27.5
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.18.2 // indirect
|
||||
cloud.google.com/go/auth v0.20.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1 // indirect
|
||||
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/bytedance/sonic/loader v0.5.1 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
@@ -33,13 +33,13 @@ require (
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.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.30.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.2 // 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-yaml v1.19.2 // indirect
|
||||
@@ -48,59 +48,59 @@ require (
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
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.12 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/gotify/server/v2 v2.9.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.21.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
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/linode/linodego v1.65.0 // indirect
|
||||
github.com/linode/linodego v1.67.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-isatty v0.0.21 // indirect
|
||||
github.com/maxatome/go-testdeep v1.14.0 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
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.108.2 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.111.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.111.0 // 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
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/pquerna/otp v1.5.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/rs/zerolog v1.35.0 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
|
||||
github.com/sony/gobreaker v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/vultr/govultr/v3 v3.27.0 // indirect
|
||||
github.com/vultr/govultr/v3 v3.30.0 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/yusing/gointernals v0.2.0 // indirect
|
||||
github.com/yusing/goutils v0.7.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
|
||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.uber.org/ratelimit v0.3.1 // indirect
|
||||
golang.org/x/arch v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/oauth2 v0.35.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
google.golang.org/api v0.268.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260223185530-2f722ef697dc // indirect
|
||||
google.golang.org/grpc v1.79.1 // indirect
|
||||
golang.org/x/arch v0.26.0 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
google.golang.org/api v0.275.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
||||
google.golang.org/grpc v1.80.0 // 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
||||
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
||||
cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
|
||||
cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
@@ -11,8 +11,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpz
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
|
||||
@@ -25,8 +25,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1 h1:edShSHV3DV90+kt+CMaEXEzR9QF7wFrPJxVGz2blMIU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.1/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
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/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
@@ -37,19 +37,18 @@ 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=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
||||
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
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/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -62,10 +61,10 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/go-acme/lego/v4 v4.32.0 h1:z7Ss7aa1noabhKj+DBzhNCO2SM96xhE3b0ucVW3x8Tc=
|
||||
github.com/go-acme/lego/v4 v4.32.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-acme/lego/v4 v4.33.0 h1:2KrRKieG+VczT4zvVXKAY7Tp/S3XLvh/QImofBALRAM=
|
||||
github.com/go-acme/lego/v4 v4.33.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -79,15 +78,14 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
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.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
||||
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
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=
|
||||
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
@@ -103,12 +101,12 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
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.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12/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/gotify/server/v2 v2.9.0 h1:2zRCl28wkq0oc6YNbyJS2n0dDOOVvOS3Oez5AG2ij54=
|
||||
github.com/gotify/server/v2 v2.9.0/go.mod h1:249wwlUqHTr0QsiKARGtFVqds0pNLIMjYLinHyMACdQ=
|
||||
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.21.0 h1:h45NjjzEO3faG9Lg/cFrBh2PgegVVgzqKzuZl/wMbiI=
|
||||
github.com/googleapis/gax-go/v2 v2.21.0/go.mod h1:But/NJU6TnZsrLai/xBAQLLz+Hc7fHZJt/hsCz3Fih4=
|
||||
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=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
@@ -131,15 +129,12 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
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/linode/linodego v1.65.0 h1:SdsuGD8VSsPWeShXpE7ihl5vec+fD3MgwhnfYC/rj7k=
|
||||
github.com/linode/linodego v1.65.0/go.mod h1:tOFiTErdjkbVnV+4S0+NmIE9dqqZUEM2HsJaGu8wMh8=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/linode/linodego v1.67.0 h1:pomhFuuCCJI4N6emtB9027h1yXHY2/MIT0hwHEFwvq4=
|
||||
github.com/linode/linodego v1.67.0/go.mod h1:+9mbdu0P3WMRCl0QbVfiFavR+Iel7TCRDJk3nInyx14=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI=
|
||||
github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
@@ -150,17 +145,16 @@ 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.108.2 h1:OWijzl3nHUApvTivl+3+78dbBwmyEHOnb+W9m6ixGbk=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 h1:9LsjN/zaIN7H8JE61NHpbWhxF0UGY96+kMlk3g8OvGU=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2/go.mod h1:32vZH06TuwZSn+IDMO1qcDvC2vHVlzUALCwXGWPA+dc=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.111.0 h1:i7tfJOtD7VRw94QwLFh2apHwKCQ+i+8SAEgrcWDKM90=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.111.0/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.111.0 h1:4OCyuewA2SntKr1cagj6OHWLbwLxCfR7fNe3QRoFT/c=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.111.0/go.mod h1:kQSJmotDqt21aMIn0k8dSZiOPSWEscfjXAYaPNsXyh4=
|
||||
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=
|
||||
github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -170,9 +164,8 @@ github.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI=
|
||||
github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM=
|
||||
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
|
||||
@@ -193,8 +186,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
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/vultr/govultr/v3 v3.27.0 h1:J8etMyu/Jh5+idMsu2YZpOWmDXXHeW4VZnkYXmJYHx8=
|
||||
github.com/vultr/govultr/v3 v3.27.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY=
|
||||
github.com/vultr/govultr/v3 v3.30.0 h1:kTeDJ+5or6g4CQJmD6Kmz4R63B18poNZ8RP87r9LZdg=
|
||||
github.com/vultr/govultr/v3 v3.30.0/go.mod h1:2zyUw9yADQaGwKnwDesmIOlBNLrm7edsCfWHFJpWKf8=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yusing/gointernals v0.2.0 h1:jyWB3kdUPkuU6s0r8QY/sS5h2WNBF4Kfisly8dtSVvg=
|
||||
@@ -203,62 +196,59 @@ github.com/yusing/goutils v0.7.0 h1:I5hd8GwZ+3WZqFPK0tWqek1Q5MY6Xg29hKZcwwQi4SY=
|
||||
github.com/yusing/goutils v0.7.0/go.mod h1:CtF/KFH4q8jkr7cvBpkaExnudE0lLu8sLe43F73Bn5Q=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
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.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
|
||||
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
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.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/arch v0.26.0 h1:jZ6dpec5haP/fUv1kLCbuJy6dnRrfX6iVK08lZBFpk4=
|
||||
golang.org/x/arch v0.26.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
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=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.1.0/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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.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/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
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=
|
||||
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.268.0 h1:hgA3aS4lt9rpF5RCCkX0Q2l7DvHgvlb53y4T4u6iKkA=
|
||||
google.golang.org/api v0.268.0/go.mod h1:HXMyMH496wz+dAJwD/GkAPLd3ZL33Kh0zEG32eNvy9w=
|
||||
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-20260223185530-2f722ef697dc h1:51Wupg8spF+5FC6D+iMKbOddFjMckETnNnEiZ+HX37s=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260223185530-2f722ef697dc/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/api v0.275.0 h1:vfY5d9vFVJeWEZT65QDd9hbndr7FyZ2+6mIzGAh71NI=
|
||||
google.golang.org/api v0.275.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
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=
|
||||
|
||||
@@ -93,6 +93,7 @@ type RWPoolLike[Route types.Route] interface {
|
||||
```go
|
||||
type Config struct {
|
||||
SupportProxyProtocol bool `json:"support_proxy_protocol"`
|
||||
InboundMTLSProfile string `json:"inbound_mtls_profile,omitempty"`
|
||||
Rules struct {
|
||||
NotFound rules.Rules `json:"not_found"`
|
||||
} `json:"rules"`
|
||||
@@ -101,6 +102,51 @@ type Config struct {
|
||||
}
|
||||
```
|
||||
|
||||
`InboundMTLSProfile` references a named root-level inbound mTLS profile and enables Go's built-in client-certificate verification (`tls.RequireAndVerifyClientCert`) for all HTTPS traffic on the entrypoint.
|
||||
|
||||
- When configured, route-level inbound mTLS overrides are not supported.
|
||||
- Without a global profile, route-level inbound mTLS may still select profiles by TLS SNI.
|
||||
- For a route that enforces client certificates, the route matched from the HTTP `Host` and the route matched from TLS SNI must be the same route (compared by route identity/key after `FindRoute`). That resolution is the entrypoint's route table, not DNS or any external name resolution.
|
||||
|
||||
### Inbound mTLS profiles
|
||||
|
||||
Root config provides reusable named inbound mTLS profiles via `config.Config.InboundMTLSProfiles`. Each profile is a [`types.InboundMTLSProfile`](../types/inbound_mtls.go): optional system trust roots plus zero or more PEM CA certificate files on disk (`ca_files`). `SetInboundMTLSProfiles` compiles those profiles into certificate pools, and the TLS server sets `ClientCAs` from the selected pool.
|
||||
|
||||
PEM content is not embedded in YAML: list file paths under `ca_files`; each file should contain one or more PEM-encoded CA certificates.
|
||||
|
||||
```yaml
|
||||
inbound_mtls_profiles:
|
||||
corp-clients:
|
||||
use_system_cas: false
|
||||
ca_files:
|
||||
- /etc/godoxy/mtls/corp-root-ca.pem
|
||||
- /etc/godoxy/mtls/corp-issuing-ca.pem
|
||||
corp-plus-extra:
|
||||
use_system_cas: true
|
||||
ca_files:
|
||||
- /etc/godoxy/mtls/private-intermediate.pem
|
||||
```
|
||||
|
||||
Apply one profile to **all** HTTPS listeners by naming it on the entrypoint:
|
||||
|
||||
```yaml
|
||||
entrypoint:
|
||||
inbound_mtls_profile: corp-clients
|
||||
```
|
||||
|
||||
#### Security considerations
|
||||
|
||||
- **Client certificates and chain verification** — The server requires a client certificate and verifies it with Go's TLS stack. The chain must build to one of the CAs in the selected pool (custom PEMs from `ca_files`, and optionally the OS trust store when `use_system_cas` is true). Leaf validity (time, EKU, and related checks) follows standard Go behavior for client-auth verification.
|
||||
- **CA management and rotation** — CA material is read from the filesystem when profiles are compiled during config load / entrypoint setup. Updating trust for a running process requires a config reload or restart so the new PEM files are read.
|
||||
- **CRL / OCSP revocation** — Go's standard inbound mTLS verification does not perform CRL or OCSP checks for client certificates, and GoDoxy does not add a custom revocation layer.
|
||||
- **Misconfigured trust pools** — A pool that is too broad (for example `use_system_cas: true` with few constraints) can trust far more clients than intended. A pool that omits required intermediates can reject otherwise valid clients.
|
||||
|
||||
#### Failure modes
|
||||
|
||||
- **Invalid or unreadable CA material** — Missing files, non-PEM content, or PEM that does not parse as CA certificates cause profile compilation to fail. `SetInboundMTLSProfiles` returns collected per-profile errors.
|
||||
- **Missing profile referenced by entrypoint** — If `entrypoint.inbound_mtls_profile` names a profile that is not present in `inbound_mtls_profiles`, initialization returns `entrypoint inbound mTLS profile "<name>" not found`.
|
||||
- **Client certificate validation failures** — Clients that omit a cert, present a cert that does not chain to the configured pool, or fail other TLS checks see a failed TLS handshake before HTTP handling starts.
|
||||
|
||||
### Context Functions
|
||||
|
||||
```go
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
// Config defines the entrypoint configuration for proxy handling,
|
||||
// including proxy protocol support, routing rules, middlewares, and access logging.
|
||||
type Config struct {
|
||||
SupportProxyProtocol bool `json:"support_proxy_protocol"`
|
||||
SupportProxyProtocol bool `json:"support_proxy_protocol"`
|
||||
InboundMTLSProfile string `json:"inbound_mtls_profile,omitempty"`
|
||||
Rules struct {
|
||||
NotFound rules.Rules `json:"not_found"`
|
||||
} `json:"rules"`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -41,6 +42,8 @@ type Entrypoint struct {
|
||||
httpPoolDisableLog atomic.Bool
|
||||
|
||||
servers *xsync.Map[string, *httpServer] // listen addr -> server
|
||||
|
||||
inboundMTLSProfiles map[string]*x509.CertPool
|
||||
}
|
||||
|
||||
var _ entrypoint.Entrypoint = &Entrypoint{}
|
||||
@@ -62,13 +65,14 @@ func NewEntrypoint(parent task.Parent, cfg *Config) *Entrypoint {
|
||||
}
|
||||
|
||||
ep := &Entrypoint{
|
||||
task: parent.Subtask("entrypoint", false),
|
||||
cfg: cfg,
|
||||
findRouteFunc: findRouteAnyDomain,
|
||||
shortLinkMatcher: newShortLinkMatcher(),
|
||||
streamRoutes: pool.New[types.StreamRoute]("stream_routes", "stream_routes"),
|
||||
excludedRoutes: pool.New[types.Route]("excluded_routes", "excluded_routes"),
|
||||
servers: xsync.NewMap[string, *httpServer](),
|
||||
task: parent.Subtask("entrypoint", false),
|
||||
cfg: cfg,
|
||||
findRouteFunc: findRouteAnyDomain,
|
||||
shortLinkMatcher: newShortLinkMatcher(),
|
||||
streamRoutes: pool.New[types.StreamRoute]("stream_routes", "stream_routes"),
|
||||
excludedRoutes: pool.New[types.Route]("excluded_routes", "excluded_routes"),
|
||||
servers: xsync.NewMap[string, *httpServer](),
|
||||
inboundMTLSProfiles: make(map[string]*x509.CertPool),
|
||||
}
|
||||
return ep
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package entrypoint
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -54,6 +55,10 @@ func newHTTPServer(ep *Entrypoint) *httpServer {
|
||||
|
||||
// Listen starts the server and stop when entrypoint is stopped.
|
||||
func (srv *httpServer) Listen(addr string, proto HTTPProto) error {
|
||||
return srv.listen(addr, proto, nil)
|
||||
}
|
||||
|
||||
func (srv *httpServer) listen(addr string, proto HTTPProto, listener net.Listener) error {
|
||||
if srv.addr != "" {
|
||||
return errors.New("server already started")
|
||||
}
|
||||
@@ -68,9 +73,12 @@ func (srv *httpServer) Listen(addr string, proto HTTPProto) error {
|
||||
switch proto {
|
||||
case HTTPProtoHTTP:
|
||||
opts.HTTPAddr = addr
|
||||
opts.HTTPListener = listener
|
||||
case HTTPProtoHTTPS:
|
||||
opts.HTTPSAddr = addr
|
||||
opts.HTTPSListener = listener
|
||||
opts.CertProvider = autocert.FromCtx(srv.ep.task.Context())
|
||||
opts.TLSConfigMutator = srv.mutateServerTLSConfig
|
||||
}
|
||||
|
||||
task := srv.ep.task.Subtask("http_server", false)
|
||||
@@ -116,8 +124,15 @@ func (srv *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}()
|
||||
}
|
||||
|
||||
route := srv.ep.findRouteFunc(srv.routes, r.Host)
|
||||
route, err := srv.resolveRequestRoute(r)
|
||||
switch {
|
||||
case errors.Is(err, errSecureRouteRequiresSNI), errors.Is(err, errSecureRouteMisdirected):
|
||||
http.Error(w, err.Error(), http.StatusMisdirectedRequest)
|
||||
return
|
||||
case err != nil:
|
||||
log.Err(err).Msg("failed to resolve HTTP route")
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
case route != nil:
|
||||
r = routes.WithRouteContext(r, route)
|
||||
if srv.ep.middleware != nil {
|
||||
@@ -134,6 +149,62 @@ func (srv *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errSecureRouteRequiresSNI = errors.New("secure route requires matching TLS SNI")
|
||||
errSecureRouteMisdirected = errors.New("secure route host must match TLS SNI")
|
||||
)
|
||||
|
||||
func (srv *httpServer) resolveRequestRoute(req *http.Request) (types.HTTPRoute, error) {
|
||||
hostRoute := srv.FindRoute(req.Host)
|
||||
if req.TLS == nil || srv.ep.cfg.InboundMTLSProfile != "" {
|
||||
return hostRoute, nil
|
||||
}
|
||||
|
||||
serverName := req.TLS.ServerName
|
||||
if serverName == "" {
|
||||
pool, err := srv.resolveInboundMTLSProfileForRoute(hostRoute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pool != nil {
|
||||
return nil, errSecureRouteRequiresSNI
|
||||
}
|
||||
return hostRoute, nil
|
||||
}
|
||||
|
||||
sniRoute := srv.FindRoute(serverName)
|
||||
pool, err := srv.resolveInboundMTLSProfileForRoute(sniRoute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pool != nil {
|
||||
if !sameHTTPRoute(hostRoute, sniRoute) {
|
||||
return nil, errSecureRouteMisdirected
|
||||
}
|
||||
return sniRoute, nil
|
||||
}
|
||||
|
||||
pool, err = srv.resolveInboundMTLSProfileForRoute(hostRoute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pool != nil {
|
||||
return nil, errSecureRouteMisdirected
|
||||
}
|
||||
return hostRoute, nil
|
||||
}
|
||||
|
||||
func sameHTTPRoute(left, right types.HTTPRoute) bool {
|
||||
switch {
|
||||
case left == nil || right == nil:
|
||||
return left == right
|
||||
case left == right:
|
||||
return true
|
||||
default:
|
||||
return left.Key() == right.Key()
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *httpServer) tryHandleShortLink(w http.ResponseWriter, r *http.Request) (handled bool) {
|
||||
host := r.Host
|
||||
if before, _, ok := strings.Cut(host, ":"); ok {
|
||||
|
||||
171
internal/entrypoint/inbound_mtls.go
Normal file
171
internal/entrypoint/inbound_mtls.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
func compileInboundMTLSProfiles(profiles map[string]types.InboundMTLSProfile) (map[string]*x509.CertPool, error) {
|
||||
if len(profiles) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
compiled := make(map[string]*x509.CertPool, len(profiles))
|
||||
errs := gperr.NewBuilder("inbound mTLS profiles error")
|
||||
|
||||
for name, profile := range profiles {
|
||||
if err := profile.Validate(); err != nil {
|
||||
errs.AddSubjectf(err, "profiles.%s", name)
|
||||
continue
|
||||
}
|
||||
|
||||
pool, err := buildInboundMTLSCAPool(profile)
|
||||
if err != nil {
|
||||
errs.AddSubjectf(err, "profiles.%s", name)
|
||||
continue
|
||||
}
|
||||
compiled[name] = pool
|
||||
}
|
||||
|
||||
if err := errs.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return compiled, nil
|
||||
}
|
||||
|
||||
func buildInboundMTLSCAPool(profile types.InboundMTLSProfile) (*x509.CertPool, error) {
|
||||
var pool *x509.CertPool
|
||||
|
||||
if profile.UseSystemCAs {
|
||||
systemPool, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if systemPool != nil {
|
||||
pool = systemPool
|
||||
}
|
||||
}
|
||||
if pool == nil {
|
||||
pool = x509.NewCertPool()
|
||||
}
|
||||
|
||||
for _, file := range profile.CAFiles {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, gperr.PrependSubject(err, file)
|
||||
}
|
||||
if !pool.AppendCertsFromPEM(data) {
|
||||
return nil, gperr.PrependSubject(errors.New("failed to parse CA certificates"), file)
|
||||
}
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) SetInboundMTLSProfiles(profiles map[string]types.InboundMTLSProfile) error {
|
||||
compiled, err := compileInboundMTLSProfiles(profiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if profileRef := ep.cfg.InboundMTLSProfile; profileRef != "" {
|
||||
if _, ok := compiled[profileRef]; !ok {
|
||||
return fmt.Errorf("entrypoint inbound mTLS profile %q not found", profileRef)
|
||||
}
|
||||
}
|
||||
ep.inboundMTLSProfiles = compiled
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *httpServer) mutateServerTLSConfig(base *tls.Config) *tls.Config {
|
||||
if base == nil {
|
||||
return base
|
||||
}
|
||||
pool, err := srv.resolveInboundMTLSProfileForRoute(nil)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("inbound mTLS: failed to resolve global profile, falling back to per-route mTLS")
|
||||
}
|
||||
if pool != nil && err == nil {
|
||||
return applyInboundMTLSProfile(base, pool)
|
||||
}
|
||||
|
||||
cfg := base.Clone()
|
||||
cfg.GetConfigForClient = func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
pool, err := srv.resolveInboundMTLSProfileForServerName(hello.ServerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pool != nil {
|
||||
return applyInboundMTLSProfile(base, pool), nil
|
||||
}
|
||||
return cloneTLSConfig(base), nil
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func applyInboundMTLSProfile(base *tls.Config, pool *x509.CertPool) *tls.Config {
|
||||
cfg := cloneTLSConfig(base)
|
||||
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
cfg.ClientCAs = pool
|
||||
return cfg
|
||||
}
|
||||
|
||||
func cloneTLSConfig(base *tls.Config) *tls.Config {
|
||||
cfg := base.Clone()
|
||||
cfg.GetConfigForClient = nil
|
||||
return cfg
|
||||
}
|
||||
|
||||
func ValidateInboundMTLSProfileRef(profileRef, globalProfile string, profiles map[string]types.InboundMTLSProfile) error {
|
||||
if profileRef == "" {
|
||||
return nil
|
||||
}
|
||||
if globalProfile != "" {
|
||||
return errors.New("route inbound_mtls_profile is not supported when entrypoint.inbound_mtls_profile is configured")
|
||||
}
|
||||
if _, ok := profiles[profileRef]; !ok {
|
||||
return fmt.Errorf("inbound mTLS profile %q not found", profileRef)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *httpServer) resolveInboundMTLSProfileForServerName(serverName string) (*x509.CertPool, error) {
|
||||
if serverName == "" || srv.ep.inboundMTLSProfiles == nil {
|
||||
return nil, nil
|
||||
}
|
||||
route := srv.FindRoute(serverName)
|
||||
if route == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return srv.resolveInboundMTLSProfileForRoute(route)
|
||||
}
|
||||
|
||||
func (srv *httpServer) resolveInboundMTLSProfileForRoute(route types.HTTPRoute) (*x509.CertPool, error) {
|
||||
if srv.ep.inboundMTLSProfiles == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if globalRef := srv.ep.cfg.InboundMTLSProfile; globalRef != "" {
|
||||
return srv.lookupInboundMTLSProfile(globalRef, "entrypoint")
|
||||
}
|
||||
if route == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if ref := route.InboundMTLSProfileRef(); ref != "" {
|
||||
return srv.lookupInboundMTLSProfile(ref, fmt.Sprintf("route %q", route.Name()))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (srv *httpServer) lookupInboundMTLSProfile(ref, owner string) (*x509.CertPool, error) {
|
||||
pool, ok := srv.ep.inboundMTLSProfiles[ref]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s inbound mTLS profile %q not found", owner, ref)
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
471
internal/entrypoint/inbound_mtls_test.go
Normal file
471
internal/entrypoint/inbound_mtls_test.go
Normal file
@@ -0,0 +1,471 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
agentcert "github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/internal/agentpool"
|
||||
autocert "github.com/yusing/godoxy/internal/autocert/types"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/homepage"
|
||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/goutils/pool"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
type fakeHTTPRoute struct {
|
||||
key string
|
||||
name string
|
||||
inboundMTLSProfile string
|
||||
listenURL *nettypes.URL
|
||||
task *task.Task
|
||||
}
|
||||
|
||||
func newFakeHTTPRoute(t *testing.T, alias, profile string) *fakeHTTPRoute {
|
||||
return newFakeHTTPRouteAt(t, alias, profile, "https://:1000")
|
||||
}
|
||||
|
||||
func newFakeHTTPRouteAt(t *testing.T, alias, profile, listenURL string) *fakeHTTPRoute {
|
||||
t.Helper()
|
||||
|
||||
return &fakeHTTPRoute{
|
||||
key: alias,
|
||||
name: alias,
|
||||
inboundMTLSProfile: profile,
|
||||
listenURL: nettypes.MustParseURL(listenURL),
|
||||
task: task.GetTestTask(t),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *fakeHTTPRoute) Key() string { return r.key }
|
||||
func (r *fakeHTTPRoute) Name() string { return r.name }
|
||||
func (r *fakeHTTPRoute) Start(task.Parent) error { return nil }
|
||||
func (r *fakeHTTPRoute) Task() *task.Task { return r.task }
|
||||
func (r *fakeHTTPRoute) Finish(any) {
|
||||
// no-op: test stub
|
||||
}
|
||||
func (r *fakeHTTPRoute) MarshalZerologObject(*zerolog.Event) {
|
||||
// no-op: test stub
|
||||
}
|
||||
func (r *fakeHTTPRoute) ProviderName() string { return "" }
|
||||
func (r *fakeHTTPRoute) GetProvider() types.RouteProvider { return nil }
|
||||
func (r *fakeHTTPRoute) ListenURL() *nettypes.URL { return r.listenURL }
|
||||
func (r *fakeHTTPRoute) TargetURL() *nettypes.URL { return nil }
|
||||
func (r *fakeHTTPRoute) HealthMonitor() types.HealthMonitor { return nil }
|
||||
func (r *fakeHTTPRoute) SetHealthMonitor(types.HealthMonitor) {
|
||||
// no-op: test stub
|
||||
}
|
||||
func (r *fakeHTTPRoute) References() []string { return nil }
|
||||
func (r *fakeHTTPRoute) ShouldExclude() bool { return false }
|
||||
func (r *fakeHTTPRoute) Started() <-chan struct{} { return nil }
|
||||
func (r *fakeHTTPRoute) IdlewatcherConfig() *types.IdlewatcherConfig { return nil }
|
||||
func (r *fakeHTTPRoute) HealthCheckConfig() types.HealthCheckConfig { return types.HealthCheckConfig{} }
|
||||
func (r *fakeHTTPRoute) LoadBalanceConfig() *types.LoadBalancerConfig {
|
||||
return nil
|
||||
}
|
||||
func (r *fakeHTTPRoute) HomepageItem() homepage.Item { return homepage.Item{} }
|
||||
func (r *fakeHTTPRoute) DisplayName() string { return r.name }
|
||||
func (r *fakeHTTPRoute) ContainerInfo() *types.Container {
|
||||
return nil
|
||||
}
|
||||
func (r *fakeHTTPRoute) GetAgent() *agentpool.Agent { return nil }
|
||||
func (r *fakeHTTPRoute) IsDocker() bool { return false }
|
||||
func (r *fakeHTTPRoute) IsAgent() bool { return false }
|
||||
func (r *fakeHTTPRoute) UseLoadBalance() bool { return false }
|
||||
func (r *fakeHTTPRoute) UseIdleWatcher() bool { return false }
|
||||
func (r *fakeHTTPRoute) UseHealthCheck() bool { return false }
|
||||
func (r *fakeHTTPRoute) UseAccessLog() bool { return false }
|
||||
func (r *fakeHTTPRoute) ServeHTTP(http.ResponseWriter, *http.Request) {
|
||||
// no-op: test stub
|
||||
}
|
||||
func (r *fakeHTTPRoute) InboundMTLSProfileRef() string { return r.inboundMTLSProfile }
|
||||
|
||||
func newTestHTTPServer(t *testing.T, ep *Entrypoint) *httpServer {
|
||||
t.Helper()
|
||||
|
||||
srv, ok := ep.servers.Load(common.ProxyHTTPAddr)
|
||||
if ok {
|
||||
return srv
|
||||
}
|
||||
|
||||
srv = &httpServer{
|
||||
ep: ep,
|
||||
addr: common.ProxyHTTPAddr,
|
||||
routes: pool.New[types.HTTPRoute]("test-http-routes", "test-http-routes"),
|
||||
}
|
||||
ep.servers.Store(common.ProxyHTTPAddr, srv)
|
||||
return srv
|
||||
}
|
||||
|
||||
func TestMutateServerTLSConfigWithGlobalProfile(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, &Config{InboundMTLSProfile: "global"})
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
require.NoError(t, ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"global": {UseSystemCAs: true},
|
||||
}))
|
||||
|
||||
base := &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
mutated := srv.mutateServerTLSConfig(base)
|
||||
|
||||
require.Equal(t, tls.RequireAndVerifyClientCert, mutated.ClientAuth)
|
||||
require.NotNil(t, mutated.ClientCAs)
|
||||
require.Nil(t, mutated.GetConfigForClient)
|
||||
}
|
||||
|
||||
func TestMutateServerTLSConfigWithRouteProfiles(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
ep.SetFindRouteDomains([]string{".example.com"})
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
srv.AddRoute(newFakeHTTPRoute(t, "secure-app", "route"))
|
||||
srv.AddRoute(newFakeHTTPRoute(t, "open-app", ""))
|
||||
require.NoError(t, ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"route": {UseSystemCAs: true},
|
||||
}))
|
||||
|
||||
base := &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
mutated := srv.mutateServerTLSConfig(base)
|
||||
|
||||
require.Zero(t, mutated.ClientAuth)
|
||||
require.Nil(t, mutated.ClientCAs)
|
||||
require.NotNil(t, mutated.GetConfigForClient)
|
||||
|
||||
secureCfg, err := mutated.GetConfigForClient(&tls.ClientHelloInfo{ServerName: "secure-app.example.com"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tls.RequireAndVerifyClientCert, secureCfg.ClientAuth)
|
||||
require.NotNil(t, secureCfg.ClientCAs)
|
||||
require.Nil(t, secureCfg.GetConfigForClient)
|
||||
|
||||
openCfg, err := mutated.GetConfigForClient(&tls.ClientHelloInfo{ServerName: "open-app.example.com"})
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, openCfg.ClientAuth)
|
||||
require.Nil(t, openCfg.ClientCAs)
|
||||
require.Nil(t, openCfg.GetConfigForClient)
|
||||
|
||||
unknownCfg, err := mutated.GetConfigForClient(&tls.ClientHelloInfo{ServerName: "unknown.example.com"})
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, unknownCfg.ClientAuth)
|
||||
require.Nil(t, unknownCfg.ClientCAs)
|
||||
require.Nil(t, unknownCfg.GetConfigForClient)
|
||||
}
|
||||
|
||||
func TestSetInboundMTLSProfilesRejectsUnknownGlobalProfile(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, &Config{InboundMTLSProfile: "missing"})
|
||||
err := ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"known": {UseSystemCAs: true},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, `entrypoint inbound mTLS profile "missing" not found`)
|
||||
}
|
||||
|
||||
func TestSetInboundMTLSProfilesRejectsBadCAFile(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, &Config{InboundMTLSProfile: "broken"})
|
||||
err := ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"broken": {CAFiles: []string{filepath.Join(t.TempDir(), "missing.pem")}},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "missing.pem")
|
||||
}
|
||||
|
||||
func TestCompileInboundMTLSProfilesReturnsNilMapOnError(t *testing.T) {
|
||||
compiled, err := compileInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"ok": {UseSystemCAs: true},
|
||||
"bad": {CAFiles: []string{filepath.Join(t.TempDir(), "missing.pem")}},
|
||||
})
|
||||
require.Nil(t, compiled)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "missing.pem")
|
||||
}
|
||||
|
||||
func TestMutateServerTLSConfigRejectsUnknownRouteProfile(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
ep.SetFindRouteDomains([]string{".example.com"})
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
srv.AddRoute(newFakeHTTPRoute(t, "secure-app", "missing"))
|
||||
|
||||
base := &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
mutated := srv.mutateServerTLSConfig(base)
|
||||
|
||||
_, err := mutated.GetConfigForClient(&tls.ClientHelloInfo{ServerName: "secure-app.example.com"})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, `route "secure-app" inbound mTLS profile "missing" not found`)
|
||||
}
|
||||
|
||||
func TestResolveRequestRouteRejectsUnknownRouteProfile(t *testing.T) {
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
ep.SetFindRouteDomains([]string{".example.com"})
|
||||
srv := newTestHTTPServer(t, ep)
|
||||
srv.AddRoute(newFakeHTTPRoute(t, "secure-app", "missing"))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://secure-app.example.com", nil)
|
||||
req.Host = "secure-app.example.com"
|
||||
req.TLS = &tls.ConnectionState{ServerName: "secure-app.example.com"}
|
||||
|
||||
route, err := srv.resolveRequestRoute(req)
|
||||
require.Nil(t, route)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, `route "secure-app" inbound mTLS profile "missing" not found`)
|
||||
}
|
||||
|
||||
func TestInboundMTLSGlobalHandshake(t *testing.T) {
|
||||
ca, srv, client, err := agentcert.NewAgent()
|
||||
require.NoError(t, err)
|
||||
|
||||
serverCert, err := srv.ToTLSCert()
|
||||
require.NoError(t, err)
|
||||
clientCert, err := client.ToTLSCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
caPath := writeTempFile(t, "ca.pem", ca.Cert)
|
||||
provider := &staticCertProvider{cert: serverCert}
|
||||
|
||||
ep := NewTestEntrypoint(t, &Config{InboundMTLSProfile: "global"})
|
||||
t.Cleanup(func() {
|
||||
closeTestServers(t, ep)
|
||||
})
|
||||
autocert.SetCtx(task.GetTestTask(t), provider)
|
||||
require.NoError(t, ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"global": {CAFiles: []string{caPath}},
|
||||
}))
|
||||
|
||||
listener, releaseListener := reserveTCPAddr(t)
|
||||
listenAddr := listener.Addr().String()
|
||||
addHTTPRouteAt(t, ep, "app1", "", listenAddr, listener)
|
||||
releaseListener()
|
||||
|
||||
t.Run("trusted client succeeds", func(t *testing.T) {
|
||||
resp, err := doHTTPSRequest(listenAddr, "app1.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{*clientCert},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
})
|
||||
|
||||
t.Run("missing client cert fails handshake", func(t *testing.T) {
|
||||
_, err := doHTTPSRequest(listenAddr, "app1.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("wrong client cert fails handshake", func(t *testing.T) {
|
||||
_, _, badClient, err := agentcert.NewAgent()
|
||||
require.NoError(t, err)
|
||||
badClientCert, err := badClient.ToTLSCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = doHTTPSRequest(listenAddr, "app1.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{*badClientCert},
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInboundMTLSRouteScopedHandshake(t *testing.T) {
|
||||
ca, srv, client, err := agentcert.NewAgent()
|
||||
require.NoError(t, err)
|
||||
|
||||
serverCert, err := srv.ToTLSCert()
|
||||
require.NoError(t, err)
|
||||
clientCert, err := client.ToTLSCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
caPath := writeTempFile(t, "ca.pem", ca.Cert)
|
||||
provider := &staticCertProvider{cert: serverCert}
|
||||
|
||||
ep := NewTestEntrypoint(t, nil)
|
||||
t.Cleanup(func() {
|
||||
closeTestServers(t, ep)
|
||||
})
|
||||
ep.SetFindRouteDomains([]string{".example.com"})
|
||||
autocert.SetCtx(task.GetTestTask(t), provider)
|
||||
require.NoError(t, ep.SetInboundMTLSProfiles(map[string]types.InboundMTLSProfile{
|
||||
"route": {CAFiles: []string{caPath}},
|
||||
}))
|
||||
|
||||
listener, releaseListener := reserveTCPAddr(t)
|
||||
listenAddr := listener.Addr().String()
|
||||
addHTTPRouteAt(t, ep, "secure-app", "route", listenAddr, listener)
|
||||
releaseListener()
|
||||
addHTTPRouteAt(t, ep, "open-app", "", listenAddr, nil)
|
||||
|
||||
t.Run("secure route requires client cert when sni matches", func(t *testing.T) {
|
||||
_, err := doHTTPSRequest(listenAddr, "secure-app.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("secure route accepts trusted client cert", func(t *testing.T) {
|
||||
resp, err := doHTTPSRequest(listenAddr, "secure-app.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{*clientCert},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
})
|
||||
|
||||
t.Run("open route without client cert succeeds", func(t *testing.T) {
|
||||
resp, err := doHTTPSRequest(listenAddr, "open-app.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
})
|
||||
|
||||
t.Run("secure route rejects requests without sni", func(t *testing.T) {
|
||||
resp, tlsConn, err := doHTTPSRequestWithServerName(listenAddr, "secure-app.example.com", "", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = tlsConn.Close() }()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
require.Equal(t, http.StatusMisdirectedRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("secure route rejects host and sni mismatch without cert", func(t *testing.T) {
|
||||
resp, tlsConn, err := doHTTPSRequestWithServerName(listenAddr, "secure-app.example.com", "open-app.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = tlsConn.Close() }()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
require.Equal(t, http.StatusMisdirectedRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("open route rejects host and sni mismatch when sni selects secure route", func(t *testing.T) {
|
||||
resp, tlsConn, err := doHTTPSRequestWithServerName(listenAddr, "open-app.example.com", "secure-app.example.com", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{*clientCert},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = tlsConn.Close() }()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
require.Equal(t, http.StatusMisdirectedRequest, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func addHTTPRouteAt(t *testing.T, ep *Entrypoint, alias, profile, listenAddr string, listener net.Listener) {
|
||||
t.Helper()
|
||||
|
||||
route := newFakeHTTPRouteAt(t, alias, profile, "https://"+listenAddr)
|
||||
if listener == nil {
|
||||
require.NoError(t, ep.StartAddRoute(route))
|
||||
return
|
||||
}
|
||||
require.NoError(t, ep.addHTTPRouteWithListener(route, listenAddr, HTTPProtoHTTPS, listener))
|
||||
}
|
||||
|
||||
func closeTestServers(t *testing.T, ep *Entrypoint) {
|
||||
t.Helper()
|
||||
for _, srv := range ep.servers.Range {
|
||||
srv.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func reserveTCPAddr(t *testing.T) (net.Listener, func()) {
|
||||
t.Helper()
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
owned := true
|
||||
t.Cleanup(func() {
|
||||
if owned {
|
||||
_ = ln.Close()
|
||||
}
|
||||
})
|
||||
return ln, func() {
|
||||
owned = false
|
||||
}
|
||||
}
|
||||
|
||||
func writeTempFile(t *testing.T, name string, data []byte) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), name)
|
||||
require.NoError(t, os.WriteFile(path, data, 0o600))
|
||||
return path
|
||||
}
|
||||
|
||||
func doHTTPSRequest(addr, host string, tlsConfig *tls.Config) (*http.Response, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, "https://"+addr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Host = host
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: cloneTLSConfigWithServerName(tlsConfig, host),
|
||||
},
|
||||
}
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
// doHTTPSRequestWithServerName sends GET https://addr/ with HTTP Host set to host and TLS
|
||||
// ServerName set to serverName (SNI may differ from Host). The returned connection stays open
|
||||
// until the caller closes it after finishing with resp (typically close resp.Body first, then
|
||||
// the tls connection).
|
||||
func doHTTPSRequestWithServerName(addr, host, serverName string, tlsConfig *tls.Config) (*http.Response, io.Closer, error) {
|
||||
conn, err := tls.Dial("tcp", addr, cloneTLSConfigWithServerName(tlsConfig, serverName))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "https://"+addr, nil)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Host = host
|
||||
if err := req.Write(conn); err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, conn, nil
|
||||
}
|
||||
|
||||
func cloneTLSConfigWithServerName(cfg *tls.Config, serverName string) *tls.Config {
|
||||
if cfg == nil {
|
||||
cfg = &tls.Config{}
|
||||
}
|
||||
cloned := cfg.Clone()
|
||||
cloned.ServerName = serverName
|
||||
return cloned
|
||||
}
|
||||
|
||||
type staticCertProvider struct {
|
||||
cert *tls.Certificate
|
||||
}
|
||||
|
||||
func (p *staticCertProvider) GetCert(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return p.cert, nil
|
||||
}
|
||||
func (p *staticCertProvider) GetCertInfos() ([]autocert.CertInfo, error) { return nil, nil }
|
||||
func (p *staticCertProvider) ScheduleRenewalAll(task.Parent) {
|
||||
// no-op: test stub
|
||||
}
|
||||
func (p *staticCertProvider) ObtainCertAll() error { return nil }
|
||||
func (p *staticCertProvider) ForceExpiryAll() bool { return false }
|
||||
func (p *staticCertProvider) WaitRenewalDone(context.Context) bool { return true }
|
||||
@@ -113,10 +113,14 @@ func (ep *Entrypoint) AddHTTPRoute(route types.HTTPRoute) error {
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) addHTTPRoute(route types.HTTPRoute, addr string, proto HTTPProto) error {
|
||||
return ep.addHTTPRouteWithListener(route, addr, proto, nil)
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) addHTTPRouteWithListener(route types.HTTPRoute, addr string, proto HTTPProto, listener net.Listener) error {
|
||||
var err error
|
||||
srv, _ := ep.servers.LoadOrCompute(addr, func() (newSrv *httpServer, cancel bool) {
|
||||
newSrv = newHTTPServer(ep)
|
||||
err = newSrv.Listen(addr, proto)
|
||||
err = newSrv.listen(addr, proto, listener)
|
||||
cancel = err != nil
|
||||
return
|
||||
})
|
||||
|
||||
Submodule internal/go-proxmox updated: d13c6c346d...ee4190e37c
Submodule internal/gopsutil updated: edfb472ac5...1636a83ac2
@@ -20,6 +20,8 @@ var CustomErrorPage = NewMiddleware[customErrorPage]()
|
||||
|
||||
const StaticFilePathPrefix = "/$gperrorpage/"
|
||||
|
||||
func (customErrorPage) isBodyResponseModifier() {}
|
||||
|
||||
// before implements RequestModifier.
|
||||
func (customErrorPage) before(w http.ResponseWriter, r *http.Request) (proceed bool) {
|
||||
return !ServeStaticErrorPageFile(w, r)
|
||||
|
||||
@@ -54,7 +54,11 @@ type (
|
||||
RequestModifier interface {
|
||||
before(w http.ResponseWriter, r *http.Request) (proceed bool)
|
||||
}
|
||||
ResponseModifier interface{ modifyResponse(r *http.Response) error }
|
||||
ResponseModifier interface{ modifyResponse(r *http.Response) error }
|
||||
BodyResponseModifier interface {
|
||||
ResponseModifier
|
||||
isBodyResponseModifier()
|
||||
}
|
||||
MiddlewareWithSetup interface{ setup() }
|
||||
MiddlewareFinalizer interface{ finalize() }
|
||||
MiddlewareFinalizerWithError interface {
|
||||
@@ -208,8 +212,15 @@ func (m *Middleware) ServeHTTP(next http.HandlerFunc, w http.ResponseWriter, r *
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
isBodyModifier := isBodyResponseModifier(exec)
|
||||
|
||||
lrm := httputils.NewLazyResponseModifier(w, canBufferAndModifyResponseBody)
|
||||
shouldBuffer := canBufferAndModifyResponseBody
|
||||
if !isBodyModifier {
|
||||
// Header-only response modifiers do not need body rewrite capability checks.
|
||||
// We still respect max buffer limits and may fall back to passthrough for large bodies.
|
||||
shouldBuffer = func(http.Header) bool { return true }
|
||||
}
|
||||
lrm := httputils.NewLazyResponseModifier(w, shouldBuffer)
|
||||
lrm.SetMaxBufferedBytes(maxModifiableBody)
|
||||
defer func() {
|
||||
_, err := lrm.FlushRelease()
|
||||
@@ -225,6 +236,9 @@ func (m *Middleware) ServeHTTP(next http.HandlerFunc, w http.ResponseWriter, r *
|
||||
}
|
||||
|
||||
rm := lrm.ResponseModifier()
|
||||
if rm.IsPassthrough() {
|
||||
return
|
||||
}
|
||||
currentBody := rm.BodyReader()
|
||||
currentResp := &http.Response{
|
||||
StatusCode: rm.StatusCode(),
|
||||
@@ -246,7 +260,7 @@ func (m *Middleware) ServeHTTP(next http.HandlerFunc, w http.ResponseWriter, r *
|
||||
maps.Copy(rm.Header(), respToModify.Header)
|
||||
|
||||
// override the body if changed
|
||||
if respToModify.Body != currentBody {
|
||||
if isBodyModifier && respToModify.Body != currentBody {
|
||||
err := rm.SetBody(respToModify.Body)
|
||||
if err != nil {
|
||||
m.LogError(r).Err(err).Msg("failed to set response body")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@@ -9,8 +8,9 @@ import (
|
||||
)
|
||||
|
||||
type middlewareChain struct {
|
||||
befores []RequestModifier
|
||||
modResps []ResponseModifier
|
||||
befores []RequestModifier
|
||||
respHeader []ResponseModifier
|
||||
respBody []ResponseModifier
|
||||
}
|
||||
|
||||
// TODO: check conflict or duplicates.
|
||||
@@ -23,7 +23,11 @@ func NewMiddlewareChain(name string, chain []*Middleware) *Middleware {
|
||||
chainMid.befores = append(chainMid.befores, before)
|
||||
}
|
||||
if mr, ok := comp.impl.(ResponseModifier); ok {
|
||||
chainMid.modResps = append(chainMid.modResps, mr)
|
||||
if isBodyResponseModifier(mr) {
|
||||
chainMid.respBody = append(chainMid.respBody, mr)
|
||||
} else {
|
||||
chainMid.respHeader = append(chainMid.respHeader, mr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return m
|
||||
@@ -44,47 +48,32 @@ func (m *middlewareChain) before(w http.ResponseWriter, r *http.Request) (procee
|
||||
|
||||
// modifyResponse implements ResponseModifier.
|
||||
func (m *middlewareChain) modifyResponse(resp *http.Response) error {
|
||||
if len(m.modResps) == 0 {
|
||||
for i, mr := range m.respHeader {
|
||||
if err := mr.modifyResponse(resp); err != nil {
|
||||
return gperr.PrependSubject(err, strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
if len(m.respBody) == 0 || !canBufferAndModifyResponseBody(responseHeaderForBodyRewriteGate(resp)) {
|
||||
return nil
|
||||
}
|
||||
for i, mr := range m.modResps {
|
||||
if err := modifyResponseWithBodyRewriteGate(mr, resp); err != nil {
|
||||
return gperr.PrependSubject(err, strconv.Itoa(i))
|
||||
headerLen := len(m.respHeader)
|
||||
for i, mr := range m.respBody {
|
||||
if err := mr.modifyResponse(resp); err != nil {
|
||||
return gperr.PrependSubject(err, strconv.Itoa(i+headerLen))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func modifyResponseWithBodyRewriteGate(mr ResponseModifier, resp *http.Response) error {
|
||||
originalBody := resp.Body
|
||||
originalContentLength := resp.ContentLength
|
||||
allowBodyRewrite := canBufferAndModifyResponseBody(responseHeaderForBodyRewriteGate(resp))
|
||||
|
||||
if err := mr.modifyResponse(resp); err != nil {
|
||||
return err
|
||||
func isBodyResponseModifier(mr ResponseModifier) bool {
|
||||
if chain, ok := mr.(*middlewareChain); ok {
|
||||
return len(chain.respBody) > 0
|
||||
}
|
||||
|
||||
if allowBodyRewrite || resp.Body == originalBody {
|
||||
return nil
|
||||
if bypass, ok := mr.(*checkBypass); ok {
|
||||
return isBodyResponseModifier(bypass.modRes)
|
||||
}
|
||||
|
||||
if resp.Body != nil {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
return fmt.Errorf("close rewritten body: %w", err)
|
||||
}
|
||||
}
|
||||
if originalBody == nil || originalBody == http.NoBody {
|
||||
resp.Body = http.NoBody
|
||||
} else {
|
||||
resp.Body = originalBody
|
||||
}
|
||||
resp.ContentLength = originalContentLength
|
||||
if originalContentLength >= 0 {
|
||||
resp.Header.Set("Content-Length", strconv.FormatInt(originalContentLength, 10))
|
||||
} else {
|
||||
resp.Header.Del("Content-Length")
|
||||
}
|
||||
return nil
|
||||
_, ok := mr.(BodyResponseModifier)
|
||||
return ok
|
||||
}
|
||||
|
||||
func responseHeaderForBodyRewriteGate(resp *http.Response) http.Header {
|
||||
|
||||
@@ -16,27 +16,37 @@ type testPriority struct {
|
||||
}
|
||||
|
||||
var test = NewMiddleware[testPriority]()
|
||||
var responseRewrite = NewMiddleware[testResponseRewrite]()
|
||||
var responseHeaderRewrite = NewMiddleware[testHeaderRewrite]()
|
||||
var responseBodyRewrite = NewMiddleware[testBodyRewrite]()
|
||||
|
||||
func (t testPriority) before(w http.ResponseWriter, r *http.Request) bool {
|
||||
w.Header().Add("Test-Value", strconv.Itoa(t.Value))
|
||||
return true
|
||||
}
|
||||
|
||||
type testResponseRewrite struct {
|
||||
type testHeaderRewrite struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
HeaderKey string `json:"header_key"`
|
||||
HeaderVal string `json:"header_val"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
func (t testResponseRewrite) modifyResponse(resp *http.Response) error {
|
||||
func (t testHeaderRewrite) modifyResponse(resp *http.Response) error {
|
||||
resp.StatusCode = t.StatusCode
|
||||
resp.Header.Set(t.HeaderKey, t.HeaderVal)
|
||||
return nil
|
||||
}
|
||||
|
||||
type testBodyRewrite struct {
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
func (t testBodyRewrite) modifyResponse(resp *http.Response) error {
|
||||
resp.Body = io.NopCloser(strings.NewReader(t.Body))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (testBodyRewrite) isBodyResponseModifier() {}
|
||||
|
||||
func TestMiddlewarePriority(t *testing.T) {
|
||||
priorities := []int{4, 7, 9, 0}
|
||||
chain := make([]*Middleware, len(priorities))
|
||||
@@ -54,50 +64,66 @@ func TestMiddlewarePriority(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMiddlewareResponseRewriteGate(t *testing.T) {
|
||||
opts := OptionsRaw{
|
||||
headerOpts := OptionsRaw{
|
||||
"status_code": 418,
|
||||
"header_key": "X-Rewrite",
|
||||
"header_val": "1",
|
||||
"body": "rewritten-body",
|
||||
}
|
||||
bodyOpts := OptionsRaw{
|
||||
"body": "rewritten-body",
|
||||
}
|
||||
headerMid, err := responseHeaderRewrite.New(headerOpts)
|
||||
expect.NoError(t, err)
|
||||
bodyMid, err := responseBodyRewrite.New(bodyOpts)
|
||||
expect.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
respHeaders http.Header
|
||||
respBody []byte
|
||||
expectBody string
|
||||
name string
|
||||
respHeaders http.Header
|
||||
respBody []byte
|
||||
expectStatus int
|
||||
expectHeader string
|
||||
expectBody string
|
||||
}{
|
||||
{
|
||||
name: "allow_body_rewrite_for_html",
|
||||
respHeaders: http.Header{
|
||||
"Content-Type": []string{"text/html; charset=utf-8"},
|
||||
},
|
||||
respBody: []byte("<html><body>original</body></html>"),
|
||||
expectBody: "rewritten-body",
|
||||
respBody: []byte("<html><body>original</body></html>"),
|
||||
expectStatus: http.StatusTeapot,
|
||||
expectHeader: "1",
|
||||
expectBody: "rewritten-body",
|
||||
},
|
||||
{
|
||||
name: "allow_body_rewrite_for_json",
|
||||
respHeaders: http.Header{
|
||||
"Content-Type": []string{"application/json"},
|
||||
},
|
||||
respBody: []byte(`{"message":"original"}`),
|
||||
expectBody: "rewritten-body",
|
||||
respBody: []byte(`{"message":"original"}`),
|
||||
expectStatus: http.StatusTeapot,
|
||||
expectHeader: "1",
|
||||
expectBody: "rewritten-body",
|
||||
},
|
||||
{
|
||||
name: "allow_body_rewrite_for_yaml",
|
||||
respHeaders: http.Header{
|
||||
"Content-Type": []string{"application/yaml"},
|
||||
},
|
||||
respBody: []byte("k: v"),
|
||||
expectBody: "rewritten-body",
|
||||
respBody: []byte("k: v"),
|
||||
expectStatus: http.StatusTeapot,
|
||||
expectHeader: "1",
|
||||
expectBody: "rewritten-body",
|
||||
},
|
||||
{
|
||||
name: "block_body_rewrite_for_binary_content",
|
||||
respHeaders: http.Header{
|
||||
"Content-Type": []string{"application/octet-stream"},
|
||||
},
|
||||
respBody: []byte("binary"),
|
||||
expectBody: "binary",
|
||||
respBody: []byte("binary"),
|
||||
expectStatus: http.StatusTeapot,
|
||||
expectHeader: "1",
|
||||
expectBody: "binary",
|
||||
},
|
||||
{
|
||||
name: "block_body_rewrite_for_transfer_encoded_html",
|
||||
@@ -105,8 +131,10 @@ func TestMiddlewareResponseRewriteGate(t *testing.T) {
|
||||
"Content-Type": []string{"text/html"},
|
||||
"Transfer-Encoding": []string{"chunked"},
|
||||
},
|
||||
respBody: []byte("<html><body>original</body></html>"),
|
||||
expectBody: "<html><body>original</body></html>",
|
||||
respBody: []byte("<html><body>original</body></html>"),
|
||||
expectStatus: http.StatusTeapot,
|
||||
expectHeader: "1",
|
||||
expectBody: "<html><body>original</body></html>",
|
||||
},
|
||||
{
|
||||
name: "block_body_rewrite_for_content_encoded_html",
|
||||
@@ -114,34 +142,42 @@ func TestMiddlewareResponseRewriteGate(t *testing.T) {
|
||||
"Content-Type": []string{"text/html"},
|
||||
"Content-Encoding": []string{"gzip"},
|
||||
},
|
||||
respBody: []byte("<html><body>original</body></html>"),
|
||||
expectBody: "<html><body>original</body></html>",
|
||||
respBody: []byte("<html><body>original</body></html>"),
|
||||
expectStatus: http.StatusTeapot,
|
||||
expectHeader: "1",
|
||||
expectBody: "<html><body>original</body></html>",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := newMiddlewareTest(responseRewrite, &testArgs{
|
||||
middlewareOpt: opts,
|
||||
respHeaders: tc.respHeaders,
|
||||
respBody: tc.respBody,
|
||||
respStatus: http.StatusOK,
|
||||
result, err := newMiddlewaresTest([]*Middleware{headerMid, bodyMid}, &testArgs{
|
||||
respHeaders: tc.respHeaders,
|
||||
respBody: tc.respBody,
|
||||
respStatus: http.StatusOK,
|
||||
})
|
||||
expect.NoError(t, err)
|
||||
expect.Equal(t, result.ResponseStatus, http.StatusTeapot)
|
||||
expect.Equal(t, result.ResponseHeaders.Get("X-Rewrite"), "1")
|
||||
expect.Equal(t, result.ResponseStatus, tc.expectStatus)
|
||||
expect.Equal(t, result.ResponseHeaders.Get("X-Rewrite"), tc.expectHeader)
|
||||
expect.Equal(t, string(result.Data), tc.expectBody)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiddlewareResponseRewriteGateServeHTTP(t *testing.T) {
|
||||
opts := OptionsRaw{
|
||||
headerOpts := OptionsRaw{
|
||||
"status_code": 418,
|
||||
"header_key": "X-Rewrite",
|
||||
"header_val": "1",
|
||||
"body": "rewritten-body",
|
||||
}
|
||||
bodyOpts := OptionsRaw{
|
||||
"body": "rewritten-body",
|
||||
}
|
||||
headerMid, err := responseHeaderRewrite.New(headerOpts)
|
||||
expect.NoError(t, err)
|
||||
bodyMid, err := responseBodyRewrite.New(bodyOpts)
|
||||
expect.NoError(t, err)
|
||||
mid := NewMiddlewareChain("test", []*Middleware{headerMid, bodyMid})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -197,9 +233,6 @@ func TestMiddlewareResponseRewriteGateServeHTTP(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mid, err := responseRewrite.New(opts)
|
||||
expect.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
@@ -226,3 +259,18 @@ func TestMiddlewareResponseRewriteGateServeHTTP(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestThemedSkipsBodyRewriteWhenRewriteBlocked(t *testing.T) {
|
||||
result, err := newMiddlewareTest(Themed, &testArgs{
|
||||
middlewareOpt: OptionsRaw{
|
||||
"theme": DarkTheme,
|
||||
},
|
||||
respHeaders: http.Header{
|
||||
"Content-Type": []string{"text/html; charset=utf-8"},
|
||||
"Transfer-Encoding": []string{"chunked"},
|
||||
},
|
||||
respBody: []byte("<html><body>original</body></html>"),
|
||||
})
|
||||
expect.NoError(t, err)
|
||||
expect.Equal(t, string(result.Data), "<html><body>original</body></html>")
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ type modifyHTML struct {
|
||||
|
||||
var ModifyHTML = NewMiddleware[modifyHTML]()
|
||||
|
||||
func (*modifyHTML) isBodyResponseModifier() {}
|
||||
|
||||
func (m *modifyHTML) before(_ http.ResponseWriter, req *http.Request) bool {
|
||||
req.Header.Set("Accept-Encoding", "identity")
|
||||
return true
|
||||
|
||||
@@ -54,6 +54,8 @@ func (m *themed) modifyResponse(resp *http.Response) error {
|
||||
return m.m.modifyResponse(resp)
|
||||
}
|
||||
|
||||
func (*themed) isBodyResponseModifier() {}
|
||||
|
||||
func (m *themed) finalize() error {
|
||||
m.m.Target = "body"
|
||||
if m.FontURL != "" && m.FontFamily != "" {
|
||||
|
||||
@@ -42,6 +42,7 @@ type Route struct {
|
||||
|
||||
// Route rules and middleware
|
||||
HTTPConfig
|
||||
InboundMTLSProfile string
|
||||
PathPatterns []string
|
||||
Rules rules.Rules
|
||||
RuleFile string
|
||||
@@ -61,6 +62,24 @@ type Route struct {
|
||||
}
|
||||
```
|
||||
|
||||
`InboundMTLSProfile` references a named root-level inbound mTLS profile for this route.
|
||||
|
||||
- It is only honored when no global `entrypoint.inbound_mtls_profile` is configured.
|
||||
- It is only valid for HTTP-based routes.
|
||||
- Route-scoped inbound mTLS is selected by TLS SNI.
|
||||
- Requests for secured routes must resolve to the same route by both HTTP `Host` and TLS SNI.
|
||||
- If the profile name does not exist, route validation fails.
|
||||
|
||||
Example route fragment:
|
||||
|
||||
```yaml
|
||||
alias: secure-api
|
||||
host: api.example.com
|
||||
scheme: https
|
||||
port: 443
|
||||
inbound_mtls_profile: corp-clients
|
||||
```
|
||||
|
||||
```go
|
||||
type Scheme string
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@ example: # matching `example.y.z`
|
||||
host: 10.0.0.254
|
||||
port: 80
|
||||
bind: 0.0.0.0
|
||||
relay_proxy_protocol_header: false # tcp only, sends PROXY header to upstream
|
||||
root: /var/www/example
|
||||
spa: true
|
||||
index: index.html
|
||||
no_tls_verify: true
|
||||
disable_compression: false
|
||||
response_header_timeout: 30s
|
||||
inbound_mtls_profile: corp # optional, only supported when no global entrypoint inbound_mtls_profile is configured; selected by TLS SNI and Host/SNI must resolve to the same route
|
||||
ssl_server_name: "" # empty uses target hostname, "off" disables SNI
|
||||
ssl_trusted_certificate: /etc/ssl/certs/ca-certificates.crt
|
||||
ssl_certificate: /etc/ssl/client.crt
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/yusing/godoxy/internal/agentpool"
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
entrypointimpl "github.com/yusing/godoxy/internal/entrypoint"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/health/monitor"
|
||||
"github.com/yusing/godoxy/internal/homepage"
|
||||
@@ -54,15 +55,17 @@ type (
|
||||
Index string `json:"index,omitempty"` // Index file to serve for single-page app mode
|
||||
|
||||
route.HTTPConfig
|
||||
PathPatterns []string `json:"path_patterns,omitempty" extensions:"x-nullable"`
|
||||
Rules rules.Rules `json:"rules,omitempty" extensions:"x-nullable"`
|
||||
RuleFile string `json:"rule_file,omitempty" extensions:"x-nullable"`
|
||||
HealthCheck types.HealthCheckConfig `json:"healthcheck,omitzero" extensions:"x-nullable"` // null on load-balancer routes
|
||||
LoadBalance *types.LoadBalancerConfig `json:"load_balance,omitempty" extensions:"x-nullable"`
|
||||
Middlewares map[string]types.LabelMap `json:"middlewares,omitempty" extensions:"x-nullable"`
|
||||
Homepage *homepage.ItemConfig `json:"homepage"`
|
||||
AccessLog *accesslog.RequestLoggerConfig `json:"access_log,omitempty" extensions:"x-nullable"`
|
||||
Agent string `json:"agent,omitempty"`
|
||||
InboundMTLSProfile string `json:"inbound_mtls_profile,omitempty"`
|
||||
PathPatterns []string `json:"path_patterns,omitempty" extensions:"x-nullable"`
|
||||
Rules rules.Rules `json:"rules,omitempty" extensions:"x-nullable"`
|
||||
RuleFile string `json:"rule_file,omitempty" extensions:"x-nullable"`
|
||||
HealthCheck types.HealthCheckConfig `json:"healthcheck,omitzero" extensions:"x-nullable"` // null on load-balancer routes
|
||||
LoadBalance *types.LoadBalancerConfig `json:"load_balance,omitempty" extensions:"x-nullable"`
|
||||
Middlewares map[string]types.LabelMap `json:"middlewares,omitempty" extensions:"x-nullable"`
|
||||
Homepage *homepage.ItemConfig `json:"homepage"`
|
||||
AccessLog *accesslog.RequestLoggerConfig `json:"access_log,omitempty" extensions:"x-nullable"`
|
||||
RelayProxyProtocolHeader bool `json:"relay_proxy_protocol_header,omitempty"` // TCP only: relay PROXY protocol header to the destination
|
||||
Agent string `json:"agent,omitempty"`
|
||||
|
||||
Proxmox *proxmox.NodeConfig `json:"proxmox,omitempty" extensions:"x-nullable"`
|
||||
|
||||
@@ -170,8 +173,23 @@ func (r *Route) validate() error {
|
||||
}
|
||||
}
|
||||
|
||||
if workingState := config.WorkingState.Load(); workingState != nil {
|
||||
cfg := workingState.Value()
|
||||
if err := entrypointimpl.ValidateInboundMTLSProfileRef(r.InboundMTLSProfile, cfg.Entrypoint.InboundMTLSProfile, cfg.InboundMTLSProfiles); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r.Finalize()
|
||||
|
||||
if r.InboundMTLSProfile != "" {
|
||||
switch r.Scheme {
|
||||
case route.SchemeHTTP, route.SchemeHTTPS, route.SchemeH2C, route.SchemeFileServer:
|
||||
default:
|
||||
return errors.New("inbound_mtls_profile is only supported for HTTP-based routes")
|
||||
}
|
||||
}
|
||||
|
||||
r.started = make(chan struct{})
|
||||
// close the channel when the route is destroyed (if not closed yet).
|
||||
runtime.AddCleanup(r, func(ch chan struct{}) {
|
||||
@@ -310,6 +328,9 @@ func (r *Route) validate() error {
|
||||
if !r.UseHealthCheck() && (r.UseLoadBalance() || r.UseIdleWatcher()) {
|
||||
errs.Adds("cannot disable healthcheck when loadbalancer or idle watcher is enabled")
|
||||
}
|
||||
if r.RelayProxyProtocolHeader && r.Scheme != route.SchemeTCP {
|
||||
errs.Adds("relay_proxy_protocol_header is only supported for tcp routes")
|
||||
}
|
||||
|
||||
if errs.HasError() {
|
||||
return errs.Error()
|
||||
@@ -763,6 +784,10 @@ func (r *Route) ContainerInfo() *types.Container {
|
||||
return r.Container
|
||||
}
|
||||
|
||||
func (r *Route) InboundMTLSProfileRef() string {
|
||||
return r.InboundMTLSProfile
|
||||
}
|
||||
|
||||
func (r *Route) IsDocker() bool {
|
||||
if r.Container == nil {
|
||||
return false
|
||||
|
||||
@@ -78,6 +78,32 @@ func TestRouteValidate(t *testing.T) {
|
||||
require.NotNil(t, r.impl, "Impl should be initialized")
|
||||
})
|
||||
|
||||
t.Run("RelayProxyProtocolHeaderTCPOnly", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test-udp-relay",
|
||||
Scheme: route.SchemeUDP,
|
||||
Host: "127.0.0.1",
|
||||
Port: route.Port{Proxy: 53, Listening: 53},
|
||||
RelayProxyProtocolHeader: true,
|
||||
}
|
||||
err := r.Validate()
|
||||
require.Error(t, err, "Validate should reject proxy protocol relay on UDP routes")
|
||||
require.ErrorContains(t, err, "relay_proxy_protocol_header is only supported for tcp routes")
|
||||
})
|
||||
|
||||
t.Run("InboundMTLSProfileHTTPOnly", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test-udp-mtls",
|
||||
Scheme: route.SchemeUDP,
|
||||
Host: "127.0.0.1",
|
||||
Port: route.Port{Proxy: 53, Listening: 53},
|
||||
InboundMTLSProfile: "corp",
|
||||
}
|
||||
err := r.Validate()
|
||||
require.Error(t, err, "Validate should reject inbound mTLS on non-HTTP routes")
|
||||
require.ErrorContains(t, err, "inbound_mtls_profile is only supported for HTTP-based routes")
|
||||
})
|
||||
|
||||
t.Run("DockerContainer", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test",
|
||||
|
||||
@@ -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/*")
|
||||
|
||||
@@ -110,7 +110,14 @@ func (r *StreamRoute) initStream() (nettypes.Stream, error) {
|
||||
|
||||
switch rScheme {
|
||||
case "tcp":
|
||||
return stream.NewTCPTCPStream(lurl.Scheme, rurl.Scheme, laddr, rurl.Host, r.GetAgent())
|
||||
return stream.NewTCPTCPStream(
|
||||
lurl.Scheme,
|
||||
rurl.Scheme,
|
||||
laddr,
|
||||
rurl.Host,
|
||||
r.GetAgent(),
|
||||
r.RelayProxyProtocolHeader,
|
||||
)
|
||||
case "udp":
|
||||
return stream.NewUDPUDPStream(lurl.Scheme, rurl.Scheme, laddr, rurl.Host, r.GetAgent())
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@ routes:
|
||||
scheme: tcp4
|
||||
bind: 0.0.0.0 # optional
|
||||
port: 2222:22 # listening port: target port
|
||||
relay_proxy_protocol_header: true # optional, tcp only
|
||||
|
||||
dns-proxy:
|
||||
scheme: udp4
|
||||
@@ -223,6 +224,7 @@ Log context includes: `protocol`, `listen`, `dst`, `action`
|
||||
|
||||
- ACL wrapping available for TCP and UDP listeners
|
||||
- PROXY protocol support for original client IP
|
||||
- TCP routes can optionally emit a fresh upstream PROXY v2 header with `relay_proxy_protocol_header: true`
|
||||
- No protocol validation (relies on upstream)
|
||||
- Connection limits managed by OS
|
||||
|
||||
|
||||
37
internal/route/stream/proxyproto.go
Normal file
37
internal/route/stream/proxyproto.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package stream
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
)
|
||||
|
||||
func writeProxyProtocolHeader(dst io.Writer, src net.Conn) error {
|
||||
srcAddr, ok := src.RemoteAddr().(*net.TCPAddr)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected source address type %T", src.RemoteAddr())
|
||||
}
|
||||
dstAddr, ok := src.LocalAddr().(*net.TCPAddr)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected destination address type %T", src.LocalAddr())
|
||||
}
|
||||
|
||||
header := &proxyproto.Header{
|
||||
Version: 2,
|
||||
Command: proxyproto.PROXY,
|
||||
TransportProtocol: transportProtocol(srcAddr, dstAddr),
|
||||
SourceAddr: srcAddr,
|
||||
DestinationAddr: dstAddr,
|
||||
}
|
||||
_, err := header.WriteTo(dst)
|
||||
return err
|
||||
}
|
||||
|
||||
func transportProtocol(src, dst *net.TCPAddr) proxyproto.AddressFamilyAndProtocol {
|
||||
if src.IP.To4() != nil && dst.IP.To4() != nil {
|
||||
return proxyproto.TCPv4
|
||||
}
|
||||
return proxyproto.TCPv6
|
||||
}
|
||||
@@ -25,13 +25,15 @@ type TCPTCPStream struct {
|
||||
dst *net.TCPAddr
|
||||
agent *agentpool.Agent
|
||||
|
||||
relayProxyProtocolHeader bool
|
||||
|
||||
preDial nettypes.HookFunc
|
||||
onRead nettypes.HookFunc
|
||||
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
func NewTCPTCPStream(network, dstNetwork, listenAddr, dstAddr string, agent *agentpool.Agent) (nettypes.Stream, error) {
|
||||
func NewTCPTCPStream(network, dstNetwork, listenAddr, dstAddr string, agent *agentpool.Agent, relayProxyProtocolHeader bool) (nettypes.Stream, error) {
|
||||
dst, err := net.ResolveTCPAddr(dstNetwork, dstAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -40,7 +42,14 @@ func NewTCPTCPStream(network, dstNetwork, listenAddr, dstAddr string, agent *age
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TCPTCPStream{network: network, dstNetwork: dstNetwork, laddr: laddr, dst: dst, agent: agent}, nil
|
||||
return &TCPTCPStream{
|
||||
network: network,
|
||||
dstNetwork: dstNetwork,
|
||||
laddr: laddr,
|
||||
dst: dst,
|
||||
agent: agent,
|
||||
relayProxyProtocolHeader: relayProxyProtocolHeader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *TCPTCPStream) ListenAndServe(ctx context.Context, preDial, onRead nettypes.HookFunc) error {
|
||||
@@ -158,6 +167,14 @@ func (s *TCPTCPStream) handle(ctx context.Context, conn net.Conn) {
|
||||
if s.closed.Load() {
|
||||
return
|
||||
}
|
||||
if s.relayProxyProtocolHeader {
|
||||
if err := writeProxyProtocolHeader(dstConn, conn); err != nil {
|
||||
if !s.closed.Load() {
|
||||
logErr(s, err, "failed to write proxy protocol header")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
src := conn
|
||||
dst := dstConn
|
||||
|
||||
148
internal/route/stream/tcp_tcp_test.go
Normal file
148
internal/route/stream/tcp_tcp_test.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package stream
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint"
|
||||
entrypointtypes "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/goutils/task"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTCPTCPStreamRelayProxyProtocolHeader(t *testing.T) {
|
||||
t.Run("Disabled", func(t *testing.T) {
|
||||
upstreamLn, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
defer upstreamLn.Close()
|
||||
|
||||
s, err := NewTCPTCPStream("tcp", "tcp", "127.0.0.1:0", upstreamLn.Addr().String(), nil, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
require.NoError(t, s.ListenAndServe(ctx, nil, nil))
|
||||
defer s.Close()
|
||||
|
||||
client, err := net.Dial("tcp", s.LocalAddr().String())
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
_, err = client.Write([]byte("ping"))
|
||||
require.NoError(t, err)
|
||||
|
||||
upstreamConn, err := upstreamLn.Accept()
|
||||
require.NoError(t, err)
|
||||
defer upstreamConn.Close()
|
||||
|
||||
payload := make([]byte, 4)
|
||||
_, err = io.ReadFull(upstreamConn, payload)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("ping"), payload)
|
||||
})
|
||||
|
||||
t.Run("Enabled", func(t *testing.T) {
|
||||
upstreamLn, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
defer upstreamLn.Close()
|
||||
|
||||
s, err := NewTCPTCPStream("tcp", "tcp", "127.0.0.1:0", upstreamLn.Addr().String(), nil, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
require.NoError(t, s.ListenAndServe(ctx, nil, nil))
|
||||
defer s.Close()
|
||||
|
||||
client, err := net.Dial("tcp", s.LocalAddr().String())
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
_, err = client.Write([]byte("ping"))
|
||||
require.NoError(t, err)
|
||||
|
||||
upstreamConn, err := upstreamLn.Accept()
|
||||
require.NoError(t, err)
|
||||
defer upstreamConn.Close()
|
||||
|
||||
reader := bufio.NewReader(upstreamConn)
|
||||
header, err := proxyproto.Read(reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, proxyproto.PROXY, header.Command)
|
||||
|
||||
srcAddr, ok := header.SourceAddr.(*net.TCPAddr)
|
||||
require.True(t, ok)
|
||||
dstAddr, ok := header.DestinationAddr.(*net.TCPAddr)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, client.LocalAddr().String(), srcAddr.String())
|
||||
require.Equal(t, s.LocalAddr().String(), dstAddr.String())
|
||||
|
||||
payload := make([]byte, 4)
|
||||
_, err = io.ReadFull(reader, payload)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("ping"), payload)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTCPTCPStreamRelayProxyProtocolUsesIncomingProxyHeader(t *testing.T) {
|
||||
upstreamLn, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
defer upstreamLn.Close()
|
||||
|
||||
s, err := NewTCPTCPStream("tcp", "tcp", "127.0.0.1:0", upstreamLn.Addr().String(), nil, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
parent := task.GetTestTask(t)
|
||||
ep := entrypoint.NewEntrypoint(parent, &entrypoint.Config{
|
||||
SupportProxyProtocol: true,
|
||||
})
|
||||
entrypointtypes.SetCtx(parent, ep)
|
||||
|
||||
ctx, cancel := context.WithCancel(parent.Context())
|
||||
defer cancel()
|
||||
require.NoError(t, s.ListenAndServe(ctx, nil, nil))
|
||||
defer s.Close()
|
||||
|
||||
client, err := net.Dial("tcp", s.LocalAddr().String())
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
downstreamHeader := &proxyproto.Header{
|
||||
Version: 2,
|
||||
Command: proxyproto.PROXY,
|
||||
TransportProtocol: proxyproto.TCPv4,
|
||||
SourceAddr: &net.TCPAddr{
|
||||
IP: net.ParseIP("203.0.113.10"),
|
||||
Port: 42300,
|
||||
},
|
||||
DestinationAddr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: s.LocalAddr().(*net.TCPAddr).Port,
|
||||
},
|
||||
}
|
||||
_, err = downstreamHeader.WriteTo(client)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.Write([]byte("pong"))
|
||||
require.NoError(t, err)
|
||||
|
||||
upstreamConn, err := upstreamLn.Accept()
|
||||
require.NoError(t, err)
|
||||
defer upstreamConn.Close()
|
||||
|
||||
reader := bufio.NewReader(upstreamConn)
|
||||
header, err := proxyproto.Read(reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, downstreamHeader.SourceAddr.String(), header.SourceAddr.String())
|
||||
require.Equal(t, downstreamHeader.DestinationAddr.String(), header.DestinationAddr.String())
|
||||
|
||||
payload := make([]byte, 4)
|
||||
_, err = io.ReadFull(reader, payload)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("pong"), payload)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package serialization
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -727,7 +728,7 @@ func SaveFile[T any](path string, src *T, perm os.FileMode, marshaler marshalFun
|
||||
|
||||
// LoadFileIfExist reads a file and unmarshals its contents to a value.
|
||||
// - The unmarshaler function converts the bytes to a value.
|
||||
// - If the file does not exist, nil is returned and dst remains unchanged.
|
||||
// - If the file does not exist or is empty, nil is returned and dst remains unchanged.
|
||||
func LoadFileIfExist[T any](path string, dst *T, unmarshaler unmarshalFunc) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -736,5 +737,8 @@ func LoadFileIfExist[T any](path string, dst *T, unmarshaler unmarshalFunc) erro
|
||||
}
|
||||
return err
|
||||
}
|
||||
if len(bytes.TrimSpace(data)) == 0 {
|
||||
return nil
|
||||
}
|
||||
return unmarshaler(data, dst)
|
||||
}
|
||||
|
||||
15
internal/types/inbound_mtls.go
Normal file
15
internal/types/inbound_mtls.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package types
|
||||
|
||||
import "errors"
|
||||
|
||||
type InboundMTLSProfile struct {
|
||||
UseSystemCAs bool `json:"use_system_cas,omitempty" yaml:"use_system_cas,omitempty"`
|
||||
CAFiles []string `json:"ca_files,omitempty" yaml:"ca_files,omitempty" validate:"omitempty,dive,filepath"`
|
||||
}
|
||||
|
||||
func (cfg InboundMTLSProfile) Validate() error {
|
||||
if !cfg.UseSystemCAs && len(cfg.CAFiles) == 0 {
|
||||
return errors.New("at least one trust source is required for inbound mTLS profile")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
34
internal/types/inbound_mtls_test.go
Normal file
34
internal/types/inbound_mtls_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInboundMTLSProfileValidate(t *testing.T) {
|
||||
t.Run("requires at least one trust source", func(t *testing.T) {
|
||||
var profile InboundMTLSProfile
|
||||
err := profile.Validate()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "trust source")
|
||||
})
|
||||
|
||||
t.Run("system CA only", func(t *testing.T) {
|
||||
profile := InboundMTLSProfile{UseSystemCAs: true}
|
||||
require.NoError(t, profile.Validate())
|
||||
})
|
||||
|
||||
t.Run("CA file only", func(t *testing.T) {
|
||||
profile := InboundMTLSProfile{CAFiles: []string{"/tmp/ca.pem"}}
|
||||
require.NoError(t, profile.Validate())
|
||||
})
|
||||
|
||||
t.Run("system CA and CA files", func(t *testing.T) {
|
||||
profile := InboundMTLSProfile{
|
||||
UseSystemCAs: true,
|
||||
CAFiles: []string{"/tmp/ca.pem"},
|
||||
}
|
||||
require.NoError(t, profile.Validate())
|
||||
})
|
||||
}
|
||||
@@ -37,6 +37,7 @@ type (
|
||||
HomepageItem() homepage.Item
|
||||
DisplayName() string
|
||||
ContainerInfo() *Container
|
||||
InboundMTLSProfileRef() string
|
||||
|
||||
GetAgent() *agentpool.Agent
|
||||
|
||||
|
||||
@@ -26,3 +26,9 @@ app2:
|
||||
scheme: udp
|
||||
host: 10.0.0.2
|
||||
port: 2223:dns
|
||||
|
||||
ssh-with-proxy-protocol:
|
||||
scheme: tcp
|
||||
host: 10.0.0.3
|
||||
port: 2222:22
|
||||
relay_proxy_protocol_header: true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Stage 1: deps
|
||||
FROM golang:1.26.0-alpine AS deps
|
||||
FROM golang:1.26.2-alpine AS deps
|
||||
HEALTHCHECK NONE
|
||||
|
||||
# package version does not matter
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
module github.com/yusing/godoxy/socketproxy
|
||||
|
||||
go 1.26.0
|
||||
go 1.26.2
|
||||
|
||||
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.50.0
|
||||
golang.org/x/net v0.53.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-isatty v0.0.21 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
github.com/rs/zerolog v1.35.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,34 +1,24 @@
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
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=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI=
|
||||
github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
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.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
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.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
Reference in New Issue
Block a user