13 Commits

Author SHA1 Message Date
Toby Chui
9230f9374d Added null check on front-end
- Added null check on TlsOption on front-end
2025-07-21 20:10:55 +08:00
Toby Chui
6493a82e5f Fixed #756
- Added missing TLS config on new http proxy creation
2025-07-21 07:21:55 +08:00
Toby Chui
39e05032c9 Merge pull request #755 from Morethanevil/main
Update CHANGELOG.md
2025-07-20 17:41:43 +08:00
Marcel
077192e08e Update CHANGELOG.md 2025-07-20 11:40:34 +02:00
Toby Chui
223ae9e112 Merge pull request #751 from tobychui/v3.2.5
* Added new API endpoint /api/proxy/setTlsConfig (for HTTP Proxy Editor TLS tab)
* Refactored TLS certificate management APIs with new handlers
* Removed redundant functions from src/cert.go and delegated to tlsCertManager
* Code optimization in tlscert module
* Introduced a new constant CONF_FOLDER and updated configuration storage paths (phasing out hard coded paths) 
* Updated functions to set default TLS options when missing, default to SNI 

By @jemmy1794 

* Added Proxy Protocol v1 support in stream proxy
* Fixed Proxy UI bug
2025-07-20 14:27:56 +08:00
Toby Chui
aff1975c5a Updated version code and defs
- Updated version code
- Replaced hardcoded path of some config folder string with const value
2025-07-20 14:03:39 +08:00
Toby Chui
5c6950ca56 Merge pull request #744 from jemmy1794/Stream_Proxy_v3.2.5
Fix Stream Proxy TCP/UDP selection not saved initially #742
2025-07-19 12:57:25 +08:00
Toby Chui
70b1ccfa6e Merge pull request #739 from AnthonyMichaelTDM/typo
fix: typo in dynamic_router.go
2025-07-16 14:20:02 +08:00
Anthony Rubick
100c1e9c04 fix: typo in dynamic_router.go
SniffResultAccpet should be SniffResultAccept
2025-07-15 22:05:06 -07:00
Jemmy
a33600d3e2 Fix Stream Proxy TCP/UDP selection not saved initially #742
- Reset the value of the form correctly in `streamprox.html`. Ref #742.
2025-07-16 08:11:25 +08:00
Toby Chui
a0a394885c Merge pull request #728 from PassiveLemon/hardening
Docker changes
2025-07-08 09:37:27 +08:00
PassiveLemon
51334a3a75 Docker: Switch to a python entrypoint 2025-07-07 13:34:50 -04:00
PassiveLemon
6f5fadc085 Docker: Do not automatically build Zoraxy plugins 2025-07-07 13:33:57 -04:00
14 changed files with 201 additions and 133 deletions

10
.gitignore vendored
View File

