Browse Source

Merge remote-tracking branch 'tc/master' into acme

Alan Yeung 1 year ago
parent
commit
f08ba744a7
10 changed files with 480 additions and 14 deletions
  1. 35 0
      acme.go
  2. 5 0
      api.go
  3. 1 0
      docs/expose proxy proposed design
  4. 5 0
      main.go
  5. 13 13
      mod/dynamicproxy/special.go
  6. 16 0
      mod/expose/expose.go
  7. 111 0
      mod/expose/security.go
  8. 100 0
      mod/pathblock/handler.go
  9. 175 0
      mod/pathblock/pathblock.go
  10. 19 1
      start.go

+ 35 - 0
acme.go

@@ -0,0 +1,35 @@
+package main
+
+import (
+	"log"
+	"net/http"
+
+	"imuslab.com/zoraxy/mod/dynamicproxy"
+)
+
+/*
+	acme.go
+
+	This script handle special routing required for acme auto cert renew functions
+*/
+
+func acmeRegisterSpecialRoutingRule() {
+	err := dynamicProxyRouter.AddRoutingRules(&dynamicproxy.RoutingRule{
+		ID: "acme-autorenew",
+		MatchRule: func(r *http.Request) bool {
+			if r.RequestURI == "/.well-known/" {
+				return true
+			}
+
+			return false
+		},
+		RoutingHandler: func(w http.ResponseWriter, r *http.Request) {
+			w.Write([]byte("HELLO WORLD, THIS IS ACME REQUEST HANDLER"))
+		},
+		Enabled: true,
+	})
+
+	if err != nil {
+		log.Println("[Err] " + err.Error())
+	}
+}

+ 5 - 0
api.go

@@ -81,6 +81,11 @@ func initAPIs() {
 	authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove)
 	authRouter.HandleFunc("/api/whitelist/enable", handleWhitelistEnable)
 
+	//Path Blocker APIs
+	authRouter.HandleFunc("/api/pathblock/add", pathBlockHandler.HandleAddBlockingPath)
+	authRouter.HandleFunc("/api/pathblock/list", pathBlockHandler.HandleListBlockingPath)
+	authRouter.HandleFunc("/api/pathblock/remove", pathBlockHandler.HandleRemoveBlockingPath)
+
 	//Statistic & uptime monitoring API
 	authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
 	authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)

+ 1 - 0
docs/expose proxy proposed design

