mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-08-11 07:37:51 +02:00
v3.0.2 init commit
+ Fixed zeroSSL bug (said by @yeungalan ) #45 + Fixed manual renew button bug + Seperated geodb module with access controller + Added per hosts access control (experimental) #69 + Fixed basic auth not working on TLS bypass mode bug + Fixed empty domain crash bug #120
This commit is contained in:
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"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@@ -40,6 +42,11 @@ type ACMEUser struct {
|
||||
key crypto.PrivateKey
|
||||
}
|
||||
|
||||
type EABConfig struct {
|
||||
Kid string `json:"kid"`
|
||||
HmacKey string `json:"HmacKey"`
|
||||
}
|
||||
|
||||
// GetEmail returns the email of the ACMEUser.
|
||||
func (u *ACMEUser) GetEmail() string {
|
||||
return u.Email
|
||||
@@ -59,13 +66,15 @@ func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey {
|
||||
type ACMEHandler struct {
|
||||
DefaultAcmeServer string
|
||||
Port string
|
||||
Database *database.Database
|
||||
}
|
||||
|
||||
// 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{
|
||||
DefaultAcmeServer: acmeServer,
|
||||
Port: port,
|
||||
Database: database,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,10 +152,63 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
}
|
||||
|
||||
// New users will need to register
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
/*
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
|
@@ -373,3 +373,34 @@ func (a *AutoRenewer) saveRenewConfigToFile() error {
|
||||
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
|
||||
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"
|
||||
"path/filepath"
|
||||
"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)
|
||||
if matchedRoutingRule != nil {
|
||||
//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)
|
||||
return
|
||||
}
|
||||
@@ -47,14 +37,6 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
//Inject headers
|
||||
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
|
||||
|
||||
/*
|
||||
General Access Check
|
||||
*/
|
||||
respWritten := h.handleAccessRouting(w, r)
|
||||
if respWritten {
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Redirection Routing
|
||||
*/
|
||||
@@ -65,19 +47,30 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
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
|
||||
if strings.Contains(r.Host, ":") {
|
||||
hostPath := strings.Split(r.Host, ":")
|
||||
domainOnly = hostPath[0]
|
||||
}
|
||||
|
||||
/*
|
||||
Host Routing
|
||||
*/
|
||||
|
||||
sep := h.Parent.getProxyEndpointFromHostname(domainOnly)
|
||||
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 {
|
||||
err := h.handleBasicAuthRouting(w, r, sep)
|
||||
if err != nil {
|
||||
@@ -136,7 +129,6 @@ Once entered this routing segment, the root routing options will take over
|
||||
for the routing logic.
|
||||
*/
|
||||
func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
domainOnly := r.Host
|
||||
if strings.Contains(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 {
|
||||
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 {
|
||||
//Check if the current path matches the exception rules
|
||||
for _, exceptionRule := range pe.BasicAuthExceptionRules {
|
||||
@@ -44,7 +54,6 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
if !matchingFound {
|
||||
h.logRequest(r, false, 401, "host", pe.Domain)
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
w.WriteHeader(401)
|
||||
return errors.New("unauthorized")
|
||||
|
@@ -115,6 +115,28 @@ func (router *Router) StartProxyService() error {
|
||||
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{
|
||||
ProxyDomain: sep.Domain,
|
||||
OriginalHost: originalHostHeader,
|
||||
|
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"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/websocketproxy"
|
||||
)
|
||||
@@ -224,7 +224,7 @@ func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, fo
|
||||
if h.Parent.Option.StatisticCollector != nil {
|
||||
go func() {
|
||||
requestInfo := statistic.RequestInfo{
|
||||
IpAddr: geodb.GetRequesterIP(r),
|
||||
IpAddr: netutils.GetRequesterIP(r),
|
||||
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||
Succ: succ,
|
||||
StatusCode: statusCode,
|
||||
|
@@ -19,6 +19,9 @@ import (
|
||||
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
||||
//Filter the tailing slash if any
|
||||
domain := endpoint.Domain
|
||||
if len(domain) == 0 {
|
||||
return nil, errors.New("invalid endpoint config")
|
||||
}
|
||||
if 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
|
||||
for _, vdir := range endpoint.VirtualDirectories {
|
||||
domain := vdir.Domain
|
||||
if len(domain) == 0 {
|
||||
//invalid vdir
|
||||
continue
|
||||
}
|
||||
if domain[len(domain)-1:] == "/" {
|
||||
domain = domain[:len(domain)-1]
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
@@ -34,7 +35,8 @@ type RouterOption struct {
|
||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||
TlsManager *tlscert.Manager
|
||||
RedirectRuleTable *redirection.RuleTable
|
||||
GeodbStore *geodb.Store //GeoIP blacklist and whitelist
|
||||
GeodbStore *geodb.Store //GeoIP resolver
|
||||
AccessController *access.Controller //Blacklist / whitelist controller
|
||||
StatisticCollector *statistic.Collector
|
||||
WebDirectory string //The static web server directory containing the templates folder
|
||||
}
|
||||
@@ -111,14 +113,17 @@ type ProxyEndpoint struct {
|
||||
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
|
||||
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||
|
||||
//Fallback routing logic
|
||||
DefaultSiteOption int //Fallback routing logic options
|
||||
DefaultSiteValue string //Fallback routing target, optional
|
||||
//Access Control
|
||||
AccessFilterUUID string //Access filter ID
|
||||
|
||||
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
|
||||
parent *Router
|
||||
parent *Router `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 (
|
||||
_ "embed"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
)
|
||||
|
||||
//go:embed geoipv4.csv
|
||||
@@ -16,12 +15,10 @@ var geoipv4 []byte //Geodb dataset for ipv4
|
||||
var geoipv6 []byte //Geodb dataset for ipv6
|
||||
|
||||
type Store struct {
|
||||
BlacklistEnabled bool
|
||||
WhitelistEnabled bool
|
||||
geodb [][]string //Parsed geodb list
|
||||
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
||||
geotrie *trie
|
||||
geotrieIpv6 *trie
|
||||
geodb [][]string //Parsed geodb list
|
||||
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
||||
geotrie *trie
|
||||
geotrieIpv6 *trie
|
||||
//geoipCache sync.Map
|
||||
sysdb *database.Database
|
||||
option *StoreOptions
|
||||
@@ -48,40 +45,6 @@ func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
|
||||
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
|
||||
if !option.AllowSlowIpv4LookUp {
|
||||
ipv4Trie = constrctTrieTree(parsedGeoData)
|
||||
@@ -93,27 +56,15 @@ func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
|
||||
}
|
||||
|
||||
return &Store{
|
||||
BlacklistEnabled: blacklistEnabled,
|
||||
WhitelistEnabled: whitelistEnabled,
|
||||
geodb: parsedGeoData,
|
||||
geotrie: ipv4Trie,
|
||||
geodbIpv6: parsedGeoDataIpv6,
|
||||
geotrieIpv6: ipv6Trie,
|
||||
sysdb: sysdb,
|
||||
option: option,
|
||||
geodb: parsedGeoData,
|
||||
geotrie: ipv4Trie,
|
||||
geodbIpv6: parsedGeoDataIpv6,
|
||||
geotrieIpv6: ipv6Trie,
|
||||
sysdb: sysdb,
|
||||
option: option,
|
||||
}, 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) {
|
||||
cc := s.search(ipstring)
|
||||
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 {
|
||||
ipAddr := GetRequesterIP(r)
|
||||
ipAddr := netutils.GetRequesterIP(r)
|
||||
if ipAddr == "" {
|
||||
return ""
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
)
|
||||
|
||||
func (s *Store) search(ip string) string {
|
||||
@@ -24,7 +26,7 @@ func (s *Store) search(ip string) string {
|
||||
|
||||
//Search in geotrie tree
|
||||
cc := ""
|
||||
if IsIPv6(ip) {
|
||||
if netutils.IsIPv6(ip) {
|
||||
if s.geotrieIpv6 == nil {
|
||||
cc = s.slowSearchIpv6(ip)
|
||||
} 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 (
|
||||
"net"
|
||||
@@ -6,7 +6,13 @@ import (
|
||||
"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 {
|
||||
ip := r.Header.Get("X-Real-Ip")
|
||||
if ip == "" {
|
Reference in New Issue
Block a user