mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 14:17: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/ReverseProxy_*_*
|
||||||
src/Zoraxy_*_*
|
src/Zoraxy_*_*
|
||||||
src/certs/*
|
src/certs/*
|
||||||
src/rules/*
|
src/rules/*
|
||||||
|
src/README.md
|
||||||
|
24
src/cert.go
24
src/cert.go
@ -1,7 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@ -41,21 +43,41 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
|
|||||||
type CertInfo struct {
|
type CertInfo struct {
|
||||||
Domain string
|
Domain string
|
||||||
LastModifiedDate string
|
LastModifiedDate string
|
||||||
|
ExpireDate string
|
||||||
}
|
}
|
||||||
|
|
||||||
results := []*CertInfo{}
|
results := []*CertInfo{}
|
||||||
|
|
||||||
for _, filename := range filenames {
|
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 {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "invalid domain certificate discovered: "+filename)
|
utils.SendErrorResponse(w, "invalid domain certificate discovered: "+filename)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
modifiedTime := fileInfo.ModTime().Format("2006-01-02 15:04:05")
|
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{
|
thisCertInfo := CertInfo{
|
||||||
Domain: filename,
|
Domain: filename,
|
||||||
LastModifiedDate: modifiedTime,
|
LastModifiedDate: modifiedTime,
|
||||||
|
ExpireDate: certExpireTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, &thisCertInfo)
|
results = append(results, &thisCertInfo)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,23 +20,22 @@ import (
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
type Record struct {
|
type Record struct {
|
||||||
ProxyType string
|
ProxyType string
|
||||||
Rootname string
|
Rootname string
|
||||||
ProxyTarget string
|
ProxyTarget string
|
||||||
UseTLS bool
|
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)
|
os.MkdirAll("conf", 0775)
|
||||||
filename := getFilenameFromRootName(rootname)
|
filename := getFilenameFromRootName(proxyConfigRecord.Rootname)
|
||||||
|
|
||||||
//Generate record
|
//Generate record
|
||||||
thisRecord := Record{
|
thisRecord := proxyConfigRecord
|
||||||
ProxyType: ptype,
|
|
||||||
Rootname: rootname,
|
|
||||||
ProxyTarget: proxyTarget,
|
|
||||||
UseTLS: useTLS,
|
|
||||||
}
|
|
||||||
|
|
||||||
//Write to file
|
//Write to file
|
||||||
js, _ := json.MarshalIndent(thisRecord, "", " ")
|
js, _ := json.MarshalIndent(thisRecord, "", " ")
|
||||||
@ -67,7 +67,6 @@ func LoadReverseProxyConfig(filename string) (*Record, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Unmarshal the content into config
|
//Unmarshal the content into config
|
||||||
|
|
||||||
err = json.Unmarshal(configContent, &thisRecord)
|
err = json.Unmarshal(configContent, &thisRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &thisRecord, err
|
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 ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||||
var (
|
var (
|
||||||
name = "Zoraxy"
|
name = "Zoraxy"
|
||||||
version = "2.5"
|
version = "2.6"
|
||||||
nodeUUID = "generic"
|
nodeUUID = "generic"
|
||||||
development = false //Set this to false to use embedded web fs
|
development = false //Set this to false to use embedded web fs
|
||||||
|
|
||||||
|
@ -12,9 +12,19 @@ import (
|
|||||||
Server.go
|
Server.go
|
||||||
|
|
||||||
Main server for dynamic proxy core
|
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) {
|
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
/*
|
||||||
|
General Access Check
|
||||||
|
*/
|
||||||
//Check if this ip is in blacklist
|
//Check if this ip is in blacklist
|
||||||
clientIpAddr := geodb.GetRequesterIP(r)
|
clientIpAddr := geodb.GetRequesterIP(r)
|
||||||
if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
|
if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
|
||||||
@ -30,6 +40,9 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Redirection Routing
|
||||||
|
*/
|
||||||
//Check if this is a redirection url
|
//Check if this is a redirection url
|
||||||
if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
|
if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
|
||||||
statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, 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]
|
domainOnly = hostPath[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Subdomain Routing
|
||||||
|
*/
|
||||||
if strings.Contains(r.Host, ".") {
|
if strings.Contains(r.Host, ".") {
|
||||||
//This might be a subdomain. See if there are any subdomain proxy router for this
|
//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)
|
sep := h.Parent.getSubdomainProxyEndpointFromHostname(domainOnly)
|
||||||
if sep != nil {
|
if sep != nil {
|
||||||
|
if sep.RequireBasicAuth {
|
||||||
|
err := h.handleBasicAuthRouting(w, r, sep)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
h.subdomainRequest(w, r, sep)
|
h.subdomainRequest(w, r, sep)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Virtual Directory Routing
|
||||||
|
*/
|
||||||
//Clean up the request URI
|
//Clean up the request URI
|
||||||
proxyingPath := strings.TrimSpace(r.RequestURI)
|
proxyingPath := strings.TrimSpace(r.RequestURI)
|
||||||
targetProxyEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath)
|
targetProxyEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath)
|
||||||
if targetProxyEndpoint != nil {
|
if targetProxyEndpoint != nil {
|
||||||
|
if targetProxyEndpoint.RequireBasicAuth {
|
||||||
|
err := h.handleBasicAuthRouting(w, r, targetProxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
h.proxyRequest(w, r, targetProxyEndpoint)
|
h.proxyRequest(w, r, targetProxyEndpoint)
|
||||||
} else if !strings.HasSuffix(proxyingPath, "/") {
|
} else if !strings.HasSuffix(proxyingPath, "/") {
|
||||||
potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
|
potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
|
||||||
@ -75,11 +104,12 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if potentialProxtEndpoint != nil {
|
if potentialProxtEndpoint != nil {
|
||||||
//Missing tailing slash. Redirect to target proxy endpoint
|
//Missing tailing slash. Redirect to target proxy endpoint
|
||||||
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
||||||
//h.proxyRequest(w, r, potentialProxtEndpoint)
|
|
||||||
} else {
|
} else {
|
||||||
|
//Passthrough the request to root
|
||||||
h.proxyRequest(w, r, h.Parent.Root)
|
h.proxyRequest(w, r, h.Parent.Root)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//No routing rules found. Route to root.
|
||||||
h.proxyRequest(w, r, h.Parent.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)
|
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
|
targetQuery := target.RawQuery
|
||||||
director := func(req *http.Request) {
|
director := func(req *http.Request) {
|
||||||
req.URL.Scheme = target.Scheme
|
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).MaxIdleConnsPerHost = 3000
|
||||||
thisTransporter.(*http.Transport).IdleConnTimeout = 10 * time.Second
|
thisTransporter.(*http.Transport).IdleConnTimeout = 10 * time.Second
|
||||||
thisTransporter.(*http.Transport).MaxConnsPerHost = 0
|
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{
|
return &ReverseProxy{
|
||||||
Director: director,
|
Director: director,
|
||||||
@ -278,9 +283,6 @@ func addXForwardedForHeader(req *http.Request) {
|
|||||||
|
|
||||||
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error {
|
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error {
|
||||||
transport := p.Transport
|
transport := p.Transport
|
||||||
if transport == nil {
|
|
||||||
transport = http.DefaultTransport
|
|
||||||
}
|
|
||||||
|
|
||||||
outreq := new(http.Request)
|
outreq := new(http.Request)
|
||||||
// Shallow copies of maps, like header
|
// Shallow copies of maps, like header
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -14,57 +13,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"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) {
|
func NewDynamicProxy(option RouterOption) (*Router, error) {
|
||||||
proxyMap := sync.Map{}
|
proxyMap := sync.Map{}
|
||||||
@ -250,8 +203,8 @@ func (router *Router) IsProxiedSubdomain(r *http.Request) bool {
|
|||||||
/*
|
/*
|
||||||
Add an URL into a custom proxy services
|
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:] == "/" {
|
if domain[len(domain)-1:] == "/" {
|
||||||
domain = domain[:len(domain)-1]
|
domain = domain[:len(domain)-1]
|
||||||
}
|
}
|
||||||
@ -263,7 +216,7 @@ func (router *Router) AddVirtualDirectoryProxyService(rootname string, domain st
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
webProxyEndpoint := domain
|
webProxyEndpoint := domain
|
||||||
if requireTLS {
|
if options.RequireTLS {
|
||||||
webProxyEndpoint = "https://" + webProxyEndpoint
|
webProxyEndpoint = "https://" + webProxyEndpoint
|
||||||
} else {
|
} else {
|
||||||
webProxyEndpoint = "http://" + webProxyEndpoint
|
webProxyEndpoint = "http://" + webProxyEndpoint
|
||||||
@ -274,18 +227,22 @@ func (router *Router) AddVirtualDirectoryProxyService(rootname string, domain st
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := dpcore.NewDynamicProxyCore(path, rootname)
|
proxy := dpcore.NewDynamicProxyCore(path, options.RootName, options.SkipCertValidations)
|
||||||
|
|
||||||
endpointObject := ProxyEndpoint{
|
endpointObject := ProxyEndpoint{
|
||||||
Root: rootname,
|
ProxyType: ProxyType_Vdir,
|
||||||
Domain: domain,
|
RootOrMatchingDomain: options.RootName,
|
||||||
RequireTLS: requireTLS,
|
Domain: domain,
|
||||||
Proxy: proxy,
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,13 +264,14 @@ func (router *Router) RemoveProxy(ptype string, key string) error {
|
|||||||
/*
|
/*
|
||||||
Add an default router for the proxy server
|
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:] == "/" {
|
if proxyLocation[len(proxyLocation)-1:] == "/" {
|
||||||
proxyLocation = proxyLocation[:len(proxyLocation)-1]
|
proxyLocation = proxyLocation[:len(proxyLocation)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
webProxyEndpoint := proxyLocation
|
webProxyEndpoint := proxyLocation
|
||||||
if requireTLS {
|
if options.RequireTLS {
|
||||||
webProxyEndpoint = "https://" + webProxyEndpoint
|
webProxyEndpoint = "https://" + webProxyEndpoint
|
||||||
} else {
|
} else {
|
||||||
webProxyEndpoint = "http://" + webProxyEndpoint
|
webProxyEndpoint = "http://" + webProxyEndpoint
|
||||||
@ -324,13 +282,17 @@ func (router *Router) SetRootProxy(proxyLocation string, requireTLS bool) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := dpcore.NewDynamicProxyCore(path, "")
|
proxy := dpcore.NewDynamicProxyCore(path, "", options.SkipCertValidations)
|
||||||
|
|
||||||
rootEndpoint := ProxyEndpoint{
|
rootEndpoint := ProxyEndpoint{
|
||||||
Root: "/",
|
ProxyType: ProxyType_Vdir,
|
||||||
Domain: proxyLocation,
|
RootOrMatchingDomain: "/",
|
||||||
RequireTLS: requireTLS,
|
Domain: proxyLocation,
|
||||||
Proxy: proxy,
|
RequireTLS: options.RequireTLS,
|
||||||
|
SkipCertValidations: options.SkipCertValidations,
|
||||||
|
RequireBasicAuth: options.RequireBasicAuth,
|
||||||
|
BasicAuthCredentials: options.BasicAuthCredentials,
|
||||||
|
Proxy: proxy,
|
||||||
}
|
}
|
||||||
|
|
||||||
router.Root = &rootEndpoint
|
router.Root = &rootEndpoint
|
||||||
@ -338,14 +300,14 @@ func (router *Router) SetRootProxy(proxyLocation string, requireTLS bool) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helpers to export the syncmap for easier processing
|
// Helpers to export the syncmap for easier processing
|
||||||
func (r *Router) GetSDProxyEndpointsAsMap() map[string]*SubdomainEndpoint {
|
func (r *Router) GetSDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
|
||||||
m := make(map[string]*SubdomainEndpoint)
|
m := make(map[string]*ProxyEndpoint)
|
||||||
r.SubdomainEndpoint.Range(func(key, value interface{}) bool {
|
r.SubdomainEndpoint.Range(func(key, value interface{}) bool {
|
||||||
k, ok := key.(string)
|
k, ok := key.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
v, ok := value.(*SubdomainEndpoint)
|
v, ok := value.(*ProxyEndpoint)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -28,23 +28,34 @@ func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *P
|
|||||||
return targetProxyEndpoint
|
return targetProxyEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (router *Router) getSubdomainProxyEndpointFromHostname(hostname string) *SubdomainEndpoint {
|
func (router *Router) getSubdomainProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
|
||||||
var targetSubdomainEndpoint *SubdomainEndpoint = nil
|
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
||||||
ep, ok := router.SubdomainEndpoint.Load(hostname)
|
ep, ok := router.SubdomainEndpoint.Load(hostname)
|
||||||
if ok {
|
if ok {
|
||||||
targetSubdomainEndpoint = ep.(*SubdomainEndpoint)
|
targetSubdomainEndpoint = ep.(*ProxyEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetSubdomainEndpoint
|
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 {
|
func (router *Router) rewriteURL(rooturl string, requestURL string) string {
|
||||||
rewrittenURL := requestURL
|
rewrittenURL := requestURL
|
||||||
rewrittenURL = strings.TrimPrefix(rewrittenURL, strings.TrimSuffix(rooturl, "/"))
|
rewrittenURL = strings.TrimPrefix(rewrittenURL, strings.TrimSuffix(rooturl, "/"))
|
||||||
|
|
||||||
|
if strings.Contains(rewrittenURL, "//") {
|
||||||
|
rewrittenURL = router.clearnURL(rewrittenURL)
|
||||||
|
}
|
||||||
return 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)
|
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||||
requestURL := r.URL.String()
|
requestURL := r.URL.String()
|
||||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Host = r.URL.Host
|
originalHostHeader := r.Host
|
||||||
err := target.Proxy.ServeHTTP(w, r)
|
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
|
var dnsError *net.DNSError
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.As(err, &dnsError) {
|
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)
|
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) {
|
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.URL, _ = url.Parse(rewriteURL)
|
||||||
|
|
||||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
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
|
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{
|
err := target.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: target.Domain,
|
ProxyDomain: target.Domain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: target.RequireTLS,
|
UseTLS: target.RequireTLS,
|
||||||
PathPrefix: target.Root,
|
PathPrefix: target.RootOrMatchingDomain,
|
||||||
})
|
})
|
||||||
|
|
||||||
var dnsError *net.DNSError
|
var dnsError *net.DNSError
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/url"
|
"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:] == "/" {
|
if domain[len(domain)-1:] == "/" {
|
||||||
domain = domain[:len(domain)-1]
|
domain = domain[:len(domain)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
webProxyEndpoint := domain
|
webProxyEndpoint := domain
|
||||||
if requireTLS {
|
if options.RequireTLS {
|
||||||
webProxyEndpoint = "https://" + webProxyEndpoint
|
webProxyEndpoint = "https://" + webProxyEndpoint
|
||||||
} else {
|
} else {
|
||||||
webProxyEndpoint = "http://" + webProxyEndpoint
|
webProxyEndpoint = "http://" + webProxyEndpoint
|
||||||
@ -30,15 +31,18 @@ func (router *Router) AddSubdomainRoutingService(hostnameWithSubdomain string, d
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := reverseproxy.NewReverseProxy(path)
|
proxy := dpcore.NewDynamicProxyCore(path, "", options.SkipCertValidations)
|
||||||
|
|
||||||
router.SubdomainEndpoint.Store(hostnameWithSubdomain, &SubdomainEndpoint{
|
router.SubdomainEndpoint.Store(options.MatchingDomain, &ProxyEndpoint{
|
||||||
MatchingDomain: hostnameWithSubdomain,
|
RootOrMatchingDomain: options.MatchingDomain,
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
RequireTLS: requireTLS,
|
RequireTLS: options.RequireTLS,
|
||||||
Proxy: proxy,
|
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
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
@ -71,24 +72,33 @@ func ReverseProxtInit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if record.ProxyType == "root" {
|
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" {
|
} 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" {
|
} 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 {
|
} else {
|
||||||
log.Println("Unsupported endpoint type: " + record.ProxyType + ". Skipping " + filepath.Base(conf))
|
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
|
//Start Service
|
||||||
//Not sure why but delay must be added if you have another
|
//Not sure why but delay must be added if you have another
|
||||||
//reverse proxy server in front of this service
|
//reverse proxy server in front of this service
|
||||||
@ -111,7 +121,6 @@ func ReverseProxtInit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReverseProxyHandleOnOff(w http.ResponseWriter, r *http.Request) {
|
func ReverseProxyHandleOnOff(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
enable, _ := utils.PostPara(r, "enable") //Support root, vdir and subd
|
enable, _ := utils.PostPara(r, "enable") //Support root, vdir and subd
|
||||||
if enable == "true" {
|
if enable == "true" {
|
||||||
err := dynamicProxyRouter.StartProxyService()
|
err := dynamicProxyRouter.StartProxyService()
|
||||||
@ -157,6 +166,49 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useTLS := (tls == "true")
|
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 := ""
|
rootname := ""
|
||||||
if eptype == "vdir" {
|
if eptype == "vdir" {
|
||||||
vdir, err := utils.PostPara(r, "rootname")
|
vdir, err := utils.PostPara(r, "rootname")
|
||||||
@ -170,7 +222,16 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
vdir = "/" + vdir
|
vdir = "/" + vdir
|
||||||
}
|
}
|
||||||
rootname = 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" {
|
} else if eptype == "subd" {
|
||||||
subdomain, err := utils.PostPara(r, "rootname")
|
subdomain, err := utils.PostPara(r, "rootname")
|
||||||
@ -179,10 +240,22 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
rootname = subdomain
|
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" {
|
} else if eptype == "root" {
|
||||||
rootname = "root"
|
rootname = "root"
|
||||||
dynamicProxyRouter.SetRootProxy(endpoint, useTLS)
|
thisOption := dynamicproxy.RootOptions{
|
||||||
|
ProxyLocation: endpoint,
|
||||||
|
RequireTLS: useTLS,
|
||||||
|
}
|
||||||
|
dynamicProxyRouter.SetRootProxy(&thisOption)
|
||||||
} else {
|
} else {
|
||||||
//Invalid eptype
|
//Invalid eptype
|
||||||
utils.SendErrorResponse(w, "Invalid endpoint type")
|
utils.SendErrorResponse(w, "Invalid endpoint type")
|
||||||
@ -190,7 +263,16 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Save it
|
//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
|
//Update utm if exists
|
||||||
if uptimeMonitor != nil {
|
if uptimeMonitor != nil {
|
||||||
@ -255,14 +337,14 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
|
|||||||
js, _ := json.Marshal(results)
|
js, _ := json.Marshal(results)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else if eptype == "subd" {
|
} else if eptype == "subd" {
|
||||||
results := []*dynamicproxy.SubdomainEndpoint{}
|
results := []*dynamicproxy.ProxyEndpoint{}
|
||||||
dynamicProxyRouter.SubdomainEndpoint.Range(func(key, value interface{}) bool {
|
dynamicProxyRouter.SubdomainEndpoint.Range(func(key, value interface{}) bool {
|
||||||
results = append(results, value.(*dynamicproxy.SubdomainEndpoint))
|
results = append(results, value.(*dynamicproxy.ProxyEndpoint))
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
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)
|
js, _ := json.Marshal(results)
|
||||||
|
@ -86,10 +86,6 @@ func startupSequence() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create a netstat buffer
|
//Create a netstat buffer
|
||||||
netstatBuffers, err = netstat.NewNetStatBuffer(300)
|
netstatBuffers, err = netstat.NewNetStatBuffer(300)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr><th>Domain</th>
|
<tr><th>Domain</th>
|
||||||
<th>Last Update</th>
|
<th>Last Update</th>
|
||||||
|
<th>Expire At</th>
|
||||||
<th class="no-sort">Remove</th>
|
<th class="no-sort">Remove</th>
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody id="certifiedDomainList">
|
<tbody id="certifiedDomainList">
|
||||||
@ -108,6 +109,7 @@
|
|||||||
$("#certifiedDomainList").append(`<tr>
|
$("#certifiedDomainList").append(`<tr>
|
||||||
<td>${entry.Domain}</td>
|
<td>${entry.Domain}</td>
|
||||||
<td>${entry.LastModifiedDate}</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>
|
<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>`);
|
</tr>`);
|
||||||
})
|
})
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Subdomain Matching Keyword / Virtual Directory Name</label>
|
<label>Subdomain Matching Keyword / Virtual Directory Name</label>
|
||||||
<input type="text" id="rootname" placeholder="s1.mydomain.com">
|
<input type="text" id="rootname" placeholder="s1.mydomain.com">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>IP Address or Domain Name with port</label>
|
<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>
|
<label>Proxy Target require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<button class="ui basic button" onclick="newProxyEndpoint();"><i class="blue add icon"></i> Create Endpoint</button>
|
||||||
<br><br>
|
<br><br>
|
||||||
</div>
|
</div>
|
||||||
@ -63,12 +114,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
$("#advanceProxyRules").accordion();
|
||||||
|
|
||||||
//New Proxy Endpoint
|
//New Proxy Endpoint
|
||||||
function newProxyEndpoint(){
|
function newProxyEndpoint(){
|
||||||
var type = $("#ptype").val();
|
var type = $("#ptype").val();
|
||||||
var rootname = $("#rootname").val();
|
var rootname = $("#rootname").val();
|
||||||
var proxyDomain = $("#proxyDomain").val();
|
var proxyDomain = $("#proxyDomain").val();
|
||||||
var useTLS = $("#reqTls")[0].checked;
|
var useTLS = $("#reqTls")[0].checked;
|
||||||
|
var skipTLSValidation = $("#skipTLSValidation")[0].checked;
|
||||||
|
var requireBasicAuth = $("#requireBasicAuth")[0].checked;
|
||||||
|
|
||||||
if (type === "vdir") {
|
if (type === "vdir") {
|
||||||
if (!rootname.startsWith("/")) {
|
if (!rootname.startsWith("/")) {
|
||||||
@ -101,7 +156,15 @@
|
|||||||
//Create the endpoint by calling add
|
//Create the endpoint by calling add
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/api/proxy/add",
|
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){
|
success: function(data){
|
||||||
if (data.error != undefined){
|
if (data.error != undefined){
|
||||||
msgbox(data.error, false, 5000);
|
msgbox(data.error, false, 5000);
|
||||||
@ -114,6 +177,8 @@
|
|||||||
//Clear old data
|
//Clear old data
|
||||||
$("#rootname").val("");
|
$("#rootname").val("");
|
||||||
$("#proxyDomain").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
|
//Check if a string is a valid subdomain
|
||||||
function isSubdomainDomain(str) {
|
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;
|
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>
|
||||||
</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>
|
</div>
|
||||||
<!-- <button class="ui icon right floated basic button" onclick="initStatisticSummery();"><i class="green refresh icon"></i> Refresh</button> -->
|
<!-- <button class="ui icon right floated basic button" onclick="initStatisticSummery();"><i class="green refresh icon"></i> Refresh</button> -->
|
||||||
<br><br>
|
<br><br>
|
||||||
@ -177,7 +213,6 @@
|
|||||||
//Two dates are given and they are not identical
|
//Two dates are given and they are not identical
|
||||||
loadStatisticByRange(startdate, enddate);
|
loadStatisticByRange(startdate, enddate);
|
||||||
console.log(startdate, enddate);
|
console.log(startdate, enddate);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +254,15 @@
|
|||||||
|
|
||||||
//Render user agent analysis
|
//Render user agent analysis
|
||||||
renderUserAgentCharts(data.UserAgent);
|
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();
|
initStatisticSummery();
|
||||||
@ -259,7 +303,16 @@
|
|||||||
|
|
||||||
//Render user agent analysis
|
//Render user agent analysis
|
||||||
renderUserAgentCharts(data.Summary.UserAgent);
|
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("");
|
$("#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){
|
function renderUserAgentCharts(userAgentsEntries){
|
||||||
let userAgents = Object.keys(userAgentsEntries);
|
let userAgents = Object.keys(userAgentsEntries);
|
||||||
let requestCounts = Object.values(userAgentsEntries);
|
let requestCounts = Object.values(userAgentsEntries);
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Matching Domain</th>
|
<th>Matching Domain</th>
|
||||||
<th>Proxy To</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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="subdList">
|
<tbody id="subdList">
|
||||||
@ -36,12 +38,17 @@
|
|||||||
data.forEach(subd => {
|
data.forEach(subd => {
|
||||||
let tlsIcon = "";
|
let tlsIcon = "";
|
||||||
if (subd.RequireTLS){
|
if (subd.RequireTLS){
|
||||||
tlsIcon = `<i class="lock icon"></i>`;
|
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||||
}
|
}
|
||||||
$("#subdList").append(`<tr>
|
$("#subdList").append(`<tr>
|
||||||
<td data-label="">${subd.MatchingDomain}</td>
|
<td data-label="">${subd.RootOrMatchingDomain}</td>
|
||||||
<td data-label="">${subd.Domain} ${tlsIcon}</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>`);
|
</tr>`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Virtual Directory</th>
|
<th>Virtual Directory</th>
|
||||||
<th>Proxy To</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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="vdirList">
|
<tbody id="vdirList">
|
||||||
@ -39,12 +41,17 @@
|
|||||||
data.forEach(vdir => {
|
data.forEach(vdir => {
|
||||||
let tlsIcon = "";
|
let tlsIcon = "";
|
||||||
if (vdir.RequireTLS){
|
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>
|
$("#vdirList").append(`<tr>
|
||||||
<td data-label="">${vdir.Root}</td>
|
<td data-label="">${vdir.RootOrMatchingDomain}</td>
|
||||||
<td data-label="">${vdir.Domain} ${tlsIcon}</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>`);
|
</tr>`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user