diff --git a/src/config.go b/src/config.go index af7d9a2..365e154 100644 --- a/src/config.go +++ b/src/config.go @@ -59,7 +59,7 @@ func LoadReverseProxyConfig(configFilepath string) error { thisConfigEndpoint.RootOrMatchingDomain = "/" } - if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Root { + if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyTypeRoot { //This is a root config file rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint) if err != nil { @@ -68,7 +68,7 @@ func LoadReverseProxyConfig(configFilepath string) error { dynamicProxyRouter.SetProxyRouteAsRoot(rootProxyEndpoint) - } else if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Host { + } else if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyTypeHost { //This is a host config file readyProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint) if err != nil { @@ -97,7 +97,7 @@ func filterProxyConfigFilename(filename string) string { func SaveReverseProxyConfig(endpoint *dynamicproxy.ProxyEndpoint) error { //Get filename for saving filename := filepath.Join("./conf/proxy/", endpoint.RootOrMatchingDomain+".config") - if endpoint.ProxyType == dynamicproxy.ProxyType_Root { + if endpoint.ProxyType == dynamicproxy.ProxyTypeRoot { filename = "./conf/proxy/root.config" } @@ -129,9 +129,15 @@ func RemoveReverseProxyConfig(endpoint string) error { // Get the default root config that point to the internal static web server // this will be used if root config is not found (new deployment / missing root.config file) func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) { + //Default Authentication Provider + defaultAuth := &dynamicproxy.AuthenticationProvider{ + AuthMethod: dynamicproxy.AuthMethodNone, + BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{}, + BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{}, + } //Default settings rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{ - ProxyType: dynamicproxy.ProxyType_Root, + ProxyType: dynamicproxy.ProxyTypeRoot, RootOrMatchingDomain: "/", ActiveOrigins: []*loadbalance.Upstream{ { @@ -141,14 +147,12 @@ func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) { Weight: 0, }, }, - InactiveOrigins: []*loadbalance.Upstream{}, - BypassGlobalTLS: false, - VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{}, - RequireBasicAuth: false, - BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{}, - BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{}, - DefaultSiteOption: dynamicproxy.DefaultSite_InternalStaticWebServer, - DefaultSiteValue: "", + InactiveOrigins: []*loadbalance.Upstream{}, + BypassGlobalTLS: false, + VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{}, + AuthenticationProvider: defaultAuth, + DefaultSiteOption: dynamicproxy.DefaultSite_InternalStaticWebServer, + DefaultSiteValue: "", }) if err != nil { return nil, err diff --git a/src/def.go b/src/def.go index 3b85cb7..10a940e 100644 --- a/src/def.go +++ b/src/def.go @@ -43,7 +43,7 @@ const ( /* Build Constants */ SYSTEM_NAME = "Zoraxy" SYSTEM_VERSION = "3.1.5" - DEVELOPMENT_BUILD = true /* Development: Set to false to use embedded web fs */ + DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */ /* System Constants */ DATABASE_PATH = "sys.db" diff --git a/src/main.go b/src/main.go index 580fff9..48d6c60 100644 --- a/src/main.go +++ b/src/main.go @@ -57,47 +57,6 @@ func SetupCloseHandler() { }() } -func ShutdownSeq() { - SystemWideLogger.Println("Shutting down " + SYSTEM_NAME) - SystemWideLogger.Println("Closing Netstats Listener") - if netstatBuffers != nil { - netstatBuffers.Close() - } - - SystemWideLogger.Println("Closing Statistic Collector") - if statisticCollector != nil { - statisticCollector.Close() - } - - if mdnsTickerStop != nil { - SystemWideLogger.Println("Stopping mDNS Discoverer (might take a few minutes)") - // Stop the mdns service - mdnsTickerStop <- true - } - if mdnsScanner != nil { - mdnsScanner.Close() - } - SystemWideLogger.Println("Shutting down load balancer") - if loadBalancer != nil { - loadBalancer.Close() - } - SystemWideLogger.Println("Closing Certificates Auto Renewer") - if acmeAutoRenewer != nil { - acmeAutoRenewer.Close() - } - //Remove the tmp folder - SystemWideLogger.Println("Cleaning up tmp files") - os.RemoveAll("./tmp") - - //Close database - SystemWideLogger.Println("Stopping system database") - sysdb.Close() - - //Close logger - SystemWideLogger.Println("Closing system wide logger") - SystemWideLogger.Close() -} - func main() { //Parse startup flags flag.Parse() @@ -141,7 +100,7 @@ func main() { csrf.SameSite(csrf.SameSiteLaxMode), ) - //Startup all modules + //Startup all modules, see start.go startupSequence() //Initiate management interface APIs diff --git a/src/mod/auth/sso/authelia/authelia.go b/src/mod/auth/sso/authelia/authelia.go new file mode 100644 index 0000000..f60cebf --- /dev/null +++ b/src/mod/auth/sso/authelia/authelia.go @@ -0,0 +1,87 @@ +package authelia + +import ( + "errors" + "fmt" + "net/http" + "net/url" + + "imuslab.com/zoraxy/mod/dynamicproxy" + "imuslab.com/zoraxy/mod/info/logger" +) + +type Options struct { + AutheliaURL string //URL of the Authelia server, e.g. authelia.example.com + UseHTTPS bool //Whether to use HTTPS for the Authelia server + Logger logger.Logger +} + +type AutheliaHandler struct { + options *Options +} + +func NewAutheliaAuthenticator(options *Options) *AutheliaHandler { + return &AutheliaHandler{ + options: options, + } +} + +// HandleAutheliaAuthRouting is the handler for Authelia authentication, if the error is not nil, the request will be forwarded to the endpoint +// Do not continue processing or write to the response writer if the error is not nil +func (h *AutheliaHandler) HandleAutheliaAuthRouting(w http.ResponseWriter, r *http.Request, pe *dynamicproxy.ProxyEndpoint) error { + err := h.handleAutheliaAuth(w, r) + if err != nil { + return nil + } + return err +} + +func (h *AutheliaHandler) handleAutheliaAuth(w http.ResponseWriter, r *http.Request) error { + client := &http.Client{} + + protocol := "http" + if h.options.UseHTTPS { + protocol = "https" + } + + autheliaBaseURL := protocol + "://" + h.options.AutheliaURL + //Remove tailing slash if any + if autheliaBaseURL[len(autheliaBaseURL)-1] == '/' { + autheliaBaseURL = autheliaBaseURL[:len(autheliaBaseURL)-1] + } + + //Make a request to Authelia to verify the request + req, err := http.NewRequest("POST", autheliaBaseURL+"/api/verify", nil) + if err != nil { + h.options.Logger.PrintAndLog("Authelia", "Unable to create request", err) + w.WriteHeader(401) + return errors.New("unauthorized") + } + + scheme := "http" + if r.TLS != nil { + scheme = "https" + } + req.Header.Add("X-Original-URL", fmt.Sprintf("%s://%s", scheme, r.Host)) + + // Copy cookies from the incoming request + for _, cookie := range r.Cookies() { + req.AddCookie(cookie) + } + + // Making the verification request + resp, err := client.Do(req) + if err != nil { + h.options.Logger.PrintAndLog("Authelia", "Unable to verify", err) + w.WriteHeader(401) + return errors.New("unauthorized") + } + + if resp.StatusCode != 200 { + redirectURL := autheliaBaseURL + "/?rd=" + url.QueryEscape(scheme+"://"+r.Host+r.URL.String()) + "&rm=" + r.Method + http.Redirect(w, r, redirectURL, http.StatusSeeOther) + return errors.New("unauthorized") + } + + return nil +} diff --git a/src/mod/database/dbleveldb/dbleveldb.go b/src/mod/database/dbleveldb/dbleveldb.go index 2511bcf..0ddff13 100644 --- a/src/mod/database/dbleveldb/dbleveldb.go +++ b/src/mod/database/dbleveldb/dbleveldb.go @@ -41,8 +41,8 @@ func NewDB(path string) (*DB, error) { batch: leveldb.Batch{}, } - //Create a ticker to flush data into disk every 5 seconds - writeFlushTicker := time.NewTicker(5 * time.Second) + //Create a ticker to flush data into disk every 1 seconds + writeFlushTicker := time.NewTicker(1 * time.Second) writeFlushStop := make(chan bool) go func() { for { diff --git a/src/mod/dynamicproxy/Server.go b/src/mod/dynamicproxy/Server.go index 38b785a..f63daa5 100644 --- a/src/mod/dynamicproxy/Server.go +++ b/src/mod/dynamicproxy/Server.go @@ -84,16 +84,18 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } //SSO Interception Mode - if sep.UseSSOIntercept { - allowPass := h.Parent.Option.SSOHandler.ServeForwardAuth(w, r) - if !allowPass { - h.Parent.Option.Logger.LogHTTPRequest(r, "sso-x", 307) - return + /* + if sep.AuthenticationProvider.SSOInterceptMode { + allowPass := h.Parent.Option.SSOHandler.ServeForwardAuth(w, r) + if !allowPass { + h.Parent.Option.Logger.LogHTTPRequest(r, "sso-x", 307) + return + } } - } + */ //Validate basic auth - if sep.RequireBasicAuth { + if sep.AuthenticationProvider.AuthMethod == AuthMethodBasic { err := h.handleBasicAuthRouting(w, r, sep) if err != nil { h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401) @@ -108,7 +110,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { //Virtual directory routing rule found. Route via vdir mode h.vdirRequest(w, r, targetProxyEndpoint) return - } else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root { + } else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyTypeRoot { potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/") if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled { //Missing tailing slash. Redirect to target proxy endpoint @@ -180,7 +182,7 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) //Virtual directory routing rule found. Route via vdir mode h.vdirRequest(w, r, targetProxyEndpoint) return - } else if !strings.HasSuffix(proxyingPath, "/") && proot.ProxyType != ProxyType_Root { + } else if !strings.HasSuffix(proxyingPath, "/") && proot.ProxyType != ProxyTypeRoot { potentialProxtEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/") if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled { //Missing tailing slash. Redirect to target proxy endpoint diff --git a/src/mod/dynamicproxy/authelia.go b/src/mod/dynamicproxy/authelia.go deleted file mode 100644 index 8687c65..0000000 --- a/src/mod/dynamicproxy/authelia.go +++ /dev/null @@ -1,57 +0,0 @@ -package dynamicproxy - -import ( - "errors" - "fmt" - "net/http" - "net/url" -) - -func (h *ProxyHandler) handleAutheliaAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error { - err := handleAutheliaAuth(w, r, pe) - if err != nil { - h.Parent.logRequest(r, false, 401, "host", r.URL.Hostname()) - } - return err -} - -func handleAutheliaAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error { - - client := &http.Client{} - - // TODO: provide authelia url by config variable - req, err := http.NewRequest("POST", "https://authelia.mydomain.com/api/verify", nil) - if err != nil { - pe.parent.Option.Logger.PrintAndLog("Authelia", "Unable to create request", err) - w.WriteHeader(401) - return errors.New("unauthorized") - } - - scheme := "http" - if r.TLS != nil { - scheme = "https" - } - req.Header.Add("X-Original-URL", fmt.Sprintf("%s://%s", scheme, r.Host)) - - // Copy cookies from the incoming request - for _, cookie := range r.Cookies() { - req.AddCookie(cookie) - } - - resp, err := client.Do(req) - if err != nil { - pe.parent.Option.Logger.PrintAndLog("Authelia", "Unable to verify", err) - w.WriteHeader(401) - return errors.New("unauthorized") - } - - if resp.StatusCode != 200 { - // TODO: provide authelia url by config variable - redirectURL := "https://authelia.mydomain.com/?rd=" + url.QueryEscape(scheme+"://"+r.Host+r.URL.String()) + "&rm=" + r.Method - - http.Redirect(w, r, redirectURL, http.StatusSeeOther) - return errors.New("unauthorized") - } - - return nil -} diff --git a/src/mod/dynamicproxy/basicAuth.go b/src/mod/dynamicproxy/basicAuth.go index 143b9b2..9f71111 100644 --- a/src/mod/dynamicproxy/basicAuth.go +++ b/src/mod/dynamicproxy/basicAuth.go @@ -26,9 +26,9 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req // Handle basic auth logic // do not write to http.ResponseWriter if err return is not nil (already handled by this function) func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error { - if len(pe.BasicAuthExceptionRules) > 0 { + if len(pe.AuthenticationProvider.BasicAuthExceptionRules) > 0 { //Check if the current path matches the exception rules - for _, exceptionRule := range pe.BasicAuthExceptionRules { + for _, exceptionRule := range pe.AuthenticationProvider.BasicAuthExceptionRules { if strings.HasPrefix(r.RequestURI, exceptionRule.PathPrefix) { //This path is excluded from basic auth return nil @@ -46,7 +46,7 @@ func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) //Check for the credentials to see if there is one matching hashedPassword := auth.Hash(p) matchingFound := false - for _, cred := range pe.BasicAuthCredentials { + for _, cred := range pe.AuthenticationProvider.BasicAuthCredentials { if u == cred.Username && hashedPassword == cred.PasswordHash { matchingFound = true diff --git a/src/mod/dynamicproxy/dynamicproxy.go b/src/mod/dynamicproxy/dynamicproxy.go index 26e337c..ce75e41 100644 --- a/src/mod/dynamicproxy/dynamicproxy.go +++ b/src/mod/dynamicproxy/dynamicproxy.go @@ -144,7 +144,7 @@ func (router *Router) StartProxyService() error { } //Validate basic auth - if sep.RequireBasicAuth { + if sep.AuthenticationProvider.AuthMethod == AuthMethodBasic { err := handleBasicAuth(w, r, sep) if err != nil { return @@ -161,8 +161,8 @@ func (router *Router) StartProxyService() error { ProxyDomain: selectedUpstream.OriginIpOrDomain, OriginalHost: originalHostHeader, UseTLS: selectedUpstream.RequireTLS, - HostHeaderOverwrite: sep.RequestHostOverwrite, - NoRemoveHopByHop: sep.DisableHopByHopHeaderRemoval, + HostHeaderOverwrite: sep.HeaderRewriteRules.RequestHostOverwrite, + NoRemoveHopByHop: sep.HeaderRewriteRules.DisableHopByHopHeaderRemoval, PathPrefix: "", Version: sep.parent.Option.HostVersion, }) diff --git a/src/mod/dynamicproxy/endpoints.go b/src/mod/dynamicproxy/endpoints.go index 0dd765a..0f661af 100644 --- a/src/mod/dynamicproxy/endpoints.go +++ b/src/mod/dynamicproxy/endpoints.go @@ -27,7 +27,7 @@ import ( // Check if a user define header exists in this endpoint, ignore case func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool { - for _, header := range ep.UserDefinedHeaders { + for _, header := range ep.HeaderRewriteRules.UserDefinedHeaders { if strings.EqualFold(header.Key, key) { return true } @@ -38,13 +38,13 @@ func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool { // Remvoe a user defined header from the list func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error { newHeaderList := []*rewrite.UserDefinedHeader{} - for _, header := range ep.UserDefinedHeaders { + for _, header := range ep.HeaderRewriteRules.UserDefinedHeaders { if !strings.EqualFold(header.Key, key) { newHeaderList = append(newHeaderList, header) } } - ep.UserDefinedHeaders = newHeaderList + ep.HeaderRewriteRules.UserDefinedHeaders = newHeaderList return nil } @@ -56,7 +56,7 @@ func (ep *ProxyEndpoint) AddUserDefinedHeader(newHeaderRule *rewrite.UserDefined } newHeaderRule.Key = cases.Title(language.Und, cases.NoLower).String(newHeaderRule.Key) - ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, newHeaderRule) + ep.HeaderRewriteRules.UserDefinedHeaders = append(ep.HeaderRewriteRules.UserDefinedHeaders, newHeaderRule) return nil } @@ -123,9 +123,9 @@ func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint) return nil, err } - if ep.ProxyType == ProxyType_Root { + if ep.ProxyType == ProxyTypeRoot { parentRouter.Root = readyRoutingRule - } else if ep.ProxyType == ProxyType_Host { + } else if ep.ProxyType == ProxyTypeHost { ep.Remove() parentRouter.AddProxyRouteToRuntime(readyRoutingRule) } else { diff --git a/src/mod/dynamicproxy/proxyRequestHandler.go b/src/mod/dynamicproxy/proxyRequestHandler.go index 348fe11..6fde1cf 100644 --- a/src/mod/dynamicproxy/proxyRequestHandler.go +++ b/src/mod/dynamicproxy/proxyRequestHandler.go @@ -143,9 +143,11 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe } h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain) wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{ - SkipTLSValidation: selectedUpstream.SkipCertValidations, - SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck, - Logger: h.Parent.Option.Logger, + SkipTLSValidation: selectedUpstream.SkipCertValidations, + SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck, + CopyAllHeaders: true, + UserDefinedHeaders: target.HeaderRewriteRules.UserDefinedHeaders, + Logger: h.Parent.Option.Logger, }) wspHandler.ServeHTTP(w, r) return @@ -160,15 +162,15 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe } //Populate the user-defined headers with the values from the request - rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, target.UserDefinedHeaders) + rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, target.HeaderRewriteRules.UserDefinedHeaders) //Build downstream and upstream header rules upstreamHeaders, downstreamHeaders := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{ UserDefinedHeaders: rewrittenUserDefinedHeaders, - HSTSMaxAge: target.HSTSMaxAge, + HSTSMaxAge: target.HeaderRewriteRules.HSTSMaxAge, HSTSIncludeSubdomains: target.ContainsWildcardName(true), - EnablePermissionPolicyHeader: target.EnablePermissionPolicyHeader, - PermissionPolicy: target.PermissionPolicy, + EnablePermissionPolicyHeader: target.HeaderRewriteRules.EnablePermissionPolicyHeader, + PermissionPolicy: target.HeaderRewriteRules.PermissionPolicy, }) //Handle the request reverse proxy @@ -180,8 +182,8 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe PathPrefix: "", UpstreamHeaders: upstreamHeaders, DownstreamHeaders: downstreamHeaders, - HostHeaderOverwrite: target.RequestHostOverwrite, - NoRemoveHopByHop: target.DisableHopByHopHeaderRemoval, + HostHeaderOverwrite: target.HeaderRewriteRules.RequestHostOverwrite, + NoRemoveHopByHop: target.HeaderRewriteRules.DisableHopByHopHeaderRemoval, Version: target.parent.Option.HostVersion, }) @@ -221,9 +223,11 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe } h.Parent.logRequest(r, true, 101, "vdir-websocket", target.Domain) wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{ - SkipTLSValidation: target.SkipCertValidations, - SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility - Logger: h.Parent.Option.Logger, + SkipTLSValidation: target.SkipCertValidations, + SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility + CopyAllHeaders: true, + UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders, + Logger: h.Parent.Option.Logger, }) wspHandler.ServeHTTP(w, r) return @@ -238,15 +242,15 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe } //Populate the user-defined headers with the values from the request - rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, target.parent.UserDefinedHeaders) + rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, target.parent.HeaderRewriteRules.UserDefinedHeaders) //Build downstream and upstream header rules, use the parent (subdomain) endpoint's headers upstreamHeaders, downstreamHeaders := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{ UserDefinedHeaders: rewrittenUserDefinedHeaders, - HSTSMaxAge: target.parent.HSTSMaxAge, + HSTSMaxAge: target.parent.HeaderRewriteRules.HSTSMaxAge, HSTSIncludeSubdomains: target.parent.ContainsWildcardName(true), - EnablePermissionPolicyHeader: target.parent.EnablePermissionPolicyHeader, - PermissionPolicy: target.parent.PermissionPolicy, + EnablePermissionPolicyHeader: target.parent.HeaderRewriteRules.EnablePermissionPolicyHeader, + PermissionPolicy: target.parent.HeaderRewriteRules.PermissionPolicy, }) //Handle the virtual directory reverse proxy request @@ -257,7 +261,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe PathPrefix: target.MatchingPath, UpstreamHeaders: upstreamHeaders, DownstreamHeaders: downstreamHeaders, - HostHeaderOverwrite: target.parent.RequestHostOverwrite, + HostHeaderOverwrite: target.parent.HeaderRewriteRules.RequestHostOverwrite, Version: target.parent.parent.Option.HostVersion, }) diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go index 49cfb02..4d9ce22 100644 --- a/src/mod/dynamicproxy/typedef.go +++ b/src/mod/dynamicproxy/typedef.go @@ -19,10 +19,12 @@ import ( "imuslab.com/zoraxy/mod/tlscert" ) +type ProxyType int + const ( - ProxyType_Root = 0 - ProxyType_Host = 1 - ProxyType_Vdir = 2 + ProxyTypeRoot ProxyType = iota //Root Proxy, everything not matching will be routed here + ProxyTypeHost //Host Proxy, match by host (domain) name + ProxyTypeVdir //Virtual Directory Proxy, match by path prefix ) type ProxyHandler struct { @@ -53,14 +55,14 @@ type RouterOption struct { /* Router Object */ type Router struct { Option *RouterOption - ProxyEndpoints *sync.Map - Running bool - Root *ProxyEndpoint - mux http.Handler - server *http.Server - tlsListener net.Listener + ProxyEndpoints *sync.Map //Map of ProxyEndpoint objects, each ProxyEndpoint object is a routing rule that handle incoming requests + Running bool //If the router is running + Root *ProxyEndpoint //Root proxy endpoint, default site + mux http.Handler //HTTP handler + server *http.Server //HTTP server + tlsListener net.Listener //TLS listener, handle SNI routing loadBalancer *loadbalance.RouteManager //Load balancer routing manager - routingRules []*RoutingRule + routingRules []*RoutingRule //Special routing rules, handle high priority routing like ACME request handling tlsRedirectStop chan bool //Stop channel for tls redirection server rateLimterStop chan bool //Stop channel for rate limiter @@ -99,9 +101,42 @@ type VirtualDirectoryEndpoint struct { parent *ProxyEndpoint `json:"-"` } +// Rules and settings for header rewriting +type HeaderRewriteRules struct { + UserDefinedHeaders []*rewrite.UserDefinedHeader //Custom headers to append when proxying requests from this endpoint + RequestHostOverwrite string //If not empty, this domain will be used to overwrite the Host field in request header + HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers + EnablePermissionPolicyHeader bool //Enable injection of permission policy header + PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header + DisableHopByHopHeaderRemoval bool //Do not remove hop-by-hop headers + +} + +/* + + Authentication Provider + + TODO: Move these into a dedicated module +*/ + +type AuthMethod int + +const ( + AuthMethodNone AuthMethod = iota //No authentication required + AuthMethodBasic //Basic Auth + AuthMethodAuthelia //Authelia + AuthMethodOauth2 //Oauth2 +) + +type AuthenticationProvider struct { + AuthMethod AuthMethod //The authentication method to use + BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials + BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target +} + // A proxy endpoint record, a general interface for handling inbound routing type ProxyEndpoint struct { - ProxyType int //The type of this proxy, see const def + ProxyType ProxyType //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 []*loadbalance.Upstream //Activated Upstream or origin servers IP or domain to proxy to @@ -117,23 +152,18 @@ type ProxyEndpoint struct { VirtualDirectories []*VirtualDirectoryEndpoint //Custom Headers - UserDefinedHeaders []*rewrite.UserDefinedHeader //Custom headers to append when proxying requests from this endpoint - RequestHostOverwrite string //If not empty, this domain will be used to overwrite the Host field in request header - HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers - EnablePermissionPolicyHeader bool //Enable injection of permission policy header - PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header - DisableHopByHopHeaderRemoval bool //Do not remove hop-by-hop headers + HeaderRewriteRules *HeaderRewriteRules //Authentication - RequireBasicAuth bool //Set to true to request basic auth before proxy - BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials - BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target - UseSSOIntercept bool //Allow SSO to intercept this endpoint and provide authentication via Oauth2 credentials + AuthenticationProvider *AuthenticationProvider // Rate Limiting RequireRateLimit bool RateLimit int64 // Rate limit in requests per second + //Uptime Monitor + DisableUptimeMonitor bool //Disable uptime monitor for this endpoint + //Access Control AccessFilterUUID string //Access filter ID diff --git a/src/mod/update/updatelogic.go b/src/mod/update/updatelogic.go index 6e11eae..8577e7b 100644 --- a/src/mod/update/updatelogic.go +++ b/src/mod/update/updatelogic.go @@ -1,6 +1,9 @@ package update -import v308 "imuslab.com/zoraxy/mod/update/v308" +import ( + v308 "imuslab.com/zoraxy/mod/update/v308" + v315 "imuslab.com/zoraxy/mod/update/v315" +) // Updater Core logic func runUpdateRoutineWithVersion(fromVersion int, toVersion int) { @@ -10,6 +13,12 @@ func runUpdateRoutineWithVersion(fromVersion int, toVersion int) { if err != nil { panic(err) } + } else if fromVersion == 314 && toVersion == 315 { + //Updating from v3.1.4 to v3.1.5 + err := v315.UpdateFrom314To315() + if err != nil { + panic(err) + } } //ADD MORE VERSIONS HERE diff --git a/src/mod/update/updateutil/updateutil.go b/src/mod/update/updateutil/updateutil.go new file mode 100644 index 0000000..adeca6e --- /dev/null +++ b/src/mod/update/updateutil/updateutil.go @@ -0,0 +1,24 @@ +package updateutil + +import ( + "io" + "os" +) + +// 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 +} diff --git a/src/mod/update/v315/typedef314.go b/src/mod/update/v315/typedef314.go new file mode 100644 index 0000000..72d1ee3 --- /dev/null +++ b/src/mod/update/v315/typedef314.go @@ -0,0 +1,50 @@ +package v315 + +import ( + "imuslab.com/zoraxy/mod/dynamicproxy/loadbalance" + "imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy" + "imuslab.com/zoraxy/mod/dynamicproxy/rewrite" +) + +// A proxy endpoint record, a general interface for handling inbound routing +type v314ProxyEndpoint 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 []*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 + + //Inbound TLS/SSL Related + BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil) + + //Virtual Directories + VirtualDirectories []*VirtualDirectoryEndpoint + + //Custom Headers + UserDefinedHeaders []*rewrite.UserDefinedHeader //Custom headers to append when proxying requests from this endpoint + RequestHostOverwrite string //If not empty, this domain will be used to overwrite the Host field in request header + HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers + EnablePermissionPolicyHeader bool //Enable injection of permission policy header + PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header + DisableHopByHopHeaderRemoval bool //Do not remove hop-by-hop headers + + //Authentication + RequireBasicAuth bool //Set to true to request basic auth before proxy + BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials + BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target + UseSSOIntercept bool //Allow SSO to intercept this endpoint and provide authentication via Oauth2 credentials + + // 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 +} diff --git a/src/mod/update/v315/typedef315.go b/src/mod/update/v315/typedef315.go new file mode 100644 index 0000000..15c281a --- /dev/null +++ b/src/mod/update/v315/typedef315.go @@ -0,0 +1,106 @@ +package v315 + +import ( + "imuslab.com/zoraxy/mod/dynamicproxy/loadbalance" + "imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy" + "imuslab.com/zoraxy/mod/dynamicproxy/rewrite" +) + +type ProxyType int + +const ( + ProxyTypeRoot ProxyType = iota //Root Proxy, everything not matching will be routed here + ProxyTypeHost //Host Proxy, match by host (domain) name + ProxyTypeVdir //Virtual Directory Proxy, match by path prefix +) + +/* Basic Auth Related Data structure*/ +// Auth credential for basic auth on certain endpoints +type BasicAuthCredentials struct { + Username string + PasswordHash string +} + +// Auth credential for basic auth on certain endpoints +type BasicAuthUnhashedCredentials struct { + Username string + Password string +} + +// Paths to exclude in basic auth enabled proxy handler +type BasicAuthExceptionRule struct { + PathPrefix string +} + +/* Routing Rule Data Structures */ + +// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better +// program structure than directly using ProxyEndpoint +type VirtualDirectoryEndpoint 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 +} + +// Rules and settings for header rewriting +type HeaderRewriteRules struct { + UserDefinedHeaders []*rewrite.UserDefinedHeader //Custom headers to append when proxying requests from this endpoint + RequestHostOverwrite string //If not empty, this domain will be used to overwrite the Host field in request header + HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers + EnablePermissionPolicyHeader bool //Enable injection of permission policy header + PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header + DisableHopByHopHeaderRemoval bool //Do not remove hop-by-hop headers +} + +type AuthProvider int + +const ( + AuthProviderNone AuthProvider = iota + AuthProviderBasicAuth + AuthProviderAuthelia + AuthProviderOauth2 +) + +type AuthenticationProvider struct { + AuthProvider AuthProvider //The type of authentication provider + RequireBasicAuth bool //Set to true to request basic auth before proxy + BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials + BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target +} + +// A proxy endpoint record, a general interface for handling inbound routing +type v315ProxyEndpoint struct { + ProxyType ProxyType //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 []*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 + + //Inbound TLS/SSL Related + BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil) + + //Virtual Directories + VirtualDirectories []*VirtualDirectoryEndpoint + + //Custom Headers + HeaderRewriteRules *HeaderRewriteRules + + //Authentication + AuthenticationProvider *AuthenticationProvider + + // 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 +} diff --git a/src/mod/update/v315/v315.go b/src/mod/update/v315/v315.go new file mode 100644 index 0000000..82e2e60 --- /dev/null +++ b/src/mod/update/v315/v315.go @@ -0,0 +1,124 @@ +package v315 + +import ( + "encoding/json" + "log" + "os" + "path/filepath" + + "imuslab.com/zoraxy/mod/update/updateutil" +) + +func UpdateFrom314To315() 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-314.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-314.old/", fileName) + + // Copy the file to the backup directory + err := updateutil.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 := v314ProxyEndpoint{} + err = json.Unmarshal(configContent, &thisOldConfigStruct) + if err != nil { + log.Println("Unable to parse file "+filepath.Base(oldConfigFile), err.Error()) + continue + } + + //Convert the old struct to the new struct + thisNewConfigStruct := convertV314ToV315(thisOldConfigStruct) + + //Write the new config to file + newConfigContent, err := json.MarshalIndent(thisNewConfigStruct, "", " ") + if err != nil { + log.Println("Unable to marshal new config "+filepath.Base(oldConfigFile), err.Error()) + continue + } + + err = os.WriteFile(oldConfigFile, newConfigContent, 0664) + if err != nil { + log.Println("Unable to write new config "+filepath.Base(oldConfigFile), err.Error()) + continue + } + } + + return nil +} + +func convertV314ToV315(thisOldConfigStruct v314ProxyEndpoint) v315ProxyEndpoint { + //Move old header and auth configs into struct + newHeaderRewriteRules := HeaderRewriteRules{ + UserDefinedHeaders: thisOldConfigStruct.UserDefinedHeaders, + RequestHostOverwrite: thisOldConfigStruct.RequestHostOverwrite, + HSTSMaxAge: thisOldConfigStruct.HSTSMaxAge, + EnablePermissionPolicyHeader: thisOldConfigStruct.EnablePermissionPolicyHeader, + PermissionPolicy: thisOldConfigStruct.PermissionPolicy, + DisableHopByHopHeaderRemoval: thisOldConfigStruct.DisableHopByHopHeaderRemoval, + } + + newAuthenticationProvider := AuthenticationProvider{ + RequireBasicAuth: thisOldConfigStruct.RequireBasicAuth, + BasicAuthCredentials: thisOldConfigStruct.BasicAuthCredentials, + BasicAuthExceptionRules: thisOldConfigStruct.BasicAuthExceptionRules, + } + + //Convert proxy type int to enum + var newConfigProxyType ProxyType + if thisOldConfigStruct.ProxyType == 0 { + newConfigProxyType = ProxyTypeRoot + } else if thisOldConfigStruct.ProxyType == 1 { + newConfigProxyType = ProxyTypeHost + } else if thisOldConfigStruct.ProxyType == 2 { + newConfigProxyType = ProxyTypeVdir + } + + //Update the config struct + thisNewConfigStruct := v315ProxyEndpoint{ + ProxyType: newConfigProxyType, + RootOrMatchingDomain: thisOldConfigStruct.RootOrMatchingDomain, + MatchingDomainAlias: thisOldConfigStruct.MatchingDomainAlias, + ActiveOrigins: thisOldConfigStruct.ActiveOrigins, + InactiveOrigins: thisOldConfigStruct.InactiveOrigins, + UseStickySession: thisOldConfigStruct.UseStickySession, + UseActiveLoadBalance: thisOldConfigStruct.UseActiveLoadBalance, + Disabled: thisOldConfigStruct.Disabled, + BypassGlobalTLS: thisOldConfigStruct.BypassGlobalTLS, + VirtualDirectories: thisOldConfigStruct.VirtualDirectories, + RequireRateLimit: thisOldConfigStruct.RequireRateLimit, + RateLimit: thisOldConfigStruct.RateLimit, + AccessFilterUUID: thisOldConfigStruct.AccessFilterUUID, + DefaultSiteOption: thisOldConfigStruct.DefaultSiteOption, + DefaultSiteValue: thisOldConfigStruct.DefaultSiteValue, + + //Append the new struct into the new config + HeaderRewriteRules: &newHeaderRewriteRules, + AuthenticationProvider: &newAuthenticationProvider, + } + + return thisNewConfigStruct +} diff --git a/src/mod/websocketproxy/websocketproxy.go b/src/mod/websocketproxy/websocketproxy.go index 0e07027..284b973 100644 --- a/src/mod/websocketproxy/websocketproxy.go +++ b/src/mod/websocketproxy/websocketproxy.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/gorilla/websocket" + "imuslab.com/zoraxy/mod/dynamicproxy/rewrite" "imuslab.com/zoraxy/mod/info/logger" ) @@ -56,9 +57,11 @@ type WebsocketProxy struct { // Additional options for websocket proxy runtime type Options struct { - SkipTLSValidation bool //Skip backend TLS validation - SkipOriginCheck bool //Skip origin check - Logger *logger.Logger //Logger, can be nil + SkipTLSValidation bool //Skip backend TLS validation + SkipOriginCheck bool //Skip origin check + CopyAllHeaders bool //Copy all headers from incoming request to backend request + UserDefinedHeaders []*rewrite.UserDefinedHeader //User defined headers + Logger *logger.Logger //Logger, can be nil } // ProxyHandler returns a new http.Handler interface that reverse proxies the @@ -78,7 +81,14 @@ func NewProxy(target *url.URL, options Options) *WebsocketProxy { u.RawQuery = r.URL.RawQuery return &u } - return &WebsocketProxy{Backend: backend, Verbal: false, Options: options} + + // Create a new websocket proxy + wsprox := &WebsocketProxy{Backend: backend, Verbal: false, Options: options} + if options.CopyAllHeaders { + wsprox.Director = DefaultDirector + } + + return wsprox } // Utilities function for log printing @@ -90,6 +100,35 @@ func (w *WebsocketProxy) Println(messsage string, err error) { log.Println("[websocketproxy] [system:info]"+messsage, err) } +// DefaultDirector is the default implementation of Director, which copies +// all headers from the incoming request to the outgoing request. +func DefaultDirector(r *http.Request, h http.Header) { + //Copy all header values from request to target header + for k, vv := range r.Header { + for _, v := range vv { + h.Set(k, v) + } + } + + // Remove hop-by-hop headers + for _, removePendingHeader := range []string{ + "Connection", + "Keep-Alive", + "Proxy-Authenticate", + "Proxy-Authorization", + "Te", + "Trailers", + "Transfer-Encoding", + "Sec-WebSocket-Extensions", + "Sec-WebSocket-Key", + "Sec-WebSocket-Protocol", + "Sec-WebSocket-Version", + "Upgrade", + } { + h.Del(removePendingHeader) + } +} + // ServeHTTP implements the http.Handler that proxies WebSocket connections. func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if w.Backend == nil { @@ -162,6 +201,15 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { w.Director(req, requestHeader) } + // Replace header variables and copy user-defined headers + rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(req, w.Options.UserDefinedHeaders) + upstreamHeaders, _ := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{ + UserDefinedHeaders: rewrittenUserDefinedHeaders, + }) + for _, headerValuePair := range upstreamHeaders { + requestHeader.Set(headerValuePair[0], headerValuePair[1]) + } + // Connect to the backend URL, also pass the headers we get from the requst // together with the Forwarded headers we prepared above. // TODO: support multiplexing on the same backend connection instead of diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 4a790e8..75211f5 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -309,10 +309,25 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { } } + //Generate a default authenticaion provider + authMethod := dynamicproxy.AuthMethodNone + if requireBasicAuth { + authMethod = dynamicproxy.AuthMethodBasic + } + thisAuthenticationProvider := dynamicproxy.AuthenticationProvider{ + AuthMethod: authMethod, + BasicAuthCredentials: basicAuthCredentials, + BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{}, + } + + thisCustomHeaderRules := dynamicproxy.HeaderRewriteRules{ + UserDefinedHeaders: []*rewrite.UserDefinedHeader{}, + } + //Generate a proxy endpoint object thisProxyEndpoint := dynamicproxy.ProxyEndpoint{ //I/O - ProxyType: dynamicproxy.ProxyType_Host, + ProxyType: dynamicproxy.ProxyTypeHost, RootOrMatchingDomain: rootOrMatchingDomain, MatchingDomainAlias: aliasHostnames, ActiveOrigins: []*loadbalance.Upstream{ @@ -333,13 +348,16 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { //VDir VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{}, //Custom headers - UserDefinedHeaders: []*rewrite.UserDefinedHeader{}, + //Auth - RequireBasicAuth: requireBasicAuth, - BasicAuthCredentials: basicAuthCredentials, - BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{}, - DefaultSiteOption: 0, - DefaultSiteValue: "", + AuthenticationProvider: &thisAuthenticationProvider, + + //Header Rewrite + HeaderRewriteRules: &thisCustomHeaderRules, + + //Default Site + DefaultSiteOption: 0, + DefaultSiteValue: "", // Rate Limit RequireRateLimit: requireRateLimit, RateLimit: int64(proxyRateLimit), @@ -379,7 +397,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { //Write the root options to file rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{ - ProxyType: dynamicproxy.ProxyType_Root, + ProxyType: dynamicproxy.ProxyTypeRoot, RootOrMatchingDomain: "/", ActiveOrigins: []*loadbalance.Upstream{ { @@ -494,7 +512,19 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { //Generate a new proxyEndpoint from the new config newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry) newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS - newProxyEndpoint.RequireBasicAuth = requireBasicAuth + if newProxyEndpoint.AuthenticationProvider == nil { + newProxyEndpoint.AuthenticationProvider = &dynamicproxy.AuthenticationProvider{ + AuthMethod: dynamicproxy.AuthMethodNone, + BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{}, + BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{}, + } + } + if requireBasicAuth { + newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodBasic + } else { + newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodNone + } + newProxyEndpoint.RequireRateLimit = requireRateLimit newProxyEndpoint.RateLimit = proxyRateLimit newProxyEndpoint.UseStickySession = useStickySession @@ -624,7 +654,7 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) { } usernames := []string{} - for _, cred := range targetProxy.BasicAuthCredentials { + for _, cred := range targetProxy.AuthenticationProvider.BasicAuthCredentials { usernames = append(usernames, cred.Username) } @@ -668,7 +698,7 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) { if credential.Password == "" { //Check if exists in the old credential files keepUnchange := false - for _, oldCredEntry := range targetProxy.BasicAuthCredentials { + for _, oldCredEntry := range targetProxy.AuthenticationProvider.BasicAuthCredentials { if oldCredEntry.Username == credential.Username { //Exists! Reuse the old hash mergedCredentials = append(mergedCredentials, &dynamicproxy.BasicAuthCredentials{ @@ -693,7 +723,7 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) { } } - targetProxy.BasicAuthCredentials = mergedCredentials + targetProxy.AuthenticationProvider.BasicAuthCredentials = mergedCredentials //Save it to file SaveReverseProxyConfig(targetProxy) @@ -727,7 +757,7 @@ func ListProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) { } //List all the exception paths for this proxy - results := targetProxy.BasicAuthExceptionRules + results := targetProxy.AuthenticationProvider.BasicAuthExceptionRules if results == nil { //It is a config from a really old version of zoraxy. Overwrite it with empty array results = []*dynamicproxy.BasicAuthExceptionRule{} @@ -764,7 +794,7 @@ func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) { //Add a new exception rule if it is not already exists alreadyExists := false - for _, thisExceptionRule := range targetProxy.BasicAuthExceptionRules { + for _, thisExceptionRule := range targetProxy.AuthenticationProvider.BasicAuthExceptionRules { if thisExceptionRule.PathPrefix == matchingPrefix { alreadyExists = true break @@ -774,7 +804,7 @@ func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) { utils.SendErrorResponse(w, "This matching path already exists") return } - targetProxy.BasicAuthExceptionRules = append(targetProxy.BasicAuthExceptionRules, &dynamicproxy.BasicAuthExceptionRule{ + targetProxy.AuthenticationProvider.BasicAuthExceptionRules = append(targetProxy.AuthenticationProvider.BasicAuthExceptionRules, &dynamicproxy.BasicAuthExceptionRule{ PathPrefix: strings.TrimSpace(matchingPrefix), }) @@ -808,7 +838,7 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) newExceptionRuleList := []*dynamicproxy.BasicAuthExceptionRule{} matchingExists := false - for _, thisExceptionalRule := range targetProxy.BasicAuthExceptionRules { + for _, thisExceptionalRule := range targetProxy.AuthenticationProvider.BasicAuthExceptionRules { if thisExceptionalRule.PathPrefix != matchingPrefix { newExceptionRuleList = append(newExceptionRuleList, thisExceptionalRule) } else { @@ -821,7 +851,7 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) return } - targetProxy.BasicAuthExceptionRules = newExceptionRuleList + targetProxy.AuthenticationProvider.BasicAuthExceptionRules = newExceptionRuleList // Save configs to runtime and file targetProxy.UpdateToRuntime() @@ -914,13 +944,13 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) { thisEndpoint := dynamicproxy.CopyEndpoint(value.(*dynamicproxy.ProxyEndpoint)) //Clear the auth passwords before showing to front-end cleanedCredentials := []*dynamicproxy.BasicAuthCredentials{} - for _, user := range thisEndpoint.BasicAuthCredentials { + for _, user := range thisEndpoint.AuthenticationProvider.BasicAuthCredentials { cleanedCredentials = append(cleanedCredentials, &dynamicproxy.BasicAuthCredentials{ Username: user.Username, PasswordHash: "", }) } - thisEndpoint.BasicAuthCredentials = cleanedCredentials + thisEndpoint.AuthenticationProvider.BasicAuthCredentials = cleanedCredentials results = append(results, thisEndpoint) return true }) @@ -1127,7 +1157,7 @@ func HandleCustomHeaderList(w http.ResponseWriter, r *http.Request) { } //List all custom headers - customHeaderList := targetProxyEndpoint.UserDefinedHeaders + customHeaderList := targetProxyEndpoint.HeaderRewriteRules.UserDefinedHeaders if customHeaderList == nil { customHeaderList = []*rewrite.UserDefinedHeader{} } @@ -1269,7 +1299,7 @@ func HandleHostOverwrite(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { //Get the current host header - js, _ := json.Marshal(targetProxyEndpoint.RequestHostOverwrite) + js, _ := json.Marshal(targetProxyEndpoint.HeaderRewriteRules.RequestHostOverwrite) utils.SendJSONResponse(w, string(js)) } else if r.Method == http.MethodPost { //Set the new host header @@ -1278,7 +1308,7 @@ func HandleHostOverwrite(w http.ResponseWriter, r *http.Request) { //As this will require change in the proxy instance we are running //we need to clone and respawn this proxy endpoint newProxyEndpoint := targetProxyEndpoint.Clone() - newProxyEndpoint.RequestHostOverwrite = newHostname + newProxyEndpoint.HeaderRewriteRules.RequestHostOverwrite = newHostname //Save proxy endpoint err = SaveReverseProxyConfig(newProxyEndpoint) if err != nil { @@ -1341,7 +1371,7 @@ func HandleHopByHop(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { //Get the current hop by hop header state - js, _ := json.Marshal(!targetProxyEndpoint.DisableHopByHopHeaderRemoval) + js, _ := json.Marshal(!targetProxyEndpoint.HeaderRewriteRules.DisableHopByHopHeaderRemoval) utils.SendJSONResponse(w, string(js)) } else if r.Method == http.MethodPost { //Set the hop by hop header state @@ -1351,7 +1381,7 @@ func HandleHopByHop(w http.ResponseWriter, r *http.Request) { //we need to clone and respawn this proxy endpoint newProxyEndpoint := targetProxyEndpoint.Clone() //Storage file use false as default, so disable removal = not enable remover - newProxyEndpoint.DisableHopByHopHeaderRemoval = !enableHopByHopRemover + newProxyEndpoint.HeaderRewriteRules.DisableHopByHopHeaderRemoval = !enableHopByHopRemover //Save proxy endpoint err = SaveReverseProxyConfig(newProxyEndpoint) @@ -1414,7 +1444,7 @@ func HandleHSTSState(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { //Return current HSTS enable state - hstsAge := targetProxyEndpoint.HSTSMaxAge + hstsAge := targetProxyEndpoint.HeaderRewriteRules.HSTSMaxAge js, _ := json.Marshal(hstsAge) utils.SendJSONResponse(w, string(js)) return @@ -1426,7 +1456,7 @@ func HandleHSTSState(w http.ResponseWriter, r *http.Request) { } if newMaxAge == 0 || newMaxAge >= 31536000 { - targetProxyEndpoint.HSTSMaxAge = int64(newMaxAge) + targetProxyEndpoint.HeaderRewriteRules.HSTSMaxAge = int64(newMaxAge) err = SaveReverseProxyConfig(targetProxyEndpoint) if err != nil { utils.SendErrorResponse(w, "save HSTS state failed: "+err.Error()) @@ -1468,11 +1498,11 @@ func HandlePermissionPolicy(w http.ResponseWriter, r *http.Request) { } currentPolicy := permissionpolicy.GetDefaultPermissionPolicy() - if targetProxyEndpoint.PermissionPolicy != nil { - currentPolicy = targetProxyEndpoint.PermissionPolicy + if targetProxyEndpoint.HeaderRewriteRules.PermissionPolicy != nil { + currentPolicy = targetProxyEndpoint.HeaderRewriteRules.PermissionPolicy } result := CurrentPolicyState{ - PPEnabled: targetProxyEndpoint.EnablePermissionPolicyHeader, + PPEnabled: targetProxyEndpoint.HeaderRewriteRules.EnablePermissionPolicyHeader, CurrentPolicy: currentPolicy, } @@ -1487,7 +1517,7 @@ func HandlePermissionPolicy(w http.ResponseWriter, r *http.Request) { return } - targetProxyEndpoint.EnablePermissionPolicyHeader = enableState + targetProxyEndpoint.HeaderRewriteRules.EnablePermissionPolicyHeader = enableState SaveReverseProxyConfig(targetProxyEndpoint) targetProxyEndpoint.UpdateToRuntime() utils.SendOK(w) @@ -1509,7 +1539,7 @@ func HandlePermissionPolicy(w http.ResponseWriter, r *http.Request) { } //Save it to file - targetProxyEndpoint.PermissionPolicy = newPermissionPolicy + targetProxyEndpoint.HeaderRewriteRules.PermissionPolicy = newPermissionPolicy SaveReverseProxyConfig(targetProxyEndpoint) targetProxyEndpoint.UpdateToRuntime() utils.SendOK(w) diff --git a/src/start.go b/src/start.go index 63b0af6..ec5b880 100644 --- a/src/start.go +++ b/src/start.go @@ -331,6 +331,7 @@ func startupSequence() { } +/* Finalize Startup Sequence */ // This sequence start after everything is initialized func finalSequence() { //Start ACME renew agent @@ -339,3 +340,45 @@ func finalSequence() { //Inject routing rules registerBuildInRoutingRules() } + +/* Shutdown Sequence */ +func ShutdownSeq() { + SystemWideLogger.Println("Shutting down " + SYSTEM_NAME) + SystemWideLogger.Println("Closing Netstats Listener") + if netstatBuffers != nil { + netstatBuffers.Close() + } + + SystemWideLogger.Println("Closing Statistic Collector") + if statisticCollector != nil { + statisticCollector.Close() + } + + if mdnsTickerStop != nil { + SystemWideLogger.Println("Stopping mDNS Discoverer (might take a few minutes)") + // Stop the mdns service + mdnsTickerStop <- true + } + if mdnsScanner != nil { + mdnsScanner.Close() + } + SystemWideLogger.Println("Shutting down load balancer") + if loadBalancer != nil { + loadBalancer.Close() + } + SystemWideLogger.Println("Closing Certificates Auto Renewer") + if acmeAutoRenewer != nil { + acmeAutoRenewer.Close() + } + //Remove the tmp folder + SystemWideLogger.Println("Cleaning up tmp files") + os.RemoveAll("./tmp") + + //Close database + SystemWideLogger.Println("Stopping system database") + sysdb.Close() + + //Close logger + SystemWideLogger.Println("Closing system wide logger") + SystemWideLogger.Close() +} diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index 237e305..61a1bfc 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -125,10 +125,10 @@

HTTP Proxy

${vdList} - ${subd.RequireBasicAuth?` Basic Auth`:``} - ${subd.RequireBasicAuth && subd.RequireRateLimit?"
":""} - ${subd.RequireRateLimit?` Rate Limit @ ${subd.RateLimit} req/s`:``} - ${!subd.RequireBasicAuth && !subd.RequireRateLimit?`No Special Settings`:""} + ${subd.AuthenticationProvider.AuthMethod == 0x1?` Basic Auth`:``} + ${subd.AuthenticationProvider.AuthMethod == 0x1 && subd.RequireRateLimit?"
":""} + ${subd.AuthenticationProvider.RequireRateLimit?` Rate Limit @ ${subd.RateLimit} req/s`:``} + ${!subd.AuthenticationProvider.AuthMethod == 0x1 && !subd.RequireRateLimit?`No Special Settings`:""}
@@ -269,7 +269,7 @@

HTTP Proxy

`); }else if (datatype == "advanced"){ - let requireBasicAuth = payload.RequireBasicAuth; + let requireBasicAuth = payload.AuthenticationProvider.AuthMethod == 0x1; let basicAuthCheckstate = ""; if (requireBasicAuth){ basicAuthCheckstate = "checked"; diff --git a/src/web/components/status.html b/src/web/components/status.html index 24d658b..1d712c7 100644 --- a/src/web/components/status.html +++ b/src/web/components/status.html @@ -1,3 +1,10 @@ +
@@ -362,9 +369,11 @@

Statistic Overview

} if (enabled){ //$("#redirect").show(); + $("#redirect").removeClass("disabled"); msgbox("Port 80 listener enabled"); }else{ //$("#redirect").hide(); + $("#redirect").addClass("disabled"); msgbox("Port 80 listener disabled"); } } @@ -402,9 +411,11 @@

Statistic Overview

$.get("/api/proxy/listenPort80", function(data){ if (data){ $("#listenP80").checkbox("set checked"); + $("#redirect").removeClass("disabled"); //$("#redirect").show(); }else{ $("#listenP80").checkbox("set unchecked"); + $("#redirect").addClass("disabled"); //$("#redirect").hide(); } diff --git a/src/wrappers.go b/src/wrappers.go index f0b90d2..3ff0da4 100644 --- a/src/wrappers.go +++ b/src/wrappers.go @@ -90,7 +90,7 @@ func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Ta UptimeTargets := []*uptime.Target{} for hostid, target := range hosts { - if target.Disabled { + if target.Disabled || target.DisableUptimeMonitor { //Skip those proxy rules that is disabled continue }