Merge pull request #644 from tobychui/v3.2.1

- Merged in authentik forward auth support
- Merged IPv6 whitelist patch
- Added support for basic per host name statistic
- Added experimental plugin store
- Added `$remote_ip` in custom header that filters port number from `$remote_addr`
- Fixed origin is not populated in log bug
- Fixed redirection location rewrite bug
This commit is contained in:
Toby Chui 2025-04-27 14:54:27 +08:00 committed by GitHub
commit 0e5550487e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 1524 additions and 268 deletions

View File

@ -83,6 +83,7 @@ func RegisterTLSAPIs(authRouter *auth.RouterDef) {
// Register the APIs for Authentication handlers like Authelia and OAUTH2 // Register the APIs for Authentication handlers like Authelia and OAUTH2
func RegisterAuthenticationHandlerAPIs(authRouter *auth.RouterDef) { func RegisterAuthenticationHandlerAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/sso/Authelia", autheliaRouter.HandleSetAutheliaURLAndHTTPS) authRouter.HandleFunc("/api/sso/Authelia", autheliaRouter.HandleSetAutheliaURLAndHTTPS)
authRouter.HandleFunc("/api/sso/Authentik", authentikRouter.HandleSetAuthentikURLAndHTTPS)
} }
// Register the APIs for redirection rules management functions // Register the APIs for redirection rules management functions
@ -233,6 +234,11 @@ func RegisterPluginAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/plugins/groups/add", pluginManager.HandleAddPluginToGroup) authRouter.HandleFunc("/api/plugins/groups/add", pluginManager.HandleAddPluginToGroup)
authRouter.HandleFunc("/api/plugins/groups/remove", pluginManager.HandleRemovePluginFromGroup) authRouter.HandleFunc("/api/plugins/groups/remove", pluginManager.HandleRemovePluginFromGroup)
authRouter.HandleFunc("/api/plugins/groups/deleteTag", pluginManager.HandleRemovePluginGroup) authRouter.HandleFunc("/api/plugins/groups/deleteTag", pluginManager.HandleRemovePluginGroup)
authRouter.HandleFunc("/api/plugins/store/list", pluginManager.HandleListDownloadablePlugins)
authRouter.HandleFunc("/api/plugins/store/resync", pluginManager.HandleResyncPluginList)
authRouter.HandleFunc("/api/plugins/store/install", pluginManager.HandleInstallPlugin)
authRouter.HandleFunc("/api/plugins/store/uninstall", pluginManager.HandleUninstallPlugin)
} }
// Register the APIs for Auth functions, due to scoping issue some functions are defined here // Register the APIs for Auth functions, due to scoping issue some functions are defined here
@ -318,7 +324,7 @@ func initAPIs(targetMux *http.ServeMux) {
// Register the standard web services URLs // Register the standard web services URLs
var staticWebRes http.Handler var staticWebRes http.Handler
if DEVELOPMENT_BUILD { if *development_build {
staticWebRes = http.FileServer(http.Dir("web/")) staticWebRes = http.FileServer(http.Dir("web/"))
} else { } else {
subFS, err := fs.Sub(webres, "web") subFS, err := fs.Sub(webres, "web")

View File

@ -13,6 +13,8 @@ import (
"net/http" "net/http"
"time" "time"
"imuslab.com/zoraxy/mod/auth/sso/authentik"
"imuslab.com/zoraxy/mod/access" "imuslab.com/zoraxy/mod/access"
"imuslab.com/zoraxy/mod/acme" "imuslab.com/zoraxy/mod/acme"
"imuslab.com/zoraxy/mod/auth" "imuslab.com/zoraxy/mod/auth"
@ -42,8 +44,7 @@ import (
const ( const (
/* Build Constants */ /* Build Constants */
SYSTEM_NAME = "Zoraxy" SYSTEM_NAME = "Zoraxy"
SYSTEM_VERSION = "3.2.0" SYSTEM_VERSION = "3.2.1"
DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */
/* System Constants */ /* System Constants */
TMP_FOLDER = "./tmp" TMP_FOLDER = "./tmp"
@ -99,8 +100,9 @@ var (
path_webserver = flag.String("webroot", "./www", "Static web server root folder. Only allow change in start paramters") path_webserver = flag.String("webroot", "./www", "Static web server root folder. Only allow change in start paramters")
path_plugin = flag.String("plugin", "./plugins", "Plugin folder path") path_plugin = flag.String("plugin", "./plugins", "Plugin folder path")
/* Maintaince Function Flags */ /* Maintaince & Development Function Flags */
geoDbUpdate = flag.Bool("update_geoip", false, "Download the latest GeoIP data and exit") geoDbUpdate = flag.Bool("update_geoip", false, "Download the latest GeoIP data and exit")
development_build = flag.Bool("dev", false, "Use external web folder for UI development")
) )
/* Global Variables and Handlers */ /* Global Variables and Handlers */
@ -143,6 +145,7 @@ var (
//Authentication Provider //Authentication Provider
autheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication autheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication
authentikRouter *authentik.AuthentikRouter //Authentik router for Authentik authentication
//Helper modules //Helper modules
EmailSender *email.Sender //Email sender that handle email sending EmailSender *email.Sender //Email sender that handle email sending

View File

@ -5,11 +5,11 @@ go 1.22.0
toolchain go1.22.2 toolchain go1.22.2
require ( require (
github.com/armon/go-radix v1.0.0
github.com/boltdb/bolt v1.3.1 github.com/boltdb/bolt v1.3.1
github.com/docker/docker v27.0.0+incompatible github.com/docker/docker v27.0.0+incompatible
github.com/go-acme/lego/v4 v4.21.0 github.com/go-acme/lego/v4 v4.21.0
github.com/go-ping/ping v1.1.0 github.com/go-ping/ping v1.1.0
github.com/go-session/session v3.1.2+incompatible
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/sessions v1.2.2 github.com/gorilla/sessions v1.2.2
github.com/gorilla/websocket v1.5.1 github.com/gorilla/websocket v1.5.1
@ -19,7 +19,6 @@ require (
github.com/shirou/gopsutil/v4 v4.25.1 github.com/shirou/gopsutil/v4 v4.25.1
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
golang.org/x/net v0.33.0 golang.org/x/net v0.33.0
golang.org/x/sys v0.28.0
golang.org/x/text v0.21.0 golang.org/x/text v0.21.0
) )
@ -27,30 +26,22 @@ require (
cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth v0.13.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect github.com/benbjohnson/clock v1.3.0 // indirect
github.com/ebitengine/purego v0.8.2 // indirect github.com/ebitengine/purego v0.8.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/snappy v0.0.1 // indirect github.com/golang/snappy v0.0.1 // indirect
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // indirect github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // indirect
github.com/monperrus/crawler-user-agents v1.1.0 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/peterhellberg/link v1.2.0 // indirect github.com/peterhellberg/link v1.2.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/shopspring/decimal v1.3.1 // indirect github.com/shopspring/decimal v1.3.1 // indirect
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 // indirect
github.com/tidwall/buntdb v1.1.2 // indirect
github.com/tidwall/gjson v1.12.1 // indirect
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e // indirect
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/vultr/govultr/v3 v3.9.1 // indirect github.com/vultr/govultr/v3 v3.9.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect go.mongodb.org/mongo-driver v1.12.0 // indirect
golang.org/x/sys v0.28.0 // indirect
) )
require ( require (
@ -111,11 +102,9 @@ require (
github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-oauth2/oauth2/v4 v4.5.2
github.com/go-resty/resty/v2 v2.16.2 // indirect github.com/go-resty/resty/v2 v2.16.2 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/goccy/go-json v0.10.4 // indirect github.com/goccy/go-json v0.10.4 // indirect
github.com/gofrs/uuid v4.4.0+incompatible
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
@ -187,7 +176,6 @@ require (
github.com/transip/gotransip/v6 v6.26.0 // indirect github.com/transip/gotransip/v6 v6.26.0 // indirect
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect
github.com/vinyldns/go-vinyldns v0.9.16 // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect
github.com/xlzd/gotp v0.1.0
github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect

View File

@ -76,15 +76,11 @@ github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jB
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 h1:HvFZUzEbNvfe8F2Mg0wBGv90bPhWDxgVtDHR5zoBOU0= github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 h1:HvFZUzEbNvfe8F2Mg0wBGv90bPhWDxgVtDHR5zoBOU0=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.72/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= github.com/aliyun/alibaba-cloud-sdk-go v1.63.72/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
@ -186,7 +182,6 @@ github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
@ -202,8 +197,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-acme/lego/v4 v4.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o= github.com/go-acme/lego/v4 v4.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o=
@ -222,16 +215,12 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-oauth2/oauth2/v4 v4.5.2 h1:CuZhD3lhGuI6aNLyUbRHXsgG2RwGRBOuCBfd4WQKqBQ=
github.com/go-oauth2/oauth2/v4 v4.5.2/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg= github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
github.com/go-session/session v3.1.2+incompatible h1:yStchEObKg4nk2F7JGE7KoFIrA/1Y078peagMWcrncg=
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
@ -242,16 +231,11 @@ github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
@ -307,7 +291,6 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -320,7 +303,6 @@ github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPq
github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk=
github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw= github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw=
github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
@ -377,8 +359,6 @@ github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128/go.mod h1:JWz2ujO9X3oU5wb6
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E=
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU= github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU=
github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
@ -395,11 +375,9 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
@ -408,8 +386,6 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -493,11 +469,11 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/monperrus/crawler-user-agents v1.1.0 h1:Xy8ZrhizT+y2FONWFFdKOP+3BhH97BDLuG7W/MswoGI=
github.com/monperrus/crawler-user-agents v1.1.0/go.mod h1:GfRyKbsbxSrRxTPYnVi4U/0stQd6BcFCxDy6i6IxQ0M=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
@ -531,7 +507,6 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
@ -609,10 +584,7 @@ github.com/sacloud/packages-go v0.0.10 h1:UiQGjy8LretewkRhsuna1TBM9Vz/l9FoYpQx+D
github.com/sacloud/packages-go v0.0.10/go.mod h1:f8QITBh9z4IZc4yE9j21Q8b0sXEMwRlRmhhjWeDVTYs= github.com/sacloud/packages-go v0.0.10/go.mod h1:f8QITBh9z4IZc4yE9j21Q8b0sXEMwRlRmhhjWeDVTYs=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 h1:yoKAVkEVwAqbGbR8n87rHQ1dulL25rKloGadb3vm770= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 h1:yoKAVkEVwAqbGbR8n87rHQ1dulL25rKloGadb3vm770=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30/go.mod h1:sH0u6fq6x4R5M7WxkoQFY/o7UaiItec0o1LinLCJNq8= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30/go.mod h1:sH0u6fq6x4R5M7WxkoQFY/o7UaiItec0o1LinLCJNq8=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs= github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
@ -628,7 +600,6 @@ github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHei
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA=
github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
@ -661,7 +632,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -675,25 +645,6 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 h1:krc
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 h1:aEFtLD1ceyeljQXB1S2BjN0zjTkf0X3XmpuxFIiC29w= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 h1:aEFtLD1ceyeljQXB1S2BjN0zjTkf0X3XmpuxFIiC29w=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065/go.mod h1:HWvwy09hFSMXrj9SMvVRWV4U7rZO3l+WuogyNuxiT3M= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065/go.mod h1:HWvwy09hFSMXrj9SMvVRWV4U7rZO3l+WuogyNuxiT3M=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI=
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE=
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2KoJQD9cTQ6dyP2co9q4yzmT9FZo=
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao=
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE=
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@ -705,11 +656,6 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI= github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI=
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss= github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss=
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.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=
github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ=
github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
github.com/vultr/govultr/v3 v3.9.1 h1:uxSIb8Miel7tqTs3ee+z3t+JelZikwqBBsZzCOPBy/8= github.com/vultr/govultr/v3 v3.9.1 h1:uxSIb8Miel7tqTs3ee+z3t+JelZikwqBBsZzCOPBy/8=
@ -717,27 +663,12 @@ github.com/vultr/govultr/v3 v3.9.1/go.mod h1:Rd8ebpXm7jxH3MDmhnEs+zrlYW212ouhx+H
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c h1:Rnr+lDYXVkP+3eT8/d68iq4G/UeIhyCQk+HKa8toTvg= github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c h1:Rnr+lDYXVkP+3eT8/d68iq4G/UeIhyCQk+HKa8toTvg=
github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo=
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 h1:qmpz0Kvr9GAng8LAhRcKIpY71CEAcL3EBkftVlsP5Cw= github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 h1:qmpz0Kvr9GAng8LAhRcKIpY71CEAcL3EBkftVlsP5Cw=
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134/go.mod h1:KgZCJrxdhdw/sKhTQ/M3S9WOLri2PCnBlc4C3s+PfKY= github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134/go.mod h1:KgZCJrxdhdw/sKhTQ/M3S9WOLri2PCnBlc4C3s+PfKY=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@ -783,7 +714,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@ -861,7 +791,6 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -903,7 +832,6 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -919,7 +847,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1009,7 +936,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -1071,7 +997,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -3,9 +3,10 @@ package authelia
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "net"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"imuslab.com/zoraxy/mod/database" "imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/info/logger"
@ -93,25 +94,20 @@ func (ar *AutheliaRouter) HandleAutheliaAuth(w http.ResponseWriter, r *http.Requ
protocol = "https" protocol = "https"
} }
autheliaBaseURL := protocol + "://" + ar.options.AutheliaURL autheliaURL := &url.URL{
//Remove tailing slash if any Scheme: protocol,
if autheliaBaseURL[len(autheliaBaseURL)-1] == '/' { Host: ar.options.AutheliaURL,
autheliaBaseURL = autheliaBaseURL[:len(autheliaBaseURL)-1]
} }
//Make a request to Authelia to verify the request //Make a request to Authelia to verify the request
req, err := http.NewRequest("POST", autheliaBaseURL+"/api/verify", nil) req, err := http.NewRequest("POST", autheliaURL.JoinPath("api", "verify").String(), nil)
if err != nil { if err != nil {
ar.options.Logger.PrintAndLog("Authelia", "Unable to create request", err) ar.options.Logger.PrintAndLog("Authelia", "Unable to create request", err)
w.WriteHeader(401) w.WriteHeader(401)
return errors.New("unauthorized") return errors.New("unauthorized")
} }
scheme := "http" originalURL := rOriginalHeaders(r, req)
if r.TLS != nil {
scheme = "https"
}
req.Header.Add("X-Original-URL", fmt.Sprintf("%s://%s", scheme, r.Host))
// Copy cookies from the incoming request // Copy cookies from the incoming request
for _, cookie := range r.Cookies() { for _, cookie := range r.Cookies() {
@ -127,10 +123,42 @@ func (ar *AutheliaRouter) HandleAutheliaAuth(w http.ResponseWriter, r *http.Requ
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
redirectURL := autheliaBaseURL + "/?rd=" + url.QueryEscape(scheme+"://"+r.Host+r.URL.String()) + "&rm=" + r.Method redirectURL := autheliaURL.JoinPath()
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
query := redirectURL.Query()
query.Set("rd", originalURL.String())
query.Set("rm", r.Method)
http.Redirect(w, r, redirectURL.String(), http.StatusSeeOther)
return errors.New("unauthorized") return errors.New("unauthorized")
} }
return nil return nil
} }
func rOriginalHeaders(r, req *http.Request) *url.URL {
if r.RemoteAddr != "" {
before, _, _ := strings.Cut(r.RemoteAddr, ":")
if ip := net.ParseIP(before); ip != nil {
req.Header.Set("X-Forwarded-For", ip.String())
}
}
originalURL := &url.URL{
Scheme: "http",
Host: r.Host,
Path: r.URL.Path,
RawPath: r.URL.RawPath,
}
if r.TLS != nil {
originalURL.Scheme = "https"
}
req.Header.Add("X-Forwarded-Method", r.Method)
req.Header.Add("X-Original-URL", originalURL.String())
return originalURL
}

View File

@ -0,0 +1,169 @@
package authentik
import (
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"strings"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils"
)
type AuthentikRouterOptions struct {
UseHTTPS bool //If the Authentik server is using HTTPS
AuthentikURL string //The URL of the Authentik server
Logger *logger.Logger
Database *database.Database
}
type AuthentikRouter struct {
options *AuthentikRouterOptions
}
// NewAuthentikRouter creates a new AuthentikRouter object
func NewAuthentikRouter(options *AuthentikRouterOptions) *AuthentikRouter {
options.Database.NewTable("authentik")
//Read settings from database, if exists
options.Database.Read("authentik", "authentikURL", &options.AuthentikURL)
options.Database.Read("authentik", "useHTTPS", &options.UseHTTPS)
return &AuthentikRouter{
options: options,
}
}
// HandleSetAuthentikURLAndHTTPS is the internal handler for setting the Authentik URL and HTTPS
func (ar *AuthentikRouter) HandleSetAuthentikURLAndHTTPS(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
//Return the current settings
js, _ := json.Marshal(map[string]interface{}{
"useHTTPS": ar.options.UseHTTPS,
"authentikURL": ar.options.AuthentikURL,
})
utils.SendJSONResponse(w, string(js))
return
} else if r.Method == http.MethodPost {
//Update the settings
AuthentikURL, err := utils.PostPara(r, "authentikURL")
if err != nil {
utils.SendErrorResponse(w, "authentikURL not found")
return
}
useHTTPS, err := utils.PostBool(r, "useHTTPS")
if err != nil {
useHTTPS = false
}
//Write changes to runtime
ar.options.AuthentikURL = AuthentikURL
ar.options.UseHTTPS = useHTTPS
//Write changes to database
ar.options.Database.Write("authentik", "authentikURL", AuthentikURL)
ar.options.Database.Write("authentik", "useHTTPS", useHTTPS)
utils.SendOK(w)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
}
// HandleAuthentikAuth is the internal handler for Authentik authentication
// Set useHTTPS to true if your Authentik server is using HTTPS
// Set AuthentikURL to the URL of the Authentik server, e.g. Authentik.example.com
func (ar *AuthentikRouter) HandleAuthentikAuth(w http.ResponseWriter, r *http.Request) error {
const outpostPrefix = "outpost.goauthentik.io"
client := &http.Client{}
if ar.options.AuthentikURL == "" {
ar.options.Logger.PrintAndLog("Authentik", "Authentik URL not set", nil)
w.WriteHeader(500)
w.Write([]byte("500 - Internal Server Error"))
return errors.New("authentik URL not set")
}
protocol := "http"
if ar.options.UseHTTPS {
protocol = "https"
}
authentikBaseURL := protocol + "://" + ar.options.AuthentikURL
//Remove tailing slash if any
authentikBaseURL = strings.TrimSuffix(authentikBaseURL, "/")
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
reqUrl := scheme + "://" + r.Host + r.RequestURI
// Pass request to outpost if path matches outpost prefix
if reqPath := strings.TrimPrefix(r.URL.Path, "/"); strings.HasPrefix(reqPath, outpostPrefix) {
req, err := http.NewRequest(r.Method, authentikBaseURL+r.RequestURI, r.Body)
if err != nil {
ar.options.Logger.PrintAndLog("Authentik", "Unable to create request", err)
w.WriteHeader(401)
return errors.New("unauthorized")
}
req.Header.Set("X-Original-URL", reqUrl)
req.Header.Set("Host", r.Host)
for _, cookie := range r.Cookies() {
req.AddCookie(cookie)
}
if resp, err := client.Do(req); err != nil {
ar.options.Logger.PrintAndLog("Authentik", "Unable to pass request to Authentik outpost", err)
w.WriteHeader(http.StatusInternalServerError)
return errors.New("internal server error")
} else {
defer resp.Body.Close()
for k := range resp.Header {
w.Header().Set(k, resp.Header.Get(k))
}
w.WriteHeader(resp.StatusCode)
if _, err = io.Copy(w, resp.Body); err != nil {
ar.options.Logger.PrintAndLog("Authentik", "Unable to pass Authentik outpost response to client", err)
w.WriteHeader(http.StatusInternalServerError)
return errors.New("internal server error")
}
}
return nil
}
//Make a request to Authentik to verify the request
req, err := http.NewRequest(http.MethodGet, authentikBaseURL+"/"+outpostPrefix+"/auth/nginx", nil)
if err != nil {
ar.options.Logger.PrintAndLog("Authentik", "Unable to create request", err)
w.WriteHeader(401)
return errors.New("unauthorized")
}
req.Header.Set("X-Original-URL", reqUrl)
// Copy cookies from the incoming request
for _, cookie := range r.Cookies() {
req.AddCookie(cookie)
}
// Making the verification request
resp, err := client.Do(req)
if err != nil {
ar.options.Logger.PrintAndLog("Authentik", "Unable to verify", err)
w.WriteHeader(401)
return errors.New("unauthorized")
}
if resp.StatusCode != 200 {
redirectURL := authentikBaseURL + "/" + outpostPrefix + "/start?rd=" + url.QueryEscape(scheme+"://"+r.Host+r.URL.String())
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
return errors.New("unauthorized")
}
return nil
}

View File

@ -48,7 +48,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//Check if this is a redirection url //Check if this is a redirection url
if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) { if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r) statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
h.Parent.logRequest(r, statusCode != 500, statusCode, "redirect", "") h.Parent.logRequest(r, statusCode != 500, statusCode, "redirect", r.Host, "")
return return
} }
@ -79,7 +79,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if sep.RequireRateLimit { if sep.RequireRateLimit {
err := h.handleRateLimitRouting(w, r, sep) err := h.handleRateLimitRouting(w, r, sep)
if err != nil { if err != nil {
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 307) h.Parent.Option.Logger.LogHTTPRequest(r, "host", 307, r.Host, "")
return return
} }
} }
@ -110,7 +110,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled { if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
//Missing tailing slash. Redirect to target proxy endpoint //Missing tailing slash. Redirect to target proxy endpoint
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect) http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
h.Parent.Option.Logger.LogHTTPRequest(r, "redirect", 307) h.Parent.Option.Logger.LogHTTPRequest(r, "redirect", 307, r.Host, "")
return return
} }
} }
@ -186,6 +186,9 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
} }
} }
//Do not log default site requests to avoid flooding the logs
//h.Parent.logRequest(r, false, 307, "root", domainOnly, "")
//No vdir match. Route via root router //No vdir match. Route via root router
h.hostRequest(w, r, h.Parent.Root) h.hostRequest(w, r, h.Parent.Root)
case DefaultSite_Redirect: case DefaultSite_Redirect:
@ -208,19 +211,19 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
} }
hostname := parsedURL.Hostname() hostname := parsedURL.Hostname()
if hostname == domainOnly { if hostname == domainOnly {
h.Parent.logRequest(r, false, 500, "root-redirect", domainOnly) h.Parent.logRequest(r, false, 500, "root-redirect", domainOnly, "")
http.Error(w, "Loopback redirects due to invalid settings", 500) http.Error(w, "Loopback redirects due to invalid settings", 500)
return return
} }
h.Parent.logRequest(r, false, 307, "root-redirect", domainOnly) h.Parent.logRequest(r, false, 307, "root-redirect", domainOnly, "")
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect) http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
case DefaultSite_NotFoundPage: case DefaultSite_NotFoundPage:
//Serve the not found page, use template if exists //Serve the not found page, use template if exists
h.serve404PageWithTemplate(w, r) h.serve404PageWithTemplate(w, r)
case DefaultSite_NoResponse: case DefaultSite_NoResponse:
//No response. Just close the connection //No response. Just close the connection
h.Parent.logRequest(r, false, 444, "root-no_resp", domainOnly) h.Parent.logRequest(r, false, 444, "root-no_resp", domainOnly, "")
hijacker, ok := w.(http.Hijacker) hijacker, ok := w.(http.Hijacker)
if !ok { if !ok {
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
@ -234,11 +237,11 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
conn.Close() conn.Close()
case DefaultSite_TeaPot: case DefaultSite_TeaPot:
//I'm a teapot //I'm a teapot
h.Parent.logRequest(r, false, 418, "root-teapot", domainOnly) h.Parent.logRequest(r, false, 418, "root-teapot", domainOnly, "")
http.Error(w, "I'm a teapot", http.StatusTeapot) http.Error(w, "I'm a teapot", http.StatusTeapot)
default: default:
//Unknown routing option. Send empty response //Unknown routing option. Send empty response
h.Parent.logRequest(r, false, 544, "root-unknown", domainOnly) h.Parent.logRequest(r, false, 544, "root-unknown", domainOnly, "")
http.Error(w, "544 - No Route Defined", 544) http.Error(w, "544 - No Route Defined", 544)
} }
} }