@@ -0,0 +1 @@
+<mxfile host="Electron" modified="2023-06-12T15:37:54.150Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/12.4.2 Chrome/78.0.3904.130 Electron/7.1.4 Safari/537.36" etag="9LISu8mGNIQjZMcC4kTR" version="12.4.2" type="device" pages="1"><diagram id="70W0q2Aty3m3e7t70Ufr" name="Page-1">3Vrfb6M4EP5rkLYPtwIcE/qYpLuttLq7PaXS6u6lcvA08ZZgzjhN0r/+bH4FbKptcwm7IQ8RDHiMv5lvPDPgoNl6dytIuvqdU4gd36U7B904vh9gV/1rwb4UjMNCsBSMFiLvIJizFyiF5bjlhlHIWjdKzmPJ0rYw4kkCkWzJiBB8277tkcftWVOyBEswj0hsS78xKleFNPTHB/kdsOWqmtkLrosra1LdXK4kWxHKtw0R+uSgmeBcFkfr3QxijV2FSzHu8ytX6wcTkMi3DKCT2224nX337r78MX/5+4WE4q/f/ELLM4k35YLLh5X7CgFI6EQDqc6imGQZixw0Xcl1rASeOiwGALVwPDyYVy9XuQnwNUixV7dsD4BWbrJqYFnJBMREsue2elLadVmrq2f4ypma2HdLF/TDUk/pgbXeSkXGNyKCclQTwB8oGnuGIknEEqSlSB00ln0Q5fZ5h63Q8G2FgxPZylTUt61Glq1mMYNyrqbFJOxk20YkZstEG1DdDkIJnkFIpiLSpLywZpTq4VMBGXshi1yVq85TvZp8fXjq4Bttb6VuI3lWhNWDB2iVsHuvD1Q8wH7bSFWE+4GP+O7r7tDC/71gYwvsf7ggu/0QwMYj99cCOxh+FLJ2jPGJohBy+41CY8tWkj9B4mjnmGrEN4tYGcd3n0Cj+6GIUVeOH8SaJwuhjpYyt1hA1qk6SBZZmp9Pc5j/3UCm15ttFpSvCdOqlfXTekEXzj5ksm9ks88LOzwRnYt+4fDpZ7HGBPPYhA2ZPD4z/a4tW5mEy0Ao/7yqCVlWMYxrIqVcyLdSMYNIwDAo57azC9Sx4dXVYS+UqyZr2PEWEhBEQjOGpoI9FyJl3JQwkQ3BHHj8yhbWjIDXfSYgnjf8EGhFrvBEGcio5zrIsxsMf34ZAi9qIM29peEgqFdavKE9kK1Iqg+jjYj3U0GiJ9C4b1dMwjwlkb62FSRt20LwTUKBng67GpjKKbtCfBd25v59OvDsev3u/v5rvi9/Vv/6ZD4Et/UM6JHXEc7dXv3WLlIGGM5x2+GPb2sZisx94dzh3C4/bhjJh6mgMSky1stnCRqHHxFqI91R+Y175YldTQyOJ2a2gs1+ybGVH+457am8oGGsSRRBqh9x1qjxBkAVjBRV3MNv1B2gmvtL595+Lt5UNBkyb8z9pW4a/9/9BffcMfHtJLZokTwUZfaDKq0fIInEPpUfip7H1U9nkeCS5HRGN/hEpPIMM6CuBLlXFtnvWCjkZtCzJdTJX3hHK2dIrSiz94G7glmvL1/8E7x9eWRxPOMxF/ntiGII6UjJMyn4EzSuhP4CBcEFhD+M23QJzN3+zWnD2FBkGvLc4e8EtZBh3scwgijqMu8ixLoOuUDzHvs6zjJvz9WTb6fwd2oYH0KsHLlGrLwefcRWtBz1GSyRnYQ3c293FvNMt+sX+3zX0mmGPmAUhmAQVMWOytlxx+bVa+Me2Y179ujol15C6E/3BgC6WcAGHW9Lgl4xt6ufHPOt4MlykIgHPxvxy+3Dd33d0FWre8HZwLP78Pcz3YbPeI7RABzWMz4pUUmYBXrXFyVHOKw6PXxeXCQxh2+00af/AA==</diagram></mxfile>

+ 5 - 0
main.go

