From 3c782118007f27fc9dfe7fe0eb67544b3e87795a Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Tue, 16 Apr 2024 23:33:24 +0800 Subject: [PATCH] Added alias support + Added alias support (use , when adding a new proxy target to automatically add alias hostnames) + Fixed some UI issues --- src/api.go | 1 + src/mod/dynamicproxy/proxyRequestHandler.go | 28 ++- src/mod/dynamicproxy/typedef.go | 7 +- src/reverseproxy.go | 89 +++++++++- src/web/components/httprp.html | 55 +++++- src/web/components/rules.html | 71 +++++++- src/web/snippet/aliasEditor.html | 178 ++++++++++++++++++++ 7 files changed, 413 insertions(+), 16 deletions(-) create mode 100644 src/web/snippet/aliasEditor.html diff --git a/src/api.go b/src/api.go index 6d5b267..9b23057 100644 --- a/src/api.go +++ b/src/api.go @@ -51,6 +51,7 @@ func initAPIs() { authRouter.HandleFunc("/api/proxy/list", ReverseProxyList) authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail) authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint) + authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias) authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint) authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials) authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS) diff --git a/src/mod/dynamicproxy/proxyRequestHandler.go b/src/mod/dynamicproxy/proxyRequestHandler.go index f3bd9a2..d268e6e 100644 --- a/src/mod/dynamicproxy/proxyRequestHandler.go +++ b/src/mod/dynamicproxy/proxyRequestHandler.go @@ -34,23 +34,45 @@ func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoi var targetSubdomainEndpoint *ProxyEndpoint = nil ep, ok := router.ProxyEndpoints.Load(hostname) if ok { + //Exact hit targetSubdomainEndpoint = ep.(*ProxyEndpoint) + if !targetSubdomainEndpoint.Disabled { + return targetSubdomainEndpoint + } } - //No hit. Try with wildcard + //No hit. Try with wildcard and alias matchProxyEndpoints := []*ProxyEndpoint{} router.ProxyEndpoints.Range(func(k, v interface{}) bool { ep := v.(*ProxyEndpoint) match, err := filepath.Match(ep.RootOrMatchingDomain, hostname) if err != nil { - //Continue + //Bad pattern. Skip this rule return true } + if match { - //targetSubdomainEndpoint = ep + //Wildcard matches. Skip checking alias matchProxyEndpoints = append(matchProxyEndpoints, ep) return true } + + //Wildcard not match. Check for alias + if ep.MatchingDomainAlias != nil && len(ep.MatchingDomainAlias) > 0 { + for _, aliasDomain := range ep.MatchingDomainAlias { + match, err := filepath.Match(aliasDomain, hostname) + if err != nil { + //Bad pattern. Skip this alias + continue + } + + if match { + //This alias match + matchProxyEndpoints = append(matchProxyEndpoints, ep) + return true + } + } + } return true }) diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go index 7dfcd7d..dad1927 100644 --- a/src/mod/dynamicproxy/typedef.go +++ b/src/mod/dynamicproxy/typedef.go @@ -92,9 +92,10 @@ type VirtualDirectoryEndpoint struct { // A proxy endpoint record, a general interface for handling inbound routing type ProxyEndpoint struct { - ProxyType int //The type of this proxy, see const def - RootOrMatchingDomain string //Matching domain for host, also act as key - Domain string //Domain or IP to proxy to + 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 diff --git a/src/reverseproxy.go b/src/reverseproxy.go index cb75ba2..eb084f1 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -195,6 +195,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { useTLS := (tls == "true") + //Bypass global TLS value / allow direct access from port 80? bypassGlobalTLS, _ := utils.PostPara(r, "bypassGlobalTLS") if bypassGlobalTLS == "" { bypassGlobalTLS = "false" @@ -202,6 +203,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { useBypassGlobalTLS := bypassGlobalTLS == "true" + //Enable TLS validation? stv, _ := utils.PostPara(r, "tlsval") if stv == "" { stv = "false" @@ -209,6 +211,17 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { skipTlsValidation := (stv == "true") + //Get access rule ID + accessRuleID, _ := utils.PostPara(r, "access") + if accessRuleID == "" { + accessRuleID = "default" + } + if !accessController.AccessRuleExists(accessRuleID) { + utils.SendErrorResponse(w, "invalid access rule ID selected") + return + } + + //Require basic auth? rba, _ := utils.PostPara(r, "bauth") if rba == "" { rba = "false" @@ -255,19 +268,37 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { if eptype == "host" { rootOrMatchingDomain, err := utils.PostPara(r, "rootname") if err != nil { - utils.SendErrorResponse(w, "subdomain not defined") + utils.SendErrorResponse(w, "hostname not defined") return } + rootOrMatchingDomain = strings.TrimSpace(rootOrMatchingDomain) + + //Check if it contains ",", if yes, split the remainings as alias + aliasHostnames := []string{} + if strings.Contains(rootOrMatchingDomain, ",") { + matchingDomains := strings.Split(rootOrMatchingDomain, ",") + if len(matchingDomains) > 1 { + rootOrMatchingDomain = matchingDomains[0] + for _, aliasHostname := range matchingDomains[1:] { + //Filter out any space + aliasHostnames = append(aliasHostnames, strings.TrimSpace(aliasHostname)) + } + } + } + + //Generate a proxy endpoint object thisProxyEndpoint := dynamicproxy.ProxyEndpoint{ //I/O ProxyType: dynamicproxy.ProxyType_Host, RootOrMatchingDomain: rootOrMatchingDomain, + MatchingDomainAlias: aliasHostnames, Domain: endpoint, //TLS RequireTLS: useTLS, BypassGlobalTLS: useBypassGlobalTLS, SkipCertValidations: skipTlsValidation, SkipWebSocketOriginCheck: bypassWebsocketOriginCheck, + AccessFilterUUID: accessRuleID, //VDir VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{}, //Custom headers @@ -440,6 +471,62 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { utils.SendOK(w) } +func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) { + rootNameOrMatchingDomain, err := utils.PostPara(r, "ep") + if err != nil { + utils.SendErrorResponse(w, "Invalid ep given") + return + } + + //No need to check for type as root (/) can be set to default route + //and hence, you will not need alias + + //Load the previous alias from current proxy rules + targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain) + if err != nil { + utils.SendErrorResponse(w, "Target proxy config not found or could not be loaded") + return + } + + newAliasJSON, err := utils.PostPara(r, "alias") + if err != nil { + //No new set of alias given + utils.SendErrorResponse(w, "new alias not given") + return + } + + //Write new alias to runtime and file + newAlias := []string{} + err = json.Unmarshal([]byte(newAliasJSON), &newAlias) + if err != nil { + SystemWideLogger.PrintAndLog("Proxy", "Unable to parse new alias list", err) + utils.SendErrorResponse(w, "Invalid alias list given") + return + } + + //Set the current alias + newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry) + newProxyEndpoint.MatchingDomainAlias = newAlias + + // Prepare to replace the current routing rule + readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + targetProxyEntry.Remove() + dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule) + + // Save it to file + err = SaveReverseProxyConfig(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, "Alias update failed") + SystemWideLogger.PrintAndLog("Proxy", "Unable to save alias update", err) + } + + utils.SendOK(w) +} + func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) { ep, err := utils.GetPara(r, "ep") if err != nil { diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index 25b4f41..b96dedc 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -7,6 +7,10 @@ #httpProxyList .ui.toggle.checkbox input:checked ~ label::before{ background-color: #00ca52 !important; } + + .subdEntry td:not(.ignoremw){ + min-width: 200px; + }
@@ -44,6 +48,8 @@ `); }else{ + //Sort by RootOrMatchingDomain field + data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0)) data.forEach(subd => { let tlsIcon = ""; let subdData = encodeURIComponent(JSON.stringify(subd)); @@ -75,14 +81,25 @@ vdList = ` No Virtual Directory`; } - var enableChecked = "checked"; + let enableChecked = "checked"; if (subd.Disabled){ enableChecked = ""; } + let aliasDomains = ``; + if (subd.MatchingDomainAlias != undefined && subd.MatchingDomainAlias.length > 0){ + aliasDomains = `Alias: `; + subd.MatchingDomainAlias.forEach(alias => { + aliasDomains += `${alias}, `; + }); + aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator + aliasDomains += `
`; + } + $("#httpProxyList").append(` @@ -90,7 +107,7 @@ -
No HTTP Proxy Record
${subd.RootOrMatchingDomain} ${inboundTlsIcon}
+ ${aliasDomains}
${subd.Domain} ${tlsIcon} ${subd.RequireBasicAuth?``:``} +
@@ -106,6 +123,28 @@ }); } + //Perform realtime alias update without refreshing the whole page + function updateAliasListForEndpoint(endpointName, newAliasDomainList){ + let targetEle = $(`.aliasDomains[eptuuid='${endpointName}']`); + console.log(targetEle); + if (targetEle.length == 0){ + return; + } + + let aliasDomains = ``; + if (newAliasDomainList != undefined && newAliasDomainList.length > 0){ + aliasDomains = `Alias: `; + newAliasDomainList.forEach(alias => { + aliasDomains += `${alias}, `; + }); + aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator + $(targetEle).html(aliasDomains); + $(targetEle).show(); + }else{ + $(targetEle).hide(); + } + } + //Resolve & Update all rule names on host PR list function resolveAccessRuleNameOnHostRPlist(){ //Resolve the access filters @@ -277,7 +316,9 @@