View File

@ -23,7 +23,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter,
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r) isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
if isBlocked { if isBlocked {
h.Parent.logRequest(r, false, 403, blockedReason, "") h.Parent.logRequest(r, false, 403, blockedReason, r.Host, "")
} }
return isBlocked return isBlocked
} }

View File

@ -31,16 +31,23 @@ and return a boolean indicate if the request is written to http.ResponseWriter
- false: the request is not handled (usually means auth ok), continue to the next handler - false: the request is not handled (usually means auth ok), continue to the next handler
*/ */
func handleAuthProviderRouting(sep *ProxyEndpoint, w http.ResponseWriter, r *http.Request, h *ProxyHandler) bool { func handleAuthProviderRouting(sep *ProxyEndpoint, w http.ResponseWriter, r *http.Request, h *ProxyHandler) bool {
requestHostname := r.Host
if sep.AuthenticationProvider.AuthMethod == AuthMethodBasic { if sep.AuthenticationProvider.AuthMethod == AuthMethodBasic {
err := h.handleBasicAuthRouting(w, r, sep) err := h.handleBasicAuthRouting(w, r, sep)
if err != nil { if err != nil {
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401) h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "")
return true return true
} }
} else if sep.AuthenticationProvider.AuthMethod == AuthMethodAuthelia { } else if sep.AuthenticationProvider.AuthMethod == AuthMethodAuthelia {
err := h.handleAutheliaAuth(w, r) err := h.handleAutheliaAuth(w, r)
if err != nil { if err != nil {
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401) h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "")
return true
}
} else if sep.AuthenticationProvider.AuthMethod == AuthMethodAuthentik {
err := h.handleAuthentikAuth(w, r)
if err != nil {
h.Parent.Option.Logger.LogHTTPRequest(r, "host-http", 401, requestHostname, "")
return true return true
} }
} }
@ -51,11 +58,8 @@ func handleAuthProviderRouting(sep *ProxyEndpoint, w http.ResponseWriter, r *htt
/* Basic Auth */ /* Basic Auth */
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error { func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
err := handleBasicAuth(w, r, pe) //Wrapper for oop style
if err != nil { return handleBasicAuth(w, r, pe)
h.Parent.logRequest(r, false, 401, "host", r.URL.Hostname())
}
return err
} }
// Handle basic auth logic // Handle basic auth logic
@ -75,6 +79,7 @@ func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint)
if !ok { if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
w.WriteHeader(401) w.WriteHeader(401)
w.Write([]byte("401 - Unauthorized"))
return errors.New("unauthorized") return errors.New("unauthorized")
} }
@ -94,6 +99,7 @@ func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint)
if !matchingFound { if !matchingFound {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
w.WriteHeader(401) w.WriteHeader(401)
w.Write([]byte("401 - Unauthorized"))
return errors.New("unauthorized") return errors.New("unauthorized")
} }
@ -106,3 +112,7 @@ func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint)
func (h *ProxyHandler) handleAutheliaAuth(w http.ResponseWriter, r *http.Request) error { func (h *ProxyHandler) handleAutheliaAuth(w http.ResponseWriter, r *http.Request) error {
return h.Parent.Option.AutheliaRouter.HandleAutheliaAuth(w, r) return h.Parent.Option.AutheliaRouter.HandleAutheliaAuth(w, r)
} }
func (h *ProxyHandler) handleAuthentikAuth(w http.ResponseWriter, r *http.Request) error {
return h.Parent.Option.AuthentikRouter.HandleAuthentikAuth(w, r)
}

