Compare commits
235 Commits
Author | SHA1 | Date | |
---|---|---|---|
0a734e0bd3 | |||
f4fa92635c | |||
7d5151bb00 | |||
54475e4b99 | |||
6ac16caf37 | |||
97502db607 | |||
0747cf4b0f | |||
94483acc92 | |||
7626857c02 | |||
0f772a715b | |||
fd1439f746 | |||
ca37bfbfa6 | |||
c1e16d55ab | |||
f595da92a1 | |||
02ff288280 | |||
b1c5bc2963 | |||
d3dbbf9052 | |||
f4a5c905e7 | |||
245379e91f | |||
955a2232df | |||
7eb7ae7ced | |||
3aa0f2d914 | |||
39b0c8c674 | |||
bddeae8365 | |||
8e0e9531e7 | |||
6ff22865e0 | |||
0828fd1958 | |||
82f84470f7 | |||
cf9a05f130 | |||
301072db90 | |||
cfcd10d64f | |||
c85760c73a | |||
b7bb918aa3 | |||
962f3e0566 | |||
0bcf2b2ae3 | |||
6bfeb8cf3d | |||
33def66386 | |||
cb469f28d2 | |||
8239f4cb53 | |||
e410b92e34 | |||
aca6e44b35 | |||
2aa35cbe6d | |||
745a54605f | |||
e3b61868a1 | |||
764b1944be | |||
100cd727fc | |||
7e62fef879 | |||
1a4a55721f | |||
bb9deccff6 | |||
a18413dd03 | |||
2cd1b1de3c | |||
3a2db63d61 | |||
123d3bcf3f | |||
3ec1d9c888 | |||
5785261c7e | |||
89e60649e5 | |||
5423b82858 | |||
57135a867e | |||
547855f30f | |||
05b477e90a | |||
3519c7841c | |||
e7b4054248 | |||
973d0b3372 | |||
704980d4f8 | |||
03974163d4 | |||
dfb81513b1 | |||
b604c66a2f | |||
dd84864dd4 | |||
443cd961d2 | |||
10048150bb | |||
85f9b297c4 | |||
07e524a007 | |||
25c7e8ac1a | |||
49babbd60f | |||
fa11422748 | |||
bb1b161ae2 | |||
9545343151 | |||
61e4d45430 | |||
6026c4fd53 | |||
e3f8c99ed3 | |||
fc88dfe72e | |||
d43322f7a5 | |||
83536a83f7 | |||
1183b0ed55 | |||
b00e302f6d | |||
deddb17803 | |||
aa96d831e1 | |||
c6f7f37aaf | |||
63f12dedcf | |||
136d1ecafb | |||
7193defad7 | |||
cf4c57298e | |||
d82a531a41 | |||
7694e317f7 | |||
ed4945ab7e | |||
ce8741bfc8 | |||
7a3db09811 | |||
e73f9b47d3 | |||
c248dacccf | |||
d596d6b843 | |||
6feb2d105d | |||
3a26a5b4d3 | |||
2cdd5654ed | |||
a0d362df4e | |||
334c1ab131 | |||
08d52024ab | |||
a3e16594e8 | |||
cced07ba2d | |||
2003992d75 | |||
71423d98b1 | |||
8ca716c59f | |||
fe48a9a0c3 | |||
ec973eb3bc | |||
7b69b5fa63 | |||
ce4f46cb50 | |||
3454a9b975 | |||
55bc939a37 | |||
1d63b679dc | |||
3df96350a3 | |||
34fab7b3d0 | |||
46817d0664 | |||
1db2ca61fa | |||
0b601406de | |||
b4c771cdee | |||
a486d42351 | |||
90c2199a1b | |||
161c61fac7 | |||
5ffacb1d06 | |||
75ebd0ffbe | |||
dc069f3c57 | |||
e1b512f78f | |||
8854a38f49 | |||
7583a4628c | |||
73c0ea0896 | |||
7dad7c7305 | |||
faa95b4e21 | |||
cb0e13976d | |||
ccd8dcff56 | |||
750656fd7f | |||
d9f515fdba | |||
176249a7d9 | |||
e2a449a7bc | |||
a9695e969e | |||
7ba997dfc2 | |||
d00117e878 | |||
43a84a3f1c | |||
e24f31bdef | |||
fc9240fbac | |||
e0f5431215 | |||
de658a3c6c | |||
73276b1918 | |||
abdb7d4d75 | |||
72299ace15 | |||
4d6c79f51b | |||
2c045f4f40 | |||
b8cf046ca6 | |||
026dd6b89d | |||
5805fe6ed2 | |||
3c78211800 | |||
8e648a8e1f | |||
a000893dd1 | |||
db88bfb752 | |||
05297d854b | |||
0d7bce4d30 | |||
8db95dddc6 | |||
05daeded37 | |||
8ce6471be5 | |||
e242c9288f | |||
c55a29e7cf | |||
6af047430c | |||
200c924acd | |||
9b2168466c | |||
7ae48bf370 | |||
ee3d76fb96 | |||
40d192524b | |||
c659e05005 | |||
676a45c222 | |||
1da0761b13 | |||
32939874f2 | |||
43a4bf389a | |||
33c7c5fa00 | |||
216b53f224 | |||
059b0a2e1c | |||
3ab952f168 | |||
4f676d6770 | |||
e980bc847b | |||
174efc9080 | |||
3228789375 | |||
36e461795a | |||
d6e7641364 | |||
15cebd6e06 | |||
e9a074d4d1 | |||
4b7fd39e57 | |||
fa005f1327 | |||
c7a9f40baa | |||
d5b9726158 | |||
f659e66cf7 | |||
801bdbf298 | |||
09da93cfb3 | |||
70ace02e80 | |||
1f758e953d | |||
ffad2cab81 | |||
dbb10644de | |||
4848392185 | |||
956f4ac30f | |||
c09ff28fd5 | |||
20cf290d37 | |||
4ca0fcc6d1 | |||
ce4ce72820 | |||
e363d55899 | |||
172479e4fb | |||
156fa5dace | |||
4d40e0aa38 | |||
045e66b631 | |||
62e60d78de | |||
23bdaa1517 | |||
50f222cced | |||
640e1adf96 | |||
d4bb84180c | |||
bda47fc36b | |||
fd6ba56143 | |||
b63a0fc246 | |||
ed92cccf0e | |||
95892802fd | |||
8a5004e828 | |||
c6c523e005 | |||
a692ec818d | |||
c65f780613 | |||
507c2ab468 | |||
1180da8d11 | |||
b1a14872c3 | |||
df9deb3fbb | |||
83f574e3ab | |||
60837f307d | |||
9369237229 |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -33,6 +33,7 @@ If applicable, add screenshots to help explain your problem.
|
|||||||
- Device: [e.g. Bananapi R2 PRO]
|
- Device: [e.g. Bananapi R2 PRO]
|
||||||
- OS: [e.g. Armbian]
|
- OS: [e.g. Armbian]
|
||||||
- Version [e.g. 23.02 Bullseye ]
|
- Version [e.g. 23.02 Bullseye ]
|
||||||
|
- Docker Version (if you are running Zoraxy in docker): [e.g. 3.0.4]
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
25
.github/ISSUE_TEMPLATE/help-needed.md
vendored
Normal 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.
|
18
.github/workflows/main.yml
vendored
@ -9,18 +9,19 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.release.tag_name }}
|
ref: ${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to Dockerhub
|
- name: Login to Docker & GHCR
|
||||||
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
run: |
|
||||||
|
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||||
|
|
||||||
- name: Setup building file structure
|
- name: Setup building file structure
|
||||||
run: |
|
run: |
|
||||||
@ -32,15 +33,8 @@ jobs:
|
|||||||
docker buildx create --name mainbuilder --driver docker-container --platform linux/amd64,linux/arm64 --use
|
docker buildx create --name mainbuilder --driver docker-container --platform linux/amd64,linux/arm64 --use
|
||||||
|
|
||||||
docker buildx build --push \
|
docker buildx build --push \
|
||||||
--build-arg VERSION=${{ github.event.release.tag_name }} \
|
|
||||||
--provenance=false \
|
--provenance=false \
|
||||||
--platform linux/amd64,linux/arm64 \
|
--platform linux/amd64,linux/arm64 \
|
||||||
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \
|
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \
|
||||||
.
|
|
||||||
|
|
||||||
docker buildx build --push \
|
|
||||||
--build-arg VERSION=${{ github.event.release.tag_name }} \
|
|
||||||
--provenance=false \
|
|
||||||
--platform linux/amd64,linux/arm64 \
|
|
||||||
--tag zoraxydocker/zoraxy:latest \
|
--tag zoraxydocker/zoraxy:latest \
|
||||||
.
|
.
|
||||||
|
8
.gitignore
vendored
@ -32,3 +32,11 @@ src/README.md
|
|||||||
docker/ContainerTester.sh
|
docker/ContainerTester.sh
|
||||||
docker/ImagePublisher.sh
|
docker/ImagePublisher.sh
|
||||||
src/mod/acme/test/stackoverflow.pem
|
src/mod/acme/test/stackoverflow.pem
|
||||||
|
/tools/dns_challenge_update/code-gen/acmedns
|
||||||
|
/tools/dns_challenge_update/code-gen/lego
|
||||||
|
src/tmp/localhost.key
|
||||||
|
src/tmp/localhost.pem
|
||||||
|
src/www/html/index.html
|
||||||
|
src/sys.uuid
|
||||||
|
src/zoraxy
|
||||||
|
src/log/
|
188
CHANGELOG.md
@ -1,8 +1,184 @@
|
|||||||
# 2.6.6 Jul 26 2023
|
# v3.0.9 16 Jul 2024
|
||||||
|
|
||||||
|
+ Added certificate download [#227](https://github.com/tobychui/zoraxy/issues/227)
|
||||||
|
+ Updated netcup timeout value [#231](https://github.com/tobychui/zoraxy/issues/231)
|
||||||
|
+ Updated geoip db
|
||||||
|
+ Removed debug print from log viewer
|
||||||
|
+ Upgraded netstat log printing to new log formatter
|
||||||
|
+ Improved update module implementation
|
||||||
|
|
||||||
|
# v3.0.8 15 Jul 2024
|
||||||
|
|
||||||
|
+ Added apache style logging mechanism (and build-in log viewer) [#218](https://github.com/tobychui/zoraxy/issues/218)
|
||||||
|
+ Fixed keep alive flushing issues [#235](https://github.com/tobychui/zoraxy/issues/235)
|
||||||
|
+ Added multi-upstream supports [#100](https://github.com/tobychui/zoraxy/issues/100)
|
||||||
|
+ Added stick session load balancer
|
||||||
|
+ Added weighted random load balancer
|
||||||
|
+ Added domain cleaning logic to domain / IP input fields
|
||||||
|
+ Added HSTS "include subdomain" auto injector
|
||||||
|
+ Added work-in-progress SSO / Oauth Server UI
|
||||||
|
+ Fixed uptime monitor not updating on proxy rule change bug
|
||||||
|
+ Optimized UI for create new proxy rule
|
||||||
|
+ Removed service expose proxy feature
|
||||||
|
|
||||||
|
# v3.0.7 20 Jun 2024
|
||||||
|
|
||||||
|
+ Fixed redirection enable bug [#199](https://github.com/tobychui/zoraxy/issues/199)
|
||||||
|
+ Fixed header tool user agent rewrite sequence
|
||||||
|
+ Optimized rate limit UI
|
||||||
|
+ Added HSTS and Permission Policy Editor [#163](https://github.com/tobychui/zoraxy/issues/163)
|
||||||
|
+ Docker UX optimization start parameter `-docker`
|
||||||
|
+ Docker container selector implementation for conditional compilations for Windows
|
||||||
|
|
||||||
|
From contributors:
|
||||||
|
|
||||||
|
+ Add Rate Limits Limits to Zoraxy fixes [185](https://github.com/tobychui/zoraxy/issues/185) by [Kirari04](https://github.com/Kirari04)
|
||||||
|
+ Add docker containers list to set rule by [7brend7](https://github.com/7brend7) [PR202](https://github.com/tobychui/zoraxy/pull/202)
|
||||||
|
|
||||||
|
# v3.0.6 10 Jun 2024
|
||||||
|
|
||||||
|
+ Added fastly_client_ip to X-Real-IP auto rewrite
|
||||||
|
+ Added atomic accumulator to TCP proxy
|
||||||
|
+ Added white logo for future dark theme
|
||||||
|
+ Added multi selection for white / blacklist [#176](https://github.com/tobychui/zoraxy/issues/176)
|
||||||
|
+ Moved custom header rewrite to dpcore
|
||||||
|
+ Restructure dpcore header rewrite sequence
|
||||||
|
+ Added advance custom header settings (zoraxy to upstream and zoraxy to downstream mode)
|
||||||
|
+ Added header remove feature
|
||||||
|
+ Removed password requirement for SMTP [#162](https://github.com/tobychui/zoraxy/issues/162) [#80](https://github.com/tobychui/zoraxy/issues/80)
|
||||||
|
+ Restructured TCP proxy into Stream Proxy (Support both TCP and UDP) [#147](https://github.com/tobychui/zoraxy/issues/147)
|
||||||
|
+ Added stream proxy auto start [#169](https://github.com/tobychui/zoraxy/issues/169)
|
||||||
|
+ Optimized UX for reminding user to click Apply after port change
|
||||||
|
+ Added version number to footer [#160](https://github.com/tobychui/zoraxy/issues/160)
|
||||||
|
|
||||||
|
From contributors:
|
||||||
|
|
||||||
|
+ Fixed missing / unnecessary error check [PR187](https://github.com/tobychui/zoraxy/pull/187) by [Kirari04](https://github.com/Kirari04)
|
||||||
|
|
||||||
|
# v3.0.5 May 26 2024
|
||||||
|
|
||||||
|
|
||||||
|
+ Optimized uptime monitor error message [#121](https://github.com/tobychui/zoraxy/issues/121)
|
||||||
|
+ Optimized detection logic for internal proxy target and header rewrite condition for HTTP_HOST [#164](https://github.com/tobychui/zoraxy/issues/164)
|
||||||
|
+ Fixed ovh DNS challenge provider form generator bug [#161](https://github.com/tobychui/zoraxy/issues/161)
|
||||||
|
+ 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 [#149](https://github.com/tobychui/zoraxy/issues/149)
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
+ Fixed redirection bug under another reverse proxy and Apache location headers [#39](https://github.com/tobychui/zoraxy/issues/39)
|
||||||
|
+ 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 backend, thx [@daluntw](https://github.com/daluntw)
|
||||||
|
+ Added bypass TLS check for custom acme server, thx [@daluntw](https://github.com/daluntw)
|
||||||
|
+ 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)
|
+ 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
|
+ 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
|
+ 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
|
# v2.6.5 Jul 19 2023
|
||||||
|
|
||||||
@ -17,7 +193,7 @@
|
|||||||
+ Added force TLS v1.2 above toggle
|
+ Added force TLS v1.2 above toggle
|
||||||
+ Added trace route
|
+ Added trace route
|
||||||
+ Added ICMP ping
|
+ 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
|
+ Fixed IPv6 check bug in black/whitelist
|
||||||
+ Optimized UI for TCP Proxy
|
+ Optimized UI for TCP Proxy
|
||||||
|
|
||||||
@ -27,7 +203,7 @@
|
|||||||
+ Split blacklist and whitelist from geodb script file
|
+ Split blacklist and whitelist from geodb script file
|
||||||
+ Optimized compile binary size
|
+ Optimized compile binary size
|
||||||
+ Added access control to TCP proxy
|
+ 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
|
+ Fixed minor bugs in advance stats panel
|
||||||
+ Reduced file size of embedded materials
|
+ Reduced file size of embedded materials
|
||||||
|
|
||||||
@ -54,6 +230,6 @@
|
|||||||
+ Basic auth
|
+ Basic auth
|
||||||
+ Support TLS verification skip (for self signed certs)
|
+ Support TLS verification skip (for self signed certs)
|
||||||
+ Added trend analysis
|
+ Added trend analysis
|
||||||
+ Added referer and file type analysis
|
+ Added referrer and file type analysis
|
||||||
+ Added cert expire day display
|
+ Added cert expire day display
|
||||||
+ Moved subdomain proxy logic to dpcore
|
+ Moved subdomain proxy logic to dpcore
|
||||||
|
147
README.md
@ -2,36 +2,58 @@
|
|||||||
|
|
||||||
# Zoraxy
|
# 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
|
### Features
|
||||||
|
|
||||||
- Simple to use interface with detail in-system instructions
|
- Simple to use interface with detail in-system instructions
|
||||||
- Reverse Proxy
|
- Reverse Proxy (HTTP/2)
|
||||||
|
- Virtual Directory
|
||||||
- Subdomain Reverse Proxy
|
- WebSocket Proxy (automatic, no set-up needed)
|
||||||
|
- Basic Auth
|
||||||
- Virtual Directory Reverse Proxy
|
- Alias Hostnames
|
||||||
|
- Custom Headers
|
||||||
- Redirection Rules
|
- Redirection Rules
|
||||||
- TLS / SSL setup and deploy
|
- TLS / SSL setup and deploy
|
||||||
- Blacklist by country or IP address (single IP, CIDR or wildcard for beginners)
|
- ACME features like auto-renew to serve your sites in http**s**
|
||||||
|
- SNI support (and SAN certs)
|
||||||
|
- 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)
|
- Global Area Network Controller Web UI (ZeroTier not included)
|
||||||
|
- Stream Proxy (TCP & UDP)
|
||||||
- Integrated Up-time Monitor
|
- Integrated Up-time Monitor
|
||||||
- Web-SSH Terminal
|
- Web-SSH Terminal
|
||||||
- Utilities
|
- Utilities
|
||||||
|
|
||||||
- CIDR IP converters
|
- CIDR IP converters
|
||||||
- mDNS Scanner
|
- mDNS Scanner
|
||||||
|
- Wake-On-Lan
|
||||||
|
- Debug Forward Proxy
|
||||||
- IP Scanner
|
- IP Scanner
|
||||||
- Others
|
- Others
|
||||||
- Basic single-admin management mode
|
- Basic single-admin management mode
|
||||||
- External permission management system for easy system integration
|
- External permission management system for easy system integration
|
||||||
- SMTP config for password reset
|
- SMTP config for password reset
|
||||||
|
|
||||||
## Build from Source
|
## Downloads
|
||||||
Require Go 1.20 or above
|
|
||||||
|
|
||||||
```
|
[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/)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
[Installing Zoraxy Reverse Proxy: Your Gateway to Efficient Web Routing](https://geekscircuit.com/installing-zoraxy-reverse-proxy-your-gateway-to-efficient-web-routing/)
|
||||||
|
|
||||||
|
Thank you for the well written and easy to follow tutorial by Reddit users [itsvmn](https://www.reddit.com/user/itsvmn/)!
|
||||||
|
If you have no background in setting up reverse proxy or web routing, you should check this out before you start setting up your Zoraxy.
|
||||||
|
|
||||||
|
## Build from Source
|
||||||
|
|
||||||
|
Requires Go 1.22 or higher
|
||||||
|
|
||||||
|
```bash
|
||||||
git clone https://github.com/tobychui/zoraxy
|
git clone https://github.com/tobychui/zoraxy
|
||||||
cd ./zoraxy/src/
|
cd ./zoraxy/src/
|
||||||
go mod tidy
|
go mod tidy
|
||||||
@ -42,11 +64,11 @@ sudo ./zoraxy -port=:8000
|
|||||||
|
|
||||||
## Usage
|
## 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
|
||||||
|
|
||||||
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
|
#### Linux
|
||||||
|
|
||||||
@ -60,18 +82,53 @@ Download the binary executable and double click the binary file to start it.
|
|||||||
|
|
||||||
#### Raspberry Pi
|
#### 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
|
#### 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.
|
The installation method is same as Linux. For other ARM SBCs, please refer to your SBC's CPU architecture and pick the one that is suitable for your device.
|
||||||
|
|
||||||
#### Docker
|
#### Docker
|
||||||
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details
|
|
||||||
|
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details.
|
||||||
|
|
||||||
|
### Start Paramters
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage of zoraxy:
|
||||||
|
-autorenew int
|
||||||
|
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
|
||||||
|
-cfgupgrade
|
||||||
|
Enable auto config upgrade if breaking change is detected (default true)
|
||||||
|
-docker
|
||||||
|
Run Zoraxy in docker compatibility mode
|
||||||
|
-fastgeoip
|
||||||
|
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
|
||||||
|
-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
|
### 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
|
```bash
|
||||||
./zoraxy -noauth=true
|
./zoraxy -noauth=true
|
||||||
@ -79,45 +136,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.*
|
*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
|
## Screenshots
|
||||||
|
|
||||||

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

|

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

|
||||||
|
|
||||||
More screenshots on the wikipage [Screenshots](https://github.com/tobychui/zoraxy/wiki/Screenshots)!
|
More screenshots on the wikipage [Screenshots](https://github.com/tobychui/zoraxy/wiki/Screenshots)!
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
@ -128,21 +152,21 @@ 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.
|
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
|
./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```.
|
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/amd64
|
||||||
- linux/arm64
|
- linux/arm64
|
||||||
@ -151,19 +175,20 @@ Web SSH currently only support Linux based OS. The following platforms are suppo
|
|||||||
|
|
||||||
### 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
|
./zoraxy -sshlb=true
|
||||||
```
|
```
|
||||||
|
|
||||||
## Sponsor This Project
|
## Sponsor This Project
|
||||||
|
|
||||||
If you like the project and want to support us, please consider a donation. You can use the links below
|
If you like the project and want to support us, please consider a donation. You can use the links below
|
||||||
|
|
||||||
- [tobychui (Primary author)](https://paypal.me/tobychui)
|
- [tobychui (Primary author)](https://paypal.me/tobychui)
|
||||||
- PassiveLemon (Docker compatibility maintainer)
|
- PassiveLemon (Docker compatibility maintainer)
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is open 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.**
|
||||||
|
|
||||||
|
@ -1,19 +1,10 @@
|
|||||||
FROM docker.io/golang:alpine
|
FROM docker.io/golang:alpine AS build
|
||||||
# VERSION comes from the main.yml workflow --build-arg
|
|
||||||
ARG VERSION
|
|
||||||
|
|
||||||
RUN apk add --no-cache bash netcat-openbsd sudo
|
|
||||||
|
|
||||||
RUN mkdir -p /opt/zoraxy/source/ &&\
|
RUN mkdir -p /opt/zoraxy/source/ &&\
|
||||||
mkdir -p /opt/zoraxy/config/ &&\
|
mkdir -p /opt/zoraxy/config/ &&\
|
||||||
mkdir -p /usr/local/bin/
|
mkdir -p /usr/local/bin/
|
||||||
|
|
||||||
COPY entrypoint.sh /opt/zoraxy/
|
RUN chmod -R 770 /opt/zoraxy/
|
||||||
|
|
||||||
RUN chmod -R 755 /opt/zoraxy/ &&\
|
|
||||||
chmod +x /opt/zoraxy/entrypoint.sh
|
|
||||||
|
|
||||||
VOLUME [ "/opt/zoraxy/config/" ]
|
|
||||||
|
|
||||||
# If you build it yourself, you will need to add the src directory into the docker directory.
|
# If you build it yourself, you will need to add the src directory into the docker directory.
|
||||||
COPY ./src/ /opt/zoraxy/source/
|
COPY ./src/ /opt/zoraxy/source/
|
||||||
@ -24,15 +15,34 @@ RUN go mod tidy &&\
|
|||||||
go build -o /usr/local/bin/zoraxy &&\
|
go build -o /usr/local/bin/zoraxy &&\
|
||||||
rm -r /opt/zoraxy/source/
|
rm -r /opt/zoraxy/source/
|
||||||
|
|
||||||
RUN chmod +x /usr/local/bin/zoraxy
|
RUN chmod 755 /usr/local/bin/zoraxy &&\
|
||||||
|
chmod +x /usr/local/bin/zoraxy
|
||||||
|
|
||||||
|
FROM docker.io/alpine:latest
|
||||||
|
|
||||||
|
RUN apk add --no-cache bash netcat-openbsd sudo
|
||||||
|
|
||||||
|
COPY --from=build /usr/local/bin/zoraxy /usr/local/bin/zoraxy
|
||||||
|
COPY --from=build /opt/zoraxy/config/ /opt/zoraxy/config
|
||||||
|
|
||||||
|
VOLUME [ "/opt/zoraxy/config/" ]
|
||||||
|
|
||||||
WORKDIR /opt/zoraxy/config/
|
WORKDIR /opt/zoraxy/config/
|
||||||
|
|
||||||
ENV VERSION=$VERSION
|
ENV AUTORENEW="86400"
|
||||||
|
ENV EARLYRENEW="30"
|
||||||
|
ENV FASTGEOIP="false"
|
||||||
|
ENV MDNS="true"
|
||||||
|
ENV MDNSNAME="''"
|
||||||
|
ENV NOAUTH="false"
|
||||||
|
ENV PORT="8000"
|
||||||
|
ENV SSHLB="false"
|
||||||
|
ENV VERSION="false"
|
||||||
|
ENV WEBFM="true"
|
||||||
|
ENV WEBROOT="./www"
|
||||||
|
ENV ZTAUTH="''"
|
||||||
|
ENV ZTPORT="9993"
|
||||||
|
|
||||||
ENV ARGS="-noauth=false"
|
ENTRYPOINT "zoraxy" "-docker=true" "-autorenew=${AUTORENEW}" "-earlyrenew=${EARLYRENEW}" "-fastgeoip=${FASTGEOIP}" "-mdns=${MDNS}" "-mdnsname=${MDNSNAME}" "-noauth=${NOAUTH}" "-port=:${PORT}" "-sshlb=${SSHLB}" "-version=${VERSION}" "-webfm=${WEBFM}" "-webroot=${WEBROOT}" "-ztauth=${ZTAUTH}" "-ztport=${ZTPORT}"
|
||||||
|
|
||||||
ENTRYPOINT ["/opt/zoraxy/entrypoint.sh"]
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=5s --timeout=5s --retries=2 CMD nc -vz 127.0.0.1 8000 || exit 1
|
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 CMD nc -vz 127.0.0.1 $PORT || exit 1
|
||||||
|
@ -10,14 +10,15 @@ Although not required, it is recommended to give Zoraxy a dedicated location on
|
|||||||
|
|
||||||
You may also need to portforward your 80/443 to allow http and https traffic. If you are accessing the interface from outside of the local network, you may also need to forward your management port. If you know how to do this, great! If not, find the manufacturer of your router and search on how to do that. There are too many to be listed here. </br>
|
You may also need to portforward your 80/443 to allow http and https traffic. If you are accessing the interface from outside of the local network, you may also need to forward your management port. If you know how to do this, great! If not, find the manufacturer of your router and search on how to do that. There are too many to be listed here. </br>
|
||||||
|
|
||||||
|
The examples below are not exactly how it should be set up, rather they give a general idea of usage.
|
||||||
|
|
||||||
### Using Docker run </br>
|
### Using Docker run </br>
|
||||||
```
|
```
|
||||||
docker run -d --name (container name) -p (ports) -v (path to storage directory):/opt/zoraxy/data/ -e ARGS='(your arguments)' zoraxydocker/zoraxy:latest
|
docker run -d --name (container name) -p 80:80 -p 443:443 -p (management external):(management internal) -v (path to storage directory):/opt/zoraxy/data/ -e (flag)="(value)" zoraxydocker/zoraxy:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Docker Compose </br>
|
### Using Docker Compose </br>
|
||||||
```yml
|
```yml
|
||||||
version: '3.3'
|
|
||||||
services:
|
services:
|
||||||
zoraxy-docker:
|
zoraxy-docker:
|
||||||
image: zoraxydocker/zoraxy:latest
|
image: zoraxydocker/zoraxy:latest
|
||||||
@ -25,11 +26,11 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
- 443:443
|
- 443:443
|
||||||
- (external):8000
|
- (management external):(management internal)
|
||||||
volumes:
|
volumes:
|
||||||
- (path to storage directory):/opt/zoraxy/config/
|
- (path to storage directory):/opt/zoraxy/config/
|
||||||
environment:
|
environment:
|
||||||
ARGS: '(your arguments)'
|
(flag): "(value)"
|
||||||
```
|
```
|
||||||
|
|
||||||
| Operator | Need | Details |
|
| Operator | Need | Details |
|
||||||
@ -38,18 +39,22 @@ services:
|
|||||||
| `--name (container name)` | No | Sets the name of the container to the following word. You can change this to whatever you want. |
|
| `--name (container name)` | No | Sets the name of the container to the following word. You can change this to whatever you want. |
|
||||||
| `-p (ports)` | Yes | Depending on how your network is setup, you may need to portforward 80, 443, and the management port. |
|
| `-p (ports)` | Yes | Depending on how your network is setup, you may need to portforward 80, 443, and the management port. |
|
||||||
| `-v (path to storage directory):/opt/zoraxy/config/` | Recommend | Sets the folder that holds your files. This should be the place you just chose. By default, it will create a Docker volume for the files for persistency but they will not be accessible. |
|
| `-v (path to storage directory):/opt/zoraxy/config/` | Recommend | Sets the folder that holds your files. This should be the place you just chose. By default, it will create a Docker volume for the files for persistency but they will not be accessible. |
|
||||||
| `-e ARGS='(your arguments)'` | No | Sets the arguments to run Zoraxy with. Enter them as you would normally. By default, it is ran with `-noauth=false` but <b>you cannot change the management port.</b> This is required for the healthcheck to work. |
|
| `-v /var/run/docker.sock:/var/run/docker.sock` | No | Used for autodiscovery. |
|
||||||
| `zoraxydocker/zoraxy:latest` | Yes | The repository on Docker hub. By default, it is the latest version that I have published. |
|
| `-v /etc/localtime:/etc/localtime` | No | Uses the hosts time in the container. |
|
||||||
|
| `-e (flag)="(value)"` | No | Arguments to run Zoraxy with. They are simply just capitalized Zoraxy flags. `-docker=true` is always set by default. See examples below. |
|
||||||
|
| `zoraxydocker/zoraxy:latest` | Yes | The repository on Docker hub. By default, it is the latest version that is published. |
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Docker usage of the port flag should not include the colon. Ex: `-e PORT="8000"` for Docker run and `PORT: "8000"` for Docker compose.
|
||||||
|
|
||||||
## Examples: </br>
|
## Examples: </br>
|
||||||
### Docker Run </br>
|
### Docker Run </br>
|
||||||
```
|
```
|
||||||
docker run -d --name zoraxy -p 80:80 -p 443:443 -p 8005:8000/tcp -v /home/docker/Containers/Zoraxy:/opt/zoraxy/config/ -e ARGS='-noauth=false' zoraxydocker/zoraxy:latest
|
docker run -d --name zoraxy -p 80:80 -p 443:443 -p 8005:8005 -v /home/docker/Containers/Zoraxy:/opt/zoraxy/config/ -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime -e PORT="8005" -e FASTGEOIP="true" zoraxydocker/zoraxy:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Compose </br>
|
### Docker Compose </br>
|
||||||
```yml
|
```yml
|
||||||
version: '3.3'
|
|
||||||
services:
|
services:
|
||||||
zoraxy-docker:
|
zoraxy-docker:
|
||||||
image: zoraxydocker/zoraxy:latest
|
image: zoraxydocker/zoraxy:latest
|
||||||
@ -57,9 +62,12 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
- 443:443
|
- 443:443
|
||||||
- 8005:8000/tcp
|
- 8005:8005
|
||||||
volumes:
|
volumes:
|
||||||
- /home/docker/Containers/Zoraxy:/opt/zoraxy/config/
|
- /home/docker/Containers/Zoraxy:/opt/zoraxy/config/
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- /etc/localtime:/etc/localtime
|
||||||
environment:
|
environment:
|
||||||
ARGS: '-noauth=false'
|
PORT: "8005"
|
||||||
|
FASTGEOIP: "true"
|
||||||
```
|
```
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
echo "Zoraxy version $VERSION"
|
|
||||||
|
|
||||||
zoraxy -port=:8000 ${ARGS}
|
|
BIN
docs/img/bg.png
Before Width: | Height: | Size: 4.5 MiB |
BIN
docs/img/bg2.png
Before Width: | Height: | Size: 9.4 MiB |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m772-635-43-100-104-46 104-45 43-95 43 95 104 45-104 46-43 100Zm0 595-43-96-104-45 104-45 43-101 43 101 104 45-104 45-43 96ZM333-194l-92-197-201-90 201-90 92-196 93 196 200 90-200 90-93 197Zm0-148 48-96 98-43-98-43-48-96-47 96-99 43 99 43 47 96Zm0-139Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m772-635-43-100-104-46 104-45 43-95 43 95 104 45-104 46-43 100Zm0 595-43-96-104-45 104-45 43-101 43 101 104 45-104 45-43 96ZM333-194l-92-197-201-90 201-90 92-196 93 196 200 90-200 90-93 197Zm0-148 48-96 98-43-98-43-48-96-47 96-99 43 99 43 47 96Zm0-139Z"/></svg>
|
Before Width: | Height: | Size: 358 B After Width: | Height: | Size: 377 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M280-453h400v-60H280v60ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>
|
<svg fill="#ff7a7a" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M280-453h400v-60H280v60ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>
|
Before Width: | Height: | Size: 433 B After Width: | Height: | Size: 448 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M320-242 80-482l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M320-242 80-482l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"/></svg>
|
Before Width: | Height: | Size: 209 B After Width: | Height: | Size: 227 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-80v-270h120v-160h210v-100H330v-270h300v270H510v100h210v160h120v270H540v-270h120v-100H300v100h120v270H120Zm270-590h180v-150H390v150ZM180-140h180v-150H180v150Zm420 0h180v-150H600v150ZM480-670ZM360-290Zm240 0Z"/></svg>
|
<svg fill="#919191" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-80v-270h120v-160h210v-100H330v-270h300v270H510v100h210v160h120v270H540v-270h120v-100H300v100h120v270H120Zm270-590h180v-150H390v150ZM180-140h180v-150H180v150Zm420 0h180v-150H600v150ZM480-670ZM360-290Zm240 0Z"/></svg>
|
Before Width: | Height: | Size: 317 B After Width: | Height: | Size: 332 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>
|
Before Width: | Height: | Size: 224 B After Width: | Height: | Size: 242 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M356-120H180q-24 0-42-18t-18-42v-176q44-5 75.5-34.5T227-463q0-43-31.5-72.5T120-570v-176q0-24 18-42t42-18h177q11-40 39.5-67t68.5-27q40 0 68.5 27t39.5 67h173q24 0 42 18t18 42v173q40 11 65.5 41.5T897-461q0 40-25.5 67T806-356v176q0 24-18 42t-42 18H570q-5-48-35.5-77.5T463-227q-41 0-71.5 29.5T356-120Zm-176-60h130q25-61 69.888-84 44.888-23 83-23T546-264q45 23 70 84h130v-235h45q20 0 33-13t13-33q0-20-13-33t-33-13h-45v-239H511v-48q0-20-13-33t-33-13q-20 0-33 13t-13 33v48H180v130q48.15 17.817 77.575 59.686Q287-514.445 287-462.777 287-412 257.5-370T180-310v130Zm329-330Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M356-120H180q-24 0-42-18t-18-42v-176q44-5 75.5-34.5T227-463q0-43-31.5-72.5T120-570v-176q0-24 18-42t42-18h177q11-40 39.5-67t68.5-27q40 0 68.5 27t39.5 67h173q24 0 42 18t18 42v173q40 11 65.5 41.5T897-461q0 40-25.5 67T806-356v176q0 24-18 42t-42 18H570q-5-48-35.5-77.5T463-227q-41 0-71.5 29.5T356-120Zm-176-60h130q25-61 69.888-84 44.888-23 83-23T546-264q45 23 70 84h130v-235h45q20 0 33-13t13-33q0-20-13-33t-33-13h-45v-239H511v-48q0-20-13-33t-33-13q-20 0-33 13t-13 33v48H180v130q48.15 17.817 77.575 59.686Q287-514.445 287-462.777 287-412 257.5-370T180-310v130Zm329-330Z"/></svg>
|
Before Width: | Height: | Size: 669 B After Width: | Height: | Size: 688 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M273-160 80-353l193-193 42 42-121 121h316v60H194l121 121-42 42Zm414-254-42-42 121-121H450v-60h316L645-758l42-42 193 193-193 193Z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48" fill="#fcba03"><path d="M273-160 80-353l193-193 42 42-121 121h316v60H194l121 121-42 42Zm414-254-42-42 121-121H450v-60h316L645-758l42-42 193 193-193 193Z"/></svg>
|
Before Width: | Height: | Size: 234 B After Width: | Height: | Size: 249 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M700-160v-410H275l153 153-42 43-226-226 226-226 42 42-153 154h485v470h-60Z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" fill="#0388fc" height="48" viewBox="0 -960 960 960" width="48"><path d="M700-160v-410H275l153 153-42 43-226-226 226-226 42 42-153 154h485v470h-60Z"/></svg>
|
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 195 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M197-197q-54-54-85.5-126.5T80-480q0-84 31.5-156.5T197-763l43 43q-46 46-73 107.5T140-480q0 71 26.5 132T240-240l-43 43Zm113-113q-32-32-51-75.5T240-480q0-51 19-94.5t51-75.5l43 43q-24 24-38.5 56.5T300-480q0 38 14 70t39 57l-43 43Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-43-43q24-24 38.5-56.5T660-480q0-38-14-70t-39-57l43-43q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-43-43q46-46 73-107.5T820-480q0-71-26.5-132T720-720l43-43q54 55 85.5 127.5T880-480q0 83-31.5 155.5T763-197Z"/></svg>
|
<svg fill="#83f2c4" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M197-197q-54-54-85.5-126.5T80-480q0-84 31.5-156.5T197-763l43 43q-46 46-73 107.5T140-480q0 71 26.5 132T240-240l-43 43Zm113-113q-32-32-51-75.5T240-480q0-51 19-94.5t51-75.5l43 43q-24 24-38.5 56.5T300-480q0 38 14 70t39 57l-43 43Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-43-43q24-24 38.5-56.5T660-480q0-38-14-70t-39-57l43-43q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-43-43q46-46 73-107.5T820-480q0-71-26.5-132T720-720l43-43q54 55 85.5 127.5T880-480q0 83-31.5 155.5T763-197Z"/></svg>
|
Before Width: | Height: | Size: 652 B After Width: | Height: | Size: 667 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M345-377h391L609-548 506-413l-68-87-93 123Zm-85 177q-24 0-42-18t-18-42v-560q0-24 18-42t42-18h560q24 0 42 18t18 42v560q0 24-18 42t-42 18H260Zm0-60h560v-560H260v560ZM140-80q-24 0-42-18t-18-42v-620h60v620h620v60H140Zm120-740v560-560Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M345-377h391L609-548 506-413l-68-87-93 123Zm-85 177q-24 0-42-18t-18-42v-560q0-24 18-42t42-18h560q24 0 42 18t18 42v560q0 24-18 42t-42 18H260Zm0-60h560v-560H260v560ZM140-80q-24 0-42-18t-18-42v-620h60v620h620v60H140Zm120-740v560-560Z"/></svg>
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 355 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg>
|
<svg fill="#edf230" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg>
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
BIN
docs/img/screenshots/1.png
Normal file
After Width: | Height: | Size: 202 KiB |
Before Width: | Height: | Size: 42 KiB |
BIN
docs/img/screenshots/10.png
Normal file
After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 62 KiB |
BIN
docs/img/screenshots/2.png
Normal file
After Width: | Height: | Size: 146 KiB |
Before Width: | Height: | Size: 32 KiB |
BIN
docs/img/screenshots/3.png
Normal file
After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 28 KiB |
BIN
docs/img/screenshots/4.png
Normal file
After Width: | Height: | Size: 203 KiB |
Before Width: | Height: | Size: 41 KiB |
BIN
docs/img/screenshots/5.png
Normal file
After Width: | Height: | Size: 123 KiB |
Before Width: | Height: | Size: 46 KiB |
BIN
docs/img/screenshots/6.png
Normal file
After Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 48 KiB |
BIN
docs/img/screenshots/7.png
Normal file
After Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 55 KiB |
BIN
docs/img/screenshots/8.png
Normal file
After Width: | Height: | Size: 185 KiB |
Before Width: | Height: | Size: 68 KiB |
BIN
docs/img/screenshots/9.png
Normal file
After Width: | Height: | Size: 867 KiB |
Before Width: | Height: | Size: 153 KiB |
@ -8,7 +8,7 @@
|
|||||||
<meta name="author" content="tobychui">
|
<meta name="author" content="tobychui">
|
||||||
|
|
||||||
<!-- HTML Meta Tags -->
|
<!-- HTML Meta Tags -->
|
||||||
<title>Cluster Proxy Gateway | Zoraxy</title>
|
<title>Reverse Proxy Server | Zoraxy</title>
|
||||||
<meta name="description" content="A reverse proxy server and cluster network gateway for noobs">
|
<meta name="description" content="A reverse proxy server and cluster network gateway for noobs">
|
||||||
|
|
||||||
<!-- Facebook Meta Tags -->
|
<!-- Facebook Meta Tags -->
|
||||||
@ -74,21 +74,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="right-content">
|
<div class="right-content">
|
||||||
<!-- Hero Banner Section -->
|
<!-- Hero Banner Section -->
|
||||||
<div class="dot-container">
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div class="dot"></div>
|
|
||||||
</div>
|
|
||||||
<div class="headbanner"></div>
|
<div class="headbanner"></div>
|
||||||
<div id="home" class="herotext">
|
<div id="home" class="herotext">
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
<div class="bannerHeaderWrapper">
|
<div class="bannerHeaderWrapper">
|
||||||
<h1 class="bannerHeader">Zoraxy</h1>
|
<h1 class="bannerHeader">Zoraxy</h1>
|
||||||
<p class="bannerSubheader">All in one homelab network routing solution</p>
|
<div class="ui divider"></div><br>
|
||||||
|
<p class="bannerSubheader">Beyond Reverse Proxy: Your Ultimate Homelab Network Tool</p>
|
||||||
</div>
|
</div>
|
||||||
<br><br>
|
<br><br>
|
||||||
<a class="ui black big button" href="#features">Learn More</a>
|
<a class="ui basic big button" style="background-color: white;" href="#features"><i class="ui blue arrow down icon"></i> Learn More</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
<table class="ui very basic collapsing unstackable celled table">
|
<table class="ui very basic collapsing unstackable celled table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -126,6 +121,22 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="wavesWrapper">
|
||||||
|
<!-- CSS waves-->
|
||||||
|
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
|
||||||
|
<defs>
|
||||||
|
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
|
||||||
|
</defs>
|
||||||
|
<g class="parallax">
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(255,255,255,0.7" />
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(255,255,255,0.5)" />
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(255,255,255,0.3)" />
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="7" fill="#fff" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Features -->
|
<!-- Features -->
|
||||||
@ -148,7 +159,7 @@
|
|||||||
Reverse Proxy
|
Reverse Proxy
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</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>
|
||||||
|
|
||||||
<div class="four wide column featureItem">
|
<div class="four wide column featureItem">
|
||||||
@ -158,7 +169,7 @@
|
|||||||
Redirection
|
Redirection
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</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>
|
||||||
|
|
||||||
<div class="four wide column featureItem">
|
<div class="four wide column featureItem">
|
||||||
@ -168,7 +179,7 @@
|
|||||||
Geo-IP & Blacklist
|
Geo-IP & Blacklist
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</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>
|
||||||
|
|
||||||
<div class="four wide column featureItem">
|
<div class="four wide column featureItem">
|
||||||
@ -189,7 +200,7 @@
|
|||||||
Web SSH
|
Web SSH
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</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>
|
||||||
|
|
||||||
<div class="four wide column featureItem">
|
<div class="four wide column featureItem">
|
||||||
@ -199,7 +210,7 @@
|
|||||||
Real Time Statistics
|
Real Time Statistics
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</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>
|
||||||
|
|
||||||
<div class="four wide column featureItem">
|
<div class="four wide column featureItem">
|
||||||
@ -209,7 +220,7 @@
|
|||||||
Scanner & Utilities
|
Scanner & Utilities
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</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>
|
||||||
|
|
||||||
<div class="four wide column featureItem">
|
<div class="four wide column featureItem">
|
||||||
@ -219,7 +230,7 @@
|
|||||||
Open Source
|
Open Source
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -240,34 +251,34 @@
|
|||||||
|
|
||||||
<div class="ui three column stackable grid">
|
<div class="ui three column stackable grid">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/1.webp" target="_blank"><img src="img/screenshots/1.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/1.png" target="_blank"><img src="img/screenshots/1.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/2.webp" target="_blank"><img src="img/screenshots/2.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/2.png" target="_blank"><img src="img/screenshots/2.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/3.webp" target="_blank"><img src="img/screenshots/3.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/3.png" target="_blank"><img src="img/screenshots/3.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/4.webp" target="_blank"><img src="img/screenshots/4.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/4.png" target="_blank"><img src="img/screenshots/4.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/5.webp" target="_blank"><img src="img/screenshots/5.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/5.png" target="_blank"><img src="img/screenshots/5.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/6.webp" target="_blank"><img src="img/screenshots/6.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/6.png" target="_blank"><img src="img/screenshots/6.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/7.webp" target="_blank"><img src="img/screenshots/7.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/7.png" target="_blank"><img src="img/screenshots/7.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/8.webp" target="_blank"><img src="img/screenshots/8.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/8.png" target="_blank"><img src="img/screenshots/8.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/9.webp" target="_blank"><img src="img/screenshots/9.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/9.png" target="_blank"><img src="img/screenshots/9.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/10.webp" target="_blank"><img src="img/screenshots/10.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/10.png" target="_blank"><img src="img/screenshots/10.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
123
docs/style.css
@ -1,5 +1,5 @@
|
|||||||
body{
|
body{
|
||||||
background: #f6f6f6 !important;
|
background: #ffffff !important;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
@ -18,7 +18,7 @@ body{
|
|||||||
.left-menu {
|
.left-menu {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
background-color: #ffffff;
|
background-color: #fcfcfc;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding-top: 1.5em;
|
padding-top: 1.5em;
|
||||||
}
|
}
|
||||||
@ -48,17 +48,19 @@ body{
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 1px solid #f6f6f6;
|
border-bottom: 1px solid #f6f6f6;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-right: 0.4em solid var(--themeTextColor);
|
|
||||||
transition: border-left ease-in-out 0.1s, background-color ease-in-out 0.1s;
|
transition: border-left ease-in-out 0.1s, background-color ease-in-out 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item.active{
|
.menu-item.active{
|
||||||
border-right: 0.4em solid var(--themeSkyblueColorDecondary);
|
background: linear-gradient(60deg, rgba(84, 58, 183, 0.3) 0%, rgba(0, 172, 193, 0.3) 100%);
|
||||||
background-color: #f0f8ff;
|
}
|
||||||
|
|
||||||
|
.menu-item .item-icon{
|
||||||
|
fill: #fcfcfc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item:hover{
|
.menu-item:hover{
|
||||||
border-right: 0.4em solid var(--themeSkyblueColorDecondary);
|
background: rgba(35,35,35,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item img{
|
.menu-item img{
|
||||||
@ -69,18 +71,6 @@ body{
|
|||||||
|
|
||||||
|
|
||||||
/* Head banner */
|
/* Head banner */
|
||||||
.headbanner{
|
|
||||||
background-image: url('img/bg.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: right center;
|
|
||||||
background-size: auto 100%;
|
|
||||||
position:absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100%;
|
|
||||||
z-index: -100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.herotext{
|
.herotext{
|
||||||
padding-top: 15em;
|
padding-top: 15em;
|
||||||
@ -91,11 +81,13 @@ body{
|
|||||||
.bannerHeader{
|
.bannerHeader{
|
||||||
font-size: 8em;
|
font-size: 8em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bannerSubheader{
|
.bannerSubheader{
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
|
color: #ebebeb;
|
||||||
margin-top: -20px;
|
margin-top: -20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +96,21 @@ body{
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#home{
|
||||||
|
background: linear-gradient(60deg, rgba(84,58,183,1) 0%, rgba(0,172,193,1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#home .table th, #home .table h4{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#home .table h4 .content, #home .table h4 .sub.header{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#home .table td a{
|
||||||
|
color: #d6ddff;
|
||||||
|
}
|
||||||
|
|
||||||
/* features */
|
/* features */
|
||||||
#features{
|
#features{
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
@ -173,56 +180,58 @@ body{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Decorative Animation */
|
/*
|
||||||
.dot-container {
|
Waves CSS
|
||||||
display: flex;
|
*/
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
#wavesWrapper{
|
||||||
height: 40px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 2em;
|
bottom: 0;
|
||||||
left: 2em;
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot {
|
.waves {
|
||||||
width: 6px;
|
position:relative;
|
||||||
height: 6px;
|
width: 100%;
|
||||||
border-radius: 50%;
|
height:15vh;
|
||||||
background-color: #d9d9d9;
|
margin-bottom:-7px; /*Fix for safari gap*/
|
||||||
margin-right: 6px;
|
min-height:100px;
|
||||||
animation-name: dot-animation;
|
max-height:150px;
|
||||||
animation-duration: 4s;
|
|
||||||
animation-timing-function: ease-in-out;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot:nth-child(1) {
|
|
||||||
animation-delay: 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot:nth-child(2) {
|
.parallax > use {
|
||||||
animation-delay: 1s;
|
animation: move-forever 25s cubic-bezier(.55,.5,.45,.5) infinite;
|
||||||
}
|
}
|
||||||
|
.parallax > use:nth-child(1) {
|
||||||
.dot:nth-child(3) {
|
animation-delay: -8s;
|
||||||
animation-delay: 2s;
|
animation-duration: 28s;
|
||||||
}
|
}
|
||||||
|
.parallax > use:nth-child(2) {
|
||||||
.dot:nth-child(4) {
|
animation-delay: -12s;
|
||||||
animation-delay: 3s;
|
animation-duration: 40s;
|
||||||
}
|
}
|
||||||
|
.parallax > use:nth-child(3) {
|
||||||
@keyframes dot-animation {
|
animation-delay: -16s;
|
||||||
|
animation-duration: 52s;
|
||||||
|
}
|
||||||
|
.parallax > use:nth-child(4) {
|
||||||
|
animation-delay: -20s;
|
||||||
|
animation-duration: 80s;
|
||||||
|
}
|
||||||
|
@keyframes move-forever {
|
||||||
0% {
|
0% {
|
||||||
background-color: #d9d9d9;
|
transform: translate3d(-90px,0,0);
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-color: #a9d1f3;
|
|
||||||
transform: scale(1.5);
|
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
background-color: #d9d9d9;
|
transform: translate3d(85px,0,0);
|
||||||
transform: scale(1);
|
}
|
||||||
|
}
|
||||||
|
/*Shrinking for mobile*/
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.waves {
|
||||||
|
height:40px;
|
||||||
|
min-height:40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
26
example/README.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# 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).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Other Templates
|
||||||
|
|
||||||
|
There are a few pre-built templates that works with Zoraxy where you can find in the `other-templates` folder. Copy the folder into `www` and rename the folder to `templates` to active them.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
It is worth mentioning that the uwu icons for not-found and access-denied are created by @SAWARATSUKI
|
185
example/other-templates/templates_cf/blacklist.html
Normal file
154
example/other-templates/templates_cf/notfound.html
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="theme-color" content="#4b75ff">
|
||||||
|
<link rel="icon" type="image/png" href="img/small_icon.png"/>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.css">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
|
||||||
|
<title>404 - Host Not Found</title>
|
||||||
|
<style>
|
||||||
|
h1, h2, h3, h4, h5, p, a, span, .ui.list .item{
|
||||||
|
font-family: 'Noto Sans TC', sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
color: rgb(88, 88, 88)
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram{
|
||||||
|
background-color: #ebebeb;
|
||||||
|
padding-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagramHeader{
|
||||||
|
margin-top: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:512px) {
|
||||||
|
.widescreenOnly{
|
||||||
|
display: none !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.four.wide.column:not(.widescreenOnly){
|
||||||
|
width: 50% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.grid{
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<br><br>
|
||||||
|
<div class="ui container">
|
||||||
|
<h1 style="font-size: 4rem;">Error 404</h1>
|
||||||
|
<p style="font-size: 2rem; margin-bottom: 0.4em;">Target Host Not Found</p>
|
||||||
|
<small id="timestamp"></small>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
</div>
|
||||||
|
<div class="diagram">
|
||||||
|
<div class="ui text container">
|
||||||
|
<div class="ui grid">
|
||||||
|
<div class="four wide column widescreenOnly" align="center">
|
||||||
|
<svg version="1.1" id="client_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||||
|
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
|
||||||
|
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
|
||||||
|
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
|
||||||
|
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
|
||||||
|
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
|
||||||
|
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
|
||||||
|
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
|
||||||
|
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
|
||||||
|
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
|
||||||
|
82.989,143.204 "/>
|
||||||
|
</svg>
|
||||||
|
<small>You</small>
|
||||||
|
<h2 class="diagramHeader">Browser</h2>
|
||||||
|
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||||
|
</div>
|
||||||
|
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||||
|
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||||
|
</div>
|
||||||
|
<div class="four wide column widescreenOnly" align="center">
|
||||||
|
<svg version="1.1" id="cloud_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||||
|
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
|
||||||
|
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
|
||||||
|
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
|
||||||
|
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
|
||||||
|
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
|
||||||
|
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
|
||||||
|
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
|
||||||
|
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
|
||||||
|
83.971,148.123 "/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<small>Gateway Node</small>
|
||||||
|
<h2 class="diagramHeader">Reverse Proxy</h2>
|
||||||
|
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||||
|
</div>
|
||||||
|
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||||
|
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||||
|
</div>
|
||||||
|
<div class="four wide column" align="center">
|
||||||
|
<svg version="1.1" id="host_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||||
|
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
|
||||||
|
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
|
||||||
|
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
|
||||||
|
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
|
||||||
|
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
|
||||||
|
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
|
||||||
|
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
|
||||||
|
</svg>
|
||||||
|
<small id="host"></small>
|
||||||
|
<h2 class="diagramHeader">Host</h2>
|
||||||
|
<p style="font-weight: 500; color: #bd2426;">Not Found</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui stackable grid">
|
||||||
|
<div class="eight wide column">
|
||||||
|
<h1>What happend?</h1>
|
||||||
|
<p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p>
|
||||||
|
</div>
|
||||||
|
<div class="eight wide column">
|
||||||
|
<h1>What can I do?</h1>
|
||||||
|
<h5 style="font-weight: 500;">If you are a visitor of this website: </h5>
|
||||||
|
<p>Please try again in a few minutes</p>
|
||||||
|
<h5 style="font-weight: 500;">If you are the owner of this website:</h5>
|
||||||
|
<div class="ui bulleted list">
|
||||||
|
<div class="item">Check if the proxy rules that match this hostname exists</div>
|
||||||
|
<div class="item">Visit the Reverse Proxy management interface to correct any setting errors</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui container" style="color: grey; font-size: 90%">
|
||||||
|
<p>Powered by Zoraxy</p>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$("#timestamp").text(new Date());
|
||||||
|
$("#host").text(location.href);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
185
example/other-templates/templates_cf/whitelist.html
Normal file
52
example/other-templates/templates_uwu/blacklist.html
Normal file
42
example/other-templates/templates_uwu/notfound.html
Normal file
52
example/other-templates/templates_uwu/whitelist.html
Normal file
229
example/www/html/index.html
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Zoraxy Firework!</title>
|
||||||
|
<style>
|
||||||
|
body{
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js" integrity="sha512-aNMyYYxdIxIaot0Y1/PLuEu3eipGCmsEUBrUq+7aVyPGMFH8z0eTP0tkqAvv34fzN6z+201d3T8HPb1svWSKHQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="c"></canvas>
|
||||||
|
<script>
|
||||||
|
var c = document.getElementById("c");
|
||||||
|
var ctx = c.getContext("2d");
|
||||||
|
var cH;
|
||||||
|
var cW;
|
||||||
|
var bgColor = "#FF6138";
|
||||||
|
var animations = [];
|
||||||
|
var circles = [];
|
||||||
|
|
||||||
|
var colorPicker = (function() {
|
||||||
|
var colors = ["#FF6138", "#FFBE53", "#2980B9", "#FCFCFC", "#282741"];
|
||||||
|
var index = 0;
|
||||||
|
function next() {
|
||||||
|
index = index++ < colors.length-1 ? index : 0;
|
||||||
|
return colors[index];
|
||||||
|
}
|
||||||
|
function current() {
|
||||||
|
return colors[index]
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
next: next,
|
||||||
|
current: current
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
function removeAnimation(animation) {
|
||||||
|
var index = animations.indexOf(animation);
|
||||||
|
if (index > -1) animations.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcPageFillRadius(x, y) {
|
||||||
|
var l = Math.max(x - 0, cW - x);
|
||||||
|
var h = Math.max(y - 0, cH - y);
|
||||||
|
return Math.sqrt(Math.pow(l, 2) + Math.pow(h, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addClickListeners() {
|
||||||
|
document.addEventListener("touchstart", handleEvent);
|
||||||
|
document.addEventListener("mousedown", handleEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleEvent(e) {
|
||||||
|
if (e.touches) {
|
||||||
|
e.preventDefault();
|
||||||
|
e = e.touches[0];
|
||||||
|
}
|
||||||
|
var currentColor = colorPicker.current();
|
||||||
|
var nextColor = colorPicker.next();
|
||||||
|
var targetR = calcPageFillRadius(e.pageX, e.pageY);
|
||||||
|
var rippleSize = Math.min(200, (cW * .4));
|
||||||
|
var minCoverDuration = 750;
|
||||||
|
|
||||||
|
var pageFill = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
r: 0,
|
||||||
|
fill: nextColor
|
||||||
|
});
|
||||||
|
var fillAnimation = anime({
|
||||||
|
targets: pageFill,
|
||||||
|
r: targetR,
|
||||||
|
duration: Math.max(targetR / 2 , minCoverDuration ),
|
||||||
|
easing: "easeOutQuart",
|
||||||
|
complete: function(){
|
||||||
|
bgColor = pageFill.fill;
|
||||||
|
removeAnimation(fillAnimation);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var ripple = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
r: 0,
|
||||||
|
fill: currentColor,
|
||||||
|
stroke: {
|
||||||
|
width: 3,
|
||||||
|
color: currentColor
|
||||||
|
},
|
||||||
|
opacity: 1
|
||||||
|
});
|
||||||
|
var rippleAnimation = anime({
|
||||||
|
targets: ripple,
|
||||||
|
r: rippleSize,
|
||||||
|
opacity: 0,
|
||||||
|
easing: "easeOutExpo",
|
||||||
|
duration: 900,
|
||||||
|
complete: removeAnimation
|
||||||
|
});
|
||||||
|
|
||||||
|
var particles = [];
|
||||||
|
for (var i=0; i<32; i++) {
|
||||||
|
var particle = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
fill: currentColor,
|
||||||
|
r: anime.random(24, 48)
|
||||||
|
})
|
||||||
|
particles.push(particle);
|
||||||
|
}
|
||||||
|
var particlesAnimation = anime({
|
||||||
|
targets: particles,
|
||||||
|
x: function(particle){
|
||||||
|
return particle.x + anime.random(rippleSize, -rippleSize);
|
||||||
|
},
|
||||||
|
y: function(particle){
|
||||||
|
return particle.y + anime.random(rippleSize * 1.15, -rippleSize * 1.15);
|
||||||
|
},
|
||||||
|
r: 0,
|
||||||
|
easing: "easeOutExpo",
|
||||||
|
duration: anime.random(1000,1300),
|
||||||
|
complete: removeAnimation
|
||||||
|
});
|
||||||
|
animations.push(fillAnimation, rippleAnimation, particlesAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extend(a, b){
|
||||||
|
for(var key in b) {
|
||||||
|
if(b.hasOwnProperty(key)) {
|
||||||
|
a[key] = b[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Circle = function(opts) {
|
||||||
|
extend(this, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
Circle.prototype.draw = function() {
|
||||||
|
ctx.globalAlpha = this.opacity || 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
|
||||||
|
if (this.stroke) {
|
||||||
|
ctx.strokeStyle = this.stroke.color;
|
||||||
|
ctx.lineWidth = this.stroke.width;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
if (this.fill) {
|
||||||
|
ctx.fillStyle = this.fill;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var animate = anime({
|
||||||
|
duration: Infinity,
|
||||||
|
update: function() {
|
||||||
|
ctx.fillStyle = bgColor;
|
||||||
|
ctx.fillRect(0, 0, cW, cH);
|
||||||
|
animations.forEach(function(anim) {
|
||||||
|
anim.animatables.forEach(function(animatable) {
|
||||||
|
animatable.target.draw();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var resizeCanvas = function() {
|
||||||
|
cW = window.innerWidth;
|
||||||
|
cH = window.innerHeight;
|
||||||
|
c.width = cW * devicePixelRatio;
|
||||||
|
c.height = cH * devicePixelRatio;
|
||||||
|
ctx.scale(devicePixelRatio, devicePixelRatio);
|
||||||
|
};
|
||||||
|
|
||||||
|
(function init() {
|
||||||
|
resizeCanvas();
|
||||||
|
if (window.CP) {
|
||||||
|
// CodePen's loop detection was causin' problems
|
||||||
|
// and I have no idea why, so...
|
||||||
|
window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 6000;
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", resizeCanvas);
|
||||||
|
addClickListeners();
|
||||||
|
if (!!window.location.pathname.match(/fullcpgrid/)) {
|
||||||
|
startFauxClicking();
|
||||||
|
}
|
||||||
|
handleInactiveUser();
|
||||||
|
})();
|
||||||
|
|
||||||
|
function handleInactiveUser() {
|
||||||
|
var inactive = setTimeout(function(){
|
||||||
|
fauxClick(cW/2, cH/2);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
function clearInactiveTimeout() {
|
||||||
|
clearTimeout(inactive);
|
||||||
|
document.removeEventListener("mousedown", clearInactiveTimeout);
|
||||||
|
document.removeEventListener("touchstart", clearInactiveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", clearInactiveTimeout);
|
||||||
|
document.addEventListener("touchstart", clearInactiveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startFauxClicking() {
|
||||||
|
setTimeout(function(){
|
||||||
|
fauxClick(anime.random( cW * .2, cW * .8), anime.random(cH * .2, cH * .8));
|
||||||
|
startFauxClicking();
|
||||||
|
}, anime.random(200, 900));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fauxClick(x, y) {
|
||||||
|
var fauxClick = new Event("mousedown");
|
||||||
|
fauxClick.pageX = x;
|
||||||
|
fauxClick.pageY = y;
|
||||||
|
document.dispatchEvent(fauxClick);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
52
example/www/templates/blacklist.html
Normal file
42
example/www/templates/notfound.html
Normal file
52
example/www/templates/whitelist.html
Normal file
Before Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 161 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 390 KiB After Width: | Height: | Size: 75 KiB |
BIN
img/title.png
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 69 KiB |
BIN
img/title.psd
@ -19,7 +19,8 @@ clean:
|
|||||||
|
|
||||||
$(PLATFORMS):
|
$(PLATFORMS):
|
||||||
@echo "Building $(os)/$(arch)"
|
@echo "Building $(os)/$(arch)"
|
||||||
GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) GOARM=6 go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath
|
GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) $(if $(filter linux/arm,$(os)/$(arch)),GOARM=6,) CGO_ENABLED="0" go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath
|
||||||
|
# GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) GOARM=6 go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath
|
||||||
|
|
||||||
|
|
||||||
fixwindows:
|
fixwindows:
|
||||||
|
@ -3,7 +3,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,6 +20,157 @@ import (
|
|||||||
banning / whitelist a specific IP address or country code
|
banning / whitelist a specific IP address or country code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
General Function
|
||||||
|
*/
|
||||||
|
|
||||||
|
func handleListAccessRules(w http.ResponseWriter, r *http.Request) {
|
||||||
|
allAccessRules := accessController.ListAllAccessRules()
|
||||||
|
js, _ := json.Marshal(allAccessRules)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAttachRuleToHost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleid, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
host, err := utils.PostPara(r, "host")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if access rule and proxy rule exists
|
||||||
|
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(host)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid host given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !accessController.AccessRuleExists(ruleid) {
|
||||||
|
utils.SendErrorResponse(w, "access rule not exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update the proxy host acess rule id
|
||||||
|
targetProxyEndpoint.AccessFilterUUID = ruleid
|
||||||
|
targetProxyEndpoint.UpdateToRuntime()
|
||||||
|
err = SaveReverseProxyConfig(targetProxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new access rule, require name and desc only
|
||||||
|
func handleCreateAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleName, err := utils.PostPara(r, "name")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleDesc, _ := utils.PostPara(r, "desc")
|
||||||
|
|
||||||
|
//Filter out injection if any
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
ruleName = p.Sanitize(ruleName)
|
||||||
|
ruleDesc = p.Sanitize(ruleDesc)
|
||||||
|
|
||||||
|
ruleUUID := uuid.New().String()
|
||||||
|
newAccessRule := access.AccessRule{
|
||||||
|
ID: ruleUUID,
|
||||||
|
Name: ruleName,
|
||||||
|
Desc: ruleDesc,
|
||||||
|
BlacklistEnabled: false,
|
||||||
|
WhitelistEnabled: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add it to runtime
|
||||||
|
err = accessController.AddNewAccessRule(&newAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle removing an access rule. All proxy endpoint using this rule will be
|
||||||
|
// set to use the default rule
|
||||||
|
func handleRemoveAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule id given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ruleID == "default" {
|
||||||
|
utils.SendErrorResponse(w, "default access rule cannot be removed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleID = strings.TrimSpace(ruleID)
|
||||||
|
|
||||||
|
//Set all proxy hosts that use this access rule back to using "default"
|
||||||
|
allProxyEndpoints := dynamicProxyRouter.GetProxyEndpointsAsMap()
|
||||||
|
for _, proxyEndpoint := range allProxyEndpoints {
|
||||||
|
if strings.EqualFold(proxyEndpoint.AccessFilterUUID, ruleID) {
|
||||||
|
//This proxy endpoint is using the current access filter.
|
||||||
|
//set it to default
|
||||||
|
proxyEndpoint.AccessFilterUUID = "default"
|
||||||
|
proxyEndpoint.UpdateToRuntime()
|
||||||
|
err = SaveReverseProxyConfig(proxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Unable to save updated proxy endpoint "+proxyEndpoint.RootOrMatchingDomain, err)
|
||||||
|
} else {
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Updated "+proxyEndpoint.RootOrMatchingDomain+" access filter to \"default\"", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove the access rule by ID
|
||||||
|
err = accessController.RemoveAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Access Rule "+ruleID+" removed", nil)
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the name and desc, for other properties use blacklist / whitelist api
|
||||||
|
func handleUpadateAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleName, err := utils.PostPara(r, "name")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleDesc, _ := utils.PostPara(r, "desc")
|
||||||
|
|
||||||
|
//Filter anything weird
|
||||||
|
p := bluemonday.StrictPolicy()
|
||||||
|
ruleName = p.Sanitize(ruleName)
|
||||||
|
ruleDesc = p.Sanitize(ruleDesc)
|
||||||
|
|
||||||
|
err = accessController.UpdateAccessRule(ruleID, ruleName, ruleDesc)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Blacklist Related
|
Blacklist Related
|
||||||
*/
|
*/
|
||||||
@ -26,11 +182,24 @@ func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
|
|||||||
bltype = "country"
|
bltype = "country"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ruleID, err := utils.GetPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
//Use default if not set
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
resulst := []string{}
|
resulst := []string{}
|
||||||
if bltype == "country" {
|
if bltype == "country" {
|
||||||
resulst = geodbStore.GetAllBlacklistedCountryCode()
|
resulst = rule.GetAllBlacklistedCountryCode()
|
||||||
} else if bltype == "ip" {
|
} else if bltype == "ip" {
|
||||||
resulst = geodbStore.GetAllBlacklistedIp()
|
resulst = rule.GetAllBlacklistedIp()
|
||||||
}
|
}
|
||||||
|
|
||||||
js, _ := json.Marshal(resulst)
|
js, _ := json.Marshal(resulst)
|
||||||
@ -45,7 +214,23 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddCountryCodeToBlackList(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.PostPara(r, "comment")
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.AddCountryCodeToBlackList(countryCode, comment)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -57,7 +242,19 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveCountryCodeFromBlackList(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveCountryCodeFromBlackList(countryCode)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -69,7 +266,24 @@ func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddIPToBlackList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.GetPara(r, "comment")
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
rule.AddIPToBlackList(ipAddr, comment)
|
||||||
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -79,23 +293,46 @@ func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveIPFromBlackList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveIPFromBlackList(ipAddr)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
|
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
enable, err := utils.PostPara(r, "enable")
|
enable, _ := utils.PostPara(r, "enable")
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Return the current enabled state
|
ruleID = "default"
|
||||||
currentEnabled := geodbStore.BlacklistEnabled
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable == "" {
|
||||||
|
//enable paramter not set
|
||||||
|
currentEnabled := rule.BlacklistEnabled
|
||||||
js, _ := json.Marshal(currentEnabled)
|
js, _ := json.Marshal(currentEnabled)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else {
|
||||||
if enable == "true" {
|
if enable == "true" {
|
||||||
geodbStore.ToggleBlacklist(true)
|
rule.ToggleBlacklist(true)
|
||||||
} else if enable == "false" {
|
} else if enable == "false" {
|
||||||
geodbStore.ToggleBlacklist(false)
|
rule.ToggleBlacklist(false)
|
||||||
} else {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||||
return
|
return
|
||||||
@ -115,11 +352,22 @@ func handleListWhitelisted(w http.ResponseWriter, r *http.Request) {
|
|||||||
bltype = "country"
|
bltype = "country"
|
||||||
}
|
}
|
||||||
|
|
||||||
resulst := []string{}
|
ruleID, err := utils.GetPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resulst := []*access.WhitelistEntry{}
|
||||||
if bltype == "country" {
|
if bltype == "country" {
|
||||||
resulst = geodbStore.GetAllWhitelistedCountryCode()
|
resulst = rule.GetAllWhitelistedCountryCode()
|
||||||
} else if bltype == "ip" {
|
} else if bltype == "ip" {
|
||||||
resulst = geodbStore.GetAllWhitelistedIp()
|
resulst = rule.GetAllWhitelistedIp()
|
||||||
}
|
}
|
||||||
|
|
||||||
js, _ := json.Marshal(resulst)
|
js, _ := json.Marshal(resulst)
|
||||||
@ -134,7 +382,22 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddCountryCodeToWhitelist(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.PostPara(r, "comment")
|
||||||
|
p := bluemonday.StrictPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
rule.AddCountryCodeToWhitelist(countryCode, comment)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -146,7 +409,18 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveCountryCodeFromWhitelist(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveCountryCodeFromWhitelist(countryCode)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -158,7 +432,23 @@ func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddIPToWhiteList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.PostPara(r, "comment")
|
||||||
|
p := bluemonday.StrictPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
rule.AddIPToWhiteList(ipAddr, comment)
|
||||||
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -168,23 +458,45 @@ func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveIPFromWhiteList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveIPFromWhiteList(ipAddr)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
|
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
enable, err := utils.PostPara(r, "enable")
|
enable, _ := utils.PostPara(r, "enable")
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable == "" {
|
||||||
//Return the current enabled state
|
//Return the current enabled state
|
||||||
currentEnabled := geodbStore.WhitelistEnabled
|
currentEnabled := rule.WhitelistEnabled
|
||||||
js, _ := json.Marshal(currentEnabled)
|
js, _ := json.Marshal(currentEnabled)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else {
|
||||||
if enable == "true" {
|
if enable == "true" {
|
||||||
geodbStore.ToggleWhitelist(true)
|
rule.ToggleWhitelist(true)
|
||||||
} else if enable == "false" {
|
} else if enable == "false" {
|
||||||
geodbStore.ToggleWhitelist(false)
|
rule.ToggleWhitelist(false)
|
||||||
} else {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||||
return
|
return
|
||||||
|
51
src/acme.go
@ -1,9 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -28,7 +28,7 @@ func getRandomPort(minPort int) int {
|
|||||||
|
|
||||||
// init the new ACME instance
|
// init the new ACME instance
|
||||||
func initACME() *acme.ACMEHandler {
|
func initACME() *acme.ACMEHandler {
|
||||||
log.Println("Starting ACME handler")
|
SystemWideLogger.Println("Starting ACME handler")
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
// Generate a random port above 30000
|
// Generate a random port above 30000
|
||||||
port := getRandomPort(30000)
|
port := getRandomPort(30000)
|
||||||
@ -38,12 +38,12 @@ func initACME() *acme.ACMEHandler {
|
|||||||
port = getRandomPort(30000)
|
port = getRandomPort(30000)
|
||||||
}
|
}
|
||||||
|
|
||||||
return acme.NewACME("https://acme-staging-v02.api.letsencrypt.org/directory", strconv.Itoa(port))
|
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the special routing rule for ACME
|
// create the special routing rule for ACME
|
||||||
func acmeRegisterSpecialRoutingRule() {
|
func acmeRegisterSpecialRoutingRule() {
|
||||||
log.Println("Assigned temporary port:" + acmeHandler.Getport())
|
SystemWideLogger.Println("Assigned temporary port:" + acmeHandler.Getport())
|
||||||
|
|
||||||
err := dynamicProxyRouter.AddRoutingRules(&dynamicproxy.RoutingRule{
|
err := dynamicProxyRouter.AddRoutingRules(&dynamicproxy.RoutingRule{
|
||||||
ID: "acme-autorenew",
|
ID: "acme-autorenew",
|
||||||
@ -65,7 +65,7 @@ func acmeRegisterSpecialRoutingRule() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resBody, err := ioutil.ReadAll(res.Body)
|
resBody, err := io.ReadAll(res.Body)
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("error reading: %s\n", err)
|
fmt.Printf("error reading: %s\n", err)
|
||||||
@ -78,17 +78,19 @@ func acmeRegisterSpecialRoutingRule() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
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
|
// This function check if the renew setup is satisfied. If not, toggle them automatically
|
||||||
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
|
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
|
||||||
isForceHttpsRedirectEnabledOriginally := false
|
isForceHttpsRedirectEnabledOriginally := false
|
||||||
|
dnsPara, _ := utils.PostBool(r, "dns")
|
||||||
|
if !dnsPara {
|
||||||
if dynamicProxyRouter.Option.Port == 443 {
|
if dynamicProxyRouter.Option.Port == 443 {
|
||||||
//Enable port 80 to 443 redirect
|
//Enable port 80 to 443 redirect
|
||||||
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
|
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
|
||||||
log.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests")
|
SystemWideLogger.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests")
|
||||||
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
|
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
|
||||||
} else {
|
} else {
|
||||||
//Set this to true, so after renew, do not turn it off
|
//Set this to true, so after renew, do not turn it off
|
||||||
@ -101,16 +103,45 @@ func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request)
|
|||||||
} else {
|
} else {
|
||||||
//This port do not support ACME
|
//This port do not support ACME
|
||||||
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
|
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add a 3 second delay to make sure everything is settle down
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
// Pass over to the acmeHandler to deal with the communication
|
// Pass over to the acmeHandler to deal with the communication
|
||||||
acmeHandler.HandleRenewCertificate(w, r)
|
acmeHandler.HandleRenewCertificate(w, r)
|
||||||
|
|
||||||
if dynamicProxyRouter.Option.Port == 443 {
|
//Update the TLS cert store buffer
|
||||||
|
tlsCertManager.UpdateLoadedCertList()
|
||||||
|
|
||||||
|
//Restore original settings
|
||||||
|
if dynamicProxyRouter.Option.Port == 443 && !dnsPara {
|
||||||
if !isForceHttpsRedirectEnabledOriginally {
|
if !isForceHttpsRedirectEnabledOriginally {
|
||||||
//Default is off. Turn the redirection off
|
//Default is off. Turn the redirection off
|
||||||
log.Println("Restoring HTTP to HTTPS redirect settings")
|
SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil)
|
||||||
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
|
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleACMEPreferredCA return the user preferred / default CA for new subdomain auto creation
|
||||||
|
func HandleACMEPreferredCA(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ca, err := utils.PostPara(r, "set")
|
||||||
|
if err != nil {
|
||||||
|
//Return the current ca to user
|
||||||
|
prefCA := "Let's Encrypt"
|
||||||
|
sysdb.Read("acmepref", "prefca", &prefCA)
|
||||||
|
js, _ := json.Marshal(prefCA)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
} else {
|
||||||
|
//Check if the CA is supported
|
||||||
|
acme.IsSupportedCA(ca)
|
||||||
|
//Set the new config
|
||||||
|
sysdb.Write("acmepref", "prefca", ca)
|
||||||
|
SystemWideLogger.Println("Updating prefered ACME CA to " + ca)
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
119
src/api.go
@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
"net/http/pprof"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||||
"imuslab.com/zoraxy/mod/acme/acmewizard"
|
"imuslab.com/zoraxy/mod/acme/acmewizard"
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/netstat"
|
"imuslab.com/zoraxy/mod/netstat"
|
||||||
@ -21,11 +22,11 @@ import (
|
|||||||
|
|
||||||
var requireAuth = true
|
var requireAuth = true
|
||||||
|
|
||||||
func initAPIs() {
|
func initAPIs(targetMux *http.ServeMux) {
|
||||||
|
|
||||||
authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
|
authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
|
||||||
AuthAgent: authAgent,
|
AuthAgent: authAgent,
|
||||||
RequireAuth: requireAuth,
|
RequireAuth: requireAuth,
|
||||||
|
TargetMux: targetMux,
|
||||||
DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
|
DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
|
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
|
||||||
},
|
},
|
||||||
@ -38,26 +39,47 @@ func initAPIs() {
|
|||||||
}
|
}
|
||||||
//Add a layer of middleware for advance control
|
//Add a layer of middleware for advance control
|
||||||
advHandler := FSHandler(fs)
|
advHandler := FSHandler(fs)
|
||||||
http.Handle("/", advHandler)
|
targetMux.Handle("/", advHandler)
|
||||||
|
|
||||||
//Authentication APIs
|
//Authentication APIs
|
||||||
registerAuthAPIs(requireAuth)
|
registerAuthAPIs(requireAuth, targetMux)
|
||||||
|
|
||||||
//Reverse proxy
|
//Reverse proxy
|
||||||
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
|
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
|
||||||
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
|
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
||||||
|
authRouter.HandleFunc("/api/proxy/toggle", ReverseProxyToggleRuleSet)
|
||||||
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
|
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
|
||||||
|
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
|
||||||
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
||||||
|
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
||||||
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
||||||
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
||||||
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
|
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
|
||||||
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
|
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
|
||||||
|
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
||||||
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
||||||
//Reverse proxy root related APIs
|
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
||||||
authRouter.HandleFunc("/api/proxy/root/listOptions", HandleRootRouteOptionList)
|
//Reverse proxy upstream (load balance) APIs
|
||||||
authRouter.HandleFunc("/api/proxy/root/updateOptions", HandleRootRouteOptionsUpdate)
|
authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate)
|
||||||
|
authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete)
|
||||||
|
//Reverse proxy virtual directory APIs
|
||||||
|
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)
|
||||||
|
authRouter.HandleFunc("/api/proxy/header/handleHSTS", HandleHSTSState)
|
||||||
|
authRouter.HandleFunc("/api/proxy/header/handleHopByHop", HandleHopByHop)
|
||||||
|
authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite)
|
||||||
|
authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy)
|
||||||
//Reverse proxy auth related APIs
|
//Reverse proxy auth related APIs
|
||||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
|
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
|
||||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
|
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
|
||||||
@ -67,6 +89,7 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
|
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
|
||||||
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
|
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
|
||||||
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
|
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
|
||||||
|
authRouter.HandleFunc("/api/cert/download", handleCertDownload)
|
||||||
authRouter.HandleFunc("/api/cert/list", handleListCertificate)
|
authRouter.HandleFunc("/api/cert/list", handleListCertificate)
|
||||||
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
||||||
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
||||||
@ -76,7 +99,14 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
|
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
|
||||||
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
|
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
|
||||||
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
||||||
|
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
|
||||||
|
|
||||||
|
//Access Rules API
|
||||||
|
authRouter.HandleFunc("/api/access/list", handleListAccessRules)
|
||||||
|
authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
|
||||||
|
authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
|
||||||
|
authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
|
||||||
|
authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)
|
||||||
//Blacklist APIs
|
//Blacklist APIs
|
||||||
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
||||||
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
||||||
@ -84,7 +114,6 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
||||||
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
||||||
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
||||||
|
|
||||||
//Whitelist APIs
|
//Whitelist APIs
|
||||||
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
||||||
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
||||||
@ -101,7 +130,7 @@ func initAPIs() {
|
|||||||
//Statistic & uptime monitoring API
|
//Statistic & uptime monitoring API
|
||||||
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
|
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
|
||||||
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
|
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
|
||||||
authRouter.HandleFunc("/api/stats/netstat", netstat.HandleGetNetworkInterfaceStats)
|
authRouter.HandleFunc("/api/stats/netstat", netstatBuffers.HandleGetNetworkInterfaceStats)
|
||||||
authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats)
|
authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats)
|
||||||
authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces)
|
authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces)
|
||||||
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
|
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
|
||||||
@ -114,21 +143,22 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/gan/network/name", ganManager.HandleNetworkNaming)
|
authRouter.HandleFunc("/api/gan/network/name", ganManager.HandleNetworkNaming)
|
||||||
//authRouter.HandleFunc("/api/gan/network/detail", ganManager.HandleNetworkDetails)
|
//authRouter.HandleFunc("/api/gan/network/detail", ganManager.HandleNetworkDetails)
|
||||||
authRouter.HandleFunc("/api/gan/network/setRange", ganManager.HandleSetRanges)
|
authRouter.HandleFunc("/api/gan/network/setRange", ganManager.HandleSetRanges)
|
||||||
|
authRouter.HandleFunc("/api/gan/network/join", ganManager.HandleServerJoinNetwork)
|
||||||
|
authRouter.HandleFunc("/api/gan/network/leave", ganManager.HandleServerLeaveNetwork)
|
||||||
authRouter.HandleFunc("/api/gan/members/list", ganManager.HandleMemberList)
|
authRouter.HandleFunc("/api/gan/members/list", ganManager.HandleMemberList)
|
||||||
authRouter.HandleFunc("/api/gan/members/ip", ganManager.HandleMemberIP)
|
authRouter.HandleFunc("/api/gan/members/ip", ganManager.HandleMemberIP)
|
||||||
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
|
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
|
||||||
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
|
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
|
||||||
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
||||||
|
|
||||||
//TCP Proxy
|
//Stream (TCP / UDP) Proxy
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/add", tcpProxyManager.HandleAddProxyConfig)
|
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/edit", tcpProxyManager.HandleEditProxyConfigs)
|
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/list", tcpProxyManager.HandleListConfigs)
|
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/start", tcpProxyManager.HandleStartProxy)
|
authRouter.HandleFunc("/api/streamprox/config/start", streamProxyManager.HandleStartProxy)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/stop", tcpProxyManager.HandleStopProxy)
|
authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/delete", tcpProxyManager.HandleRemoveProxy)
|
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/status", tcpProxyManager.HandleGetProxyStatus)
|
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/validate", tcpProxyManager.HandleConfigValidate)
|
|
||||||
|
|
||||||
//mDNS APIs
|
//mDNS APIs
|
||||||
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
|
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
|
||||||
@ -153,47 +183,78 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet)
|
authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet)
|
||||||
authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet)
|
authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet)
|
||||||
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
|
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
|
||||||
|
authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle)
|
||||||
|
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
|
||||||
|
|
||||||
//Account Reset
|
//Account Reset
|
||||||
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
||||||
http.HandleFunc("/api/account/new", HandleNewPasswordSetup)
|
targetMux.HandleFunc("/api/account/new", HandleNewPasswordSetup)
|
||||||
|
|
||||||
//ACME & Auto Renewer
|
//ACME & Auto Renewer
|
||||||
authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
|
authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
|
||||||
authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
|
authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable)
|
authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HanldeSetDNS)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
||||||
|
authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson)
|
||||||
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard
|
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard
|
||||||
|
|
||||||
|
//Static Web Server
|
||||||
|
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", HandleStaticWebServerPortChange)
|
||||||
|
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
|
||||||
|
if *allowWebFileManager {
|
||||||
|
//Web Directory Manager file operation functions
|
||||||
|
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
|
||||||
|
authRouter.HandleFunc("/api/fs/upload", staticWebServer.FileManager.HandleUpload)
|
||||||
|
authRouter.HandleFunc("/api/fs/download", staticWebServer.FileManager.HandleDownload)
|
||||||
|
authRouter.HandleFunc("/api/fs/newFolder", staticWebServer.FileManager.HandleNewFolder)
|
||||||
|
authRouter.HandleFunc("/api/fs/copy", staticWebServer.FileManager.HandleFileCopy)
|
||||||
|
authRouter.HandleFunc("/api/fs/move", staticWebServer.FileManager.HandleFileMove)
|
||||||
|
authRouter.HandleFunc("/api/fs/properties", staticWebServer.FileManager.HandleFileProperties)
|
||||||
|
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Docker UX Optimizations
|
||||||
|
authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable)
|
||||||
|
authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList)
|
||||||
|
|
||||||
//Others
|
//Others
|
||||||
http.HandleFunc("/api/info/x", HandleZoraxyInfo)
|
targetMux.HandleFunc("/api/info/x", HandleZoraxyInfo)
|
||||||
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
|
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
|
||||||
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
|
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
|
||||||
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
|
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
|
||||||
|
authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog)
|
||||||
|
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
|
||||||
|
|
||||||
//Debug
|
//Debug
|
||||||
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
|
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
|
||||||
|
|
||||||
//If you got APIs to add, append them here
|
//If you got APIs to add, append them here
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to renders Auth related APIs
|
// Function to renders Auth related APIs
|
||||||
func registerAuthAPIs(requireAuth bool) {
|
func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
||||||
//Auth APIs
|
//Auth APIs
|
||||||
http.HandleFunc("/api/auth/login", authAgent.HandleLogin)
|
targetMux.HandleFunc("/api/auth/login", authAgent.HandleLogin)
|
||||||
http.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
|
targetMux.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
|
||||||
http.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
|
targetMux.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if requireAuth {
|
if requireAuth {
|
||||||
authAgent.CheckLogin(w, r)
|
authAgent.CheckLogin(w, r)
|
||||||
} else {
|
} else {
|
||||||
utils.SendJSONResponse(w, "true")
|
utils.SendJSONResponse(w, "true")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
http.HandleFunc("/api/auth/username", func(w http.ResponseWriter, r *http.Request) {
|
targetMux.HandleFunc("/api/auth/username", func(w http.ResponseWriter, r *http.Request) {
|
||||||
username, err := authAgent.GetUserName(w, r)
|
username, err := authAgent.GetUserName(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
@ -203,12 +264,12 @@ func registerAuthAPIs(requireAuth bool) {
|
|||||||
js, _ := json.Marshal(username)
|
js, _ := json.Marshal(username)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
})
|
})
|
||||||
http.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
|
targetMux.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
|
||||||
uc := authAgent.GetUserCounts()
|
uc := authAgent.GetUserCounts()
|
||||||
js, _ := json.Marshal(uc)
|
js, _ := json.Marshal(uc)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
})
|
})
|
||||||
http.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) {
|
targetMux.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if authAgent.GetUserCounts() == 0 {
|
if authAgent.GetUserCounts() == 0 {
|
||||||
//Allow register root admin
|
//Allow register root admin
|
||||||
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {
|
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {
|
||||||
@ -219,7 +280,7 @@ func registerAuthAPIs(requireAuth bool) {
|
|||||||
utils.SendErrorResponse(w, "Root management account already exists")
|
utils.SendErrorResponse(w, "Root management account already exists")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
http.HandleFunc("/api/auth/changePassword", func(w http.ResponseWriter, r *http.Request) {
|
targetMux.HandleFunc("/api/auth/changePassword", func(w http.ResponseWriter, r *http.Request) {
|
||||||
username, err := authAgent.GetUserName(w, r)
|
username, err := authAgent.GetUserName(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
|
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
|
||||||
|
101
src/cert.go
@ -6,13 +6,13 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,12 +47,13 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
|
|||||||
LastModifiedDate string
|
LastModifiedDate string
|
||||||
ExpireDate string
|
ExpireDate string
|
||||||
RemainingDays int
|
RemainingDays int
|
||||||
|
UseDNS bool
|
||||||
}
|
}
|
||||||
|
|
||||||
results := []*CertInfo{}
|
results := []*CertInfo{}
|
||||||
|
|
||||||
for _, filename := range filenames {
|
for _, filename := range filenames {
|
||||||
certFilepath := filepath.Join(tlsCertManager.CertStore, filename+".crt")
|
certFilepath := filepath.Join(tlsCertManager.CertStore, filename+".pem")
|
||||||
//keyFilepath := filepath.Join(tlsCertManager.CertStore, filename+".key")
|
//keyFilepath := filepath.Join(tlsCertManager.CertStore, filename+".key")
|
||||||
fileInfo, err := os.Stat(certFilepath)
|
fileInfo, err := os.Stat(certFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -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{
|
thisCertInfo := CertInfo{
|
||||||
Domain: filename,
|
Domain: filename,
|
||||||
LastModifiedDate: modifiedTime,
|
LastModifiedDate: modifiedTime,
|
||||||
ExpireDate: certExpireTime,
|
ExpireDate: certExpireTime,
|
||||||
RemainingDays: expiredIn,
|
RemainingDays: expiredIn,
|
||||||
|
UseDNS: useDNSValidation,
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, &thisCertInfo)
|
results = append(results, &thisCertInfo)
|
||||||
@ -128,7 +136,7 @@ func handleListDomains(w http.ResponseWriter, r *http.Request) {
|
|||||||
certBtyes, err := os.ReadFile(certFilepath)
|
certBtyes, err := os.ReadFile(certFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Unable to load this file
|
// Unable to load this file
|
||||||
log.Println("Unable to load certificate: " + certFilepath)
|
SystemWideLogger.PrintAndLog("TLS", "Unable to load certificate: "+certFilepath, err)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
// Cert loaded. Check its expiry time
|
// Cert loaded. Check its expiry time
|
||||||
@ -174,27 +182,28 @@ func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
sysdb.Read("settings", "usetls", ¤tTlsSetting)
|
sysdb.Read("settings", "usetls", ¤tTlsSetting)
|
||||||
}
|
}
|
||||||
|
|
||||||
newState, err := utils.PostPara(r, "set")
|
if r.Method == http.MethodGet {
|
||||||
if err != nil {
|
//Get the current status
|
||||||
//No setting. Get the current status
|
|
||||||
js, _ := json.Marshal(currentTlsSetting)
|
js, _ := json.Marshal(currentTlsSetting)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else if r.Method == http.MethodPost {
|
||||||
if newState == "true" {
|
newState, err := utils.PostBool(r, "set")
|
||||||
sysdb.Write("settings", "usetls", true)
|
if err != nil {
|
||||||
log.Println("Enabling TLS mode on reverse proxy")
|
utils.SendErrorResponse(w, "new state not set or invalid")
|
||||||
dynamicProxyRouter.UpdateTLSSetting(true)
|
|
||||||
} else if newState == "false" {
|
|
||||||
sysdb.Write("settings", "usetls", false)
|
|
||||||
log.Println("Disabling TLS mode on reverse proxy")
|
|
||||||
dynamicProxyRouter.UpdateTLSSetting(false)
|
|
||||||
} else {
|
|
||||||
utils.SendErrorResponse(w, "invalid state given. Only support true or false")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if newState {
|
||||||
|
sysdb.Write("settings", "usetls", true)
|
||||||
|
SystemWideLogger.Println("Enabling TLS mode on reverse proxy")
|
||||||
|
dynamicProxyRouter.UpdateTLSSetting(true)
|
||||||
|
} else {
|
||||||
|
sysdb.Write("settings", "usetls", false)
|
||||||
|
SystemWideLogger.Println("Disabling TLS mode on reverse proxy")
|
||||||
|
dynamicProxyRouter.UpdateTLSSetting(false)
|
||||||
|
}
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,11 +222,11 @@ func handleSetTlsRequireLatest(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
if newState == "true" {
|
if newState == "true" {
|
||||||
sysdb.Write("settings", "forceLatestTLS", 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)
|
dynamicProxyRouter.UpdateTLSVersion(true)
|
||||||
} else if newState == "false" {
|
} else if newState == "false" {
|
||||||
sysdb.Write("settings", "forceLatestTLS", 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)
|
dynamicProxyRouter.UpdateTLSVersion(false)
|
||||||
} else {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid state given")
|
utils.SendErrorResponse(w, "invalid state given")
|
||||||
@ -225,6 +234,51 @@ func handleSetTlsRequireLatest(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle download of the selected certificate
|
||||||
|
func handleCertDownload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get the certificate name
|
||||||
|
certname, err := utils.GetPara(r, "certname")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid certname given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
certname = filepath.Base(certname) //prevent path escape
|
||||||
|
|
||||||
|
// check if the cert exists
|
||||||
|
pubKey := filepath.Join(filepath.Join("./conf/certs"), certname+".key")
|
||||||
|
priKey := filepath.Join(filepath.Join("./conf/certs"), certname+".pem")
|
||||||
|
|
||||||
|
if utils.FileExists(pubKey) && utils.FileExists(priKey) {
|
||||||
|
//Zip them and serve them via http download
|
||||||
|
seeking, _ := utils.GetBool(r, "seek")
|
||||||
|
if seeking {
|
||||||
|
//This request only check if the key exists. Do not provide download
|
||||||
|
utils.SendOK(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Serve both file in zip
|
||||||
|
zipTmpFolder := "./tmp/download"
|
||||||
|
os.MkdirAll(zipTmpFolder, 0775)
|
||||||
|
zipFileName := filepath.Join(zipTmpFolder, certname+".zip")
|
||||||
|
err := utils.ZipFiles(zipFileName, pubKey, priKey)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to create zip file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(zipFileName) // Clean up the zip file after serving
|
||||||
|
|
||||||
|
// Serve the zip file
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=\""+certname+"_export.zip\"")
|
||||||
|
w.Header().Set("Content-Type", "application/zip")
|
||||||
|
http.ServeFile(w, r, zipFileName)
|
||||||
|
} else {
|
||||||
|
//Not both key exists
|
||||||
|
utils.SendErrorResponse(w, "invalid key-pairs: private key or public key not found in key store")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle upload of the certificate
|
// Handle upload of the certificate
|
||||||
func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
||||||
// check if request method is POST
|
// check if request method is POST
|
||||||
@ -249,7 +303,7 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if keytype == "pub" {
|
if keytype == "pub" {
|
||||||
overWriteFilename = domain + ".crt"
|
overWriteFilename = domain + ".pem"
|
||||||
} else if keytype == "pri" {
|
} else if keytype == "pri" {
|
||||||
overWriteFilename = domain + ".key"
|
overWriteFilename = domain + ".key"
|
||||||
} else {
|
} else {
|
||||||
@ -288,6 +342,9 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Update cert list
|
||||||
|
tlsCertManager.UpdateLoadedCertList()
|
||||||
|
|
||||||
// send response
|
// send response
|
||||||
fmt.Fprintln(w, "File upload successful!")
|
fmt.Fprintln(w, "File upload successful!")
|
||||||
}
|
}
|
||||||
|
189
src/config.go
@ -3,9 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,101 +30,131 @@ type Record struct {
|
|||||||
Rootname string
|
Rootname string
|
||||||
ProxyTarget string
|
ProxyTarget string
|
||||||
UseTLS bool
|
UseTLS bool
|
||||||
|
BypassGlobalTLS bool
|
||||||
SkipTlsValidation bool
|
SkipTlsValidation bool
|
||||||
RequireBasicAuth bool
|
RequireBasicAuth bool
|
||||||
BasicAuthCredentials []*dynamicproxy.BasicAuthCredentials
|
BasicAuthCredentials []*dynamicproxy.BasicAuthCredentials
|
||||||
BasicAuthExceptionRules []*dynamicproxy.BasicAuthExceptionRule
|
BasicAuthExceptionRules []*dynamicproxy.BasicAuthExceptionRule
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save a reverse proxy config record to file
|
/*
|
||||||
func SaveReverseProxyConfigToFile(proxyConfigRecord *Record) error {
|
Load Reverse Proxy Config from file and append it to current runtime proxy router
|
||||||
//TODO: Make this accept new def types
|
*/
|
||||||
os.MkdirAll("./conf/proxy/", 0775)
|
func LoadReverseProxyConfig(configFilepath string) error {
|
||||||
filename := getFilenameFromRootName(proxyConfigRecord.Rootname)
|
//Load the config file from disk
|
||||||
|
endpointConfig, err := os.ReadFile(configFilepath)
|
||||||
//Generate record
|
|
||||||
thisRecord := proxyConfigRecord
|
|
||||||
|
|
||||||
//Write to file
|
|
||||||
js, _ := json.MarshalIndent(thisRecord, "", " ")
|
|
||||||
return os.WriteFile(filepath.Join("./conf/proxy/", filename), js, 0775)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save a running reverse proxy endpoint to file (with automatic endpoint to record conversion)
|
|
||||||
func SaveReverseProxyEndpointToFile(proxyEndpoint *dynamicproxy.ProxyEndpoint) error {
|
|
||||||
recordToSave, err := ConvertProxyEndpointToRecord(proxyEndpoint)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return SaveReverseProxyConfigToFile(recordToSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveReverseProxyConfigFile(rootname string) error {
|
//Parse it into dynamic proxy endpoint
|
||||||
filename := getFilenameFromRootName(rootname)
|
thisConfigEndpoint := dynamicproxy.ProxyEndpoint{}
|
||||||
removePendingFile := strings.ReplaceAll(filepath.Join("./conf/proxy/", filename), "\\", "/")
|
err = json.Unmarshal(endpointConfig, &thisConfigEndpoint)
|
||||||
log.Println("Config Removed: ", removePendingFile)
|
|
||||||
if utils.FileExists(removePendingFile) {
|
|
||||||
err := os.Remove(removePendingFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Matching domain not set. Assume root
|
||||||
|
if thisConfigEndpoint.RootOrMatchingDomain == "" {
|
||||||
|
thisConfigEndpoint.RootOrMatchingDomain = "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
//File already gone
|
if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Root {
|
||||||
|
//This is a root config file
|
||||||
|
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicProxyRouter.SetProxyRouteAsRoot(rootProxyEndpoint)
|
||||||
|
|
||||||
|
} else if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Host {
|
||||||
|
//This is a host config file
|
||||||
|
readyProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicProxyRouter.AddProxyRouteToRuntime(readyProxyEndpoint)
|
||||||
|
} else {
|
||||||
|
return errors.New("not supported proxy type")
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemWideLogger.PrintAndLog("proxy-config", thisConfigEndpoint.RootOrMatchingDomain+" -> "+loadbalance.GetUpstreamsAsString(thisConfigEndpoint.ActiveOrigins)+" routing rule loaded", nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return ptype, rootname and proxyTarget, error if any
|
func filterProxyConfigFilename(filename string) string {
|
||||||
func LoadReverseProxyConfig(filename string) (*Record, error) {
|
//Filter out wildcard characters
|
||||||
thisRecord := Record{
|
filename = strings.ReplaceAll(filename, "*", "(ST)")
|
||||||
ProxyType: "",
|
filename = strings.ReplaceAll(filename, "?", "(QM)")
|
||||||
Rootname: "",
|
filename = strings.ReplaceAll(filename, "[", "(OB)")
|
||||||
ProxyTarget: "",
|
filename = strings.ReplaceAll(filename, "]", "(CB)")
|
||||||
UseTLS: false,
|
filename = strings.ReplaceAll(filename, "#", "(HT)")
|
||||||
SkipTlsValidation: false,
|
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: "/",
|
||||||
|
ActiveOrigins: []*loadbalance.Upstream{
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
||||||
|
RequireTLS: false,
|
||||||
|
SkipCertValidations: false,
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InactiveOrigins: []*loadbalance.Upstream{},
|
||||||
|
BypassGlobalTLS: false,
|
||||||
|
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||||
RequireBasicAuth: false,
|
RequireBasicAuth: false,
|
||||||
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
||||||
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
|
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
|
||||||
}
|
DefaultSiteOption: dynamicproxy.DefaultSite_InternalStaticWebServer,
|
||||||
|
DefaultSiteValue: "",
|
||||||
configContent, err := os.ReadFile(filename)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &thisRecord, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//Unmarshal the content into config
|
return rootProxyEndpoint, nil
|
||||||
err = json.Unmarshal(configContent, &thisRecord)
|
|
||||||
if err != nil {
|
|
||||||
return &thisRecord, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//Return it
|
|
||||||
return &thisRecord, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a running proxy endpoint object into a save-able record struct
|
|
||||||
func ConvertProxyEndpointToRecord(targetProxyEndpoint *dynamicproxy.ProxyEndpoint) (*Record, error) {
|
|
||||||
thisProxyConfigRecord := Record{
|
|
||||||
ProxyType: targetProxyEndpoint.GetProxyTypeString(),
|
|
||||||
Rootname: targetProxyEndpoint.RootOrMatchingDomain,
|
|
||||||
ProxyTarget: targetProxyEndpoint.Domain,
|
|
||||||
UseTLS: targetProxyEndpoint.RequireTLS,
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -131,7 +162,7 @@ func getFilenameFromRootName(rootname string) string {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
||||||
includeSysDBRaw, err := utils.GetPara(r, "includeDB")
|
includeSysDBRaw, _ := utils.GetPara(r, "includeDB")
|
||||||
includeSysDB := false
|
includeSysDB := false
|
||||||
if includeSysDBRaw == "true" {
|
if includeSysDBRaw == "true" {
|
||||||
//Include the system database in backup snapshot
|
//Include the system database in backup snapshot
|
||||||
@ -153,7 +184,7 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer zipWriter.Close()
|
defer zipWriter.Close()
|
||||||
|
|
||||||
// Walk through the folder and add files to the zip
|
// Walk through the folder and add files to the zip
|
||||||
err = filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error {
|
err := filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -191,14 +222,14 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Also zip in the sysdb
|
//Also zip in the sysdb
|
||||||
zipFile, err := zipWriter.Create("sys.db")
|
zipFile, err := zipWriter.Create("sys.db")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("[Backup] Unable to zip sysdb: " + err.Error())
|
SystemWideLogger.PrintAndLog("Backup", "Unable to zip sysdb", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the file on disk
|
// Open the file on disk
|
||||||
file, err := os.Open("sys.db")
|
file, err := os.Open("sys.db")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("[Backup] Unable to open sysdb: " + err.Error())
|
SystemWideLogger.PrintAndLog("Backup", "Unable to open sysdb", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
@ -206,7 +237,7 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Copy the file contents to the zip file
|
// Copy the file contents to the zip file
|
||||||
_, err = io.Copy(zipFile, file)
|
_, err = io.Copy(zipFile, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
SystemWideLogger.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,12 +342,12 @@ func ImportConfigFromZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Send a success response
|
// Send a success response
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
log.Println("Configuration restored")
|
SystemWideLogger.Println("Configuration restored")
|
||||||
fmt.Fprintln(w, "Configuration restored")
|
fmt.Fprintln(w, "Configuration restored")
|
||||||
|
|
||||||
if restoreDatabase {
|
if restoreDatabase {
|
||||||
go func() {
|
go func() {
|
||||||
log.Println("Database altered. Restarting in 3 seconds...")
|
SystemWideLogger.Println("Database altered. Restarting in 3 seconds...")
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
|
@ -25,12 +25,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domain, err := utils.PostPara(r, "domain")
|
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, "domain cannot be empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
portString, err := utils.PostPara(r, "port")
|
portString, err := utils.PostPara(r, "port")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "port must be a valid integer")
|
utils.SendErrorResponse(w, "port must be a valid integer")
|
||||||
@ -76,7 +70,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Set the email sender properties
|
//Set the email sender properties
|
||||||
thisEmailSender := email.Sender{
|
thisEmailSender := email.Sender{
|
||||||
Hostname: strings.TrimSpace(hostname),
|
Hostname: strings.TrimSpace(hostname),
|
||||||
Domain: strings.TrimSpace(domain),
|
|
||||||
Port: port,
|
Port: port,
|
||||||
Username: strings.TrimSpace(username),
|
Username: strings.TrimSpace(username),
|
||||||
Password: strings.TrimSpace(password),
|
Password: strings.TrimSpace(password),
|
||||||
@ -206,7 +199,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
|
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
|
||||||
if EmailSender.Username == "" || EmailSender.Domain == "" {
|
if EmailSender.Username == "" {
|
||||||
//Reset account not setup
|
//Reset account not setup
|
||||||
utils.SendErrorResponse(w, "Reset account not setup.")
|
utils.SendErrorResponse(w, "Reset account not setup.")
|
||||||
return
|
return
|
||||||
@ -279,17 +272,14 @@ func HandleNewPasswordSetup(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Delete the user account
|
// Un register the user account
|
||||||
authAgent.UnregisterUser(username)
|
if err := authAgent.UnregisterUser(username); err != nil {
|
||||||
|
|
||||||
//Ok. Set the new password
|
|
||||||
err = authAgent.CreateUserAccount(username, newPassword, "")
|
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, err.Error())
|
utils.SendErrorResponse(w, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
//Ok. Set the new password
|
||||||
|
if err := authAgent.CreateUserAccount(username, newPassword, ""); err != nil {
|
||||||
utils.SendErrorResponse(w, err.Error())
|
utils.SendErrorResponse(w, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
192
src/go.mod
@ -1,18 +1,192 @@
|
|||||||
module imuslab.com/zoraxy
|
module imuslab.com/zoraxy
|
||||||
|
|
||||||
go 1.16
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/boltdb/bolt v1.3.1
|
github.com/boltdb/bolt v1.3.1
|
||||||
github.com/go-acme/lego/v4 v4.14.0
|
github.com/docker/docker v27.0.0+incompatible
|
||||||
|
github.com/go-acme/lego/v4 v4.16.1
|
||||||
github.com/go-ping/ping v1.1.0
|
github.com/go-ping/ping v1.1.0
|
||||||
github.com/google/uuid v1.3.1
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/sessions v1.2.1
|
github.com/gorilla/sessions v1.2.2
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.1
|
||||||
github.com/grandcat/zeroconf v1.0.0
|
github.com/grandcat/zeroconf v1.0.0
|
||||||
github.com/likexian/whois v1.15.1
|
github.com/likexian/whois v1.15.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.25
|
github.com/microcosm-cc/bluemonday v1.0.26
|
||||||
golang.org/x/net v0.14.0
|
golang.org/x/net v0.25.0
|
||||||
golang.org/x/sys v0.11.0
|
golang.org/x/sys v0.20.0
|
||||||
golang.org/x/tools v0.12.0 // indirect
|
golang.org/x/text v0.15.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go/compute v1.25.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/Microsoft/go-winio v0.4.14 // indirect
|
||||||
|
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // 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/containerd/log v0.1.0 // 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/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/dnsimple/dnsimple-go v1.2.0 // indirect
|
||||||
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
|
github.com/exoscale/egoscale v0.102.3 // indirect
|
||||||
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // 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-logr/logr v1.4.1 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // 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/gogo/protobuf v1.3.2 // 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.4 // indirect
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.12.2 // indirect
|
||||||
|
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
||||||
|
github.com/gorilla/csrf v1.7.2 // 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/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/moby/term v0.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/morikuni/aec v1.0.0 // 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/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||||
|
github.com/ovh/go-ovh v1.4.3 // 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.2 // indirect
|
||||||
|
github.com/stretchr/testify v1.9.0 // 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.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.27.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
||||||
|
go.uber.org/ratelimit v0.2.0 // indirect
|
||||||
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
|
golang.org/x/mod v0.16.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.18.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.169.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
||||||
|
google.golang.org/grpc v1.64.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // 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
|
||||||
|
gotest.tools/v3 v3.5.1 // indirect
|
||||||
)
|
)
|
||||||
|
1422
src/go.sum
110
src/main.go
@ -12,39 +12,55 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/acme"
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/aroz"
|
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/dockerux"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||||
"imuslab.com/zoraxy/mod/email"
|
"imuslab.com/zoraxy/mod/email"
|
||||||
|
"imuslab.com/zoraxy/mod/forwardproxy"
|
||||||
"imuslab.com/zoraxy/mod/ganserv"
|
"imuslab.com/zoraxy/mod/ganserv"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logviewer"
|
||||||
"imuslab.com/zoraxy/mod/mdns"
|
"imuslab.com/zoraxy/mod/mdns"
|
||||||
"imuslab.com/zoraxy/mod/netstat"
|
"imuslab.com/zoraxy/mod/netstat"
|
||||||
"imuslab.com/zoraxy/mod/pathrule"
|
"imuslab.com/zoraxy/mod/pathrule"
|
||||||
"imuslab.com/zoraxy/mod/sshprox"
|
"imuslab.com/zoraxy/mod/sshprox"
|
||||||
"imuslab.com/zoraxy/mod/statistic"
|
"imuslab.com/zoraxy/mod/statistic"
|
||||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||||
"imuslab.com/zoraxy/mod/tcpprox"
|
"imuslab.com/zoraxy/mod/streamproxy"
|
||||||
"imuslab.com/zoraxy/mod/tlscert"
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
|
"imuslab.com/zoraxy/mod/update"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
"imuslab.com/zoraxy/mod/webserv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// General flags
|
// General flags
|
||||||
|
var webUIPort = flag.String("port", ":8000", "Management web interface listening port")
|
||||||
var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
|
var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
|
||||||
var showver = flag.Bool("version", false, "Show version of this server")
|
var showver = flag.Bool("version", false, "Show version of this server")
|
||||||
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
||||||
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
||||||
|
var mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
|
||||||
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
||||||
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||||
|
var runningInDocker = flag.Bool("docker", false, "Run Zoraxy in docker compatibility mode")
|
||||||
var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
||||||
|
var acmeCertAutoRenewDays = flag.Int("earlyrenew", 30, "Number of days to early renew a soon expiring certificate (days)")
|
||||||
var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
|
var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
|
||||||
|
var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
|
||||||
|
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
|
||||||
|
var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
name = "Zoraxy"
|
name = "Zoraxy"
|
||||||
version = "2.6.6"
|
version = "3.1.0"
|
||||||
nodeUUID = "generic"
|
nodeUUID = "generic" //System uuid, in uuidv4 format
|
||||||
development = false //Set this to false to use embedded web fs
|
development = false //Set this to false to use embedded web fs
|
||||||
bootTime = time.Now().Unix()
|
bootTime = time.Now().Unix()
|
||||||
|
|
||||||
@ -57,26 +73,35 @@ var (
|
|||||||
/*
|
/*
|
||||||
Handler Modules
|
Handler Modules
|
||||||
*/
|
*/
|
||||||
handler *aroz.ArozHandler //Handle arozos managed permission system
|
|
||||||
sysdb *database.Database //System database
|
sysdb *database.Database //System database
|
||||||
authAgent *auth.AuthAgent //Authentication agent
|
authAgent *auth.AuthAgent //Authentication agent
|
||||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||||
|
webminPanelMux *http.ServeMux //Server mux for handling webmin panel APIs
|
||||||
|
csrfMiddleware func(http.Handler) http.Handler //CSRF protection middleware
|
||||||
|
|
||||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||||
geodbStore *geodb.Store //GeoIP database, also handle black list and whitelist features
|
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||||
|
accessController *access.Controller //Access controller, handle black list and white list
|
||||||
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||||
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||||
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
||||||
mdnsScanner *mdns.MDNSHost //mDNS discovery services
|
mdnsScanner *mdns.MDNSHost //mDNS discovery services
|
||||||
ganManager *ganserv.NetworkManager //Global Area Network Manager
|
ganManager *ganserv.NetworkManager //Global Area Network Manager
|
||||||
webSshManager *sshprox.Manager //Web SSH connection service
|
webSshManager *sshprox.Manager //Web SSH connection service
|
||||||
tcpProxyManager *tcpprox.Manager //TCP Proxy Manager
|
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
|
||||||
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
|
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
|
||||||
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
||||||
|
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
||||||
|
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
|
||||||
|
loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing
|
||||||
|
|
||||||
//Helper modules
|
//Helper modules
|
||||||
EmailSender *email.Sender //Email sender that handle email sending
|
EmailSender *email.Sender //Email sender that handle email sending
|
||||||
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
||||||
|
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
|
||||||
|
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
||||||
|
LogViewer *logviewer.Viewer
|
||||||
)
|
)
|
||||||
|
|
||||||
// Kill signal handler. Do something before the system the core terminate.
|
// Kill signal handler. Do something before the system the core terminate.
|
||||||
@ -91,51 +116,54 @@ func SetupCloseHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ShutdownSeq() {
|
func ShutdownSeq() {
|
||||||
fmt.Println("- Shutting down " + name)
|
SystemWideLogger.Println("Shutting down " + name)
|
||||||
fmt.Println("- Closing GeoDB ")
|
SystemWideLogger.Println("Closing GeoDB ")
|
||||||
geodbStore.Close()
|
geodbStore.Close()
|
||||||
fmt.Println("- Closing Netstats Listener")
|
SystemWideLogger.Println("Closing Netstats Listener")
|
||||||
netstatBuffers.Close()
|
netstatBuffers.Close()
|
||||||
fmt.Println("- Closing Statistic Collector")
|
SystemWideLogger.Println("Closing Statistic Collector")
|
||||||
statisticCollector.Close()
|
statisticCollector.Close()
|
||||||
if mdnsTickerStop != nil {
|
if mdnsTickerStop != nil {
|
||||||
fmt.Println("- Stopping mDNS Discoverer (might take a few minutes)")
|
SystemWideLogger.Println("Stopping mDNS Discoverer (might take a few minutes)")
|
||||||
// Stop the mdns service
|
// Stop the mdns service
|
||||||
mdnsTickerStop <- true
|
mdnsTickerStop <- true
|
||||||
}
|
}
|
||||||
|
|
||||||
mdnsScanner.Close()
|
mdnsScanner.Close()
|
||||||
fmt.Println("- Closing Certificates Auto Renewer")
|
SystemWideLogger.Println("Shutting down load balancer")
|
||||||
|
loadBalancer.Close()
|
||||||
|
SystemWideLogger.Println("Closing Certificates Auto Renewer")
|
||||||
acmeAutoRenewer.Close()
|
acmeAutoRenewer.Close()
|
||||||
//Remove the tmp folder
|
//Remove the tmp folder
|
||||||
fmt.Println("- Cleaning up tmp files")
|
SystemWideLogger.Println("Cleaning up tmp files")
|
||||||
os.RemoveAll("./tmp")
|
os.RemoveAll("./tmp")
|
||||||
|
|
||||||
//Close database, final
|
//Close database
|
||||||
fmt.Println("- Stopping system database")
|
SystemWideLogger.Println("Stopping system database")
|
||||||
sysdb.Close()
|
sysdb.Close()
|
||||||
|
|
||||||
|
//Close logger
|
||||||
|
SystemWideLogger.Println("Closing system wide logger")
|
||||||
|
SystemWideLogger.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
//Start the aoModule pipeline (which will parse the flags as well). Pass in the module launch information
|
//Parse startup flags
|
||||||
handler = aroz.HandleFlagParse(aroz.ServiceInfo{
|
flag.Parse()
|
||||||
Name: name,
|
|
||||||
Desc: "Dynamic Reverse Proxy Server",
|
|
||||||
Group: "Network",
|
|
||||||
IconPath: "zoraxy/img/small_icon.png",
|
|
||||||
Version: version,
|
|
||||||
StartDir: "zoraxy/index.html",
|
|
||||||
SupportFW: true,
|
|
||||||
LaunchFWDir: "zoraxy/index.html",
|
|
||||||
SupportEmb: false,
|
|
||||||
InitFWSize: []int{1080, 580},
|
|
||||||
})
|
|
||||||
|
|
||||||
if *showver {
|
if *showver {
|
||||||
fmt.Println(name + " - Version " + version)
|
fmt.Println(name + " - Version " + version)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !utils.ValidateListeningAddress(*webUIPort) {
|
||||||
|
fmt.Println("Malformed -port (listening address) paramter. Do you mean -port=:" + *webUIPort + "?")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *enableAutoUpdate {
|
||||||
|
fmt.Println("Checking required config update")
|
||||||
|
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version))
|
||||||
|
}
|
||||||
|
|
||||||
SetupCloseHandler()
|
SetupCloseHandler()
|
||||||
|
|
||||||
//Read or create the system uuid
|
//Read or create the system uuid
|
||||||
@ -146,17 +174,27 @@ func main() {
|
|||||||
}
|
}
|
||||||
uuidBytes, err := os.ReadFile(uuidRecord)
|
uuidBytes, err := os.ReadFile(uuidRecord)
|
||||||
if err != nil {
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
nodeUUID = string(uuidBytes)
|
nodeUUID = string(uuidBytes)
|
||||||
|
|
||||||
|
//Create a new webmin mux and csrf middleware layer
|
||||||
|
webminPanelMux = http.NewServeMux()
|
||||||
|
csrfMiddleware = csrf.Protect(
|
||||||
|
[]byte(nodeUUID),
|
||||||
|
csrf.CookieName("zoraxy-csrf"),
|
||||||
|
csrf.Secure(false),
|
||||||
|
csrf.Path("/"),
|
||||||
|
csrf.SameSite(csrf.SameSiteLaxMode),
|
||||||
|
)
|
||||||
|
|
||||||
//Startup all modules
|
//Startup all modules
|
||||||
startupSequence()
|
startupSequence()
|
||||||
|
|
||||||
//Initiate management interface APIs
|
//Initiate management interface APIs
|
||||||
requireAuth = !(*noauth || handler.IsUsingExternalPermissionManager())
|
requireAuth = !(*noauth)
|
||||||
initAPIs()
|
initAPIs(webminPanelMux)
|
||||||
|
|
||||||
//Start the reverse proxy server in go routine
|
//Start the reverse proxy server in go routine
|
||||||
go func() {
|
go func() {
|
||||||
@ -168,8 +206,8 @@ func main() {
|
|||||||
//Start the finalize sequences
|
//Start the finalize sequences
|
||||||
finalSequence()
|
finalSequence()
|
||||||
|
|
||||||
log.Println("Zoraxy started. Visit control panel at http://localhost" + handler.Port)
|
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
|
||||||
err = http.ListenAndServe(handler.Port, nil)
|
err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
221
src/mod/access/access.go
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Access.go
|
||||||
|
|
||||||
|
This module is the new version of access control system
|
||||||
|
where now the blacklist / whitelist are seperated from
|
||||||
|
geodb module
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Create a new access controller to handle blacklist / whitelist
|
||||||
|
func NewAccessController(options *Options) (*Controller, error) {
|
||||||
|
sysdb := options.Database
|
||||||
|
if sysdb == nil {
|
||||||
|
return nil, errors.New("missing database access")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create the config folder if not exists
|
||||||
|
confFolder := options.ConfigFolder
|
||||||
|
if !utils.FileExists(confFolder) {
|
||||||
|
err := os.MkdirAll(confFolder, 0775)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the global access rule if not exists
|
||||||
|
var defaultAccessRule = AccessRule{
|
||||||
|
ID: "default",
|
||||||
|
Name: "Default",
|
||||||
|
Desc: "Default access rule for all HTTP proxy hosts",
|
||||||
|
BlacklistEnabled: false,
|
||||||
|
WhitelistEnabled: false,
|
||||||
|
WhiteListCountryCode: &map[string]string{},
|
||||||
|
WhiteListIP: &map[string]string{},
|
||||||
|
BlackListContryCode: &map[string]string{},
|
||||||
|
BlackListIP: &map[string]string{},
|
||||||
|
}
|
||||||
|
defaultRuleSettingFile := filepath.Join(confFolder, "default.json")
|
||||||
|
if utils.FileExists(defaultRuleSettingFile) {
|
||||||
|
//Load from file
|
||||||
|
defaultRuleBytes, err := os.ReadFile(defaultRuleSettingFile)
|
||||||
|
if err == nil {
|
||||||
|
err = json.Unmarshal(defaultRuleBytes, &defaultAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to parse default routing rule config file. Using default", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Create one
|
||||||
|
js, _ := json.MarshalIndent(defaultAccessRule, "", " ")
|
||||||
|
os.WriteFile(defaultRuleSettingFile, js, 0775)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate a controller object
|
||||||
|
thisController := Controller{
|
||||||
|
DefaultAccessRule: &defaultAccessRule,
|
||||||
|
ProxyAccessRule: &sync.Map{},
|
||||||
|
Options: options,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assign default access rule parent
|
||||||
|
thisController.DefaultAccessRule.parent = &thisController
|
||||||
|
|
||||||
|
//Load all acccess rules from file
|
||||||
|
configFiles, err := filepath.Glob(options.ConfigFolder + "/*.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ProxyAccessRules := sync.Map{}
|
||||||
|
for _, configFile := range configFiles {
|
||||||
|
if filepath.Base(configFile) == "default.json" {
|
||||||
|
//Skip this, as this was already loaded as default
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configContent, err := os.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to load config "+filepath.Base(configFile), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse the config file into AccessRule
|
||||||
|
thisAccessRule := AccessRule{}
|
||||||
|
err = json.Unmarshal(configContent, &thisAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to parse config "+filepath.Base(configFile), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
thisAccessRule.parent = &thisController
|
||||||
|
ProxyAccessRules.Store(thisAccessRule.ID, &thisAccessRule)
|
||||||
|
}
|
||||||
|
thisController.ProxyAccessRule = &ProxyAccessRules
|
||||||
|
|
||||||
|
return &thisController, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the global access rule
|
||||||
|
func (c *Controller) GetGlobalAccessRule() (*AccessRule, error) {
|
||||||
|
if c.DefaultAccessRule == nil {
|
||||||
|
return nil, errors.New("global access rule is not set")
|
||||||
|
}
|
||||||
|
return c.DefaultAccessRule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load access rules to runtime, require rule ID
|
||||||
|
func (c *Controller) GetAccessRuleByID(accessRuleID string) (*AccessRule, error) {
|
||||||
|
if accessRuleID == "default" || accessRuleID == "" {
|
||||||
|
|
||||||
|
return c.DefaultAccessRule, nil
|
||||||
|
}
|
||||||
|
//Load from sync.Map, should be O(1)
|
||||||
|
targetRule, ok := c.ProxyAccessRule.Load(accessRuleID)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("target access rule not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
ar, ok := targetRule.(*AccessRule)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("assertion of access rule failed, version too old?")
|
||||||
|
}
|
||||||
|
return ar, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all the access rules currently in runtime, including default
|
||||||
|
func (c *Controller) ListAllAccessRules() []*AccessRule {
|
||||||
|
results := []*AccessRule{c.DefaultAccessRule}
|
||||||
|
c.ProxyAccessRule.Range(func(key, value interface{}) bool {
|
||||||
|
results = append(results, value.(*AccessRule))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an access rule exists given the rule id
|
||||||
|
func (c *Controller) AccessRuleExists(ruleID string) bool {
|
||||||
|
r, _ := c.GetAccessRuleByID(ruleID)
|
||||||
|
if r != nil {
|
||||||
|
//An access rule with identical ID exists
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new access rule to runtime and save it to file
|
||||||
|
func (c *Controller) AddNewAccessRule(newRule *AccessRule) error {
|
||||||
|
r, _ := c.GetAccessRuleByID(newRule.ID)
|
||||||
|
if r != nil {
|
||||||
|
//An access rule with identical ID exists
|
||||||
|
return errors.New("access rule already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the blacklist and whitelist are populated with empty map
|
||||||
|
if newRule.BlackListContryCode == nil {
|
||||||
|
newRule.BlackListContryCode = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.BlackListIP == nil {
|
||||||
|
newRule.BlackListIP = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.WhiteListCountryCode == nil {
|
||||||
|
newRule.WhiteListCountryCode = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.WhiteListIP == nil {
|
||||||
|
newRule.WhiteListIP = &map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add access rule to runtime
|
||||||
|
newRule.parent = c
|
||||||
|
c.ProxyAccessRule.Store(newRule.ID, newRule)
|
||||||
|
|
||||||
|
//Save rule to file
|
||||||
|
newRule.SaveChanges()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the access rule meta info.
|
||||||
|
func (c *Controller) UpdateAccessRule(ruleID string, name string, desc string) error {
|
||||||
|
targetAccessRule, err := c.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
///Update the name and desc
|
||||||
|
targetAccessRule.Name = name
|
||||||
|
targetAccessRule.Desc = desc
|
||||||
|
|
||||||
|
//Overwrite the rule currently in sync map
|
||||||
|
if ruleID == "default" {
|
||||||
|
c.DefaultAccessRule = targetAccessRule
|
||||||
|
} else {
|
||||||
|
c.ProxyAccessRule.Store(ruleID, targetAccessRule)
|
||||||
|
}
|
||||||
|
return targetAccessRule.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the access rule by its id
|
||||||
|
func (c *Controller) RemoveAccessRuleByID(ruleID string) error {
|
||||||
|
if !c.AccessRuleExists(ruleID) {
|
||||||
|
return errors.New("access rule not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Default cannot be removed
|
||||||
|
if ruleID == "default" {
|
||||||
|
return errors.New("default access rule cannot be removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove it
|
||||||
|
return c.DeleteAccessRuleByID(ruleID)
|
||||||
|
}
|
153
src/mod/access/accessRule.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check both blacklist and whitelist for access for both geoIP and ip / CIDR ranges
|
||||||
|
func (s *AccessRule) AllowIpAccess(ipaddr string) bool {
|
||||||
|
if s.IsBlacklisted(ipaddr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.IsWhitelisted(ipaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check both blacklist and whitelist for access using net.Conn
|
||||||
|
func (s *AccessRule) AllowConnectionAccess(conn net.Conn) bool {
|
||||||
|
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||||
|
return s.AllowIpAccess(addr.IP.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle black list
|
||||||
|
func (s *AccessRule) ToggleBlacklist(enabled bool) {
|
||||||
|
s.BlacklistEnabled = enabled
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggel white list
|
||||||
|
func (s *AccessRule) ToggleWhitelist(enabled bool) {
|
||||||
|
s.WhitelistEnabled = enabled
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if a IP address is blacklisted, in either country or IP blacklist
|
||||||
|
IsBlacklisted default return is false (allow access)
|
||||||
|
*/
|
||||||
|
func (s *AccessRule) IsBlacklisted(ipAddr string) bool {
|
||||||
|
if !s.BlacklistEnabled {
|
||||||
|
//Blacklist not enabled. Always return false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipAddr == "" {
|
||||||
|
//Unable to get the target IP address
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsCountryCodeBlacklisted(countryCode.CountryIsoCode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsIPBlacklisted(ipAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsWhitelisted check if a given IP address is in the current
|
||||||
|
server's white list.
|
||||||
|
|
||||||
|
Note that the Whitelist default result is true even
|
||||||
|
when encountered error
|
||||||
|
*/
|
||||||
|
func (s *AccessRule) IsWhitelisted(ipAddr string) bool {
|
||||||
|
if !s.WhitelistEnabled {
|
||||||
|
//Whitelist not enabled. Always return true (allow access)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipAddr == "" {
|
||||||
|
//Unable to get the target IP address, assume ok
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsCountryCodeWhitelisted(countryCode.CountryIsoCode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsIPWhitelisted(ipAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilities function */
|
||||||
|
|
||||||
|
// Update the current access rule to json file
|
||||||
|
func (s *AccessRule) SaveChanges() error {
|
||||||
|
if s.parent == nil {
|
||||||
|
return errors.New("save failed: access rule detached from controller")
|
||||||
|
}
|
||||||
|
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
|
||||||
|
js, err := json.MarshalIndent(s, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(saveTarget, js, 0775)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete this access rule, this will only delete the config file.
|
||||||
|
// for runtime delete, use DeleteAccessRuleByID from parent Controller
|
||||||
|
func (s *AccessRule) DeleteConfigFile() error {
|
||||||
|
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
|
||||||
|
return os.Remove(saveTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the access rule by given ID
|
||||||
|
func (c *Controller) DeleteAccessRuleByID(accessRuleID string) error {
|
||||||
|
targetAccessRule, err := c.GetAccessRuleByID(accessRuleID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete config file associated with this access rule
|
||||||
|
err = targetAccessRule.DeleteConfigFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete the access rule in runtime
|
||||||
|
c.ProxyAccessRule.Delete(accessRuleID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a deep copy object of the access rule list
|
||||||
|
func deepCopy(valueList map[string]string) map[string]string {
|
||||||
|
result := map[string]string{}
|
||||||
|
js, _ := json.Marshal(valueList)
|
||||||
|
json.Unmarshal(js, &result)
|
||||||
|
return result
|
||||||
|
}
|
94
src/mod/access/blacklist.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Blacklist.go
|
||||||
|
|
||||||
|
This script store the blacklist related functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Geo Blacklist
|
||||||
|
func (s *AccessRule) AddCountryCodeToBlackList(countryCode string, comment string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
|
||||||
|
newBlacklistCountryCode[countryCode] = comment
|
||||||
|
s.BlackListContryCode = &newBlacklistCountryCode
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveCountryCodeFromBlackList(countryCode string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
|
||||||
|
delete(newBlacklistCountryCode, countryCode)
|
||||||
|
s.BlackListContryCode = &newBlacklistCountryCode
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsCountryCodeBlacklisted(countryCode string) bool {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
blacklistMap := *s.BlackListContryCode
|
||||||
|
_, ok := blacklistMap[countryCode]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllBlacklistedCountryCode() []string {
|
||||||
|
bannedCountryCodes := []string{}
|
||||||
|
blacklistMap := *s.BlackListContryCode
|
||||||
|
for cc, _ := range blacklistMap {
|
||||||
|
bannedCountryCodes = append(bannedCountryCodes, cc)
|
||||||
|
}
|
||||||
|
return bannedCountryCodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP Blacklsits
|
||||||
|
func (s *AccessRule) AddIPToBlackList(ipAddr string, comment string) {
|
||||||
|
newBlackListIP := deepCopy(*s.BlackListIP)
|
||||||
|
newBlackListIP[ipAddr] = comment
|
||||||
|
s.BlackListIP = &newBlackListIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveIPFromBlackList(ipAddr string) {
|
||||||
|
newBlackListIP := deepCopy(*s.BlackListIP)
|
||||||
|
delete(newBlackListIP, ipAddr)
|
||||||
|
s.BlackListIP = &newBlackListIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllBlacklistedIp() []string {
|
||||||
|
bannedIps := []string{}
|
||||||
|
blacklistMap := *s.BlackListIP
|
||||||
|
for ip, _ := range blacklistMap {
|
||||||
|
bannedIps = append(bannedIps, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bannedIps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsIPBlacklisted(ipAddr string) bool {
|
||||||
|
IPBlacklist := *s.BlackListIP
|
||||||
|
_, ok := IPBlacklist[ipAddr]
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for CIDR
|
||||||
|
for ipOrCIDR, _ := range IPBlacklist {
|
||||||
|
wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR)
|
||||||
|
if wildcardMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR)
|
||||||
|
if cidrMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
38
src/mod/access/typedef.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Logger logger.Logger
|
||||||
|
ConfigFolder string //Path for storing config files
|
||||||
|
GeoDB *geodb.Store //For resolving country code
|
||||||
|
Database *database.Database //System key-value database
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessRule struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Desc string
|
||||||
|
BlacklistEnabled bool
|
||||||
|
WhitelistEnabled bool
|
||||||
|
|
||||||
|
/* Whitelist Blacklist Table, value is comment if supported */
|
||||||
|
WhiteListCountryCode *map[string]string
|
||||||
|
WhiteListIP *map[string]string
|
||||||
|
BlackListContryCode *map[string]string
|
||||||
|
BlackListIP *map[string]string
|
||||||
|
|
||||||
|
parent *Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
DefaultAccessRule *AccessRule
|
||||||
|
ProxyAccessRule *sync.Map
|
||||||
|
Options *Options
|
||||||
|
}
|
112
src/mod/access/whitelist.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Whitelist.go
|
||||||
|
|
||||||
|
This script handles whitelist related functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
EntryType_CountryCode int = 0
|
||||||
|
EntryType_IP int = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type WhitelistEntry struct {
|
||||||
|
EntryType int //Entry type of whitelist, Country Code or IP
|
||||||
|
CC string //ISO Country Code
|
||||||
|
IP string //IP address or range
|
||||||
|
Comment string //Comment for this entry
|
||||||
|
}
|
||||||
|
|
||||||
|
//Geo Whitelist
|
||||||
|
|
||||||
|
func (s *AccessRule) AddCountryCodeToWhitelist(countryCode string, comment string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
|
||||||
|
newWhitelistCC[countryCode] = comment
|
||||||
|
s.WhiteListCountryCode = &newWhitelistCC
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveCountryCodeFromWhitelist(countryCode string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
|
||||||
|
delete(newWhitelistCC, countryCode)
|
||||||
|
s.WhiteListCountryCode = &newWhitelistCC
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsCountryCodeWhitelisted(countryCode string) bool {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
whitelistCC := *s.WhiteListCountryCode
|
||||||
|
_, ok := whitelistCC[countryCode]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllWhitelistedCountryCode() []*WhitelistEntry {
|
||||||
|
whitelistedCountryCode := []*WhitelistEntry{}
|
||||||
|
whitelistCC := *s.WhiteListCountryCode
|
||||||
|
for cc, comment := range whitelistCC {
|
||||||
|
whitelistedCountryCode = append(whitelistedCountryCode, &WhitelistEntry{
|
||||||
|
EntryType: EntryType_CountryCode,
|
||||||
|
CC: cc,
|
||||||
|
Comment: comment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return whitelistedCountryCode
|
||||||
|
}
|
||||||
|
|
||||||
|
//IP Whitelist
|
||||||
|
|
||||||
|
func (s *AccessRule) AddIPToWhiteList(ipAddr string, comment string) {
|
||||||
|
newWhitelistIP := deepCopy(*s.WhiteListIP)
|
||||||
|
newWhitelistIP[ipAddr] = comment
|
||||||
|
s.WhiteListIP = &newWhitelistIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveIPFromWhiteList(ipAddr string) {
|
||||||
|
newWhitelistIP := deepCopy(*s.WhiteListIP)
|
||||||
|
delete(newWhitelistIP, ipAddr)
|
||||||
|
s.WhiteListIP = &newWhitelistIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsIPWhitelisted(ipAddr string) bool {
|
||||||
|
//Check for IP wildcard and CIRD rules
|
||||||
|
WhitelistedIP := *s.WhiteListIP
|
||||||
|
for ipOrCIDR, _ := range WhitelistedIP {
|
||||||
|
wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR)
|
||||||
|
if wildcardMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR)
|
||||||
|
if cidrMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllWhitelistedIp() []*WhitelistEntry {
|
||||||
|
whitelistedIp := []*WhitelistEntry{}
|
||||||
|
currentWhitelistedIP := *s.WhiteListIP
|
||||||
|
for ipOrCIDR, comment := range currentWhitelistedIP {
|
||||||
|
thisEntry := WhitelistEntry{
|
||||||
|
EntryType: EntryType_IP,
|
||||||
|
IP: ipOrCIDR,
|
||||||
|
Comment: comment,
|
||||||
|
}
|
||||||
|
whitelistedIp = append(whitelistedIp, &thisEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return whitelistedIp
|
||||||
|
}
|
@ -9,8 +9,8 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ type CertificateInfoJSON struct {
|
|||||||
AcmeName string `json:"acme_name"`
|
AcmeName string `json:"acme_name"`
|
||||||
AcmeUrl string `json:"acme_url"`
|
AcmeUrl string `json:"acme_url"`
|
||||||
SkipTLS bool `json:"skip_tls"`
|
SkipTLS bool `json:"skip_tls"`
|
||||||
|
UseDNS bool `json:"dns"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACMEUser represents a user in the ACME system.
|
// ACMEUser represents a user in the ACME system.
|
||||||
@ -41,6 +43,11 @@ type ACMEUser struct {
|
|||||||
key crypto.PrivateKey
|
key crypto.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EABConfig struct {
|
||||||
|
Kid string `json:"kid"`
|
||||||
|
HmacKey string `json:"HmacKey"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetEmail returns the email of the ACMEUser.
|
// GetEmail returns the email of the ACMEUser.
|
||||||
func (u *ACMEUser) GetEmail() string {
|
func (u *ACMEUser) GetEmail() string {
|
||||||
return u.Email
|
return u.Email
|
||||||
@ -60,18 +67,20 @@ func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey {
|
|||||||
type ACMEHandler struct {
|
type ACMEHandler struct {
|
||||||
DefaultAcmeServer string
|
DefaultAcmeServer string
|
||||||
Port string
|
Port string
|
||||||
|
Database *database.Database
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewACME creates a new ACMEHandler instance.
|
// NewACME creates a new ACMEHandler instance.
|
||||||
func NewACME(acmeServer string, port string) *ACMEHandler {
|
func NewACME(acmeServer string, port string, database *database.Database) *ACMEHandler {
|
||||||
return &ACMEHandler{
|
return &ACMEHandler{
|
||||||
DefaultAcmeServer: acmeServer,
|
DefaultAcmeServer: acmeServer,
|
||||||
Port: port,
|
Port: port,
|
||||||
|
Database: database,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObtainCert obtains a certificate for the specified domains.
|
// ObtainCert obtains a certificate for the specified domains.
|
||||||
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool) (bool, error) {
|
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool, useDNS bool) (bool, error) {
|
||||||
log.Println("[ACME] Obtaining certificate...")
|
log.Println("[ACME] Obtaining certificate...")
|
||||||
|
|
||||||
// generate private key
|
// generate private key
|
||||||
@ -108,6 +117,11 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Fallback to Let's Encrypt if it is not set
|
||||||
|
if caName == "" {
|
||||||
|
caName = "Let's Encrypt"
|
||||||
|
}
|
||||||
|
|
||||||
// setup the custom ACME url endpoint.
|
// setup the custom ACME url endpoint.
|
||||||
if caUrl != "" {
|
if caUrl != "" {
|
||||||
config.CADirURL = caUrl
|
config.CADirURL = caUrl
|
||||||
@ -137,18 +151,108 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setup how to receive challenge
|
// setup how to receive challenge
|
||||||
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
|
if useDNS {
|
||||||
|
if !a.Database.TableExists("acme") {
|
||||||
|
a.Database.NewTable("acme")
|
||||||
|
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.Database.KeyExists("acme", certificateName+"_dns_provider") || !a.Database.KeyExists("acme", certificateName+"_dns_credentials") {
|
||||||
|
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -2)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var dnsCredentials string
|
||||||
|
err := a.Database.Read("acme", certificateName+"_dns_credentials", &dnsCredentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dnsProvider string
|
||||||
|
err = a.Database.Read("acme", certificateName+"_dns_provider", &dnsProvider)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := GetDnsChallengeProviderByName(dnsProvider, dnsCredentials)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.Challenge.SetDNS01Provider(provider)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// New users will need to register
|
// New users will need to register
|
||||||
|
/*
|
||||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
var reg *registration.Resource
|
||||||
|
// New users will need to register
|
||||||
|
if client.GetExternalAccountRequired() {
|
||||||
|
log.Println("External Account Required for this ACME Provider.")
|
||||||
|
// IF KID and HmacEncoded is overidden
|
||||||
|
|
||||||
|
if !a.Database.TableExists("acme") {
|
||||||
|
a.Database.NewTable("acme")
|
||||||
|
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.Database.KeyExists("acme", config.CADirURL+"_kid") || !a.Database.KeyExists("acme", config.CADirURL+"_hmacEncoded") {
|
||||||
|
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -2)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var kid string
|
||||||
|
var hmacEncoded string
|
||||||
|
err := a.Database.Read("acme", config.CADirURL+"_kid", &kid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Database.Read("acme", config.CADirURL+"_hmacEncoded", &hmacEncoded)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("EAB Credential retrieved.", kid, hmacEncoded)
|
||||||
|
if kid != "" && hmacEncoded != "" {
|
||||||
|
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||||
|
TermsOfServiceAgreed: true,
|
||||||
|
Kid: kid,
|
||||||
|
HmacEncoded: hmacEncoded,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
//return false, errors.New("External Account Required for this ACME Provider.")
|
||||||
|
} else {
|
||||||
|
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
adminUser.Registration = reg
|
adminUser.Registration = reg
|
||||||
|
|
||||||
// obtain the certificate
|
// obtain the certificate
|
||||||
@ -164,12 +268,12 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
|
|
||||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||||
// private key, and a certificate URL.
|
// private key, and a certificate URL.
|
||||||
err = ioutil.WriteFile("./conf/certs/"+certificateName+".crt", certificates.Certificate, 0777)
|
err = os.WriteFile("./conf/certs/"+certificateName+".pem", certificates.Certificate, 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile("./conf/certs/"+certificateName+".key", certificates.PrivateKey, 0777)
|
err = os.WriteFile("./conf/certs/"+certificateName+".key", certificates.PrivateKey, 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return false, err
|
return false, err
|
||||||
@ -180,6 +284,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
AcmeName: caName,
|
AcmeName: caName,
|
||||||
AcmeUrl: caUrl,
|
AcmeUrl: caUrl,
|
||||||
SkipTLS: skipTLS,
|
SkipTLS: skipTLS,
|
||||||
|
UseDNS: useDNS,
|
||||||
}
|
}
|
||||||
|
|
||||||
certInfoBytes, err := json.Marshal(certInfo)
|
certInfoBytes, err := json.Marshal(certInfo)
|
||||||
@ -292,6 +397,8 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//Make sure the wildcard * do not goes into the filename
|
||||||
|
filename = strings.ReplaceAll(filename, "*", "_")
|
||||||
|
|
||||||
email, err := utils.PostPara(r, "email")
|
email, err := utils.PostPara(r, "email")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -303,18 +410,23 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
ca, err := utils.PostPara(r, "ca")
|
ca, err := utils.PostPara(r, "ca")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("CA not set. Using default")
|
log.Println("[INFO] CA not set. Using default")
|
||||||
ca, caUrl = "", ""
|
ca, caUrl = "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if ca == "custom" {
|
if ca == "custom" {
|
||||||
caUrl, err = utils.PostPara(r, "caURL")
|
caUrl, err = utils.PostPara(r, "caURL")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Custom CA set but no URL provide, Using default")
|
log.Println("[INFO] Custom CA set but no URL provide, Using default")
|
||||||
ca, caUrl = "", ""
|
ca, caUrl = "", ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ca == "" {
|
||||||
|
//default. Use Let's Encrypt
|
||||||
|
ca = "Let's Encrypt"
|
||||||
|
}
|
||||||
|
|
||||||
var skipTLS bool
|
var skipTLS bool
|
||||||
|
|
||||||
if skipTLSString, err := utils.PostPara(r, "skipTLS"); err != nil {
|
if skipTLSString, err := utils.PostPara(r, "skipTLS"); err != nil {
|
||||||
@ -325,8 +437,23 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
skipTLS = true
|
skipTLS = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dns bool
|
||||||
|
|
||||||
|
if dnsString, err := utils.PostPara(r, "dns"); err != nil {
|
||||||
|
dns = false
|
||||||
|
} else if dnsString != "true" {
|
||||||
|
dns = false
|
||||||
|
} else {
|
||||||
|
dns = true
|
||||||
|
}
|
||||||
|
|
||||||
domains := strings.Split(domainPara, ",")
|
domains := strings.Split(domainPara, ",")
|
||||||
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS)
|
//Clean spaces in front or behind each domain
|
||||||
|
cleanedDomains := []string{}
|
||||||
|
for _, domain := range domains {
|
||||||
|
cleanedDomains = append(cleanedDomains, strings.TrimSpace(domain))
|
||||||
|
}
|
||||||
|
result, err := a.ObtainCert(cleanedDomains, filename, email, ca, caUrl, skipTLS, dns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||||
return
|
return
|
||||||
@ -357,8 +484,8 @@ func IsPortInUse(port int) bool {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
|
// Load cert information from json file
|
||||||
|
func LoadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
|
||||||
certInfoBytes, err := os.ReadFile(filename)
|
certInfoBytes, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
70
src/mod/acme/acme_dns.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
1103
src/mod/acme/acmedns/acmedns.go
Normal file
27
src/mod/acme/acmedns/acmedns_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package acmedns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test if the structure of ACME DNS config can be reflected from lego source code definations
|
||||||
|
func TestACMEDNSConfigStructureReflector(t *testing.T) {
|
||||||
|
providers := []string{
|
||||||
|
"gandi",
|
||||||
|
"cloudflare",
|
||||||
|
"azure",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, provider := range providers {
|
||||||
|
strcture, err := acmedns.GetProviderConfigStructure(provider)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(strcture)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3557
src/mod/acme/acmedns/providers.json
Normal file
80
src/mod/acme/acmedns/providerutils.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package acmedns
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed providers.json
|
||||||
|
var providers []byte //A list of providers generated by acmedns code-generator
|
||||||
|
|
||||||
|
type ConfigTemplate struct {
|
||||||
|
Name string `json:"Name"`
|
||||||
|
ConfigableFields []struct {
|
||||||
|
Title string `json:"Title"`
|
||||||
|
Datatype string `json:"Datatype"`
|
||||||
|
} `json:"ConfigableFields"`
|
||||||
|
HiddenFields []struct {
|
||||||
|
Title string `json:"Title"`
|
||||||
|
Datatype string `json:"Datatype"`
|
||||||
|
} `json:"HiddenFields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a map of string => datatype
|
||||||
|
func GetProviderConfigStructure(providerName string) (map[string]string, error) {
|
||||||
|
//Load the target config template from embedded providers.json
|
||||||
|
configTemplateMap := map[string]ConfigTemplate{}
|
||||||
|
err := json.Unmarshal(providers, &configTemplateMap)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetConfigTemplate, ok := configTemplateMap[providerName]
|
||||||
|
if !ok {
|
||||||
|
return map[string]string{}, errors.New("provider not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
results := map[string]string{}
|
||||||
|
for _, field := range targetConfigTemplate.ConfigableFields {
|
||||||
|
results[field.Title] = field.Datatype
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleServeProvidersJson return the list of supported providers as json
|
||||||
|
func HandleServeProvidersJson(w http.ResponseWriter, r *http.Request) {
|
||||||
|
providerName, _ := utils.GetPara(r, "name")
|
||||||
|
if providerName == "" {
|
||||||
|
//Send the current list of providers
|
||||||
|
configTemplateMap := map[string]ConfigTemplate{}
|
||||||
|
err := json.Unmarshal(providers, &configTemplateMap)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "failed to load DNS provider")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse the provider names into an array
|
||||||
|
providers := []string{}
|
||||||
|
for providerName, _ := range configTemplateMap {
|
||||||
|
providers = append(providers, providerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(providers)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//Get the config for that provider
|
||||||
|
confTemplate, err := GetProviderConfigStructure(providerName)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(confTemplate)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
@ -34,22 +34,26 @@ type AutoRenewer struct {
|
|||||||
AcmeHandler *ACMEHandler
|
AcmeHandler *ACMEHandler
|
||||||
RenewerConfig *AutoRenewConfig
|
RenewerConfig *AutoRenewConfig
|
||||||
RenewTickInterval int64
|
RenewTickInterval int64
|
||||||
|
EarlyRenewDays int //How many days before cert expire to renew certificate
|
||||||
TickerstopChan chan bool
|
TickerstopChan chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpiredCerts struct {
|
type ExpiredCerts struct {
|
||||||
Domains []string
|
Domains []string
|
||||||
Filepath string
|
Filepath string
|
||||||
CA string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an auto renew agent, require config filepath and auto scan & renew interval (seconds)
|
// Create an auto renew agent, require config filepath and auto scan & renew interval (seconds)
|
||||||
// Set renew check interval to 0 for auto (1 day)
|
// Set renew check interval to 0 for auto (1 day)
|
||||||
func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64, AcmeHandler *ACMEHandler) (*AutoRenewer, error) {
|
func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64, earlyRenewDays int, AcmeHandler *ACMEHandler) (*AutoRenewer, error) {
|
||||||
if renewCheckInterval == 0 {
|
if renewCheckInterval == 0 {
|
||||||
renewCheckInterval = 86400 //1 day
|
renewCheckInterval = 86400 //1 day
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if earlyRenewDays == 0 {
|
||||||
|
earlyRenewDays = 30
|
||||||
|
}
|
||||||
|
|
||||||
//Load the config file. If not found, create one
|
//Load the config file. If not found, create one
|
||||||
if !utils.FileExists(config) {
|
if !utils.FileExists(config) {
|
||||||
//Create one
|
//Create one
|
||||||
@ -136,7 +140,7 @@ func (a *AutoRenewer) StopAutoRenewTicker() {
|
|||||||
// opr = setSelected -> Enter a list of file names (or matching rules) for auto renew
|
// opr = setSelected -> Enter a list of file names (or matching rules) for auto renew
|
||||||
// opr = setAuto -> Set to use auto detect certificates and renew
|
// opr = setAuto -> Set to use auto detect certificates and renew
|
||||||
func (a *AutoRenewer) HandleSetAutoRenewDomains(w http.ResponseWriter, r *http.Request) {
|
func (a *AutoRenewer) HandleSetAutoRenewDomains(w http.ResponseWriter, r *http.Request) {
|
||||||
opr, err := utils.GetPara(r, "opr")
|
opr, err := utils.PostPara(r, "opr")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "Operation not set")
|
utils.SendErrorResponse(w, "Operation not set")
|
||||||
return
|
return
|
||||||
@ -166,6 +170,8 @@ func (a *AutoRenewer) HandleSetAutoRenewDomains(w http.ResponseWriter, r *http.R
|
|||||||
a.RenewerConfig.RenewAll = true
|
a.RenewerConfig.RenewAll = true
|
||||||
a.saveRenewConfigToFile()
|
a.saveRenewConfigToFile()
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
|
} else {
|
||||||
|
utils.SendErrorResponse(w, "invalid operation given")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -209,19 +215,22 @@ func (a *AutoRenewer) HandleRenewNow(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleAutoRenewEnable get and set the auto renew enable state
|
||||||
func (a *AutoRenewer) HandleAutoRenewEnable(w http.ResponseWriter, r *http.Request) {
|
func (a *AutoRenewer) HandleAutoRenewEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
val, err := utils.PostPara(r, "enable")
|
if r.Method == http.MethodGet {
|
||||||
if err != nil {
|
|
||||||
js, _ := json.Marshal(a.RenewerConfig.Enabled)
|
js, _ := json.Marshal(a.RenewerConfig.Enabled)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else if r.Method == http.MethodPost {
|
||||||
if val == "true" {
|
val, err := utils.PostBool(r, "enable")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid or empty enable state")
|
||||||
|
}
|
||||||
|
if val {
|
||||||
//Check if the email is not empty
|
//Check if the email is not empty
|
||||||
if a.RenewerConfig.Email == "" {
|
if a.RenewerConfig.Email == "" {
|
||||||
utils.SendErrorResponse(w, "Email is not set")
|
utils.SendErrorResponse(w, "Email is not set")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
a.RenewerConfig.Enabled = true
|
a.RenewerConfig.Enabled = true
|
||||||
a.saveRenewConfigToFile()
|
a.saveRenewConfigToFile()
|
||||||
log.Println("[ACME] ACME auto renew enabled")
|
log.Println("[ACME] ACME auto renew enabled")
|
||||||
@ -232,19 +241,26 @@ func (a *AutoRenewer) HandleAutoRenewEnable(w http.ResponseWriter, r *http.Reque
|
|||||||
log.Println("[ACME] ACME auto renew disabled")
|
log.Println("[ACME] ACME auto renew disabled")
|
||||||
a.StopAutoRenewTicker()
|
a.StopAutoRenewTicker()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AutoRenewer) HandleACMEEmail(w http.ResponseWriter, r *http.Request) {
|
func (a *AutoRenewer) HandleACMEEmail(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
email, err := utils.PostPara(r, "set")
|
|
||||||
if err != nil {
|
|
||||||
//Return the current email to user
|
//Return the current email to user
|
||||||
js, _ := json.Marshal(a.RenewerConfig.Email)
|
js, _ := json.Marshal(a.RenewerConfig.Email)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else if r.Method == http.MethodPost {
|
||||||
|
email, err := utils.PostPara(r, "set")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid or empty email given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//Check if the email is valid
|
//Check if the email is valid
|
||||||
_, err := mail.ParseAddress(email)
|
_, err = mail.ParseAddress(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, err.Error())
|
utils.SendErrorResponse(w, err.Error())
|
||||||
return
|
return
|
||||||
@ -253,8 +269,11 @@ func (a *AutoRenewer) HandleACMEEmail(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Set the new config
|
//Set the new config
|
||||||
a.RenewerConfig.Email = email
|
a.RenewerConfig.Email = email
|
||||||
a.saveRenewConfigToFile()
|
a.saveRenewConfigToFile()
|
||||||
}
|
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check and renew certificates. This check all the certificates in the
|
// Check and renew certificates. This check all the certificates in the
|
||||||
@ -278,14 +297,8 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if CertExpireSoon(certBytes) || CertIsExpired(certBytes) {
|
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
|
||||||
//This cert is expired
|
//This cert is expired
|
||||||
CAName, err := ExtractIssuerName(certBytes)
|
|
||||||
if err != nil {
|
|
||||||
//Maybe self signed. Ignore this
|
|
||||||
log.Println("Unable to extract issuer name for cert " + file.Name())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
DNSName, err := ExtractDomains(certBytes)
|
DNSName, err := ExtractDomains(certBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -296,7 +309,6 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
|||||||
|
|
||||||
expiredCertList = append(expiredCertList, &ExpiredCerts{
|
expiredCertList = append(expiredCertList, &ExpiredCerts{
|
||||||
Filepath: filepath.Join(certFolder, file.Name()),
|
Filepath: filepath.Join(certFolder, file.Name()),
|
||||||
CA: CAName,
|
|
||||||
Domains: DNSName,
|
Domains: DNSName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -313,14 +325,8 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if CertExpireSoon(certBytes) || CertIsExpired(certBytes) {
|
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
|
||||||
//This cert is expired
|
//This cert is expired
|
||||||
CAName, err := ExtractIssuerName(certBytes)
|
|
||||||
if err != nil {
|
|
||||||
//Maybe self signed. Ignore this
|
|
||||||
log.Println("Unable to extract issuer name for cert " + file.Name())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
DNSName, err := ExtractDomains(certBytes)
|
DNSName, err := ExtractDomains(certBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -331,7 +337,6 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
|||||||
|
|
||||||
expiredCertList = append(expiredCertList, &ExpiredCerts{
|
expiredCertList = append(expiredCertList, &ExpiredCerts{
|
||||||
Filepath: filepath.Join(certFolder, file.Name()),
|
Filepath: filepath.Join(certFolder, file.Name()),
|
||||||
CA: CAName,
|
|
||||||
Domains: DNSName,
|
Domains: DNSName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -359,13 +364,19 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
|||||||
|
|
||||||
// Load certificate info for ACME detail
|
// Load certificate info for ACME detail
|
||||||
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
|
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
|
||||||
certInfo, err := loadCertInfoJSON(certInfoFilename)
|
certInfo, err := LoadCertInfoJSON(certInfoFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Renew %s certificate error, can't get the ACME detail for cert: %v, using default ACME", certName, err)
|
log.Printf("Renew %s certificate error, can't get the ACME detail for cert: %v, trying org section as ca", certName, err)
|
||||||
|
|
||||||
|
if CAName, extractErr := ExtractIssuerNameFromPEM(expiredCert.Filepath); extractErr != nil {
|
||||||
|
log.Printf("extract issuer name for cert error: %v, using default ca", extractErr)
|
||||||
certInfo = &CertificateInfoJSON{}
|
certInfo = &CertificateInfoJSON{}
|
||||||
|
} else {
|
||||||
|
certInfo = &CertificateInfoJSON{AcmeName: CAName}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS)
|
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS, certInfo.UseDNS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
|
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
|
||||||
} else {
|
} else {
|
||||||
@ -382,3 +393,65 @@ func (a *AutoRenewer) saveRenewConfigToFile() error {
|
|||||||
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
|
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
|
||||||
return os.WriteFile(a.ConfigFilePath, js, 0775)
|
return os.WriteFile(a.ConfigFilePath, js, 0775)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle update auto renew EAD configuration
|
||||||
|
func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
|
||||||
|
kid, err := utils.GetPara(r, "kid")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "kid not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hmacEncoded, err := utils.GetPara(r, "hmacEncoded")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "hmacEncoded not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
acmeDirectoryURL, err := utils.GetPara(r, "acmeDirectoryURL")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "acmeDirectoryURL not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.AcmeHandler.Database.TableExists("acme") {
|
||||||
|
a.AcmeHandler.Database.NewTable("acme")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_kid", kid)
|
||||||
|
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_hmacEncoded", hmacEncoded)
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle update auto renew DNS configuration
|
||||||
|
func (a *AutoRenewer) HanldeSetDNS(w http.ResponseWriter, r *http.Request) {
|
||||||
|
dnsProvider, err := utils.PostPara(r, "dnsProvider")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "dnsProvider not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsCredentials, err := utils.PostPara(r, "dnsCredentials")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "dnsCredentials not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename, err := utils.PostPara(r, "filename")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "filename not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.AcmeHandler.Database.TableExists("acme") {
|
||||||
|
a.AcmeHandler.Database.NewTable("acme")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.AcmeHandler.Database.Write("acme", filename+"_dns_provider", dnsProvider)
|
||||||
|
a.AcmeHandler.Database.Write("acme", filename+"_dns_credentials", dnsCredentials)
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CA Defination, load from embeded json when startup
|
// CA Defination, load from embeded json when startup
|
||||||
@ -32,14 +33,24 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
caDef = runtimeCaDef
|
caDef = runtimeCaDef
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the CA ACME server endpoint and error if not found
|
// Get the CA ACME server endpoint and error if not found
|
||||||
func loadCAApiServerFromName(caName string) (string, error) {
|
func loadCAApiServerFromName(caName string) (string, error) {
|
||||||
|
// handle BuyPass cert org section (Buypass AS-983163327)
|
||||||
|
if strings.HasPrefix(caName, "Buypass AS") {
|
||||||
|
caName = "Buypass"
|
||||||
|
}
|
||||||
|
|
||||||
val, ok := caDef.Production[caName]
|
val, ok := caDef.Production[caName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", errors.New("This CA is not supported")
|
return "", errors.New("This CA is not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsSupportedCA(caName string) bool {
|
||||||
|
_, err := loadCAApiServerFromName(caName)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
@ -53,6 +53,11 @@ func ExtractIssuerName(certBytes []byte) (string, error) {
|
|||||||
return "", fmt.Errorf("failed to parse certificate: %v", err)
|
return "", fmt.Errorf("failed to parse certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if exist incase some acme server didn't have org section
|
||||||
|
if len(cert.Issuer.Organization) == 0 {
|
||||||
|
return "", fmt.Errorf("cert didn't have org section exist")
|
||||||
|
}
|
||||||
|
|
||||||
// Extract the issuer name
|
// Extract the issuer name
|
||||||
issuer := cert.Issuer.Organization[0]
|
issuer := cert.Issuer.Organization[0]
|
||||||
|
|
||||||
@ -76,13 +81,14 @@ func CertIsExpired(certBytes []byte) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func CertExpireSoon(certBytes []byte) bool {
|
// CertExpireSoon check if the given cert bytes will expires within the given number of days from now
|
||||||
|
func CertExpireSoon(certBytes []byte, numberOfDays int) bool {
|
||||||
block, _ := pem.Decode(certBytes)
|
block, _ := pem.Decode(certBytes)
|
||||||
if block != nil {
|
if block != nil {
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
expirationDate := cert.NotAfter
|
expirationDate := cert.NotAfter
|
||||||
threshold := 14 * 24 * time.Hour // 14 days
|
threshold := time.Duration(numberOfDays) * 24 * time.Hour
|
||||||
|
|
||||||
timeRemaining := time.Until(expirationDate)
|
timeRemaining := time.Until(expirationDate)
|
||||||
if timeRemaining <= threshold {
|
if timeRemaining <= threshold {
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -14,10 +14,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
db "imuslab.com/zoraxy/mod/database"
|
db "imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ type AuthAgent struct {
|
|||||||
SessionStore *sessions.CookieStore
|
SessionStore *sessions.CookieStore
|
||||||
Database *db.Database
|
Database *db.Database
|
||||||
LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
|
LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
|
||||||
|
Logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthEndpoints struct {
|
type AuthEndpoints struct {
|
||||||
@ -38,11 +39,11 @@ type AuthEndpoints struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
|
func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, systemLogger *logger.Logger, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
|
||||||
store := sessions.NewCookieStore(key)
|
store := sessions.NewCookieStore(key)
|
||||||
err := sysdb.NewTable("auth")
|
err := sysdb.NewTable("auth")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to create auth database. Terminating.")
|
systemLogger.Println("Failed to create auth database. Terminating.")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,13 +53,14 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database,
|
|||||||
SessionStore: store,
|
SessionStore: store,
|
||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
LoginRedirectionHandler: loginRedirectionHandler,
|
LoginRedirectionHandler: loginRedirectionHandler,
|
||||||
|
Logger: systemLogger,
|
||||||
}
|
}
|
||||||
|
|
||||||
//Return the authAgent
|
//Return the authAgent
|
||||||
return &newAuthAgent
|
return &newAuthAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSessionKey(sysdb *db.Database) (string, error) {
|
func GetSessionKey(sysdb *db.Database, logger *logger.Logger) (string, error) {
|
||||||
sysdb.NewTable("auth")
|
sysdb.NewTable("auth")
|
||||||
sessionKey := ""
|
sessionKey := ""
|
||||||
if !sysdb.KeyExists("auth", "sessionkey") {
|
if !sysdb.KeyExists("auth", "sessionkey") {
|
||||||
@ -66,9 +68,9 @@ func GetSessionKey(sysdb *db.Database) (string, error) {
|
|||||||
rand.Read(key)
|
rand.Read(key)
|
||||||
sessionKey = string(key)
|
sessionKey = string(key)
|
||||||
sysdb.Write("auth", "sessionkey", sessionKey)
|
sysdb.Write("auth", "sessionkey", sessionKey)
|
||||||
log.Println("[Auth] New authentication session key generated")
|
logger.PrintAndLog("auth", "New authentication session key generated", nil)
|
||||||
} else {
|
} else {
|
||||||
log.Println("[Auth] Authentication session key loaded from database")
|
logger.PrintAndLog("auth", "Authentication session key loaded from database", nil)
|
||||||
err := sysdb.Read("auth", "sessionkey", &sessionKey)
|
err := sysdb.Read("auth", "sessionkey", &sessionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("database read error. Is the database file corrupted?")
|
return "", errors.New("database read error. Is the database file corrupted?")
|
||||||
@ -95,7 +97,7 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
username, err := utils.PostPara(r, "username")
|
username, err := utils.PostPara(r, "username")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Username not defined
|
//Username not defined
|
||||||
log.Println("[Auth] " + r.RemoteAddr + " trying to login with username: " + username)
|
a.Logger.PrintAndLog("auth", r.RemoteAddr+" trying to login with username: "+username, nil)
|
||||||
utils.SendErrorResponse(w, "Username not defined or empty.")
|
utils.SendErrorResponse(w, "Username not defined or empty.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -124,11 +126,11 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
a.LoginUserByRequest(w, r, username, rememberme)
|
a.LoginUserByRequest(w, r, username, rememberme)
|
||||||
|
|
||||||
//Print the login message to console
|
//Print the login message to console
|
||||||
log.Println(username + " logged in.")
|
a.Logger.PrintAndLog("auth", username+" logged in.", nil)
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
} else {
|
} else {
|
||||||
//Password incorrect
|
//Password incorrect
|
||||||
log.Println(username + " login request rejected: " + rejectionReason)
|
a.Logger.PrintAndLog("auth", username+" login request rejected: "+rejectionReason, nil)
|
||||||
|
|
||||||
utils.SendErrorResponse(w, rejectionReason)
|
utils.SendErrorResponse(w, rejectionReason)
|
||||||
return
|
return
|
||||||
@ -147,7 +149,7 @@ func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, passw
|
|||||||
err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
|
err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//User not found or db exception
|
//User not found or db exception
|
||||||
log.Println("[Auth] " + username + " login with incorrect password")
|
a.Logger.PrintAndLog("auth", username+" login with incorrect password", nil)
|
||||||
return false, "Invalid username or password"
|
return false, "Invalid username or password"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,8 +186,12 @@ func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, u
|
|||||||
// Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
|
// Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
|
||||||
func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
|
func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
username, err := a.GetUserName(w, r)
|
username, err := a.GetUserName(w, r)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "user not logged in")
|
||||||
|
return
|
||||||
|
}
|
||||||
if username != "" {
|
if username != "" {
|
||||||
log.Println(username + " logged out.")
|
a.Logger.PrintAndLog("auth", username+" logged out", nil)
|
||||||
}
|
}
|
||||||
// Revoke users authentication
|
// Revoke users authentication
|
||||||
err = a.Logout(w, r)
|
err = a.Logout(w, r)
|
||||||
@ -194,7 +200,7 @@ func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write([]byte("OK"))
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
|
func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
|
||||||
@ -291,7 +297,7 @@ func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callb
|
|||||||
|
|
||||||
//Return to the client with OK
|
//Return to the client with OK
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
log.Println("[Auth] New user " + newusername + " added to system.")
|
a.Logger.PrintAndLog("auth", "New user "+newusername+" added to system.", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new user register without confirmation email. Require POST username, password, group.
|
// Handle new user register without confirmation email. Require POST username, password, group.
|
||||||
@ -324,7 +330,7 @@ func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
//Return to the client with OK
|
//Return to the client with OK
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
log.Println("[Auth] Admin account created: " + newusername)
|
a.Logger.PrintAndLog("auth", "Admin account created: "+newusername, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check authentication from request header's session value
|
// Check authentication from request header's session value
|
||||||
@ -365,7 +371,7 @@ func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
//Return to the client with OK
|
//Return to the client with OK
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
log.Println("[Auth] User " + username + " has been removed from the system.")
|
a.Logger.PrintAndLog("auth", "User "+username+" has been removed from the system", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthAgent) UnregisterUser(username string) error {
|
func (a *AuthAgent) UnregisterUser(username string) error {
|
||||||
@ -393,7 +399,7 @@ func (a *AuthAgent) GetUserCounts() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if usercount == 0 {
|
if usercount == 0 {
|
||||||
log.Println("There are no user in the database.")
|
a.Logger.PrintAndLog("auth", "There are no user in the database", nil)
|
||||||
}
|
}
|
||||||
return usercount
|
return usercount
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ type RouterOption struct {
|
|||||||
AuthAgent *AuthAgent
|
AuthAgent *AuthAgent
|
||||||
RequireAuth bool //This router require authentication
|
RequireAuth bool //This router require authentication
|
||||||
DeniedHandler func(http.ResponseWriter, *http.Request) //Things to do when request is rejected
|
DeniedHandler func(http.ResponseWriter, *http.Request) //Things to do when request is rejected
|
||||||
|
TargetMux *http.ServeMux
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouterDef struct {
|
type RouterDef struct {
|
||||||
@ -28,13 +28,14 @@ func NewManagedHTTPRouter(option RouterOption) *RouterDef {
|
|||||||
func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error {
|
func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error {
|
||||||
//Check if the endpoint already registered
|
//Check if the endpoint already registered
|
||||||
if _, exist := router.endpoints[endpoint]; exist {
|
if _, exist := router.endpoints[endpoint]; exist {
|
||||||
log.Println("WARNING! Duplicated registering of web endpoint: " + endpoint)
|
fmt.Println("WARNING! Duplicated registering of web endpoint: " + endpoint)
|
||||||
return errors.New("endpoint register duplicated")
|
return errors.New("endpoint register duplicated")
|
||||||
}
|
}
|
||||||
|
|
||||||
authAgent := router.option.AuthAgent
|
authAgent := router.option.AuthAgent
|
||||||
|
|
||||||
//OK. Register handler
|
//OK. Register handler
|
||||||
|
if router.option.TargetMux == nil {
|
||||||
http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||||
//Check authentication of the user
|
//Check authentication of the user
|
||||||
if router.option.RequireAuth {
|
if router.option.RequireAuth {
|
||||||
@ -46,6 +47,19 @@ func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseW
|
|||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
router.option.TargetMux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
//Check authentication of the user
|
||||||
|
if router.option.RequireAuth {
|
||||||
|
authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
handler(w, r)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
handler(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
router.endpoints[endpoint] = handler
|
router.endpoints[endpoint] = handler
|
||||||
|
|
||||||
|