Enter the username and password for allowing them to access this proxy endpoint
+Username | +Password | +Remove | +
---|---|---|
No Entered Credential | +
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 @@
Domain
Last Update
+ Expire At
Remove
Enter the username and password for allowing them to access this proxy endpoint
+Username | +Password | +Remove | +
---|---|---|
No Entered Credential | +