View File

@ -105,7 +105,6 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp
thisTransporter := http.DefaultTransport thisTransporter := http.DefaultTransport
//Hack the default transporter to handle more connections //Hack the default transporter to handle more connections
optimalConcurrentConnection := 256 optimalConcurrentConnection := 256
if dpcOptions.MaxConcurrentConnection > 0 { if dpcOptions.MaxConcurrentConnection > 0 {
optimalConcurrentConnection = dpcOptions.MaxConcurrentConnection optimalConcurrentConnection = dpcOptions.MaxConcurrentConnection
@ -137,18 +136,6 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp
} }
} }
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
func joinURLPath(a, b *url.URL) (path, rawpath string) { func joinURLPath(a, b *url.URL) (path, rawpath string) {
apath, bpath := a.EscapedPath(), b.EscapedPath() apath, bpath := a.EscapedPath(), b.EscapedPath()
aslash, bslash := strings.HasSuffix(apath, "/"), strings.HasPrefix(bpath, "/") aslash, bslash := strings.HasSuffix(apath, "/"), strings.HasPrefix(bpath, "/")
@ -352,7 +339,6 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
} }
} else if strings.HasPrefix(originLocation, "/") && rrr.PathPrefix != "" { } else if strings.HasPrefix(originLocation, "/") && rrr.PathPrefix != "" {
//Back to the root of this proxy object //Back to the root of this proxy object
//fmt.Println(rrr.ProxyDomain, rrr.OriginalHost)
locationRewrite = strings.TrimSuffix(rrr.PathPrefix, "/") + originLocation locationRewrite = strings.TrimSuffix(rrr.PathPrefix, "/") + originLocation
} else { } else {
//Relative path. Do not modifiy location header //Relative path. Do not modifiy location header

View File

@ -36,6 +36,24 @@ func replaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS b
//Do not modify location header //Do not modify location header
return urlString, nil return urlString, nil
} }
//Issue #626: Check if the location header is another subdomain with port
//E.g. Proxy config: blog.example.com -> 127.0.0.1:80
//Check if it is actually redirecting to (*.)blog.example.com:8080 instead of current domain
//like Location: http://x.blog.example.com:1234/
_, newLocationPort, err := net.SplitHostPort(u.Host)
if (newLocationPort == "80" || newLocationPort == "443") && err == nil {
//Port 80 or 443, some web server use this to switch between http and https
//E.g. http://example.com:80 -> https://example.com:443
//E.g. http://example.com:443 -> https://example.com:80
//That usually means the user have invalidly configured the web server to use port 80 or 443
//for http or https. We should not modify the location header in this case.
} else if strings.Contains(u.Host, ":") && err == nil {
//Other port numbers. Do not modify location header
return urlString, nil
}
u.Host = rrr.OriginalHost u.Host = rrr.OriginalHost
if strings.Contains(rrr.ProxyDomain, "/") { if strings.Contains(rrr.ProxyDomain, "/") {

View File

@ -155,7 +155,7 @@ func (router *Router) StartProxyService() error {
if err != nil { if err != nil {
http.ServeFile(w, r, "./web/hosterror.html") http.ServeFile(w, r, "./web/hosterror.html")
router.Option.Logger.PrintAndLog("dprouter", "failed to get upstream for hostname", err) router.Option.Logger.PrintAndLog("dprouter", "failed to get upstream for hostname", err)
router.logRequest(r, false, 404, "vdir-http", r.Host) router.logRequest(r, false, 404, "vdir-http", r.Host, "")
} }
endpointProxyRewriteRules := GetDefaultHeaderRewriteRules() endpointProxyRewriteRules := GetDefaultHeaderRewriteRules()

View File

@ -0,0 +1,108 @@
package exploits
/*
exploits.go
This file is used to define routing rules that blocks common exploits.
*/
import (
_ "embed"
"net/http"
"regexp"
agents "github.com/monperrus/crawler-user-agents"
)
type Detector struct {
}
func NewExploitDetector() *Detector {
return &Detector{}
}
// RequestContainCommonExploits checks if the request contains common exploits
// such as SQL injection, file injection, and other common attack patterns.
func (d *Detector) RequestContainCommonExploits(r *http.Request) bool {
query := r.URL.RawQuery
userAgent := r.UserAgent()
// Block SQL injections
sqlInjectionPatterns := []string{
`union.*select.*\(`,
`union.*all.*select.*`,
`concat.*\(`,
}
for _, pattern := range sqlInjectionPatterns {
if match, _ := regexp.MatchString(pattern, query); match {
return true
}
}
// Block file injections
fileInjectionPatterns := []string{
`[a-zA-Z0-9_]=http://`,
`[a-zA-Z0-9_]=(\.\.//?)+`,
`[a-zA-Z0-9_]=/([a-z0-9_.]//?)+`,
}
for _, pattern := range fileInjectionPatterns {
if match, _ := regexp.MatchString(pattern, query); match {
return true
}
}
// Block common exploits
commonExploitPatterns := []string{
`(<|%3C).*script.*(>|%3E)`,
`GLOBALS(=|\[|\%[0-9A-Z]{0,2})`,
`_REQUEST(=|\[|\%[0-9A-Z]{0,2})`,
`proc/self/environ`,
`mosConfig_[a-zA-Z_]{1,21}(=|\%3D)`,
`base64_(en|de)code\(.*\)`,
}
for _, pattern := range commonExploitPatterns {
if match, _ := regexp.MatchString(pattern, query); match {
return true
}
}
// Block spam
spamPatterns := []string{
`\b(ultram|unicauca|valium|viagra|vicodin|xanax|ypxaieo)\b`,
`\b(erections|hoodia|huronriveracres|impotence|levitra|libido)\b`,
`\b(ambien|blue\spill|cialis|cocaine|ejaculation|erectile)\b`,
`\b(lipitor|phentermin|pro[sz]ac|sandyauer|tramadol|troyhamby)\b`,
}
for _, pattern := range spamPatterns {
if match, _ := regexp.MatchString(pattern, query); match {
return true
}
}
// Block user agents
userAgentPatterns := []string{
`Indy Library`,
`libwww-perl`,
`GetRight`,
`GetWeb!`,
`Go!Zilla`,
`Download Demon`,
`Go-Ahead-Got-It`,
`TurnitinBot`,
`GrabNet`,
}
for _, pattern := range userAgentPatterns {
if match, _ := regexp.MatchString(pattern, userAgent); match {
return true
}
}
return false
}
// RequestIsMadeByBots checks if the request is made by bots or crawlers
func (d *Detector) RequestIsMadeByBots(r *http.Request) bool {
userAgent := r.UserAgent()
return agents.IsCrawler(userAgent)
}

View File

@ -116,13 +116,13 @@ func (router *Router) rewriteURL(rooturl string, requestURL string) string {
func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) { func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
r.Header.Set("X-Forwarded-Host", r.Host) r.Header.Set("X-Forwarded-Host", r.Host)
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID) r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
reqHostname := r.Host
/* Load balancing */ /* Load balancing */
selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession) selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession)
if err != nil { if err != nil {
http.ServeFile(w, r, "./web/rperror.html") http.ServeFile(w, r, "./web/rperror.html")
h.Parent.Option.Logger.PrintAndLog("proxy", "Failed to assign an upstream for this request", err) h.Parent.Option.Logger.PrintAndLog("proxy", "Failed to assign an upstream for this request", err)
h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname()) h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname(), r.Host)
return return
} }
@ -144,7 +144,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
if selectedUpstream.RequireTLS { if selectedUpstream.RequireTLS {
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL) u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
} }
h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain) h.Parent.logRequest(r, true, 101, "host-websocket", reqHostname, selectedUpstream.OriginIpOrDomain)
if target.HeaderRewriteRules == nil { if target.HeaderRewriteRules == nil {
target.HeaderRewriteRules = GetDefaultHeaderRewriteRules() target.HeaderRewriteRules = GetDefaultHeaderRewriteRules()
@ -161,12 +161,11 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
return return
} }
originalHostHeader := r.Host
if r.URL != nil { if r.URL != nil {
r.Host = r.URL.Host r.Host = r.URL.Host
} else { } else {
//Fallback when the upstream proxy screw something up in the header //Fallback when the upstream proxy screw something up in the header
r.URL, _ = url.Parse(originalHostHeader) r.URL, _ = url.Parse(reqHostname)
} }
//Populate the user-defined headers with the values from the request //Populate the user-defined headers with the values from the request
@ -188,7 +187,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
//Handle the request reverse proxy //Handle the request reverse proxy
statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: selectedUpstream.OriginIpOrDomain, ProxyDomain: selectedUpstream.OriginIpOrDomain,
OriginalHost: originalHostHeader, OriginalHost: reqHostname,
UseTLS: selectedUpstream.RequireTLS, UseTLS: selectedUpstream.RequireTLS,
NoCache: h.Parent.Option.NoCache, NoCache: h.Parent.Option.NoCache,
PathPrefix: "", PathPrefix: "",
@ -201,28 +200,28 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
//validate the error //validate the error
var dnsError *net.DNSError var dnsError *net.DNSError
upstreamHostname := selectedUpstream.OriginIpOrDomain
if err != nil { if err != nil {
if errors.As(err, &dnsError) { if errors.As(err, &dnsError) {
http.ServeFile(w, r, "./web/hosterror.html") http.ServeFile(w, r, "./web/hosterror.html")
h.Parent.logRequest(r, false, 404, "host-http", r.URL.Hostname()) h.Parent.logRequest(r, false, 404, "host-http", reqHostname, upstreamHostname)
} else if errors.Is(err, context.Canceled) { } else if errors.Is(err, context.Canceled) {
//Request canceled by client, usually due to manual refresh before page load //Request canceled by client, usually due to manual refresh before page load
http.Error(w, "Request canceled", http.StatusRequestTimeout) http.Error(w, "Request canceled", http.StatusRequestTimeout)
h.Parent.logRequest(r, false, http.StatusRequestTimeout, "host-http", r.URL.Hostname()) h.Parent.logRequest(r, false, http.StatusRequestTimeout, "host-http", reqHostname, upstreamHostname)
} else { } else {
http.ServeFile(w, r, "./web/rperror.html") http.ServeFile(w, r, "./web/rperror.html")
h.Parent.logRequest(r, false, 521, "host-http", r.URL.Hostname()) h.Parent.logRequest(r, false, 521, "host-http", reqHostname, upstreamHostname)
} }
} }
h.Parent.logRequest(r, true, statusCode, "host-http", r.URL.Hostname()) h.Parent.logRequest(r, true, statusCode, "host-http", reqHostname, upstreamHostname)
} }
// Handle vdir type request // Handle vdir type request
func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, target *VirtualDirectoryEndpoint) { func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, target *VirtualDirectoryEndpoint) {
rewriteURL := h.Parent.rewriteURL(target.MatchingPath, r.RequestURI) rewriteURL := h.Parent.rewriteURL(target.MatchingPath, r.RequestURI)
r.URL, _ = url.Parse(rewriteURL) r.URL, _ = url.Parse(rewriteURL)
r.Header.Set("X-Forwarded-Host", r.Host) r.Header.Set("X-Forwarded-Host", r.Host)
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID) r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
@ -242,7 +241,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
target.parent.HeaderRewriteRules = GetDefaultHeaderRewriteRules() target.parent.HeaderRewriteRules = GetDefaultHeaderRewriteRules()
} }
h.Parent.logRequest(r, true, 101, "vdir-websocket", target.Domain) h.Parent.logRequest(r, true, 101, "vdir-websocket", r.Host, target.Domain)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{ wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: target.SkipCertValidations, SkipTLSValidation: target.SkipCertValidations,
SkipOriginCheck: target.parent.EnableWebsocketCustomHeaders, //You should not use websocket via virtual directory. But keep this to true for compatibility SkipOriginCheck: target.parent.EnableWebsocketCustomHeaders, //You should not use websocket via virtual directory. But keep this to true for compatibility
@ -254,12 +253,12 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
return return
} }
originalHostHeader := r.Host reqHostname := r.Host
if r.URL != nil { if r.URL != nil {
r.Host = r.URL.Host r.Host = r.URL.Host
} else { } else {
//Fallback when the upstream proxy screw something up in the header //Fallback when the upstream proxy screw something up in the header
r.URL, _ = url.Parse(originalHostHeader) r.URL, _ = url.Parse(reqHostname)
} }
//Populate the user-defined headers with the values from the request //Populate the user-defined headers with the values from the request
@ -282,7 +281,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
//Handle the virtual directory reverse proxy request //Handle the virtual directory reverse proxy request
statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: target.Domain, ProxyDomain: target.Domain,
OriginalHost: originalHostHeader, OriginalHost: reqHostname,
UseTLS: target.RequireTLS, UseTLS: target.RequireTLS,
PathPrefix: target.MatchingPath, PathPrefix: target.MatchingPath,
UpstreamHeaders: upstreamHeaders, UpstreamHeaders: upstreamHeaders,
@ -296,19 +295,19 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
if errors.As(err, &dnsError) { if errors.As(err, &dnsError) {
http.ServeFile(w, r, "./web/hosterror.html") http.ServeFile(w, r, "./web/hosterror.html")
log.Println(err.Error()) log.Println(err.Error())
h.Parent.logRequest(r, false, 404, "vdir-http", target.Domain) h.Parent.logRequest(r, false, 404, "vdir-http", reqHostname, target.Domain)
} else { } else {
http.ServeFile(w, r, "./web/rperror.html") http.ServeFile(w, r, "./web/rperror.html")
log.Println(err.Error()) log.Println(err.Error())
h.Parent.logRequest(r, false, 521, "vdir-http", target.Domain) h.Parent.logRequest(r, false, 521, "vdir-http", reqHostname, target.Domain)
} }
} }
h.Parent.logRequest(r, true, statusCode, "vdir-http", target.Domain) h.Parent.logRequest(r, true, statusCode, "vdir-http", reqHostname, target.Domain)
} }
// This logger collect data for the statistical analysis. For log to file logger, check the Logger and LogHTTPRequest handler // This logger collect data for the statistical analysis. For log to file logger, check the Logger and LogHTTPRequest handler
func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) { func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, originalHostname string, upstreamHostname string) {
if router.Option.StatisticCollector != nil { if router.Option.StatisticCollector != nil {
go func() { go func() {
requestInfo := statistic.RequestInfo{ requestInfo := statistic.RequestInfo{
@ -320,10 +319,11 @@ func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, for
Referer: r.Referer(), Referer: r.Referer(),
UserAgent: r.UserAgent(), UserAgent: r.UserAgent(),
RequestURL: r.Host + r.RequestURI, RequestURL: r.Host + r.RequestURI,
Target: target, Target: originalHostname,
Upstream: upstreamHostname,
} }
router.Option.StatisticCollector.RecordRequest(requestInfo) router.Option.StatisticCollector.RecordRequest(requestInfo)
}() }()
} }
router.Option.Logger.LogHTTPRequest(r, forwardType, statusCode) router.Option.Logger.LogHTTPRequest(r, forwardType, statusCode, originalHostname, upstreamHostname)
} }

