mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00
Fixed #255
- Added host header manual overwrite feature - Added toggle for automatic hop-by-hop header removing
This commit is contained in:
parent
d3dbbf9052
commit
b1c5bc2963
@ -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)
|
||||
|
@ -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()
|
||||
|
||||
/*
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -83,6 +83,41 @@
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
||||
<div class="ui fluid accordion">
|
||||
<div class="title">
|
||||
<i class="dropdown icon" tabindex="0"><div class="menu" tabindex="-1"></div></i>
|
||||
Advance Settings
|
||||
</div>
|
||||
<div class="content">
|
||||
<br>
|
||||
<div class="ui yellow message">
|
||||
<p><i class="exclamation triangle icon"></i>Settings in this section are for advanced users. Invalid settings might cause werid, unexpected behavior.</p>
|
||||
</div>
|
||||
<div class="ui container">
|
||||
<h4>Overwrite Host Header</h4>
|
||||
<p>Manual override the automatic "Host" header rewrite logic. Leave empty for automatic.</p>
|
||||
<div class="ui fluid action input">
|
||||
<input type="text" id="manualHostOverwrite" placeholder="Overwrite Host name">
|
||||
<button onclick="updateManualHostOverwrite();" class="ui basic icon button" title="Update"><i class="ui green save icon"></i></button>
|
||||
<button onclick="clearManualHostOverwrite();" class="ui basic icon button" title="Clear"><i class="ui grey remove icon"></i></button>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<h4>Remove Hop-by-hop Headers</h4>
|
||||
<p>Remove headers like "Connection" and "Keep-Alive" from both upstream and downstream requests. Set to ON by default.</p>
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" id="removeHopByHop" name="">
|
||||
<label>Remove Hop-by-hop Header<br>
|
||||
<small>This should be ON by default</small></label>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="ui tab basic segment" data-tab="security">
|
||||
<h4>HTTP Strict Transport Security</h4>
|
||||
@ -129,6 +164,7 @@
|
||||
|
||||
<script>
|
||||
$('.menu .item').tab();
|
||||
$(".accordion").accordion();
|
||||
let permissionPolicyKeys = [];
|
||||
|
||||
let editingEndpoint = {};
|
||||
@ -512,6 +548,93 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/* Manual HOST header overwrite */
|
||||
function updateManualHostOverwrite(){
|
||||
updateManualHostOverwriteVal(function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("Host field Overwrite Updated");
|
||||
initManualHostOverwriteValue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearManualHostOverwrite(){
|
||||
$('#manualHostOverwrite').val('');
|
||||
updateManualHostOverwriteVal(function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("Host field Overwrite Cleared");
|
||||
initManualHostOverwriteValue();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function updateManualHostOverwriteVal(callback=undefined){
|
||||
let newHostname = $("#manualHostOverwrite").val().trim();
|
||||
$.ajax({
|
||||
url: "/api/proxy/header/handleHostOverwrite",
|
||||
method: "POST",
|
||||
data: {
|
||||
"domain": editingEndpoint.ep,
|
||||
"hostname": newHostname,
|
||||
},
|
||||
success: function(data){
|
||||
callback(data);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function initManualHostOverwriteValue(){
|
||||
$.get("/api/proxy/header/handleHostOverwrite?domain=" + editingEndpoint.ep, function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
$("#manualHostOverwrite").val(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
initManualHostOverwriteValue();
|
||||
|
||||
/* Hop-by-hop headers */
|
||||
function initHopByHopRemoverState(){
|
||||
$.get("/api/proxy/header/handleHopByHop?domain=" + editingEndpoint.ep, function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error);
|
||||
}else{
|
||||
if (data == true){
|
||||
$("#removeHopByHop").parent().checkbox("set checked");
|
||||
}else{
|
||||
$("#removeHopByHop").parent().checkbox("set unchecked");
|
||||
}
|
||||
|
||||
//Bind event to the checkbox
|
||||
$("#removeHopByHop").on("change", function(evt){
|
||||
let isChecked = $(this)[0].checked;
|
||||
$.ajax({
|
||||
url: "/api/proxy/header/handleHopByHop",
|
||||
method: "POST",
|
||||
data: {
|
||||
"domain": editingEndpoint.ep,
|
||||
"removeHopByHop": isChecked,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("Hop-by-Hop header rule updated");
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
initHopByHopRemoverState();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user