@@ -21,6 +21,7 @@ import (
 	"imuslab.com/zoraxy/mod/geodb"
 	"imuslab.com/zoraxy/mod/mdns"
 	"imuslab.com/zoraxy/mod/netstat"
+	"imuslab.com/zoraxy/mod/pathblock"
 	"imuslab.com/zoraxy/mod/sshprox"
 	"imuslab.com/zoraxy/mod/statistic"
 	"imuslab.com/zoraxy/mod/statistic/analytic"
@@ -57,6 +58,7 @@ var (
 	authAgent          *auth.AuthAgent         //Authentication agent
 	tlsCertManager     *tlscert.Manager        //TLS / SSL management
 	redirectTable      *redirection.RuleTable  //Handle special redirection rule sets
+	pathBlockHandler   *pathblock.Handler      //Handle specific path blocking or custom headers
 	geodbStore         *geodb.Store            //GeoIP database, also handle black list and whitelist features
 	netstatBuffers     *netstat.NetStatBuffers //Realtime graph buffers
 	statisticCollector *statistic.Collector    //Collecting statistic from visitors
@@ -149,6 +151,9 @@ func main() {
 
 	time.Sleep(500 * time.Millisecond)
 
+	//Start the finalize sequences
+	finalSequence()
+
 	log.Println("Zoraxy started. Visit control panel at http://localhost" + handler.Port)
 	err = http.ListenAndServe(handler.Port, nil)
 

+ 13 - 13
mod/dynamicproxy/special.go

@@ -15,12 +15,12 @@ import (
 type RoutingRule struct {
 	ID             string
 	MatchRule      func(r *http.Request) bool
-	RoutingHandler http.Handler
+	RoutingHandler func(http.ResponseWriter, *http.Request)
 	Enabled        bool
 }
 
-//Router functions
-//Check if a routing rule exists given its id
+// Router functions
+// Check if a routing rule exists given its id
 func (router *Router) GetRoutingRuleById(rrid string) (*RoutingRule, error) {
 	for _, rr := range router.routingRules {
 		if rr.ID == rrid {
@@ -31,19 +31,19 @@ func (router *Router) GetRoutingRuleById(rrid string) (*RoutingRule, error) {
 	return nil, errors.New("routing rule with given id not found")
 }
 
-//Add a routing rule to the router
+// Add a routing rule to the router
 func (router *Router) AddRoutingRules(rr *RoutingRule) error {
 	_, err := router.GetRoutingRuleById(rr.ID)
-	if err != nil {
+	if err == nil {
 		//routing rule with given id already exists
-		return err
+		return errors.New("routing rule with same id already exists")
 	}
 
 	router.routingRules = append(router.routingRules, rr)
 	return nil
 }
 
-//Remove a routing rule from the router
+// Remove a routing rule from the router
 func (router *Router) RemoveRoutingRule(rrid string) {
 	newRoutingRules := []*RoutingRule{}
 	for _, rr := range router.routingRules {
@@ -55,13 +55,13 @@ func (router *Router) RemoveRoutingRule(rrid string) {
 	router.routingRules = newRoutingRules
 }
 
-//Get all routing rules
+// Get all routing rules
 func (router *Router) GetAllRoutingRules() []*RoutingRule {
 	return router.routingRules
 }
 
-//Get the matching routing rule that describe this request.
-//Return nil if no routing rule is match
+// Get the matching routing rule that describe this request.
+// Return nil if no routing rule is match
 func (router *Router) GetMatchingRoutingRule(r *http.Request) *RoutingRule {
 	for _, thisRr := range router.routingRules {
 		if thisRr.IsMatch(r) {
@@ -71,8 +71,8 @@ func (router *Router) GetMatchingRoutingRule(r *http.Request) *RoutingRule {
 	return nil
 }
 
-//Routing Rule functions
-//Check if a request object match the
+// Routing Rule functions
+// Check if a request object match the
 func (e *RoutingRule) IsMatch(r *http.Request) bool {
 	if !e.Enabled {
 		return false
@@ -81,5 +81,5 @@ func (e *RoutingRule) IsMatch(r *http.Request) bool {
 }
 
 func (e *RoutingRule) Route(w http.ResponseWriter, r *http.Request) {
-	e.RoutingHandler.ServeHTTP(w, r)
+	e.RoutingHandler(w, r)
 }

+ 16 - 0
mod/expose/expose.go

@@ -0,0 +1,16 @@
+package expose
+
+/*
+	Service Expose Proxy
+
+	A tunnel for getting your local server online in one line
+	(No, this is not ngrok)
+*/
+
+type Router struct {
+}
+
+//Create a new service expose router
+func NewServiceExposeRouter() {
+
+}

+ 111 - 0
mod/expose/security.go

@@ -0,0 +1,111 @@
+package expose
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha512"
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+	"log"
+)
+
+// GenerateKeyPair generates a new key pair
+func GenerateKeyPair(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) {
+	privkey, err := rsa.GenerateKey(rand.Reader, bits)
+	if err != nil {
+		return nil, nil, err
+	}
+	return privkey, &privkey.PublicKey, nil
+}
+
+// PrivateKeyToBytes private key to bytes
+func PrivateKeyToBytes(priv *rsa.PrivateKey) []byte {
+	privBytes := pem.EncodeToMemory(
+		&pem.Block{
+			Type:  "RSA PRIVATE KEY",
+			Bytes: x509.MarshalPKCS1PrivateKey(priv),
+		},
+	)
+
+	return privBytes
+}
+
+// PublicKeyToBytes public key to bytes
+func PublicKeyToBytes(pub *rsa.PublicKey) ([]byte, error) {
+	pubASN1, err := x509.MarshalPKIXPublicKey(pub)
+	if err != nil {
+		return []byte(""), err
+	}
+
+	pubBytes := pem.EncodeToMemory(&pem.Block{
+		Type:  "RSA PUBLIC KEY",
+		Bytes: pubASN1,
+	})
+
+	return pubBytes, nil
+}
+
+// BytesToPrivateKey bytes to private key
+func BytesToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
+	block, _ := pem.Decode(priv)
+	enc := x509.IsEncryptedPEMBlock(block)
+	b := block.Bytes
+	var err error
+	if enc {
+		log.Println("is encrypted pem block")
+		b, err = x509.DecryptPEMBlock(block, nil)
+		if err != nil {
+			return nil, err
+		}
+	}
+	key, err := x509.ParsePKCS1PrivateKey(b)
+	if err != nil {
+		return nil, err
+	}
+	return key, nil
+}
+
+// BytesToPublicKey bytes to public key
+func BytesToPublicKey(pub []byte) (*rsa.PublicKey, error) {
+	block, _ := pem.Decode(pub)
+	enc := x509.IsEncryptedPEMBlock(block)
+	b := block.Bytes
+	var err error
+	if enc {
+		log.Println("is encrypted pem block")
+		b, err = x509.DecryptPEMBlock(block, nil)
+		if err != nil {
+			return nil, err
+		}
+	}
+	ifc, err := x509.ParsePKIXPublicKey(b)
+	if err != nil {
+		return nil, err
+	}
+	key, ok := ifc.(*rsa.PublicKey)
+	if !ok {
+		return nil, errors.New("key not valid")
+	}
+	return key, nil
+}
+
+// EncryptWithPublicKey encrypts data with public key
+func EncryptWithPublicKey(msg []byte, pub *rsa.PublicKey) ([]byte, error) {
+	hash := sha512.New()
+	ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil)
+	if err != nil {
+		return []byte(""), err
+	}
+	return ciphertext, nil
+}
+
+// DecryptWithPrivateKey decrypts data with private key
+func DecryptWithPrivateKey(ciphertext []byte, priv *rsa.PrivateKey) ([]byte, error) {
+	hash := sha512.New()
+	plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil)
+	if err != nil {
+		return []byte(""), err
+	}
+	return plaintext, nil
+}

+ 100 - 0
mod/pathblock/handler.go

@@ -0,0 +1,100 @@
+package pathblock
+
+import (
+	"encoding/json"
+	"net/http"
+	"strconv"
+
+	uuid "github.com/satori/go.uuid"
+	"imuslab.com/zoraxy/mod/utils"
+)
+
+/*
+	handler.go
+
+	This script handles pathblock api
+*/
+
+func (h *Handler) HandleListBlockingPath(w http.ResponseWriter, r *http.Request) {
+	js, _ := json.Marshal(h.BlockingPaths)
+	utils.SendJSONResponse(w, string(js))
+}
+
+func (h *Handler) HandleAddBlockingPath(w http.ResponseWriter, r *http.Request) {
+	matchingPath, err := utils.PostPara(r, "matchingPath")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid matching path given")
+		return
+	}
+
+	exactMatch, err := utils.PostPara(r, "exactMatch")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid exact match value given")
+		return
+	}
+
+	statusCodeString, err := utils.PostPara(r, "statusCode")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid status code given")
+		return
+	}
+
+	statusCode, err := strconv.Atoi(statusCodeString)
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid status code given")
+		return
+	}
+
+	enabled, err := utils.PostPara(r, "enabled")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid enabled value given")
+		return
+	}
+
+	caseSensitive, err := utils.PostPara(r, "caseSensitive")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid case sensitive value given")
+		return
+	}
+
+	targetBlockingPath := BlockingPath{
+		UUID:          uuid.NewV4().String(),
+		MatchingPath:  matchingPath,
+		ExactMatch:    exactMatch == "true",
+		StatusCode:    statusCode,
+		CustomHeaders: http.Header{},
+		CustomHTML:    []byte(""),
+		Enabled:       enabled == "true",
+		CaseSenitive:  caseSensitive == "true",
+	}
+
+	err = h.AddBlockingPath(&targetBlockingPath)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	utils.SendOK(w)
+}
+
+func (h *Handler) HandleRemoveBlockingPath(w http.ResponseWriter, r *http.Request) {
+	blockerUUID, err := utils.PostPara(r, "uuid")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid uuid given")
+		return
+	}
+
+	targetRule := h.GetPathBlockerFromUUID(blockerUUID)
+	if targetRule == nil {
+		//Not found
+		utils.SendErrorResponse(w, "target path blocker not found")
+		return
+	}
+
+	err = h.RemoveBlockingPathByUUID(blockerUUID)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+	utils.SendOK(w)
+}

+ 175 - 0
mod/pathblock/pathblock.go

@@ -0,0 +1,175 @@
+package pathblock
+
+import (
+	"encoding/json"
+	"errors"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"imuslab.com/zoraxy/mod/utils"
+)
+
+/*
+	Pathblock.go
+
+	This script block off some of the specific pathname in access
+	For example, this module can help you block request for a particular
+	apache directory or functional endpoints like /.well-known/ when you
+	are not using it
+*/
+
+type Options struct {
+	ConfigFolder string //The folder to store the path blocking config files
+}
+
+type BlockingPath struct {
+	UUID          string
+	MatchingPath  string
+	ExactMatch    bool
+	StatusCode    int
+	CustomHeaders http.Header
+	CustomHTML    []byte
+	Enabled       bool
+	CaseSenitive  bool
+}
+
+type Handler struct {
+	Options       *Options
+	BlockingPaths []*BlockingPath
+}
+
+// Create a new path blocker handler
+func NewPathBlocker(options *Options) *Handler {
+	//Create folder if not exists
+	if !utils.FileExists(options.ConfigFolder) {
+		os.Mkdir(options.ConfigFolder, 0775)
+	}
+
+	//Load the configs from file
+	//TODO
+
+	return &Handler{
+		Options:       options,
+		BlockingPaths: []*BlockingPath{},
+	}
+}
+
+func (h *Handler) ListBlockingPath() []*BlockingPath {
+	return h.BlockingPaths
+}
+
+// Get the blocker from matching path (path match, ignore tailing slash)
+func (h *Handler) GetPathBlockerFromMatchingPath(matchingPath string) *BlockingPath {
+	for _, blocker := range h.BlockingPaths {
+		if blocker.MatchingPath == matchingPath {
+			return blocker
+		} else if strings.TrimSuffix(blocker.MatchingPath, "/") == strings.TrimSuffix(matchingPath, "/") {
+			return blocker
+		}
+	}
+
+	return nil
+}
+
+func (h *Handler) GetPathBlockerFromUUID(UUID string) *BlockingPath {
+	for _, blocker := range h.BlockingPaths {
+		if blocker.UUID == UUID {
+			return blocker
+		}
+	}
+
+	return nil
+}
+
+func (h *Handler) AddBlockingPath(pathBlocker *BlockingPath) error {
+	//Check if the blocker exists
+	blockerPath := pathBlocker.MatchingPath
+	targetBlocker := h.GetPathBlockerFromMatchingPath(blockerPath)
+	if targetBlocker != nil {
+		//Blocker with the same matching path already exists
+		return errors.New("path blocker with the same path already exists")
+	}
+
+	h.BlockingPaths = append(h.BlockingPaths, pathBlocker)
+
+	//Write the new config to file
+	return h.SaveBlockerToFile(pathBlocker)
+}
+
+func (h *Handler) RemoveBlockingPathByUUID(uuid string) error {
+	newBlockingList := []*BlockingPath{}
+	for _, thisBlocker := range h.BlockingPaths {
+		if thisBlocker.UUID != uuid {
+			newBlockingList = append(newBlockingList, thisBlocker)
+		}
+	}
+
+	if len(h.BlockingPaths) == len(newBlockingList) {
+		//Nothing is removed
+		return errors.New("given matching path blocker not exists")
+	}
+
+	h.BlockingPaths = newBlockingList
+
+	return h.RemoveBlockerFromFile(uuid)
+}
+
+func (h *Handler) SaveBlockerToFile(pathBlocker *BlockingPath) error {
+	saveFilename := filepath.Join(h.Options.ConfigFolder, pathBlocker.UUID)
+	js, _ := json.MarshalIndent(pathBlocker, "", " ")
+	return os.WriteFile(saveFilename, js, 0775)
+}
+
+func (h *Handler) RemoveBlockerFromFile(uuid string) error {
+	expectedConfigFile := filepath.Join(h.Options.ConfigFolder, uuid)
+	if !utils.FileExists(expectedConfigFile) {
+		return errors.New("config file not found on disk")
+	}
+
+	return os.Remove(expectedConfigFile)
+}
+
+// Get all the matching blockers for the given URL path
+// return all the path blockers and the max length matching rule
+func (h *Handler) GetMatchingBlockers(urlPath string) ([]*BlockingPath, *BlockingPath) {
+	urlPath = strings.TrimSuffix(urlPath, "/")
+	matchingBlockers := []*BlockingPath{}
+	var longestMatchingPrefix *BlockingPath = nil
+	for _, thisBlocker := range h.BlockingPaths {
+		if thisBlocker.Enabled == false {
+			//This blocker is not enabled. Ignore this
+			continue
+		}
+
+		incomingURLPath := urlPath
+		matchingPath := strings.TrimSuffix(thisBlocker.MatchingPath, "/")
+
+		if !thisBlocker.CaseSenitive {
+			//This is not case sensitive
+			incomingURLPath = strings.ToLower(incomingURLPath)
+			matchingPath = strings.ToLower(matchingPath)
+		}
+
+		if matchingPath == incomingURLPath {
+			//This blocker have exact url path match
+			matchingBlockers = append(matchingBlockers, thisBlocker)
+			if longestMatchingPrefix == nil || len(thisBlocker.MatchingPath) > len(longestMatchingPrefix.MatchingPath) {
+				longestMatchingPrefix = thisBlocker
+			}
+			continue
+		}
+
+		if !thisBlocker.ExactMatch && strings.HasPrefix(incomingURLPath, matchingPath) {
+			//This blocker have prefix url match
+			matchingBlockers = append(matchingBlockers, thisBlocker)
+			if longestMatchingPrefix == nil || len(thisBlocker.MatchingPath) > len(longestMatchingPrefix.MatchingPath) {
+				longestMatchingPrefix = thisBlocker
+			}
+			continue
+		}
+	}
+
+	return matchingBlockers, longestMatchingPrefix
+}

+ 19 - 1
start.go

@@ -16,6 +16,7 @@ import (
 	"imuslab.com/zoraxy/mod/geodb"
 	"imuslab.com/zoraxy/mod/mdns"
 	"imuslab.com/zoraxy/mod/netstat"
+	"imuslab.com/zoraxy/mod/pathblock"
 	"imuslab.com/zoraxy/mod/sshprox"
 	"imuslab.com/zoraxy/mod/statistic"
 	"imuslab.com/zoraxy/mod/statistic/analytic"
@@ -68,7 +69,7 @@ func startupSequence() {
 	}
 
 	//Create a redirection rule table
-	redirectTable, err = redirection.NewRuleTable("./rules")
+	redirectTable, err = redirection.NewRuleTable("./rules/redirect")
 	if err != nil {
 		panic(err)
 	}
@@ -94,6 +95,17 @@ func startupSequence() {
 		panic(err)
 	}
 
+	/*
+		Path Blocker
+
+		This section of starutp script start the pathblocker
+		from file.
+	*/
+
+	pathBlockHandler = pathblock.NewPathBlocker(&pathblock.Options{
+		ConfigFolder: "./rules/pathblock",
+	})
+
 	/*
 		MDNS Discovery Service
 
@@ -186,3 +198,9 @@ func startupSequence() {
 	a := acme.NewACME("[email protected]", []string{"r5desktop.alanyeung.co"})
 	a.ObtainCert()
 }
+
+// This sequence start after everything is initialized
+func finalSequence() {
+	//Start ACME renew agent
+	acmeRegisterSpecialRoutingRule()
+}