View File

@ -51,7 +51,7 @@ func (t *RequestCountPerIpTable) Clear() {
func (h *ProxyHandler) handleRateLimitRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error { func (h *ProxyHandler) handleRateLimitRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
err := h.Parent.handleRateLimit(w, r, pe) err := h.Parent.handleRateLimit(w, r, pe)
if err != nil { if err != nil {
h.Parent.logRequest(r, false, 429, "ratelimit", r.URL.Hostname()) h.Parent.logRequest(r, false, 429, "ratelimit", r.URL.Hostname(), "")
} }
return err return err
} }

View File

@ -2,6 +2,7 @@ package rewrite
import ( import (
"fmt" "fmt"
"net"
"net/http" "net/http"
"strings" "strings"
) )
@ -14,6 +15,11 @@ func GetHeaderVariableValuesFromRequest(r *http.Request) map[string]string {
// Request-specific variables // Request-specific variables
vars["$host"] = r.Host vars["$host"] = r.Host
vars["$remote_addr"] = r.RemoteAddr vars["$remote_addr"] = r.RemoteAddr
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
remoteIP = r.RemoteAddr // Fallback to the full RemoteAddr if parsing fails
}
vars["$remote_ip"] = remoteIP
vars["$request_uri"] = r.RequestURI vars["$request_uri"] = r.RequestURI
vars["$request_method"] = r.Method vars["$request_method"] = r.Method
vars["$content_length"] = fmt.Sprintf("%d", r.ContentLength) vars["$content_length"] = fmt.Sprintf("%d", r.ContentLength)

View File

