diff --git a/.gitignore b/.gitignore index 6392056..d7d03d5 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ src/conf/* src/ReverseProxy_*_* src/Zoraxy_*_* src/certs/* -src/rules/* \ No newline at end of file +src/rules/* +src/README.md diff --git a/src/cert.go b/src/cert.go index 897bc2a..85873d3 100644 --- a/src/cert.go +++ b/src/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) diff --git a/src/config.go b/src/config.go index b2eba05..e4c7da8 100644 --- a/src/config.go +++ b/src/config.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" + "imuslab.com/zoraxy/mod/dynamicproxy" "imuslab.com/zoraxy/mod/utils" ) @@ -19,23 +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, "", " ") @@ -67,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 diff --git a/src/main.go b/src/main.go index f95bcf8..fda5e31 100644 --- a/src/main.go +++ b/src/main.go @@ -38,7 +38,7 @@ var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local no var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port") var ( name = "Zoraxy" - version = "2.5" + version = "2.6" nodeUUID = "generic" development = false //Set this to false to use embedded web fs diff --git a/src/mod/dynamicproxy/Server.go b/src/mod/dynamicproxy/Server.go index e93efef..3e524d7 100644 --- a/src/mod/dynamicproxy/Server.go +++ b/src/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 := h.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 := h.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) } } diff --git a/src/mod/dynamicproxy/basicAuth.go b/src/mod/dynamicproxy/basicAuth.go new file mode 100644 index 0000000..d08e76a --- /dev/null +++ b/src/mod/dynamicproxy/basicAuth.go @@ -0,0 +1,47 @@ +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 (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error { + proxyType := "vdir-auth" + if pe.ProxyType == ProxyType_Subdomain { + proxyType = "subd-auth" + } + 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 { + h.logRequest(r, false, 401, proxyType, pe.Domain) + w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) + w.WriteHeader(401) + return errors.New("unauthorized") + } + + return nil +} diff --git a/src/mod/dynamicproxy/dpcore/dpcore.go b/src/mod/dynamicproxy/dpcore/dpcore.go index 79f19c4..0aa1259 100644 --- a/src/mod/dynamicproxy/dpcore/dpcore.go +++ b/src/mod/dynamicproxy/dpcore/dpcore.go @@ -71,7 +71,7 @@ type requestCanceler interface { CancelRequest(req *http.Request) } -func NewDynamicProxyCore(target *url.URL, prepender string) *ReverseProxy { +func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerification bool) *ReverseProxy { targetQuery := target.RawQuery director := func(req *http.Request) { req.URL.Scheme = target.Scheme @@ -95,7 +95,12 @@ func NewDynamicProxyCore(target *url.URL, prepender string) *ReverseProxy { thisTransporter.(*http.Transport).MaxIdleConnsPerHost = 3000 thisTransporter.(*http.Transport).IdleConnTimeout = 10 * time.Second thisTransporter.(*http.Transport).MaxConnsPerHost = 0 - thisTransporter.(*http.Transport).DisableCompression = true + //thisTransporter.(*http.Transport).DisableCompression = true + + if ignoreTLSVerification { + //Ignore TLS certificate validation error + thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true + } return &ReverseProxy{ Director: director, @@ -278,9 +283,6 @@ func addXForwardedForHeader(req *http.Request) { func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error { transport := p.Transport - if transport == nil { - transport = http.DefaultTransport - } outreq := new(http.Request) // Shallow copies of maps, like header diff --git a/src/mod/dynamicproxy/dynamicproxy.go b/src/mod/dynamicproxy/dynamicproxy.go index 6475bc7..b4b06d7 100644 --- a/src/mod/dynamicproxy/dynamicproxy.go +++ b/src/mod/dynamicproxy/dynamicproxy.go @@ -5,7 +5,6 @@ import ( "crypto/tls" "errors" "log" - "net" "net/http" "net/url" "strconv" @@ -14,57 +13,11 @@ import ( "time" "imuslab.com/zoraxy/mod/dynamicproxy/dpcore" - "imuslab.com/zoraxy/mod/dynamicproxy/redirection" - "imuslab.com/zoraxy/mod/geodb" - "imuslab.com/zoraxy/mod/reverseproxy" - "imuslab.com/zoraxy/mod/statistic" - "imuslab.com/zoraxy/mod/tlscert" ) /* -Zoraxy Dynamic Proxy + Zoraxy Dynamic Proxy */ -type RouterOption struct { - Port int - UseTls bool - ForceHttpsRedirect bool - TlsManager *tlscert.Manager - RedirectRuleTable *redirection.RuleTable - GeodbStore *geodb.Store - StatisticCollector *statistic.Collector -} - -type Router struct { - Option *RouterOption - ProxyEndpoints *sync.Map - SubdomainEndpoint *sync.Map - Running bool - Root *ProxyEndpoint - mux http.Handler - server *http.Server - tlsListener net.Listener - routingRules []*RoutingRule - - tlsRedirectStop chan bool -} - -type ProxyEndpoint struct { - Root string - Domain string - RequireTLS bool - Proxy *dpcore.ReverseProxy `json:"-"` -} - -type SubdomainEndpoint struct { - MatchingDomain string - Domain string - RequireTLS bool - Proxy *reverseproxy.ReverseProxy `json:"-"` -} - -type ProxyHandler struct { - Parent *Router -} func NewDynamicProxy(option RouterOption) (*Router, error) { proxyMap := sync.Map{} @@ -250,8 +203,8 @@ func (router *Router) IsProxiedSubdomain(r *http.Request) bool { /* Add an URL into a custom proxy services */ -func (router *Router) AddVirtualDirectoryProxyService(rootname string, domain string, requireTLS bool) error { - +func (router *Router) AddVirtualDirectoryProxyService(options *VdirOptions) error { + domain := options.Domain if domain[len(domain)-1:] == "/" { domain = domain[:len(domain)-1] } @@ -263,7 +216,7 @@ func (router *Router) AddVirtualDirectoryProxyService(rootname string, domain st */ webProxyEndpoint := domain - if requireTLS { + if options.RequireTLS { webProxyEndpoint = "https://" + webProxyEndpoint } else { webProxyEndpoint = "http://" + webProxyEndpoint @@ -274,18 +227,22 @@ func (router *Router) AddVirtualDirectoryProxyService(rootname string, domain st return err } - proxy := dpcore.NewDynamicProxyCore(path, rootname) + proxy := dpcore.NewDynamicProxyCore(path, options.RootName, options.SkipCertValidations) endpointObject := ProxyEndpoint{ - Root: rootname, - Domain: domain, - RequireTLS: requireTLS, - Proxy: proxy, + ProxyType: ProxyType_Vdir, + RootOrMatchingDomain: options.RootName, + Domain: domain, + RequireTLS: options.RequireTLS, + SkipCertValidations: options.SkipCertValidations, + RequireBasicAuth: options.RequireBasicAuth, + BasicAuthCredentials: options.BasicAuthCredentials, + Proxy: proxy, } - router.ProxyEndpoints.Store(rootname, &endpointObject) + router.ProxyEndpoints.Store(options.RootName, &endpointObject) - log.Println("Adding Proxy Rule: ", rootname+" to "+domain) + log.Println("Adding Proxy Rule: ", options.RootName+" to "+domain) return nil } @@ -307,13 +264,14 @@ func (router *Router) RemoveProxy(ptype string, key string) error { /* Add an default router for the proxy server */ -func (router *Router) SetRootProxy(proxyLocation string, requireTLS bool) error { +func (router *Router) SetRootProxy(options *RootOptions) error { + proxyLocation := options.ProxyLocation if proxyLocation[len(proxyLocation)-1:] == "/" { proxyLocation = proxyLocation[:len(proxyLocation)-1] } webProxyEndpoint := proxyLocation - if requireTLS { + if options.RequireTLS { webProxyEndpoint = "https://" + webProxyEndpoint } else { webProxyEndpoint = "http://" + webProxyEndpoint @@ -324,13 +282,17 @@ func (router *Router) SetRootProxy(proxyLocation string, requireTLS bool) error return err } - proxy := dpcore.NewDynamicProxyCore(path, "") + proxy := dpcore.NewDynamicProxyCore(path, "", options.SkipCertValidations) rootEndpoint := ProxyEndpoint{ - Root: "/", - Domain: proxyLocation, - RequireTLS: requireTLS, - Proxy: proxy, + ProxyType: ProxyType_Vdir, + RootOrMatchingDomain: "/", + Domain: proxyLocation, + RequireTLS: options.RequireTLS, + SkipCertValidations: options.SkipCertValidations, + RequireBasicAuth: options.RequireBasicAuth, + BasicAuthCredentials: options.BasicAuthCredentials, + Proxy: proxy, } router.Root = &rootEndpoint @@ -338,14 +300,14 @@ func (router *Router) SetRootProxy(proxyLocation string, requireTLS bool) error } // Helpers to export the syncmap for easier processing -func (r *Router) GetSDProxyEndpointsAsMap() map[string]*SubdomainEndpoint { - m := make(map[string]*SubdomainEndpoint) +func (r *Router) GetSDProxyEndpointsAsMap() map[string]*ProxyEndpoint { + m := make(map[string]*ProxyEndpoint) r.SubdomainEndpoint.Range(func(key, value interface{}) bool { k, ok := key.(string) if !ok { return true } - v, ok := value.(*SubdomainEndpoint) + v, ok := value.(*ProxyEndpoint) if !ok { return true } diff --git a/src/mod/dynamicproxy/proxyRequestHandler.go b/src/mod/dynamicproxy/proxyRequestHandler.go index 187e3b0..fb2a969 100644 --- a/src/mod/dynamicproxy/proxyRequestHandler.go +++ b/src/mod/dynamicproxy/proxyRequestHandler.go @@ -28,23 +28,34 @@ func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *P return targetProxyEndpoint } -func (router *Router) getSubdomainProxyEndpointFromHostname(hostname string) *SubdomainEndpoint { - var targetSubdomainEndpoint *SubdomainEndpoint = nil +func (router *Router) getSubdomainProxyEndpointFromHostname(hostname string) *ProxyEndpoint { + var targetSubdomainEndpoint *ProxyEndpoint = nil ep, ok := router.SubdomainEndpoint.Load(hostname) if ok { - targetSubdomainEndpoint = ep.(*SubdomainEndpoint) + targetSubdomainEndpoint = ep.(*ProxyEndpoint) } return targetSubdomainEndpoint } +// Clearn URL Path (without the http:// part) replaces // in a URL to / +func (router *Router) clearnURL(targetUrlOPath string) string { + return strings.ReplaceAll(targetUrlOPath, "//", "/") +} + +// Rewrite URL rewrite the prefix part of a virtual directory URL with / func (router *Router) rewriteURL(rooturl string, requestURL string) string { rewrittenURL := requestURL rewrittenURL = strings.TrimPrefix(rewrittenURL, strings.TrimSuffix(rooturl, "/")) + + if strings.Contains(rewrittenURL, "//") { + rewrittenURL = router.clearnURL(rewrittenURL) + } return rewrittenURL } -func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request, target *SubdomainEndpoint) { +// Handle subdomain request +func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) { r.Header.Set("X-Forwarded-Host", r.Host) requestURL := r.URL.String() if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" { @@ -69,8 +80,20 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request, return } - r.Host = r.URL.Host - err := target.Proxy.ServeHTTP(w, r) + originalHostHeader := r.Host + if r.URL != nil { + r.Host = r.URL.Host + } else { + //Fallback when the upstream proxy screw something up in the header + r.URL, _ = url.Parse(originalHostHeader) + } + + err := target.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ + ProxyDomain: target.Domain, + OriginalHost: originalHostHeader, + UseTLS: target.RequireTLS, + PathPrefix: "", + }) var dnsError *net.DNSError if err != nil { if errors.As(err, &dnsError) { @@ -87,8 +110,9 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request, h.logRequest(r, true, 200, "subdomain-http", target.Domain) } +// Handle vdir type request func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) { - rewriteURL := h.Parent.rewriteURL(target.Root, r.RequestURI) + rewriteURL := h.Parent.rewriteURL(target.RootOrMatchingDomain, r.RequestURI) r.URL, _ = url.Parse(rewriteURL) r.Header.Set("X-Forwarded-Host", r.Host) @@ -110,12 +134,18 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ } originalHostHeader := r.Host - r.Host = r.URL.Host + if r.URL != nil { + r.Host = r.URL.Host + } else { + //Fallback when the upstream proxy screw something up in the header + r.URL, _ = url.Parse(originalHostHeader) + } + err := target.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ ProxyDomain: target.Domain, OriginalHost: originalHostHeader, UseTLS: target.RequireTLS, - PathPrefix: target.Root, + PathPrefix: target.RootOrMatchingDomain, }) var dnsError *net.DNSError diff --git a/src/mod/dynamicproxy/subdomain.go b/src/mod/dynamicproxy/subdomain.go index ef1873b..4be9c36 100644 --- a/src/mod/dynamicproxy/subdomain.go +++ b/src/mod/dynamicproxy/subdomain.go @@ -4,7 +4,7 @@ import ( "log" "net/url" - "imuslab.com/zoraxy/mod/reverseproxy" + "imuslab.com/zoraxy/mod/dynamicproxy/dpcore" ) /* @@ -12,13 +12,14 @@ import ( */ -func (router *Router) AddSubdomainRoutingService(hostnameWithSubdomain string, domain string, requireTLS bool) error { +func (router *Router) AddSubdomainRoutingService(options *SubdOptions) error { + domain := options.Domain if domain[len(domain)-1:] == "/" { domain = domain[:len(domain)-1] } webProxyEndpoint := domain - if requireTLS { + if options.RequireTLS { webProxyEndpoint = "https://" + webProxyEndpoint } else { webProxyEndpoint = "http://" + webProxyEndpoint @@ -30,15 +31,18 @@ func (router *Router) AddSubdomainRoutingService(hostnameWithSubdomain string, d return err } - proxy := reverseproxy.NewReverseProxy(path) + proxy := dpcore.NewDynamicProxyCore(path, "", options.SkipCertValidations) - router.SubdomainEndpoint.Store(hostnameWithSubdomain, &SubdomainEndpoint{ - MatchingDomain: hostnameWithSubdomain, - Domain: domain, - RequireTLS: requireTLS, - Proxy: proxy, + router.SubdomainEndpoint.Store(options.MatchingDomain, &ProxyEndpoint{ + RootOrMatchingDomain: options.MatchingDomain, + Domain: domain, + RequireTLS: options.RequireTLS, + Proxy: proxy, + SkipCertValidations: options.SkipCertValidations, + RequireBasicAuth: options.RequireBasicAuth, + BasicAuthCredentials: options.BasicAuthCredentials, }) - log.Println("Adding Subdomain Rule: ", hostnameWithSubdomain+" to "+domain) + log.Println("Adding Subdomain Rule: ", options.MatchingDomain+" to "+domain) return nil } diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go new file mode 100644 index 0000000..cb7c8dc --- /dev/null +++ b/src/mod/dynamicproxy/typedef.go @@ -0,0 +1,112 @@ +package dynamicproxy + +import ( + "net" + "net/http" + "sync" + + "imuslab.com/zoraxy/mod/dynamicproxy/dpcore" + "imuslab.com/zoraxy/mod/dynamicproxy/redirection" + "imuslab.com/zoraxy/mod/geodb" + "imuslab.com/zoraxy/mod/statistic" + "imuslab.com/zoraxy/mod/tlscert" +) + +const ( + ProxyType_Subdomain = 0 + ProxyType_Vdir = 1 +) + +type ProxyHandler struct { + Parent *Router +} + +type RouterOption struct { + Port int + UseTls bool + ForceHttpsRedirect bool + TlsManager *tlscert.Manager + RedirectRuleTable *redirection.RuleTable + GeodbStore *geodb.Store + StatisticCollector *statistic.Collector +} + +type Router struct { + Option *RouterOption + ProxyEndpoints *sync.Map + SubdomainEndpoint *sync.Map + Running bool + Root *ProxyEndpoint + mux http.Handler + server *http.Server + tlsListener net.Listener + routingRules []*RoutingRule + + tlsRedirectStop chan bool +} + +// Auth credential for basic auth on certain endpoints +type BasicAuthCredentials struct { + Username string + 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 + RootOrMatchingDomain string //Root for vdir or Matching domain for subd + Domain string //Domain or IP to proxy to + RequireTLS bool //Target domain require TLS + SkipCertValidations bool //Set to true to accept self signed certs + RequireBasicAuth bool //Set to true to request basic auth before proxy + BasicAuthCredentials []*BasicAuthCredentials `json:"-"` + Proxy *dpcore.ReverseProxy `json:"-"` +} + +type RootOptions struct { + ProxyLocation string + RequireTLS bool + SkipCertValidations bool + RequireBasicAuth bool + BasicAuthCredentials []*BasicAuthCredentials +} + +type VdirOptions struct { + RootName string + Domain string + RequireTLS bool + SkipCertValidations bool + RequireBasicAuth bool + BasicAuthCredentials []*BasicAuthCredentials +} + +type SubdOptions struct { + MatchingDomain string + Domain string + RequireTLS bool + SkipCertValidations bool + RequireBasicAuth bool + BasicAuthCredentials []*BasicAuthCredentials +} + +/* +type ProxyEndpoint struct { + Root string + Domain string + RequireTLS bool + Proxy *reverseproxy.ReverseProxy `json:"-"` +} + +type SubdomainEndpoint struct { + MatchingDomain string + Domain string + RequireTLS bool + Proxy *reverseproxy.ReverseProxy `json:"-"` +} +*/ diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 9863de5..d1792b9 100644 --- a/src/reverseproxy.go +++ b/src/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" @@ -71,24 +72,33 @@ func ReverseProxtInit() { } if record.ProxyType == "root" { - dynamicProxyRouter.SetRootProxy(record.ProxyTarget, record.UseTLS) + dynamicProxyRouter.SetRootProxy(&dynamicproxy.RootOptions{ + ProxyLocation: record.ProxyTarget, + RequireTLS: record.UseTLS, + }) } else if record.ProxyType == "subd" { - dynamicProxyRouter.AddSubdomainRoutingService(record.Rootname, record.ProxyTarget, record.UseTLS) + dynamicProxyRouter.AddSubdomainRoutingService(&dynamicproxy.SubdOptions{ + 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(record.Rootname, record.ProxyTarget, record.UseTLS) + dynamicProxyRouter.AddVirtualDirectoryProxyService(&dynamicproxy.VdirOptions{ + 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 @@ -111,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() @@ -157,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") @@ -170,7 +222,16 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { vdir = "/" + vdir } rootname = vdir - dynamicProxyRouter.AddVirtualDirectoryProxyService(vdir, endpoint, useTLS) + + thisOption := dynamicproxy.VdirOptions{ + RootName: vdir, + Domain: endpoint, + RequireTLS: useTLS, + SkipCertValidations: skipTlsValidation, + RequireBasicAuth: requireBasicAuth, + BasicAuthCredentials: basicAuthCredentials, + } + dynamicProxyRouter.AddVirtualDirectoryProxyService(&thisOption) } else if eptype == "subd" { subdomain, err := utils.PostPara(r, "rootname") @@ -179,10 +240,22 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { return } rootname = subdomain - dynamicProxyRouter.AddSubdomainRoutingService(subdomain, endpoint, useTLS) + thisOption := dynamicproxy.SubdOptions{ + MatchingDomain: subdomain, + Domain: endpoint, + RequireTLS: useTLS, + SkipCertValidations: skipTlsValidation, + RequireBasicAuth: requireBasicAuth, + BasicAuthCredentials: basicAuthCredentials, + } + dynamicProxyRouter.AddSubdomainRoutingService(&thisOption) } else if eptype == "root" { rootname = "root" - dynamicProxyRouter.SetRootProxy(endpoint, useTLS) + thisOption := dynamicproxy.RootOptions{ + ProxyLocation: endpoint, + RequireTLS: useTLS, + } + dynamicProxyRouter.SetRootProxy(&thisOption) } else { //Invalid eptype utils.SendErrorResponse(w, "Invalid endpoint type") @@ -190,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 { @@ -255,14 +337,14 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) { js, _ := json.Marshal(results) utils.SendJSONResponse(w, string(js)) } else if eptype == "subd" { - results := []*dynamicproxy.SubdomainEndpoint{} + results := []*dynamicproxy.ProxyEndpoint{} dynamicProxyRouter.SubdomainEndpoint.Range(func(key, value interface{}) bool { - results = append(results, value.(*dynamicproxy.SubdomainEndpoint)) + results = append(results, value.(*dynamicproxy.ProxyEndpoint)) return true }) sort.Slice(results, func(i, j int) bool { - return results[i].MatchingDomain < results[j].MatchingDomain + return results[i].RootOrMatchingDomain < results[j].RootOrMatchingDomain }) js, _ := json.Marshal(results) diff --git a/src/start.go b/src/start.go index 5b3e3d4..e46d572 100644 --- a/src/start.go +++ b/src/start.go @@ -86,10 +86,6 @@ func startupSequence() { panic(err) } - if err != nil { - panic(err) - } - //Create a netstat buffer netstatBuffers, err = netstat.NewNetStatBuffer(300) if err != nil { diff --git a/src/web/components/cert.html b/src/web/components/cert.html index 96d0175..d2de227 100644 --- a/src/web/components/cert.html +++ b/src/web/components/cert.html @@ -58,6 +58,7 @@

Sub-domain Certificates

Domain Last Update + Expire At Remove @@ -108,6 +109,7 @@

Sub-domain Certificates

$("#certifiedDomainList").append(` ${entry.Domain} ${entry.LastModifiedDate} + ${entry.ExpireDate} `); }) diff --git a/src/web/components/rules.html b/src/web/components/rules.html index 8e52529..298f1db 100644 --- a/src/web/components/rules.html +++ b/src/web/components/rules.html @@ -20,7 +20,6 @@

New Proxy Rule

-
@@ -33,6 +32,58 @@

New Proxy Rule

+ +
+
+
+ + Advance Settings +
+
+

+
+
+ + +
+
+
+
+ + +
+
+
+

Enter the username and password for allowing them to access this proxy endpoint

+ + + + + + + + + + + + +
UsernamePasswordRemove
No Entered Credential
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+


@@ -63,12 +114,16 @@

New Proxy Rule