mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-29 19:01:45 +02:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
82f84470f7 | |||
cf9a05f130 | |||
301072db90 | |||
cfcd10d64f | |||
c85760c73a | |||
b7bb918aa3 | |||
962f3e0566 | |||
0bcf2b2ae3 | |||
6bfeb8cf3d | |||
33def66386 | |||
cb469f28d2 | |||
8239f4cb53 | |||
e410b92e34 | |||
aca6e44b35 | |||
2aa35cbe6d | |||
745a54605f | |||
e3b61868a1 | |||
764b1944be | |||
100cd727fc | |||
7e62fef879 | |||
1a4a55721f | |||
bb9deccff6 | |||
a18413dd03 | |||
2cd1b1de3c | |||
3a2db63d61 | |||
123d3bcf3f | |||
3ec1d9c888 | |||
5785261c7e | |||
89e60649e5 | |||
5423b82858 |
1
.github/workflows/main.yml
vendored
1
.github/workflows/main.yml
vendored
@ -33,7 +33,6 @@ jobs:
|
|||||||
docker buildx create --name mainbuilder --driver docker-container --platform linux/amd64,linux/arm64 --use
|
docker buildx create --name mainbuilder --driver docker-container --platform linux/amd64,linux/arm64 --use
|
||||||
|
|
||||||
docker buildx build --push \
|
docker buildx build --push \
|
||||||
--build-arg VERSION=${{ github.event.release.tag_name }} \
|
|
||||||
--provenance=false \
|
--provenance=false \
|
||||||
--platform linux/amd64,linux/arm64 \
|
--platform linux/amd64,linux/arm64 \
|
||||||
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \
|
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \
|
||||||
|
31
CHANGELOG.md
31
CHANGELOG.md
@ -1,3 +1,31 @@
|
|||||||
|
# v3.0.8 15 Jul 2024
|
||||||
|
|
||||||
|
+ Added apache style logging mechanism (and build-in log viewer) [#218](https://github.com/tobychui/zoraxy/issues/218)
|
||||||
|
+ Fixed keep alive flushing issues [#235](https://github.com/tobychui/zoraxy/issues/235)
|
||||||
|
+ Added multi-upstream supports [#100](https://github.com/tobychui/zoraxy/issues/100)
|
||||||
|
+ Added stick session load balancer
|
||||||
|
+ Added weighted random load balancer
|
||||||
|
+ Added domain cleaning logic to domain / IP input fields
|
||||||
|
+ Added HSTS "include subdomain" auto injector
|
||||||
|
+ Added work-in-progress SSO / Oauth Server UI
|
||||||
|
+ Fixed uptime monitor not updating on proxy rule change bug
|
||||||
|
+ Optimized UI for create new proxy rule
|
||||||
|
+ Removed service expose proxy feature
|
||||||
|
|
||||||
|
# v3.0.7 20 Jun 2024
|
||||||
|
|
||||||
|
+ Fixed redirection enable bug [#199](https://github.com/tobychui/zoraxy/issues/199)
|
||||||
|
+ Fixed header tool user agent rewrite sequence
|
||||||
|
+ Optimized rate limit UI
|
||||||
|
+ Added HSTS and Permission Policy Editor [#163](https://github.com/tobychui/zoraxy/issues/163)
|
||||||
|
+ Docker UX optimization start parameter `-docker`
|
||||||
|
+ Docker container selector implementation for conditional compilations for Windows
|
||||||
|
|
||||||
|
From contributors:
|
||||||
|
|
||||||
|
+ Add Rate Limits Limits to Zoraxy fixes [185](https://github.com/tobychui/zoraxy/issues/185) by [Kirari04](https://github.com/Kirari04)
|
||||||
|
+ Add docker containers list to set rule by [7brend7](https://github.com/7brend7) [PR202](https://github.com/tobychui/zoraxy/pull/202)
|
||||||
|
|
||||||
# v3.0.6 10 Jun 2024
|
# v3.0.6 10 Jun 2024
|
||||||
|
|
||||||
+ Added fastly_client_ip to X-Real-IP auto rewrite
|
+ Added fastly_client_ip to X-Real-IP auto rewrite
|
||||||
@ -13,6 +41,9 @@
|
|||||||
+ Added stream proxy auto start [#169](https://github.com/tobychui/zoraxy/issues/169)
|
+ Added stream proxy auto start [#169](https://github.com/tobychui/zoraxy/issues/169)
|
||||||
+ Optimized UX for reminding user to click Apply after port change
|
+ Optimized UX for reminding user to click Apply after port change
|
||||||
+ Added version number to footer [#160](https://github.com/tobychui/zoraxy/issues/160)
|
+ Added version number to footer [#160](https://github.com/tobychui/zoraxy/issues/160)
|
||||||
|
|
||||||
|
From contributors:
|
||||||
|
|
||||||
+ Fixed missing / unnecessary error check [PR187](https://github.com/tobychui/zoraxy/pull/187) by [Kirari04](https://github.com/Kirari04)
|
+ Fixed missing / unnecessary error check [PR187](https://github.com/tobychui/zoraxy/pull/187) by [Kirari04](https://github.com/Kirari04)
|
||||||
|
|
||||||
# v3.0.5 May 26 2024
|
# v3.0.5 May 26 2024
|
||||||
|
@ -21,12 +21,14 @@ A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
|
|||||||
- DNS Challenge for Let's Encrypt and [these DNS providers](https://go-acme.github.io/lego/dns/)
|
- DNS Challenge for Let's Encrypt and [these DNS providers](https://go-acme.github.io/lego/dns/)
|
||||||
- Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)
|
- Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)
|
||||||
- Global Area Network Controller Web UI (ZeroTier not included)
|
- Global Area Network Controller Web UI (ZeroTier not included)
|
||||||
- TCP Tunneling / Proxy
|
- Stream Proxy (TCP & UDP)
|
||||||
- Integrated Up-time Monitor
|
- Integrated Up-time Monitor
|
||||||
- Web-SSH Terminal
|
- Web-SSH Terminal
|
||||||
- Utilities
|
- Utilities
|
||||||
- CIDR IP converters
|
- CIDR IP converters
|
||||||
- mDNS Scanner
|
- mDNS Scanner
|
||||||
|
- Wake-On-Lan
|
||||||
|
- Debug Forward Proxy
|
||||||
- IP Scanner
|
- IP Scanner
|
||||||
- Others
|
- Others
|
||||||
- Basic single-admin management mode
|
- Basic single-admin management mode
|
||||||
@ -96,12 +98,12 @@ See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder fo
|
|||||||
Usage of zoraxy:
|
Usage of zoraxy:
|
||||||
-autorenew int
|
-autorenew int
|
||||||
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
|
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
|
||||||
|
-cfgupgrade
|
||||||
|
Enable auto config upgrade if breaking change is detected (default true)
|
||||||
-docker
|
-docker
|
||||||
Run Zoraxy in docker compatibility mode
|
Run Zoraxy in docker compatibility mode
|
||||||
-fastgeoip
|
-fastgeoip
|
||||||
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
|
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
|
||||||
-log
|
|
||||||
Log terminal output to file (default true)
|
|
||||||
-mdns
|
-mdns
|
||||||
Enable mDNS scanner and transponder (default true)
|
Enable mDNS scanner and transponder (default true)
|
||||||
-mdnsname string
|
-mdnsname string
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
FROM docker.io/golang:alpine
|
FROM docker.io/golang:alpine AS build
|
||||||
# VERSION comes from the main.yml workflow --build-arg
|
|
||||||
ARG VERSION
|
|
||||||
|
|
||||||
RUN apk add --no-cache bash netcat-openbsd sudo
|
|
||||||
|
|
||||||
RUN mkdir -p /opt/zoraxy/source/ &&\
|
RUN mkdir -p /opt/zoraxy/source/ &&\
|
||||||
mkdir -p /opt/zoraxy/config/ &&\
|
mkdir -p /opt/zoraxy/config/ &&\
|
||||||
@ -10,8 +6,6 @@ RUN mkdir -p /opt/zoraxy/source/ &&\
|
|||||||
|
|
||||||
RUN chmod -R 770 /opt/zoraxy/
|
RUN chmod -R 770 /opt/zoraxy/
|
||||||
|
|
||||||
VOLUME [ "/opt/zoraxy/config/" ]
|
|
||||||
|
|
||||||
# If you build it yourself, you will need to add the src directory into the docker directory.
|
# If you build it yourself, you will need to add the src directory into the docker directory.
|
||||||
COPY ./src/ /opt/zoraxy/source/
|
COPY ./src/ /opt/zoraxy/source/
|
||||||
|
|
||||||
@ -24,12 +18,30 @@ RUN go mod tidy &&\
|
|||||||
RUN chmod 755 /usr/local/bin/zoraxy &&\
|
RUN chmod 755 /usr/local/bin/zoraxy &&\
|
||||||
chmod +x /usr/local/bin/zoraxy
|
chmod +x /usr/local/bin/zoraxy
|
||||||
|
|
||||||
|
FROM docker.io/alpine:3.20
|
||||||
|
|
||||||
|
RUN apk add --no-cache bash netcat-openbsd sudo
|
||||||
|
|
||||||
|
COPY --from=build /usr/local/bin/zoraxy /usr/local/bin/zoraxy
|
||||||
|
COPY --from=build /opt/zoraxy/config/ /opt/zoraxy/config
|
||||||
|
|
||||||
|
VOLUME [ "/opt/zoraxy/config/" ]
|
||||||
|
|
||||||
WORKDIR /opt/zoraxy/config/
|
WORKDIR /opt/zoraxy/config/
|
||||||
|
|
||||||
ENV VERSION=$VERSION
|
ENV AUTORENEW="86400"
|
||||||
ENV ARGS="-noauth=false"
|
ENV FASTGEOIP="false"
|
||||||
|
ENV MDNS="true"
|
||||||
|
ENV MDNSNAME="''"
|
||||||
|
ENV NOAUTH="false"
|
||||||
|
ENV PORT="8000"
|
||||||
|
ENV SSHLB="false"
|
||||||
|
ENV VERSION="false"
|
||||||
|
ENV WEBFM="true"
|
||||||
|
ENV WEBROOT="./www"
|
||||||
|
ENV ZTAUTH="''"
|
||||||
|
ENV ZTPORT="9993"
|
||||||
|
|
||||||
ENTRYPOINT "zoraxy" "-port=:8000" "-docker=true" "${ARGS}"
|
ENTRYPOINT "zoraxy" "-docker=true" "-autorenew=${AUTORENEW}" "-fastgeoip=${FASTGEOIP}" "-mdns=${MDNS}" "-mdnsname=${MDNSNAME}" "-noauth=${NOAUTH}" "-port=:${PORT}" "-sshlb=${SSHLB}" "-version=${VERSION}" "-webfm=${WEBFM}" "-webroot=${WEBROOT}" "-ztauth=${ZTAUTH}" "-ztport=${ZTPORT}"
|
||||||
|
|
||||||
HEALTHCHECK --interval=5s --timeout=5s --retries=2 CMD nc -vz 127.0.0.1 8000 || exit 1
|
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 CMD nc -vz 127.0.0.1 $PORT || exit 1
|
||||||
|
@ -10,14 +10,15 @@ Although not required, it is recommended to give Zoraxy a dedicated location on
|
|||||||
|
|
||||||
You may also need to portforward your 80/443 to allow http and https traffic. If you are accessing the interface from outside of the local network, you may also need to forward your management port. If you know how to do this, great! If not, find the manufacturer of your router and search on how to do that. There are too many to be listed here. </br>
|
You may also need to portforward your 80/443 to allow http and https traffic. If you are accessing the interface from outside of the local network, you may also need to forward your management port. If you know how to do this, great! If not, find the manufacturer of your router and search on how to do that. There are too many to be listed here. </br>
|
||||||
|
|
||||||
|
The examples below are not exactly how it should be set up, rather they give a general idea of usage.
|
||||||
|
|
||||||
### Using Docker run </br>
|
### Using Docker run </br>
|
||||||
```
|
```
|
||||||
docker run -d --name (container name) -p (ports) -v (path to storage directory):/opt/zoraxy/data/ -e ARGS='(your arguments)' zoraxydocker/zoraxy:latest
|
docker run -d --name (container name) -p 80:80 -p 443:443 -p (management external):(management internal) -v (path to storage directory):/opt/zoraxy/data/ -e (flag)="(value)" zoraxydocker/zoraxy:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Docker Compose </br>
|
### Using Docker Compose </br>
|
||||||
```yml
|
```yml
|
||||||
version: '3.3'
|
|
||||||
services:
|
services:
|
||||||
zoraxy-docker:
|
zoraxy-docker:
|
||||||
image: zoraxydocker/zoraxy:latest
|
image: zoraxydocker/zoraxy:latest
|
||||||
@ -25,11 +26,11 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
- 443:443
|
- 443:443
|
||||||
- (external):8000
|
- (management external):(management internal)
|
||||||
volumes:
|
volumes:
|
||||||
- (path to storage directory):/opt/zoraxy/config/
|
- (path to storage directory):/opt/zoraxy/config/
|
||||||
environment:
|
environment:
|
||||||
ARGS: '(your arguments)'
|
(flag): "(value)"
|
||||||
```
|
```
|
||||||
|
|
||||||
| Operator | Need | Details |
|
| Operator | Need | Details |
|
||||||
@ -38,18 +39,21 @@ services:
|
|||||||
| `--name (container name)` | No | Sets the name of the container to the following word. You can change this to whatever you want. |
|
| `--name (container name)` | No | Sets the name of the container to the following word. You can change this to whatever you want. |
|
||||||
| `-p (ports)` | Yes | Depending on how your network is setup, you may need to portforward 80, 443, and the management port. |
|
| `-p (ports)` | Yes | Depending on how your network is setup, you may need to portforward 80, 443, and the management port. |
|
||||||
| `-v (path to storage directory):/opt/zoraxy/config/` | Recommend | Sets the folder that holds your files. This should be the place you just chose. By default, it will create a Docker volume for the files for persistency but they will not be accessible. |
|
| `-v (path to storage directory):/opt/zoraxy/config/` | Recommend | Sets the folder that holds your files. This should be the place you just chose. By default, it will create a Docker volume for the files for persistency but they will not be accessible. |
|
||||||
| `-e ARGS='(your arguments)'` | No | Sets the arguments to run Zoraxy with. Enter them as you would normally. By default, it is ran with `-noauth=false` but <b>you cannot change the management port.</b> This is required for the healthcheck to work. |
|
| `-v /var/run/docker.sock:/var/run/docker.sock` | No | Used for autodiscovery. |
|
||||||
|
| `-e (flag)="(value)"` | No | Arguments to run Zoraxy with. They are simply just capitalized Zoraxy flags. `-docker=true` is always set by default. See examples below. |
|
||||||
| `zoraxydocker/zoraxy:latest` | Yes | The repository on Docker hub. By default, it is the latest version that is published. |
|
| `zoraxydocker/zoraxy:latest` | Yes | The repository on Docker hub. By default, it is the latest version that is published. |
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Docker usage of the port flag should not include the colon. Ex: PORT="8000"
|
||||||
|
|
||||||
## Examples: </br>
|
## Examples: </br>
|
||||||
### Docker Run </br>
|
### Docker Run </br>
|
||||||
```
|
```
|
||||||
docker run -d --name zoraxy -p 80:80 -p 443:443 -p 8005:8000/tcp -v /home/docker/Containers/Zoraxy:/opt/zoraxy/config/ -e ARGS='-noauth=false' zoraxydocker/zoraxy:latest
|
docker run -d --name zoraxy -p 80:80 -p 443:443 -p 8005:8005 -v /home/docker/Containers/Zoraxy:/opt/zoraxy/config/ -v /var/run/docker.sock:/var/run/docker.sock -e PORT="8005" -e FASTGEOIP="true" zoraxydocker/zoraxy:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Compose </br>
|
### Docker Compose </br>
|
||||||
```yml
|
```yml
|
||||||
version: '3.3'
|
|
||||||
services:
|
services:
|
||||||
zoraxy-docker:
|
zoraxy-docker:
|
||||||
image: zoraxydocker/zoraxy:latest
|
image: zoraxydocker/zoraxy:latest
|
||||||
@ -57,9 +61,11 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
- 443:443
|
- 443:443
|
||||||
- 8005:8000/tcp
|
- 8005:8005
|
||||||
volumes:
|
volumes:
|
||||||
- /home/docker/Containers/Zoraxy:/opt/zoraxy/config/
|
- /home/docker/Containers/Zoraxy:/opt/zoraxy/config/
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
environment:
|
environment:
|
||||||
ARGS: '-noauth=false'
|
PORT: "8005"
|
||||||
|
FASTGEOIP: "true"
|
||||||
```
|
```
|
||||||
|
14
src/api.go
14
src/api.go
@ -61,6 +61,12 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
||||||
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
||||||
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
||||||
|
//Reverse proxy upstream (load balance) APIs
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete)
|
||||||
//Reverse proxy virtual directory APIs
|
//Reverse proxy virtual directory APIs
|
||||||
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
|
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
|
||||||
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
|
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
|
||||||
@ -81,6 +87,7 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
|
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
|
||||||
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
|
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
|
||||||
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
|
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
|
||||||
|
authRouter.HandleFunc("/api/cert/download", handleCertDownload)
|
||||||
authRouter.HandleFunc("/api/cert/list", handleListCertificate)
|
authRouter.HandleFunc("/api/cert/list", handleListCertificate)
|
||||||
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
||||||
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
||||||
@ -121,7 +128,7 @@ func initAPIs() {
|
|||||||
//Statistic & uptime monitoring API
|
//Statistic & uptime monitoring API
|
||||||
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
|
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
|
||||||
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
|
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
|
||||||
authRouter.HandleFunc("/api/stats/netstat", netstat.HandleGetNetworkInterfaceStats)
|
authRouter.HandleFunc("/api/stats/netstat", netstatBuffers.HandleGetNetworkInterfaceStats)
|
||||||
authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats)
|
authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats)
|
||||||
authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces)
|
authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces)
|
||||||
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
|
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
|
||||||
@ -142,7 +149,7 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
|
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
|
||||||
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
||||||
|
|
||||||
//TCP Proxy
|
//Stream (TCP / UDP) Proxy
|
||||||
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
||||||
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
|
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
|
||||||
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
|
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
|
||||||
@ -223,12 +230,13 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
|
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
|
||||||
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
|
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
|
||||||
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
|
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
|
||||||
|
authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog)
|
||||||
|
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
|
||||||
|
|
||||||
//Debug
|
//Debug
|
||||||
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
|
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
|
||||||
|
|
||||||
//If you got APIs to add, append them here
|
//If you got APIs to add, append them here
|
||||||
// get available docker containers
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
45
src/cert.go
45
src/cert.go
@ -233,6 +233,51 @@ func handleSetTlsRequireLatest(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle download of the selected certificate
|
||||||
|
func handleCertDownload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get the certificate name
|
||||||
|
certname, err := utils.GetPara(r, "certname")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid certname given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
certname = filepath.Base(certname) //prevent path escape
|
||||||
|
|
||||||
|
// check if the cert exists
|
||||||
|
pubKey := filepath.Join(filepath.Join("./conf/certs"), certname+".key")
|
||||||
|
priKey := filepath.Join(filepath.Join("./conf/certs"), certname+".pem")
|
||||||
|
|
||||||
|
if utils.FileExists(pubKey) && utils.FileExists(priKey) {
|
||||||
|
//Zip them and serve them via http download
|
||||||
|
seeking, _ := utils.GetBool(r, "seek")
|
||||||
|
if seeking {
|
||||||
|
//This request only check if the key exists. Do not provide download
|
||||||
|
utils.SendOK(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Serve both file in zip
|
||||||
|
zipTmpFolder := "./tmp/download"
|
||||||
|
os.MkdirAll(zipTmpFolder, 0775)
|
||||||
|
zipFileName := filepath.Join(zipTmpFolder, certname+".zip")
|
||||||
|
err := utils.ZipFiles(zipFileName, pubKey, priKey)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to create zip file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(zipFileName) // Clean up the zip file after serving
|
||||||
|
|
||||||
|
// Serve the zip file
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=\""+certname+"_export.zip\"")
|
||||||
|
w.Header().Set("Content-Type", "application/zip")
|
||||||
|
http.ServeFile(w, r, zipFileName)
|
||||||
|
} else {
|
||||||
|
//Not both key exists
|
||||||
|
utils.SendErrorResponse(w, "invalid key-pairs: private key or public key not found in key store")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle upload of the certificate
|
// Handle upload of the certificate
|
||||||
func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
||||||
// check if request method is POST
|
// check if request method is POST
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -79,7 +80,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
|||||||
return errors.New("not supported proxy type")
|
return errors.New("not supported proxy type")
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemWideLogger.PrintAndLog("Proxy", thisConfigEndpoint.RootOrMatchingDomain+" -> "+thisConfigEndpoint.Domain+" routing rule loaded", nil)
|
SystemWideLogger.PrintAndLog("proxy-config", thisConfigEndpoint.RootOrMatchingDomain+" -> "+loadbalance.GetUpstreamsAsString(thisConfigEndpoint.ActiveOrigins)+" routing rule loaded", nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,10 +133,16 @@ func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
|
|||||||
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
|
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
|
||||||
ProxyType: dynamicproxy.ProxyType_Root,
|
ProxyType: dynamicproxy.ProxyType_Root,
|
||||||
RootOrMatchingDomain: "/",
|
RootOrMatchingDomain: "/",
|
||||||
Domain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
ActiveOrigins: []*loadbalance.Upstream{
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
||||||
RequireTLS: false,
|
RequireTLS: false,
|
||||||
BypassGlobalTLS: false,
|
|
||||||
SkipCertValidations: false,
|
SkipCertValidations: false,
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InactiveOrigins: []*loadbalance.Upstream{},
|
||||||
|
BypassGlobalTLS: false,
|
||||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||||
RequireBasicAuth: false,
|
RequireBasicAuth: false,
|
||||||
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
||||||
|
50
src/main.go
50
src/main.go
@ -24,6 +24,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/ganserv"
|
"imuslab.com/zoraxy/mod/ganserv"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logviewer"
|
||||||
"imuslab.com/zoraxy/mod/mdns"
|
"imuslab.com/zoraxy/mod/mdns"
|
||||||
"imuslab.com/zoraxy/mod/netstat"
|
"imuslab.com/zoraxy/mod/netstat"
|
||||||
"imuslab.com/zoraxy/mod/pathrule"
|
"imuslab.com/zoraxy/mod/pathrule"
|
||||||
@ -32,6 +33,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||||
"imuslab.com/zoraxy/mod/streamproxy"
|
"imuslab.com/zoraxy/mod/streamproxy"
|
||||||
"imuslab.com/zoraxy/mod/tlscert"
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
|
"imuslab.com/zoraxy/mod/update"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
"imuslab.com/zoraxy/mod/webserv"
|
"imuslab.com/zoraxy/mod/webserv"
|
||||||
@ -51,12 +53,12 @@ var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL cert
|
|||||||
var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
|
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")
|
var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
|
||||||
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
|
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
|
||||||
var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
|
var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
name = "Zoraxy"
|
name = "Zoraxy"
|
||||||
version = "3.0.7"
|
version = "3.0.9"
|
||||||
nodeUUID = "generic"
|
nodeUUID = "generic" //System uuid, in uuidv4 format
|
||||||
development = false //Set this to false to use embedded web fs
|
development = false //Set this to false to use embedded web fs
|
||||||
bootTime = time.Now().Unix()
|
bootTime = time.Now().Unix()
|
||||||
|
|
||||||
@ -73,7 +75,7 @@ var (
|
|||||||
authAgent *auth.AuthAgent //Authentication agent
|
authAgent *auth.AuthAgent //Authentication agent
|
||||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||||
loadbalancer *loadbalance.RouteManager //Load balancer manager to get routing targets from proxy rules
|
|
||||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||||
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||||
accessController *access.Controller //Access controller, handle black list and white list
|
accessController *access.Controller //Access controller, handle black list and white list
|
||||||
@ -88,12 +90,14 @@ var (
|
|||||||
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
||||||
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
||||||
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
|
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
|
||||||
|
loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing
|
||||||
|
|
||||||
//Helper modules
|
//Helper modules
|
||||||
EmailSender *email.Sender //Email sender that handle email sending
|
EmailSender *email.Sender //Email sender that handle email sending
|
||||||
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
||||||
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
|
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
|
||||||
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
||||||
|
LogViewer *logviewer.Viewer
|
||||||
)
|
)
|
||||||
|
|
||||||
// Kill signal handler. Do something before the system the core terminate.
|
// Kill signal handler. Do something before the system the core terminate.
|
||||||
@ -108,32 +112,34 @@ func SetupCloseHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ShutdownSeq() {
|
func ShutdownSeq() {
|
||||||
fmt.Println("- Shutting down " + name)
|
SystemWideLogger.Println("Shutting down " + name)
|
||||||
fmt.Println("- Closing GeoDB ")
|
SystemWideLogger.Println("Closing GeoDB ")
|
||||||
geodbStore.Close()
|
geodbStore.Close()
|
||||||
fmt.Println("- Closing Netstats Listener")
|
SystemWideLogger.Println("Closing Netstats Listener")
|
||||||
netstatBuffers.Close()
|
netstatBuffers.Close()
|
||||||
fmt.Println("- Closing Statistic Collector")
|
SystemWideLogger.Println("Closing Statistic Collector")
|
||||||
statisticCollector.Close()
|
statisticCollector.Close()
|
||||||
if mdnsTickerStop != nil {
|
if mdnsTickerStop != nil {
|
||||||
fmt.Println("- Stopping mDNS Discoverer (might take a few minutes)")
|
SystemWideLogger.Println("Stopping mDNS Discoverer (might take a few minutes)")
|
||||||
// Stop the mdns service
|
// Stop the mdns service
|
||||||
mdnsTickerStop <- true
|
mdnsTickerStop <- true
|
||||||
}
|
}
|
||||||
|
|
||||||
mdnsScanner.Close()
|
mdnsScanner.Close()
|
||||||
fmt.Println("- Closing Certificates Auto Renewer")
|
SystemWideLogger.Println("Shutting down load balancer")
|
||||||
|
loadBalancer.Close()
|
||||||
|
SystemWideLogger.Println("Closing Certificates Auto Renewer")
|
||||||
acmeAutoRenewer.Close()
|
acmeAutoRenewer.Close()
|
||||||
//Remove the tmp folder
|
//Remove the tmp folder
|
||||||
fmt.Println("- Cleaning up tmp files")
|
SystemWideLogger.Println("Cleaning up tmp files")
|
||||||
os.RemoveAll("./tmp")
|
os.RemoveAll("./tmp")
|
||||||
|
|
||||||
fmt.Println("- Closing system wide logger")
|
//Close database
|
||||||
SystemWideLogger.Close()
|
SystemWideLogger.Println("Stopping system database")
|
||||||
|
|
||||||
//Close database, final
|
|
||||||
fmt.Println("- Stopping system database")
|
|
||||||
sysdb.Close()
|
sysdb.Close()
|
||||||
|
|
||||||
|
//Close logger
|
||||||
|
SystemWideLogger.Println("Closing system wide logger")
|
||||||
|
SystemWideLogger.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -144,6 +150,16 @@ func main() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !utils.ValidateListeningAddress(*webUIPort) {
|
||||||
|
fmt.Println("Malformed -port (listening address) paramter. Do you mean -port=:" + *webUIPort + "?")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *enableAutoUpdate {
|
||||||
|
fmt.Println("Checking required config update")
|
||||||
|
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version))
|
||||||
|
}
|
||||||
|
|
||||||
SetupCloseHandler()
|
SetupCloseHandler()
|
||||||
|
|
||||||
//Read or create the system uuid
|
//Read or create the system uuid
|
||||||
|
@ -448,7 +448,12 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
domains := strings.Split(domainPara, ",")
|
domains := strings.Split(domainPara, ",")
|
||||||
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS, dns)
|
//Clean spaces in front or behind each domain
|
||||||
|
cleanedDomains := []string{}
|
||||||
|
for _, domain := range domains {
|
||||||
|
cleanedDomains = append(cleanedDomains, strings.TrimSpace(domain))
|
||||||
|
}
|
||||||
|
result, err := a.ObtainCert(cleanedDomains, filename, email, ca, caUrl, skipTLS, dns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||||
return
|
return
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
"imuslab.com/zoraxy/mod/acme/acmedns"
|
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||||
)
|
)
|
||||||
@ -29,7 +24,7 @@ func GetDnsChallengeProviderByName(dnsProvider string, dnsCredentials string) (c
|
|||||||
/*
|
/*
|
||||||
Original implementation of DNS ACME using OS.Env as payload
|
Original implementation of DNS ACME using OS.Env as payload
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
func setCredentialsIntoEnvironmentVariables(credentials map[string]string) {
|
func setCredentialsIntoEnvironmentVariables(credentials map[string]string) {
|
||||||
for key, value := range credentials {
|
for key, value := range credentials {
|
||||||
err := os.Setenv(key, value)
|
err := os.Setenv(key, value)
|
||||||
@ -41,6 +36,7 @@ func setCredentialsIntoEnvironmentVariables(credentials map[string]string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func extractDnsCredentials(input string) (map[string]string, error) {
|
func extractDnsCredentials(input string) (map[string]string, error) {
|
||||||
result := make(map[string]string)
|
result := make(map[string]string)
|
||||||
|
|
||||||
@ -70,3 +66,5 @@ func extractDnsCredentials(input string) (map[string]string, error) {
|
|||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
@ -6,6 +6,7 @@ package acmedns
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||||
@ -660,6 +661,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
cfg.PropagationTimeout = 1200 * time.Second
|
||||||
return netcup.NewDNSProviderConfig(cfg)
|
return netcup.NewDNSProviderConfig(cfg)
|
||||||
case "netlify":
|
case "netlify":
|
||||||
cfg := netlify.NewDefaultConfig()
|
cfg := netlify.NewDefaultConfig()
|
||||||
|
@ -14,10 +14,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
db "imuslab.com/zoraxy/mod/database"
|
db "imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ type AuthAgent struct {
|
|||||||
SessionStore *sessions.CookieStore
|
SessionStore *sessions.CookieStore
|
||||||
Database *db.Database
|
Database *db.Database
|
||||||
LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
|
LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
|
||||||
|
Logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthEndpoints struct {
|
type AuthEndpoints struct {
|
||||||
@ -38,11 +39,11 @@ type AuthEndpoints struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
|
func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, systemLogger *logger.Logger, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
|
||||||
store := sessions.NewCookieStore(key)
|
store := sessions.NewCookieStore(key)
|
||||||
err := sysdb.NewTable("auth")
|
err := sysdb.NewTable("auth")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to create auth database. Terminating.")
|
systemLogger.Println("Failed to create auth database. Terminating.")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,13 +53,14 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database,
|
|||||||
SessionStore: store,
|
SessionStore: store,
|
||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
LoginRedirectionHandler: loginRedirectionHandler,
|
LoginRedirectionHandler: loginRedirectionHandler,
|
||||||
|
Logger: systemLogger,
|
||||||
}
|
}
|
||||||
|
|
||||||
//Return the authAgent
|
//Return the authAgent
|
||||||
return &newAuthAgent
|
return &newAuthAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSessionKey(sysdb *db.Database) (string, error) {
|
func GetSessionKey(sysdb *db.Database, logger *logger.Logger) (string, error) {
|
||||||
sysdb.NewTable("auth")
|
sysdb.NewTable("auth")
|
||||||
sessionKey := ""
|
sessionKey := ""
|
||||||
if !sysdb.KeyExists("auth", "sessionkey") {
|
if !sysdb.KeyExists("auth", "sessionkey") {
|
||||||
@ -66,9 +68,9 @@ func GetSessionKey(sysdb *db.Database) (string, error) {
|
|||||||
rand.Read(key)
|
rand.Read(key)
|
||||||
sessionKey = string(key)
|
sessionKey = string(key)
|
||||||
sysdb.Write("auth", "sessionkey", sessionKey)
|
sysdb.Write("auth", "sessionkey", sessionKey)
|
||||||
log.Println("[Auth] New authentication session key generated")
|
logger.PrintAndLog("auth", "New authentication session key generated", nil)
|
||||||
} else {
|
} else {
|
||||||
log.Println("[Auth] Authentication session key loaded from database")
|
logger.PrintAndLog("auth", "Authentication session key loaded from database", nil)
|
||||||
err := sysdb.Read("auth", "sessionkey", &sessionKey)
|
err := sysdb.Read("auth", "sessionkey", &sessionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("database read error. Is the database file corrupted?")
|
return "", errors.New("database read error. Is the database file corrupted?")
|
||||||
@ -95,7 +97,7 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
username, err := utils.PostPara(r, "username")
|
username, err := utils.PostPara(r, "username")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Username not defined
|
//Username not defined
|
||||||
log.Println("[Auth] " + r.RemoteAddr + " trying to login with username: " + username)
|
a.Logger.PrintAndLog("auth", r.RemoteAddr+" trying to login with username: "+username, nil)
|
||||||
utils.SendErrorResponse(w, "Username not defined or empty.")
|
utils.SendErrorResponse(w, "Username not defined or empty.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -124,11 +126,11 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
a.LoginUserByRequest(w, r, username, rememberme)
|
a.LoginUserByRequest(w, r, username, rememberme)
|
||||||
|
|
||||||
//Print the login message to console
|
//Print the login message to console
|
||||||
log.Println(username + " logged in.")
|
a.Logger.PrintAndLog("auth", username+" logged in.", nil)
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
} else {
|
} else {
|
||||||
//Password incorrect
|
//Password incorrect
|
||||||
log.Println(username + " login request rejected: " + rejectionReason)
|
a.Logger.PrintAndLog("auth", username+" login request rejected: "+rejectionReason, nil)
|
||||||
|
|
||||||
utils.SendErrorResponse(w, rejectionReason)
|
utils.SendErrorResponse(w, rejectionReason)
|
||||||
return
|
return
|
||||||
@ -147,7 +149,7 @@ func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, passw
|
|||||||
err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
|
err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//User not found or db exception
|
//User not found or db exception
|
||||||
log.Println("[Auth] " + username + " login with incorrect password")
|
a.Logger.PrintAndLog("auth", username+" login with incorrect password", nil)
|
||||||
return false, "Invalid username or password"
|
return false, "Invalid username or password"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,8 +186,12 @@ func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, u
|
|||||||
// Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
|
// Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
|
||||||
func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
|
func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
username, err := a.GetUserName(w, r)
|
username, err := a.GetUserName(w, r)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "user not logged in")
|
||||||
|
return
|
||||||
|
}
|
||||||
if username != "" {
|
if username != "" {
|
||||||
log.Println(username + " logged out.")
|
a.Logger.PrintAndLog("auth", username+" logged out", nil)
|
||||||
}
|
}
|
||||||
// Revoke users authentication
|
// Revoke users authentication
|
||||||
err = a.Logout(w, r)
|
err = a.Logout(w, r)
|
||||||
@ -194,7 +200,7 @@ func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write([]byte("OK"))
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
|
func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
|
||||||
@ -291,7 +297,7 @@ func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callb
|
|||||||
|
|
||||||
//Return to the client with OK
|
//Return to the client with OK
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
log.Println("[Auth] New user " + newusername + " added to system.")
|
a.Logger.PrintAndLog("auth", "New user "+newusername+" added to system.", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new user register without confirmation email. Require POST username, password, group.
|
// Handle new user register without confirmation email. Require POST username, password, group.
|
||||||
@ -324,7 +330,7 @@ func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
//Return to the client with OK
|
//Return to the client with OK
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
log.Println("[Auth] Admin account created: " + newusername)
|
a.Logger.PrintAndLog("auth", "Admin account created: "+newusername, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check authentication from request header's session value
|
// Check authentication from request header's session value
|
||||||
@ -365,7 +371,7 @@ func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
//Return to the client with OK
|
//Return to the client with OK
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
log.Println("[Auth] User " + username + " has been removed from the system.")
|
a.Logger.PrintAndLog("auth", "User "+username+" has been removed from the system", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthAgent) UnregisterUser(username string) error {
|
func (a *AuthAgent) UnregisterUser(username string) error {
|
||||||
@ -393,7 +399,7 @@ func (a *AuthAgent) GetUserCounts() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if usercount == 0 {
|
if usercount == 0 {
|
||||||
log.Println("There are no user in the database.")
|
a.Logger.PrintAndLog("auth", "There are no user in the database", nil)
|
||||||
}
|
}
|
||||||
return usercount
|
return usercount
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ func NewManagedHTTPRouter(option RouterOption) *RouterDef {
|
|||||||
func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error {
|
func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error {
|
||||||
//Check if the endpoint already registered
|
//Check if the endpoint already registered
|
||||||
if _, exist := router.endpoints[endpoint]; exist {
|
if _, exist := router.endpoints[endpoint]; exist {
|
||||||
log.Println("WARNING! Duplicated registering of web endpoint: " + endpoint)
|
fmt.Println("WARNING! Duplicated registering of web endpoint: " + endpoint)
|
||||||
return errors.New("endpoint register duplicated")
|
return errors.New("endpoint register duplicated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
- Access Router
|
- Access Router
|
||||||
- Blacklist
|
- Blacklist
|
||||||
- Whitelist
|
- Whitelist
|
||||||
|
- Rate Limitor
|
||||||
- Basic Auth
|
- Basic Auth
|
||||||
- Vitrual Directory Proxy
|
- Vitrual Directory Proxy
|
||||||
- Subdomain Proxy
|
- Subdomain Proxy
|
||||||
@ -30,7 +31,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
/*
|
/*
|
||||||
Special Routing Rules, bypass most of the limitations
|
Special Routing Rules, bypass most of the limitations
|
||||||
*/
|
*/
|
||||||
//Check if there are external routing rule matches.
|
//Check if there are external routing rule (rr) matches.
|
||||||
//If yes, route them via external rr
|
//If yes, route them via external rr
|
||||||
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
||||||
if matchedRoutingRule != nil {
|
if matchedRoutingRule != nil {
|
||||||
@ -45,7 +46,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Check if this is a redirection url
|
//Check if this is a redirection url
|
||||||
if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
|
if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
|
||||||
statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
|
statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
|
||||||
h.logRequest(r, statusCode != 500, statusCode, "redirect", "")
|
h.Parent.logRequest(r, statusCode != 500, statusCode, "redirect", "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +77,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if sep.RequireRateLimit {
|
if sep.RequireRateLimit {
|
||||||
err := h.handleRateLimitRouting(w, r, sep)
|
err := h.handleRateLimitRouting(w, r, sep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 429)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,6 +86,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if sep.RequireBasicAuth {
|
if sep.RequireBasicAuth {
|
||||||
err := h.handleBasicAuthRouting(w, r, sep)
|
err := h.handleBasicAuthRouting(w, r, sep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,6 +103,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
|
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
|
||||||
//Missing tailing slash. Redirect to target proxy endpoint
|
//Missing tailing slash. Redirect to target proxy endpoint
|
||||||
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
||||||
|
h.Parent.Option.Logger.LogHTTPRequest(r, "redirect", 307)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,12 +197,12 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
hostname := parsedURL.Hostname()
|
hostname := parsedURL.Hostname()
|
||||||
if hostname == domainOnly {
|
if hostname == domainOnly {
|
||||||
h.logRequest(r, false, 500, "root-redirect", domainOnly)
|
h.Parent.logRequest(r, false, 500, "root-redirect", domainOnly)
|
||||||
http.Error(w, "Loopback redirects due to invalid settings", 500)
|
http.Error(w, "Loopback redirects due to invalid settings", 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.logRequest(r, false, 307, "root-redirect", domainOnly)
|
h.Parent.logRequest(r, false, 307, "root-redirect", domainOnly)
|
||||||
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
|
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
|
||||||
case DefaultSite_NotFoundPage:
|
case DefaultSite_NotFoundPage:
|
||||||
//Serve the not found page, use template if exists
|
//Serve the not found page, use template if exists
|
||||||
|
@ -24,7 +24,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter,
|
|||||||
|
|
||||||
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
|
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
|
||||||
if isBlocked {
|
if isBlocked {
|
||||||
h.logRequest(r, false, 403, blockedReason, "")
|
h.Parent.logRequest(r, false, 403, blockedReason, "")
|
||||||
}
|
}
|
||||||
return isBlocked
|
return isBlocked
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||||
err := handleBasicAuth(w, r, pe)
|
err := handleBasicAuth(w, r, pe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logRequest(r, false, 401, "host", pe.Domain)
|
h.Parent.logRequest(r, false, 401, "host", r.URL.Hostname())
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,13 @@ func (ept *ProxyEndpoint) SplitInboundOutboundHeaders() ([][]string, [][]string)
|
|||||||
|
|
||||||
//Check if the endpoint require HSTS headers
|
//Check if the endpoint require HSTS headers
|
||||||
if ept.HSTSMaxAge > 0 {
|
if ept.HSTSMaxAge > 0 {
|
||||||
|
if ept.ContainsWildcardName(true) {
|
||||||
|
//Endpoint listening domain includes wildcards.
|
||||||
|
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(ept.HSTSMaxAge)) + "; includeSubdomains"}
|
||||||
|
} else {
|
||||||
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(ept.HSTSMaxAge))}
|
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(ept.HSTSMaxAge))}
|
||||||
|
}
|
||||||
|
|
||||||
downstreamHeaderCounter++
|
downstreamHeaderCounter++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ type ResponseRewriteRuleSet struct {
|
|||||||
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
||||||
UpstreamHeaders [][]string
|
UpstreamHeaders [][]string
|
||||||
DownstreamHeaders [][]string
|
DownstreamHeaders [][]string
|
||||||
|
NoRemoveHopByHop bool //Do not remove hop-by-hop headers, dangerous
|
||||||
Version string //Version number of Zoraxy, use for X-Proxy-By
|
Version string //Version number of Zoraxy, use for X-Proxy-By
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +181,7 @@ var hopHeaders = []string{
|
|||||||
"Te", // canonicalized version of "TE"
|
"Te", // canonicalized version of "TE"
|
||||||
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
|
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
|
||||||
"Transfer-Encoding",
|
"Transfer-Encoding",
|
||||||
//"Upgrade",
|
//"Upgrade", // handled by websocket proxy in higher layer abstraction
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
|
// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
|
||||||
|
@ -3,6 +3,7 @@ package dpcore
|
|||||||
import (
|
import (
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,6 +18,12 @@ func (p *ReverseProxy) getFlushInterval(req *http.Request, res *http.Response) t
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixed issue #235: Added auto detection for ollama / llm output stream
|
||||||
|
connectionHeader := req.Header["Connection"]
|
||||||
|
if len(connectionHeader) > 0 && strings.Contains(strings.Join(connectionHeader, ","), "keep-alive") {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
//Cannot sniff anything. Use default value
|
//Cannot sniff anything. Use default value
|
||||||
return p.FlushInterval
|
return p.FlushInterval
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ func NewDynamicProxy(option RouterOption) (*Router, error) {
|
|||||||
Running: false,
|
Running: false,
|
||||||
server: nil,
|
server: nil,
|
||||||
routingRules: []*RoutingRule{},
|
routingRules: []*RoutingRule{},
|
||||||
|
loadBalancer: option.LoadBalancer,
|
||||||
rateLimitCounter: RequestCountPerIpTable{},
|
rateLimitCounter: RequestCountPerIpTable{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,10 +151,17 @@ func (router *Router) StartProxyService() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
selectedUpstream, err := router.loadBalancer.GetRequestUpstreamTarget(w, r, sep.ActiveOrigins, sep.UseStickySession)
|
||||||
ProxyDomain: sep.Domain,
|
if err != nil {
|
||||||
|
http.ServeFile(w, r, "./web/hosterror.html")
|
||||||
|
router.Option.Logger.PrintAndLog("dprouter", "failed to get upstream for hostname", err)
|
||||||
|
router.logRequest(r, false, 404, "vdir-http", r.Host)
|
||||||
|
}
|
||||||
|
selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
|
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: sep.RequireTLS,
|
UseTLS: selectedUpstream.RequireTLS,
|
||||||
|
NoRemoveHopByHop: sep.DisableHopByHopHeaderRemoval,
|
||||||
PathPrefix: "",
|
PathPrefix: "",
|
||||||
Version: sep.parent.Option.HostVersion,
|
Version: sep.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
@ -187,7 +195,7 @@ func (router *Router) StartProxyService() error {
|
|||||||
IdleTimeout: 120 * time.Second,
|
IdleTimeout: 120 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Starting HTTP-to-HTTPS redirector (port 80)")
|
router.Option.Logger.PrintAndLog("dprouter", "Starting HTTP-to-HTTPS redirector (port 80)", nil)
|
||||||
|
|
||||||
//Create a redirection stop channel
|
//Create a redirection stop channel
|
||||||
stopChan := make(chan bool)
|
stopChan := make(chan bool)
|
||||||
@ -198,7 +206,7 @@ func (router *Router) StartProxyService() error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
httpServer.Shutdown(ctx)
|
httpServer.Shutdown(ctx)
|
||||||
log.Println("HTTP to HTTPS redirection listener stopped")
|
router.Option.Logger.PrintAndLog("dprouter", "HTTP to HTTPS redirection listener stopped", nil)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
//Start the http server that listens to port 80 and redirect to 443
|
//Start the http server that listens to port 80 and redirect to 443
|
||||||
@ -213,10 +221,10 @@ func (router *Router) StartProxyService() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Start the TLS server
|
//Start the TLS server
|
||||||
log.Println("Reverse proxy service started in the background (TLS mode)")
|
router.Option.Logger.PrintAndLog("dprouter", "Reverse proxy service started in the background (TLS mode)", nil)
|
||||||
go func() {
|
go func() {
|
||||||
if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
|
if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
|
||||||
log.Fatalf("Could not start proxy server: %v\n", err)
|
router.Option.Logger.PrintAndLog("dprouter", "Could not start proxy server", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
@ -224,10 +232,9 @@ func (router *Router) StartProxyService() error {
|
|||||||
router.tlsListener = nil
|
router.tlsListener = nil
|
||||||
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
|
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
|
||||||
router.Running = true
|
router.Running = true
|
||||||
log.Println("Reverse proxy service started in the background (Plain HTTP mode)")
|
router.Option.Logger.PrintAndLog("dprouter", "Reverse proxy service started in the background (Plain HTTP mode)", nil)
|
||||||
go func() {
|
go func() {
|
||||||
router.server.ListenAndServe()
|
router.server.ListenAndServe()
|
||||||
//log.Println("[DynamicProxy] " + err.Error())
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -133,6 +134,116 @@ func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint)
|
|||||||
return readyRoutingRule, nil
|
return readyRoutingRule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Upstream related wrapper functions */
|
||||||
|
//Check if there already exists another upstream with identical origin
|
||||||
|
func (ep *ProxyEndpoint) UpstreamOriginExists(originURL string) bool {
|
||||||
|
for _, origin := range ep.ActiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain == originURL {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, origin := range ep.InactiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain == originURL {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a upstream origin from given origin ip or domain
|
||||||
|
func (ep *ProxyEndpoint) GetUpstreamOriginByMatchingIP(originIpOrDomain string) (*loadbalance.Upstream, error) {
|
||||||
|
for _, origin := range ep.ActiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain == originIpOrDomain {
|
||||||
|
return origin, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, origin := range ep.InactiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain == originIpOrDomain {
|
||||||
|
return origin, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("target upstream origin not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add upstream to endpoint and update it to runtime
|
||||||
|
func (ep *ProxyEndpoint) AddUpstreamOrigin(newOrigin *loadbalance.Upstream, activate bool) error {
|
||||||
|
//Check if the upstream already exists
|
||||||
|
if ep.UpstreamOriginExists(newOrigin.OriginIpOrDomain) {
|
||||||
|
return errors.New("upstream with same origin already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
if activate {
|
||||||
|
//Add it to the active origin list
|
||||||
|
err := newOrigin.StartProxy()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ep.ActiveOrigins = append(ep.ActiveOrigins, newOrigin)
|
||||||
|
} else {
|
||||||
|
//Add to inactive origin list
|
||||||
|
ep.InactiveOrigins = append(ep.InactiveOrigins, newOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
ep.UpdateToRuntime()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove upstream from endpoint and update it to runtime
|
||||||
|
func (ep *ProxyEndpoint) RemoveUpstreamOrigin(originIpOrDomain string) error {
|
||||||
|
//Just to make sure there are no spaces
|
||||||
|
originIpOrDomain = strings.TrimSpace(originIpOrDomain)
|
||||||
|
|
||||||
|
//Check if the upstream already been removed
|
||||||
|
if !ep.UpstreamOriginExists(originIpOrDomain) {
|
||||||
|
//Not exists in the first place
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newActiveOriginList := []*loadbalance.Upstream{}
|
||||||
|
for _, origin := range ep.ActiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain != originIpOrDomain {
|
||||||
|
newActiveOriginList = append(newActiveOriginList, origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newInactiveOriginList := []*loadbalance.Upstream{}
|
||||||
|
for _, origin := range ep.InactiveOrigins {
|
||||||
|
if origin.OriginIpOrDomain != originIpOrDomain {
|
||||||
|
newInactiveOriginList = append(newInactiveOriginList, origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Ok, set the origin list to the new one
|
||||||
|
ep.ActiveOrigins = newActiveOriginList
|
||||||
|
ep.InactiveOrigins = newInactiveOriginList
|
||||||
|
ep.UpdateToRuntime()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the proxy endpoint hostname or alias name contains subdomain wildcard
|
||||||
|
func (ep *ProxyEndpoint) ContainsWildcardName(skipAliasCheck bool) bool {
|
||||||
|
hostname := ep.RootOrMatchingDomain
|
||||||
|
aliasHostnames := ep.MatchingDomainAlias
|
||||||
|
|
||||||
|
wildcardCheck := func(hostname string) bool {
|
||||||
|
return len(hostname) > 0 && hostname[0] == '*'
|
||||||
|
}
|
||||||
|
|
||||||
|
if wildcardCheck(hostname) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !skipAliasCheck {
|
||||||
|
for _, aliasHostname := range aliasHostnames {
|
||||||
|
if wildcardCheck(aliasHostname) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Create a deep clone object of the proxy endpoint
|
// Create a deep clone object of the proxy endpoint
|
||||||
// Note the returned object is not activated. Call to prepare function before pushing into runtime
|
// Note the returned object is not activated. Call to prepare function before pushing into runtime
|
||||||
func (ep *ProxyEndpoint) Clone() *ProxyEndpoint {
|
func (ep *ProxyEndpoint) Clone() *ProxyEndpoint {
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
package loadbalance
|
package loadbalance
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -12,49 +17,83 @@ import (
|
|||||||
Handleing load balance request for upstream destinations
|
Handleing load balance request for upstream destinations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type BalancePolicy int
|
|
||||||
|
|
||||||
const (
|
|
||||||
BalancePolicy_RoundRobin BalancePolicy = 0 //Round robin, will ignore upstream if down
|
|
||||||
BalancePolicy_Fallback BalancePolicy = 1 //Fallback only. Will only switch to next node if the first one failed
|
|
||||||
BalancePolicy_Random BalancePolicy = 2 //Random, randomly pick one from the list that is online
|
|
||||||
BalancePolicy_GeoRegion BalancePolicy = 3 //Use the one defined for this geo-location, when down, pick the next avaible node
|
|
||||||
)
|
|
||||||
|
|
||||||
type LoadBalanceRule struct {
|
|
||||||
Upstreams []string //Reverse proxy upstream servers
|
|
||||||
LoadBalancePolicy BalancePolicy //Policy in deciding which target IP to proxy
|
|
||||||
UseRegionLock bool //If this is enabled with BalancePolicy_Geo, when the main site failed, it will not pick another node
|
|
||||||
UseStickySession bool //Use sticky session, if you are serving EU countries, make sure to add the "Do you want cookie" warning
|
|
||||||
|
|
||||||
parent *RouteManager
|
|
||||||
}
|
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
SystemUUID string //Use for the session store
|
||||||
|
UseActiveHealthCheck bool //Use active health check, default to false
|
||||||
Geodb *geodb.Store //GeoIP resolver for checking incoming request origin country
|
Geodb *geodb.Store //GeoIP resolver for checking incoming request origin country
|
||||||
UptimeMonitor *uptime.Monitor //For checking if the target is online, this might be nil when the module starts
|
|
||||||
}
|
|
||||||
|
|
||||||
type RouteManager struct {
|
|
||||||
Options Options
|
|
||||||
Logger *logger.Logger
|
Logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new load balance route manager
|
type RouteManager struct {
|
||||||
func NewRouteManager(options *Options, logger *logger.Logger) *RouteManager {
|
SessionStore *sessions.CookieStore
|
||||||
newManager := RouteManager{
|
LoadBalanceMap sync.Map //Sync map to store the last load balance state of a given node
|
||||||
Options: *options,
|
OnlineStatusMap sync.Map //Sync map to store the online status of a given ip address or domain name
|
||||||
Logger: logger,
|
onlineStatusTickerStop chan bool //Stopping channel for the online status pinger
|
||||||
}
|
Options Options //Options for the load balancer
|
||||||
logger.PrintAndLog("INFO", "Load Balance Route Manager started", nil)
|
}
|
||||||
return &newManager
|
|
||||||
|
/* Upstream or Origin Server */
|
||||||
|
type Upstream struct {
|
||||||
|
//Upstream Proxy Configs
|
||||||
|
OriginIpOrDomain string //Target IP address or domain name with port
|
||||||
|
RequireTLS bool //Require TLS connection
|
||||||
|
SkipCertValidations bool //Set to true to accept self signed certs
|
||||||
|
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||||
|
|
||||||
|
//Load balancing configs
|
||||||
|
Weight int //Random weight for round robin, 0 for fallback only
|
||||||
|
MaxConn int //TODO: Maxmium connection to this server, 0 for unlimited
|
||||||
|
|
||||||
|
//currentConnectionCounts atomic.Uint64 //Counter for number of client currently connected
|
||||||
|
proxy *dpcore.ReverseProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new load balancer
|
||||||
|
func NewLoadBalancer(options *Options) *RouteManager {
|
||||||
|
if options.SystemUUID == "" {
|
||||||
|
//System UUID not passed in. Use random key
|
||||||
|
options.SystemUUID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate a session store for stickySession
|
||||||
|
store := sessions.NewCookieStore([]byte(options.SystemUUID))
|
||||||
|
return &RouteManager{
|
||||||
|
SessionStore: store,
|
||||||
|
LoadBalanceMap: sync.Map{},
|
||||||
|
OnlineStatusMap: sync.Map{},
|
||||||
|
onlineStatusTickerStop: nil,
|
||||||
|
Options: *options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamsReady checks if the group of upstreams contains at least one
|
||||||
|
// origin server that is ready
|
||||||
|
func (m *RouteManager) UpstreamsReady(upstreams []*Upstream) bool {
|
||||||
|
for _, upstream := range upstreams {
|
||||||
|
if upstream.IsReady() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// String format and convert a list of upstream into a string representations
|
||||||
|
func GetUpstreamsAsString(upstreams []*Upstream) string {
|
||||||
|
targets := []string{}
|
||||||
|
for _, upstream := range upstreams {
|
||||||
|
targets = append(targets, upstream.String())
|
||||||
|
}
|
||||||
|
return strings.Join(targets, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RouteManager) Close() {
|
||||||
|
if m.onlineStatusTickerStop != nil {
|
||||||
|
m.onlineStatusTickerStop <- true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LoadBalanceRule) GetProxyTargetIP() {
|
|
||||||
//TODO: Implement get proxy target IP logic here
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print debug message
|
// Print debug message
|
||||||
func (m *RouteManager) debugPrint(message string, err error) {
|
func (m *RouteManager) debugPrint(message string, err error) {
|
||||||
m.Logger.PrintAndLog("LB", message, err)
|
m.Options.Logger.PrintAndLog("LoadBalancer", message, err)
|
||||||
}
|
}
|
||||||
|
39
src/mod/dynamicproxy/loadbalance/onlineStatus.go
Normal file
39
src/mod/dynamicproxy/loadbalance/onlineStatus.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package loadbalance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return the last ping status to see if the target is online
|
||||||
|
func (m *RouteManager) IsTargetOnline(matchingDomainOrIp string) bool {
|
||||||
|
value, ok := m.LoadBalanceMap.Load(matchingDomainOrIp)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
isOnline, ok := value.(bool)
|
||||||
|
return ok && isOnline
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping a target to see if it is online
|
||||||
|
func PingTarget(targetMatchingDomainOrIp string, requireTLS bool) bool {
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
url := targetMatchingDomainOrIp
|
||||||
|
if requireTLS {
|
||||||
|
url = "https://" + url
|
||||||
|
} else {
|
||||||
|
url = "http://" + url
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
return resp.StatusCode >= 200 && resp.StatusCode <= 600
|
||||||
|
}
|
154
src/mod/dynamicproxy/loadbalance/originPicker.go
Normal file
154
src/mod/dynamicproxy/loadbalance/originPicker.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package loadbalance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Origin Picker
|
||||||
|
|
||||||
|
This script contains the code to pick the best origin
|
||||||
|
by this request.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// GetRequestUpstreamTarget return the upstream target where this
|
||||||
|
// request should be routed
|
||||||
|
func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.Request, origins []*Upstream, useStickySession bool) (*Upstream, error) {
|
||||||
|
if len(origins) == 0 {
|
||||||
|
return nil, errors.New("no upstream is defined for this host")
|
||||||
|
}
|
||||||
|
var targetOrigin = origins[0]
|
||||||
|
if useStickySession {
|
||||||
|
//Use stick session, check which origins this request previously used
|
||||||
|
targetOriginId, err := m.getSessionHandler(r, origins)
|
||||||
|
if err != nil {
|
||||||
|
//No valid session found. Assign a new upstream
|
||||||
|
targetOrigin, index, err := getRandomUpstreamByWeight(origins)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Oops. Unable to get random upstream")
|
||||||
|
targetOrigin = origins[0]
|
||||||
|
index = 0
|
||||||
|
}
|
||||||
|
m.setSessionHandler(w, r, targetOrigin.OriginIpOrDomain, index)
|
||||||
|
return targetOrigin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Valid session found. Resume the previous session
|
||||||
|
return origins[targetOriginId], nil
|
||||||
|
} else {
|
||||||
|
//Do not use stick session. Get a random one
|
||||||
|
var err error
|
||||||
|
targetOrigin, _, err = getRandomUpstreamByWeight(origins)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
targetOrigin = origins[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Println("DEBUG: Picking origin " + targetOrigin.OriginIpOrDomain)
|
||||||
|
return targetOrigin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Features related to session access */
|
||||||
|
//Set a new origin for this connection by session
|
||||||
|
func (m *RouteManager) setSessionHandler(w http.ResponseWriter, r *http.Request, originIpOrDomain string, index int) error {
|
||||||
|
session, err := m.SessionStore.Get(r, "STICKYSESSION")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
session.Values["zr_sid_origin"] = originIpOrDomain
|
||||||
|
session.Values["zr_sid_index"] = index
|
||||||
|
session.Options.MaxAge = 86400 //1 day
|
||||||
|
session.Options.Path = "/"
|
||||||
|
err = session.Save(r, w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the previous connected origin from session
|
||||||
|
func (m *RouteManager) getSessionHandler(r *http.Request, upstreams []*Upstream) (int, error) {
|
||||||
|
// Get existing session
|
||||||
|
session, err := m.SessionStore.Get(r, "STICKYSESSION")
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve session values for origin
|
||||||
|
originDomainRaw := session.Values["zr_sid_origin"]
|
||||||
|
originIDRaw := session.Values["zr_sid_index"]
|
||||||
|
|
||||||
|
if originDomainRaw == nil || originIDRaw == nil {
|
||||||
|
return -1, errors.New("no session has been set")
|
||||||
|
}
|
||||||
|
originDomain := originDomainRaw.(string)
|
||||||
|
originID := originIDRaw.(int)
|
||||||
|
|
||||||
|
//Check if it has been modified
|
||||||
|
if len(upstreams) < originID || upstreams[originID].OriginIpOrDomain != originDomain {
|
||||||
|
//Mismatch or upstreams has been updated
|
||||||
|
return -1, errors.New("upstreams has been changed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return originID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Functions related to random upstream picking */
|
||||||
|
// Get a random upstream by the weights defined in Upstream struct, return the upstream, index value and any error
|
||||||
|
func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) {
|
||||||
|
var ret *Upstream
|
||||||
|
sum := 0
|
||||||
|
for _, c := range upstreams {
|
||||||
|
sum += c.Weight
|
||||||
|
}
|
||||||
|
r, err := intRange(0, sum)
|
||||||
|
if err != nil {
|
||||||
|
return ret, -1, err
|
||||||
|
}
|
||||||
|
counter := 0
|
||||||
|
for _, c := range upstreams {
|
||||||
|
r -= c.Weight
|
||||||
|
if r < 0 {
|
||||||
|
return c, counter, nil
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret == nil {
|
||||||
|
//All fallback
|
||||||
|
//use the first one that is with weight = 0
|
||||||
|
fallbackUpstreams := []*Upstream{}
|
||||||
|
fallbackUpstreamsOriginalID := []int{}
|
||||||
|
for ix, upstream := range upstreams {
|
||||||
|
if upstream.Weight == 0 {
|
||||||
|
fallbackUpstreams = append(fallbackUpstreams, upstream)
|
||||||
|
fallbackUpstreamsOriginalID = append(fallbackUpstreamsOriginalID, ix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
upstreamID := rand.Intn(len(fallbackUpstreams))
|
||||||
|
return fallbackUpstreams[upstreamID], fallbackUpstreamsOriginalID[upstreamID], nil
|
||||||
|
}
|
||||||
|
return ret, -1, errors.New("failed to pick an upstream origin server")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntRange returns a random integer in the range from min to max.
|
||||||
|
func intRange(min, max int) (int, error) {
|
||||||
|
var result int
|
||||||
|
switch {
|
||||||
|
case min > max:
|
||||||
|
// Fail with error
|
||||||
|
return result, errors.New("min is greater than max")
|
||||||
|
case max == min:
|
||||||
|
result = max
|
||||||
|
case max > min:
|
||||||
|
b := rand.Intn(max-min) + min
|
||||||
|
result = min + int(b)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
77
src/mod/dynamicproxy/loadbalance/upstream.go
Normal file
77
src/mod/dynamicproxy/loadbalance/upstream.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package loadbalance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartProxy create and start a HTTP proxy using dpcore
|
||||||
|
// Example of webProxyEndpoint: https://example.com:443 or http://192.168.1.100:8080
|
||||||
|
func (u *Upstream) StartProxy() error {
|
||||||
|
//Filter the tailing slash if any
|
||||||
|
domain := u.OriginIpOrDomain
|
||||||
|
if len(domain) == 0 {
|
||||||
|
return errors.New("invalid endpoint config")
|
||||||
|
}
|
||||||
|
if domain[len(domain)-1:] == "/" {
|
||||||
|
domain = domain[:len(domain)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
||||||
|
//TLS is not hardcoded in proxy target domain
|
||||||
|
if u.RequireTLS {
|
||||||
|
domain = "https://" + domain
|
||||||
|
} else {
|
||||||
|
domain = "http://" + domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create a new proxy agent for this upstream
|
||||||
|
path, err := url.Parse(domain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
|
||||||
|
IgnoreTLSVerification: u.SkipCertValidations,
|
||||||
|
FlushInterval: 100 * time.Millisecond,
|
||||||
|
})
|
||||||
|
|
||||||
|
u.proxy = proxy
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsReady return the proxy ready state of the upstream server
|
||||||
|
// Return false if StartProxy() is not called on this upstream before
|
||||||
|
func (u *Upstream) IsReady() bool {
|
||||||
|
return u.proxy != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone return a new deep copy object of the identical upstream
|
||||||
|
func (u *Upstream) Clone() *Upstream {
|
||||||
|
newUpstream := Upstream{}
|
||||||
|
js, _ := json.Marshal(u)
|
||||||
|
json.Unmarshal(js, &newUpstream)
|
||||||
|
return &newUpstream
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP uses this upstream proxy router to route the current request
|
||||||
|
func (u *Upstream) ServeHTTP(w http.ResponseWriter, r *http.Request, rrr *dpcore.ResponseRewriteRuleSet) error {
|
||||||
|
//Auto rewrite to upstream origin if not set
|
||||||
|
if rrr.ProxyDomain == "" {
|
||||||
|
rrr.ProxyDomain = u.OriginIpOrDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.proxy.ServeHTTP(w, r, rrr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String return the string representations of endpoints in this upstream
|
||||||
|
func (u *Upstream) String() string {
|
||||||
|
return u.OriginIpOrDomain
|
||||||
|
}
|
@ -16,6 +16,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/websocketproxy"
|
"imuslab.com/zoraxy/mod/websocketproxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Check if the request URI matches any of the proxy endpoint
|
||||||
func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
|
func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
|
||||||
var targetProxyEndpoint *ProxyEndpoint = nil
|
var targetProxyEndpoint *ProxyEndpoint = nil
|
||||||
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||||
@ -30,6 +31,7 @@ func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *P
|
|||||||
return targetProxyEndpoint
|
return targetProxyEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the proxy endpoint from hostname, which might includes checking of wildcard certificates
|
||||||
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
|
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
|
||||||
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
||||||
ep, ok := router.ProxyEndpoints.Load(hostname)
|
ep, ok := router.ProxyEndpoints.Load(hostname)
|
||||||
@ -110,12 +112,18 @@ func (router *Router) rewriteURL(rooturl string, requestURL string) string {
|
|||||||
func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
|
func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
|
||||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||||
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
|
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
|
||||||
|
selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession)
|
||||||
|
if err != nil {
|
||||||
|
http.ServeFile(w, r, "./web/rperror.html")
|
||||||
|
log.Println(err.Error())
|
||||||
|
h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname())
|
||||||
|
return
|
||||||
|
}
|
||||||
requestURL := r.URL.String()
|
requestURL := r.URL.String()
|
||||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||||
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||||
wsRedirectionEndpoint := target.Domain
|
wsRedirectionEndpoint := selectedUpstream.OriginIpOrDomain
|
||||||
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
||||||
//Append / to the end of the redirection endpoint if not exists
|
//Append / to the end of the redirection endpoint if not exists
|
||||||
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
|
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
|
||||||
@ -125,13 +133,13 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
requestURL = requestURL[1:]
|
requestURL = requestURL[1:]
|
||||||
}
|
}
|
||||||
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL)
|
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL)
|
||||||
if target.RequireTLS {
|
if selectedUpstream.RequireTLS {
|
||||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
||||||
}
|
}
|
||||||
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
|
h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain)
|
||||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
SkipTLSValidation: target.SkipCertValidations,
|
SkipTLSValidation: selectedUpstream.SkipCertValidations,
|
||||||
SkipOriginCheck: target.SkipWebSocketOriginCheck,
|
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
|
||||||
})
|
})
|
||||||
wspHandler.ServeHTTP(w, r)
|
wspHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
@ -148,14 +156,15 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
//Build downstream and upstream header rules
|
//Build downstream and upstream header rules
|
||||||
upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()
|
upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()
|
||||||
|
|
||||||
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
err = selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: target.Domain,
|
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: target.RequireTLS,
|
UseTLS: selectedUpstream.RequireTLS,
|
||||||
NoCache: h.Parent.Option.NoCache,
|
NoCache: h.Parent.Option.NoCache,
|
||||||
PathPrefix: "",
|
PathPrefix: "",
|
||||||
UpstreamHeaders: upstreamHeaders,
|
UpstreamHeaders: upstreamHeaders,
|
||||||
DownstreamHeaders: downstreamHeaders,
|
DownstreamHeaders: downstreamHeaders,
|
||||||
|
NoRemoveHopByHop: target.DisableHopByHopHeaderRemoval,
|
||||||
Version: target.parent.Option.HostVersion,
|
Version: target.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -164,15 +173,15 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
if errors.As(err, &dnsError) {
|
if errors.As(err, &dnsError) {
|
||||||
http.ServeFile(w, r, "./web/hosterror.html")
|
http.ServeFile(w, r, "./web/hosterror.html")
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
h.logRequest(r, false, 404, "subdomain-http", target.Domain)
|
h.Parent.logRequest(r, false, 404, "host-http", r.URL.Hostname())
|
||||||
} else {
|
} else {
|
||||||
http.ServeFile(w, r, "./web/rperror.html")
|
http.ServeFile(w, r, "./web/rperror.html")
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
h.logRequest(r, false, 521, "subdomain-http", target.Domain)
|
h.Parent.logRequest(r, false, 521, "host-http", r.URL.Hostname())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h.logRequest(r, true, 200, "subdomain-http", target.Domain)
|
h.Parent.logRequest(r, true, 200, "host-http", r.URL.Hostname())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle vdir type request
|
// Handle vdir type request
|
||||||
@ -194,10 +203,10 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
if target.RequireTLS {
|
if target.RequireTLS {
|
||||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
||||||
}
|
}
|
||||||
h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
h.Parent.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
SkipTLSValidation: target.SkipCertValidations,
|
SkipTLSValidation: target.SkipCertValidations,
|
||||||
SkipOriginCheck: target.parent.SkipWebSocketOriginCheck,
|
SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
|
||||||
})
|
})
|
||||||
wspHandler.ServeHTTP(w, r)
|
wspHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
@ -229,23 +238,24 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
if errors.As(err, &dnsError) {
|
if errors.As(err, &dnsError) {
|
||||||
http.ServeFile(w, r, "./web/hosterror.html")
|
http.ServeFile(w, r, "./web/hosterror.html")
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
h.logRequest(r, false, 404, "vdir-http", target.Domain)
|
h.Parent.logRequest(r, false, 404, "vdir-http", target.Domain)
|
||||||
} else {
|
} else {
|
||||||
http.ServeFile(w, r, "./web/rperror.html")
|
http.ServeFile(w, r, "./web/rperror.html")
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
h.logRequest(r, false, 521, "vdir-http", target.Domain)
|
h.Parent.logRequest(r, false, 521, "vdir-http", target.Domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.logRequest(r, true, 200, "vdir-http", target.Domain)
|
h.Parent.logRequest(r, true, 200, "vdir-http", target.Domain)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) {
|
// This logger collect data for the statistical analysis. For log to file logger, check the Logger and LogHTTPRequest handler
|
||||||
if h.Parent.Option.StatisticCollector != nil {
|
func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) {
|
||||||
|
if router.Option.StatisticCollector != nil {
|
||||||
go func() {
|
go func() {
|
||||||
requestInfo := statistic.RequestInfo{
|
requestInfo := statistic.RequestInfo{
|
||||||
IpAddr: netutils.GetRequesterIP(r),
|
IpAddr: netutils.GetRequesterIP(r),
|
||||||
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
RequestOriginalCountryISOCode: router.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||||
Succ: succ,
|
Succ: succ,
|
||||||
StatusCode: statusCode,
|
StatusCode: statusCode,
|
||||||
ForwardType: forwardType,
|
ForwardType: forwardType,
|
||||||
@ -254,7 +264,8 @@ func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, fo
|
|||||||
RequestURL: r.Host + r.RequestURI,
|
RequestURL: r.Host + r.RequestURI,
|
||||||
Target: target,
|
Target: target,
|
||||||
}
|
}
|
||||||
h.Parent.Option.StatisticCollector.RecordRequest(requestInfo)
|
router.Option.StatisticCollector.RecordRequest(requestInfo)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
router.Option.Logger.LogHTTPRequest(r, forwardType, statusCode)
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ func (t *RequestCountPerIpTable) Clear() {
|
|||||||
func (h *ProxyHandler) handleRateLimitRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
func (h *ProxyHandler) handleRateLimitRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||||
err := h.Parent.handleRateLimit(w, r, pe)
|
err := h.Parent.handleRateLimit(w, r, pe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logRequest(r, false, 429, "ratelimit", pe.Domain)
|
h.Parent.logRequest(r, false, 429, "ratelimit", r.URL.Hostname())
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -195,6 +195,6 @@ func (t *RuleTable) log(message string, err error) {
|
|||||||
log.Println("[Redirect] " + message + ": " + err.Error())
|
log.Println("[Redirect] " + message + ": " + err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.Logger.PrintAndLog("Redirect", message, err)
|
t.Logger.PrintAndLog("redirect", message, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,10 @@ package dynamicproxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
)
|
)
|
||||||
@ -17,41 +19,18 @@ import (
|
|||||||
|
|
||||||
// Prepare proxy route generate a proxy handler service object for your endpoint
|
// Prepare proxy route generate a proxy handler service object for your endpoint
|
||||||
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
||||||
//Filter the tailing slash if any
|
for _, thisOrigin := range endpoint.ActiveOrigins {
|
||||||
domain := endpoint.Domain
|
|
||||||
if len(domain) == 0 {
|
|
||||||
return nil, errors.New("invalid endpoint config")
|
|
||||||
}
|
|
||||||
if domain[len(domain)-1:] == "/" {
|
|
||||||
domain = domain[:len(domain)-1]
|
|
||||||
}
|
|
||||||
endpoint.Domain = domain
|
|
||||||
|
|
||||||
//Parse the web proxy endpoint
|
|
||||||
webProxyEndpoint := domain
|
|
||||||
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
|
||||||
//TLS is not hardcoded in proxy target domain
|
|
||||||
if endpoint.RequireTLS {
|
|
||||||
webProxyEndpoint = "https://" + webProxyEndpoint
|
|
||||||
} else {
|
|
||||||
webProxyEndpoint = "http://" + webProxyEndpoint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create a new proxy agent for this root
|
|
||||||
path, err := url.Parse(webProxyEndpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create the proxy routing handler
|
//Create the proxy routing handler
|
||||||
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
|
err := thisOrigin.StartProxy()
|
||||||
IgnoreTLSVerification: endpoint.SkipCertValidations,
|
if err != nil {
|
||||||
})
|
log.Println("Unable to setup upstream " + thisOrigin.OriginIpOrDomain + ": " + err.Error())
|
||||||
endpoint.proxy = proxy
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
endpoint.parent = router
|
endpoint.parent = router
|
||||||
|
|
||||||
//Prepare proxy routing hjandler for each of the virtual directories
|
//Prepare proxy routing handler for each of the virtual directories
|
||||||
for _, vdir := range endpoint.VirtualDirectories {
|
for _, vdir := range endpoint.VirtualDirectories {
|
||||||
domain := vdir.Domain
|
domain := vdir.Domain
|
||||||
if len(domain) == 0 {
|
if len(domain) == 0 {
|
||||||
@ -63,7 +42,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Parse the web proxy endpoint
|
//Parse the web proxy endpoint
|
||||||
webProxyEndpoint = domain
|
webProxyEndpoint := domain
|
||||||
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
||||||
//TLS is not hardcoded in proxy target domain
|
//TLS is not hardcoded in proxy target domain
|
||||||
if vdir.RequireTLS {
|
if vdir.RequireTLS {
|
||||||
@ -80,6 +59,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
|||||||
|
|
||||||
proxy := dpcore.NewDynamicProxyCore(path, vdir.MatchingPath, &dpcore.DpcoreOptions{
|
proxy := dpcore.NewDynamicProxyCore(path, vdir.MatchingPath, &dpcore.DpcoreOptions{
|
||||||
IgnoreTLSVerification: vdir.SkipCertValidations,
|
IgnoreTLSVerification: vdir.SkipCertValidations,
|
||||||
|
FlushInterval: 500 * time.Millisecond,
|
||||||
})
|
})
|
||||||
vdir.proxy = proxy
|
vdir.proxy = proxy
|
||||||
vdir.parent = endpoint
|
vdir.parent = endpoint
|
||||||
@ -90,7 +70,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
|||||||
|
|
||||||
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
|
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
|
||||||
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
||||||
if endpoint.proxy == nil {
|
if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
|
||||||
//This endpoint is not prepared
|
//This endpoint is not prepared
|
||||||
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
||||||
}
|
}
|
||||||
@ -101,7 +81,7 @@ func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
|||||||
|
|
||||||
// Set given Proxy Route as Root. Call to PrepareProxyRoute before adding to runtime
|
// Set given Proxy Route as Root. Call to PrepareProxyRoute before adding to runtime
|
||||||
func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error {
|
func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error {
|
||||||
if endpoint.proxy == nil {
|
if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
|
||||||
//This endpoint is not prepared
|
//This endpoint is not prepared
|
||||||
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,11 @@ import (
|
|||||||
|
|
||||||
"imuslab.com/zoraxy/mod/access"
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/statistic"
|
"imuslab.com/zoraxy/mod/statistic"
|
||||||
"imuslab.com/zoraxy/mod/tlscert"
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
)
|
)
|
||||||
@ -25,6 +27,7 @@ type ProxyHandler struct {
|
|||||||
Parent *Router
|
Parent *Router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Router Object Options */
|
||||||
type RouterOption struct {
|
type RouterOption struct {
|
||||||
HostUUID string //The UUID of Zoraxy, use for heading mod
|
HostUUID string //The UUID of Zoraxy, use for heading mod
|
||||||
HostVersion string //The version of Zoraxy, use for heading mod
|
HostVersion string //The version of Zoraxy, use for heading mod
|
||||||
@ -34,14 +37,17 @@ type RouterOption struct {
|
|||||||
NoCache bool //Force set Cache-Control: no-store
|
NoCache bool //Force set Cache-Control: no-store
|
||||||
ListenOnPort80 bool //Enable port 80 http listener
|
ListenOnPort80 bool //Enable port 80 http listener
|
||||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||||
TlsManager *tlscert.Manager
|
TlsManager *tlscert.Manager //TLS manager for serving SAN certificates
|
||||||
RedirectRuleTable *redirection.RuleTable
|
RedirectRuleTable *redirection.RuleTable //Redirection rules handler and table
|
||||||
GeodbStore *geodb.Store //GeoIP resolver
|
GeodbStore *geodb.Store //GeoIP resolver
|
||||||
AccessController *access.Controller //Blacklist / whitelist controller
|
AccessController *access.Controller //Blacklist / whitelist controller
|
||||||
StatisticCollector *statistic.Collector
|
StatisticCollector *statistic.Collector //Statistic collector for storing stats on incoming visitors
|
||||||
WebDirectory string //The static web server directory containing the templates folder
|
WebDirectory string //The static web server directory containing the templates folder
|
||||||
|
LoadBalancer *loadbalance.RouteManager //Load balancer that handle load balancing of proxy target
|
||||||
|
Logger *logger.Logger //Logger for reverse proxy requets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Router Object */
|
||||||
type Router struct {
|
type Router struct {
|
||||||
Option *RouterOption
|
Option *RouterOption
|
||||||
ProxyEndpoints *sync.Map
|
ProxyEndpoints *sync.Map
|
||||||
@ -50,6 +56,7 @@ type Router struct {
|
|||||||
mux http.Handler
|
mux http.Handler
|
||||||
server *http.Server
|
server *http.Server
|
||||||
tlsListener net.Listener
|
tlsListener net.Listener
|
||||||
|
loadBalancer *loadbalance.RouteManager //Load balancer routing manager
|
||||||
routingRules []*RoutingRule
|
routingRules []*RoutingRule
|
||||||
|
|
||||||
tlsRedirectStop chan bool //Stop channel for tls redirection server
|
tlsRedirectStop chan bool //Stop channel for tls redirection server
|
||||||
@ -57,6 +64,7 @@ type Router struct {
|
|||||||
rateLimitCounter RequestCountPerIpTable //Request counter for rate limter
|
rateLimitCounter RequestCountPerIpTable //Request counter for rate limter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Basic Auth Related Data structure*/
|
||||||
// Auth credential for basic auth on certain endpoints
|
// Auth credential for basic auth on certain endpoints
|
||||||
type BasicAuthCredentials struct {
|
type BasicAuthCredentials struct {
|
||||||
Username string
|
Username string
|
||||||
@ -74,6 +82,7 @@ type BasicAuthExceptionRule struct {
|
|||||||
PathPrefix string
|
PathPrefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Header Related Data structure */
|
||||||
// Header injection direction type
|
// Header injection direction type
|
||||||
type HeaderDirection int
|
type HeaderDirection int
|
||||||
|
|
||||||
@ -90,6 +99,8 @@ type UserDefinedHeader struct {
|
|||||||
IsRemove bool //Instead of set, remove this key instead
|
IsRemove bool //Instead of set, remove this key instead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Routing Rule Data Structures */
|
||||||
|
|
||||||
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||||
// program structure than directly using ProxyEndpoint
|
// program structure than directly using ProxyEndpoint
|
||||||
type VirtualDirectoryEndpoint struct {
|
type VirtualDirectoryEndpoint struct {
|
||||||
@ -107,13 +118,14 @@ type ProxyEndpoint struct {
|
|||||||
ProxyType int //The type of this proxy, see const def
|
ProxyType int //The type of this proxy, see const def
|
||||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||||
Domain string //Domain or IP to proxy to
|
ActiveOrigins []*loadbalance.Upstream //Activated Upstream or origin servers IP or domain to proxy to
|
||||||
|
InactiveOrigins []*loadbalance.Upstream //Disabled Upstream or origin servers IP or domain to proxy to
|
||||||
|
UseStickySession bool //Use stick session for load balancing
|
||||||
|
UseActiveLoadBalance bool //Use active loadbalancing, default passive
|
||||||
|
Disabled bool //If the rule is disabled
|
||||||
|
|
||||||
//TLS/SSL Related
|
//Inbound TLS/SSL Related
|
||||||
RequireTLS bool //Target domain require TLS
|
|
||||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||||
SkipCertValidations bool //Set to true to accept self signed certs
|
|
||||||
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
|
||||||
|
|
||||||
//Virtual Directories
|
//Virtual Directories
|
||||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||||
@ -123,6 +135,7 @@ type ProxyEndpoint struct {
|
|||||||
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||||
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||||
PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header
|
PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header
|
||||||
|
DisableHopByHopHeaderRemoval bool //TODO: Do not remove hop-by-hop headers
|
||||||
|
|
||||||
//Authentication
|
//Authentication
|
||||||
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||||
@ -136,15 +149,12 @@ type ProxyEndpoint struct {
|
|||||||
//Access Control
|
//Access Control
|
||||||
AccessFilterUUID string //Access filter ID
|
AccessFilterUUID string //Access filter ID
|
||||||
|
|
||||||
Disabled bool //If the rule is disabled
|
|
||||||
|
|
||||||
//Fallback routing logic (Special Rule Sets Only)
|
//Fallback routing logic (Special Rule Sets Only)
|
||||||
DefaultSiteOption int //Fallback routing logic options
|
DefaultSiteOption int //Fallback routing logic options
|
||||||
DefaultSiteValue string //Fallback routing target, optional
|
DefaultSiteValue string //Fallback routing target, optional
|
||||||
|
|
||||||
//Internal Logic Elements
|
//Internal Logic Elements
|
||||||
parent *Router `json:"-"`
|
parent *Router `json:"-"`
|
||||||
proxy *dpcore.ReverseProxy `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -13,29 +13,31 @@ import (
|
|||||||
Zoraxy Logger
|
Zoraxy Logger
|
||||||
|
|
||||||
This script is designed to make a managed log for the Zoraxy
|
This script is designed to make a managed log for the Zoraxy
|
||||||
and replace the ton of log.Println in the system core
|
and replace the ton of log.Println in the system core.
|
||||||
|
The core logger is based in golang's build-in log package
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
LogToFile bool //Set enable write to file
|
|
||||||
Prefix string //Prefix for log files
|
Prefix string //Prefix for log files
|
||||||
LogFolder string //Folder to store the log file
|
LogFolder string //Folder to store the log file
|
||||||
CurrentLogFile string //Current writing filename
|
CurrentLogFile string //Current writing filename
|
||||||
|
logger *log.Logger
|
||||||
file *os.File
|
file *os.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger, error) {
|
// Create a new logger that log to files
|
||||||
|
func NewLogger(logFilePrefix string, logFolder string) (*Logger, error) {
|
||||||
err := os.MkdirAll(logFolder, 0775)
|
err := os.MkdirAll(logFolder, 0775)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
thisLogger := Logger{
|
thisLogger := Logger{
|
||||||
LogToFile: logToFile,
|
|
||||||
Prefix: logFilePrefix,
|
Prefix: logFilePrefix,
|
||||||
LogFolder: logFolder,
|
LogFolder: logFolder,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Create the log file if not exists
|
||||||
logFilePath := thisLogger.getLogFilepath()
|
logFilePath := thisLogger.getLogFilepath()
|
||||||
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
|
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -43,9 +45,26 @@ func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger,
|
|||||||
}
|
}
|
||||||
thisLogger.CurrentLogFile = logFilePath
|
thisLogger.CurrentLogFile = logFilePath
|
||||||
thisLogger.file = f
|
thisLogger.file = f
|
||||||
|
|
||||||
|
//Start the logger
|
||||||
|
logger := log.New(f, "", log.Flags()&^(log.Ldate|log.Ltime))
|
||||||
|
logger.SetFlags(0)
|
||||||
|
logger.SetOutput(f)
|
||||||
|
thisLogger.logger = logger
|
||||||
return &thisLogger, nil
|
return &thisLogger, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a fmt logger that only log to STDOUT
|
||||||
|
func NewFmtLogger() (*Logger, error) {
|
||||||
|
return &Logger{
|
||||||
|
Prefix: "",
|
||||||
|
LogFolder: "",
|
||||||
|
CurrentLogFile: "",
|
||||||
|
logger: nil,
|
||||||
|
file: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Logger) getLogFilepath() string {
|
func (l *Logger) getLogFilepath() string {
|
||||||
year, month, _ := time.Now().Date()
|
year, month, _ := time.Now().Date()
|
||||||
return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log")
|
return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log")
|
||||||
@ -54,9 +73,8 @@ func (l *Logger) getLogFilepath() string {
|
|||||||
// PrintAndLog will log the message to file and print the log to STDOUT
|
// PrintAndLog will log the message to file and print the log to STDOUT
|
||||||
func (l *Logger) PrintAndLog(title string, message string, originalError error) {
|
func (l *Logger) PrintAndLog(title string, message string, originalError error) {
|
||||||
go func() {
|
go func() {
|
||||||
l.Log(title, message, originalError)
|
l.Log(title, message, originalError, true)
|
||||||
}()
|
}()
|
||||||
log.Println("[" + title + "] " + message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Println is a fast snap-in replacement for log.Println
|
// Println is a fast snap-in replacement for log.Println
|
||||||
@ -64,18 +82,26 @@ func (l *Logger) Println(v ...interface{}) {
|
|||||||
//Convert the array of interfaces into string
|
//Convert the array of interfaces into string
|
||||||
message := fmt.Sprint(v...)
|
message := fmt.Sprint(v...)
|
||||||
go func() {
|
go func() {
|
||||||
l.Log("info", string(message), nil)
|
l.Log("internal", string(message), nil, true)
|
||||||
}()
|
}()
|
||||||
log.Println("[INFO] " + string(message))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Log(title string, errorMessage string, originalError error) {
|
func (l *Logger) Log(title string, errorMessage string, originalError error, copyToSTDOUT bool) {
|
||||||
l.ValidateAndUpdateLogFilepath()
|
l.ValidateAndUpdateLogFilepath()
|
||||||
if l.LogToFile {
|
if l.logger == nil || copyToSTDOUT {
|
||||||
|
//Use STDOUT instead of logger
|
||||||
if originalError == nil {
|
if originalError == nil {
|
||||||
l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [INFO]" + errorMessage + "\n")
|
fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
|
||||||
} else {
|
} else {
|
||||||
l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [ERROR]" + errorMessage + " " + originalError.Error() + "\n")
|
fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:error] " + errorMessage + ": " + originalError.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.logger != nil {
|
||||||
|
if originalError == nil {
|
||||||
|
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
|
||||||
|
} else {
|
||||||
|
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:error] " + errorMessage + ": " + originalError.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,18 +109,28 @@ func (l *Logger) Log(title string, errorMessage string, originalError error) {
|
|||||||
|
|
||||||
// Validate if the logging target is still valid (detect any months change)
|
// Validate if the logging target is still valid (detect any months change)
|
||||||
func (l *Logger) ValidateAndUpdateLogFilepath() {
|
func (l *Logger) ValidateAndUpdateLogFilepath() {
|
||||||
|
if l.file == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
expectedCurrentLogFilepath := l.getLogFilepath()
|
expectedCurrentLogFilepath := l.getLogFilepath()
|
||||||
if l.CurrentLogFile != expectedCurrentLogFilepath {
|
if l.CurrentLogFile != expectedCurrentLogFilepath {
|
||||||
//Change of month. Update to a new log file
|
//Change of month. Update to a new log file
|
||||||
l.file.Close()
|
l.file.Close()
|
||||||
|
l.file = nil
|
||||||
|
|
||||||
|
//Create a new log file
|
||||||
f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
|
f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("[Logger] Unable to create new log. Logging to file disabled.")
|
log.Println("Unable to create new log. Logging is disabled: ", err.Error())
|
||||||
l.LogToFile = false
|
l.logger = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.CurrentLogFile = expectedCurrentLogFilepath
|
l.CurrentLogFile = expectedCurrentLogFilepath
|
||||||
l.file = f
|
l.file = f
|
||||||
|
|
||||||
|
//Start a new logger
|
||||||
|
logger := log.New(f, "", log.Default().Flags())
|
||||||
|
l.logger = logger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
32
src/mod/info/logger/trafficlog.go
Normal file
32
src/mod/info/logger/trafficlog.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
/*
|
||||||
|
Traffic Log
|
||||||
|
|
||||||
|
This script log the traffic of HTTP requests
|
||||||
|
|
||||||
|
*/
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log HTTP request. Note that this must run in go routine to prevent any blocking
|
||||||
|
// in reverse proxy router
|
||||||
|
func (l *Logger) LogHTTPRequest(r *http.Request, reqclass string, statusCode int) {
|
||||||
|
go func() {
|
||||||
|
l.ValidateAndUpdateLogFilepath()
|
||||||
|
if l.logger == nil || l.file == nil {
|
||||||
|
//logger is not initiated. Do not log http request
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientIP := netutils.GetRequesterIP(r)
|
||||||
|
requestURI := r.RequestURI
|
||||||
|
statusCodeString := strconv.Itoa(statusCode)
|
||||||
|
//fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [router:" + reqclass + "] [client " + clientIP + "] " + r.Method + " " + requestURI + " " + statusCodeString)
|
||||||
|
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [router:" + reqclass + "] [origin:" + r.URL.Hostname() + "] [client " + clientIP + "] " + r.Method + " " + requestURI + " " + statusCodeString)
|
||||||
|
}()
|
||||||
|
}
|
@ -51,13 +51,7 @@ func (v *Viewer) HandleReadLog(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
catergory, err := utils.GetPara(r, "catergory")
|
content, err := v.LoadLogFile(strings.TrimSpace(filepath.Base(filename)))
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, "invalid catergory given")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := v.LoadLogFile(strings.TrimSpace(filepath.Base(catergory)), strings.TrimSpace(filepath.Base(filename)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, err.Error())
|
utils.SendErrorResponse(w, err.Error())
|
||||||
return
|
return
|
||||||
@ -106,8 +100,10 @@ func (v *Viewer) ListLogFiles(showFullpath bool) map[string][]*LogFile {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Viewer) LoadLogFile(catergory string, filename string) (string, error) {
|
func (v *Viewer) LoadLogFile(filename string) (string, error) {
|
||||||
logFilepath := filepath.Join(v.option.RootFolder, catergory, filename)
|
filename = filepath.ToSlash(filename)
|
||||||
|
filename = strings.ReplaceAll(filename, "../", "")
|
||||||
|
logFilepath := filepath.Join(v.option.RootFolder, filename)
|
||||||
if utils.FileExists(logFilepath) {
|
if utils.FileExists(logFilepath) {
|
||||||
//Load it
|
//Load it
|
||||||
content, err := os.ReadFile(logFilepath)
|
content, err := os.ReadFile(logFilepath)
|
||||||
|
@ -3,8 +3,6 @@ package netstat
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -14,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,11 +34,11 @@ type NetStatBuffers struct {
|
|||||||
Stats []*FlowStat //Statistic of the flow
|
Stats []*FlowStat //Statistic of the flow
|
||||||
StopChan chan bool //Channel to stop the ticker
|
StopChan chan bool //Channel to stop the ticker
|
||||||
EventTicker *time.Ticker //Ticker for event logging
|
EventTicker *time.Ticker //Ticker for event logging
|
||||||
|
logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a new network statistic buffers
|
// Get a new network statistic buffers
|
||||||
func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
|
func NewNetStatBuffer(recordCount int, systemWideLogger *logger.Logger) (*NetStatBuffers, error) {
|
||||||
|
|
||||||
//Flood fill the stats with 0
|
//Flood fill the stats with 0
|
||||||
initialStats := []*FlowStat{}
|
initialStats := []*FlowStat{}
|
||||||
for i := 0; i < recordCount; i++ {
|
for i := 0; i < recordCount; i++ {
|
||||||
@ -66,21 +65,22 @@ func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
|
|||||||
Stats: initialStats,
|
Stats: initialStats,
|
||||||
StopChan: stopCh,
|
StopChan: stopCh,
|
||||||
EventTicker: ticker,
|
EventTicker: ticker,
|
||||||
|
logger: systemWideLogger,
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get the initial measurements of netstats
|
//Get the initial measurements of netstats
|
||||||
rx, tx, err := GetNetworkInterfaceStats()
|
rx, tx, err := thisNetBuffer.GetNetworkInterfaceStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Unable to get NIC stats: ", err.Error())
|
systemWideLogger.PrintAndLog("netstat", "Unable to get NIC stats: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
retryCount := 0
|
retryCount := 0
|
||||||
for rx == 0 && tx == 0 && retryCount < 10 {
|
for rx == 0 && tx == 0 && retryCount < 10 {
|
||||||
//Strange. Retry
|
//Strange. Retry
|
||||||
log.Println("NIC stats return all 0. Retrying...")
|
systemWideLogger.PrintAndLog("netstat", "NIC stats return all 0. Retrying...", nil)
|
||||||
rx, tx, err = GetNetworkInterfaceStats()
|
rx, tx, err = thisNetBuffer.GetNetworkInterfaceStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Unable to get NIC stats: ", err.Error())
|
systemWideLogger.PrintAndLog("netstat", "Unable to get NIC stats: ", err)
|
||||||
}
|
}
|
||||||
retryCount++
|
retryCount++
|
||||||
}
|
}
|
||||||
@ -95,20 +95,20 @@ func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-n.StopChan:
|
case <-n.StopChan:
|
||||||
fmt.Println("- Netstats listener stopped")
|
systemWideLogger.PrintAndLog("netstat", "Netstats listener stopped", nil)
|
||||||
return
|
return
|
||||||
|
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if n.PreviousStat.RX == 0 && n.PreviousStat.TX == 0 {
|
if n.PreviousStat.RX == 0 && n.PreviousStat.TX == 0 {
|
||||||
//Initiation state is still not done. Ignore request
|
//Initiation state is still not done. Ignore request
|
||||||
log.Println("No initial states. Waiting")
|
systemWideLogger.PrintAndLog("netstat", "No initial states. Waiting", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get the latest network interface stats
|
// Get the latest network interface stats
|
||||||
rx, tx, err := GetNetworkInterfaceStats()
|
rx, tx, err := thisNetBuffer.GetNetworkInterfaceStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log the error, but don't stop the buffer
|
// Log the error, but don't stop the buffer
|
||||||
log.Printf("Failed to get network interface stats: %v", err)
|
systemWideLogger.PrintAndLog("netstat", "Failed to get network interface stats", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,8 +174,8 @@ func (n *NetStatBuffers) Close() {
|
|||||||
n.EventTicker.Stop()
|
n.EventTicker.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
|
func (n *NetStatBuffers) HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
|
||||||
rx, tx, err := GetNetworkInterfaceStats()
|
rx, tx, err := n.GetNetworkInterfaceStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, err.Error())
|
utils.SendErrorResponse(w, err.Error())
|
||||||
return
|
return
|
||||||
@ -194,7 +194,7 @@ func HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get network interface stats, return accumulated rx bits, tx bits and error if any
|
// Get network interface stats, return accumulated rx bits, tx bits and error if any
|
||||||
func GetNetworkInterfaceStats() (int64, int64, error) {
|
func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
//Windows wmic sometime freeze and not respond.
|
//Windows wmic sometime freeze and not respond.
|
||||||
//The safer way is to make a bypass mechanism
|
//The safer way is to make a bypass mechanism
|
||||||
@ -263,7 +263,7 @@ func GetNetworkInterfaceStats() (int64, int64, error) {
|
|||||||
result = <-callbackChan
|
result = <-callbackChan
|
||||||
cmd = nil
|
cmd = nil
|
||||||
if result.Err != nil {
|
if result.Err != nil {
|
||||||
log.Println("Unable to extract NIC info from wmic: " + result.Err.Error())
|
n.logger.PrintAndLog("netstat", "Unable to extract NIC info from wmic", result.Err)
|
||||||
}
|
}
|
||||||
return result.RX, result.TX, result.Err
|
return result.RX, result.TX, result.Err
|
||||||
} else if runtime.GOOS == "linux" {
|
} else if runtime.GOOS == "linux" {
|
||||||
|
108
src/mod/update/update.go
Normal file
108
src/mod/update/update.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package update
|
||||||
|
|
||||||
|
/*
|
||||||
|
Update.go
|
||||||
|
|
||||||
|
This module handle cross version updates that contains breaking changes
|
||||||
|
update command should always exit after the update is completed
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run config update. Version numbers are int. For example
|
||||||
|
// to update 3.0.7 to 3.0.8, use RunConfigUpdate(307, 308)
|
||||||
|
// This function support cross versions updates (e.g. 307 -> 310)
|
||||||
|
func RunConfigUpdate(fromVersion int, toVersion int) {
|
||||||
|
versionFile := "./conf/version"
|
||||||
|
isFirstTimeInit, _ := isFirstTimeInitialize("./conf/proxy/")
|
||||||
|
if isFirstTimeInit {
|
||||||
|
//Create version file and exit
|
||||||
|
os.MkdirAll("./conf/", 0775)
|
||||||
|
os.WriteFile(versionFile, []byte(strconv.Itoa(toVersion)), 0775)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fromVersion == 0 {
|
||||||
|
//Run auto previous version detection
|
||||||
|
fromVersion = 307
|
||||||
|
if utils.FileExists(versionFile) {
|
||||||
|
//Read the version file
|
||||||
|
previousVersionText, err := os.ReadFile(versionFile)
|
||||||
|
if err != nil {
|
||||||
|
panic("Unable to read version file at " + versionFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert the version to int
|
||||||
|
versionInt, err := strconv.Atoi(strings.TrimSpace(string(previousVersionText)))
|
||||||
|
if err != nil {
|
||||||
|
panic("Unable to read version file at " + versionFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
fromVersion = versionInt
|
||||||
|
}
|
||||||
|
|
||||||
|
if fromVersion == toVersion {
|
||||||
|
//No need to update
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Do iterate update
|
||||||
|
for i := fromVersion; i < toVersion; i++ {
|
||||||
|
oldVersion := fromVersion
|
||||||
|
newVersion := fromVersion + 1
|
||||||
|
fmt.Println("Updating from v", oldVersion, " to v", newVersion)
|
||||||
|
runUpdateRoutineWithVersion(oldVersion, newVersion)
|
||||||
|
//Write the updated version to file
|
||||||
|
os.WriteFile(versionFile, []byte(strconv.Itoa(newVersion)), 0775)
|
||||||
|
}
|
||||||
|
fmt.Println("Update completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVersionIntFromVersionNumber(version string) int {
|
||||||
|
versionNumberOnly := strings.ReplaceAll(version, ".", "")
|
||||||
|
versionInt, _ := strconv.Atoi(versionNumberOnly)
|
||||||
|
return versionInt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the folder "./conf/proxy/" exists and contains files
|
||||||
|
func isFirstTimeInitialize(path string) (bool, error) {
|
||||||
|
// Check if the folder exists
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// The folder does not exist
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// Some other error occurred
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it is a directory
|
||||||
|
if !info.IsDir() {
|
||||||
|
// The path is not a directory
|
||||||
|
return false, fmt.Errorf("%s is not a directory", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the directory contents
|
||||||
|
files, err := ioutil.ReadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the directory is empty
|
||||||
|
if len(files) == 0 {
|
||||||
|
// The folder exists but is empty
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The folder exists and contains files
|
||||||
|
return false, nil
|
||||||
|
}
|
16
src/mod/update/updatelogic.go
Normal file
16
src/mod/update/updatelogic.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package update
|
||||||
|
|
||||||
|
import v308 "imuslab.com/zoraxy/mod/update/v308"
|
||||||
|
|
||||||
|
// Updater Core logic
|
||||||
|
func runUpdateRoutineWithVersion(fromVersion int, toVersion int) {
|
||||||
|
if fromVersion == 307 && toVersion == 308 {
|
||||||
|
//Updating from v3.0.7 to v3.0.8
|
||||||
|
err := v308.UpdateFrom307To308()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//ADD MORE VERSIONS HERE
|
||||||
|
}
|
138
src/mod/update/v308/typedef307.go
Normal file
138
src/mod/update/v308/typedef307.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package v308
|
||||||
|
|
||||||
|
/*
|
||||||
|
v307 type definations
|
||||||
|
|
||||||
|
This file wrap up the self-contained data structure
|
||||||
|
for v3.0.7 structure and allow automatic updates
|
||||||
|
for future releases if required
|
||||||
|
*/
|
||||||
|
|
||||||
|
type v307PermissionsPolicy struct {
|
||||||
|
Accelerometer []string `json:"accelerometer"`
|
||||||
|
AmbientLightSensor []string `json:"ambient_light_sensor"`
|
||||||
|
Autoplay []string `json:"autoplay"`
|
||||||
|
Battery []string `json:"battery"`
|
||||||
|
Camera []string `json:"camera"`
|
||||||
|
CrossOriginIsolated []string `json:"cross_origin_isolated"`
|
||||||
|
DisplayCapture []string `json:"display_capture"`
|
||||||
|
DocumentDomain []string `json:"document_domain"`
|
||||||
|
EncryptedMedia []string `json:"encrypted_media"`
|
||||||
|
ExecutionWhileNotRendered []string `json:"execution_while_not_rendered"`
|
||||||
|
ExecutionWhileOutOfView []string `json:"execution_while_out_of_viewport"`
|
||||||
|
Fullscreen []string `json:"fullscreen"`
|
||||||
|
Geolocation []string `json:"geolocation"`
|
||||||
|
Gyroscope []string `json:"gyroscope"`
|
||||||
|
KeyboardMap []string `json:"keyboard_map"`
|
||||||
|
Magnetometer []string `json:"magnetometer"`
|
||||||
|
Microphone []string `json:"microphone"`
|
||||||
|
Midi []string `json:"midi"`
|
||||||
|
NavigationOverride []string `json:"navigation_override"`
|
||||||
|
Payment []string `json:"payment"`
|
||||||
|
PictureInPicture []string `json:"picture_in_picture"`
|
||||||
|
PublicKeyCredentialsGet []string `json:"publickey_credentials_get"`
|
||||||
|
ScreenWakeLock []string `json:"screen_wake_lock"`
|
||||||
|
SyncXHR []string `json:"sync_xhr"`
|
||||||
|
USB []string `json:"usb"`
|
||||||
|
WebShare []string `json:"web_share"`
|
||||||
|
XRSpatialTracking []string `json:"xr_spatial_tracking"`
|
||||||
|
ClipboardRead []string `json:"clipboard_read"`
|
||||||
|
ClipboardWrite []string `json:"clipboard_write"`
|
||||||
|
Gamepad []string `json:"gamepad"`
|
||||||
|
SpeakerSelection []string `json:"speaker_selection"`
|
||||||
|
ConversionMeasurement []string `json:"conversion_measurement"`
|
||||||
|
FocusWithoutUserActivation []string `json:"focus_without_user_activation"`
|
||||||
|
HID []string `json:"hid"`
|
||||||
|
IdleDetection []string `json:"idle_detection"`
|
||||||
|
InterestCohort []string `json:"interest_cohort"`
|
||||||
|
Serial []string `json:"serial"`
|
||||||
|
SyncScript []string `json:"sync_script"`
|
||||||
|
TrustTokenRedemption []string `json:"trust_token_redemption"`
|
||||||
|
Unload []string `json:"unload"`
|
||||||
|
WindowPlacement []string `json:"window_placement"`
|
||||||
|
VerticalScroll []string `json:"vertical_scroll"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth credential for basic auth on certain endpoints
|
||||||
|
type v307BasicAuthCredentials struct {
|
||||||
|
Username string
|
||||||
|
PasswordHash string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth credential for basic auth on certain endpoints
|
||||||
|
type v307BasicAuthUnhashedCredentials struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paths to exclude in basic auth enabled proxy handler
|
||||||
|
type v307BasicAuthExceptionRule struct {
|
||||||
|
PathPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header injection direction type
|
||||||
|
type v307HeaderDirection int
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderDirection_ZoraxyToUpstream v307HeaderDirection = 0 //Inject (or remove) header to request out-going from Zoraxy to backend server
|
||||||
|
HeaderDirection_ZoraxyToDownstream v307HeaderDirection = 1 //Inject (or remove) header to request out-going from Zoraxy to client (e.g. browser)
|
||||||
|
)
|
||||||
|
|
||||||
|
// User defined headers to add into a proxy endpoint
|
||||||
|
type v307UserDefinedHeader struct {
|
||||||
|
Direction v307HeaderDirection
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
IsRemove bool //Instead of set, remove this key instead
|
||||||
|
}
|
||||||
|
|
||||||
|
// The original proxy endpoint structure from v3.0.7
|
||||||
|
type v307ProxyEndpoint struct {
|
||||||
|
ProxyType int //The type of this proxy, see const def
|
||||||
|
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||||
|
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||||
|
Domain string //Domain or IP to proxy to
|
||||||
|
|
||||||
|
//TLS/SSL Related
|
||||||
|
RequireTLS bool //Target domain require TLS
|
||||||
|
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||||
|
SkipCertValidations bool //Set to true to accept self signed certs
|
||||||
|
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||||
|
|
||||||
|
//Virtual Directories
|
||||||
|
VirtualDirectories []*v307VirtualDirectoryEndpoint
|
||||||
|
|
||||||
|
//Custom Headers
|
||||||
|
UserDefinedHeaders []*v307UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||||
|
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||||
|
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||||
|
PermissionPolicy *v307PermissionsPolicy //Permission policy header
|
||||||
|
|
||||||
|
//Authentication
|
||||||
|
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||||
|
BasicAuthCredentials []*v307BasicAuthCredentials //Basic auth credentials
|
||||||
|
BasicAuthExceptionRules []*v307BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||||
|
|
||||||
|
// Rate Limiting
|
||||||
|
RequireRateLimit bool
|
||||||
|
RateLimit int64 // Rate limit in requests per second
|
||||||
|
|
||||||
|
//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
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||||
|
// program structure than directly using ProxyEndpoint
|
||||||
|
type v307VirtualDirectoryEndpoint struct {
|
||||||
|
MatchingPath string //Matching prefix of the request path, also act as key
|
||||||
|
Domain string //Domain or IP to proxy to
|
||||||
|
RequireTLS bool //Target domain require TLS
|
||||||
|
SkipCertValidations bool //Set to true to accept self signed certs
|
||||||
|
Disabled bool //If the rule is enabled
|
||||||
|
}
|
63
src/mod/update/v308/typedef308.go
Normal file
63
src/mod/update/v308/typedef308.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package v308
|
||||||
|
|
||||||
|
/*
|
||||||
|
v308 type definations
|
||||||
|
|
||||||
|
This file wrap up the self-contained data structure
|
||||||
|
for v3.0.8 structure and allow automatic updates
|
||||||
|
for future releases if required
|
||||||
|
|
||||||
|
Some struct are identical as v307 and hence it is not redefined here
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Upstream or Origin Server */
|
||||||
|
type v308Upstream struct {
|
||||||
|
//Upstream Proxy Configs
|
||||||
|
OriginIpOrDomain string //Target IP address or domain name with port
|
||||||
|
RequireTLS bool //Require TLS connection
|
||||||
|
SkipCertValidations bool //Set to true to accept self signed certs
|
||||||
|
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||||
|
|
||||||
|
//Load balancing configs
|
||||||
|
Weight int //Prirotiy of fallback, set all to 0 for round robin
|
||||||
|
MaxConn int //Maxmium connection to this server
|
||||||
|
}
|
||||||
|
|
||||||
|
// A proxy endpoint record, a general interface for handling inbound routing
|
||||||
|
type v308ProxyEndpoint struct {
|
||||||
|
ProxyType int //The type of this proxy, see const def
|
||||||
|
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||||
|
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||||
|
ActiveOrigins []*v308Upstream //Activated Upstream or origin servers IP or domain to proxy to
|
||||||
|
InactiveOrigins []*v308Upstream //Disabled Upstream or origin servers IP or domain to proxy to
|
||||||
|
UseStickySession bool //Use stick session for load balancing
|
||||||
|
Disabled bool //If the rule is disabled
|
||||||
|
|
||||||
|
//Inbound TLS/SSL Related
|
||||||
|
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||||
|
|
||||||
|
//Virtual Directories
|
||||||
|
VirtualDirectories []*v307VirtualDirectoryEndpoint
|
||||||
|
|
||||||
|
//Custom Headers
|
||||||
|
UserDefinedHeaders []*v307UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||||
|
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||||
|
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||||
|
PermissionPolicy *v307PermissionsPolicy //Permission policy header
|
||||||
|
|
||||||
|
//Authentication
|
||||||
|
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||||
|
BasicAuthCredentials []*v307BasicAuthCredentials //Basic auth credentials
|
||||||
|
BasicAuthExceptionRules []*v307BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||||
|
|
||||||
|
// Rate Limiting
|
||||||
|
RequireRateLimit bool
|
||||||
|
RateLimit int64 // Rate limit in requests per second
|
||||||
|
|
||||||
|
//Access Control
|
||||||
|
AccessFilterUUID string //Access filter ID
|
||||||
|
|
||||||
|
//Fallback routing logic (Special Rule Sets Only)
|
||||||
|
DefaultSiteOption int //Fallback routing logic options
|
||||||
|
DefaultSiteValue string //Fallback routing target, optional
|
||||||
|
}
|
132
src/mod/update/v308/v308.go
Normal file
132
src/mod/update/v308/v308.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package v308
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
v3.0.7 update to v3.0.8
|
||||||
|
|
||||||
|
This update function
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Update proxy config files from v3.0.7 to v3.0.8
|
||||||
|
func UpdateFrom307To308() error {
|
||||||
|
|
||||||
|
//Load the configs
|
||||||
|
oldConfigFiles, err := filepath.Glob("./conf/proxy/*.config")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Backup all the files
|
||||||
|
err = os.MkdirAll("./conf/proxy.old/", 0775)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, oldConfigFile := range oldConfigFiles {
|
||||||
|
// Extract the file name from the path
|
||||||
|
fileName := filepath.Base(oldConfigFile)
|
||||||
|
// Construct the backup file path
|
||||||
|
backupFile := filepath.Join("./conf/proxy.old/", fileName)
|
||||||
|
|
||||||
|
// Copy the file to the backup directory
|
||||||
|
err := copyFile(oldConfigFile, backupFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//read the config into the old struct
|
||||||
|
for _, oldConfigFile := range oldConfigFiles {
|
||||||
|
configContent, err := os.ReadFile(oldConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to read config file "+filepath.Base(oldConfigFile), err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
thisOldConfigStruct := v307ProxyEndpoint{}
|
||||||
|
err = json.Unmarshal(configContent, &thisOldConfigStruct)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to parse file "+filepath.Base(oldConfigFile), err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert the old config to new config
|
||||||
|
newProxyStructure := convertV307ToV308(thisOldConfigStruct)
|
||||||
|
js, _ := json.MarshalIndent(newProxyStructure, "", " ")
|
||||||
|
err = os.WriteFile(oldConfigFile, js, 0775)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertV307ToV308(old v307ProxyEndpoint) v308ProxyEndpoint {
|
||||||
|
// Create a new v308ProxyEndpoint instance
|
||||||
|
|
||||||
|
matchingDomainsSlice := old.MatchingDomainAlias
|
||||||
|
if matchingDomainsSlice == nil {
|
||||||
|
matchingDomainsSlice = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
newEndpoint := v308ProxyEndpoint{
|
||||||
|
ProxyType: old.ProxyType,
|
||||||
|
RootOrMatchingDomain: old.RootOrMatchingDomain,
|
||||||
|
MatchingDomainAlias: matchingDomainsSlice,
|
||||||
|
ActiveOrigins: []*v308Upstream{{ // Mapping Domain field to v308Upstream struct
|
||||||
|
OriginIpOrDomain: old.Domain,
|
||||||
|
RequireTLS: old.RequireTLS,
|
||||||
|
SkipCertValidations: old.SkipCertValidations,
|
||||||
|
SkipWebSocketOriginCheck: old.SkipWebSocketOriginCheck,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0,
|
||||||
|
}},
|
||||||
|
InactiveOrigins: []*v308Upstream{},
|
||||||
|
UseStickySession: false,
|
||||||
|
Disabled: old.Disabled,
|
||||||
|
BypassGlobalTLS: old.BypassGlobalTLS,
|
||||||
|
VirtualDirectories: old.VirtualDirectories,
|
||||||
|
UserDefinedHeaders: old.UserDefinedHeaders,
|
||||||
|
HSTSMaxAge: old.HSTSMaxAge,
|
||||||
|
EnablePermissionPolicyHeader: old.EnablePermissionPolicyHeader,
|
||||||
|
PermissionPolicy: old.PermissionPolicy,
|
||||||
|
RequireBasicAuth: old.RequireBasicAuth,
|
||||||
|
BasicAuthCredentials: old.BasicAuthCredentials,
|
||||||
|
BasicAuthExceptionRules: old.BasicAuthExceptionRules,
|
||||||
|
RequireRateLimit: old.RequireRateLimit,
|
||||||
|
RateLimit: old.RateLimit,
|
||||||
|
AccessFilterUUID: old.AccessFilterUUID,
|
||||||
|
DefaultSiteOption: old.DefaultSiteOption,
|
||||||
|
DefaultSiteValue: old.DefaultSiteValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
return newEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to copy files
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
sourceFile, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sourceFile.Close()
|
||||||
|
|
||||||
|
destinationFile, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destinationFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(destinationFile, sourceFile)
|
||||||
|
return err
|
||||||
|
}
|
@ -2,16 +2,23 @@ package uptime
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
logModuleName = "uptime-monitor"
|
||||||
|
)
|
||||||
|
|
||||||
type Record struct {
|
type Record struct {
|
||||||
Timestamp int64
|
Timestamp int64
|
||||||
ID string
|
ID string
|
||||||
@ -23,17 +30,26 @@ type Record struct {
|
|||||||
Latency int64
|
Latency int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProxyType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProxyType_Host ProxyType = "Origin Server"
|
||||||
|
ProxyType_Vdir ProxyType = "Virtual Directory"
|
||||||
|
)
|
||||||
|
|
||||||
type Target struct {
|
type Target struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
URL string
|
URL string
|
||||||
Protocol string
|
Protocol string
|
||||||
|
ProxyType ProxyType
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Targets []*Target
|
Targets []*Target
|
||||||
Interval int
|
Interval int
|
||||||
MaxRecordsStore int
|
MaxRecordsStore int
|
||||||
|
Logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
@ -56,6 +72,12 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
|
|||||||
Config: config,
|
Config: config,
|
||||||
OnlineStatusLog: map[string][]*Record{},
|
OnlineStatusLog: map[string][]*Record{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Logger == nil {
|
||||||
|
//Use default fmt to log if logger is not provided
|
||||||
|
config.Logger, _ = logger.NewFmtLogger()
|
||||||
|
}
|
||||||
|
|
||||||
//Start the endpoint listener
|
//Start the endpoint listener
|
||||||
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
|
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
@ -69,7 +91,7 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
|
|||||||
case <-done:
|
case <-done:
|
||||||
return
|
return
|
||||||
case t := <-ticker.C:
|
case t := <-ticker.C:
|
||||||
log.Println("Uptime updated - ", t.Unix())
|
thisMonitor.Config.Logger.PrintAndLog(logModuleName, "Uptime updated - "+strconv.Itoa(int(t.Unix())), nil)
|
||||||
thisMonitor.ExecuteUptimeCheck()
|
thisMonitor.ExecuteUptimeCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +105,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
|
|||||||
//For each target to check online, do the following
|
//For each target to check online, do the following
|
||||||
var thisRecord Record
|
var thisRecord Record
|
||||||
if target.Protocol == "http" || target.Protocol == "https" {
|
if target.Protocol == "http" || target.Protocol == "https" {
|
||||||
online, laterncy, statusCode := getWebsiteStatusWithLatency(target.URL)
|
online, laterncy, statusCode := m.getWebsiteStatusWithLatency(target.URL)
|
||||||
thisRecord = Record{
|
thisRecord = Record{
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
ID: target.ID,
|
ID: target.ID,
|
||||||
@ -96,7 +118,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.Println("Unknown protocol: " + target.Protocol + ". Skipping")
|
m.Config.Logger.PrintAndLog(logModuleName, "Unknown protocol: "+target.Protocol, errors.New("unsupported protocol"))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,8 +138,6 @@ func (m *Monitor) ExecuteUptimeCheck() {
|
|||||||
m.OnlineStatusLog[target.ID] = thisRecords
|
m.OnlineStatusLog[target.ID] = thisRecords
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Write results to db
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Monitor) AddTargetToMonitor(target *Target) {
|
func (m *Monitor) AddTargetToMonitor(target *Target) {
|
||||||
@ -193,12 +213,12 @@ func (m *Monitor) HandleUptimeLogRead(w http.ResponseWriter, r *http.Request) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Get website stauts with latency given URL, return is conn succ and its latency and status code
|
// Get website stauts with latency given URL, return is conn succ and its latency and status code
|
||||||
func getWebsiteStatusWithLatency(url string) (bool, int64, int) {
|
func (m *Monitor) getWebsiteStatusWithLatency(url string) (bool, int64, int) {
|
||||||
start := time.Now().UnixNano() / int64(time.Millisecond)
|
start := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
statusCode, err := getWebsiteStatus(url)
|
statusCode, err := getWebsiteStatus(url)
|
||||||
end := time.Now().UnixNano() / int64(time.Millisecond)
|
end := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
m.Config.Logger.PrintAndLog(logModuleName, "Ping upstream timeout. Assume offline", err)
|
||||||
return false, 0, 0
|
return false, 0, 0
|
||||||
} else {
|
} else {
|
||||||
diff := end - start
|
diff := end - start
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -50,3 +54,52 @@ func ReplaceSpecialCharacters(filename string) string {
|
|||||||
|
|
||||||
return filename
|
return filename
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Zip File Handler */
|
||||||
|
// zipFiles compresses multiple files into a single zip archive file
|
||||||
|
func ZipFiles(filename string, files ...string) error {
|
||||||
|
newZipFile, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer newZipFile.Close()
|
||||||
|
|
||||||
|
zipWriter := zip.NewWriter(newZipFile)
|
||||||
|
defer zipWriter.Close()
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if err := addFileToZip(zipWriter, file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addFileToZip adds an individual file to a zip archive
|
||||||
|
func addFileToZip(zipWriter *zip.Writer, filename string) error {
|
||||||
|
fileToZip, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fileToZip.Close()
|
||||||
|
|
||||||
|
info, err := fileToZip.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := zip.FileInfoHeader(info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
header.Name = filepath.Base(filename)
|
||||||
|
header.Method = zip.Deflate
|
||||||
|
|
||||||
|
writer, err := zipWriter.CreateHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(writer, fileToZip)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -48,6 +49,24 @@ func GetPara(r *http.Request, key string) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get GET paramter as boolean, accept 1 or true
|
||||||
|
func GetBool(r *http.Request, key string) (bool, error) {
|
||||||
|
x, err := GetPara(r, key)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
x = strings.TrimSpace(x)
|
||||||
|
|
||||||
|
if x == "1" || strings.ToLower(x) == "true" || strings.ToLower(x) == "on" {
|
||||||
|
return true, nil
|
||||||
|
} else if x == "0" || strings.ToLower(x) == "false" || strings.ToLower(x) == "off" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, errors.New("invalid boolean given")
|
||||||
|
}
|
||||||
|
|
||||||
// Get POST paramter
|
// Get POST paramter
|
||||||
func PostPara(r *http.Request, key string) (string, error) {
|
func PostPara(r *http.Request, key string) (string, error) {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
@ -141,3 +160,35 @@ func StringInArrayIgnoreCase(arr []string, str string) bool {
|
|||||||
|
|
||||||
return StringInArray(smallArray, strings.ToLower(str))
|
return StringInArray(smallArray, strings.ToLower(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate if the listening address is correct
|
||||||
|
func ValidateListeningAddress(address string) bool {
|
||||||
|
// Check if the address starts with a colon, indicating it's just a port
|
||||||
|
if strings.HasPrefix(address, ":") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the address into host and port parts
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
// Try to parse it as just a port
|
||||||
|
if _, err := strconv.Atoi(address); err == nil {
|
||||||
|
return false // It's just a port number
|
||||||
|
}
|
||||||
|
return false // It's an invalid address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the port part is a valid number
|
||||||
|
if _, err := strconv.Atoi(port); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the host part is a valid IP address or empty (indicating any IP)
|
||||||
|
if host != "" {
|
||||||
|
if net.ParseIP(host) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -5,13 +5,13 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
"imuslab.com/zoraxy/mod/webserv/filemanager"
|
"imuslab.com/zoraxy/mod/webserv/filemanager"
|
||||||
)
|
)
|
||||||
@ -30,6 +30,7 @@ type WebServerOptions struct {
|
|||||||
EnableDirectoryListing bool //Enable listing of directory
|
EnableDirectoryListing bool //Enable listing of directory
|
||||||
WebRoot string //Folder for stroing the static web folders
|
WebRoot string //Folder for stroing the static web folders
|
||||||
EnableWebDirManager bool //Enable web file manager to handle files in web directory
|
EnableWebDirManager bool //Enable web file manager to handle files in web directory
|
||||||
|
Logger *logger.Logger //System logger
|
||||||
Sysdb *database.Database //Database for storing configs
|
Sysdb *database.Database //Database for storing configs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,13 +46,16 @@ type WebServer struct {
|
|||||||
|
|
||||||
// NewWebServer creates a new WebServer instance. One instance only
|
// NewWebServer creates a new WebServer instance. One instance only
|
||||||
func NewWebServer(options *WebServerOptions) *WebServer {
|
func NewWebServer(options *WebServerOptions) *WebServer {
|
||||||
|
if options.Logger == nil {
|
||||||
|
options.Logger, _ = logger.NewFmtLogger()
|
||||||
|
}
|
||||||
if !utils.FileExists(options.WebRoot) {
|
if !utils.FileExists(options.WebRoot) {
|
||||||
//Web root folder not exists. Create one with default templates
|
//Web root folder not exists. Create one with default templates
|
||||||
os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775)
|
os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775)
|
||||||
os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775)
|
os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775)
|
||||||
indexTemplate, err := templates.ReadFile("templates/index.html")
|
indexTemplate, err := templates.ReadFile("templates/index.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to read static wev server template file: ", err.Error())
|
options.Logger.PrintAndLog("static-webserv", "Failed to read static wev server template file: ", err)
|
||||||
} else {
|
} else {
|
||||||
os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775)
|
os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775)
|
||||||
}
|
}
|
||||||
@ -102,7 +106,7 @@ func (ws *WebServer) RestorePreviousState() {
|
|||||||
// ChangePort changes the server's port.
|
// ChangePort changes the server's port.
|
||||||
func (ws *WebServer) ChangePort(port string) error {
|
func (ws *WebServer) ChangePort(port string) error {
|
||||||
if IsPortInUse(port) {
|
if IsPortInUse(port) {
|
||||||
return errors.New("Selected port is used by another process")
|
return errors.New("selected port is used by another process")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ws.isRunning {
|
if ws.isRunning {
|
||||||
@ -119,6 +123,7 @@ func (ws *WebServer) ChangePort(port string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ws.option.Logger.PrintAndLog("static-webserv", "Listening port updated to "+port, nil)
|
||||||
ws.option.Sysdb.Write("webserv", "port", port)
|
ws.option.Sysdb.Write("webserv", "port", port)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -141,7 +146,7 @@ func (ws *WebServer) Start() error {
|
|||||||
|
|
||||||
//Check if the port is usable
|
//Check if the port is usable
|
||||||
if IsPortInUse(ws.option.Port) {
|
if IsPortInUse(ws.option.Port) {
|
||||||
return errors.New("Port already in use or access denied by host OS")
|
return errors.New("port already in use or access denied by host OS")
|
||||||
}
|
}
|
||||||
|
|
||||||
//Dispose the old mux and create a new one
|
//Dispose the old mux and create a new one
|
||||||
@ -159,12 +164,12 @@ func (ws *WebServer) Start() error {
|
|||||||
go func() {
|
go func() {
|
||||||
if err := ws.server.ListenAndServe(); err != nil {
|
if err := ws.server.ListenAndServe(); err != nil {
|
||||||
if err != http.ErrServerClosed {
|
if err != http.ErrServerClosed {
|
||||||
fmt.Printf("Web server error: %v\n", err)
|
ws.option.Logger.PrintAndLog("static-webserv", "Web server failed to start", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Println("Static Web Server started. Listeing on :" + ws.option.Port)
|
ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server started. Listeing on :"+ws.option.Port, nil)
|
||||||
ws.isRunning = true
|
ws.isRunning = true
|
||||||
ws.option.Sysdb.Write("webserv", "enabled", true)
|
ws.option.Sysdb.Write("webserv", "enabled", true)
|
||||||
return nil
|
return nil
|
||||||
@ -182,7 +187,7 @@ func (ws *WebServer) Stop() error {
|
|||||||
if err := ws.server.Close(); err != nil {
|
if err := ws.server.Close(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server stopped", nil)
|
||||||
ws.isRunning = false
|
ws.isRunning = false
|
||||||
ws.option.Sysdb.Write("webserv", "enabled", false)
|
ws.option.Sysdb.Write("webserv", "enabled", false)
|
||||||
return nil
|
return nil
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
@ -96,9 +97,11 @@ func ReverseProxtInit() {
|
|||||||
StatisticCollector: statisticCollector,
|
StatisticCollector: statisticCollector,
|
||||||
WebDirectory: *staticWebServerRoot,
|
WebDirectory: *staticWebServerRoot,
|
||||||
AccessController: accessController,
|
AccessController: accessController,
|
||||||
|
LoadBalancer: loadBalancer,
|
||||||
|
Logger: SystemWideLogger,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to create dynamic proxy router", err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Unable to create dynamic proxy router", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +116,7 @@ func ReverseProxtInit() {
|
|||||||
for _, conf := range confs {
|
for _, conf := range confs {
|
||||||
err := LoadReverseProxyConfig(conf)
|
err := LoadReverseProxyConfig(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Failed to load config file: "+filepath.Base(conf), err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Failed to load config file: "+filepath.Base(conf), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +125,7 @@ func ReverseProxtInit() {
|
|||||||
//Root config not set (new deployment?), use internal static web server as root
|
//Root config not set (new deployment?), use internal static web server as root
|
||||||
defaultRootRouter, err := GetDefaultRootConfig()
|
defaultRootRouter, err := GetDefaultRootConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Failed to generate default root routing", err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Failed to generate default root routing", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter)
|
dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter)
|
||||||
@ -143,11 +146,9 @@ func ReverseProxtInit() {
|
|||||||
Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter),
|
Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter),
|
||||||
Interval: 300, //5 minutes
|
Interval: 300, //5 minutes
|
||||||
MaxRecordsStore: 288, //1 day
|
MaxRecordsStore: 288, //1 day
|
||||||
|
Logger: SystemWideLogger, //Logger
|
||||||
})
|
})
|
||||||
|
|
||||||
//Pass the pointer of this uptime monitor into the load balancer
|
|
||||||
loadbalancer.Options.UptimeMonitor = uptimeMonitor
|
|
||||||
|
|
||||||
SystemWideLogger.Println("Uptime Monitor background service started")
|
SystemWideLogger.Println("Uptime Monitor background service started")
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -208,12 +209,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
useBypassGlobalTLS := bypassGlobalTLS == "true"
|
useBypassGlobalTLS := bypassGlobalTLS == "true"
|
||||||
|
|
||||||
//Enable TLS validation?
|
//Enable TLS validation?
|
||||||
stv, _ := utils.PostPara(r, "tlsval")
|
skipTlsValidation, _ := utils.PostBool(r, "tlsval")
|
||||||
if stv == "" {
|
|
||||||
stv = "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
skipTlsValidation := (stv == "true")
|
|
||||||
|
|
||||||
//Get access rule ID
|
//Get access rule ID
|
||||||
accessRuleID, _ := utils.PostPara(r, "access")
|
accessRuleID, _ := utils.PostPara(r, "access")
|
||||||
@ -226,12 +222,10 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Require basic auth?
|
// Require basic auth?
|
||||||
rba, _ := utils.PostPara(r, "bauth")
|
requireBasicAuth, _ := utils.PostBool(r, "bauth")
|
||||||
if rba == "" {
|
|
||||||
rba = "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
requireBasicAuth := (rba == "true")
|
//Use sticky session?
|
||||||
|
useStickySession, _ := utils.PostBool(r, "stickysess")
|
||||||
|
|
||||||
// Require Rate Limiting?
|
// Require Rate Limiting?
|
||||||
requireRateLimit := false
|
requireRateLimit := false
|
||||||
@ -319,12 +313,20 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
ProxyType: dynamicproxy.ProxyType_Host,
|
ProxyType: dynamicproxy.ProxyType_Host,
|
||||||
RootOrMatchingDomain: rootOrMatchingDomain,
|
RootOrMatchingDomain: rootOrMatchingDomain,
|
||||||
MatchingDomainAlias: aliasHostnames,
|
MatchingDomainAlias: aliasHostnames,
|
||||||
Domain: endpoint,
|
ActiveOrigins: []*loadbalance.Upstream{
|
||||||
//TLS
|
{
|
||||||
|
OriginIpOrDomain: endpoint,
|
||||||
RequireTLS: useTLS,
|
RequireTLS: useTLS,
|
||||||
BypassGlobalTLS: useBypassGlobalTLS,
|
|
||||||
SkipCertValidations: skipTlsValidation,
|
SkipCertValidations: skipTlsValidation,
|
||||||
SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
|
SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InactiveOrigins: []*loadbalance.Upstream{},
|
||||||
|
UseStickySession: useStickySession,
|
||||||
|
|
||||||
|
//TLS
|
||||||
|
BypassGlobalTLS: useBypassGlobalTLS,
|
||||||
AccessFilterUUID: accessRuleID,
|
AccessFilterUUID: accessRuleID,
|
||||||
//VDir
|
//VDir
|
||||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||||
@ -377,12 +379,17 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{
|
rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{
|
||||||
ProxyType: dynamicproxy.ProxyType_Root,
|
ProxyType: dynamicproxy.ProxyType_Root,
|
||||||
RootOrMatchingDomain: "/",
|
RootOrMatchingDomain: "/",
|
||||||
Domain: endpoint,
|
ActiveOrigins: []*loadbalance.Upstream{
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: endpoint,
|
||||||
RequireTLS: useTLS,
|
RequireTLS: useTLS,
|
||||||
BypassGlobalTLS: false,
|
SkipCertValidations: true,
|
||||||
SkipCertValidations: false,
|
|
||||||
SkipWebSocketOriginCheck: true,
|
SkipWebSocketOriginCheck: true,
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InactiveOrigins: []*loadbalance.Upstream{},
|
||||||
|
BypassGlobalTLS: false,
|
||||||
DefaultSiteOption: defaultSiteOption,
|
DefaultSiteOption: defaultSiteOption,
|
||||||
DefaultSiteValue: dsVal,
|
DefaultSiteValue: dsVal,
|
||||||
}
|
}
|
||||||
@ -392,7 +399,11 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamicProxyRouter.SetProxyRouteAsRoot(preparedRootProxyRoute)
|
err = dynamicProxyRouter.SetProxyRouteAsRoot(preparedRootProxyRoute)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "unable to update default site: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
proxyEndpointCreated = &rootRoutingEndpoint
|
proxyEndpointCreated = &rootRoutingEndpoint
|
||||||
} else {
|
} else {
|
||||||
//Invalid eptype
|
//Invalid eptype
|
||||||
@ -403,7 +414,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Save the config to file
|
//Save the config to file
|
||||||
err = SaveReverseProxyConfig(proxyEndpointCreated)
|
err = SaveReverseProxyConfig(proxyEndpointCreated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to save new proxy rule to file", err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Unable to save new proxy rule to file", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,24 +437,12 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint, err := utils.PostPara(r, "ep")
|
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, "endpoint not defined")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tls, _ := utils.PostPara(r, "tls")
|
tls, _ := utils.PostPara(r, "tls")
|
||||||
if tls == "" {
|
if tls == "" {
|
||||||
tls = "false"
|
tls = "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
useTLS := (tls == "true")
|
useStickySession, _ := utils.PostBool(r, "ss")
|
||||||
|
|
||||||
stv, _ := utils.PostPara(r, "tlsval")
|
|
||||||
if stv == "" {
|
|
||||||
stv = "false"
|
|
||||||
}
|
|
||||||
skipTlsValidation := (stv == "true")
|
|
||||||
|
|
||||||
//Load bypass TLS option
|
//Load bypass TLS option
|
||||||
bpgtls, _ := utils.PostPara(r, "bpgtls")
|
bpgtls, _ := utils.PostPara(r, "bpgtls")
|
||||||
@ -475,18 +474,14 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendErrorResponse(w, "invalid rate limit number")
|
utils.SendErrorResponse(w, "invalid rate limit number")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if proxyRateLimit <= 0 {
|
|
||||||
|
if requireRateLimit && proxyRateLimit <= 0 {
|
||||||
utils.SendErrorResponse(w, "rate limit number must be greater than 0")
|
utils.SendErrorResponse(w, "rate limit number must be greater than 0")
|
||||||
return
|
return
|
||||||
|
} else if proxyRateLimit < 0 {
|
||||||
|
proxyRateLimit = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bypass WebSocket Origin Check
|
|
||||||
strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
|
|
||||||
if strbpwsorg == "" {
|
|
||||||
strbpwsorg = "false"
|
|
||||||
}
|
|
||||||
bypassWebsocketOriginCheck := (strbpwsorg == "true")
|
|
||||||
|
|
||||||
//Load the previous basic auth credentials from current proxy rules
|
//Load the previous basic auth credentials from current proxy rules
|
||||||
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
|
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -496,14 +491,11 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
//Generate a new proxyEndpoint from the new config
|
//Generate a new proxyEndpoint from the new config
|
||||||
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
|
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
|
||||||
newProxyEndpoint.Domain = endpoint
|
|
||||||
newProxyEndpoint.RequireTLS = useTLS
|
|
||||||
newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
|
newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
|
||||||
newProxyEndpoint.SkipCertValidations = skipTlsValidation
|
|
||||||
newProxyEndpoint.RequireBasicAuth = requireBasicAuth
|
newProxyEndpoint.RequireBasicAuth = requireBasicAuth
|
||||||
newProxyEndpoint.RequireRateLimit = requireRateLimit
|
newProxyEndpoint.RequireRateLimit = requireRateLimit
|
||||||
newProxyEndpoint.RateLimit = proxyRateLimit
|
newProxyEndpoint.RateLimit = proxyRateLimit
|
||||||
newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck
|
newProxyEndpoint.UseStickySession = useStickySession
|
||||||
|
|
||||||
//Prepare to replace the current routing rule
|
//Prepare to replace the current routing rule
|
||||||
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
|
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
|
||||||
@ -517,9 +509,6 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Save it to file
|
//Save it to file
|
||||||
SaveReverseProxyConfig(newProxyEndpoint)
|
SaveReverseProxyConfig(newProxyEndpoint)
|
||||||
|
|
||||||
//Update uptime monitor
|
|
||||||
UpdateUptimeMonitorTargets()
|
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,7 +540,7 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
|
|||||||
newAlias := []string{}
|
newAlias := []string{}
|
||||||
err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
|
err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to parse new alias list", err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Unable to parse new alias list", err)
|
||||||
utils.SendErrorResponse(w, "Invalid alias list given")
|
utils.SendErrorResponse(w, "Invalid alias list given")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -573,7 +562,7 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
|
|||||||
err = SaveReverseProxyConfig(newProxyEndpoint)
|
err = SaveReverseProxyConfig(newProxyEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "Alias update failed")
|
utils.SendErrorResponse(w, "Alias update failed")
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to save alias update", err)
|
SystemWideLogger.PrintAndLog("proxy-config", "Unable to save alias update", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
@ -877,6 +866,10 @@ func ReverseProxyToggleRuleSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendErrorResponse(w, "unable to save updated rule")
|
utils.SendErrorResponse(w, "unable to save updated rule")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Update uptime monitor
|
||||||
|
UpdateUptimeMonitorTargets()
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -936,7 +929,7 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
sort.Slice(results, func(i, j int) bool {
|
||||||
return results[i].Domain < results[j].Domain
|
return results[i].RootOrMatchingDomain < results[j].RootOrMatchingDomain
|
||||||
})
|
})
|
||||||
|
|
||||||
js, _ := json.Marshal(results)
|
js, _ := json.Marshal(results)
|
||||||
@ -1056,15 +1049,20 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rootProxyTargetOrigin := ""
|
||||||
|
if len(dynamicProxyRouter.Root.ActiveOrigins) > 0 {
|
||||||
|
rootProxyTargetOrigin = dynamicProxyRouter.Root.ActiveOrigins[0].OriginIpOrDomain
|
||||||
|
}
|
||||||
|
|
||||||
//Check if it is identical as proxy root (recursion!)
|
//Check if it is identical as proxy root (recursion!)
|
||||||
if dynamicProxyRouter.Root == nil || dynamicProxyRouter.Root.Domain == "" {
|
if dynamicProxyRouter.Root == nil || rootProxyTargetOrigin == "" {
|
||||||
//Check if proxy root is set before checking recursive listen
|
//Check if proxy root is set before checking recursive listen
|
||||||
//Fixing issue #43
|
//Fixing issue #43
|
||||||
utils.SendErrorResponse(w, "Set Proxy Root before changing inbound port")
|
utils.SendErrorResponse(w, "Set Proxy Root before changing inbound port")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyRoot := strings.TrimSuffix(dynamicProxyRouter.Root.Domain, "/")
|
proxyRoot := strings.TrimSuffix(rootProxyTargetOrigin, "/")
|
||||||
if strings.EqualFold(proxyRoot, "localhost:"+strconv.Itoa(newIncomingPortInt)) || strings.EqualFold(proxyRoot, "127.0.0.1:"+strconv.Itoa(newIncomingPortInt)) {
|
if strings.EqualFold(proxyRoot, "localhost:"+strconv.Itoa(newIncomingPortInt)) || strings.EqualFold(proxyRoot, "127.0.0.1:"+strconv.Itoa(newIncomingPortInt)) {
|
||||||
//Listening port is same as proxy root
|
//Listening port is same as proxy root
|
||||||
//Not allow recursive settings
|
//Not allow recursive settings
|
||||||
|
37
src/start.go
37
src/start.go
@ -20,6 +20,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/ganserv"
|
"imuslab.com/zoraxy/mod/ganserv"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logviewer"
|
||||||
"imuslab.com/zoraxy/mod/mdns"
|
"imuslab.com/zoraxy/mod/mdns"
|
||||||
"imuslab.com/zoraxy/mod/netstat"
|
"imuslab.com/zoraxy/mod/netstat"
|
||||||
"imuslab.com/zoraxy/mod/pathrule"
|
"imuslab.com/zoraxy/mod/pathrule"
|
||||||
@ -47,6 +48,18 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func startupSequence() {
|
func startupSequence() {
|
||||||
|
//Start a system wide logger and log viewer
|
||||||
|
l, err := logger.NewLogger("zr", "./log")
|
||||||
|
if err == nil {
|
||||||
|
SystemWideLogger = l
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
|
||||||
|
RootFolder: "./log",
|
||||||
|
Extension: ".log",
|
||||||
|
})
|
||||||
|
|
||||||
//Create database
|
//Create database
|
||||||
db, err := database.NewDatabase("sys.db", false)
|
db, err := database.NewDatabase("sys.db", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -61,11 +74,11 @@ func startupSequence() {
|
|||||||
os.MkdirAll("./conf/proxy/", 0775)
|
os.MkdirAll("./conf/proxy/", 0775)
|
||||||
|
|
||||||
//Create an auth agent
|
//Create an auth agent
|
||||||
sessionKey, err := auth.GetSessionKey(sysdb)
|
sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, func(w http.ResponseWriter, r *http.Request) {
|
authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, SystemWideLogger, func(w http.ResponseWriter, r *http.Request) {
|
||||||
//Not logged in. Redirecting to login page
|
//Not logged in. Redirecting to login page
|
||||||
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
|
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
|
||||||
})
|
})
|
||||||
@ -76,14 +89,6 @@ func startupSequence() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create a system wide logger
|
|
||||||
l, err := logger.NewLogger("zr", "./log", *logOutputToFile)
|
|
||||||
if err == nil {
|
|
||||||
SystemWideLogger = l
|
|
||||||
} else {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create a redirection rule table
|
//Create a redirection rule table
|
||||||
db.NewTable("redirect")
|
db.NewTable("redirect")
|
||||||
redirectAllowRegexp := false
|
redirectAllowRegexp := false
|
||||||
@ -102,10 +107,12 @@ func startupSequence() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create a load balance route manager
|
//Create a load balancer
|
||||||
loadbalancer = loadbalance.NewRouteManager(&loadbalance.Options{
|
loadBalancer = loadbalance.NewLoadBalancer(&loadbalance.Options{
|
||||||
|
SystemUUID: nodeUUID,
|
||||||
Geodb: geodbStore,
|
Geodb: geodbStore,
|
||||||
}, SystemWideLogger)
|
Logger: SystemWideLogger,
|
||||||
|
})
|
||||||
|
|
||||||
//Create the access controller
|
//Create the access controller
|
||||||
accessController, err = access.NewAccessController(&access.Options{
|
accessController, err = access.NewAccessController(&access.Options{
|
||||||
@ -132,12 +139,13 @@ func startupSequence() {
|
|||||||
WebRoot: *staticWebServerRoot,
|
WebRoot: *staticWebServerRoot,
|
||||||
EnableDirectoryListing: true,
|
EnableDirectoryListing: true,
|
||||||
EnableWebDirManager: *allowWebFileManager,
|
EnableWebDirManager: *allowWebFileManager,
|
||||||
|
Logger: SystemWideLogger,
|
||||||
})
|
})
|
||||||
//Restore the web server to previous shutdown state
|
//Restore the web server to previous shutdown state
|
||||||
staticWebServer.RestorePreviousState()
|
staticWebServer.RestorePreviousState()
|
||||||
|
|
||||||
//Create a netstat buffer
|
//Create a netstat buffer
|
||||||
netstatBuffers, err = netstat.NewNetStatBuffer(300)
|
netstatBuffers, err = netstat.NewNetStatBuffer(300, SystemWideLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Network", "Failed to load network statistic info", err)
|
SystemWideLogger.PrintAndLog("Network", "Failed to load network statistic info", err)
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -291,5 +299,4 @@ func finalSequence() {
|
|||||||
|
|
||||||
//Inject routing rules
|
//Inject routing rules
|
||||||
registerBuildInRoutingRules()
|
registerBuildInRoutingRules()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
283
src/upstreams.go
Normal file
283
src/upstreams.go
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Upstreams.go
|
||||||
|
|
||||||
|
This script handle upstream and load balancer
|
||||||
|
related API
|
||||||
|
*/
|
||||||
|
|
||||||
|
// List upstreams from a endpoint
|
||||||
|
func ReverseProxyUpstreamList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target endpoint not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activeUpstreams := targetEndpoint.ActiveOrigins
|
||||||
|
inactiveUpstreams := targetEndpoint.InactiveOrigins
|
||||||
|
// Sort the upstreams slice by weight, then by origin domain alphabetically
|
||||||
|
sort.Slice(activeUpstreams, func(i, j int) bool {
|
||||||
|
if activeUpstreams[i].Weight != activeUpstreams[j].Weight {
|
||||||
|
return activeUpstreams[i].Weight > activeUpstreams[j].Weight
|
||||||
|
}
|
||||||
|
return activeUpstreams[i].OriginIpOrDomain < activeUpstreams[j].OriginIpOrDomain
|
||||||
|
})
|
||||||
|
|
||||||
|
sort.Slice(inactiveUpstreams, func(i, j int) bool {
|
||||||
|
if inactiveUpstreams[i].Weight != inactiveUpstreams[j].Weight {
|
||||||
|
return inactiveUpstreams[i].Weight > inactiveUpstreams[j].Weight
|
||||||
|
}
|
||||||
|
return inactiveUpstreams[i].OriginIpOrDomain < inactiveUpstreams[j].OriginIpOrDomain
|
||||||
|
})
|
||||||
|
|
||||||
|
type UpstreamCombinedList struct {
|
||||||
|
ActiveOrigins []*loadbalance.Upstream
|
||||||
|
InactiveOrigins []*loadbalance.Upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(UpstreamCombinedList{
|
||||||
|
ActiveOrigins: activeUpstreams,
|
||||||
|
InactiveOrigins: inactiveUpstreams,
|
||||||
|
})
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an upstream to a given proxy upstream endpoint
|
||||||
|
func ReverseProxyUpstreamAdd(w http.ResponseWriter, r *http.Request) {
|
||||||
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target endpoint not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
upstreamOrigin, err := utils.PostPara(r, "origin")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "upstream origin not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
requireTLS, _ := utils.PostBool(r, "tls")
|
||||||
|
skipTlsValidation, _ := utils.PostBool(r, "tlsval")
|
||||||
|
bpwsorg, _ := utils.PostBool(r, "bpwsorg")
|
||||||
|
preactivate, _ := utils.PostBool(r, "active")
|
||||||
|
|
||||||
|
//Create a new upstream object
|
||||||
|
newUpstream := loadbalance.Upstream{
|
||||||
|
OriginIpOrDomain: upstreamOrigin,
|
||||||
|
RequireTLS: requireTLS,
|
||||||
|
SkipCertValidations: skipTlsValidation,
|
||||||
|
SkipWebSocketOriginCheck: bpwsorg,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add the new upstream to endpoint
|
||||||
|
err = targetEndpoint.AddUpstreamOrigin(&newUpstream, preactivate)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Save changes to configs
|
||||||
|
err = SaveReverseProxyConfig(targetEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("INFO", "Unable to save new upstream to proxy config", err)
|
||||||
|
utils.SendErrorResponse(w, "Failed to save new upstream config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update Uptime Monitor
|
||||||
|
UpdateUptimeMonitorTargets()
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the connection configuration of this origin
|
||||||
|
// pass in the whole new upstream origin json via "payload" POST variable
|
||||||
|
// for missing fields, original value will be used instead
|
||||||
|
func ReverseProxyUpstreamUpdate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target endpoint not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Editing upstream origin IP
|
||||||
|
originIP, err := utils.PostPara(r, "origin")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "origin ip or matching address not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originIP = strings.TrimSpace(originIP)
|
||||||
|
|
||||||
|
//Update content payload
|
||||||
|
payload, err := utils.PostPara(r, "payload")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "update payload not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive, _ := utils.PostBool(r, "active")
|
||||||
|
|
||||||
|
targetUpstream, err := targetEndpoint.GetUpstreamOriginByMatchingIP(originIP)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Deep copy the upstream so other request handling goroutine won't be effected
|
||||||
|
newUpstream := targetUpstream.Clone()
|
||||||
|
|
||||||
|
//Overwrite the new value into the old upstream
|
||||||
|
err = json.Unmarshal([]byte(payload), &newUpstream)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Replace the old upstream with the new one
|
||||||
|
err = targetEndpoint.RemoveUpstreamOrigin(originIP)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = targetEndpoint.AddUpstreamOrigin(newUpstream, isActive)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Save changes to configs
|
||||||
|
err = SaveReverseProxyConfig(targetEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("INFO", "Unable to save upstream update to proxy config", err)
|
||||||
|
utils.SendErrorResponse(w, "Failed to save updated upstream config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update Uptime Monitor
|
||||||
|
UpdateUptimeMonitorTargets()
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReverseProxyUpstreamSetPriority(w http.ResponseWriter, r *http.Request) {
|
||||||
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target endpoint not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
weight, err := utils.PostInt(r, "weight")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "priority not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if weight < 0 {
|
||||||
|
utils.SendErrorResponse(w, "invalid weight given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Editing upstream origin IP
|
||||||
|
originIP, err := utils.PostPara(r, "origin")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "origin ip or matching address not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originIP = strings.TrimSpace(originIP)
|
||||||
|
|
||||||
|
editingUpstream, err := targetEndpoint.GetUpstreamOriginByMatchingIP(originIP)
|
||||||
|
editingUpstream.Weight = weight
|
||||||
|
// The editing upstream is a pointer to the runtime object
|
||||||
|
// and the change of weight do not requre a respawn of the proxy object
|
||||||
|
// so no need to remove & re-prepare the upstream on weight changes
|
||||||
|
|
||||||
|
err = SaveReverseProxyConfig(targetEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("INFO", "Unable to update upstream weight", err)
|
||||||
|
utils.SendErrorResponse(w, "Failed to update upstream weight")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReverseProxyUpstreamDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target endpoint not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Editing upstream origin IP
|
||||||
|
originIP, err := utils.PostPara(r, "origin")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "origin ip or matching address not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originIP = strings.TrimSpace(originIP)
|
||||||
|
|
||||||
|
if !targetEndpoint.UpstreamOriginExists(originIP) {
|
||||||
|
utils.SendErrorResponse(w, "target upstream not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = targetEndpoint.RemoveUpstreamOrigin(originIP)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//Save changes to configs
|
||||||
|
err = SaveReverseProxyConfig(targetEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("INFO", "Unable to remove upstream", err)
|
||||||
|
utils.SendErrorResponse(w, "Failed to remove upstream from proxy rule")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update uptime monitor
|
||||||
|
UpdateUptimeMonitorTargets()
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
@ -28,7 +28,7 @@ func ReverseProxyListVdir(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
var targetEndpoint *dynamicproxy.ProxyEndpoint
|
var targetEndpoint *dynamicproxy.ProxyEndpoint
|
||||||
if eptype == "host" {
|
if eptype == "host" {
|
||||||
endpoint, err := utils.PostPara(r, "ep") //Support root and host
|
endpoint, err := utils.PostPara(r, "ep")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "endpoint not defined")
|
utils.SendErrorResponse(w, "endpoint not defined")
|
||||||
return
|
return
|
||||||
|
@ -358,10 +358,10 @@
|
|||||||
let isExpired = entry.RemainingDays <= 0;
|
let isExpired = entry.RemainingDays <= 0;
|
||||||
|
|
||||||
$("#certifiedDomainList").append(`<tr>
|
$("#certifiedDomainList").append(`<tr>
|
||||||
<td>${entry.Domain}</td>
|
<td><a style="cursor: pointer;" title="Download certificate" onclick="handleCertDownload('${entry.Domain}');">${entry.Domain}</a></td>
|
||||||
<td>${entry.LastModifiedDate}</td>
|
<td>${entry.LastModifiedDate}</td>
|
||||||
<td class="${isExpired?"expired":"valid"} certdate">${entry.ExpireDate} (${!isExpired?entry.RemainingDays+" days left":"Expired"})</td>
|
<td class="${isExpired?"expired":"valid"} certdate">${entry.ExpireDate} (${!isExpired?entry.RemainingDays+" days left":"Expired"})</td>
|
||||||
<td><i class="${entry.UseDNS?"green check": "red times"} circle outline icon"></i></td>
|
<td><i class="${entry.UseDNS?"green check": "red times"} icon"></i></td>
|
||||||
<td><button title="Renew Certificate" class="ui mini basic icon button renewButton" onclick="renewCertificate('${entry.Domain}', '${entry.UseDNS}', this);"><i class="ui green refresh icon"></i></button></td>
|
<td><button title="Renew Certificate" class="ui mini basic icon button renewButton" onclick="renewCertificate('${entry.Domain}', '${entry.UseDNS}', this);"><i class="ui green refresh icon"></i></button></td>
|
||||||
<td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
|
<td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
|
||||||
</tr>`);
|
</tr>`);
|
||||||
@ -397,6 +397,19 @@
|
|||||||
initManagedDomainCertificateList();
|
initManagedDomainCertificateList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCertDownload(certName){
|
||||||
|
$.get("/api/cert/download?seek=true&certname=" + certName, function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
//Error resolving certificate
|
||||||
|
msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
//Continue to download
|
||||||
|
window.open("/api/cert/download?certname=" + certName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//Handle domain keys upload
|
//Handle domain keys upload
|
||||||
function handleDomainKeysUpload(callback=undefined){
|
function handleDomainKeysUpload(callback=undefined){
|
||||||
let domain = $("#certdomain").val();
|
let domain = $("#certdomain").val();
|
||||||
|
@ -51,15 +51,31 @@
|
|||||||
//Sort by RootOrMatchingDomain field
|
//Sort by RootOrMatchingDomain field
|
||||||
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
|
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
|
||||||
data.forEach(subd => {
|
data.forEach(subd => {
|
||||||
let tlsIcon = "";
|
|
||||||
let subdData = encodeURIComponent(JSON.stringify(subd));
|
let subdData = encodeURIComponent(JSON.stringify(subd));
|
||||||
if (subd.RequireTLS){
|
|
||||||
|
//Build the upstream list
|
||||||
|
let upstreams = "";
|
||||||
|
if (subd.ActiveOrigins.length == 0){
|
||||||
|
//Invalid config
|
||||||
|
upstreams = `<i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin<br>`;
|
||||||
|
}else{
|
||||||
|
subd.ActiveOrigins.forEach(upstream => {
|
||||||
|
console.log(upstream);
|
||||||
|
//Check if the upstreams require TLS connections
|
||||||
|
let tlsIcon = "";
|
||||||
|
if (upstream.RequireTLS){
|
||||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||||
if (subd.SkipCertValidations){
|
if (upstream.SkipCertValidations){
|
||||||
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
|
||||||
|
|
||||||
|
upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let inboundTlsIcon = "";
|
let inboundTlsIcon = "";
|
||||||
if ($("#tls").checkbox("is checked")){
|
if ($("#tls").checkbox("is checked")){
|
||||||
inboundTlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
inboundTlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||||
@ -102,7 +118,11 @@
|
|||||||
${aliasDomains}
|
${aliasDomains}
|
||||||
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
|
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
|
||||||
</td>
|
</td>
|
||||||
<td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
|
<td data-label="" editable="true" datatype="domain">
|
||||||
|
<div class="upstreamList">
|
||||||
|
${upstreams}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
||||||
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
||||||
${subd.RequireBasicAuth?`<i class="ui green check icon"></i> Basic Auth`:``}
|
${subd.RequireBasicAuth?`<i class="ui green check icon"></i> Basic Auth`:``}
|
||||||
@ -214,7 +234,6 @@
|
|||||||
var payload = $(row).attr("payload");
|
var payload = $(row).attr("payload");
|
||||||
payload = JSON.parse(decodeURIComponent(payload));
|
payload = JSON.parse(decodeURIComponent(payload));
|
||||||
console.log(payload);
|
console.log(payload);
|
||||||
//console.log(payload);
|
|
||||||
columns.each(function(index) {
|
columns.each(function(index) {
|
||||||
var column = $(this);
|
var column = $(this);
|
||||||
var oldValue = column.text().trim();
|
var oldValue = column.text().trim();
|
||||||
@ -228,39 +247,21 @@
|
|||||||
var input;
|
var input;
|
||||||
var datatype = $(this).attr("datatype");
|
var datatype = $(this).attr("datatype");
|
||||||
if (datatype == "domain"){
|
if (datatype == "domain"){
|
||||||
let domain = payload.Domain;
|
let useStickySessionChecked = "";
|
||||||
//Target require TLS for proxying
|
if (payload.UseStickySession){
|
||||||
let tls = payload.RequireTLS;
|
useStickySessionChecked = "checked";
|
||||||
if (tls){
|
|
||||||
tls = "checked";
|
|
||||||
}else{
|
|
||||||
tls = "";
|
|
||||||
}
|
}
|
||||||
|
input = `<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 1em;" onclick="editUpstreams('${uuid}');"><i class="grey server icon"></i> Edit Upstreams</button>
|
||||||
//Require TLS validation
|
<div class="ui divider"></div>
|
||||||
let skipTLSValidation = payload.SkipCertValidations;
|
|
||||||
let checkstate = "";
|
|
||||||
if (skipTLSValidation){
|
|
||||||
checkstate = "checked";
|
|
||||||
}
|
|
||||||
|
|
||||||
input = `
|
|
||||||
<div class="ui mini fluid input">
|
|
||||||
<input type="text" class="Domain" onchange="cleanProxyTargetValue(this)" value="${domain}">
|
|
||||||
</div>
|
|
||||||
<div class="ui checkbox" style="margin-top: 0.6em;">
|
|
||||||
<input type="checkbox" class="RequireTLS" ${tls}>
|
|
||||||
<label>Require TLS<br>
|
|
||||||
<small>Proxy target require HTTPS connection</small></label>
|
|
||||||
</div><br>
|
|
||||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
<input type="checkbox" class="SkipCertValidations" ${checkstate}>
|
<input type="checkbox" class="UseStickySession" ${useStickySessionChecked}>
|
||||||
<label>Skip Verification<br>
|
<label>Use Sticky Session<br>
|
||||||
<small>Check this if proxy target is using self signed certificates</small></label>
|
<small>Enable stick session on load balancing</small></label>
|
||||||
</div><br>
|
</div>
|
||||||
<!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="purple server icon"></i> Load Balance</button> -->
|
|
||||||
`;
|
`;
|
||||||
column.empty().append(input);
|
column.append(input);
|
||||||
|
$(column).find(".upstreamList").addClass("editing");
|
||||||
}else if (datatype == "vdir"){
|
}else if (datatype == "vdir"){
|
||||||
//Append a quick access button for vdir page
|
//Append a quick access button for vdir page
|
||||||
column.append(`<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="quickEditVdir('${uuid}');">
|
column.append(`<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="quickEditVdir('${uuid}');">
|
||||||
@ -311,12 +312,6 @@
|
|||||||
Security Options
|
Security Options
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
|
||||||
<input type="checkbox" class="SkipWebSocketOriginCheck" ${wsCheckstate}>
|
|
||||||
<label>Skip WebSocket Origin Check<br>
|
|
||||||
<small>Check this to allow cross-origin websocket requests</small></label>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
|
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
|
||||||
<label>Require Rate Limit<br>
|
<label>Require Rate Limit<br>
|
||||||
@ -399,15 +394,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var epttype = "host";
|
var epttype = "host";
|
||||||
let newDomain = $(row).find(".Domain").val();
|
let useStickySession = $(row).find(".UseStickySession")[0].checked;
|
||||||
let requireTLS = $(row).find(".RequireTLS")[0].checked;
|
|
||||||
let skipCertValidations = $(row).find(".SkipCertValidations")[0].checked;
|
|
||||||
let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
|
let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
|
||||||
let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
|
let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
|
||||||
let rateLimit = $(row).find(".RateLimit").val();
|
let rateLimit = $(row).find(".RateLimit").val();
|
||||||
let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
|
let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
|
||||||
let bypassWebsocketOrigin = $(row).find(".SkipWebSocketOriginCheck")[0].checked;
|
|
||||||
console.log(newDomain, requireTLS, skipCertValidations, requireBasicAuth)
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/api/proxy/edit",
|
url: "/api/proxy/edit",
|
||||||
@ -415,11 +406,8 @@
|
|||||||
data: {
|
data: {
|
||||||
"type": epttype,
|
"type": epttype,
|
||||||
"rootname": uuid,
|
"rootname": uuid,
|
||||||
"ep":newDomain,
|
"ss":useStickySession,
|
||||||
"bpgtls": bypassGlobalTLS,
|
"bpgtls": bypassGlobalTLS,
|
||||||
"tls" :requireTLS,
|
|
||||||
"tlsval": skipCertValidations,
|
|
||||||
"bpwsorg" : bypassWebsocketOrigin,
|
|
||||||
"bauth" :requireBasicAuth,
|
"bauth" :requireBasicAuth,
|
||||||
"rate" :requireRateLimit,
|
"rate" :requireRateLimit,
|
||||||
"ratenum" :rateLimit,
|
"ratenum" :rateLimit,
|
||||||
@ -435,21 +423,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
|
||||||
//and auto select TLS checkbox if https:// exists
|
|
||||||
function cleanProxyTargetValue(input){
|
|
||||||
let targetDomain = $(input).val().trim();
|
|
||||||
if (targetDomain.startsWith("http://")){
|
|
||||||
targetDomain = targetDomain.substr(7);
|
|
||||||
$(input).val(targetDomain);
|
|
||||||
$("#httpProxyList input.RequireTLS").parent().checkbox("set unchecked");
|
|
||||||
}else if (targetDomain.startsWith("https://")){
|
|
||||||
targetDomain = targetDomain.substr(8);
|
|
||||||
$(input).val(targetDomain);
|
|
||||||
$("#httpProxyList input.RequireTLS").parent().checkbox("set checked");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* button events */
|
/* button events */
|
||||||
function editBasicAuthCredentials(uuid){
|
function editBasicAuthCredentials(uuid){
|
||||||
let payload = encodeURIComponent(JSON.stringify({
|
let payload = encodeURIComponent(JSON.stringify({
|
||||||
@ -490,12 +463,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Open the load balance option
|
//Open the load balance option
|
||||||
function editLoadBalanceOptions(uuid){
|
function editUpstreams(uuid){
|
||||||
let payload = encodeURIComponent(JSON.stringify({
|
let payload = encodeURIComponent(JSON.stringify({
|
||||||
ept: "host",
|
ept: "host",
|
||||||
ep: uuid
|
ep: uuid
|
||||||
}));
|
}));
|
||||||
showSideWrapper("snippet/loadBalancer.html?t=" + Date.now() + "#" + payload);
|
showSideWrapper("snippet/upstreams.html?t=" + Date.now() + "#" + payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleProxyRuleToggle(object){
|
function handleProxyRuleToggle(object){
|
||||||
|
@ -122,7 +122,7 @@
|
|||||||
function initRootInfo(callback=undefined){
|
function initRootInfo(callback=undefined){
|
||||||
$.get("/api/proxy/list?type=root", function(data){
|
$.get("/api/proxy/list?type=root", function(data){
|
||||||
if (data == null){
|
if (data == null){
|
||||||
|
msgbox("Default site load failed", false);
|
||||||
}else{
|
}else{
|
||||||
var $radios = $('input:radio[name=defaultsiteOption]');
|
var $radios = $('input:radio[name=defaultsiteOption]');
|
||||||
let proxyType = data.DefaultSiteOption;
|
let proxyType = data.DefaultSiteOption;
|
||||||
@ -140,8 +140,8 @@
|
|||||||
}
|
}
|
||||||
updateAvaibleDefaultSiteOptions();
|
updateAvaibleDefaultSiteOptions();
|
||||||
|
|
||||||
$("#proxyRoot").val(data.Domain);
|
$("#proxyRoot").val(data.ActiveOrigins[0].OriginIpOrDomain);
|
||||||
checkRootRequireTLS(data.Domain);
|
checkRootRequireTLS(data.ActiveOrigins[0].OriginIpOrDomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callback != undefined){
|
if (callback != undefined){
|
||||||
@ -247,7 +247,9 @@
|
|||||||
msgbox(data.error, false, 5000);
|
msgbox(data.error, false, 5000);
|
||||||
}else{
|
}else{
|
||||||
//OK
|
//OK
|
||||||
|
|
||||||
initRootInfo(function(){
|
initRootInfo(function(){
|
||||||
|
|
||||||
//Check if WebServ is enabled
|
//Check if WebServ is enabled
|
||||||
isUsingStaticWebServerAsRoot(function(isUsingWebServ){
|
isUsingStaticWebServerAsRoot(function(isUsingWebServ){
|
||||||
if (isUsingWebServ){
|
if (isUsingWebServ){
|
||||||
@ -256,11 +258,7 @@
|
|||||||
setWebServerRunningState(true);
|
setWebServerRunningState(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(function(){
|
|
||||||
//Update the checkbox
|
|
||||||
msgbox("Default Site Updated");
|
msgbox("Default Site Updated");
|
||||||
}, 100);
|
|
||||||
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -269,6 +267,9 @@
|
|||||||
if (btn != undefined){
|
if (btn != undefined){
|
||||||
$(btn).removeClass("disabled");
|
$(btn).removeClass("disabled");
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
error: function(){
|
||||||
|
msgbox("Unknown error occured", false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,6 +11,17 @@
|
|||||||
border-radius: 0.6em;
|
border-radius: 0.6em;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.descheader{
|
||||||
|
display:none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1367px) {
|
||||||
|
.descheader{
|
||||||
|
display:auto !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="standardContainer">
|
<div class="standardContainer">
|
||||||
<div class="ui stackable grid">
|
<div class="ui stackable grid">
|
||||||
@ -26,8 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Target IP Address or Domain Name with port</label>
|
<label>Target IP Address or Domain Name with port</label>
|
||||||
<input type="text" id="proxyDomain" onchange="autoCheckTls(this.value);">
|
<input type="text" id="proxyDomain" onchange="autoFillTargetTLS(this);">
|
||||||
<small>E.g. 192.168.0.101:8000 or example.com</small>
|
<small>e.g. 192.168.0.101:8000 or example.com</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="field dockerOptimizations" style="display:none;">
|
<div class="field dockerOptimizations" style="display:none;">
|
||||||
<button style="margin-top: -2em;" class="ui basic small button" onclick="openDockerContainersList();"><i class="blue docker icon"></i> Pick from Docker Containers</button>
|
<button style="margin-top: -2em;" class="ui basic small button" onclick="openDockerContainersList();"><i class="blue docker icon"></i> Pick from Docker Containers</button>
|
||||||
@ -47,16 +58,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Access Rule</label>
|
<div class="ui checkbox">
|
||||||
<div class="ui selection dropdown">
|
<input type="checkbox" id="useStickySessionLB">
|
||||||
<input type="hidden" id="newProxyRuleAccessFilter" value="default">
|
<label>Sticky Session<br><small>Enable stick session on upstream load balancing</small></label>
|
||||||
<i class="dropdown icon"></i>
|
|
||||||
<div class="default text">Default</div>
|
|
||||||
<div class="menu" id="newProxyRuleAccessList">
|
|
||||||
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small>Allow regional access control using blacklist or whitelist. Use "default" for "allow all".</small>
|
<div class="ui horizontal divider">
|
||||||
|
<i class="ui green lock icon"></i>
|
||||||
|
Security
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
@ -76,21 +85,21 @@
|
|||||||
<label>Allow plain HTTP access<br><small>Allow this subdomain to be connected without TLS (Require HTTP server enabled on port 80)</small></label>
|
<label>Allow plain HTTP access<br><small>Allow this subdomain to be connected without TLS (Require HTTP server enabled on port 80)</small></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="ui horizontal divider">
|
||||||
<div class="ui checkbox">
|
<i class="ui red ban icon"></i>
|
||||||
<input type="checkbox" id="requireRateLimit">
|
Access Control
|
||||||
<label>Require Rate Limit<br><small>This proxy endpoint will be rate limited.</small></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Rate Limit</label>
|
<label>Access Rule</label>
|
||||||
<div class="ui fluid right labeled input">
|
<div class="ui selection dropdown">
|
||||||
<input type="number" id="proxyRateLimit" placeholder="100" min="1" max="1000" value="100">
|
<input type="hidden" id="newProxyRuleAccessFilter" value="default">
|
||||||
<div class="ui basic label">
|
<i class="dropdown icon"></i>
|
||||||
req / sec / IP
|
<div class="default text">Default</div>
|
||||||
|
<div class="menu" id="newProxyRuleAccessList">
|
||||||
|
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small>Return a 429 error code if request rate exceed the rate limit.</small>
|
<small>Allow regional access control using blacklist or whitelist. Use "default" for "allow all".</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
@ -125,6 +134,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" id="requireRateLimit">
|
||||||
|
<label>Require Rate Limit<br><small>This proxy endpoint will be rate limited.</small></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Rate Limit</label>
|
||||||
|
<div class="ui fluid right labeled input">
|
||||||
|
<input type="number" id="proxyRateLimit" placeholder="100" min="1" max="1000" value="100">
|
||||||
|
<div class="ui basic label">
|
||||||
|
req / sec / IP
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small>Return a 429 error code if request rate exceed the rate limit.</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -160,16 +185,17 @@
|
|||||||
|
|
||||||
//New Proxy Endpoint
|
//New Proxy Endpoint
|
||||||
function newProxyEndpoint(){
|
function newProxyEndpoint(){
|
||||||
var rootname = $("#rootname").val();
|
let rootname = $("#rootname").val();
|
||||||
var proxyDomain = $("#proxyDomain").val();
|
let proxyDomain = $("#proxyDomain").val();
|
||||||
var useTLS = $("#reqTls")[0].checked;
|
let useTLS = $("#reqTls")[0].checked;
|
||||||
var skipTLSValidation = $("#skipTLSValidation")[0].checked;
|
let skipTLSValidation = $("#skipTLSValidation")[0].checked;
|
||||||
var bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked;
|
let bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked;
|
||||||
var requireBasicAuth = $("#requireBasicAuth")[0].checked;
|
let requireBasicAuth = $("#requireBasicAuth")[0].checked;
|
||||||
var proxyRateLimit = $("#proxyRateLimit").val();
|
let proxyRateLimit = $("#proxyRateLimit").val();
|
||||||
var requireRateLimit = $("#requireRateLimit")[0].checked;
|
let requireRateLimit = $("#requireRateLimit")[0].checked;
|
||||||
var skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked;
|
let skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked;
|
||||||
var accessRuleToUse = $("#newProxyRuleAccessFilter").val();
|
let accessRuleToUse = $("#newProxyRuleAccessFilter").val();
|
||||||
|
let useStickySessionLB = $("#useStickySessionLB")[0].checked;
|
||||||
|
|
||||||
if (rootname.trim() == ""){
|
if (rootname.trim() == ""){
|
||||||
$("#rootname").parent().addClass("error");
|
$("#rootname").parent().addClass("error");
|
||||||
@ -201,6 +227,7 @@
|
|||||||
ratenum: proxyRateLimit,
|
ratenum: proxyRateLimit,
|
||||||
cred: JSON.stringify(credentials),
|
cred: JSON.stringify(credentials),
|
||||||
access: accessRuleToUse,
|
access: accessRuleToUse,
|
||||||
|
stickysess: useStickySessionLB,
|
||||||
},
|
},
|
||||||
success: function(data){
|
success: function(data){
|
||||||
if (data.error != undefined){
|
if (data.error != undefined){
|
||||||
@ -259,7 +286,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
||||||
|
//and auto select TLS checkbox if https:// exists
|
||||||
|
function autoFillTargetTLS(input){
|
||||||
|
let targetDomain = $(input).val().trim();
|
||||||
|
if (targetDomain.startsWith("http://")){
|
||||||
|
targetDomain = targetDomain.substr(7);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$("#reqTls").parent().checkbox("set unchecked");
|
||||||
|
}else if (targetDomain.startsWith("https://")){
|
||||||
|
targetDomain = targetDomain.substr(8);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$("#reqTls").parent().checkbox("set checked");
|
||||||
|
}else{
|
||||||
|
//No http or https was given. Sniff it
|
||||||
|
autoCheckTls(targetDomain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Automatic check if the site require TLS and check the checkbox if needed
|
||||||
function autoCheckTls(targetDomain){
|
function autoCheckTls(targetDomain){
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/api/proxy/tlscheck",
|
url: "/api/proxy/tlscheck",
|
||||||
@ -453,7 +499,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* UI Element Initialization */
|
/* UI Element Initialization */
|
||||||
|
function initAdvanceSettingsAccordion(){
|
||||||
|
function hasClickEvent(element) {
|
||||||
|
var events = $._data(element, "events");
|
||||||
|
return events && events.click && events.click.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasClickEvent($("#advanceProxyRules"))){
|
||||||
|
// Not sure why sometime the accordion events are not binding
|
||||||
|
// to the DOM element. This makes sure the element is binded
|
||||||
|
// correctly by checking it again after 300ms
|
||||||
$("#advanceProxyRules").accordion();
|
$("#advanceProxyRules").accordion();
|
||||||
$("#newProxyRuleAccessFilter").parent().dropdown();
|
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||||
|
setTimeout(function(){
|
||||||
|
initAdvanceSettingsAccordion();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initAdvanceSettingsAccordion();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
@ -1,7 +1,7 @@
|
|||||||
<div class="standardContainer">
|
<div class="standardContainer">
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
<h2>Service Expose Proxy</h2>
|
<h2>Single-Sign-On</h2>
|
||||||
<p>Expose your local test-site on the internet with single command</p>
|
<p>Create and manage accounts with Zoraxy!</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui message">
|
<div class="ui message">
|
||||||
<h4>Work In Progress</h4>
|
<h4>Work In Progress</h4>
|
@ -120,7 +120,12 @@
|
|||||||
<!-- Config Tools -->
|
<!-- Config Tools -->
|
||||||
<h3>System Backup & Restore</h3>
|
<h3>System Backup & Restore</h3>
|
||||||
<p>Options related to system backup, migrate and restore.</p>
|
<p>Options related to system backup, migrate and restore.</p>
|
||||||
<button class="ui basic button" onclick="showSideWrapper('snippet/configTools.html');">Open Config Tools</button>
|
<button class="ui basic button" onclick="showSideWrapper('snippet/configTools.html');"><i class="ui green undo icon icon"></i> Open Config Tools</button>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<!-- Log Viewer -->
|
||||||
|
<h3>System Log Viewer</h3>
|
||||||
|
<p>View and download Zoraxy log</p>
|
||||||
|
<button class="ui basic button" onclick="launchToolWithSize('snippet/logview.html', 1024, 768);"><i class="ui blue file icon"></i> Open Log Viewer</button>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<!-- System Information -->
|
<!-- System Information -->
|
||||||
<div id="zoraxyinfo">
|
<div id="zoraxyinfo">
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
<title>Control Panel | Zoraxy</title>
|
<title>Control Panel | Zoraxy</title>
|
||||||
<link rel="stylesheet" href="script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="script/semantic/semantic.min.css">
|
||||||
<script src="script/jquery-3.6.0.min.js"></script>
|
<script src="script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/ao_module.js"></script>
|
|
||||||
<script src="script/semantic/semantic.min.js"></script>
|
<script src="script/semantic/semantic.min.js"></script>
|
||||||
<script src="script/tablesort.js"></script>
|
<script src="script/tablesort.js"></script>
|
||||||
<script src="script/countryCode.js"></script>
|
<script src="script/countryCode.js"></script>
|
||||||
@ -56,21 +55,21 @@
|
|||||||
<i class="simplistic exchange icon"></i> Stream Proxy
|
<i class="simplistic exchange icon"></i> Stream Proxy
|
||||||
</a>
|
</a>
|
||||||
<div class="ui divider menudivider">Access & Connections</div>
|
<div class="ui divider menudivider">Access & Connections</div>
|
||||||
<a class="item" tag="cert">
|
|
||||||
<i class="simplistic lock icon"></i> TLS / SSL certificates
|
|
||||||
</a>
|
|
||||||
<a class="item" tag="redirectset">
|
<a class="item" tag="redirectset">
|
||||||
<i class="simplistic level up alternate icon"></i> Redirection
|
<i class="simplistic level up alternate icon"></i> Redirection
|
||||||
</a>
|
</a>
|
||||||
<a class="item" tag="access">
|
<a class="item" tag="access">
|
||||||
<i class="simplistic ban icon"></i> Access Control
|
<i class="simplistic ban icon"></i> Access Control
|
||||||
</a>
|
</a>
|
||||||
<div class="ui divider menudivider">Bridging</div>
|
|
||||||
<a class="item" tag="gan">
|
<a class="item" tag="gan">
|
||||||
<i class="simplistic globe icon"></i> Global Area Network
|
<i class="simplistic globe icon"></i> Global Area Network
|
||||||
</a>
|
</a>
|
||||||
<a class="item" tag="zgrok">
|
<div class="ui divider menudivider">Security</div>
|
||||||
<i class="simplistic podcast icon"></i> Service Expose Proxy
|
<a class="item" tag="cert">
|
||||||
|
<i class="simplistic lock icon"></i> TLS / SSL certificates
|
||||||
|
</a>
|
||||||
|
<a class="item" tag="sso">
|
||||||
|
<i class="simplistic user circle icon"></i> SSO / Oauth
|
||||||
</a>
|
</a>
|
||||||
<div class="ui divider menudivider">Others</div>
|
<div class="ui divider menudivider">Others</div>
|
||||||
<a class="item" tag="webserv">
|
<a class="item" tag="webserv">
|
||||||
@ -121,8 +120,8 @@
|
|||||||
<!-- Global Area Networking -->
|
<!-- Global Area Networking -->
|
||||||
<div id="gan" class="functiontab" target="gan.html"></div>
|
<div id="gan" class="functiontab" target="gan.html"></div>
|
||||||
|
|
||||||
<!-- Service Expose Proxy -->
|
<!-- SSO / Oauth services -->
|
||||||
<div id="zgrok" class="functiontab" target="zgrok.html"></div>
|
<div id="sso" class="functiontab" target="sso.html"></div>
|
||||||
|
|
||||||
<!-- TCP Proxy -->
|
<!-- TCP Proxy -->
|
||||||
<div id="streamproxy" class="functiontab" target="streamprox.html"></div>
|
<div id="streamproxy" class="functiontab" target="streamprox.html"></div>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
@ -133,11 +134,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field dnsChallengeOnly" style="display:none;">
|
<div class="field dnsChallengeOnly" style="display:none;">
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<p>DNS Credentials (Leave all fields empty to use previous settings)<br>
|
<p>DNS Credentials</p>
|
||||||
<small><i class="yellow exclamation triangle icon"></i> Note that domain DNS credentials are stored separately. For each new subdomain, you will need to enter a new DNS credentials.</small></p>
|
|
||||||
<div id="dnsProviderAPIFields">
|
<div id="dnsProviderAPIFields">
|
||||||
|
<p><i class="ui loading circle notch icon"></i> Generating WebForm</p>
|
||||||
</div>
|
</div>
|
||||||
|
<h4><i class="yellow exclamation triangle icon"></i> Notes & FAQ</h4>
|
||||||
|
<div class="ui bulleted list">
|
||||||
|
<div class="item">Domain DNS credentials are stored separately. For each new subdomain, you will need to enter a new DNS credentials.</div>
|
||||||
|
<div class="item">For some DNS providers like CloudFlare, you do not need to fill in all fields.</div>
|
||||||
|
<div class="item">If you are not sure what to fill in, check out the documentation from <a href="https://go-acme.github.io/lego/dns/" target="_blank">lego (DNS challenge library)</a></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<label>Credentials File Content</label>
|
<label>Credentials File Content</label>
|
||||||
<textarea id="dnsCredentials" placeholder=""></textarea>
|
<textarea id="dnsCredentials" placeholder=""></textarea>
|
||||||
@ -740,6 +747,9 @@
|
|||||||
function toggleDnsChallenge(){
|
function toggleDnsChallenge(){
|
||||||
if ( $("#useDnsChallenge")[0].checked){
|
if ( $("#useDnsChallenge")[0].checked){
|
||||||
$(".dnsChallengeOnly").show();
|
$(".dnsChallengeOnly").show();
|
||||||
|
setTimeout(function(){
|
||||||
|
$("#dnsProvider").dropdown("set text", "Cloudflare");
|
||||||
|
}, 500);
|
||||||
}else{
|
}else{
|
||||||
$(".dnsChallengeOnly").hide();
|
$(".dnsChallengeOnly").hide();
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
@ -51,8 +52,8 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p>
|
<p>
|
||||||
<i class="angle double right blue icon"></i> Sent additional custom headers to origin server <br>
|
<i class="angle double right blue icon"></i> Add or remove headers before sending to origin server <br>
|
||||||
<i class="angle double left orange icon"></i> Inject custom headers into origin server responses
|
<i class="angle double left orange icon"></i> Modify headers from origin server responses before sending to client
|
||||||
</p>
|
</p>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<h4>Edit Custom Header</h4>
|
<h4>Edit Custom Header</h4>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css" />
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css" />
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Notes: This should be open in its original path-->
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<!-- Notes: This should be open in its original path-->
|
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<br>
|
|
||||||
<div class="ui container">
|
|
||||||
<div class="ui header">
|
|
||||||
<div class="content">
|
|
||||||
Load Balance
|
|
||||||
<div class="sub header epname"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
<div class="field" >
|
|
||||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br><br><br><br>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
let aliasList = [];
|
|
||||||
let editingEndpoint = {};
|
|
||||||
|
|
||||||
if (window.location.hash.length > 1){
|
|
||||||
let payloadHash = window.location.hash.substr(1);
|
|
||||||
try{
|
|
||||||
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
|
||||||
$(".epname").text(payloadHash.ep);
|
|
||||||
editingEndpoint = payloadHash;
|
|
||||||
}catch(ex){
|
|
||||||
console.log("Unable to load endpoint data from hash")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeThisWrapper(){
|
|
||||||
parent.hideSideWrapper(true);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
155
src/web/snippet/logview.html
Normal file
155
src/web/snippet/logview.html
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html ng-app="App">
|
||||||
|
<head>
|
||||||
|
<title>System Logs</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
|
||||||
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
|
<script type="text/javascript" src="../script/jquery-3.6.0.min.js"></script>
|
||||||
|
<script type="text/javascript" src="../script/semantic/semantic.min.js"></script>
|
||||||
|
<style>
|
||||||
|
.clickable{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.clickable:hover{
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.logfile{
|
||||||
|
padding-left: 1em !important;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loglist{
|
||||||
|
background-color: rgb(250, 250, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfile .showing{
|
||||||
|
position: absolute;
|
||||||
|
top: 0.18em;
|
||||||
|
right: 0em;
|
||||||
|
margin-right: -0.4em;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfile.active .showing{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logrender{
|
||||||
|
width: 100% !important;
|
||||||
|
height: calc(100% - 1.2em);
|
||||||
|
min-height: calc(90vh - 1.2em) !important;
|
||||||
|
border: 0px solid transparent !important;
|
||||||
|
background-color: #252630;
|
||||||
|
color: white;
|
||||||
|
font-family: monospace;
|
||||||
|
overflow-x: scroll !important;
|
||||||
|
white-space: pre;
|
||||||
|
resize: none;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logrender::selection{
|
||||||
|
background:#3643bb;
|
||||||
|
color:white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui stackable grid">
|
||||||
|
<div class="four wide column loglist">
|
||||||
|
<h3 class="ui header" style="padding-top: 1em;">
|
||||||
|
<div class="content">
|
||||||
|
Log View
|
||||||
|
<div class="sub header">Check System Log in Real Time</div>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div id="logList" class="ui accordion">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<small>Notes: Some log files might be huge. Make sure you have checked the log file size before opening</small>
|
||||||
|
</div>
|
||||||
|
<div class="twelve wide column">
|
||||||
|
<textarea id="logrender" spellcheck="false" readonly="true">
|
||||||
|
← Pick a log file from the left menu to start debugging
|
||||||
|
</textarea>
|
||||||
|
<a href="#" onclick="openLogInNewTab();">Open In New Tab</a>
|
||||||
|
<br><br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
var currentOpenedLogURL = "";
|
||||||
|
|
||||||
|
function openLogInNewTab(){
|
||||||
|
if (currentOpenedLogURL != ""){
|
||||||
|
window.open(currentOpenedLogURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openLog(object, catergory, filename){
|
||||||
|
$(".logfile.active").removeClass('active');
|
||||||
|
$(object).addClass("active");
|
||||||
|
currentOpenedLogURL = "/api/log/read?file=" + filename;
|
||||||
|
$.get(currentOpenedLogURL, function(data){
|
||||||
|
if (data.error !== undefined){
|
||||||
|
alert(data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#logrender").val(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initLogList(){
|
||||||
|
$("#logList").html("");
|
||||||
|
$.get("/api/log/list", function(data){
|
||||||
|
//console.log(data);
|
||||||
|
for (let [key, value] of Object.entries(data)) {
|
||||||
|
console.log(key, value);
|
||||||
|
value.reverse(); //Default value was from oldest to newest
|
||||||
|
var fileItemList = "";
|
||||||
|
value.forEach(file => {
|
||||||
|
fileItemList += `<div class="item clickable logfile" onclick="openLog(this, '${key}','${file.Filename}');">
|
||||||
|
<i class="file outline icon"></i>
|
||||||
|
<div class="content">
|
||||||
|
${file.Title} (${formatBytes(file.Filesize)})
|
||||||
|
<div class="showing"><i class="green chevron right icon"></i></div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
})
|
||||||
|
$("#logList").append(`<div class="title">
|
||||||
|
<i class="dropdown icon"></i>
|
||||||
|
${key}
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="ui list">
|
||||||
|
${fileItemList}
|
||||||
|
</div>
|
||||||
|
</div>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".ui.accordion").accordion();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
initLogList();
|
||||||
|
|
||||||
|
|
||||||
|
function formatBytes(x){
|
||||||
|
var units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
|
let l = 0, n = parseInt(x, 10) || 0;
|
||||||
|
while(n >= 1024 && ++l){
|
||||||
|
n = n/1024;
|
||||||
|
}
|
||||||
|
return(n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + units[l]);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</html>
|
@ -1,6 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
<script src="../script/semantic/semantic.min.js"></script>
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
528
src/web/snippet/upstreams.html
Normal file
528
src/web/snippet/upstreams.html
Normal file
@ -0,0 +1,528 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
<style>
|
||||||
|
.upstreamActions{
|
||||||
|
position: absolute;
|
||||||
|
top: 0.6em;
|
||||||
|
right: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upstreamLink{
|
||||||
|
max-width: 220px;
|
||||||
|
display: inline-block;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upstreamEntry .ui.toggle.checkbox input:checked ~ label::before{
|
||||||
|
background-color: #00ca52 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#activateNewUpstream.ui.toggle.checkbox input:checked ~ label::before{
|
||||||
|
background-color: #00ca52 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upstreamTable{
|
||||||
|
max-height: 480px;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
padding: 0.3em;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upstreamEntry.inactive{
|
||||||
|
background-color: #f3f3f3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upstreamEntry{
|
||||||
|
border-radius: 0.4em !important;
|
||||||
|
border: 1px solid rgb(233, 233, 233) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 499px) {
|
||||||
|
.upstreamActions{
|
||||||
|
position: relative;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-left: 0.4em;
|
||||||
|
margin-bottom: 0.4em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui header">
|
||||||
|
<div class="content">
|
||||||
|
Upstreams / Load Balance
|
||||||
|
<div class="sub header epname"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui small pointing secondary menu">
|
||||||
|
<a class="item active narrowpadding" data-tab="upstreamlist">Upstreams</a>
|
||||||
|
<a class="item narrowpadding" data-tab="newupstream">Add Upstream</a>
|
||||||
|
</div>
|
||||||
|
<div class="ui tab basic segment active" data-tab="upstreamlist">
|
||||||
|
<!-- A list of current existing upstream on this reverse proxy-->
|
||||||
|
<div id="upstreamTable">
|
||||||
|
<div class="ui segment">
|
||||||
|
<a><i class="ui loading spinner icon"></i> Loading</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui message">
|
||||||
|
<i class="ui blue info circle icon"></i> Weighted random will be used for load-balancing. Set weight to 0 for fallback only.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui tab basic segment" data-tab="newupstream">
|
||||||
|
<!-- Web Form to create a new upstream -->
|
||||||
|
<h4 class="ui header">
|
||||||
|
<i class="green add icon"></i>
|
||||||
|
<div class="content">
|
||||||
|
Add Upstream Server
|
||||||
|
<div class="sub header">Create new load balance or fallback upstream origin</div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
<p style="margin-bottom: 0.4em;">Target IP address with port</p>
|
||||||
|
<div class="ui fluid small input">
|
||||||
|
<input type="text" id="originURL" onchange="cleanProxyTargetValue(this);"><br>
|
||||||
|
</div>
|
||||||
|
<small>E.g. 192.168.0.101:8000 or example.com</small>
|
||||||
|
<br><br>
|
||||||
|
<div id="activateNewUpstream" class="ui toggle checkbox" style="display:inline-block;">
|
||||||
|
<input type="checkbox" id="activateNewUpstreamCheckbox" style="margin-top: 0.4em;" checked>
|
||||||
|
<label>Activate<br>
|
||||||
|
<small>Enable this upstream for load balancing</small></label>
|
||||||
|
</div><br>
|
||||||
|
<div class="ui checkbox" style="margin-top: 1.2em;">
|
||||||
|
<input type="checkbox" id="requireTLS">
|
||||||
|
<label>Require TLS<br>
|
||||||
|
<small>Proxy target require HTTPS connection</small></label>
|
||||||
|
</div><br>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.6em;">
|
||||||
|
<input type="checkbox" id="skipTlsVerification">
|
||||||
|
<label>Skip Verification<br>
|
||||||
|
<small>Check this if proxy target is using self signed certificates</small></label>
|
||||||
|
</div><br>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
|
<input type="checkbox" id="SkipWebSocketOriginCheck" checked>
|
||||||
|
<label>Skip WebSocket Origin Check<br>
|
||||||
|
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
<button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="field" >
|
||||||
|
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br><br><br><br>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let origins = [];
|
||||||
|
let editingEndpoint = {};
|
||||||
|
$('.menu .item').tab();
|
||||||
|
|
||||||
|
|
||||||
|
function initOriginList(){
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/upstream/list",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
"type":"host",
|
||||||
|
"ep": editingEndpoint.ep
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
//This endpoint not exists?
|
||||||
|
alert(data.error);
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
$("#upstreamTable").html("");
|
||||||
|
|
||||||
|
if (data.ActiveOrigins.length == 0){
|
||||||
|
//There is no upstream for this proxy rule
|
||||||
|
$("#upstreamTable").append(`<tr>
|
||||||
|
<td colspan="2"><b><i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin</b><br>
|
||||||
|
This HTTP proxy rule will always return Error 521 when requested. To fix this, add or enable a upstream origin to this proxy endpoint.
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
</td>
|
||||||
|
</tr>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.ActiveOrigins.forEach(upstream => {
|
||||||
|
renderUpstreamEntryToTable(upstream, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
data.InactiveOrigins.forEach(upstream => {
|
||||||
|
renderUpstreamEntryToTable(upstream, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
let totalUpstreams = data.ActiveOrigins.length + data.InactiveOrigins.length;
|
||||||
|
if (totalUpstreams == 1){
|
||||||
|
$(".lowPriorityButton").addClass('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent && parent.document.getElementById("httpProxyList") != null){
|
||||||
|
//Also update the parent display
|
||||||
|
let element = $(parent.document.getElementById("httpProxyList")).find(".upstreamList.editing");
|
||||||
|
let upstreams = "";
|
||||||
|
data.ActiveOrigins.forEach(upstream => {
|
||||||
|
//Check if the upstreams require TLS connections
|
||||||
|
let tlsIcon = "";
|
||||||
|
if (upstream.RequireTLS){
|
||||||
|
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||||
|
if (upstream.SkipCertValidations){
|
||||||
|
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
|
||||||
|
|
||||||
|
upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.ActiveOrigins.length == 0){
|
||||||
|
upstreams = `<i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin<br>`
|
||||||
|
}
|
||||||
|
$(element).html(upstreams);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".ui.checkbox").checkbox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUpstreamEntryToTable(upstream, isActive){
|
||||||
|
function newUID(){return"00000000-0000-4000-8000-000000000000".replace(/0/g,function(){return(0|Math.random()*16).toString(16)})};
|
||||||
|
let tlsIcon = "";
|
||||||
|
if (upstream.RequireTLS){
|
||||||
|
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||||
|
if (upstream.SkipCertValidations){
|
||||||
|
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Priority Arrows
|
||||||
|
let downArrowClass = "";
|
||||||
|
if (upstream.Weight == 0 ){
|
||||||
|
//Cannot go any lower
|
||||||
|
downArrowClass = "disabled";
|
||||||
|
}
|
||||||
|
let url = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`
|
||||||
|
let payload = encodeURIComponent(JSON.stringify(upstream));
|
||||||
|
let domUID = newUID();
|
||||||
|
$("#upstreamTable").append(`<div class="ui upstreamEntry ${isActive?"":"inactive"} basic segment" data-domid="${domUID}" data-payload="${payload}" data-priority="${upstream.Priority}">
|
||||||
|
<h4 class="ui header">
|
||||||
|
<div class="ui toggle checkbox" style="display:inline-block;">
|
||||||
|
<input type="checkbox" class="enableState" name="enabled" style="margin-top: 0.4em;" onchange="saveUpstreamUpdate('${domUID}');" ${isActive?"checked":""}>
|
||||||
|
<label></label>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<a href="${url}" target="_blank" class="upstreamLink">${upstream.OriginIpOrDomain} ${tlsIcon}</a>
|
||||||
|
<div class="sub header">${isActive?(upstream.Weight==0?"Fallback Only":"Active"):"Inactive"} | Weight: ${upstream.Weight}x </div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
<div class="advanceOptions" style="display:none;">
|
||||||
|
<div class="upstreamOriginField">
|
||||||
|
<p>New upstream origin IP address or domain</p>
|
||||||
|
<div class="ui small fluid input" style="margin-top: -0.6em;">
|
||||||
|
<input type="text" class="newOrigin" value="${upstream.OriginIpOrDomain}" onchange="handleAutoOriginClean('${domUID}');">
|
||||||
|
</div>
|
||||||
|
<small>e.g. 192.168.0.101:8000 or example.com</small>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" class="reqTLSCheckbox" ${upstream.RequireTLS?"checked":""}>
|
||||||
|
<label>Require TLS<br>
|
||||||
|
<small>Proxy target require HTTPS connection</small></label>
|
||||||
|
</div><br>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.6em;">
|
||||||
|
<input type="checkbox" class="skipVerificationCheckbox" ${upstream.SkipCertValidations?"checked":""}>
|
||||||
|
<label>Skip Verification<br>
|
||||||
|
<small>Check this if proxy target is using self signed certificates</small></label>
|
||||||
|
</div><br>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
|
<input type="checkbox" class="SkipWebSocketOriginCheck" ${upstream.SkipWebSocketOriginCheck?"checked":""}>
|
||||||
|
<label>Skip WebSocket Origin Check<br>
|
||||||
|
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||||
|
</div><br>
|
||||||
|
</div>
|
||||||
|
<div class="upstreamActions">
|
||||||
|
<!-- Change Priority -->
|
||||||
|
<button class="ui basic circular icon button highPriorityButton" title="Increase Weight" onclick="increaseUpstreamWeight('${domUID}');"><i class="ui arrow up icon"></i></button>
|
||||||
|
<button class="ui basic circular icon button lowPriorityButton ${downArrowClass}" title="Reduce Weight" onclick="decreaseUpstreamWeight('${domUID}');"><i class="ui arrow down icon"></i></button>
|
||||||
|
<button class="ui basic circular icon editbtn button" onclick="handleUpstreamOriginEdit('${domUID}');" title="Edit Upstream Destination"><i class="ui grey edit icon"></i></button>
|
||||||
|
<button style="display:none;" class="ui basic circular icon trashbtn button" onclick="removeUpstream('${domUID}');" title="Remove Upstream"><i class="ui red trash icon"></i></button>
|
||||||
|
<button style="display:none;" class="ui basic circular icon savebtn button" onclick="saveUpstreamUpdate('${domUID}');" title="Save Changes"><i class="ui green save icon"></i></button>
|
||||||
|
<button style="display:none;" class="ui basic circular icon cancelbtn button" onclick="initOriginList();" title="Cancel"><i class="ui grey times icon"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* New Upstream Origin Functions */
|
||||||
|
|
||||||
|
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
||||||
|
//and auto select TLS checkbox if https:// exists
|
||||||
|
function cleanProxyTargetValue(input){
|
||||||
|
let targetDomain = $(input).val().trim();
|
||||||
|
if (targetDomain.startsWith("http://")){
|
||||||
|
targetDomain = targetDomain.substr(7);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$("#requireTLS").parent().checkbox("set unchecked");
|
||||||
|
}else if (targetDomain.startsWith("https://")){
|
||||||
|
targetDomain = targetDomain.substr(8);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$("#requireTLS").parent().checkbox("set checked");
|
||||||
|
}else{
|
||||||
|
//URL does not contains https or http protocol tag
|
||||||
|
//sniff header
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/tlscheck",
|
||||||
|
data: {url: targetDomain},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
|
||||||
|
}else if (data == "https"){
|
||||||
|
$("#requireTLS").parent().checkbox("set checked");
|
||||||
|
}else if (data == "http"){
|
||||||
|
$("#requireTLS").parent().checkbox("set unchecked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add a new upstream to this http proxy rule
|
||||||
|
function addNewUpstream(){
|
||||||
|
let origin = $("#originURL").val().trim();
|
||||||
|
let requireTLS = $("#requireTLS")[0].checked;
|
||||||
|
let skipVerification = $("#skipTlsVerification")[0].checked;
|
||||||
|
let skipWebSocketOriginCheck = $("#SkipWebSocketOriginCheck")[0].checked;
|
||||||
|
let activateLoadbalancer = $("#activateNewUpstreamCheckbox")[0].checked;
|
||||||
|
|
||||||
|
if (origin == ""){
|
||||||
|
parent.msgbox("Upstream origin cannot be empty", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/upstream/add",
|
||||||
|
method: "POST",
|
||||||
|
data:{
|
||||||
|
"ep": editingEndpoint.ep,
|
||||||
|
"origin": origin,
|
||||||
|
"tls": requireTLS,
|
||||||
|
"tlsval": skipVerification,
|
||||||
|
"bpwsorg":skipWebSocketOriginCheck,
|
||||||
|
"active": activateLoadbalancer,
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
parent.msgbox("New upstream origin added");
|
||||||
|
initOriginList();
|
||||||
|
$("#originURL").val("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get a upstream setting data from DOM element
|
||||||
|
function getUpstreamSettingFromDOM(upstream){
|
||||||
|
//Get the original setting from DOM payload
|
||||||
|
let originalSettings = $(upstream).attr("data-payload");
|
||||||
|
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||||
|
|
||||||
|
//Get the updated settings if any
|
||||||
|
let requireTLS = $(upstream).find(".reqTLSCheckbox")[0].checked;
|
||||||
|
let skipTLSVerification = $(upstream).find(".skipVerificationCheckbox")[0].checked;
|
||||||
|
let skipWebSocketOriginCheck = $(upstream).find(".SkipWebSocketOriginCheck")[0].checked;
|
||||||
|
|
||||||
|
//Update the original setting with new one just applied
|
||||||
|
originalSettings.OriginIpOrDomain = $(upstream).find(".newOrigin").val();
|
||||||
|
originalSettings.RequireTLS = requireTLS;
|
||||||
|
originalSettings.SkipCertValidations = skipTLSVerification;
|
||||||
|
originalSettings.SkipWebSocketOriginCheck = skipWebSocketOriginCheck;
|
||||||
|
|
||||||
|
//console.log(originalSettings);
|
||||||
|
return originalSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle setting change on upstream config
|
||||||
|
function saveUpstreamUpdate(upstreamDomID){
|
||||||
|
let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
|
||||||
|
let originalSettings = $(targetDOM).attr("data-payload");
|
||||||
|
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||||
|
let newConfig = getUpstreamSettingFromDOM(targetDOM);
|
||||||
|
let isActive = $(targetDOM).find(".enableState")[0].checked;
|
||||||
|
console.log(newConfig);
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/upstream/update",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
ep: editingEndpoint.ep,
|
||||||
|
origin: originalSettings.OriginIpOrDomain, //Original ip or domain as key
|
||||||
|
payload: JSON.stringify(newConfig),
|
||||||
|
active: isActive,
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
parent.msgbox("Upstream setting updated");
|
||||||
|
initOriginList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Edit the upstream origin of this upstream entry
|
||||||
|
function handleUpstreamOriginEdit(upstreamDomID){
|
||||||
|
let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
|
||||||
|
let originalSettings = getUpstreamSettingsFromDomID(upstreamDomID);
|
||||||
|
let originIP = originalSettings.OriginIpOrDomain;
|
||||||
|
|
||||||
|
//Change the UI to edit mode
|
||||||
|
$(".editbtn").hide();
|
||||||
|
$(".lowPriorityButton").hide();
|
||||||
|
$(".highPriorityButton").hide();
|
||||||
|
$(targetDOM).find(".trashbtn").show();
|
||||||
|
$(targetDOM).find(".savebtn").show();
|
||||||
|
$(targetDOM).find(".cancelbtn").show();
|
||||||
|
$(targetDOM).find(".advanceOptions").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the entered URL contains http or https
|
||||||
|
function handleAutoOriginClean(domid){
|
||||||
|
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||||
|
let targetTLSCheckbox = $(targetDOM).find(".reqTLSCheckbox");
|
||||||
|
let targetDomain = $(targetDOM).find(".newOrigin").val().trim();
|
||||||
|
if (targetDomain.startsWith("http://")){
|
||||||
|
targetDomain = targetDomain.substr(7);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$(targetTLSCheckbox).parent().checkbox("set unchecked");
|
||||||
|
}else if (targetDomain.startsWith("https://")){
|
||||||
|
targetDomain = targetDomain.substr(8);
|
||||||
|
$(input).val(targetDomain);
|
||||||
|
$(targetTLSCheckbox).parent().checkbox("set checked");
|
||||||
|
}else{
|
||||||
|
//URL does not contains https or http protocol tag
|
||||||
|
//sniff header
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/tlscheck",
|
||||||
|
data: {url: targetDomain},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
|
||||||
|
}else if (data == "https"){
|
||||||
|
$(targetTLSCheckbox).parent().checkbox("set checked");
|
||||||
|
}else if (data == "http"){
|
||||||
|
$(targetTLSCheckbox).parent().checkbox("set unchecked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUpstreamSettingsFromDomID(domid){
|
||||||
|
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||||
|
if (targetDOM.length == 0){
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let upstreamSettings = $(targetDOM).attr("data-payload");
|
||||||
|
upstreamSettings = JSON.parse(decodeURIComponent(upstreamSettings));
|
||||||
|
return upstreamSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
function increaseUpstreamWeight(domid){
|
||||||
|
let upstreamSetting = getUpstreamSettingsFromDomID(domid);
|
||||||
|
let originIP = upstreamSetting.OriginIpOrDomain;
|
||||||
|
let currentWeight = upstreamSetting.Weight;
|
||||||
|
setUpstreamWeight(originIP, currentWeight+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decreaseUpstreamWeight(domid){
|
||||||
|
let upstreamSetting = getUpstreamSettingsFromDomID(domid);
|
||||||
|
let originIP = upstreamSetting.OriginIpOrDomain;
|
||||||
|
let currentWeight = upstreamSetting.Weight;
|
||||||
|
setUpstreamWeight(originIP, currentWeight-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set a weight of a upstream
|
||||||
|
function setUpstreamWeight(originIP, newWeight){
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/upstream/setPriority",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
ep: editingEndpoint.ep,
|
||||||
|
origin: originIP,
|
||||||
|
weight: newWeight,
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
parent.msgbox("Upstream Weight Updated");
|
||||||
|
initOriginList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle removal of an upstream
|
||||||
|
function removeUpstream(domid){
|
||||||
|
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||||
|
let originalSettings = $(targetDOM).attr("data-payload");
|
||||||
|
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||||
|
let UpstreamKey = originalSettings.OriginIpOrDomain;
|
||||||
|
if (!confirm("Confirm removing " + UpstreamKey + " from upstream?")){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Remove the upstream
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/proxy/upstream/remove",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
ep: editingEndpoint.ep,
|
||||||
|
origin: originalSettings.OriginIpOrDomain, //Original ip or domain as key
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
parent.msgbox("Upstream deleted");
|
||||||
|
initOriginList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.location.hash.length > 1){
|
||||||
|
let payloadHash = window.location.hash.substr(1);
|
||||||
|
try{
|
||||||
|
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
||||||
|
$(".epname").text(payloadHash.ep);
|
||||||
|
editingEndpoint = payloadHash;
|
||||||
|
initOriginList();
|
||||||
|
}catch(ex){
|
||||||
|
console.log("Unable to load endpoint data from hash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeThisWrapper(){
|
||||||
|
parent.hideSideWrapper(true);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -25,6 +25,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/ipscan"
|
"imuslab.com/zoraxy/mod/ipscan"
|
||||||
"imuslab.com/zoraxy/mod/mdns"
|
"imuslab.com/zoraxy/mod/mdns"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
@ -114,7 +115,7 @@ func UpdateUptimeMonitorTargets() {
|
|||||||
uptimeMonitor.ExecuteUptimeCheck()
|
uptimeMonitor.ExecuteUptimeCheck()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
SystemWideLogger.PrintAndLog("Uptime", "Uptime monitor config updated", nil)
|
SystemWideLogger.PrintAndLog("uptime-monitor", "Uptime monitor config updated", nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,26 +125,37 @@ func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Ta
|
|||||||
|
|
||||||
UptimeTargets := []*uptime.Target{}
|
UptimeTargets := []*uptime.Target{}
|
||||||
for hostid, target := range hosts {
|
for hostid, target := range hosts {
|
||||||
url := "http://" + target.Domain
|
if target.Disabled {
|
||||||
|
//Skip those proxy rules that is disabled
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
isMultipleUpstreams := len(target.ActiveOrigins) > 1
|
||||||
|
for i, origin := range target.ActiveOrigins {
|
||||||
|
url := "http://" + origin.OriginIpOrDomain
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
if target.RequireTLS {
|
if origin.RequireTLS {
|
||||||
url = "https://" + target.Domain
|
url = "https://" + origin.OriginIpOrDomain
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add the root url
|
//Add the root url
|
||||||
|
hostIdAndName := hostid
|
||||||
|
if isMultipleUpstreams {
|
||||||
|
hostIdAndName = hostIdAndName + " (upstream:" + strconv.Itoa(i) + ")"
|
||||||
|
}
|
||||||
UptimeTargets = append(UptimeTargets, &uptime.Target{
|
UptimeTargets = append(UptimeTargets, &uptime.Target{
|
||||||
ID: hostid,
|
ID: hostIdAndName,
|
||||||
Name: hostid,
|
Name: hostIdAndName,
|
||||||
URL: url,
|
URL: url,
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
|
ProxyType: uptime.ProxyType_Host,
|
||||||
})
|
})
|
||||||
|
|
||||||
//Add each virtual directory into the list
|
//Add each virtual directory into the list
|
||||||
for _, vdir := range target.VirtualDirectories {
|
for _, vdir := range target.VirtualDirectories {
|
||||||
url := "http://" + vdir.Domain
|
url := "http://" + vdir.Domain
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
if target.RequireTLS {
|
if origin.RequireTLS {
|
||||||
url = "https://" + vdir.Domain
|
url = "https://" + vdir.Domain
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
}
|
}
|
||||||
@ -153,10 +165,12 @@ func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Ta
|
|||||||
Name: hostid + vdir.MatchingPath,
|
Name: hostid + vdir.MatchingPath,
|
||||||
URL: url,
|
URL: url,
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
|
ProxyType: uptime.ProxyType_Vdir,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return UptimeTargets
|
return UptimeTargets
|
||||||
}
|
}
|
||||||
@ -187,7 +201,16 @@ func HandleStaticWebServerPortChange(w http.ResponseWriter, r *http.Request) {
|
|||||||
if dynamicProxyRouter.Root.DefaultSiteOption == dynamicproxy.DefaultSite_InternalStaticWebServer {
|
if dynamicProxyRouter.Root.DefaultSiteOption == dynamicproxy.DefaultSite_InternalStaticWebServer {
|
||||||
//Update the root site as well
|
//Update the root site as well
|
||||||
newDraftingRoot := dynamicProxyRouter.Root.Clone()
|
newDraftingRoot := dynamicProxyRouter.Root.Clone()
|
||||||
newDraftingRoot.Domain = "127.0.0.1:" + strconv.Itoa(newPort)
|
|
||||||
|
newDraftingRoot.ActiveOrigins = []*loadbalance.Upstream{
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "127.0.0.1:" + strconv.Itoa(newPort),
|
||||||
|
RequireTLS: false,
|
||||||
|
SkipCertValidations: false,
|
||||||
|
SkipWebSocketOriginCheck: true,
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
activatedNewRoot, err := dynamicProxyRouter.PrepareProxyRoute(newDraftingRoot)
|
activatedNewRoot, err := dynamicProxyRouter.PrepareProxyRoute(newDraftingRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "unable to update root routing rule")
|
utils.SendErrorResponse(w, "unable to update root routing rule")
|
||||||
|
@ -28,6 +28,7 @@ var defTemplate string = `package acmedns
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
{{imports}}
|
{{imports}}
|
||||||
@ -282,6 +283,18 @@ func main() {
|
|||||||
}
|
}
|
||||||
return ` + providerName + `.NewDNSProviderConfig(cfg)`
|
return ` + providerName + `.NewDNSProviderConfig(cfg)`
|
||||||
|
|
||||||
|
//Add fixed for Netcup timeout
|
||||||
|
if strings.ToLower(providerName) == "netcup" {
|
||||||
|
codeSegment = `
|
||||||
|
case "` + providerName + `":
|
||||||
|
cfg := ` + providerName + `.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.PropagationTimeout = 1200 * time.Second
|
||||||
|
return ` + providerName + `.NewDNSProviderConfig(cfg)`
|
||||||
|
}
|
||||||
generatedConvertcode += codeSegment
|
generatedConvertcode += codeSegment
|
||||||
importList += ` "github.com/go-acme/lego/v4/providers/dns/` + providerName + "\"\n"
|
importList += ` "github.com/go-acme/lego/v4/providers/dns/` + providerName + "\"\n"
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ go run ./extract.go
|
|||||||
go run ./extract.go -- "win7"
|
go run ./extract.go -- "win7"
|
||||||
|
|
||||||
echo "Cleaning up lego"
|
echo "Cleaning up lego"
|
||||||
|
sleep 2
|
||||||
# Comment the line below if you dont want to pull everytime update
|
# Comment the line below if you dont want to pull everytime update
|
||||||
# This is to help go compiler to not load all the lego source file when compile
|
# This is to help go compiler to not load all the lego source file when compile
|
||||||
#rm -rf ./lego/
|
#rm -rf ./lego/
|
||||||
|
Reference in New Issue
Block a user