@ -9,6 +9,7 @@ package dynamicproxy
*/ */
import ( import (
_ "embed" _ "embed"
"imuslab.com/zoraxy/mod/auth/sso/authentik"
"net" "net"
"net/http" "net/http"
"sync" "sync"
@ -64,6 +65,7 @@ type RouterOption struct {
/* Authentication Providers */ /* Authentication Providers */
AutheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication AutheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication
AuthentikRouter *authentik.AuthentikRouter //Authentik router for Authentik authentication
/* Utilities */ /* Utilities */
Logger *logger.Logger //Logger for reverse proxy requets Logger *logger.Logger //Logger for reverse proxy requets
@ -143,6 +145,7 @@ const (
AuthMethodBasic //Basic Auth AuthMethodBasic //Basic Auth
AuthMethodAuthelia //Authelia AuthMethodAuthelia //Authelia
AuthMethodOauth2 //Oauth2 AuthMethodOauth2 //Oauth2
AuthMethodAuthentik
) )
type AuthenticationProvider struct { type AuthenticationProvider struct {

View File

@ -16,7 +16,7 @@ import (
// Log HTTP request. Note that this must run in go routine to prevent any blocking // Log HTTP request. Note that this must run in go routine to prevent any blocking
// in reverse proxy router // in reverse proxy router
func (l *Logger) LogHTTPRequest(r *http.Request, reqclass string, statusCode int) { func (l *Logger) LogHTTPRequest(r *http.Request, reqclass string, statusCode int, downstreamHostname string, upstreamHostname string) {
go func() { go func() {
l.ValidateAndUpdateLogFilepath() l.ValidateAndUpdateLogFilepath()
if l.logger == nil || l.file == nil { if l.logger == nil || l.file == nil {
@ -26,7 +26,9 @@ func (l *Logger) LogHTTPRequest(r *http.Request, reqclass string, statusCode int
clientIP := netutils.GetRequesterIP(r) clientIP := netutils.GetRequesterIP(r)
requestURI := r.RequestURI requestURI := r.RequestURI
statusCodeString := strconv.Itoa(statusCode) statusCodeString := strconv.Itoa(statusCode)
//fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [router:" + reqclass + "] [client " + clientIP + "] " + r.Method + " " + requestURI + " " + statusCodeString)
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [router:" + reqclass + "] [origin:" + r.URL.Hostname() + "] [client: " + clientIP + "] [useragent: " + r.UserAgent() + "] " + r.Method + " " + requestURI + " " + statusCodeString) //Pretty print for debugging
//fmt.Printf("------------\nRequest URL: %s (class: %s) \nUpstream Hostname: %s\nDownstream Hostname: %s\nStatus Code: %s\n", r.URL, reqclass, upstreamHostname, downstreamHostname, statusCodeString)
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [router:" + reqclass + "] [origin:" + downstreamHostname + "] [client: " + clientIP + "] [useragent: " + r.UserAgent() + "] " + r.Method + " " + requestURI + " " + statusCodeString)
}() }()
} }

View File

@ -87,6 +87,11 @@ func MatchIpWildcard(ipAddress, wildcard string) bool {
// Match ip address with CIDR // Match ip address with CIDR
func MatchIpCIDR(ip string, cidr string) bool { func MatchIpCIDR(ip string, cidr string) bool {
// Trim away scope ID if present in IP (e.g. fe80::1%eth0)
if i := strings.Index(ip, "%"); i != -1 {
ip = ip[:i]
}
// parse the CIDR string // parse the CIDR string
_, cidrnet, err := net.ParseCIDR(cidr) _, cidrnet, err := net.ParseCIDR(cidr)
if err != nil { if err != nil {

View File

@ -249,3 +249,5 @@ func (m *Manager) HandleDisablePlugin(w http.ResponseWriter, r *http.Request) {
utils.SendOK(w) utils.SendOK(w)
} }
/* Plugin Store */

View File

@ -274,13 +274,10 @@ func (m *Manager) StopPlugin(pluginID string) error {
} }
// Check if the plugin is still running // Check if the plugin is still running
func (m *Manager) PluginStillRunning(pluginID string) bool { func (m *Manager) PluginIsRunning(pluginID string) bool {
plugin, err := m.GetPluginByID(pluginID) plugin, err := m.GetPluginByID(pluginID)
if err != nil { if err != nil {
return false return false
} }
if plugin.process == nil { return plugin.IsRunning()
return false
}
return plugin.process.ProcessState == nil
} }

View File

@ -58,6 +58,59 @@ func NewPluginManager(options *ManagerOptions) *Manager {
} }
} }
// Reload all plugins from disk
func (m *Manager) ReloadPluginFromDisk() {
//Check each of the current plugins if the directory exists
//If not, remove the plugin from the loaded plugins list
m.loadedPluginsMutex.Lock()
for pluginID, plugin := range m.LoadedPlugins {
if !utils.FileExists(plugin.RootDir) {
m.Log("Plugin directory not found, removing plugin from runtime: "+pluginID, nil)
delete(m.LoadedPlugins, pluginID)
//Remove the plugin enable state from the database
m.Options.Database.Delete("plugins", pluginID)
}
}
m.loadedPluginsMutex.Unlock()
//Scan the plugin directory for new plugins
foldersInPluginDir, err := os.ReadDir(m.Options.PluginDir)
if err != nil {
m.Log("Failed to read plugin directory", err)
return
}
for _, folder := range foldersInPluginDir {
if folder.IsDir() {
pluginPath := filepath.Join(m.Options.PluginDir, folder.Name())
thisPlugin, err := m.LoadPluginSpec(pluginPath)
if err != nil {
m.Log("Failed to load plugin: "+filepath.Base(pluginPath), err)
continue
}
//Check if the plugin id is already loaded into the runtime
m.loadedPluginsMutex.RLock()
_, ok := m.LoadedPlugins[thisPlugin.Spec.ID]
m.loadedPluginsMutex.RUnlock()
if ok {
//Plugin already loaded, skip it
continue
}
thisPlugin.RootDir = filepath.ToSlash(pluginPath)
thisPlugin.staticRouteProxy = make(map[string]*dpcore.ReverseProxy)
m.loadedPluginsMutex.Lock()
m.LoadedPlugins[thisPlugin.Spec.ID] = thisPlugin
m.loadedPluginsMutex.Unlock()
m.Log("Added new plugin: "+thisPlugin.Spec.Name, nil)
// The default state of the plugin is disabled, so no need to start it
}
}
}
// LoadPluginsFromDisk loads all plugins from the plugin directory // LoadPluginsFromDisk loads all plugins from the plugin directory
func (m *Manager) LoadPluginsFromDisk() error { func (m *Manager) LoadPluginsFromDisk() error {
// Load all plugins from the plugin directory // Load all plugins from the plugin directory
@ -82,7 +135,7 @@ func (m *Manager) LoadPluginsFromDisk() error {
m.Log("Loaded plugin: "+thisPlugin.Spec.Name, nil) m.Log("Loaded plugin: "+thisPlugin.Spec.Name, nil)
// If the plugin was enabled, start it now // If the plugin was enabled, start it now
fmt.Println("Plugin enabled state", m.GetPluginPreviousEnableState(thisPlugin.Spec.ID)) //fmt.Println("Plugin enabled state", m.GetPluginPreviousEnableState(thisPlugin.Spec.ID))
if m.GetPluginPreviousEnableState(thisPlugin.Spec.ID) { if m.GetPluginPreviousEnableState(thisPlugin.Spec.ID) {
err = m.StartPlugin(thisPlugin.Spec.ID) err = m.StartPlugin(thisPlugin.Spec.ID)
if err != nil { if err != nil {
@ -258,3 +311,8 @@ func (p *Plugin) HandleStaticRoute(w http.ResponseWriter, r *http.Request, longe
}) })
} }
// IsRunning checks if the plugin is currently running
func (p *Plugin) IsRunning() bool {
return p.process != nil && p.process.Process != nil
}

356
src/mod/plugins/store.go Normal file
View File

@ -0,0 +1,356 @@
package plugins
import (
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"imuslab.com/zoraxy/mod/plugins/zoraxy_plugin"
"imuslab.com/zoraxy/mod/utils"
)
/*
Plugin Store
*/
// See https://github.com/aroz-online/zoraxy-official-plugins/blob/main/directories/index.json for the standard format
type Checksums struct {
LinuxAmd64 string `json:"linux_amd64"`
Linux386 string `json:"linux_386"`
LinuxArm string `json:"linux_arm"`
LinuxArm64 string `json:"linux_arm64"`
LinuxMipsle string `json:"linux_mipsle"`
LinuxRiscv64 string `json:"linux_riscv64"`
WindowsAmd64 string `json:"windows_amd64"`
}
type DownloadablePlugin struct {
IconPath string
PluginIntroSpect zoraxy_plugin.IntroSpect //Plugin introspect information
ChecksumsSHA256 Checksums //Checksums for the plugin binary
DownloadURLs map[string]string //Download URLs for different platforms
}
/* Plugin Store Index List Sync */
//Update the plugin list from the plugin store URLs
func (m *Manager) UpdateDownloadablePluginList() error {
//Get downloadable plugins from each of the plugin store URLS
m.Options.DownloadablePluginCache = []*DownloadablePlugin{}
for _, url := range m.Options.PluginStoreURLs {
pluginList, err := m.getPluginListFromURL(url)
if err != nil {
return fmt.Errorf("failed to get plugin list from %s: %w", url, err)
}
m.Options.DownloadablePluginCache = append(m.Options.DownloadablePluginCache, pluginList...)
}
m.Options.LastSuccPluginSyncTime = time.Now().Unix()
return nil
}
// Get the plugin list from the URL
func (m *Manager) getPluginListFromURL(url string) ([]*DownloadablePlugin, error) {
//Get the plugin list from the URL
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get plugin list from %s: %s", url, resp.Status)
}
var pluginList []*DownloadablePlugin
content, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read plugin list from %s: %w", url, err)
}
content = []byte(strings.TrimSpace(string(content)))
err = json.Unmarshal(content, &pluginList)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal plugin list from %s: %w", url, err)
}
return pluginList, nil
}
func (m *Manager) ListDownloadablePlugins() []*DownloadablePlugin {
//List all downloadable plugins
if len(m.Options.DownloadablePluginCache) == 0 {
return []*DownloadablePlugin{}
}
return m.Options.DownloadablePluginCache
}
// InstallPlugin installs the given plugin by moving it to the PluginDir.
func (m *Manager) InstallPlugin(plugin *DownloadablePlugin) error {
pluginDir := filepath.Join(m.Options.PluginDir, plugin.PluginIntroSpect.Name)
pluginFile := plugin.PluginIntroSpect.Name
if runtime.GOOS == "windows" {
pluginFile += ".exe"
}
//Check if the plugin id already exists in runtime plugin map
if _, ok := m.LoadedPlugins[plugin.PluginIntroSpect.ID]; ok {
return fmt.Errorf("plugin already installed: %s", plugin.PluginIntroSpect.ID)
}
// Create the plugin directory if it doesn't exist
err := os.MkdirAll(pluginDir, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to create plugin directory: %w", err)
}
// Download the plugin binary
downloadURL, ok := plugin.DownloadURLs[runtime.GOOS+"_"+runtime.GOARCH]
if !ok {
return fmt.Errorf("no download URL available for the current platform")
}
resp, err := http.Get(downloadURL)
if err != nil {
return fmt.Errorf("failed to download plugin: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to download plugin: %s", resp.Status)
}
// Write the plugin binary to the plugin directory
pluginPath := filepath.Join(pluginDir, pluginFile)
out, err := os.Create(pluginPath)
if err != nil {
return fmt.Errorf("failed to create plugin file: %w", err)
}
_, err = io.Copy(out, resp.Body)
if err != nil {
out.Close()
return fmt.Errorf("failed to write plugin file: %w", err)
}
// Make the plugin executable
err = os.Chmod(pluginPath, 0755)
if err != nil {
out.Close()
return fmt.Errorf("failed to set executable permissions: %w", err)
}
// Verify the checksum of the downloaded plugin binary
checksums, err := plugin.ChecksumsSHA256.GetCurrentPlatformChecksum()
if err == nil {
if !verifyChecksumForFile(pluginPath, checksums) {
out.Close()
return fmt.Errorf("checksum verification failed for plugin binary")
}
}
//Ok, also download the icon if exists
if plugin.IconPath != "" {
iconURL := strings.TrimSpace(plugin.IconPath)
if iconURL != "" {
resp, err := http.Get(iconURL)
if err != nil {
return fmt.Errorf("failed to download plugin icon: %w", err)
}
defer resp.Body.Close()
//Save the icon to the plugin directory
iconPath := filepath.Join(pluginDir, "icon.png")
out, err := os.Create(iconPath)
if err != nil {
return fmt.Errorf("failed to create plugin icon file: %w", err)
}
defer out.Close()
io.Copy(out, resp.Body)
}
}
//Close the plugin exeutable
out.Close()
//Reload the plugin list
m.ReloadPluginFromDisk()
return nil
}
// UninstallPlugin uninstalls the plugin by removing its directory.
func (m *Manager) UninstallPlugin(pluginID string) error {
//Stop the plugin process if it's running
plugin, ok := m.LoadedPlugins[pluginID]
if !ok {
return fmt.Errorf("plugin not found: %s", pluginID)
}
if plugin.IsRunning() {
err := m.StopPlugin(plugin.Spec.ID)
if err != nil {
return fmt.Errorf("failed to stop plugin: %w", err)
}
}
//Make sure the plugin process is stopped
m.Options.Logger.PrintAndLog("plugin-manager", "Removing plugin in 3 seconds...", nil)
time.Sleep(3 * time.Second)
// Remove the plugin directory
err := os.RemoveAll(plugin.RootDir)
if err != nil {
return fmt.Errorf("failed to remove plugin directory: %w", err)
}
//Reload the plugin list
m.ReloadPluginFromDisk()
return nil
}
// GetCurrentPlatformChecksum returns the checksum for the current platform
func (c *Checksums) GetCurrentPlatformChecksum() (string, error) {
switch runtime.GOOS {
case "linux":
switch runtime.GOARCH {
case "amd64":
return c.LinuxAmd64, nil
case "386":
return c.Linux386, nil
case "arm":
return c.LinuxArm, nil
case "arm64":
return c.LinuxArm64, nil
case "mipsle":
return c.LinuxMipsle, nil
case "riscv64":
return c.LinuxRiscv64, nil
default:
return "", fmt.Errorf("unsupported architecture: %s", runtime.GOARCH)
}
case "windows":
switch runtime.GOARCH {
case "amd64":
return c.WindowsAmd64, nil
default:
return "", fmt.Errorf("unsupported architecture: %s", runtime.GOARCH)
}
default:
return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
}
}
// VerifyChecksum verifies the checksum of the downloaded plugin binary.
func verifyChecksumForFile(filePath string, checksum string) bool {
file, err := os.Open(filePath)
if err != nil {
return false
}
defer file.Close()
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return false
}
calculatedChecksum := fmt.Sprintf("%x", hash.Sum(nil))
return calculatedChecksum == checksum
}
/*
Handlers for Plugin Store
*/
func (m *Manager) HandleListDownloadablePlugins(w http.ResponseWriter, r *http.Request) {
//List all downloadable plugins
plugins := m.ListDownloadablePlugins()
js, _ := json.Marshal(plugins)
utils.SendJSONResponse(w, string(js))
}
// HandleResyncPluginList is the handler for resyncing the plugin list from the plugin store URLs
func (m *Manager) HandleResyncPluginList(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
//Make sure this function require csrf token
utils.SendErrorResponse(w, "Method not allowed")
return
}
//Resync the plugin list from the plugin store URLs
err := m.UpdateDownloadablePluginList()
if err != nil {
utils.SendErrorResponse(w, "Failed to resync plugin list: "+err.Error())
return
}
utils.SendOK(w)
}
// HandleInstallPlugin is the handler for installing a plugin
func (m *Manager) HandleInstallPlugin(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
utils.SendErrorResponse(w, "Method not allowed")
return
}
pluginID, err := utils.PostPara(r, "pluginID")
if err != nil {
utils.SendErrorResponse(w, "pluginID is required")
return
}
// Find the plugin info from cache
var plugin *DownloadablePlugin
for _, p := range m.Options.DownloadablePluginCache {
if p.PluginIntroSpect.ID == pluginID {
plugin = p
break
}
}
if plugin == nil {
utils.SendErrorResponse(w, "Plugin not found")
return
}
// Install the plugin (implementation depends on your system)
err = m.InstallPlugin(plugin)
if err != nil {
utils.SendErrorResponse(w, "Failed to install plugin: "+err.Error())
return
}
utils.SendOK(w)
}
// HandleUninstallPlugin is the handler for uninstalling a plugin
func (m *Manager) HandleUninstallPlugin(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
utils.SendErrorResponse(w, "Method not allowed")
return
}
pluginID, err := utils.PostPara(r, "pluginID")
if err != nil {
utils.SendErrorResponse(w, "pluginID is required")
return
}
// Uninstall the plugin (implementation depends on your system)
err = m.UninstallPlugin(pluginID)
if err != nil {
utils.SendErrorResponse(w, "Failed to uninstall plugin: "+err.Error())
return
}
utils.SendOK(w)
}

View File

@ -0,0 +1,52 @@
package plugins
import (
"testing"
)
func TestUpdateDownloadablePluginList(t *testing.T) {
mockManager := &Manager{
Options: &ManagerOptions{
DownloadablePluginCache: []*DownloadablePlugin{},
PluginStoreURLs: []string{},
},
}
//Inject a mock URL for testing
mockManager.Options.PluginStoreURLs = []string{"https://raw.githubusercontent.com/aroz-online/zoraxy-official-plugins/refs/heads/main/directories/index.json"}
err := mockManager.UpdateDownloadablePluginList()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(mockManager.Options.DownloadablePluginCache) == 0 {
t.Fatalf("expected plugin cache to be updated, but it was empty")
}
if mockManager.Options.LastSuccPluginSyncTime == 0 {
t.Fatalf("expected LastSuccPluginSyncTime to be updated, but it was not")
}
}
func TestGetPluginListFromURL(t *testing.T) {
mockManager := &Manager{
Options: &ManagerOptions{
DownloadablePluginCache: []*DownloadablePlugin{},
PluginStoreURLs: []string{},
},
}
pluginList, err := mockManager.getPluginListFromURL("https://raw.githubusercontent.com/aroz-online/zoraxy-official-plugins/refs/heads/main/directories/index.json")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(pluginList) == 0 {
t.Fatalf("expected plugin list to be populated, but it was empty")
}
for _, plugin := range pluginList {
t.Logf("Plugin: %+v", plugin)
}
}

View File

@ -29,10 +29,16 @@ type Plugin struct {
} }
type ManagerOptions struct { type ManagerOptions struct {
/* Plugins */
PluginDir string //The directory where the plugins are stored PluginDir string //The directory where the plugins are stored
PluginGroups map[string][]string //The plugin groups,key is the tag name and the value is an array of plugin IDs PluginGroups map[string][]string //The plugin groups,key is the tag name and the value is an array of plugin IDs
PluginGroupsConfig string //The group / tag configuration file, if set the plugin groups will be loaded from this file PluginGroupsConfig string //The group / tag configuration file, if set the plugin groups will be loaded from this file
/* Plugin Downloader */
PluginStoreURLs []string //The plugin store URLs, used to download the plugins
DownloadablePluginCache []*DownloadablePlugin //The cache for the downloadable plugins, key is the plugin ID and value is the DownloadablePlugin struct
LastSuccPluginSyncTime int64 //The last sync time for the plugin store URLs, used to check if the plugin store URLs need to be synced again
/* Runtime */ /* Runtime */
SystemConst *zoraxyPlugin.RuntimeConstantValue //The system constant value SystemConst *zoraxyPlugin.RuntimeConstantValue //The system constant value
CSRFTokenGen func(*http.Request) string `json:"-"` //The CSRF token generator function CSRFTokenGen func(*http.Request) string `json:"-"` //The CSRF token generator function

View File

@ -36,6 +36,8 @@ func mergeDailySummaryExports(exports []*statistic.DailySummaryExport) *statisti
Referer: make(map[string]int), Referer: make(map[string]int),
UserAgent: make(map[string]int), UserAgent: make(map[string]int),
RequestURL: make(map[string]int), RequestURL: make(map[string]int),
Downstreams: make(map[string]int),
Upstreams: make(map[string]int),
} }
for _, export := range exports { for _, export := range exports {
@ -66,6 +68,14 @@ func mergeDailySummaryExports(exports []*statistic.DailySummaryExport) *statisti
for key, value := range export.RequestURL { for key, value := range export.RequestURL {
mergedExport.RequestURL[key] += value mergedExport.RequestURL[key] += value
} }
for key, value := range export.Downstreams {
mergedExport.Downstreams[key] += value
}
for key, value := range export.Upstreams {
mergedExport.Upstreams[key] += value
}
} }
return mergedExport return mergedExport

View File

@ -30,6 +30,8 @@ type DailySummary struct {
Referer *sync.Map //Map that store where the user was refered from Referer *sync.Map //Map that store where the user was refered from
UserAgent *sync.Map //Map that store the useragent of the request UserAgent *sync.Map //Map that store the useragent of the request
RequestURL *sync.Map //Request URL of the request object RequestURL *sync.Map //Request URL of the request object
DownstreamHostnames *sync.Map //Request count of downstream hostname
UpstreamHostnames *sync.Map //Forwarded request count of upstream hostname
} }
type RequestInfo struct { type RequestInfo struct {
@ -42,6 +44,7 @@ type RequestInfo struct {
UserAgent string //UserAgent of the downstream request UserAgent string //UserAgent of the downstream request
RequestURL string //Request URL RequestURL string //Request URL
Target string //Target domain or hostname Target string //Target domain or hostname
Upstream string ////Upstream domain or hostname, if the request is forwarded to upstream
} }
type CollectorOption struct { type CollectorOption struct {
@ -233,6 +236,24 @@ func (c *Collector) RecordRequest(ri RequestInfo) {
} else { } else {
c.DailySummary.RequestURL.Store(ri.RequestURL, ru.(int)+1) c.DailySummary.RequestURL.Store(ri.RequestURL, ru.(int)+1)
} }
//Record the downstream hostname
//This is the hostname that the user visited, not the target domain
ds, ok := c.DailySummary.DownstreamHostnames.Load(ri.Target)
if !ok {
c.DailySummary.DownstreamHostnames.Store(ri.Target, 1)
} else {
c.DailySummary.DownstreamHostnames.Store(ri.Target, ds.(int)+1)
}
//Record the upstream hostname
//This is the selected load balancer upstream hostname or ip
us, ok := c.DailySummary.UpstreamHostnames.Load(ri.Upstream)
if !ok {
c.DailySummary.UpstreamHostnames.Store(ri.Upstream, 1)
} else {
c.DailySummary.UpstreamHostnames.Store(ri.Upstream, us.(int)+1)
}
}() }()
//ADD MORE HERE IF NEEDED //ADD MORE HERE IF NEEDED
@ -280,6 +301,8 @@ func NewDailySummary() *DailySummary {
Referer: &sync.Map{}, Referer: &sync.Map{},
UserAgent: &sync.Map{}, UserAgent: &sync.Map{},
RequestURL: &sync.Map{}, RequestURL: &sync.Map{},
DownstreamHostnames: &sync.Map{},
UpstreamHostnames: &sync.Map{},
} }
} }

