mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-01 21:27:20 +02:00
Updates v2.6 experimental build
+ Basic auth + Support TLS verification skip (for self signed certs) + Added trend analysis + Added referer and file type analysis + Added cert expire day display + Moved subdomain proxy logic to dpcore
This commit is contained in:
parent
edd19e2b30
commit
5952a1b55f
3
.gitignore
vendored
3
.gitignore
vendored
@ -27,4 +27,5 @@ src/conf/*
|
||||
src/ReverseProxy_*_*
|
||||
src/Zoraxy_*_*
|
||||
src/certs/*
|
||||
src/rules/*
|
||||
src/rules/*
|
||||
src/README.md
|
||||
|
24
src/cert.go
24
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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
47
src/mod/dynamicproxy/basicAuth.go
Normal file
47
src/mod/dynamicproxy/basicAuth.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
112
src/mod/dynamicproxy/typedef.go
Normal file
112
src/mod/dynamicproxy/typedef.go
Normal file
@ -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:"-"`
|
||||
}
|
||||
*/
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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>`);
|
||||
})
|
||||
|
@ -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>
|
||||
@ -33,6 +32,58 @@
|
||||
<label>Proxy Target require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Advance configs -->
|
||||
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
||||
<div id="advanceProxyRules" class="ui fluid accordion">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
Advance Settings
|
||||
</div>
|
||||
<div class="content">
|
||||
<p></p>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="skipTLSValidation">
|
||||
<label>Ignore TLS/SSL Verification Error<br><small>E.g. self-signed, expired certificate (Not Recommended)</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="requireBasicAuth">
|
||||
<label>Require Basic Auth<br><small>Require client to login in order to view the page</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="basicAuthCredentials" class="field">
|
||||
<p>Enter the username and password for allowing them to access this proxy endpoint</p>
|
||||
<table class="ui very basic celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Password</th>
|
||||
<th>Remove</th>
|
||||
</tr></thead>
|
||||
<tbody id="basicAuthCredentialTable">
|
||||
<tr>
|
||||
<td colspan="3"><i class="ui green circle check icon"></i> No Entered Credential</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="three small fields credentialEntry">
|
||||
<div class="field">
|
||||
<input id="basicAuthCredUsername" type="text" placeholder="Username" autocomplete="off">
|
||||
</div>
|
||||
<div class="field">
|
||||
<input id="basicAuthCredPassword" type="password" placeholder="Password" autocomplete="off">
|
||||
</div>
|
||||
<div class="field">
|
||||
<button class="ui basic button" onclick="addCredentials();"><i class="blue add icon"></i> Add Credential</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<button class="ui basic button" onclick="newProxyEndpoint();"><i class="blue add icon"></i> Create Endpoint</button>
|
||||
<br><br>
|
||||
</div>
|
||||
@ -63,12 +114,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$("#advanceProxyRules").accordion();
|
||||
|
||||
//New Proxy Endpoint
|
||||
function newProxyEndpoint(){
|
||||
var type = $("#ptype").val();
|
||||
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("/")) {
|
||||
@ -101,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);
|
||||
@ -114,6 +177,8 @@
|
||||
//Clear old data
|
||||
$("#rootname").val("");
|
||||
$("#proxyDomain").val("");
|
||||
credentials = [];
|
||||
updateTable();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -152,6 +217,83 @@
|
||||
}
|
||||
|
||||
|
||||
function toggleBasicAuth() {
|
||||
var basicAuthDiv = document.getElementById('basicAuthOnly');
|
||||
if ($("#requireBasicAuth").parent().checkbox("is checked")) {
|
||||
$("#basicAuthCredentials").removeClass("disabled");
|
||||
} else {
|
||||
$("#basicAuthCredentials").addClass("disabled");
|
||||
}
|
||||
}
|
||||
$("#requireBasicAuth").on('change', toggleBasicAuth);
|
||||
toggleBasicAuth();
|
||||
|
||||
|
||||
/*
|
||||
Credential Managements
|
||||
|
||||
*/
|
||||
let credentials = []; // Global variable to store credentials
|
||||
|
||||
function addCredentials() {
|
||||
// Retrieve the username and password input values
|
||||
var username = $('#basicAuthCredUsername').val();
|
||||
var password = $('#basicAuthCredPassword').val();
|
||||
|
||||
if(username == "" || password == ""){
|
||||
msgbox("Username or password cannot be empty", false, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new credential object
|
||||
var credential = {
|
||||
username: username,
|
||||
password: password
|
||||
};
|
||||
|
||||
// Add the credential to the global credentials array
|
||||
credentials.push(credential);
|
||||
|
||||
// Clear the input fields
|
||||
$('#basicAuthCredUsername').val('');
|
||||
$('#basicAuthCredPassword').val('');
|
||||
|
||||
// Update the table body with the credentials
|
||||
updateTable();
|
||||
}
|
||||
|
||||
function updateTable() {
|
||||
var tableBody = $('#basicAuthCredentialTable');
|
||||
tableBody.empty();
|
||||
|
||||
if (credentials.length === 0) {
|
||||
tableBody.append('<tr><td colspan="3"><i class="ui green circle check icon"></i> No Entered Credential</td></tr>');
|
||||
} else {
|
||||
for (var i = 0; i < credentials.length; i++) {
|
||||
var credential = credentials[i];
|
||||
var username = credential.username;
|
||||
var password = credential.password.replace(/./g, '*'); // Replace each character with '*'
|
||||
|
||||
var row = '<tr>' +
|
||||
'<td>' + username + '</td>' +
|
||||
'<td>' + password + '</td>' +
|
||||
'<td><button class="ui basic button" onclick="removeCredential(' + i + ');"><i class="red remove icon"></i> Remove</button></td>' +
|
||||
'</tr>';
|
||||
|
||||
tableBody.append(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeCredential(index) {
|
||||
// Remove the credential from the credentials array
|
||||
credentials.splice(index, 1);
|
||||
|
||||
// Update the table body
|
||||
updateTable();
|
||||
}
|
||||
|
||||
|
||||
//Check if a string is a valid subdomain
|
||||
function isSubdomainDomain(str) {
|
||||
const regex = /^(localhost|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}\.)$/i;
|
||||
|
@ -157,6 +157,42 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui stackable grid">
|
||||
<div class="eight wide column">
|
||||
<h3>Request File Types</h3>
|
||||
<p>The file types being served by this proxy</p>
|
||||
<div>
|
||||
<canvas id="stats_filetype"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<h3>Referring Sites</h3>
|
||||
<p>The Top 100 sources of traffic according to referer header</p>
|
||||
<div>
|
||||
<div style="height: 500px; overflow-y: auto;">
|
||||
<table class="ui unstackable striped celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="no-sort">Referer</th>
|
||||
<th class="no-sort">Requests</th>
|
||||
</tr></thead>
|
||||
<tbody id="stats_RefererTable">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment" id="trendGraphs">
|
||||
<h3>Visitor Trend Analysis</h3>
|
||||
<p>Request trends in the selected time range</p>
|
||||
<div>
|
||||
<canvas id="requestTrends"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <button class="ui icon right floated basic button" onclick="initStatisticSummery();"><i class="green refresh icon"></i> Refresh</button> -->
|
||||
<br><br>
|
||||
@ -177,7 +213,6 @@
|
||||
//Two dates are given and they are not identical
|
||||
loadStatisticByRange(startdate, enddate);
|
||||
console.log(startdate, enddate);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,6 +254,15 @@
|
||||
|
||||
//Render user agent analysis
|
||||
renderUserAgentCharts(data.UserAgent);
|
||||
|
||||
//Render file type by analysising request URL paths
|
||||
renderFileTypeGraph(data.RequestURL);
|
||||
|
||||
//Render Referer header
|
||||
renderRefererTable(data.Referer);
|
||||
|
||||
//Hide the trend graphs
|
||||
$("#trendGraphs").hide();
|
||||
});
|
||||
}
|
||||
initStatisticSummery();
|
||||
@ -259,7 +303,16 @@
|
||||
|
||||
//Render user agent analysis
|
||||
renderUserAgentCharts(data.Summary.UserAgent);
|
||||
|
||||
//Render file type by analysising request URL paths
|
||||
renderFileTypeGraph(data.Summary.RequestURL);
|
||||
|
||||
//Render Referer header
|
||||
renderRefererTable(data.Summary.Referer);
|
||||
|
||||
//Render the trend graph
|
||||
$("#trendGraphs").show();
|
||||
renderTrendGraph(data.Records);
|
||||
});
|
||||
}
|
||||
|
||||
@ -313,6 +366,155 @@
|
||||
$("#statsRangeEnd").val("");
|
||||
}
|
||||
|
||||
function renderRefererTable(refererList){
|
||||
const sortedEntries = Object.entries(refererList).sort(([, valueA], [, valueB]) => valueB - valueA);
|
||||
console.log(sortedEntries);
|
||||
$("#stats_RefererTable").html("");
|
||||
let endStop = 100;
|
||||
if (sortedEntries.length < 100){
|
||||
endStop = sortedEntries.length;
|
||||
}
|
||||
for (var i = 0; i < endStop; i++) {
|
||||
let referer = (decodeURIComponent(sortedEntries[i][0])).replace(/(<([^>]+)>)/ig,"");
|
||||
if (sortedEntries[i][0] == ""){
|
||||
//Root
|
||||
referer = `<span style="color: #b5b5b5;">(<i class="eye slash outline icon"></i> Unknown or Hidden)</span>`;
|
||||
}
|
||||
$("#stats_RefererTable").append(`<tr>
|
||||
<td>${referer}</td>
|
||||
<td>${sortedEntries[i][1]}</td>
|
||||
</tr>`);
|
||||
}
|
||||
}
|
||||
|
||||
function renderFileTypeGraph(requestURLs){
|
||||
//Create the device chart
|
||||
let fileExtensions = {};
|
||||
for (const [url, count] of Object.entries(requestURLs)) {
|
||||
let filename = url.split("/").pop();
|
||||
let ext = "";
|
||||
if (filename == ""){
|
||||
//Loading from a folder
|
||||
ext = "Folder path"
|
||||
}else{
|
||||
if (filename.includes(".")){
|
||||
ext = filename.split(".").pop();
|
||||
}else{
|
||||
ext = "API call"
|
||||
}
|
||||
}
|
||||
|
||||
if (fileExtensions[ext] != undefined){
|
||||
fileExtensions[ext] = fileExtensions[ext] + count;
|
||||
}else{
|
||||
//First time this ext show up
|
||||
fileExtensions[ext] = count;
|
||||
}
|
||||
}
|
||||
|
||||
//Convert the key-value pairs to array for graph render
|
||||
let fileTypes = [];
|
||||
let fileCounts = [];
|
||||
let colors = [];
|
||||
for (const [ftype, count] of Object.entries(fileExtensions)) {
|
||||
fileTypes.push(ftype);
|
||||
fileCounts.push(count);
|
||||
colors.push(generateColorFromHash(ftype));
|
||||
}
|
||||
|
||||
let filetypeChart = new Chart(document.getElementById("stats_filetype"), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: fileTypes,
|
||||
datasets: [{
|
||||
data: fileCounts,
|
||||
backgroundColor: colors,
|
||||
hoverBackgroundColor: colors,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
}
|
||||
});
|
||||
|
||||
statisticCharts.push(filetypeChart);
|
||||
}
|
||||
|
||||
function renderTrendGraph(dailySummary){
|
||||
// Get the canvas element
|
||||
const canvas = document.getElementById('requestTrends');
|
||||
|
||||
//Generate the X axis labels
|
||||
let datesLabel = [];
|
||||
let succData = [];
|
||||
let errorData = [];
|
||||
let totalData = [];
|
||||
for (var i = 0; i < dailySummary.length; i++){
|
||||
let thisDayData = dailySummary[i];
|
||||
datesLabel.push("Day " + i);
|
||||
succData.push(thisDayData.ValidRequest);
|
||||
errorData.push(thisDayData.ErrorRequest);
|
||||
totalData.push(thisDayData.TotalRequest);
|
||||
}
|
||||
// Create the chart
|
||||
let TrendChart = new Chart(canvas, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: datesLabel,
|
||||
datasets: [
|
||||
{
|
||||
label: 'All Requests',
|
||||
data: totalData,
|
||||
borderColor: '#7d99f7',
|
||||
backgroundColor: 'rgba(125, 153, 247, 0.4)',
|
||||
fill: false
|
||||
},
|
||||
{
|
||||
label: 'Success Requests',
|
||||
data: succData,
|
||||
borderColor: '#6dad7c',
|
||||
backgroundColor: "rgba(109, 173, 124, 0.4)",
|
||||
fill: true
|
||||
},
|
||||
{
|
||||
label: 'Error Requests',
|
||||
data: errorData,
|
||||
borderColor: '#de7373',
|
||||
backgroundColor: "rgba(222, 115, 115, 0.4)",
|
||||
fill: true
|
||||
},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
title: {
|
||||
display: true,
|
||||
//text: 'Line Chart Example'
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
title: {
|
||||
display: false,
|
||||
text: 'Time'
|
||||
}
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Request Counts'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
statisticCharts.push(TrendChart);
|
||||
}
|
||||
|
||||
function renderUserAgentCharts(userAgentsEntries){
|
||||
let userAgents = Object.keys(userAgentsEntries);
|
||||
let requestCounts = Object.values(userAgentsEntries);
|
||||
|
@ -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.MatchingDomain}</td>
|
||||
<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>`);
|
||||
});
|
||||
}
|
||||
|
@ -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.Root}</td>
|
||||
<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>`);
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user