Compare commits
77 Commits
Author | SHA1 | Date | |
---|---|---|---|
fe48a9a0c3 | |||
ec973eb3bc | |||
7b69b5fa63 | |||
ce4f46cb50 | |||
3454a9b975 | |||
55bc939a37 | |||
1d63b679dc | |||
3df96350a3 | |||
34fab7b3d0 | |||
46817d0664 | |||
1db2ca61fa | |||
0b601406de | |||
b4c771cdee | |||
a486d42351 | |||
90c2199a1b | |||
161c61fac7 | |||
5ffacb1d06 | |||
75ebd0ffbe | |||
dc069f3c57 | |||
e1b512f78f | |||
8854a38f49 | |||
7583a4628c | |||
73c0ea0896 | |||
7dad7c7305 | |||
faa95b4e21 | |||
cb0e13976d | |||
ccd8dcff56 | |||
750656fd7f | |||
d9f515fdba | |||
176249a7d9 | |||
e2a449a7bc | |||
a9695e969e | |||
7ba997dfc2 | |||
d00117e878 | |||
43a84a3f1c | |||
e24f31bdef | |||
fc9240fbac | |||
e0f5431215 | |||
de658a3c6c | |||
73276b1918 | |||
abdb7d4d75 | |||
72299ace15 | |||
4d6c79f51b | |||
2c045f4f40 | |||
b8cf046ca6 | |||
026dd6b89d | |||
5805fe6ed2 | |||
3c78211800 | |||
8e648a8e1f | |||
a000893dd1 | |||
db88bfb752 | |||
05297d854b | |||
0d7bce4d30 | |||
8db95dddc6 | |||
05daeded37 | |||
8ce6471be5 | |||
e242c9288f | |||
c55a29e7cf | |||
6af047430c | |||
200c924acd | |||
9b2168466c | |||
7ae48bf370 | |||
ee3d76fb96 | |||
40d192524b | |||
c659e05005 | |||
676a45c222 | |||
1da0761b13 | |||
32939874f2 | |||
43a4bf389a | |||
33c7c5fa00 | |||
216b53f224 | |||
059b0a2e1c | |||
3ab952f168 | |||
4f676d6770 | |||
e980bc847b | |||
174efc9080 | |||
3228789375 |
4
.github/workflows/main.yml
vendored
@ -22,7 +22,6 @@ jobs:
|
|||||||
- name: Login to Docker & GHCR
|
- name: Login to Docker & GHCR
|
||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||||
#echo "${{ secrets.GHCR_PASSWORD }}" | docker login ghcr.io -u "${{ secrets.GHCR_USERNAME }}" --password-stdin
|
|
||||||
|
|
||||||
- name: Setup building file structure
|
- name: Setup building file structure
|
||||||
run: |
|
run: |
|
||||||
@ -39,7 +38,4 @@ jobs:
|
|||||||
--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 }} \
|
||||||
--tag zoraxydocker/zoraxy:latest \
|
--tag zoraxydocker/zoraxy:latest \
|
||||||
# Since this is still undetermined, I will leave it commented
|
|
||||||
#--tag ghcr.io/zoraxydocker/zoraxy:${{ steps.get_latest_release_tag.outputs.latest_tag }} \
|
|
||||||
#--tag ghcr.io/zoraxydocker/zoraxy:latest \
|
|
||||||
.
|
.
|
||||||
|
2
.gitignore
vendored
@ -32,3 +32,5 @@ src/README.md
|
|||||||
docker/ContainerTester.sh
|
docker/ContainerTester.sh
|
||||||
docker/ImagePublisher.sh
|
docker/ImagePublisher.sh
|
||||||
src/mod/acme/test/stackoverflow.pem
|
src/mod/acme/test/stackoverflow.pem
|
||||||
|
/tools/dns_challenge_update/code-gen/acmedns
|
||||||
|
/tools/dns_challenge_update/code-gen/lego
|
||||||
|
64
CHANGELOG.md
@ -1,3 +1,67 @@
|
|||||||
|
# v3.0.3 Apr 30 2024
|
||||||
|
## Breaking Change
|
||||||
|
|
||||||
|
For users using SMTP with older versions, you might need to update the settings by moving the domains (the part after @ in the username and domain setup field) into the username field.
|
||||||
|
|
||||||
|
+ Updated SMTP UI for non email login username [#129](https://github.com/tobychui/zoraxy/issues/129)
|
||||||
|
+ Fixed ACME cert store reload after cert request [#126](https://github.com/tobychui/zoraxy/issues/126)
|
||||||
|
+ Fixed default rule not applying to default site when default site is set to proxy target [#130](https://github.com/tobychui/zoraxy/issues/130)
|
||||||
|
+ Fixed blacklist-ip not working with CIDR bug
|
||||||
|
+ Fixed minor vdir bug in tailing slash detection and redirect logic
|
||||||
|
+ Added custom mdns name support (-mdnsname flag)
|
||||||
|
+ Added LAN tag in statistic [#131](https://github.com/tobychui/zoraxy/issues/131)
|
||||||
|
|
||||||
|
|
||||||
|
# v3.0.2 Apr 24 2024
|
||||||
|
|
||||||
|
+ Added alias for HTTP proxy host names [#76](https://github.com/tobychui/zoraxy/issues/76)
|
||||||
|
+ Added separator support for create new proxy rules (use "," to add alias when creating new proxy rule)
|
||||||
|
+ Added HTTP proxy host based access rules [#69](https://github.com/tobychui/zoraxy/issues/69)
|
||||||
|
+ Added EAD Configuration for ACME (by [yeungalan](https://github.com/yeungalan)) [#45](https://github.com/tobychui/zoraxy/issues/45)
|
||||||
|
+ Fixed bug for bypassGlobalTLS endpoint do not support basic-auth
|
||||||
|
+ Fixed panic due to empty domain field in json config [#120](https://github.com/tobychui/zoraxy/issues/120)
|
||||||
|
+ Removed dependencies on management panel css for online font files
|
||||||
|
|
||||||
|
# v3.0.1 Apr 04 2024
|
||||||
|
|
||||||
|
## Bugfixupdate for big release of V3, read update notes from V3 if you are still on V2
|
||||||
|
|
||||||
|
+ Added regex support for redirect (slow, don't use it unless you really needs it) [#42](https://github.com/tobychui/zoraxy/issues/42)
|
||||||
|
+ Added new dpcore implementations for faster proxy speed
|
||||||
|
+ Added support for CF-Connecting-IP to X-Real-IP auto rewrite [#114](https://github.com/tobychui/zoraxy/issues/114)
|
||||||
|
+ Added enable / disable of HTTP proxy rules in runtime via slider [#108](https://github.com/tobychui/zoraxy/issues/108)
|
||||||
|
+ Added better 404 page
|
||||||
|
+ Added option to bypass websocket origin check [#107](https://github.com/tobychui/zoraxy/issues/107)
|
||||||
|
+ Updated project homepage design
|
||||||
|
+ Fixed recursive port detection logic
|
||||||
|
+ Fixed UserAgent in resp bug
|
||||||
|
+ Updated minimum required Go version to v1.22 (Notes: Windows 7 support is dropped) [#112](https://github.com/tobychui/zoraxy/issues/112)
|
||||||
|
|
||||||
|
|
||||||
|
# v3.0.0 Feb 18 2024
|
||||||
|
|
||||||
|
## IMPORTANT: V3 is a big rewrite and it is incompatible with V2! There is NO migration, if you want to stay on V2, please use V2 branch!
|
||||||
|
|
||||||
|
+ Added comments for whitelist [#97](https://github.com/tobychui/zoraxy/issues/97)
|
||||||
|
+ Added force-renew for certificates [#92](https://github.com/tobychui/zoraxy/issues/92)
|
||||||
|
+ Added automatic cert pick for multi-host certs (SNI)
|
||||||
|
+ Renamed .crt to .pem for cert store
|
||||||
|
+ Added best-fit selection for wildcard matching rules
|
||||||
|
+ Added x-proxy-by header / Added X-real-Ip header [#93](https://github.com/tobychui/zoraxy/issues/93)
|
||||||
|
+ Added Development Mode (Cache-Control: no-store)
|
||||||
|
+ Updated utm timeout to 10 seconds instead of 90
|
||||||
|
+ Added "Add controller as member" feature to Global Area Network editor
|
||||||
|
+ Added custom header
|
||||||
|
+ Deprecated aroz subservice support
|
||||||
|
+ Updated visuals, improving logical structure, less depressing colors [#95](https://github.com/tobychui/zoraxy/issues/95)
|
||||||
|
+ Added virtual directory into host routing object (each host now got its own sets of virtual directories)
|
||||||
|
+ Added support for wildcard host names (e.g. *.example.com)
|
||||||
|
+ Added best-fit selection for wildcard matching rules (e.g. *.a.example.com > *.example.com in routing)
|
||||||
|
+ Generalized root and hosts routing struct (no more conversion between runtime & save record object
|
||||||
|
+ Added "Default Site" to replace "Proxy Root" interface
|
||||||
|
+ Added Redirect & 404 page for "Default Site"
|
||||||
|
|
||||||
|
|
||||||
# v2.6.8 Nov 25 2023
|
# v2.6.8 Nov 25 2023
|
||||||
|
|
||||||
+ Added opt-out for subdomains for global TLS settings: See [release notes](https://github.com/tobychui/zoraxy/releases/tag/2.6.8)
|
+ Added opt-out for subdomains for global TLS settings: See [release notes](https://github.com/tobychui/zoraxy/releases/tag/2.6.8)
|
||||||
|
81
README.md
@ -2,18 +2,26 @@
|
|||||||
|
|
||||||
# Zoraxy
|
# Zoraxy
|
||||||
|
|
||||||
General purpose request (reverse) proxy and forwarding tool for low power devices. Now written in Go!
|
General purpose request (reverse) proxy and forwarding tool for networking noobs. Now written in Go!
|
||||||
|
|
||||||
|
*Zoraxy v3 HTTP proxy config is not compatible with the older v2. If you are looking for the legacy version of Zoraxy, take a look at the [v2 branch](https://github.com/tobychui/zoraxy/tree/v2)*
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- Simple to use interface with detail in-system instructions
|
- Simple to use interface with detail in-system instructions
|
||||||
- Reverse Proxy
|
- Reverse Proxy (HTTP/2)
|
||||||
- Subdomain Reverse Proxy
|
- Virtual Directory
|
||||||
- Virtual Directory Reverse Proxy
|
- WebSocket Proxy (automatic, no set-up needed)
|
||||||
|
- Basic Auth
|
||||||
|
- Alias Hostnames
|
||||||
|
- Custom Headers
|
||||||
- Redirection Rules
|
- Redirection Rules
|
||||||
- TLS / SSL setup and deploy
|
- TLS / SSL setup and deploy
|
||||||
- Blacklist by country or IP address (single IP, CIDR or wildcard for beginners)
|
- ACME features like auto-renew to serve your sites in http**s**
|
||||||
|
- SNI support (and SAN certs)
|
||||||
|
- 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
|
||||||
- Integrated Up-time Monitor
|
- Integrated Up-time Monitor
|
||||||
- Web-SSH Terminal
|
- Web-SSH Terminal
|
||||||
- Utilities
|
- Utilities
|
||||||
@ -25,8 +33,17 @@ General purpose request (reverse) proxy and forwarding tool for low power device
|
|||||||
- External permission management system for easy system integration
|
- External permission management system for easy system integration
|
||||||
- SMTP config for password reset
|
- SMTP config for password reset
|
||||||
|
|
||||||
|
## Downloads
|
||||||
|
|
||||||
|
[Windows](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe)
|
||||||
|
/[Linux (amd64)](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_amd64)
|
||||||
|
/[Linux (arm64)](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_arm64)
|
||||||
|
|
||||||
|
For other systems or architectures, please see [Release](https://github.com/tobychui/zoraxy/releases/latest/)
|
||||||
|
|
||||||
## Build from Source
|
## Build from Source
|
||||||
Requires Go 1.20 or higher
|
|
||||||
|
Requires Go 1.22 or higher
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/tobychui/zoraxy
|
git clone https://github.com/tobychui/zoraxy
|
||||||
@ -64,27 +81,27 @@ The installation method is same as Linux. If you are using a Raspberry Pi 4 or n
|
|||||||
The installation method is same as Linux. For other ARM SBCs, please refer to your SBC's CPU architecture and pick the one that is suitable for your device.
|
The installation method is same as Linux. For other ARM SBCs, please refer to your SBC's CPU architecture and pick the one that is suitable for your device.
|
||||||
|
|
||||||
#### Docker
|
#### Docker
|
||||||
|
|
||||||
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details.
|
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details.
|
||||||
|
|
||||||
### Start Paramters
|
### Start Paramters
|
||||||
|
|
||||||
```
|
```
|
||||||
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)
|
||||||
-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)
|
||||||
-info
|
|
||||||
Show information about this program in JSON
|
|
||||||
-log
|
-log
|
||||||
Log terminal output to file (default true)
|
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
|
||||||
|
mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)
|
||||||
-noauth
|
-noauth
|
||||||
Disable authentication for management interface
|
Disable authentication for management interface
|
||||||
-port string
|
-port string
|
||||||
Management web interface listening port (default ":8000")
|
Management web interface listening port (default ":8000")
|
||||||
-rpt string
|
|
||||||
Reserved
|
|
||||||
-sshlb
|
-sshlb
|
||||||
Allow loopback web ssh connection (DANGER)
|
Allow loopback web ssh connection (DANGER)
|
||||||
-version
|
-version
|
||||||
@ -109,45 +126,12 @@ If you already have an upstream reverse proxy server in place with permission ma
|
|||||||
|
|
||||||
*Note: For security reaons, you should only enable no-auth if you are running Zoraxy in a trusted environment or with another authentication management proxy in front.*
|
*Note: For security reaons, you should only enable no-auth if you are running Zoraxy in a trusted environment or with another authentication management proxy in front.*
|
||||||
|
|
||||||
#### Use with ArozOS
|
|
||||||
|
|
||||||
The [ArozOS](https://arozos.com) subservice is a built-in, permission-managed, reverse proxy server. To use Zoraxy with ArozOS, connect to your ArozOS host via SSH and use the following command to install Zoraxy:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# cd into your ArozOS subservice folder. Sometimes it is under ~/arozos/src/subservice.
|
|
||||||
cd ~/arozos/subservices
|
|
||||||
mkdir zoraxy
|
|
||||||
cd ./zoraxy
|
|
||||||
|
|
||||||
# Download the release binary from Github release.
|
|
||||||
wget {binary executable link from release page}
|
|
||||||
|
|
||||||
# Set permission. Change this if required.
|
|
||||||
sudo chmod 775 -R ./
|
|
||||||
|
|
||||||
# Start zoraxy to see if the downloaded arch is correct.
|
|
||||||
./zoraxy
|
|
||||||
|
|
||||||
# After unzipping, press Ctrl + C to kill it.
|
|
||||||
# Rename it to validate the ArozOS subservice binary format.
|
|
||||||
mv ./zoraxy zoraxy_linux_amd64
|
|
||||||
|
|
||||||
# If you are using SBCs with a different CPU arch, use the following names:
|
|
||||||
# mv ./zoraxy zoraxy_linux_arm
|
|
||||||
# mv ./zoraxy zoraxy_linux_arm64
|
|
||||||
|
|
||||||
# Restart ArozOS
|
|
||||||
sudo systemctl restart arozos
|
|
||||||
```
|
|
||||||
|
|
||||||
To start the module, go to System Settings > Modules > Subservice and enable it in the menu. You should be able to see a new module named "Zoraxy" pop up in the start menu.
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
More screenshots on the wikipage [Screenshots](https://github.com/tobychui/zoraxy/wiki/Screenshots)!
|
More screenshots on the wikipage [Screenshots](https://github.com/tobychui/zoraxy/wiki/Screenshots)!
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
@ -173,6 +157,7 @@ This allows you to have an infinite number of network members in your Global Are
|
|||||||
## Web SSH
|
## Web SSH
|
||||||
|
|
||||||
Web SSH currently only supports Linux based OSes. The following platforms are supported:
|
Web SSH currently only supports Linux based OSes. The following platforms are supported:
|
||||||
|
|
||||||
- linux/amd64
|
- linux/amd64
|
||||||
- linux/arm64
|
- linux/arm64
|
||||||
- linux/armv6 (experimental)
|
- linux/armv6 (experimental)
|
||||||
@ -187,12 +172,14 @@ Loopback web SSH connection, by default, is disabled. This means that if you are
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Sponsor This Project
|
## Sponsor This Project
|
||||||
|
|
||||||
If you like the project and want to support us, please consider a donation. You can use the links below
|
If you like the project and want to support us, please consider a donation. You can use the links below
|
||||||
|
|
||||||
- [tobychui (Primary author)](https://paypal.me/tobychui)
|
- [tobychui (Primary author)](https://paypal.me/tobychui)
|
||||||
- PassiveLemon (Docker compatibility maintainer)
|
- PassiveLemon (Docker compatibility maintainer)
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is open-sourced under AGPL. I open-sourced this project so everyone can check for security issues and benefit all users. **If you plan to use this project in a commercial environment (which violate the AGPL terms), please contact toby@imuslab.com for an alternative commercial license.**
|
This project is open-sourced under AGPL. I open-sourced this project so everyone can check for security issues and benefit all users. **This software is intended to be free of charge. If you have acquired this software from a third-party seller, the authors of this repository bears no responsibility for any technical difficulties assistance or support.**
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,17 +3,12 @@ FROM docker.io/golang:alpine
|
|||||||
ARG VERSION
|
ARG VERSION
|
||||||
|
|
||||||
RUN apk add --no-cache bash netcat-openbsd sudo
|
RUN apk add --no-cache bash netcat-openbsd sudo
|
||||||
# Alternatives for security
|
|
||||||
RUN apk add --no-cache openssl=3.1.4-r1
|
|
||||||
|
|
||||||
RUN mkdir -p /opt/zoraxy/source/ &&\
|
RUN mkdir -p /opt/zoraxy/source/ &&\
|
||||||
mkdir -p /opt/zoraxy/config/ &&\
|
mkdir -p /opt/zoraxy/config/ &&\
|
||||||
mkdir -p /usr/local/bin/
|
mkdir -p /usr/local/bin/
|
||||||
|
|
||||||
COPY entrypoint.sh /opt/zoraxy/
|
RUN chmod -R 770 /opt/zoraxy/
|
||||||
|
|
||||||
RUN chmod -R 755 /opt/zoraxy/ &&\
|
|
||||||
chmod +x /opt/zoraxy/entrypoint.sh
|
|
||||||
|
|
||||||
VOLUME [ "/opt/zoraxy/config/" ]
|
VOLUME [ "/opt/zoraxy/config/" ]
|
||||||
|
|
||||||
@ -26,15 +21,15 @@ RUN go mod tidy &&\
|
|||||||
go build -o /usr/local/bin/zoraxy &&\
|
go build -o /usr/local/bin/zoraxy &&\
|
||||||
rm -r /opt/zoraxy/source/
|
rm -r /opt/zoraxy/source/
|
||||||
|
|
||||||
RUN chmod +x /usr/local/bin/zoraxy
|
RUN chmod 755 /usr/local/bin/zoraxy &&\
|
||||||
|
chmod +x /usr/local/bin/zoraxy
|
||||||
|
|
||||||
WORKDIR /opt/zoraxy/config/
|
WORKDIR /opt/zoraxy/config/
|
||||||
|
|
||||||
ENV VERSION=$VERSION
|
ENV VERSION=$VERSION
|
||||||
|
|
||||||
ENV ARGS="-noauth=false"
|
ENV ARGS="-noauth=false"
|
||||||
|
|
||||||
ENTRYPOINT ["/opt/zoraxy/entrypoint.sh"]
|
ENTRYPOINT "zoraxy" "-port=:8000" "${ARGS}"
|
||||||
|
|
||||||
HEALTHCHECK --interval=5s --timeout=5s --retries=2 CMD nc -vz 127.0.0.1 8000 || exit 1
|
HEALTHCHECK --interval=5s --timeout=5s --retries=2 CMD nc -vz 127.0.0.1 8000 || exit 1
|
||||||
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
echo "Zoraxy version $VERSION"
|
|
||||||
|
|
||||||
zoraxy -port=:8000 ${ARGS}
|
|
BIN
docs/img/bg.png
Before Width: | Height: | Size: 4.5 MiB |
BIN
docs/img/bg2.png
Before Width: | Height: | Size: 9.4 MiB |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m772-635-43-100-104-46 104-45 43-95 43 95 104 45-104 46-43 100Zm0 595-43-96-104-45 104-45 43-101 43 101 104 45-104 45-43 96ZM333-194l-92-197-201-90 201-90 92-196 93 196 200 90-200 90-93 197Zm0-148 48-96 98-43-98-43-48-96-47 96-99 43 99 43 47 96Zm0-139Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m772-635-43-100-104-46 104-45 43-95 43 95 104 45-104 46-43 100Zm0 595-43-96-104-45 104-45 43-101 43 101 104 45-104 45-43 96ZM333-194l-92-197-201-90 201-90 92-196 93 196 200 90-200 90-93 197Zm0-148 48-96 98-43-98-43-48-96-47 96-99 43 99 43 47 96Zm0-139Z"/></svg>
|
Before Width: | Height: | Size: 358 B After Width: | Height: | Size: 377 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M280-453h400v-60H280v60ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>
|
<svg fill="#ff7a7a" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M280-453h400v-60H280v60ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>
|
Before Width: | Height: | Size: 433 B After Width: | Height: | Size: 448 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M320-242 80-482l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M320-242 80-482l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"/></svg>
|
Before Width: | Height: | Size: 209 B After Width: | Height: | Size: 227 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-80v-270h120v-160h210v-100H330v-270h300v270H510v100h210v160h120v270H540v-270h120v-100H300v100h120v270H120Zm270-590h180v-150H390v150ZM180-140h180v-150H180v150Zm420 0h180v-150H600v150ZM480-670ZM360-290Zm240 0Z"/></svg>
|
<svg fill="#919191" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-80v-270h120v-160h210v-100H330v-270h300v270H510v100h210v160h120v270H540v-270h120v-100H300v100h120v270H120Zm270-590h180v-150H390v150ZM180-140h180v-150H180v150Zm420 0h180v-150H600v150ZM480-670ZM360-290Zm240 0Z"/></svg>
|
Before Width: | Height: | Size: 317 B After Width: | Height: | Size: 332 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>
|
Before Width: | Height: | Size: 224 B After Width: | Height: | Size: 242 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M356-120H180q-24 0-42-18t-18-42v-176q44-5 75.5-34.5T227-463q0-43-31.5-72.5T120-570v-176q0-24 18-42t42-18h177q11-40 39.5-67t68.5-27q40 0 68.5 27t39.5 67h173q24 0 42 18t18 42v173q40 11 65.5 41.5T897-461q0 40-25.5 67T806-356v176q0 24-18 42t-42 18H570q-5-48-35.5-77.5T463-227q-41 0-71.5 29.5T356-120Zm-176-60h130q25-61 69.888-84 44.888-23 83-23T546-264q45 23 70 84h130v-235h45q20 0 33-13t13-33q0-20-13-33t-33-13h-45v-239H511v-48q0-20-13-33t-33-13q-20 0-33 13t-13 33v48H180v130q48.15 17.817 77.575 59.686Q287-514.445 287-462.777 287-412 257.5-370T180-310v130Zm329-330Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M356-120H180q-24 0-42-18t-18-42v-176q44-5 75.5-34.5T227-463q0-43-31.5-72.5T120-570v-176q0-24 18-42t42-18h177q11-40 39.5-67t68.5-27q40 0 68.5 27t39.5 67h173q24 0 42 18t18 42v173q40 11 65.5 41.5T897-461q0 40-25.5 67T806-356v176q0 24-18 42t-42 18H570q-5-48-35.5-77.5T463-227q-41 0-71.5 29.5T356-120Zm-176-60h130q25-61 69.888-84 44.888-23 83-23T546-264q45 23 70 84h130v-235h45q20 0 33-13t13-33q0-20-13-33t-33-13h-45v-239H511v-48q0-20-13-33t-33-13q-20 0-33 13t-13 33v48H180v130q48.15 17.817 77.575 59.686Q287-514.445 287-462.777 287-412 257.5-370T180-310v130Zm329-330Z"/></svg>
|
Before Width: | Height: | Size: 669 B After Width: | Height: | Size: 688 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M273-160 80-353l193-193 42 42-121 121h316v60H194l121 121-42 42Zm414-254-42-42 121-121H450v-60h316L645-758l42-42 193 193-193 193Z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48" fill="#fcba03"><path d="M273-160 80-353l193-193 42 42-121 121h316v60H194l121 121-42 42Zm414-254-42-42 121-121H450v-60h316L645-758l42-42 193 193-193 193Z"/></svg>
|
Before Width: | Height: | Size: 234 B After Width: | Height: | Size: 249 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M700-160v-410H275l153 153-42 43-226-226 226-226 42 42-153 154h485v470h-60Z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" fill="#0388fc" height="48" viewBox="0 -960 960 960" width="48"><path d="M700-160v-410H275l153 153-42 43-226-226 226-226 42 42-153 154h485v470h-60Z"/></svg>
|
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 195 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M197-197q-54-54-85.5-126.5T80-480q0-84 31.5-156.5T197-763l43 43q-46 46-73 107.5T140-480q0 71 26.5 132T240-240l-43 43Zm113-113q-32-32-51-75.5T240-480q0-51 19-94.5t51-75.5l43 43q-24 24-38.5 56.5T300-480q0 38 14 70t39 57l-43 43Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-43-43q24-24 38.5-56.5T660-480q0-38-14-70t-39-57l43-43q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-43-43q46-46 73-107.5T820-480q0-71-26.5-132T720-720l43-43q54 55 85.5 127.5T880-480q0 83-31.5 155.5T763-197Z"/></svg>
|
<svg fill="#83f2c4" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M197-197q-54-54-85.5-126.5T80-480q0-84 31.5-156.5T197-763l43 43q-46 46-73 107.5T140-480q0 71 26.5 132T240-240l-43 43Zm113-113q-32-32-51-75.5T240-480q0-51 19-94.5t51-75.5l43 43q-24 24-38.5 56.5T300-480q0 38 14 70t39 57l-43 43Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-43-43q24-24 38.5-56.5T660-480q0-38-14-70t-39-57l43-43q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-43-43q46-46 73-107.5T820-480q0-71-26.5-132T720-720l43-43q54 55 85.5 127.5T880-480q0 83-31.5 155.5T763-197Z"/></svg>
|
Before Width: | Height: | Size: 652 B After Width: | Height: | Size: 667 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M345-377h391L609-548 506-413l-68-87-93 123Zm-85 177q-24 0-42-18t-18-42v-560q0-24 18-42t42-18h560q24 0 42 18t18 42v560q0 24-18 42t-42 18H260Zm0-60h560v-560H260v560ZM140-80q-24 0-42-18t-18-42v-620h60v620h620v60H140Zm120-740v560-560Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M345-377h391L609-548 506-413l-68-87-93 123Zm-85 177q-24 0-42-18t-18-42v-560q0-24 18-42t42-18h560q24 0 42 18t18 42v560q0 24-18 42t-42 18H260Zm0-60h560v-560H260v560ZM140-80q-24 0-42-18t-18-42v-620h60v620h620v60H140Zm120-740v560-560Z"/></svg>
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 355 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg>
|
<svg fill="#edf230" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg>
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
BIN
docs/img/screenshots/1.png
Normal file
After Width: | Height: | Size: 202 KiB |
Before Width: | Height: | Size: 42 KiB |
BIN
docs/img/screenshots/10.png
Normal file
After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 62 KiB |
BIN
docs/img/screenshots/2.png
Normal file
After Width: | Height: | Size: 146 KiB |
Before Width: | Height: | Size: 32 KiB |
BIN
docs/img/screenshots/3.png
Normal file
After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 28 KiB |
BIN
docs/img/screenshots/4.png
Normal file
After Width: | Height: | Size: 203 KiB |
Before Width: | Height: | Size: 41 KiB |
BIN
docs/img/screenshots/5.png
Normal file
After Width: | Height: | Size: 123 KiB |
Before Width: | Height: | Size: 46 KiB |
BIN
docs/img/screenshots/6.png
Normal file
After Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 48 KiB |
BIN
docs/img/screenshots/7.png
Normal file
After Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 55 KiB |
BIN
docs/img/screenshots/8.png
Normal file
After Width: | Height: | Size: 185 KiB |
Before Width: | Height: | Size: 68 KiB |
BIN
docs/img/screenshots/9.png
Normal file
After Width: | Height: | Size: 867 KiB |
Before Width: | Height: | Size: 153 KiB |
@ -8,7 +8,7 @@
|
|||||||
<meta name="author" content="tobychui">
|
<meta name="author" content="tobychui">
|
||||||
|
|
||||||
<!-- HTML Meta Tags -->
|
<!-- HTML Meta Tags -->
|
||||||
<title>Cluster Proxy Gateway | Zoraxy</title>
|
<title>Reverse Proxy Server | Zoraxy</title>
|
||||||
<meta name="description" content="A reverse proxy server and cluster network gateway for noobs">
|
<meta name="description" content="A reverse proxy server and cluster network gateway for noobs">
|
||||||
|
|
||||||
<!-- Facebook Meta Tags -->
|
<!-- Facebook Meta Tags -->
|
||||||
@ -74,21 +74,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="right-content">
|
<div class="right-content">
|
||||||
<!-- Hero Banner Section -->
|
<!-- Hero Banner Section -->
|
||||||
<div class="dot-container">
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div class="dot"></div>
|
|
||||||
</div>
|
|
||||||
<div class="headbanner"></div>
|
<div class="headbanner"></div>
|
||||||
<div id="home" class="herotext">
|
<div id="home" class="herotext">
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
<div class="bannerHeaderWrapper">
|
<div class="bannerHeaderWrapper">
|
||||||
<h1 class="bannerHeader">Zoraxy</h1>
|
<h1 class="bannerHeader">Zoraxy</h1>
|
||||||
|
<div class="ui divider"></div><br>
|
||||||
<p class="bannerSubheader">All in one homelab network routing solution</p>
|
<p class="bannerSubheader">All in one homelab network routing solution</p>
|
||||||
</div>
|
</div>
|
||||||
<br><br>
|
<br><br>
|
||||||
<a class="ui black big button" href="#features">Learn More</a>
|
<a class="ui basic big button" style="background-color: white;" href="#features"><i class="ui blue arrow down icon"></i> Learn More</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
<table class="ui very basic collapsing unstackable celled table">
|
<table class="ui very basic collapsing unstackable celled table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -126,6 +121,22 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="wavesWrapper">
|
||||||
|
<!-- CSS waves-->
|
||||||
|
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
|
||||||
|
<defs>
|
||||||
|
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
|
||||||
|
</defs>
|
||||||
|
<g class="parallax">
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(255,255,255,0.7" />
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(255,255,255,0.5)" />
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(255,255,255,0.3)" />
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="7" fill="#fff" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Features -->
|
<!-- Features -->
|
||||||
@ -240,34 +251,34 @@
|
|||||||
|
|
||||||
<div class="ui three column stackable grid">
|
<div class="ui three column stackable grid">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/1.webp" target="_blank"><img src="img/screenshots/1.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/1.png" target="_blank"><img src="img/screenshots/1.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/2.webp" target="_blank"><img src="img/screenshots/2.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/2.png" target="_blank"><img src="img/screenshots/2.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/3.webp" target="_blank"><img src="img/screenshots/3.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/3.png" target="_blank"><img src="img/screenshots/3.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/4.webp" target="_blank"><img src="img/screenshots/4.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/4.png" target="_blank"><img src="img/screenshots/4.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/5.webp" target="_blank"><img src="img/screenshots/5.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/5.png" target="_blank"><img src="img/screenshots/5.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/6.webp" target="_blank"><img src="img/screenshots/6.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/6.png" target="_blank"><img src="img/screenshots/6.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/7.webp" target="_blank"><img src="img/screenshots/7.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/7.png" target="_blank"><img src="img/screenshots/7.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/8.webp" target="_blank"><img src="img/screenshots/8.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/8.png" target="_blank"><img src="img/screenshots/8.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/9.webp" target="_blank"><img src="img/screenshots/9.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/9.png" target="_blank"><img src="img/screenshots/9.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/10.webp" target="_blank"><img src="img/screenshots/10.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/10.png" target="_blank"><img src="img/screenshots/10.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
123
docs/style.css
@ -1,5 +1,5 @@
|
|||||||
body{
|
body{
|
||||||
background: #f6f6f6 !important;
|
background: #ffffff !important;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
@ -18,7 +18,7 @@ body{
|
|||||||
.left-menu {
|
.left-menu {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
background-color: #ffffff;
|
background-color: #fcfcfc;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding-top: 1.5em;
|
padding-top: 1.5em;
|
||||||
}
|
}
|
||||||
@ -48,17 +48,19 @@ body{
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 1px solid #f6f6f6;
|
border-bottom: 1px solid #f6f6f6;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-right: 0.4em solid var(--themeTextColor);
|
|
||||||
transition: border-left ease-in-out 0.1s, background-color ease-in-out 0.1s;
|
transition: border-left ease-in-out 0.1s, background-color ease-in-out 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item.active{
|
.menu-item.active{
|
||||||
border-right: 0.4em solid var(--themeSkyblueColorDecondary);
|
background: linear-gradient(60deg, rgba(84, 58, 183, 0.3) 0%, rgba(0, 172, 193, 0.3) 100%);
|
||||||
background-color: #f0f8ff;
|
}
|
||||||
|
|
||||||
|
.menu-item .item-icon{
|
||||||
|
fill: #fcfcfc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item:hover{
|
.menu-item:hover{
|
||||||
border-right: 0.4em solid var(--themeSkyblueColorDecondary);
|
background: rgba(35,35,35,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item img{
|
.menu-item img{
|
||||||
@ -69,18 +71,6 @@ body{
|
|||||||
|
|
||||||
|
|
||||||
/* Head banner */
|
/* Head banner */
|
||||||
.headbanner{
|
|
||||||
background-image: url('img/bg.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: right center;
|
|
||||||
background-size: auto 100%;
|
|
||||||
position:absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100%;
|
|
||||||
z-index: -100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.herotext{
|
.herotext{
|
||||||
padding-top: 15em;
|
padding-top: 15em;
|
||||||
@ -91,11 +81,13 @@ body{
|
|||||||
.bannerHeader{
|
.bannerHeader{
|
||||||
font-size: 8em;
|
font-size: 8em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bannerSubheader{
|
.bannerSubheader{
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
|
color: #ebebeb;
|
||||||
margin-top: -20px;
|
margin-top: -20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +96,21 @@ body{
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#home{
|
||||||
|
background: linear-gradient(60deg, rgba(84,58,183,1) 0%, rgba(0,172,193,1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#home .table th, #home .table h4{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#home .table h4 .content, #home .table h4 .sub.header{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#home .table td a{
|
||||||
|
color: #d6ddff;
|
||||||
|
}
|
||||||
|
|
||||||
/* features */
|
/* features */
|
||||||
#features{
|
#features{
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
@ -173,56 +180,58 @@ body{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Decorative Animation */
|
/*
|
||||||
.dot-container {
|
Waves CSS
|
||||||
display: flex;
|
*/
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
#wavesWrapper{
|
||||||
height: 40px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 2em;
|
bottom: 0;
|
||||||
left: 2em;
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot {
|
.waves {
|
||||||
width: 6px;
|
position:relative;
|
||||||
height: 6px;
|
width: 100%;
|
||||||
border-radius: 50%;
|
height:15vh;
|
||||||
background-color: #d9d9d9;
|
margin-bottom:-7px; /*Fix for safari gap*/
|
||||||
margin-right: 6px;
|
min-height:100px;
|
||||||
animation-name: dot-animation;
|
max-height:150px;
|
||||||
animation-duration: 4s;
|
|
||||||
animation-timing-function: ease-in-out;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot:nth-child(1) {
|
|
||||||
animation-delay: 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot:nth-child(2) {
|
.parallax > use {
|
||||||
animation-delay: 1s;
|
animation: move-forever 25s cubic-bezier(.55,.5,.45,.5) infinite;
|
||||||
}
|
}
|
||||||
|
.parallax > use:nth-child(1) {
|
||||||
.dot:nth-child(3) {
|
animation-delay: -8s;
|
||||||
animation-delay: 2s;
|
animation-duration: 28s;
|
||||||
}
|
}
|
||||||
|
.parallax > use:nth-child(2) {
|
||||||
.dot:nth-child(4) {
|
animation-delay: -12s;
|
||||||
animation-delay: 3s;
|
animation-duration: 40s;
|
||||||
}
|
}
|
||||||
|
.parallax > use:nth-child(3) {
|
||||||
@keyframes dot-animation {
|
animation-delay: -16s;
|
||||||
|
animation-duration: 52s;
|
||||||
|
}
|
||||||
|
.parallax > use:nth-child(4) {
|
||||||
|
animation-delay: -20s;
|
||||||
|
animation-duration: 80s;
|
||||||
|
}
|
||||||
|
@keyframes move-forever {
|
||||||
0% {
|
0% {
|
||||||
background-color: #d9d9d9;
|
transform: translate3d(-90px,0,0);
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-color: #a9d1f3;
|
|
||||||
transform: scale(1.5);
|
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
background-color: #d9d9d9;
|
transform: translate3d(85px,0,0);
|
||||||
transform: scale(1);
|
}
|
||||||
|
}
|
||||||
|
/*Shrinking for mobile*/
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.waves {
|
||||||
|
height:40px;
|
||||||
|
min-height:40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
18
example/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Example www Folder
|
||||||
|
|
||||||
|
This is an example www folder that contains two sub-folders.
|
||||||
|
|
||||||
|
- `html/`
|
||||||
|
- `templates/`
|
||||||
|
|
||||||
|
The html file contain static resources that will be served by Zoraxy build-in static web server. You can use it as a generic web server with a static site generator like [Hugo](https://gohugo.io/) or use it as a small CDN for serving your scripts / image that commonly use across many of your sites.
|
||||||
|
|
||||||
|
The templates folder contains the template for overriding the build in error or access denied pages. The following templates are supported
|
||||||
|
|
||||||
|
- notfound.html (Default site Not-Found error page)
|
||||||
|
- whitelist.html (Error page when client being blocked by whitelist rule)
|
||||||
|
- blacklist.html (Error page when client being blocked by blacklist rule)
|
||||||
|
|
||||||
|
To use the template, copy and paste the `wwww` folder to the same directory as zoraxy executable (aka the src/ file if you `go build` with the current folder tree).
|
||||||
|
|
||||||
|
It is worth mentioning that the uwu icons for not-found and access-denied are created by @SAWARATSUKI
|
229
example/www/html/index.html
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Zoraxy Firework!</title>
|
||||||
|
<style>
|
||||||
|
body{
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js" integrity="sha512-aNMyYYxdIxIaot0Y1/PLuEu3eipGCmsEUBrUq+7aVyPGMFH8z0eTP0tkqAvv34fzN6z+201d3T8HPb1svWSKHQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="c"></canvas>
|
||||||
|
<script>
|
||||||
|
var c = document.getElementById("c");
|
||||||
|
var ctx = c.getContext("2d");
|
||||||
|
var cH;
|
||||||
|
var cW;
|
||||||
|
var bgColor = "#FF6138";
|
||||||
|
var animations = [];
|
||||||
|
var circles = [];
|
||||||
|
|
||||||
|
var colorPicker = (function() {
|
||||||
|
var colors = ["#FF6138", "#FFBE53", "#2980B9", "#FCFCFC", "#282741"];
|
||||||
|
var index = 0;
|
||||||
|
function next() {
|
||||||
|
index = index++ < colors.length-1 ? index : 0;
|
||||||
|
return colors[index];
|
||||||
|
}
|
||||||
|
function current() {
|
||||||
|
return colors[index]
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
next: next,
|
||||||
|
current: current
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
function removeAnimation(animation) {
|
||||||
|
var index = animations.indexOf(animation);
|
||||||
|
if (index > -1) animations.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcPageFillRadius(x, y) {
|
||||||
|
var l = Math.max(x - 0, cW - x);
|
||||||
|
var h = Math.max(y - 0, cH - y);
|
||||||
|
return Math.sqrt(Math.pow(l, 2) + Math.pow(h, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addClickListeners() {
|
||||||
|
document.addEventListener("touchstart", handleEvent);
|
||||||
|
document.addEventListener("mousedown", handleEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleEvent(e) {
|
||||||
|
if (e.touches) {
|
||||||
|
e.preventDefault();
|
||||||
|
e = e.touches[0];
|
||||||
|
}
|
||||||
|
var currentColor = colorPicker.current();
|
||||||
|
var nextColor = colorPicker.next();
|
||||||
|
var targetR = calcPageFillRadius(e.pageX, e.pageY);
|
||||||
|
var rippleSize = Math.min(200, (cW * .4));
|
||||||
|
var minCoverDuration = 750;
|
||||||
|
|
||||||
|
var pageFill = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
r: 0,
|
||||||
|
fill: nextColor
|
||||||
|
});
|
||||||
|
var fillAnimation = anime({
|
||||||
|
targets: pageFill,
|
||||||
|
r: targetR,
|
||||||
|
duration: Math.max(targetR / 2 , minCoverDuration ),
|
||||||
|
easing: "easeOutQuart",
|
||||||
|
complete: function(){
|
||||||
|
bgColor = pageFill.fill;
|
||||||
|
removeAnimation(fillAnimation);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var ripple = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
r: 0,
|
||||||
|
fill: currentColor,
|
||||||
|
stroke: {
|
||||||
|
width: 3,
|
||||||
|
color: currentColor
|
||||||
|
},
|
||||||
|
opacity: 1
|
||||||
|
});
|
||||||
|
var rippleAnimation = anime({
|
||||||
|
targets: ripple,
|
||||||
|
r: rippleSize,
|
||||||
|
opacity: 0,
|
||||||
|
easing: "easeOutExpo",
|
||||||
|
duration: 900,
|
||||||
|
complete: removeAnimation
|
||||||
|
});
|
||||||
|
|
||||||
|
var particles = [];
|
||||||
|
for (var i=0; i<32; i++) {
|
||||||
|
var particle = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
fill: currentColor,
|
||||||
|
r: anime.random(24, 48)
|
||||||
|
})
|
||||||
|
particles.push(particle);
|
||||||
|
}
|
||||||
|
var particlesAnimation = anime({
|
||||||
|
targets: particles,
|
||||||
|
x: function(particle){
|
||||||
|
return particle.x + anime.random(rippleSize, -rippleSize);
|
||||||
|
},
|
||||||
|
y: function(particle){
|
||||||
|
return particle.y + anime.random(rippleSize * 1.15, -rippleSize * 1.15);
|
||||||
|
},
|
||||||
|
r: 0,
|
||||||
|
easing: "easeOutExpo",
|
||||||
|
duration: anime.random(1000,1300),
|
||||||
|
complete: removeAnimation
|
||||||
|
});
|
||||||
|
animations.push(fillAnimation, rippleAnimation, particlesAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extend(a, b){
|
||||||
|
for(var key in b) {
|
||||||
|
if(b.hasOwnProperty(key)) {
|
||||||
|
a[key] = b[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Circle = function(opts) {
|
||||||
|
extend(this, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
Circle.prototype.draw = function() {
|
||||||
|
ctx.globalAlpha = this.opacity || 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
|
||||||
|
if (this.stroke) {
|
||||||
|
ctx.strokeStyle = this.stroke.color;
|
||||||
|
ctx.lineWidth = this.stroke.width;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
if (this.fill) {
|
||||||
|
ctx.fillStyle = this.fill;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var animate = anime({
|
||||||
|
duration: Infinity,
|
||||||
|
update: function() {
|
||||||
|
ctx.fillStyle = bgColor;
|
||||||
|
ctx.fillRect(0, 0, cW, cH);
|
||||||
|
animations.forEach(function(anim) {
|
||||||
|
anim.animatables.forEach(function(animatable) {
|
||||||
|
animatable.target.draw();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var resizeCanvas = function() {
|
||||||
|
cW = window.innerWidth;
|
||||||
|
cH = window.innerHeight;
|
||||||
|
c.width = cW * devicePixelRatio;
|
||||||
|
c.height = cH * devicePixelRatio;
|
||||||
|
ctx.scale(devicePixelRatio, devicePixelRatio);
|
||||||
|
};
|
||||||
|
|
||||||
|
(function init() {
|
||||||
|
resizeCanvas();
|
||||||
|
if (window.CP) {
|
||||||
|
// CodePen's loop detection was causin' problems
|
||||||
|
// and I have no idea why, so...
|
||||||
|
window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 6000;
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", resizeCanvas);
|
||||||
|
addClickListeners();
|
||||||
|
if (!!window.location.pathname.match(/fullcpgrid/)) {
|
||||||
|
startFauxClicking();
|
||||||
|
}
|
||||||
|
handleInactiveUser();
|
||||||
|
})();
|
||||||
|
|
||||||
|
function handleInactiveUser() {
|
||||||
|
var inactive = setTimeout(function(){
|
||||||
|
fauxClick(cW/2, cH/2);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
function clearInactiveTimeout() {
|
||||||
|
clearTimeout(inactive);
|
||||||
|
document.removeEventListener("mousedown", clearInactiveTimeout);
|
||||||
|
document.removeEventListener("touchstart", clearInactiveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", clearInactiveTimeout);
|
||||||
|
document.addEventListener("touchstart", clearInactiveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startFauxClicking() {
|
||||||
|
setTimeout(function(){
|
||||||
|
fauxClick(anime.random( cW * .2, cW * .8), anime.random(cH * .2, cH * .8));
|
||||||
|
startFauxClicking();
|
||||||
|
}, anime.random(200, 900));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fauxClick(x, y) {
|
||||||
|
var fauxClick = new Event("mousedown");
|
||||||
|
fauxClick.pageX = x;
|
||||||
|
fauxClick.pageY = y;
|
||||||
|
document.dispatchEvent(fauxClick);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
52
example/www/templates/blacklist.html
Normal file
42
example/www/templates/notfound.html
Normal file
52
example/www/templates/whitelist.html
Normal file
Before Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 161 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 390 KiB After Width: | Height: | Size: 74 KiB |
@ -3,7 +3,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,6 +20,157 @@ import (
|
|||||||
banning / whitelist a specific IP address or country code
|
banning / whitelist a specific IP address or country code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
General Function
|
||||||
|
*/
|
||||||
|
|
||||||
|
func handleListAccessRules(w http.ResponseWriter, r *http.Request) {
|
||||||
|
allAccessRules := accessController.ListAllAccessRules()
|
||||||
|
js, _ := json.Marshal(allAccessRules)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAttachRuleToHost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleid, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
host, err := utils.PostPara(r, "host")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if access rule and proxy rule exists
|
||||||
|
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(host)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid host given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !accessController.AccessRuleExists(ruleid) {
|
||||||
|
utils.SendErrorResponse(w, "access rule not exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update the proxy host acess rule id
|
||||||
|
targetProxyEndpoint.AccessFilterUUID = ruleid
|
||||||
|
targetProxyEndpoint.UpdateToRuntime()
|
||||||
|
err = SaveReverseProxyConfig(targetProxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new access rule, require name and desc only
|
||||||
|
func handleCreateAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleName, err := utils.PostPara(r, "name")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleDesc, _ := utils.PostPara(r, "desc")
|
||||||
|
|
||||||
|
//Filter out injection if any
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
ruleName = p.Sanitize(ruleName)
|
||||||
|
ruleDesc = p.Sanitize(ruleDesc)
|
||||||
|
|
||||||
|
ruleUUID := uuid.New().String()
|
||||||
|
newAccessRule := access.AccessRule{
|
||||||
|
ID: ruleUUID,
|
||||||
|
Name: ruleName,
|
||||||
|
Desc: ruleDesc,
|
||||||
|
BlacklistEnabled: false,
|
||||||
|
WhitelistEnabled: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add it to runtime
|
||||||
|
err = accessController.AddNewAccessRule(&newAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle removing an access rule. All proxy endpoint using this rule will be
|
||||||
|
// set to use the default rule
|
||||||
|
func handleRemoveAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule id given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ruleID == "default" {
|
||||||
|
utils.SendErrorResponse(w, "default access rule cannot be removed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleID = strings.TrimSpace(ruleID)
|
||||||
|
|
||||||
|
//Set all proxy hosts that use this access rule back to using "default"
|
||||||
|
allProxyEndpoints := dynamicProxyRouter.GetProxyEndpointsAsMap()
|
||||||
|
for _, proxyEndpoint := range allProxyEndpoints {
|
||||||
|
if strings.EqualFold(proxyEndpoint.AccessFilterUUID, ruleID) {
|
||||||
|
//This proxy endpoint is using the current access filter.
|
||||||
|
//set it to default
|
||||||
|
proxyEndpoint.AccessFilterUUID = "default"
|
||||||
|
proxyEndpoint.UpdateToRuntime()
|
||||||
|
err = SaveReverseProxyConfig(proxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Unable to save updated proxy endpoint "+proxyEndpoint.RootOrMatchingDomain, err)
|
||||||
|
} else {
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Updated "+proxyEndpoint.RootOrMatchingDomain+" access filter to \"default\"", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove the access rule by ID
|
||||||
|
err = accessController.RemoveAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Access Rule "+ruleID+" removed", nil)
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the name and desc, for other properties use blacklist / whitelist api
|
||||||
|
func handleUpadateAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleName, err := utils.PostPara(r, "name")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleDesc, _ := utils.PostPara(r, "desc")
|
||||||
|
|
||||||
|
//Filter anything weird
|
||||||
|
p := bluemonday.StrictPolicy()
|
||||||
|
ruleName = p.Sanitize(ruleName)
|
||||||
|
ruleDesc = p.Sanitize(ruleDesc)
|
||||||
|
|
||||||
|
err = accessController.UpdateAccessRule(ruleID, ruleName, ruleDesc)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Blacklist Related
|
Blacklist Related
|
||||||
*/
|
*/
|
||||||
@ -26,11 +182,24 @@ func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
|
|||||||
bltype = "country"
|
bltype = "country"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ruleID, err := utils.GetPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
//Use default if not set
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
resulst := []string{}
|
resulst := []string{}
|
||||||
if bltype == "country" {
|
if bltype == "country" {
|
||||||
resulst = geodbStore.GetAllBlacklistedCountryCode()
|
resulst = rule.GetAllBlacklistedCountryCode()
|
||||||
} else if bltype == "ip" {
|
} else if bltype == "ip" {
|
||||||
resulst = geodbStore.GetAllBlacklistedIp()
|
resulst = rule.GetAllBlacklistedIp()
|
||||||
}
|
}
|
||||||
|
|
||||||
js, _ := json.Marshal(resulst)
|
js, _ := json.Marshal(resulst)
|
||||||
@ -45,7 +214,23 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddCountryCodeToBlackList(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.PostPara(r, "comment")
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.AddCountryCodeToBlackList(countryCode, comment)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -57,7 +242,19 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveCountryCodeFromBlackList(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveCountryCodeFromBlackList(countryCode)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -69,7 +266,24 @@ func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddIPToBlackList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.GetPara(r, "comment")
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
rule.AddIPToBlackList(ipAddr, comment)
|
||||||
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -79,23 +293,46 @@ func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveIPFromBlackList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveIPFromBlackList(ipAddr)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
|
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
enable, err := utils.PostPara(r, "enable")
|
enable, _ := utils.PostPara(r, "enable")
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Return the current enabled state
|
ruleID = "default"
|
||||||
currentEnabled := geodbStore.BlacklistEnabled
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable == "" {
|
||||||
|
//enable paramter not set
|
||||||
|
currentEnabled := rule.BlacklistEnabled
|
||||||
js, _ := json.Marshal(currentEnabled)
|
js, _ := json.Marshal(currentEnabled)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else {
|
||||||
if enable == "true" {
|
if enable == "true" {
|
||||||
geodbStore.ToggleBlacklist(true)
|
rule.ToggleBlacklist(true)
|
||||||
} else if enable == "false" {
|
} else if enable == "false" {
|
||||||
geodbStore.ToggleBlacklist(false)
|
rule.ToggleBlacklist(false)
|
||||||
} else {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||||
return
|
return
|
||||||
@ -115,11 +352,22 @@ func handleListWhitelisted(w http.ResponseWriter, r *http.Request) {
|
|||||||
bltype = "country"
|
bltype = "country"
|
||||||
}
|
}
|
||||||
|
|
||||||
resulst := []string{}
|
ruleID, err := utils.GetPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resulst := []*access.WhitelistEntry{}
|
||||||
if bltype == "country" {
|
if bltype == "country" {
|
||||||
resulst = geodbStore.GetAllWhitelistedCountryCode()
|
resulst = rule.GetAllWhitelistedCountryCode()
|
||||||
} else if bltype == "ip" {
|
} else if bltype == "ip" {
|
||||||
resulst = geodbStore.GetAllWhitelistedIp()
|
resulst = rule.GetAllWhitelistedIp()
|
||||||
}
|
}
|
||||||
|
|
||||||
js, _ := json.Marshal(resulst)
|
js, _ := json.Marshal(resulst)
|
||||||
@ -134,7 +382,22 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddCountryCodeToWhitelist(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.PostPara(r, "comment")
|
||||||
|
p := bluemonday.StrictPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
rule.AddCountryCodeToWhitelist(countryCode, comment)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -146,7 +409,18 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveCountryCodeFromWhitelist(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveCountryCodeFromWhitelist(countryCode)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -158,7 +432,23 @@ func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddIPToWhiteList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.PostPara(r, "comment")
|
||||||
|
p := bluemonday.StrictPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
rule.AddIPToWhiteList(ipAddr, comment)
|
||||||
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -168,23 +458,45 @@ func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveIPFromWhiteList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveIPFromWhiteList(ipAddr)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
|
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
enable, err := utils.PostPara(r, "enable")
|
enable, _ := utils.PostPara(r, "enable")
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable == "" {
|
||||||
//Return the current enabled state
|
//Return the current enabled state
|
||||||
currentEnabled := geodbStore.WhitelistEnabled
|
currentEnabled := rule.WhitelistEnabled
|
||||||
js, _ := json.Marshal(currentEnabled)
|
js, _ := json.Marshal(currentEnabled)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else {
|
||||||
if enable == "true" {
|
if enable == "true" {
|
||||||
geodbStore.ToggleWhitelist(true)
|
rule.ToggleWhitelist(true)
|
||||||
} else if enable == "false" {
|
} else if enable == "false" {
|
||||||
geodbStore.ToggleWhitelist(false)
|
rule.ToggleWhitelist(false)
|
||||||
} else {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||||
return
|
return
|
||||||
|
15
src/acme.go
@ -38,7 +38,7 @@ func initACME() *acme.ACMEHandler {
|
|||||||
port = getRandomPort(30000)
|
port = getRandomPort(30000)
|
||||||
}
|
}
|
||||||
|
|
||||||
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port))
|
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the special routing rule for ACME
|
// create the special routing rule for ACME
|
||||||
@ -85,6 +85,8 @@ func acmeRegisterSpecialRoutingRule() {
|
|||||||
// This function check if the renew setup is satisfied. If not, toggle them automatically
|
// This function check if the renew setup is satisfied. If not, toggle them automatically
|
||||||
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
|
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
|
||||||
isForceHttpsRedirectEnabledOriginally := false
|
isForceHttpsRedirectEnabledOriginally := false
|
||||||
|
dnsPara, _ := utils.PostBool(r, "dns")
|
||||||
|
if !dnsPara {
|
||||||
if dynamicProxyRouter.Option.Port == 443 {
|
if dynamicProxyRouter.Option.Port == 443 {
|
||||||
//Enable port 80 to 443 redirect
|
//Enable port 80 to 443 redirect
|
||||||
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
|
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
|
||||||
@ -101,12 +103,21 @@ func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request)
|
|||||||
} else {
|
} else {
|
||||||
//This port do not support ACME
|
//This port do not support ACME
|
||||||
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
|
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add a 3 second delay to make sure everything is settle down
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
// Pass over to the acmeHandler to deal with the communication
|
// Pass over to the acmeHandler to deal with the communication
|
||||||
acmeHandler.HandleRenewCertificate(w, r)
|
acmeHandler.HandleRenewCertificate(w, r)
|
||||||
|
|
||||||
if dynamicProxyRouter.Option.Port == 443 {
|
//Update the TLS cert store buffer
|
||||||
|
tlsCertManager.UpdateLoadedCertList()
|
||||||
|
|
||||||
|
//Restore original settings
|
||||||
|
if dynamicProxyRouter.Option.Port == 443 && !dnsPara {
|
||||||
if !isForceHttpsRedirectEnabledOriginally {
|
if !isForceHttpsRedirectEnabledOriginally {
|
||||||
//Default is off. Turn the redirection off
|
//Default is off. Turn the redirection off
|
||||||
SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil)
|
SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil)
|
||||||
|
34
src/api.go
@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
"net/http/pprof"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||||
"imuslab.com/zoraxy/mod/acme/acmewizard"
|
"imuslab.com/zoraxy/mod/acme/acmewizard"
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/netstat"
|
"imuslab.com/zoraxy/mod/netstat"
|
||||||
@ -47,8 +48,11 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
|
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
|
||||||
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
|
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
||||||
|
authRouter.HandleFunc("/api/proxy/toggle", ReverseProxyToggleRuleSet)
|
||||||
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
|
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
|
||||||
|
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
|
||||||
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
||||||
|
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
||||||
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
||||||
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
||||||
@ -56,9 +60,16 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
|
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
|
||||||
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
||||||
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
||||||
//Reverse proxy root related APIs
|
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
||||||
authRouter.HandleFunc("/api/proxy/root/listOptions", HandleRootRouteOptionList)
|
//Reverse proxy virtual directory APIs
|
||||||
authRouter.HandleFunc("/api/proxy/root/updateOptions", HandleRootRouteOptionsUpdate)
|
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
|
||||||
|
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
|
||||||
|
authRouter.HandleFunc("/api/proxy/vdir/del", ReverseProxyDeleteVdir)
|
||||||
|
authRouter.HandleFunc("/api/proxy/vdir/edit", ReverseProxyEditVdir)
|
||||||
|
//Reverse proxy user define header apis
|
||||||
|
authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList)
|
||||||
|
authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd)
|
||||||
|
authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove)
|
||||||
//Reverse proxy auth related APIs
|
//Reverse proxy auth related APIs
|
||||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
|
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
|
||||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
|
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
|
||||||
@ -77,7 +88,14 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
|
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
|
||||||
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
|
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
|
||||||
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
||||||
|
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
|
||||||
|
|
||||||
|
//Access Rules API
|
||||||
|
authRouter.HandleFunc("/api/access/list", handleListAccessRules)
|
||||||
|
authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
|
||||||
|
authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
|
||||||
|
authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
|
||||||
|
authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)
|
||||||
//Blacklist APIs
|
//Blacklist APIs
|
||||||
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
||||||
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
||||||
@ -85,7 +103,6 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
||||||
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
||||||
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
||||||
|
|
||||||
//Whitelist APIs
|
//Whitelist APIs
|
||||||
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
||||||
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
||||||
@ -115,6 +132,8 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/gan/network/name", ganManager.HandleNetworkNaming)
|
authRouter.HandleFunc("/api/gan/network/name", ganManager.HandleNetworkNaming)
|
||||||
//authRouter.HandleFunc("/api/gan/network/detail", ganManager.HandleNetworkDetails)
|
//authRouter.HandleFunc("/api/gan/network/detail", ganManager.HandleNetworkDetails)
|
||||||
authRouter.HandleFunc("/api/gan/network/setRange", ganManager.HandleSetRanges)
|
authRouter.HandleFunc("/api/gan/network/setRange", ganManager.HandleSetRanges)
|
||||||
|
authRouter.HandleFunc("/api/gan/network/join", ganManager.HandleServerJoinNetwork)
|
||||||
|
authRouter.HandleFunc("/api/gan/network/leave", ganManager.HandleServerLeaveNetwork)
|
||||||
authRouter.HandleFunc("/api/gan/members/list", ganManager.HandleMemberList)
|
authRouter.HandleFunc("/api/gan/members/list", ganManager.HandleMemberList)
|
||||||
authRouter.HandleFunc("/api/gan/members/ip", ganManager.HandleMemberIP)
|
authRouter.HandleFunc("/api/gan/members/ip", ganManager.HandleMemberIP)
|
||||||
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
|
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
|
||||||
@ -154,6 +173,8 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet)
|
authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet)
|
||||||
authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet)
|
authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet)
|
||||||
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
|
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
|
||||||
|
authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle)
|
||||||
|
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
|
||||||
|
|
||||||
//Account Reset
|
//Account Reset
|
||||||
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
||||||
@ -166,16 +187,19 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HanldeSetDNS)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
||||||
|
authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson)
|
||||||
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard
|
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard
|
||||||
|
|
||||||
//Static Web Server
|
//Static Web Server
|
||||||
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
|
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
|
||||||
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
|
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
|
||||||
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
|
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
|
||||||
authRouter.HandleFunc("/api/webserv/setPort", staticWebServer.HandlePortChange)
|
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
|
||||||
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
|
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
|
||||||
if *allowWebFileManager {
|
if *allowWebFileManager {
|
||||||
//Web Directory Manager file operation functions
|
//Web Directory Manager file operation functions
|
||||||
|
16
src/cert.go
@ -12,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -46,12 +47,13 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
|
|||||||
LastModifiedDate string
|
LastModifiedDate string
|
||||||
ExpireDate string
|
ExpireDate string
|
||||||
RemainingDays int
|
RemainingDays int
|
||||||
|
UseDNS bool
|
||||||
}
|
}
|
||||||
|
|
||||||
results := []*CertInfo{}
|
results := []*CertInfo{}
|
||||||
|
|
||||||
for _, filename := range filenames {
|
for _, filename := range filenames {
|
||||||
certFilepath := filepath.Join(tlsCertManager.CertStore, filename+".crt")
|
certFilepath := filepath.Join(tlsCertManager.CertStore, filename+".pem")
|
||||||
//keyFilepath := filepath.Join(tlsCertManager.CertStore, filename+".key")
|
//keyFilepath := filepath.Join(tlsCertManager.CertStore, filename+".key")
|
||||||
fileInfo, err := os.Stat(certFilepath)
|
fileInfo, err := os.Stat(certFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -81,12 +83,19 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
certInfoFilename := filepath.Join(tlsCertManager.CertStore, filename+".json")
|
||||||
|
useDNSValidation := false //Default to false for HTTP TLS certificates
|
||||||
|
certInfo, err := acme.LoadCertInfoJSON(certInfoFilename) //Note: Not all certs have info json
|
||||||
|
if err == nil {
|
||||||
|
useDNSValidation = certInfo.UseDNS
|
||||||
|
}
|
||||||
|
|
||||||
thisCertInfo := CertInfo{
|
thisCertInfo := CertInfo{
|
||||||
Domain: filename,
|
Domain: filename,
|
||||||
LastModifiedDate: modifiedTime,
|
LastModifiedDate: modifiedTime,
|
||||||
ExpireDate: certExpireTime,
|
ExpireDate: certExpireTime,
|
||||||
RemainingDays: expiredIn,
|
RemainingDays: expiredIn,
|
||||||
|
UseDNS: useDNSValidation,
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, &thisCertInfo)
|
results = append(results, &thisCertInfo)
|
||||||
@ -248,7 +257,7 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if keytype == "pub" {
|
if keytype == "pub" {
|
||||||
overWriteFilename = domain + ".crt"
|
overWriteFilename = domain + ".pem"
|
||||||
} else if keytype == "pri" {
|
} else if keytype == "pri" {
|
||||||
overWriteFilename = domain + ".key"
|
overWriteFilename = domain + ".key"
|
||||||
} else {
|
} else {
|
||||||
@ -287,6 +296,9 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Update cert list
|
||||||
|
tlsCertManager.UpdateLoadedCertList()
|
||||||
|
|
||||||
// send response
|
// send response
|
||||||
fmt.Fprintln(w, "File upload successful!")
|
fmt.Fprintln(w, "File upload successful!")
|
||||||
}
|
}
|
||||||
|
170
src/config.go
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -35,97 +36,118 @@ type Record struct {
|
|||||||
BasicAuthExceptionRules []*dynamicproxy.BasicAuthExceptionRule
|
BasicAuthExceptionRules []*dynamicproxy.BasicAuthExceptionRule
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save a reverse proxy config record to file
|
/*
|
||||||
func SaveReverseProxyConfigToFile(proxyConfigRecord *Record) error {
|
Load Reverse Proxy Config from file and append it to current runtime proxy router
|
||||||
//TODO: Make this accept new def types
|
*/
|
||||||
os.MkdirAll("./conf/proxy/", 0775)
|
func LoadReverseProxyConfig(configFilepath string) error {
|
||||||
filename := getFilenameFromRootName(proxyConfigRecord.Rootname)
|
//Load the config file from disk
|
||||||
|
endpointConfig, err := os.ReadFile(configFilepath)
|
||||||
//Generate record
|
|
||||||
thisRecord := proxyConfigRecord
|
|
||||||
|
|
||||||
//Write to file
|
|
||||||
js, _ := json.MarshalIndent(thisRecord, "", " ")
|
|
||||||
return os.WriteFile(filepath.Join("./conf/proxy/", filename), js, 0775)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save a running reverse proxy endpoint to file (with automatic endpoint to record conversion)
|
|
||||||
func SaveReverseProxyEndpointToFile(proxyEndpoint *dynamicproxy.ProxyEndpoint) error {
|
|
||||||
recordToSave, err := ConvertProxyEndpointToRecord(proxyEndpoint)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return SaveReverseProxyConfigToFile(recordToSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveReverseProxyConfigFile(rootname string) error {
|
//Parse it into dynamic proxy endpoint
|
||||||
filename := getFilenameFromRootName(rootname)
|
thisConfigEndpoint := dynamicproxy.ProxyEndpoint{}
|
||||||
removePendingFile := strings.ReplaceAll(filepath.Join("./conf/proxy/", filename), "\\", "/")
|
err = json.Unmarshal(endpointConfig, &thisConfigEndpoint)
|
||||||
SystemWideLogger.Println("Config Removed: ", removePendingFile)
|
|
||||||
if utils.FileExists(removePendingFile) {
|
|
||||||
err := os.Remove(removePendingFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Proxy", "Unabel to remove config file", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Matching domain not set. Assume root
|
||||||
|
if thisConfigEndpoint.RootOrMatchingDomain == "" {
|
||||||
|
thisConfigEndpoint.RootOrMatchingDomain = "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
//File already gone
|
if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Root {
|
||||||
|
//This is a root config file
|
||||||
|
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicProxyRouter.SetProxyRouteAsRoot(rootProxyEndpoint)
|
||||||
|
|
||||||
|
} else if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Host {
|
||||||
|
//This is a host config file
|
||||||
|
readyProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicProxyRouter.AddProxyRouteToRuntime(readyProxyEndpoint)
|
||||||
|
} else {
|
||||||
|
return errors.New("not supported proxy type")
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemWideLogger.PrintAndLog("Proxy", thisConfigEndpoint.RootOrMatchingDomain+" -> "+thisConfigEndpoint.Domain+" routing rule loaded", nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return ptype, rootname and proxyTarget, error if any
|
func filterProxyConfigFilename(filename string) string {
|
||||||
func LoadReverseProxyConfig(filename string) (*Record, error) {
|
//Filter out wildcard characters
|
||||||
thisRecord := Record{
|
filename = strings.ReplaceAll(filename, "*", "(ST)")
|
||||||
ProxyType: "",
|
filename = strings.ReplaceAll(filename, "?", "(QM)")
|
||||||
Rootname: "",
|
filename = strings.ReplaceAll(filename, "[", "(OB)")
|
||||||
ProxyTarget: "",
|
filename = strings.ReplaceAll(filename, "]", "(CB)")
|
||||||
UseTLS: false,
|
filename = strings.ReplaceAll(filename, "#", "(HT)")
|
||||||
|
return filepath.ToSlash(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveReverseProxyConfig(endpoint *dynamicproxy.ProxyEndpoint) error {
|
||||||
|
//Get filename for saving
|
||||||
|
filename := filepath.Join("./conf/proxy/", endpoint.RootOrMatchingDomain+".config")
|
||||||
|
if endpoint.ProxyType == dynamicproxy.ProxyType_Root {
|
||||||
|
filename = "./conf/proxy/root.config"
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = filterProxyConfigFilename(filename)
|
||||||
|
|
||||||
|
//Save config to file
|
||||||
|
js, err := json.MarshalIndent(endpoint, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(filename, js, 0775)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveReverseProxyConfig(endpoint string) error {
|
||||||
|
filename := filepath.Join("./conf/proxy/", endpoint+".config")
|
||||||
|
if endpoint == "/" {
|
||||||
|
filename = "./conf/proxy/root.config"
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = filterProxyConfigFilename(filename)
|
||||||
|
|
||||||
|
if !utils.FileExists(filename) {
|
||||||
|
return errors.New("target endpoint not exists")
|
||||||
|
}
|
||||||
|
return os.Remove(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the default root config that point to the internal static web server
|
||||||
|
// this will be used if root config is not found (new deployment / missing root.config file)
|
||||||
|
func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
|
||||||
|
//Default settings
|
||||||
|
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
|
||||||
|
ProxyType: dynamicproxy.ProxyType_Root,
|
||||||
|
RootOrMatchingDomain: "/",
|
||||||
|
Domain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
||||||
|
RequireTLS: false,
|
||||||
BypassGlobalTLS: false,
|
BypassGlobalTLS: false,
|
||||||
SkipTlsValidation: false,
|
SkipCertValidations: false,
|
||||||
|
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||||
RequireBasicAuth: false,
|
RequireBasicAuth: false,
|
||||||
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
||||||
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
|
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
|
||||||
}
|
DefaultSiteOption: dynamicproxy.DefaultSite_InternalStaticWebServer,
|
||||||
|
DefaultSiteValue: "",
|
||||||
configContent, err := os.ReadFile(filename)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &thisRecord, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//Unmarshal the content into config
|
return rootProxyEndpoint, nil
|
||||||
err = json.Unmarshal(configContent, &thisRecord)
|
|
||||||
if err != nil {
|
|
||||||
return &thisRecord, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//Return it
|
|
||||||
return &thisRecord, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a running proxy endpoint object into a save-able record struct
|
|
||||||
func ConvertProxyEndpointToRecord(targetProxyEndpoint *dynamicproxy.ProxyEndpoint) (*Record, error) {
|
|
||||||
thisProxyConfigRecord := Record{
|
|
||||||
ProxyType: targetProxyEndpoint.GetProxyTypeString(),
|
|
||||||
Rootname: targetProxyEndpoint.RootOrMatchingDomain,
|
|
||||||
ProxyTarget: targetProxyEndpoint.Domain,
|
|
||||||
UseTLS: targetProxyEndpoint.RequireTLS,
|
|
||||||
BypassGlobalTLS: targetProxyEndpoint.BypassGlobalTLS,
|
|
||||||
SkipTlsValidation: targetProxyEndpoint.SkipCertValidations,
|
|
||||||
RequireBasicAuth: targetProxyEndpoint.RequireBasicAuth,
|
|
||||||
BasicAuthCredentials: targetProxyEndpoint.BasicAuthCredentials,
|
|
||||||
BasicAuthExceptionRules: targetProxyEndpoint.BasicAuthExceptionRules,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &thisProxyConfigRecord, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFilenameFromRootName(rootname string) string {
|
|
||||||
//Generate a filename for this rootname
|
|
||||||
filename := strings.ReplaceAll(rootname, ".", "_")
|
|
||||||
filename = strings.ReplaceAll(filename, "/", "-")
|
|
||||||
filename = filename + ".config"
|
|
||||||
return filename
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -133,7 +155,7 @@ func getFilenameFromRootName(rootname string) string {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
||||||
includeSysDBRaw, err := utils.GetPara(r, "includeDB")
|
includeSysDBRaw, _ := utils.GetPara(r, "includeDB")
|
||||||
includeSysDB := false
|
includeSysDB := false
|
||||||
if includeSysDBRaw == "true" {
|
if includeSysDBRaw == "true" {
|
||||||
//Include the system database in backup snapshot
|
//Include the system database in backup snapshot
|
||||||
@ -155,7 +177,7 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer zipWriter.Close()
|
defer zipWriter.Close()
|
||||||
|
|
||||||
// Walk through the folder and add files to the zip
|
// Walk through the folder and add files to the zip
|
||||||
err = filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error {
|
err := filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domain, err := utils.PostPara(r, "domain")
|
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, "domain cannot be empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
portString, err := utils.PostPara(r, "port")
|
portString, err := utils.PostPara(r, "port")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "port must be a valid integer")
|
utils.SendErrorResponse(w, "port must be a valid integer")
|
||||||
@ -76,7 +70,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Set the email sender properties
|
//Set the email sender properties
|
||||||
thisEmailSender := email.Sender{
|
thisEmailSender := email.Sender{
|
||||||
Hostname: strings.TrimSpace(hostname),
|
Hostname: strings.TrimSpace(hostname),
|
||||||
Domain: strings.TrimSpace(domain),
|
|
||||||
Port: port,
|
Port: port,
|
||||||
Username: strings.TrimSpace(username),
|
Username: strings.TrimSpace(username),
|
||||||
Password: strings.TrimSpace(password),
|
Password: strings.TrimSpace(password),
|
||||||
@ -206,7 +199,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
|
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
|
||||||
if EmailSender.Username == "" || EmailSender.Domain == "" {
|
if EmailSender.Username == "" {
|
||||||
//Reset account not setup
|
//Reset account not setup
|
||||||
utils.SendErrorResponse(w, "Reset account not setup.")
|
utils.SendErrorResponse(w, "Reset account not setup.")
|
||||||
return
|
return
|
||||||
|
174
src/go.mod
@ -1,18 +1,174 @@
|
|||||||
module imuslab.com/zoraxy
|
module imuslab.com/zoraxy
|
||||||
|
|
||||||
go 1.16
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/boltdb/bolt v1.3.1
|
github.com/boltdb/bolt v1.3.1
|
||||||
github.com/go-acme/lego/v4 v4.14.0
|
github.com/go-acme/lego/v4 v4.16.1
|
||||||
github.com/go-ping/ping v1.1.0
|
github.com/go-ping/ping v1.1.0
|
||||||
github.com/google/uuid v1.3.1
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/sessions v1.2.1
|
github.com/gorilla/sessions v1.2.2
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.1
|
||||||
github.com/grandcat/zeroconf v1.0.0
|
github.com/grandcat/zeroconf v1.0.0
|
||||||
github.com/likexian/whois v1.15.1
|
github.com/likexian/whois v1.15.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.25
|
github.com/microcosm-cc/bluemonday v1.0.26
|
||||||
golang.org/x/net v0.14.0
|
golang.org/x/net v0.23.0
|
||||||
golang.org/x/sys v0.11.0
|
golang.org/x/sys v0.18.0
|
||||||
golang.org/x/tools v0.12.0 // indirect
|
golang.org/x/text v0.14.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go/compute v1.20.1 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
|
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
||||||
|
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||||
|
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
|
||||||
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
|
||||||
|
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
||||||
|
github.com/aws/smithy-go v1.19.0 // indirect
|
||||||
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
|
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/civo/civogo v0.3.11 // indirect
|
||||||
|
github.com/cloudflare/cloudflare-go v0.86.0 // indirect
|
||||||
|
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
||||||
|
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||||
|
github.com/dnsimple/dnsimple-go v1.2.0 // indirect
|
||||||
|
github.com/exoscale/egoscale v0.102.3 // indirect
|
||||||
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
|
github.com/go-errors/errors v1.0.1 // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||||
|
github.com/go-resty/resty/v2 v2.11.0 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/google/s2a-go v0.1.4 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
|
||||||
|
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
||||||
|
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
||||||
|
github.com/gorilla/css v1.0.1 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||||
|
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||||
|
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||||
|
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||||
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
|
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||||
|
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||||
|
github.com/linode/linodego v1.28.0 // indirect
|
||||||
|
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
|
||||||
|
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/miekg/dns v1.1.58 // indirect
|
||||||
|
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||||
|
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||||
|
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect
|
||||||
|
github.com/nrdcg/desec v0.7.0 // indirect
|
||||||
|
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||||
|
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||||
|
github.com/nrdcg/goinwx v0.10.0 // indirect
|
||||||
|
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
||||||
|
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||||
|
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||||
|
github.com/nrdcg/porkbun v0.3.0 // indirect
|
||||||
|
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||||
|
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||||
|
github.com/ovh/go-ovh v1.4.3 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/pquerna/otp v1.4.0 // indirect
|
||||||
|
github.com/sacloud/api-client-go v0.2.8 // indirect
|
||||||
|
github.com/sacloud/go-http v0.1.6 // indirect
|
||||||
|
github.com/sacloud/iaas-api-go v1.11.1 // indirect
|
||||||
|
github.com/sacloud/packages-go v0.0.9 // indirect
|
||||||
|
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||||
|
github.com/softlayer/softlayer-go v1.1.3 // indirect
|
||||||
|
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||||
|
github.com/spf13/cast v1.3.1 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.1 // indirect
|
||||||
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||||
|
github.com/transip/gotransip/v6 v6.23.0 // indirect
|
||||||
|
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect
|
||||||
|
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||||
|
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
||||||
|
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
|
||||||
|
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
go.uber.org/ratelimit v0.2.0 // indirect
|
||||||
|
golang.org/x/crypto v0.21.0 // indirect
|
||||||
|
golang.org/x/mod v0.16.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.16.0 // indirect
|
||||||
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
|
golang.org/x/time v0.5.0 // indirect
|
||||||
|
golang.org/x/tools v0.19.0 // indirect
|
||||||
|
google.golang.org/api v0.126.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||||
|
google.golang.org/grpc v1.55.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
1347
src/go.sum
34
src/main.go
@ -12,12 +12,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/acme"
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/aroz"
|
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||||
"imuslab.com/zoraxy/mod/email"
|
"imuslab.com/zoraxy/mod/email"
|
||||||
|
"imuslab.com/zoraxy/mod/forwardproxy"
|
||||||
"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"
|
||||||
@ -35,10 +36,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// General flags
|
// General flags
|
||||||
|
var webUIPort = flag.String("port", ":8000", "Management web interface listening port")
|
||||||
var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
|
var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
|
||||||
var showver = flag.Bool("version", false, "Show version of this server")
|
var showver = flag.Bool("version", false, "Show version of this server")
|
||||||
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
||||||
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
||||||
|
var mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
|
||||||
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
||||||
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||||
var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
||||||
@ -49,7 +52,7 @@ var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
name = "Zoraxy"
|
name = "Zoraxy"
|
||||||
version = "2.6.8"
|
version = "3.0.4"
|
||||||
nodeUUID = "generic"
|
nodeUUID = "generic"
|
||||||
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()
|
||||||
@ -63,13 +66,13 @@ var (
|
|||||||
/*
|
/*
|
||||||
Handler Modules
|
Handler Modules
|
||||||
*/
|
*/
|
||||||
handler *aroz.ArozHandler //Handle arozos managed permission system
|
|
||||||
sysdb *database.Database //System database
|
sysdb *database.Database //System database
|
||||||
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
|
||||||
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, also handle black list and whitelist features
|
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||||
|
accessController *access.Controller //Access controller, handle black list and white list
|
||||||
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||||
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||||
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
||||||
@ -80,6 +83,7 @@ var (
|
|||||||
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
|
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
|
||||||
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
|
||||||
|
|
||||||
//Helper modules
|
//Helper modules
|
||||||
EmailSender *email.Sender //Email sender that handle email sending
|
EmailSender *email.Sender //Email sender that handle email sending
|
||||||
@ -128,20 +132,8 @@ func ShutdownSeq() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
//Start the aoModule pipeline (which will parse the flags as well). Pass in the module launch information
|
//Parse startup flags
|
||||||
handler = aroz.HandleFlagParse(aroz.ServiceInfo{
|
flag.Parse()
|
||||||
Name: name,
|
|
||||||
Desc: "Dynamic Reverse Proxy Server",
|
|
||||||
Group: "Network",
|
|
||||||
IconPath: "zoraxy/img/small_icon.png",
|
|
||||||
Version: version,
|
|
||||||
StartDir: "zoraxy/index.html",
|
|
||||||
SupportFW: true,
|
|
||||||
LaunchFWDir: "zoraxy/index.html",
|
|
||||||
SupportEmb: false,
|
|
||||||
InitFWSize: []int{1080, 580},
|
|
||||||
})
|
|
||||||
|
|
||||||
if *showver {
|
if *showver {
|
||||||
fmt.Println(name + " - Version " + version)
|
fmt.Println(name + " - Version " + version)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@ -166,7 +158,7 @@ func main() {
|
|||||||
startupSequence()
|
startupSequence()
|
||||||
|
|
||||||
//Initiate management interface APIs
|
//Initiate management interface APIs
|
||||||
requireAuth = !(*noauth || handler.IsUsingExternalPermissionManager())
|
requireAuth = !(*noauth)
|
||||||
initAPIs()
|
initAPIs()
|
||||||
|
|
||||||
//Start the reverse proxy server in go routine
|
//Start the reverse proxy server in go routine
|
||||||
@ -179,8 +171,8 @@ func main() {
|
|||||||
//Start the finalize sequences
|
//Start the finalize sequences
|
||||||
finalSequence()
|
finalSequence()
|
||||||
|
|
||||||
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + handler.Port)
|
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
|
||||||
err = http.ListenAndServe(handler.Port, nil)
|
err = http.ListenAndServe(*webUIPort, nil)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
221
src/mod/access/access.go
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Access.go
|
||||||
|
|
||||||
|
This module is the new version of access control system
|
||||||
|
where now the blacklist / whitelist are seperated from
|
||||||
|
geodb module
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Create a new access controller to handle blacklist / whitelist
|
||||||
|
func NewAccessController(options *Options) (*Controller, error) {
|
||||||
|
sysdb := options.Database
|
||||||
|
if sysdb == nil {
|
||||||
|
return nil, errors.New("missing database access")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create the config folder if not exists
|
||||||
|
confFolder := options.ConfigFolder
|
||||||
|
if !utils.FileExists(confFolder) {
|
||||||
|
err := os.MkdirAll(confFolder, 0775)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the global access rule if not exists
|
||||||
|
var defaultAccessRule = AccessRule{
|
||||||
|
ID: "default",
|
||||||
|
Name: "Default",
|
||||||
|
Desc: "Default access rule for all HTTP proxy hosts",
|
||||||
|
BlacklistEnabled: false,
|
||||||
|
WhitelistEnabled: false,
|
||||||
|
WhiteListCountryCode: &map[string]string{},
|
||||||
|
WhiteListIP: &map[string]string{},
|
||||||
|
BlackListContryCode: &map[string]string{},
|
||||||
|
BlackListIP: &map[string]string{},
|
||||||
|
}
|
||||||
|
defaultRuleSettingFile := filepath.Join(confFolder, "default.json")
|
||||||
|
if utils.FileExists(defaultRuleSettingFile) {
|
||||||
|
//Load from file
|
||||||
|
defaultRuleBytes, err := os.ReadFile(defaultRuleSettingFile)
|
||||||
|
if err == nil {
|
||||||
|
err = json.Unmarshal(defaultRuleBytes, &defaultAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to parse default routing rule config file. Using default", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Create one
|
||||||
|
js, _ := json.MarshalIndent(defaultAccessRule, "", " ")
|
||||||
|
os.WriteFile(defaultRuleSettingFile, js, 0775)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate a controller object
|
||||||
|
thisController := Controller{
|
||||||
|
DefaultAccessRule: &defaultAccessRule,
|
||||||
|
ProxyAccessRule: &sync.Map{},
|
||||||
|
Options: options,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assign default access rule parent
|
||||||
|
thisController.DefaultAccessRule.parent = &thisController
|
||||||
|
|
||||||
|
//Load all acccess rules from file
|
||||||
|
configFiles, err := filepath.Glob(options.ConfigFolder + "/*.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ProxyAccessRules := sync.Map{}
|
||||||
|
for _, configFile := range configFiles {
|
||||||
|
if filepath.Base(configFile) == "default.json" {
|
||||||
|
//Skip this, as this was already loaded as default
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configContent, err := os.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to load config "+filepath.Base(configFile), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse the config file into AccessRule
|
||||||
|
thisAccessRule := AccessRule{}
|
||||||
|
err = json.Unmarshal(configContent, &thisAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to parse config "+filepath.Base(configFile), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
thisAccessRule.parent = &thisController
|
||||||
|
ProxyAccessRules.Store(thisAccessRule.ID, &thisAccessRule)
|
||||||
|
}
|
||||||
|
thisController.ProxyAccessRule = &ProxyAccessRules
|
||||||
|
|
||||||
|
return &thisController, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the global access rule
|
||||||
|
func (c *Controller) GetGlobalAccessRule() (*AccessRule, error) {
|
||||||
|
if c.DefaultAccessRule == nil {
|
||||||
|
return nil, errors.New("global access rule is not set")
|
||||||
|
}
|
||||||
|
return c.DefaultAccessRule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load access rules to runtime, require rule ID
|
||||||
|
func (c *Controller) GetAccessRuleByID(accessRuleID string) (*AccessRule, error) {
|
||||||
|
if accessRuleID == "default" || accessRuleID == "" {
|
||||||
|
|
||||||
|
return c.DefaultAccessRule, nil
|
||||||
|
}
|
||||||
|
//Load from sync.Map, should be O(1)
|
||||||
|
targetRule, ok := c.ProxyAccessRule.Load(accessRuleID)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("target access rule not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
ar, ok := targetRule.(*AccessRule)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("assertion of access rule failed, version too old?")
|
||||||
|
}
|
||||||
|
return ar, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all the access rules currently in runtime, including default
|
||||||
|
func (c *Controller) ListAllAccessRules() []*AccessRule {
|
||||||
|
results := []*AccessRule{c.DefaultAccessRule}
|
||||||
|
c.ProxyAccessRule.Range(func(key, value interface{}) bool {
|
||||||
|
results = append(results, value.(*AccessRule))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an access rule exists given the rule id
|
||||||
|
func (c *Controller) AccessRuleExists(ruleID string) bool {
|
||||||
|
r, _ := c.GetAccessRuleByID(ruleID)
|
||||||
|
if r != nil {
|
||||||
|
//An access rule with identical ID exists
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new access rule to runtime and save it to file
|
||||||
|
func (c *Controller) AddNewAccessRule(newRule *AccessRule) error {
|
||||||
|
r, _ := c.GetAccessRuleByID(newRule.ID)
|
||||||
|
if r != nil {
|
||||||
|
//An access rule with identical ID exists
|
||||||
|
return errors.New("access rule already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the blacklist and whitelist are populated with empty map
|
||||||
|
if newRule.BlackListContryCode == nil {
|
||||||
|
newRule.BlackListContryCode = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.BlackListIP == nil {
|
||||||
|
newRule.BlackListIP = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.WhiteListCountryCode == nil {
|
||||||
|
newRule.WhiteListCountryCode = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.WhiteListIP == nil {
|
||||||
|
newRule.WhiteListIP = &map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add access rule to runtime
|
||||||
|
newRule.parent = c
|
||||||
|
c.ProxyAccessRule.Store(newRule.ID, newRule)
|
||||||
|
|
||||||
|
//Save rule to file
|
||||||
|
newRule.SaveChanges()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the access rule meta info.
|
||||||
|
func (c *Controller) UpdateAccessRule(ruleID string, name string, desc string) error {
|
||||||
|
targetAccessRule, err := c.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
///Update the name and desc
|
||||||
|
targetAccessRule.Name = name
|
||||||
|
targetAccessRule.Desc = desc
|
||||||
|
|
||||||
|
//Overwrite the rule currently in sync map
|
||||||
|
if ruleID == "default" {
|
||||||
|
c.DefaultAccessRule = targetAccessRule
|
||||||
|
} else {
|
||||||
|
c.ProxyAccessRule.Store(ruleID, targetAccessRule)
|
||||||
|
}
|
||||||
|
return targetAccessRule.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the access rule by its id
|
||||||
|
func (c *Controller) RemoveAccessRuleByID(ruleID string) error {
|
||||||
|
if !c.AccessRuleExists(ruleID) {
|
||||||
|
return errors.New("access rule not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Default cannot be removed
|
||||||
|
if ruleID == "default" {
|
||||||
|
return errors.New("default access rule cannot be removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove it
|
||||||
|
return c.DeleteAccessRuleByID(ruleID)
|
||||||
|
}
|
153
src/mod/access/accessRule.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check both blacklist and whitelist for access for both geoIP and ip / CIDR ranges
|
||||||
|
func (s *AccessRule) AllowIpAccess(ipaddr string) bool {
|
||||||
|
if s.IsBlacklisted(ipaddr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.IsWhitelisted(ipaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check both blacklist and whitelist for access using net.Conn
|
||||||
|
func (s *AccessRule) AllowConnectionAccess(conn net.Conn) bool {
|
||||||
|
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||||
|
return s.AllowIpAccess(addr.IP.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle black list
|
||||||
|
func (s *AccessRule) ToggleBlacklist(enabled bool) {
|
||||||
|
s.BlacklistEnabled = enabled
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggel white list
|
||||||
|
func (s *AccessRule) ToggleWhitelist(enabled bool) {
|
||||||
|
s.WhitelistEnabled = enabled
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if a IP address is blacklisted, in either country or IP blacklist
|
||||||
|
IsBlacklisted default return is false (allow access)
|
||||||
|
*/
|
||||||
|
func (s *AccessRule) IsBlacklisted(ipAddr string) bool {
|
||||||
|
if !s.BlacklistEnabled {
|
||||||
|
//Blacklist not enabled. Always return false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipAddr == "" {
|
||||||
|
//Unable to get the target IP address
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsCountryCodeBlacklisted(countryCode.CountryIsoCode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsIPBlacklisted(ipAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsWhitelisted check if a given IP address is in the current
|
||||||
|
server's white list.
|
||||||
|
|
||||||
|
Note that the Whitelist default result is true even
|
||||||
|
when encountered error
|
||||||
|
*/
|
||||||
|
func (s *AccessRule) IsWhitelisted(ipAddr string) bool {
|
||||||
|
if !s.WhitelistEnabled {
|
||||||
|
//Whitelist not enabled. Always return true (allow access)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipAddr == "" {
|
||||||
|
//Unable to get the target IP address, assume ok
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsCountryCodeWhitelisted(countryCode.CountryIsoCode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsIPWhitelisted(ipAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilities function */
|
||||||
|
|
||||||
|
// Update the current access rule to json file
|
||||||
|
func (s *AccessRule) SaveChanges() error {
|
||||||
|
if s.parent == nil {
|
||||||
|
return errors.New("save failed: access rule detached from controller")
|
||||||
|
}
|
||||||
|
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
|
||||||
|
js, err := json.MarshalIndent(s, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(saveTarget, js, 0775)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete this access rule, this will only delete the config file.
|
||||||
|
// for runtime delete, use DeleteAccessRuleByID from parent Controller
|
||||||
|
func (s *AccessRule) DeleteConfigFile() error {
|
||||||
|
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
|
||||||
|
return os.Remove(saveTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the access rule by given ID
|
||||||
|
func (c *Controller) DeleteAccessRuleByID(accessRuleID string) error {
|
||||||
|
targetAccessRule, err := c.GetAccessRuleByID(accessRuleID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete config file associated with this access rule
|
||||||
|
err = targetAccessRule.DeleteConfigFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete the access rule in runtime
|
||||||
|
c.ProxyAccessRule.Delete(accessRuleID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a deep copy object of the access rule list
|
||||||
|
func deepCopy(valueList map[string]string) map[string]string {
|
||||||
|
result := map[string]string{}
|
||||||
|
js, _ := json.Marshal(valueList)
|
||||||
|
json.Unmarshal(js, &result)
|
||||||
|
return result
|
||||||
|
}
|
94
src/mod/access/blacklist.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Blacklist.go
|
||||||
|
|
||||||
|
This script store the blacklist related functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Geo Blacklist
|
||||||
|
func (s *AccessRule) AddCountryCodeToBlackList(countryCode string, comment string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
|
||||||
|
newBlacklistCountryCode[countryCode] = comment
|
||||||
|
s.BlackListContryCode = &newBlacklistCountryCode
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveCountryCodeFromBlackList(countryCode string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
|
||||||
|
delete(newBlacklistCountryCode, countryCode)
|
||||||
|
s.BlackListContryCode = &newBlacklistCountryCode
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsCountryCodeBlacklisted(countryCode string) bool {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
blacklistMap := *s.BlackListContryCode
|
||||||
|
_, ok := blacklistMap[countryCode]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllBlacklistedCountryCode() []string {
|
||||||
|
bannedCountryCodes := []string{}
|
||||||
|
blacklistMap := *s.BlackListContryCode
|
||||||
|
for cc, _ := range blacklistMap {
|
||||||
|
bannedCountryCodes = append(bannedCountryCodes, cc)
|
||||||
|
}
|
||||||
|
return bannedCountryCodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP Blacklsits
|
||||||
|
func (s *AccessRule) AddIPToBlackList(ipAddr string, comment string) {
|
||||||
|
newBlackListIP := deepCopy(*s.BlackListIP)
|
||||||
|
newBlackListIP[ipAddr] = comment
|
||||||
|
s.BlackListIP = &newBlackListIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveIPFromBlackList(ipAddr string) {
|
||||||
|
newBlackListIP := deepCopy(*s.BlackListIP)
|
||||||
|
delete(newBlackListIP, ipAddr)
|
||||||
|
s.BlackListIP = &newBlackListIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllBlacklistedIp() []string {
|
||||||
|
bannedIps := []string{}
|
||||||
|
blacklistMap := *s.BlackListIP
|
||||||
|
for ip, _ := range blacklistMap {
|
||||||
|
bannedIps = append(bannedIps, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bannedIps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsIPBlacklisted(ipAddr string) bool {
|
||||||
|
IPBlacklist := *s.BlackListIP
|
||||||
|
_, ok := IPBlacklist[ipAddr]
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for CIDR
|
||||||
|
for ipOrCIDR, _ := range IPBlacklist {
|
||||||
|
wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR)
|
||||||
|
if wildcardMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR)
|
||||||
|
if cidrMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
38
src/mod/access/typedef.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Logger logger.Logger
|
||||||
|
ConfigFolder string //Path for storing config files
|
||||||
|
GeoDB *geodb.Store //For resolving country code
|
||||||
|
Database *database.Database //System key-value database
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessRule struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Desc string
|
||||||
|
BlacklistEnabled bool
|
||||||
|
WhitelistEnabled bool
|
||||||
|
|
||||||
|
/* Whitelist Blacklist Table, value is comment if supported */
|
||||||
|
WhiteListCountryCode *map[string]string
|
||||||
|
WhiteListIP *map[string]string
|
||||||
|
BlackListContryCode *map[string]string
|
||||||
|
BlackListIP *map[string]string
|
||||||
|
|
||||||
|
parent *Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
DefaultAccessRule *AccessRule
|
||||||
|
ProxyAccessRule *sync.Map
|
||||||
|
Options *Options
|
||||||
|
}
|
112
src/mod/access/whitelist.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Whitelist.go
|
||||||
|
|
||||||
|
This script handles whitelist related functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
EntryType_CountryCode int = 0
|
||||||
|
EntryType_IP int = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type WhitelistEntry struct {
|
||||||
|
EntryType int //Entry type of whitelist, Country Code or IP
|
||||||
|
CC string //ISO Country Code
|
||||||
|
IP string //IP address or range
|
||||||
|
Comment string //Comment for this entry
|
||||||
|
}
|
||||||
|
|
||||||
|
//Geo Whitelist
|
||||||
|
|
||||||
|
func (s *AccessRule) AddCountryCodeToWhitelist(countryCode string, comment string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
|
||||||
|
newWhitelistCC[countryCode] = comment
|
||||||
|
s.WhiteListCountryCode = &newWhitelistCC
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveCountryCodeFromWhitelist(countryCode string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
|
||||||
|
delete(newWhitelistCC, countryCode)
|
||||||
|
s.WhiteListCountryCode = &newWhitelistCC
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsCountryCodeWhitelisted(countryCode string) bool {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
whitelistCC := *s.WhiteListCountryCode
|
||||||
|
_, ok := whitelistCC[countryCode]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllWhitelistedCountryCode() []*WhitelistEntry {
|
||||||
|
whitelistedCountryCode := []*WhitelistEntry{}
|
||||||
|
whitelistCC := *s.WhiteListCountryCode
|
||||||
|
for cc, comment := range whitelistCC {
|
||||||
|
whitelistedCountryCode = append(whitelistedCountryCode, &WhitelistEntry{
|
||||||
|
EntryType: EntryType_CountryCode,
|
||||||
|
CC: cc,
|
||||||
|
Comment: comment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return whitelistedCountryCode
|
||||||
|
}
|
||||||
|
|
||||||
|
//IP Whitelist
|
||||||
|
|
||||||
|
func (s *AccessRule) AddIPToWhiteList(ipAddr string, comment string) {
|
||||||
|
newWhitelistIP := deepCopy(*s.WhiteListIP)
|
||||||
|
newWhitelistIP[ipAddr] = comment
|
||||||
|
s.WhiteListIP = &newWhitelistIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveIPFromWhiteList(ipAddr string) {
|
||||||
|
newWhitelistIP := deepCopy(*s.WhiteListIP)
|
||||||
|
delete(newWhitelistIP, ipAddr)
|
||||||
|
s.WhiteListIP = &newWhitelistIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsIPWhitelisted(ipAddr string) bool {
|
||||||
|
//Check for IP wildcard and CIRD rules
|
||||||
|
WhitelistedIP := *s.WhiteListIP
|
||||||
|
for ipOrCIDR, _ := range WhitelistedIP {
|
||||||
|
wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR)
|
||||||
|
if wildcardMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR)
|
||||||
|
if cidrMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllWhitelistedIp() []*WhitelistEntry {
|
||||||
|
whitelistedIp := []*WhitelistEntry{}
|
||||||
|
currentWhitelistedIP := *s.WhiteListIP
|
||||||
|
for ipOrCIDR, comment := range currentWhitelistedIP {
|
||||||
|
thisEntry := WhitelistEntry{
|
||||||
|
EntryType: EntryType_IP,
|
||||||
|
IP: ipOrCIDR,
|
||||||
|
Comment: comment,
|
||||||
|
}
|
||||||
|
whitelistedIp = append(whitelistedIp, &thisEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return whitelistedIp
|
||||||
|
}
|
@ -9,6 +9,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@ -24,6 +25,7 @@ import (
|
|||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,6 +33,7 @@ type CertificateInfoJSON struct {
|
|||||||
AcmeName string `json:"acme_name"`
|
AcmeName string `json:"acme_name"`
|
||||||
AcmeUrl string `json:"acme_url"`
|
AcmeUrl string `json:"acme_url"`
|
||||||
SkipTLS bool `json:"skip_tls"`
|
SkipTLS bool `json:"skip_tls"`
|
||||||
|
UseDNS bool `json:"dns"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACMEUser represents a user in the ACME system.
|
// ACMEUser represents a user in the ACME system.
|
||||||
@ -40,6 +43,11 @@ type ACMEUser struct {
|
|||||||
key crypto.PrivateKey
|
key crypto.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EABConfig struct {
|
||||||
|
Kid string `json:"kid"`
|
||||||
|
HmacKey string `json:"HmacKey"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetEmail returns the email of the ACMEUser.
|
// GetEmail returns the email of the ACMEUser.
|
||||||
func (u *ACMEUser) GetEmail() string {
|
func (u *ACMEUser) GetEmail() string {
|
||||||
return u.Email
|
return u.Email
|
||||||
@ -59,18 +67,20 @@ func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey {
|
|||||||
type ACMEHandler struct {
|
type ACMEHandler struct {
|
||||||
DefaultAcmeServer string
|
DefaultAcmeServer string
|
||||||
Port string
|
Port string
|
||||||
|
Database *database.Database
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewACME creates a new ACMEHandler instance.
|
// NewACME creates a new ACMEHandler instance.
|
||||||
func NewACME(acmeServer string, port string) *ACMEHandler {
|
func NewACME(acmeServer string, port string, database *database.Database) *ACMEHandler {
|
||||||
return &ACMEHandler{
|
return &ACMEHandler{
|
||||||
DefaultAcmeServer: acmeServer,
|
DefaultAcmeServer: acmeServer,
|
||||||
Port: port,
|
Port: port,
|
||||||
|
Database: database,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObtainCert obtains a certificate for the specified domains.
|
// ObtainCert obtains a certificate for the specified domains.
|
||||||
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool) (bool, error) {
|
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool, useDNS bool) (bool, error) {
|
||||||
log.Println("[ACME] Obtaining certificate...")
|
log.Println("[ACME] Obtaining certificate...")
|
||||||
|
|
||||||
// generate private key
|
// generate private key
|
||||||
@ -107,6 +117,11 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Fallback to Let's Encrypt if it is not set
|
||||||
|
if caName == "" {
|
||||||
|
caName = "Let's Encrypt"
|
||||||
|
}
|
||||||
|
|
||||||
// setup the custom ACME url endpoint.
|
// setup the custom ACME url endpoint.
|
||||||
if caUrl != "" {
|
if caUrl != "" {
|
||||||
config.CADirURL = caUrl
|
config.CADirURL = caUrl
|
||||||
@ -136,18 +151,108 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setup how to receive challenge
|
// setup how to receive challenge
|
||||||
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
|
if useDNS {
|
||||||
|
if !a.Database.TableExists("acme") {
|
||||||
|
a.Database.NewTable("acme")
|
||||||
|
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.Database.KeyExists("acme", certificateName+"_dns_provider") || !a.Database.KeyExists("acme", certificateName+"_dns_credentials") {
|
||||||
|
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -2)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var dnsCredentials string
|
||||||
|
err := a.Database.Read("acme", certificateName+"_dns_credentials", &dnsCredentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dnsProvider string
|
||||||
|
err = a.Database.Read("acme", certificateName+"_dns_provider", &dnsProvider)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := GetDnsChallengeProviderByName(dnsProvider, dnsCredentials)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.Challenge.SetDNS01Provider(provider)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// New users will need to register
|
// New users will need to register
|
||||||
|
/*
|
||||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
var reg *registration.Resource
|
||||||
|
// New users will need to register
|
||||||
|
if client.GetExternalAccountRequired() {
|
||||||
|
log.Println("External Account Required for this ACME Provider.")
|
||||||
|
// IF KID and HmacEncoded is overidden
|
||||||
|
|
||||||
|
if !a.Database.TableExists("acme") {
|
||||||
|
a.Database.NewTable("acme")
|
||||||
|
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.Database.KeyExists("acme", config.CADirURL+"_kid") || !a.Database.KeyExists("acme", config.CADirURL+"_hmacEncoded") {
|
||||||
|
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -2)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var kid string
|
||||||
|
var hmacEncoded string
|
||||||
|
err := a.Database.Read("acme", config.CADirURL+"_kid", &kid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Database.Read("acme", config.CADirURL+"_hmacEncoded", &hmacEncoded)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("EAB Credential retrieved.", kid, hmacEncoded)
|
||||||
|
if kid != "" && hmacEncoded != "" {
|
||||||
|
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||||
|
TermsOfServiceAgreed: true,
|
||||||
|
Kid: kid,
|
||||||
|
HmacEncoded: hmacEncoded,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
//return false, errors.New("External Account Required for this ACME Provider.")
|
||||||
|
} else {
|
||||||
|
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
adminUser.Registration = reg
|
adminUser.Registration = reg
|
||||||
|
|
||||||
// obtain the certificate
|
// obtain the certificate
|
||||||
@ -163,7 +268,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
|
|
||||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||||
// private key, and a certificate URL.
|
// private key, and a certificate URL.
|
||||||
err = os.WriteFile("./conf/certs/"+certificateName+".crt", certificates.Certificate, 0777)
|
err = os.WriteFile("./conf/certs/"+certificateName+".pem", certificates.Certificate, 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return false, err
|
return false, err
|
||||||
@ -179,6 +284,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
AcmeName: caName,
|
AcmeName: caName,
|
||||||
AcmeUrl: caUrl,
|
AcmeUrl: caUrl,
|
||||||
SkipTLS: skipTLS,
|
SkipTLS: skipTLS,
|
||||||
|
UseDNS: useDNS,
|
||||||
}
|
}
|
||||||
|
|
||||||
certInfoBytes, err := json.Marshal(certInfo)
|
certInfoBytes, err := json.Marshal(certInfo)
|
||||||
@ -291,6 +397,8 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//Make sure the wildcard * do not goes into the filename
|
||||||
|
filename = strings.ReplaceAll(filename, "*", "_")
|
||||||
|
|
||||||
email, err := utils.PostPara(r, "email")
|
email, err := utils.PostPara(r, "email")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -329,8 +437,18 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
skipTLS = true
|
skipTLS = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dns bool
|
||||||
|
|
||||||
|
if dnsString, err := utils.PostPara(r, "dns"); err != nil {
|
||||||
|
dns = false
|
||||||
|
} else if dnsString != "true" {
|
||||||
|
dns = false
|
||||||
|
} else {
|
||||||
|
dns = true
|
||||||
|
}
|
||||||
|
|
||||||
domains := strings.Split(domainPara, ",")
|
domains := strings.Split(domainPara, ",")
|
||||||
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS)
|
result, err := a.ObtainCert(domains, 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
|
||||||
@ -362,7 +480,7 @@ func IsPortInUse(port int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load cert information from json file
|
// Load cert information from json file
|
||||||
func loadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
|
func LoadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
|
||||||
certInfoBytes, err := os.ReadFile(filename)
|
certInfoBytes, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
72
src/mod/acme/acme_dns.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
|
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetDnsChallengeProviderByName(dnsProvider string, dnsCredentials string) (challenge.Provider, error) {
|
||||||
|
|
||||||
|
//Original Implementation
|
||||||
|
/*credentials, err := extractDnsCredentials(dnsCredentials)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
setCredentialsIntoEnvironmentVariables(credentials)
|
||||||
|
|
||||||
|
provider, err := dns.NewDNSChallengeProviderByName(dnsProvider)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//New implementation using acmedns CICD pipeline generated datatype
|
||||||
|
return acmedns.GetDNSProviderByJsonConfig(dnsProvider, dnsCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Original implementation of DNS ACME using OS.Env as payload
|
||||||
|
*/
|
||||||
|
|
||||||
|
func setCredentialsIntoEnvironmentVariables(credentials map[string]string) {
|
||||||
|
for key, value := range credentials {
|
||||||
|
err := os.Setenv(key, value)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERR] Failed to set environment variable %s: %v", key, err)
|
||||||
|
} else {
|
||||||
|
log.Println("[INFO] Environment variable %s set successfully", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractDnsCredentials(input string) (map[string]string, error) {
|
||||||
|
result := make(map[string]string)
|
||||||
|
|
||||||
|
// Split the input string by newline character
|
||||||
|
lines := strings.Split(input, "\n")
|
||||||
|
|
||||||
|
// Iterate over each line
|
||||||
|
for _, line := range lines {
|
||||||
|
// Split the line by "=" character
|
||||||
|
//use SpliyN to make sure not to split the value if the value is base64
|
||||||
|
parts := strings.SplitN(line, "=", 1)
|
||||||
|
|
||||||
|
// Check if the line is in the correct format
|
||||||
|
if len(parts) == 2 {
|
||||||
|
key := strings.TrimSpace(parts[0])
|
||||||
|
value := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
|
// Add the key-value pair to the map
|
||||||
|
result[key] = value
|
||||||
|
|
||||||
|
if value == "" || key == "" {
|
||||||
|
//invalid config
|
||||||
|
return result, errors.New("DNS credential extract failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
982
src/mod/acme/acmedns/acmedns.go
Normal file
@ -0,0 +1,982 @@
|
|||||||
|
package acmedns
|
||||||
|
/*
|
||||||
|
THIS MODULE IS GENERATED AUTOMATICALLY
|
||||||
|
DO NOT EDIT THIS FILE
|
||||||
|
*/
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/allinkl"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/arvancloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/auroradns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/autodns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/azure"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/azuredns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/bindman"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/bluecat"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/brandit"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/bunny"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/checkdomain"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/civo"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/clouddns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/cloudns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/cloudru"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/cloudxns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/conoha"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/constellix"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/cpanel"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/derak"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/desec"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/digitalocean"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dnshomede"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dnsimple"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dnsmadeeasy"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dnspod"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dode"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/domeneshop"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dreamhost"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/duckdns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dyn"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dynu"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/easydns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/efficientip"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/epik"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/exoscale"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/freemyip"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/gandi"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/gandiv5"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/gcore"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/glesys"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/godaddy"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/googledomains"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/hetzner"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/hostingde"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/hosttech"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/httpnet"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/hyperone"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ibmcloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/iij"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/iijdpf"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/infoblox"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/infomaniak"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/internetbs"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/inwx"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ionos"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ipv64"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/iwantmyname"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/joker"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/liara"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/lightsail"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/linode"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/liquidweb"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/loopia"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/luadns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/mailinabox"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/metaname"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/mydnsjp"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/namecheap"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/namedotcom"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/namesilo"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/nearlyfreespeech"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/netcup"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/netlify"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/nicmanager"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/nifcloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/njalla"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/nodion"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ns1"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/otc"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ovh"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/pdns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/plesk"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/porkbun"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/rackspace"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/rcodezero"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/regru"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/rfc2136"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/rimuhosting"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/route53"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/safedns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/sakuracloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/scaleway"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/selectel"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/servercow"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/shellrent"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/simply"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/sonic"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/stackpath"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/transip"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ultradns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/variomedia"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vegadns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vercel"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/versio"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vinyldns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vkcloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vscale"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vultr"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/webnames"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/websupport"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/wedos"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/yandex"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/yandex360"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/yandexcloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/zoneee"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/zonomi"
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
//name is the DNS provider name, e.g. cloudflare or gandi
|
||||||
|
//JSON (js) must be in key-value string that match ConfigableFields Title in providers.json, e.g. {"Username":"far","Password":"boo"}
|
||||||
|
func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, error){
|
||||||
|
switch name {
|
||||||
|
|
||||||
|
case "alidns":
|
||||||
|
cfg := alidns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return alidns.NewDNSProviderConfig(cfg)
|
||||||
|
case "allinkl":
|
||||||
|
cfg := allinkl.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return allinkl.NewDNSProviderConfig(cfg)
|
||||||
|
case "arvancloud":
|
||||||
|
cfg := arvancloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return arvancloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "auroradns":
|
||||||
|
cfg := auroradns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return auroradns.NewDNSProviderConfig(cfg)
|
||||||
|
case "autodns":
|
||||||
|
cfg := autodns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return autodns.NewDNSProviderConfig(cfg)
|
||||||
|
case "azure":
|
||||||
|
cfg := azure.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return azure.NewDNSProviderConfig(cfg)
|
||||||
|
case "azuredns":
|
||||||
|
cfg := azuredns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return azuredns.NewDNSProviderConfig(cfg)
|
||||||
|
case "bindman":
|
||||||
|
cfg := bindman.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bindman.NewDNSProviderConfig(cfg)
|
||||||
|
case "bluecat":
|
||||||
|
cfg := bluecat.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bluecat.NewDNSProviderConfig(cfg)
|
||||||
|
case "brandit":
|
||||||
|
cfg := brandit.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return brandit.NewDNSProviderConfig(cfg)
|
||||||
|
case "bunny":
|
||||||
|
cfg := bunny.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bunny.NewDNSProviderConfig(cfg)
|
||||||
|
case "checkdomain":
|
||||||
|
cfg := checkdomain.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return checkdomain.NewDNSProviderConfig(cfg)
|
||||||
|
case "civo":
|
||||||
|
cfg := civo.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return civo.NewDNSProviderConfig(cfg)
|
||||||
|
case "clouddns":
|
||||||
|
cfg := clouddns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return clouddns.NewDNSProviderConfig(cfg)
|
||||||
|
case "cloudflare":
|
||||||
|
cfg := cloudflare.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cloudflare.NewDNSProviderConfig(cfg)
|
||||||
|
case "cloudns":
|
||||||
|
cfg := cloudns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cloudns.NewDNSProviderConfig(cfg)
|
||||||
|
case "cloudru":
|
||||||
|
cfg := cloudru.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cloudru.NewDNSProviderConfig(cfg)
|
||||||
|
case "cloudxns":
|
||||||
|
cfg := cloudxns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cloudxns.NewDNSProviderConfig(cfg)
|
||||||
|
case "conoha":
|
||||||
|
cfg := conoha.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conoha.NewDNSProviderConfig(cfg)
|
||||||
|
case "constellix":
|
||||||
|
cfg := constellix.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return constellix.NewDNSProviderConfig(cfg)
|
||||||
|
case "cpanel":
|
||||||
|
cfg := cpanel.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cpanel.NewDNSProviderConfig(cfg)
|
||||||
|
case "derak":
|
||||||
|
cfg := derak.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return derak.NewDNSProviderConfig(cfg)
|
||||||
|
case "desec":
|
||||||
|
cfg := desec.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return desec.NewDNSProviderConfig(cfg)
|
||||||
|
case "digitalocean":
|
||||||
|
cfg := digitalocean.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return digitalocean.NewDNSProviderConfig(cfg)
|
||||||
|
case "dnshomede":
|
||||||
|
cfg := dnshomede.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dnshomede.NewDNSProviderConfig(cfg)
|
||||||
|
case "dnsimple":
|
||||||
|
cfg := dnsimple.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dnsimple.NewDNSProviderConfig(cfg)
|
||||||
|
case "dnsmadeeasy":
|
||||||
|
cfg := dnsmadeeasy.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dnsmadeeasy.NewDNSProviderConfig(cfg)
|
||||||
|
case "dnspod":
|
||||||
|
cfg := dnspod.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dnspod.NewDNSProviderConfig(cfg)
|
||||||
|
case "dode":
|
||||||
|
cfg := dode.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dode.NewDNSProviderConfig(cfg)
|
||||||
|
case "domeneshop":
|
||||||
|
cfg := domeneshop.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return domeneshop.NewDNSProviderConfig(cfg)
|
||||||
|
case "dreamhost":
|
||||||
|
cfg := dreamhost.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dreamhost.NewDNSProviderConfig(cfg)
|
||||||
|
case "duckdns":
|
||||||
|
cfg := duckdns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return duckdns.NewDNSProviderConfig(cfg)
|
||||||
|
case "dyn":
|
||||||
|
cfg := dyn.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dyn.NewDNSProviderConfig(cfg)
|
||||||
|
case "dynu":
|
||||||
|
cfg := dynu.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dynu.NewDNSProviderConfig(cfg)
|
||||||
|
case "easydns":
|
||||||
|
cfg := easydns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return easydns.NewDNSProviderConfig(cfg)
|
||||||
|
case "efficientip":
|
||||||
|
cfg := efficientip.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return efficientip.NewDNSProviderConfig(cfg)
|
||||||
|
case "epik":
|
||||||
|
cfg := epik.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return epik.NewDNSProviderConfig(cfg)
|
||||||
|
case "exoscale":
|
||||||
|
cfg := exoscale.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return exoscale.NewDNSProviderConfig(cfg)
|
||||||
|
case "freemyip":
|
||||||
|
cfg := freemyip.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return freemyip.NewDNSProviderConfig(cfg)
|
||||||
|
case "gandi":
|
||||||
|
cfg := gandi.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return gandi.NewDNSProviderConfig(cfg)
|
||||||
|
case "gandiv5":
|
||||||
|
cfg := gandiv5.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return gandiv5.NewDNSProviderConfig(cfg)
|
||||||
|
case "gcore":
|
||||||
|
cfg := gcore.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return gcore.NewDNSProviderConfig(cfg)
|
||||||
|
case "glesys":
|
||||||
|
cfg := glesys.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return glesys.NewDNSProviderConfig(cfg)
|
||||||
|
case "godaddy":
|
||||||
|
cfg := godaddy.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return godaddy.NewDNSProviderConfig(cfg)
|
||||||
|
case "googledomains":
|
||||||
|
cfg := googledomains.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return googledomains.NewDNSProviderConfig(cfg)
|
||||||
|
case "hetzner":
|
||||||
|
cfg := hetzner.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hetzner.NewDNSProviderConfig(cfg)
|
||||||
|
case "hostingde":
|
||||||
|
cfg := hostingde.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hostingde.NewDNSProviderConfig(cfg)
|
||||||
|
case "hosttech":
|
||||||
|
cfg := hosttech.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hosttech.NewDNSProviderConfig(cfg)
|
||||||
|
case "httpnet":
|
||||||
|
cfg := httpnet.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return httpnet.NewDNSProviderConfig(cfg)
|
||||||
|
case "hyperone":
|
||||||
|
cfg := hyperone.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hyperone.NewDNSProviderConfig(cfg)
|
||||||
|
case "ibmcloud":
|
||||||
|
cfg := ibmcloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ibmcloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "iij":
|
||||||
|
cfg := iij.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return iij.NewDNSProviderConfig(cfg)
|
||||||
|
case "iijdpf":
|
||||||
|
cfg := iijdpf.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return iijdpf.NewDNSProviderConfig(cfg)
|
||||||
|
case "infoblox":
|
||||||
|
cfg := infoblox.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return infoblox.NewDNSProviderConfig(cfg)
|
||||||
|
case "infomaniak":
|
||||||
|
cfg := infomaniak.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return infomaniak.NewDNSProviderConfig(cfg)
|
||||||
|
case "internetbs":
|
||||||
|
cfg := internetbs.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return internetbs.NewDNSProviderConfig(cfg)
|
||||||
|
case "inwx":
|
||||||
|
cfg := inwx.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return inwx.NewDNSProviderConfig(cfg)
|
||||||
|
case "ionos":
|
||||||
|
cfg := ionos.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ionos.NewDNSProviderConfig(cfg)
|
||||||
|
case "ipv64":
|
||||||
|
cfg := ipv64.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ipv64.NewDNSProviderConfig(cfg)
|
||||||
|
case "iwantmyname":
|
||||||
|
cfg := iwantmyname.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return iwantmyname.NewDNSProviderConfig(cfg)
|
||||||
|
case "joker":
|
||||||
|
cfg := joker.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return joker.NewDNSProviderConfig(cfg)
|
||||||
|
case "liara":
|
||||||
|
cfg := liara.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return liara.NewDNSProviderConfig(cfg)
|
||||||
|
case "lightsail":
|
||||||
|
cfg := lightsail.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return lightsail.NewDNSProviderConfig(cfg)
|
||||||
|
case "linode":
|
||||||
|
cfg := linode.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return linode.NewDNSProviderConfig(cfg)
|
||||||
|
case "liquidweb":
|
||||||
|
cfg := liquidweb.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return liquidweb.NewDNSProviderConfig(cfg)
|
||||||
|
case "loopia":
|
||||||
|
cfg := loopia.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return loopia.NewDNSProviderConfig(cfg)
|
||||||
|
case "luadns":
|
||||||
|
cfg := luadns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return luadns.NewDNSProviderConfig(cfg)
|
||||||
|
case "mailinabox":
|
||||||
|
cfg := mailinabox.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mailinabox.NewDNSProviderConfig(cfg)
|
||||||
|
case "metaname":
|
||||||
|
cfg := metaname.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return metaname.NewDNSProviderConfig(cfg)
|
||||||
|
case "mydnsjp":
|
||||||
|
cfg := mydnsjp.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mydnsjp.NewDNSProviderConfig(cfg)
|
||||||
|
case "namecheap":
|
||||||
|
cfg := namecheap.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return namecheap.NewDNSProviderConfig(cfg)
|
||||||
|
case "namedotcom":
|
||||||
|
cfg := namedotcom.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return namedotcom.NewDNSProviderConfig(cfg)
|
||||||
|
case "namesilo":
|
||||||
|
cfg := namesilo.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return namesilo.NewDNSProviderConfig(cfg)
|
||||||
|
case "nearlyfreespeech":
|
||||||
|
cfg := nearlyfreespeech.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nearlyfreespeech.NewDNSProviderConfig(cfg)
|
||||||
|
case "netcup":
|
||||||
|
cfg := netcup.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return netcup.NewDNSProviderConfig(cfg)
|
||||||
|
case "netlify":
|
||||||
|
cfg := netlify.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return netlify.NewDNSProviderConfig(cfg)
|
||||||
|
case "nicmanager":
|
||||||
|
cfg := nicmanager.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nicmanager.NewDNSProviderConfig(cfg)
|
||||||
|
case "nifcloud":
|
||||||
|
cfg := nifcloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nifcloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "njalla":
|
||||||
|
cfg := njalla.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return njalla.NewDNSProviderConfig(cfg)
|
||||||
|
case "nodion":
|
||||||
|
cfg := nodion.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nodion.NewDNSProviderConfig(cfg)
|
||||||
|
case "ns1":
|
||||||
|
cfg := ns1.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ns1.NewDNSProviderConfig(cfg)
|
||||||
|
case "otc":
|
||||||
|
cfg := otc.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return otc.NewDNSProviderConfig(cfg)
|
||||||
|
case "ovh":
|
||||||
|
cfg := ovh.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ovh.NewDNSProviderConfig(cfg)
|
||||||
|
case "pdns":
|
||||||
|
cfg := pdns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pdns.NewDNSProviderConfig(cfg)
|
||||||
|
case "plesk":
|
||||||
|
cfg := plesk.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return plesk.NewDNSProviderConfig(cfg)
|
||||||
|
case "porkbun":
|
||||||
|
cfg := porkbun.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return porkbun.NewDNSProviderConfig(cfg)
|
||||||
|
case "rackspace":
|
||||||
|
cfg := rackspace.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rackspace.NewDNSProviderConfig(cfg)
|
||||||
|
case "rcodezero":
|
||||||
|
cfg := rcodezero.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rcodezero.NewDNSProviderConfig(cfg)
|
||||||
|
case "regru":
|
||||||
|
cfg := regru.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return regru.NewDNSProviderConfig(cfg)
|
||||||
|
case "rfc2136":
|
||||||
|
cfg := rfc2136.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rfc2136.NewDNSProviderConfig(cfg)
|
||||||
|
case "rimuhosting":
|
||||||
|
cfg := rimuhosting.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rimuhosting.NewDNSProviderConfig(cfg)
|
||||||
|
case "route53":
|
||||||
|
cfg := route53.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return route53.NewDNSProviderConfig(cfg)
|
||||||
|
case "safedns":
|
||||||
|
cfg := safedns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return safedns.NewDNSProviderConfig(cfg)
|
||||||
|
case "sakuracloud":
|
||||||
|
cfg := sakuracloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sakuracloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "scaleway":
|
||||||
|
cfg := scaleway.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return scaleway.NewDNSProviderConfig(cfg)
|
||||||
|
case "selectel":
|
||||||
|
cfg := selectel.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return selectel.NewDNSProviderConfig(cfg)
|
||||||
|
case "servercow":
|
||||||
|
cfg := servercow.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return servercow.NewDNSProviderConfig(cfg)
|
||||||
|
case "shellrent":
|
||||||
|
cfg := shellrent.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return shellrent.NewDNSProviderConfig(cfg)
|
||||||
|
case "simply":
|
||||||
|
cfg := simply.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return simply.NewDNSProviderConfig(cfg)
|
||||||
|
case "sonic":
|
||||||
|
cfg := sonic.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sonic.NewDNSProviderConfig(cfg)
|
||||||
|
case "stackpath":
|
||||||
|
cfg := stackpath.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stackpath.NewDNSProviderConfig(cfg)
|
||||||
|
case "tencentcloud":
|
||||||
|
cfg := tencentcloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tencentcloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "transip":
|
||||||
|
cfg := transip.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return transip.NewDNSProviderConfig(cfg)
|
||||||
|
case "ultradns":
|
||||||
|
cfg := ultradns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ultradns.NewDNSProviderConfig(cfg)
|
||||||
|
case "variomedia":
|
||||||
|
cfg := variomedia.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return variomedia.NewDNSProviderConfig(cfg)
|
||||||
|
case "vegadns":
|
||||||
|
cfg := vegadns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vegadns.NewDNSProviderConfig(cfg)
|
||||||
|
case "vercel":
|
||||||
|
cfg := vercel.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vercel.NewDNSProviderConfig(cfg)
|
||||||
|
case "versio":
|
||||||
|
cfg := versio.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return versio.NewDNSProviderConfig(cfg)
|
||||||
|
case "vinyldns":
|
||||||
|
cfg := vinyldns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vinyldns.NewDNSProviderConfig(cfg)
|
||||||
|
case "vkcloud":
|
||||||
|
cfg := vkcloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vkcloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "vscale":
|
||||||
|
cfg := vscale.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vscale.NewDNSProviderConfig(cfg)
|
||||||
|
case "vultr":
|
||||||
|
cfg := vultr.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vultr.NewDNSProviderConfig(cfg)
|
||||||
|
case "webnames":
|
||||||
|
cfg := webnames.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webnames.NewDNSProviderConfig(cfg)
|
||||||
|
case "websupport":
|
||||||
|
cfg := websupport.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return websupport.NewDNSProviderConfig(cfg)
|
||||||
|
case "wedos":
|
||||||
|
cfg := wedos.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return wedos.NewDNSProviderConfig(cfg)
|
||||||
|
case "yandex":
|
||||||
|
cfg := yandex.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return yandex.NewDNSProviderConfig(cfg)
|
||||||
|
case "yandex360":
|
||||||
|
cfg := yandex360.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return yandex360.NewDNSProviderConfig(cfg)
|
||||||
|
case "yandexcloud":
|
||||||
|
cfg := yandexcloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return yandexcloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "zoneee":
|
||||||
|
cfg := zoneee.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return zoneee.NewDNSProviderConfig(cfg)
|
||||||
|
case "zonomi":
|
||||||
|
cfg := zonomi.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return zonomi.NewDNSProviderConfig(cfg)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unrecognized DNS provider: %s", name)
|
||||||
|
}
|
||||||
|
}
|
27
src/mod/acme/acmedns/acmedns_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package acmedns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test if the structure of ACME DNS config can be reflected from lego source code definations
|
||||||
|
func TestACMEDNSConfigStructureReflector(t *testing.T) {
|
||||||
|
providers := []string{
|
||||||
|
"gandi",
|
||||||
|
"cloudflare",
|
||||||
|
"azure",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, provider := range providers {
|
||||||
|
strcture, err := acmedns.GetProviderConfigStructure(provider)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(strcture)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3493
src/mod/acme/acmedns/providers.json
Normal file
80
src/mod/acme/acmedns/providerutils.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package acmedns
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed providers.json
|
||||||
|
var providers []byte //A list of providers generated by acmedns code-generator
|
||||||
|
|
||||||
|
type ConfigTemplate struct {
|
||||||
|
Name string `json:"Name"`
|
||||||
|
ConfigableFields []struct {
|
||||||
|
Title string `json:"Title"`
|
||||||
|
Datatype string `json:"Datatype"`
|
||||||
|
} `json:"ConfigableFields"`
|
||||||
|
HiddenFields []struct {
|
||||||
|
Title string `json:"Title"`
|
||||||
|
Datatype string `json:"Datatype"`
|
||||||
|
} `json:"HiddenFields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a map of string => datatype
|
||||||
|
func GetProviderConfigStructure(providerName string) (map[string]string, error) {
|
||||||
|
//Load the target config template from embedded providers.json
|
||||||
|
configTemplateMap := map[string]ConfigTemplate{}
|
||||||
|
err := json.Unmarshal(providers, &configTemplateMap)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetConfigTemplate, ok := configTemplateMap[providerName]
|
||||||
|
if !ok {
|
||||||
|
return map[string]string{}, errors.New("provider not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
results := map[string]string{}
|
||||||
|
for _, field := range targetConfigTemplate.ConfigableFields {
|
||||||
|
results[field.Title] = field.Datatype
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleServeProvidersJson return the list of supported providers as json
|
||||||
|
func HandleServeProvidersJson(w http.ResponseWriter, r *http.Request) {
|
||||||
|
providerName, _ := utils.GetPara(r, "name")
|
||||||
|
if providerName == "" {
|
||||||
|
//Send the current list of providers
|
||||||
|
configTemplateMap := map[string]ConfigTemplate{}
|
||||||
|
err := json.Unmarshal(providers, &configTemplateMap)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "failed to load DNS provider")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse the provider names into an array
|
||||||
|
providers := []string{}
|
||||||
|
for providerName, _ := range configTemplateMap {
|
||||||
|
providers = append(providers, providerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(providers)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//Get the config for that provider
|
||||||
|
confTemplate, err := GetProviderConfigStructure(providerName)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(confTemplate)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
@ -344,7 +344,7 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
|||||||
|
|
||||||
// Load certificate info for ACME detail
|
// Load certificate info for ACME detail
|
||||||
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
|
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
|
||||||
certInfo, err := loadCertInfoJSON(certInfoFilename)
|
certInfo, err := LoadCertInfoJSON(certInfoFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Renew %s certificate error, can't get the ACME detail for cert: %v, trying org section as ca", certName, err)
|
log.Printf("Renew %s certificate error, can't get the ACME detail for cert: %v, trying org section as ca", certName, err)
|
||||||
|
|
||||||
@ -356,7 +356,7 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS)
|
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS, certInfo.UseDNS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
|
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
|
||||||
} else {
|
} else {
|
||||||
@ -373,3 +373,65 @@ func (a *AutoRenewer) saveRenewConfigToFile() error {
|
|||||||
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
|
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
|
||||||
return os.WriteFile(a.ConfigFilePath, js, 0775)
|
return os.WriteFile(a.ConfigFilePath, js, 0775)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle update auto renew EAD configuration
|
||||||
|
func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
|
||||||
|
kid, err := utils.GetPara(r, "kid")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "kid not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hmacEncoded, err := utils.GetPara(r, "hmacEncoded")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "hmacEncoded not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
acmeDirectoryURL, err := utils.GetPara(r, "acmeDirectoryURL")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "acmeDirectoryURL not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.AcmeHandler.Database.TableExists("acme") {
|
||||||
|
a.AcmeHandler.Database.NewTable("acme")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_kid", kid)
|
||||||
|
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_hmacEncoded", hmacEncoded)
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle update auto renew DNS configuration
|
||||||
|
func (a *AutoRenewer) HanldeSetDNS(w http.ResponseWriter, r *http.Request) {
|
||||||
|
dnsProvider, err := utils.PostPara(r, "dnsProvider")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "dnsProvider not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsCredentials, err := utils.PostPara(r, "dnsCredentials")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "dnsCredentials not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename, err := utils.PostPara(r, "filename")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "filename not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.AcmeHandler.Database.TableExists("acme") {
|
||||||
|
a.AcmeHandler.Database.NewTable("acme")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.AcmeHandler.Database.Write("acme", filename+"_dns_provider", dnsProvider)
|
||||||
|
a.AcmeHandler.Database.Write("acme", filename+"_dns_credentials", dnsCredentials)
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
package aroz
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
//To be used with arozos system
|
|
||||||
type ArozHandler struct {
|
|
||||||
Port string
|
|
||||||
restfulEndpoint string
|
|
||||||
}
|
|
||||||
|
|
||||||
//Information required for registering this subservice to arozos
|
|
||||||
type ServiceInfo struct {
|
|
||||||
Name string //Name of this module. e.g. "Audio"
|
|
||||||
Desc string //Description for this module
|
|
||||||
Group string //Group of the module, e.g. "system" / "media" etc
|
|
||||||
IconPath string //Module icon image path e.g. "Audio/img/function_icon.png"
|
|
||||||
Version string //Version of the module. Format: [0-9]*.[0-9][0-9].[0-9]
|
|
||||||
StartDir string //Default starting dir, e.g. "Audio/index.html"
|
|
||||||
SupportFW bool //Support floatWindow. If yes, floatWindow dir will be loaded
|
|
||||||
LaunchFWDir string //This link will be launched instead of 'StartDir' if fw mode
|
|
||||||
SupportEmb bool //Support embedded mode
|
|
||||||
LaunchEmb string //This link will be launched instead of StartDir / Fw if a file is opened with this module
|
|
||||||
InitFWSize []int //Floatwindow init size. [0] => Width, [1] => Height
|
|
||||||
InitEmbSize []int //Embedded mode init size. [0] => Width, [1] => Height
|
|
||||||
SupportedExt []string //Supported File Extensions. e.g. ".mp3", ".flac", ".wav"
|
|
||||||
}
|
|
||||||
|
|
||||||
//This function will request the required flag from the startup paramters and parse it to the need of the arozos.
|
|
||||||
func HandleFlagParse(info ServiceInfo) *ArozHandler {
|
|
||||||
var infoRequestMode = flag.Bool("info", false, "Show information about this program in JSON")
|
|
||||||
var port = flag.String("port", ":8000", "Management web interface listening port")
|
|
||||||
var restful = flag.String("rpt", "", "Reserved")
|
|
||||||
//Parse the flags
|
|
||||||
flag.Parse()
|
|
||||||
if *infoRequestMode {
|
|
||||||
//Information request mode
|
|
||||||
jsonString, _ := json.MarshalIndent(info, "", " ")
|
|
||||||
fmt.Println(string(jsonString))
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
return &ArozHandler{
|
|
||||||
Port: *port,
|
|
||||||
restfulEndpoint: *restful,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Get the username and resources access token from the request, return username, token
|
|
||||||
func (a *ArozHandler) GetUserInfoFromRequest(w http.ResponseWriter, r *http.Request) (string, string) {
|
|
||||||
username := r.Header.Get("aouser")
|
|
||||||
token := r.Header.Get("aotoken")
|
|
||||||
|
|
||||||
return username, token
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ArozHandler) IsUsingExternalPermissionManager() bool {
|
|
||||||
return !(a.restfulEndpoint == "")
|
|
||||||
}
|
|
||||||
|
|
||||||
//Request gateway interface for advance permission sandbox control
|
|
||||||
func (a *ArozHandler) RequestGatewayInterface(token string, script string) (*http.Response, error) {
|
|
||||||
resp, err := http.PostForm(a.restfulEndpoint,
|
|
||||||
url.Values{"token": {token}, "script": {script}})
|
|
||||||
if err != nil {
|
|
||||||
// handle error
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
@ -1,16 +1,11 @@
|
|||||||
package dynamicproxy
|
package dynamicproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -26,41 +21,21 @@ import (
|
|||||||
- Vitrual Directory Routing
|
- Vitrual Directory Routing
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var (
|
|
||||||
//go:embed tld.json
|
|
||||||
rawTldMap []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
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 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 {
|
||||||
//Matching routing rule found. Let the sub-router handle it
|
//Matching routing rule found. Let the sub-router handle it
|
||||||
if matchedRoutingRule.UseSystemAccessControl {
|
|
||||||
//This matching rule request system access control.
|
|
||||||
//check access logic
|
|
||||||
respWritten := h.handleAccessRouting(w, r)
|
|
||||||
if respWritten {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
matchedRoutingRule.Route(w, r)
|
matchedRoutingRule.Route(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
//Inject headers
|
||||||
General Access Check
|
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
|
||||||
*/
|
|
||||||
|
|
||||||
respWritten := h.handleAccessRouting(w, r)
|
|
||||||
if respWritten {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Redirection Routing
|
Redirection Routing
|
||||||
@ -72,46 +47,71 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Extract request host to see if it is virtual directory or subdomain
|
/*
|
||||||
|
Host Routing
|
||||||
|
*/
|
||||||
|
//Extract request host to see if any proxy rule is matched
|
||||||
domainOnly := r.Host
|
domainOnly := r.Host
|
||||||
if strings.Contains(r.Host, ":") {
|
if strings.Contains(r.Host, ":") {
|
||||||
hostPath := strings.Split(r.Host, ":")
|
hostPath := strings.Split(r.Host, ":")
|
||||||
domainOnly = hostPath[0]
|
domainOnly = hostPath[0]
|
||||||
}
|
}
|
||||||
|
sep := h.Parent.getProxyEndpointFromHostname(domainOnly)
|
||||||
|
if sep != nil && !sep.Disabled {
|
||||||
|
//Matching proxy rule found
|
||||||
|
//Access Check (blacklist / whitelist)
|
||||||
|
ruleID := sep.AccessFilterUUID
|
||||||
|
if sep.AccessFilterUUID == "" {
|
||||||
|
//Use default rule
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
if h.handleAccessRouting(ruleID, w, r) {
|
||||||
|
//Request handled by subroute
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
//Validate basic auth
|
||||||
Subdomain Routing
|
|
||||||
*/
|
|
||||||
if strings.Contains(r.Host, ".") {
|
|
||||||
//This might be a subdomain. See if there are any subdomain proxy router for this
|
|
||||||
sep := h.Parent.getSubdomainProxyEndpointFromHostname(domainOnly)
|
|
||||||
if sep != nil {
|
|
||||||
if sep.RequireBasicAuth {
|
if sep.RequireBasicAuth {
|
||||||
err := h.handleBasicAuthRouting(w, r, sep)
|
err := h.handleBasicAuthRouting(w, r, sep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.subdomainRequest(w, r, sep)
|
|
||||||
|
//Check if any virtual directory rules matches
|
||||||
|
proxyingPath := strings.TrimSpace(r.RequestURI)
|
||||||
|
targetProxyEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath)
|
||||||
|
if targetProxyEndpoint != nil && !targetProxyEndpoint.Disabled {
|
||||||
|
//Virtual directory routing rule found. Route via vdir mode
|
||||||
|
h.vdirRequest(w, r, targetProxyEndpoint)
|
||||||
|
return
|
||||||
|
} else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root {
|
||||||
|
potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
|
||||||
|
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
|
||||||
|
//Missing tailing slash. Redirect to target proxy endpoint
|
||||||
|
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
//Fallback to handle by the host proxy forwarder
|
||||||
Virtual Directory Routing
|
h.hostRequest(w, r, sep)
|
||||||
*/
|
|
||||||
//Clean up the request URI
|
|
||||||
proxyingPath := strings.TrimSpace(r.RequestURI)
|
|
||||||
targetProxyEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath)
|
|
||||||
if targetProxyEndpoint != nil {
|
|
||||||
if targetProxyEndpoint.RequireBasicAuth {
|
|
||||||
err := h.handleBasicAuthRouting(w, r, targetProxyEndpoint)
|
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Root Router Handling
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Root access control based on default rule
|
||||||
|
blocked := h.handleAccessRouting("default", w, r)
|
||||||
|
if blocked {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
h.proxyRequest(w, r, targetProxyEndpoint)
|
|
||||||
} else if !strings.HasSuffix(proxyingPath, "/") {
|
//Clean up the request URI
|
||||||
|
proxyingPath := strings.TrimSpace(r.RequestURI)
|
||||||
|
if !strings.HasSuffix(proxyingPath, "/") {
|
||||||
potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
|
potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
|
||||||
if potentialProxtEndpoint != nil {
|
if potentialProxtEndpoint != nil {
|
||||||
//Missing tailing slash. Redirect to target proxy endpoint
|
//Missing tailing slash. Redirect to target proxy endpoint
|
||||||
@ -142,121 +142,63 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
|||||||
domainOnly = hostPath[0]
|
domainOnly = hostPath[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.Parent.RootRoutingOptions.EnableRedirectForUnsetRules {
|
//Get the proxy root config
|
||||||
//Route to custom domain
|
proot := h.Parent.Root
|
||||||
if h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget == "" {
|
switch proot.DefaultSiteOption {
|
||||||
//Not set. Redirect to first level of domain redirectable
|
case DefaultSite_InternalStaticWebServer:
|
||||||
fld, err := h.getTopLevelRedirectableDomain(domainOnly)
|
fallthrough
|
||||||
if err != nil {
|
case DefaultSite_ReverseProxy:
|
||||||
//Redirect to proxy root
|
//They both share the same behavior
|
||||||
h.proxyRequest(w, r, h.Parent.Root)
|
|
||||||
} else {
|
//Check if any virtual directory rules matches
|
||||||
log.Println("[Router] Redirecting request from " + domainOnly + " to " + fld)
|
proxyingPath := strings.TrimSpace(r.RequestURI)
|
||||||
h.logRequest(r, false, 307, "root-redirect", domainOnly)
|
targetProxyEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath)
|
||||||
http.Redirect(w, r, fld, http.StatusTemporaryRedirect)
|
if targetProxyEndpoint != nil && !targetProxyEndpoint.Disabled {
|
||||||
}
|
//Virtual directory routing rule found. Route via vdir mode
|
||||||
|
h.vdirRequest(w, r, targetProxyEndpoint)
|
||||||
return
|
return
|
||||||
} else if h.isTopLevelRedirectableDomain(domainOnly) {
|
} else if !strings.HasSuffix(proxyingPath, "/") && proot.ProxyType != ProxyType_Root {
|
||||||
//This is requesting a top level private domain that should be serving root
|
potentialProxtEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
|
||||||
h.proxyRequest(w, r, h.Parent.Root)
|
if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
|
||||||
} else {
|
//Missing tailing slash. Redirect to target proxy endpoint
|
||||||
//Validate the redirection target URL
|
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
||||||
parsedURL, err := url.Parse(h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget)
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//No vdir match. Route via root router
|
||||||
|
h.hostRequest(w, r, h.Parent.Root)
|
||||||
|
case DefaultSite_Redirect:
|
||||||
|
redirectTarget := strings.TrimSpace(proot.DefaultSiteValue)
|
||||||
|
if redirectTarget == "" {
|
||||||
|
redirectTarget = "about:blank"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if it is an infinite loopback redirect
|
||||||
|
parsedURL, err := url.Parse(proot.DefaultSiteValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Error when parsing target. Send to root
|
//Error when parsing target. Send to root
|
||||||
h.proxyRequest(w, r, h.Parent.Root)
|
h.hostRequest(w, r, h.Parent.Root)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hostname := parsedURL.Hostname()
|
hostname := parsedURL.Hostname()
|
||||||
if domainOnly != hostname {
|
if hostname == domainOnly {
|
||||||
//Redirect to target
|
h.logRequest(r, false, 500, "root-redirect", domainOnly)
|
||||||
h.logRequest(r, false, 307, "root-redirect", domainOnly)
|
http.Error(w, "Loopback redirects due to invalid settings", 500)
|
||||||
http.Redirect(w, r, h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget, http.StatusTemporaryRedirect)
|
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
//Loopback request due to bad settings (Shd leave it empty)
|
|
||||||
//Forward it to root proxy
|
|
||||||
h.proxyRequest(w, r, h.Parent.Root)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//Route to root
|
|
||||||
h.proxyRequest(w, r, h.Parent.Root)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle access routing logic. Return true if the request is handled or blocked by the access control logic
|
h.logRequest(r, false, 307, "root-redirect", domainOnly)
|
||||||
// if the return value is false, you can continue process the response writer
|
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
|
||||||
func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Request) bool {
|
case DefaultSite_NotFoundPage:
|
||||||
//Check if this ip is in blacklist
|
//Serve the not found page, use template if exists
|
||||||
clientIpAddr := geodb.GetRequesterIP(r)
|
|
||||||
if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/blacklist.html"))
|
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/notfound.html"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.Write(page_forbidden)
|
w.Write(page_hosterror)
|
||||||
} else {
|
} else {
|
||||||
w.Write(template)
|
w.Write(template)
|
||||||
}
|
}
|
||||||
h.logRequest(r, false, 403, "blacklist", "")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check if this ip is in whitelist
|
|
||||||
if !h.Parent.Option.GeodbStore.IsWhitelisted(clientIpAddr) {
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/whitelist.html"))
|
|
||||||
if err != nil {
|
|
||||||
w.Write(page_forbidden)
|
|
||||||
} else {
|
|
||||||
w.Write(template)
|
|
||||||
}
|
|
||||||
h.logRequest(r, false, 403, "whitelist", "")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return if the given host is already topped (e.g. example.com or example.co.uk) instead of
|
|
||||||
// a host with subdomain (e.g. test.example.com)
|
|
||||||
func (h *ProxyHandler) isTopLevelRedirectableDomain(requestHost string) bool {
|
|
||||||
parts := strings.Split(requestHost, ".")
|
|
||||||
if len(parts) > 2 {
|
|
||||||
//Cases where strange tld is used like .co.uk or .com.hk
|
|
||||||
_, ok := h.Parent.tldMap[strings.Join(parts[1:], ".")]
|
|
||||||
if ok {
|
|
||||||
//Already topped
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//Already topped
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTopLevelRedirectableDomain returns the toppest level of domain
|
|
||||||
// that is redirectable. E.g. a.b.c.example.co.uk will return example.co.uk
|
|
||||||
func (h *ProxyHandler) getTopLevelRedirectableDomain(unsetSubdomainHost string) (string, error) {
|
|
||||||
parts := strings.Split(unsetSubdomainHost, ".")
|
|
||||||
if h.isTopLevelRedirectableDomain(unsetSubdomainHost) {
|
|
||||||
//Already topped
|
|
||||||
return "", errors.New("already at top level domain")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(parts); i++ {
|
|
||||||
possibleTld := parts[i:]
|
|
||||||
_, ok := h.Parent.tldMap[strings.Join(possibleTld, ".")]
|
|
||||||
if ok {
|
|
||||||
//This is tld length
|
|
||||||
tld := strings.Join(parts[i-1:], ".")
|
|
||||||
return "//" + tld, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", errors.New("unsupported top level domain given")
|
|
||||||
}
|
|
||||||
|
65
src/mod/dynamicproxy/access.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package dynamicproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle access check (blacklist / whitelist), return true if request is handled (aka blocked)
|
||||||
|
// if the return value is false, you can continue process the response writer
|
||||||
|
func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
//Unable to load access rule. Target rule not found?
|
||||||
|
log.Println("[Proxy] Unable to load access rule: " + ruleID)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte("500 - Internal Server Error"))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
|
||||||
|
if isBlocked {
|
||||||
|
h.logRequest(r, false, 403, blockedReason, "")
|
||||||
|
}
|
||||||
|
return isBlocked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return boolean, return true if access is blocked
|
||||||
|
// For string, it will return the blocked reason (if any)
|
||||||
|
func accessRequestBlocked(accessRule *access.AccessRule, templateDirectory string, w http.ResponseWriter, r *http.Request) (bool, string) {
|
||||||
|
//Check if this ip is in blacklist
|
||||||
|
clientIpAddr := netutils.GetRequesterIP(r)
|
||||||
|
if accessRule.IsBlacklisted(clientIpAddr) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
template, err := os.ReadFile(filepath.Join(templateDirectory, "templates/blacklist.html"))
|
||||||
|
if err != nil {
|
||||||
|
w.Write(page_forbidden)
|
||||||
|
} else {
|
||||||
|
w.Write(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, "blacklist"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if this ip is in whitelist
|
||||||
|
if !accessRule.IsWhitelisted(clientIpAddr) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
template, err := os.ReadFile(filepath.Join(templateDirectory, "templates/whitelist.html"))
|
||||||
|
if err != nil {
|
||||||
|
w.Write(page_forbidden)
|
||||||
|
} else {
|
||||||
|
w.Write(template)
|
||||||
|
}
|
||||||
|
return true, "whitelist"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Not blocked.
|
||||||
|
return false, ""
|
||||||
|
}
|
@ -16,6 +16,16 @@ import (
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||||
|
err := handleBasicAuth(w, r, pe)
|
||||||
|
if err != nil {
|
||||||
|
h.logRequest(r, false, 401, "host", pe.Domain)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle basic auth logic
|
||||||
|
// do not write to http.ResponseWriter if err return is not nil (already handled by this function)
|
||||||
|
func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||||
if len(pe.BasicAuthExceptionRules) > 0 {
|
if len(pe.BasicAuthExceptionRules) > 0 {
|
||||||
//Check if the current path matches the exception rules
|
//Check if the current path matches the exception rules
|
||||||
for _, exceptionRule := range pe.BasicAuthExceptionRules {
|
for _, exceptionRule := range pe.BasicAuthExceptionRules {
|
||||||
@ -26,10 +36,6 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyType := "vdir-auth"
|
|
||||||
if pe.ProxyType == ProxyType_Subdomain {
|
|
||||||
proxyType = "subd-auth"
|
|
||||||
}
|
|
||||||
u, p, ok := r.BasicAuth()
|
u, p, ok := r.BasicAuth()
|
||||||
if !ok {
|
if !ok {
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
@ -48,7 +54,6 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !matchingFound {
|
if !matchingFound {
|
||||||
h.logRequest(r, false, 401, proxyType, pe.Domain)
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
w.WriteHeader(401)
|
w.WriteHeader(401)
|
||||||
return errors.New("unauthorized")
|
return errors.New("unauthorized")
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dpcore
|
package dpcore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@ -8,12 +9,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var onExitFlushLoop func()
|
|
||||||
|
|
||||||
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
||||||
// sends it to another server, proxying the response back to the
|
// sends it to another server, proxying the response back to the
|
||||||
// client, support http, also support https tunnel using http.hijacker
|
// client, support http, also support https tunnel using http.hijacker
|
||||||
@ -60,6 +58,7 @@ type ResponseRewriteRuleSet struct {
|
|||||||
ProxyDomain string
|
ProxyDomain string
|
||||||
OriginalHost string
|
OriginalHost string
|
||||||
UseTLS bool
|
UseTLS bool
|
||||||
|
NoCache bool
|
||||||
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +66,12 @@ type requestCanceler interface {
|
|||||||
CancelRequest(req *http.Request)
|
CancelRequest(req *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerification bool) *ReverseProxy {
|
type DpcoreOptions struct {
|
||||||
|
IgnoreTLSVerification bool
|
||||||
|
FlushInterval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy {
|
||||||
targetQuery := target.RawQuery
|
targetQuery := target.RawQuery
|
||||||
director := func(req *http.Request) {
|
director := func(req *http.Request) {
|
||||||
req.URL.Scheme = target.Scheme
|
req.URL.Scheme = target.Scheme
|
||||||
@ -79,10 +83,6 @@ func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerificatio
|
|||||||
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := req.Header["User-Agent"]; !ok {
|
|
||||||
req.Header.Set("User-Agent", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Hack the default transporter to handle more connections
|
//Hack the default transporter to handle more connections
|
||||||
@ -94,7 +94,7 @@ func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerificatio
|
|||||||
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
|
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
|
||||||
thisTransporter.(*http.Transport).DisableCompression = true
|
thisTransporter.(*http.Transport).DisableCompression = true
|
||||||
|
|
||||||
if ignoreTLSVerification {
|
if dpcOptions.IgnoreTLSVerification {
|
||||||
//Ignore TLS certificate validation error
|
//Ignore TLS certificate validation error
|
||||||
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
|
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
|
||||||
}
|
}
|
||||||
@ -102,6 +102,7 @@ func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerificatio
|
|||||||
return &ReverseProxy{
|
return &ReverseProxy{
|
||||||
Director: director,
|
Director: director,
|
||||||
Prepender: prepender,
|
Prepender: prepender,
|
||||||
|
FlushInterval: dpcOptions.FlushInterval,
|
||||||
Verbal: false,
|
Verbal: false,
|
||||||
Transport: thisTransporter,
|
Transport: thisTransporter,
|
||||||
}
|
}
|
||||||
@ -177,62 +178,64 @@ var hopHeaders = []string{
|
|||||||
//"Upgrade",
|
//"Upgrade",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
|
// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
|
||||||
if p.FlushInterval != 0 {
|
func (p *ReverseProxy) copyResponse(dst http.ResponseWriter, src io.Reader, flushInterval time.Duration) error {
|
||||||
if wf, ok := dst.(writeFlusher); ok {
|
var w io.Writer = dst
|
||||||
|
if flushInterval != 0 {
|
||||||
mlw := &maxLatencyWriter{
|
mlw := &maxLatencyWriter{
|
||||||
dst: wf,
|
dst: dst,
|
||||||
latency: p.FlushInterval,
|
flush: http.NewResponseController(dst).Flush,
|
||||||
done: make(chan bool),
|
latency: flushInterval,
|
||||||
}
|
}
|
||||||
|
|
||||||
go mlw.flushLoop()
|
|
||||||
defer mlw.stop()
|
defer mlw.stop()
|
||||||
dst = mlw
|
// set up initial timer so headers get flushed even if body writes are delayed
|
||||||
}
|
mlw.flushPending = true
|
||||||
|
mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
|
||||||
|
w = mlw
|
||||||
}
|
}
|
||||||
|
|
||||||
io.Copy(dst, src)
|
var buf []byte
|
||||||
|
_, err := p.copyBuffer(w, src, buf)
|
||||||
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type writeFlusher interface {
|
// Copy with given buffer size. Default to 64k
|
||||||
io.Writer
|
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
|
||||||
http.Flusher
|
if len(buf) == 0 {
|
||||||
|
buf = make([]byte, 64*1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
type maxLatencyWriter struct {
|
var written int64
|
||||||
dst writeFlusher
|
|
||||||
latency time.Duration
|
|
||||||
mu sync.Mutex
|
|
||||||
done chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *maxLatencyWriter) Write(b []byte) (int, error) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
return m.dst.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *maxLatencyWriter) flushLoop() {
|
|
||||||
t := time.NewTicker(m.latency)
|
|
||||||
defer t.Stop()
|
|
||||||
for {
|
for {
|
||||||
select {
|
nr, rerr := src.Read(buf)
|
||||||
case <-m.done:
|
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
|
||||||
if onExitFlushLoop != nil {
|
p.logf("dpcore read error during body copy: %v", rerr)
|
||||||
onExitFlushLoop()
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
case <-t.C:
|
if nr > 0 {
|
||||||
m.mu.Lock()
|
nw, werr := dst.Write(buf[:nr])
|
||||||
m.dst.Flush()
|
if nw > 0 {
|
||||||
m.mu.Unlock()
|
written += int64(nw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if werr != nil {
|
||||||
|
return written, werr
|
||||||
|
}
|
||||||
|
|
||||||
|
if nr != nw {
|
||||||
|
return written, io.ErrShortWrite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *maxLatencyWriter) stop() {
|
if rerr != nil {
|
||||||
m.done <- true
|
if rerr == io.EOF {
|
||||||
|
rerr = nil
|
||||||
|
}
|
||||||
|
return written, rerr
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReverseProxy) logf(format string, args ...interface{}) {
|
func (p *ReverseProxy) logf(format string, args ...interface{}) {
|
||||||
@ -243,7 +246,7 @@ func (p *ReverseProxy) logf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeHeaders(header http.Header) {
|
func removeHeaders(header http.Header, noCache bool) {
|
||||||
// Remove hop-by-hop headers listed in the "Connection" header.
|
// Remove hop-by-hop headers listed in the "Connection" header.
|
||||||
if c := header.Get("Connection"); c != "" {
|
if c := header.Get("Connection"); c != "" {
|
||||||
for _, f := range strings.Split(c, ",") {
|
for _, f := range strings.Split(c, ",") {
|
||||||
@ -260,10 +263,25 @@ func removeHeaders(header http.Header) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if header.Get("A-Upgrade") != "" {
|
//Restore the Upgrade header if any
|
||||||
header.Set("Upgrade", header.Get("A-Upgrade"))
|
if header.Get("Zr-Origin-Upgrade") != "" {
|
||||||
header.Del("A-Upgrade")
|
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
|
||||||
|
header.Del("Zr-Origin-Upgrade")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Disable cache if nocache is set
|
||||||
|
if noCache {
|
||||||
|
header.Del("Cache-Control")
|
||||||
|
header.Set("Cache-Control", "no-store")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Hide Go-HTTP-Client UA if the client didnt sent us one
|
||||||
|
if _, ok := header["User-Agent"]; !ok {
|
||||||
|
// If the outbound request doesn't have a User-Agent header set,
|
||||||
|
// don't send the default Go HTTP client User-Agent.
|
||||||
|
header.Set("User-Agent", "")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addXForwardedForHeader(req *http.Request) {
|
func addXForwardedForHeader(req *http.Request) {
|
||||||
@ -281,6 +299,22 @@ func addXForwardedForHeader(req *http.Request) {
|
|||||||
req.Header.Set("X-Forwarded-Proto", "http")
|
req.Header.Set("X-Forwarded-Proto", "http")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.Header.Get("X-Real-Ip") == "" {
|
||||||
|
//Check if CF-Connecting-IP header exists
|
||||||
|
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
|
||||||
|
if CF_Connecting_IP != "" {
|
||||||
|
//Use CF Connecting IP
|
||||||
|
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
|
||||||
|
} else {
|
||||||
|
// Not exists. Fill it in with first entry in X-Forwarded-For
|
||||||
|
ips := strings.Split(clientIP, ",")
|
||||||
|
if len(ips) > 0 {
|
||||||
|
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +357,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
copyHeader(outreq.Header, req.Header)
|
copyHeader(outreq.Header, req.Header)
|
||||||
|
|
||||||
// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
|
// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
|
||||||
removeHeaders(outreq.Header)
|
removeHeaders(outreq.Header, rrr.NoCache)
|
||||||
|
|
||||||
// Add X-Forwarded-For Header.
|
// Add X-Forwarded-For Header.
|
||||||
addXForwardedForHeader(outreq)
|
addXForwardedForHeader(outreq)
|
||||||
@ -339,7 +373,13 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
|
// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
|
||||||
removeHeaders(res.Header)
|
removeHeaders(res.Header, rrr.NoCache)
|
||||||
|
|
||||||
|
//Remove the User-Agent header if exists
|
||||||
|
if _, ok := res.Header["User-Agent"]; ok {
|
||||||
|
//Server to client request should not contains a User-Agent header
|
||||||
|
res.Header.Del("User-Agent")
|
||||||
|
}
|
||||||
|
|
||||||
if p.ModifyResponse != nil {
|
if p.ModifyResponse != nil {
|
||||||
if err := p.ModifyResponse(res); err != nil {
|
if err := p.ModifyResponse(res); err != nil {
|
||||||
@ -352,6 +392,12 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if res.StatusCode == 501 || res.StatusCode == 500 {
|
||||||
|
// fmt.Println(outreq.Proto, outreq.RemoteAddr, outreq.RequestURI)
|
||||||
|
// fmt.Println(">>>", outreq.Method, res.Header, res.ContentLength, res.StatusCode)
|
||||||
|
// fmt.Println(outreq.Header, req.Host)
|
||||||
|
//}
|
||||||
|
|
||||||
//Custom header rewriter functions
|
//Custom header rewriter functions
|
||||||
if res.Header.Get("Location") != "" {
|
if res.Header.Get("Location") != "" {
|
||||||
locationRewrite := res.Header.Get("Location")
|
locationRewrite := res.Header.Get("Location")
|
||||||
@ -400,7 +446,10 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.copyResponse(rw, res.Body)
|
//Get flush interval in real time and start copying the request
|
||||||
|
flushInterval := p.getFlushInterval(req, res)
|
||||||
|
p.copyResponse(rw, res.Body, flushInterval)
|
||||||
|
|
||||||
// close now, instead of defer, to populate res.Trailer
|
// close now, instead of defer, to populate res.Trailer
|
||||||
res.Body.Close()
|
res.Body.Close()
|
||||||
copyHeader(rw.Header(), res.Trailer)
|
copyHeader(rw.Header(), res.Trailer)
|
||||||
|
38
src/mod/dynamicproxy/dpcore/flush.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package dpcore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auto sniff of flush interval from header
|
||||||
|
func (p *ReverseProxy) getFlushInterval(req *http.Request, res *http.Response) time.Duration {
|
||||||
|
contentType := req.Header.Get("Content-Type")
|
||||||
|
if actualContentType, _, _ := mime.ParseMediaType(contentType); actualContentType == "text/event-stream" {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ContentLength == -1 || p.isBidirectionalStream(req, res) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
//Cannot sniff anything. Use default value
|
||||||
|
return p.FlushInterval
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for bidirectional stream, copy from Caddy :D
|
||||||
|
func (p *ReverseProxy) isBidirectionalStream(req *http.Request, res *http.Response) bool {
|
||||||
|
// We have to check the encoding here; only flush headers with identity encoding.
|
||||||
|
// Non-identity encoding might combine with "encode" directive, and in that case,
|
||||||
|
// if body size larger than enc.MinLength, upper level encode handle might have
|
||||||
|
// Content-Encoding header to write.
|
||||||
|
// (see https://github.com/caddyserver/caddy/issues/3606 for use case)
|
||||||
|
ae := req.Header.Get("Accept-Encoding")
|
||||||
|
|
||||||
|
return req.ProtoMajor == 2 &&
|
||||||
|
res.ProtoMajor == 2 &&
|
||||||
|
res.ContentLength == -1 &&
|
||||||
|
(ae == "identity" || ae == "")
|
||||||
|
}
|
73
src/mod/dynamicproxy/dpcore/maxLatencyWriter.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package dpcore
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Max Latency Writer
|
||||||
|
|
||||||
|
This script implements a io writer with periodic flushing base on a ticker
|
||||||
|
Mostly based on httputil.ReverseProxy
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type maxLatencyWriter struct {
|
||||||
|
dst io.Writer
|
||||||
|
flush func() error
|
||||||
|
latency time.Duration // non-zero; negative means to flush immediately
|
||||||
|
mu sync.Mutex // protects t, flushPending, and dst.Flush
|
||||||
|
t *time.Timer
|
||||||
|
flushPending bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
n, err = m.dst.Write(p)
|
||||||
|
if m.latency < 0 {
|
||||||
|
//Flush immediately
|
||||||
|
m.flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.flushPending {
|
||||||
|
//Flush in next tick cycle
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.t == nil {
|
||||||
|
m.t = time.AfterFunc(m.latency, m.delayedFlush)
|
||||||
|
} else {
|
||||||
|
m.t.Reset(m.latency)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.flushPending = true
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *maxLatencyWriter) delayedFlush() {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
if !m.flushPending {
|
||||||
|
// if stop was called but AfterFunc already started this goroutine
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.flush()
|
||||||
|
m.flushPending = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *maxLatencyWriter) stop() {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
m.flushPending = false
|
||||||
|
if m.t != nil {
|
||||||
|
m.t.Stop()
|
||||||
|
}
|
||||||
|
}
|
@ -22,11 +22,9 @@ import (
|
|||||||
|
|
||||||
func NewDynamicProxy(option RouterOption) (*Router, error) {
|
func NewDynamicProxy(option RouterOption) (*Router, error) {
|
||||||
proxyMap := sync.Map{}
|
proxyMap := sync.Map{}
|
||||||
domainMap := sync.Map{}
|
|
||||||
thisRouter := Router{
|
thisRouter := Router{
|
||||||
Option: &option,
|
Option: &option,
|
||||||
ProxyEndpoints: &proxyMap,
|
ProxyEndpoints: &proxyMap,
|
||||||
SubdomainEndpoint: &domainMap,
|
|
||||||
Running: false,
|
Running: false,
|
||||||
server: nil,
|
server: nil,
|
||||||
routingRules: []*RoutingRule{},
|
routingRules: []*RoutingRule{},
|
||||||
@ -37,12 +35,6 @@ func NewDynamicProxy(option RouterOption) (*Router, error) {
|
|||||||
Parent: &thisRouter,
|
Parent: &thisRouter,
|
||||||
}
|
}
|
||||||
|
|
||||||
//Prase the tld map for tld redirection in main router
|
|
||||||
//See Server.go declarations
|
|
||||||
if len(rawTldMap) > 0 {
|
|
||||||
json.Unmarshal(rawTldMap, &thisRouter.tldMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &thisRouter, nil
|
return &thisRouter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,21 +68,14 @@ func (router *Router) UpdateHttpToHttpsRedirectSetting(useRedirect bool) {
|
|||||||
func (router *Router) StartProxyService() error {
|
func (router *Router) StartProxyService() error {
|
||||||
//Create a new server object
|
//Create a new server object
|
||||||
if router.server != nil {
|
if router.server != nil {
|
||||||
return errors.New("Reverse proxy server already running")
|
return errors.New("reverse proxy server already running")
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if root route is set
|
//Check if root route is set
|
||||||
if router.Root == nil {
|
if router.Root == nil {
|
||||||
return errors.New("Reverse proxy router root not set")
|
return errors.New("reverse proxy router root not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load root options from file
|
|
||||||
loadedRootOption, err := loadRootRoutingOptionsFromFile()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
router.RootRoutingOptions = loadedRootOption
|
|
||||||
|
|
||||||
minVersion := tls.VersionTLS10
|
minVersion := tls.VersionTLS10
|
||||||
if router.Option.ForceTLSLatest {
|
if router.Option.ForceTLSLatest {
|
||||||
minVersion = tls.VersionTLS12
|
minVersion = tls.VersionTLS12
|
||||||
@ -101,16 +86,6 @@ func (router *Router) StartProxyService() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if router.Option.UseTls {
|
if router.Option.UseTls {
|
||||||
/*
|
|
||||||
//Serve with TLS mode
|
|
||||||
ln, err := tls.Listen("tcp", ":"+strconv.Itoa(router.Option.Port), config)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
router.Running = false
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
router.tlsListener = ln
|
|
||||||
*/
|
|
||||||
router.server = &http.Server{
|
router.server = &http.Server{
|
||||||
Addr: ":" + strconv.Itoa(router.Option.Port),
|
Addr: ":" + strconv.Itoa(router.Option.Port),
|
||||||
Handler: router.mux,
|
Handler: router.mux,
|
||||||
@ -129,7 +104,7 @@ func (router *Router) StartProxyService() error {
|
|||||||
hostPath := strings.Split(r.Host, ":")
|
hostPath := strings.Split(r.Host, ":")
|
||||||
domainOnly = hostPath[0]
|
domainOnly = hostPath[0]
|
||||||
}
|
}
|
||||||
sep := router.getSubdomainProxyEndpointFromHostname(domainOnly)
|
sep := router.getProxyEndpointFromHostname(domainOnly)
|
||||||
if sep != nil && sep.BypassGlobalTLS {
|
if sep != nil && sep.BypassGlobalTLS {
|
||||||
//Allow routing via non-TLS handler
|
//Allow routing via non-TLS handler
|
||||||
originalHostHeader := r.Host
|
originalHostHeader := r.Host
|
||||||
@ -140,7 +115,29 @@ func (router *Router) StartProxyService() error {
|
|||||||
r.URL, _ = url.Parse(originalHostHeader)
|
r.URL, _ = url.Parse(originalHostHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
sep.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
//Access Check (blacklist / whitelist)
|
||||||
|
ruleID := sep.AccessFilterUUID
|
||||||
|
if sep.AccessFilterUUID == "" {
|
||||||
|
//Use default rule
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
accessRule, err := router.Option.AccessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err == nil {
|
||||||
|
isBlocked, _ := accessRequestBlocked(accessRule, router.Option.WebDirectory, w, r)
|
||||||
|
if isBlocked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Validate basic auth
|
||||||
|
if sep.RequireBasicAuth {
|
||||||
|
err := handleBasicAuth(w, r, sep)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: sep.Domain,
|
ProxyDomain: sep.Domain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: sep.RequireTLS,
|
UseTLS: sep.RequireTLS,
|
||||||
@ -225,7 +222,7 @@ func (router *Router) StartProxyService() error {
|
|||||||
|
|
||||||
func (router *Router) StopProxyService() error {
|
func (router *Router) StopProxyService() error {
|
||||||
if router.server == nil {
|
if router.server == nil {
|
||||||
return errors.New("Reverse proxy server already stopped")
|
return errors.New("reverse proxy server already stopped")
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -253,13 +250,13 @@ func (router *Router) StopProxyService() error {
|
|||||||
// Restart the current router if it is running.
|
// Restart the current router if it is running.
|
||||||
func (router *Router) Restart() error {
|
func (router *Router) Restart() error {
|
||||||
//Stop the router if it is already running
|
//Stop the router if it is already running
|
||||||
var err error = nil
|
|
||||||
if router.Running {
|
if router.Running {
|
||||||
err := router.StopProxyService()
|
err := router.StopProxyService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time.Sleep(300 * time.Millisecond)
|
||||||
// Start the server
|
// Start the server
|
||||||
err = router.StartProxyService()
|
err = router.StartProxyService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -267,7 +264,7 @@ func (router *Router) Restart() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -280,128 +277,17 @@ func (router *Router) IsProxiedSubdomain(r *http.Request) bool {
|
|||||||
hostname = r.Host
|
hostname = r.Host
|
||||||
}
|
}
|
||||||
hostname = strings.Split(hostname, ":")[0]
|
hostname = strings.Split(hostname, ":")[0]
|
||||||
subdEndpoint := router.getSubdomainProxyEndpointFromHostname(hostname)
|
subdEndpoint := router.getProxyEndpointFromHostname(hostname)
|
||||||
return subdEndpoint != nil
|
return subdEndpoint != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Add an URL into a custom proxy services
|
|
||||||
*/
|
|
||||||
func (router *Router) AddVirtualDirectoryProxyService(options *VdirOptions) error {
|
|
||||||
domain := options.Domain
|
|
||||||
if domain[len(domain)-1:] == "/" {
|
|
||||||
domain = domain[:len(domain)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
if rootname[len(rootname)-1:] == "/" {
|
|
||||||
rootname = rootname[:len(rootname)-1]
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
webProxyEndpoint := domain
|
|
||||||
if options.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 err
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy := dpcore.NewDynamicProxyCore(path, options.RootName, options.SkipCertValidations)
|
|
||||||
|
|
||||||
endpointObject := ProxyEndpoint{
|
|
||||||
ProxyType: ProxyType_Vdir,
|
|
||||||
RootOrMatchingDomain: options.RootName,
|
|
||||||
Domain: domain,
|
|
||||||
RequireTLS: options.RequireTLS,
|
|
||||||
SkipCertValidations: options.SkipCertValidations,
|
|
||||||
RequireBasicAuth: options.RequireBasicAuth,
|
|
||||||
BasicAuthCredentials: options.BasicAuthCredentials,
|
|
||||||
BasicAuthExceptionRules: options.BasicAuthExceptionRules,
|
|
||||||
Proxy: proxy,
|
|
||||||
}
|
|
||||||
|
|
||||||
router.ProxyEndpoints.Store(options.RootName, &endpointObject)
|
|
||||||
|
|
||||||
log.Println("Registered Proxy Rule: ", options.RootName+" to "+domain)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Load routing from RP
|
Load routing from RP
|
||||||
*/
|
*/
|
||||||
func (router *Router) LoadProxy(ptype string, key string) (*ProxyEndpoint, error) {
|
func (router *Router) LoadProxy(matchingDomain string) (*ProxyEndpoint, error) {
|
||||||
if ptype == "vdir" {
|
var targetProxyEndpoint *ProxyEndpoint
|
||||||
proxy, ok := router.ProxyEndpoints.Load(key)
|
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||||
if !ok {
|
key, ok := key.(string)
|
||||||
return nil, errors.New("target proxy not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
targetProxy := proxy.(*ProxyEndpoint)
|
|
||||||
targetProxy.parent = router
|
|
||||||
return targetProxy, nil
|
|
||||||
} else if ptype == "subd" {
|
|
||||||
proxy, ok := router.SubdomainEndpoint.Load(key)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("target proxy not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
targetProxy := proxy.(*ProxyEndpoint)
|
|
||||||
targetProxy.parent = router
|
|
||||||
return targetProxy, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("unsupported ptype")
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add an default router for the proxy server
|
|
||||||
*/
|
|
||||||
func (router *Router) SetRootProxy(options *RootOptions) error {
|
|
||||||
proxyLocation := options.ProxyLocation
|
|
||||||
if proxyLocation[len(proxyLocation)-1:] == "/" {
|
|
||||||
proxyLocation = proxyLocation[:len(proxyLocation)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
webProxyEndpoint := proxyLocation
|
|
||||||
if options.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 err
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy := dpcore.NewDynamicProxyCore(path, "", options.SkipCertValidations)
|
|
||||||
|
|
||||||
rootEndpoint := ProxyEndpoint{
|
|
||||||
ProxyType: ProxyType_Vdir,
|
|
||||||
RootOrMatchingDomain: "/",
|
|
||||||
Domain: proxyLocation,
|
|
||||||
RequireTLS: options.RequireTLS,
|
|
||||||
SkipCertValidations: options.SkipCertValidations,
|
|
||||||
RequireBasicAuth: options.RequireBasicAuth,
|
|
||||||
BasicAuthCredentials: options.BasicAuthCredentials,
|
|
||||||
BasicAuthExceptionRules: options.BasicAuthExceptionRules,
|
|
||||||
Proxy: proxy,
|
|
||||||
}
|
|
||||||
|
|
||||||
router.Root = &rootEndpoint
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers to export the syncmap for easier processing
|
|
||||||
func (r *Router) GetSDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
|
|
||||||
m := make(map[string]*ProxyEndpoint)
|
|
||||||
r.SubdomainEndpoint.Range(func(key, value interface{}) bool {
|
|
||||||
k, ok := key.(string)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -409,13 +295,32 @@ func (r *Router) GetSDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
m[k] = v
|
|
||||||
|
if key == matchingDomain {
|
||||||
|
targetProxyEndpoint = v
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return m
|
|
||||||
|
if targetProxyEndpoint == nil {
|
||||||
|
return nil, errors.New("target routing rule not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) GetVDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
|
return targetProxyEndpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep copy a proxy endpoint, excluding runtime paramters
|
||||||
|
func CopyEndpoint(endpoint *ProxyEndpoint) *ProxyEndpoint {
|
||||||
|
js, _ := json.Marshal(endpoint)
|
||||||
|
newProxyEndpoint := ProxyEndpoint{}
|
||||||
|
err := json.Unmarshal(js, &newProxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &newProxyEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) GetProxyEndpointsAsMap() map[string]*ProxyEndpoint {
|
||||||
m := make(map[string]*ProxyEndpoint)
|
m := make(map[string]*ProxyEndpoint)
|
||||||
r.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
r.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||||
k, ok := key.(string)
|
k, ok := key.(string)
|
||||||
|
160
src/mod/dynamicproxy/endpoints.go
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package dynamicproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
endpoint.go
|
||||||
|
author: tobychui
|
||||||
|
|
||||||
|
This script handle the proxy endpoint object actions
|
||||||
|
so proxyEndpoint can be handled like a proper oop object
|
||||||
|
|
||||||
|
Most of the functions are implemented in dynamicproxy.go
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
User Defined Header Functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Check if a user define header exists in this endpoint, ignore case
|
||||||
|
func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
|
||||||
|
for _, header := range ep.UserDefinedHeaders {
|
||||||
|
if strings.EqualFold(header.Key, key) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remvoe a user defined header from the list
|
||||||
|
func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error {
|
||||||
|
newHeaderList := []*UserDefinedHeader{}
|
||||||
|
for _, header := range ep.UserDefinedHeaders {
|
||||||
|
if !strings.EqualFold(header.Key, key) {
|
||||||
|
newHeaderList = append(newHeaderList, header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ep.UserDefinedHeaders = newHeaderList
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a user defined header to the list, duplicates will be automatically removed
|
||||||
|
func (ep *ProxyEndpoint) AddUserDefinedHeader(key string, value string) error {
|
||||||
|
if ep.UserDefinedHeaderExists(key) {
|
||||||
|
ep.RemoveUserDefinedHeader(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, &UserDefinedHeader{
|
||||||
|
Key: cases.Title(language.Und, cases.NoLower).String(key), //e.g. x-proxy-by -> X-Proxy-By
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Virtual Directory Functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get virtual directory handler from given URI
|
||||||
|
func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI string) *VirtualDirectoryEndpoint {
|
||||||
|
for _, vdir := range ep.VirtualDirectories {
|
||||||
|
if strings.HasPrefix(requestURI, vdir.MatchingPath) {
|
||||||
|
thisVdir := vdir
|
||||||
|
return thisVdir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get virtual directory handler by matching path (exact match required)
|
||||||
|
func (ep *ProxyEndpoint) GetVirtualDirectoryRuleByMatchingPath(matchingPath string) *VirtualDirectoryEndpoint {
|
||||||
|
for _, vdir := range ep.VirtualDirectories {
|
||||||
|
if vdir.MatchingPath == matchingPath {
|
||||||
|
thisVdir := vdir
|
||||||
|
return thisVdir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a vdir rule by its matching path
|
||||||
|
func (ep *ProxyEndpoint) RemoveVirtualDirectoryRuleByMatchingPath(matchingPath string) error {
|
||||||
|
entryFound := false
|
||||||
|
newVirtualDirectoryList := []*VirtualDirectoryEndpoint{}
|
||||||
|
for _, vdir := range ep.VirtualDirectories {
|
||||||
|
if vdir.MatchingPath == matchingPath {
|
||||||
|
entryFound = true
|
||||||
|
} else {
|
||||||
|
newVirtualDirectoryList = append(newVirtualDirectoryList, vdir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if entryFound {
|
||||||
|
//Update the list of vdirs
|
||||||
|
ep.VirtualDirectories = newVirtualDirectoryList
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("target virtual directory routing rule not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a vdir rule by its matching path
|
||||||
|
func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint) (*ProxyEndpoint, error) {
|
||||||
|
//Check for matching path duplicate
|
||||||
|
if ep.GetVirtualDirectoryRuleByMatchingPath(vdir.MatchingPath) != nil {
|
||||||
|
return nil, errors.New("rule with same matching path already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Append it to the list of virtual directory
|
||||||
|
ep.VirtualDirectories = append(ep.VirtualDirectories, vdir)
|
||||||
|
|
||||||
|
//Prepare to replace the current routing rule
|
||||||
|
parentRouter := ep.parent
|
||||||
|
readyRoutingRule, err := parentRouter.PrepareProxyRoute(ep)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ep.ProxyType == ProxyType_Root {
|
||||||
|
parentRouter.Root = readyRoutingRule
|
||||||
|
} else if ep.ProxyType == ProxyType_Host {
|
||||||
|
ep.Remove()
|
||||||
|
parentRouter.AddProxyRouteToRuntime(readyRoutingRule)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("unsupported proxy type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return readyRoutingRule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a deep clone object of the proxy endpoint
|
||||||
|
// Note the returned object is not activated. Call to prepare function before pushing into runtime
|
||||||
|
func (ep *ProxyEndpoint) Clone() *ProxyEndpoint {
|
||||||
|
clonedProxyEndpoint := ProxyEndpoint{}
|
||||||
|
js, _ := json.Marshal(ep)
|
||||||
|
json.Unmarshal(js, &clonedProxyEndpoint)
|
||||||
|
return &clonedProxyEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove this proxy endpoint from running proxy endpoint list
|
||||||
|
func (ep *ProxyEndpoint) Remove() error {
|
||||||
|
ep.parent.ProxyEndpoints.Delete(ep.RootOrMatchingDomain)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write changes to runtime without respawning the proxy handler
|
||||||
|
// use prepare -> remove -> add if you change anything in the endpoint
|
||||||
|
// that effects the proxy routing src / dest
|
||||||
|
func (ep *ProxyEndpoint) UpdateToRuntime() {
|
||||||
|
ep.parent.ProxyEndpoints.Store(ep.RootOrMatchingDomain, ep)
|
||||||
|
}
|
@ -1,68 +0,0 @@
|
|||||||
package dynamicproxy
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
/*
|
|
||||||
ProxyEndpoint.go
|
|
||||||
author: tobychui
|
|
||||||
|
|
||||||
This script handle the proxy endpoint object actions
|
|
||||||
so proxyEndpoint can be handled like a proper oop object
|
|
||||||
|
|
||||||
Most of the functions are implemented in dynamicproxy.go
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Get the string version of proxy type
|
|
||||||
func (ep *ProxyEndpoint) GetProxyTypeString() string {
|
|
||||||
if ep.ProxyType == ProxyType_Subdomain {
|
|
||||||
return "subd"
|
|
||||||
} else if ep.ProxyType == ProxyType_Vdir {
|
|
||||||
return "vdir"
|
|
||||||
}
|
|
||||||
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
//Update change in the current running proxy endpoint config
|
|
||||||
func (ep *ProxyEndpoint) UpdateToRuntime() {
|
|
||||||
if ep.IsVdir() {
|
|
||||||
ep.parent.ProxyEndpoints.Store(ep.RootOrMatchingDomain, ep)
|
|
||||||
|
|
||||||
} else if ep.IsSubDomain() {
|
|
||||||
ep.parent.SubdomainEndpoint.Store(ep.RootOrMatchingDomain, ep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Return true if the endpoint type is virtual directory
|
|
||||||
func (ep *ProxyEndpoint) IsVdir() bool {
|
|
||||||
return ep.ProxyType == ProxyType_Vdir
|
|
||||||
}
|
|
||||||
|
|
||||||
//Return true if the endpoint type is subdomain
|
|
||||||
func (ep *ProxyEndpoint) IsSubDomain() bool {
|
|
||||||
return ep.ProxyType == ProxyType_Subdomain
|
|
||||||
}
|
|
||||||
|
|
||||||
//Remove this proxy endpoint from running proxy endpoint list
|
|
||||||
func (ep *ProxyEndpoint) Remove() error {
|
|
||||||
//fmt.Println(ptype, key)
|
|
||||||
if ep.IsVdir() {
|
|
||||||
ep.parent.ProxyEndpoints.Delete(ep.RootOrMatchingDomain)
|
|
||||||
return nil
|
|
||||||
} else if ep.IsSubDomain() {
|
|
||||||
ep.parent.SubdomainEndpoint.Delete(ep.RootOrMatchingDomain)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("invalid or unsupported type")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//ProxyEndpoint remove provide global access by key
|
|
||||||
func (router *Router) RemoveProxyEndpointByRootname(proxyType string, rootnameOrMatchingDomain string) error {
|
|
||||||
targetEpt, err := router.LoadProxy(proxyType, rootnameOrMatchingDomain)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return targetEpt.Remove()
|
|
||||||
}
|
|
@ -6,10 +6,12 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
"imuslab.com/zoraxy/mod/statistic"
|
"imuslab.com/zoraxy/mod/statistic"
|
||||||
"imuslab.com/zoraxy/mod/websocketproxy"
|
"imuslab.com/zoraxy/mod/websocketproxy"
|
||||||
)
|
)
|
||||||
@ -28,11 +30,61 @@ func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *P
|
|||||||
return targetProxyEndpoint
|
return targetProxyEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (router *Router) getSubdomainProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
|
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
|
||||||
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
||||||
ep, ok := router.SubdomainEndpoint.Load(hostname)
|
ep, ok := router.ProxyEndpoints.Load(hostname)
|
||||||
if ok {
|
if ok {
|
||||||
|
//Exact hit
|
||||||
targetSubdomainEndpoint = ep.(*ProxyEndpoint)
|
targetSubdomainEndpoint = ep.(*ProxyEndpoint)
|
||||||
|
if !targetSubdomainEndpoint.Disabled {
|
||||||
|
return targetSubdomainEndpoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//No hit. Try with wildcard and alias
|
||||||
|
matchProxyEndpoints := []*ProxyEndpoint{}
|
||||||
|
router.ProxyEndpoints.Range(func(k, v interface{}) bool {
|
||||||
|
ep := v.(*ProxyEndpoint)
|
||||||
|
match, err := filepath.Match(ep.RootOrMatchingDomain, hostname)
|
||||||
|
if err != nil {
|
||||||
|
//Bad pattern. Skip this rule
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
//Wildcard matches. Skip checking alias
|
||||||
|
matchProxyEndpoints = append(matchProxyEndpoints, ep)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//Wildcard not match. Check for alias
|
||||||
|
if ep.MatchingDomainAlias != nil && len(ep.MatchingDomainAlias) > 0 {
|
||||||
|
for _, aliasDomain := range ep.MatchingDomainAlias {
|
||||||
|
match, err := filepath.Match(aliasDomain, hostname)
|
||||||
|
if err != nil {
|
||||||
|
//Bad pattern. Skip this alias
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
//This alias match
|
||||||
|
matchProxyEndpoints = append(matchProxyEndpoints, ep)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(matchProxyEndpoints) == 1 {
|
||||||
|
//Only 1 match
|
||||||
|
return matchProxyEndpoints[0]
|
||||||
|
} else if len(matchProxyEndpoints) > 1 {
|
||||||
|
//More than one match. Get the best match one
|
||||||
|
sort.Slice(matchProxyEndpoints, func(i, j int) bool {
|
||||||
|
return matchProxyEndpoints[i].RootOrMatchingDomain < matchProxyEndpoints[j].RootOrMatchingDomain
|
||||||
|
})
|
||||||
|
return matchProxyEndpoints[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetSubdomainEndpoint
|
return targetSubdomainEndpoint
|
||||||
@ -54,14 +106,22 @@ func (router *Router) rewriteURL(rooturl string, requestURL string) string {
|
|||||||
return rewrittenURL
|
return rewrittenURL
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle subdomain request
|
// Handle host request
|
||||||
func (h *ProxyHandler) subdomainRequest(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)
|
||||||
|
|
||||||
|
//Inject custom headers
|
||||||
|
if len(target.UserDefinedHeaders) > 0 {
|
||||||
|
for _, customHeader := range target.UserDefinedHeaders {
|
||||||
|
r.Header.Set(customHeader.Key, customHeader.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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("A-Upgrade", "websocket")
|
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||||
wsRedirectionEndpoint := target.Domain
|
wsRedirectionEndpoint := target.Domain
|
||||||
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
|
||||||
@ -76,7 +136,10 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
|
|||||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
||||||
}
|
}
|
||||||
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
|
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
|
||||||
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
|
SkipTLSValidation: target.SkipCertValidations,
|
||||||
|
SkipOriginCheck: target.SkipWebSocketOriginCheck,
|
||||||
|
})
|
||||||
wspHandler.ServeHTTP(w, r)
|
wspHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -89,10 +152,11 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
|
|||||||
r.URL, _ = url.Parse(originalHostHeader)
|
r.URL, _ = url.Parse(originalHostHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := target.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: target.Domain,
|
ProxyDomain: target.Domain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: target.RequireTLS,
|
UseTLS: target.RequireTLS,
|
||||||
|
NoCache: h.Parent.Option.NoCache,
|
||||||
PathPrefix: "",
|
PathPrefix: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -113,15 +177,23 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle vdir type request
|
// Handle vdir type request
|
||||||
func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
|
func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, target *VirtualDirectoryEndpoint) {
|
||||||
rewriteURL := h.Parent.rewriteURL(target.RootOrMatchingDomain, r.RequestURI)
|
rewriteURL := h.Parent.rewriteURL(target.MatchingPath, r.RequestURI)
|
||||||
r.URL, _ = url.Parse(rewriteURL)
|
r.URL, _ = url.Parse(rewriteURL)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
//Inject custom headers
|
||||||
|
if len(target.parent.UserDefinedHeaders) > 0 {
|
||||||
|
for _, customHeader := range target.parent.UserDefinedHeaders {
|
||||||
|
r.Header.Set(customHeader.Key, customHeader.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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("A-Upgrade", "websocket")
|
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||||
wsRedirectionEndpoint := target.Domain
|
wsRedirectionEndpoint := target.Domain
|
||||||
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
||||||
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
|
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
|
||||||
@ -131,7 +203,10 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ
|
|||||||
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.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||||
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
|
SkipTLSValidation: target.SkipCertValidations,
|
||||||
|
SkipOriginCheck: target.parent.SkipWebSocketOriginCheck,
|
||||||
|
})
|
||||||
wspHandler.ServeHTTP(w, r)
|
wspHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -144,11 +219,11 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ
|
|||||||
r.URL, _ = url.Parse(originalHostHeader)
|
r.URL, _ = url.Parse(originalHostHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := target.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: target.Domain,
|
ProxyDomain: target.Domain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: target.RequireTLS,
|
UseTLS: target.RequireTLS,
|
||||||
PathPrefix: target.RootOrMatchingDomain,
|
PathPrefix: target.MatchingPath,
|
||||||
})
|
})
|
||||||
|
|
||||||
var dnsError *net.DNSError
|
var dnsError *net.DNSError
|
||||||
@ -171,7 +246,7 @@ func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, fo
|
|||||||
if h.Parent.Option.StatisticCollector != nil {
|
if h.Parent.Option.StatisticCollector != nil {
|
||||||
go func() {
|
go func() {
|
||||||
requestInfo := statistic.RequestInfo{
|
requestInfo := statistic.RequestInfo{
|
||||||
IpAddr: geodb.GetRequesterIP(r),
|
IpAddr: netutils.GetRequesterIP(r),
|
||||||
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||||
Succ: succ,
|
Succ: succ,
|
||||||
StatusCode: statusCode,
|
StatusCode: statusCode,
|
||||||
|
@ -2,19 +2,25 @@ package redirection
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RuleTable struct {
|
type RuleTable struct {
|
||||||
|
AllowRegex bool //Allow regular expression to be used in rule matching. Require up to O(n^m) time complexity
|
||||||
|
Logger *logger.Logger
|
||||||
configPath string //The location where the redirection rules is stored
|
configPath string //The location where the redirection rules is stored
|
||||||
rules sync.Map //Store the redirection rules for this reverse proxy instance
|
rules sync.Map //Store the redirection rules for this reverse proxy instance
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedirectRules struct {
|
type RedirectRules struct {
|
||||||
@ -24,10 +30,11 @@ type RedirectRules struct {
|
|||||||
StatusCode int //Status Code for redirection
|
StatusCode int //Status Code for redirection
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRuleTable(configPath string) (*RuleTable, error) {
|
func NewRuleTable(configPath string, allowRegex bool) (*RuleTable, error) {
|
||||||
thisRuleTable := RuleTable{
|
thisRuleTable := RuleTable{
|
||||||
rules: sync.Map{},
|
rules: sync.Map{},
|
||||||
configPath: configPath,
|
configPath: configPath,
|
||||||
|
AllowRegex: allowRegex,
|
||||||
}
|
}
|
||||||
//Load all the rules from the config path
|
//Load all the rules from the config path
|
||||||
if !utils.FileExists(configPath) {
|
if !utils.FileExists(configPath) {
|
||||||
@ -77,7 +84,7 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
||||||
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
|
filename := utils.ReplaceSpecialCharacters(redirectURL) + ".json"
|
||||||
|
|
||||||
// Create the full file path by joining the t.configPath with the filename
|
// Create the full file path by joining the t.configPath with the filename
|
||||||
filepath := path.Join(t.configPath, filename)
|
filepath := path.Join(t.configPath, filename)
|
||||||
@ -105,11 +112,12 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
|
|||||||
|
|
||||||
func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
|
func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
|
||||||
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
||||||
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
|
filename := utils.ReplaceSpecialCharacters(redirectURL) + ".json"
|
||||||
|
|
||||||
// Create the full file path by joining the t.configPath with the filename
|
// Create the full file path by joining the t.configPath with the filename
|
||||||
filepath := path.Join(t.configPath, filename)
|
filepath := path.Join(t.configPath, filename)
|
||||||
|
|
||||||
|
fmt.Println(redirectURL, filename, filepath)
|
||||||
// Check if the file exists
|
// Check if the file exists
|
||||||
if _, err := os.Stat(filepath); os.IsNotExist(err) {
|
if _, err := os.Stat(filepath); os.IsNotExist(err) {
|
||||||
return nil // File doesn't exist, nothing to delete
|
return nil // File doesn't exist, nothing to delete
|
||||||
@ -145,9 +153,23 @@ func (t *RuleTable) MatchRedirectRule(requestedURL string) *RedirectRules {
|
|||||||
// Iterate through all the keys in the rules map
|
// Iterate through all the keys in the rules map
|
||||||
var targetRedirectionRule *RedirectRules = nil
|
var targetRedirectionRule *RedirectRules = nil
|
||||||
var maxMatch int = 0
|
var maxMatch int = 0
|
||||||
|
|
||||||
t.rules.Range(func(key interface{}, value interface{}) bool {
|
t.rules.Range(func(key interface{}, value interface{}) bool {
|
||||||
// Check if the requested URL starts with the key as a prefix
|
// Check if the requested URL starts with the key as a prefix
|
||||||
|
if t.AllowRegex {
|
||||||
|
//Regexp matching rule
|
||||||
|
matched, err := regexp.MatchString(key.(string), requestedURL)
|
||||||
|
if err != nil {
|
||||||
|
//Something wrong with the regex?
|
||||||
|
t.log("Unable to match regex", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
maxMatch = len(key.(string))
|
||||||
|
targetRedirectionRule = value.(*RedirectRules)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//Default: prefix matching redirect
|
||||||
if strings.HasPrefix(requestedURL, key.(string)) {
|
if strings.HasPrefix(requestedURL, key.(string)) {
|
||||||
// This request URL matched the domain
|
// This request URL matched the domain
|
||||||
if len(key.(string)) > maxMatch {
|
if len(key.(string)) > maxMatch {
|
||||||
@ -155,8 +177,23 @@ func (t *RuleTable) MatchRedirectRule(requestedURL string) *RedirectRules {
|
|||||||
targetRedirectionRule = value.(*RedirectRules)
|
targetRedirectionRule = value.(*RedirectRules)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
return targetRedirectionRule
|
return targetRedirectionRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the message to log file, use STDOUT if logger not set
|
||||||
|
func (t *RuleTable) log(message string, err error) {
|
||||||
|
if t.Logger == nil {
|
||||||
|
if err == nil {
|
||||||
|
log.Println("[Redirect] " + message)
|
||||||
|
} else {
|
||||||
|
log.Println("[Redirect] " + message + ": " + err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Logger.PrintAndLog("Redirect", message, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
package dynamicproxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
rootRoute.go
|
|
||||||
|
|
||||||
This script handle special case in routing where the root proxy
|
|
||||||
entity is involved. This also include its setting object
|
|
||||||
RootRoutingOptions
|
|
||||||
*/
|
|
||||||
|
|
||||||
var rootConfigFilepath string = "conf/root_config.json"
|
|
||||||
|
|
||||||
func loadRootRoutingOptionsFromFile() (*RootRoutingOptions, error) {
|
|
||||||
if !utils.FileExists(rootConfigFilepath) {
|
|
||||||
//Not found. Create a root option
|
|
||||||
js, _ := json.MarshalIndent(RootRoutingOptions{}, "", " ")
|
|
||||||
err := os.WriteFile(rootConfigFilepath, js, 0775)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Unable to write root config to file: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newRootOption := RootRoutingOptions{}
|
|
||||||
rootOptionsBytes, err := os.ReadFile(rootConfigFilepath)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("[Error] Unable to read root config file at " + rootConfigFilepath + ": " + err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(rootOptionsBytes, &newRootOption)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("[Error] Unable to parse root config file: " + err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &newRootOption, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the new config to file. Note that this will not overwrite the runtime one
|
|
||||||
func (opt *RootRoutingOptions) SaveToFile() error {
|
|
||||||
js, _ := json.MarshalIndent(opt, "", " ")
|
|
||||||
err := os.WriteFile(rootConfigFilepath, js, 0775)
|
|
||||||
return err
|
|
||||||
}
|
|
121
src/mod/dynamicproxy/router.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package dynamicproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dynamic Proxy Router Functions
|
||||||
|
|
||||||
|
This script handle the proxy rules router spawning
|
||||||
|
and preparation
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Prepare proxy route generate a proxy handler service object for your endpoint
|
||||||
|
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
||||||
|
//Filter the tailing slash if any
|
||||||
|
domain := endpoint.Domain
|
||||||
|
if len(domain) == 0 {
|
||||||
|
return nil, errors.New("invalid endpoint config")
|
||||||
|
}
|
||||||
|
if domain[len(domain)-1:] == "/" {
|
||||||
|
domain = domain[:len(domain)-1]
|
||||||
|
}
|
||||||
|
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
|
||||||
|
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
|
||||||
|
IgnoreTLSVerification: endpoint.SkipCertValidations,
|
||||||
|
})
|
||||||
|
endpoint.proxy = proxy
|
||||||
|
endpoint.parent = router
|
||||||
|
|
||||||
|
//Prepare proxy routing hjandler for each of the virtual directories
|
||||||
|
for _, vdir := range endpoint.VirtualDirectories {
|
||||||
|
domain := vdir.Domain
|
||||||
|
if len(domain) == 0 {
|
||||||
|
//invalid vdir
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if domain[len(domain)-1:] == "/" {
|
||||||
|
domain = domain[:len(domain)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
//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 vdir.RequireTLS {
|
||||||
|
webProxyEndpoint = "https://" + webProxyEndpoint
|
||||||
|
} else {
|
||||||
|
webProxyEndpoint = "http://" + webProxyEndpoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := url.Parse(webProxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := dpcore.NewDynamicProxyCore(path, vdir.MatchingPath, &dpcore.DpcoreOptions{
|
||||||
|
IgnoreTLSVerification: vdir.SkipCertValidations,
|
||||||
|
})
|
||||||
|
vdir.proxy = proxy
|
||||||
|
vdir.parent = endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
|
||||||
|
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
||||||
|
if endpoint.proxy == nil {
|
||||||
|
//This endpoint is not prepared
|
||||||
|
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
||||||
|
}
|
||||||
|
// Push record into running subdomain endpoints
|
||||||
|
router.ProxyEndpoints.Store(endpoint.RootOrMatchingDomain, endpoint)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set given Proxy Route as Root. Call to PrepareProxyRoute before adding to runtime
|
||||||
|
func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error {
|
||||||
|
if endpoint.proxy == nil {
|
||||||
|
//This endpoint is not prepared
|
||||||
|
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
||||||
|
}
|
||||||
|
// Push record into running root endpoints
|
||||||
|
router.Root = endpoint
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyEndpoint remove provide global access by key
|
||||||
|
func (router *Router) RemoveProxyEndpointByRootname(rootnameOrMatchingDomain string) error {
|
||||||
|
targetEpt, err := router.LoadProxy(rootnameOrMatchingDomain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetEpt.Remove()
|
||||||
|
}
|
@ -1,50 +0,0 @@
|
|||||||
package dynamicproxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add an URL intoa custom subdomain service
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (router *Router) AddSubdomainRoutingService(options *SubdOptions) error {
|
|
||||||
domain := options.Domain
|
|
||||||
if domain[len(domain)-1:] == "/" {
|
|
||||||
domain = domain[:len(domain)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
webProxyEndpoint := domain
|
|
||||||
if options.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 err
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy := dpcore.NewDynamicProxyCore(path, "", options.SkipCertValidations)
|
|
||||||
|
|
||||||
router.SubdomainEndpoint.Store(options.MatchingDomain, &ProxyEndpoint{
|
|
||||||
RootOrMatchingDomain: options.MatchingDomain,
|
|
||||||
Domain: domain,
|
|
||||||
RequireTLS: options.RequireTLS,
|
|
||||||
Proxy: proxy,
|
|
||||||
BypassGlobalTLS: options.BypassGlobalTLS,
|
|
||||||
SkipCertValidations: options.SkipCertValidations,
|
|
||||||
RequireBasicAuth: options.RequireBasicAuth,
|
|
||||||
BasicAuthCredentials: options.BasicAuthCredentials,
|
|
||||||
BasicAuthExceptionRules: options.BasicAuthExceptionRules,
|
|
||||||
})
|
|
||||||
|
|
||||||
log.Println("Adding Subdomain Rule: ", options.MatchingDomain+" to "+domain)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -39,7 +39,7 @@
|
|||||||
<h3 style="margin-top: 1em;">403 - Forbidden</h3>
|
<h3 style="margin-top: 1em;">403 - Forbidden</h3>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<p>You do not have permission to view this directory or page. <br>
|
<p>You do not have permission to view this directory or page. <br>
|
||||||
This might cause by the region limit setting of this site.</p>
|
This might be caused by the region limit setting of this site.</p>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div style="text-align: left;">
|
<div style="text-align: left;">
|
||||||
<small>Request time: <span id="reqtime"></span></small><br>
|
<small>Request time: <span id="reqtime"></span></small><br>
|
||||||
|
157
src/mod/dynamicproxy/templates/hosterror.html
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="theme-color" content="#4b75ff">
|
||||||
|
<link rel="icon" type="image/png" href="img/small_icon.png"/>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.css">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
|
||||||
|
<title>404 - Host Not Found</title>
|
||||||
|
<style>
|
||||||
|
h1, h2, h3, h4, h5, p, a, span{
|
||||||
|
font-family: 'Noto Sans TC', sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
color: rgb(88, 88, 88)
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram{
|
||||||
|
background-color: #ebebeb;
|
||||||
|
box-shadow:
|
||||||
|
inset 0px 11px 8px -10px #CCC,
|
||||||
|
inset 0px -11px 8px -10px #CCC;
|
||||||
|
padding-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagramHeader{
|
||||||
|
margin-top: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:512px) {
|
||||||
|
.widescreenOnly{
|
||||||
|
display: none !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.four.wide.column:not(.widescreenOnly){
|
||||||
|
width: 50% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.grid{
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<br><br>
|
||||||
|
<div class="ui container">
|
||||||
|
<h1 style="font-size: 4rem;">Error 404</h1>
|
||||||
|
<p style="font-size: 2rem; margin-bottom: 0.4em;">Target Host Not Found</p>
|
||||||
|
<small id="timestamp"></small>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
</div>
|
||||||
|
<div class="diagram">
|
||||||
|
<div class="ui text container">
|
||||||
|
<div class="ui grid">
|
||||||
|
<div class="four wide column widescreenOnly" align="center">
|
||||||
|
<svg version="1.1" id="client_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||||
|
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
|
||||||
|
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
|
||||||
|
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
|
||||||
|
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
|
||||||
|
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
|
||||||
|
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
|
||||||
|
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
|
||||||
|
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
|
||||||
|
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
|
||||||
|
82.989,143.204 "/>
|
||||||
|
</svg>
|
||||||
|
<small>You</small>
|
||||||
|
<h2 class="diagramHeader">Browser</h2>
|
||||||
|
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||||
|
</div>
|
||||||
|
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||||
|
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||||
|
</div>
|
||||||
|
<div class="four wide column widescreenOnly" align="center">
|
||||||
|
<svg version="1.1" id="cloud_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||||
|
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
|
||||||
|
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
|
||||||
|
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
|
||||||
|
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
|
||||||
|
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
|
||||||
|
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
|
||||||
|
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
|
||||||
|
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
|
||||||
|
83.971,148.123 "/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<small>Gateway Node</small>
|
||||||
|
<h2 class="diagramHeader">Reverse Proxy</h2>
|
||||||
|
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||||
|
</div>
|
||||||
|
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||||
|
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||||
|
</div>
|
||||||
|
<div class="four wide column" align="center">
|
||||||
|
<svg version="1.1" id="host_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||||
|
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
|
||||||
|
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
|
||||||
|
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
|
||||||
|
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
|
||||||
|
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
|
||||||
|
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
|
||||||
|
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
|
||||||
|
</svg>
|
||||||
|
<small id="host"></small>
|
||||||
|
<h2 class="diagramHeader">Host</h2>
|
||||||
|
<p style="font-weight: 500; color: #bd2426;">Not Found</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui stackable grid">
|
||||||
|
<div class="eight wide column">
|
||||||
|
<h1>What happend?</h1>
|
||||||
|
<p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p>
|
||||||
|
</div>
|
||||||
|
<div class="eight wide column">
|
||||||
|
<h1>What can I do?</h1>
|
||||||
|
<h5 style="font-weight: 500;">If you are a visitor of this website: </h5>
|
||||||
|
<p>Please try again in a few minutes</p>
|
||||||
|
<h5 style="font-weight: 500;">If you are the owner of this website:</h5>
|
||||||
|
<div class="ui bulleted list">
|
||||||
|
<div class="item">Check if the proxy rules that match this hostname exists</div>
|
||||||
|
<div class="item">Visit the Reverse Proxy management interface to correct any setting errors</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui container" style="color: grey; font-size: 90%">
|
||||||
|
<p>Powered by Zoraxy</p>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$("#timestamp").text(new Date());
|
||||||
|
$("#host").text(location.href);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|