@@ -29,8 +29,6 @@ src/Zoraxy_*_*
src/certs/*
src/rules/*
src/README.md
docker/ContainerTester.sh
docker/docker-compose.yaml
src/mod/acme/test/stackoverflow.pem
/tools/dns_challenge_update/code-gen/acmedns
/tools/dns_challenge_update/code-gen/lego
@@ -41,11 +39,15 @@ src/sys.uuid
src/zoraxy
src/log/
# dev-tags
/Dockerfile
/Entrypoint.sh
# docker testing stuff
docker/test/
docker/container-builder.sh
docker/docker-compose.yaml
# plugins
example/plugins/ztnc/ztnc.db
example/plugins/ztnc/authtoken.secret
@@ -58,3 +60,5 @@ sys.*
www/html/index.html
*.exe
/src/dist
/src/plugins

View File

@@ -1,3 +1,20 @@
# v3.2.5 20 Jul 2025
+ Added new API endpoint /api/proxy/setTlsConfig (for HTTP Proxy Editor TLS tab)
+ Refactored TLS certificate management APIs with new handlers
+ Removed redundant functions from src/cert.go and delegated to tlsCertManager
+ Code optimization in tlscert module
+ Introduced a new constant CONF_FOLDER and updated configuration storage paths (phasing out hard coded paths)
+ Updated functions to set default TLS options when missing, default to SNI
+ Added Proxy Protocol v1 support in stream proxy [jemmy1794](https://github.com/jemmy1794)
+ Fixed Proxy UI bug [jemmy1794](https://github.com/jemmy1794)
+ Fixed assign static server to localhost or all interfaces [#688](https://github.com/tobychui/zoraxy/issues/688)
+ fixed empty SSO parameters by [7brend7](https://github.com/7brend7)
+ sort list of loaded certificates by expire date by [7brend7](https://github.com/7brend7)
+ Docker hardening by [PassiveLemon](https://github.com/PassiveLemon)
+ Fixed sort by destination [#713](https://github.com/tobychui/zoraxy/issues/713)
# v3.2.4 28 Jun 2025
A big release since v3.1.9. Versions from 3.2.0 to 3.2.3 were prereleases.

View File

@@ -34,34 +34,18 @@ RUN curl -Lo ZeroTierOne.tar.gz https://codeload.github.com/zerotier/ZeroTierOne
chmod 755 /usr/local/bin/zerotier-one
## Fetch plugin
FROM docker.io/golang:alpine AS fetch-plugin
RUN mkdir -p /opt/zoraxy/zoraxy_plugin/
RUN apk add --update --no-cache git
WORKDIR /opt/zoraxy/
RUN git clone https://github.com/aroz-online/zoraxy-official-plugins &&\
cp -r ./zoraxy-official-plugins/src/ztnc/mod/zoraxy_plugin/ /opt/zoraxy/zoraxy_plugin/
## Main
FROM docker.io/golang:alpine
FROM docker.io/alpine:latest
# If you build it yourself, you will need to add the example directory into the docker directory.
RUN apk add --update --no-cache python3 sudo netcat-openbsd libressl-dev openssh ca-certificates libc6-compat libstdc++ &&\
rm -rf /var/cache/apk/* /tmp/*
COPY --chmod=700 ./entrypoint.sh /opt/zoraxy/
COPY --chmod=700 ./build_plugins.sh /usr/local/bin/build_plugins
COPY --from=fetch-plugin --chmod=700 /opt/zoraxy/zoraxy_plugin/ /opt/zoraxy/zoraxy_plugin/
COPY --chmod=700 ./entrypoint.py /opt/zoraxy/
COPY --from=build-zerotier /usr/local/bin/zerotier-one /usr/local/bin/zerotier-one
COPY --from=build-zoraxy /usr/local/bin/zoraxy /usr/local/bin/zoraxy
RUN apk add --update --no-cache bash sudo netcat-openbsd libressl-dev openssh ca-certificates libc6-compat libstdc++ &&\
mkdir -p /opt/zoraxy/plugin/ &&\
RUN mkdir -p /opt/zoraxy/plugin/ &&\
echo "tun" | tee -a /etc/modules
WORKDIR /opt/zoraxy/config/
@@ -89,7 +73,7 @@ VOLUME [ "/opt/zoraxy/config/" ]
LABEL com.imuslab.zoraxy.container-identifier="Zoraxy"
ENTRYPOINT [ "/opt/zoraxy/entrypoint.sh" ]
ENTRYPOINT [ "python3", "-u", "/opt/zoraxy/entrypoint.py" ]
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 CMD nc -vz 127.0.0.1 $PORT || exit 1

View File

@@ -119,18 +119,14 @@ Or for Docker Compose:
### Plugins
You can find official plugins at https://github.com/aroz-online/zoraxy-official-plugins
Place your plugins inside the volume `/path/to/zoraxy/plugin/:/opt/zoraxy/plugin/` (Adjust to your actual install location). Any plugins you have added will then be built and used on the next restart.
> [!IMPORTANT]
> Plugins are currently experimental.
Zoraxy includes a (experimental) store to download and use official plugins right from inside Zoraxy, no preparation required.
For those looking to use custom plugins, build your plugins and place them inside the volume `/path/to/zoraxy/plugin/:/opt/zoraxy/plugin/` (Adjust to your actual install location).
### Building
To build the Docker image:
- Check out the repository/branch.
- Copy the Zoraxy `src/` and `example/` directory into the `docker/` (here) directory.
- Copy the Zoraxy `src/` directory into the `docker/` (here) directory.
- Run the build command with `docker build -t zoraxy_build .`
- You can now use the image `zoraxy_build`
- If you wish to change the image name, then modify`zoraxy_build` in the previous step and then build again.

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env bash
echo "Copying zoraxy_plugin to all mods..."
for dir in "$1"/*; do
if [ -d "$dir" ]; then
cp -r "/opt/zoraxy/zoraxy_plugin/" "$dir/mod/"
fi
done
echo "Running go mod tidy and go build for all directories..."
for dir in "$1"/*; do
if [ -d "$dir" ]; then
cd "$dir" || exit 1
go mod tidy
go build
cd "$1" || exit 1
fi
done

128
docker/entrypoint.py Normal file
View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
import os
import signal
import subprocess
import sys
import time
zoraxy_proc = None
zerotier_proc = None
def getenv(key, default=None):
return os.environ.get(key, default)
def run(command):
try:
subprocess.run(command, check=True)
except subprocess.CalledProcessError as e:
print(f"Command failed: {command} - {e}")
sys.exit(1)
def popen(command):
proc = subprocess.Popen(command)
time.sleep(1)
if proc.poll() is not None:
print(f"{command} exited early with code {proc.returncode}")
raise RuntimeError(f"Failed to start {command}")
return proc
def cleanup(_signum, _frame):
print("Shutdown signal received. Cleaning up...")
global zoraxy_proc, zerotier_proc
if zoraxy_proc and zoraxy_proc.poll() is None:
print("Terminating Zoraxy...")
zoraxy_proc.terminate()
if zerotier_proc and zerotier_proc.poll() is None:
print("Terminating ZeroTier-One...")
zerotier_proc.terminate()
if zoraxy_proc:
try:
zoraxy_proc.wait(timeout=8)
except subprocess.TimeoutExpired:
zoraxy_proc.kill()
zoraxy_proc.wait()
if zerotier_proc:
try:
zerotier_proc.wait(timeout=8)
except subprocess.TimeoutExpired:
zerotier_proc.kill()
zerotier_proc.wait()
try:
os.unlink("/var/lib/zerotier-one")
except FileNotFoundError:
pass
except Exception as e:
print(f"Failed to unlink ZeroTier socket: {e}")
sys.exit(0)
def start_zerotier():
print("Starting ZeroTier...")
global zerotier_proc
config_dir = "/opt/zoraxy/config/zerotier/"
zt_path = "/var/lib/zerotier-one"
os.makedirs(config_dir, exist_ok=True)
os.symlink(config_dir, zt_path, target_is_directory=True)
zerotier_proc = popen(["zerotier-one"])
def start_zoraxy():
print("Starting Zoraxy...")
global zoraxy_proc
zoraxy_args = [
"zoraxy",
f"-autorenew={getenv('AUTORENEW', '86400')}",
f"-cfgupgrade={getenv('CFGUPGRADE', 'true')}",
f"-db={getenv('DB', 'auto')}",
f"-docker={getenv('DOCKER', 'true')}",
f"-earlyrenew={getenv('EARLYRENEW', '30')}",
f"-fastgeoip={getenv('FASTGEOIP', 'false')}",
f"-mdns={getenv('MDNS', 'true')}",
f"-mdnsname={getenv('MDNSNAME', "''")}",
f"-noauth={getenv('NOAUTH', 'false')}",
f"-plugin={getenv('PLUGIN', '/opt/zoraxy/plugin/')}",
f"-port=:{getenv('PORT', '8000')}",
f"-sshlb={getenv('SSHLB', 'false')}",
f"-update_geoip={getenv('UPDATE_GEOIP', 'false')}",
f"-version={getenv('VERSION', 'false')}",
f"-webfm={getenv('WEBFM', 'true')}",
f"-webroot={getenv('WEBROOT', './www')}",
]
zoraxy_proc = popen(zoraxy_args)
def main():
signal.signal(signal.SIGTERM, cleanup)
signal.signal(signal.SIGINT, cleanup)
print("Updating CA certificates...")
run(["update-ca-certificates"])
print("Updating GeoIP data...")
run(["zoraxy", "-update_geoip=true"])
os.chdir("/opt/zoraxy/config/")
if getenv("ZEROTIER", "false") == "true":
start_zerotier()
start_zoraxy()
signal.pause()
if __name__ == "__main__":
main()

View File

@@ -1,55 +0,0 @@
#!/usr/bin/env bash
cleanup() {
echo "Stop signal received. Shutting down..."
kill -TERM "$(pidof zoraxy)" &> /dev/null && echo "Zoraxy stopped."
kill -TERM "$(pidof zerotier-one)" &> /dev/null && echo "ZeroTier-One stopped."
unlink /var/lib/zerotier-one/zerotier/
exit 0
}
trap cleanup SIGTERM SIGINT TERM INT
update-ca-certificates && echo "CA certificates updated."
zoraxy -update_geoip=true && echo "GeoIP data updated ."
echo "Building plugins..."
cd /opt/zoraxy/plugin/ || exit 1
build_plugins "$PWD"
echo "Plugins built."
cd /opt/zoraxy/config/ || exit 1
if [ "$ZEROTIER" = "true" ]; then
if [ ! -d "/opt/zoraxy/config/zerotier/" ]; then
mkdir -p /opt/zoraxy/config/zerotier/
fi
ln -s /opt/zoraxy/config/zerotier/ /var/lib/zerotier-one
zerotier-one -d &
zerotierpid=$!
echo "ZeroTier daemon started."
fi
echo "Starting Zoraxy..."
zoraxy \
-autorenew="$AUTORENEW" \
-cfgupgrade="$CFGUPGRADE" \
-db="$DB" \
-docker="$DOCKER" \
-earlyrenew="$EARLYRENEW" \
-fastgeoip="$FASTGEOIP" \
-mdns="$MDNS" \
-mdnsname="$MDNSNAME" \
-noauth="$NOAUTH" \
-plugin="$PLUGIN" \
-port=:"$PORT" \
-sshlb="$SSHLB" \
-update_geoip="$UPDATE_GEOIP" \
-version="$VERSION" \
-webfm="$WEBFM" \
-webroot="$WEBROOT" \
&
zoraxypid=$!
wait "$zoraxypid"
wait "$zerotierpid"

View File

@@ -108,9 +108,9 @@ func filterProxyConfigFilename(filename string) string {
func SaveReverseProxyConfig(endpoint *dynamicproxy.ProxyEndpoint) error {
//Get filename for saving
filename := filepath.Join("./conf/proxy/", endpoint.RootOrMatchingDomain+".config")
filename := filepath.Join(CONF_HTTP_PROXY, endpoint.RootOrMatchingDomain+".config")
if endpoint.ProxyType == dynamicproxy.ProxyTypeRoot {
filename = "./conf/proxy/root.config"
filename = filepath.Join(CONF_HTTP_PROXY, "root.config")
}
filename = filterProxyConfigFilename(filename)
@@ -125,9 +125,9 @@ func SaveReverseProxyConfig(endpoint *dynamicproxy.ProxyEndpoint) error {
}
func RemoveReverseProxyConfig(endpoint string) error {
filename := filepath.Join("./conf/proxy/", endpoint+".config")
filename := filepath.Join(CONF_HTTP_PROXY, endpoint+".config")
if endpoint == "/" {
filename = "./conf/proxy/root.config"
filename = filepath.Join(CONF_HTTP_PROXY, "/root.config")
}
filename = filterProxyConfigFilename(filename)
@@ -179,11 +179,11 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
}
// Specify the folder path to be zipped
if !utils.FileExists("./conf") {
if !utils.FileExists(CONF_FOLDER) {
SystemWideLogger.PrintAndLog("Backup", "Configuration folder not found", nil)
return
}
folderPath := "./conf"
folderPath := CONF_FOLDER
// Set the Content-Type header to indicate it's a zip file
w.Header().Set("Content-Type", "application/zip")
@@ -284,12 +284,12 @@ func ImportConfigFromZip(w http.ResponseWriter, r *http.Request) {
return
}
// Create the target directory to unzip the files
targetDir := "./conf"
targetDir := CONF_FOLDER
if utils.FileExists(targetDir) {
//Backup the old config to old
//backupPath := filepath.Dir(*path_conf) + filepath.Base(*path_conf) + ".old_" + strconv.Itoa(int(time.Now().Unix()))
//os.Rename(*path_conf, backupPath)
os.Rename("./conf", "./conf.old_"+strconv.Itoa(int(time.Now().Unix())))
os.Rename(CONF_FOLDER, CONF_FOLDER+".old_"+strconv.Itoa(int(time.Now().Unix())))
}
err = os.MkdirAll(targetDir, os.ModePerm)

View File

@@ -44,7 +44,7 @@ import (
const (
/* Build Constants */
SYSTEM_NAME = "Zoraxy"
SYSTEM_VERSION = "3.2.4"
SYSTEM_VERSION = "3.2.5"
DEVELOPMENT_BUILD = false
/* System Constants */
@@ -63,14 +63,19 @@ const (
LOG_EXTENSION = ".log"
STATISTIC_AUTO_SAVE_INTERVAL = 600 /* Seconds */
/* Configuration Folder Storage Path Constants */
CONF_HTTP_PROXY = "./conf/proxy"
CONF_STREAM_PROXY = "./conf/streamproxy"
CONF_CERT_STORE = "./conf/certs"
CONF_REDIRECTION = "./conf/redirect"
CONF_ACCESS_RULE = "./conf/access"
CONF_PATH_RULE = "./conf/rules/pathrules"
CONF_PLUGIN_GROUPS = "./conf/plugin_groups.json"
/*
Configuration Folder Storage Path Constants
Note: No tailing slash in the path
*/
CONF_FOLDER = "./conf"
CONF_HTTP_PROXY = CONF_FOLDER + "/proxy"
CONF_STREAM_PROXY = CONF_FOLDER + "/streamproxy"
CONF_CERT_STORE = CONF_FOLDER + "/certs"
CONF_REDIRECTION = CONF_FOLDER + "/redirect"
CONF_ACCESS_RULE = CONF_FOLDER + "/access"
CONF_PATH_RULE = CONF_FOLDER + "/rules/pathrules"
CONF_PLUGIN_GROUPS = CONF_FOLDER + "/plugin_groups.json"
CONF_GEODB_PATH = CONF_FOLDER + "/geodb"
)
/* System Startup Flags */