View File

@ -13,6 +13,21 @@ type DailySummaryExport struct {
Referer map[string]int Referer map[string]int
UserAgent map[string]int UserAgent map[string]int
RequestURL map[string]int RequestURL map[string]int
Downstreams map[string]int
Upstreams map[string]int
}
func SyncMapToMapStringInt(syncMap *sync.Map) map[string]int {
result := make(map[string]int)
syncMap.Range(func(key, value interface{}) bool {
strKey, okKey := key.(string)
intValue, okValue := value.(int)
if okKey && okValue {
result[strKey] = intValue
}
return true
})
return result
} }
func DailySummaryToExport(summary DailySummary) DailySummaryExport { func DailySummaryToExport(summary DailySummary) DailySummaryExport {
@ -26,41 +41,30 @@ func DailySummaryToExport(summary DailySummary) DailySummaryExport {
Referer: make(map[string]int), Referer: make(map[string]int),
UserAgent: make(map[string]int), UserAgent: make(map[string]int),
RequestURL: make(map[string]int), RequestURL: make(map[string]int),
Downstreams: make(map[string]int),
Upstreams: make(map[string]int),
} }
summary.ForwardTypes.Range(func(key, value interface{}) bool { export.ForwardTypes = SyncMapToMapStringInt(summary.ForwardTypes)
export.ForwardTypes[key.(string)] = value.(int) export.RequestOrigin = SyncMapToMapStringInt(summary.RequestOrigin)
return true export.RequestClientIp = SyncMapToMapStringInt(summary.RequestClientIp)
}) export.Referer = SyncMapToMapStringInt(summary.Referer)
export.UserAgent = SyncMapToMapStringInt(summary.UserAgent)
summary.RequestOrigin.Range(func(key, value interface{}) bool { export.RequestURL = SyncMapToMapStringInt(summary.RequestURL)
export.RequestOrigin[key.(string)] = value.(int) export.Downstreams = SyncMapToMapStringInt(summary.DownstreamHostnames)
return true export.Upstreams = SyncMapToMapStringInt(summary.UpstreamHostnames)
})
summary.RequestClientIp.Range(func(key, value interface{}) bool {
export.RequestClientIp[key.(string)] = value.(int)
return true
})
summary.Referer.Range(func(key, value interface{}) bool {
export.Referer[key.(string)] = value.(int)
return true
})
summary.UserAgent.Range(func(key, value interface{}) bool {
export.UserAgent[key.(string)] = value.(int)
return true
})
summary.RequestURL.Range(func(key, value interface{}) bool {
export.RequestURL[key.(string)] = value.(int)
return true
})
return export return export
} }
func MapStringIntToSyncMap(m map[string]int) *sync.Map {
syncMap := &sync.Map{}
for k, v := range m {
syncMap.Store(k, v)
}
return syncMap
}
func DailySummaryExportToSummary(export DailySummaryExport) DailySummary { func DailySummaryExportToSummary(export DailySummaryExport) DailySummary {
summary := DailySummary{ summary := DailySummary{
TotalRequest: export.TotalRequest, TotalRequest: export.TotalRequest,
@ -72,31 +76,18 @@ func DailySummaryExportToSummary(export DailySummaryExport) DailySummary {
Referer: &sync.Map{}, Referer: &sync.Map{},
UserAgent: &sync.Map{}, UserAgent: &sync.Map{},
RequestURL: &sync.Map{}, RequestURL: &sync.Map{},
DownstreamHostnames: &sync.Map{},
UpstreamHostnames: &sync.Map{},
} }
for k, v := range export.ForwardTypes { summary.ForwardTypes = MapStringIntToSyncMap(export.ForwardTypes)
summary.ForwardTypes.Store(k, v) summary.RequestOrigin = MapStringIntToSyncMap(export.RequestOrigin)
} summary.RequestClientIp = MapStringIntToSyncMap(export.RequestClientIp)
summary.Referer = MapStringIntToSyncMap(export.Referer)
for k, v := range export.RequestOrigin { summary.UserAgent = MapStringIntToSyncMap(export.UserAgent)
summary.RequestOrigin.Store(k, v) summary.RequestURL = MapStringIntToSyncMap(export.RequestURL)
} summary.DownstreamHostnames = MapStringIntToSyncMap(export.Downstreams)
summary.UpstreamHostnames = MapStringIntToSyncMap(export.Upstreams)
for k, v := range export.RequestClientIp {
summary.RequestClientIp.Store(k, v)
}
for k, v := range export.Referer {
summary.Referer.Store(k, v)
}
for k, v := range export.UserAgent {
summary.UserAgent.Store(k, v)
}
for k, v := range export.RequestURL {
summary.RequestURL.Store(k, v)
}
return summary return summary
} }

View File

@ -116,6 +116,7 @@ func ReverseProxtInit() {
WebDirectory: *path_webserver, WebDirectory: *path_webserver,
AccessController: accessController, AccessController: accessController,
AutheliaRouter: autheliaRouter, AutheliaRouter: autheliaRouter,
AuthentikRouter: authentikRouter,
LoadBalancer: loadBalancer, LoadBalancer: loadBalancer,
PluginManager: pluginManager, PluginManager: pluginManager,
/* Utilities */ /* Utilities */
@ -587,6 +588,8 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthelia newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthelia
} else if authProviderType == 3 { } else if authProviderType == 3 {
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOauth2 newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOauth2
} else if authProviderType == 4 {
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthentik
} else { } else {
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodNone newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodNone
} }

View File

