mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00
Updated a lot of stuffs
+ Added comments for whitelist + Added automatic cert pick for multi-host certs (SNI) + Renamed .crt to .pem for cert store + Added best-fit selection for wildcard matching rules + Added x-proxy-by header + Added X-real-Ip header + Added Development Mode (Cache-Control: no-store) + Updated utm timeout to 10 seconds instead of 90
This commit is contained in:
parent
174efc9080
commit
e980bc847b
@ -4,6 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
strip "github.com/grokify/html-strip-tags-go"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -115,7 +117,7 @@ func handleListWhitelisted(w http.ResponseWriter, r *http.Request) {
|
||||
bltype = "country"
|
||||
}
|
||||
|
||||
resulst := []string{}
|
||||
resulst := []*geodb.WhitelistEntry{}
|
||||
if bltype == "country" {
|
||||
resulst = geodbStore.GetAllWhitelistedCountryCode()
|
||||
} else if bltype == "ip" {
|
||||
@ -134,7 +136,10 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.AddCountryCodeToWhitelist(countryCode)
|
||||
comment, _ := utils.PostPara(r, "comment")
|
||||
comment = strip.StripTags(comment)
|
||||
|
||||
geodbStore.AddCountryCodeToWhitelist(countryCode, comment)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
@ -158,7 +163,10 @@ func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.AddIPToWhiteList(ipAddr)
|
||||
comment, _ := utils.PostPara(r, "comment")
|
||||
comment = strip.StripTags(comment)
|
||||
|
||||
geodbStore.AddIPToWhiteList(ipAddr, comment)
|
||||
}
|
||||
|
||||
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -56,6 +56,7 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
|
||||
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
||||
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
||||
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
||||
//Reverse proxy virtual directory APIs
|
||||
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
|
||||
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
|
||||
@ -178,7 +179,7 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
|
||||
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
|
||||
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
|
||||
authRouter.HandleFunc("/api/webserv/setPort", staticWebServer.HandlePortChange)
|
||||
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
|
||||
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
|
||||
if *allowWebFileManager {
|
||||
//Web Directory Manager file operation functions
|
||||
|
@ -51,7 +51,7 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
|
||||
results := []*CertInfo{}
|
||||
|
||||
for _, filename := range filenames {
|
||||
certFilepath := filepath.Join(tlsCertManager.CertStore, filename+".crt")
|
||||
certFilepath := filepath.Join(tlsCertManager.CertStore, filename+".pem")
|
||||
//keyFilepath := filepath.Join(tlsCertManager.CertStore, filename+".key")
|
||||
fileInfo, err := os.Stat(certFilepath)
|
||||
if err != nil {
|
||||
@ -248,7 +248,7 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if keytype == "pub" {
|
||||
overWriteFilename = domain + ".crt"
|
||||
overWriteFilename = domain + ".pem"
|
||||
} else if keytype == "pri" {
|
||||
overWriteFilename = domain + ".key"
|
||||
} else {
|
||||
@ -287,6 +287,9 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Update cert list
|
||||
tlsCertManager.UpdateLoadedCertList()
|
||||
|
||||
// send response
|
||||
fmt.Fprintln(w, "File upload successful!")
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ require (
|
||||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/grandcat/zeroconf v1.0.0
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
|
||||
github.com/likexian/whois v1.15.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.25
|
||||
golang.org/x/net v0.14.0
|
||||
|
@ -740,6 +740,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
|
||||
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
|
@ -163,7 +163,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
|
||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||
// private key, and a certificate URL.
|
||||
err = os.WriteFile("./conf/certs/"+certificateName+".crt", certificates.Certificate, 0777)
|
||||
err = os.WriteFile("./conf/certs/"+certificateName+".pem", certificates.Certificate, 0777)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
|
@ -1,8 +1,6 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -25,11 +23,6 @@ import (
|
||||
- Vitrual Directory Routing
|
||||
*/
|
||||
|
||||
var (
|
||||
//go:embed tld.json
|
||||
rawTldMap []byte
|
||||
)
|
||||
|
||||
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
Special Routing Rules, bypass most of the limitations
|
||||
@ -52,10 +45,12 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Inject debug headers
|
||||
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
|
||||
|
||||
/*
|
||||
General Access Check
|
||||
*/
|
||||
|
||||
respWritten := h.handleAccessRouting(w, r)
|
||||
if respWritten {
|
||||
return
|
||||
@ -81,6 +76,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
Host Routing
|
||||
*/
|
||||
|
||||
sep := h.Parent.getProxyEndpointFromHostname(domainOnly)
|
||||
if sep != nil && !sep.Disabled {
|
||||
if sep.RequireBasicAuth {
|
||||
@ -235,44 +231,3 @@ func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Return if the given host is already topped (e.g. example.com or example.co.uk) instead of
|
||||
// a host with subdomain (e.g. test.example.com)
|
||||
func (h *ProxyHandler) isTopLevelRedirectableDomain(requestHost string) bool {
|
||||
parts := strings.Split(requestHost, ".")
|
||||
if len(parts) > 2 {
|
||||
//Cases where strange tld is used like .co.uk or .com.hk
|
||||
_, ok := h.Parent.tldMap[strings.Join(parts[1:], ".")]
|
||||
if ok {
|
||||
//Already topped
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
//Already topped
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetTopLevelRedirectableDomain returns the toppest level of domain
|
||||
// that is redirectable. E.g. a.b.c.example.co.uk will return example.co.uk
|
||||
func (h *ProxyHandler) getTopLevelRedirectableDomain(unsetSubdomainHost string) (string, error) {
|
||||
parts := strings.Split(unsetSubdomainHost, ".")
|
||||
if h.isTopLevelRedirectableDomain(unsetSubdomainHost) {
|
||||
//Already topped
|
||||
return "", errors.New("already at top level domain")
|
||||
}
|
||||
|
||||
for i := 0; i < len(parts); i++ {
|
||||
possibleTld := parts[i:]
|
||||
_, ok := h.Parent.tldMap[strings.Join(possibleTld, ".")]
|
||||
if ok {
|
||||
//This is tld length
|
||||
tld := strings.Join(parts[i-1:], ".")
|
||||
return "//" + tld, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("unsupported top level domain given")
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ type ResponseRewriteRuleSet struct {
|
||||
ProxyDomain string
|
||||
OriginalHost string
|
||||
UseTLS bool
|
||||
NoCache bool
|
||||
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
||||
}
|
||||
|
||||
@ -243,7 +244,7 @@ func (p *ReverseProxy) logf(format string, args ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func removeHeaders(header http.Header) {
|
||||
func removeHeaders(header http.Header, noCache bool) {
|
||||
// Remove hop-by-hop headers listed in the "Connection" header.
|
||||
if c := header.Get("Connection"); c != "" {
|
||||
for _, f := range strings.Split(c, ",") {
|
||||
@ -260,9 +261,16 @@ func removeHeaders(header http.Header) {
|
||||
}
|
||||
}
|
||||
|
||||
if header.Get("A-Upgrade") != "" {
|
||||
header.Set("Upgrade", header.Get("A-Upgrade"))
|
||||
header.Del("A-Upgrade")
|
||||
//Restore the Upgrade header if any
|
||||
if header.Get("Zr-Origin-Upgrade") != "" {
|
||||
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
|
||||
header.Del("Zr-Origin-Upgrade")
|
||||
}
|
||||
|
||||
//Disable cache if nocache is set
|
||||
if noCache {
|
||||
header.Del("Cache-Control")
|
||||
header.Set("Cache-Control", "no-store")
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,6 +289,11 @@ func addXForwardedForHeader(req *http.Request) {
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
}
|
||||
|
||||
if req.Header.Get("X-Real-Ip") == "" {
|
||||
//Not exists. Fill it in with client IP
|
||||
req.Header.Set("X-Real-Ip", clientIP)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,7 +336,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
copyHeader(outreq.Header, req.Header)
|
||||
|
||||
// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
|
||||
removeHeaders(outreq.Header)
|
||||
removeHeaders(outreq.Header, rrr.NoCache)
|
||||
|
||||
// Add X-Forwarded-For Header.
|
||||
addXForwardedForHeader(outreq)
|
||||
@ -339,7 +352,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
}
|
||||
|
||||
// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
|
||||
removeHeaders(res.Header)
|
||||
removeHeaders(res.Header, rrr.NoCache)
|
||||
|
||||
if p.ModifyResponse != nil {
|
||||
if err := p.ModifyResponse(res); err != nil {
|
||||
|
@ -35,12 +35,6 @@ func NewDynamicProxy(option RouterOption) (*Router, error) {
|
||||
Parent: &thisRouter,
|
||||
}
|
||||
|
||||
//Prase the tld map for tld redirection in main router
|
||||
//See Server.go declarations
|
||||
if len(rawTldMap) > 0 {
|
||||
json.Unmarshal(rawTldMap, &thisRouter.tldMap)
|
||||
}
|
||||
|
||||
return &thisRouter, nil
|
||||
}
|
||||
|
||||
@ -74,12 +68,12 @@ func (router *Router) UpdateHttpToHttpsRedirectSetting(useRedirect bool) {
|
||||
func (router *Router) StartProxyService() error {
|
||||
//Create a new server object
|
||||
if router.server != nil {
|
||||
return errors.New("Reverse proxy server already running")
|
||||
return errors.New("reverse proxy server already running")
|
||||
}
|
||||
|
||||
//Check if root route is set
|
||||
if router.Root == nil {
|
||||
return errors.New("Reverse proxy router root not set")
|
||||
return errors.New("reverse proxy router root not set")
|
||||
}
|
||||
|
||||
minVersion := tls.VersionTLS10
|
||||
@ -92,16 +86,6 @@ func (router *Router) StartProxyService() error {
|
||||
}
|
||||
|
||||
if router.Option.UseTls {
|
||||
/*
|
||||
//Serve with TLS mode
|
||||
ln, err := tls.Listen("tcp", ":"+strconv.Itoa(router.Option.Port), config)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
router.Running = false
|
||||
return err
|
||||
}
|
||||
router.tlsListener = ln
|
||||
*/
|
||||
router.server = &http.Server{
|
||||
Addr: ":" + strconv.Itoa(router.Option.Port),
|
||||
Handler: router.mux,
|
||||
@ -216,7 +200,7 @@ func (router *Router) StartProxyService() error {
|
||||
|
||||
func (router *Router) StopProxyService() error {
|
||||
if router.server == nil {
|
||||
return errors.New("Reverse proxy server already stopped")
|
||||
return errors.New("reverse proxy server already stopped")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
@ -251,6 +235,7 @@ func (router *Router) Restart() error {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
// Start the server
|
||||
err = router.StartProxyService()
|
||||
if err != nil {
|
||||
|
@ -7,7 +7,13 @@ import (
|
||||
)
|
||||
|
||||
/*
|
||||
Endpoint Functions
|
||||
endpoint.go
|
||||
author: tobychui
|
||||
|
||||
This script handle the proxy endpoint object actions
|
||||
so proxyEndpoint can be handled like a proper oop object
|
||||
|
||||
Most of the functions are implemented in dynamicproxy.go
|
||||
*/
|
||||
|
||||
// Get virtual directory handler from given URI
|
||||
@ -87,3 +93,16 @@ func (ep *ProxyEndpoint) Clone() *ProxyEndpoint {
|
||||
json.Unmarshal(js, &clonedProxyEndpoint)
|
||||
return &clonedProxyEndpoint
|
||||
}
|
||||
|
||||
// Remove this proxy endpoint from running proxy endpoint list
|
||||
func (ep *ProxyEndpoint) Remove() error {
|
||||
ep.parent.ProxyEndpoints.Delete(ep.RootOrMatchingDomain)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write changes to runtime without respawning the proxy handler
|
||||
// use prepare -> remove -> add if you change anything in the endpoint
|
||||
// that effects the proxy routing src / dest
|
||||
func (ep *ProxyEndpoint) UpdateToRuntime() {
|
||||
ep.parent.ProxyEndpoints.Store(ep.RootOrMatchingDomain, ep)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
@ -37,6 +38,7 @@ func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoi
|
||||
}
|
||||
|
||||
//No hit. Try with wildcard
|
||||
matchProxyEndpoints := []*ProxyEndpoint{}
|
||||
router.ProxyEndpoints.Range(func(k, v interface{}) bool {
|
||||
ep := v.(*ProxyEndpoint)
|
||||
match, err := filepath.Match(ep.RootOrMatchingDomain, hostname)
|
||||
@ -45,12 +47,24 @@ func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoi
|
||||
return true
|
||||
}
|
||||
if match {
|
||||
targetSubdomainEndpoint = ep
|
||||
return false
|
||||
//targetSubdomainEndpoint = ep
|
||||
matchProxyEndpoints = append(matchProxyEndpoints, ep)
|
||||
return true
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if len(matchProxyEndpoints) == 1 {
|
||||
//Only 1 match
|
||||
return matchProxyEndpoints[0]
|
||||
} else if len(matchProxyEndpoints) > 1 {
|
||||
//More than one match. Get the best match one
|
||||
sort.Slice(matchProxyEndpoints, func(i, j int) bool {
|
||||
return matchProxyEndpoints[i].RootOrMatchingDomain < matchProxyEndpoints[j].RootOrMatchingDomain
|
||||
})
|
||||
return matchProxyEndpoints[0]
|
||||
}
|
||||
|
||||
return targetSubdomainEndpoint
|
||||
}
|
||||
|
||||
@ -77,7 +91,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
requestURL := r.URL.String()
|
||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||
r.Header.Set("A-Upgrade", "websocket")
|
||||
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||
wsRedirectionEndpoint := target.Domain
|
||||
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
||||
//Append / to the end of the redirection endpoint if not exists
|
||||
@ -109,6 +123,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
ProxyDomain: target.Domain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: target.RequireTLS,
|
||||
NoCache: h.Parent.Option.NoCache,
|
||||
PathPrefix: "",
|
||||
})
|
||||
|
||||
@ -137,7 +152,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
|
||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||
r.Header.Set("A-Upgrade", "websocket")
|
||||
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||
wsRedirectionEndpoint := target.Domain
|
||||
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
||||
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
|
||||
|
@ -97,3 +97,13 @@ func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error {
|
||||
router.Root = endpoint
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProxyEndpoint remove provide global access by key
|
||||
func (router *Router) RemoveProxyEndpointByRootname(rootnameOrMatchingDomain string) error {
|
||||
targetEpt, err := router.LoadProxy(rootnameOrMatchingDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return targetEpt.Remove()
|
||||
}
|
||||
|
@ -25,9 +25,11 @@ type ProxyHandler struct {
|
||||
|
||||
type RouterOption struct {
|
||||
HostUUID string //The UUID of Zoraxy, use for heading mod
|
||||
HostVersion string //The version of Zoraxy, use for heading mod
|
||||
Port int //Incoming port
|
||||
UseTls bool //Use TLS to serve incoming requsts
|
||||
ForceTLSLatest bool //Force TLS1.2 or above
|
||||
NoCache bool //Force set Cache-Control: no-store
|
||||
ListenOnPort80 bool //Enable port 80 http listener
|
||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||
TlsManager *tlscert.Manager
|
||||
|
@ -1,6 +1,9 @@
|
||||
package geodb
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Whitelist.go
|
||||
@ -8,11 +11,29 @@ import "strings"
|
||||
This script handles whitelist related functions
|
||||
*/
|
||||
|
||||
const (
|
||||
EntryType_CountryCode int = 0
|
||||
EntryType_IP int = 1
|
||||
)
|
||||
|
||||
type WhitelistEntry struct {
|
||||
EntryType int //Entry type of whitelist, Country Code or IP
|
||||
CC string //ISO Country Code
|
||||
IP string //IP address or range
|
||||
Comment string //Comment for this entry
|
||||
}
|
||||
|
||||
//Geo Whitelist
|
||||
|
||||
func (s *Store) AddCountryCodeToWhitelist(countryCode string) {
|
||||
func (s *Store) AddCountryCodeToWhitelist(countryCode string, comment string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
s.sysdb.Write("whitelist-cn", countryCode, true)
|
||||
entry := WhitelistEntry{
|
||||
EntryType: EntryType_CountryCode,
|
||||
CC: countryCode,
|
||||
Comment: comment,
|
||||
}
|
||||
|
||||
s.sysdb.Write("whitelist-cn", countryCode, entry)
|
||||
}
|
||||
|
||||
func (s *Store) RemoveCountryCodeFromWhitelist(countryCode string) {
|
||||
@ -22,20 +43,19 @@ func (s *Store) RemoveCountryCodeFromWhitelist(countryCode string) {
|
||||
|
||||
func (s *Store) IsCountryCodeWhitelisted(countryCode string) bool {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
var isWhitelisted bool = false
|
||||
s.sysdb.Read("whitelist-cn", countryCode, &isWhitelisted)
|
||||
return isWhitelisted
|
||||
return s.sysdb.KeyExists("whitelist-cn", countryCode)
|
||||
}
|
||||
|
||||
func (s *Store) GetAllWhitelistedCountryCode() []string {
|
||||
whitelistedCountryCode := []string{}
|
||||
func (s *Store) GetAllWhitelistedCountryCode() []*WhitelistEntry {
|
||||
whitelistedCountryCode := []*WhitelistEntry{}
|
||||
entries, err := s.sysdb.ListTable("whitelist-cn")
|
||||
if err != nil {
|
||||
return whitelistedCountryCode
|
||||
}
|
||||
for _, keypairs := range entries {
|
||||
ip := string(keypairs[0])
|
||||
whitelistedCountryCode = append(whitelistedCountryCode, ip)
|
||||
thisWhitelistEntry := WhitelistEntry{}
|
||||
json.Unmarshal(keypairs[1], &thisWhitelistEntry)
|
||||
whitelistedCountryCode = append(whitelistedCountryCode, &thisWhitelistEntry)
|
||||
}
|
||||
|
||||
return whitelistedCountryCode
|
||||
@ -43,8 +63,14 @@ func (s *Store) GetAllWhitelistedCountryCode() []string {
|
||||
|
||||
//IP Whitelist
|
||||
|
||||
func (s *Store) AddIPToWhiteList(ipAddr string) {
|
||||
s.sysdb.Write("whitelist-ip", ipAddr, true)
|
||||
func (s *Store) AddIPToWhiteList(ipAddr string, comment string) {
|
||||
thisIpEntry := WhitelistEntry{
|
||||
EntryType: EntryType_IP,
|
||||
IP: ipAddr,
|
||||
Comment: comment,
|
||||
}
|
||||
|
||||
s.sysdb.Write("whitelist-ip", ipAddr, thisIpEntry)
|
||||
}
|
||||
|
||||
func (s *Store) RemoveIPFromWhiteList(ipAddr string) {
|
||||
@ -52,14 +78,14 @@ func (s *Store) RemoveIPFromWhiteList(ipAddr string) {
|
||||
}
|
||||
|
||||
func (s *Store) IsIPWhitelisted(ipAddr string) bool {
|
||||
var isWhitelisted bool = false
|
||||
s.sysdb.Read("whitelist-ip", ipAddr, &isWhitelisted)
|
||||
isWhitelisted := s.sysdb.KeyExists("whitelist-ip", ipAddr)
|
||||
if isWhitelisted {
|
||||
//single IP whitelist entry
|
||||
return true
|
||||
}
|
||||
|
||||
//Check for IP wildcard and CIRD rules
|
||||
AllWhitelistedIps := s.GetAllWhitelistedIp()
|
||||
AllWhitelistedIps := s.GetAllWhitelistedIpAsStringSlice()
|
||||
for _, whitelistRules := range AllWhitelistedIps {
|
||||
wildcardMatch := MatchIpWildcard(ipAddr, whitelistRules)
|
||||
if wildcardMatch {
|
||||
@ -75,17 +101,29 @@ func (s *Store) IsIPWhitelisted(ipAddr string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Store) GetAllWhitelistedIp() []string {
|
||||
whitelistedIp := []string{}
|
||||
func (s *Store) GetAllWhitelistedIp() []*WhitelistEntry {
|
||||
whitelistedIp := []*WhitelistEntry{}
|
||||
entries, err := s.sysdb.ListTable("whitelist-ip")
|
||||
if err != nil {
|
||||
return whitelistedIp
|
||||
}
|
||||
|
||||
for _, keypairs := range entries {
|
||||
ip := string(keypairs[0])
|
||||
whitelistedIp = append(whitelistedIp, ip)
|
||||
//ip := string(keypairs[0])
|
||||
thisEntry := WhitelistEntry{}
|
||||
json.Unmarshal(keypairs[1], &thisEntry)
|
||||
whitelistedIp = append(whitelistedIp, &thisEntry)
|
||||
}
|
||||
|
||||
return whitelistedIp
|
||||
}
|
||||
|
||||
func (s *Store) GetAllWhitelistedIpAsStringSlice() []string {
|
||||
allWhitelistedIPs := []string{}
|
||||
entries := s.GetAllWhitelistedIp()
|
||||
for _, entry := range entries {
|
||||
allWhitelistedIPs = append(allWhitelistedIPs, entry.IP)
|
||||
}
|
||||
|
||||
return allWhitelistedIPs
|
||||
}
|
||||
|
@ -211,9 +211,9 @@ func removeHeaders(header http.Header) {
|
||||
}
|
||||
}
|
||||
|
||||
if header.Get("A-Upgrade") != "" {
|
||||
header.Set("Upgrade", header.Get("A-Upgrade"))
|
||||
header.Del("A-Upgrade")
|
||||
if header.Get("Zr-Origin-Upgrade") != "" {
|
||||
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
|
||||
header.Del("Zr-Origin-Upgrade")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWrite
|
||||
requestURL := r.URL.String()
|
||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||
r.Header.Set("A-Upgrade", "websocket")
|
||||
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||
requestURL = strings.TrimPrefix(requestURL, "/")
|
||||
u, _ := url.Parse("ws://127.0.0.1:" + strconv.Itoa(targetInstance.AssignedPort) + "/" + requestURL)
|
||||
wspHandler := websocketproxy.NewProxy(u, false)
|
||||
|
@ -5,22 +5,22 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
//This remove the certificates in the list where either the
|
||||
//public key or the private key is missing
|
||||
// This remove the certificates in the list where either the
|
||||
// public key or the private key is missing
|
||||
func getCertPairs(certFiles []string) []string {
|
||||
crtMap := make(map[string]bool)
|
||||
pemMap := make(map[string]bool)
|
||||
keyMap := make(map[string]bool)
|
||||
|
||||
for _, filename := range certFiles {
|
||||
if filepath.Ext(filename) == ".crt" {
|
||||
crtMap[strings.TrimSuffix(filename, ".crt")] = true
|
||||
if filepath.Ext(filename) == ".pem" {
|
||||
pemMap[strings.TrimSuffix(filename, ".pem")] = true
|
||||
} else if filepath.Ext(filename) == ".key" {
|
||||
keyMap[strings.TrimSuffix(filename, ".key")] = true
|
||||
}
|
||||
}
|
||||
|
||||
var result []string
|
||||
for domain := range crtMap {
|
||||
for domain := range pemMap {
|
||||
if keyMap[domain] {
|
||||
result = append(result, domain)
|
||||
}
|
||||
@ -29,7 +29,7 @@ func getCertPairs(certFiles []string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
//Get the cloest subdomain certificate from a list of domains
|
||||
// Get the cloest subdomain certificate from a list of domains
|
||||
func matchClosestDomainCertificate(subdomain string, domains []string) string {
|
||||
var matchingDomain string = ""
|
||||
maxLength := 0
|
||||
@ -43,18 +43,3 @@ func matchClosestDomainCertificate(subdomain string, domains []string) string {
|
||||
|
||||
return matchingDomain
|
||||
}
|
||||
|
||||
//Check if a requesting domain is a subdomain of a given domain
|
||||
func isSubdomain(subdomain, domain string) bool {
|
||||
subdomainParts := strings.Split(subdomain, ".")
|
||||
domainParts := strings.Split(domain, ".")
|
||||
if len(subdomainParts) < len(domainParts) {
|
||||
return false
|
||||
}
|
||||
for i := range domainParts {
|
||||
if subdomainParts[len(subdomainParts)-1-i] != domainParts[len(domainParts)-1-i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
22
src/mod/tlscert/localhost.pem
Normal file
22
src/mod/tlscert/localhost.pem
Normal file
@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDuTCCAqCgAwIBAgIBADANBgkqhkiG9w0BAQ0FADB2MQswCQYDVQQGEwJoazES
|
||||
MBAGA1UECAwJSG9uZyBLb25nMRQwEgYDVQQKDAtpbXVzbGFiLmNvbTEZMBcGA1UE
|
||||
AwwQWm9yYXh5IFNlbGYtaG9zdDEQMA4GA1UEBwwHSU1VU0xBQjEQMA4GA1UECwwH
|
||||
SU1VU0xBQjAeFw0yMzA1MjcxMDQyNDJaFw0zODA1MjgxMDQyNDJaMHYxCzAJBgNV
|
||||
BAYTAmhrMRIwEAYDVQQIDAlIb25nIEtvbmcxFDASBgNVBAoMC2ltdXNsYWIuY29t
|
||||
MRkwFwYDVQQDDBBab3JheHkgU2VsZi1ob3N0MRAwDgYDVQQHDAdJTVVTTEFCMRAw
|
||||
DgYDVQQLDAdJTVVTTEFCMIIBIzANBgkqhkiG9w0BAQEFAAOCARAAMIIBCwKCAQIA
|
||||
xav3Qq4DBooHsGW9m+r0dgjI832grX2c0Z6MJQQoE7B6wfpUI0OyfRugTXyXoiRZ
|
||||
gLxuROgiCUmp8FaLbl7RsvbImMbCPo3D/RbCT1aJCNXLZ0a7yvcDYc6woQW4nUyk
|
||||
ohHfT2otcu+OYS6aYRZuXGsKTAqPSwEXRMtr89wkPgZPsrCD27LFHBOmIcVABDvF
|
||||
KRuiwHWSHhFfU5n1AZLyYeYoLNQ9fZPvzPpkMD+HMKi4MMwr/vLE0DwU5jSfVFq+
|
||||
cd68zVihp9N/T77yah5EIH9CYm4m8Acs4bfL8DALxnaSN3KmGw6J35rOXrJvJLdh
|
||||
t42PDROmQrXN8uG8wGkBiBkCAwEAAaNQME4wHQYDVR0OBBYEFLhXihE+1K6MoL0P
|
||||
Nx5htfuSatpiMB8GA1UdIwQYMBaAFLhXihE+1K6MoL0PNx5htfuSatpiMAwGA1Ud
|
||||
EwQFMAMBAf8wDQYJKoZIhvcNAQENBQADggECAMCn0ed1bfLefGvoQJV/q+X9p61U
|
||||
HunSFJAAhp0N2Q3tq/zjIu0kJX7N0JBciEw2c0ZmqJIqR8V8Im/h/4XuuOR+53hg
|
||||
opOSPo39ww7mpxyBlQm63v1nXcNQcvw4U0JqXQ4Kyv8cgX7DIuyjRWHQpc5+6joy
|
||||
L5Nz5hzQbgpnPdHQEMorfnm8q6bWg/291IAV3ZA9Z6T5gn4YuyjeUdDczQtpT6nu
|
||||
1iTNPqtO6R3aeTVT+OSJT9sH2MHfDAsf371HBM6MzM/5QBc/62Bgau7NUjNKeSEA
|
||||
EtUBil8wBHwT7vOtqbyNk5FHEfoCpYsQtP7AtEo10izKCQpDXPftfiJefkOY
|
||||
-----END CERTIFICATE-----
|
@ -6,7 +6,6 @@ import (
|
||||
"embed"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -15,12 +14,19 @@ import (
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
CertStore string
|
||||
verbal bool
|
||||
type CertCache struct {
|
||||
Cert *x509.Certificate
|
||||
PubKey string
|
||||
PriKey string
|
||||
}
|
||||
|
||||
//go:embed localhost.crt localhost.key
|
||||
type Manager struct {
|
||||
CertStore string //Path where all the certs are stored
|
||||
LoadedCerts []*CertCache //A list of loaded certs
|
||||
verbal bool
|
||||
}
|
||||
|
||||
//go:embed localhost.pem localhost.key
|
||||
var buildinCertStore embed.FS
|
||||
|
||||
func NewManager(certStore string, verbal bool) (*Manager, error) {
|
||||
@ -28,14 +34,99 @@ func NewManager(certStore string, verbal bool) (*Manager, error) {
|
||||
os.MkdirAll(certStore, 0775)
|
||||
}
|
||||
|
||||
pubKey := "./tmp/localhost.pem"
|
||||
priKey := "./tmp/localhost.key"
|
||||
|
||||
//Check if this is initial setup
|
||||
if !utils.FileExists(pubKey) {
|
||||
buildInPubKey, _ := buildinCertStore.ReadFile(filepath.Base(pubKey))
|
||||
os.WriteFile(pubKey, buildInPubKey, 0775)
|
||||
}
|
||||
|
||||
if !utils.FileExists(priKey) {
|
||||
buildInPriKey, _ := buildinCertStore.ReadFile(filepath.Base(priKey))
|
||||
os.WriteFile(priKey, buildInPriKey, 0775)
|
||||
}
|
||||
|
||||
thisManager := Manager{
|
||||
CertStore: certStore,
|
||||
verbal: verbal,
|
||||
CertStore: certStore,
|
||||
LoadedCerts: []*CertCache{},
|
||||
verbal: verbal,
|
||||
}
|
||||
|
||||
err := thisManager.UpdateLoadedCertList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &thisManager, nil
|
||||
}
|
||||
|
||||
// Update domain mapping from file
|
||||
func (m *Manager) UpdateLoadedCertList() error {
|
||||
//Get a list of certificates from file
|
||||
domainList, err := m.ListCertDomains()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Load each of the certificates into memory
|
||||
certList := []*CertCache{}
|
||||
for _, certname := range domainList {
|
||||
//Read their certificate into memory
|
||||
pubKey := filepath.Join(m.CertStore, certname+".pem")
|
||||
priKey := filepath.Join(m.CertStore, certname+".key")
|
||||
certificate, err := tls.LoadX509KeyPair(pubKey, priKey)
|
||||
if err != nil {
|
||||
log.Println("Certificate loaded failed: " + certname)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, thisCert := range certificate.Certificate {
|
||||
loadedCert, err := x509.ParseCertificate(thisCert)
|
||||
if err != nil {
|
||||
//Error pasring cert, skip this byte segment
|
||||
continue
|
||||
}
|
||||
|
||||
thisCacheEntry := CertCache{
|
||||
Cert: loadedCert,
|
||||
PubKey: pubKey,
|
||||
PriKey: priKey,
|
||||
}
|
||||
certList = append(certList, &thisCacheEntry)
|
||||
}
|
||||
}
|
||||
|
||||
//Replace runtime cert array
|
||||
m.LoadedCerts = certList
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Match cert by CN
|
||||
func (m *Manager) CertMatchExists(serverName string) bool {
|
||||
for _, certCacheEntry := range m.LoadedCerts {
|
||||
if certCacheEntry.Cert.VerifyHostname(serverName) == nil || certCacheEntry.Cert.Issuer.CommonName == serverName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get cert entry by matching server name, return pubKey and priKey if found
|
||||
// check with CertMatchExists before calling to the load function
|
||||
func (m *Manager) GetCertByX509CNHostname(serverName string) (string, string) {
|
||||
for _, certCacheEntry := range m.LoadedCerts {
|
||||
if certCacheEntry.Cert.VerifyHostname(serverName) == nil || certCacheEntry.Cert.Issuer.CommonName == serverName {
|
||||
return certCacheEntry.PubKey, certCacheEntry.PriKey
|
||||
}
|
||||
}
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// Return a list of domains by filename
|
||||
func (m *Manager) ListCertDomains() ([]string, error) {
|
||||
filenames, err := m.ListCerts()
|
||||
if err != nil {
|
||||
@ -48,8 +139,9 @@ func (m *Manager) ListCertDomains() ([]string, error) {
|
||||
return filenames, nil
|
||||
}
|
||||
|
||||
// Return a list of cert files (public and private keys)
|
||||
func (m *Manager) ListCerts() ([]string, error) {
|
||||
certs, err := ioutil.ReadDir(m.CertStore)
|
||||
certs, err := os.ReadDir(m.CertStore)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
@ -64,44 +156,52 @@ func (m *Manager) ListCerts() ([]string, error) {
|
||||
return filenames, nil
|
||||
}
|
||||
|
||||
// Get a certificate from disk where its certificate matches with the helloinfo
|
||||
func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
//Check if the domain corrisponding cert exists
|
||||
pubKey := "./tmp/localhost.crt"
|
||||
pubKey := "./tmp/localhost.pem"
|
||||
priKey := "./tmp/localhost.key"
|
||||
|
||||
//Check if this is initial setup
|
||||
if !utils.FileExists(pubKey) {
|
||||
buildInPubKey, _ := buildinCertStore.ReadFile(filepath.Base(pubKey))
|
||||
os.WriteFile(pubKey, buildInPubKey, 0775)
|
||||
}
|
||||
|
||||
if !utils.FileExists(priKey) {
|
||||
buildInPriKey, _ := buildinCertStore.ReadFile(filepath.Base(priKey))
|
||||
os.WriteFile(priKey, buildInPriKey, 0775)
|
||||
}
|
||||
|
||||
if utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".crt")) && utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".key")) {
|
||||
pubKey = filepath.Join(m.CertStore, helloInfo.ServerName+".crt")
|
||||
if utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".pem")) && utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".key")) {
|
||||
//Direct hit
|
||||
pubKey = filepath.Join(m.CertStore, helloInfo.ServerName+".pem")
|
||||
priKey = filepath.Join(m.CertStore, helloInfo.ServerName+".key")
|
||||
|
||||
} else if m.CertMatchExists(helloInfo.ServerName) {
|
||||
//Use x509
|
||||
pubKey, priKey = m.GetCertByX509CNHostname(helloInfo.ServerName)
|
||||
} else {
|
||||
domainCerts, _ := m.ListCertDomains()
|
||||
cloestDomainCert := matchClosestDomainCertificate(helloInfo.ServerName, domainCerts)
|
||||
if cloestDomainCert != "" {
|
||||
//There is a matching parent domain for this subdomain. Use this instead.
|
||||
pubKey = filepath.Join(m.CertStore, cloestDomainCert+".crt")
|
||||
priKey = filepath.Join(m.CertStore, cloestDomainCert+".key")
|
||||
} else if m.DefaultCertExists() {
|
||||
//Use default.crt and default.key
|
||||
pubKey = filepath.Join(m.CertStore, "default.crt")
|
||||
//Fallback to legacy method of matching certificates
|
||||
/*
|
||||
domainCerts, _ := m.ListCertDomains()
|
||||
cloestDomainCert := matchClosestDomainCertificate(helloInfo.ServerName, domainCerts)
|
||||
if cloestDomainCert != "" {
|
||||
//There is a matching parent domain for this subdomain. Use this instead.
|
||||
pubKey = filepath.Join(m.CertStore, cloestDomainCert+".pem")
|
||||
priKey = filepath.Join(m.CertStore, cloestDomainCert+".key")
|
||||
} else if m.DefaultCertExists() {
|
||||
//Use default.pem and default.key
|
||||
pubKey = filepath.Join(m.CertStore, "default.pem")
|
||||
priKey = filepath.Join(m.CertStore, "default.key")
|
||||
if m.verbal {
|
||||
log.Println("No matching certificate found. Serving with default")
|
||||
}
|
||||
} else {
|
||||
if m.verbal {
|
||||
log.Println("Matching certificate not found. Serving with build-in certificate. Requesting server name: ", helloInfo.ServerName)
|
||||
}
|
||||
}*/
|
||||
|
||||
if m.DefaultCertExists() {
|
||||
//Use default.pem and default.key
|
||||
pubKey = filepath.Join(m.CertStore, "default.pem")
|
||||
priKey = filepath.Join(m.CertStore, "default.key")
|
||||
if m.verbal {
|
||||
log.Println("No matching certificate found. Serving with default")
|
||||
}
|
||||
//if m.verbal {
|
||||
// log.Println("No matching certificate found. Serving with default")
|
||||
//}
|
||||
} else {
|
||||
if m.verbal {
|
||||
log.Println("Matching certificate not found. Serving with build-in certificate. Requesting server name: ", helloInfo.ServerName)
|
||||
}
|
||||
//if m.verbal {
|
||||
// log.Println("Matching certificate not found. Serving with build-in certificate. Requesting server name: ", helloInfo.ServerName)
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,17 +217,17 @@ func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, err
|
||||
|
||||
// Check if both the default cert public key and private key exists
|
||||
func (m *Manager) DefaultCertExists() bool {
|
||||
return utils.FileExists(filepath.Join(m.CertStore, "default.crt")) && utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
||||
return utils.FileExists(filepath.Join(m.CertStore, "default.pem")) && utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
||||
}
|
||||
|
||||
// Check if the default cert exists returning seperate results for pubkey and prikey
|
||||
func (m *Manager) DefaultCertExistsSep() (bool, bool) {
|
||||
return utils.FileExists(filepath.Join(m.CertStore, "default.crt")), utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
||||
return utils.FileExists(filepath.Join(m.CertStore, "default.pem")), utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
||||
}
|
||||
|
||||
// Delete the cert if exists
|
||||
func (m *Manager) RemoveCert(domain string) error {
|
||||
pubKey := filepath.Join(m.CertStore, domain+".crt")
|
||||
pubKey := filepath.Join(m.CertStore, domain+".pem")
|
||||
priKey := filepath.Join(m.CertStore, domain+".key")
|
||||
if utils.FileExists(pubKey) {
|
||||
err := os.Remove(pubKey)
|
||||
@ -143,6 +243,9 @@ func (m *Manager) RemoveCert(domain string) error {
|
||||
}
|
||||
}
|
||||
|
||||
//Update the cert list
|
||||
m.UpdateLoadedCertList()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -171,15 +274,11 @@ func IsValidTLSFile(file io.Reader) bool {
|
||||
return false
|
||||
}
|
||||
// Check if the certificate is a valid TLS/SSL certificate
|
||||
return cert.IsCA == false && cert.KeyUsage&x509.KeyUsageDigitalSignature != 0 && cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0
|
||||
return !cert.IsCA && cert.KeyUsage&x509.KeyUsageDigitalSignature != 0 && cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0
|
||||
} else if strings.Contains(block.Type, "PRIVATE KEY") {
|
||||
// The file contains a private key
|
||||
_, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
// Handle the error
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return err == nil
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -217,7 +217,11 @@ func getWebsiteStatusWithLatency(url string) (bool, int64, int) {
|
||||
}
|
||||
|
||||
func getWebsiteStatus(url string) (int, error) {
|
||||
resp, err := http.Get(url)
|
||||
client := http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
//Try replace the http with https and vise versa
|
||||
rewriteURL := ""
|
||||
@ -227,7 +231,7 @@ func getWebsiteStatus(url string) (int, error) {
|
||||
rewriteURL = strings.ReplaceAll(url, "http://", "https://")
|
||||
}
|
||||
|
||||
resp, err = http.Get(rewriteURL)
|
||||
resp, err = client.Get(rewriteURL)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "http: server gave HTTP response to HTTPS client") {
|
||||
//Invalid downstream reverse proxy settings, but it is online
|
||||
|
@ -72,6 +72,7 @@ func (ws *WebServer) HandlePortChange(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,14 @@ func ReverseProxtInit() {
|
||||
SystemWideLogger.Println("Force latest TLS mode disabled. Minimum TLS version is set to v1.0")
|
||||
}
|
||||
|
||||
developmentMode := false
|
||||
sysdb.Read("settings", "devMode", &developmentMode)
|
||||
if useTls {
|
||||
SystemWideLogger.Println("Development mode enabled. Using no-store Cache Control policy")
|
||||
} else {
|
||||
SystemWideLogger.Println("Development mode disabled. Proxying with default Cache Control policy")
|
||||
}
|
||||
|
||||
listenOnPort80 := false
|
||||
sysdb.Read("settings", "listenP80", &listenOnPort80)
|
||||
if listenOnPort80 {
|
||||
@ -74,9 +82,11 @@ func ReverseProxtInit() {
|
||||
|
||||
dprouter, err := dynamicproxy.NewDynamicProxy(dynamicproxy.RouterOption{
|
||||
HostUUID: nodeUUID,
|
||||
HostVersion: version,
|
||||
Port: inboundPort,
|
||||
UseTls: useTls,
|
||||
ForceTLSLatest: forceLatestTLSVersion,
|
||||
NoCache: developmentMode,
|
||||
ListenOnPort80: listenOnPort80,
|
||||
ForceHttpsRedirect: forceHttpsRedirect,
|
||||
TlsManager: tlsCertManager,
|
||||
@ -325,10 +335,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
//Update utm if exists
|
||||
if uptimeMonitor != nil {
|
||||
uptimeMonitor.Config.Targets = GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter)
|
||||
uptimeMonitor.CleanRecords()
|
||||
}
|
||||
UpdateUptimeMonitorTargets()
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
@ -741,7 +748,7 @@ func HandleUpdatePort80Listener(w http.ResponseWriter, r *http.Request) {
|
||||
} else if enabled == "false" {
|
||||
sysdb.Write("settings", "listenP80", false)
|
||||
SystemWideLogger.Println("Disabling port 80 listener")
|
||||
dynamicProxyRouter.UpdatePort80ListenerState(true)
|
||||
dynamicProxyRouter.UpdatePort80ListenerState(false)
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid mode given: "+enabled)
|
||||
}
|
||||
@ -789,6 +796,30 @@ func HandleManagementProxyCheck(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func HandleDevelopmentModeChange(w http.ResponseWriter, r *http.Request) {
|
||||
enableDevelopmentModeStr, err := utils.GetPara(r, "enable")
|
||||
if err != nil {
|
||||
//Load the current development mode toggle state
|
||||
js, _ := json.Marshal(dynamicProxyRouter.Option.NoCache)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
//Write changes to runtime
|
||||
enableDevelopmentMode := false
|
||||
if enableDevelopmentModeStr == "true" {
|
||||
enableDevelopmentMode = true
|
||||
}
|
||||
|
||||
//Write changes to runtime
|
||||
dynamicProxyRouter.Option.NoCache = enableDevelopmentMode
|
||||
|
||||
//Write changes to database
|
||||
sysdb.Write("settings", "devMode", enableDevelopmentMode)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle incoming port set. Change the current proxy incoming port
|
||||
func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
|
||||
newIncomingPort, err := utils.PostPara(r, "incoming")
|
||||
|
@ -15,6 +15,8 @@ import (
|
||||
|
||||
This script holds the static resources router
|
||||
for the reverse proxy service
|
||||
|
||||
If you are looking for reverse proxy handler, see Server.go in mod/dynamicproxy/
|
||||
*/
|
||||
|
||||
func FSHandler(handler http.Handler) http.Handler {
|
||||
|
35
src/routingrule.go
Normal file
35
src/routingrule.go
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||
)
|
||||
|
||||
/*
|
||||
Routing Rule
|
||||
|
||||
This script handle special routing rules for some utilities functions
|
||||
*/
|
||||
|
||||
// Register the system build-in routing rules into the core
|
||||
func registerBuildInRoutingRules() {
|
||||
//Cloudflare email decoder
|
||||
//It decode the email address if you are proxying a cloudflare protected site
|
||||
//[email-protected] -> real@email.com
|
||||
dynamicProxyRouter.AddRoutingRules(&dynamicproxy.RoutingRule{
|
||||
ID: "cloudflare-decoder",
|
||||
MatchRule: func(r *http.Request) bool {
|
||||
return strings.HasSuffix(r.RequestURI, "cloudflare-static/email-decode.min.js")
|
||||
},
|
||||
RoutingHandler: func(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := "function fixObfuscatedEmails(){let t=document.getElementsByClassName(\"__cf_email__\");for(let e=0;e<t.length;e++){let r=t[e],l=r.getAttribute(\"data-cfemail\");if(l){let a=decrypt(l);r.setAttribute(\"href\",\"mailto:\"+a),r.innerHTML=a}}}function decrypt(t){let e=\"\",r=parseInt(t.substr(0,2),16);for(let l=2;l<t.length;l+=2){let a=parseInt(t.substr(l,2),16)^r;e+=String.fromCharCode(a)}try{e=decodeURIComponent(escape(e))}catch(f){console.error(f)}return e}fixObfuscatedEmails();"
|
||||
w.Header().Set("Content-type", "text/javascript")
|
||||
w.Write([]byte(decoder))
|
||||
},
|
||||
Enabled: false,
|
||||
UseSystemAccessControl: false,
|
||||
})
|
||||
|
||||
}
|
@ -238,4 +238,7 @@ func startupSequence() {
|
||||
func finalSequence() {
|
||||
//Start ACME renew agent
|
||||
acmeRegisterSpecialRoutingRule()
|
||||
|
||||
//Inject routing rules
|
||||
registerBuildInRoutingRules()
|
||||
}
|
||||
|
@ -615,8 +615,12 @@
|
||||
<p>Whitelist a certain IP or IP range</p>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>IP Address</label>
|
||||
<input id="ipAddressInputWhitelist" type="text" placeholder="IP Address">
|
||||
<label>IP Address</label>
|
||||
<input id="ipAddressInputWhitelist" type="text" placeholder="IP Address">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Remarks (Optional)</label>
|
||||
<input id="ipAddressCommentsWhitelist" type="text" placeholder="Comments or remarks for this IP range">
|
||||
</div>
|
||||
<button id="addIpButton" onclick="addIpWhitelist();" class="ui basic green button">
|
||||
<i class="green add icon"></i> Whitelist IP
|
||||
@ -634,6 +638,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Remarks</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -793,11 +798,12 @@
|
||||
if (data.length === 0) {
|
||||
$('#whitelistIpTable').append(`
|
||||
<tr>
|
||||
<td colspan="2"><i class="green check circle icon"></i>There are no whitelisted IP addresses</td>
|
||||
<td colspan="3"><i class="green check circle icon"></i>There are no whitelisted IP addresses</td>
|
||||
</tr>
|
||||
`);
|
||||
} else {
|
||||
$.each(data, function(index, ip) {
|
||||
$.each(data, function(index, ipEntry) {
|
||||
let ip = ipEntry.IP;
|
||||
let icon = "globe icon";
|
||||
if (isLAN(ip)){
|
||||
icon = "desktop icon";
|
||||
@ -807,6 +813,7 @@
|
||||
$('#whitelistIpTable').append(`
|
||||
<tr class="whitelistItem" ip="${encodeURIComponent(ip)}">
|
||||
<td><i class="${icon}"></i> ${ip}</td>
|
||||
<td>${ipEntry.Comment}</td>
|
||||
<td><button class="ui icon basic mini red button" onclick="removeIpWhitelist('${ip}');"><i class="trash alternate icon"></i></button></td>
|
||||
</tr>
|
||||
`);
|
||||
@ -1003,6 +1010,7 @@
|
||||
|
||||
function addIpWhitelist(){
|
||||
let targetIp = $("#ipAddressInputWhitelist").val().trim();
|
||||
let remarks = $("#ipAddressCommentsWhitelist").val().trim();
|
||||
if (targetIp == ""){
|
||||
alert("IP address is empty")
|
||||
return
|
||||
@ -1016,7 +1024,7 @@
|
||||
$.ajax({
|
||||
url: "/api/whitelist/ip/add",
|
||||
type: "POST",
|
||||
data: {ip: targetIp.toLowerCase()},
|
||||
data: {ip: targetIp.toLowerCase(), "comment": remarks},
|
||||
success: function(response) {
|
||||
if (response.error !== undefined) {
|
||||
msgbox(response.error, false, 6000);
|
||||
@ -1025,6 +1033,7 @@
|
||||
}
|
||||
|
||||
$("#ipAddressInputWhitelist").val("");
|
||||
$("#ipAddressCommentsWhitelist").val("");
|
||||
$("#ipAddressInputWhitelist").parent().remvoeClass("error");
|
||||
},
|
||||
error: function() {
|
||||
|
@ -15,42 +15,19 @@
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<h4>Default Certificates</h4>
|
||||
<p>When there are no matching certificate for the requested server name, reverse proxy router will always fallback to this one.<br>Note that you need both of them uploaded for it to fallback properly</p>
|
||||
<table class="ui very basic unstackable celled table">
|
||||
<thead>
|
||||
<tr><th class="no-sort">Key Type</th>
|
||||
<th class="no-sort">Exists</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><i class="globe icon"></i> Default Public Key</td>
|
||||
<td id="pubkeyExists"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i class="lock icon"></i> Default Private Key</td>
|
||||
<td id="prikeyExists"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p>
|
||||
<div class="ui buttons">
|
||||
<button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button>
|
||||
<button class="ui basic black button" onclick="uploadPrivateKey();"><i class="black lock icon"></i> Private Key</button>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<h4>Sub-domain Certificates</h4>
|
||||
<h3>Hosts Certificates</h3>
|
||||
<p>Provide certificates for multiple domains reverse proxy</p>
|
||||
<div class="ui fluid form">
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>Server Name (Domain)</label>
|
||||
<input type="text" id="certdomain" placeholder="example.com / blog.example.com">
|
||||
<small><i class="exclamation circle yellow icon"></i> Match the server name with your CN/DNS entry in certificate for faster resolve time</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Public Key (.pem)</label>
|
||||
<input type="file" id="pubkeySelector" onchange="handleFileSelect(event, 'pub')">
|
||||
<small>or .crt files in order systems</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Private Key (.key)</label>
|
||||
@ -63,14 +40,33 @@
|
||||
<div id="certUploadSuccMsg" class="ui green message" style="display:none;">
|
||||
<i class="ui checkmark icon"></i> Certificate for domain <span id="certUploadingDomain"></span> uploaded.
|
||||
</div>
|
||||
<br>
|
||||
<div class="ui message">
|
||||
<h4>Tips about Server Names & SNI</h4>
|
||||
<div class="ui bulleted list">
|
||||
<div class="item">
|
||||
If you have two subdomains like <code>a.example.com</code> and <code>b.example.com</code> ,
|
||||
for faster response speed, you might want to setup them one by one (i.e. having two seperate certificate for
|
||||
<code>a.example.com</code> and <code>b.example.com</code>).
|
||||
</div>
|
||||
<div class="item">
|
||||
If you have a wildcard certificate that covers <code>*.example.com</code>,
|
||||
you can just enter <code>example.com</code> as server name to add a certificate.
|
||||
</div>
|
||||
<div class="item">
|
||||
If you have a certificate contain multiple host, you can enter the first domain in your certificate
|
||||
and Zoraxy will try to match the remaining CN/DNS for you.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Current list of loaded certificates</p>
|
||||
<div>
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em;">
|
||||
<table class="ui sortable unstackable celled table">
|
||||
<table class="ui sortable unstackable basic celled table">
|
||||
<thead>
|
||||
<tr><th>Domain</th>
|
||||
<th>Last Update</th>
|
||||
<th>Expire At</th>
|
||||
<th class="no-sort">Renew</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
</tr></thead>
|
||||
<tbody id="certifiedDomainList">
|
||||
@ -81,15 +77,34 @@
|
||||
|
||||
<button class="ui basic button" onclick="initManagedDomainCertificateList();"><i class="green refresh icon"></i> Refresh List</button>
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<h4><i class="info circle icon"></i> Sub-domain Certificates</h4>
|
||||
If you have 3rd or even 4th level subdomains like <code>blog.example.com</code> or <code>en.blog.example.com</code> ,
|
||||
depending on your certificates coverage, you might need to setup them one by one (i.e. having two seperate certificate for <code>a.example.com</code> and <code>b.example.com</code>).<br>
|
||||
If you have a wildcard certificate that covers <code>*.example.com</code>, you can just enter <code>example.com</code> as server name in the form below to add a certificate.
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h4>Certificate Authority (CA) and Auto Renew (ACME)</h4>
|
||||
<h3>Fallback Certificate</h3>
|
||||
<p>When there are no matching certificate for the requested server name, reverse proxy router will always fallback to this one.<br>Note that you need both of them uploaded for it to fallback properly</p>
|
||||
<table class="ui very basic unstackable celled table">
|
||||
<thead>
|
||||
<tr><th class="no-sort">Key Type</th>
|
||||
<th class="no-sort">Found</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><i class="globe icon"></i> Fallback Public Key</td>
|
||||
<td id="pubkeyExists"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i class="lock icon"></i> Fallback Private Key</td>
|
||||
<td id="prikeyExists"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p>
|
||||
<div class="ui buttons">
|
||||
<button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button>
|
||||
<button class="ui basic black button" onclick="uploadPrivateKey();"><i class="black lock icon"></i> Private Key</button>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h3>Certificate Authority (CA) and Auto Renew (ACME)</h3>
|
||||
<p>Management features regarding CA and ACME</p>
|
||||
<h4>Prefered Certificate Authority</h4>
|
||||
<p>The default CA to use when create a new subdomain proxy endpoint with TLS certificate</p>
|
||||
<div class="ui fluid form">
|
||||
<div class="field">
|
||||
@ -112,12 +127,12 @@
|
||||
<button class="ui basic icon button" onclick="saveDefaultCA();"><i class="ui blue save icon"></i> Save Settings</button>
|
||||
</div><br>
|
||||
<h5>Certificate Renew / Generation (ACME) Settings</h5>
|
||||
<div class="ui basic segment">
|
||||
<div class="ui basic segment acmeRenewStateWrapper">
|
||||
<h4 class="ui header" id="acmeAutoRenewer">
|
||||
<i class="red circle icon"></i>
|
||||
<i class="white remove icon"></i>
|
||||
<div class="content">
|
||||
<span id="acmeAutoRenewerStatus">Disabled</span>
|
||||
<div class="sub header">Auto-Renewer Status</div>
|
||||
<div class="sub header">ACME Auto-Renewer</div>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
@ -130,6 +145,110 @@
|
||||
|
||||
$("#defaultCA").dropdown();
|
||||
|
||||
|
||||
//Renew certificate by button press
|
||||
function renewCertificate(domain, btn=undefined){
|
||||
let defaultCA = $("#defaultCA").dropdown("get value");
|
||||
if (defaultCA.trim() == ""){
|
||||
defaultCA = "Let's Encrypt";
|
||||
}
|
||||
//Get a new cert using ACME
|
||||
msgbox("Requesting certificate via " + defaultCA +"...");
|
||||
|
||||
//Request ACME for certificate
|
||||
if (btn != undefined){
|
||||
$(btn).addClass('disabled');
|
||||
$(btn).html(`<i class="ui loading spinner icon"></i>`);
|
||||
}
|
||||
obtainCertificate(domain, defaultCA.trim(), function(succ){
|
||||
if (btn != undefined){
|
||||
$(btn).removeClass('disabled');
|
||||
if (succ){
|
||||
$(btn).html(`<i class="ui green check icon"></i>`);
|
||||
}else{
|
||||
$(btn).html(`<i class="ui red times icon"></i>`);
|
||||
}
|
||||
|
||||
setTimeout(function(){
|
||||
initManagedDomainCertificateList();
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Obtain Certificate via ACME
|
||||
*/
|
||||
|
||||
// Obtain certificate from API, only support one domain
|
||||
function obtainCertificate(domains, usingCa = "Let's Encrypt", callback=undefined) {
|
||||
//Load the ACME email from server side
|
||||
let acmeEmail = "";
|
||||
$.get("/api/acme/autoRenew/email", function(data){
|
||||
if (data != "" && data != undefined && data != null){
|
||||
acmeEmail = data;
|
||||
}
|
||||
|
||||
let filename = "";
|
||||
let email = acmeEmail;
|
||||
if (acmeEmail == ""){
|
||||
msgbox("Unable to obtain certificate: ACME email not set", false, 8000);
|
||||
if (callback != undefined){
|
||||
callback(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (filename.trim() == "" && !domains.includes(",")){
|
||||
//Zoraxy filename are the matching name for domains.
|
||||
//Use the same as domains
|
||||
filename = domains;
|
||||
}else if (filename != "" && !domains.includes(",")){
|
||||
//Invalid settings. Force the filename to be same as domain
|
||||
//if there are only 1 domain
|
||||
filename = domains;
|
||||
}else{
|
||||
msgbox("Filename cannot be empty for certs containing multiple domains.")
|
||||
if (callback != undefined){
|
||||
callback(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/acme/obtainCert",
|
||||
method: "GET",
|
||||
data: {
|
||||
domains: domains,
|
||||
filename: filename,
|
||||
email: email,
|
||||
ca: usingCa,
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.error) {
|
||||
console.log("Error:", response.error);
|
||||
// Show error message
|
||||
msgbox(response.error, false, 12000);
|
||||
if (callback != undefined){
|
||||
callback(false);
|
||||
}
|
||||
} else {
|
||||
console.log("Certificate installed successfully");
|
||||
// Show success message
|
||||
msgbox("Certificate installed successfully");
|
||||
|
||||
if (callback != undefined){
|
||||
callback(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function(error) {
|
||||
console.log("Failed to install certificate:", error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//Delete the certificate by its domain
|
||||
function deleteCertificate(domain){
|
||||
if (confirm("Confirm delete certificate for " + domain + " ?")){
|
||||
@ -154,6 +273,12 @@
|
||||
//Initialize the current default CA options
|
||||
$.get("/api/acme/autoRenew/email", function(data){
|
||||
$("#prefACMEEmail").val(data);
|
||||
if (data.trim() == ""){
|
||||
//acme email is not yet set
|
||||
$(".renewButton").addClass('disabled');
|
||||
}else{
|
||||
$(".renewButton").removeClass('disabled');
|
||||
}
|
||||
});
|
||||
|
||||
$.get("/api/acme/autoRenew/ca", function(data){
|
||||
@ -167,7 +292,13 @@
|
||||
//Set the status of the acme enable icon
|
||||
function setACMEEnableStates(enabled){
|
||||
$("#acmeAutoRenewerStatus").text(enabled?"Enabled":"Disabled");
|
||||
$("#acmeAutoRenewer").find("i").attr("class", enabled?"green circle icon":"red circle icon");
|
||||
if (enabled){
|
||||
$(".acmeRenewStateWrapper").addClass("enabled");
|
||||
}else{
|
||||
$(".acmeRenewStateWrapper").removeClass("enabled");
|
||||
}
|
||||
|
||||
$("#acmeAutoRenewer").find("i").attr("class", enabled?"white circle check icon":"white circle times icon");
|
||||
}
|
||||
initAcmeStatus();
|
||||
|
||||
@ -187,6 +318,9 @@
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false);
|
||||
}else{
|
||||
//Update the renew button states
|
||||
$(".renewButton").removeClass('disabled');
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -223,13 +357,14 @@
|
||||
<td>${entry.Domain}</td>
|
||||
<td>${entry.LastModifiedDate}</td>
|
||||
<td class="${isExpired?"expired":"valid"} certdate">${entry.ExpireDate} (${!isExpired?entry.RemainingDays+" days left":"Expired"})</td>
|
||||
<td><button title="Renew Certificate" class="ui mini basic icon button renewButton" onclick="renewCertificate('${entry.Domain}', this);"><i class="ui green refresh 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>`);
|
||||
});
|
||||
|
||||
if (data.length == 0){
|
||||
$("#certifiedDomainList").append(`<tr>
|
||||
<td colspan="4"><i class="ui times circle icon"></i> No valid keypairs found</td>
|
||||
<td colspan="4"><i class="ui times red circle icon"></i> No valid keypairs found</td>
|
||||
</tr>`);
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,15 @@
|
||||
<h2>HTTP Proxy</h2>
|
||||
<p>Proxy HTTP server with HTTP or HTTPS for multiple hosts. If you are only proxying for one host / domain, use Default Site instead.</p>
|
||||
</div>
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em; min-width: 400px;">
|
||||
<table class="ui celled sortable unstackable basic compact table">
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em;">
|
||||
<table class="ui celled sortable unstackable compact table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Host</th>
|
||||
<th>Destination</th>
|
||||
<!-- <th>Virtual Directory</th> -->
|
||||
<th>Basic Auth</th>
|
||||
<th class="no-sort" style="min-width: 7.2em;">Actions</th>
|
||||
<th class="no-sort" style="min-width:100px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="httpProxyList">
|
||||
@ -19,6 +19,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button class="ui icon right floated basic button" onclick="listProxyEndpoints();"><i class="green refresh icon"></i> Refresh</button>
|
||||
<br><br>
|
||||
</div>
|
||||
|
@ -110,6 +110,7 @@
|
||||
$("#ruleAddSucc").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
|
||||
}
|
||||
initRedirectionRuleList();
|
||||
resetForm();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -141,7 +142,7 @@
|
||||
$.get("/api/redirect/list", function(data){
|
||||
data.forEach(function(entry){
|
||||
$("#redirectionRuleList").append(`<tr>
|
||||
<td>${entry.RedirectURL} </td>
|
||||
<td><a href="${entry.RedirectURL}" target="_blank">${entry.RedirectURL}</a></td>
|
||||
<td>${entry.TargetURL}</td>
|
||||
<td>${entry.ForwardChildpath?"<i class='ui green checkmark icon'></i>":"<i class='ui red remove icon'></i>"}</td>
|
||||
<td>${entry.StatusCode==307?"Temporary Redirect (307)":"Moved Permanently (301)"}</td>
|
||||
|
@ -198,19 +198,20 @@
|
||||
|
||||
//Set the new proxy root option
|
||||
function setProxyRoot(btn=undefined){
|
||||
if (btn != undefined){
|
||||
$(btn).addClass("disabled");
|
||||
}
|
||||
var newpr = $("#proxyRoot").val();
|
||||
if (newpr.trim() == ""){
|
||||
$("#proxyRoot").parent().addClass('error');
|
||||
return
|
||||
}else{
|
||||
$("#proxyRoot").parent().removeClass('error');
|
||||
if (newpr.trim() == "" && currentDefaultSiteOption == 0){
|
||||
//Fill in the web server info
|
||||
newpr = "127.0.0.1:" + $("#webserv_listenPort").val();
|
||||
$("#proxyRoot").val(newpr);
|
||||
|
||||
}
|
||||
|
||||
var rootReqTls = $("#rootReqTLS")[0].checked;
|
||||
|
||||
if (btn != undefined){
|
||||
$(btn).addClass("disabled");
|
||||
}
|
||||
|
||||
//proxy mode or redirect mode, check for input values
|
||||
var defaultSiteValue = "";
|
||||
if (currentDefaultSiteOption == 1){
|
||||
|
@ -117,7 +117,6 @@
|
||||
<script>
|
||||
$("#advanceProxyRules").accordion();
|
||||
|
||||
|
||||
|
||||
//New Proxy Endpoint
|
||||
function newProxyEndpoint(){
|
||||
@ -164,7 +163,7 @@
|
||||
$("#proxyDomain").val("");
|
||||
credentials = [];
|
||||
updateTable();
|
||||
|
||||
reloadUptimeList();
|
||||
//Check if it is a new subdomain and TLS enabled
|
||||
if ($("#tls").checkbox("is checked")){
|
||||
confirmBox("Request new SSL Cert for this subdomain?", function(choice){
|
||||
@ -177,7 +176,12 @@
|
||||
//Get a new cert using ACME
|
||||
msgbox("Requesting certificate via " + defaultCA +"...");
|
||||
console.log("Trying to get a new certificate via ACME");
|
||||
obtainCertificate(rootname, defaultCA.trim());
|
||||
|
||||
//Request ACME for certificate, see cert.html component
|
||||
obtainCertificate(rootname, defaultCA.trim(), function(){
|
||||
// Renew the parent certificate list
|
||||
initManagedDomainCertificateList();
|
||||
});
|
||||
}else{
|
||||
msgbox("Proxy Endpoint Added");
|
||||
}
|
||||
@ -193,7 +197,7 @@
|
||||
|
||||
//Generic functions for delete rp endpoints
|
||||
function deleteEndpoint(epoint){
|
||||
epoint = decodeURIComponent(epoint);
|
||||
epoint = decodeURIComponent(epoint).hexDecode();
|
||||
if (confirm("Confirm remove proxy for :" + epoint + "?")){
|
||||
$.ajax({
|
||||
url: "/api/proxy/del",
|
||||
@ -201,6 +205,7 @@
|
||||
success: function(){
|
||||
listProxyEndpoints();
|
||||
msgbox("Proxy Rule Deleted", true);
|
||||
reloadUptimeList();
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -300,67 +305,7 @@
|
||||
updateTable();
|
||||
}
|
||||
|
||||
/*
|
||||
Obtain Certificate via ACME
|
||||
*/
|
||||
|
||||
//Load the ACME email from server side
|
||||
let acmeEmail = "";
|
||||
$.get("/api/acme/autoRenew/email", function(data){
|
||||
if (data != "" && data != undefined && data != null){
|
||||
acmeEmail = data;
|
||||
}
|
||||
});
|
||||
|
||||
// Obtain certificate from API, only support one domain
|
||||
function obtainCertificate(domains, usingCa = "Let's Encrypt") {
|
||||
let filename = "";
|
||||
let email = acmeEmail;
|
||||
if (acmeEmail == ""){
|
||||
let rootDomain = domains.split(".").pop();
|
||||
email = "admin@" + rootDomain;
|
||||
}
|
||||
if (filename.trim() == "" && !domains.includes(",")){
|
||||
//Zoraxy filename are the matching name for domains.
|
||||
//Use the same as domains
|
||||
filename = domains;
|
||||
}else if (filename != "" && !domains.includes(",")){
|
||||
//Invalid settings. Force the filename to be same as domain
|
||||
//if there are only 1 domain
|
||||
filename = domains;
|
||||
}else{
|
||||
parent.msgbox("Filename cannot be empty for certs containing multiple domains.")
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/acme/obtainCert",
|
||||
method: "GET",
|
||||
data: {
|
||||
domains: domains,
|
||||
filename: filename,
|
||||
email: email,
|
||||
ca: usingCa,
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.error) {
|
||||
console.log("Error:", response.error);
|
||||
// Show error message
|
||||
msgbox(response.error, false, 12000);
|
||||
} else {
|
||||
console.log("Certificate installed successfully");
|
||||
// Show success message
|
||||
msgbox("Certificate installed successfully");
|
||||
|
||||
// Renew the parent certificate list
|
||||
initManagedDomainCertificateList();
|
||||
}
|
||||
},
|
||||
error: function(error) {
|
||||
console.log("Failed to install certificate:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//Update v3.0.0
|
||||
//Since some proxy rules now contains wildcard characters
|
||||
|
@ -44,7 +44,7 @@
|
||||
<div class="content">
|
||||
<span id="country"></span>
|
||||
<div class="sub header" id="countryList">
|
||||
|
||||
<i class="ui loading circle notch icon"></i> Resolving GeoIP
|
||||
</div>
|
||||
</div>
|
||||
</h5>
|
||||
@ -96,13 +96,18 @@
|
||||
Advance Settings
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>If you have no idea what are these, you can leave them as default :)</p>
|
||||
<div id="tlsMinVer" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox">
|
||||
<label>Force TLS v1.2 or above<br>
|
||||
<small>(Enhance security, but not compatible with legacy browsers)</small></label>
|
||||
</div>
|
||||
<br>
|
||||
<div id="developmentMode" class="ui toggle checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox">
|
||||
<label>Development Mode<br>
|
||||
<small>(Set Cache-Control to no-store so browser will always fetch new contents from your sites)</small></label>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -436,6 +441,30 @@
|
||||
}
|
||||
initTlsVersionSetting();
|
||||
|
||||
function initDevelopmentMode(){
|
||||
$.get("/api/proxy/developmentMode", function(data){
|
||||
if (data === true){
|
||||
$("#developmentMode").checkbox("set checked")
|
||||
}else{
|
||||
$("#developmentMode").checkbox("set unchecked")
|
||||
}
|
||||
|
||||
//Bind change events
|
||||
$("#developmentMode").off("change").on("change", function(data){
|
||||
let enableDevMode = ($(this).find("input[type='checkbox']")[0].checked);
|
||||
$.get("/api/proxy/developmentMode?enable=" + enableDevMode, function(data){
|
||||
if (enableDevMode){
|
||||
msgbox("Development mode enabled");
|
||||
}else{
|
||||
msgbox("Development mode disabled");
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
initDevelopmentMode();
|
||||
|
||||
function initTlsSetting(){
|
||||
$.get("/api/cert/tls", function(data){
|
||||
if (data == true){
|
||||
|
@ -3,11 +3,33 @@
|
||||
<h2>TCP Proxy</h2>
|
||||
<p>Proxy traffic flow on layer 3 via TCP/IP</p>
|
||||
</div>
|
||||
<button class="ui basic orange button" id="addProxyConfigButton"><i class="ui add icon"></i> Add Proxy Config</button>
|
||||
<button class="ui basic circular right floated icon button" onclick="initProxyConfigList();" title="Refresh List"><i class="ui green refresh icon"></i></button>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment" id="addproxyConfig" style="display:none;">
|
||||
<h3>TCP Proxy Config</h3>
|
||||
<div class="ui basic segment" style="margin-top: 0;">
|
||||
<h4>TCP Proxy Rules</h4>
|
||||
<p>A list of TCP proxy rules created on this host. To enable them, use the toggle button on the right.</p>
|
||||
<div style="overflow-x: auto; min-height: 400px;">
|
||||
<table id="proxyTable" class="ui celled unstackable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Port/Addr A</th>
|
||||
<th>Port/Addr B</th>
|
||||
<th>Mode</th>
|
||||
<th>Timeout (s)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button class="ui basic right floated button" onclick="initProxyConfigList();" title="Refresh List"><i class="ui green refresh icon"></i>Refresh</button>
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment" id="addproxyConfig">
|
||||
<h4>Add or Edit TCP Proxy</h4>
|
||||
<p>Create or edit a new proxy instance</p>
|
||||
<form id="tcpProxyForm" class="ui form">
|
||||
<div class="field" style="display:none;">
|
||||
@ -39,11 +61,10 @@
|
||||
<option value="starter">Starter</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="addTcpProxyButton" class="ui basic button" type="submit"><i class="ui blue add icon"></i> Create</button>
|
||||
<button id="editTcpProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);"><i class="ui blue save icon"></i> Update</button>
|
||||
<button id="addTcpProxyButton" class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
|
||||
<button id="editTcpProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);" style="display:none;"><i class="ui green check icon"></i> Update</button>
|
||||
<button class="ui basic red button" onclick="event.preventDefault(); cancelTCPProxyEdit(event);"><i class="ui red remove icon"></i> Cancel</button>
|
||||
<div class="ui basic inverted segment" style="background-color: #414141; border-radius: 0.6em;">
|
||||
<h3>Proxy Mode Instructions</h3>
|
||||
<div class="ui basic inverted segment" style="background: var(--theme_background_inverted); border-radius: 0.6em;">
|
||||
<p>TCP Proxy support the following TCP sockets proxy modes</p>
|
||||
<table class="ui celled padded inverted basic table">
|
||||
<thead>
|
||||
@ -108,28 +129,6 @@
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div class="ui basic segment" style="margin-top: 0;">
|
||||
<h3>TCP Proxy Configs</h3>
|
||||
<p>A list of TCP proxy configs created on this host. To enable them, use the toggle button on the right.</p>
|
||||
<div style="overflow-x: auto; min-height: 400px;">
|
||||
<table id="proxyTable" class="ui celled unstackable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Port/Addr A</th>
|
||||
<th>Port/Addr B</th>
|
||||
<th>Mode</th>
|
||||
<th>Timeout (s)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
@ -138,6 +137,13 @@
|
||||
$("#tcpProxyForm .dropdown").dropdown();
|
||||
$('#tcpProxyForm').on('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
//Check if update mode
|
||||
if ($("#editTcpProxyButton").is(":visible")){
|
||||
confirmEditTCPProxyConfig(event);
|
||||
return;
|
||||
}
|
||||
|
||||
var form = $(this);
|
||||
|
||||
var formValid = validateTCPProxyConfig(form);
|
||||
@ -165,23 +171,16 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//Add proxy button pressed. Show add TCP proxy menu
|
||||
$("#addProxyConfigButton").on("click", function(){
|
||||
$('#addproxyConfig').slideToggle('fast');
|
||||
$("#addTcpProxyButton").show();
|
||||
$("#editTcpProxyButton").hide();
|
||||
});
|
||||
|
||||
|
||||
function clearTCPProxyAddEditForm(){
|
||||
$('#tcpProxyForm input, #tcpProxyForm select').val('');
|
||||
$('#tcpProxyForm select').dropdown('clear');
|
||||
}
|
||||
|
||||
function cancelTCPProxyEdit(event) {
|
||||
function cancelTCPProxyEdit(event=undefined) {
|
||||
clearTCPProxyAddEditForm();
|
||||
$('#addproxyConfig').slideUp('fast');
|
||||
$("#addTcpProxyButton").show();
|
||||
$("#editTcpProxyButton").hide();
|
||||
}
|
||||
|
||||
function validateTCPProxyConfig(form){
|
||||
@ -231,7 +230,7 @@
|
||||
proxyConfigs.forEach(function(config) {
|
||||
var runningLogo = 'Stopped';
|
||||
var runningClass = "stopped";
|
||||
var startButton = `<button onclick="startTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="play icon"></i> Start Proxy</button>`;
|
||||
var startButton = `<button onclick="startTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="green play icon"></i> Start Proxy</button>`;
|
||||
if (config.Running){
|
||||
runningLogo = 'Running';
|
||||
startButton = `<button onclick="stopTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="red stop icon"></i> Stop Proxy</button>`;
|
||||
@ -354,8 +353,8 @@
|
||||
msgbox("Config Updated");
|
||||
}
|
||||
initProxyConfigList();
|
||||
clearTCPProxyAddEditForm();
|
||||
$("#addproxyConfig").slideUp("fast");
|
||||
cancelTCPProxyEdit();
|
||||
|
||||
},
|
||||
error: function() {
|
||||
msgbox('An error occurred while processing the request', false);
|
||||
|
@ -3,166 +3,179 @@
|
||||
<h2>Utilities</h2>
|
||||
<p>You might find these tools or information helpful when setting up your gateway server</p>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
|
||||
<div class="selfauthOnly">
|
||||
<h3>Account Management</h3>
|
||||
<p>Functions to help management the current account</p>
|
||||
<div class="ui basic segment">
|
||||
<h5><i class="chevron down icon"></i> Change Password</h5>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Current Password</label>
|
||||
<input type="password" name="oldPassword" placeholder="Current Password">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>New Password</label>
|
||||
<input type="password" name="newPassword" placeholder="New Password">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Confirm New Password</label>
|
||||
<input type="password" name="confirmNewPassword" placeholder="Confirm New Password">
|
||||
</div>
|
||||
<button class="ui basic button" onclick="changePassword()"><i class="ui teal key icon"></i> Change Password</button>
|
||||
</div>
|
||||
<div class="ui top attached tabular menu">
|
||||
<a class="nettools item active" data-tab="tab1"><i class="ui user circle blue icon"></i> Accounts</a>
|
||||
<a class="nettools item" data-tab="tab2">Toolbox</a>
|
||||
<a class="nettools item" data-tab="tab3">System</a>
|
||||
</div>
|
||||
|
||||
<div id="passwordChangeSuccMsg" class="ui green message" style="display:none;">
|
||||
<i class="ui circle checkmark green icon "></i> Password Updated
|
||||
<div class="ui bottom attached tab segment nettoolstab active" data-tab="tab1">
|
||||
<div class="extAuthOnly" style="display:none;">
|
||||
<div class="ui basic segment">
|
||||
<i class="ui green circle check icon"></i> Account options are not available due to -noauth flag is set to true.
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h3>Forget Password Email</h3>
|
||||
<p>The following SMTP settings help you to reset your password in case you have lost your management account.</p>
|
||||
<form id="email-form" class="ui form">
|
||||
<div class="field">
|
||||
<label>Sender Address</label>
|
||||
<input type="text" name="senderAddr" placeholder="E.g. noreply@zoraxy.arozos.com">
|
||||
</div>
|
||||
<div class="field">
|
||||
<p><i class="caret down icon"></i> Connection setup for email service provider</p>
|
||||
<div class="fields">
|
||||
<div class="twelve wide field">
|
||||
<label>SMTP Provider Hostname</label>
|
||||
<input type="text" name="hostname" placeholder="E.g. mail.gandi.net">
|
||||
<div class="selfauthOnly">
|
||||
<h3>Change Password</h3>
|
||||
<p>Update the current account credentials</p>
|
||||
<div class="ui basic segment">
|
||||
<h5><i class="chevron down icon"></i> Change Password</h5>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Current Password</label>
|
||||
<input type="password" name="oldPassword" placeholder="Current Password">
|
||||
</div>
|
||||
|
||||
<div class="four wide field">
|
||||
<label>Port</label>
|
||||
<input type="number" name="port" placeholder="E.g. 587" value="587">
|
||||
<div class="field">
|
||||
<label>New Password</label>
|
||||
<input type="password" name="newPassword" placeholder="New Password">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Confirm New Password</label>
|
||||
<input type="password" name="confirmNewPassword" placeholder="Confirm New Password">
|
||||
</div>
|
||||
<button class="ui basic button" onclick="changePassword()"><i class="ui teal key icon"></i> Change Password</button>
|
||||
</div>
|
||||
|
||||
<div id="passwordChangeSuccMsg" class="ui green message" style="display:none;">
|
||||
<i class="ui circle checkmark green icon "></i> Password Updated
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p><i class="caret down icon"></i> Credentials for SMTP server authentications</p>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>Sender Username</label>
|
||||
<input type="text" name="username" placeholder="E.g. admin">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Sender Domain</label>
|
||||
<div class="ui labeled input">
|
||||
<div class="ui basic label">
|
||||
@
|
||||
</div>
|
||||
<input type="text" name="domain" min="1" max="65534" placeholder="E.g. arozos.com">
|
||||
<div class="ui divider"></div>
|
||||
<h3>Forget Password Email</h3>
|
||||
<p>The following SMTP settings help you to reset your password in case you have lost your management account.</p>
|
||||
<form id="email-form" class="ui form">
|
||||
<div class="field">
|
||||
<label>Sender Address</label>
|
||||
<input type="text" name="senderAddr" placeholder="E.g. noreply@zoraxy.arozos.com">
|
||||
</div>
|
||||
<div class="field">
|
||||
<p><i class="caret down icon"></i> Connection setup for email service provider</p>
|
||||
<div class="fields">
|
||||
<div class="twelve wide field">
|
||||
<label>SMTP Provider Hostname</label>
|
||||
<input type="text" name="hostname" placeholder="E.g. mail.gandi.net">
|
||||
</div>
|
||||
|
||||
<div class="four wide field">
|
||||
<label>Port</label>
|
||||
<input type="number" name="port" placeholder="E.g. 587" value="587">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Sender Password</label>
|
||||
<input type="password" name="password" placeholder="Password of the email account">
|
||||
<small>Leave empty to use the old password</small>
|
||||
</div>
|
||||
|
||||
<p> <i class="caret down icon"></i> Email for sending account reset link</p>
|
||||
<div class="field">
|
||||
<label>Admin / Receiver Address</label>
|
||||
<input type="text" name="recvAddr" placeholder="E.g. personalEmail@gmail.com">
|
||||
</div>
|
||||
|
||||
<button class="ui basic button" type="submit"><i class="blue save icon"></i> Set SMTP Configs</button>
|
||||
<button class="ui basic button" onclick="event.preventDefault(); sendTestEmail(this);"><i class="teal mail icon"></i> Send Test Email</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h3> IP Address to CIDR</h3>
|
||||
<p>No experience with CIDR notations? Here are some tools you can use to make setting up easier.</p>
|
||||
<div class="ui basic segment">
|
||||
<h5><i class="chevron down icon"></i> IP Range to CIDR Conversion</h5>
|
||||
<div class="ui message">
|
||||
<i class="info circle icon"></i> Note that the CIDR generated here covers additional IP address before or after the given range. If you need more details settings, please use CIDR with a smaller range and add additional IPs for detail range adjustment.
|
||||
</div>
|
||||
<div class="ui input">
|
||||
<input type="text" placeholder="Start IP" id="startIpInput">
|
||||
</div>
|
||||
<div class="ui input">
|
||||
<input type="text" placeholder="End IP" id="endIpInput">
|
||||
</div>
|
||||
<br>
|
||||
<button style="margin-top: 0.6em;" class="ui basic button" onclick="convertToCIDR()">Convert</button>
|
||||
<p>Results: <div id="cidrOutput">N/A</div></p>
|
||||
</div>
|
||||
|
||||
<div class="ui basic segment">
|
||||
<h5><i class="chevron down icon"></i> CIDR to IP Range Conversion</h5>
|
||||
<div class="ui action input">
|
||||
<input type="text" placeholder="CIDR" id="cidrInput">
|
||||
<button class="ui basic button" onclick="convertToIPRange()">Convert</button>
|
||||
</div>
|
||||
<p>Results: <div id="ipRangeOutput">N/A</div></p>
|
||||
</div>
|
||||
<!-- Config Tools -->
|
||||
<div class="ui divider"></div>
|
||||
<h3>System Backup & Restore</h3>
|
||||
<p>Options related to system backup, migrate and restore.</p>
|
||||
<button class="ui basic button" onclick="showSideWrapper('snippet/configTools.html');">Open Config Tools</button>
|
||||
<!-- System Information -->
|
||||
<div class="ui divider"></div>
|
||||
<div id="zoraxyinfo">
|
||||
<h3 class="ui header">
|
||||
System Information
|
||||
</h3>
|
||||
<p>Basic information about this zoraxy host</p>
|
||||
<table class="ui very basic collapsing celled table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Host UUID</td>
|
||||
<td class="uuid"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td class="version"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Build</td>
|
||||
<td class="development"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Running Since</td>
|
||||
<td class="boottime"></td>
|
||||
</tr>
|
||||
|
||||
<div class="field">
|
||||
<p><i class="caret down icon"></i> Credentials for SMTP server authentications</p>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>Sender Username</label>
|
||||
<input type="text" name="username" placeholder="E.g. admin">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Sender Domain</label>
|
||||
<div class="ui labeled input">
|
||||
<div class="ui basic label">
|
||||
@
|
||||
</div>
|
||||
<input type="text" name="domain" min="1" max="65534" placeholder="E.g. arozos.com">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Sender Password</label>
|
||||
<input type="password" name="password" placeholder="Password of the email account">
|
||||
<small>Leave empty to use the old password</small>
|
||||
</div>
|
||||
|
||||
<p> <i class="caret down icon"></i> Email for sending account reset link</p>
|
||||
<div class="field">
|
||||
<label>Admin / Receiver Address</label>
|
||||
<input type="text" name="recvAddr" placeholder="E.g. personalEmail@gmail.com">
|
||||
</div>
|
||||
|
||||
<tr>
|
||||
<td>ZeroTier Linked</td>
|
||||
<td class="zt"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Enable SSH Loopback</td>
|
||||
<td class="sshlb"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Zoraxy is developed by tobychui for <a href="//imuslab.com" target="_blank">imuslab</a> and open source under <a href="https://www.gnu.org/licenses/agpl-3.0.txt">AGPL</a></p>
|
||||
<button class="ui basic button" type="submit"><i class="blue save icon"></i> Set SMTP Configs</button>
|
||||
<button class="ui basic button" onclick="event.preventDefault(); sendTestEmail(this);"><i class="teal mail icon"></i> Send Test Email</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui bottom attached tab segment nettoolstab" data-tab="tab2">
|
||||
<h3> IP Address to CIDR</h3>
|
||||
<p>No experience with CIDR notations? Here are some tools you can use to make setting up easier.</p>
|
||||
<div class="ui basic segment">
|
||||
<h5><i class="chevron down icon"></i> IP Range to CIDR Conversion</h5>
|
||||
<div class="ui message">
|
||||
<i class="info circle icon"></i> Note that the CIDR generated here covers additional IP address before or after the given range. If you need more details settings, please use CIDR with a smaller range and add additional IPs for detail range adjustment.
|
||||
</div>
|
||||
<div class="ui input">
|
||||
<input type="text" placeholder="Start IP" id="startIpInput">
|
||||
</div>
|
||||
<div class="ui input">
|
||||
<input type="text" placeholder="End IP" id="endIpInput">
|
||||
</div>
|
||||
<br>
|
||||
<button style="margin-top: 0.6em;" class="ui basic button" onclick="convertToCIDR()">Convert</button>
|
||||
<p>Results: <div id="cidrOutput">N/A</div></p>
|
||||
</div>
|
||||
|
||||
<div class="ui basic segment">
|
||||
<h5><i class="chevron down icon"></i> CIDR to IP Range Conversion</h5>
|
||||
<div class="ui action input">
|
||||
<input type="text" placeholder="CIDR" id="cidrInput">
|
||||
<button class="ui basic button" onclick="convertToIPRange()">Convert</button>
|
||||
</div>
|
||||
<p>Results: <div id="ipRangeOutput">N/A</div></p>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div class="ui bottom attached tab segment nettoolstab" data-tab="tab3">
|
||||
<!-- Config Tools -->
|
||||
<h3>System Backup & Restore</h3>
|
||||
<p>Options related to system backup, migrate and restore.</p>
|
||||
<button class="ui basic button" onclick="showSideWrapper('snippet/configTools.html');">Open Config Tools</button>
|
||||
<div class="ui divider"></div>
|
||||
<!-- System Information -->
|
||||
<div id="zoraxyinfo">
|
||||
<h3 class="ui header">
|
||||
System Information
|
||||
</h3>
|
||||
<p>Basic information about this zoraxy host</p>
|
||||
<table class="ui very basic collapsing celled table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Host UUID</td>
|
||||
<td class="uuid"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td class="version"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Build</td>
|
||||
<td class="development"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Running Since</td>
|
||||
<td class="boottime"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>ZeroTier Linked</td>
|
||||
<td class="zt"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Enable SSH Loopback</td>
|
||||
<td class="sshlb"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Zoraxy is developed by tobychui for <a href="//imuslab.com" target="_blank">imuslab</a> and open source under <a href="https://www.gnu.org/licenses/agpl-3.0.txt">AGPL</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
$('.menu .nettools.item').tab();
|
||||
/*
|
||||
Account Password utilities
|
||||
*/
|
||||
@ -171,6 +184,7 @@
|
||||
if (data == 0){
|
||||
//Using external auth manager. Hide options
|
||||
$(".selfauthOnly").hide();
|
||||
$(".extAuthOnly").show();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
<h4>Edit Virtual Directory Routing Rules</h4>
|
||||
<p>The following are the list of Virtual Directories currently handled by the host router above</p>
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em;">
|
||||
<table class="ui celled sortable basic unstackable compact table">
|
||||
<table class="ui celled sortable unstackable compact table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Virtual Directory</th>
|
||||
@ -162,6 +162,7 @@
|
||||
//List the vdirs
|
||||
console.log(data);
|
||||
data.forEach(vdir => {
|
||||
var tlsIcon = "";
|
||||
if (vdir.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
if (vdir.SkipCertValidations){
|
||||
|
@ -4,7 +4,7 @@
|
||||
<p>A simple static web server that serve html css and js files</p>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment">
|
||||
<div class="ui basic segment webservRunningStateWrapper">
|
||||
<h4 class="ui header" id="webservRunningState">
|
||||
<i class="green circle icon"></i>
|
||||
<div class="content">
|
||||
@ -102,12 +102,14 @@
|
||||
function setWebServerRunningState(running){
|
||||
if (running){
|
||||
$("#webserv_enable").parent().checkbox("set checked");
|
||||
$("#webservRunningState").find("i").attr("class", "green circle icon");
|
||||
$("#webservRunningState").find("i").attr("class", "white circle check icon");
|
||||
$("#webservRunningState").find(".webserv_status").text("Running");
|
||||
$(".webservRunningStateWrapper").addClass("enabled")
|
||||
}else{
|
||||
$("#webserv_enable").parent().checkbox("set unchecked");
|
||||
$("#webservRunningState").find("i").attr("class", "red circle icon");
|
||||
$("#webservRunningState").find("i").attr("class", "white circle times icon");
|
||||
$("#webservRunningState").find(".webserv_status").text("Stopped");
|
||||
$(".webservRunningStateWrapper").removeClass("enabled")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,9 +26,12 @@
|
||||
<div class="ui right floated buttons menutoggle" style="padding-top: 2px;">
|
||||
<button class="ui basic icon button" onclick="$('.toolbar').fadeToggle('fast');"><i class="content icon"></i></button>
|
||||
</div>
|
||||
<div class="ui right floated buttons" style="padding-top: 2px;">
|
||||
<button class="ui basic icon button" onclick="logout();"><i class="sign-out icon"></i></button>
|
||||
<div class="ui right floated buttons" style="padding-top: 2px; padding-right: 0.4em;">
|
||||
<button class="ui basic white icon button" onclick="logout();"><i class="sign-out icon"></i></button>
|
||||
</div>
|
||||
<!-- <div class="ui right floated buttons" style="padding-top: 2px;">
|
||||
<button id="themeColorButton" class="ui icon button" onclick="toggleTheme();"><i class="sun icon"></i></button>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<div class="toolbar">
|
||||
@ -52,8 +55,6 @@
|
||||
<a class="item" tag="tcpprox">
|
||||
<i class="simplistic exchange icon"></i> TCP Proxy
|
||||
</a>
|
||||
|
||||
|
||||
<div class="ui divider menudivider">Access & Connections</div>
|
||||
<a class="item" tag="cert">
|
||||
<i class="simplistic lock icon"></i> TLS / SSL certificates
|
||||
@ -71,14 +72,13 @@
|
||||
<a class="item" tag="zgrok">
|
||||
<i class="simplistic podcast icon"></i> Service Expose Proxy
|
||||
</a>
|
||||
|
||||
<div class="ui divider menudivider">Hosting</div>
|
||||
<div class="ui divider menudivider">Others</div>
|
||||
<a class="item" tag="webserv">
|
||||
<i class="simplistic globe icon"></i> Static Web Server
|
||||
</a>
|
||||
|
||||
<div class="ui divider menudivider">Others</div>
|
||||
|
||||
<a class="item" tag="utm">
|
||||
<i class="simplistic time icon"></i> Uptime Monitor
|
||||
</a>
|
||||
<a class="item" tag="networktool">
|
||||
<i class="simplistic terminal icon"></i> Network Tools
|
||||
</a>
|
||||
@ -245,6 +245,16 @@
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTheme(){
|
||||
if ($("body").hasClass("darkTheme")){
|
||||
$("body").removeClass("darkTheme")
|
||||
$("#themeColorButton").html(`<i class="ui moon icon"></i>`);
|
||||
}else{
|
||||
$("body").addClass("darkTheme");
|
||||
$("#themeColorButton").html(`<i class="ui sun icon"></i>`);
|
||||
}
|
||||
}
|
||||
|
||||
function getTabButtonById(targetTabId){
|
||||
let targetTabBtn = undefined;
|
||||
$("#mainmenu").find(".item").each(function(){
|
||||
|
130
src/web/main.css
130
src/web/main.css
@ -2,16 +2,66 @@
|
||||
index.html style overwrite
|
||||
*/
|
||||
:root{
|
||||
--theme_grey: #414141;
|
||||
--theme_lgrey: #f6f6f6;
|
||||
--theme_green: #3c9c63;
|
||||
--theme_fcolor: #979797;
|
||||
--theme_advance: #f8f8f9;
|
||||
|
||||
|
||||
|
||||
--theme_background: linear-gradient(60deg, rgb(84, 58, 183) 0%, rgb(0, 172, 193) 100%);
|
||||
--theme_background_inverted: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%);
|
||||
--theme_green: linear-gradient(270deg, #27e7ff, #00ca52);
|
||||
--theme_red: linear-gradient(203deg, rgba(250,172,38,1) 17%, rgba(202,0,37,1) 78%);
|
||||
}
|
||||
|
||||
/* Theme Color Definations */
|
||||
body:not(.darkTheme){
|
||||
--theme_bg: #f6f6f6;
|
||||
--theme_bg_primary: #ffffff;
|
||||
--theme_bg_secondary: #ffffff;
|
||||
--theme_bg_active: #ececec;
|
||||
--theme_highlight: #a9d1f3;
|
||||
--theme_bg_inverted: #27292d;
|
||||
--theme_advance: #f8f8f9;
|
||||
--item_color: #5e5d5d;
|
||||
--item_color_select: rgba(0,0,0,.87);
|
||||
--text_color: #414141;
|
||||
--input_color: white;
|
||||
--divider_color: #cacaca;
|
||||
--text_color_inverted: #fcfcfc;
|
||||
--button_text_color: #878787;
|
||||
--button_border_color: #dedede;
|
||||
}
|
||||
|
||||
body.darkTheme{
|
||||
--theme_bg: #27292d;
|
||||
--theme_bg_primary: #3d3f47;
|
||||
--theme_bg_secondary: #373a42;
|
||||
--theme_highlight: #6682c4;
|
||||
--theme_bg_active: #292929;
|
||||
--theme_bg_inverted: #f8f8f9;
|
||||
--theme_advance: #333333;
|
||||
--item_color: #cacaca;
|
||||
--text_color: #fcfcfc;
|
||||
--text_color_secondary: #dfdfdf;
|
||||
--input_color: black;
|
||||
--divider_color: #3b3b3b;
|
||||
--item_color_select: rgba(255, 255, 255, 0.87);
|
||||
--text_color_inverted: #414141;
|
||||
--button_text_color: #e9e9e9;
|
||||
--button_border_color: #646464;
|
||||
}
|
||||
|
||||
/* Theme Toggle Css */
|
||||
#themeColorButton{
|
||||
background-color: black;
|
||||
color: var(--text_color_inverted);
|
||||
}
|
||||
|
||||
body.darkTheme #themeColorButton{
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
body{
|
||||
background-color:#f6f6f6;
|
||||
color: #414141;
|
||||
background-color:var(--theme_bg);
|
||||
color: var(--text_color);
|
||||
}
|
||||
|
||||
.functiontab{
|
||||
@ -32,9 +82,9 @@ body{
|
||||
padding: 0.4em;
|
||||
padding-left: 1.2em;
|
||||
padding-right: 1.2em;
|
||||
background-color: #ffffff;
|
||||
background-color: var(--theme_bg_secondary);
|
||||
margin-bottom: 1em;
|
||||
border-bottom: 1px solid rgb(226, 226, 226);
|
||||
border-bottom: 1px solid var(--theme_highlight);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
@ -68,7 +118,7 @@ body{
|
||||
display: inline-block;
|
||||
width: calc(100% - 240px);
|
||||
vertical-align: top;
|
||||
background-color: white;
|
||||
background-color: var(--theme_bg_primary);
|
||||
border-radius: 1em;
|
||||
margin-right: 2em;
|
||||
}
|
||||
@ -301,13 +351,18 @@ body{
|
||||
}
|
||||
|
||||
.ui.menu .item{
|
||||
color: #5e5d5d;
|
||||
color: var(--item_color);
|
||||
}
|
||||
|
||||
.ui.menu .item:hover{
|
||||
color: var(--item_color_select) !important;
|
||||
}
|
||||
|
||||
.ui.segment{
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
|
||||
.ui.secondary.vertical.menu .active.item{
|
||||
background: var(--theme_background);
|
||||
font-weight: 600;
|
||||
@ -318,11 +373,6 @@ body{
|
||||
animation: blinker 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.ui.important.basic.segment{
|
||||
background: linear-gradient(217deg, rgba(234,238,175,1) 16%, rgba(254,255,242,1) 78%);
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
.basic.segment.advanceoptions{
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 1em;
|
||||
@ -522,13 +572,59 @@ body{
|
||||
position: absolute;
|
||||
bottom: 0.3em;
|
||||
left: 0.2em;
|
||||
font-size: 2em;
|
||||
font-size: 1.4em;
|
||||
color:rgb(224, 224, 224);
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/*
|
||||
ACME Renewer Status Panel
|
||||
*/
|
||||
|
||||
.acmeRenewStateWrapper{
|
||||
padding: 1em;
|
||||
border-radius: 1em !important;
|
||||
}
|
||||
|
||||
|
||||
.acmeRenewStateWrapper .ui.header, .acmeRenewStateWrapper .sub.header{
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.acmeRenewStateWrapper:not(.enabled){
|
||||
background: var(--theme_red) !important;
|
||||
}
|
||||
|
||||
|
||||
.acmeRenewStateWrapper.enabled{
|
||||
background: var(--theme_green) !important;
|
||||
}
|
||||
|
||||
/*
|
||||
Static Web Server
|
||||
*/
|
||||
|
||||
.webservRunningStateWrapper{
|
||||
padding: 1em;
|
||||
border-radius: 1em !important;
|
||||
}
|
||||
|
||||
|
||||
.webservRunningStateWrapper .ui.header, .webservRunningStateWrapper .sub.header{
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.webservRunningStateWrapper:not(.enabled){
|
||||
background: var(--theme_red) !important;
|
||||
}
|
||||
|
||||
.webservRunningStateWrapper.enabled{
|
||||
background: var(--theme_green) !important;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Uptime Monitor
|
||||
*/
|
||||
|
@ -191,6 +191,7 @@
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false, 5000);
|
||||
|
||||
}else{
|
||||
parent.msgbox("Email updated");
|
||||
$(btn).html(`<i class="green check icon"></i>`);
|
||||
@ -214,14 +215,18 @@
|
||||
$("#enableCertAutoRenew").parent().checkbox("set unchecked");
|
||||
enableTrigerOnChangeEvent = true;
|
||||
}
|
||||
if (parent && parent.setACMEEnableStates){
|
||||
parent.setACMEEnableStates(!enabled);
|
||||
}
|
||||
}else{
|
||||
$("#enableToggleSucc").stop().finish().fadeIn("fast").delay(3000).fadeOut("fast");
|
||||
if (parent && parent.setACMEEnableStates){
|
||||
parent.setACMEEnableStates(enabled);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (parent && parent.setACMEEnableStates){
|
||||
parent.setACMEEnableStates(enabled);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@
|
||||
</b></p>
|
||||
<form class="ui form" id="uploadForm" action="/api/conf/import" method="POST" enctype="multipart/form-data">
|
||||
<input type="file" name="file" id="fileInput" accept=".zip">
|
||||
<button style="margin-top: 0.6em;" class="ui basic button" type="submit"><i class="ui green upload icon"></i> Upload</button>
|
||||
<button style="margin-top: 0.6em;" class="ui basic button" type="submit"><i class="ui green upload icon"></i> Restore & Exit</button>
|
||||
</form>
|
||||
<small>The current config will be backup to ./conf_old{timestamp}. If you screw something up, you can always restore it by ssh to your host and restore the configs from the old folder.</small>
|
||||
<br><br>
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -151,6 +152,48 @@ func HandleUptimeMonitorListing(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Static Web Server
|
||||
*/
|
||||
|
||||
// Handle port change, if root router is using internal static web server
|
||||
// update the root router as well
|
||||
func HandleStaticWebServerPortChange(w http.ResponseWriter, r *http.Request) {
|
||||
newPort, err := utils.PostInt(r, "port")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid port number given")
|
||||
return
|
||||
}
|
||||
|
||||
if dynamicProxyRouter.Root.DefaultSiteOption == dynamicproxy.DefaultSite_InternalStaticWebServer {
|
||||
//Update the root site as well
|
||||
newDraftingRoot := dynamicProxyRouter.Root.Clone()
|
||||
newDraftingRoot.Domain = "127.0.0.1:" + strconv.Itoa(newPort)
|
||||
activatedNewRoot, err := dynamicProxyRouter.PrepareProxyRoute(newDraftingRoot)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to update root routing rule")
|
||||
return
|
||||
}
|
||||
|
||||
//Replace the root
|
||||
dynamicProxyRouter.Root = activatedNewRoot
|
||||
|
||||
SaveReverseProxyConfig(newDraftingRoot)
|
||||
}
|
||||
|
||||
err = staticWebServer.ChangePort(strconv.Itoa(newPort))
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
/*
|
||||
mDNS Scanning
|
||||
*/
|
||||
|
||||
// Handle listing current registered mdns nodes
|
||||
func HandleMdnsListing(w http.ResponseWriter, r *http.Request) {
|
||||
if mdnsScanner == nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user