Browse Source

auto update script executed

Toby Chui 1 year ago
parent
commit
6572f1968d

+ 23 - 1
cert.go

@@ -1,7 +1,9 @@
 package main
 
 import (
+	"crypto/x509"
 	"encoding/json"
+	"encoding/pem"
 	"fmt"
 	"io"
 	"log"
@@ -41,21 +43,41 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
 		type CertInfo struct {
 			Domain           string
 			LastModifiedDate string
+			ExpireDate       string
 		}
 
 		results := []*CertInfo{}
 
 		for _, filename := range filenames {
-			fileInfo, err := os.Stat(filepath.Join(tlsCertManager.CertStore, filename+".crt"))
+			certFilepath := filepath.Join(tlsCertManager.CertStore, filename+".crt")
+			//keyFilepath := filepath.Join(tlsCertManager.CertStore, filename+".key")
+			fileInfo, err := os.Stat(certFilepath)
 			if err != nil {
 				utils.SendErrorResponse(w, "invalid domain certificate discovered: "+filename)
 				return
 			}
 			modifiedTime := fileInfo.ModTime().Format("2006-01-02 15:04:05")
 
+			certExpireTime := "Unknown"
+			certBtyes, err := os.ReadFile(certFilepath)
+			if err != nil {
+				//Unable to load this file
+				continue
+			} else {
+				//Cert loaded. Check its expire time
+				block, _ := pem.Decode(certBtyes)
+				if block != nil {
+					cert, err := x509.ParseCertificate(block.Bytes)
+					if err == nil {
+						certExpireTime = cert.NotAfter.Format("2006-01-02 15:04:05")
+					}
+				}
+			}
+
 			thisCertInfo := CertInfo{
 				Domain:           filename,
 				LastModifiedDate: modifiedTime,
+				ExpireDate:       certExpireTime,
 			}
 
 			results = append(results, &thisCertInfo)

+ 11 - 13
config.go

@@ -8,6 +8,7 @@ import (
 	"path/filepath"
 	"strings"
 
+	"imuslab.com/zoraxy/mod/dynamicproxy"
 	"imuslab.com/zoraxy/mod/utils"
 )
 
@@ -19,24 +20,22 @@ import (
 */
 
 type Record struct {
-	ProxyType   string
-	Rootname    string
-	ProxyTarget string
-	UseTLS      bool
+	ProxyType            string
+	Rootname             string
+	ProxyTarget          string
+	UseTLS               bool
+	SkipTlsValidation    bool
+	RequireBasicAuth     bool
+	BasicAuthCredentials []*dynamicproxy.BasicAuthCredentials
 }
 
-func SaveReverseProxyConfig(ptype string, rootname string, proxyTarget string, useTLS bool) error {
+func SaveReverseProxyConfig(proxyConfigRecord *Record) error {
 	//TODO: Make this accept new def types
 	os.MkdirAll("conf", 0775)
-	filename := getFilenameFromRootName(rootname)
+	filename := getFilenameFromRootName(proxyConfigRecord.Rootname)
 
 	//Generate record
-	thisRecord := Record{
-		ProxyType:   ptype,
-		Rootname:    rootname,
-		ProxyTarget: proxyTarget,
-		UseTLS:      useTLS,
-	}
+	thisRecord := proxyConfigRecord
 
 	//Write to file
 	js, _ := json.MarshalIndent(thisRecord, "", " ")
@@ -68,7 +67,6 @@ func LoadReverseProxyConfig(filename string) (*Record, error) {
 	}
 
 	//Unmarshal the content into config
-
 	err = json.Unmarshal(configContent, &thisRecord)
 	if err != nil {
 		return &thisRecord, err

+ 33 - 3
mod/dynamicproxy/Server.go

@@ -12,9 +12,19 @@ import (
 	Server.go
 
 	Main server for dynamic proxy core
+
+	Routing Handler Priority (High to Low)
+	- Blacklist
+	- Whitelist
+	- Redirectable
+	- Subdomain Routing
+	- Vitrual Directory Routing
 */
 
 func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	/*
+		General Access Check
+	*/
 	//Check if this ip is in blacklist
 	clientIpAddr := geodb.GetRequesterIP(r)
 	if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
@@ -30,6 +40,9 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	/*
+		Redirection Routing
+	*/
 	//Check if this is a redirection url
 	if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
 		statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
@@ -53,21 +66,37 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		domainOnly = hostPath[0]
 	}
 
+	/*
+		Subdomain Routing
+	*/
 	if strings.Contains(r.Host, ".") {
 		//This might be a subdomain. See if there are any subdomain proxy router for this
-		//Remove the port if any
-
 		sep := h.Parent.getSubdomainProxyEndpointFromHostname(domainOnly)
 		if sep != nil {
+			if sep.RequireBasicAuth {
+				err := handleBasicAuthRouting(w, r, sep)
+				if err != nil {
+					return
+				}
+			}
 			h.subdomainRequest(w, r, sep)
 			return
 		}
 	}
 
+	/*
+		Virtual Directory Routing
+	*/
 	//Clean up the request URI
 	proxyingPath := strings.TrimSpace(r.RequestURI)
 	targetProxyEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath)
 	if targetProxyEndpoint != nil {
+		if targetProxyEndpoint.RequireBasicAuth {
+			err := handleBasicAuthRouting(w, r, targetProxyEndpoint)
+			if err != nil {
+				return
+			}
+		}
 		h.proxyRequest(w, r, targetProxyEndpoint)
 	} else if !strings.HasSuffix(proxyingPath, "/") {
 		potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
@@ -75,11 +104,12 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		if potentialProxtEndpoint != nil {
 			//Missing tailing slash. Redirect to target proxy endpoint
 			http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
-			//h.proxyRequest(w, r, potentialProxtEndpoint)
 		} else {
+			//Passthrough the request to root
 			h.proxyRequest(w, r, h.Parent.Root)
 		}
 	} else {
+		//No routing rules found. Route to root.
 		h.proxyRequest(w, r, h.Parent.Root)
 	}
 }

+ 41 - 0
mod/dynamicproxy/basicAuth.go

@@ -0,0 +1,41 @@
+package dynamicproxy
+
+import (
+	"errors"
+	"net/http"
+
+	"imuslab.com/zoraxy/mod/auth"
+)
+
+/*
+	BasicAuth.go
+
+	This file handles the basic auth on proxy endpoints
+	if RequireBasicAuth is set to true
+*/
+
+func handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
+	u, p, ok := r.BasicAuth()
+	if !ok {
+		w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
+		w.WriteHeader(401)
+		return errors.New("unauthorized")
+	}
+
+	//Check for the credentials to see if there is one matching
+	hashedPassword := auth.Hash(p)
+	matchingFound := false
+	for _, cred := range pe.BasicAuthCredentials {
+		if u == cred.Username && hashedPassword == cred.PasswordHash {
+			matchingFound = true
+			break
+		}
+	}
+
+	if !matchingFound {
+		w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
+		w.WriteHeader(401)
+		return errors.New("unauthorized")
+	}
+	return nil
+}

+ 6 - 0
mod/dynamicproxy/typedef.go

@@ -51,6 +51,12 @@ type BasicAuthCredentials struct {
 	PasswordHash string
 }
 
+// Auth credential for basic auth on certain endpoints
+type BasicAuthUnhashedCredentials struct {
+	Username string
+	Password string
+}
+
 // A proxy endpoint record
 type ProxyEndpoint struct {
 	ProxyType            int                     //The type of this proxy, see const def

+ 78 - 22
reverseproxy.go

@@ -10,6 +10,7 @@ import (
 	"strings"
 	"time"
 
+	"imuslab.com/zoraxy/mod/auth"
 	"imuslab.com/zoraxy/mod/dynamicproxy"
 	"imuslab.com/zoraxy/mod/uptime"
 	"imuslab.com/zoraxy/mod/utils"
@@ -77,29 +78,27 @@ func ReverseProxtInit() {
 			})
 		} else if record.ProxyType == "subd" {
 			dynamicProxyRouter.AddSubdomainRoutingService(&dynamicproxy.SubdOptions{
-				MatchingDomain: record.Rootname,
-				Domain:         record.ProxyTarget,
-				RequireTLS:     record.UseTLS,
+				MatchingDomain:       record.Rootname,
+				Domain:               record.ProxyTarget,
+				RequireTLS:           record.UseTLS,
+				SkipCertValidations:  record.SkipTlsValidation,
+				RequireBasicAuth:     record.RequireBasicAuth,
+				BasicAuthCredentials: record.BasicAuthCredentials,
 			})
 		} else if record.ProxyType == "vdir" {
 			dynamicProxyRouter.AddVirtualDirectoryProxyService(&dynamicproxy.VdirOptions{
-				RootName:   record.Rootname,
-				Domain:     record.ProxyTarget,
-				RequireTLS: record.UseTLS,
+				RootName:             record.Rootname,
+				Domain:               record.ProxyTarget,
+				RequireTLS:           record.UseTLS,
+				SkipCertValidations:  record.SkipTlsValidation,
+				RequireBasicAuth:     record.RequireBasicAuth,
+				BasicAuthCredentials: record.BasicAuthCredentials,
 			})
 		} else {
 			log.Println("Unsupported endpoint type: " + record.ProxyType + ". Skipping " + filepath.Base(conf))
 		}
 	}
 
-	/*
-		dynamicProxyRouter.SetRootProxy("192.168.0.107:8080", false)
-		dynamicProxyRouter.AddSubdomainRoutingService("aroz.localhost", "192.168.0.107:8080/private/AOB/", false)
-		dynamicProxyRouter.AddSubdomainRoutingService("loopback.localhost", "localhost:8080", false)
-		dynamicProxyRouter.AddSubdomainRoutingService("git.localhost", "mc.alanyeung.co:3000", false)
-		dynamicProxyRouter.AddVirtualDirectoryProxyService("/git/server/", "mc.alanyeung.co:3000", false)
-	*/
-
 	//Start Service
 	//Not sure why but delay must be added if you have another
 	//reverse proxy server in front of this service
@@ -122,7 +121,6 @@ func ReverseProxtInit() {
 }
 
 func ReverseProxyHandleOnOff(w http.ResponseWriter, r *http.Request) {
-
 	enable, _ := utils.PostPara(r, "enable") //Support root, vdir and subd
 	if enable == "true" {
 		err := dynamicProxyRouter.StartProxyService()
@@ -168,6 +166,49 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 	}
 
 	useTLS := (tls == "true")
+
+	stv, _ := utils.PostPara(r, "tlsval")
+	if stv == "" {
+		stv = "false"
+	}
+
+	skipTlsValidation := (stv == "true")
+
+	rba, _ := utils.PostPara(r, "bauth")
+	if rba == "" {
+		rba = "false"
+	}
+
+	requireBasicAuth := (rba == "true")
+
+	//Prase the basic auth to correct structure
+	cred, _ := utils.PostPara(r, "cred")
+	basicAuthCredentials := []*dynamicproxy.BasicAuthCredentials{}
+	if requireBasicAuth {
+		preProcessCredentials := []*dynamicproxy.BasicAuthUnhashedCredentials{}
+		err = json.Unmarshal([]byte(cred), &preProcessCredentials)
+		if err != nil {
+			utils.SendErrorResponse(w, "invalid user credentials")
+			return
+		}
+
+		//Check if there are empty password credentials
+		for _, credObj := range preProcessCredentials {
+			if strings.TrimSpace(credObj.Password) == "" {
+				utils.SendErrorResponse(w, credObj.Username+" has empty password")
+				return
+			}
+		}
+
+		//Convert and hash the passwords
+		for _, credObj := range preProcessCredentials {
+			basicAuthCredentials = append(basicAuthCredentials, &dynamicproxy.BasicAuthCredentials{
+				Username:     credObj.Username,
+				PasswordHash: auth.Hash(credObj.Password),
+			})
+		}
+	}
+
 	rootname := ""
 	if eptype == "vdir" {
 		vdir, err := utils.PostPara(r, "rootname")
@@ -183,9 +224,12 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 		rootname = vdir
 
 		thisOption := dynamicproxy.VdirOptions{
-			RootName:   vdir,
-			Domain:     endpoint,
-			RequireTLS: useTLS,
+			RootName:             vdir,
+			Domain:               endpoint,
+			RequireTLS:           useTLS,
+			SkipCertValidations:  skipTlsValidation,
+			RequireBasicAuth:     requireBasicAuth,
+			BasicAuthCredentials: basicAuthCredentials,
 		}
 		dynamicProxyRouter.AddVirtualDirectoryProxyService(&thisOption)
 
@@ -197,9 +241,12 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 		}
 		rootname = subdomain
 		thisOption := dynamicproxy.SubdOptions{
-			MatchingDomain: subdomain,
-			Domain:         endpoint,
-			RequireTLS:     useTLS,
+			MatchingDomain:       subdomain,
+			Domain:               endpoint,
+			RequireTLS:           useTLS,
+			SkipCertValidations:  skipTlsValidation,
+			RequireBasicAuth:     requireBasicAuth,
+			BasicAuthCredentials: basicAuthCredentials,
 		}
 		dynamicProxyRouter.AddSubdomainRoutingService(&thisOption)
 	} else if eptype == "root" {
@@ -216,7 +263,16 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 	}
 
 	//Save it
-	SaveReverseProxyConfig(eptype, rootname, endpoint, useTLS)
+	thisProxyConfigRecord := Record{
+		ProxyType:            eptype,
+		Rootname:             rootname,
+		ProxyTarget:          endpoint,
+		UseTLS:               useTLS,
+		SkipTlsValidation:    skipTlsValidation,
+		RequireBasicAuth:     requireBasicAuth,
+		BasicAuthCredentials: basicAuthCredentials,
+	}
+	SaveReverseProxyConfig(&thisProxyConfigRecord)
 
 	//Update utm if exists
 	if uptimeMonitor != nil {

+ 2 - 0
web/components/cert.html

@@ -58,6 +58,7 @@
             <thead>
             <tr><th>Domain</th>
             <th>Last Update</th>
+            <th>Expire At</th>
             <th class="no-sort">Remove</th>
             </tr></thead>
         <tbody id="certifiedDomainList">
@@ -108,6 +109,7 @@
                     $("#certifiedDomainList").append(`<tr>
                     <td>${entry.Domain}</td>
                     <td>${entry.LastModifiedDate}</td>
+                    <td>${entry.ExpireDate}</td>
                     <td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
                     </tr>`);
                 })

+ 15 - 4
web/components/rules.html

@@ -20,7 +20,6 @@
                         <div class="field">
                             <label>Subdomain Matching Keyword / Virtual Directory Name</label>
                             <input type="text" id="rootname" placeholder="s1.mydomain.com">
-                        
                         </div>
                         <div class="field">
                             <label>IP Address or Domain Name with port</label>
@@ -123,6 +122,8 @@
         var rootname = $("#rootname").val();
         var proxyDomain = $("#proxyDomain").val();
         var useTLS = $("#reqTls")[0].checked;
+        var skipTLSValidation = $("#skipTLSValidation")[0].checked;
+        var requireBasicAuth = $("#requireBasicAuth")[0].checked;
 
         if (type === "vdir") {
             if (!rootname.startsWith("/")) {
@@ -155,7 +156,15 @@
         //Create the endpoint by calling add
         $.ajax({
             url: "/api/proxy/add",
-            data: {type: type, rootname: rootname, tls: useTLS, ep: proxyDomain},
+            data: {
+                type: type, 
+                rootname: rootname, 
+                tls: useTLS, 
+                ep: proxyDomain,
+                tlsval: skipTLSValidation,
+                bauth: requireBasicAuth,
+                cred: JSON.stringify(credentials),
+            },
             success: function(data){
                 if (data.error != undefined){
                     msgbox(data.error, false, 5000);
@@ -168,6 +177,8 @@
                     //Clear old data
                     $("#rootname").val("");
                     $("#proxyDomain").val("");
+                    credentials = [];
+                    updateTable();
                 }
             }
         });
@@ -244,8 +255,8 @@
         credentials.push(credential);
 
         // Clear the input fields
-        $('input[type="text"]').val('');
-        $('input[type="password"]').val('');
+        $('#basicAuthCredUsername').val('');
+        $('#basicAuthCredPassword').val('');
 
         // Update the table body with the credentials
         updateTable();

+ 10 - 3
web/components/subd.html

@@ -9,7 +9,9 @@
                 <tr>
                     <th>Matching Domain</th>
                     <th>Proxy To</th>
-                    <th class="no-sort">Remove</th>
+                    <th>TLS/SSL Verification</th>
+                    <th>Basic Auth</th>
+                    <th class="no-sort" style="min-width: 7.2em;">Actions</th>
                 </tr>
             </thead>
             <tbody id="subdList">
@@ -36,12 +38,17 @@
                 data.forEach(subd => {
                     let tlsIcon = "";
                     if (subd.RequireTLS){
-                        tlsIcon = `<i class="lock icon"></i>`;
+                        tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
                     }
                     $("#subdList").append(`<tr>
                         <td data-label="">${subd.RootOrMatchingDomain}</td>
                         <td data-label="">${subd.Domain} ${tlsIcon}</td>
-                        <td class="center aligned" data-label=""><button class="ui circular mini red basic icon button" onclick='deleteEndpoint("subd","${subd.MatchingDomain}")'><i class="trash icon"></i></button></td>
+                        <td data-label="">${!subd.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td>
+                        <td data-label="">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
+                        <td class="center aligned" data-label="">
+                            <button class="ui circular mini basic icon button" onclick='editEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="edit icon"></i></button>
+                            <button class="ui circular mini red basic icon button" onclick='deleteEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="trash icon"></i></button>
+                        </td>
                     </tr>`);
                 });
             }

+ 10 - 3
web/components/vdir.html

@@ -9,7 +9,9 @@
                 <tr>
                     <th>Virtual Directory</th>
                     <th>Proxy To</th>
-                    <th class="no-sort">Remove</th>
+                    <th>TLS/SSL Verification</th>
+                    <th>Basic Auth</th>
+                    <th class="no-sort" style="min-width: 7.2em;">Actions</th>
                 </tr>
             </thead>
             <tbody id="vdirList">
@@ -39,12 +41,17 @@
                 data.forEach(vdir => {
                     let tlsIcon = "";
                     if (vdir.RequireTLS){
-                        tlsIcon = `<i title="TLS mode" class="lock icon"></i>`;
+                        tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
                     }
                     $("#vdirList").append(`<tr>
                         <td data-label="">${vdir.RootOrMatchingDomain}</td>
                         <td data-label="">${vdir.Domain} ${tlsIcon}</td>
-                        <td class="center aligned" data-label=""><button class="ui circular mini red basic icon button"  onclick='deleteEndpoint("vdir","${vdir.Root}")'><i class="trash icon"></i></button></td>
+                        <td data-label="">${!subd.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td>
+                        <td data-label="">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
+                        <td class="center aligned" data-label="">
+                            <button class="ui circular mini basic icon button" onclick='editEndpoint("vdir","${vdir.RootOrMatchingDomain}")'><i class="edit icon"></i></button>
+                            <button class="ui circular mini red basic icon button"  onclick='deleteEndpoint("vdir","${vdir.RootOrMatchingDomain}")'><i class="trash icon"></i></button>
+                        </td>
                     </tr>`);
                 });
             }