mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-04 22:57:20 +02:00
Merge pull request #124 from tobychui/v3.0.2
V3.0.2 Updates Pre-checks on git.hkwtc is working and approved - Added alias for HTTP proxy host names - Added separator support for create new proxy rules (use "," to add alias when creating new proxy rule) - Added HTTP proxy host based access rules - Added EAD Configuration for ACME (by @yeungalan ) - Fixed bug for bypassGlobalTLS endpoint do not support basic-auth - Removed dependencies on management panel css for online font files
This commit is contained in:
commit
2c045f4f40
@ -3,9 +3,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,6 +20,157 @@ import (
|
|||||||
banning / whitelist a specific IP address or country code
|
banning / whitelist a specific IP address or country code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
General Function
|
||||||
|
*/
|
||||||
|
|
||||||
|
func handleListAccessRules(w http.ResponseWriter, r *http.Request) {
|
||||||
|
allAccessRules := accessController.ListAllAccessRules()
|
||||||
|
js, _ := json.Marshal(allAccessRules)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAttachRuleToHost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleid, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
host, err := utils.PostPara(r, "host")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if access rule and proxy rule exists
|
||||||
|
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(host)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid host given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !accessController.AccessRuleExists(ruleid) {
|
||||||
|
utils.SendErrorResponse(w, "access rule not exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update the proxy host acess rule id
|
||||||
|
targetProxyEndpoint.AccessFilterUUID = ruleid
|
||||||
|
targetProxyEndpoint.UpdateToRuntime()
|
||||||
|
err = SaveReverseProxyConfig(targetProxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new access rule, require name and desc only
|
||||||
|
func handleCreateAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleName, err := utils.PostPara(r, "name")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleDesc, _ := utils.PostPara(r, "desc")
|
||||||
|
|
||||||
|
//Filter out injection if any
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
ruleName = p.Sanitize(ruleName)
|
||||||
|
ruleDesc = p.Sanitize(ruleDesc)
|
||||||
|
|
||||||
|
ruleUUID := uuid.New().String()
|
||||||
|
newAccessRule := access.AccessRule{
|
||||||
|
ID: ruleUUID,
|
||||||
|
Name: ruleName,
|
||||||
|
Desc: ruleDesc,
|
||||||
|
BlacklistEnabled: false,
|
||||||
|
WhitelistEnabled: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add it to runtime
|
||||||
|
err = accessController.AddNewAccessRule(&newAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle removing an access rule. All proxy endpoint using this rule will be
|
||||||
|
// set to use the default rule
|
||||||
|
func handleRemoveAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule id given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ruleID == "default" {
|
||||||
|
utils.SendErrorResponse(w, "default access rule cannot be removed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleID = strings.TrimSpace(ruleID)
|
||||||
|
|
||||||
|
//Set all proxy hosts that use this access rule back to using "default"
|
||||||
|
allProxyEndpoints := dynamicProxyRouter.GetProxyEndpointsAsMap()
|
||||||
|
for _, proxyEndpoint := range allProxyEndpoints {
|
||||||
|
if strings.EqualFold(proxyEndpoint.AccessFilterUUID, ruleID) {
|
||||||
|
//This proxy endpoint is using the current access filter.
|
||||||
|
//set it to default
|
||||||
|
proxyEndpoint.AccessFilterUUID = "default"
|
||||||
|
proxyEndpoint.UpdateToRuntime()
|
||||||
|
err = SaveReverseProxyConfig(proxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Unable to save updated proxy endpoint "+proxyEndpoint.RootOrMatchingDomain, err)
|
||||||
|
} else {
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Updated "+proxyEndpoint.RootOrMatchingDomain+" access filter to \"default\"", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove the access rule by ID
|
||||||
|
err = accessController.RemoveAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Access Rule "+ruleID+" removed", nil)
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the name and desc, for other properties use blacklist / whitelist api
|
||||||
|
func handleUpadateAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleName, err := utils.PostPara(r, "name")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleDesc, _ := utils.PostPara(r, "desc")
|
||||||
|
|
||||||
|
//Filter anything weird
|
||||||
|
p := bluemonday.StrictPolicy()
|
||||||
|
ruleName = p.Sanitize(ruleName)
|
||||||
|
ruleDesc = p.Sanitize(ruleDesc)
|
||||||
|
|
||||||
|
err = accessController.UpdateAccessRule(ruleID, ruleName, ruleDesc)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Blacklist Related
|
Blacklist Related
|
||||||
*/
|
*/
|
||||||
@ -28,11 +182,24 @@ func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
|
|||||||
bltype = "country"
|
bltype = "country"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ruleID, err := utils.GetPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
//Use default if not set
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
resulst := []string{}
|
resulst := []string{}
|
||||||
if bltype == "country" {
|
if bltype == "country" {
|
||||||
resulst = geodbStore.GetAllBlacklistedCountryCode()
|
resulst = rule.GetAllBlacklistedCountryCode()
|
||||||
} else if bltype == "ip" {
|
} else if bltype == "ip" {
|
||||||
resulst = geodbStore.GetAllBlacklistedIp()
|
resulst = rule.GetAllBlacklistedIp()
|
||||||
}
|
}
|
||||||
|
|
||||||
js, _ := json.Marshal(resulst)
|
js, _ := json.Marshal(resulst)
|
||||||
@ -47,7 +214,23 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddCountryCodeToBlackList(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.PostPara(r, "comment")
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.AddCountryCodeToBlackList(countryCode, comment)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -59,7 +242,19 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveCountryCodeFromBlackList(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveCountryCodeFromBlackList(countryCode)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -71,7 +266,24 @@ func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddIPToBlackList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.GetPara(r, "comment")
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
rule.AddIPToBlackList(ipAddr, comment)
|
||||||
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -81,23 +293,46 @@ func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveIPFromBlackList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveIPFromBlackList(ipAddr)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
|
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
enable, err := utils.PostPara(r, "enable")
|
enable, _ := utils.PostPara(r, "enable")
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Return the current enabled state
|
ruleID = "default"
|
||||||
currentEnabled := geodbStore.BlacklistEnabled
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable == "" {
|
||||||
|
//enable paramter not set
|
||||||
|
currentEnabled := rule.BlacklistEnabled
|
||||||
js, _ := json.Marshal(currentEnabled)
|
js, _ := json.Marshal(currentEnabled)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else {
|
||||||
if enable == "true" {
|
if enable == "true" {
|
||||||
geodbStore.ToggleBlacklist(true)
|
rule.ToggleBlacklist(true)
|
||||||
} else if enable == "false" {
|
} else if enable == "false" {
|
||||||
geodbStore.ToggleBlacklist(false)
|
rule.ToggleBlacklist(false)
|
||||||
} else {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||||
return
|
return
|
||||||
@ -117,11 +352,22 @@ func handleListWhitelisted(w http.ResponseWriter, r *http.Request) {
|
|||||||
bltype = "country"
|
bltype = "country"
|
||||||
}
|
}
|
||||||
|
|
||||||
resulst := []*geodb.WhitelistEntry{}
|
ruleID, err := utils.GetPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resulst := []*access.WhitelistEntry{}
|
||||||
if bltype == "country" {
|
if bltype == "country" {
|
||||||
resulst = geodbStore.GetAllWhitelistedCountryCode()
|
resulst = rule.GetAllWhitelistedCountryCode()
|
||||||
} else if bltype == "ip" {
|
} else if bltype == "ip" {
|
||||||
resulst = geodbStore.GetAllWhitelistedIp()
|
resulst = rule.GetAllWhitelistedIp()
|
||||||
}
|
}
|
||||||
|
|
||||||
js, _ := json.Marshal(resulst)
|
js, _ := json.Marshal(resulst)
|
||||||
@ -136,11 +382,22 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
comment, _ := utils.PostPara(r, "comment")
|
comment, _ := utils.PostPara(r, "comment")
|
||||||
p := bluemonday.StrictPolicy()
|
p := bluemonday.StrictPolicy()
|
||||||
comment = p.Sanitize(comment)
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
geodbStore.AddCountryCodeToWhitelist(countryCode, comment)
|
rule.AddCountryCodeToWhitelist(countryCode, comment)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -152,7 +409,18 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveCountryCodeFromWhitelist(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveCountryCodeFromWhitelist(countryCode)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -164,11 +432,23 @@ func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
comment, _ := utils.PostPara(r, "comment")
|
comment, _ := utils.PostPara(r, "comment")
|
||||||
p := bluemonday.StrictPolicy()
|
p := bluemonday.StrictPolicy()
|
||||||
comment = p.Sanitize(comment)
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
geodbStore.AddIPToWhiteList(ipAddr, comment)
|
rule.AddIPToWhiteList(ipAddr, comment)
|
||||||
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -178,23 +458,45 @@ func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveIPFromWhiteList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveIPFromWhiteList(ipAddr)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
|
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
enable, err := utils.PostPara(r, "enable")
|
enable, _ := utils.PostPara(r, "enable")
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable == "" {
|
||||||
//Return the current enabled state
|
//Return the current enabled state
|
||||||
currentEnabled := geodbStore.WhitelistEnabled
|
currentEnabled := rule.WhitelistEnabled
|
||||||
js, _ := json.Marshal(currentEnabled)
|
js, _ := json.Marshal(currentEnabled)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else {
|
||||||
if enable == "true" {
|
if enable == "true" {
|
||||||
geodbStore.ToggleWhitelist(true)
|
rule.ToggleWhitelist(true)
|
||||||
} else if enable == "false" {
|
} else if enable == "false" {
|
||||||
geodbStore.ToggleWhitelist(false)
|
rule.ToggleWhitelist(false)
|
||||||
} else {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||||
return
|
return
|
||||||
|
@ -38,7 +38,7 @@ func initACME() *acme.ACMEHandler {
|
|||||||
port = getRandomPort(30000)
|
port = getRandomPort(30000)
|
||||||
}
|
}
|
||||||
|
|
||||||
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port))
|
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the special routing rule for ACME
|
// create the special routing rule for ACME
|
||||||
|
10
src/api.go
10
src/api.go
@ -49,7 +49,9 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
||||||
authRouter.HandleFunc("/api/proxy/toggle", ReverseProxyToggleRuleSet)
|
authRouter.HandleFunc("/api/proxy/toggle", ReverseProxyToggleRuleSet)
|
||||||
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
|
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
|
||||||
|
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
|
||||||
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
||||||
|
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
||||||
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
||||||
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
||||||
@ -87,6 +89,12 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
||||||
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
|
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
|
||||||
|
|
||||||
|
//Access Rules API
|
||||||
|
authRouter.HandleFunc("/api/access/list", handleListAccessRules)
|
||||||
|
authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
|
||||||
|
authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
|
||||||
|
authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
|
||||||
|
authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)
|
||||||
//Blacklist APIs
|
//Blacklist APIs
|
||||||
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
||||||
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
||||||
@ -94,7 +102,6 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
||||||
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
||||||
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
||||||
|
|
||||||
//Whitelist APIs
|
//Whitelist APIs
|
||||||
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
||||||
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
||||||
@ -179,6 +186,7 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/acme"
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
@ -50,7 +51,7 @@ var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
name = "Zoraxy"
|
name = "Zoraxy"
|
||||||
version = "3.0.1"
|
version = "3.0.2"
|
||||||
nodeUUID = "generic"
|
nodeUUID = "generic"
|
||||||
development = false //Set this to false to use embedded web fs
|
development = false //Set this to false to use embedded web fs
|
||||||
bootTime = time.Now().Unix()
|
bootTime = time.Now().Unix()
|
||||||
@ -69,7 +70,8 @@ var (
|
|||||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||||
geodbStore *geodb.Store //GeoIP database, also handle black list and whitelist features
|
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||||
|
accessController *access.Controller //Access controller, handle black list and white list
|
||||||
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||||
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||||
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
||||||
|
217
src/mod/access/access.go
Normal file
217
src/mod/access/access.go
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Access.go
|
||||||
|
|
||||||
|
This module is the new version of access control system
|
||||||
|
where now the blacklist / whitelist are seperated from
|
||||||
|
geodb module
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Create a new access controller to handle blacklist / whitelist
|
||||||
|
func NewAccessController(options *Options) (*Controller, error) {
|
||||||
|
sysdb := options.Database
|
||||||
|
if sysdb == nil {
|
||||||
|
return nil, errors.New("missing database access")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create the config folder if not exists
|
||||||
|
confFolder := options.ConfigFolder
|
||||||
|
if !utils.FileExists(confFolder) {
|
||||||
|
err := os.MkdirAll(confFolder, 0775)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the global access rule if not exists
|
||||||
|
var defaultAccessRule = AccessRule{
|
||||||
|
ID: "default",
|
||||||
|
Name: "Default",
|
||||||
|
Desc: "Default access rule for all HTTP proxy hosts",
|
||||||
|
BlacklistEnabled: false,
|
||||||
|
WhitelistEnabled: false,
|
||||||
|
WhiteListCountryCode: &map[string]string{},
|
||||||
|
WhiteListIP: &map[string]string{},
|
||||||
|
BlackListContryCode: &map[string]string{},
|
||||||
|
BlackListIP: &map[string]string{},
|
||||||
|
}
|
||||||
|
defaultRuleSettingFile := filepath.Join(confFolder, "default.json")
|
||||||
|
if utils.FileExists(defaultRuleSettingFile) {
|
||||||
|
//Load from file
|
||||||
|
defaultRuleBytes, err := os.ReadFile(defaultRuleSettingFile)
|
||||||
|
if err == nil {
|
||||||
|
err = json.Unmarshal(defaultRuleBytes, &defaultAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to parse default routing rule config file. Using default", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Create one
|
||||||
|
js, _ := json.MarshalIndent(defaultAccessRule, "", " ")
|
||||||
|
os.WriteFile(defaultRuleSettingFile, js, 0775)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate a controller object
|
||||||
|
thisController := Controller{
|
||||||
|
DefaultAccessRule: &defaultAccessRule,
|
||||||
|
ProxyAccessRule: &sync.Map{},
|
||||||
|
Options: options,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load all acccess rules from file
|
||||||
|
configFiles, err := filepath.Glob(options.ConfigFolder + "/*.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ProxyAccessRules := sync.Map{}
|
||||||
|
for _, configFile := range configFiles {
|
||||||
|
if filepath.Base(configFile) == "default.json" {
|
||||||
|
//Skip this, as this was already loaded as default
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configContent, err := os.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to load config "+filepath.Base(configFile), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse the config file into AccessRule
|
||||||
|
thisAccessRule := AccessRule{}
|
||||||
|
err = json.Unmarshal(configContent, &thisAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to parse config "+filepath.Base(configFile), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
thisAccessRule.parent = &thisController
|
||||||
|
ProxyAccessRules.Store(thisAccessRule.ID, &thisAccessRule)
|
||||||
|
}
|
||||||
|
thisController.ProxyAccessRule = &ProxyAccessRules
|
||||||
|
|
||||||
|
return &thisController, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the global access rule
|
||||||
|
func (c *Controller) GetGlobalAccessRule() (*AccessRule, error) {
|
||||||
|
if c.DefaultAccessRule == nil {
|
||||||
|
return nil, errors.New("global access rule is not set")
|
||||||
|
}
|
||||||
|
return c.DefaultAccessRule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load access rules to runtime, require rule ID
|
||||||
|
func (c *Controller) GetAccessRuleByID(accessRuleID string) (*AccessRule, error) {
|
||||||
|
if accessRuleID == "default" || accessRuleID == "" {
|
||||||
|
return c.DefaultAccessRule, nil
|
||||||
|
}
|
||||||
|
//Load from sync.Map, should be O(1)
|
||||||
|
targetRule, ok := c.ProxyAccessRule.Load(accessRuleID)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("target access rule not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
ar, ok := targetRule.(*AccessRule)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("assertion of access rule failed, version too old?")
|
||||||
|
}
|
||||||
|
return ar, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all the access rules currently in runtime, including default
|
||||||
|
func (c *Controller) ListAllAccessRules() []*AccessRule {
|
||||||
|
results := []*AccessRule{c.DefaultAccessRule}
|
||||||
|
c.ProxyAccessRule.Range(func(key, value interface{}) bool {
|
||||||
|
results = append(results, value.(*AccessRule))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an access rule exists given the rule id
|
||||||
|
func (c *Controller) AccessRuleExists(ruleID string) bool {
|
||||||
|
r, _ := c.GetAccessRuleByID(ruleID)
|
||||||
|
if r != nil {
|
||||||
|
//An access rule with identical ID exists
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new access rule to runtime and save it to file
|
||||||
|
func (c *Controller) AddNewAccessRule(newRule *AccessRule) error {
|
||||||
|
r, _ := c.GetAccessRuleByID(newRule.ID)
|
||||||
|
if r != nil {
|
||||||
|
//An access rule with identical ID exists
|
||||||
|
return errors.New("access rule already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the blacklist and whitelist are populated with empty map
|
||||||
|
if newRule.BlackListContryCode == nil {
|
||||||
|
newRule.BlackListContryCode = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.BlackListIP == nil {
|
||||||
|
newRule.BlackListIP = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.WhiteListCountryCode == nil {
|
||||||
|
newRule.WhiteListCountryCode = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.WhiteListIP == nil {
|
||||||
|
newRule.WhiteListIP = &map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add access rule to runtime
|
||||||
|
newRule.parent = c
|
||||||
|
c.ProxyAccessRule.Store(newRule.ID, newRule)
|
||||||
|
|
||||||
|
//Save rule to file
|
||||||
|
newRule.SaveChanges()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the access rule meta info.
|
||||||
|
func (c *Controller) UpdateAccessRule(ruleID string, name string, desc string) error {
|
||||||
|
targetAccessRule, err := c.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
///Update the name and desc
|
||||||
|
targetAccessRule.Name = name
|
||||||
|
targetAccessRule.Desc = desc
|
||||||
|
|
||||||
|
//Overwrite the rule currently in sync map
|
||||||
|
if ruleID == "default" {
|
||||||
|
c.DefaultAccessRule = targetAccessRule
|
||||||
|
} else {
|
||||||
|
c.ProxyAccessRule.Store(ruleID, targetAccessRule)
|
||||||
|
}
|
||||||
|
return targetAccessRule.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the access rule by its id
|
||||||
|
func (c *Controller) RemoveAccessRuleByID(ruleID string) error {
|
||||||
|
if !c.AccessRuleExists(ruleID) {
|
||||||
|
return errors.New("access rule not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Default cannot be removed
|
||||||
|
if ruleID == "default" {
|
||||||
|
return errors.New("default access rule cannot be removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove it
|
||||||
|
return c.DeleteAccessRuleByID(ruleID)
|
||||||
|
}
|
153
src/mod/access/accessRule.go
Normal file
153
src/mod/access/accessRule.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check both blacklist and whitelist for access for both geoIP and ip / CIDR ranges
|
||||||
|
func (s *AccessRule) AllowIpAccess(ipaddr string) bool {
|
||||||
|
if s.IsBlacklisted(ipaddr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.IsWhitelisted(ipaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check both blacklist and whitelist for access using net.Conn
|
||||||
|
func (s *AccessRule) AllowConnectionAccess(conn net.Conn) bool {
|
||||||
|
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||||
|
return s.AllowIpAccess(addr.IP.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle black list
|
||||||
|
func (s *AccessRule) ToggleBlacklist(enabled bool) {
|
||||||
|
s.BlacklistEnabled = enabled
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggel white list
|
||||||
|
func (s *AccessRule) ToggleWhitelist(enabled bool) {
|
||||||
|
s.WhitelistEnabled = enabled
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if a IP address is blacklisted, in either country or IP blacklist
|
||||||
|
IsBlacklisted default return is false (allow access)
|
||||||
|
*/
|
||||||
|
func (s *AccessRule) IsBlacklisted(ipAddr string) bool {
|
||||||
|
if !s.BlacklistEnabled {
|
||||||
|
//Blacklist not enabled. Always return false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipAddr == "" {
|
||||||
|
//Unable to get the target IP address
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsCountryCodeBlacklisted(countryCode.CountryIsoCode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsIPBlacklisted(ipAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsWhitelisted check if a given IP address is in the current
|
||||||
|
server's white list.
|
||||||
|
|
||||||
|
Note that the Whitelist default result is true even
|
||||||
|
when encountered error
|
||||||
|
*/
|
||||||
|
func (s *AccessRule) IsWhitelisted(ipAddr string) bool {
|
||||||
|
if !s.WhitelistEnabled {
|
||||||
|
//Whitelist not enabled. Always return true (allow access)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipAddr == "" {
|
||||||
|
//Unable to get the target IP address, assume ok
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsCountryCodeWhitelisted(countryCode.CountryIsoCode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsIPWhitelisted(ipAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilities function */
|
||||||
|
|
||||||
|
// Update the current access rule to json file
|
||||||
|
func (s *AccessRule) SaveChanges() error {
|
||||||
|
if s.parent == nil {
|
||||||
|
return errors.New("save failed: access rule detached from controller")
|
||||||
|
}
|
||||||
|
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
|
||||||
|
js, err := json.MarshalIndent(s, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(saveTarget, js, 0775)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete this access rule, this will only delete the config file.
|
||||||
|
// for runtime delete, use DeleteAccessRuleByID from parent Controller
|
||||||
|
func (s *AccessRule) DeleteConfigFile() error {
|
||||||
|
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
|
||||||
|
return os.Remove(saveTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the access rule by given ID
|
||||||
|
func (c *Controller) DeleteAccessRuleByID(accessRuleID string) error {
|
||||||
|
targetAccessRule, err := c.GetAccessRuleByID(accessRuleID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete config file associated with this access rule
|
||||||
|
err = targetAccessRule.DeleteConfigFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete the access rule in runtime
|
||||||
|
c.ProxyAccessRule.Delete(accessRuleID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a deep copy object of the access rule list
|
||||||
|
func deepCopy(valueList map[string]string) map[string]string {
|
||||||
|
result := map[string]string{}
|
||||||
|
js, _ := json.Marshal(valueList)
|
||||||
|
json.Unmarshal(js, &result)
|
||||||
|
return result
|
||||||
|
}
|
75
src/mod/access/blacklist.go
Normal file
75
src/mod/access/blacklist.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Blacklist.go
|
||||||
|
|
||||||
|
This script store the blacklist related functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Geo Blacklist
|
||||||
|
func (s *AccessRule) AddCountryCodeToBlackList(countryCode string, comment string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
|
||||||
|
newBlacklistCountryCode[countryCode] = comment
|
||||||
|
s.BlackListContryCode = &newBlacklistCountryCode
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveCountryCodeFromBlackList(countryCode string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
|
||||||
|
delete(newBlacklistCountryCode, countryCode)
|
||||||
|
s.BlackListContryCode = &newBlacklistCountryCode
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsCountryCodeBlacklisted(countryCode string) bool {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
blacklistMap := *s.BlackListContryCode
|
||||||
|
_, ok := blacklistMap[countryCode]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllBlacklistedCountryCode() []string {
|
||||||
|
bannedCountryCodes := []string{}
|
||||||
|
blacklistMap := *s.BlackListContryCode
|
||||||
|
for cc, _ := range blacklistMap {
|
||||||
|
bannedCountryCodes = append(bannedCountryCodes, cc)
|
||||||
|
}
|
||||||
|
return bannedCountryCodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP Blacklsits
|
||||||
|
func (s *AccessRule) AddIPToBlackList(ipAddr string, comment string) {
|
||||||
|
newBlackListIP := deepCopy(*s.BlackListIP)
|
||||||
|
newBlackListIP[ipAddr] = comment
|
||||||
|
s.BlackListIP = &newBlackListIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveIPFromBlackList(ipAddr string) {
|
||||||
|
newBlackListIP := deepCopy(*s.BlackListIP)
|
||||||
|
delete(newBlackListIP, ipAddr)
|
||||||
|
s.BlackListIP = &newBlackListIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllBlacklistedIp() []string {
|
||||||
|
bannedIps := []string{}
|
||||||
|
blacklistMap := *s.BlackListIP
|
||||||
|
for ip, _ := range blacklistMap {
|
||||||
|
bannedIps = append(bannedIps, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bannedIps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsIPBlacklisted(ipAddr string) bool {
|
||||||
|
IPBlacklist := *s.BlackListIP
|
||||||
|
_, ok := IPBlacklist[ipAddr]
|
||||||
|
return ok
|
||||||
|
}
|
38
src/mod/access/typedef.go
Normal file
38
src/mod/access/typedef.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Logger logger.Logger
|
||||||
|
ConfigFolder string //Path for storing config files
|
||||||
|
GeoDB *geodb.Store //For resolving country code
|
||||||
|
Database *database.Database //System key-value database
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessRule struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Desc string
|
||||||
|
BlacklistEnabled bool
|
||||||
|
WhitelistEnabled bool
|
||||||
|
|
||||||
|
/* Whitelist Blacklist Table, value is comment if supported */
|
||||||
|
WhiteListCountryCode *map[string]string
|
||||||
|
WhiteListIP *map[string]string
|
||||||
|
BlackListContryCode *map[string]string
|
||||||
|
BlackListIP *map[string]string
|
||||||
|
|
||||||
|
parent *Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
DefaultAccessRule *AccessRule
|
||||||
|
ProxyAccessRule *sync.Map
|
||||||
|
Options *Options
|
||||||
|
}
|
112
src/mod/access/whitelist.go
Normal file
112
src/mod/access/whitelist.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Whitelist.go
|
||||||
|
|
||||||
|
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 *AccessRule) AddCountryCodeToWhitelist(countryCode string, comment string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
|
||||||
|
newWhitelistCC[countryCode] = comment
|
||||||
|
s.WhiteListCountryCode = &newWhitelistCC
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveCountryCodeFromWhitelist(countryCode string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
|
||||||
|
delete(newWhitelistCC, countryCode)
|
||||||
|
s.WhiteListCountryCode = &newWhitelistCC
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsCountryCodeWhitelisted(countryCode string) bool {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
whitelistCC := *s.WhiteListCountryCode
|
||||||
|
_, ok := whitelistCC[countryCode]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllWhitelistedCountryCode() []*WhitelistEntry {
|
||||||
|
whitelistedCountryCode := []*WhitelistEntry{}
|
||||||
|
whitelistCC := *s.WhiteListCountryCode
|
||||||
|
for cc, comment := range whitelistCC {
|
||||||
|
whitelistedCountryCode = append(whitelistedCountryCode, &WhitelistEntry{
|
||||||
|
EntryType: EntryType_CountryCode,
|
||||||
|
CC: cc,
|
||||||
|
Comment: comment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return whitelistedCountryCode
|
||||||
|
}
|
||||||
|
|
||||||
|
//IP Whitelist
|
||||||
|
|
||||||
|
func (s *AccessRule) AddIPToWhiteList(ipAddr string, comment string) {
|
||||||
|
newWhitelistIP := deepCopy(*s.WhiteListIP)
|
||||||
|
newWhitelistIP[ipAddr] = comment
|
||||||
|
s.WhiteListIP = &newWhitelistIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveIPFromWhiteList(ipAddr string) {
|
||||||
|
newWhitelistIP := deepCopy(*s.WhiteListIP)
|
||||||
|
delete(newWhitelistIP, ipAddr)
|
||||||
|
s.WhiteListIP = &newWhitelistIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsIPWhitelisted(ipAddr string) bool {
|
||||||
|
//Check for IP wildcard and CIRD rules
|
||||||
|
WhitelistedIP := *s.WhiteListIP
|
||||||
|
for ipOrCIDR, _ := range WhitelistedIP {
|
||||||
|
wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR)
|
||||||
|
if wildcardMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR)
|
||||||
|
if cidrMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllWhitelistedIp() []*WhitelistEntry {
|
||||||
|
whitelistedIp := []*WhitelistEntry{}
|
||||||
|
currentWhitelistedIP := *s.WhiteListIP
|
||||||
|
for ipOrCIDR, comment := range currentWhitelistedIP {
|
||||||
|
thisEntry := WhitelistEntry{
|
||||||
|
EntryType: EntryType_IP,
|
||||||
|
IP: ipOrCIDR,
|
||||||
|
Comment: comment,
|
||||||
|
}
|
||||||
|
whitelistedIp = append(whitelistedIp, &thisEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return whitelistedIp
|
||||||
|
}
|
@ -9,6 +9,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@ -24,6 +25,7 @@ import (
|
|||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,6 +42,11 @@ type ACMEUser struct {
|
|||||||
key crypto.PrivateKey
|
key crypto.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EABConfig struct {
|
||||||
|
Kid string `json:"kid"`
|
||||||
|
HmacKey string `json:"HmacKey"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetEmail returns the email of the ACMEUser.
|
// GetEmail returns the email of the ACMEUser.
|
||||||
func (u *ACMEUser) GetEmail() string {
|
func (u *ACMEUser) GetEmail() string {
|
||||||
return u.Email
|
return u.Email
|
||||||
@ -59,13 +66,15 @@ func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey {
|
|||||||
type ACMEHandler struct {
|
type ACMEHandler struct {
|
||||||
DefaultAcmeServer string
|
DefaultAcmeServer string
|
||||||
Port string
|
Port string
|
||||||
|
Database *database.Database
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewACME creates a new ACMEHandler instance.
|
// NewACME creates a new ACMEHandler instance.
|
||||||
func NewACME(acmeServer string, port string) *ACMEHandler {
|
func NewACME(acmeServer string, port string, database *database.Database) *ACMEHandler {
|
||||||
return &ACMEHandler{
|
return &ACMEHandler{
|
||||||
DefaultAcmeServer: acmeServer,
|
DefaultAcmeServer: acmeServer,
|
||||||
Port: port,
|
Port: port,
|
||||||
|
Database: database,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,10 +152,63 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New users will need to register
|
// New users will need to register
|
||||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
/*
|
||||||
if err != nil {
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
log.Println(err)
|
if err != nil {
|
||||||
return false, err
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
var reg *registration.Resource
|
||||||
|
// New users will need to register
|
||||||
|
if client.GetExternalAccountRequired() {
|
||||||
|
log.Println("External Account Required for this ACME Provider.")
|
||||||
|
// IF KID and HmacEncoded is overidden
|
||||||
|
|
||||||
|
if !a.Database.TableExists("acme") {
|
||||||
|
a.Database.NewTable("acme")
|
||||||
|
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.Database.KeyExists("acme", config.CADirURL+"_kid") || !a.Database.KeyExists("acme", config.CADirURL+"_hmacEncoded") {
|
||||||
|
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -2)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var kid string
|
||||||
|
var hmacEncoded string
|
||||||
|
err := a.Database.Read("acme", config.CADirURL+"_kid", &kid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Database.Read("acme", config.CADirURL+"_hmacEncoded", &hmacEncoded)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("EAB Credential retrieved.", kid, hmacEncoded)
|
||||||
|
if kid != "" && hmacEncoded != "" {
|
||||||
|
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||||
|
TermsOfServiceAgreed: true,
|
||||||
|
Kid: kid,
|
||||||
|
HmacEncoded: hmacEncoded,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
//return false, errors.New("External Account Required for this ACME Provider.")
|
||||||
|
} else {
|
||||||
|
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
adminUser.Registration = reg
|
adminUser.Registration = reg
|
||||||
|
|
||||||
|
@ -373,3 +373,34 @@ func (a *AutoRenewer) saveRenewConfigToFile() error {
|
|||||||
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
|
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
|
||||||
return os.WriteFile(a.ConfigFilePath, js, 0775)
|
return os.WriteFile(a.ConfigFilePath, js, 0775)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle update auto renew EAD configuration
|
||||||
|
func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
|
||||||
|
kid, err := utils.GetPara(r, "kid")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "kid not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hmacEncoded, err := utils.GetPara(r, "hmacEncoded")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "hmacEncoded not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
acmeDirectoryURL, err := utils.GetPara(r, "acmeDirectoryURL")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "acmeDirectoryURL not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.AcmeHandler.Database.TableExists("acme") {
|
||||||
|
a.AcmeHandler.Database.NewTable("acme")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_kid", kid)
|
||||||
|
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_hmacEncoded", hmacEncoded)
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
package aroz
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
//To be used with arozos system
|
|
||||||
type ArozHandler struct {
|
|
||||||
Port string
|
|
||||||
restfulEndpoint string
|
|
||||||
}
|
|
||||||
|
|
||||||
//Information required for registering this subservice to arozos
|
|
||||||
type ServiceInfo struct {
|
|
||||||
Name string //Name of this module. e.g. "Audio"
|
|
||||||
Desc string //Description for this module
|
|
||||||
Group string //Group of the module, e.g. "system" / "media" etc
|
|
||||||
IconPath string //Module icon image path e.g. "Audio/img/function_icon.png"
|
|
||||||
Version string //Version of the module. Format: [0-9]*.[0-9][0-9].[0-9]
|
|
||||||
StartDir string //Default starting dir, e.g. "Audio/index.html"
|
|
||||||
SupportFW bool //Support floatWindow. If yes, floatWindow dir will be loaded
|
|
||||||
LaunchFWDir string //This link will be launched instead of 'StartDir' if fw mode
|
|
||||||
SupportEmb bool //Support embedded mode
|
|
||||||
LaunchEmb string //This link will be launched instead of StartDir / Fw if a file is opened with this module
|
|
||||||
InitFWSize []int //Floatwindow init size. [0] => Width, [1] => Height
|
|
||||||
InitEmbSize []int //Embedded mode init size. [0] => Width, [1] => Height
|
|
||||||
SupportedExt []string //Supported File Extensions. e.g. ".mp3", ".flac", ".wav"
|
|
||||||
}
|
|
||||||
|
|
||||||
//This function will request the required flag from the startup paramters and parse it to the need of the arozos.
|
|
||||||
func HandleFlagParse(info ServiceInfo) *ArozHandler {
|
|
||||||
var infoRequestMode = flag.Bool("info", false, "Show information about this program in JSON")
|
|
||||||
var port = flag.String("port", ":8000", "Management web interface listening port")
|
|
||||||
var restful = flag.String("rpt", "", "Reserved")
|
|
||||||
//Parse the flags
|
|
||||||
flag.Parse()
|
|
||||||
if *infoRequestMode {
|
|
||||||
//Information request mode
|
|
||||||
jsonString, _ := json.MarshalIndent(info, "", " ")
|
|
||||||
fmt.Println(string(jsonString))
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
return &ArozHandler{
|
|
||||||
Port: *port,
|
|
||||||
restfulEndpoint: *restful,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Get the username and resources access token from the request, return username, token
|
|
||||||
func (a *ArozHandler) GetUserInfoFromRequest(w http.ResponseWriter, r *http.Request) (string, string) {
|
|
||||||
username := r.Header.Get("aouser")
|
|
||||||
token := r.Header.Get("aotoken")
|
|
||||||
|
|
||||||
return username, token
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ArozHandler) IsUsingExternalPermissionManager() bool {
|
|
||||||
return !(a.restfulEndpoint == "")
|
|
||||||
}
|
|
||||||
|
|
||||||
//Request gateway interface for advance permission sandbox control
|
|
||||||
func (a *ArozHandler) RequestGatewayInterface(token string, script string) (*http.Response, error) {
|
|
||||||
resp, err := http.PostForm(a.restfulEndpoint,
|
|
||||||
url.Values{"token": {token}, "script": {script}})
|
|
||||||
if err != nil {
|
|
||||||
// handle error
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
Binary file not shown.
@ -6,8 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -32,14 +30,6 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
||||||
if matchedRoutingRule != nil {
|
if matchedRoutingRule != nil {
|
||||||
//Matching routing rule found. Let the sub-router handle it
|
//Matching routing rule found. Let the sub-router handle it
|
||||||
if matchedRoutingRule.UseSystemAccessControl {
|
|
||||||
//This matching rule request system access control.
|
|
||||||
//check access logic
|
|
||||||
respWritten := h.handleAccessRouting(w, r)
|
|
||||||
if respWritten {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
matchedRoutingRule.Route(w, r)
|
matchedRoutingRule.Route(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -47,14 +37,6 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Inject headers
|
//Inject headers
|
||||||
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
|
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
|
||||||
|
|
||||||
/*
|
|
||||||
General Access Check
|
|
||||||
*/
|
|
||||||
respWritten := h.handleAccessRouting(w, r)
|
|
||||||
if respWritten {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Redirection Routing
|
Redirection Routing
|
||||||
*/
|
*/
|
||||||
@ -65,19 +47,30 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Extract request host to see if it is virtual directory or subdomain
|
/*
|
||||||
|
Host Routing
|
||||||
|
*/
|
||||||
|
//Extract request host to see if any proxy rule is matched
|
||||||
domainOnly := r.Host
|
domainOnly := r.Host
|
||||||
if strings.Contains(r.Host, ":") {
|
if strings.Contains(r.Host, ":") {
|
||||||
hostPath := strings.Split(r.Host, ":")
|
hostPath := strings.Split(r.Host, ":")
|
||||||
domainOnly = hostPath[0]
|
domainOnly = hostPath[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Host Routing
|
|
||||||
*/
|
|
||||||
|
|
||||||
sep := h.Parent.getProxyEndpointFromHostname(domainOnly)
|
sep := h.Parent.getProxyEndpointFromHostname(domainOnly)
|
||||||
if sep != nil && !sep.Disabled {
|
if sep != nil && !sep.Disabled {
|
||||||
|
//Matching proxy rule found
|
||||||
|
//Access Check (blacklist / whitelist)
|
||||||
|
ruleID := sep.AccessFilterUUID
|
||||||
|
if sep.AccessFilterUUID == "" {
|
||||||
|
//Use default rule
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
if h.handleAccessRouting(ruleID, w, r) {
|
||||||
|
//Request handled by subroute
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Validate basic auth
|
||||||
if sep.RequireBasicAuth {
|
if sep.RequireBasicAuth {
|
||||||
err := h.handleBasicAuthRouting(w, r, sep)
|
err := h.handleBasicAuthRouting(w, r, sep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -136,7 +129,6 @@ Once entered this routing segment, the root routing options will take over
|
|||||||
for the routing logic.
|
for the routing logic.
|
||||||
*/
|
*/
|
||||||
func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) {
|
func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
domainOnly := r.Host
|
domainOnly := r.Host
|
||||||
if strings.Contains(r.Host, ":") {
|
if strings.Contains(r.Host, ":") {
|
||||||
hostPath := strings.Split(r.Host, ":")
|
hostPath := strings.Split(r.Host, ":")
|
||||||
@ -203,38 +195,3 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle access routing logic. Return true if the request is handled or blocked by the access control logic
|
|
||||||
// if the return value is false, you can continue process the response writer
|
|
||||||
func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Request) bool {
|
|
||||||
//Check if this ip is in blacklist
|
|
||||||
clientIpAddr := geodb.GetRequesterIP(r)
|
|
||||||
if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/blacklist.html"))
|
|
||||||
if err != nil {
|
|
||||||
w.Write(page_forbidden)
|
|
||||||
} else {
|
|
||||||
w.Write(template)
|
|
||||||
}
|
|
||||||
h.logRequest(r, false, 403, "blacklist", "")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check if this ip is in whitelist
|
|
||||||
if !h.Parent.Option.GeodbStore.IsWhitelisted(clientIpAddr) {
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/whitelist.html"))
|
|
||||||
if err != nil {
|
|
||||||
w.Write(page_forbidden)
|
|
||||||
} else {
|
|
||||||
w.Write(template)
|
|
||||||
}
|
|
||||||
h.logRequest(r, false, 403, "whitelist", "")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
64
src/mod/dynamicproxy/access.go
Normal file
64
src/mod/dynamicproxy/access.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package dynamicproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle access check (blacklist / whitelist), return true if request is handled (aka blocked)
|
||||||
|
// if the return value is false, you can continue process the response writer
|
||||||
|
func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
//Unable to load access rule. Target rule not found?
|
||||||
|
log.Println("[Proxy] Unable to load access rule: " + ruleID)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte("500 - Internal Server Error"))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
|
||||||
|
if isBlocked {
|
||||||
|
h.logRequest(r, false, 403, blockedReason, "")
|
||||||
|
}
|
||||||
|
return isBlocked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return boolean, return true if access is blocked
|
||||||
|
// For string, it will return the blocked reason (if any)
|
||||||
|
func accessRequestBlocked(accessRule *access.AccessRule, templateDirectory string, w http.ResponseWriter, r *http.Request) (bool, string) {
|
||||||
|
//Check if this ip is in blacklist
|
||||||
|
clientIpAddr := netutils.GetRequesterIP(r)
|
||||||
|
if accessRule.IsBlacklisted(clientIpAddr) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
template, err := os.ReadFile(filepath.Join(templateDirectory, "templates/blacklist.html"))
|
||||||
|
if err != nil {
|
||||||
|
w.Write(page_forbidden)
|
||||||
|
} else {
|
||||||
|
w.Write(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, "blacklist"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if this ip is in whitelist
|
||||||
|
if !accessRule.IsWhitelisted(clientIpAddr) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
template, err := os.ReadFile(filepath.Join(templateDirectory, "templates/whitelist.html"))
|
||||||
|
if err != nil {
|
||||||
|
w.Write(page_forbidden)
|
||||||
|
} else {
|
||||||
|
w.Write(template)
|
||||||
|
}
|
||||||
|
return true, "whitelist"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Not blocked.
|
||||||
|
return false, ""
|
||||||
|
}
|
@ -16,6 +16,16 @@ import (
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||||
|
err := handleBasicAuth(w, r, pe)
|
||||||
|
if err != nil {
|
||||||
|
h.logRequest(r, false, 401, "host", pe.Domain)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle basic auth logic
|
||||||
|
// do not write to http.ResponseWriter if err return is not nil (already handled by this function)
|
||||||
|
func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||||
if len(pe.BasicAuthExceptionRules) > 0 {
|
if len(pe.BasicAuthExceptionRules) > 0 {
|
||||||
//Check if the current path matches the exception rules
|
//Check if the current path matches the exception rules
|
||||||
for _, exceptionRule := range pe.BasicAuthExceptionRules {
|
for _, exceptionRule := range pe.BasicAuthExceptionRules {
|
||||||
@ -44,7 +54,6 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !matchingFound {
|
if !matchingFound {
|
||||||
h.logRequest(r, false, 401, "host", pe.Domain)
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
w.WriteHeader(401)
|
w.WriteHeader(401)
|
||||||
return errors.New("unauthorized")
|
return errors.New("unauthorized")
|
||||||
|
@ -115,6 +115,28 @@ func (router *Router) StartProxyService() error {
|
|||||||
r.URL, _ = url.Parse(originalHostHeader)
|
r.URL, _ = url.Parse(originalHostHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Access Check (blacklist / whitelist)
|
||||||
|
ruleID := sep.AccessFilterUUID
|
||||||
|
if sep.AccessFilterUUID == "" {
|
||||||
|
//Use default rule
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
accessRule, err := router.Option.AccessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err == nil {
|
||||||
|
isBlocked, _ := accessRequestBlocked(accessRule, router.Option.WebDirectory, w, r)
|
||||||
|
if isBlocked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Validate basic auth
|
||||||
|
if sep.RequireBasicAuth {
|
||||||
|
err := handleBasicAuth(w, r, sep)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: sep.Domain,
|
ProxyDomain: sep.Domain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
"imuslab.com/zoraxy/mod/statistic"
|
"imuslab.com/zoraxy/mod/statistic"
|
||||||
"imuslab.com/zoraxy/mod/websocketproxy"
|
"imuslab.com/zoraxy/mod/websocketproxy"
|
||||||
)
|
)
|
||||||
@ -34,23 +34,45 @@ func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoi
|
|||||||
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
||||||
ep, ok := router.ProxyEndpoints.Load(hostname)
|
ep, ok := router.ProxyEndpoints.Load(hostname)
|
||||||
if ok {
|
if ok {
|
||||||
|
//Exact hit
|
||||||
targetSubdomainEndpoint = ep.(*ProxyEndpoint)
|
targetSubdomainEndpoint = ep.(*ProxyEndpoint)
|
||||||
|
if !targetSubdomainEndpoint.Disabled {
|
||||||
|
return targetSubdomainEndpoint
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//No hit. Try with wildcard
|
//No hit. Try with wildcard and alias
|
||||||
matchProxyEndpoints := []*ProxyEndpoint{}
|
matchProxyEndpoints := []*ProxyEndpoint{}
|
||||||
router.ProxyEndpoints.Range(func(k, v interface{}) bool {
|
router.ProxyEndpoints.Range(func(k, v interface{}) bool {
|
||||||
ep := v.(*ProxyEndpoint)
|
ep := v.(*ProxyEndpoint)
|
||||||
match, err := filepath.Match(ep.RootOrMatchingDomain, hostname)
|
match, err := filepath.Match(ep.RootOrMatchingDomain, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Continue
|
//Bad pattern. Skip this rule
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if match {
|
if match {
|
||||||
//targetSubdomainEndpoint = ep
|
//Wildcard matches. Skip checking alias
|
||||||
matchProxyEndpoints = append(matchProxyEndpoints, ep)
|
matchProxyEndpoints = append(matchProxyEndpoints, ep)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Wildcard not match. Check for alias
|
||||||
|
if ep.MatchingDomainAlias != nil && len(ep.MatchingDomainAlias) > 0 {
|
||||||
|
for _, aliasDomain := range ep.MatchingDomainAlias {
|
||||||
|
match, err := filepath.Match(aliasDomain, hostname)
|
||||||
|
if err != nil {
|
||||||
|
//Bad pattern. Skip this alias
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
//This alias match
|
||||||
|
matchProxyEndpoints = append(matchProxyEndpoints, ep)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -224,7 +246,7 @@ func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, fo
|
|||||||
if h.Parent.Option.StatisticCollector != nil {
|
if h.Parent.Option.StatisticCollector != nil {
|
||||||
go func() {
|
go func() {
|
||||||
requestInfo := statistic.RequestInfo{
|
requestInfo := statistic.RequestInfo{
|
||||||
IpAddr: geodb.GetRequesterIP(r),
|
IpAddr: netutils.GetRequesterIP(r),
|
||||||
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||||
Succ: succ,
|
Succ: succ,
|
||||||
StatusCode: statusCode,
|
StatusCode: statusCode,
|
||||||
|
@ -19,6 +19,9 @@ import (
|
|||||||
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
||||||
//Filter the tailing slash if any
|
//Filter the tailing slash if any
|
||||||
domain := endpoint.Domain
|
domain := endpoint.Domain
|
||||||
|
if len(domain) == 0 {
|
||||||
|
return nil, errors.New("invalid endpoint config")
|
||||||
|
}
|
||||||
if domain[len(domain)-1:] == "/" {
|
if domain[len(domain)-1:] == "/" {
|
||||||
domain = domain[:len(domain)-1]
|
domain = domain[:len(domain)-1]
|
||||||
}
|
}
|
||||||
@ -51,6 +54,10 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
|||||||
//Prepare proxy routing hjandler for each of the virtual directories
|
//Prepare proxy routing hjandler for each of the virtual directories
|
||||||
for _, vdir := range endpoint.VirtualDirectories {
|
for _, vdir := range endpoint.VirtualDirectories {
|
||||||
domain := vdir.Domain
|
domain := vdir.Domain
|
||||||
|
if len(domain) == 0 {
|
||||||
|
//invalid vdir
|
||||||
|
continue
|
||||||
|
}
|
||||||
if domain[len(domain)-1:] == "/" {
|
if domain[len(domain)-1:] == "/" {
|
||||||
domain = domain[:len(domain)-1]
|
domain = domain[:len(domain)-1]
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
@ -34,7 +35,8 @@ type RouterOption struct {
|
|||||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||||
TlsManager *tlscert.Manager
|
TlsManager *tlscert.Manager
|
||||||
RedirectRuleTable *redirection.RuleTable
|
RedirectRuleTable *redirection.RuleTable
|
||||||
GeodbStore *geodb.Store //GeoIP blacklist and whitelist
|
GeodbStore *geodb.Store //GeoIP resolver
|
||||||
|
AccessController *access.Controller //Blacklist / whitelist controller
|
||||||
StatisticCollector *statistic.Collector
|
StatisticCollector *statistic.Collector
|
||||||
WebDirectory string //The static web server directory containing the templates folder
|
WebDirectory string //The static web server directory containing the templates folder
|
||||||
}
|
}
|
||||||
@ -90,9 +92,10 @@ type VirtualDirectoryEndpoint struct {
|
|||||||
|
|
||||||
// A proxy endpoint record, a general interface for handling inbound routing
|
// A proxy endpoint record, a general interface for handling inbound routing
|
||||||
type ProxyEndpoint struct {
|
type ProxyEndpoint struct {
|
||||||
ProxyType int //The type of this proxy, see const def
|
ProxyType int //The type of this proxy, see const def
|
||||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||||
Domain string //Domain or IP to proxy to
|
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||||
|
Domain string //Domain or IP to proxy to
|
||||||
|
|
||||||
//TLS/SSL Related
|
//TLS/SSL Related
|
||||||
RequireTLS bool //Target domain require TLS
|
RequireTLS bool //Target domain require TLS
|
||||||
@ -111,14 +114,17 @@ type ProxyEndpoint struct {
|
|||||||
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
|
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
|
||||||
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||||
|
|
||||||
//Fallback routing logic
|
//Access Control
|
||||||
DefaultSiteOption int //Fallback routing logic options
|
AccessFilterUUID string //Access filter ID
|
||||||
DefaultSiteValue string //Fallback routing target, optional
|
|
||||||
|
|
||||||
Disabled bool //If the rule is disabled
|
Disabled bool //If the rule is disabled
|
||||||
|
|
||||||
|
//Fallback routing logic (Special Rule Sets Only)
|
||||||
|
DefaultSiteOption int //Fallback routing logic options
|
||||||
|
DefaultSiteValue string //Fallback routing target, optional
|
||||||
|
|
||||||
//Internal Logic Elements
|
//Internal Logic Elements
|
||||||
parent *Router
|
parent *Router `json:"-"`
|
||||||
proxy *dpcore.ReverseProxy `json:"-"`
|
proxy *dpcore.ReverseProxy `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
package geodb
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
/*
|
|
||||||
Blacklist.go
|
|
||||||
|
|
||||||
This script store the blacklist related functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Geo Blacklist
|
|
||||||
|
|
||||||
func (s *Store) AddCountryCodeToBlackList(countryCode string) {
|
|
||||||
countryCode = strings.ToLower(countryCode)
|
|
||||||
s.sysdb.Write("blacklist-cn", countryCode, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) RemoveCountryCodeFromBlackList(countryCode string) {
|
|
||||||
countryCode = strings.ToLower(countryCode)
|
|
||||||
s.sysdb.Delete("blacklist-cn", countryCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) IsCountryCodeBlacklisted(countryCode string) bool {
|
|
||||||
countryCode = strings.ToLower(countryCode)
|
|
||||||
var isBlacklisted bool = false
|
|
||||||
s.sysdb.Read("blacklist-cn", countryCode, &isBlacklisted)
|
|
||||||
return isBlacklisted
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) GetAllBlacklistedCountryCode() []string {
|
|
||||||
bannedCountryCodes := []string{}
|
|
||||||
entries, err := s.sysdb.ListTable("blacklist-cn")
|
|
||||||
if err != nil {
|
|
||||||
return bannedCountryCodes
|
|
||||||
}
|
|
||||||
for _, keypairs := range entries {
|
|
||||||
ip := string(keypairs[0])
|
|
||||||
bannedCountryCodes = append(bannedCountryCodes, ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
return bannedCountryCodes
|
|
||||||
}
|
|
||||||
|
|
||||||
//IP Blacklsits
|
|
||||||
|
|
||||||
func (s *Store) AddIPToBlackList(ipAddr string) {
|
|
||||||
s.sysdb.Write("blacklist-ip", ipAddr, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) RemoveIPFromBlackList(ipAddr string) {
|
|
||||||
s.sysdb.Delete("blacklist-ip", ipAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) GetAllBlacklistedIp() []string {
|
|
||||||
bannedIps := []string{}
|
|
||||||
entries, err := s.sysdb.ListTable("blacklist-ip")
|
|
||||||
if err != nil {
|
|
||||||
return bannedIps
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, keypairs := range entries {
|
|
||||||
ip := string(keypairs[0])
|
|
||||||
bannedIps = append(bannedIps, ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
return bannedIps
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) IsIPBlacklisted(ipAddr string) bool {
|
|
||||||
var isBlacklisted bool = false
|
|
||||||
s.sysdb.Read("blacklist-ip", ipAddr, &isBlacklisted)
|
|
||||||
if isBlacklisted {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check for IP wildcard and CIRD rules
|
|
||||||
AllBlacklistedIps := s.GetAllBlacklistedIp()
|
|
||||||
for _, blacklistRule := range AllBlacklistedIps {
|
|
||||||
wildcardMatch := MatchIpWildcard(ipAddr, blacklistRule)
|
|
||||||
if wildcardMatch {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
cidrMatch := MatchIpCIDR(ipAddr, blacklistRule)
|
|
||||||
if cidrMatch {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
@ -2,11 +2,10 @@ package geodb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed geoipv4.csv
|
//go:embed geoipv4.csv
|
||||||
@ -16,12 +15,10 @@ var geoipv4 []byte //Geodb dataset for ipv4
|
|||||||
var geoipv6 []byte //Geodb dataset for ipv6
|
var geoipv6 []byte //Geodb dataset for ipv6
|
||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
BlacklistEnabled bool
|
geodb [][]string //Parsed geodb list
|
||||||
WhitelistEnabled bool
|
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
||||||
geodb [][]string //Parsed geodb list
|
geotrie *trie
|
||||||
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
geotrieIpv6 *trie
|
||||||
geotrie *trie
|
|
||||||
geotrieIpv6 *trie
|
|
||||||
//geoipCache sync.Map
|
//geoipCache sync.Map
|
||||||
sysdb *database.Database
|
sysdb *database.Database
|
||||||
option *StoreOptions
|
option *StoreOptions
|
||||||
@ -48,40 +45,6 @@ func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
blacklistEnabled := false
|
|
||||||
whitelistEnabled := false
|
|
||||||
if sysdb != nil {
|
|
||||||
err = sysdb.NewTable("blacklist-cn")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sysdb.NewTable("blacklist-ip")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sysdb.NewTable("whitelist-cn")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sysdb.NewTable("whitelist-ip")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sysdb.NewTable("blackwhitelist")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sysdb.Read("blackwhitelist", "blacklistEnabled", &blacklistEnabled)
|
|
||||||
sysdb.Read("blackwhitelist", "whitelistEnabled", &whitelistEnabled)
|
|
||||||
} else {
|
|
||||||
log.Println("Database pointer set to nil: Entering debug mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipv4Trie *trie
|
var ipv4Trie *trie
|
||||||
if !option.AllowSlowIpv4LookUp {
|
if !option.AllowSlowIpv4LookUp {
|
||||||
ipv4Trie = constrctTrieTree(parsedGeoData)
|
ipv4Trie = constrctTrieTree(parsedGeoData)
|
||||||
@ -93,27 +56,15 @@ func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Store{
|
return &Store{
|
||||||
BlacklistEnabled: blacklistEnabled,
|
geodb: parsedGeoData,
|
||||||
WhitelistEnabled: whitelistEnabled,
|
geotrie: ipv4Trie,
|
||||||
geodb: parsedGeoData,
|
geodbIpv6: parsedGeoDataIpv6,
|
||||||
geotrie: ipv4Trie,
|
geotrieIpv6: ipv6Trie,
|
||||||
geodbIpv6: parsedGeoDataIpv6,
|
sysdb: sysdb,
|
||||||
geotrieIpv6: ipv6Trie,
|
option: option,
|
||||||
sysdb: sysdb,
|
|
||||||
option: option,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) ToggleBlacklist(enabled bool) {
|
|
||||||
s.sysdb.Write("blackwhitelist", "blacklistEnabled", enabled)
|
|
||||||
s.BlacklistEnabled = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) ToggleWhitelist(enabled bool) {
|
|
||||||
s.sysdb.Write("blackwhitelist", "whitelistEnabled", enabled)
|
|
||||||
s.WhitelistEnabled = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) {
|
func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) {
|
||||||
cc := s.search(ipstring)
|
cc := s.search(ipstring)
|
||||||
return &CountryInfo{
|
return &CountryInfo{
|
||||||
@ -127,90 +78,8 @@ func (s *Store) Close() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Check if a IP address is blacklisted, in either country or IP blacklist
|
|
||||||
IsBlacklisted default return is false (allow access)
|
|
||||||
*/
|
|
||||||
func (s *Store) IsBlacklisted(ipAddr string) bool {
|
|
||||||
if !s.BlacklistEnabled {
|
|
||||||
//Blacklist not enabled. Always return false
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipAddr == "" {
|
|
||||||
//Unable to get the target IP address
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.IsCountryCodeBlacklisted(countryCode.CountryIsoCode) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.IsIPBlacklisted(ipAddr) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
IsWhitelisted check if a given IP address is in the current
|
|
||||||
server's white list.
|
|
||||||
|
|
||||||
Note that the Whitelist default result is true even
|
|
||||||
when encountered error
|
|
||||||
*/
|
|
||||||
func (s *Store) IsWhitelisted(ipAddr string) bool {
|
|
||||||
if !s.WhitelistEnabled {
|
|
||||||
//Whitelist not enabled. Always return true (allow access)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipAddr == "" {
|
|
||||||
//Unable to get the target IP address, assume ok
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
|
|
||||||
if err != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.IsCountryCodeWhitelisted(countryCode.CountryIsoCode) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.IsIPWhitelisted(ipAddr) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// A helper function that check both blacklist and whitelist for access
|
|
||||||
// for both geoIP and ip / CIDR ranges
|
|
||||||
func (s *Store) AllowIpAccess(ipaddr string) bool {
|
|
||||||
if s.IsBlacklisted(ipaddr) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.IsWhitelisted(ipaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) AllowConnectionAccess(conn net.Conn) bool {
|
|
||||||
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
|
||||||
return s.AllowIpAccess(addr.IP.String())
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
|
func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
|
||||||
ipAddr := GetRequesterIP(r)
|
ipAddr := netutils.GetRequesterIP(r)
|
||||||
if ipAddr == "" {
|
if ipAddr == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Store) search(ip string) string {
|
func (s *Store) search(ip string) string {
|
||||||
@ -24,7 +26,7 @@ func (s *Store) search(ip string) string {
|
|||||||
|
|
||||||
//Search in geotrie tree
|
//Search in geotrie tree
|
||||||
cc := ""
|
cc := ""
|
||||||
if IsIPv6(ip) {
|
if netutils.IsIPv6(ip) {
|
||||||
if s.geotrieIpv6 == nil {
|
if s.geotrieIpv6 == nil {
|
||||||
cc = s.slowSearchIpv6(ip)
|
cc = s.slowSearchIpv6(ip)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
package geodb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Whitelist.go
|
|
||||||
|
|
||||||
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, comment string) {
|
|
||||||
countryCode = strings.ToLower(countryCode)
|
|
||||||
entry := WhitelistEntry{
|
|
||||||
EntryType: EntryType_CountryCode,
|
|
||||||
CC: countryCode,
|
|
||||||
Comment: comment,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.sysdb.Write("whitelist-cn", countryCode, entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) RemoveCountryCodeFromWhitelist(countryCode string) {
|
|
||||||
countryCode = strings.ToLower(countryCode)
|
|
||||||
s.sysdb.Delete("whitelist-cn", countryCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) IsCountryCodeWhitelisted(countryCode string) bool {
|
|
||||||
countryCode = strings.ToLower(countryCode)
|
|
||||||
return s.sysdb.KeyExists("whitelist-cn", countryCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) GetAllWhitelistedCountryCode() []*WhitelistEntry {
|
|
||||||
whitelistedCountryCode := []*WhitelistEntry{}
|
|
||||||
entries, err := s.sysdb.ListTable("whitelist-cn")
|
|
||||||
if err != nil {
|
|
||||||
return whitelistedCountryCode
|
|
||||||
}
|
|
||||||
for _, keypairs := range entries {
|
|
||||||
thisWhitelistEntry := WhitelistEntry{}
|
|
||||||
json.Unmarshal(keypairs[1], &thisWhitelistEntry)
|
|
||||||
whitelistedCountryCode = append(whitelistedCountryCode, &thisWhitelistEntry)
|
|
||||||
}
|
|
||||||
|
|
||||||
return whitelistedCountryCode
|
|
||||||
}
|
|
||||||
|
|
||||||
//IP Whitelist
|
|
||||||
|
|
||||||
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) {
|
|
||||||
s.sysdb.Delete("whitelist-ip", ipAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) IsIPWhitelisted(ipAddr string) bool {
|
|
||||||
isWhitelisted := s.sysdb.KeyExists("whitelist-ip", ipAddr)
|
|
||||||
if isWhitelisted {
|
|
||||||
//single IP whitelist entry
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check for IP wildcard and CIRD rules
|
|
||||||
AllWhitelistedIps := s.GetAllWhitelistedIpAsStringSlice()
|
|
||||||
for _, whitelistRules := range AllWhitelistedIps {
|
|
||||||
wildcardMatch := MatchIpWildcard(ipAddr, whitelistRules)
|
|
||||||
if wildcardMatch {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
cidrMatch := MatchIpCIDR(ipAddr, whitelistRules)
|
|
||||||
if cidrMatch {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
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])
|
|
||||||
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
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package geodb
|
package netutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
@ -6,7 +6,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Utilities function
|
/*
|
||||||
|
MatchIP.go
|
||||||
|
|
||||||
|
This script contains function for matching IP address, comparing
|
||||||
|
CIDR and IPv4 / v6 validations
|
||||||
|
*/
|
||||||
|
|
||||||
func GetRequesterIP(r *http.Request) string {
|
func GetRequesterIP(r *http.Request) string {
|
||||||
ip := r.Header.Get("X-Real-Ip")
|
ip := r.Header.Get("X-Real-Ip")
|
||||||
if ip == "" {
|
if ip == "" {
|
@ -94,6 +94,7 @@ func ReverseProxtInit() {
|
|||||||
GeodbStore: geodbStore,
|
GeodbStore: geodbStore,
|
||||||
StatisticCollector: statisticCollector,
|
StatisticCollector: statisticCollector,
|
||||||
WebDirectory: *staticWebServerRoot,
|
WebDirectory: *staticWebServerRoot,
|
||||||
|
AccessController: accessController,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to create dynamic proxy router", err)
|
SystemWideLogger.PrintAndLog("Proxy", "Unable to create dynamic proxy router", err)
|
||||||
@ -194,6 +195,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
useTLS := (tls == "true")
|
useTLS := (tls == "true")
|
||||||
|
|
||||||
|
//Bypass global TLS value / allow direct access from port 80?
|
||||||
bypassGlobalTLS, _ := utils.PostPara(r, "bypassGlobalTLS")
|
bypassGlobalTLS, _ := utils.PostPara(r, "bypassGlobalTLS")
|
||||||
if bypassGlobalTLS == "" {
|
if bypassGlobalTLS == "" {
|
||||||
bypassGlobalTLS = "false"
|
bypassGlobalTLS = "false"
|
||||||
@ -201,6 +203,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
useBypassGlobalTLS := bypassGlobalTLS == "true"
|
useBypassGlobalTLS := bypassGlobalTLS == "true"
|
||||||
|
|
||||||
|
//Enable TLS validation?
|
||||||
stv, _ := utils.PostPara(r, "tlsval")
|
stv, _ := utils.PostPara(r, "tlsval")
|
||||||
if stv == "" {
|
if stv == "" {
|
||||||
stv = "false"
|
stv = "false"
|
||||||
@ -208,6 +211,17 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
skipTlsValidation := (stv == "true")
|
skipTlsValidation := (stv == "true")
|
||||||
|
|
||||||
|
//Get access rule ID
|
||||||
|
accessRuleID, _ := utils.PostPara(r, "access")
|
||||||
|
if accessRuleID == "" {
|
||||||
|
accessRuleID = "default"
|
||||||
|
}
|
||||||
|
if !accessController.AccessRuleExists(accessRuleID) {
|
||||||
|
utils.SendErrorResponse(w, "invalid access rule ID selected")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Require basic auth?
|
||||||
rba, _ := utils.PostPara(r, "bauth")
|
rba, _ := utils.PostPara(r, "bauth")
|
||||||
if rba == "" {
|
if rba == "" {
|
||||||
rba = "false"
|
rba = "false"
|
||||||
@ -254,19 +268,37 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
if eptype == "host" {
|
if eptype == "host" {
|
||||||
rootOrMatchingDomain, err := utils.PostPara(r, "rootname")
|
rootOrMatchingDomain, err := utils.PostPara(r, "rootname")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "subdomain not defined")
|
utils.SendErrorResponse(w, "hostname not defined")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
rootOrMatchingDomain = strings.TrimSpace(rootOrMatchingDomain)
|
||||||
|
|
||||||
|
//Check if it contains ",", if yes, split the remainings as alias
|
||||||
|
aliasHostnames := []string{}
|
||||||
|
if strings.Contains(rootOrMatchingDomain, ",") {
|
||||||
|
matchingDomains := strings.Split(rootOrMatchingDomain, ",")
|
||||||
|
if len(matchingDomains) > 1 {
|
||||||
|
rootOrMatchingDomain = matchingDomains[0]
|
||||||
|
for _, aliasHostname := range matchingDomains[1:] {
|
||||||
|
//Filter out any space
|
||||||
|
aliasHostnames = append(aliasHostnames, strings.TrimSpace(aliasHostname))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate a proxy endpoint object
|
||||||
thisProxyEndpoint := dynamicproxy.ProxyEndpoint{
|
thisProxyEndpoint := dynamicproxy.ProxyEndpoint{
|
||||||
//I/O
|
//I/O
|
||||||
ProxyType: dynamicproxy.ProxyType_Host,
|
ProxyType: dynamicproxy.ProxyType_Host,
|
||||||
RootOrMatchingDomain: rootOrMatchingDomain,
|
RootOrMatchingDomain: rootOrMatchingDomain,
|
||||||
|
MatchingDomainAlias: aliasHostnames,
|
||||||
Domain: endpoint,
|
Domain: endpoint,
|
||||||
//TLS
|
//TLS
|
||||||
RequireTLS: useTLS,
|
RequireTLS: useTLS,
|
||||||
BypassGlobalTLS: useBypassGlobalTLS,
|
BypassGlobalTLS: useBypassGlobalTLS,
|
||||||
SkipCertValidations: skipTlsValidation,
|
SkipCertValidations: skipTlsValidation,
|
||||||
SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
|
SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
|
||||||
|
AccessFilterUUID: accessRuleID,
|
||||||
//VDir
|
//VDir
|
||||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||||
//Custom headers
|
//Custom headers
|
||||||
@ -439,6 +471,62 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
|
||||||
|
rootNameOrMatchingDomain, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Invalid ep given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//No need to check for type as root (/) can be set to default route
|
||||||
|
//and hence, you will not need alias
|
||||||
|
|
||||||
|
//Load the previous alias from current proxy rules
|
||||||
|
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Target proxy config not found or could not be loaded")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newAliasJSON, err := utils.PostPara(r, "alias")
|
||||||
|
if err != nil {
|
||||||
|
//No new set of alias given
|
||||||
|
utils.SendErrorResponse(w, "new alias not given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write new alias to runtime and file
|
||||||
|
newAlias := []string{}
|
||||||
|
err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("Proxy", "Unable to parse new alias list", err)
|
||||||
|
utils.SendErrorResponse(w, "Invalid alias list given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set the current alias
|
||||||
|
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
|
||||||
|
newProxyEndpoint.MatchingDomainAlias = newAlias
|
||||||
|
|
||||||
|
// Prepare to replace the current routing rule
|
||||||
|
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetProxyEntry.Remove()
|
||||||
|
dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
|
||||||
|
|
||||||
|
// Save it to file
|
||||||
|
err = SaveReverseProxyConfig(newProxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Alias update failed")
|
||||||
|
SystemWideLogger.PrintAndLog("Proxy", "Unable to save alias update", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
|
func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
ep, err := utils.GetPara(r, "ep")
|
ep, err := utils.GetPara(r, "ep")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -740,6 +828,35 @@ func ReverseProxyToggleRuleSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReverseProxyListDetail(w http.ResponseWriter, r *http.Request) {
|
||||||
|
eptype, err := utils.PostPara(r, "type") //Support root and host
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "type not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if eptype == "host" {
|
||||||
|
epname, err := utils.PostPara(r, "epname")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "epname not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endpointRaw, ok := dynamicProxyRouter.ProxyEndpoints.Load(epname)
|
||||||
|
if !ok {
|
||||||
|
utils.SendErrorResponse(w, "proxy rule not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetEndpoint := dynamicproxy.CopyEndpoint(endpointRaw.(*dynamicproxy.ProxyEndpoint))
|
||||||
|
js, _ := json.Marshal(targetEndpoint)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
} else if eptype == "root" {
|
||||||
|
js, _ := json.Marshal(dynamicProxyRouter.Root)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
} else {
|
||||||
|
utils.SendErrorResponse(w, "Invalid type given")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
|
func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
|
||||||
eptype, err := utils.PostPara(r, "type") //Support root and host
|
eptype, err := utils.PostPara(r, "type") //Support root and host
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
13
src/start.go
13
src/start.go
@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/acme"
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
@ -91,6 +92,16 @@ func startupSequence() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Create the access controller
|
||||||
|
accessController, err = access.NewAccessController(&access.Options{
|
||||||
|
Database: sysdb,
|
||||||
|
GeoDB: geodbStore,
|
||||||
|
ConfigFolder: "./conf/access",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
//Create a statistic collector
|
//Create a statistic collector
|
||||||
statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{
|
statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{
|
||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
@ -211,7 +222,7 @@ func startupSequence() {
|
|||||||
//Create TCP Proxy Manager
|
//Create TCP Proxy Manager
|
||||||
tcpProxyManager = tcpprox.NewTCProxy(&tcpprox.Options{
|
tcpProxyManager = tcpprox.NewTCProxy(&tcpprox.Options{
|
||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
AccessControlHandler: geodbStore.AllowConnectionAccess,
|
AccessControlHandler: accessController.DefaultAccessRule.AllowConnectionAccess,
|
||||||
})
|
})
|
||||||
|
|
||||||
//Create WoL MAC storage table
|
//Create WoL MAC storage table
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -237,7 +237,7 @@
|
|||||||
msgbox("Certificate installed successfully");
|
msgbox("Certificate installed successfully");
|
||||||
|
|
||||||
if (callback != undefined){
|
if (callback != undefined){
|
||||||
callback(false);
|
callback(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -7,8 +7,12 @@
|
|||||||
#httpProxyList .ui.toggle.checkbox input:checked ~ label::before{
|
#httpProxyList .ui.toggle.checkbox input:checked ~ label::before{
|
||||||
background-color: #00ca52 !important;
|
background-color: #00ca52 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subdEntry td:not(.ignoremw){
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em;">
|
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em; min-height: 300px;">
|
||||||
<table class="ui celled sortable unstackable compact table">
|
<table class="ui celled sortable unstackable compact table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -16,7 +20,7 @@
|
|||||||
<th>Destination</th>
|
<th>Destination</th>
|
||||||
<th>Virtual Directory</th>
|
<th>Virtual Directory</th>
|
||||||
<th>Basic Auth</th>
|
<th>Basic Auth</th>
|
||||||
<th class="no-sort" style="min-width:100px;">Actions</th>
|
<th class="no-sort" style="min-width:150px;">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="httpProxyList">
|
<tbody id="httpProxyList">
|
||||||
@ -30,6 +34,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
/* List all proxy endpoints */
|
||||||
function listProxyEndpoints(){
|
function listProxyEndpoints(){
|
||||||
$.get("/api/proxy/list?type=host", function(data){
|
$.get("/api/proxy/list?type=host", function(data){
|
||||||
$("#httpProxyList").html(``);
|
$("#httpProxyList").html(``);
|
||||||
@ -42,6 +48,8 @@
|
|||||||
<td data-label="" colspan="5"><i class="green check circle icon"></i> No HTTP Proxy Record</td>
|
<td data-label="" colspan="5"><i class="green check circle icon"></i> No HTTP Proxy Record</td>
|
||||||
</tr>`);
|
</tr>`);
|
||||||
}else{
|
}else{
|
||||||
|
//Sort by RootOrMatchingDomain field
|
||||||
|
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
|
||||||
data.forEach(subd => {
|
data.forEach(subd => {
|
||||||
let tlsIcon = "";
|
let tlsIcon = "";
|
||||||
let subdData = encodeURIComponent(JSON.stringify(subd));
|
let subdData = encodeURIComponent(JSON.stringify(subd));
|
||||||
@ -73,17 +81,33 @@
|
|||||||
vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;"><i class="check icon"></i> No Virtual Directory</small>`;
|
vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;"><i class="check icon"></i> No Virtual Directory</small>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
var enableChecked = "checked";
|
let enableChecked = "checked";
|
||||||
if (subd.Disabled){
|
if (subd.Disabled){
|
||||||
enableChecked = "";
|
enableChecked = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let aliasDomains = ``;
|
||||||
|
if (subd.MatchingDomainAlias != undefined && subd.MatchingDomainAlias.length > 0){
|
||||||
|
aliasDomains = `<small class="aliasDomains" eptuuid="${subd.RootOrMatchingDomain}" style="color: #636363;">Alias: `;
|
||||||
|
subd.MatchingDomainAlias.forEach(alias => {
|
||||||
|
aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `;
|
||||||
|
});
|
||||||
|
aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
|
||||||
|
aliasDomains += `</small><br>`;
|
||||||
|
}
|
||||||
|
|
||||||
$("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
|
$("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
|
||||||
<td data-label="" editable="true" datatype="inbound"><a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}</td>
|
<td data-label="" editable="true" datatype="inbound">
|
||||||
|
<a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}<br>
|
||||||
|
${aliasDomains}
|
||||||
|
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
|
||||||
|
</td>
|
||||||
<td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
|
<td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
|
||||||
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
||||||
<td data-label="" editable="true" datatype="basicauth">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
|
<td data-label="" editable="true" datatype="basicauth">
|
||||||
<td class="center aligned" editable="true" datatype="action" data-label="">
|
${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}
|
||||||
|
</td>
|
||||||
|
<td class="center aligned ignoremw" editable="true" datatype="action" data-label="">
|
||||||
<div class="ui toggle tiny fitted checkbox" style="margin-bottom: -0.5em; margin-right: 0.4em;" title="Enable / Disable Rule">
|
<div class="ui toggle tiny fitted checkbox" style="margin-bottom: -0.5em; margin-right: 0.4em;" title="Enable / Disable Rule">
|
||||||
<input type="checkbox" class="enableToggle" name="active" ${enableChecked} eptuuid="${subd.RootOrMatchingDomain}" onchange="handleProxyRuleToggle(this);">
|
<input type="checkbox" class="enableToggle" name="active" ${enableChecked} eptuuid="${subd.RootOrMatchingDomain}" onchange="handleProxyRuleToggle(this);">
|
||||||
<label></label>
|
<label></label>
|
||||||
@ -94,9 +118,87 @@
|
|||||||
</tr>`);
|
</tr>`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolveAccessRuleNameOnHostRPlist();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Perform realtime alias update without refreshing the whole page
|
||||||
|
function updateAliasListForEndpoint(endpointName, newAliasDomainList){
|
||||||
|
let targetEle = $(`.aliasDomains[eptuuid='${endpointName}']`);
|
||||||
|
console.log(targetEle);
|
||||||
|
if (targetEle.length == 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let aliasDomains = ``;
|
||||||
|
if (newAliasDomainList != undefined && newAliasDomainList.length > 0){
|
||||||
|
aliasDomains = `Alias: `;
|
||||||
|
newAliasDomainList.forEach(alias => {
|
||||||
|
aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `;
|
||||||
|
});
|
||||||
|
aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
|
||||||
|
$(targetEle).html(aliasDomains);
|
||||||
|
$(targetEle).show();
|
||||||
|
}else{
|
||||||
|
$(targetEle).hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Resolve & Update all rule names on host PR list
|
||||||
|
function resolveAccessRuleNameOnHostRPlist(){
|
||||||
|
//Resolve the access filters
|
||||||
|
$.get("/api/access/list", function(data){
|
||||||
|
console.log(data);
|
||||||
|
if (data.error == undefined){
|
||||||
|
//Build a map base on the data
|
||||||
|
let accessRuleMap = {};
|
||||||
|
for (var i = 0; i < data.length; i++){
|
||||||
|
accessRuleMap[data[i].ID] = data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$(".accessRuleNameUnderHost").each(function(){
|
||||||
|
let thisAccessRuleID = $(this).attr("ruleid");
|
||||||
|
if (thisAccessRuleID== ""){
|
||||||
|
thisAccessRuleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thisAccessRuleID == "default"){
|
||||||
|
//No need to label default access rules
|
||||||
|
$(this).html("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rule = accessRuleMap[thisAccessRuleID];
|
||||||
|
let icon = `<i class="ui grey filter icon"></i>`;
|
||||||
|
if (rule.ID == "default"){
|
||||||
|
icon = `<i class="ui yellow star icon"></i>`;
|
||||||
|
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
|
||||||
|
//This is a blacklist filter
|
||||||
|
icon = `<i class="ui red filter icon"></i>`;
|
||||||
|
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
|
||||||
|
//This is a whitelist filter
|
||||||
|
icon = `<i class="ui green filter icon"></i>`;
|
||||||
|
}else if (rule.WhitelistEnabled && rule.BlacklistEnabled){
|
||||||
|
//Whitelist and blacklist filter
|
||||||
|
icon = `<i class="ui yellow filter icon"></i>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule != undefined){
|
||||||
|
$(this).html(`${icon} ${rule.Name}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update the access rule name on given epuuid, call by hostAccessEditor.html
|
||||||
|
function updateAccessRuleNameUnderHost(epuuid, newruleUID){
|
||||||
|
$(`tr[eptuuid='${epuuid}'].subdEntry`).find(".accessRuleNameUnderHost").attr("ruleid", newruleUID);
|
||||||
|
resolveAccessRuleNameOnHostRPlist();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Inline editor for httprp.html
|
Inline editor for httprp.html
|
||||||
@ -176,7 +278,7 @@
|
|||||||
|
|
||||||
column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
|
column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
<input type="checkbox" class="RequireBasicAuth" ${checkstate}>
|
<input type="checkbox" class="RequireBasicAuth" ${checkstate}>
|
||||||
<label>Require Basic Auth</label>
|
<label>Require Basic Auth</label>
|
||||||
</div>
|
</div>
|
||||||
<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button>
|
<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button>
|
||||||
<div class="ui basic advance segment" style="padding: 0.4em !important; border-radius: 0.4em;">
|
<div class="ui basic advance segment" style="padding: 0.4em !important; border-radius: 0.4em;">
|
||||||
@ -191,6 +293,7 @@
|
|||||||
<label>Skip WebSocket Origin Check<br>
|
<label>Skip WebSocket Origin Check<br>
|
||||||
<small>Check this to allow cross-origin websocket requests</small></label>
|
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||||
</div>
|
</div>
|
||||||
|
<br>
|
||||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>
|
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>
|
||||||
<!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="blue server icon"></i> Load Balance</button> -->
|
<!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="blue server icon"></i> Load Balance</button> -->
|
||||||
</div>
|
</div>
|
||||||
@ -213,7 +316,12 @@
|
|||||||
<label>Allow plain HTTP access<br>
|
<label>Allow plain HTTP access<br>
|
||||||
<small>Allow inbound connections without TLS/SSL</small></label>
|
<small>Allow inbound connections without TLS/SSL</small></label>
|
||||||
</div><br>
|
</div><br>
|
||||||
|
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAliasHostnames('${uuid}');"><i class=" blue at icon"></i> Alias</button>
|
||||||
|
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAccessRule('${uuid}');"><i class="ui filter icon"></i> Access Rule</button>
|
||||||
|
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
$(".hostAccessRuleSelector").dropdown();
|
||||||
}else{
|
}else{
|
||||||
//Unknown field. Leave it untouched
|
//Unknown field. Leave it untouched
|
||||||
}
|
}
|
||||||
@ -277,6 +385,22 @@
|
|||||||
showSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload);
|
showSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function editAccessRule(uuid){
|
||||||
|
let payload = encodeURIComponent(JSON.stringify({
|
||||||
|
ept: "host",
|
||||||
|
ep: uuid
|
||||||
|
}));
|
||||||
|
showSideWrapper("snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function editAliasHostnames(uuid){
|
||||||
|
let payload = encodeURIComponent(JSON.stringify({
|
||||||
|
ept: "host",
|
||||||
|
ep: uuid
|
||||||
|
}));
|
||||||
|
showSideWrapper("snippet/aliasEditor.html?t=" + Date.now() + "#" + payload);
|
||||||
|
}
|
||||||
|
|
||||||
function quickEditVdir(uuid){
|
function quickEditVdir(uuid){
|
||||||
openTabById("vdir");
|
openTabById("vdir");
|
||||||
$("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid);
|
$("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid);
|
||||||
@ -313,6 +437,9 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Access List handling */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Bind on tab switch events
|
//Bind on tab switch events
|
||||||
|
@ -5,6 +5,12 @@
|
|||||||
color: var(--theme_lgrey);
|
color: var(--theme_lgrey);
|
||||||
border-radius: 1em !important;
|
border-radius: 1em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui.form .sub.field{
|
||||||
|
background-color: var(--theme_advance);
|
||||||
|
border-radius: 0.6em;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="standardContainer">
|
<div class="standardContainer">
|
||||||
<div class="ui stackable grid">
|
<div class="ui stackable grid">
|
||||||
@ -16,7 +22,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Matching Keyword / Domain</label>
|
<label>Matching Keyword / Domain</label>
|
||||||
<input type="text" id="rootname" placeholder="mydomain.com">
|
<input type="text" id="rootname" placeholder="mydomain.com">
|
||||||
<small>Support subdomain and wildcard, e.g. s1.mydomain.com or *.test.mydomain.com</small>
|
<small>Support subdomain and wildcard, e.g. s1.mydomain.com or *.test.mydomain.com. Use comma (,) for alias hostnames. </small>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Target IP Address or Domain Name with port</label>
|
<label>Target IP Address or Domain Name with port</label>
|
||||||
@ -37,7 +43,18 @@
|
|||||||
Advance Settings
|
Advance Settings
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p></p>
|
<div class="field">
|
||||||
|
<label>Access Rule</label>
|
||||||
|
<div class="ui selection dropdown">
|
||||||
|
<input type="hidden" id="newProxyRuleAccessFilter" value="default">
|
||||||
|
<i class="dropdown icon"></i>
|
||||||
|
<div class="default text">Default</div>
|
||||||
|
<div class="menu" id="newProxyRuleAccessList">
|
||||||
|
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small>Allow regional access control using blacklist or whitelist. Use "default" for "allow all".</small>
|
||||||
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" id="skipTLSValidation">
|
<input type="checkbox" id="skipTLSValidation">
|
||||||
@ -121,8 +138,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$("#advanceProxyRules").accordion();
|
|
||||||
|
|
||||||
|
|
||||||
//New Proxy Endpoint
|
//New Proxy Endpoint
|
||||||
function newProxyEndpoint(){
|
function newProxyEndpoint(){
|
||||||
@ -133,7 +148,8 @@
|
|||||||
var bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked;
|
var bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked;
|
||||||
var requireBasicAuth = $("#requireBasicAuth")[0].checked;
|
var requireBasicAuth = $("#requireBasicAuth")[0].checked;
|
||||||
var skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked;
|
var skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked;
|
||||||
|
var accessRuleToUse = $("#newProxyRuleAccessFilter").val();
|
||||||
|
|
||||||
if (rootname.trim() == ""){
|
if (rootname.trim() == ""){
|
||||||
$("#rootname").parent().addClass("error");
|
$("#rootname").parent().addClass("error");
|
||||||
return
|
return
|
||||||
@ -161,7 +177,7 @@
|
|||||||
bypassGlobalTLS: bypassGlobalTLS,
|
bypassGlobalTLS: bypassGlobalTLS,
|
||||||
bauth: requireBasicAuth,
|
bauth: requireBasicAuth,
|
||||||
cred: JSON.stringify(credentials),
|
cred: JSON.stringify(credentials),
|
||||||
|
access: accessRuleToUse,
|
||||||
},
|
},
|
||||||
success: function(data){
|
success: function(data){
|
||||||
if (data.error != undefined){
|
if (data.error != undefined){
|
||||||
@ -343,4 +359,47 @@
|
|||||||
return back;
|
return back;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Access Rule dropdown Initialization
|
||||||
|
*/
|
||||||
|
|
||||||
|
function initNewProxyRuleAccessDropdownList(callback=undefined){
|
||||||
|
$.get("/api/access/list", function(data){
|
||||||
|
if (data.error == undefined){
|
||||||
|
$("#newProxyRuleAccessList").html("");
|
||||||
|
data.forEach(function(rule){
|
||||||
|
let icon = `<i class="ui grey filter icon"></i>`;
|
||||||
|
if (rule.ID == "default"){
|
||||||
|
icon = `<i class="ui yellow star icon"></i>`;
|
||||||
|
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
|
||||||
|
//This is a blacklist filter
|
||||||
|
icon = `<i class="ui red filter icon"></i>`;
|
||||||
|
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
|
||||||
|
//This is a whitelist filter
|
||||||
|
icon = `<i class="ui green filter icon"></i>`;
|
||||||
|
}
|
||||||
|
$("#newProxyRuleAccessList").append(`<div class="item" data-value="${rule.ID}">${icon} ${rule.Name}</div>`);
|
||||||
|
});
|
||||||
|
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||||
|
if (callback != undefined){
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
msgbox("Access rule load failed: " + data.error, false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
initNewProxyRuleAccessDropdownList();
|
||||||
|
|
||||||
|
//Bind on tab switch events
|
||||||
|
tabSwitchEventBind["rules"] = function(){
|
||||||
|
//Update the access rule list
|
||||||
|
initNewProxyRuleAccessDropdownList();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
$("#advanceProxyRules").accordion();
|
||||||
|
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
@ -2,9 +2,6 @@
|
|||||||
index.html style overwrite
|
index.html style overwrite
|
||||||
*/
|
*/
|
||||||
:root{
|
:root{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--theme_background: linear-gradient(60deg, rgb(84, 58, 183) 0%, rgb(0, 172, 193) 100%);
|
--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_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_green: linear-gradient(270deg, #27e7ff, #00ca52);
|
||||||
@ -256,7 +253,7 @@ body{
|
|||||||
|
|
||||||
.sideWrapperMenu{
|
.sideWrapperMenu{
|
||||||
height: 3px;
|
height: 3px;
|
||||||
background-color: #414141;
|
background: var(--theme_background);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
src: local('Lato Bold'), local('Lato-Bold'), url(https://fonts.gstatic.com/s/lato/v17/S6u9w4BMUTPHh6UVSwaPGR_p.woff2) format('woff2');
|
src: local('Lato Bold'), local('Lato-Bold'), url(fonts/S6u9w4BMUTPHh6UVSwaPGR_p.woff2) format('woff2');
|
||||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@ -59,6 +59,6 @@
|
|||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
src: local('Lato Bold'), local('Lato-Bold'), url(https://fonts.gstatic.com/s/lato/v17/S6u9w4BMUTPHh6UVSwiPGQ.woff2) format('woff2');
|
src: local('Lato Bold'), local('Lato-Bold'), url(fonts/S6u9w4BMUTPHh6UVSwiPGQ.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
|
267
src/web/snippet/accessRuleEditor.html
Normal file
267
src/web/snippet/accessRuleEditor.html
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
<style>
|
||||||
|
#refreshAccessRuleListBtn{
|
||||||
|
position: absolute;
|
||||||
|
top: 0.4em;
|
||||||
|
right: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui header">
|
||||||
|
<div class="content">
|
||||||
|
Access Rule Editor
|
||||||
|
<div class="sub header">Create, Edit or Remove Access Rules</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui top attached tabular menu">
|
||||||
|
<a class="active item" data-tab="new"><i class="ui green add icon"></i> New</a>
|
||||||
|
<a class="item" data-tab="edit"><i class="ui grey edit icon"></i> Edit</a>
|
||||||
|
</div>
|
||||||
|
<div class="ui bottom attached active tab segment" data-tab="new">
|
||||||
|
<p>Create a new Access Rule</p>
|
||||||
|
<form class="ui form" id="accessRuleForm">
|
||||||
|
<div class="field">
|
||||||
|
<label>Rule Name</label>
|
||||||
|
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Description</label>
|
||||||
|
<textarea name="description" placeholder="Description" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
<div class="ui bottom attached tab segment" data-tab="edit">
|
||||||
|
<p>Select an Access Rule to edit</p>
|
||||||
|
<button id="refreshAccessRuleListBtn" class="ui circular basic icon button" onclick="reloadAccessRuleList()"><i class="ui green refresh icon"></i></button>
|
||||||
|
<div class="ui selection fluid dropdown" id="accessRuleSelector">
|
||||||
|
<input type="hidden" name="targetAccessRule" value="default">
|
||||||
|
<i class="dropdown icon"></i>
|
||||||
|
<div class="default text"></div>
|
||||||
|
<div class="menu" id="accessRuleList">
|
||||||
|
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<form class="ui form" id="modifyRuleInfo">
|
||||||
|
<div class="disabled field">
|
||||||
|
<label>Rule ID</label>
|
||||||
|
<input type="text" name="accessRuleUUID">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Rule Name</label>
|
||||||
|
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Description</label>
|
||||||
|
<textarea name="description" placeholder="Description" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button class="ui basic button" type="submit"><i class="ui green save icon"></i> Save Changes</button>
|
||||||
|
<button class="ui basic button" onclick="removeAccessRule(event);"><i class="ui red trash icon"></i> Remove Rule</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
|
||||||
|
<br><br><br>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let accessRuleList = [];
|
||||||
|
$('.dropdown').dropdown();
|
||||||
|
$('.menu .item').tab();
|
||||||
|
|
||||||
|
function handleCreateNewAccessRule(event) {
|
||||||
|
event.preventDefault(); // Prevent the default form submission
|
||||||
|
const formData = new FormData(event.target);
|
||||||
|
const accessRuleName = formData.get('accessRuleName');
|
||||||
|
const description = formData.get('description');
|
||||||
|
|
||||||
|
console.log('Access Rule Name:', accessRuleName);
|
||||||
|
console.log('Description:', description);
|
||||||
|
|
||||||
|
$("#accessRuleForm input[name='accessRuleName']").val("");
|
||||||
|
$("#accessRuleForm textarea[name='description']").val("");
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/access/create",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
"name": accessRuleName,
|
||||||
|
"desc": description
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
parent.msgbox("Access Rule Created", true);
|
||||||
|
reloadAccessRuleList();
|
||||||
|
if (parent != undefined && parent.reloadAccessRules != undefined){
|
||||||
|
parent.reloadAccessRules();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle on change of the dropdown selection
|
||||||
|
function handleSelectEditingAccessRule(){
|
||||||
|
const selectedValue = document.querySelector('#accessRuleSelector').querySelector('input').value;
|
||||||
|
console.log('Selected Value:', selectedValue);
|
||||||
|
//Load the information from list
|
||||||
|
loadAccessRuleInfoIntoEditFields(selectedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the access rules information into the fields
|
||||||
|
function loadAccessRuleInfoIntoEditFields(targetAccessRuleUUID){
|
||||||
|
var targetAccessRule = undefined;
|
||||||
|
for (var i = 0; i < accessRuleList.length; i++){
|
||||||
|
let thisAccessRule = accessRuleList[i];
|
||||||
|
if (thisAccessRule.ID == targetAccessRuleUUID){
|
||||||
|
targetAccessRule = thisAccessRule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetAccessRule == undefined){
|
||||||
|
//Target exists rule no longer exists
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let accessRuleID = targetAccessRule.ID;
|
||||||
|
let accessRuleName = targetAccessRule.Name;
|
||||||
|
let accessRuleDesc = targetAccessRule.Desc;
|
||||||
|
|
||||||
|
//Load the information into the form input field
|
||||||
|
//Load the information into the form input field
|
||||||
|
document.querySelector('#modifyRuleInfo input[name="accessRuleUUID"]').value = accessRuleID;
|
||||||
|
document.querySelector('#modifyRuleInfo input[name="accessRuleName"]').value = accessRuleName;
|
||||||
|
document.querySelector('#modifyRuleInfo textarea[name="description"]').value = accessRuleDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Bind events to modify rule form
|
||||||
|
document.getElementById('modifyRuleInfo').addEventListener('submit', function(event){
|
||||||
|
event.preventDefault(); // Prevent the default form submission
|
||||||
|
|
||||||
|
const accessRuleUUID = document.querySelector('#modifyRuleInfo input[name="accessRuleUUID"]').value;
|
||||||
|
const accessRuleName = document.querySelector('#modifyRuleInfo input[name="accessRuleName"]').value;
|
||||||
|
const description = document.querySelector('#modifyRuleInfo textarea[name="description"]').value;
|
||||||
|
|
||||||
|
|
||||||
|
console.log('Access Rule UUID:', accessRuleUUID);
|
||||||
|
console.log('Access Rule Name:', accessRuleName);
|
||||||
|
console.log('Description:', description);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/access/update",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
"id":accessRuleUUID,
|
||||||
|
"name":accessRuleName,
|
||||||
|
"desc":description
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
parent.msgbox("Access rule updated", true);
|
||||||
|
initAccessRuleList(function(){
|
||||||
|
$("#accessRuleSelector").dropdown("set selected", accessRuleUUID);
|
||||||
|
loadAccessRuleInfoIntoEditFields(accessRuleUUID);
|
||||||
|
});
|
||||||
|
if (parent != undefined && parent.reloadAccessRules != undefined){
|
||||||
|
parent.reloadAccessRules();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
function initAccessRuleList(callback=undefined){
|
||||||
|
$.get("/api/access/list", function(data){
|
||||||
|
if (data.error == undefined){
|
||||||
|
$("#accessRuleList").html("");
|
||||||
|
data.forEach(function(rule){
|
||||||
|
let icon = `<i class="ui grey filter icon"></i>`;
|
||||||
|
if (rule.ID == "default"){
|
||||||
|
icon = `<i class="ui yellow star icon"></i>`;
|
||||||
|
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
|
||||||
|
//This is a blacklist filter
|
||||||
|
icon = `<i class="ui red filter icon"></i>`;
|
||||||
|
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
|
||||||
|
//This is a whitelist filter
|
||||||
|
icon = `<i class="ui green filter icon"></i>`;
|
||||||
|
}
|
||||||
|
$("#accessRuleList").append(`<div class="item" data-value="${rule.ID}">${icon} ${rule.Name}</div>`);
|
||||||
|
});
|
||||||
|
accessRuleList = data;
|
||||||
|
$(".dropdown").dropdown();
|
||||||
|
if (callback != undefined){
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
initAccessRuleList(function(){
|
||||||
|
$("#accessRuleSelector").dropdown("set selected", "default");
|
||||||
|
loadAccessRuleInfoIntoEditFields("default");
|
||||||
|
});
|
||||||
|
|
||||||
|
function reloadAccessRuleList(){
|
||||||
|
initAccessRuleList(function(){
|
||||||
|
$("#accessRuleSelector").dropdown("set selected", "default");
|
||||||
|
loadAccessRuleInfoIntoEditFields("default");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAccessRule(event){
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
|
let accessRuleUUID = $("#modifyRuleInfo input[name='accessRuleUUID']").val();
|
||||||
|
if (accessRuleUUID == ""){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (accessRuleUUID == "default"){
|
||||||
|
parent.msgbox("Default access rule cannot be removed", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let accessRuleName = $("#modifyRuleInfo input[name='accessRuleName']").val();
|
||||||
|
if (confirm("Confirm removing access rule " + accessRuleName + "?")){
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/access/remove",
|
||||||
|
data: {
|
||||||
|
"id": accessRuleUUID
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
parent.msgbox("Access rule removed", true);
|
||||||
|
reloadAccessRuleList();
|
||||||
|
if (parent != undefined && parent.reloadAccessRules != undefined){
|
||||||
|
parent.reloadAccessRules();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById('accessRuleSelector').addEventListener('change', handleSelectEditingAccessRule);
|
||||||
|
document.getElementById('accessRuleForm').addEventListener('submit', handleCreateNewAccessRule);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -118,6 +118,14 @@
|
|||||||
<label>ACME Server URL</label>
|
<label>ACME Server URL</label>
|
||||||
<input id="caURL" type="text" placeholder="https://example.com/acme/dictionary">
|
<input id="caURL" type="text" placeholder="https://example.com/acme/dictionary">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field" id="kidInput" style="display:none;">
|
||||||
|
<label>EAB Credentials (KID) for current provider</label>
|
||||||
|
<input id="eab_kid" type="text" placeholder="Leave this field blank to keep the current configuration">
|
||||||
|
</div>
|
||||||
|
<div class="field" id="hmacInput" style="display:none;">
|
||||||
|
<label>EAB HMAC Key for current provider</label>
|
||||||
|
<input id="eab_hmac" type="text" placeholder="Leave this field blank to keep the current configuration">
|
||||||
|
</div>
|
||||||
<div class="field" id="skipTLS" style="display:none;">
|
<div class="field" id="skipTLS" style="display:none;">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" id="skipTLSCheckbox">
|
<input type="checkbox" id="skipTLSCheckbox">
|
||||||
@ -314,19 +322,88 @@
|
|||||||
// Button click event handler for obtaining certificate
|
// Button click event handler for obtaining certificate
|
||||||
$("#obtainButton").click(function() {
|
$("#obtainButton").click(function() {
|
||||||
$("#obtainButton").addClass("loading").addClass("disabled");
|
$("#obtainButton").addClass("loading").addClass("disabled");
|
||||||
|
updateCertificateEAB();
|
||||||
obtainCertificate();
|
obtainCertificate();
|
||||||
});
|
});
|
||||||
|
|
||||||
$("input[name=ca]").on('change', function() {
|
$("input[name=ca]").on('change', function() {
|
||||||
if(this.value == "Custom ACME Server") {
|
if(this.value == "Custom ACME Server") {
|
||||||
$("#caInput").show();
|
$("#caInput").show();
|
||||||
|
$("#kidInput").show();
|
||||||
|
$("#hmacInput").show();
|
||||||
$("#skipTLS").show();
|
$("#skipTLS").show();
|
||||||
} else {
|
} else if (this.value == "ZeroSSL") {
|
||||||
|
$("#kidInput").show();
|
||||||
|
$("#hmacInput").show();
|
||||||
|
} else if (this.value == "Buypass") {
|
||||||
|
$("#kidInput").show();
|
||||||
|
$("#hmacInput").show();
|
||||||
|
}else {
|
||||||
$("#caInput").hide();
|
$("#caInput").hide();
|
||||||
$("#skipTLS").hide();
|
$("#skipTLS").hide();
|
||||||
|
$("#kidInput").hide();
|
||||||
|
$("#hmacInput").hide();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// Obtain certificate from API
|
||||||
|
function updateCertificateEAB() {
|
||||||
|
var ca = $("#ca").dropdown("get value");
|
||||||
|
var caURL = "";
|
||||||
|
if (ca == "Custom ACME Server") {
|
||||||
|
ca = "custom";
|
||||||
|
caURL = $("#caURL").val();
|
||||||
|
}else if(ca == "Buypass") {
|
||||||
|
caURL = "https://api.buypass.com/acme/directory";
|
||||||
|
}else if(ca == "ZeroSSL") {
|
||||||
|
caURL = "https://acme.zerossl.com/v2/DV90";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(caURL == "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var kid = $("#eab_kid").val();
|
||||||
|
var hmac = $("#eab_hmac").val();
|
||||||
|
|
||||||
|
if(kid == "" || hmac == "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(caURL + " " + kid + " " + hmac);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/acme/autoRenew/setEAB",
|
||||||
|
method: "GET",
|
||||||
|
data: {
|
||||||
|
acmeDirectoryURL: caURL,
|
||||||
|
kid: kid,
|
||||||
|
hmacEncoded: hmac,
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
//$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||||
|
if (response.error) {
|
||||||
|
console.log("Error:", response.error);
|
||||||
|
// Show error message
|
||||||
|
parent.msgbox(response.error, false, 12000);
|
||||||
|
} else {
|
||||||
|
console.log("Certificate EAB updated successfully");
|
||||||
|
// Show success message
|
||||||
|
parent.msgbox("Certificate EAB updated successfully");
|
||||||
|
|
||||||
|
// Renew the parent certificate list
|
||||||
|
parent.initManagedDomainCertificateList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
//$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||||
|
console.log("Failed to update EAB configuration:", error);
|
||||||
|
parent.msgbox("Failed to update EAB configuration");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Obtain certificate from API
|
// Obtain certificate from API
|
||||||
function obtainCertificate() {
|
function obtainCertificate() {
|
||||||
var domains = $("#domainsInput").val();
|
var domains = $("#domainsInput").val();
|
||||||
|
178
src/web/snippet/aliasEditor.html
Normal file
178
src/web/snippet/aliasEditor.html
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui header">
|
||||||
|
<div class="content">
|
||||||
|
Alias Hostname
|
||||||
|
<div class="sub header epname"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="scrolling content ui form">
|
||||||
|
<div id="inlineEditBasicAuthCredentials" class="field">
|
||||||
|
<p>Enter alias hostname or wildcard matching keywords for <code class="epname"></code></p>
|
||||||
|
<table class="ui very basic compacted unstackable celled table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Alias Hostname</th>
|
||||||
|
<th>Remove</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody id="inlineEditTable">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><i class="ui green circle check icon"></i> No Alias Hostname</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="three small fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>Alias Hostname</label>
|
||||||
|
<input id="aliasHostname" type="text" placeholder="alias.mydomain.com" autocomplete="off">
|
||||||
|
<small>Support wildcards e.g. alias.mydomain.com or *.alias.mydomain.com</small>
|
||||||
|
</div>
|
||||||
|
<div class="field" >
|
||||||
|
<button class="ui basic button" onclick="addAliasToRoutingRule();"><i class="green add icon"></i> Add Alias</button>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="field" >
|
||||||
|
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br><br><br><br>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let aliasList = [];
|
||||||
|
let editingEndpoint = {};
|
||||||
|
|
||||||
|
if (window.location.hash.length > 1){
|
||||||
|
let payloadHash = window.location.hash.substr(1);
|
||||||
|
try{
|
||||||
|
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
||||||
|
$(".epname").text(payloadHash.ep);
|
||||||
|
editingEndpoint = payloadHash;
|
||||||
|
}catch(ex){
|
||||||
|
console.log("Unable to load endpoint data from hash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initAliasNames(){
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/detail",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
"type":"host",
|
||||||
|
"epname": editingEndpoint.ep
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
//This endpoint not exists?
|
||||||
|
alert(data.error);
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
$("#inlineEditTable").html("");
|
||||||
|
if (data.MatchingDomainAlias != undefined){
|
||||||
|
aliasList = data.MatchingDomainAlias;
|
||||||
|
renderAliasList();
|
||||||
|
}else{
|
||||||
|
//Assume no alias
|
||||||
|
$("#inlineEditTable").html(`<tr>
|
||||||
|
<td colspan="2"><i class="ui green circle check icon"></i> No Alias Hostname</td>
|
||||||
|
</tr>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
initAliasNames();
|
||||||
|
|
||||||
|
function removeAliasDomain(targetDomain){
|
||||||
|
aliasList.splice(aliasList.indexOf(targetDomain), 1);
|
||||||
|
saveCurrentAliasList(function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error);
|
||||||
|
}else{
|
||||||
|
initAliasNames();
|
||||||
|
parent.msgbox("Alias hostname removed")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAliasToRoutingRule(){
|
||||||
|
let newAliasHostname = $("#aliasHostname").val().trim();
|
||||||
|
aliasList.push(newAliasHostname);
|
||||||
|
$("#aliasHostname").val("");
|
||||||
|
saveCurrentAliasList(function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error);
|
||||||
|
}else{
|
||||||
|
parent.msgbox("New alias hostname added")
|
||||||
|
initAliasNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCurrentAliasList(callback=undefined){
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/setAlias",
|
||||||
|
method: "POST",
|
||||||
|
data:{
|
||||||
|
"ep":editingEndpoint.ep,
|
||||||
|
"alias": JSON.stringify(aliasList)
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (callback != undefined){
|
||||||
|
callback(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.error == undefined && parent != undefined && parent.document != undefined){
|
||||||
|
//Try to update the parent object's rules if exists
|
||||||
|
parent.updateAliasListForEndpoint(editingEndpoint.ep, aliasList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAliasList(){
|
||||||
|
$("#inlineEditTable").html("");
|
||||||
|
aliasList.forEach(aliasDomain => {
|
||||||
|
let domainLink = `<a href="//${aliasDomain}" target="_blank">${aliasDomain}</a>`
|
||||||
|
if (aliasDomain.includes("*")){
|
||||||
|
//This is a wildcard hostname
|
||||||
|
domainLink = aliasDomain;
|
||||||
|
}
|
||||||
|
$("#inlineEditTable").append(`<tr>
|
||||||
|
<td>${domainLink}</td>
|
||||||
|
<td><button class="ui basic button" onclick="removeAliasDomain('${aliasDomain}');"><i class="red remove icon"></i> Remove</button></td>
|
||||||
|
</tr>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (aliasList.length == 0){
|
||||||
|
$("#inlineEditTable").html(`<tr>
|
||||||
|
<td colspan="2"><i class="ui green circle check icon"></i> No Alias Hostname</td>
|
||||||
|
</tr>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeThisWrapper(){
|
||||||
|
parent.hideSideWrapper(true);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
187
src/web/snippet/hostAccessEditor.html
Normal file
187
src/web/snippet/hostAccessEditor.html
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
<style>
|
||||||
|
.accessRule{
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0.4em !important;
|
||||||
|
border: 1px solid rgb(233, 233, 233) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessRule:hover{
|
||||||
|
background-color: rgb(241, 241, 241) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessRule.active{
|
||||||
|
background-color: rgb(241, 241, 241) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessRule .selected{
|
||||||
|
position: absolute;
|
||||||
|
top: 1em;
|
||||||
|
right: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessRule:not(.active) .selected{
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#accessRuleList{
|
||||||
|
padding: 0.6em;
|
||||||
|
border: 1px solid rgb(228, 228, 228);
|
||||||
|
border-radius: 0.4em !important;
|
||||||
|
max-height: calc(100vh - 15em);
|
||||||
|
min-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui header">
|
||||||
|
<div class="content">
|
||||||
|
Host Access Settings
|
||||||
|
<div class="sub header" id="epname"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<p>Select an access rule to apply blacklist / whitelist filtering</p>
|
||||||
|
<div id="accessRuleList">
|
||||||
|
<div class="ui segment accessRule">
|
||||||
|
<div class="ui header">
|
||||||
|
<i class="filter icon"></i>
|
||||||
|
<div class="content">
|
||||||
|
Account Settings
|
||||||
|
<div class="sub header">Manage your preferences</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<button class="ui basic button" onclick="applyChangeAndClose()"><i class="ui green check icon"></i> Apply Change</button>
|
||||||
|
|
||||||
|
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
|
||||||
|
<br><br><br>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let editingEndpoint = {};
|
||||||
|
if (window.location.hash.length > 1){
|
||||||
|
let payloadHash = window.location.hash.substr(1);
|
||||||
|
try{
|
||||||
|
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
||||||
|
$("#epname").text(payloadHash.ep);
|
||||||
|
editingEndpoint = payloadHash;
|
||||||
|
}catch(ex){
|
||||||
|
console.log("Unable to load endpoint data from hash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initAccessRuleList(callback = undefined){
|
||||||
|
$("#accessRuleList").html("<small>Loading</small>");
|
||||||
|
$.get("/api/access/list", function(data){
|
||||||
|
if (data.error == undefined){
|
||||||
|
$("#accessRuleList").html("");
|
||||||
|
data.forEach(function(rule){
|
||||||
|
let icon = `<i class="ui grey filter icon"></i>`;
|
||||||
|
if (rule.ID == "default"){
|
||||||
|
icon = `<i class="ui yellow star icon"></i>`;
|
||||||
|
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
|
||||||
|
//This is a blacklist filter
|
||||||
|
icon = `<i class="ui red filter icon"></i>`;
|
||||||
|
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
|
||||||
|
//This is a whitelist filter
|
||||||
|
icon = `<i class="ui green filter icon"></i>`;
|
||||||
|
}else if (rule.WhitelistEnabled && rule.BlacklistEnabled){
|
||||||
|
//Whitelist and blacklist filter
|
||||||
|
icon = `<i class="ui yellow filter icon"></i>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#accessRuleList").append(`<div class="ui basic segment accessRule" ruleid="${rule.ID}" onclick="selectThisRule(this);">
|
||||||
|
<h5 class="ui header">
|
||||||
|
${icon}
|
||||||
|
<div class="content">
|
||||||
|
${rule.Name}
|
||||||
|
<div class="sub header">${rule.ID}</div>
|
||||||
|
</div>
|
||||||
|
</h5>
|
||||||
|
<p>${rule.Desc}</p>
|
||||||
|
${rule.BlacklistEnabled?`<small><i class="ui red filter icon"></i> Blacklist Enabled</small>`:""}
|
||||||
|
${rule.WhitelistEnabled?`<small><i class="ui green filter icon"></i> Whitelist Enabled</small>`:""}
|
||||||
|
<div class="selected"><i class="ui large green check icon"></i></div>
|
||||||
|
</div>`);
|
||||||
|
});
|
||||||
|
accessRuleList = data;
|
||||||
|
$(".dropdown").dropdown();
|
||||||
|
if (callback != undefined){
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
initAccessRuleList(function(){
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/detail",
|
||||||
|
method: "POST",
|
||||||
|
data: {"type":"host", "epname": editingEndpoint.ep },
|
||||||
|
success: function(data){
|
||||||
|
console.log(data);
|
||||||
|
if (data.error != undefined){
|
||||||
|
alert(data.error);
|
||||||
|
}else{
|
||||||
|
let currentAccessFilter = data.AccessFilterUUID;
|
||||||
|
if (currentAccessFilter == ""){
|
||||||
|
//Use default
|
||||||
|
currentAccessFilter = "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
$(`.accessRule[ruleid=${currentAccessFilter}]`).addClass("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function selectThisRule(accessRuleObject){
|
||||||
|
let accessRuleID = $(accessRuleObject).attr("ruleid");
|
||||||
|
$(".accessRule").removeClass('active');
|
||||||
|
$(accessRuleObject).addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyChangeAndClose(){
|
||||||
|
let newAccessRuleID = $(".accessRule.active").attr("ruleid");
|
||||||
|
let targetEndpoint = editingEndpoint.ep;
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/access/attach",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
id: newAccessRuleID,
|
||||||
|
host: targetEndpoint
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
parent.msgbox("Access Rule Updated");
|
||||||
|
|
||||||
|
//Modify the parent list if exists
|
||||||
|
if (parent != undefined && parent.updateAccessRuleNameUnderHost){
|
||||||
|
parent.updateAccessRuleNameUnderHost(targetEndpoint, newAccessRuleID);
|
||||||
|
}
|
||||||
|
parent.hideSideWrapper();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user