Browse Source

Completed hosts base access rules

Toby Chui 11 months ago
parent
commit
1052ece308

+ 36 - 0
accesslist.go

@@ -30,6 +30,42 @@ func handleListAccessRules(w http.ResponseWriter, r *http.Request) {
 	utils.SendJSONResponse(w, string(js))
 }
 
+func handleAttachRuleToHost(w http.ResponseWriter, r *http.Request) {
+	ruleid, err := utils.PostPara(r, "id")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid rule name")
+		return
+	}
+
+	host, err := utils.PostPara(r, "host")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid rule name")
+		return
+	}
+
+	//Check if access rule and proxy rule exists
+	targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(host)
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid host given")
+		return
+	}
+	if !accessController.AccessRuleExists(ruleid) {
+		utils.SendErrorResponse(w, "access rule not exists")
+		return
+	}
+
+	//Update the proxy host acess rule id
+	targetProxyEndpoint.AccessFilterUUID = ruleid
+	targetProxyEndpoint.UpdateToRuntime()
+	err = SaveReverseProxyConfig(targetProxyEndpoint)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	utils.SendOK(w)
+}
+
 // Create a new access rule, require name and desc only
 func handleCreateAccessRule(w http.ResponseWriter, r *http.Request) {
 	ruleName, err := utils.PostPara(r, "name")

+ 1 - 0
api.go

@@ -90,6 +90,7 @@ func initAPIs() {
 
 	//Access Rules API
 	authRouter.HandleFunc("/api/access/list", handleListAccessRules)
+	authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
 	authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
 	authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
 	authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)

+ 2 - 1
mod/access/access.go

@@ -65,6 +65,7 @@ func NewAccessController(options *Options) (*Controller, error) {
 	//Generate a controller object
 	thisController := Controller{
 		DefaultAccessRule: &defaultAccessRule,
+		ProxyAccessRule:   &sync.Map{},
 		Options:           options,
 	}
 
@@ -111,7 +112,7 @@ func (c *Controller) GetGlobalAccessRule() (*AccessRule, error) {
 
 // Load access rules to runtime, require rule ID
 func (c *Controller) GetAccessRuleByID(accessRuleID string) (*AccessRule, error) {
-	if accessRuleID == "default" {
+	if accessRuleID == "default" || accessRuleID == "" {
 		return c.DefaultAccessRule, nil
 	}
 	//Load from sync.Map, should be O(1)

+ 0 - 47
mod/dynamicproxy/Server.go

@@ -1,14 +1,11 @@
 package dynamicproxy
 
 import (
-	"log"
 	"net/http"
 	"net/url"
 	"os"
 	"path/filepath"
 	"strings"
-
-	"imuslab.com/zoraxy/mod/netutils"
 )
 
 /*
@@ -198,47 +195,3 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
 		}
 	}
 }
-
-// Handle access check (blacklist / whitelist), return true if request is handled (aka blocked)
-// if the return value is false, you can continue process the response writer
-func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter, r *http.Request) bool {
-	accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
-	if err != nil {
-		//Unable to load access rule. Target rule not found?
-		log.Println("[Proxy] Unable to load access rule: " + ruleID)
-		w.WriteHeader(http.StatusInternalServerError)
-		w.Write([]byte("500 - Internal Server Error"))
-		return true
-	}
-
-	//Check if this ip is in blacklist
-	clientIpAddr := netutils.GetRequesterIP(r)
-	if accessRule.IsBlacklisted(clientIpAddr) {
-		w.Header().Set("Content-Type", "text/html; charset=utf-8")
-		w.WriteHeader(http.StatusForbidden)
-		template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/blacklist.html"))
-		if err != nil {
-			w.Write(page_forbidden)
-		} else {
-			w.Write(template)
-		}
-		h.logRequest(r, false, 403, "blacklist", "")
-		return true
-	}
-
-	//Check if this ip is in whitelist
-	if !accessRule.IsWhitelisted(clientIpAddr) {
-		w.Header().Set("Content-Type", "text/html; charset=utf-8")
-		w.WriteHeader(http.StatusForbidden)
-		template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/whitelist.html"))
-		if err != nil {
-			w.Write(page_forbidden)
-		} else {
-			w.Write(template)
-		}
-		h.logRequest(r, false, 403, "whitelist", "")
-		return true
-	}
-
-	return false
-}

+ 64 - 0
mod/dynamicproxy/access.go

@@ -0,0 +1,64 @@
+package dynamicproxy
+
+import (
+	"log"
+	"net/http"
+	"os"
+	"path/filepath"
+
+	"imuslab.com/zoraxy/mod/access"
+	"imuslab.com/zoraxy/mod/netutils"
+)
+
+// Handle access check (blacklist / whitelist), return true if request is handled (aka blocked)
+// if the return value is false, you can continue process the response writer
+func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter, r *http.Request) bool {
+	accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
+	if err != nil {
+		//Unable to load access rule. Target rule not found?
+		log.Println("[Proxy] Unable to load access rule: " + ruleID)
+		w.WriteHeader(http.StatusInternalServerError)
+		w.Write([]byte("500 - Internal Server Error"))
+		return true
+	}
+	isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
+	if isBlocked {
+		h.logRequest(r, false, 403, blockedReason, "")
+	}
+	return isBlocked
+}
+
+// Return boolean, return true if access is blocked
+// For string, it will return the blocked reason (if any)
+func accessRequestBlocked(accessRule *access.AccessRule, templateDirectory string, w http.ResponseWriter, r *http.Request) (bool, string) {
+	//Check if this ip is in blacklist
+	clientIpAddr := netutils.GetRequesterIP(r)
+	if accessRule.IsBlacklisted(clientIpAddr) {
+		w.Header().Set("Content-Type", "text/html; charset=utf-8")
+		w.WriteHeader(http.StatusForbidden)
+		template, err := os.ReadFile(filepath.Join(templateDirectory, "templates/blacklist.html"))
+		if err != nil {
+			w.Write(page_forbidden)
+		} else {
+			w.Write(template)
+		}
+
+		return true, "blacklist"
+	}
+
+	//Check if this ip is in whitelist
+	if !accessRule.IsWhitelisted(clientIpAddr) {
+		w.Header().Set("Content-Type", "text/html; charset=utf-8")
+		w.WriteHeader(http.StatusForbidden)
+		template, err := os.ReadFile(filepath.Join(templateDirectory, "templates/whitelist.html"))
+		if err != nil {
+			w.Write(page_forbidden)
+		} else {
+			w.Write(template)
+		}
+		return true, "whitelist"
+	}
+
+	//Not blocked.
+	return false, ""
+}

+ 10 - 1
mod/dynamicproxy/basicAuth.go

@@ -16,6 +16,16 @@ import (
 */
 
 func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
+	err := handleBasicAuth(w, r, pe)
+	if err != nil {
+		h.logRequest(r, false, 401, "host", pe.Domain)
+	}
+	return err
+}
+
+// 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 {
 		//Check if the current path matches the exception rules
 		for _, exceptionRule := range pe.BasicAuthExceptionRules {
@@ -44,7 +54,6 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
 	}
 
 	if !matchingFound {
-		h.logRequest(r, false, 401, "host", pe.Domain)
 		w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
 		w.WriteHeader(401)
 		return errors.New("unauthorized")

+ 22 - 0
mod/dynamicproxy/dynamicproxy.go

@@ -115,6 +115,28 @@ func (router *Router) StartProxyService() error {
 							r.URL, _ = url.Parse(originalHostHeader)
 						}
 
+						//Access Check (blacklist / whitelist)
+						ruleID := sep.AccessFilterUUID
+						if sep.AccessFilterUUID == "" {
+							//Use default rule
+							ruleID = "default"
+						}
+						accessRule, err := router.Option.AccessController.GetAccessRuleByID(ruleID)
+						if err == nil {
+							isBlocked, _ := accessRequestBlocked(accessRule, router.Option.WebDirectory, w, r)
+							if isBlocked {
+								return
+							}
+						}
+
+						//Validate basic auth
+						if sep.RequireBasicAuth {
+							err := handleBasicAuth(w, r, sep)
+							if err != nil {
+								return
+							}
+						}
+
 						sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
 							ProxyDomain:  sep.Domain,
 							OriginalHost: originalHostHeader,

+ 1 - 0
reverseproxy.go

@@ -94,6 +94,7 @@ func ReverseProxtInit() {
 		GeodbStore:         geodbStore,
 		StatisticCollector: statisticCollector,
 		WebDirectory:       *staticWebServerRoot,
+		AccessController:   accessController,
 	})
 	if err != nil {
 		SystemWideLogger.PrintAndLog("Proxy", "Unable to create dynamic proxy router", err)

+ 60 - 1
web/components/httprp.html

@@ -81,7 +81,10 @@
                     }
 
                     $("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
-                        <td data-label="" editable="true" datatype="inbound"><a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}</td>
+                        <td data-label="" editable="true" datatype="inbound">
+                            <a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}<br>
+                            <small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
+                        </td>
                         <td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
                         <td data-label="" editable="true" datatype="vdir">${vdList}</td>
                         <td data-label="" editable="true" datatype="basicauth">
@@ -98,9 +101,65 @@
                     </tr>`);
                 });
             }
+
+            resolveAccessRuleNameOnHostRPlist();
         });
     }
 
+    //Resolve & Update all rule names on host PR list
+    function resolveAccessRuleNameOnHostRPlist(){
+        //Resolve the access filters
+        $.get("/api/access/list", function(data){
+            console.log(data);
+            if (data.error == undefined){
+                //Build a map base on the data
+                let accessRuleMap = {};
+                for (var i = 0; i < data.length; i++){
+                    accessRuleMap[data[i].ID] = data[i];
+                }
+                
+                
+                $(".accessRuleNameUnderHost").each(function(){
+                    let thisAccessRuleID = $(this).attr("ruleid");
+                    if (thisAccessRuleID== ""){
+                        thisAccessRuleID = "default"
+                    }
+
+                    if (thisAccessRuleID == "default"){
+                        //No need to label default access rules
+                        $(this).html("");
+                        return;
+                    }
+
+                    let rule = accessRuleMap[thisAccessRuleID];
+                    let icon = `<i class="ui grey filter icon"></i>`;
+                    if (rule.ID == "default"){
+                        icon = `<i class="ui yellow star icon"></i>`;
+                    }else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
+                        //This is a blacklist filter
+                        icon = `<i class="ui red filter icon"></i>`;
+                    }else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
+                        //This is a whitelist filter
+                        icon = `<i class="ui green filter icon"></i>`;
+                    }else if (rule.WhitelistEnabled && rule.BlacklistEnabled){
+                        //Whitelist and blacklist filter
+                        icon = `<i class="ui yellow filter icon"></i>`;
+                    }
+
+                    if (rule != undefined){
+                        $(this).html(`${icon} ${rule.Name}`);
+                    }
+                });
+            }
+        })
+    }
+
+    //Update the access rule name on given epuuid, call by hostAccessEditor.html
+    function updateAccessRuleNameUnderHost(epuuid, newruleUID){
+        $(`tr[eptuuid='${epuuid}'].subdEntry`).find(".accessRuleNameUnderHost").attr("ruleid", newruleUID);
+        resolveAccessRuleNameOnHostRPlist();
+    }
+
     
     /*
         Inline editor for httprp.html

+ 22 - 1
web/snippet/hostAccessEditor.html

@@ -158,7 +158,28 @@
 
             function applyChangeAndClose(){
                 let newAccessRuleID = $(".accessRule.active").attr("ruleid");
-                alert(newAccessRuleID);
+                let targetEndpoint = editingEndpoint.ep;
+                $.ajax({
+                    url: "/api/access/attach",
+                    method: "POST",
+                    data: {
+                        id: newAccessRuleID,
+                        host: targetEndpoint
+                    },
+                    success: function(data){
+                        if (data.error != undefined){
+                            parent.msgbox(data.error, false);
+                        }else{
+                            parent.msgbox("Access Rule Updated");
+
+                            //Modify the parent list if exists
+                            if (parent != undefined && parent.updateAccessRuleNameUnderHost){
+                                parent.updateAccessRuleNameUnderHost(targetEndpoint, newAccessRuleID);
+                            }
+                            parent.hideSideWrapper();
+                        }
+                    }
+                })
             }
           
         </script>