- + + + `); $(".hostAccessRuleSelector").dropdown(); @@ -352,6 +393,14 @@ showSideWrapper("snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload); } + function editAliasHostnames(uuid){ + let payload = encodeURIComponent(JSON.stringify({ + ept: "host", + ep: uuid + })); + showSideWrapper("snippet/aliasEditor.html?t=" + Date.now() + "#" + payload); + } + function quickEditVdir(uuid){ openTabById("vdir"); $("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid); diff --git a/src/web/components/rules.html b/src/web/components/rules.html index 15019bf..6a4faad 100644 --- a/src/web/components/rules.html +++ b/src/web/components/rules.html @@ -5,6 +5,12 @@ color: var(--theme_lgrey); border-radius: 1em !important; } + + .ui.form .sub.field{ + background-color: var(--theme_advance); + border-radius: 0.6em; + padding: 1em; + }
@@ -16,7 +22,7 @@
- Support subdomain and wildcard, e.g. s1.mydomain.com or *.test.mydomain.com + Support subdomain and wildcard, e.g. s1.mydomain.com or *.test.mydomain.com. Use comma (,) for alias hostnames.
@@ -37,7 +43,18 @@ Advance Settings
-

+
+ + + Allow regional access control using blacklist or whitelist. Use "default" for "allow all". +
@@ -121,8 +138,6 @@
\ No newline at end of file diff --git a/src/web/snippet/aliasEditor.html b/src/web/snippet/aliasEditor.html new file mode 100644 index 0000000..026230a --- /dev/null +++ b/src/web/snippet/aliasEditor.html @@ -0,0 +1,178 @@ + + + + + + + + + +
+
+
+
+ Alias Hostname +
+
+
+
+
+
+

Enter alias hostname or wildcard matching keywords for

+ + + + + + + + + + + +
Alias HostnameRemove
No Alias Hostname
+
+
+
+ + + Support wildcards e.g. alias.mydomain.com or *.alias.mydomain.com +
+
+ +
+
+
+
+
+
+
+ +
+
+ +



+ +
+ + + \ No newline at end of file