@ -101,7 +101,7 @@ func handleInjectHTML(w http.ResponseWriter, r *http.Request, relativeFilepath s
if len(relativeFilepath) > 0 && relativeFilepath[len(relativeFilepath)-1:] == "/" { if len(relativeFilepath) > 0 && relativeFilepath[len(relativeFilepath)-1:] == "/" {
relativeFilepath = relativeFilepath + "index.html" relativeFilepath = relativeFilepath + "index.html"
} }
if DEVELOPMENT_BUILD { if *development_build {
//Load from disk //Load from disk
targetFilePath := strings.ReplaceAll(filepath.Join("web/", relativeFilepath), "\\", "/") targetFilePath := strings.ReplaceAll(filepath.Join("web/", relativeFilepath), "\\", "/")
content, err = os.ReadFile(targetFilePath) content, err = os.ReadFile(targetFilePath)

View File

@ -9,6 +9,8 @@ import (
"strings" "strings"
"time" "time"
"imuslab.com/zoraxy/mod/auth/sso/authentik"
"github.com/gorilla/csrf" "github.com/gorilla/csrf"
"imuslab.com/zoraxy/mod/access" "imuslab.com/zoraxy/mod/access"
"imuslab.com/zoraxy/mod/acme" "imuslab.com/zoraxy/mod/acme"
@ -98,7 +100,7 @@ func startupSequence() {
}) })
//Create a TLS certificate manager //Create a TLS certificate manager
tlsCertManager, err = tlscert.NewManager(CONF_CERT_STORE, DEVELOPMENT_BUILD, SystemWideLogger) tlsCertManager, err = tlscert.NewManager(CONF_CERT_STORE, *development_build, SystemWideLogger)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -148,6 +150,13 @@ func startupSequence() {
Database: sysdb, Database: sysdb,
}) })
authentikRouter = authentik.NewAuthentikRouter(&authentik.AuthentikRouterOptions{
UseHTTPS: false, // Automatic populate in router initiation
AuthentikURL: "", // Automatic populate in router initiation
Logger: SystemWideLogger,
Database: sysdb,
})
//Create a statistic collector //Create a statistic collector
statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{ statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{
Database: sysdb, Database: sysdb,
@ -312,7 +321,10 @@ func startupSequence() {
SystemConst: &zoraxy_plugin.RuntimeConstantValue{ SystemConst: &zoraxy_plugin.RuntimeConstantValue{
ZoraxyVersion: SYSTEM_VERSION, ZoraxyVersion: SYSTEM_VERSION,
ZoraxyUUID: nodeUUID, ZoraxyUUID: nodeUUID,
DevelopmentBuild: DEVELOPMENT_BUILD, DevelopmentBuild: *development_build,
},
PluginStoreURLs: []string{
"https://raw.githubusercontent.com/aroz-online/zoraxy-official-plugins/refs/heads/main/directories/index.json",
}, },
Database: sysdb, Database: sysdb,
Logger: SystemWideLogger, Logger: SystemWideLogger,
@ -322,9 +334,19 @@ func startupSequence() {
}, },
}) })
//Sync latest plugin list from the plugin store
go func() {
err = pluginManager.UpdateDownloadablePluginList()
if err != nil {
SystemWideLogger.PrintAndLog("plugin-manager", "Failed to sync plugin list from plugin store", err)
} else {
SystemWideLogger.PrintAndLog("plugin-manager", "Plugin list synced from plugin store", nil)
}
}()
err = pluginManager.LoadPluginsFromDisk() err = pluginManager.LoadPluginsFromDisk()
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Plugin Manager", "Failed to load plugins", err) SystemWideLogger.PrintAndLog("plugin-manager", "Failed to load plugins", err)
} }
/* Docker UX Optimizer */ /* Docker UX Optimizer */

View File

