123 Commits
2.6.7 ... 3.0.5

Author SHA1 Message Date
ce8741bfc8 Merge pull request #174 from tobychui/v3.0.5
- Optimized uptime monitor error message
- Optimized detection logic for internal proxy target and header rewrite condition for HTTP_HOST
- Fixed ovh DNS challenge provider form generator bug
- Configuration for OVH DNS Challenge
- Added permission policy module (not enabled)
- Added single-use cookiejar to uptime monitor request client to handle cookie issues on some poorly written back-end server
2024-05-26 13:26:03 +08:00
7a3db09811 Updated acmedns generator results 2024-05-26 13:13:43 +08:00
e73f9b47d3 Update issue templates 2024-05-26 12:32:29 +08:00
c248dacccf Update uptime.go
+ Added cookiejar to request client #149
2024-05-25 14:44:48 +08:00
d596d6b843 v3.0.5 init commit
+ Added external domain name detection for PR #168
+ Updated uptime error message in 5xx range
+ Modernized reverse proxy error page template
+ Added wip permission policy module
2024-05-24 22:24:14 +08:00
6feb2d105d Merge pull request #168 from nettybun/issue-164-http-host-header
Use correct Host HTTP header
2024-05-24 20:13:33 +08:00
3a26a5b4d3 Use correct Host HTTP header 2024-05-23 12:03:00 -07:00
2cdd5654ed Update README.md
Fixed wordings
2024-05-21 15:23:54 +08:00
a0d362df4e Update README.md
Added getting started guide
2024-05-21 15:22:55 +08:00
334c1ab131 Updated provider dns credential fields 2024-05-20 21:56:40 +08:00
08d52024ab Fixed bug in generator
Fixed bug for acmedns module auto generation for ovh provider #161
2024-05-20 21:53:53 +08:00
a3e16594e8 Merge pull request #155 from Morethanevil/main
Update CHANGELOG.md
2024-05-18 21:30:42 +08:00
cced07ba2d Update CHANGELOG.md 2024-05-18 14:11:57 +02:00
2003992d75 Update README.md
Updated project desc
2024-05-18 15:30:21 +08:00
71423d98b1 Updated README banner
Updated readme banner
2024-05-18 15:27:53 +08:00
8ca716c59f Update README.md
Added DNS challenge in feature list
2024-05-18 15:12:25 +08:00
fe48a9a0c3 Updated acmedns tool
Add support for automatically generating acmedns module for Windows 7 using older version of lego
2024-05-18 14:48:55 +08:00
ec973eb3bc Merge pull request #152 from tobychui/v3.0.4
V3.0.4 Updates

- Optimized DNS challenge implementation
- Removed dependencies on environment variable write and keep all data contained
- Fixed panic on loading certificate generated by Zoraxy v2
- Added automatic form generator for DNS challenge / providers
- Added CA name default value
- Added code generator for acmedns module (storing the DNS challenge provider contents extracted from lego)
- Fixed ACME snippet "Obtain Certificate" concurrent issues in save EAB and DNS credentials
2024-05-18 13:07:59 +08:00
7b69b5fa63 Updated README
- Added start.sh
- Updated provider.json
2024-05-15 14:27:59 +08:00
ce4f46cb50 Fixed bugs caused by DNS challenge PR
- Fixed concurrency in EAB and DNS credential save
- Fixed missing CA name in ACME Obtain certificate handler
- Optimized acmedns code config
- Fixed a lot of front-end bugs in acme snippet
2024-05-14 16:21:47 +08:00
3454a9b975 Added backend generated dns providers arch
- Added acmedns
- Added auto dns credential form generator
2024-05-10 23:34:02 +08:00
55bc939a37 Updated code generation tool
- Module rename to acmedns
2024-05-10 20:02:39 +08:00
1d63b679dc Added tools folder
- Moved providers scrapper by @Teifun2 into tool folder
- Added code generator for avoid the use of env variables (wip)
2024-05-09 21:26:00 +08:00
3df96350a3 Updated version number 2024-05-06 19:06:59 +08:00
34fab7b3d0 Fixed cert upgrade panic
- Fixed no config json cert upgrade panic bug
2024-05-06 19:06:26 +08:00
46817d0664 Fixed minor code style in PR
- Fixed minor coding style in PR
- Updated geoip list
2024-05-06 18:50:12 +08:00
1db2ca61fa Create README.txt
Added source location for geodb
2024-05-06 16:26:02 +08:00
0b601406de Merge pull request #144 from Teifun2/dns-challenge-for-letsencrypt
DNS challenge for letsencrypt
2024-05-06 10:50:18 +08:00
b4c771cdee Merge pull request #146 from Frostplexx/main
Fix spelling mistake
2024-05-04 19:21:11 +08:00
a486d42351 fix spelling mistake 2024-05-04 12:49:23 +02:00
90c2199a1b Added Logging Statement 2024-05-04 10:50:30 +02:00
161c61fac7 Scraped all DNS Providers with their Configuration 2024-05-04 10:35:31 +02:00
5ffacb1d06 Support all DNS Provders with acme-lego 4.16 2024-05-04 09:13:37 +02:00
75ebd0ffbe DNS Provider Agnostic Solution 2024-05-03 17:23:23 +02:00
dc069f3c57 Fixed Error Messages 2024-05-03 01:08:50 +02:00
e1b512f78f Manual Renew knows if DNS Challenge is required 2024-05-03 00:57:47 +02:00
8854a38f49 DNS Credentials are saved in Database 2024-05-02 22:52:51 +02:00
7583a4628c Show DNS Challenge only for Lets Encrypt 2024-05-02 22:09:04 +02:00
73c0ea0896 POC DNS Challenge with Dynu 2024-05-02 21:42:18 +02:00
7dad7c7305 Send Values to Backend 2024-05-02 17:56:07 +02:00
faa95b4e21 Update README.md
Added citation to original uwu icon creator
2024-05-01 20:41:02 +08:00
cb0e13976d Added ui components needed for dns challenge 2024-05-01 14:40:53 +02:00
ccd8dcff56 Added template examples
- Added a uwu version of the notfound and access control template
- Added example index file for internal static web server
2024-05-01 20:39:26 +08:00
750656fd7f Merge pull request #140 from Morethanevil/main
Update CHANGELOG.md
2024-04-30 17:29:39 +08:00
d9f515fdba Update CHANGELOG.md
Update changelog
2024-04-30 11:06:24 +02:00
176249a7d9 Merge pull request #138 from tobychui/v3.0.3
Update V3.0.3

- Updated SMTP UI for non email login username
- Fixed ACME cert store reload after cert request
- Fixed default rule not applying to default site when default site is set to proxy target
- 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
2024-04-30 14:39:49 +08:00
e2a449a7bc Update blacklist.go
Fixed blacklist CIDR not working bug
2024-04-30 13:39:48 +08:00
a9695e969e Update Server.go
Fixed default site bypassing access filter bug
2024-04-30 13:25:26 +08:00
7ba997dfc2 Added support for changing mdns name
+ Added `mdnsname` startup flag
2024-04-30 11:49:34 +08:00
d00117e878 Merge pull request #135 from PassiveLemon/Graceful-Shutdown
Fix: Graceful container shutdown
2024-04-29 09:18:03 +08:00
43a84a3f1c Fix: Graceful container shutdown 2024-04-28 10:55:45 -04:00
e24f31bdef Fixed #126
- Added cert store hot reload to fix newly ssl cert not loaded
- Optimized SMTP structure and UI
2024-04-28 22:25:05 +08:00
fc9240fbac Fixed #131
- Added LAN detection in geoip resolver
- Updated UI for LAN/loopback request origin rendering
2024-04-28 11:27:00 +08:00
e0f5431215 Fixed #129
- Removed requirements for Domain (now domain field can be empty and no error will be shown)
2024-04-27 22:37:55 +08:00
de658a3c6c Minor bug fix
- Added potential fix for #130
- Added fix for disabled virtual directory check (future features)
- Updated version number to v3.0.3
2024-04-26 22:40:27 +08:00
73276b1918 Update README.md
Fixed description that easily cause misunderstanding
2024-04-26 22:36:12 +08:00
abdb7d4d75 Merge pull request #125 from Morethanevil/main
Update CHANGELOG.md
2024-04-24 23:55:23 +08:00
72299ace15 Update CHANGELOG.md 2024-04-24 17:51:36 +02:00
4d6c79f51b Update README.md
Added Alias support in Features
2024-04-24 16:17:47 +08:00
2c045f4f40 Merge pull request #124 from tobychui/v3.0.2
V3.0.2 Updates

Pre-checks on git.hkwtc is working and approved

