mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-21 06:53:04 +02:00
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:
commit
0e5550487e
@ -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")
|
||||||
|
15
src/def.go
15
src/def.go
@ -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"
|
||||||
@ -41,9 +43,8 @@ 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 */
|
||||||
@ -142,7 +144,8 @@ var (
|
|||||||
pluginManager *plugins.Manager //Plugin manager for managing plugins
|
pluginManager *plugins.Manager //Plugin manager for managing plugins
|
||||||
|
|
||||||
//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
|
||||||
|
18
src/go.mod
18
src/go.mod
@ -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
|
||||||
|
79
src/go.sum
79
src/go.sum
@ -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=
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
169
src/mod/auth/sso/authentik/authentik.go
Normal file
169
src/mod/auth/sso/authentik/authentik.go
Normal 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
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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, "/") {
|
||||||
|
@ -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()
|
||||||
|
108
src/mod/dynamicproxy/exploits/exploits.go
Normal file
108
src/mod/dynamicproxy/exploits/exploits.go
Normal 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)
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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"
|
||||||
@ -63,7 +64,8 @@ type RouterOption struct {
|
|||||||
PluginManager *plugins.Manager //Plugin manager for handling plugin routing
|
PluginManager *plugins.Manager //Plugin manager for handling plugin routing
|
||||||
|
|
||||||
/* 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 {
|
||||||
|
@ -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)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -249,3 +249,5 @@ func (m *Manager) HandleDisablePlugin(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Plugin Store */
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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
356
src/mod/plugins/store.go
Normal 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)
|
||||||
|
}
|
52
src/mod/plugins/store_test.go
Normal file
52
src/mod/plugins/store_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -24,12 +24,14 @@ type DailySummary struct {
|
|||||||
ErrorRequest int64 //Invalid request of the day, including error or not found
|
ErrorRequest int64 //Invalid request of the day, including error or not found
|
||||||
ValidRequest int64 //Valid request of the day
|
ValidRequest int64 //Valid request of the day
|
||||||
//Type counters
|
//Type counters
|
||||||
ForwardTypes *sync.Map //Map that hold the forward types
|
ForwardTypes *sync.Map //Map that hold the forward types
|
||||||
RequestOrigin *sync.Map //Map that hold [country ISO code]: visitor counter
|
RequestOrigin *sync.Map //Map that hold [country ISO code]: visitor counter
|
||||||
RequestClientIp *sync.Map //Map that hold all unique request IPs
|
RequestClientIp *sync.Map //Map that hold all unique request IPs
|
||||||
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
|
||||||
@ -271,15 +292,17 @@ func (c *Collector) ScheduleResetRealtimeStats() chan bool {
|
|||||||
|
|
||||||
func NewDailySummary() *DailySummary {
|
func NewDailySummary() *DailySummary {
|
||||||
return &DailySummary{
|
return &DailySummary{
|
||||||
TotalRequest: 0,
|
TotalRequest: 0,
|
||||||
ErrorRequest: 0,
|
ErrorRequest: 0,
|
||||||
ValidRequest: 0,
|
ValidRequest: 0,
|
||||||
ForwardTypes: &sync.Map{},
|
ForwardTypes: &sync.Map{},
|
||||||
RequestOrigin: &sync.Map{},
|
RequestOrigin: &sync.Map{},
|
||||||
RequestClientIp: &sync.Map{},
|
RequestClientIp: &sync.Map{},
|
||||||
Referer: &sync.Map{},
|
Referer: &sync.Map{},
|
||||||
UserAgent: &sync.Map{},
|
UserAgent: &sync.Map{},
|
||||||
RequestURL: &sync.Map{},
|
RequestURL: &sync.Map{},
|
||||||
|
DownstreamHostnames: &sync.Map{},
|
||||||
|
UpstreamHostnames: &sync.Map{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,77 +41,53 @@ 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,
|
||||||
ErrorRequest: export.ErrorRequest,
|
ErrorRequest: export.ErrorRequest,
|
||||||
ValidRequest: export.ValidRequest,
|
ValidRequest: export.ValidRequest,
|
||||||
ForwardTypes: &sync.Map{},
|
ForwardTypes: &sync.Map{},
|
||||||
RequestOrigin: &sync.Map{},
|
RequestOrigin: &sync.Map{},
|
||||||
RequestClientIp: &sync.Map{},
|
RequestClientIp: &sync.Map{},
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
28
src/start.go
28
src/start.go
@ -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 */
|
||||||
|
@ -47,8 +47,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui small input" style="width: 300px; height: 38px;">
|
<div class="ui small input" style="width: 300px; height: 38px;">
|
||||||
<input type="text" id="searchInput" placeholder="Quick Search" onkeydown="handleSearchInput(event);" onchange="handleSearchInput(event);" onblur="handleSearchInput(event);">
|
<!-- 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);">
|
||||||
</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>
|
||||||
|
@ -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,7 +482,10 @@ 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="getPluginInfo('${plugin.Spec.id}', this);" class="ui basic icon button" pluginid="${plugin.Spec.id}">
|
<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}">
|
||||||
<i class="info circle icon"></i>
|
<i class="info circle icon"></i>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="stopPlugin('${plugin.Spec.id}', this);" class="ui basic button pluginEnableButton" pluginid="${plugin.Spec.id}" ${plugin.Enabled ? '' : 'style="display:none;"'}>
|
<button onclick="stopPlugin('${plugin.Spec.id}', this);" class="ui basic button pluginEnableButton" pluginid="${plugin.Spec.id}" ${plugin.Enabled ? '' : 'style="display:none;"'}>
|
||||||
@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
@ -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>
|
@ -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 = {};
|
||||||
|
@ -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
|
||||||
|
@ -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%;
|
||||||
|
270
src/web/snippet/pluginstore.html
Normal file
270
src/web/snippet/pluginstore.html
Normal 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>
|
@ -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,
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user