From 03974163d487c4c63113c6025ebb0a45fdf32af9 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Mon, 17 Jun 2024 00:24:24 +0800 Subject: [PATCH] Added docker conditional compilation - Moved docker UX optimization into module - Added conditional compilation for Windows build - Added Permission Policy header editor - Fixed docker container list ui error message bug --- src/api.go | 4 +- src/docker.go | 84 ------------ src/main.go | 9 +- src/mod/dockerux/docker.go | 60 +++++++++ src/mod/dockerux/docker_windows.go | 32 +++++ src/mod/dockerux/dockerux.go | 24 ++++ src/mod/dynamicproxy/customHeader.go | 25 +++- src/reverseproxy.go | 33 +++++ src/start.go | 8 ++ src/web/components/rules.html | 23 ++-- src/web/login.html | 2 +- src/web/snippet/customHeaders.html | 148 ++++++++++++++++++++-- src/web/snippet/dockerContainersList.html | 2 + 13 files changed, 334 insertions(+), 120 deletions(-) delete mode 100644 src/docker.go create mode 100644 src/mod/dockerux/docker.go create mode 100644 src/mod/dockerux/docker_windows.go create mode 100644 src/mod/dockerux/dockerux.go diff --git a/src/api.go b/src/api.go index 519b532..27bf4ac 100644 --- a/src/api.go +++ b/src/api.go @@ -215,8 +215,8 @@ func initAPIs() { } //Docker UX Optimizations - authRouter.HandleFunc("/api/docker/available", HandleDockerAvailable) - authRouter.HandleFunc("/api/docker/containers", HandleDockerContainersList) + authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable) + authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList) //Others http.HandleFunc("/api/info/x", HandleZoraxyInfo) diff --git a/src/docker.go b/src/docker.go deleted file mode 100644 index da68c3a..0000000 --- a/src/docker.go +++ /dev/null @@ -1,84 +0,0 @@ -package main - -import ( - "bufio" - "context" - "encoding/json" - "net/http" - "os" - "os/exec" - "strings" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/client" - "imuslab.com/zoraxy/mod/utils" -) - -// IsDockerized checks if the program is running in a Docker container. -func IsDockerized() bool { - // Check for the /proc/1/cgroup file - file, err := os.Open("/proc/1/cgroup") - if err != nil { - return false - } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - if strings.Contains(scanner.Text(), "docker") { - return true - } - } - - return false -} - -// IsDockerInstalled checks if Docker is installed on the host. -func IsDockerInstalled() bool { - _, err := exec.LookPath("docker") - return err == nil -} - -// HandleDockerAvaible check if teh docker related functions should be avaible in front-end -func HandleDockerAvailable(w http.ResponseWriter, r *http.Request) { - dockerAvailable := IsDockerized() - js, _ := json.Marshal(dockerAvailable) - utils.SendJSONResponse(w, string(js)) -} - -// handleDockerContainersList return the current list of docker containers -// currently listening to the same bridge network interface. See PR #202 for details. -func HandleDockerContainersList(w http.ResponseWriter, r *http.Request) { - apiClient, err := client.NewClientWithOpts(client.WithVersion("1.43")) - if err != nil { - utils.SendErrorResponse(w, err.Error()) - return - } - defer apiClient.Close() - - containers, err := apiClient.ContainerList(context.Background(), container.ListOptions{All: true}) - if err != nil { - utils.SendErrorResponse(w, err.Error()) - return - } - - networks, err := apiClient.NetworkList(context.Background(), types.NetworkListOptions{}) - if err != nil { - utils.SendErrorResponse(w, err.Error()) - return - } - - result := map[string]interface{}{ - "network": networks, - "containers": containers, - } - - js, err := json.Marshal(result) - if err != nil { - utils.SendErrorResponse(w, err.Error()) - return - } - - utils.SendJSONResponse(w, string(js)) -} diff --git a/src/main.go b/src/main.go index 44cf82b..f2a8dc5 100644 --- a/src/main.go +++ b/src/main.go @@ -16,6 +16,7 @@ import ( "imuslab.com/zoraxy/mod/acme" "imuslab.com/zoraxy/mod/auth" "imuslab.com/zoraxy/mod/database" + "imuslab.com/zoraxy/mod/dockerux" "imuslab.com/zoraxy/mod/dynamicproxy/redirection" "imuslab.com/zoraxy/mod/email" "imuslab.com/zoraxy/mod/forwardproxy" @@ -44,6 +45,7 @@ var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transpo var mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)") var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node") var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port") +var runningInDocker = flag.Bool("docker", false, "Run Zoraxy in docker compatibility mode") var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)") var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)") var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters") @@ -86,9 +88,10 @@ var ( forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser //Helper modules - EmailSender *email.Sender //Email sender that handle email sending - AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic - SystemWideLogger *logger.Logger //Logger for Zoraxy + EmailSender *email.Sender //Email sender that handle email sending + AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic + DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only + SystemWideLogger *logger.Logger //Logger for Zoraxy ) // Kill signal handler. Do something before the system the core terminate. diff --git a/src/mod/dockerux/docker.go b/src/mod/dockerux/docker.go new file mode 100644 index 0000000..90aa0c7 --- /dev/null +++ b/src/mod/dockerux/docker.go @@ -0,0 +1,60 @@ +//go:build !windows +// +build !windows + +package dockerux + +/* Windows docker optimizer*/ + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "imuslab.com/zoraxy/mod/utils" +) + +// Windows build not support docker +func (d *UXOptimizer) HandleDockerAvailable(w http.ResponseWriter, r *http.Request) { + js, _ := json.Marshal(d.RunninInDocker) + utils.SendJSONResponse(w, string(js)) +} + +func (d *UXOptimizer) HandleDockerContainersList(w http.ResponseWriter, r *http.Request) { + apiClient, err := client.NewClientWithOpts(client.WithVersion("1.43")) + if err != nil { + d.SystemWideLogger.PrintAndLog("Docker", "Unable to create new docker client", err) + utils.SendErrorResponse(w, "Docker client initiation failed") + return + } + defer apiClient.Close() + + containers, err := apiClient.ContainerList(context.Background(), container.ListOptions{All: true}) + if err != nil { + d.SystemWideLogger.PrintAndLog("Docker", "List docker container failed", err) + utils.SendErrorResponse(w, "List docker container failed") + return + } + + networks, err := apiClient.NetworkList(context.Background(), types.NetworkListOptions{}) + if err != nil { + d.SystemWideLogger.PrintAndLog("Docker", "List docker network failed", err) + utils.SendErrorResponse(w, "List docker network failed") + return + } + + result := map[string]interface{}{ + "network": networks, + "containers": containers, + } + + js, err := json.Marshal(result) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + utils.SendJSONResponse(w, string(js)) +} diff --git a/src/mod/dockerux/docker_windows.go b/src/mod/dockerux/docker_windows.go new file mode 100644 index 0000000..f70d079 --- /dev/null +++ b/src/mod/dockerux/docker_windows.go @@ -0,0 +1,32 @@ +//go:build windows +// +build windows + +package dockerux + +/* + + Windows docker UX optimizer dummy + + This is a dummy module for Windows as docker features + is useless on Windows and create a larger binary size + + docker on Windows build are trimmed to reduce binary size + and make it compatibile with Windows 7 +*/ + +import ( + "encoding/json" + "net/http" + + "imuslab.com/zoraxy/mod/utils" +) + +// Windows build not support docker +func (d *UXOptimizer) HandleDockerAvailable(w http.ResponseWriter, r *http.Request) { + js, _ := json.Marshal(d.RunninInDocker) + utils.SendJSONResponse(w, string(js)) +} + +func (d *UXOptimizer) HandleDockerContainersList(w http.ResponseWriter, r *http.Request) { + utils.SendErrorResponse(w, "Platform not supported") +} diff --git a/src/mod/dockerux/dockerux.go b/src/mod/dockerux/dockerux.go new file mode 100644 index 0000000..deca314 --- /dev/null +++ b/src/mod/dockerux/dockerux.go @@ -0,0 +1,24 @@ +package dockerux + +import "imuslab.com/zoraxy/mod/info/logger" + +/* + Docker Optimizer + + This script add support for optimizing docker user experience + Note that this module are community contribute only. For bug + report, please directly tag the Pull Request author. +*/ + +type UXOptimizer struct { + RunninInDocker bool + SystemWideLogger *logger.Logger +} + +//Create a new docker optimizer +func NewDockerOptimizer(IsRunningInDocker bool, logger *logger.Logger) *UXOptimizer { + return &UXOptimizer{ + RunninInDocker: IsRunningInDocker, + SystemWideLogger: logger, + } +} diff --git a/src/mod/dynamicproxy/customHeader.go b/src/mod/dynamicproxy/customHeader.go index d99f6b7..796b176 100644 --- a/src/mod/dynamicproxy/customHeader.go +++ b/src/mod/dynamicproxy/customHeader.go @@ -1,6 +1,10 @@ package dynamicproxy -import "strconv" +import ( + "strconv" + + "imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy" +) /* CustomHeader.go @@ -9,9 +13,9 @@ import "strconv" into the dpcore routing logic */ -//SplitInboundOutboundHeaders split user defined headers into upstream and downstream headers -//return upstream header and downstream header key-value pairs -//if the header is expected to be deleted, the value will be set to empty string +// SplitInboundOutboundHeaders split user defined headers into upstream and downstream headers +// return upstream header and downstream header key-value pairs +// if the header is expected to be deleted, the value will be set to empty string func (ept *ProxyEndpoint) SplitInboundOutboundHeaders() ([][]string, [][]string) { if len(ept.UserDefinedHeaders) == 0 { //Early return if there are no defined headers @@ -52,8 +56,17 @@ func (ept *ProxyEndpoint) SplitInboundOutboundHeaders() ([][]string, [][]string) } //Check if the endpoint require Permission Policy - if ept.EnablePermissionPolicyHeader && ept.PermissionPolicy != nil { - downstreamHeaders[downstreamHeaderCounter] = ept.PermissionPolicy.ToKeyValueHeader() + if ept.EnablePermissionPolicyHeader { + var usingPermissionPolicy *permissionpolicy.PermissionsPolicy + if ept.PermissionPolicy != nil { + //Custom permission policy + usingPermissionPolicy = ept.PermissionPolicy + } else { + //Permission policy is enabled but not customized. Use default + usingPermissionPolicy = permissionpolicy.GetDefaultPermissionPolicy() + } + + downstreamHeaders[downstreamHeaderCounter] = usingPermissionPolicy.ToKeyValueHeader() downstreamHeaderCounter++ } diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 7e4dc02..5c50631 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -1314,7 +1314,40 @@ func HandlePermissionPolicy(w http.ResponseWriter, r *http.Request) { utils.SendJSONResponse(w, string(js)) return } else if r.Method == http.MethodPost { + //Update the enable state of permission policy + enableState, err := utils.PostBool(r, "enable") + if err != nil { + utils.SendErrorResponse(w, "invalid enable state given") + return + } + targetProxyEndpoint.EnablePermissionPolicyHeader = enableState + SaveReverseProxyConfig(targetProxyEndpoint) + targetProxyEndpoint.UpdateToRuntime() + utils.SendOK(w) + return + } else if r.Method == http.MethodPut { + //Store the new permission policy + newPermissionPolicyJSONString, err := utils.PostPara(r, "pp") + if err != nil { + utils.SendErrorResponse(w, "missing pp (permission policy) paramter") + return + } + + //Parse the permission policy from JSON string + newPermissionPolicy := permissionpolicy.GetDefaultPermissionPolicy() + err = json.Unmarshal([]byte(newPermissionPolicyJSONString), &newPermissionPolicy) + if err != nil { + utils.SendErrorResponse(w, "permission policy parse error: "+err.Error()) + return + } + + //Save it to file + targetProxyEndpoint.PermissionPolicy = newPermissionPolicy + SaveReverseProxyConfig(targetProxyEndpoint) + targetProxyEndpoint.UpdateToRuntime() + utils.SendOK(w) + return } http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed) diff --git a/src/start.go b/src/start.go index f7120bd..389bcd4 100644 --- a/src/start.go +++ b/src/start.go @@ -4,6 +4,7 @@ import ( "log" "net/http" "os" + "runtime" "strconv" "strings" "time" @@ -12,6 +13,7 @@ import ( "imuslab.com/zoraxy/mod/acme" "imuslab.com/zoraxy/mod/auth" "imuslab.com/zoraxy/mod/database" + "imuslab.com/zoraxy/mod/dockerux" "imuslab.com/zoraxy/mod/dynamicproxy/redirection" "imuslab.com/zoraxy/mod/forwardproxy" "imuslab.com/zoraxy/mod/ganserv" @@ -269,6 +271,12 @@ func startupSequence() { log.Fatal(err) } + /* Docker UX Optimizer */ + if runtime.GOOS == "windows" && *runningInDocker { + SystemWideLogger.PrintAndLog("WARNING", "Invalid start flag combination: docker=true && runtime.GOOS == windows. Running in docker UX development mode.", nil) + } + DockerUXOptimizer = dockerux.NewDockerOptimizer(*runningInDocker, SystemWideLogger) + } // This sequence start after everything is initialized diff --git a/src/web/components/rules.html b/src/web/components/rules.html index cbe0b31..e42c261 100644 --- a/src/web/components/rules.html +++ b/src/web/components/rules.html @@ -26,13 +26,11 @@
- - E.g. 192.168.0.101:8000 or example.com
-