- Added alias for HTTP proxy host names
- Added separator support for create new proxy rules (use "," to add alias when creating new proxy rule)
- Added HTTP proxy host based access rules
- Added EAD Configuration for ACME (by @yeungalan )
- Fixed bug for bypassGlobalTLS endpoint do not support basic-auth
- Removed dependencies on management panel css for online font files
2024-04-24 16:15:53 +08:00
b8cf046ca6 Fixed offline font bug
- Fixed offline font bug
- Set to pre-release embedded webui
2024-04-24 11:34:00 +08:00
026dd6b89d Update README.md
Added more info
2024-04-19 09:57:34 +08:00
5805fe6ed2 Update README.md
Added more info
2024-04-19 09:56:58 +08:00
3c78211800 Added alias support
+ Added alias support (use , when adding a new proxy target to automatically add alias hostnames)
+ Fixed some UI issues
2024-04-16 23:33:24 +08:00
8e648a8e1f v3.0.2 init commit
+ Fixed zeroSSL bug (said by @yeungalan ) #45
+ Fixed manual renew button bug
+ Seperated geodb module with access controller
+ Added per hosts access control (experimental) #69
+ Fixed basic auth not working on TLS bypass mode bug
+ Fixed empty domain crash bug #120
2024-04-14 19:37:01 +08:00
a000893dd1 Merge pull request #118 from Morethanevil/main
Update CHANGELOG.md
2024-04-04 18:44:09 +08:00
db88bfb752 Update CHANGELOG.md
Thanks again for your hard work
2024-04-04 11:54:38 +02:00
05297d854b Merge pull request #117 from tobychui/v3.0.1
V3.0.1 Updates
- Added regex support for redirect (slow, don't use it unless you really needs it)
- Added new dpcore implementations for faster proxy speed
- Added support for CF-Connecting-IP to X-Real-IP auto rewrite
- Added better 404 page
- Added option to bypass websocket origin check
- 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)
2024-04-04 15:02:10 +08:00
0d7bce4d30 Updated to go 1.22 2024-04-04 14:48:17 +08:00
8db95dddc6 Added regexp redirect support 2024-04-04 14:24:38 +08:00
05daeded37 Updated dpcore
- Added immediate flush for buffer (i.e. no chunked encoding)
- Added support for stream mode proxy sniffing logic
2024-04-01 21:36:11 +08:00
8ce6471be5 Updated homepage design 2024-03-30 16:26:03 +08:00
e242c9288f Fixed recursive port detection logic
+ Fixed recursive port detection logic
+ Added support for CF-Connecting-IP #114
2024-03-30 15:52:10 +08:00
c55a29e7cf Better 404 page and rules disable toggle 2024-03-16 20:45:05 +08:00
6af047430c Added UI for WebSocket Origin Check bypass 2024-03-12 14:03:31 +08:00
200c924acd 3.0.1 init commit
- Removed Go HTTP client UA
- Added optional bypass of websocket origin check #107
- Added basic forward proxy for debug
- Fixed UI error in network utils tab
2024-03-10 14:49:18 +08:00
9b2168466c Update access.html
Fixed issue #103
2024-02-24 14:04:33 +08:00
7ae48bf370 Update README.md
Added quick download links for Github noobs
2024-02-20 22:00:58 +08:00
ee3d76fb96 Fix: Failure to build upon release
If we need these back, we can just re-add them.
2024-02-18 14:20:34 -05:00
40d192524b Merge pull request #102 from Morethanevil/main
Update CHANGELOG.md
2024-02-18 21:08:50 +08:00
c659e05005 Update CHANGELOG.md
Updated Changelog
2024-02-18 13:36:35 +01:00
676a45c222 Merge pull request #101 from tobychui/v3.0.0
V3.0.0 Updates
2024-02-18 20:00:20 +08:00
1da0761b13 Update vdir.html
Fixed minor issue on vdir not loading on start
2024-02-18 19:59:10 +08:00
32939874f2 Fix: Remove override for OpenSSL 2024-02-17 12:24:31 -05:00
43a4bf389a Updated README 2024-02-17 21:07:28 +08:00
33c7c5fa00 Custom header support
+ Added custom header
+ Removed unused files
2024-02-17 20:28:19 +08:00
216b53f224 Updated GAN features
+ Added add controller as memeber feature
+ Deprecated aroz subservice support
2024-02-16 21:16:14 +08:00
059b0a2e1c Update 1.png 2024-02-16 16:00:17 +08:00
3ab952f168 Update main.css
Fixed text color on rule instruction bug
2024-02-16 15:51:56 +08:00
4f676d6770 Updated system screenshots 2024-02-16 15:51:23 +08:00
e980bc847b Updated a lot of stuffs
+ Added comments for whitelist
+ 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
+ Added Development Mode (Cache-Control: no-store)
+ Updated utm timeout to 10 seconds instead of 90
2024-02-16 15:44:09 +08:00
174efc9080 Added per host vdir implementation 2024-02-14 22:52:56 +08:00
3228789375 Restructured proxy routing logic
- Moved virtual directory into host routing object
- Generalized root and hosts routing struct
- Optimized UI
2024-02-13 21:46:43 +08:00
36e461795a Added zoraxy start paramters for reference 2024-01-30 15:22:59 +08:00
d6e7641364 Merge pull request #88 from PassiveLemon/EnglishCorrection
English correction
2024-01-01 21:38:42 +08:00
15cebd6e06 Update README.md 2023-12-04 21:03:45 -05:00
e9a074d4d1 Merge branch 'EnglishCorrection' of https://github.com/PassiveLemon/zoraxy-dev into EnglishCorrection 2023-12-04 21:01:28 -05:00
4b7fd39e57 Update web root 2023-12-04 20:59:50 -05:00
fa005f1327 Update README.md 2023-12-04 20:59:25 -05:00
c7a9f40baa Update README.md 2023-12-04 20:44:42 -05:00
d5b9726158 Update index.html 2023-12-04 20:44:41 -05:00
f659e66cf7 Update CHANGELOG.md 2023-12-04 20:44:39 -05:00
801bdbf298 Fix: main.yml
Some things somehow passed by me and needed fixing.
Updated the actions in the process.
2023-11-28 23:16:51 -05:00
09da93cfb3 Merge pull request #70 from PassiveLemon/Ghcr
Update container (2.6.8)
2023-11-28 18:46:42 +08:00
70ace02e80 Revert port update 2023-11-27 20:18:24 -05:00
1f758e953d Fix: OpenSSL CVE 2023-11-26 16:16:31 -05:00
ffad2cab81 Comment out GHCR 2023-11-25 12:20:52 -05:00
dbb10644de Update to new port 2023-11-25 12:14:26 -05:00
4848392185 Merge branch 'tobychui:main' into Ghcr 2023-11-25 12:09:43 -05:00
956f4ac30f Merge pull request #85 from Morethanevil/patch-7
Update CHANGELOG.md
2023-11-25 23:07:14 +08:00
c09ff28fd5 Update CHANGELOG.md 2023-11-25 16:00:54 +01:00
20cf290d37 Merge pull request #84 from tobychui/2.6.8
Update v2.6.8
2023-11-25 22:25:22 +08:00
4ca0fcc6d1 Update main.go
Swap to use embedded web fs
2023-11-25 16:38:15 +08:00
ce4ce72820 Added optional TLS bypass mechanism
+ Added opt-out for subdomains for global TLS settings #44
+ Optimized subdomain / vdir editing interface
2023-11-25 15:54:28 +08:00
e363d55899 Fixed #77 and added systemwide logger
+ Added system wide logger (wip)
+ Fixed issue #77 uptime monitor bug
+ Added backend options for bypass global TLS (or allow per rule TLS settings, wip)
+
2023-11-25 12:50:30 +08:00
172479e4fb Added default HTTP/2 Mode
+ Added automatic HTTP/2 switch to TLS mode (Related to #40)
2023-10-20 13:44:47 +08:00
156fa5dace Bug fix
+ Added potential fix for #67
+ Update version number
+ Changed default static web port to 5487 so it is even more unlikely to be used
+
2023-10-20 11:01:28 +08:00
4d40e0aa38 Publish to GHCR 2023-10-03 16:36:53 -04:00
045e66b631 Revert "Publish to GitHub Container Registry"
This reverts commit 23bdaa1517.
2023-10-03 16:35:10 -04:00
62e60d78de Merge branch 'tobychui:main' into main 2023-10-03 16:34:43 -04:00
23bdaa1517 Publish to GitHub Container Registry 2023-10-03 16:34:23 -04:00
50f222cced Merge pull request #68 from Morethanevil/patch-6
Update CHANGELOG.md
2023-09-26 21:30:23 +08:00
640e1adf96 Update CHANGELOG.md
Updated to latest version
2023-09-26 09:28:57 +02:00
206 changed files with 100934 additions and 102583 deletions

View File

@ -33,6 +33,7 @@ If applicable, add screenshots to help explain your problem.
- Device: [e.g. Bananapi R2 PRO]
- OS: [e.g. Armbian]
- Version [e.g. 23.02 Bullseye ]
- Docker Version (if you are running Zoraxy in docker): [e.g. 3.0.4]
**Additional context**
Add any other context about the problem here.

25
.github/ISSUE_TEMPLATE/help-needed.md vendored Normal file
View File

@ -0,0 +1,25 @@
---
name: Help Needed
about: Something went wrong but I don't know why
title: "[HELP]"
labels: help wanted
assignees: ''
---
**What happened?**
A clear and concise description of what the problem is. Ex. I tried to create a proxy rule but it doesn't work. When I connects to my domain, I see [...]
**Describe what have you tried**
A clear and concise description of what you expect to see and what you have tried to debug it.
**Describe the networking setup you are using**
Here are some example, commonly asked questions from our maintainers:
- Are you using the docker build of Zoraxy? [yes (with docker setup & networking config attach) /no]
- Your Zoraxy version? [e.g. 3.0.4]
- Are you using Cloudflare? [yes/no]
- Are your system hosted under a NAT router? [e.g. yes, with subnet is e.g. 192.168.0.0/24 and include port forwarding config if any]
- DNS record (if any)
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -9,18 +9,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to Dockerhub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Login to Docker & GHCR
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Setup building file structure
run: |
@ -36,11 +37,5 @@ jobs:
--provenance=false \
--platform linux/amd64,linux/arm64 \
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \
.
docker buildx build --push \
--build-arg VERSION=${{ github.event.release.tag_name }} \
--provenance=false \
--platform linux/amd64,linux/arm64 \
--tag zoraxydocker/zoraxy:latest \
.

4
.gitignore vendored
View File

@ -31,4 +31,6 @@ src/rules/*
src/README.md
docker/ContainerTester.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

View File

@ -1,3 +1,101 @@
# v3.0.4 May 18 2024
## This release tidied up the contribution by [Teifun2](https://github.com/Teifun2) and added a new way to generate DNS challenge based certificate (e.g. wildcards) from Let's Encrypt without changing any environment variables. This also fixes a few previous ACME module EAB settings bug related to concurrent save.
You can find the DNS challenge settings under TLS / SSL > ACME snippet > Generate New Certificate > (Check the "Use a DNS Challenge" checkbox)
+ Optimized DNS challenge implementation [thanks to Teifun2](https://github.com/Teifun2) / Issues [#49](https://github.com/tobychui/zoraxy/issues/49) [#79](https://github.com/tobychui/zoraxy/issues/79)
+ Removed dependencies on environment variable write and keep all data contained
+ Fixed panic on loading certificate generated by Zoraxy v2
+ Added automatic form generator for DNS challenge / providers
+ Added CA name default value
+ Added code generator for acmedns module (storing the DNS challenge provider contents extracted from lego)
+ Fixed ACME snippet "Obtain Certificate" concurrent issues in save EAB and DNS credentials
# 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
+ Added opt-out for subdomains for global TLS settings: See [release notes](https://github.com/tobychui/zoraxy/releases/tag/2.6.8)
+ Optimized subdomain / vdir editing interface
+ Added system-wide logger (Work in progress)
+ Fixed issue for uptime monitor bug [#77](https://github.com/tobychui/zoraxy/issues/77)
+ Changed default static web port to 5487 (prevent already in use)
+ Added automatic HTTP/2 to TLS mode
+ Bug fix for webserver autostart [67](https://github.com/tobychui/zoraxy/issues/67)
# v2.6.7 Sep 26 2023
+ Added Static Web Server function [#56](https://github.com/tobychui/zoraxy/issues/56)
+ Web Directory Manager (see static webserver tab)
+ Added static web server and black / whitelist template [#38](https://github.com/tobychui/zoraxy/issues/38)
+ Added default / preferred CA features for ACME [#47](https://github.com/tobychui/zoraxy/issues/47)
+ Optimized TLS/SSL page and added dedicated section for ACME related features
+ Bugfixes [#61](https://github.com/tobychui/zoraxy/issues/61) [#58](https://github.com/tobychui/zoraxy/issues/58)
# v2.6.6 Aug 30 2023
+ Added basic auth editor custom exception rules
@ -5,20 +103,20 @@
+ Optimized memory usage (from 1.2GB to 61MB for low speed geoip lookup) [#52](https://github.com/tobychui/zoraxy/issues/52)
+ Added unset subdomain custom redirection feature [#46](https://github.com/tobychui/zoraxy/issues/46)
+ Fixed potential security issue in satori/go.uuid [#55](https://github.com/tobychui/zoraxy/issues/55)
+ Added custom acme feature in back-end, thx [@daluntw](https://github.com/daluntw)
+ Added custom ACME feature in backend, thx [@daluntw](https://github.com/daluntw)
+ Added bypass TLS check for custom acme server, thx [@daluntw](https://github.com/daluntw)
+ Introduce new startparameter `-fastgeoip=true`, see [Releases](https://github.com/tobychui/zoraxy/releases/tag/2.6.6)
+ Introduce new start parameter `-fastgeoip=true`: see [release notes](https://github.com/tobychui/zoraxy/releases/tag/2.6.6)
# v2.6.5.1 Jul 26 2023
+ Patch on memory leaking for Windows netstat module (do not effect any of the previous non Windows builds)
+ Fixed potential memory leak in acme handler logic
+ Added "Do you want to get a TLS certificate for this subdomain?" dialog when a new subdomain proxy rule is created
+ Fixed potential memory leak in ACME handler logic
+ Added "Do you want to get a TLS certificate for this subdomain?" dialogue when a new subdomain proxy rule is created
# v2.6.5 Jul 19 2023
+ Added Import / Export-Feature
+ Moved configurationfiles to a separate folder [#26](https://github.com/tobychui/zoraxy/issues/26)
+ Moved configuration files to a separate folder [#26](https://github.com/tobychui/zoraxy/issues/26)
+ Added auto-renew with ACME [#6](https://github.com/tobychui/zoraxy/issues/6)
+ Fixed Whitelistbug [#18](https://github.com/tobychui/zoraxy/issues/18)
+ Added Whois
@ -28,7 +126,7 @@
+ Added force TLS v1.2 above toggle
+ Added trace route
+ Added ICMP ping
+ Added special routing rules module for up-coming acme integration
+ Added special routing rules module for up-coming ACME integration
+ Fixed IPv6 check bug in black/whitelist
+ Optimized UI for TCP Proxy
@ -38,7 +136,7 @@
+ Split blacklist and whitelist from geodb script file
+ Optimized compile binary size
+ Added access control to TCP proxy
+ Added "invalid config detect" in up time monitor for isse [#7](https://github.com/tobychui/zoraxy/issues/7)
+ Added "invalid config detect" in up time monitor for issue [#7](https://github.com/tobychui/zoraxy/issues/7)
+ Fixed minor bugs in advance stats panel
+ Reduced file size of embedded materials
@ -65,6 +163,6 @@
+ Basic auth
+ Support TLS verification skip (for self signed certs)
+ Added trend analysis
+ Added referer and file type analysis
+ Added referrer and file type analysis
+ Added cert expire day display
+ Moved subdomain proxy logic to dpcore

140
README.md
View File

@ -2,24 +2,29 @@
# Zoraxy
General purpose request (reverse) proxy and forwarding tool for low power devices. Now written in Go!
A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
### Features
- Simple to use interface with detail in-system instructions
- Reverse Proxy
- Subdomain Reverse Proxy
- Virtual Directory Reverse Proxy
- Reverse Proxy (HTTP/2)
- Virtual Directory
- WebSocket Proxy (automatic, no set-up needed)
- Basic Auth
- Alias Hostnames
- Custom Headers
- Redirection Rules
- 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)
- DNS Challenge for Let's Encrypt and [these DNS providers](https://go-acme.github.io/lego/dns/)
- Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)
- Global Area Network Controller Web UI (ZeroTier not included)
- TCP Tunneling / Proxy
- Integrated Up-time Monitor
- Web-SSH Terminal
- Utilities
- CIDR IP converters
- mDNS Scanner
- IP Scanner
@ -28,10 +33,19 @@ General purpose request (reverse) proxy and forwarding tool for low power device
- External permission management system for easy system integration
- SMTP config for password reset
## Build from Source
Require Go 1.20 or above
## 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
Requires Go 1.22 or higher
```bash
git clone https://github.com/tobychui/zoraxy
cd ./zoraxy/src/
go mod tidy
@ -42,11 +56,11 @@ sudo ./zoraxy -port=:8000
## Usage
Zoraxy provide basic authentication system for standalone mode. To use it in standalone mode, follow the instruction below for your desired deployment platform.
Zoraxy provides basic authentication system for standalone mode. To use it in standalone mode, follow the instructionss below for your desired deployment platform.
### Standalone Mode
Standalone mode is the default mode for Zoraxy. This allow single account to manage your reverse proxy server just like a home router. This mode is suitable for new owners for homelab or makers start growing their web services into multiple servers.
Standalone mode is the default mode for Zoraxy. This allows a single account to manage your reverse proxy server just like a basic home router. This mode is suitable for new owners to homelabs or makers starting growing their web services into multiple servers. A full "Getting Started" guide can be found [here](https://github.com/tobychui/zoraxy/wiki/Getting-Started).
#### Linux
@ -60,18 +74,51 @@ Download the binary executable and double click the binary file to start it.
#### Raspberry Pi
The installation method is same as Linux. If you are using Raspberry Pi 4 or newer models, pick the arm64 release. For older version of Pis, use the arm (armv6) version instead.
The installation method is same as Linux. If you are using a Raspberry Pi 4 or newer models, pick the arm64 release. For older version of Pis, use the arm (armv6) version instead.
#### Other ARM SBCs or Android phone with Termux
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
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
```
Usage of zoraxy:
-autorenew int
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
-fastgeoip
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
-log
Log terminal output to file (default true)
-mdns
Enable mDNS scanner and transponder (default true)
-mdnsname string
mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)
-noauth
Disable authentication for management interface
-port string
Management web interface listening port (default ":8000")
-sshlb
Allow loopback web ssh connection (DANGER)
-version
Show version of this server
-webfm
Enable web file manager for static web server root folder (default true)
-webroot string
Static web server root folder. Only allow chnage in start paramters (default "./www")
-ztauth string
ZeroTier authtoken for the local node
-ztport int
ZeroTier controller API port (default 9993)
```
### External Permission Management Mode
If you already have a up-stream reverse proxy server in place with permission management, you can use Zoraxy in noauth mode. To enable noauth mode, start Zoraxy with the following flag
If you already have an upstream reverse proxy server in place with permission management, you can use Zoraxy in noauth mode. To enable noauth mode, start Zoraxy with the following flag:
```bash
./zoraxy -noauth=true
@ -79,45 +126,12 @@ If you already have a up-stream 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.*
#### Use with ArozOS
[ArozOS ](https://arozos.com)subservice is a build 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. Sometime 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 the unzip done, press Ctrl + C to kill it
# Rename it to valid arozos subservice binary format
mv ./zoraxy zoraxy_linux_amd64
# If you are using SBCs with 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
![](img/screenshots/0_1.png)
![](img/screenshots/1.png)
![](img/screenshots/2.png)
More screenshots on the wikipage [Screenshots](https://github.com/tobychui/zoraxy/wiki/Screenshots)!
## FAQ
@ -128,42 +142,44 @@ There is a wikipage with [Frequently-Asked-Questions](https://github.com/tobychu
This project also compatible with [ZeroTier](https://www.zerotier.com/). However, due to licensing issues, ZeroTier is not included in the binary.
Assuming you already have a valid license, to use Zoraxy with ZeroTier, install ZeroTier on your host and then run Zoraxy in sudo mode (or Run As Administrator if you are on Windows). The program will automatically grab the authtoken at correct location in your host.
To use Zoraxy with ZeroTier, assuming you already have a valid license, install ZeroTier on your host and then run Zoraxy in sudo mode (or Run As Administrator if you are on Windows). The program will automatically grab the authtoken in the correct location on your host.
If you prefer not to run Zoraxy in sudo mode or you have some weird installation profile, you can also pass in the ZeroTier auth token using the following flags
If you prefer not to run Zoraxy in sudo mode or you have some weird installation profile, you can also pass in the ZeroTier auth token using the following flags::
```
```bash
./zoraxy -ztauth="your_zerotier_authtoken" -ztport=9993
```
The ZeroTier auth token can usually be found at ```/var/lib/zerotier-one/authtoken.secret``` or ```C:\ProgramData\ZeroTier\One\authtoken.secret```.
This allows you to have infinite number of network members in your Global Area Network controller. For more technical details, see [here](https://docs.zerotier.com/self-hosting/network-controllers/).
This allows you to have an infinite number of network members in your Global Area Network controller. For more technical details, see [here](https://docs.zerotier.com/self-hosting/network-controllers/).
## Web.SSH
## Web SSH
Web SSH currently only support Linux based OS. The following platforms are supported
Web SSH currently only supports Linux based OSes. The following platforms are supported:
- linux/amd64
- linux/arm64
- linux/armv6 (experimental)
- linux/386 (experimental)
### Loopback Connection
### Loopback Connection
Loopback web ssh connection, by default, is disabled. This means that if you are trying to connect to address like 127.0.0.1 or localhost, the system will reject your connection due to security issues. To enable loopback for testing or development purpose, use the following flags to override the loopback checking.
Loopback web SSH connection, by default, is disabled. This means that if you are trying to connect to an address like 127.0.0.1 or localhost, the system will reject your connection for security reasons. To enable loopback for testing or development purpose, use the following flags to override the loopback checking:
```
```bash
./zoraxy -sshlb=true
```
## Sponsor This Project
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)
- PassiveLemon (Docker compatibility maintainer)
## License
This project is open source under AGPL. I open source this project so everyone can check for security issues and benefit all users. **If your plans to use this project in 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.**

View File

@ -8,10 +8,7 @@ RUN mkdir -p /opt/zoraxy/source/ &&\
mkdir -p /opt/zoraxy/config/ &&\
mkdir -p /usr/local/bin/
COPY entrypoint.sh /opt/zoraxy/
RUN chmod -R 755 /opt/zoraxy/ &&\
chmod +x /opt/zoraxy/entrypoint.sh
RUN chmod -R 770 /opt/zoraxy/
VOLUME [ "/opt/zoraxy/config/" ]
@ -24,15 +21,15 @@ RUN go mod tidy &&\
go build -o /usr/local/bin/zoraxy &&\
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/
ENV VERSION=$VERSION
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

View File

@ -39,7 +39,7 @@ services:
| `-p (ports)` | Yes | Depending on how your network is setup, you may need to portforward 80, 443, and the management port. |
| `-v (path to storage directory):/opt/zoraxy/config/` | Recommend | Sets the folder that holds your files. This should be the place you just chose. By default, it will create a Docker volume for the files for persistency but they will not be accessible. |
| `-e ARGS='(your arguments)'` | No | Sets the arguments to run Zoraxy with. Enter them as you would normally. By default, it is ran with `-noauth=false` but <b>you cannot change the management port.</b> This is required for the healthcheck to work. |
| `zoraxydocker/zoraxy:latest` | Yes | The repository on Docker hub. By default, it is the latest version that I have published. |
| `zoraxydocker/zoraxy:latest` | Yes | The repository on Docker hub. By default, it is the latest version that is published. |
## Examples: </br>
### Docker Run </br>

View File

@ -1,4 +0,0 @@
#!/usr/bin/env bash
echo "Zoraxy version $VERSION"
zoraxy -port=:8000 ${ARGS}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 MiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

BIN
docs/img/screenshots/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

BIN
docs/img/screenshots/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/img/screenshots/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/img/screenshots/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

BIN
docs/img/screenshots/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/img/screenshots/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/img/screenshots/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

BIN
docs/img/screenshots/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

BIN
docs/img/screenshots/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

View File

@ -8,7 +8,7 @@
<meta name="author" content="tobychui">
<!-- 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">
<!-- Facebook Meta Tags -->
@ -74,21 +74,16 @@
</div>
<div class="right-content">
<!-- 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 id="home" class="herotext">
<div class="ui basic segment">
<div class="bannerHeaderWrapper">
<h1 class="bannerHeader">Zoraxy</h1>
<div class="ui divider"></div><br>
<p class="bannerSubheader">All in one homelab network routing solution</p>
</div>
<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>
<table class="ui very basic collapsing unstackable celled table">
<thead>
@ -126,6 +121,22 @@
</tr>
</table>
</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>
<!-- Features -->
@ -148,7 +159,7 @@
Reverse Proxy
</div>
</h3>
<p>Simple to use, noobs friendly reverse proxy server that can be easily set-up using a web form and a few toggle switches.</p>
<p>Simple to use noob-friendly reverse proxy server that can be easily set up using a web form and a few toggle switches.</p>
</div>
<div class="four wide column featureItem">
@ -158,7 +169,7 @@
Redirection
</div>
</h3>
<p>Direct and intuitive redirection rules with basic rewrite options. Suitable for most of the simple use cases.</p>
<p>Direct and intuitive redirection rules with basic rewrite options. Suitable for most simple use cases.</p>
</div>
<div class="four wide column featureItem">
@ -168,7 +179,7 @@
Geo-IP & Blacklist
</div>
</h3>
<p>Blacklist with GeoIP support. Allow easy setup for regional services.</p>
<p>Blacklist with GeoIP support. Allows easy setup for regional services.</p>
</div>
<div class="four wide column featureItem">
@ -189,7 +200,7 @@
Web SSH
</div>
</h3>
<p>Integrated with Gotty Web SSH terminal, allow one-stop management of your nodes inside private LAN via gateway nodes.</p>
<p>Integration with Gotty Web SSH terminal allows one-stop management of your nodes inside private LAN via gateway nodes.</p>
</div>
<div class="four wide column featureItem">
@ -199,7 +210,7 @@
Real Time Statistics
</div>
</h3>
<p>Traffic data collection and real time analytic tools, provide you the best insights of visitors data without cookies.</p>
<p>Traffic data collection and real-time analytic tools provide you the best insight of visitors data without cookies.</p>
</div>
<div class="four wide column featureItem">
@ -209,7 +220,7 @@
Scanner & Utilities
</div>
</h3>
<p>Build in IP scanner and mDNS discovering service, enable automatic service discovery within LAN.</p>
<p>Build in IP scanner and mDNS discovery service to enable automatic service discovery within LAN.</p>
</div>
<div class="four wide column featureItem">
@ -219,7 +230,7 @@
Open Source
</div>
</h3>
<p>Project is open source under AGPL on Github. Feel free to contribute on missing functions you need! </p>
<p>Project is open-source under AGPL on Github. Feel free to contribute on missing functions you need! </p>
</div>
</div>
</div>
@ -240,34 +251,34 @@
<div class="ui three column stackable grid">
<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 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 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 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 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 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 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 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 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 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>

View File

@ -1,5 +1,5 @@
body{
background: #f6f6f6 !important;
background: #ffffff !important;
margin: 0;
padding: 0;
overflow-y: hidden;
@ -18,7 +18,7 @@ body{
.left-menu {
width: 80px;
min-width: 80px;
background-color: #ffffff;
background-color: #fcfcfc;
min-height: 100vh;
padding-top: 1.5em;
}
@ -48,17 +48,19 @@ body{
text-align: center;
border-bottom: 1px solid #f6f6f6;
width: 100%;
border-right: 0.4em solid var(--themeTextColor);
transition: border-left ease-in-out 0.1s, background-color ease-in-out 0.1s;
}
.menu-item.active{
border-right: 0.4em solid var(--themeSkyblueColorDecondary);
background-color: #f0f8ff;
background: linear-gradient(60deg, rgba(84, 58, 183, 0.3) 0%, rgba(0, 172, 193, 0.3) 100%);
}
.menu-item .item-icon{
fill: #fcfcfc;
}
.menu-item:hover{
border-right: 0.4em solid var(--themeSkyblueColorDecondary);
background: rgba(35,35,35,0.1);
}
.menu-item img{
@ -69,18 +71,6 @@ body{
/* 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{
padding-top: 15em;
@ -91,11 +81,13 @@ body{
.bannerHeader{
font-size: 8em;
font-weight: 600;
color: white;
}
.bannerSubheader{
font-weight: 400;
font-size: 1.2em;
color: #ebebeb;
margin-top: -20px;
}
@ -104,6 +96,21 @@ body{
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{
padding-top: 4em;
@ -173,56 +180,58 @@ body{
}
}
/* Decorative Animation */
.dot-container {
display: flex;
justify-content: center;
align-items: center;
height: 40px;
position: absolute;
top: 2em;
left: 2em;
}
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #d9d9d9;
margin-right: 6px;
animation-name: dot-animation;
animation-duration: 4s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
/*
Waves CSS
*/
#wavesWrapper{
position: absolute;
bottom: 0;
width: 100%;
left: 0;
}
.dot:nth-child(1) {
animation-delay: 0s;
.waves {
position:relative;
width: 100%;
height:15vh;
margin-bottom:-7px; /*Fix for safari gap*/
min-height:100px;
max-height:150px;
}
.dot:nth-child(2) {
animation-delay: 1s;
}
.dot:nth-child(3) {
animation-delay: 2s;
.parallax > use {
animation: move-forever 25s cubic-bezier(.55,.5,.45,.5) infinite;
}
.dot:nth-child(4) {
animation-delay: 3s;
.parallax > use:nth-child(1) {
animation-delay: -8s;
animation-duration: 28s;
}
@keyframes dot-animation {
0% {
background-color: #d9d9d9;
transform: scale(1);
}
50% {
background-color: #a9d1f3;
transform: scale(1.5);
}
100% {
background-color: #d9d9d9;
transform: scale(1);
}
.parallax > use:nth-child(2) {
animation-delay: -12s;
animation-duration: 40s;
}
.parallax > use:nth-child(3) {
animation-delay: -16s;
animation-duration: 52s;
}
.parallax > use:nth-child(4) {
animation-delay: -20s;
animation-duration: 80s;
}
@keyframes move-forever {
0% {
transform: translate3d(-90px,0,0);
}
100% {
transform: translate3d(85px,0,0);
}
}
/*Shrinking for mobile*/
@media (max-width: 768px) {
.waves {
height:40px;
min-height:40px;
}
}

18
example/README.md Normal file
View 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
View 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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

View File

@ -3,7 +3,12 @@ package main
import (
"encoding/json"
"net/http"
"strings"
"github.com/google/uuid"
"github.com/microcosm-cc/bluemonday"
"imuslab.com/zoraxy/mod/access"
"imuslab.com/zoraxy/mod/utils"
)
@ -15,6 +20,157 @@ import (
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
*/
@ -26,11 +182,24 @@ func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
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{}
if bltype == "country" {
resulst = geodbStore.GetAllBlacklistedCountryCode()
resulst = rule.GetAllBlacklistedCountryCode()
} else if bltype == "ip" {
resulst = geodbStore.GetAllBlacklistedIp()
resulst = rule.GetAllBlacklistedIp()
}
js, _ := json.Marshal(resulst)
@ -45,7 +214,23 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
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)
}
@ -57,7 +242,19 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
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)
}
@ -69,7 +266,24 @@ func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
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) {
@ -79,23 +293,46 @@ func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
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)
}
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 {
//Return the current enabled state
currentEnabled := geodbStore.BlacklistEnabled
ruleID = "default"
}
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)
utils.SendJSONResponse(w, string(js))
} else {
if enable == "true" {
geodbStore.ToggleBlacklist(true)
rule.ToggleBlacklist(true)
} else if enable == "false" {
geodbStore.ToggleBlacklist(false)
rule.ToggleBlacklist(false)
} else {
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
return
@ -115,11 +352,22 @@ func handleListWhitelisted(w http.ResponseWriter, r *http.Request) {
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" {
resulst = geodbStore.GetAllWhitelistedCountryCode()
resulst = rule.GetAllWhitelistedCountryCode()
} else if bltype == "ip" {
resulst = geodbStore.GetAllWhitelistedIp()
resulst = rule.GetAllWhitelistedIp()
}
js, _ := json.Marshal(resulst)
@ -134,7 +382,22 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
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)
}
@ -146,7 +409,18 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
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)
}
@ -158,7 +432,23 @@ func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
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) {
@ -168,23 +458,45 @@ func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
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)
}
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 {
ruleID = "default"
}
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
if enable == "" {
//Return the current enabled state
currentEnabled := geodbStore.WhitelistEnabled
currentEnabled := rule.WhitelistEnabled
js, _ := json.Marshal(currentEnabled)
utils.SendJSONResponse(w, string(js))
} else {
if enable == "true" {
geodbStore.ToggleWhitelist(true)
rule.ToggleWhitelist(true)
} else if enable == "false" {
geodbStore.ToggleWhitelist(false)
rule.ToggleWhitelist(false)
} else {
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
return

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"math/rand"
"net/http"
"regexp"
@ -29,7 +28,7 @@ func getRandomPort(minPort int) int {
// init the new ACME instance
func initACME() *acme.ACMEHandler {
log.Println("Starting ACME handler")
SystemWideLogger.Println("Starting ACME handler")
rand.Seed(time.Now().UnixNano())
// Generate a random port above 30000
port := getRandomPort(30000)
@ -39,12 +38,12 @@ func initACME() *acme.ACMEHandler {
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
func acmeRegisterSpecialRoutingRule() {
log.Println("Assigned temporary port:" + acmeHandler.Getport())
SystemWideLogger.Println("Assigned temporary port:" + acmeHandler.Getport())
err := dynamicProxyRouter.AddRoutingRules(&dynamicproxy.RoutingRule{
ID: "acme-autorenew",
@ -79,38 +78,49 @@ func acmeRegisterSpecialRoutingRule() {
})
if err != nil {
log.Println("[Err] " + err.Error())
SystemWideLogger.PrintAndLog("ACME", "Unable register temp port for DNS resolver", err)
}
}
// This function check if the renew setup is satisfied. If not, toggle them automatically
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
isForceHttpsRedirectEnabledOriginally := false
if dynamicProxyRouter.Option.Port == 443 {
//Enable port 80 to 443 redirect
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
log.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests")
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
dnsPara, _ := utils.PostBool(r, "dns")
if !dnsPara {
if dynamicProxyRouter.Option.Port == 443 {
//Enable port 80 to 443 redirect
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
SystemWideLogger.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests")
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
} else {
//Set this to true, so after renew, do not turn it off
isForceHttpsRedirectEnabledOriginally = true
}
} else if dynamicProxyRouter.Option.Port == 80 {
//Go ahead
} else {
//Set this to true, so after renew, do not turn it off
isForceHttpsRedirectEnabledOriginally = true
//This port do not support ACME
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
return
}
} else if dynamicProxyRouter.Option.Port == 80 {
//Go ahead
} else {
//This port do not support ACME
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
}
//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
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 {
//Default is off. Turn the redirection off
log.Println("Restoring HTTP to HTTPS redirect settings")
SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil)
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
}
}
@ -130,7 +140,7 @@ func HandleACMEPreferredCA(w http.ResponseWriter, r *http.Request) {
acme.IsSupportedCA(ca)
//Set the new config
sysdb.Write("acmepref", "prefca", ca)
log.Println("Updating prefered ACME CA to " + ca)
SystemWideLogger.Println("Updating prefered ACME CA to " + ca)
utils.SendOK(w)
}

View File

@ -5,6 +5,7 @@ import (
"net/http"
"net/http/pprof"
"imuslab.com/zoraxy/mod/acme/acmedns"
"imuslab.com/zoraxy/mod/acme/acmewizard"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/netstat"
@ -47,17 +48,28 @@ func initAPIs() {
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
authRouter.HandleFunc("/api/proxy/toggle", ReverseProxyToggleRuleSet)
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
//Reverse proxy root related APIs
authRouter.HandleFunc("/api/proxy/root/listOptions", HandleRootRouteOptionList)
authRouter.HandleFunc("/api/proxy/root/updateOptions", HandleRootRouteOptionsUpdate)
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
//Reverse proxy virtual directory APIs
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
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
@ -76,7 +88,14 @@ func initAPIs() {
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
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
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
@ -84,7 +103,6 @@ func initAPIs() {
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
//Whitelist APIs
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
@ -114,6 +132,8 @@ func initAPIs() {
authRouter.HandleFunc("/api/gan/network/name", ganManager.HandleNetworkNaming)
//authRouter.HandleFunc("/api/gan/network/detail", ganManager.HandleNetworkDetails)
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/ip", ganManager.HandleMemberIP)
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
@ -153,6 +173,8 @@ func initAPIs() {
authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet)
authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet)
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
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
@ -165,16 +187,19 @@ func initAPIs() {
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
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/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
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
//Static Web Server
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
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)
if *allowWebFileManager {
//Web Directory Manager file operation functions

View File

@ -6,13 +6,13 @@ import (
"encoding/pem"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"imuslab.com/zoraxy/mod/acme"
"imuslab.com/zoraxy/mod/utils"
)
@ -47,12 +47,13 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
LastModifiedDate string
ExpireDate string
RemainingDays int
UseDNS bool
}
results := []*CertInfo{}
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")
fileInfo, err := os.Stat(certFilepath)
if err != nil {
@ -82,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{
Domain: filename,
LastModifiedDate: modifiedTime,
ExpireDate: certExpireTime,
RemainingDays: expiredIn,
UseDNS: useDNSValidation,
}
results = append(results, &thisCertInfo)
@ -128,7 +136,7 @@ func handleListDomains(w http.ResponseWriter, r *http.Request) {
certBtyes, err := os.ReadFile(certFilepath)
if err != nil {
// Unable to load this file
log.Println("Unable to load certificate: " + certFilepath)
SystemWideLogger.PrintAndLog("TLS", "Unable to load certificate: "+certFilepath, err)
continue
} else {
// Cert loaded. Check its expiry time
@ -182,11 +190,11 @@ func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
} else {
if newState == "true" {
sysdb.Write("settings", "usetls", true)
log.Println("Enabling TLS mode on reverse proxy")
SystemWideLogger.Println("Enabling TLS mode on reverse proxy")
dynamicProxyRouter.UpdateTLSSetting(true)
} else if newState == "false" {
sysdb.Write("settings", "usetls", false)
log.Println("Disabling TLS mode on reverse proxy")
SystemWideLogger.Println("Disabling TLS mode on reverse proxy")
dynamicProxyRouter.UpdateTLSSetting(false)
} else {
utils.SendErrorResponse(w, "invalid state given. Only support true or false")
@ -213,11 +221,11 @@ func handleSetTlsRequireLatest(w http.ResponseWriter, r *http.Request) {
} else {
if newState == "true" {
sysdb.Write("settings", "forceLatestTLS", true)
log.Println("Updating minimum TLS version to v1.2 or above")
SystemWideLogger.Println("Updating minimum TLS version to v1.2 or above")
dynamicProxyRouter.UpdateTLSVersion(true)
} else if newState == "false" {
sysdb.Write("settings", "forceLatestTLS", false)
log.Println("Updating minimum TLS version to v1.0 or above")
SystemWideLogger.Println("Updating minimum TLS version to v1.0 or above")
dynamicProxyRouter.UpdateTLSVersion(false)
} else {
utils.SendErrorResponse(w, "invalid state given")
@ -249,7 +257,7 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
}
if keytype == "pub" {
overWriteFilename = domain + ".crt"
overWriteFilename = domain + ".pem"
} else if keytype == "pri" {
overWriteFilename = domain + ".key"
} else {
@ -288,6 +296,9 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
return
}
//Update cert list
tlsCertManager.UpdateLoadedCertList()
// send response
fmt.Fprintln(w, "File upload successful!")
}

View File

@ -3,9 +3,9 @@ package main
import (
"archive/zip"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
@ -29,101 +29,125 @@ type Record struct {
Rootname string
ProxyTarget string
UseTLS bool
BypassGlobalTLS bool
SkipTlsValidation bool
RequireBasicAuth bool
BasicAuthCredentials []*dynamicproxy.BasicAuthCredentials
BasicAuthExceptionRules []*dynamicproxy.BasicAuthExceptionRule
}
// Save a reverse proxy config record to file
func SaveReverseProxyConfigToFile(proxyConfigRecord *Record) error {
//TODO: Make this accept new def types
os.MkdirAll("./conf/proxy/", 0775)
filename := getFilenameFromRootName(proxyConfigRecord.Rootname)
//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)
/*
Load Reverse Proxy Config from file and append it to current runtime proxy router
*/
func LoadReverseProxyConfig(configFilepath string) error {
//Load the config file from disk
endpointConfig, err := os.ReadFile(configFilepath)
if err != nil {
return err
}
return SaveReverseProxyConfigToFile(recordToSave)
}
func RemoveReverseProxyConfigFile(rootname string) error {
filename := getFilenameFromRootName(rootname)
removePendingFile := strings.ReplaceAll(filepath.Join("./conf/proxy/", filename), "\\", "/")
log.Println("Config Removed: ", removePendingFile)
if utils.FileExists(removePendingFile) {
err := os.Remove(removePendingFile)
if err != nil {
log.Println(err.Error())
return err
}
//Parse it into dynamic proxy endpoint
thisConfigEndpoint := dynamicproxy.ProxyEndpoint{}
err = json.Unmarshal(endpointConfig, &thisConfigEndpoint)
if err != nil {
return err
}
//File already gone
//Matching domain not set. Assume root
if thisConfigEndpoint.RootOrMatchingDomain == "" {
thisConfigEndpoint.RootOrMatchingDomain = "/"
}
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 ptype, rootname and proxyTarget, error if any
func LoadReverseProxyConfig(filename string) (*Record, error) {
thisRecord := Record{
ProxyType: "",
Rootname: "",
ProxyTarget: "",
UseTLS: false,
SkipTlsValidation: false,
func filterProxyConfigFilename(filename string) string {
//Filter out wildcard characters
filename = strings.ReplaceAll(filename, "*", "(ST)")
filename = strings.ReplaceAll(filename, "?", "(QM)")
filename = strings.ReplaceAll(filename, "[", "(OB)")
filename = strings.ReplaceAll(filename, "]", "(CB)")
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,
SkipCertValidations: false,
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
RequireBasicAuth: false,
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
}
configContent, err := os.ReadFile(filename)
DefaultSiteOption: dynamicproxy.DefaultSite_InternalStaticWebServer,
DefaultSiteValue: "",
})
if err != nil {
return &thisRecord, err
return nil, err
}
//Unmarshal the content into config
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,
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
return rootProxyEndpoint, nil
}
/*
@ -131,7 +155,7 @@ func getFilenameFromRootName(rootname string) string {
*/
func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
includeSysDBRaw, err := utils.GetPara(r, "includeDB")
includeSysDBRaw, _ := utils.GetPara(r, "includeDB")
includeSysDB := false
if includeSysDBRaw == "true" {
//Include the system database in backup snapshot
@ -153,7 +177,7 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
defer zipWriter.Close()
// 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 {
return err
}
@ -191,14 +215,14 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
//Also zip in the sysdb
zipFile, err := zipWriter.Create("sys.db")
if err != nil {
log.Println("[Backup] Unable to zip sysdb: " + err.Error())
SystemWideLogger.PrintAndLog("Backup", "Unable to zip sysdb", err)
return
}
// Open the file on disk
file, err := os.Open("sys.db")
if err != nil {
log.Println("[Backup] Unable to open sysdb: " + err.Error())
SystemWideLogger.PrintAndLog("Backup", "Unable to open sysdb", err)
return
}
defer file.Close()
@ -206,7 +230,7 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
// Copy the file contents to the zip file
_, err = io.Copy(zipFile, file)
if err != nil {
log.Println(err)
SystemWideLogger.Println(err)
return
}
@ -311,12 +335,12 @@ func ImportConfigFromZip(w http.ResponseWriter, r *http.Request) {
// Send a success response
w.WriteHeader(http.StatusOK)
log.Println("Configuration restored")
SystemWideLogger.Println("Configuration restored")
fmt.Fprintln(w, "Configuration restored")
if restoreDatabase {
go func() {
log.Println("Database altered. Restarting in 3 seconds...")
SystemWideLogger.Println("Database altered. Restarting in 3 seconds...")
time.Sleep(3 * time.Second)
os.Exit(0)
}()

View File

@ -25,12 +25,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
return
}
domain, err := utils.PostPara(r, "domain")
if err != nil {
utils.SendErrorResponse(w, "domain cannot be empty")
return
}
portString, err := utils.PostPara(r, "port")
if err != nil {
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
thisEmailSender := email.Sender{
Hostname: strings.TrimSpace(hostname),
Domain: strings.TrimSpace(domain),
Port: port,
Username: strings.TrimSpace(username),
Password: strings.TrimSpace(password),
@ -206,7 +199,7 @@ var (
)
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
if EmailSender.Username == "" || EmailSender.Domain == "" {
if EmailSender.Username == "" {
//Reset account not setup
utils.SendErrorResponse(w, "Reset account not setup.")
return

View File

@ -1,18 +1,174 @@
module imuslab.com/zoraxy
go 1.16
go 1.21
toolchain go1.22.2
require (
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/google/uuid v1.3.1
github.com/gorilla/sessions v1.2.1
github.com/gorilla/websocket v1.5.0
github.com/google/uuid v1.6.0
github.com/gorilla/sessions v1.2.2
github.com/gorilla/websocket v1.5.1
github.com/grandcat/zeroconf v1.0.0
github.com/likexian/whois v1.15.1
github.com/microcosm-cc/bluemonday v1.0.25
golang.org/x/net v0.14.0
golang.org/x/sys v0.11.0
golang.org/x/tools v0.12.0 // indirect
github.com/microcosm-cc/bluemonday v1.0.26
golang.org/x/net v0.23.0
golang.org/x/sys v0.18.0
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

File diff suppressed because it is too large Load Diff

View File

@ -12,14 +12,16 @@ import (
"time"
"github.com/google/uuid"
"imuslab.com/zoraxy/mod/access"
"imuslab.com/zoraxy/mod/acme"
"imuslab.com/zoraxy/mod/aroz"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/email"
"imuslab.com/zoraxy/mod/forwardproxy"
"imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/mdns"
"imuslab.com/zoraxy/mod/netstat"
"imuslab.com/zoraxy/mod/pathrule"
@ -34,20 +36,23 @@ import (
)
// 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 showver = flag.Bool("version", false, "Show version of this server")
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
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 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 enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
var (
name = "Zoraxy"
version = "2.6.7"
version = "3.0.5"
nodeUUID = "generic"
development = false //Set this to false to use embedded web fs
bootTime = time.Now().Unix()
@ -61,13 +66,13 @@ var (
/*
Handler Modules
*/
handler *aroz.ArozHandler //Handle arozos managed permission system
sysdb *database.Database //System database
authAgent *auth.AuthAgent //Authentication agent
tlsCertManager *tlscert.Manager //TLS / SSL management
redirectTable *redirection.RuleTable //Handle special redirection rule sets
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
statisticCollector *statistic.Collector //Collecting statistic from visitors
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
@ -78,10 +83,12 @@ var (
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
//Helper modules
EmailSender *email.Sender //Email sender that handle email sending
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
EmailSender *email.Sender //Email sender that handle email sending
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
SystemWideLogger *logger.Logger //Logger for Zoraxy
)
// Kill signal handler. Do something before the system the core terminate.
@ -116,26 +123,17 @@ func ShutdownSeq() {
fmt.Println("- Cleaning up tmp files")
os.RemoveAll("./tmp")
fmt.Println("- Closing system wide logger")
SystemWideLogger.Close()
//Close database, final
fmt.Println("- Stopping system database")
sysdb.Close()
}
func main() {
//Start the aoModule pipeline (which will parse the flags as well). Pass in the module launch information
handler = aroz.HandleFlagParse(aroz.ServiceInfo{
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},
})
//Parse startup flags
flag.Parse()
if *showver {
fmt.Println(name + " - Version " + version)
os.Exit(0)
@ -151,7 +149,7 @@ func main() {
}
uuidBytes, err := os.ReadFile(uuidRecord)
if err != nil {
log.Println("Unable to read system uuid from file system")
SystemWideLogger.PrintAndLog("ZeroTier", "Unable to read system uuid from file system", nil)
panic(err)
}
nodeUUID = string(uuidBytes)
@ -160,7 +158,7 @@ func main() {
startupSequence()
//Initiate management interface APIs
requireAuth = !(*noauth || handler.IsUsingExternalPermissionManager())
requireAuth = !(*noauth)
initAPIs()
//Start the reverse proxy server in go routine
@ -173,8 +171,8 @@ func main() {
//Start the finalize sequences
finalSequence()
log.Println("Zoraxy started. Visit control panel at http://localhost" + handler.Port)
err = http.ListenAndServe(handler.Port, nil)
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
err = http.ListenAndServe(*webUIPort, nil)
if err != nil {
log.Fatal(err)

221
src/mod/access/access.go Normal file
View 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)
}

View 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
}

View 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
View 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
View 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
}

View File

@ -9,6 +9,7 @@ import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"log"
"net"
@ -24,6 +25,7 @@ import (
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/utils"
)
@ -31,6 +33,7 @@ type CertificateInfoJSON struct {
AcmeName string `json:"acme_name"`
AcmeUrl string `json:"acme_url"`
SkipTLS bool `json:"skip_tls"`
UseDNS bool `json:"dns"`
}
// ACMEUser represents a user in the ACME system.
@ -40,6 +43,11 @@ type ACMEUser struct {
key crypto.PrivateKey
}
type EABConfig struct {
Kid string `json:"kid"`
HmacKey string `json:"HmacKey"`
}
// GetEmail returns the email of the ACMEUser.
func (u *ACMEUser) GetEmail() string {
return u.Email
@ -59,18 +67,20 @@ func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey {
type ACMEHandler struct {
DefaultAcmeServer string
Port string
Database *database.Database
}
// 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{
DefaultAcmeServer: acmeServer,
Port: port,
Database: database,
}
}
// 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...")
// 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.
if caUrl != "" {
config.CADirURL = caUrl
@ -136,17 +151,107 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
}
// setup how to receive challenge
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
if err != nil {
log.Println(err)
return false, err
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 {
log.Println(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
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
log.Println(err)
return false, err
/*
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
log.Println(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
@ -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
// 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 {
log.Println(err)
return false, err
@ -179,6 +284,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
AcmeName: caName,
AcmeUrl: caUrl,
SkipTLS: skipTLS,
UseDNS: useDNS,
}
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()))
return
}
//Make sure the wildcard * do not goes into the filename
filename = strings.ReplaceAll(filename, "*", "_")
email, err := utils.PostPara(r, "email")
if err != nil {
@ -329,8 +437,18 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
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, ",")
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS)
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS, dns)
if err != nil {
utils.SendErrorResponse(w, jsonEscape(err.Error()))
return
@ -362,7 +480,7 @@ func IsPortInUse(port int) bool {
}
// Load cert information from json file
func loadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
func LoadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
certInfoBytes, err := os.ReadFile(filename)
if err != nil {
return nil, err

72
src/mod/acme/acme_dns.go Normal file
View 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
}

View 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)
}
}

View 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)
}
}

File diff suppressed because it is too large Load Diff

View 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))
}

View File

@ -344,7 +344,7 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
// Load certificate info for ACME detail
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
certInfo, err := loadCertInfoJSON(certInfoFilename)
certInfo, err := LoadCertInfoJSON(certInfoFilename)
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)
@ -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 {
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
} else {
@ -373,3 +373,65 @@ func (a *AutoRenewer) saveRenewConfigToFile() error {
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
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)
}

View File

@ -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
}

Binary file not shown.

View File

@ -1,16 +1,11 @@
package dynamicproxy
import (
_ "embed"
"errors"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"imuslab.com/zoraxy/mod/geodb"
)
/*
@ -26,41 +21,21 @@ import (
- Vitrual Directory Routing
*/
var (
//go:embed tld.json
rawTldMap []byte
)
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
/*
Special Routing Rules, bypass most of the limitations
*/
//Check if there are external routing rule matches.
//If yes, route them via external rr
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
if matchedRoutingRule != nil {
//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)
return
}
/*
General Access Check
*/
respWritten := h.handleAccessRouting(w, r)
if respWritten {
return
}
//Inject headers
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
/*
Redirection Routing
@ -72,46 +47,71 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
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
if strings.Contains(r.Host, ":") {
hostPath := strings.Split(r.Host, ":")
domainOnly = hostPath[0]
}
/*
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 {
err := h.handleBasicAuthRouting(w, r, sep)
if err != nil {
return
}
}
h.subdomainRequest(w, r, sep)
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
}
}
/*
Virtual Directory Routing
*/
//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)
//Validate basic auth
if sep.RequireBasicAuth {
err := h.handleBasicAuthRouting(w, r, sep)
if err != nil {
return
}
}
h.proxyRequest(w, r, targetProxyEndpoint)
} else if !strings.HasSuffix(proxyingPath, "/") {
//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
}
}
//Fallback to handle by the host proxy forwarder
h.hostRequest(w, r, sep)
return
}
/*
Root Router Handling
*/
//Root access control based on default rule
blocked := h.handleAccessRouting("default", w, r)
if blocked {
return
}
//Clean up the request URI
proxyingPath := strings.TrimSpace(r.RequestURI)
if !strings.HasSuffix(proxyingPath, "/") {
potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
if potentialProxtEndpoint != nil {
//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]
}
if h.Parent.RootRoutingOptions.EnableRedirectForUnsetRules {
//Route to custom domain
if h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget == "" {
//Not set. Redirect to first level of domain redirectable
fld, err := h.getTopLevelRedirectableDomain(domainOnly)
if err != nil {
//Redirect to proxy root
h.proxyRequest(w, r, h.Parent.Root)
} else {
log.Println("[Router] Redirecting request from " + domainOnly + " to " + fld)
h.logRequest(r, false, 307, "root-redirect", domainOnly)
http.Redirect(w, r, fld, http.StatusTemporaryRedirect)
}
//Get the proxy root config
proot := h.Parent.Root
switch proot.DefaultSiteOption {
case DefaultSite_InternalStaticWebServer:
fallthrough
case DefaultSite_ReverseProxy:
//They both share the same behavior
//Check if any virtual directory rules matches
proxyingPath := strings.TrimSpace(r.RequestURI)
targetProxyEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath)
if targetProxyEndpoint != nil && !targetProxyEndpoint.Disabled {
//Virtual directory routing rule found. Route via vdir mode
h.vdirRequest(w, r, targetProxyEndpoint)
return
} else if h.isTopLevelRedirectableDomain(domainOnly) {
//This is requesting a top level private domain that should be serving root
h.proxyRequest(w, r, h.Parent.Root)
} else {
//Validate the redirection target URL
parsedURL, err := url.Parse(h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget)
if err != nil {
//Error when parsing target. Send to root
h.proxyRequest(w, r, h.Parent.Root)
} else if !strings.HasSuffix(proxyingPath, "/") && proot.ProxyType != ProxyType_Root {
potentialProxtEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
//Missing tailing slash. Redirect to target proxy endpoint
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
return
}
hostname := parsedURL.Hostname()
if domainOnly != hostname {
//Redirect to target
h.logRequest(r, false, 307, "root-redirect", domainOnly)
http.Redirect(w, r, h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget, http.StatusTemporaryRedirect)
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
// if the return value is false, you can continue process the response writer
func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Request) bool {
//Check if this ip is in blacklist
clientIpAddr := geodb.GetRequesterIP(r)
if h.Parent.Option.GeodbStore.IsBlacklisted(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/blacklist.html"))
//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 {
w.Write(page_forbidden)
//Error when parsing target. Send to root
h.hostRequest(w, r, h.Parent.Root)
return
}
hostname := parsedURL.Hostname()
if hostname == domainOnly {
h.logRequest(r, false, 500, "root-redirect", domainOnly)
http.Error(w, "Loopback redirects due to invalid settings", 500)
return
}
h.logRequest(r, false, 307, "root-redirect", domainOnly)
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
case DefaultSite_NotFoundPage:
//Serve the not found page, use template if exists
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusNotFound)
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/notfound.html"))
if err != nil {
w.Write(page_hosterror)
} else {
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")
}

View 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, ""
}

View File

@ -16,6 +16,16 @@ import (
*/
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 {
//Check if the current path matches the exception rules
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()
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
@ -48,7 +54,6 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
}
if !matchingFound {
h.logRequest(r, false, 401, proxyType, pe.Domain)
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
w.WriteHeader(401)
return errors.New("unauthorized")

View File

@ -1,6 +1,7 @@
package dpcore
import (
"context"
"errors"
"io"
"log"
@ -8,11 +9,10 @@ import (
"net/http"
"net/url"
"strings"
"sync"
"time"
)
var onExitFlushLoop func()
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
)
// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
@ -60,6 +60,7 @@ type ResponseRewriteRuleSet struct {
ProxyDomain string
OriginalHost string
UseTLS bool
NoCache bool
PathPrefix string //Vdir prefix for root, / will be rewrite to this
}
@ -67,7 +68,12 @@ type requestCanceler interface {
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
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
@ -79,10 +85,6 @@ func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerificatio
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
@ -94,16 +96,17 @@ func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerificatio
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
thisTransporter.(*http.Transport).DisableCompression = true
if ignoreTLSVerification {
if dpcOptions.IgnoreTLSVerification {
//Ignore TLS certificate validation error
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
}
return &ReverseProxy{
Director: director,
Prepender: prepender,
Verbal: false,
Transport: thisTransporter,
Director: director,
Prepender: prepender,
FlushInterval: dpcOptions.FlushInterval,
Verbal: false,
Transport: thisTransporter,
}
}
@ -177,64 +180,66 @@ var hopHeaders = []string{
//"Upgrade",
}
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
if p.FlushInterval != 0 {
if wf, ok := dst.(writeFlusher); ok {
mlw := &maxLatencyWriter{
dst: wf,
latency: p.FlushInterval,
done: make(chan bool),
}
go mlw.flushLoop()
defer mlw.stop()
dst = mlw
// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
func (p *ReverseProxy) copyResponse(dst http.ResponseWriter, src io.Reader, flushInterval time.Duration) error {
var w io.Writer = dst
if flushInterval != 0 {
mlw := &maxLatencyWriter{
dst: dst,
flush: http.NewResponseController(dst).Flush,
latency: flushInterval,
}
defer mlw.stop()
// 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 {
io.Writer
http.Flusher
}
// Copy with given buffer size. Default to 64k
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
if len(buf) == 0 {
buf = make([]byte, 64*1024)
}
type maxLatencyWriter struct {
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()
var written int64
for {
select {
case <-m.done:
if onExitFlushLoop != nil {
onExitFlushLoop()
nr, rerr := src.Read(buf)
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
p.logf("dpcore read error during body copy: %v", rerr)
}
if nr > 0 {
nw, werr := dst.Write(buf[:nr])
if nw > 0 {
written += int64(nw)
}
return
case <-t.C:
m.mu.Lock()
m.dst.Flush()
m.mu.Unlock()
if werr != nil {
return written, werr
}
if nr != nw {
return written, io.ErrShortWrite
}
}
if rerr != nil {
if rerr == io.EOF {
rerr = nil
}
return written, rerr
}
}
}
func (m *maxLatencyWriter) stop() {
m.done <- true
}
func (p *ReverseProxy) logf(format string, args ...interface{}) {
if p.ErrorLog != nil {
p.ErrorLog.Printf(format, args...)
@ -243,7 +248,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.
if c := header.Get("Connection"); c != "" {
for _, f := range strings.Split(c, ",") {
@ -260,10 +265,25 @@ func removeHeaders(header http.Header) {
}
}
if header.Get("A-Upgrade") != "" {
header.Set("Upgrade", header.Get("A-Upgrade"))
header.Del("A-Upgrade")
//Restore the Upgrade header if any
if header.Get("Zr-Origin-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) {
@ -281,6 +301,22 @@ func addXForwardedForHeader(req *http.Request) {
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]))
}
}
}
}
}
@ -312,9 +348,9 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
p.Director(outreq)
outreq.Close = false
if !rrr.UseTLS {
//This seems to be routing to external sites
//Do not keep the original host
//Only skip origin rewrite iff proxy target require TLS and it is external domain name like github.com
if !(rrr.UseTLS && isExternalDomainName(rrr.ProxyDomain)) {
// Always use the original host, see issue #164
outreq.Host = rrr.OriginalHost
}
@ -323,7 +359,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
copyHeader(outreq.Header, req.Header)
// 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.
addXForwardedForHeader(outreq)
@ -339,7 +375,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.
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 err := p.ModifyResponse(res); err != nil {
@ -352,6 +394,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
if res.Header.Get("Location") != "" {
locationRewrite := res.Header.Get("Location")
@ -381,6 +429,10 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
// Copy header from response to client.
copyHeader(rw.Header(), res.Header)
// inject permission policy headers
//TODO: Load permission policy from rrr
permissionpolicy.InjectPermissionPolicyHeader(rw, nil)
// The "Trailer" header isn't included in the Transport's response, Build it up from Trailer.
if len(res.Trailer) > 0 {
trailerKeys := make([]string, 0, len(res.Trailer))
@ -400,7 +452,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
res.Body.Close()
copyHeader(rw.Header(), res.Trailer)

View 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 == "")
}

View 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()
}
}

View File

@ -1,6 +1,7 @@
package dpcore
import (
"net"
"net/url"
"strings"
)
@ -60,3 +61,34 @@ func replaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS b
func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
return replaceLocationHost(urlString, rrr, useTLS)
}
// isExternalDomainName check and return if the hostname is external domain name (e.g. github.com)
// instead of internal (like 192.168.1.202:8443 (ip address) or domains end with .local or .internal)
func isExternalDomainName(hostname string) bool {
host, _, err := net.SplitHostPort(hostname)
if err != nil {
//hostname doesnt contain port
ip := net.ParseIP(hostname)
if ip != nil {
//IP address, not a domain name
return false
}
} else {
//Hostname contain port, use hostname without port to check if it is ip
ip := net.ParseIP(host)
if ip != nil {
//IP address, not a domain name
return false
}
}
//Check if it is internal DNS assigned domains
internalDNSTLD := []string{".local", ".internal", ".localhost", ".home.arpa"}
for _, tld := range internalDNSTLD {
if strings.HasSuffix(strings.ToLower(hostname), tld) {
return false
}
}
return true
}

View File

@ -22,27 +22,19 @@ import (
func NewDynamicProxy(option RouterOption) (*Router, error) {
proxyMap := sync.Map{}
domainMap := sync.Map{}
thisRouter := Router{
Option: &option,
ProxyEndpoints: &proxyMap,
SubdomainEndpoint: &domainMap,
Running: false,
server: nil,
routingRules: []*RoutingRule{},
tldMap: map[string]int{},
Option: &option,
ProxyEndpoints: &proxyMap,
Running: false,
server: nil,
routingRules: []*RoutingRule{},
tldMap: map[string]int{},
}
thisRouter.mux = &ProxyHandler{
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
}
@ -60,6 +52,12 @@ func (router *Router) UpdateTLSVersion(requireLatest bool) {
router.Restart()
}
// Update port 80 listener state
func (router *Router) UpdatePort80ListenerState(useRedirect bool) {
router.Option.ListenOnPort80 = useRedirect
router.Restart()
}
// Update https redirect, which will require updates
func (router *Router) UpdateHttpToHttpsRedirectSetting(useRedirect bool) {
router.Option.ForceHttpsRedirect = useRedirect
@ -70,21 +68,14 @@ func (router *Router) UpdateHttpToHttpsRedirectSetting(useRedirect bool) {
func (router *Router) StartProxyService() error {
//Create a new server object
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
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
if router.Option.ForceTLSLatest {
minVersion = tls.VersionTLS12
@ -95,27 +86,85 @@ func (router *Router) StartProxyService() error {
}
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.server = &http.Server{
Addr: ":" + strconv.Itoa(router.Option.Port),
Handler: router.mux,
TLSConfig: config,
}
router.tlsListener = ln
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
router.Running = true
if router.Option.Port != 80 && router.Option.ForceHttpsRedirect {
if router.Option.Port != 80 && router.Option.ListenOnPort80 {
//Add a 80 to 443 redirector
httpServer := &http.Server{
Addr: ":80",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
protocol := "https://"
if router.Option.Port == 443 {
http.Redirect(w, r, protocol+r.Host+r.RequestURI, http.StatusTemporaryRedirect)
//Check if the domain requesting allow non TLS mode
domainOnly := r.Host
if strings.Contains(r.Host, ":") {
hostPath := strings.Split(r.Host, ":")
domainOnly = hostPath[0]
}
sep := router.getProxyEndpointFromHostname(domainOnly)
if sep != nil && sep.BypassGlobalTLS {
//Allow routing via non-TLS handler
originalHostHeader := r.Host
if r.URL != nil {
r.Host = r.URL.Host
} else {
//Fallback when the upstream proxy screw something up in the header
r.URL, _ = url.Parse(originalHostHeader)
}
//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,
OriginalHost: originalHostHeader,
UseTLS: sep.RequireTLS,
PathPrefix: "",
})
return
}
if router.Option.ForceHttpsRedirect {
//Redirect to https is enabled
protocol := "https://"
if router.Option.Port == 443 {
http.Redirect(w, r, protocol+r.Host+r.RequestURI, http.StatusTemporaryRedirect)
} else {
http.Redirect(w, r, protocol+r.Host+":"+strconv.Itoa(router.Option.Port)+r.RequestURI, http.StatusTemporaryRedirect)
}
} else {
http.Redirect(w, r, protocol+r.Host+":"+strconv.Itoa(router.Option.Port)+r.RequestURI, http.StatusTemporaryRedirect)
//Do not do redirection
if sep != nil {
//Sub-domain exists but not allow non-TLS access
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("400 - Bad Request"))
} else {
//No defined sub-domain
http.NotFound(w, r)
}
}
}),
@ -143,7 +192,7 @@ func (router *Router) StartProxyService() error {
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
//Unable to startup port 80 listener. Handle shutdown process gracefully
stopChan <- true
log.Fatalf("Could not start server: %v\n", err)
log.Fatalf("Could not start redirection server: %v\n", err)
}
}()
router.tlsRedirectStop = stopChan
@ -152,8 +201,8 @@ func (router *Router) StartProxyService() error {
//Start the TLS server
log.Println("Reverse proxy service started in the background (TLS mode)")
go func() {
if err := router.server.Serve(ln); err != nil && err != http.ErrServerClosed {
log.Fatalf("Could not start server: %v\n", err)
if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
log.Fatalf("Could not start proxy server: %v\n", err)
}
}()
} else {
@ -173,7 +222,7 @@ func (router *Router) StartProxyService() error {
func (router *Router) StopProxyService() error {
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)
defer cancel()
@ -201,13 +250,13 @@ func (router *Router) StopProxyService() error {
// Restart the current router if it is running.
func (router *Router) Restart() error {
//Stop the router if it is already running
var err error = nil
if router.Running {
err := router.StopProxyService()
if err != nil {
return err
}
time.Sleep(300 * time.Millisecond)
// Start the server
err = router.StartProxyService()
if err != nil {
@ -215,7 +264,7 @@ func (router *Router) Restart() error {
}
}
return err
return nil
}
/*
@ -228,128 +277,17 @@ func (router *Router) IsProxiedSubdomain(r *http.Request) bool {
hostname = r.Host
}
hostname = strings.Split(hostname, ":")[0]
subdEndpoint := router.getSubdomainProxyEndpointFromHostname(hostname)
subdEndpoint := router.getProxyEndpointFromHostname(hostname)
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
*/
func (router *Router) LoadProxy(ptype string, key string) (*ProxyEndpoint, error) {
if ptype == "vdir" {
proxy, ok := router.ProxyEndpoints.Load(key)
if !ok {
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)
func (router *Router) LoadProxy(matchingDomain string) (*ProxyEndpoint, error) {
var targetProxyEndpoint *ProxyEndpoint
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
key, ok := key.(string)
if !ok {
return true
}
@ -357,13 +295,32 @@ func (r *Router) GetSDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
if !ok {
return true
}
m[k] = v
if key == matchingDomain {
targetProxyEndpoint = v
}
return true
})
return m
if targetProxyEndpoint == nil {
return nil, errors.New("target routing rule not found")
}
return targetProxyEndpoint, nil
}
func (r *Router) GetVDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
// 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)
r.ProxyEndpoints.Range(func(key, value interface{}) bool {
k, ok := key.(string)

View 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)
}

View File

@ -0,0 +1,193 @@
package permissionpolicy
import (
"fmt"
"net/http"
"strings"
)
/*
Permisson Policy
This is a permission policy header modifier that changes
the request permission related policy fields
author: tobychui
*/
type PermissionsPolicy struct {
Accelerometer []string `json:"accelerometer"`
AmbientLightSensor []string `json:"ambient_light_sensor"`
Autoplay []string `json:"autoplay"`
Battery []string `json:"battery"`
Camera []string `json:"camera"`
CrossOriginIsolated []string `json:"cross_origin_isolated"`
DisplayCapture []string `json:"display_capture"`
DocumentDomain []string `json:"document_domain"`
EncryptedMedia []string `json:"encrypted_media"`
ExecutionWhileNotRendered []string `json:"execution_while_not_rendered"`
ExecutionWhileOutOfView []string `json:"execution_while_out_of_viewport"`
Fullscreen []string `json:"fullscreen"`
Geolocation []string `json:"geolocation"`
Gyroscope []string `json:"gyroscope"`
KeyboardMap []string `json:"keyboard_map"`
Magnetometer []string `json:"magnetometer"`
Microphone []string `json:"microphone"`
Midi []string `json:"midi"`
NavigationOverride []string `json:"navigation_override"`
Payment []string `json:"payment"`
PictureInPicture []string `json:"picture_in_picture"`
PublicKeyCredentialsGet []string `json:"publickey_credentials_get"`
ScreenWakeLock []string `json:"screen_wake_lock"`
SyncXHR []string `json:"sync_xhr"`
USB []string `json:"usb"`
WebShare []string `json:"web_share"`
XRSpatialTracking []string `json:"xr_spatial_tracking"`
ClipboardRead []string `json:"clipboard_read"`
ClipboardWrite []string `json:"clipboard_write"`
Gamepad []string `json:"gamepad"`
SpeakerSelection []string `json:"speaker_selection"`
ConversionMeasurement []string `json:"conversion_measurement"`
FocusWithoutUserActivation []string `json:"focus_without_user_activation"`
HID []string `json:"hid"`
IdleDetection []string `json:"idle_detection"`
InterestCohort []string `json:"interest_cohort"`
Serial []string `json:"serial"`
SyncScript []string `json:"sync_script"`
TrustTokenRedemption []string `json:"trust_token_redemption"`
Unload []string `json:"unload"`
WindowPlacement []string `json:"window_placement"`
VerticalScroll []string `json:"vertical_scroll"`
}
// GetDefaultPermissionPolicy returns a PermissionsPolicy struct with all policies set to *
func GetDefaultPermissionPolicy() *PermissionsPolicy {
return &PermissionsPolicy{
Accelerometer: []string{"*"},
AmbientLightSensor: []string{"*"},
Autoplay: []string{"*"},
Battery: []string{"*"},
Camera: []string{"*"},
CrossOriginIsolated: []string{"*"},
DisplayCapture: []string{"*"},
DocumentDomain: []string{"*"},
EncryptedMedia: []string{"*"},
ExecutionWhileNotRendered: []string{"*"},
ExecutionWhileOutOfView: []string{"*"},
Fullscreen: []string{"*"},
Geolocation: []string{"*"},
Gyroscope: []string{"*"},
KeyboardMap: []string{"*"},
Magnetometer: []string{"*"},
Microphone: []string{"*"},
Midi: []string{"*"},
NavigationOverride: []string{"*"},
Payment: []string{"*"},
PictureInPicture: []string{"*"},
PublicKeyCredentialsGet: []string{"*"},
ScreenWakeLock: []string{"*"},
SyncXHR: []string{"*"},
USB: []string{"*"},
WebShare: []string{"*"},
XRSpatialTracking: []string{"*"},
ClipboardRead: []string{"*"},
ClipboardWrite: []string{"*"},
Gamepad: []string{"*"},
SpeakerSelection: []string{"*"},
ConversionMeasurement: []string{"*"},
FocusWithoutUserActivation: []string{"*"},
HID: []string{"*"},
IdleDetection: []string{"*"},
InterestCohort: []string{"*"},
Serial: []string{"*"},
SyncScript: []string{"*"},
TrustTokenRedemption: []string{"*"},
Unload: []string{"*"},
WindowPlacement: []string{"*"},
VerticalScroll: []string{"*"},
}
}
// InjectPermissionPolicyHeader inject the permission policy into headers
func InjectPermissionPolicyHeader(w http.ResponseWriter, policy *PermissionsPolicy) {
//Keep the original Permission Policy if exists, or there are no policy given
if policy == nil || w.Header().Get("Permissions-Policy") != "" {
return
}
policyHeader := []string{}
// Helper function to add policy directives
addDirective := func(name string, sources []string) {
if len(sources) > 0 {
if sources[0] == "*" {
//Allow all
policyHeader = append(policyHeader, fmt.Sprintf("%s=%s", name, "*"))
} else {
//Other than "self" which do not need double quote, others domain need double quote in place
formatedSources := []string{}
for _, source := range sources {
if source == "self" {
formatedSources = append(formatedSources, "self")
} else {
formatedSources = append(formatedSources, "\""+source+"\"")
}
}
policyHeader = append(policyHeader, fmt.Sprintf("%s=(%s)", name, strings.Join(formatedSources, " ")))
}
} else {
//There are no setting for this field. Assume no permission
policyHeader = append(policyHeader, fmt.Sprintf("%s=()", name))
}
}
// Add each policy directive to the header
addDirective("accelerometer", policy.Accelerometer)
addDirective("ambient-light-sensor", policy.AmbientLightSensor)
addDirective("autoplay", policy.Autoplay)
addDirective("battery", policy.Battery)
addDirective("camera", policy.Camera)
addDirective("cross-origin-isolated", policy.CrossOriginIsolated)
addDirective("display-capture", policy.DisplayCapture)
addDirective("document-domain", policy.DocumentDomain)
addDirective("encrypted-media", policy.EncryptedMedia)
addDirective("execution-while-not-rendered", policy.ExecutionWhileNotRendered)
addDirective("execution-while-out-of-viewport", policy.ExecutionWhileOutOfView)
addDirective("fullscreen", policy.Fullscreen)
addDirective("geolocation", policy.Geolocation)
addDirective("gyroscope", policy.Gyroscope)
addDirective("keyboard-map", policy.KeyboardMap)
addDirective("magnetometer", policy.Magnetometer)
addDirective("microphone", policy.Microphone)
addDirective("midi", policy.Midi)
addDirective("navigation-override", policy.NavigationOverride)
addDirective("payment", policy.Payment)
addDirective("picture-in-picture", policy.PictureInPicture)
addDirective("publickey-credentials-get", policy.PublicKeyCredentialsGet)
addDirective("screen-wake-lock", policy.ScreenWakeLock)
addDirective("sync-xhr", policy.SyncXHR)
addDirective("usb", policy.USB)
addDirective("web-share", policy.WebShare)
addDirective("xr-spatial-tracking", policy.XRSpatialTracking)
addDirective("clipboard-read", policy.ClipboardRead)
addDirective("clipboard-write", policy.ClipboardWrite)
addDirective("gamepad", policy.Gamepad)
addDirective("speaker-selection", policy.SpeakerSelection)
addDirective("conversion-measurement", policy.ConversionMeasurement)
addDirective("focus-without-user-activation", policy.FocusWithoutUserActivation)
addDirective("hid", policy.HID)
addDirective("idle-detection", policy.IdleDetection)
addDirective("interest-cohort", policy.InterestCohort)
addDirective("serial", policy.Serial)
addDirective("sync-script", policy.SyncScript)
addDirective("trust-token-redemption", policy.TrustTokenRedemption)
addDirective("unload", policy.Unload)
addDirective("window-placement", policy.WindowPlacement)
addDirective("vertical-scroll", policy.VerticalScroll)
// Join the directives and set the header
policyHeaderValue := strings.Join(policyHeader, ", ")
//Inject the new policy into the header
w.Header().Set("Permissions-Policy", policyHeaderValue)
}

View File

@ -0,0 +1,47 @@
package permissionpolicy_test
import (
"net/http/httptest"
"strings"
"testing"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
)
func TestInjectPermissionPolicyHeader(t *testing.T) {
//Prepare the data for permission policy
testPermissionPolicy := permissionpolicy.GetDefaultPermissionPolicy()
testPermissionPolicy.Geolocation = []string{"self"}
testPermissionPolicy.Microphone = []string{"self", "https://example.com"}
testPermissionPolicy.Camera = []string{"*"}
tests := []struct {
name string
existingHeader string
policy *permissionpolicy.PermissionsPolicy
expectedHeader string
}{
{
name: "Default policy with a few limitations",
existingHeader: "",
policy: testPermissionPolicy,
expectedHeader: `accelerometer=*, ambient-light-sensor=*, autoplay=*, battery=*, camera=*, cross-origin-isolated=*, display-capture=*, document-domain=*, encrypted-media=*, execution-while-not-rendered=*, execution-while-out-of-viewport=*, fullscreen=*, geolocation=(self), gyroscope=*, keyboard-map=*, magnetometer=*, microphone=(self "https://example.com"), midi=*, navigation-override=*, payment=*, picture-in-picture=*, publickey-credentials-get=*, screen-wake-lock=*, sync-xhr=*, usb=*, web-share=*, xr-spatial-tracking=*, clipboard-read=*, clipboard-write=*, gamepad=*, speaker-selection=*, conversion-measurement=*, focus-without-user-activation=*, hid=*, idle-detection=*, interest-cohort=*, serial=*, sync-script=*, trust-token-redemption=*, unload=*, window-placement=*, vertical-scroll=*`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rr := httptest.NewRecorder()
if tt.existingHeader != "" {
rr.Header().Set("Permissions-Policy", tt.existingHeader)
}
permissionpolicy.InjectPermissionPolicyHeader(rr, tt.policy)
gotHeader := rr.Header().Get("Permissions-Policy")
if !strings.Contains(gotHeader, tt.expectedHeader) {
t.Errorf("got header %s, want %s", gotHeader, tt.expectedHeader)
}
})
}
}

Some files were not shown because too many files have changed in this diff Show More