@ -48,6 +48,8 @@
</div> </div>
</div> </div>
<div class="ui small input" style="width: 300px; height: 38px;"> <div class="ui small input" style="width: 300px; height: 38px;">
<!-- Prevent the browser from filling the saved Zoraxy login account into the input searchInput below -->
<input type="password" autocomplete="off" hidden/>
<input type="text" id="searchInput" placeholder="Quick Search" onkeydown="handleSearchInput(event);" onchange="handleSearchInput(event);" onblur="handleSearchInput(event);"> <input type="text" id="searchInput" placeholder="Quick Search" onkeydown="handleSearchInput(event);" onchange="handleSearchInput(event);" onblur="handleSearchInput(event);">
</div> </div>
</div> </div>
@ -142,12 +144,23 @@
if (subd.Disabled){ if (subd.Disabled){
enableChecked = ""; enableChecked = "";
} }
let httpProto = "http://";
if ($("#tls").checkbox("is checked")) {
httpProto = "https://";
} else {
httpProto = "http://";
}
let hostnameRedirectPort = currentListeningPort;
if (hostnameRedirectPort == 80 || hostnameRedirectPort == 443){
hostnameRedirectPort = "";
}else{
hostnameRedirectPort = ":" + hostnameRedirectPort;
}
let aliasDomains = ``; let aliasDomains = ``;
if (subd.MatchingDomainAlias != undefined && subd.MatchingDomainAlias.length > 0){ if (subd.MatchingDomainAlias != undefined && subd.MatchingDomainAlias.length > 0){
aliasDomains = `<small class="aliasDomains" eptuuid="${subd.RootOrMatchingDomain}" style="color: #636363;">Alias: `; aliasDomains = `<small class="aliasDomains" eptuuid="${subd.RootOrMatchingDomain}" style="color: #636363;">Alias: `;
subd.MatchingDomainAlias.forEach(alias => { subd.MatchingDomainAlias.forEach(alias => {
aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `; aliasDomains += `<a href="${httpProto}${alias}${hostnameRedirectPort}" target="_blank">${alias}</a>, `;
}); });
aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
aliasDomains += `</small><br>`; aliasDomains += `</small><br>`;
@ -155,7 +168,7 @@
$("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry"> $("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
<td data-label="" editable="true" datatype="inbound"> <td data-label="" editable="true" datatype="inbound">
<a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}<br> <a href="${httpProto}${subd.RootOrMatchingDomain}${hostnameRedirectPort}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}<br>
${aliasDomains} ${aliasDomains}
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small> <small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
</td> </td>
@ -174,6 +187,7 @@
${subd.AuthenticationProvider.AuthMethod == 0x1?`<i class="ui grey key icon"></i> Basic Auth`:``} ${subd.AuthenticationProvider.AuthMethod == 0x1?`<i class="ui grey key icon"></i> Basic Auth`:``}
${subd.AuthenticationProvider.AuthMethod == 0x2?`<i class="ui blue key icon"></i> Authelia`:``} ${subd.AuthenticationProvider.AuthMethod == 0x2?`<i class="ui blue key icon"></i> Authelia`:``}
${subd.AuthenticationProvider.AuthMethod == 0x3?`<i class="ui yellow key icon"></i> Oauth2`:``} ${subd.AuthenticationProvider.AuthMethod == 0x3?`<i class="ui yellow key icon"></i> Oauth2`:``}
${subd.AuthenticationProvider.AuthMethod == 0x4?`<i class="ui blue key icon"></i> Authentik`:``}
${subd.AuthenticationProvider.AuthMethod != 0x0 && subd.RequireRateLimit?"<br>":""} ${subd.AuthenticationProvider.AuthMethod != 0x0 && subd.RequireRateLimit?"<br>":""}
${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``} ${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``}
${subd.AuthenticationProvider.AuthMethod == 0x0 && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""} ${subd.AuthenticationProvider.AuthMethod == 0x0 && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""}
@ -382,6 +396,12 @@
<label>Authelia</label> <label>Authelia</label>
</div> </div>
</div> </div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" value="4" name="authProviderType" ${authProvider==0x4?"checked":""}>
<label>Authentik</label>
</div>
</div>
</div> </div>
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button> <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button>
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button> <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>

View File

@ -185,6 +185,8 @@
</tbody> </tbody>
</table> </table>
<br>
<button class="ui basic violet button" onclick="openPluginStore();"><i class="download icon"></i>Plugin Store (Experimental)</button>
</div> </div>
<script> <script>
@ -480,6 +482,9 @@ function initiatePluginList(){
<a href="${plugin.Spec.url}" target="_blank">${plugin.Spec.url}</a></td> <a href="${plugin.Spec.url}" target="_blank">${plugin.Spec.url}</a></td>
<td data-label="Category">${plugin.Spec.type==0?"Router":"Utilities"}</td> <td data-label="Category">${plugin.Spec.type==0?"Router":"Utilities"}</td>
<td data-label="Action"> <td data-label="Action">
<button onclick="uninstallPlugin('${plugin.Spec.id}', '${plugin.Spec.name}', this);" class="ui basic red icon button">
<i class="trash icon"></i>
</button>
<button onclick="getPluginInfo('${plugin.Spec.id}', this);" class="ui basic icon button" pluginid="${plugin.Spec.id}"> <button onclick="getPluginInfo('${plugin.Spec.id}', this);" class="ui basic icon button" pluginid="${plugin.Spec.id}">
<i class="info circle icon"></i> <i class="info circle icon"></i>
</button> </button>
@ -509,9 +514,6 @@ function initiatePluginList(){
initiatePluginList(); initiatePluginList();
/* Tag Assignment */
/* Plugin Lifecycle */ /* Plugin Lifecycle */
@ -563,6 +565,33 @@ function getPluginInfo(pluginId, btn){
showSideWrapper("snippet/pluginInfo.html?t=" + Date.now() + "#" + payload); showSideWrapper("snippet/pluginInfo.html?t=" + Date.now() + "#" + payload);
} }
function openPluginStore(){
//Open plugin store in extended mode
showSideWrapper("snippet/pluginstore.html?t=" + Date.now(), true);
}
function uninstallPlugin(pluginId, pluginName, btn=undefined) {
if (confirm("Are you sure you want to remove " + pluginName + " plugin?")) {
if (btn) {
$(btn).html('<i class="spinner loading icon"></i>');
$(btn).addClass('disabled');
}
$.cjax({
url: '/api/plugins/store/uninstall',
type: 'POST',
data: { "pluginID": pluginId },
success: function(data) {
if (data.error != undefined) {
msgbox(data.error, false);
} else {
msgbox(pluginName + " uninstalled successfully", true);
initiatePluginList();
}
}
});
}
}
</script> </script>

View File

@ -34,6 +34,27 @@
</form> </form>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="ui basic segment">
<h3>Authentik</h3>
<p>Configuration settings for Authentik authentication provider.</p>
<form class="ui form">
<div class="field">
<label for="authentikServerUrl">Authentik Server URL</label>
<input type="text" id="authentikServerUrl" name="authentikServerUrl" placeholder="Enter Authentik Server URL">
<small>Example: auth.example.com</small>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="authentikUseHttps" name="useHttps">
<label for="authentikUseHttps">Use HTTPS</label>
<small>Check this if your Authentik server uses HTTPS</small>
</div>
</div>
<button class="ui basic button" onclick="event.preventDefault(); updateAuthentikSettings();"><i class="green check icon"></i> Apply Change</button>
</form>
</div>
<div class="ui divider"></div>
</div> </div>
<script> <script>
@ -50,6 +71,18 @@
console.error('Error fetching SSO settings:', textStatus, errorThrown); console.error('Error fetching SSO settings:', textStatus, errorThrown);
} }
}); });
$.cjax({
url: '/api/sso/Authentik',
method: 'GET',
dataType: 'json',
success: function(data) {
$('#authentikServerUrl').val(data.authentikURL);
$('#authentikUseHttps').prop('checked', data.useHTTPS);
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('Error fetching SSO settings:', textStatus, errorThrown);
}
});
}); });
function updateAutheliaSettings(){ function updateAutheliaSettings(){
@ -76,4 +109,28 @@
} }
}); });
} }
function updateAuthentikSettings(){
var authentikServerUrl = $('#authentikServerUrl').val();
var useHttps = $('#authentikUseHttps').prop('checked');
$.cjax({
url: '/api/sso/Authentik',
method: 'POST',
data: {
authentikURL: authentikServerUrl,
useHTTPS: useHttps
},
success: function(data) {
if (data.error != undefined) {
$.msgbox(data.error, false);
return;
}
msgbox('Authentik settings updated', true);
console.log('Authentik settings updated:', data);
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('Error updating Authentik settings:', textStatus, errorThrown);
}
});
}
</script> </script>

View File

@ -186,6 +186,45 @@
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="ui stackable grid">
<div class="eight wide column">
<h3>Requested Hostnames</h3>
<p>Most requested hostnames from downstream</p>
<div>
<div style="height: 500px; overflow-y: auto;">
<table class="ui unstackable striped celled table">
<thead>
<tr>
<th class="no-sort">Hostname</th>
<th class="no-sort">Requests</th>
</tr></thead>
<tbody id="stats_downstreamTable">
</tbody>
</table>
</div>
</div>
</div>
<div class="eight wide column">
<h3>Forwarded Upstreams</h3>
<p>The Top 100 upstreams where the requests are forwarded to</p>
<div>
<div style="height: 500px; overflow-y: auto;">
<table class="ui unstackable striped celled table">
<thead>
<tr>
<th class="no-sort">Upstream Endpoint</th>
<th class="no-sort">Requests</th>
</tr></thead>
<tbody id="stats_upstreamTable">
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="ui divider"></div>
<div class="ui basic segment" id="trendGraphs"> <div class="ui basic segment" id="trendGraphs">
<h3>Visitor Trend Analysis</h3> <h3>Visitor Trend Analysis</h3>
<p>Request trends in the selected time range</p> <p>Request trends in the selected time range</p>
@ -263,6 +302,22 @@
//Render Referer header //Render Referer header
renderRefererTable(data.Referer); renderRefererTable(data.Referer);
if (data.Downstreams == null){
//No downstream data to show
$("#stats_downstreamTable").html("<tr><td colspan='2'>No data</td></tr>");
}else{
//Render the downstream table
renderDownstreamTable(data.Downstreams);
}
if (data.Upstreams == null){
//No upstream data to show
$("#stats_upstreamTable").html("<tr><td colspan='2'>No data</td></tr>");
}else{
//Render the upstream table
renderUpstreamTable(data.Upstreams);
}
//Hide the trend graphs //Hide the trend graphs
$("#trendGraphs").hide(); $("#trendGraphs").hide();
}); });
@ -410,6 +465,46 @@
} }
} }
function renderDownstreamTable(downstreamList){
const sortedEntries = Object.entries(downstreamList).sort(([, valueA], [, valueB]) => valueB - valueA);
$("#stats_downstreamTable").html("");
let endStop = 100;
if (sortedEntries.length < 100){
endStop = sortedEntries.length;
}
for (var i = 0; i < endStop; i++) {
let referer = (decodeURIComponent(sortedEntries[i][0])).replace(/(<([^>]+)>)/ig,"");
if (sortedEntries[i][0] == ""){
//Root
referer = `<span style="color: #b5b5b5;">(<i class="eye slash outline icon"></i> Unknown or Hidden)</span>`;
}
$("#stats_downstreamTable").append(`<tr>
<td>${referer}</td>
<td>${sortedEntries[i][1]}</td>
</tr>`);
}
}
function renderUpstreamTable(upstreamList){
const sortedEntries = Object.entries(upstreamList).sort(([, valueA], [, valueB]) => valueB - valueA);
$("#stats_upstreamTable").html("");
let endStop = 100;
if (sortedEntries.length < 100){
endStop = sortedEntries.length;
}
for (var i = 0; i < endStop; i++) {
let referer = (decodeURIComponent(sortedEntries[i][0])).replace(/(<([^>]+)>)/ig,"");
if (sortedEntries[i][0] == ""){
//Root
referer = `<span style="color: #b5b5b5;">(<i class="eye slash outline icon"></i> Unknown or Hidden)</span>`;
}
$("#stats_upstreamTable").append(`<tr>
<td>${referer}</td>
<td>${sortedEntries[i][1]}</td>
</tr>`);
}
}
function renderFileTypeGraph(requestURLs){ function renderFileTypeGraph(requestURLs){
//Create the device chart //Create the device chart
let fileExtensions = {}; let fileExtensions = {};

View File

@ -524,7 +524,6 @@
$("#tls").checkbox("set checked"); $("#tls").checkbox("set checked");
}else{ }else{
$(".tlsEnabledOnly").addClass('disabled'); $(".tlsEnabledOnly").addClass('disabled');
$(".tlsEnabledOnly").addClass('disabled');
} }
//Initiate the input listener on the checkbox //Initiate the input listener on the checkbox

View File

@ -197,6 +197,12 @@ body.darkTheme .menubar{
max-width: calc(80% - 1em); max-width: calc(80% - 1em);
} }
@media screen and (max-width: 478px) {
.sideWrapper.extendedMode {
max-width: calc(100% - 1em);
}
}
.sideWrapper .content{ .sideWrapper .content{
height: 100%; height: 100%;
width: 100%; width: 100%;

View File

@ -0,0 +1,270 @@
<!DOCTYPE html>
<html>
<head>
<!-- Notes: This should be open in its original path-->
<meta charset="utf-8">
<meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">
<title>Plugin Store</title>
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
<script src="../script/jquery-3.6.0.min.js"></script>
<script src="../script/semantic/semantic.min.js"></script>
<script src="../script/utils.js"></script>
<style>
#pluginList{
padding: 1em;
border: 1px solid #ccc;
height: 500px;
overflow-y: scroll;
}
body.darkTheme #pluginList .header{
color: #fff;
}
.installablePlugin{
position: relative;
}
.installablePlugin .action{
position: absolute;
top: 0.4em;
right: 0.4em;
}
@media screen and (max-width: 768px) {
#pluginList .item .image {
display: none;
}
}
</style>
</head>
<body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br>
<div class="ui container">
<div class="ui warning message">
<div class="header">Experimental Feature</div>
<p>The Plugin Store is an experimental feature. Use it at your own risk.</p>
</div>
<div class="ui fluid search">
<div class="ui fluid icon input">
<input id="searchInput" class="prompt" type="text" placeholder="Search plugins">
<i class="search icon"></i>
</div>
</div>
<div class="ui divided items" id="pluginList">
</div>
<button class="ui basic button" onclick="forceResyncPlugins();"><i class="ui green refresh icon"></i> Update Plugin List</button>
<!-- <div class="ui divider"></div>
<div class="ui basic segment advanceoptions">
<div class="ui accordion advanceSettings">
<div class="title">
<i class="dropdown icon"></i>
Advance Settings
</div>
<div class="content">
<p>Plugin Store URLs</p>
<div class="ui form">
<div class="field">
<textarea id="pluginStoreURLs" rows="5"></textarea>
<label>Enter plugin store URLs, separating each URL with a new line</label>
</div>
<button class="ui basic button" onclick="savePluginStoreURLs()">
<i class="ui green save icon"></i>Save
</button>
</div>
</div>
</div>
</div>
-->
<div class="ui divider"></div>
<div class="field" >
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
</div>
<br><br><br><br>
</div>
<script>
let availablePlugins = [];
let installedPlugins = [];
$(".accordion").accordion();
function initStoreList(){
$.get("/api/plugins/list", function(data) {
if (data.error != undefined) {
parent.msgbox(data.error, false);
return;
}else{
installedPlugins = data || [];
console.log(installedPlugins);
}
$.cjax({
url: '/api/plugins/store/list',
type: 'GET',
success: function(data) {
if (data.error != undefined) {
parent.msgbox(data.error, false);
}else{
availablePlugins = data || [];
populatePluginList(availablePlugins);
}
}
});
});
}
initStoreList();
/* Plugin Search */
function searchPlugins() {
const query = document.getElementById('searchInput').value.toLowerCase();
const items = document.querySelectorAll('#pluginList .item');
if (query.trim() === '') {
items.forEach(item => {
item.style.display = '';
});
return;
}
items.forEach(item => {
const name = item.querySelector('.header').textContent.toLowerCase();
const description = item.querySelector('.description p').textContent.toLowerCase();
const authorElement = item.querySelector('.plugin_author');
const author = authorElement ? authorElement.textContent.toLowerCase() : '';
const id = item.getAttribute('plugin_id').toLowerCase();
if (name.includes(query) || description.includes(query) || author.includes(query) || id.includes(query)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
}
//Bind search function to input field and Enter key
document.getElementById('searchInput').addEventListener('input', searchPlugins);
document.getElementById('searchInput').addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
searchPlugins();
}
});
function forceResyncPlugins() {
parent.msgbox("Updating plugin list...", true);
document.getElementById('searchInput').value = '';
$.cjax({
url: '/api/plugins/store/resync',
type: 'POST',
success: function(data) {
if (data.error != undefined) {
parent.msgbox(data.error, false);
} else {
parent.msgbox("Plugin list updated successfully", true);
initStoreList();
}
}
});
}
/* Plugin Store */
function populatePluginList(plugins) {
const pluginList = document.getElementById('pluginList');
pluginList.innerHTML = ''; // Clear existing items
plugins.forEach(plugin => {
console.log(plugin);
let thisPluginIsInstalled = false;
installedPlugins.forEach(installedPlugin => {
if (installedPlugin.Spec.id == plugin.PluginIntroSpect.id) {
thisPluginIsInstalled = true;
}
});
const item = `
<div class="item installablePlugin" plugin_id="${plugin.PluginIntroSpect.id}">
<div class="ui tiny image">
<img src="${plugin.IconPath}" alt="${plugin.PluginIntroSpect.name}">
</div>
<div class="content">
<div class="header">${plugin.PluginIntroSpect.name} </div> <a class="section" href="${plugin.PluginIntroSpect.url}" target="_blank"><i class="ui linkify icon"></i></a>
<div class="meta">
<p>v${plugin.PluginIntroSpect.version_major}.${plugin.PluginIntroSpect.version_minor}.${plugin.PluginIntroSpect.version_patch} by <span class="plugin_author">${plugin.PluginIntroSpect.author}</span></p>
</div>
<div class="description">
<p>${plugin.PluginIntroSpect.description}</p>
</div>
<div class="action">
${thisPluginIsInstalled
? `<button class="ui basic circular disabled button">Installed</button>`
: `<button class="ui basic circular button" onclick="installPlugin('${plugin.PluginIntroSpect.id}', this);",><i class="ui download icon"></i> Install</button>`}
</div>
</div>
</div>
`;
$('#pluginList').append(item);
});
// Reapply search filter if there's a query in the search bar
const searchQuery = document.getElementById('searchInput').value.toLowerCase();
if (searchQuery.trim() !== '') {
searchPlugins();
}
}
/* Plugin Actions */
function installPlugin(pluginId, btn=undefined) {
if (btn !== undefined) {
$(btn).addClass('loading').prop('disabled', true);
}
$.cjax({
url: '/api/plugins/store/install',
type: 'POST',
data: { "pluginID": pluginId },
success: function(data) {
if (btn !== undefined) {
$(btn).removeClass('loading').prop('disabled', false);
}
if (data.error != undefined) {
parent.msgbox(data.error, false);
} else {
parent.msgbox("Plugin installed successfully", true);
initStoreList();
//Also reload the parent plugin list
parent.initiatePluginList();
}
},
error: function() {
if (btn !== undefined) {
$(btn).removeClass('loading').prop('disabled', false);
}
parent.msgbox("An error occurred while installing the plugin", false);
}
});
}
function closeThisWrapper(){
parent.hideSideWrapper(true);
}
/* Advanced Options */
function savePluginManagerURLs() {
const urls = document.getElementById('pluginStoreURLs').value.split('\n').map(url => url.trim()).filter(url => url !== '');
console.log('Saving URLs:', urls);
// Add your logic to save the URLs here, e.g., send them to the server
$.cjax({
url: '/api/plugins/store/saveURLs',
type: 'POST',
data: { urls },
success: function(data) {
if (data.error != undefined) {
parent.msgbox(data.error, false);
} else {
parent.msgbox("URLs saved successfully", true);
}
}
});
}
</script>
</body>
</html>

View File

@ -345,7 +345,7 @@ func HandleZoraxyInfo(w http.ResponseWriter, r *http.Request) {
info := ZoraxyInfo{ info := ZoraxyInfo{
Version: SYSTEM_VERSION, Version: SYSTEM_VERSION,
NodeUUID: displayUUID, NodeUUID: displayUUID,
Development: DEVELOPMENT_BUILD, Development: *development_build,
BootTime: displayBootTime, BootTime: displayBootTime,
EnableSshLoopback: displayAllowSSHLB, EnableSshLoopback: displayAllowSSHLB,
} }