View File

@@ -69,7 +69,7 @@ func main() {
os.Exit(0)
}
if *geoDbUpdate {
geodb.DownloadGeoDBUpdate("./conf/geodb")
geodb.DownloadGeoDBUpdate(CONF_GEODB_PATH)
os.Exit(0)
}

View File

@@ -17,7 +17,7 @@ import (
type SniffResult int
const (
SniffResultAccpet SniffResult = iota // Forward the request to this plugin dynamic capture ingress
SniffResultAccept SniffResult = iota // Forward the request to this plugin dynamic capture ingress
SniffResultSkip // Skip this plugin and let the next plugin handle the request
)
@@ -62,7 +62,7 @@ func (p *PathRouter) RegisterDynamicSniffHandler(sniff_ingress string, mux *http
payload.rawRequest = r
sniffResult := handler(&payload)
if sniffResult == SniffResultAccpet {
if sniffResult == SniffResultAccept {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
} else {

View File

@@ -135,7 +135,7 @@ func ReverseProxtInit() {
Load all conf from files
*/
confs, _ := filepath.Glob("./conf/proxy/*.config")
confs, _ := filepath.Glob(CONF_HTTP_PROXY + "/*.config")
for _, conf := range confs {
err := LoadReverseProxyConfig(conf)
if err != nil {
@@ -389,6 +389,8 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
//TLS
BypassGlobalTLS: useBypassGlobalTLS,
AccessFilterUUID: accessRuleID,
TlsOptions: tlscert.GetDefaultHostSpecificTlsBehavior(),
//VDir
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
//Custom headers

View File

@@ -1423,10 +1423,16 @@
/* ------------ TLS ------------ */
updateTlsResolveList(uuid);
if (subd.TlsOptions){
editor.find(".Tls_EnableSNI").prop("checked", !subd.TlsOptions.DisableSNI);
editor.find(".Tls_EnableLegacyCertificateMatching").prop("checked", !subd.TlsOptions.DisableLegacyCertificateMatching);
editor.find(".Tls_EnableAutoHTTPS").prop("checked", !!subd.TlsOptions.EnableAutoHTTPS);
}else{
//Use default options
editor.find(".Tls_EnableSNI").prop("checked", true);
editor.find(".Tls_EnableLegacyCertificateMatching").prop("checked", true);
editor.find(".Tls_EnableAutoHTTPS").prop("checked", false);
}
editor.find(".Tls_EnableSNI").off("change").on("change", function() {
saveTlsConfigs(uuid);

View File

@@ -137,7 +137,7 @@
});
function clearStreamProxyAddEditForm(){
$('#streamProxyForm input, #streamProxyForm select').val('');
$('#streamProxyForm').find('input:not([type=checkbox]), select').val('');
$('#streamProxyForm select').dropdown('clear');
$("#streamProxyForm input[name=timeout]").val(10);
$("#streamProxyForm .toggle.checkbox").checkbox("set unchecked");