diff --git a/src/api.go b/src/api.go index b9e86e8..0d2f7c9 100644 --- a/src/api.go +++ b/src/api.go @@ -77,6 +77,8 @@ func initAPIs() { authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd) authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove) authRouter.HandleFunc("/api/proxy/header/handleHSTS", HandleHSTSState) + authRouter.HandleFunc("/api/proxy/header/handleHopByHop", HandleHopByHop) + authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite) authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy) //Reverse proxy auth related APIs authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths) diff --git a/src/main.go b/src/main.go index bcd49ec..fc933a5 100644 --- a/src/main.go +++ b/src/main.go @@ -60,7 +60,7 @@ var ( name = "Zoraxy" version = "3.1.0" nodeUUID = "generic" //System uuid, in uuidv4 format - development = false //Set this to false to use embedded web fs + development = true //Set this to false to use embedded web fs bootTime = time.Now().Unix() /* diff --git a/src/mod/dynamicproxy/dpcore/dpcore.go b/src/mod/dynamicproxy/dpcore/dpcore.go index 2c64eaa..c659435 100644 --- a/src/mod/dynamicproxy/dpcore/dpcore.go +++ b/src/mod/dynamicproxy/dpcore/dpcore.go @@ -57,6 +57,7 @@ type ReverseProxy struct { } type ResponseRewriteRuleSet struct { + /* Basic Rewrite Rulesets */ ProxyDomain string OriginalHost string UseTLS bool @@ -64,8 +65,13 @@ type ResponseRewriteRuleSet struct { PathPrefix string //Vdir prefix for root, / will be rewrite to this UpstreamHeaders [][]string DownstreamHeaders [][]string - NoRemoveHopByHop bool //Do not remove hop-by-hop headers, dangerous - Version string //Version number of Zoraxy, use for X-Proxy-By + + /* Advance Usecase Options */ + HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase) + NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase) + + /* System Information Payload */ + Version string //Version number of Zoraxy, use for X-Proxy-By } type requestCanceler interface { @@ -73,8 +79,8 @@ type requestCanceler interface { } type DpcoreOptions struct { - IgnoreTLSVerification bool - FlushInterval time.Duration + IgnoreTLSVerification bool //Disable all TLS verification when request pass through this proxy router + FlushInterval time.Duration //Duration to flush in normal requests. Stream request or keep-alive request will always flush with interval of -1 (immediately) } func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy { @@ -281,7 +287,10 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr outreq.Close = false //Only skip origin rewrite iff proxy target require TLS and it is external domain name like github.com - if !(rrr.UseTLS && isExternalDomainName(rrr.ProxyDomain)) { + if rrr.HostHeaderOverwrite != "" { + //Use user defined overwrite header value, see issue #255 + outreq.Host = rrr.HostHeaderOverwrite + } else if !(rrr.UseTLS && isExternalDomainName(rrr.ProxyDomain)) { // Always use the original host, see issue #164 outreq.Host = rrr.OriginalHost } @@ -291,7 +300,9 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr copyHeader(outreq.Header, req.Header) // Remove hop-by-hop headers. - removeHeaders(outreq.Header, rrr.NoCache) + if !rrr.NoRemoveHopByHop { + removeHeaders(outreq.Header, rrr.NoCache) + } // Add X-Forwarded-For Header. addXForwardedForHeader(outreq) @@ -313,7 +324,9 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr } // Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers. - removeHeaders(res.Header, rrr.NoCache) + if !rrr.NoRemoveHopByHop { + removeHeaders(res.Header, rrr.NoCache) + } //Remove the User-Agent header if exists if _, ok := res.Header["User-Agent"]; ok { diff --git a/src/mod/dynamicproxy/dynamicproxy.go b/src/mod/dynamicproxy/dynamicproxy.go index 4f6e6a8..17f8523 100644 --- a/src/mod/dynamicproxy/dynamicproxy.go +++ b/src/mod/dynamicproxy/dynamicproxy.go @@ -158,12 +158,13 @@ func (router *Router) StartProxyService() error { router.logRequest(r, false, 404, "vdir-http", r.Host) } selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ - ProxyDomain: selectedUpstream.OriginIpOrDomain, - OriginalHost: originalHostHeader, - UseTLS: selectedUpstream.RequireTLS, - NoRemoveHopByHop: sep.DisableHopByHopHeaderRemoval, - PathPrefix: "", - Version: sep.parent.Option.HostVersion, + ProxyDomain: selectedUpstream.OriginIpOrDomain, + OriginalHost: originalHostHeader, + UseTLS: selectedUpstream.RequireTLS, + HostHeaderOverwrite: sep.RequestHostOverwrite, + NoRemoveHopByHop: sep.DisableHopByHopHeaderRemoval, + PathPrefix: "", + Version: sep.parent.Option.HostVersion, }) return } diff --git a/src/mod/dynamicproxy/proxyRequestHandler.go b/src/mod/dynamicproxy/proxyRequestHandler.go index 389fb03..f4ae58d 100644 --- a/src/mod/dynamicproxy/proxyRequestHandler.go +++ b/src/mod/dynamicproxy/proxyRequestHandler.go @@ -157,15 +157,16 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders() err = selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ - ProxyDomain: selectedUpstream.OriginIpOrDomain, - OriginalHost: originalHostHeader, - UseTLS: selectedUpstream.RequireTLS, - NoCache: h.Parent.Option.NoCache, - PathPrefix: "", - UpstreamHeaders: upstreamHeaders, - DownstreamHeaders: downstreamHeaders, - NoRemoveHopByHop: target.DisableHopByHopHeaderRemoval, - Version: target.parent.Option.HostVersion, + ProxyDomain: selectedUpstream.OriginIpOrDomain, + OriginalHost: originalHostHeader, + UseTLS: selectedUpstream.RequireTLS, + NoCache: h.Parent.Option.NoCache, + PathPrefix: "", + UpstreamHeaders: upstreamHeaders, + DownstreamHeaders: downstreamHeaders, + HostHeaderOverwrite: target.RequestHostOverwrite, + NoRemoveHopByHop: target.DisableHopByHopHeaderRemoval, + Version: target.parent.Option.HostVersion, }) var dnsError *net.DNSError @@ -224,13 +225,14 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe upstreamHeaders, downstreamHeaders := target.parent.SplitInboundOutboundHeaders() err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ - ProxyDomain: target.Domain, - OriginalHost: originalHostHeader, - UseTLS: target.RequireTLS, - PathPrefix: target.MatchingPath, - UpstreamHeaders: upstreamHeaders, - DownstreamHeaders: downstreamHeaders, - Version: target.parent.parent.Option.HostVersion, + ProxyDomain: target.Domain, + OriginalHost: originalHostHeader, + UseTLS: target.RequireTLS, + PathPrefix: target.MatchingPath, + UpstreamHeaders: upstreamHeaders, + DownstreamHeaders: downstreamHeaders, + HostHeaderOverwrite: target.parent.RequestHostOverwrite, + Version: target.parent.parent.Option.HostVersion, }) var dnsError *net.DNSError diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go index 2effa6c..16ebd45 100644 --- a/src/mod/dynamicproxy/typedef.go +++ b/src/mod/dynamicproxy/typedef.go @@ -132,6 +132,7 @@ type ProxyEndpoint struct { //Custom Headers UserDefinedHeaders []*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 diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 90a9f77..cea0c50 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -1235,6 +1235,149 @@ func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) { } +func HandleHostOverwrite(w http.ResponseWriter, r *http.Request) { + domain, err := utils.PostPara(r, "domain") + if err != nil { + domain, err = utils.GetPara(r, "domain") + if err != nil { + utils.SendErrorResponse(w, "domain or matching rule not defined") + return + } + } + //Get the proxy endpoint object dedicated to this domain + targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain) + if err != nil { + utils.SendErrorResponse(w, "target endpoint not exists") + return + } + + if r.Method == http.MethodGet { + //Get the current host header + js, _ := json.Marshal(targetProxyEndpoint.RequestHostOverwrite) + utils.SendJSONResponse(w, string(js)) + } else if r.Method == http.MethodPost { + //Set the new host header + newHostname, _ := utils.PostPara(r, "hostname") + + //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 + //Save proxy endpoint + err = SaveReverseProxyConfig(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Spawn a new endpoint with updated dpcore + preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Remove the old endpoint + err = targetProxyEndpoint.Remove() + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Add the newly prepared endpoint to runtime + err = dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Print log message + if newHostname != "" { + SystemWideLogger.Println("Updated " + domain + " hostname overwrite to: " + newHostname) + } else { + SystemWideLogger.Println("Removed " + domain + " hostname overwrite") + } + + utils.SendOK(w) + } else { + //Invalid method + http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed) + } +} + +// HandleHopByHop get and set the hop by hop remover state +// note that it shows the DISABLE STATE of hop-by-hop remover, not the enable state +func HandleHopByHop(w http.ResponseWriter, r *http.Request) { + domain, err := utils.PostPara(r, "domain") + if err != nil { + domain, err = utils.GetPara(r, "domain") + if err != nil { + utils.SendErrorResponse(w, "domain or matching rule not defined") + return + } + } + + targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain) + if err != nil { + utils.SendErrorResponse(w, "target endpoint not exists") + return + } + + if r.Method == http.MethodGet { + //Get the current hop by hop header state + js, _ := json.Marshal(!targetProxyEndpoint.DisableHopByHopHeaderRemoval) + utils.SendJSONResponse(w, string(js)) + } else if r.Method == http.MethodPost { + //Set the hop by hop header state + enableHopByHopRemover, _ := utils.PostBool(r, "removeHopByHop") + + //As this will require change in the proxy instance we are running + //we need to clone and respawn this proxy endpoint + newProxyEndpoint := targetProxyEndpoint.Clone() + //Storage file use false as default, so disable removal = not enable remover + targetProxyEndpoint.DisableHopByHopHeaderRemoval = !enableHopByHopRemover + //Save proxy endpoint + err = SaveReverseProxyConfig(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Spawn a new endpoint with updated dpcore + preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Remove the old endpoint + err = targetProxyEndpoint.Remove() + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Add the newly prepared endpoint to runtime + err = dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Print log message + if enableHopByHopRemover { + SystemWideLogger.Println("Enabled hop-by-hop headers removal on " + domain) + } else { + SystemWideLogger.Println("Disabled hop-by-hop headers removal on " + domain) + } + + utils.SendOK(w) + + } else { + http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed) + } +} + // Handle view or edit HSTS states func HandleHSTSState(w http.ResponseWriter, r *http.Request) { domain, err := utils.PostPara(r, "domain") diff --git a/src/web/snippet/customHeaders.html b/src/web/snippet/customHeaders.html index b995638..522e08a 100644 --- a/src/web/snippet/customHeaders.html +++ b/src/web/snippet/customHeaders.html @@ -83,6 +83,41 @@
+
+
+
+
+ + Advance Settings +
+
+
+
+

Settings in this section are for advanced users. Invalid settings might cause werid, unexpected behavior.

+
+
+

Overwrite Host Header

+

Manual override the automatic "Host" header rewrite logic. Leave empty for automatic.

+
+ + + +
+ +
+

Remove Hop-by-hop Headers

+

Remove headers like "Connection" and "Keep-Alive" from both upstream and downstream requests. Set to ON by default.

+
+ + +
+
+
+
+
+
+

HTTP Strict Transport Security

@@ -129,6 +164,7 @@ \ No newline at end of file