mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-06 23:57:21 +02:00
Merge branch 'v3.0.8' into main
This commit is contained in:
commit
33def66386
11
src/api.go
11
src/api.go
@ -61,6 +61,12 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
||||||
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
||||||
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
||||||
|
//Reverse proxy upstream (load balance) APIs
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete)
|
||||||
//Reverse proxy virtual directory APIs
|
//Reverse proxy virtual directory APIs
|
||||||
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
|
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
|
||||||
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
|
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
|
||||||
@ -142,7 +148,7 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
|
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
|
||||||
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
||||||
|
|
||||||
//TCP Proxy
|
//Stream (TCP / UDP) Proxy
|
||||||
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
||||||
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
|
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
|
||||||
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
|
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
|
||||||
@ -223,12 +229,13 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
|
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
|
||||||
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
|
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
|
||||||
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
|
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
|
||||||
|
authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog)
|
||||||
|
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
|
||||||
|
|
||||||
//Debug
|
//Debug
|
||||||
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
|
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
|
||||||
|
|
||||||
//If you got APIs to add, append them here
|
//If you got APIs to add, append them here
|
||||||
// get available docker containers
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -79,7 +80,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
|||||||
return errors.New("not supported proxy type")
|
return errors.New("not supported proxy type")
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemWideLogger.PrintAndLog("Proxy", thisConfigEndpoint.RootOrMatchingDomain+" -> "+thisConfigEndpoint.Domain+" routing rule loaded", nil)
|
SystemWideLogger.PrintAndLog("proxy-config", thisConfigEndpoint.RootOrMatchingDomain+" -> "+loadbalance.GetUpstreamsAsString(thisConfigEndpoint.ActiveOrigins)+" routing rule loaded", nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,10 +133,16 @@ func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
|
|||||||
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
|
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
|
||||||
ProxyType: dynamicproxy.ProxyType_Root,
|
ProxyType: dynamicproxy.ProxyType_Root,
|
||||||
RootOrMatchingDomain: "/",
|
RootOrMatchingDomain: "/",
|
||||||
Domain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
ActiveOrigins: []*loadbalance.Upstream{
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
||||||
RequireTLS: false,
|
RequireTLS: false,
|
||||||
BypassGlobalTLS: false,
|
|
||||||
SkipCertValidations: false,
|
SkipCertValidations: false,
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InactiveOrigins: []*loadbalance.Upstream{},
|
||||||
|
BypassGlobalTLS: false,
|
||||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||||
RequireBasicAuth: false,
|
RequireBasicAuth: false,
|
||||||
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
||||||
|
50
src/main.go
50
src/main.go
@ -24,6 +24,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/ganserv"
|
"imuslab.com/zoraxy/mod/ganserv"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logviewer"
|
||||||
"imuslab.com/zoraxy/mod/mdns"
|
"imuslab.com/zoraxy/mod/mdns"
|
||||||
"imuslab.com/zoraxy/mod/netstat"
|
"imuslab.com/zoraxy/mod/netstat"
|
||||||
"imuslab.com/zoraxy/mod/pathrule"
|
"imuslab.com/zoraxy/mod/pathrule"
|
||||||
@ -32,6 +33,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||||
"imuslab.com/zoraxy/mod/streamproxy"
|
"imuslab.com/zoraxy/mod/streamproxy"
|
||||||
"imuslab.com/zoraxy/mod/tlscert"
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
|
"imuslab.com/zoraxy/mod/update"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
"imuslab.com/zoraxy/mod/webserv"
|
"imuslab.com/zoraxy/mod/webserv"
|
||||||
@ -51,12 +53,12 @@ var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL cert
|
|||||||
var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
|
var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
|
||||||
var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
|
var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
|
||||||
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
|
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
|
||||||
var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
|
var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
name = "Zoraxy"
|
name = "Zoraxy"
|
||||||
version = "3.0.7"
|
version = "3.0.8"
|
||||||
nodeUUID = "generic"
|
nodeUUID = "generic" //System uuid, in uuidv4 format
|
||||||
development = false //Set this to false to use embedded web fs
|
development = false //Set this to false to use embedded web fs
|
||||||
bootTime = time.Now().Unix()
|
bootTime = time.Now().Unix()
|
||||||
|
|
||||||
@ -73,7 +75,7 @@ var (
|
|||||||
authAgent *auth.AuthAgent //Authentication agent
|
authAgent *auth.AuthAgent //Authentication agent
|
||||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||||
loadbalancer *loadbalance.RouteManager //Load balancer manager to get routing targets from proxy rules
|
|
||||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||||
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||||
accessController *access.Controller //Access controller, handle black list and white list
|
accessController *access.Controller //Access controller, handle black list and white list
|
||||||
@ -88,12 +90,14 @@ var (
|
|||||||
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
||||||
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
||||||
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
|
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
|
||||||
|
loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing
|
||||||
|
|
||||||
//Helper modules
|
//Helper modules
|
||||||
EmailSender *email.Sender //Email sender that handle email sending
|
EmailSender *email.Sender //Email sender that handle email sending
|
||||||
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
||||||
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
|
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
|
||||||
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
||||||
|
LogViewer *logviewer.Viewer
|
||||||
)
|
)
|
||||||
|
|
||||||
// Kill signal handler. Do something before the system the core terminate.
|
// Kill signal handler. Do something before the system the core terminate.
|
||||||
@ -108,32 +112,34 @@ func SetupCloseHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ShutdownSeq() {
|
func ShutdownSeq() {
|
||||||
fmt.Println("- Shutting down " + name)
|
SystemWideLogger.Println("Shutting down " + name)
|
||||||
fmt.Println("- Closing GeoDB ")
|
SystemWideLogger.Println("Closing GeoDB ")
|
||||||
geodbStore.Close()
|
geodbStore.Close()
|
||||||
fmt.Println("- Closing Netstats Listener")
|
SystemWideLogger.Println("Closing Netstats Listener")
|
||||||
netstatBuffers.Close()
|
netstatBuffers.Close()
|
||||||
fmt.Println("- Closing Statistic Collector")
|
SystemWideLogger.Println("Closing Statistic Collector")
|
||||||
statisticCollector.Close()
|
statisticCollector.Close()
|
||||||
if mdnsTickerStop != nil {
|
if mdnsTickerStop != nil {
|
||||||
fmt.Println("- Stopping mDNS Discoverer (might take a few minutes)")
|
SystemWideLogger.Println("Stopping mDNS Discoverer (might take a few minutes)")
|
||||||
// Stop the mdns service
|
// Stop the mdns service
|
||||||
mdnsTickerStop <- true
|
mdnsTickerStop <- true
|
||||||
}
|
}
|
||||||
|
|
||||||
mdnsScanner.Close()
|
mdnsScanner.Close()
|
||||||
fmt.Println("- Closing Certificates Auto Renewer")
|
SystemWideLogger.Println("Shutting down load balancer")
|
||||||
|
loadBalancer.Close()
|
||||||
|
SystemWideLogger.Println("Closing Certificates Auto Renewer")
|
||||||
acmeAutoRenewer.Close()
|
acmeAutoRenewer.Close()
|
||||||
//Remove the tmp folder
|
//Remove the tmp folder
|
||||||
fmt.Println("- Cleaning up tmp files")
|
SystemWideLogger.Println("Cleaning up tmp files")
|
||||||
os.RemoveAll("./tmp")
|
os.RemoveAll("./tmp")
|
||||||
|
|
||||||
fmt.Println("- Closing system wide logger")
|
//Close database
|
||||||
SystemWideLogger.Close()
|
SystemWideLogger.Println("Stopping system database")
|
||||||
|
|
||||||
//Close database, final
|
|
||||||
fmt.Println("- Stopping system database")
|
|
||||||
sysdb.Close()
|
sysdb.Close()
|
||||||
|
|
||||||
|
//Close logger
|
||||||
|
SystemWideLogger.Println("Closing system wide logger")
|
||||||
|
SystemWideLogger.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -144,6 +150,16 @@ func main() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !utils.ValidateListeningAddress(*webUIPort) {
|
||||||
|
fmt.Println("Malformed -port (listening address) paramter. Do you mean -port=:" + *webUIPort + "?")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *enableAutoUpdate {
|
||||||
|
fmt.Println("Checking required config update")
|
||||||
|
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version))
|
||||||
|
}
|
||||||
|
|
||||||
SetupCloseHandler()
|
SetupCloseHandler()
|
||||||
|
|
||||||
//Read or create the system uuid
|
//Read or create the system uuid
|
||||||
|
@ -448,7 +448,12 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
domains := strings.Split(domainPara, ",")
|
domains := strings.Split(domainPara, ",")
|
||||||
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS, dns)
|
//Clean spaces in front or behind each domain
|
||||||
|
cleanedDomains := []string{}
|
||||||
|
for _, domain := range domains {
|
||||||
|
cleanedDomains = append(cleanedDomains, strings.TrimSpace(domain))
|
||||||
|
}
|
||||||
|
result, err := a.ObtainCert(cleanedDomains, filename, email, ca, caUrl, skipTLS, dns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||||
return
|
return
|
||||||
|
@ -14,10 +14,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
db "imuslab.com/zoraxy/mod/database"
|
db "imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ type AuthAgent struct {
|
|||||||
SessionStore *sessions.CookieStore
|
SessionStore *sessions.CookieStore
|
||||||
Database *db.Database
|
Database *db.Database
|
||||||
LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
|
LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
|
||||||
|
Logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthEndpoints struct {
|
type AuthEndpoints struct {
|
||||||
@ -38,11 +39,11 @@ type AuthEndpoints struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
|
func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, systemLogger *logger.Logger, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
|
||||||
store := sessions.NewCookieStore(key)
|
store := sessions.NewCookieStore(key)
|
||||||
err := sysdb.NewTable("auth")
|
err := sysdb.NewTable("auth")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to create auth database. Terminating.")
|
systemLogger.Println("Failed to create auth database. Terminating.")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,13 +53,14 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database,
|
|||||||
SessionStore: store,
|
SessionStore: store,
|
||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
LoginRedirectionHandler: loginRedirectionHandler,
|
LoginRedirectionHandler: loginRedirectionHandler,
|
||||||
|
Logger: systemLogger,
|
||||||
}
|
}
|
||||||
|
|
||||||
//Return the authAgent
|
//Return the authAgent
|
||||||
return &newAuthAgent
|
return &newAuthAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSessionKey(sysdb *db.Database) (string, error) {
|
func GetSessionKey(sysdb *db.Database, logger *logger.Logger) (string, error) {
|
||||||
sysdb.NewTable("auth")
|
sysdb.NewTable("auth")
|
||||||
sessionKey := ""
|
sessionKey := ""
|
||||||
if !sysdb.KeyExists("auth", "sessionkey") {
|
if !sysdb.KeyExists("auth", "sessionkey") {
|
||||||
@ -66,9 +68,9 @@ func GetSessionKey(sysdb *db.Database) (string, error) {
|
|||||||
rand.Read(key)
|
rand.Read(key)
|
||||||
sessionKey = string(key)
|
sessionKey = string(key)
|
||||||
sysdb.Write("auth", "sessionkey", sessionKey)
|
sysdb.Write("auth", "sessionkey", sessionKey)
|
||||||
log.Println("[Auth] New authentication session key generated")
|
logger.PrintAndLog("auth", "New authentication session key generated", nil)
|
||||||
} else {
|
} else {
|
||||||
log.Println("[Auth] Authentication session key loaded from database")
|
logger.PrintAndLog("auth", "Authentication session key loaded from database", nil)
|
||||||
err := sysdb.Read("auth", "sessionkey", &sessionKey)
|
err := sysdb.Read("auth", "sessionkey", &sessionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("database read error. Is the database file corrupted?")
|
return "", errors.New("database read error. Is the database file corrupted?")
|
||||||
@ -95,7 +97,7 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
username, err := utils.PostPara(r, "username")
|
username, err := utils.PostPara(r, "username")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Username not defined
|
//Username not defined
|
||||||
log.Println("[Auth] " + r.RemoteAddr + " trying to login with username: " + username)
|
a.Logger.PrintAndLog("auth", r.RemoteAddr+" trying to login with username: "+username, nil)
|
||||||
utils.SendErrorResponse(w, "Username not defined or empty.")
|
utils.SendErrorResponse(w, "Username not defined or empty.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -124,11 +126,11 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
a.LoginUserByRequest(w, r, username, rememberme)
|
a.LoginUserByRequest(w, r, username, rememberme)
|
||||||
|
|
||||||
//Print the login message to console
|
//Print the login message to console
|
||||||
log.Println(username + " logged in.")
|
a.Logger.PrintAndLog("auth", username+" logged in.", nil)
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
} else {
|
} else {
|
||||||
//Password incorrect
|
//Password incorrect
|
||||||
log.Println(username + " login request rejected: " + rejectionReason)
|
a.Logger.PrintAndLog("auth", username+" login request rejected: "+rejectionReason, nil)
|
||||||
|
|
||||||
utils.SendErrorResponse(w, rejectionReason)
|
utils.SendErrorResponse(w, rejectionReason)
|
||||||
return
|
return
|
||||||
@ -147,7 +149,7 @@ func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, passw
|
|||||||
err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
|
err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//User not found or db exception
|
//User not found or db exception
|
||||||
log.Println("[Auth] " + username + " login with incorrect password")
|
a.Logger.PrintAndLog("auth", username+" login with incorrect password", nil)
|
||||||
return false, "Invalid username or password"
|
return false, "Invalid username or password"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,8 +186,12 @@ func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, u
|
|||||||
// Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
|
// Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
|
||||||
func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
|
func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
username, err := a.GetUserName(w, r)
|
username, err := a.GetUserName(w, r)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "user not logged in")
|
||||||
|
return
|
||||||
|
}
|
||||||
if username != "" {
|
if username != "" {
|
||||||
log.Println(username + " logged out.")
|
a.Logger.PrintAndLog("auth", username+" logged out", nil)
|
||||||
}
|
}
|
||||||
// Revoke users authentication
|
// Revoke users authentication
|
||||||
err = a.Logout(w, r)
|
err = a.Logout(w, r)
|
||||||
@ -194,7 +200,7 @@ func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write([]byte("OK"))
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
|
func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
|
||||||
@ -291,7 +297,7 @@ func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callb
|
|||||||
|
|
||||||
//Return to the client with OK
|
//Return to the client with OK
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
log.Println("[Auth] New user " + newusername + " added to system.")
|
a.Logger.PrintAndLog("auth", "New user "+newusername+" added to system.", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new user register without confirmation email. Require POST username, password, group.
|
// Handle new user register without confirmation email. Require POST username, password, group.
|
||||||
@ -324,7 +330,7 @@ func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
//Return to the client with OK
|
//Return to the client with OK
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
log.Println("[Auth] Admin account created: " + newusername)
|
a.Logger.PrintAndLog("auth", "Admin account created: "+newusername, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check authentication from request header's session value
|
// Check authentication from request header's session value
|
||||||
@ -365,7 +371,7 @@ func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
//Return to the client with OK
|
//Return to the client with OK
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
log.Println("[Auth] User " + username + " has been removed from the system.")
|
a.Logger.PrintAndLog("auth", "User "+username+" has been removed from the system", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthAgent) UnregisterUser(username string) error {
|
func (a *AuthAgent) UnregisterUser(username string) error {
|
||||||
@ -393,7 +399,7 @@ func (a *AuthAgent) GetUserCounts() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if usercount == 0 {
|
if usercount == 0 {
|
||||||
log.Println("There are no user in the database.")
|
a.Logger.PrintAndLog("auth", "There are no user in the database", nil)
|
||||||
}
|
}
|
||||||
return usercount
|
return usercount
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ func NewManagedHTTPRouter(option RouterOption) *RouterDef {
|
|||||||
func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error {
|
func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error {
|
||||||
//Check if the endpoint already registered
|
//Check if the endpoint already registered
|
||||||
if _, exist := router.endpoints[endpoint]; exist {
|
if _, exist := router.endpoints[endpoint]; exist {
|
||||||
log.Println("WARNING! Duplicated registering of web endpoint: " + endpoint)
|
fmt.Println("WARNING! Duplicated registering of web endpoint: " + endpoint)
|
||||||
return errors.New("endpoint register duplicated")
|
return errors.New("endpoint register duplicated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
- Access Router
|
- Access Router
|
||||||
- Blacklist
|
- Blacklist
|
||||||
- Whitelist
|
- Whitelist
|
||||||
|
- Rate Limitor
|
||||||
- Basic Auth
|
- Basic Auth
|
||||||
- Vitrual Directory Proxy
|
- Vitrual Directory Proxy
|
||||||
- Subdomain Proxy
|
- Subdomain Proxy
|
||||||
@ -30,7 +31,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
/*
|
/*
|
||||||
Special Routing Rules, bypass most of the limitations
|
Special Routing Rules, bypass most of the limitations
|
||||||
*/
|
*/
|
||||||
//Check if there are external routing rule matches.
|
//Check if there are external routing rule (rr) matches.
|
||||||
//If yes, route them via external rr
|
//If yes, route them via external rr
|
||||||
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
||||||
if matchedRoutingRule != nil {
|
if matchedRoutingRule != nil {
|
||||||
@ -45,7 +46,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.logRequest(r, statusCode != 500, statusCode, "redirect", "")
|
h.Parent.logRequest(r, statusCode != 500, statusCode, "redirect", "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +77,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", 429)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,6 +86,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if sep.RequireBasicAuth {
|
if sep.RequireBasicAuth {
|
||||||
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)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,6 +103,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)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,12 +197,12 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
hostname := parsedURL.Hostname()
|
hostname := parsedURL.Hostname()
|
||||||
if hostname == domainOnly {
|
if hostname == domainOnly {
|
||||||
h.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.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
|
||||||
|
@ -24,7 +24,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.logRequest(r, false, 403, blockedReason, "")
|
h.Parent.logRequest(r, false, 403, blockedReason, "")
|
||||||
}
|
}
|
||||||
return isBlocked
|
return isBlocked
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
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)
|
err := handleBasicAuth(w, r, pe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logRequest(r, false, 401, "host", pe.Domain)
|
h.Parent.logRequest(r, false, 401, "host", r.URL.Hostname())
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,13 @@ func (ept *ProxyEndpoint) SplitInboundOutboundHeaders() ([][]string, [][]string)
|
|||||||
|
|
||||||
//Check if the endpoint require HSTS headers
|
//Check if the endpoint require HSTS headers
|
||||||
if ept.HSTSMaxAge > 0 {
|
if ept.HSTSMaxAge > 0 {
|
||||||
|
if ept.ContainsWildcardName(true) {
|
||||||
|
//Endpoint listening domain includes wildcards.
|
||||||
|
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(ept.HSTSMaxAge)) + "; includeSubdomains"}
|
||||||
|
} else {
|
||||||
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(ept.HSTSMaxAge))}
|
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(ept.HSTSMaxAge))}
|
||||||
|
}
|
||||||
|
|
||||||
downstreamHeaderCounter++
|
downstreamHeaderCounter++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ type ResponseRewriteRuleSet struct {
|
|||||||
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
||||||
UpstreamHeaders [][]string
|
UpstreamHeaders [][]string
|
||||||
DownstreamHeaders [][]string
|
DownstreamHeaders [][]string
|
||||||
|
NoRemoveHopByHop bool //Do not remove hop-by-hop headers, dangerous
|
||||||
Version string //Version number of Zoraxy, use for X-Proxy-By
|
Version string //Version number of Zoraxy, use for X-Proxy-By
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +181,7 @@ var hopHeaders = []string{
|
|||||||
"Te", // canonicalized version of "TE"
|
"Te", // canonicalized version of "TE"
|
||||||
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
|
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
|
||||||
"Transfer-Encoding",
|
"Transfer-Encoding",
|
||||||
//"Upgrade",
|
//"Upgrade", // handled by websocket proxy in higher layer abstraction
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
|
// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
|
||||||
|
@ -3,6 +3,7 @@ package dpcore
|
|||||||
import (
|
import (
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,6 +18,12 @@ func (p *ReverseProxy) getFlushInterval(req *http.Request, res *http.Response) t
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixed issue #235: Added auto detection for ollama / llm output stream
|
||||||
|
connectionHeader := req.Header["Connection"]
|
||||||
|
if len(connectionHeader) > 0 && strings.Contains(strings.Join(connectionHeader, ","), "keep-alive") {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
//Cannot sniff anything. Use default value
|
//Cannot sniff anything. Use default value
|
||||||
return p.FlushInterval
|
return p.FlushInterval
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ func NewDynamicProxy(option RouterOption) (*Router, error) {
|
|||||||
Running: false,
|
Running: false,
|
||||||
server: nil,
|
server: nil,
|
||||||
routingRules: []*RoutingRule{},
|
routingRules: []*RoutingRule{},
|
||||||
|
loadBalancer: option.LoadBalancer,
|
||||||
rateLimitCounter: RequestCountPerIpTable{},
|
rateLimitCounter: RequestCountPerIpTable{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,10 +151,17 @@ func (router *Router) StartProxyService() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
selectedUpstream, err := router.loadBalancer.GetRequestUpstreamTarget(w, r, sep.ActiveOrigins, sep.UseStickySession)
|
||||||
ProxyDomain: sep.Domain,
|
if err != nil {
|
||||||
|
http.ServeFile(w, r, "./web/hosterror.html")
|
||||||
|
router.Option.Logger.PrintAndLog("dprouter", "failed to get upstream for hostname", err)
|
||||||
|
router.logRequest(r, false, 404, "vdir-http", r.Host)
|
||||||
|
}
|
||||||
|
selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
|
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: sep.RequireTLS,
|
UseTLS: selectedUpstream.RequireTLS,
|
||||||
|
NoRemoveHopByHop: sep.DisableHopByHopHeaderRemoval,
|
||||||
PathPrefix: "",
|
PathPrefix: "",
|
||||||
Version: sep.parent.Option.HostVersion,
|
Version: sep.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
@ -187,7 +195,7 @@ func (router *Router) StartProxyService() error {
|
|||||||
IdleTimeout: 120 * time.Second,
|
IdleTimeout: 120 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Starting HTTP-to-HTTPS redirector (port 80)")
|
router.Option.Logger.PrintAndLog("dprouter", "Starting HTTP-to-HTTPS redirector (port 80)", nil)
|
||||||
|
|
||||||
//Create a redirection stop channel
|
//Create a redirection stop channel
|
||||||
stopChan := make(chan bool)
|
stopChan := make(chan bool)
|
||||||
@ -198,7 +206,7 @@ func (router *Router) StartProxyService() error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
httpServer.Shutdown(ctx)
|
httpServer.Shutdown(ctx)
|
||||||
log.Println("HTTP to HTTPS redirection listener stopped")
|
router.Option.Logger.PrintAndLog("dprouter", "HTTP to HTTPS redirection listener stopped", nil)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
//Start the http server that listens to port 80 and redirect to 443
|
//Start the http server that listens to port 80 and redirect to 443
|
||||||
@ -213,10 +221,10 @@ func (router *Router) StartProxyService() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Start the TLS server
|
//Start the TLS server
|
||||||
log.Println("Reverse proxy service started in the background (TLS mode)")
|
router.Option.Logger.PrintAndLog("dprouter", "Reverse proxy service started in the background (TLS mode)", nil)
|
||||||
go func() {
|
go func() {
|
||||||
if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
|
if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
|
||||||
log.Fatalf("Could not start proxy server: %v\n", err)
|
router.Option.Logger.PrintAndLog("dprouter", "Could not start proxy server", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
@ -224,10 +232,9 @@ func (router *Router) StartProxyService() error {
|
|||||||
router.tlsListener = nil
|
router.tlsListener = nil
|
||||||
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
|
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
|
||||||
router.Running = true
|
router.Running = true
|
||||||
log.Println("Reverse proxy service started in the background (Plain HTTP mode)")
|
router.Option.Logger.PrintAndLog("dprouter", "Reverse proxy service started in the background (Plain HTTP mode)", nil)
|
||||||
go func() {
|
go func() {
|
||||||
router.server.ListenAndServe()
|
router.server.ListenAndServe()
|
||||||
//log.Println("[DynamicProxy] " + err.Error())
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -133,6 +134,116 @@ func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint)
|
|||||||
return readyRoutingRule, nil
|
return readyRoutingRule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Upstream related wrapper functions */
|
||||||
|
//Check if there already exists another upstream with identical origin
|
||||||
|
func (ep *ProxyEndpoint) UpstreamOriginExists(originURL string) bool {
|
||||||
|
for _, origin := range ep.ActiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain == originURL {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, origin := range ep.InactiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain == originURL {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a upstream origin from given origin ip or domain
|
||||||
|
func (ep *ProxyEndpoint) GetUpstreamOriginByMatchingIP(originIpOrDomain string) (*loadbalance.Upstream, error) {
|
||||||
|
for _, origin := range ep.ActiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain == originIpOrDomain {
|
||||||
|
return origin, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, origin := range ep.InactiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain == originIpOrDomain {
|
||||||
|
return origin, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("target upstream origin not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add upstream to endpoint and update it to runtime
|
||||||
|
func (ep *ProxyEndpoint) AddUpstreamOrigin(newOrigin *loadbalance.Upstream, activate bool) error {
|
||||||
|
//Check if the upstream already exists
|
||||||
|
if ep.UpstreamOriginExists(newOrigin.OriginIpOrDomain) {
|
||||||
|
return errors.New("upstream with same origin already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
if activate {
|
||||||
|
//Add it to the active origin list
|
||||||
|
err := newOrigin.StartProxy()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ep.ActiveOrigins = append(ep.ActiveOrigins, newOrigin)
|
||||||
|
} else {
|
||||||
|
//Add to inactive origin list
|
||||||
|
ep.InactiveOrigins = append(ep.InactiveOrigins, newOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
ep.UpdateToRuntime()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove upstream from endpoint and update it to runtime
|
||||||
|
func (ep *ProxyEndpoint) RemoveUpstreamOrigin(originIpOrDomain string) error {
|
||||||
|
//Just to make sure there are no spaces
|
||||||
|
originIpOrDomain = strings.TrimSpace(originIpOrDomain)
|
||||||
|
|
||||||
|
//Check if the upstream already been removed
|
||||||
|
if !ep.UpstreamOriginExists(originIpOrDomain) {
|
||||||
|
//Not exists in the first place
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newActiveOriginList := []*loadbalance.Upstream{}
|
||||||
|
for _, origin := range ep.ActiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain != originIpOrDomain {
|
||||||
|
newActiveOriginList = append(newActiveOriginList, origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newInactiveOriginList := []*loadbalance.Upstream{}
|
||||||
|
for _, origin := range ep.InactiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain != originIpOrDomain {
|
||||||
|
newInactiveOriginList = append(newInactiveOriginList, origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Ok, set the origin list to the new one
|
||||||
|
ep.ActiveOrigins = newActiveOriginList
|
||||||
|
ep.InactiveOrigins = newInactiveOriginList
|
||||||
|
ep.UpdateToRuntime()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the proxy endpoint hostname or alias name contains subdomain wildcard
|
||||||
|
func (ep *ProxyEndpoint) ContainsWildcardName(skipAliasCheck bool) bool {
|
||||||
|
hostname := ep.RootOrMatchingDomain
|
||||||
|
aliasHostnames := ep.MatchingDomainAlias
|
||||||
|
|
||||||
|
wildcardCheck := func(hostname string) bool {
|
||||||
|
return len(hostname) > 0 && hostname[0] == '*'
|
||||||
|
}
|
||||||
|
|
||||||
|
if wildcardCheck(hostname) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !skipAliasCheck {
|
||||||
|
for _, aliasHostname := range aliasHostnames {
|
||||||
|
if wildcardCheck(aliasHostname) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Create a deep clone object of the proxy endpoint
|
// Create a deep clone object of the proxy endpoint
|
||||||
// Note the returned object is not activated. Call to prepare function before pushing into runtime
|
// Note the returned object is not activated. Call to prepare function before pushing into runtime
|
||||||
func (ep *ProxyEndpoint) Clone() *ProxyEndpoint {
|
func (ep *ProxyEndpoint) Clone() *ProxyEndpoint {
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
package loadbalance
|
package loadbalance
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -12,49 +17,83 @@ import (
|
|||||||
Handleing load balance request for upstream destinations
|
Handleing load balance request for upstream destinations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type BalancePolicy int
|
|
||||||
|
|
||||||
const (
|
|
||||||
BalancePolicy_RoundRobin BalancePolicy = 0 //Round robin, will ignore upstream if down
|
|
||||||
BalancePolicy_Fallback BalancePolicy = 1 //Fallback only. Will only switch to next node if the first one failed
|
|
||||||
BalancePolicy_Random BalancePolicy = 2 //Random, randomly pick one from the list that is online
|
|
||||||
BalancePolicy_GeoRegion BalancePolicy = 3 //Use the one defined for this geo-location, when down, pick the next avaible node
|
|
||||||
)
|
|
||||||
|
|
||||||
type LoadBalanceRule struct {
|
|
||||||
Upstreams []string //Reverse proxy upstream servers
|
|
||||||
LoadBalancePolicy BalancePolicy //Policy in deciding which target IP to proxy
|
|
||||||
UseRegionLock bool //If this is enabled with BalancePolicy_Geo, when the main site failed, it will not pick another node
|
|
||||||
UseStickySession bool //Use sticky session, if you are serving EU countries, make sure to add the "Do you want cookie" warning
|
|
||||||
|
|
||||||
parent *RouteManager
|
|
||||||
}
|
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
SystemUUID string //Use for the session store
|
||||||
|
UseActiveHealthCheck bool //Use active health check, default to false
|
||||||
Geodb *geodb.Store //GeoIP resolver for checking incoming request origin country
|
Geodb *geodb.Store //GeoIP resolver for checking incoming request origin country
|
||||||
UptimeMonitor *uptime.Monitor //For checking if the target is online, this might be nil when the module starts
|
|
||||||
}
|
|
||||||
|
|
||||||
type RouteManager struct {
|
|
||||||
Options Options
|
|
||||||
Logger *logger.Logger
|
Logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new load balance route manager
|
type RouteManager struct {
|
||||||
func NewRouteManager(options *Options, logger *logger.Logger) *RouteManager {
|
SessionStore *sessions.CookieStore
|
||||||
newManager := RouteManager{
|
LoadBalanceMap sync.Map //Sync map to store the last load balance state of a given node
|
||||||
Options: *options,
|
OnlineStatusMap sync.Map //Sync map to store the online status of a given ip address or domain name
|
||||||
Logger: logger,
|
onlineStatusTickerStop chan bool //Stopping channel for the online status pinger
|
||||||
}
|
Options Options //Options for the load balancer
|
||||||
logger.PrintAndLog("INFO", "Load Balance Route Manager started", nil)
|
}
|
||||||
return &newManager
|
|
||||||
|
/* Upstream or Origin Server */
|
||||||
|
type Upstream struct {
|
||||||
|
//Upstream Proxy Configs
|
||||||
|
OriginIpOrDomain string //Target IP address or domain name with port
|
||||||
|
RequireTLS bool //Require TLS connection
|
||||||
|
SkipCertValidations bool //Set to true to accept self signed certs
|
||||||
|
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||||
|
|
||||||
|
//Load balancing configs
|
||||||
|
Weight int //Random weight for round robin, 0 for fallback only
|
||||||
|
MaxConn int //TODO: Maxmium connection to this server, 0 for unlimited
|
||||||
|
|
||||||
|
//currentConnectionCounts atomic.Uint64 //Counter for number of client currently connected
|
||||||
|
proxy *dpcore.ReverseProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new load balancer
|
||||||
|
func NewLoadBalancer(options *Options) *RouteManager {
|
||||||
|
if options.SystemUUID == "" {
|
||||||
|
//System UUID not passed in. Use random key
|
||||||
|
options.SystemUUID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate a session store for stickySession
|
||||||
|
store := sessions.NewCookieStore([]byte(options.SystemUUID))
|
||||||
|
return &RouteManager{
|
||||||
|
SessionStore: store,
|
||||||
|
LoadBalanceMap: sync.Map{},
|
||||||
|
OnlineStatusMap: sync.Map{},
|
||||||
|
onlineStatusTickerStop: nil,
|
||||||
|
Options: *options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamsReady checks if the group of upstreams contains at least one
|
||||||
|
// origin server that is ready
|
||||||
|
func (m *RouteManager) UpstreamsReady(upstreams []*Upstream) bool {
|
||||||
|
for _, upstream := range upstreams {
|
||||||
|
if upstream.IsReady() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// String format and convert a list of upstream into a string representations
|
||||||
|
func GetUpstreamsAsString(upstreams []*Upstream) string {
|
||||||
|
targets := []string{}
|
||||||
|
for _, upstream := range upstreams {
|
||||||
|
targets = append(targets, upstream.String())
|
||||||
|
}
|
||||||
|
return strings.Join(targets, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RouteManager) Close() {
|
||||||
|
if m.onlineStatusTickerStop != nil {
|
||||||
|
m.onlineStatusTickerStop <- true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LoadBalanceRule) GetProxyTargetIP() {
|
|
||||||
//TODO: Implement get proxy target IP logic here
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print debug message
|
// Print debug message
|
||||||
func (m *RouteManager) debugPrint(message string, err error) {
|
func (m *RouteManager) debugPrint(message string, err error) {
|
||||||
m.Logger.PrintAndLog("LB", message, err)
|
m.Options.Logger.PrintAndLog("LoadBalancer", message, err)
|
||||||
}
|
}
|
||||||
|
39
src/mod/dynamicproxy/loadbalance/onlineStatus.go
Normal file
39
src/mod/dynamicproxy/loadbalance/onlineStatus.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package loadbalance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return the last ping status to see if the target is online
|
||||||
|
func (m *RouteManager) IsTargetOnline(matchingDomainOrIp string) bool {
|
||||||
|
value, ok := m.LoadBalanceMap.Load(matchingDomainOrIp)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
isOnline, ok := value.(bool)
|
||||||
|
return ok && isOnline
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping a target to see if it is online
|
||||||
|
func PingTarget(targetMatchingDomainOrIp string, requireTLS bool) bool {
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
url := targetMatchingDomainOrIp
|
||||||
|
if requireTLS {
|
||||||
|
url = "https://" + url
|
||||||
|
} else {
|
||||||
|
url = "http://" + url
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
return resp.StatusCode >= 200 && resp.StatusCode <= 600
|
||||||
|
}
|
154
src/mod/dynamicproxy/loadbalance/originPicker.go
Normal file
154
src/mod/dynamicproxy/loadbalance/originPicker.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package loadbalance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Origin Picker
|
||||||
|
|
||||||
|
This script contains the code to pick the best origin
|
||||||
|
by this request.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// GetRequestUpstreamTarget return the upstream target where this
|
||||||
|
// request should be routed
|
||||||
|
func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.Request, origins []*Upstream, useStickySession bool) (*Upstream, error) {
|
||||||
|
if len(origins) == 0 {
|
||||||
|
return nil, errors.New("no upstream is defined for this host")
|
||||||
|
}
|
||||||
|
var targetOrigin = origins[0]
|
||||||
|
if useStickySession {
|
||||||
|
//Use stick session, check which origins this request previously used
|
||||||
|
targetOriginId, err := m.getSessionHandler(r, origins)
|
||||||
|
if err != nil {
|
||||||
|
//No valid session found. Assign a new upstream
|
||||||
|
targetOrigin, index, err := getRandomUpstreamByWeight(origins)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Oops. Unable to get random upstream")
|
||||||
|
targetOrigin = origins[0]
|
||||||
|
index = 0
|
||||||
|
}
|
||||||
|
m.setSessionHandler(w, r, targetOrigin.OriginIpOrDomain, index)
|
||||||
|
return targetOrigin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Valid session found. Resume the previous session
|
||||||
|
return origins[targetOriginId], nil
|
||||||
|
} else {
|
||||||
|
//Do not use stick session. Get a random one
|
||||||
|
var err error
|
||||||
|
targetOrigin, _, err = getRandomUpstreamByWeight(origins)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
targetOrigin = origins[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Println("DEBUG: Picking origin " + targetOrigin.OriginIpOrDomain)
|
||||||
|
return targetOrigin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Features related to session access */
|
||||||
|
//Set a new origin for this connection by session
|
||||||
|
func (m *RouteManager) setSessionHandler(w http.ResponseWriter, r *http.Request, originIpOrDomain string, index int) error {
|
||||||
|
session, err := m.SessionStore.Get(r, "STICKYSESSION")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
session.Values["zr_sid_origin"] = originIpOrDomain
|
||||||
|
session.Values["zr_sid_index"] = index
|
||||||
|
session.Options.MaxAge = 86400 //1 day
|
||||||
|
session.Options.Path = "/"
|
||||||
|
err = session.Save(r, w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the previous connected origin from session
|
||||||
|
func (m *RouteManager) getSessionHandler(r *http.Request, upstreams []*Upstream) (int, error) {
|
||||||
|
// Get existing session
|
||||||
|
session, err := m.SessionStore.Get(r, "STICKYSESSION")
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve session values for origin
|
||||||
|
originDomainRaw := session.Values["zr_sid_origin"]
|
||||||
|
originIDRaw := session.Values["zr_sid_index"]
|
||||||
|
|
||||||
|
if originDomainRaw == nil || originIDRaw == nil {
|
||||||
|
return -1, errors.New("no session has been set")
|
||||||
|
}
|
||||||
|
originDomain := originDomainRaw.(string)
|
||||||
|
originID := originIDRaw.(int)
|
||||||
|
|
||||||
|
//Check if it has been modified
|
||||||
|
if len(upstreams) < originID || upstreams[originID].OriginIpOrDomain != originDomain {
|
||||||
|
//Mismatch or upstreams has been updated
|
||||||
|
return -1, errors.New("upstreams has been changed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return originID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Functions related to random upstream picking */
|
||||||
|
// Get a random upstream by the weights defined in Upstream struct, return the upstream, index value and any error
|
||||||
|
func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) {
|
||||||
|
var ret *Upstream
|
||||||
|
sum := 0
|
||||||
|
for _, c := range upstreams {
|
||||||
|
sum += c.Weight
|
||||||
|
}
|
||||||
|
r, err := intRange(0, sum)
|
||||||
|
if err != nil {
|
||||||
|
return ret, -1, err
|
||||||
|
}
|
||||||
|
counter := 0
|
||||||
|
for _, c := range upstreams {
|
||||||
|
r -= c.Weight
|
||||||
|
if r < 0 {
|
||||||
|
return c, counter, nil
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret == nil {
|
||||||
|
//All fallback
|
||||||
|
//use the first one that is with weight = 0
|
||||||
|
fallbackUpstreams := []*Upstream{}
|
||||||
|
fallbackUpstreamsOriginalID := []int{}
|
||||||
|
for ix, upstream := range upstreams {
|
||||||
|
if upstream.Weight == 0 {
|
||||||
|
fallbackUpstreams = append(fallbackUpstreams, upstream)
|
||||||
|
fallbackUpstreamsOriginalID = append(fallbackUpstreamsOriginalID, ix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
upstreamID := rand.Intn(len(fallbackUpstreams))
|
||||||
|
return fallbackUpstreams[upstreamID], fallbackUpstreamsOriginalID[upstreamID], nil
|
||||||
|
}
|
||||||
|
return ret, -1, errors.New("failed to pick an upstream origin server")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntRange returns a random integer in the range from min to max.
|
||||||
|
func intRange(min, max int) (int, error) {
|
||||||
|
var result int
|
||||||
|
switch {
|
||||||
|
case min > max:
|
||||||
|
// Fail with error
|
||||||
|
return result, errors.New("min is greater than max")
|
||||||
|
case max == min:
|
||||||
|
result = max
|
||||||
|
case max > min:
|
||||||
|
b := rand.Intn(max-min) + min
|
||||||
|
result = min + int(b)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
77
src/mod/dynamicproxy/loadbalance/upstream.go
Normal file
77
src/mod/dynamicproxy/loadbalance/upstream.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package loadbalance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartProxy create and start a HTTP proxy using dpcore
|
||||||
|
// Example of webProxyEndpoint: https://example.com:443 or http://192.168.1.100:8080
|
||||||
|
func (u *Upstream) StartProxy() error {
|
||||||
|
//Filter the tailing slash if any
|
||||||
|
domain := u.OriginIpOrDomain
|
||||||
|
if len(domain) == 0 {
|
||||||
|
return errors.New("invalid endpoint config")
|
||||||
|
}
|
||||||
|
if domain[len(domain)-1:] == "/" {
|
||||||
|
domain = domain[:len(domain)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
||||||
|
//TLS is not hardcoded in proxy target domain
|
||||||
|
if u.RequireTLS {
|
||||||
|
domain = "https://" + domain
|
||||||
|
} else {
|
||||||
|
domain = "http://" + domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create a new proxy agent for this upstream
|
||||||
|
path, err := url.Parse(domain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
|
||||||
|
IgnoreTLSVerification: u.SkipCertValidations,
|
||||||
|
FlushInterval: 100 * time.Millisecond,
|
||||||
|
})
|
||||||
|
|
||||||
|
u.proxy = proxy
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsReady return the proxy ready state of the upstream server
|
||||||
|
// Return false if StartProxy() is not called on this upstream before
|
||||||
|
func (u *Upstream) IsReady() bool {
|
||||||
|
return u.proxy != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone return a new deep copy object of the identical upstream
|
||||||
|
func (u *Upstream) Clone() *Upstream {
|
||||||
|
newUpstream := Upstream{}
|
||||||
|
js, _ := json.Marshal(u)
|
||||||
|
json.Unmarshal(js, &newUpstream)
|
||||||
|
return &newUpstream
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP uses this upstream proxy router to route the current request
|
||||||
|
func (u *Upstream) ServeHTTP(w http.ResponseWriter, r *http.Request, rrr *dpcore.ResponseRewriteRuleSet) error {
|
||||||
|
//Auto rewrite to upstream origin if not set
|
||||||
|
if rrr.ProxyDomain == "" {
|
||||||
|
rrr.ProxyDomain = u.OriginIpOrDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.proxy.ServeHTTP(w, r, rrr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String return the string representations of endpoints in this upstream
|
||||||
|
func (u *Upstream) String() string {
|
||||||
|
return u.OriginIpOrDomain
|
||||||
|
}
|
@ -16,6 +16,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/websocketproxy"
|
"imuslab.com/zoraxy/mod/websocketproxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Check if the request URI matches any of the proxy endpoint
|
||||||
func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
|
func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
|
||||||
var targetProxyEndpoint *ProxyEndpoint = nil
|
var targetProxyEndpoint *ProxyEndpoint = nil
|
||||||
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||||
@ -30,6 +31,7 @@ func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *P
|
|||||||
return targetProxyEndpoint
|
return targetProxyEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the proxy endpoint from hostname, which might includes checking of wildcard certificates
|
||||||
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
|
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
|
||||||
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
||||||
ep, ok := router.ProxyEndpoints.Load(hostname)
|
ep, ok := router.ProxyEndpoints.Load(hostname)
|
||||||
@ -110,12 +112,18 @@ 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)
|
||||||
|
selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession)
|
||||||
|
if err != nil {
|
||||||
|
http.ServeFile(w, r, "./web/rperror.html")
|
||||||
|
log.Println(err.Error())
|
||||||
|
h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname())
|
||||||
|
return
|
||||||
|
}
|
||||||
requestURL := r.URL.String()
|
requestURL := r.URL.String()
|
||||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||||
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||||
wsRedirectionEndpoint := target.Domain
|
wsRedirectionEndpoint := selectedUpstream.OriginIpOrDomain
|
||||||
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
||||||
//Append / to the end of the redirection endpoint if not exists
|
//Append / to the end of the redirection endpoint if not exists
|
||||||
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
|
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
|
||||||
@ -125,13 +133,13 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
requestURL = requestURL[1:]
|
requestURL = requestURL[1:]
|
||||||
}
|
}
|
||||||
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL)
|
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL)
|
||||||
if target.RequireTLS {
|
if selectedUpstream.RequireTLS {
|
||||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
||||||
}
|
}
|
||||||
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
|
h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain)
|
||||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
SkipTLSValidation: target.SkipCertValidations,
|
SkipTLSValidation: selectedUpstream.SkipCertValidations,
|
||||||
SkipOriginCheck: target.SkipWebSocketOriginCheck,
|
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
|
||||||
})
|
})
|
||||||
wspHandler.ServeHTTP(w, r)
|
wspHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
@ -148,14 +156,15 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
//Build downstream and upstream header rules
|
//Build downstream and upstream header rules
|
||||||
upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()
|
upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()
|
||||||
|
|
||||||
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
err = selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: target.Domain,
|
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: target.RequireTLS,
|
UseTLS: selectedUpstream.RequireTLS,
|
||||||
NoCache: h.Parent.Option.NoCache,
|
NoCache: h.Parent.Option.NoCache,
|
||||||
PathPrefix: "",
|
PathPrefix: "",
|
||||||
UpstreamHeaders: upstreamHeaders,
|
UpstreamHeaders: upstreamHeaders,
|
||||||
DownstreamHeaders: downstreamHeaders,
|
DownstreamHeaders: downstreamHeaders,
|
||||||
|
NoRemoveHopByHop: target.DisableHopByHopHeaderRemoval,
|
||||||
Version: target.parent.Option.HostVersion,
|
Version: target.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -164,15 +173,15 @@ func (h *ProxyHandler) hostRequest(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.logRequest(r, false, 404, "subdomain-http", target.Domain)
|
h.Parent.logRequest(r, false, 404, "host-http", r.URL.Hostname())
|
||||||
} 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.logRequest(r, false, 521, "subdomain-http", target.Domain)
|
h.Parent.logRequest(r, false, 521, "host-http", r.URL.Hostname())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h.logRequest(r, true, 200, "subdomain-http", target.Domain)
|
h.Parent.logRequest(r, true, 200, "host-http", r.URL.Hostname())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle vdir type request
|
// Handle vdir type request
|
||||||
@ -194,10 +203,10 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
if target.RequireTLS {
|
if target.RequireTLS {
|
||||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
||||||
}
|
}
|
||||||
h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
h.Parent.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
SkipTLSValidation: target.SkipCertValidations,
|
SkipTLSValidation: target.SkipCertValidations,
|
||||||
SkipOriginCheck: target.parent.SkipWebSocketOriginCheck,
|
SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
|
||||||
})
|
})
|
||||||
wspHandler.ServeHTTP(w, r)
|
wspHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
@ -229,23 +238,24 @@ 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.logRequest(r, false, 404, "vdir-http", target.Domain)
|
h.Parent.logRequest(r, false, 404, "vdir-http", 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.logRequest(r, false, 521, "vdir-http", target.Domain)
|
h.Parent.logRequest(r, false, 521, "vdir-http", target.Domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.logRequest(r, true, 200, "vdir-http", target.Domain)
|
h.Parent.logRequest(r, true, 200, "vdir-http", target.Domain)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) {
|
// This logger collect data for the statistical analysis. For log to file logger, check the Logger and LogHTTPRequest handler
|
||||||
if h.Parent.Option.StatisticCollector != nil {
|
func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) {
|
||||||
|
if router.Option.StatisticCollector != nil {
|
||||||
go func() {
|
go func() {
|
||||||
requestInfo := statistic.RequestInfo{
|
requestInfo := statistic.RequestInfo{
|
||||||
IpAddr: netutils.GetRequesterIP(r),
|
IpAddr: netutils.GetRequesterIP(r),
|
||||||
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
RequestOriginalCountryISOCode: router.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||||
Succ: succ,
|
Succ: succ,
|
||||||
StatusCode: statusCode,
|
StatusCode: statusCode,
|
||||||
ForwardType: forwardType,
|
ForwardType: forwardType,
|
||||||
@ -254,7 +264,8 @@ func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, fo
|
|||||||
RequestURL: r.Host + r.RequestURI,
|
RequestURL: r.Host + r.RequestURI,
|
||||||
Target: target,
|
Target: target,
|
||||||
}
|
}
|
||||||
h.Parent.Option.StatisticCollector.RecordRequest(requestInfo)
|
router.Option.StatisticCollector.RecordRequest(requestInfo)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
router.Option.Logger.LogHTTPRequest(r, forwardType, statusCode)
|
||||||
}
|
}
|
||||||
|
@ -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.logRequest(r, false, 429, "ratelimit", pe.Domain)
|
h.Parent.logRequest(r, false, 429, "ratelimit", r.URL.Hostname())
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,10 @@ package dynamicproxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
)
|
)
|
||||||
@ -17,41 +19,18 @@ import (
|
|||||||
|
|
||||||
// Prepare proxy route generate a proxy handler service object for your endpoint
|
// Prepare proxy route generate a proxy handler service object for your endpoint
|
||||||
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
||||||
//Filter the tailing slash if any
|
for _, thisOrigin := range endpoint.ActiveOrigins {
|
||||||
domain := endpoint.Domain
|
|
||||||
if len(domain) == 0 {
|
|
||||||
return nil, errors.New("invalid endpoint config")
|
|
||||||
}
|
|
||||||
if domain[len(domain)-1:] == "/" {
|
|
||||||
domain = domain[:len(domain)-1]
|
|
||||||
}
|
|
||||||
endpoint.Domain = domain
|
|
||||||
|
|
||||||
//Parse the web proxy endpoint
|
|
||||||
webProxyEndpoint := domain
|
|
||||||
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
|
||||||
//TLS is not hardcoded in proxy target domain
|
|
||||||
if endpoint.RequireTLS {
|
|
||||||
webProxyEndpoint = "https://" + webProxyEndpoint
|
|
||||||
} else {
|
|
||||||
webProxyEndpoint = "http://" + webProxyEndpoint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create a new proxy agent for this root
|
|
||||||
path, err := url.Parse(webProxyEndpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create the proxy routing handler
|
//Create the proxy routing handler
|
||||||
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
|
err := thisOrigin.StartProxy()
|
||||||
IgnoreTLSVerification: endpoint.SkipCertValidations,
|
if err != nil {
|
||||||
})
|
log.Println("Unable to setup upstream " + thisOrigin.OriginIpOrDomain + ": " + err.Error())
|
||||||
endpoint.proxy = proxy
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
endpoint.parent = router
|
endpoint.parent = router
|
||||||
|
|
||||||
//Prepare proxy routing hjandler for each of the virtual directories
|
//Prepare proxy routing handler for each of the virtual directories
|
||||||
for _, vdir := range endpoint.VirtualDirectories {
|
for _, vdir := range endpoint.VirtualDirectories {
|
||||||
domain := vdir.Domain
|
domain := vdir.Domain
|
||||||
if len(domain) == 0 {
|
if len(domain) == 0 {
|
||||||
@ -63,7 +42,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Parse the web proxy endpoint
|
//Parse the web proxy endpoint
|
||||||
webProxyEndpoint = domain
|
webProxyEndpoint := domain
|
||||||
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
||||||
//TLS is not hardcoded in proxy target domain
|
//TLS is not hardcoded in proxy target domain
|
||||||
if vdir.RequireTLS {
|
if vdir.RequireTLS {
|
||||||
@ -80,6 +59,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
|||||||
|
|
||||||
proxy := dpcore.NewDynamicProxyCore(path, vdir.MatchingPath, &dpcore.DpcoreOptions{
|
proxy := dpcore.NewDynamicProxyCore(path, vdir.MatchingPath, &dpcore.DpcoreOptions{
|
||||||
IgnoreTLSVerification: vdir.SkipCertValidations,
|
IgnoreTLSVerification: vdir.SkipCertValidations,
|
||||||
|
FlushInterval: 500 * time.Millisecond,
|
||||||
})
|
})
|
||||||
vdir.proxy = proxy
|
vdir.proxy = proxy
|
||||||
vdir.parent = endpoint
|
vdir.parent = endpoint
|
||||||
@ -90,7 +70,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
|||||||
|
|
||||||
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
|
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
|
||||||
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
||||||
if endpoint.proxy == nil {
|
if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
|
||||||
//This endpoint is not prepared
|
//This endpoint is not prepared
|
||||||
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
||||||
}
|
}
|
||||||
@ -101,7 +81,7 @@ func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
|||||||
|
|
||||||
// Set given Proxy Route as Root. Call to PrepareProxyRoute before adding to runtime
|
// Set given Proxy Route as Root. Call to PrepareProxyRoute before adding to runtime
|
||||||
func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error {
|
func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error {
|
||||||
if endpoint.proxy == nil {
|
if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
|
||||||
//This endpoint is not prepared
|
//This endpoint is not prepared
|
||||||
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,11 @@ import (
|
|||||||
|
|
||||||
"imuslab.com/zoraxy/mod/access"
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/statistic"
|
"imuslab.com/zoraxy/mod/statistic"
|
||||||
"imuslab.com/zoraxy/mod/tlscert"
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
)
|
)
|
||||||
@ -25,6 +27,7 @@ type ProxyHandler struct {
|
|||||||
Parent *Router
|
Parent *Router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Router Object Options */
|
||||||
type RouterOption struct {
|
type RouterOption struct {
|
||||||
HostUUID string //The UUID of Zoraxy, use for heading mod
|
HostUUID string //The UUID of Zoraxy, use for heading mod
|
||||||
HostVersion string //The version of Zoraxy, use for heading mod
|
HostVersion string //The version of Zoraxy, use for heading mod
|
||||||
@ -34,14 +37,17 @@ type RouterOption struct {
|
|||||||
NoCache bool //Force set Cache-Control: no-store
|
NoCache bool //Force set Cache-Control: no-store
|
||||||
ListenOnPort80 bool //Enable port 80 http listener
|
ListenOnPort80 bool //Enable port 80 http listener
|
||||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||||
TlsManager *tlscert.Manager
|
TlsManager *tlscert.Manager //TLS manager for serving SAN certificates
|
||||||
RedirectRuleTable *redirection.RuleTable
|
RedirectRuleTable *redirection.RuleTable //Redirection rules handler and table
|
||||||
GeodbStore *geodb.Store //GeoIP resolver
|
GeodbStore *geodb.Store //GeoIP resolver
|
||||||
AccessController *access.Controller //Blacklist / whitelist controller
|
AccessController *access.Controller //Blacklist / whitelist controller
|
||||||
StatisticCollector *statistic.Collector
|
StatisticCollector *statistic.Collector //Statistic collector for storing stats on incoming visitors
|
||||||
WebDirectory string //The static web server directory containing the templates folder
|
WebDirectory string //The static web server directory containing the templates folder
|
||||||
|
LoadBalancer *loadbalance.RouteManager //Load balancer that handle load balancing of proxy target
|
||||||
|
Logger *logger.Logger //Logger for reverse proxy requets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Router Object */
|
||||||
type Router struct {
|
type Router struct {
|
||||||
Option *RouterOption
|
Option *RouterOption
|
||||||
ProxyEndpoints *sync.Map
|
ProxyEndpoints *sync.Map
|
||||||
@ -50,6 +56,7 @@ type Router struct {
|
|||||||
mux http.Handler
|
mux http.Handler
|
||||||
server *http.Server
|
server *http.Server
|
||||||
tlsListener net.Listener
|
tlsListener net.Listener
|
||||||
|
loadBalancer *loadbalance.RouteManager //Load balancer routing manager
|
||||||
routingRules []*RoutingRule
|
routingRules []*RoutingRule
|
||||||
|
|
||||||
tlsRedirectStop chan bool //Stop channel for tls redirection server
|
tlsRedirectStop chan bool //Stop channel for tls redirection server
|
||||||
@ -57,6 +64,7 @@ type Router struct {
|
|||||||
rateLimitCounter RequestCountPerIpTable //Request counter for rate limter
|
rateLimitCounter RequestCountPerIpTable //Request counter for rate limter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Basic Auth Related Data structure*/
|
||||||
// Auth credential for basic auth on certain endpoints
|
// Auth credential for basic auth on certain endpoints
|
||||||
type BasicAuthCredentials struct {
|
type BasicAuthCredentials struct {
|
||||||
Username string
|
Username string
|
||||||
@ -74,6 +82,7 @@ type BasicAuthExceptionRule struct {
|
|||||||
PathPrefix string
|
PathPrefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Header Related Data structure */
|
||||||
// Header injection direction type
|
// Header injection direction type
|
||||||
type HeaderDirection int
|
type HeaderDirection int
|
||||||
|
|
||||||
@ -90,6 +99,8 @@ type UserDefinedHeader struct {
|
|||||||
IsRemove bool //Instead of set, remove this key instead
|
IsRemove bool //Instead of set, remove this key instead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Routing Rule Data Structures */
|
||||||
|
|
||||||
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||||
// program structure than directly using ProxyEndpoint
|
// program structure than directly using ProxyEndpoint
|
||||||
type VirtualDirectoryEndpoint struct {
|
type VirtualDirectoryEndpoint struct {
|
||||||
@ -107,13 +118,14 @@ type ProxyEndpoint struct {
|
|||||||
ProxyType int //The type of this proxy, see const def
|
ProxyType int //The type of this proxy, see const def
|
||||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||||
Domain string //Domain or IP to proxy to
|
ActiveOrigins []*loadbalance.Upstream //Activated Upstream or origin servers IP or domain to proxy to
|
||||||
|
InactiveOrigins []*loadbalance.Upstream //Disabled Upstream or origin servers IP or domain to proxy to
|
||||||
|
UseStickySession bool //Use stick session for load balancing
|
||||||
|
UseActiveLoadBalance bool //Use active loadbalancing, default passive
|
||||||
|
Disabled bool //If the rule is disabled
|
||||||
|
|
||||||
//TLS/SSL Related
|
//Inbound TLS/SSL Related
|
||||||
RequireTLS bool //Target domain require TLS
|
|
||||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||||
SkipCertValidations bool //Set to true to accept self signed certs
|
|
||||||
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
|
||||||
|
|
||||||
//Virtual Directories
|
//Virtual Directories
|
||||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||||
@ -123,6 +135,7 @@ type ProxyEndpoint struct {
|
|||||||
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||||
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||||
PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header
|
PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header
|
||||||
|
DisableHopByHopHeaderRemoval bool //TODO: Do not remove hop-by-hop headers
|
||||||
|
|
||||||
//Authentication
|
//Authentication
|
||||||
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||||
@ -136,15 +149,12 @@ type ProxyEndpoint struct {
|
|||||||
//Access Control
|
//Access Control
|
||||||
AccessFilterUUID string //Access filter ID
|
AccessFilterUUID string //Access filter ID
|
||||||
|
|
||||||
Disabled bool //If the rule is disabled
|
|
||||||
|
|
||||||
//Fallback routing logic (Special Rule Sets Only)
|
//Fallback routing logic (Special Rule Sets Only)
|
||||||
DefaultSiteOption int //Fallback routing logic options
|
DefaultSiteOption int //Fallback routing logic options
|
||||||
DefaultSiteValue string //Fallback routing target, optional
|
DefaultSiteValue string //Fallback routing target, optional
|
||||||
|
|
||||||
//Internal Logic Elements
|
//Internal Logic Elements
|
||||||
parent *Router `json:"-"`
|
parent *Router `json:"-"`
|
||||||
proxy *dpcore.ReverseProxy `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -13,29 +13,31 @@ import (
|
|||||||
Zoraxy Logger
|
Zoraxy Logger
|
||||||
|
|
||||||
This script is designed to make a managed log for the Zoraxy
|
This script is designed to make a managed log for the Zoraxy
|
||||||
and replace the ton of log.Println in the system core
|
and replace the ton of log.Println in the system core.
|
||||||
|
The core logger is based in golang's build-in log package
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
LogToFile bool //Set enable write to file
|
|
||||||
Prefix string //Prefix for log files
|
Prefix string //Prefix for log files
|
||||||
LogFolder string //Folder to store the log file
|
LogFolder string //Folder to store the log file
|
||||||
CurrentLogFile string //Current writing filename
|
CurrentLogFile string //Current writing filename
|
||||||
|
logger *log.Logger
|
||||||
file *os.File
|
file *os.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger, error) {
|
// Create a new logger that log to files
|
||||||
|
func NewLogger(logFilePrefix string, logFolder string) (*Logger, error) {
|
||||||
err := os.MkdirAll(logFolder, 0775)
|
err := os.MkdirAll(logFolder, 0775)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
thisLogger := Logger{
|
thisLogger := Logger{
|
||||||
LogToFile: logToFile,
|
|
||||||
Prefix: logFilePrefix,
|
Prefix: logFilePrefix,
|
||||||
LogFolder: logFolder,
|
LogFolder: logFolder,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Create the log file if not exists
|
||||||
logFilePath := thisLogger.getLogFilepath()
|
logFilePath := thisLogger.getLogFilepath()
|
||||||
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
|
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -43,9 +45,26 @@ func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger,
|
|||||||
}
|
}
|
||||||
thisLogger.CurrentLogFile = logFilePath
|
thisLogger.CurrentLogFile = logFilePath
|
||||||
thisLogger.file = f
|
thisLogger.file = f
|
||||||
|
|
||||||
|
//Start the logger
|
||||||
|
logger := log.New(f, "", log.Flags()&^(log.Ldate|log.Ltime))
|
||||||
|
logger.SetFlags(0)
|
||||||
|
logger.SetOutput(f)
|
||||||
|
thisLogger.logger = logger
|
||||||
return &thisLogger, nil
|
return &thisLogger, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a fmt logger that only log to STDOUT
|
||||||
|
func NewFmtLogger() (*Logger, error) {
|
||||||
|
return &Logger{
|
||||||
|
Prefix: "",
|
||||||
|
LogFolder: "",
|
||||||
|
CurrentLogFile: "",
|
||||||
|
logger: nil,
|
||||||
|
file: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Logger) getLogFilepath() string {
|
func (l *Logger) getLogFilepath() string {
|
||||||
year, month, _ := time.Now().Date()
|
year, month, _ := time.Now().Date()
|
||||||
return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log")
|
return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log")
|
||||||
@ -54,9 +73,8 @@ func (l *Logger) getLogFilepath() string {
|
|||||||
// PrintAndLog will log the message to file and print the log to STDOUT
|
// PrintAndLog will log the message to file and print the log to STDOUT
|
||||||
func (l *Logger) PrintAndLog(title string, message string, originalError error) {
|
func (l *Logger) PrintAndLog(title string, message string, originalError error) {
|
||||||
go func() {
|
go func() {
|
||||||
l.Log(title, message, originalError)
|
l.Log(title, message, originalError, true)
|
||||||
}()
|
}()
|
||||||
log.Println("[" + title + "] " + message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Println is a fast snap-in replacement for log.Println
|
// Println is a fast snap-in replacement for log.Println
|
||||||
@ -64,18 +82,26 @@ func (l *Logger) Println(v ...interface{}) {
|
|||||||
//Convert the array of interfaces into string
|
//Convert the array of interfaces into string
|
||||||
message := fmt.Sprint(v...)
|
message := fmt.Sprint(v...)
|
||||||
go func() {
|
go func() {
|
||||||
l.Log("info", string(message), nil)
|
l.Log("internal", string(message), nil, true)
|
||||||
}()
|
}()
|
||||||
log.Println("[INFO] " + string(message))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Log(title string, errorMessage string, originalError error) {
|
func (l *Logger) Log(title string, errorMessage string, originalError error, copyToSTDOUT bool) {
|
||||||
l.ValidateAndUpdateLogFilepath()
|
l.ValidateAndUpdateLogFilepath()
|
||||||
if l.LogToFile {
|
if l.logger == nil || copyToSTDOUT {
|
||||||
|
//Use STDOUT instead of logger
|
||||||
if originalError == nil {
|
if originalError == nil {
|
||||||
l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [INFO]" + errorMessage + "\n")
|
fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
|
||||||
} else {
|
} else {
|
||||||
l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [ERROR]" + errorMessage + " " + originalError.Error() + "\n")
|
fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:error] " + errorMessage + ": " + originalError.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.logger != nil {
|
||||||
|
if originalError == nil {
|
||||||
|
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
|
||||||
|
} else {
|
||||||
|
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:error] " + errorMessage + ": " + originalError.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,18 +109,28 @@ func (l *Logger) Log(title string, errorMessage string, originalError error) {
|
|||||||
|
|
||||||
// Validate if the logging target is still valid (detect any months change)
|
// Validate if the logging target is still valid (detect any months change)
|
||||||
func (l *Logger) ValidateAndUpdateLogFilepath() {
|
func (l *Logger) ValidateAndUpdateLogFilepath() {
|
||||||
|
if l.file == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
expectedCurrentLogFilepath := l.getLogFilepath()
|
expectedCurrentLogFilepath := l.getLogFilepath()
|
||||||
if l.CurrentLogFile != expectedCurrentLogFilepath {
|
if l.CurrentLogFile != expectedCurrentLogFilepath {
|
||||||
//Change of month. Update to a new log file
|
//Change of month. Update to a new log file
|
||||||
l.file.Close()
|
l.file.Close()
|
||||||
|
l.file = nil
|
||||||
|
|
||||||
|
//Create a new log file
|
||||||
f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
|
f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("[Logger] Unable to create new log. Logging to file disabled.")
|
log.Println("Unable to create new log. Logging is disabled: ", err.Error())
|
||||||
l.LogToFile = false
|
l.logger = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.CurrentLogFile = expectedCurrentLogFilepath
|
l.CurrentLogFile = expectedCurrentLogFilepath
|
||||||
l.file = f
|
l.file = f
|
||||||
|
|
||||||
|
//Start a new logger
|
||||||
|
logger := log.New(f, "", log.Default().Flags())
|
||||||
|
l.logger = logger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
32
src/mod/info/logger/trafficlog.go
Normal file
32
src/mod/info/logger/trafficlog.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
/*
|
||||||
|
Traffic Log
|
||||||
|
|
||||||
|
This script log the traffic of HTTP requests
|
||||||
|
|
||||||
|
*/
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log HTTP request. Note that this must run in go routine to prevent any blocking
|
||||||
|
// in reverse proxy router
|
||||||
|
func (l *Logger) LogHTTPRequest(r *http.Request, reqclass string, statusCode int) {
|
||||||
|
go func() {
|
||||||
|
l.ValidateAndUpdateLogFilepath()
|
||||||
|
if l.logger == nil || l.file == nil {
|
||||||
|
//logger is not initiated. Do not log http request
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientIP := netutils.GetRequesterIP(r)
|
||||||
|
requestURI := r.RequestURI
|
||||||
|
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 + "] " + r.Method + " " + requestURI + " " + statusCodeString)
|
||||||
|
}()
|
||||||
|
}
|
@ -3,6 +3,7 @@ package logviewer
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -51,13 +52,7 @@ func (v *Viewer) HandleReadLog(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
catergory, err := utils.GetPara(r, "catergory")
|
content, err := v.LoadLogFile(strings.TrimSpace(filepath.Base(filename)))
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, "invalid catergory given")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := v.LoadLogFile(strings.TrimSpace(filepath.Base(catergory)), strings.TrimSpace(filepath.Base(filename)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, err.Error())
|
utils.SendErrorResponse(w, err.Error())
|
||||||
return
|
return
|
||||||
@ -106,8 +101,11 @@ func (v *Viewer) ListLogFiles(showFullpath bool) map[string][]*LogFile {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Viewer) LoadLogFile(catergory string, filename string) (string, error) {
|
func (v *Viewer) LoadLogFile(filename string) (string, error) {
|
||||||
logFilepath := filepath.Join(v.option.RootFolder, catergory, filename)
|
filename = filepath.ToSlash(filename)
|
||||||
|
filename = strings.ReplaceAll(filename, "../", "")
|
||||||
|
logFilepath := filepath.Join(v.option.RootFolder, filename)
|
||||||
|
fmt.Println(logFilepath)
|
||||||
if utils.FileExists(logFilepath) {
|
if utils.FileExists(logFilepath) {
|
||||||
//Load it
|
//Load it
|
||||||
content, err := os.ReadFile(logFilepath)
|
content, err := os.ReadFile(logFilepath)
|
||||||
|
@ -39,7 +39,6 @@ type NetStatBuffers struct {
|
|||||||
|
|
||||||
// Get a new network statistic buffers
|
// Get a new network statistic buffers
|
||||||
func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
|
func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
|
||||||
|
|
||||||
//Flood fill the stats with 0
|
//Flood fill the stats with 0
|
||||||
initialStats := []*FlowStat{}
|
initialStats := []*FlowStat{}
|
||||||
for i := 0; i < recordCount; i++ {
|
for i := 0; i < recordCount; i++ {
|
||||||
|
76
src/mod/update/update.go
Normal file
76
src/mod/update/update.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package update
|
||||||
|
|
||||||
|
/*
|
||||||
|
Update.go
|
||||||
|
|
||||||
|
This module handle cross version updates that contains breaking changes
|
||||||
|
update command should always exit after the update is completed
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
v308 "imuslab.com/zoraxy/mod/update/v308"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run config update. Version numbers are int. For example
|
||||||
|
// to update 3.0.7 to 3.0.8, use RunConfigUpdate(307, 308)
|
||||||
|
// This function support cross versions updates (e.g. 307 -> 310)
|
||||||
|
func RunConfigUpdate(fromVersion int, toVersion int) {
|
||||||
|
versionFile := "./conf/version"
|
||||||
|
if fromVersion == 0 {
|
||||||
|
//Run auto previous version detection
|
||||||
|
fromVersion = 307
|
||||||
|
if utils.FileExists(versionFile) {
|
||||||
|
//Read the version file
|
||||||
|
previousVersionText, err := os.ReadFile(versionFile)
|
||||||
|
if err != nil {
|
||||||
|
panic("Unable to read version file at " + versionFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert the version to int
|
||||||
|
versionInt, err := strconv.Atoi(strings.TrimSpace(string(previousVersionText)))
|
||||||
|
if err != nil {
|
||||||
|
panic("Unable to read version file at " + versionFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
fromVersion = versionInt
|
||||||
|
}
|
||||||
|
|
||||||
|
if fromVersion == toVersion {
|
||||||
|
//No need to update
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Do iterate update
|
||||||
|
for i := fromVersion; i < toVersion; i++ {
|
||||||
|
oldVersion := fromVersion
|
||||||
|
newVersion := fromVersion + 1
|
||||||
|
fmt.Println("Updating from v", oldVersion, " to v", newVersion)
|
||||||
|
runUpdateRoutineWithVersion(oldVersion, newVersion)
|
||||||
|
//Write the updated version to file
|
||||||
|
os.WriteFile(versionFile, []byte(strconv.Itoa(newVersion)), 0775)
|
||||||
|
}
|
||||||
|
fmt.Println("Update completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVersionIntFromVersionNumber(version string) int {
|
||||||
|
versionNumberOnly := strings.ReplaceAll(version, ".", "")
|
||||||
|
versionInt, _ := strconv.Atoi(versionNumberOnly)
|
||||||
|
return versionInt
|
||||||
|
}
|
||||||
|
|
||||||
|
func runUpdateRoutineWithVersion(fromVersion int, toVersion int) {
|
||||||
|
if fromVersion == 307 && toVersion == 308 {
|
||||||
|
//Updating from v3.0.7 to v3.0.8
|
||||||
|
err := v308.UpdateFrom307To308()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
src/mod/update/v308/typedef307.go
Normal file
138
src/mod/update/v308/typedef307.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package v308
|
||||||
|
|
||||||
|
/*
|
||||||
|
v307 type definations
|
||||||
|
|
||||||
|
This file wrap up the self-contained data structure
|
||||||
|
for v3.0.7 structure and allow automatic updates
|
||||||
|
for future releases if required
|
||||||
|
*/
|
||||||
|
|
||||||
|
type v307PermissionsPolicy struct {
|
||||||
|
Accelerometer []string `json:"accelerometer"`
|
||||||
|
AmbientLightSensor []string `json:"ambient_light_sensor"`
|
||||||
|
Autoplay []string `json:"autoplay"`
|
||||||
|
Battery []string `json:"battery"`
|
||||||
|
Camera []string `json:"camera"`
|
||||||
|
CrossOriginIsolated []string `json:"cross_origin_isolated"`
|
||||||
|
DisplayCapture []string `json:"display_capture"`
|
||||||
|
DocumentDomain []string `json:"document_domain"`
|
||||||
|
EncryptedMedia []string `json:"encrypted_media"`
|
||||||
|
ExecutionWhileNotRendered []string `json:"execution_while_not_rendered"`
|
||||||
|
ExecutionWhileOutOfView []string `json:"execution_while_out_of_viewport"`
|
||||||
|
Fullscreen []string `json:"fullscreen"`
|
||||||
|
Geolocation []string `json:"geolocation"`
|
||||||
|
Gyroscope []string `json:"gyroscope"`
|
||||||
|
KeyboardMap []string `json:"keyboard_map"`
|
||||||
|
Magnetometer []string `json:"magnetometer"`
|
||||||
|
Microphone []string `json:"microphone"`
|
||||||
|
Midi []string `json:"midi"`
|
||||||
|
NavigationOverride []string `json:"navigation_override"`
|
||||||
|
Payment []string `json:"payment"`
|
||||||
|
PictureInPicture []string `json:"picture_in_picture"`
|
||||||
|
PublicKeyCredentialsGet []string `json:"publickey_credentials_get"`
|
||||||
|
ScreenWakeLock []string `json:"screen_wake_lock"`
|
||||||
|
SyncXHR []string `json:"sync_xhr"`
|
||||||
|
USB []string `json:"usb"`
|
||||||
|
WebShare []string `json:"web_share"`
|
||||||
|
XRSpatialTracking []string `json:"xr_spatial_tracking"`
|
||||||
|
ClipboardRead []string `json:"clipboard_read"`
|
||||||
|
ClipboardWrite []string `json:"clipboard_write"`
|
||||||
|
Gamepad []string `json:"gamepad"`
|
||||||
|
SpeakerSelection []string `json:"speaker_selection"`
|
||||||
|
ConversionMeasurement []string `json:"conversion_measurement"`
|
||||||
|
FocusWithoutUserActivation []string `json:"focus_without_user_activation"`
|
||||||
|
HID []string `json:"hid"`
|
||||||
|
IdleDetection []string `json:"idle_detection"`
|
||||||
|
InterestCohort []string `json:"interest_cohort"`
|
||||||
|
Serial []string `json:"serial"`
|
||||||
|
SyncScript []string `json:"sync_script"`
|
||||||
|
TrustTokenRedemption []string `json:"trust_token_redemption"`
|
||||||
|
Unload []string `json:"unload"`
|
||||||
|
WindowPlacement []string `json:"window_placement"`
|
||||||
|
VerticalScroll []string `json:"vertical_scroll"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth credential for basic auth on certain endpoints
|
||||||
|
type v307BasicAuthCredentials struct {
|
||||||
|
Username string
|
||||||
|
PasswordHash string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth credential for basic auth on certain endpoints
|
||||||
|
type v307BasicAuthUnhashedCredentials struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paths to exclude in basic auth enabled proxy handler
|
||||||
|
type v307BasicAuthExceptionRule struct {
|
||||||
|
PathPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header injection direction type
|
||||||
|
type v307HeaderDirection int
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderDirection_ZoraxyToUpstream v307HeaderDirection = 0 //Inject (or remove) header to request out-going from Zoraxy to backend server
|
||||||
|
HeaderDirection_ZoraxyToDownstream v307HeaderDirection = 1 //Inject (or remove) header to request out-going from Zoraxy to client (e.g. browser)
|
||||||
|
)
|
||||||
|
|
||||||
|
// User defined headers to add into a proxy endpoint
|
||||||
|
type v307UserDefinedHeader struct {
|
||||||
|
Direction v307HeaderDirection
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
IsRemove bool //Instead of set, remove this key instead
|
||||||
|
}
|
||||||
|
|
||||||
|
// The original proxy endpoint structure from v3.0.7
|
||||||
|
type v307ProxyEndpoint struct {
|
||||||
|
ProxyType int //The type of this proxy, see const def
|
||||||
|
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||||
|
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||||
|
Domain string //Domain or IP to proxy to
|
||||||
|
|
||||||
|
//TLS/SSL Related
|
||||||
|
RequireTLS bool //Target domain require TLS
|
||||||
|
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||||
|
SkipCertValidations bool //Set to true to accept self signed certs
|
||||||
|
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||||
|
|
||||||
|
//Virtual Directories
|
||||||
|
VirtualDirectories []*v307VirtualDirectoryEndpoint
|
||||||
|
|
||||||
|
//Custom Headers
|
||||||
|
UserDefinedHeaders []*v307UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||||
|
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||||
|
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||||
|
PermissionPolicy *v307PermissionsPolicy //Permission policy header
|
||||||
|
|
||||||
|
//Authentication
|
||||||
|
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||||
|
BasicAuthCredentials []*v307BasicAuthCredentials //Basic auth credentials
|
||||||
|
BasicAuthExceptionRules []*v307BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||||
|
|
||||||
|
// Rate Limiting
|
||||||
|
RequireRateLimit bool
|
||||||
|
RateLimit int64 // Rate limit in requests per second
|
||||||
|
|
||||||
|
//Access Control
|
||||||
|
AccessFilterUUID string //Access filter ID
|
||||||
|
|
||||||
|
Disabled bool //If the rule is disabled
|
||||||
|
|
||||||
|
//Fallback routing logic (Special Rule Sets Only)
|
||||||
|
DefaultSiteOption int //Fallback routing logic options
|
||||||
|
DefaultSiteValue string //Fallback routing target, optional
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||||
|
// program structure than directly using ProxyEndpoint
|
||||||
|
type v307VirtualDirectoryEndpoint struct {
|
||||||
|
MatchingPath string //Matching prefix of the request path, also act as key
|
||||||
|
Domain string //Domain or IP to proxy to
|
||||||
|
RequireTLS bool //Target domain require TLS
|
||||||
|
SkipCertValidations bool //Set to true to accept self signed certs
|
||||||
|
Disabled bool //If the rule is enabled
|
||||||
|
}
|
63
src/mod/update/v308/typedef308.go
Normal file
63
src/mod/update/v308/typedef308.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package v308
|
||||||
|
|
||||||
|
/*
|
||||||
|
v308 type definations
|
||||||
|
|
||||||
|
This file wrap up the self-contained data structure
|
||||||
|
for v3.0.8 structure and allow automatic updates
|
||||||
|
for future releases if required
|
||||||
|
|
||||||
|
Some struct are identical as v307 and hence it is not redefined here
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Upstream or Origin Server */
|
||||||
|
type v308Upstream struct {
|
||||||
|
//Upstream Proxy Configs
|
||||||
|
OriginIpOrDomain string //Target IP address or domain name with port
|
||||||
|
RequireTLS bool //Require TLS connection
|
||||||
|
SkipCertValidations bool //Set to true to accept self signed certs
|
||||||
|
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||||
|
|
||||||
|
//Load balancing configs
|
||||||
|
Weight int //Prirotiy of fallback, set all to 0 for round robin
|
||||||
|
MaxConn int //Maxmium connection to this server
|
||||||
|
}
|
||||||
|
|
||||||
|
// A proxy endpoint record, a general interface for handling inbound routing
|
||||||
|
type v308ProxyEndpoint struct {
|
||||||
|
ProxyType int //The type of this proxy, see const def
|
||||||
|
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||||
|
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||||
|
ActiveOrigins []*v308Upstream //Activated Upstream or origin servers IP or domain to proxy to
|
||||||
|
InactiveOrigins []*v308Upstream //Disabled Upstream or origin servers IP or domain to proxy to
|
||||||
|
UseStickySession bool //Use stick session for load balancing
|
||||||
|
Disabled bool //If the rule is disabled
|
||||||
|
|
||||||
|
//Inbound TLS/SSL Related
|
||||||
|
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||||
|
|
||||||
|
//Virtual Directories
|
||||||
|
VirtualDirectories []*v307VirtualDirectoryEndpoint
|
||||||
|
|
||||||
|
//Custom Headers
|
||||||
|
UserDefinedHeaders []*v307UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||||
|
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||||
|
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||||
|
PermissionPolicy *v307PermissionsPolicy //Permission policy header
|
||||||
|
|
||||||
|
//Authentication
|
||||||
|
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||||
|
BasicAuthCredentials []*v307BasicAuthCredentials //Basic auth credentials
|
||||||
|
BasicAuthExceptionRules []*v307BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||||
|
|
||||||
|
// Rate Limiting
|
||||||
|
RequireRateLimit bool
|
||||||
|
RateLimit int64 // Rate limit in requests per second
|
||||||
|
|
||||||
|
//Access Control
|
||||||
|
AccessFilterUUID string //Access filter ID
|
||||||
|
|
||||||
|
//Fallback routing logic (Special Rule Sets Only)
|
||||||
|
DefaultSiteOption int //Fallback routing logic options
|
||||||
|
DefaultSiteValue string //Fallback routing target, optional
|
||||||
|
}
|
132
src/mod/update/v308/v308.go
Normal file
132
src/mod/update/v308/v308.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package v308
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
v3.0.7 update to v3.0.8
|
||||||
|
|
||||||
|
This update function
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Update proxy config files from v3.0.7 to v3.0.8
|
||||||
|
func UpdateFrom307To308() error {
|
||||||
|
|
||||||
|
//Load the configs
|
||||||
|
oldConfigFiles, err := filepath.Glob("./conf/proxy/*.config")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Backup all the files
|
||||||
|
err = os.MkdirAll("./conf/proxy.old/", 0775)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, oldConfigFile := range oldConfigFiles {
|
||||||
|
// Extract the file name from the path
|
||||||
|
fileName := filepath.Base(oldConfigFile)
|
||||||
|
// Construct the backup file path
|
||||||
|
backupFile := filepath.Join("./conf/proxy.old/", fileName)
|
||||||
|
|
||||||
|
// Copy the file to the backup directory
|
||||||
|
err := copyFile(oldConfigFile, backupFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//read the config into the old struct
|
||||||
|
for _, oldConfigFile := range oldConfigFiles {
|
||||||
|
configContent, err := os.ReadFile(oldConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to read config file "+filepath.Base(oldConfigFile), err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
thisOldConfigStruct := v307ProxyEndpoint{}
|
||||||
|
err = json.Unmarshal(configContent, &thisOldConfigStruct)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to parse file "+filepath.Base(oldConfigFile), err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert the old config to new config
|
||||||
|
newProxyStructure := convertV307ToV308(thisOldConfigStruct)
|
||||||
|
js, _ := json.MarshalIndent(newProxyStructure, "", " ")
|
||||||
|
err = os.WriteFile(oldConfigFile, js, 0775)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertV307ToV308(old v307ProxyEndpoint) v308ProxyEndpoint {
|
||||||
|
// Create a new v308ProxyEndpoint instance
|
||||||
|
|
||||||
|
matchingDomainsSlice := old.MatchingDomainAlias
|
||||||
|
if matchingDomainsSlice == nil {
|
||||||
|
matchingDomainsSlice = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
newEndpoint := v308ProxyEndpoint{
|
||||||
|
ProxyType: old.ProxyType,
|
||||||
|
RootOrMatchingDomain: old.RootOrMatchingDomain,
|
||||||
|
MatchingDomainAlias: matchingDomainsSlice,
|
||||||
|
ActiveOrigins: []*v308Upstream{{ // Mapping Domain field to v308Upstream struct
|
||||||
|
OriginIpOrDomain: old.Domain,
|
||||||
|
RequireTLS: old.RequireTLS,
|
||||||
|
SkipCertValidations: old.SkipCertValidations,
|
||||||
|
SkipWebSocketOriginCheck: old.SkipWebSocketOriginCheck,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0,
|
||||||
|
}},
|
||||||
|
InactiveOrigins: []*v308Upstream{},
|
||||||
|
UseStickySession: false,
|
||||||
|
Disabled: old.Disabled,
|
||||||
|
BypassGlobalTLS: old.BypassGlobalTLS,
|
||||||
|
VirtualDirectories: old.VirtualDirectories,
|
||||||
|
UserDefinedHeaders: old.UserDefinedHeaders,
|
||||||
|
HSTSMaxAge: old.HSTSMaxAge,
|
||||||
|
EnablePermissionPolicyHeader: old.EnablePermissionPolicyHeader,
|
||||||
|
PermissionPolicy: old.PermissionPolicy,
|
||||||
|
RequireBasicAuth: old.RequireBasicAuth,
|
||||||
|
BasicAuthCredentials: old.BasicAuthCredentials,
|
||||||
|
BasicAuthExceptionRules: old.BasicAuthExceptionRules,
|
||||||
|
RequireRateLimit: old.RequireRateLimit,
|
||||||
|
RateLimit: old.RateLimit,
|
||||||
|
AccessFilterUUID: old.AccessFilterUUID,
|
||||||
|
DefaultSiteOption: old.DefaultSiteOption,
|
||||||
|
DefaultSiteValue: old.DefaultSiteValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
return newEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to copy files
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
sourceFile, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sourceFile.Close()
|
||||||
|
|
||||||
|
destinationFile, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destinationFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(destinationFile, sourceFile)
|
||||||
|
return err
|
||||||
|
}
|
@ -2,16 +2,23 @@ package uptime
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
logModuleName = "uptime-monitor"
|
||||||
|
)
|
||||||
|
|
||||||
type Record struct {
|
type Record struct {
|
||||||
Timestamp int64
|
Timestamp int64
|
||||||
ID string
|
ID string
|
||||||
@ -23,17 +30,26 @@ type Record struct {
|
|||||||
Latency int64
|
Latency int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProxyType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProxyType_Host ProxyType = "Origin Server"
|
||||||
|
ProxyType_Vdir ProxyType = "Virtual Directory"
|
||||||
|
)
|
||||||
|
|
||||||
type Target struct {
|
type Target struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
URL string
|
URL string
|
||||||
Protocol string
|
Protocol string
|
||||||
|
ProxyType ProxyType
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Targets []*Target
|
Targets []*Target
|
||||||
Interval int
|
Interval int
|
||||||
MaxRecordsStore int
|
MaxRecordsStore int
|
||||||
|
Logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
@ -56,6 +72,12 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
|
|||||||
Config: config,
|
Config: config,
|
||||||
OnlineStatusLog: map[string][]*Record{},
|
OnlineStatusLog: map[string][]*Record{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Logger == nil {
|
||||||
|
//Use default fmt to log if logger is not provided
|
||||||
|
config.Logger, _ = logger.NewFmtLogger()
|
||||||
|
}
|
||||||
|
|
||||||
//Start the endpoint listener
|
//Start the endpoint listener
|
||||||
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
|
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
@ -69,7 +91,7 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
|
|||||||
case <-done:
|
case <-done:
|
||||||
return
|
return
|
||||||
case t := <-ticker.C:
|
case t := <-ticker.C:
|
||||||
log.Println("Uptime updated - ", t.Unix())
|
thisMonitor.Config.Logger.PrintAndLog(logModuleName, "Uptime updated - "+strconv.Itoa(int(t.Unix())), nil)
|
||||||
thisMonitor.ExecuteUptimeCheck()
|
thisMonitor.ExecuteUptimeCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +105,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
|
|||||||
//For each target to check online, do the following
|
//For each target to check online, do the following
|
||||||
var thisRecord Record
|
var thisRecord Record
|
||||||
if target.Protocol == "http" || target.Protocol == "https" {
|
if target.Protocol == "http" || target.Protocol == "https" {
|
||||||
online, laterncy, statusCode := getWebsiteStatusWithLatency(target.URL)
|
online, laterncy, statusCode := m.getWebsiteStatusWithLatency(target.URL)
|
||||||
thisRecord = Record{
|
thisRecord = Record{
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
ID: target.ID,
|
ID: target.ID,
|
||||||
@ -96,7 +118,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.Println("Unknown protocol: " + target.Protocol + ". Skipping")
|
m.Config.Logger.PrintAndLog(logModuleName, "Unknown protocol: "+target.Protocol, errors.New("unsupported protocol"))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,8 +138,6 @@ func (m *Monitor) ExecuteUptimeCheck() {
|
|||||||
m.OnlineStatusLog[target.ID] = thisRecords
|
m.OnlineStatusLog[target.ID] = thisRecords
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Write results to db
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Monitor) AddTargetToMonitor(target *Target) {
|
func (m *Monitor) AddTargetToMonitor(target *Target) {
|
||||||
@ -193,12 +213,12 @@ func (m *Monitor) HandleUptimeLogRead(w http.ResponseWriter, r *http.Request) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Get website stauts with latency given URL, return is conn succ and its latency and status code
|
// Get website stauts with latency given URL, return is conn succ and its latency and status code
|
||||||
func getWebsiteStatusWithLatency(url string) (bool, int64, int) {
|
func (m *Monitor) getWebsiteStatusWithLatency(url string) (bool, int64, int) {
|
||||||
start := time.Now().UnixNano() / int64(time.Millisecond)
|
start := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
statusCode, err := getWebsiteStatus(url)
|
statusCode, err := getWebsiteStatus(url)
|
||||||
end := time.Now().UnixNano() / int64(time.Millisecond)
|
end := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
m.Config.Logger.PrintAndLog(logModuleName, "Ping upstream timeout. Assume offline", err)
|
||||||
return false, 0, 0
|
return false, 0, 0
|
||||||
} else {
|
} else {
|
||||||
diff := end - start
|
diff := end - start
|
||||||
|
@ -3,6 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -141,3 +142,35 @@ func StringInArrayIgnoreCase(arr []string, str string) bool {
|
|||||||
|
|
||||||
return StringInArray(smallArray, strings.ToLower(str))
|
return StringInArray(smallArray, strings.ToLower(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate if the listening address is correct
|
||||||
|
func ValidateListeningAddress(address string) bool {
|
||||||
|
// Check if the address starts with a colon, indicating it's just a port
|
||||||
|
if strings.HasPrefix(address, ":") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the address into host and port parts
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
// Try to parse it as just a port
|
||||||
|
if _, err := strconv.Atoi(address); err == nil {
|
||||||
|
return false // It's just a port number
|
||||||
|
}
|
||||||
|
return false // It's an invalid address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the port part is a valid number
|
||||||
|
if _, err := strconv.Atoi(port); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the host part is a valid IP address or empty (indicating any IP)
|
||||||
|
if host != "" {
|
||||||
|
if net.ParseIP(host) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -5,13 +5,13 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
"imuslab.com/zoraxy/mod/webserv/filemanager"
|
"imuslab.com/zoraxy/mod/webserv/filemanager"
|
||||||
)
|
)
|
||||||
@ -30,6 +30,7 @@ type WebServerOptions struct {
|
|||||||
EnableDirectoryListing bool //Enable listing of directory
|
EnableDirectoryListing bool //Enable listing of directory
|
||||||
WebRoot string //Folder for stroing the static web folders
|
WebRoot string //Folder for stroing the static web folders
|
||||||
EnableWebDirManager bool //Enable web file manager to handle files in web directory
|
EnableWebDirManager bool //Enable web file manager to handle files in web directory
|
||||||
|
Logger *logger.Logger //System logger
|
||||||
Sysdb *database.Database //Database for storing configs
|
Sysdb *database.Database //Database for storing configs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,13 +46,16 @@ type WebServer struct {
|
|||||||
|
|
||||||
// NewWebServer creates a new WebServer instance. One instance only
|
// NewWebServer creates a new WebServer instance. One instance only
|
||||||
func NewWebServer(options *WebServerOptions) *WebServer {
|
func NewWebServer(options *WebServerOptions) *WebServer {
|
||||||
|
if options.Logger == nil {
|
||||||
|
options.Logger, _ = logger.NewFmtLogger()
|
||||||
|
}
|
||||||
if !utils.FileExists(options.WebRoot) {
|
if !utils.FileExists(options.WebRoot) {
|
||||||
//Web root folder not exists. Create one with default templates
|
//Web root folder not exists. Create one with default templates
|
||||||
os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775)
|
os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775)
|
||||||
os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775)
|
os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775)
|
||||||
indexTemplate, err := templates.ReadFile("templates/index.html")
|
indexTemplate, err := templates.ReadFile("templates/index.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to read static wev server template file: ", err.Error())
|
options.Logger.PrintAndLog("static-webserv", "Failed to read static wev server template file: ", err)
|
||||||
} else {
|
} else {
|
||||||
os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775)
|
os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775)
|
||||||
}
|
}
|
||||||
@ -102,7 +106,7 @@ func (ws *WebServer) RestorePreviousState() {
|
|||||||
// ChangePort changes the server's port.
|
// ChangePort changes the server's port.
|
||||||
func (ws *WebServer) ChangePort(port string) error {
|
func (ws *WebServer) ChangePort(port string) error {
|
||||||
if IsPortInUse(port) {
|
if IsPortInUse(port) {
|
||||||
return errors.New("Selected port is used by another process")
|
return errors.New("selected port is used by another process")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ws.isRunning {
|
if ws.isRunning {
|
||||||
@ -119,6 +123,7 @@ func (ws *WebServer) ChangePort(port string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ws.option.Logger.PrintAndLog("static-webserv", "Listening port updated to "+port, nil)
|
||||||
ws.option.Sysdb.Write("webserv", "port", port)
|
ws.option.Sysdb.Write("webserv", "port", port)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -141,7 +146,7 @@ func (ws *WebServer) Start() error {
|
|||||||
|
|
||||||
//Check if the port is usable
|
//Check if the port is usable
|
||||||
if IsPortInUse(ws.option.Port) {
|
if IsPortInUse(ws.option.Port) {
|
||||||
return errors.New("Port already in use or access denied by host OS")
|
return errors.New("port already in use or access denied by host OS")
|
||||||
}
|
}
|
||||||
|
|
||||||
//Dispose the old mux and create a new one
|
//Dispose the old mux and create a new one
|
||||||
@ -159,12 +164,12 @@ func (ws *WebServer) Start() error {
|
|||||||
go func() {
|
go func() {
|
||||||
if err := ws.server.ListenAndServe(); err != nil {
|
if err := ws.server.ListenAndServe(); err != nil {
|
||||||
if err != http.ErrServerClosed {
|
if err != http.ErrServerClosed {
|
||||||
fmt.Printf("Web server error: %v\n", err)
|
ws.option.Logger.PrintAndLog("static-webserv", "Web server failed to start", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Println("Static Web Server started. Listeing on :" + ws.option.Port)
|
ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server started. Listeing on :"+ws.option.Port, nil)
|
||||||
ws.isRunning = true
|
ws.isRunning = true
|
||||||
ws.option.Sysdb.Write("webserv", "enabled", true)
|
ws.option.Sysdb.Write("webserv", "enabled", true)
|
||||||
return nil
|
return nil
|
||||||
@ -182,7 +187,7 @@ func (ws *WebServer) Stop() error {
|
|||||||
if err := ws.server.Close(); err != nil {
|
if err := ws.server.Close(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server stopped", nil)
|
||||||
ws.isRunning = false
|
ws.isRunning = false
|
||||||
ws.option.Sysdb.Write("webserv", "enabled", false)
|
ws.option.Sysdb.Write("webserv", "enabled", false)
|
||||||
return nil
|
return nil
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
@ -96,9 +97,11 @@ func ReverseProxtInit() {
|
|||||||
StatisticCollector: statisticCollector,
|
StatisticCollector: statisticCollector,
|
||||||
WebDirectory: *staticWebServerRoot,
|
WebDirectory: *staticWebServerRoot,
|
||||||
AccessController: accessController,
|
AccessController: accessController,
|
||||||
|
LoadBalancer: loadBalancer,
|
||||||
|
Logger: SystemWideLogger,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to create dynamic proxy router", err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Unable to create dynamic proxy router", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +116,7 @@ func ReverseProxtInit() {
|
|||||||
for _, conf := range confs {
|
for _, conf := range confs {
|
||||||
err := LoadReverseProxyConfig(conf)
|
err := LoadReverseProxyConfig(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Failed to load config file: "+filepath.Base(conf), err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Failed to load config file: "+filepath.Base(conf), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +125,7 @@ func ReverseProxtInit() {
|
|||||||
//Root config not set (new deployment?), use internal static web server as root
|
//Root config not set (new deployment?), use internal static web server as root
|
||||||
defaultRootRouter, err := GetDefaultRootConfig()
|
defaultRootRouter, err := GetDefaultRootConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Failed to generate default root routing", err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Failed to generate default root routing", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter)
|
dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter)
|
||||||
@ -143,11 +146,9 @@ func ReverseProxtInit() {
|
|||||||
Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter),
|
Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter),
|
||||||
Interval: 300, //5 minutes
|
Interval: 300, //5 minutes
|
||||||
MaxRecordsStore: 288, //1 day
|
MaxRecordsStore: 288, //1 day
|
||||||
|
Logger: SystemWideLogger, //Logger
|
||||||
})
|
})
|
||||||
|
|
||||||
//Pass the pointer of this uptime monitor into the load balancer
|
|
||||||
loadbalancer.Options.UptimeMonitor = uptimeMonitor
|
|
||||||
|
|
||||||
SystemWideLogger.Println("Uptime Monitor background service started")
|
SystemWideLogger.Println("Uptime Monitor background service started")
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -208,12 +209,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
useBypassGlobalTLS := bypassGlobalTLS == "true"
|
useBypassGlobalTLS := bypassGlobalTLS == "true"
|
||||||
|
|
||||||
//Enable TLS validation?
|
//Enable TLS validation?
|
||||||
stv, _ := utils.PostPara(r, "tlsval")
|
skipTlsValidation, _ := utils.PostBool(r, "tlsval")
|
||||||
if stv == "" {
|
|
||||||
stv = "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
skipTlsValidation := (stv == "true")
|
|
||||||
|
|
||||||
//Get access rule ID
|
//Get access rule ID
|
||||||
accessRuleID, _ := utils.PostPara(r, "access")
|
accessRuleID, _ := utils.PostPara(r, "access")
|
||||||
@ -226,12 +222,10 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Require basic auth?
|
// Require basic auth?
|
||||||
rba, _ := utils.PostPara(r, "bauth")
|
requireBasicAuth, _ := utils.PostBool(r, "bauth")
|
||||||
if rba == "" {
|
|
||||||
rba = "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
requireBasicAuth := (rba == "true")
|
//Use sticky session?
|
||||||
|
useStickySession, _ := utils.PostBool(r, "stickysess")
|
||||||
|
|
||||||
// Require Rate Limiting?
|
// Require Rate Limiting?
|
||||||
requireRateLimit := false
|
requireRateLimit := false
|
||||||
@ -319,12 +313,20 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
ProxyType: dynamicproxy.ProxyType_Host,
|
ProxyType: dynamicproxy.ProxyType_Host,
|
||||||
RootOrMatchingDomain: rootOrMatchingDomain,
|
RootOrMatchingDomain: rootOrMatchingDomain,
|
||||||
MatchingDomainAlias: aliasHostnames,
|
MatchingDomainAlias: aliasHostnames,
|
||||||
Domain: endpoint,
|
ActiveOrigins: []*loadbalance.Upstream{
|
||||||
//TLS
|
{
|
||||||
|
OriginIpOrDomain: endpoint,
|
||||||
RequireTLS: useTLS,
|
RequireTLS: useTLS,
|
||||||
BypassGlobalTLS: useBypassGlobalTLS,
|
|
||||||
SkipCertValidations: skipTlsValidation,
|
SkipCertValidations: skipTlsValidation,
|
||||||
SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
|
SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InactiveOrigins: []*loadbalance.Upstream{},
|
||||||
|
UseStickySession: useStickySession,
|
||||||
|
|
||||||
|
//TLS
|
||||||
|
BypassGlobalTLS: useBypassGlobalTLS,
|
||||||
AccessFilterUUID: accessRuleID,
|
AccessFilterUUID: accessRuleID,
|
||||||
//VDir
|
//VDir
|
||||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||||
@ -377,12 +379,17 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{
|
rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{
|
||||||
ProxyType: dynamicproxy.ProxyType_Root,
|
ProxyType: dynamicproxy.ProxyType_Root,
|
||||||
RootOrMatchingDomain: "/",
|
RootOrMatchingDomain: "/",
|
||||||
Domain: endpoint,
|
ActiveOrigins: []*loadbalance.Upstream{
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: endpoint,
|
||||||
RequireTLS: useTLS,
|
RequireTLS: useTLS,
|
||||||
BypassGlobalTLS: false,
|
SkipCertValidations: true,
|
||||||
SkipCertValidations: false,
|
|
||||||
SkipWebSocketOriginCheck: true,
|
SkipWebSocketOriginCheck: true,
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InactiveOrigins: []*loadbalance.Upstream{},
|
||||||
|
BypassGlobalTLS: false,
|
||||||
DefaultSiteOption: defaultSiteOption,
|
DefaultSiteOption: defaultSiteOption,
|
||||||
DefaultSiteValue: dsVal,
|
DefaultSiteValue: dsVal,
|
||||||
}
|
}
|
||||||
@ -392,7 +399,11 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamicProxyRouter.SetProxyRouteAsRoot(preparedRootProxyRoute)
|
err = dynamicProxyRouter.SetProxyRouteAsRoot(preparedRootProxyRoute)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "unable to update default site: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
proxyEndpointCreated = &rootRoutingEndpoint
|
proxyEndpointCreated = &rootRoutingEndpoint
|
||||||
} else {
|
} else {
|
||||||
//Invalid eptype
|
//Invalid eptype
|
||||||
@ -403,7 +414,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Save the config to file
|
//Save the config to file
|
||||||
err = SaveReverseProxyConfig(proxyEndpointCreated)
|
err = SaveReverseProxyConfig(proxyEndpointCreated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to save new proxy rule to file", err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Unable to save new proxy rule to file", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,24 +437,12 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint, err := utils.PostPara(r, "ep")
|
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, "endpoint not defined")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tls, _ := utils.PostPara(r, "tls")
|
tls, _ := utils.PostPara(r, "tls")
|
||||||
if tls == "" {
|
if tls == "" {
|
||||||
tls = "false"
|
tls = "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
useTLS := (tls == "true")
|
useStickySession, _ := utils.PostBool(r, "ss")
|
||||||
|
|
||||||
stv, _ := utils.PostPara(r, "tlsval")
|
|
||||||
if stv == "" {
|
|
||||||
stv = "false"
|
|
||||||
}
|
|
||||||
skipTlsValidation := (stv == "true")
|
|
||||||
|
|
||||||
//Load bypass TLS option
|
//Load bypass TLS option
|
||||||
bpgtls, _ := utils.PostPara(r, "bpgtls")
|
bpgtls, _ := utils.PostPara(r, "bpgtls")
|
||||||
@ -475,6 +474,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendErrorResponse(w, "invalid rate limit number")
|
utils.SendErrorResponse(w, "invalid rate limit number")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if requireRateLimit && proxyRateLimit <= 0 {
|
if requireRateLimit && proxyRateLimit <= 0 {
|
||||||
utils.SendErrorResponse(w, "rate limit number must be greater than 0")
|
utils.SendErrorResponse(w, "rate limit number must be greater than 0")
|
||||||
return
|
return
|
||||||
@ -482,13 +482,6 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
proxyRateLimit = 1000
|
proxyRateLimit = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bypass WebSocket Origin Check
|
|
||||||
strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
|
|
||||||
if strbpwsorg == "" {
|
|
||||||
strbpwsorg = "false"
|
|
||||||
}
|
|
||||||
bypassWebsocketOriginCheck := (strbpwsorg == "true")
|
|
||||||
|
|
||||||
//Load the previous basic auth credentials from current proxy rules
|
//Load the previous basic auth credentials from current proxy rules
|
||||||
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
|
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -498,14 +491,11 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
//Generate a new proxyEndpoint from the new config
|
//Generate a new proxyEndpoint from the new config
|
||||||
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
|
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
|
||||||
newProxyEndpoint.Domain = endpoint
|
|
||||||
newProxyEndpoint.RequireTLS = useTLS
|
|
||||||
newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
|
newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
|
||||||
newProxyEndpoint.SkipCertValidations = skipTlsValidation
|
|
||||||
newProxyEndpoint.RequireBasicAuth = requireBasicAuth
|
newProxyEndpoint.RequireBasicAuth = requireBasicAuth
|
||||||
newProxyEndpoint.RequireRateLimit = requireRateLimit
|
newProxyEndpoint.RequireRateLimit = requireRateLimit
|
||||||
newProxyEndpoint.RateLimit = proxyRateLimit
|
newProxyEndpoint.RateLimit = proxyRateLimit
|
||||||
newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck
|
newProxyEndpoint.UseStickySession = useStickySession
|
||||||
|
|
||||||
//Prepare to replace the current routing rule
|
//Prepare to replace the current routing rule
|
||||||
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
|
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
|
||||||
@ -519,9 +509,6 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Save it to file
|
//Save it to file
|
||||||
SaveReverseProxyConfig(newProxyEndpoint)
|
SaveReverseProxyConfig(newProxyEndpoint)
|
||||||
|
|
||||||
//Update uptime monitor
|
|
||||||
UpdateUptimeMonitorTargets()
|
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,7 +540,7 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
|
|||||||
newAlias := []string{}
|
newAlias := []string{}
|
||||||
err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
|
err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to parse new alias list", err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Unable to parse new alias list", err)
|
||||||
utils.SendErrorResponse(w, "Invalid alias list given")
|
utils.SendErrorResponse(w, "Invalid alias list given")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -575,7 +562,7 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
|
|||||||
err = SaveReverseProxyConfig(newProxyEndpoint)
|
err = SaveReverseProxyConfig(newProxyEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "Alias update failed")
|
utils.SendErrorResponse(w, "Alias update failed")
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to save alias update", err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Unable to save alias update", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
@ -879,6 +866,10 @@ func ReverseProxyToggleRuleSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendErrorResponse(w, "unable to save updated rule")
|
utils.SendErrorResponse(w, "unable to save updated rule")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Update uptime monitor
|
||||||
|
UpdateUptimeMonitorTargets()
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -938,7 +929,7 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
sort.Slice(results, func(i, j int) bool {
|
||||||
return results[i].Domain < results[j].Domain
|
return results[i].RootOrMatchingDomain < results[j].RootOrMatchingDomain
|
||||||
})
|
})
|
||||||
|
|
||||||
js, _ := json.Marshal(results)
|
js, _ := json.Marshal(results)
|
||||||
@ -1058,15 +1049,20 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rootProxyTargetOrigin := ""
|
||||||
|
if len(dynamicProxyRouter.Root.ActiveOrigins) > 0 {
|
||||||
|
rootProxyTargetOrigin = dynamicProxyRouter.Root.ActiveOrigins[0].OriginIpOrDomain
|
||||||
|
}
|
||||||
|
|
||||||
//Check if it is identical as proxy root (recursion!)
|
//Check if it is identical as proxy root (recursion!)
|
||||||
if dynamicProxyRouter.Root == nil || dynamicProxyRouter.Root.Domain == "" {
|
if dynamicProxyRouter.Root == nil || rootProxyTargetOrigin == "" {
|
||||||
//Check if proxy root is set before checking recursive listen
|
//Check if proxy root is set before checking recursive listen
|
||||||
//Fixing issue #43
|
//Fixing issue #43
|
||||||
utils.SendErrorResponse(w, "Set Proxy Root before changing inbound port")
|
utils.SendErrorResponse(w, "Set Proxy Root before changing inbound port")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyRoot := strings.TrimSuffix(dynamicProxyRouter.Root.Domain, "/")
|
proxyRoot := strings.TrimSuffix(rootProxyTargetOrigin, "/")
|
||||||
if strings.EqualFold(proxyRoot, "localhost:"+strconv.Itoa(newIncomingPortInt)) || strings.EqualFold(proxyRoot, "127.0.0.1:"+strconv.Itoa(newIncomingPortInt)) {
|
if strings.EqualFold(proxyRoot, "localhost:"+strconv.Itoa(newIncomingPortInt)) || strings.EqualFold(proxyRoot, "127.0.0.1:"+strconv.Itoa(newIncomingPortInt)) {
|
||||||
//Listening port is same as proxy root
|
//Listening port is same as proxy root
|
||||||
//Not allow recursive settings
|
//Not allow recursive settings
|
||||||
|
35
src/start.go
35
src/start.go
@ -20,6 +20,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/ganserv"
|
"imuslab.com/zoraxy/mod/ganserv"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logviewer"
|
||||||
"imuslab.com/zoraxy/mod/mdns"
|
"imuslab.com/zoraxy/mod/mdns"
|
||||||
"imuslab.com/zoraxy/mod/netstat"
|
"imuslab.com/zoraxy/mod/netstat"
|
||||||
"imuslab.com/zoraxy/mod/pathrule"
|
"imuslab.com/zoraxy/mod/pathrule"
|
||||||
@ -47,6 +48,18 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func startupSequence() {
|
func startupSequence() {
|
||||||
|
//Start a system wide logger and log viewer
|
||||||
|
l, err := logger.NewLogger("zr", "./log")
|
||||||
|
if err == nil {
|
||||||
|
SystemWideLogger = l
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
|
||||||
|
RootFolder: "./log",
|
||||||
|
Extension: ".log",
|
||||||
|
})
|
||||||
|
|
||||||
//Create database
|
//Create database
|
||||||
db, err := database.NewDatabase("sys.db", false)
|
db, err := database.NewDatabase("sys.db", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -61,11 +74,11 @@ func startupSequence() {
|
|||||||
os.MkdirAll("./conf/proxy/", 0775)
|
os.MkdirAll("./conf/proxy/", 0775)
|
||||||
|
|
||||||
//Create an auth agent
|
//Create an auth agent
|
||||||
sessionKey, err := auth.GetSessionKey(sysdb)
|
sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, func(w http.ResponseWriter, r *http.Request) {
|
authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, SystemWideLogger, func(w http.ResponseWriter, r *http.Request) {
|
||||||
//Not logged in. Redirecting to login page
|
//Not logged in. Redirecting to login page
|
||||||
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
|
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
|
||||||
})
|
})
|
||||||
@ -76,14 +89,6 @@ func startupSequence() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create a system wide logger
|
|
||||||
l, err := logger.NewLogger("zr", "./log", *logOutputToFile)
|
|
||||||
if err == nil {
|
|
||||||
SystemWideLogger = l
|
|
||||||
} else {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create a redirection rule table
|
//Create a redirection rule table
|
||||||
db.NewTable("redirect")
|
db.NewTable("redirect")
|
||||||
redirectAllowRegexp := false
|
redirectAllowRegexp := false
|
||||||
@ -102,10 +107,12 @@ func startupSequence() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create a load balance route manager
|
//Create a load balancer
|
||||||
loadbalancer = loadbalance.NewRouteManager(&loadbalance.Options{
|
loadBalancer = loadbalance.NewLoadBalancer(&loadbalance.Options{
|
||||||
|
SystemUUID: nodeUUID,
|
||||||
Geodb: geodbStore,
|
Geodb: geodbStore,
|
||||||
}, SystemWideLogger)
|
Logger: SystemWideLogger,
|
||||||
|
})
|
||||||
|
|
||||||
//Create the access controller
|
//Create the access controller
|
||||||
accessController, err = access.NewAccessController(&access.Options{
|
accessController, err = access.NewAccessController(&access.Options{
|
||||||
@ -132,6 +139,7 @@ func startupSequence() {
|
|||||||
WebRoot: *staticWebServerRoot,
|
WebRoot: *staticWebServerRoot,
|
||||||
EnableDirectoryListing: true,
|
EnableDirectoryListing: true,
|
||||||
EnableWebDirManager: *allowWebFileManager,
|
EnableWebDirManager: *allowWebFileManager,
|
||||||
|
Logger: SystemWideLogger,
|
||||||
})
|
})
|
||||||
//Restore the web server to previous shutdown state
|
//Restore the web server to previous shutdown state
|
||||||
staticWebServer.RestorePreviousState()
|
staticWebServer.RestorePreviousState()
|
||||||
@ -291,5 +299,4 @@ func finalSequence() {
|
|||||||
|
|
||||||
//Inject routing rules
|
//Inject routing rules
|
||||||
registerBuildInRoutingRules()
|
registerBuildInRoutingRules()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
283
src/upstreams.go
Normal file
283
src/upstreams.go
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Upstreams.go
|
||||||
|
|
||||||
|
This script handle upstream and load balancer
|
||||||
|
related API
|
||||||
|
*/
|
||||||
|
|
||||||
|
// List upstreams from a endpoint
|
||||||
|
func ReverseProxyUpstreamList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target endpoint not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activeUpstreams := targetEndpoint.ActiveOrigins
|
||||||
|
inactiveUpstreams := targetEndpoint.InactiveOrigins
|
||||||
|
// Sort the upstreams slice by weight, then by origin domain alphabetically
|
||||||
|
sort.Slice(activeUpstreams, func(i, j int) bool {
|
||||||
|
if activeUpstreams[i].Weight != activeUpstreams[j].Weight {
|
||||||
|
return activeUpstreams[i].Weight > activeUpstreams[j].Weight
|
||||||
|
}
|
||||||
|
return activeUpstreams[i].OriginIpOrDomain < activeUpstreams[j].OriginIpOrDomain
|
||||||
|
})
|
||||||
|
|
||||||
|
sort.Slice(inactiveUpstreams, func(i, j int) bool {
|
||||||
|
if inactiveUpstreams[i].Weight != inactiveUpstreams[j].Weight {
|
||||||
|
return inactiveUpstreams[i].Weight > inactiveUpstreams[j].Weight
|
||||||
|
}
|
||||||
|
return inactiveUpstreams[i].OriginIpOrDomain < inactiveUpstreams[j].OriginIpOrDomain
|
||||||
|
})
|
||||||
|
|
||||||
|
type UpstreamCombinedList struct {
|
||||||
|
ActiveOrigins []*loadbalance.Upstream
|
||||||
|
InactiveOrigins []*loadbalance.Upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(UpstreamCombinedList{
|
||||||
|
ActiveOrigins: activeUpstreams,
|
||||||
|
InactiveOrigins: inactiveUpstreams,
|
||||||
|
})
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an upstream to a given proxy upstream endpoint
|
||||||
|
func ReverseProxyUpstreamAdd(w http.ResponseWriter, r *http.Request) {
|
||||||
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target endpoint not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
upstreamOrigin, err := utils.PostPara(r, "origin")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "upstream origin not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
requireTLS, _ := utils.PostBool(r, "tls")
|
||||||
|
skipTlsValidation, _ := utils.PostBool(r, "tlsval")
|
||||||
|
bpwsorg, _ := utils.PostBool(r, "bpwsorg")
|
||||||
|
preactivate, _ := utils.PostBool(r, "active")
|
||||||
|
|
||||||
|
//Create a new upstream object
|
||||||
|
newUpstream := loadbalance.Upstream{
|
||||||
|
OriginIpOrDomain: upstreamOrigin,
|
||||||
|
RequireTLS: requireTLS,
|
||||||
|
SkipCertValidations: skipTlsValidation,
|
||||||
|
SkipWebSocketOriginCheck: bpwsorg,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add the new upstream to endpoint
|
||||||
|
err = targetEndpoint.AddUpstreamOrigin(&newUpstream, preactivate)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Save changes to configs
|
||||||
|
err = SaveReverseProxyConfig(targetEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("INFO", "Unable to save new upstream to proxy config", err)
|
||||||
|
utils.SendErrorResponse(w, "Failed to save new upstream config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update Uptime Monitor
|
||||||
|
UpdateUptimeMonitorTargets()
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the connection configuration of this origin
|
||||||
|
// pass in the whole new upstream origin json via "payload" POST variable
|
||||||
|
// for missing fields, original value will be used instead
|
||||||
|
func ReverseProxyUpstreamUpdate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target endpoint not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Editing upstream origin IP
|
||||||
|
originIP, err := utils.PostPara(r, "origin")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "origin ip or matching address not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originIP = strings.TrimSpace(originIP)
|
||||||
|
|
||||||
|
//Update content payload
|
||||||
|
payload, err := utils.PostPara(r, "payload")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "update payload not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive, _ := utils.PostBool(r, "active")
|
||||||
|
|
||||||
|
targetUpstream, err := targetEndpoint.GetUpstreamOriginByMatchingIP(originIP)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Deep copy the upstream so other request handling goroutine won't be effected
|
||||||
|
newUpstream := targetUpstream.Clone()
|
||||||
|
|
||||||
|
//Overwrite the new value into the old upstream
|
||||||
|
err = json.Unmarshal([]byte(payload), &newUpstream)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Replace the old upstream with the new one
|
||||||
|
err = targetEndpoint.RemoveUpstreamOrigin(originIP)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = targetEndpoint.AddUpstreamOrigin(newUpstream, isActive)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Save changes to configs
|
||||||
|
err = SaveReverseProxyConfig(targetEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("INFO", "Unable to save upstream update to proxy config", err)
|
||||||
|
utils.SendErrorResponse(w, "Failed to save updated upstream config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update Uptime Monitor
|
||||||
|
UpdateUptimeMonitorTargets()
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReverseProxyUpstreamSetPriority(w http.ResponseWriter, r *http.Request) {
|
||||||
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target endpoint not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
weight, err := utils.PostInt(r, "weight")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "priority not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if weight < 0 {
|
||||||
|
utils.SendErrorResponse(w, "invalid weight given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Editing upstream origin IP
|
||||||
|
originIP, err := utils.PostPara(r, "origin")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "origin ip or matching address not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originIP = strings.TrimSpace(originIP)
|
||||||
|
|
||||||
|
editingUpstream, err := targetEndpoint.GetUpstreamOriginByMatchingIP(originIP)
|
||||||
|
editingUpstream.Weight = weight
|
||||||
|
// The editing upstream is a pointer to the runtime object
|
||||||
|
// and the change of weight do not requre a respawn of the proxy object
|
||||||
|
// so no need to remove & re-prepare the upstream on weight changes
|
||||||
|
|
||||||
|
err = SaveReverseProxyConfig(targetEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("INFO", "Unable to update upstream weight", err)
|
||||||
|
utils.SendErrorResponse(w, "Failed to update upstream weight")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReverseProxyUpstreamDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target endpoint not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Editing upstream origin IP
|
||||||
|
originIP, err := utils.PostPara(r, "origin")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "origin ip or matching address not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originIP = strings.TrimSpace(originIP)
|
||||||
|
|
||||||
|
if !targetEndpoint.UpstreamOriginExists(originIP) {
|
||||||
|
utils.SendErrorResponse(w, "target upstream not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = targetEndpoint.RemoveUpstreamOrigin(originIP)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//Save changes to configs
|
||||||
|
err = SaveReverseProxyConfig(targetEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("INFO", "Unable to remove upstream", err)
|
||||||
|
utils.SendErrorResponse(w, "Failed to remove upstream from proxy rule")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update uptime monitor
|
||||||
|
UpdateUptimeMonitorTargets()
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
@ -28,7 +28,7 @@ func ReverseProxyListVdir(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
var targetEndpoint *dynamicproxy.ProxyEndpoint
|
var targetEndpoint *dynamicproxy.ProxyEndpoint
|
||||||
if eptype == "host" {
|
if eptype == "host" {
|
||||||
endpoint, err := utils.PostPara(r, "ep") //Support root and host
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "endpoint not defined")
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
return
|
return
|
||||||
|
@ -51,15 +51,31 @@
|
|||||||
//Sort by RootOrMatchingDomain field
|
//Sort by RootOrMatchingDomain field
|
||||||
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
|
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
|
||||||
data.forEach(subd => {
|
data.forEach(subd => {
|
||||||
let tlsIcon = "";
|
|
||||||
let subdData = encodeURIComponent(JSON.stringify(subd));
|
let subdData = encodeURIComponent(JSON.stringify(subd));
|
||||||
if (subd.RequireTLS){
|
|
||||||
|
//Build the upstream list
|
||||||
|
let upstreams = "";
|
||||||
|
if (subd.ActiveOrigins.length == 0){
|
||||||
|
//Invalid config
|
||||||
|
upstreams = `<i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin<br>`;
|
||||||
|
}else{
|
||||||
|
subd.ActiveOrigins.forEach(upstream => {
|
||||||
|
console.log(upstream);
|
||||||
|
//Check if the upstreams require TLS connections
|
||||||
|
let tlsIcon = "";
|
||||||
|
if (upstream.RequireTLS){
|
||||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||||
if (subd.SkipCertValidations){
|
if (upstream.SkipCertValidations){
|
||||||
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
|
||||||
|
|
||||||
|
upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let inboundTlsIcon = "";
|
let inboundTlsIcon = "";
|
||||||
if ($("#tls").checkbox("is checked")){
|
if ($("#tls").checkbox("is checked")){
|
||||||
inboundTlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
inboundTlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||||
@ -102,7 +118,11 @@
|
|||||||
${aliasDomains}
|
${aliasDomains}
|
||||||
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
|
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
|
||||||
</td>
|
</td>
|
||||||
<td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
|
<td data-label="" editable="true" datatype="domain">
|
||||||
|
<div class="upstreamList">
|
||||||
|
${upstreams}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
||||||
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
||||||
${subd.RequireBasicAuth?`<i class="ui green check icon"></i> Basic Auth`:``}
|
${subd.RequireBasicAuth?`<i class="ui green check icon"></i> Basic Auth`:``}
|
||||||
@ -214,7 +234,6 @@
|
|||||||
var payload = $(row).attr("payload");
|
var payload = $(row).attr("payload");
|
||||||
payload = JSON.parse(decodeURIComponent(payload));
|
payload = JSON.parse(decodeURIComponent(payload));
|
||||||
console.log(payload);
|
console.log(payload);
|
||||||
//console.log(payload);
|
|
||||||
columns.each(function(index) {
|
columns.each(function(index) {
|
||||||
var column = $(this);
|
var column = $(this);
|
||||||
var oldValue = column.text().trim();
|
var oldValue = column.text().trim();
|
||||||
@ -228,39 +247,21 @@
|
|||||||
var input;
|
var input;
|
||||||
var datatype = $(this).attr("datatype");
|
var datatype = $(this).attr("datatype");
|
||||||
if (datatype == "domain"){
|
if (datatype == "domain"){
|
||||||
let domain = payload.Domain;
|
let useStickySessionChecked = "";
|
||||||
//Target require TLS for proxying
|
if (payload.UseStickySession){
|
||||||
let tls = payload.RequireTLS;
|
useStickySessionChecked = "checked";
|
||||||
if (tls){
|
|
||||||
tls = "checked";
|
|
||||||
}else{
|
|
||||||
tls = "";
|
|
||||||
}
|
}
|
||||||
|
input = `<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 1em;" onclick="editUpstreams('${uuid}');"><i class="grey server icon"></i> Edit Upstreams</button>
|
||||||
//Require TLS validation
|
<div class="ui divider"></div>
|
||||||
let skipTLSValidation = payload.SkipCertValidations;
|
|
||||||
let checkstate = "";
|
|
||||||
if (skipTLSValidation){
|
|
||||||
checkstate = "checked";
|
|
||||||
}
|
|
||||||
|
|
||||||
input = `
|
|
||||||
<div class="ui mini fluid input">
|
|
||||||
<input type="text" class="Domain" onchange="cleanProxyTargetValue(this)" value="${domain}">
|
|
||||||
</div>
|
|
||||||
<div class="ui checkbox" style="margin-top: 0.6em;">
|
|
||||||
<input type="checkbox" class="RequireTLS" ${tls}>
|
|
||||||
<label>Require TLS<br>
|
|
||||||
<small>Proxy target require HTTPS connection</small></label>
|
|
||||||
</div><br>
|
|
||||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
<input type="checkbox" class="SkipCertValidations" ${checkstate}>
|
<input type="checkbox" class="UseStickySession" ${useStickySessionChecked}>
|
||||||
<label>Skip Verification<br>
|
<label>Use Sticky Session<br>
|
||||||
<small>Check this if proxy target is using self signed certificates</small></label>
|
<small>Enable stick session on load balancing</small></label>
|
||||||
</div><br>
|
</div>
|
||||||
<!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="purple server icon"></i> Load Balance</button> -->
|
|
||||||
`;
|
`;
|
||||||
column.empty().append(input);
|
column.append(input);
|
||||||
|
$(column).find(".upstreamList").addClass("editing");
|
||||||
}else if (datatype == "vdir"){
|
}else if (datatype == "vdir"){
|
||||||
//Append a quick access button for vdir page
|
//Append a quick access button for vdir page
|
||||||
column.append(`<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="quickEditVdir('${uuid}');">
|
column.append(`<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="quickEditVdir('${uuid}');">
|
||||||
@ -311,12 +312,6 @@
|
|||||||
Security Options
|
Security Options
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
|
||||||
<input type="checkbox" class="SkipWebSocketOriginCheck" ${wsCheckstate}>
|
|
||||||
<label>Skip WebSocket Origin Check<br>
|
|
||||||
<small>Check this to allow cross-origin websocket requests</small></label>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
|
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
|
||||||
<label>Require Rate Limit<br>
|
<label>Require Rate Limit<br>
|
||||||
@ -399,15 +394,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var epttype = "host";
|
var epttype = "host";
|
||||||
let newDomain = $(row).find(".Domain").val();
|
let useStickySession = $(row).find(".UseStickySession")[0].checked;
|
||||||
let requireTLS = $(row).find(".RequireTLS")[0].checked;
|
|
||||||
let skipCertValidations = $(row).find(".SkipCertValidations")[0].checked;
|
|
||||||
let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
|
let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
|
||||||
let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
|
let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
|
||||||
let rateLimit = $(row).find(".RateLimit").val();
|
let rateLimit = $(row).find(".RateLimit").val();
|
||||||
let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
|
let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
|
||||||
let bypassWebsocketOrigin = $(row).find(".SkipWebSocketOriginCheck")[0].checked;
|
|
||||||
console.log(newDomain, requireTLS, skipCertValidations, requireBasicAuth)
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/api/proxy/edit",
|
url: "/api/proxy/edit",
|
||||||
@ -415,11 +406,8 @@
|
|||||||
data: {
|
data: {
|
||||||
"type": epttype,
|
"type": epttype,
|
||||||
"rootname": uuid,
|
"rootname": uuid,
|
||||||
"ep":newDomain,
|
"ss":useStickySession,
|
||||||
"bpgtls": bypassGlobalTLS,
|
"bpgtls": bypassGlobalTLS,
|
||||||
"tls" :requireTLS,
|
|
||||||
"tlsval": skipCertValidations,
|
|
||||||
"bpwsorg" : bypassWebsocketOrigin,
|
|
||||||
"bauth" :requireBasicAuth,
|
"bauth" :requireBasicAuth,
|
||||||
"rate" :requireRateLimit,
|
"rate" :requireRateLimit,
|
||||||
"ratenum" :rateLimit,
|
"ratenum" :rateLimit,
|
||||||
@ -435,21 +423,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
|
||||||
//and auto select TLS checkbox if https:// exists
|
|
||||||
function cleanProxyTargetValue(input){
|
|
||||||
let targetDomain = $(input).val().trim();
|
|
||||||
if (targetDomain.startsWith("http://")){
|
|
||||||
targetDomain = targetDomain.substr(7);
|
|
||||||
$(input).val(targetDomain);
|
|
||||||
$("#httpProxyList input.RequireTLS").parent().checkbox("set unchecked");
|
|
||||||
}else if (targetDomain.startsWith("https://")){
|
|
||||||
targetDomain = targetDomain.substr(8);
|
|
||||||
$(input).val(targetDomain);
|
|
||||||
$("#httpProxyList input.RequireTLS").parent().checkbox("set checked");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* button events */
|
/* button events */
|
||||||
function editBasicAuthCredentials(uuid){
|
function editBasicAuthCredentials(uuid){
|
||||||
let payload = encodeURIComponent(JSON.stringify({
|
let payload = encodeURIComponent(JSON.stringify({
|
||||||
@ -490,12 +463,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Open the load balance option
|
//Open the load balance option
|
||||||
function editLoadBalanceOptions(uuid){
|
function editUpstreams(uuid){
|
||||||
let payload = encodeURIComponent(JSON.stringify({
|
let payload = encodeURIComponent(JSON.stringify({
|
||||||
ept: "host",
|
ept: "host",
|
||||||
ep: uuid
|
ep: uuid
|
||||||
}));
|
}));
|
||||||
showSideWrapper("snippet/loadBalancer.html?t=" + Date.now() + "#" + payload);
|
showSideWrapper("snippet/upstreams.html?t=" + Date.now() + "#" + payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleProxyRuleToggle(object){
|
function handleProxyRuleToggle(object){
|
||||||
|
@ -122,7 +122,7 @@
|
|||||||
function initRootInfo(callback=undefined){
|
function initRootInfo(callback=undefined){
|
||||||
$.get("/api/proxy/list?type=root", function(data){
|
$.get("/api/proxy/list?type=root", function(data){
|
||||||
if (data == null){
|
if (data == null){
|
||||||
|
msgbox("Default site load failed", false);
|
||||||
}else{
|
}else{
|
||||||
var $radios = $('input:radio[name=defaultsiteOption]');
|
var $radios = $('input:radio[name=defaultsiteOption]');
|
||||||
let proxyType = data.DefaultSiteOption;
|
let proxyType = data.DefaultSiteOption;
|
||||||
@ -140,8 +140,8 @@
|
|||||||
}
|
}
|
||||||
updateAvaibleDefaultSiteOptions();
|
updateAvaibleDefaultSiteOptions();
|
||||||
|
|
||||||
$("#proxyRoot").val(data.Domain);
|
$("#proxyRoot").val(data.ActiveOrigins[0].OriginIpOrDomain);
|
||||||
checkRootRequireTLS(data.Domain);
|
checkRootRequireTLS(data.ActiveOrigins[0].OriginIpOrDomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callback != undefined){
|
if (callback != undefined){
|
||||||
@ -247,7 +247,9 @@
|
|||||||
msgbox(data.error, false, 5000);
|
msgbox(data.error, false, 5000);
|
||||||
}else{
|
}else{
|
||||||
//OK
|
//OK
|
||||||
|
|
||||||
initRootInfo(function(){
|
initRootInfo(function(){
|
||||||
|
|
||||||
//Check if WebServ is enabled
|
//Check if WebServ is enabled
|
||||||
isUsingStaticWebServerAsRoot(function(isUsingWebServ){
|
isUsingStaticWebServerAsRoot(function(isUsingWebServ){
|
||||||
if (isUsingWebServ){
|
if (isUsingWebServ){
|
||||||
@ -256,11 +258,7 @@
|
|||||||
setWebServerRunningState(true);
|
setWebServerRunningState(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(function(){
|
|
||||||
//Update the checkbox
|
|
||||||
msgbox("Default Site Updated");
|
msgbox("Default Site Updated");
|
||||||
}, 100);
|
|
||||||
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -269,6 +267,9 @@
|
|||||||
if (btn != undefined){
|
if (btn != undefined){
|
||||||
$(btn).removeClass("disabled");
|
$(btn).removeClass("disabled");
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
error: function(){
|
||||||
|
msgbox("Unknown error occured", false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,6 +11,17 @@
|
|||||||
border-radius: 0.6em;
|
border-radius: 0.6em;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.descheader{
|
||||||
|
display:none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1367px) {
|
||||||
|
.descheader{
|
||||||
|
display:auto !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="standardContainer">
|
<div class="standardContainer">
|
||||||
<div class="ui stackable grid">
|
<div class="ui stackable grid">
|
||||||
@ -26,8 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Target IP Address or Domain Name with port</label>
|
<label>Target IP Address or Domain Name with port</label>
|
||||||
<input type="text" id="proxyDomain" onchange="autoCheckTls(this.value);">
|
<input type="text" id="proxyDomain" onchange="autoFillTargetTLS(this);">
|
||||||
<small>E.g. 192.168.0.101:8000 or example.com</small>
|
<small>e.g. 192.168.0.101:8000 or example.com</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="field dockerOptimizations" style="display:none;">
|
<div class="field dockerOptimizations" style="display:none;">
|
||||||
<button style="margin-top: -2em;" class="ui basic small button" onclick="openDockerContainersList();"><i class="blue docker icon"></i> Pick from Docker Containers</button>
|
<button style="margin-top: -2em;" class="ui basic small button" onclick="openDockerContainersList();"><i class="blue docker icon"></i> Pick from Docker Containers</button>
|
||||||
@ -47,16 +58,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Access Rule</label>
|
<div class="ui checkbox">
|
||||||
<div class="ui selection dropdown">
|
<input type="checkbox" id="useStickySessionLB">
|
||||||
<input type="hidden" id="newProxyRuleAccessFilter" value="default">
|
<label>Sticky Session<br><small>Enable stick session on upstream load balancing</small></label>
|
||||||
<i class="dropdown icon"></i>
|
|
||||||
<div class="default text">Default</div>
|
|
||||||
<div class="menu" id="newProxyRuleAccessList">
|
|
||||||
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small>Allow regional access control using blacklist or whitelist. Use "default" for "allow all".</small>
|
<div class="ui horizontal divider">
|
||||||
|
<i class="ui green lock icon"></i>
|
||||||
|
Security
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
@ -76,21 +85,21 @@
|
|||||||
<label>Allow plain HTTP access<br><small>Allow this subdomain to be connected without TLS (Require HTTP server enabled on port 80)</small></label>
|
<label>Allow plain HTTP access<br><small>Allow this subdomain to be connected without TLS (Require HTTP server enabled on port 80)</small></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="ui horizontal divider">
|
||||||
<div class="ui checkbox">
|
<i class="ui red ban icon"></i>
|
||||||
<input type="checkbox" id="requireRateLimit">
|
Access Control
|
||||||
<label>Require Rate Limit<br><small>This proxy endpoint will be rate limited.</small></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Rate Limit</label>
|
<label>Access Rule</label>
|
||||||
<div class="ui fluid right labeled input">
|
<div class="ui selection dropdown">
|
||||||
<input type="number" id="proxyRateLimit" placeholder="100" min="1" max="1000" value="100">
|
<input type="hidden" id="newProxyRuleAccessFilter" value="default">
|
||||||
<div class="ui basic label">
|
<i class="dropdown icon"></i>
|
||||||
req / sec / IP
|
<div class="default text">Default</div>
|
||||||
|
<div class="menu" id="newProxyRuleAccessList">
|
||||||
|
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small>Return a 429 error code if request rate exceed the rate limit.</small>
|
<small>Allow regional access control using blacklist or whitelist. Use "default" for "allow all".</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
@ -125,6 +134,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" id="requireRateLimit">
|
||||||
|
<label>Require Rate Limit<br><small>This proxy endpoint will be rate limited.</small></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Rate Limit</label>
|
||||||
|
<div class="ui fluid right labeled input">
|
||||||
|
<input type="number" id="proxyRateLimit" placeholder="100" min="1" max="1000" value="100">
|
||||||
|
<div class="ui basic label">
|
||||||
|
req / sec / IP
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small>Return a 429 error code if request rate exceed the rate limit.</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -160,16 +185,17 @@
|
|||||||
|
|
||||||
//New Proxy Endpoint
|
//New Proxy Endpoint
|
||||||
function newProxyEndpoint(){
|
function newProxyEndpoint(){
|
||||||
var rootname = $("#rootname").val();
|
let rootname = $("#rootname").val();
|
||||||
var proxyDomain = $("#proxyDomain").val();
|
let proxyDomain = $("#proxyDomain").val();
|
||||||
var useTLS = $("#reqTls")[0].checked;
|
let useTLS = $("#reqTls")[0].checked;
|
||||||
var skipTLSValidation = $("#skipTLSValidation")[0].checked;
|
let skipTLSValidation = $("#skipTLSValidation")[0].checked;
|
||||||
var bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked;
|
let bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked;
|
||||||
var requireBasicAuth = $("#requireBasicAuth")[0].checked;
|
let requireBasicAuth = $("#requireBasicAuth")[0].checked;
|
||||||
var proxyRateLimit = $("#proxyRateLimit").val();
|
let proxyRateLimit = $("#proxyRateLimit").val();
|
||||||
var requireRateLimit = $("#requireRateLimit")[0].checked;
|
let requireRateLimit = $("#requireRateLimit")[0].checked;
|
||||||
var skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked;
|
let skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked;
|
||||||
var accessRuleToUse = $("#newProxyRuleAccessFilter").val();
|
let accessRuleToUse = $("#newProxyRuleAccessFilter").val();
|
||||||
|
let useStickySessionLB = $("#useStickySessionLB")[0].checked;
|
||||||
|
|
||||||
if (rootname.trim() == ""){
|
if (rootname.trim() == ""){
|
||||||
$("#rootname").parent().addClass("error");
|
$("#rootname").parent().addClass("error");
|
||||||
@ -201,6 +227,7 @@
|
|||||||
ratenum: proxyRateLimit,
|
ratenum: proxyRateLimit,
|
||||||
cred: JSON.stringify(credentials),
|
cred: JSON.stringify(credentials),
|
||||||
access: accessRuleToUse,
|
access: accessRuleToUse,
|
||||||
|
stickysess: useStickySessionLB,
|
||||||
},
|
},
|
||||||
success: function(data){
|
success: function(data){
|
||||||
if (data.error != undefined){
|
if (data.error != undefined){
|
||||||
@ -259,7 +286,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
||||||
|
//and auto select TLS checkbox if https:// exists
|
||||||
|
function autoFillTargetTLS(input){
|
||||||
|
let targetDomain = $(input).val().trim();
|
||||||
|
if (targetDomain.startsWith("http://")){
|
||||||
|
targetDomain = targetDomain.substr(7);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$("#reqTls").parent().checkbox("set unchecked");
|
||||||
|
}else if (targetDomain.startsWith("https://")){
|
||||||
|
targetDomain = targetDomain.substr(8);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$("#reqTls").parent().checkbox("set checked");
|
||||||
|
}else{
|
||||||
|
//No http or https was given. Sniff it
|
||||||
|
autoCheckTls(targetDomain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Automatic check if the site require TLS and check the checkbox if needed
|
||||||
function autoCheckTls(targetDomain){
|
function autoCheckTls(targetDomain){
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/api/proxy/tlscheck",
|
url: "/api/proxy/tlscheck",
|
||||||
@ -453,7 +499,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* UI Element Initialization */
|
/* UI Element Initialization */
|
||||||
|
function initAdvanceSettingsAccordion(){
|
||||||
|
function hasClickEvent(element) {
|
||||||
|
var events = $._data(element, "events");
|
||||||
|
return events && events.click && events.click.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasClickEvent($("#advanceProxyRules"))){
|
||||||
|
// Not sure why sometime the accordion events are not binding
|
||||||
|
// to the DOM element. This makes sure the element is binded
|
||||||
|
// correctly by checking it again after 300ms
|
||||||
$("#advanceProxyRules").accordion();
|
$("#advanceProxyRules").accordion();
|
||||||
$("#newProxyRuleAccessFilter").parent().dropdown();
|
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||||
|
setTimeout(function(){
|
||||||
|
initAdvanceSettingsAccordion();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initAdvanceSettingsAccordion();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
@ -1,7 +1,7 @@
|
|||||||
<div class="standardContainer">
|
<div class="standardContainer">
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
<h2>Service Expose Proxy</h2>
|
<h2>Single-Sign-On</h2>
|
||||||
<p>Expose your local test-site on the internet with single command</p>
|
<p>Create and manage accounts with Zoraxy!</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui message">
|
<div class="ui message">
|
||||||
<h4>Work In Progress</h4>
|
<h4>Work In Progress</h4>
|
@ -120,7 +120,12 @@
|
|||||||
<!-- Config Tools -->
|
<!-- Config Tools -->
|
||||||
<h3>System Backup & Restore</h3>
|
<h3>System Backup & Restore</h3>
|
||||||
<p>Options related to system backup, migrate and restore.</p>
|
<p>Options related to system backup, migrate and restore.</p>
|
||||||
<button class="ui basic button" onclick="showSideWrapper('snippet/configTools.html');">Open Config Tools</button>
|
<button class="ui basic button" onclick="showSideWrapper('snippet/configTools.html');"><i class="ui green undo icon icon"></i> Open Config Tools</button>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<!-- Log Viewer -->
|
||||||
|
<h3>System Log Viewer</h3>
|
||||||
|
<p>View and download Zoraxy log</p>
|
||||||
|
<button class="ui basic button" onclick="launchToolWithSize('snippet/logview.html', 1024, 768);"><i class="ui blue file icon"></i> Open Log Viewer</button>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<!-- System Information -->
|
<!-- System Information -->
|
||||||
<div id="zoraxyinfo">
|
<div id="zoraxyinfo">
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
<title>Control Panel | Zoraxy</title>
|
<title>Control Panel | Zoraxy</title>
|
||||||
<link rel="stylesheet" href="script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="script/semantic/semantic.min.css">
|
||||||
<script src="script/jquery-3.6.0.min.js"></script>
|
<script src="script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/ao_module.js"></script>
|
|
||||||
<script src="script/semantic/semantic.min.js"></script>
|
<script src="script/semantic/semantic.min.js"></script>
|
||||||
<script src="script/tablesort.js"></script>
|
<script src="script/tablesort.js"></script>
|
||||||
<script src="script/countryCode.js"></script>
|
<script src="script/countryCode.js"></script>
|
||||||
@ -56,21 +55,21 @@
|
|||||||
<i class="simplistic exchange icon"></i> Stream Proxy
|
<i class="simplistic exchange icon"></i> Stream Proxy
|
||||||
</a>
|
</a>
|
||||||
<div class="ui divider menudivider">Access & Connections</div>
|
<div class="ui divider menudivider">Access & Connections</div>
|
||||||
<a class="item" tag="cert">
|
|
||||||
<i class="simplistic lock icon"></i> TLS / SSL certificates
|
|
||||||
</a>
|
|
||||||
<a class="item" tag="redirectset">
|
<a class="item" tag="redirectset">
|
||||||
<i class="simplistic level up alternate icon"></i> Redirection
|
<i class="simplistic level up alternate icon"></i> Redirection
|
||||||
</a>
|
</a>
|
||||||
<a class="item" tag="access">
|
<a class="item" tag="access">
|
||||||
<i class="simplistic ban icon"></i> Access Control
|
<i class="simplistic ban icon"></i> Access Control
|
||||||
</a>
|
</a>
|
||||||
<div class="ui divider menudivider">Bridging</div>
|
|
||||||
<a class="item" tag="gan">
|
<a class="item" tag="gan">
|
||||||
<i class="simplistic globe icon"></i> Global Area Network
|
<i class="simplistic globe icon"></i> Global Area Network
|
||||||
</a>
|
</a>
|
||||||
<a class="item" tag="zgrok">
|
<div class="ui divider menudivider">Security</div>
|
||||||
<i class="simplistic podcast icon"></i> Service Expose Proxy
|
<a class="item" tag="cert">
|
||||||
|
<i class="simplistic lock icon"></i> TLS / SSL certificates
|
||||||
|
</a>
|
||||||
|
<a class="item" tag="sso">
|
||||||
|
<i class="simplistic user circle icon"></i> SSO / Oauth
|
||||||
</a>
|
</a>
|
||||||
<div class="ui divider menudivider">Others</div>
|
<div class="ui divider menudivider">Others</div>
|
||||||
<a class="item" tag="webserv">
|
<a class="item" tag="webserv">
|
||||||
@ -121,8 +120,8 @@
|
|||||||
<!-- Global Area Networking -->
|
<!-- Global Area Networking -->
|
||||||
<div id="gan" class="functiontab" target="gan.html"></div>
|
<div id="gan" class="functiontab" target="gan.html"></div>
|
||||||
|
|
||||||
<!-- Service Expose Proxy -->
|
<!-- SSO / Oauth services -->
|
||||||
<div id="zgrok" class="functiontab" target="zgrok.html"></div>
|
<div id="sso" class="functiontab" target="sso.html"></div>
|
||||||
|
|
||||||
<!-- TCP Proxy -->
|
<!-- TCP Proxy -->
|
||||||
<div id="streamproxy" class="functiontab" target="streamprox.html"></div>
|
<div id="streamproxy" class="functiontab" target="streamprox.html"></div>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
@ -133,11 +134,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field dnsChallengeOnly" style="display:none;">
|
<div class="field dnsChallengeOnly" style="display:none;">
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<p>DNS Credentials (Leave all fields empty to use previous settings)<br>
|
<p>DNS Credentials</p>
|
||||||
<small><i class="yellow exclamation triangle icon"></i> Note that domain DNS credentials are stored separately. For each new subdomain, you will need to enter a new DNS credentials.</small></p>
|
|
||||||
<div id="dnsProviderAPIFields">
|
<div id="dnsProviderAPIFields">
|
||||||
|
<p><i class="ui loading circle notch icon"></i> Generating WebForm</p>
|
||||||
</div>
|
</div>
|
||||||
|
<h4><i class="yellow exclamation triangle icon"></i> Notes & FAQ</h4>
|
||||||
|
<div class="ui bulleted list">
|
||||||
|
<div class="item">Domain DNS credentials are stored separately. For each new subdomain, you will need to enter a new DNS credentials.</div>
|
||||||
|
<div class="item">For some DNS providers like CloudFlare, you do not need to fill in all fields.</div>
|
||||||
|
<div class="item">If you are not sure what to fill in, check out the documentation from <a href="https://go-acme.github.io/lego/dns/" target="_blank">lego (DNS challenge library)</a></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<label>Credentials File Content</label>
|
<label>Credentials File Content</label>
|
||||||
<textarea id="dnsCredentials" placeholder=""></textarea>
|
<textarea id="dnsCredentials" placeholder=""></textarea>
|
||||||
@ -740,6 +747,9 @@
|
|||||||
function toggleDnsChallenge(){
|
function toggleDnsChallenge(){
|
||||||
if ( $("#useDnsChallenge")[0].checked){
|
if ( $("#useDnsChallenge")[0].checked){
|
||||||
$(".dnsChallengeOnly").show();
|
$(".dnsChallengeOnly").show();
|
||||||
|
setTimeout(function(){
|
||||||
|
$("#dnsProvider").dropdown("set text", "Cloudflare");
|
||||||
|
}, 500);
|
||||||
}else{
|
}else{
|
||||||
$(".dnsChallengeOnly").hide();
|
$(".dnsChallengeOnly").hide();
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
@ -51,8 +52,8 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p>
|
<p>
|
||||||
<i class="angle double right blue icon"></i> Sent additional custom headers to origin server <br>
|
<i class="angle double right blue icon"></i> Add or remove headers before sending to origin server <br>
|
||||||
<i class="angle double left orange icon"></i> Inject custom headers into origin server responses
|
<i class="angle double left orange icon"></i> Modify headers from origin server responses before sending to client
|
||||||
</p>
|
</p>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<h4>Edit Custom Header</h4>
|
<h4>Edit Custom Header</h4>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css" />
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css" />
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<!-- Notes: This should be open in its original path-->
|
|
||||||
<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>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<br>
|
|
||||||
<div class="ui container">
|
|
||||||
<div class="ui header">
|
|
||||||
<div class="content">
|
|
||||||
Load Balance
|
|
||||||
<div class="sub header epname"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
<div class="field" >
|
|
||||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br><br><br><br>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
let aliasList = [];
|
|
||||||
let editingEndpoint = {};
|
|
||||||
|
|
||||||
if (window.location.hash.length > 1){
|
|
||||||
let payloadHash = window.location.hash.substr(1);
|
|
||||||
try{
|
|
||||||
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
|
||||||
$(".epname").text(payloadHash.ep);
|
|
||||||
editingEndpoint = payloadHash;
|
|
||||||
}catch(ex){
|
|
||||||
console.log("Unable to load endpoint data from hash")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeThisWrapper(){
|
|
||||||
parent.hideSideWrapper(true);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
155
src/web/snippet/logview.html
Normal file
155
src/web/snippet/logview.html
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html ng-app="App">
|
||||||
|
<head>
|
||||||
|
<title>System Logs</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
|
||||||
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
|
<script type="text/javascript" src="../script/jquery-3.6.0.min.js"></script>
|
||||||
|
<script type="text/javascript" src="../script/semantic/semantic.min.js"></script>
|
||||||
|
<style>
|
||||||
|
.clickable{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.clickable:hover{
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.logfile{
|
||||||
|
padding-left: 1em !important;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loglist{
|
||||||
|
background-color: rgb(250, 250, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfile .showing{
|
||||||
|
position: absolute;
|
||||||
|
top: 0.18em;
|
||||||
|
right: 0em;
|
||||||
|
margin-right: -0.4em;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfile.active .showing{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logrender{
|
||||||
|
width: 100% !important;
|
||||||
|
height: calc(100% - 1.2em);
|
||||||
|
min-height: calc(90vh - 1.2em) !important;
|
||||||
|
border: 0px solid transparent !important;
|
||||||
|
background-color: #252630;
|
||||||
|
color: white;
|
||||||
|
font-family: monospace;
|
||||||
|
overflow-x: scroll !important;
|
||||||
|
white-space: pre;
|
||||||
|
resize: none;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logrender::selection{
|
||||||
|
background:#3643bb;
|
||||||
|
color:white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui stackable grid">
|
||||||
|
<div class="four wide column loglist">
|
||||||
|
<h3 class="ui header" style="padding-top: 1em;">
|
||||||
|
<div class="content">
|
||||||
|
Log View
|
||||||
|
<div class="sub header">Check System Log in Real Time</div>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div id="logList" class="ui accordion">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<small>Notes: Some log files might be huge. Make sure you have checked the log file size before opening</small>
|
||||||
|
</div>
|
||||||
|
<div class="twelve wide column">
|
||||||
|
<textarea id="logrender" spellcheck="false" readonly="true">
|
||||||
|
← Pick a log file from the left menu to start debugging
|
||||||
|
</textarea>
|
||||||
|
<a href="#" onclick="openLogInNewTab();">Open In New Tab</a>
|
||||||
|
<br><br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
var currentOpenedLogURL = "";
|
||||||
|
|
||||||
|
function openLogInNewTab(){
|
||||||
|
if (currentOpenedLogURL != ""){
|
||||||
|
window.open(currentOpenedLogURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openLog(object, catergory, filename){
|
||||||
|
$(".logfile.active").removeClass('active');
|
||||||
|
$(object).addClass("active");
|
||||||
|
currentOpenedLogURL = "/api/log/read?file=" + filename;
|
||||||
|
$.get(currentOpenedLogURL, function(data){
|
||||||
|
if (data.error !== undefined){
|
||||||
|
alert(data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#logrender").val(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initLogList(){
|
||||||
|
$("#logList").html("");
|
||||||
|
$.get("/api/log/list", function(data){
|
||||||
|
//console.log(data);
|
||||||
|
for (let [key, value] of Object.entries(data)) {
|
||||||
|
console.log(key, value);
|
||||||
|
value.reverse(); //Default value was from oldest to newest
|
||||||
|
var fileItemList = "";
|
||||||
|
value.forEach(file => {
|
||||||
|
fileItemList += `<div class="item clickable logfile" onclick="openLog(this, '${key}','${file.Filename}');">
|
||||||
|
<i class="file outline icon"></i>
|
||||||
|
<div class="content">
|
||||||
|
${file.Title} (${formatBytes(file.Filesize)})
|
||||||
|
<div class="showing"><i class="green chevron right icon"></i></div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
})
|
||||||
|
$("#logList").append(`<div class="title">
|
||||||
|
<i class="dropdown icon"></i>
|
||||||
|
${key}
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="ui list">
|
||||||
|
${fileItemList}
|
||||||
|
</div>
|
||||||
|
</div>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".ui.accordion").accordion();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
initLogList();
|
||||||
|
|
||||||
|
|
||||||
|
function formatBytes(x){
|
||||||
|
var units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
|
let l = 0, n = parseInt(x, 10) || 0;
|
||||||
|
while(n >= 1024 && ++l){
|
||||||
|
n = n/1024;
|
||||||
|
}
|
||||||
|
return(n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + units[l]);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</html>
|
@ -1,6 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
528
src/web/snippet/upstreams.html
Normal file
528
src/web/snippet/upstreams.html
Normal file
@ -0,0 +1,528 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<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>
|
||||||
|
<style>
|
||||||
|
.upstreamActions{
|
||||||
|
position: absolute;
|
||||||
|
top: 0.6em;
|
||||||
|
right: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upstreamLink{
|
||||||
|
max-width: 220px;
|
||||||
|
display: inline-block;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upstreamEntry .ui.toggle.checkbox input:checked ~ label::before{
|
||||||
|
background-color: #00ca52 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#activateNewUpstream.ui.toggle.checkbox input:checked ~ label::before{
|
||||||
|
background-color: #00ca52 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upstreamTable{
|
||||||
|
max-height: 480px;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
padding: 0.3em;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upstreamEntry.inactive{
|
||||||
|
background-color: #f3f3f3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upstreamEntry{
|
||||||
|
border-radius: 0.4em !important;
|
||||||
|
border: 1px solid rgb(233, 233, 233) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 499px) {
|
||||||
|
.upstreamActions{
|
||||||
|
position: relative;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-left: 0.4em;
|
||||||
|
margin-bottom: 0.4em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui header">
|
||||||
|
<div class="content">
|
||||||
|
Upstreams / Load Balance
|
||||||
|
<div class="sub header epname"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui small pointing secondary menu">
|
||||||
|
<a class="item active narrowpadding" data-tab="upstreamlist">Upstreams</a>
|
||||||
|
<a class="item narrowpadding" data-tab="newupstream">Add Upstream</a>
|
||||||
|
</div>
|
||||||
|
<div class="ui tab basic segment active" data-tab="upstreamlist">
|
||||||
|
<!-- A list of current existing upstream on this reverse proxy-->
|
||||||
|
<div id="upstreamTable">
|
||||||
|
<div class="ui segment">
|
||||||
|
<a><i class="ui loading spinner icon"></i> Loading</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui message">
|
||||||
|
<i class="ui blue info circle icon"></i> Weighted random will be used for load-balancing. Set weight to 0 for fallback only.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui tab basic segment" data-tab="newupstream">
|
||||||
|
<!-- Web Form to create a new upstream -->
|
||||||
|
<h4 class="ui header">
|
||||||
|
<i class="green add icon"></i>
|
||||||
|
<div class="content">
|
||||||
|
Add Upstream Server
|
||||||
|
<div class="sub header">Create new load balance or fallback upstream origin</div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
<p style="margin-bottom: 0.4em;">Target IP address with port</p>
|
||||||
|
<div class="ui fluid small input">
|
||||||
|
<input type="text" id="originURL" onchange="cleanProxyTargetValue(this);"><br>
|
||||||
|
</div>
|
||||||
|
<small>E.g. 192.168.0.101:8000 or example.com</small>
|
||||||
|
<br><br>
|
||||||
|
<div id="activateNewUpstream" class="ui toggle checkbox" style="display:inline-block;">
|
||||||
|
<input type="checkbox" id="activateNewUpstreamCheckbox" style="margin-top: 0.4em;" checked>
|
||||||
|
<label>Activate<br>
|
||||||
|
<small>Enable this upstream for load balancing</small></label>
|
||||||
|
</div><br>
|
||||||
|
<div class="ui checkbox" style="margin-top: 1.2em;">
|
||||||
|
<input type="checkbox" id="requireTLS">
|
||||||
|
<label>Require TLS<br>
|
||||||
|
<small>Proxy target require HTTPS connection</small></label>
|
||||||
|
</div><br>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.6em;">
|
||||||
|
<input type="checkbox" id="skipTlsVerification">
|
||||||
|
<label>Skip Verification<br>
|
||||||
|
<small>Check this if proxy target is using self signed certificates</small></label>
|
||||||
|
</div><br>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
|
<input type="checkbox" id="SkipWebSocketOriginCheck" checked>
|
||||||
|
<label>Skip WebSocket Origin Check<br>
|
||||||
|
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
<button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="field" >
|
||||||
|
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br><br><br><br>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let origins = [];
|
||||||
|
let editingEndpoint = {};
|
||||||
|
$('.menu .item').tab();
|
||||||
|
|
||||||
|
|
||||||
|
function initOriginList(){
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/upstream/list",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
"type":"host",
|
||||||
|
"ep": editingEndpoint.ep
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
//This endpoint not exists?
|
||||||
|
alert(data.error);
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
$("#upstreamTable").html("");
|
||||||
|
|
||||||
|
if (data.ActiveOrigins.length == 0){
|
||||||
|
//There is no upstream for this proxy rule
|
||||||
|
$("#upstreamTable").append(`<tr>
|
||||||
|
<td colspan="2"><b><i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin</b><br>
|
||||||
|
This HTTP proxy rule will always return Error 521 when requested. To fix this, add or enable a upstream origin to this proxy endpoint.
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
</td>
|
||||||
|
</tr>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.ActiveOrigins.forEach(upstream => {
|
||||||
|
renderUpstreamEntryToTable(upstream, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
data.InactiveOrigins.forEach(upstream => {
|
||||||
|
renderUpstreamEntryToTable(upstream, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
let totalUpstreams = data.ActiveOrigins.length + data.InactiveOrigins.length;
|
||||||
|
if (totalUpstreams == 1){
|
||||||
|
$(".lowPriorityButton").addClass('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent && parent.document.getElementById("httpProxyList") != null){
|
||||||
|
//Also update the parent display
|
||||||
|
let element = $(parent.document.getElementById("httpProxyList")).find(".upstreamList.editing");
|
||||||
|
let upstreams = "";
|
||||||
|
data.ActiveOrigins.forEach(upstream => {
|
||||||
|
//Check if the upstreams require TLS connections
|
||||||
|
let tlsIcon = "";
|
||||||
|
if (upstream.RequireTLS){
|
||||||
|
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||||
|
if (upstream.SkipCertValidations){
|
||||||
|
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
|
||||||
|
|
||||||
|
upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.ActiveOrigins.length == 0){
|
||||||
|
upstreams = `<i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin<br>`
|
||||||
|
}
|
||||||
|
$(element).html(upstreams);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".ui.checkbox").checkbox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUpstreamEntryToTable(upstream, isActive){
|
||||||
|
function newUID(){return"00000000-0000-4000-8000-000000000000".replace(/0/g,function(){return(0|Math.random()*16).toString(16)})};
|
||||||
|
let tlsIcon = "";
|
||||||
|
if (upstream.RequireTLS){
|
||||||
|
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||||
|
if (upstream.SkipCertValidations){
|
||||||
|
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Priority Arrows
|
||||||
|
let downArrowClass = "";
|
||||||
|
if (upstream.Weight == 0 ){
|
||||||
|
//Cannot go any lower
|
||||||
|
downArrowClass = "disabled";
|
||||||
|
}
|
||||||
|
let url = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`
|
||||||
|
let payload = encodeURIComponent(JSON.stringify(upstream));
|
||||||
|
let domUID = newUID();
|
||||||
|
$("#upstreamTable").append(`<div class="ui upstreamEntry ${isActive?"":"inactive"} basic segment" data-domid="${domUID}" data-payload="${payload}" data-priority="${upstream.Priority}">
|
||||||
|
<h4 class="ui header">
|
||||||
|
<div class="ui toggle checkbox" style="display:inline-block;">
|
||||||
|
<input type="checkbox" class="enableState" name="enabled" style="margin-top: 0.4em;" onchange="saveUpstreamUpdate('${domUID}');" ${isActive?"checked":""}>
|
||||||
|
<label></label>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<a href="${url}" target="_blank" class="upstreamLink">${upstream.OriginIpOrDomain} ${tlsIcon}</a>
|
||||||
|
<div class="sub header">${isActive?(upstream.Weight==0?"Fallback Only":"Active"):"Inactive"} | Weight: ${upstream.Weight}x </div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
<div class="advanceOptions" style="display:none;">
|
||||||
|
<div class="upstreamOriginField">
|
||||||
|
<p>New upstream origin IP address or domain</p>
|
||||||
|
<div class="ui small fluid input" style="margin-top: -0.6em;">
|
||||||
|
<input type="text" class="newOrigin" value="${upstream.OriginIpOrDomain}" onchange="handleAutoOriginClean('${domUID}');">
|
||||||
|
</div>
|
||||||
|
<small>e.g. 192.168.0.101:8000 or example.com</small>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" class="reqTLSCheckbox" ${upstream.RequireTLS?"checked":""}>
|
||||||
|
<label>Require TLS<br>
|
||||||
|
<small>Proxy target require HTTPS connection</small></label>
|
||||||
|
</div><br>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.6em;">
|
||||||
|
<input type="checkbox" class="skipVerificationCheckbox" ${upstream.SkipCertValidations?"checked":""}>
|
||||||
|
<label>Skip Verification<br>
|
||||||
|
<small>Check this if proxy target is using self signed certificates</small></label>
|
||||||
|
</div><br>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
|
<input type="checkbox" class="SkipWebSocketOriginCheck" ${upstream.SkipWebSocketOriginCheck?"checked":""}>
|
||||||
|
<label>Skip WebSocket Origin Check<br>
|
||||||
|
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||||
|
</div><br>
|
||||||
|
</div>
|
||||||
|
<div class="upstreamActions">
|
||||||
|
<!-- Change Priority -->
|
||||||
|
<button class="ui basic circular icon button highPriorityButton" title="Increase Weight" onclick="increaseUpstreamWeight('${domUID}');"><i class="ui arrow up icon"></i></button>
|
||||||
|
<button class="ui basic circular icon button lowPriorityButton ${downArrowClass}" title="Reduce Weight" onclick="decreaseUpstreamWeight('${domUID}');"><i class="ui arrow down icon"></i></button>
|
||||||
|
<button class="ui basic circular icon editbtn button" onclick="handleUpstreamOriginEdit('${domUID}');" title="Edit Upstream Destination"><i class="ui grey edit icon"></i></button>
|
||||||
|
<button style="display:none;" class="ui basic circular icon trashbtn button" onclick="removeUpstream('${domUID}');" title="Remove Upstream"><i class="ui red trash icon"></i></button>
|
||||||
|
<button style="display:none;" class="ui basic circular icon savebtn button" onclick="saveUpstreamUpdate('${domUID}');" title="Save Changes"><i class="ui green save icon"></i></button>
|
||||||
|
<button style="display:none;" class="ui basic circular icon cancelbtn button" onclick="initOriginList();" title="Cancel"><i class="ui grey times icon"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* New Upstream Origin Functions */
|
||||||
|
|
||||||
|
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
||||||
|
//and auto select TLS checkbox if https:// exists
|
||||||
|
function cleanProxyTargetValue(input){
|
||||||
|
let targetDomain = $(input).val().trim();
|
||||||
|
if (targetDomain.startsWith("http://")){
|
||||||
|
targetDomain = targetDomain.substr(7);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$("#requireTLS").parent().checkbox("set unchecked");
|
||||||
|
}else if (targetDomain.startsWith("https://")){
|
||||||
|
targetDomain = targetDomain.substr(8);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$("#requireTLS").parent().checkbox("set checked");
|
||||||
|
}else{
|
||||||
|
//URL does not contains https or http protocol tag
|
||||||
|
//sniff header
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/tlscheck",
|
||||||
|
data: {url: targetDomain},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
|
||||||
|
}else if (data == "https"){
|
||||||
|
$("#requireTLS").parent().checkbox("set checked");
|
||||||
|
}else if (data == "http"){
|
||||||
|
$("#requireTLS").parent().checkbox("set unchecked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add a new upstream to this http proxy rule
|
||||||
|
function addNewUpstream(){
|
||||||
|
let origin = $("#originURL").val().trim();
|
||||||
|
let requireTLS = $("#requireTLS")[0].checked;
|
||||||
|
let skipVerification = $("#skipTlsVerification")[0].checked;
|
||||||
|
let skipWebSocketOriginCheck = $("#SkipWebSocketOriginCheck")[0].checked;
|
||||||
|
let activateLoadbalancer = $("#activateNewUpstreamCheckbox")[0].checked;
|
||||||
|
|
||||||
|
if (origin == ""){
|
||||||
|
parent.msgbox("Upstream origin cannot be empty", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/upstream/add",
|
||||||
|
method: "POST",
|
||||||
|
data:{
|
||||||
|
"ep": editingEndpoint.ep,
|
||||||
|
"origin": origin,
|
||||||
|
"tls": requireTLS,
|
||||||
|
"tlsval": skipVerification,
|
||||||
|
"bpwsorg":skipWebSocketOriginCheck,
|
||||||
|
"active": activateLoadbalancer,
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
parent.msgbox("New upstream origin added");
|
||||||
|
initOriginList();
|
||||||
|
$("#originURL").val("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get a upstream setting data from DOM element
|
||||||
|
function getUpstreamSettingFromDOM(upstream){
|
||||||
|
//Get the original setting from DOM payload
|
||||||
|
let originalSettings = $(upstream).attr("data-payload");
|
||||||
|
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||||
|
|
||||||
|
//Get the updated settings if any
|
||||||
|
let requireTLS = $(upstream).find(".reqTLSCheckbox")[0].checked;
|
||||||
|
let skipTLSVerification = $(upstream).find(".skipVerificationCheckbox")[0].checked;
|
||||||
|
let skipWebSocketOriginCheck = $(upstream).find(".SkipWebSocketOriginCheck")[0].checked;
|
||||||
|
|
||||||
|
//Update the original setting with new one just applied
|
||||||
|
originalSettings.OriginIpOrDomain = $(upstream).find(".newOrigin").val();
|
||||||
|
originalSettings.RequireTLS = requireTLS;
|
||||||
|
originalSettings.SkipCertValidations = skipTLSVerification;
|
||||||
|
originalSettings.SkipWebSocketOriginCheck = skipWebSocketOriginCheck;
|
||||||
|
|
||||||
|
//console.log(originalSettings);
|
||||||
|
return originalSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle setting change on upstream config
|
||||||
|
function saveUpstreamUpdate(upstreamDomID){
|
||||||
|
let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
|
||||||
|
let originalSettings = $(targetDOM).attr("data-payload");
|
||||||
|
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||||
|
let newConfig = getUpstreamSettingFromDOM(targetDOM);
|
||||||
|
let isActive = $(targetDOM).find(".enableState")[0].checked;
|
||||||
|
console.log(newConfig);
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/upstream/update",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
ep: editingEndpoint.ep,
|
||||||
|
origin: originalSettings.OriginIpOrDomain, //Original ip or domain as key
|
||||||
|
payload: JSON.stringify(newConfig),
|
||||||
|
active: isActive,
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
parent.msgbox("Upstream setting updated");
|
||||||
|
initOriginList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Edit the upstream origin of this upstream entry
|
||||||
|
function handleUpstreamOriginEdit(upstreamDomID){
|
||||||
|
let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
|
||||||
|
let originalSettings = getUpstreamSettingsFromDomID(upstreamDomID);
|
||||||
|
let originIP = originalSettings.OriginIpOrDomain;
|
||||||
|
|
||||||
|
//Change the UI to edit mode
|
||||||
|
$(".editbtn").hide();
|
||||||
|
$(".lowPriorityButton").hide();
|
||||||
|
$(".highPriorityButton").hide();
|
||||||
|
$(targetDOM).find(".trashbtn").show();
|
||||||
|
$(targetDOM).find(".savebtn").show();
|
||||||
|
$(targetDOM).find(".cancelbtn").show();
|
||||||
|
$(targetDOM).find(".advanceOptions").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the entered URL contains http or https
|
||||||
|
function handleAutoOriginClean(domid){
|
||||||
|
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||||
|
let targetTLSCheckbox = $(targetDOM).find(".reqTLSCheckbox");
|
||||||
|
let targetDomain = $(targetDOM).find(".newOrigin").val().trim();
|
||||||
|
if (targetDomain.startsWith("http://")){
|
||||||
|
targetDomain = targetDomain.substr(7);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$(targetTLSCheckbox).parent().checkbox("set unchecked");
|
||||||
|
}else if (targetDomain.startsWith("https://")){
|
||||||
|
targetDomain = targetDomain.substr(8);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$(targetTLSCheckbox).parent().checkbox("set checked");
|
||||||
|
}else{
|
||||||
|
//URL does not contains https or http protocol tag
|
||||||
|
//sniff header
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/tlscheck",
|
||||||
|
data: {url: targetDomain},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
|
||||||
|
}else if (data == "https"){
|
||||||
|
$(targetTLSCheckbox).parent().checkbox("set checked");
|
||||||
|
}else if (data == "http"){
|
||||||
|
$(targetTLSCheckbox).parent().checkbox("set unchecked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUpstreamSettingsFromDomID(domid){
|
||||||
|
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||||
|
if (targetDOM.length == 0){
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let upstreamSettings = $(targetDOM).attr("data-payload");
|
||||||
|
upstreamSettings = JSON.parse(decodeURIComponent(upstreamSettings));
|
||||||
|
return upstreamSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
function increaseUpstreamWeight(domid){
|
||||||
|
let upstreamSetting = getUpstreamSettingsFromDomID(domid);
|
||||||
|
let originIP = upstreamSetting.OriginIpOrDomain;
|
||||||
|
let currentWeight = upstreamSetting.Weight;
|
||||||
|
setUpstreamWeight(originIP, currentWeight+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decreaseUpstreamWeight(domid){
|
||||||
|
let upstreamSetting = getUpstreamSettingsFromDomID(domid);
|
||||||
|
let originIP = upstreamSetting.OriginIpOrDomain;
|
||||||
|
let currentWeight = upstreamSetting.Weight;
|
||||||
|
setUpstreamWeight(originIP, currentWeight-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set a weight of a upstream
|
||||||
|
function setUpstreamWeight(originIP, newWeight){
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/upstream/setPriority",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
ep: editingEndpoint.ep,
|
||||||
|
origin: originIP,
|
||||||
|
weight: newWeight,
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
parent.msgbox("Upstream Weight Updated");
|
||||||
|
initOriginList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle removal of an upstream
|
||||||
|
function removeUpstream(domid){
|
||||||
|
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||||
|
let originalSettings = $(targetDOM).attr("data-payload");
|
||||||
|
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||||
|
let UpstreamKey = originalSettings.OriginIpOrDomain;
|
||||||
|
if (!confirm("Confirm removing " + UpstreamKey + " from upstream?")){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Remove the upstream
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/upstream/remove",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
ep: editingEndpoint.ep,
|
||||||
|
origin: originalSettings.OriginIpOrDomain, //Original ip or domain as key
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
parent.msgbox("Upstream deleted");
|
||||||
|
initOriginList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.location.hash.length > 1){
|
||||||
|
let payloadHash = window.location.hash.substr(1);
|
||||||
|
try{
|
||||||
|
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
||||||
|
$(".epname").text(payloadHash.ep);
|
||||||
|
editingEndpoint = payloadHash;
|
||||||
|
initOriginList();
|
||||||
|
}catch(ex){
|
||||||
|
console.log("Unable to load endpoint data from hash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeThisWrapper(){
|
||||||
|
parent.hideSideWrapper(true);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -25,6 +25,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/ipscan"
|
"imuslab.com/zoraxy/mod/ipscan"
|
||||||
"imuslab.com/zoraxy/mod/mdns"
|
"imuslab.com/zoraxy/mod/mdns"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
@ -114,7 +115,7 @@ func UpdateUptimeMonitorTargets() {
|
|||||||
uptimeMonitor.ExecuteUptimeCheck()
|
uptimeMonitor.ExecuteUptimeCheck()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
SystemWideLogger.PrintAndLog("Uptime", "Uptime monitor config updated", nil)
|
SystemWideLogger.PrintAndLog("uptime-monitor", "Uptime monitor config updated", nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,26 +125,37 @@ func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Ta
|
|||||||
|
|
||||||
UptimeTargets := []*uptime.Target{}
|
UptimeTargets := []*uptime.Target{}
|
||||||
for hostid, target := range hosts {
|
for hostid, target := range hosts {
|
||||||
url := "http://" + target.Domain
|
if target.Disabled {
|
||||||
|
//Skip those proxy rules that is disabled
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
isMultipleUpstreams := len(target.ActiveOrigins) > 1
|
||||||
|
for i, origin := range target.ActiveOrigins {
|
||||||
|
url := "http://" + origin.OriginIpOrDomain
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
if target.RequireTLS {
|
if origin.RequireTLS {
|
||||||
url = "https://" + target.Domain
|
url = "https://" + origin.OriginIpOrDomain
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add the root url
|
//Add the root url
|
||||||
|
hostIdAndName := hostid
|
||||||
|
if isMultipleUpstreams {
|
||||||
|
hostIdAndName = hostIdAndName + " (upstream:" + strconv.Itoa(i) + ")"
|
||||||
|
}
|
||||||
UptimeTargets = append(UptimeTargets, &uptime.Target{
|
UptimeTargets = append(UptimeTargets, &uptime.Target{
|
||||||
ID: hostid,
|
ID: hostIdAndName,
|
||||||
Name: hostid,
|
Name: hostIdAndName,
|
||||||
URL: url,
|
URL: url,
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
|
ProxyType: uptime.ProxyType_Host,
|
||||||
})
|
})
|
||||||
|
|
||||||
//Add each virtual directory into the list
|
//Add each virtual directory into the list
|
||||||
for _, vdir := range target.VirtualDirectories {
|
for _, vdir := range target.VirtualDirectories {
|
||||||
url := "http://" + vdir.Domain
|
url := "http://" + vdir.Domain
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
if target.RequireTLS {
|
if origin.RequireTLS {
|
||||||
url = "https://" + vdir.Domain
|
url = "https://" + vdir.Domain
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
}
|
}
|
||||||
@ -153,10 +165,12 @@ func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Ta
|
|||||||
Name: hostid + vdir.MatchingPath,
|
Name: hostid + vdir.MatchingPath,
|
||||||
URL: url,
|
URL: url,
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
|
ProxyType: uptime.ProxyType_Vdir,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return UptimeTargets
|
return UptimeTargets
|
||||||
}
|
}
|
||||||
@ -187,7 +201,16 @@ func HandleStaticWebServerPortChange(w http.ResponseWriter, r *http.Request) {
|
|||||||
if dynamicProxyRouter.Root.DefaultSiteOption == dynamicproxy.DefaultSite_InternalStaticWebServer {
|
if dynamicProxyRouter.Root.DefaultSiteOption == dynamicproxy.DefaultSite_InternalStaticWebServer {
|
||||||
//Update the root site as well
|
//Update the root site as well
|
||||||
newDraftingRoot := dynamicProxyRouter.Root.Clone()
|
newDraftingRoot := dynamicProxyRouter.Root.Clone()
|
||||||
newDraftingRoot.Domain = "127.0.0.1:" + strconv.Itoa(newPort)
|
|
||||||
|
newDraftingRoot.ActiveOrigins = []*loadbalance.Upstream{
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "127.0.0.1:" + strconv.Itoa(newPort),
|
||||||
|
RequireTLS: false,
|
||||||
|
SkipCertValidations: false,
|
||||||
|
SkipWebSocketOriginCheck: true,
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
activatedNewRoot, err := dynamicProxyRouter.PrepareProxyRoute(newDraftingRoot)
|
activatedNewRoot, err := dynamicProxyRouter.PrepareProxyRoute(newDraftingRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "unable to update root routing rule")
|
utils.SendErrorResponse(w, "unable to update root routing rule")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user