114 Commits
3.0.4 ... 3.1.0

Author SHA1 Message Date
0a734e0bd3 Merge pull request #275 from tobychui/v3.1.0
v3.1.0 Update
2024-07-31 22:39:01 +08:00
f4fa92635c Added example go.mod files for windows 7 2024-07-31 22:35:25 +08:00
7d5151bb00 Add EarlyRenew flag to Dockerfile 2024-07-31 10:21:57 -04:00
54475e4b99 Fixed #271
- Fixed implementation in geoip resolver trie tree
2024-07-31 21:57:59 +08:00
6ac16caf37 Update main.go
- Updated main to internal web fs
2024-07-31 16:15:59 +08:00
97502db607 Update extract.go
- Updated lego config extractor
2024-07-31 16:12:28 +08:00
0747cf4b0f Fixed gandi DNS bug
- Fixed gandi DNS challenge extra input field
- Updated geoip list
2024-07-31 16:11:50 +08:00
94483acc92 Added log viewer filter
+ Added filter to log viewer #243
+ Added auto log refresh
2024-07-31 16:01:49 +08:00
7626857c02 Updated acme dns list
- Updated acme dns configs
- Updated dns propagation timeout from default (2min) to 5 minutes
2024-07-29 12:55:37 +08:00
0f772a715b Update extract.go
Updared extractor to compatible with later version of lego
2024-07-29 12:50:57 +08:00
fd1439f746 Fixed csrf token error in cert upload ui
- Fixed csrf token error in cert upload interface
- Added system wide logger into tls cert manager
2024-07-29 12:28:21 +08:00
ca37bfbfa6 Fixed #106
- Added experimental proxmox fixes
- Fixed upstream error resp code not logging bug
2024-07-27 17:33:41 +08:00
c1e16d55ab Optimized csrf mux
- Forced same site to lax mode for better browser compatibility
- Set zoraxy-csrf as cookie name
2024-07-24 22:47:49 +08:00
f595da92a1 Fixed #267
- Added csrf middleware to management portal mux
- Added csrf token to all html templates
- Added csrf validation to all endpoints
- Optimized some old endpoints implementation
2024-07-24 21:58:44 +08:00
02ff288280 Doc: Note about PORT usage for Docker run and compose 2024-07-22 14:03:10 -04:00
b1c5bc2963 Fixed #255
- Added host header manual overwrite feature
- Added toggle for automatic hop-by-hop header removing
2024-07-21 17:06:09 +08:00
d3dbbf9052 Merge branch 'v3.1.0' of https://github.com/tobychui/zoraxy into v3.1.0 2024-07-21 15:11:27 +08:00
f4a5c905e7 Fixed #256
- Added startup paramter to change the early renew days of certificates
- Changed the default early renew days of certificates from 14 days to 30 days
- Fixed vdir update not updating uptime monitor bug
2024-07-21 15:11:13 +08:00
245379e91f Fixed #254
- Added uptime cleaning logic to update function
2024-07-19 10:21:26 +08:00
955a2232df Update Makefile
- Fixed bug in CICD pipeline
2024-07-18 18:50:45 +08:00
7eb7ae7ced Merge pull request #251 from PassiveLemon/docker-timezone
Doc: Document on how to use host time in the container
2024-07-16 23:12:14 +08:00
3aa0f2d914 Target latest alpine image 2024-07-16 11:07:47 -04:00
39b0c8c674 Doc: Document on how to use host time in the container 2024-07-16 10:56:12 -04:00
bddeae8365 Fixed manual renew certificate bug
- Fixed manual renew certificate bug in wildcard certs
- Updated version no
2024-07-16 22:08:51 +08:00
8e0e9531e7 Merge pull request #250 from Morethanevil/main
Update CHANGELOG.md
2024-07-16 20:35:04 +08:00
6ff22865e0 Update CHANGELOG.md 2024-07-16 14:26:19 +02:00
0828fd1958 Update update.go
Fixed bug in skip version upgrade
2024-07-16 15:14:49 +08:00
82f84470f7 Merge pull request #246 from tobychui/3.0.9
Update 3.0.9
2024-07-16 13:15:02 +08:00
cf9a05f130 Updated v3.0.9
- Added certificate download
- Updated netcup timeout value
- Updated geoip db
- Removed debug print from log viewer
- Upgraded netstat log printing to new log formatter
- Improved updater implementation
2024-07-16 11:30:12 +08:00
301072db90 Fixed #231
- Added higher propagation timeout for netcup
- Fixed bug in CICD script
2024-07-16 10:37:10 +08:00
cfcd10d64f Update README.md
Updated new start parameters and feature list
2024-07-15 23:00:59 +08:00
c85760c73a Merge pull request #242 from Morethanevil/main
Update CHANGELOG.md
2024-07-15 21:39:01 +08:00
b7bb918aa3 Fix: Container issue due to deprecated flag 2024-07-15 09:21:14 -04:00
962f3e0566 Update CHANGELOG.md 2024-07-15 14:16:46 +02:00
0bcf2b2ae3 Updates v3.0.8
Merge pull request #239 from tobychui/main
2024-07-15 14:49:46 +08:00
6bfeb8cf3d Merge pull request #239 from tobychui/main
Merge change in main branch before v3.0.8 release
2024-07-15 14:43:07 +08:00
33def66386 Merge branch 'v3.0.8' into main 2024-07-15 14:42:19 +08:00
cb469f28d2 Updated geodb 2024-07-15 14:39:04 +08:00
8239f4cb53 Added apache compatible logger
- Rewritten the logger to make it more apache log parser friendly
- Fixed uptime not updating after upstream change bug
- Added SSO page (wip)
- Added log viewer
2024-07-14 22:25:49 +08:00
e410b92e34 Fixed #235
- Added flush sniffing for keep-alive request
- Set default flush interval to 100ms for hostname and 500ms for virtual directory
2024-07-13 23:12:23 +08:00
aca6e44b35 Added load balance origin picker
+ Added load balance picker
+ Added fallback mode for upstream
+ Added stick session
2024-07-12 20:14:31 +08:00
2aa35cbe6d Added load balancer (wip)
+ Added support for multiple upstreams
+ Added load balancer
+ Added upstream abstraction in endpoint
+ Added load balancer structure
+ Added breaking change auto-updater
+ Added uptime monitor proxy type definitions
+ Added upstream editor UI
+ Fixed charset bug in many snippets HTML files
2024-07-01 21:17:20 +08:00
745a54605f Merge pull request #225 from Kegelcizer/image-size-shrink
Update Dockerfile to shrink image
2024-06-29 12:02:27 +08:00
e3b61868a1 Update Dockerfile
Created folders and set permissions at build stage. Install stuff and copy only required folders with permissions already set at build stage.
2024-06-28 11:23:35 +03:00
764b1944be Merge pull request #216 from Kirari04/main
fix: unable to edit proxy if ratelimit is not set or <= 0
2024-06-28 09:58:15 +08:00
100cd727fc format docker file 2024-06-26 19:54:35 +02:00
7e62fef879 3.0.8 init
- Fixed rate limit bug  #216
- Added HSTS auto subdomain include check
2024-06-25 18:10:39 +08:00
1a4a55721f Improve Docker Image Size by 500mb by using 2 stage build 2024-06-24 23:32:38 +02:00
bb9deccff6 fix: unable to edit proxy if ratelimit is not set or <= 0
this fix checks the ratelimit value only if the
requireRateLimit is set to true else it will use
the provided ratelimit value unless it is less or equal to 0
then it will default to 1000 (the same value as set inside the ui)
2024-06-24 22:59:51 +02:00
a18413dd03 Merge pull request #213 from PassiveLemon/docker-env-rework
Docker env rework
2024-06-21 10:38:44 +08:00
2cd1b1de3c Update: Docker README 2024-06-20 10:58:13 -04:00
3a2db63d61 Refactor: Docker environment variables 2024-06-20 10:58:04 -04:00
123d3bcf3f Remove: Deprecated VERSION variable 2024-06-20 10:57:34 -04:00
3ec1d9c888 Update CHANGELOG.md
Example is removed while docker maintainers is discussing how to better support docker parameter
2024-06-20 15:49:48 +08:00
5785261c7e Merge pull request #210 from Morethanevil/main
Update CHANGELOG.md
2024-06-20 15:29:37 +08:00
89e60649e5 Update CHANGELOG.md 2024-06-20 09:07:22 +02:00
5423b82858 Update CHANGELOG.md
Updated changelog, providing an example configuration for docker
2024-06-20 09:01:14 +02:00
57135a867e Merge pull request #209 from PassiveLemon/3.0.7
Update Dockerfile
2024-06-20 09:39:39 +08:00
547855f30f Merge pull request #208 from tobychui/v3.0.7
V3.0.7 Update
2024-06-20 09:38:56 +08:00
05b477e90a Update README.md 2024-06-20 09:37:47 +08:00
3519c7841c Update Dockerfile 2024-06-19 17:26:18 -04:00
e7b4054248 Finalized v3.0.7 codebase 2024-06-19 10:44:12 +08:00
973d0b3372 Added load balancer module
- Added load balancer module wip
- Updated geoipv4
- Reduced uptime timeout to 5 sec
- Optimized rate limit implementation
- Fixed minor UI bug in stream proxy
2024-06-19 00:38:48 +08:00
704980d4f8 Added cf-style error templates 2024-06-18 16:37:58 +08:00
03974163d4 Added docker conditional compilation
- Moved docker UX optimization into module
- Added conditional compilation for Windows build
- Added Permission Policy header editor
- Fixed docker container list ui error message bug
2024-06-17 00:24:24 +08:00
dfb81513b1 Optimized docker detection structure
- Merged #202 and optimized UI elements
- Added HSTS headers toggle
- Added permission policy injector in dynamicproxy
- Fixed slow search LAN ip detection
- Optimized UI for HTTP reverse proxy rules
- Added wip permission policy and load balancer
2024-06-16 12:46:29 +08:00
b604c66a2f Merge pull request #202 from 7brend7/main
add docker containers list to set rules
2024-06-16 11:42:47 +08:00
dd84864dd4 Merge branch 'v3.0.7' into main 2024-06-16 11:42:31 +08:00
443cd961d2 add docker containers list to set rules 2024-06-15 17:19:19 +03:00
10048150bb Optimized rate limiter implementation
- Moved rate limiter scope into proxy router
- Give IpTable a better name following clean code guideline
- Optimized client IP retrieval method
- Added stop channel for request counter ticker
- Fixed #199
- Optimized UI for rate limit
2024-06-14 23:42:52 +08:00
85f9b297c4 Merge pull request #196 from Kirari04/main
[ENHANCEMENTS] Add Rate Limits Limits to Zoraxy
2024-06-14 20:41:41 +08:00
07e524a007 Merge remote-tracking branch 'origin' into v3.0.7 2024-06-13 23:01:57 -07:00
25c7e8ac1a update git ignore 2024-06-12 18:00:08 +02:00
49babbd60f implemented update ratelimit 2024-06-11 22:45:46 +02:00
fa11422748 Implemented ui part for rate limit 2024-06-11 22:36:03 +02:00
bb1b161ae2 clean up implementation 2024-06-11 22:04:30 +02:00
9545343151 Removing Benchmark & Updated implementation 2024-06-11 16:56:59 +02:00
61e4d45430 improoved benchmark 2024-06-11 16:53:29 +02:00
6026c4fd53 implement sync.Map and atomic values with benchmark 2024-06-11 16:40:04 +02:00
e3f8c99ed3 poc of an ratelimit implementation 2024-06-10 17:52:16 +02:00
fc88dfe72e Merge pull request #193 from Morethanevil/main
Update CHANGELOG.md
2024-06-10 19:22:45 +08:00
d43322f7a5 Update CHANGELOG.md
Added mentioning of PR 187 by Kirari04
2024-06-10 13:03:45 +02:00
83536a83f7 Merge pull request #192 from tobychui/v3.0.6
V3.0.6 Update

- 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 
- 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 #80 
- Restructured TCP proxy into Stream Proxy (Support both TCP and UDP) #147 
- Added stream proxy auto start #169 
- Optimized UX for reminding user to click Apply after port change
- Added version number to footer #160
2024-06-10 16:32:39 +08:00
1183b0ed55 Finalized v3.0.6 changes
- Updated geodb database
- Updated custom header UI
- Added tools for update acmedns and geodb
2024-06-10 15:36:20 +08:00
b00e302f6d Added new custom header structure
+ 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 #80
+ Completed stream proxy module (TCP and UDP)
+ Optimized UX for reminding user to click Apply after port change
+ Added version number to footer #160
2024-06-09 22:49:35 +08:00
deddb17803 Updated Stream Proxy module
- Fixed stream proxy stopping racing condition bug
- Merged PR #187
- Updated stream proxy UI
2024-06-08 00:33:29 +08:00
aa96d831e1 Merge pull request #187 from Kirari04/main
fix missing / unnecessary error check
2024-06-07 01:14:14 +08:00
c6f7f37aaf Added stream proxy UDP support
+ Added UDP support #147 (wip)
+ Updated structure for proxy storage
+ Renamed TCPprox module to streamproxy
+ Added multi selection for white / blacklist #176
2024-06-07 01:12:42 +08:00
63f12dedcf fix missing / unnecessary error check 2024-06-06 18:13:02 +02:00
136d1ecafb init commit
+ Added fastly_client_ip to X-Real-IP auto rewrite
+ Updated header rewrite data structure (wip)
+ Added atomic accumulator to TCP proxy
+ Added wip UDP proxy
+ Added white logo for future dark theme
2024-06-06 11:30:16 +08:00
7193defad7 Merge branch 'main' of https://github.com/tobychui/zoraxy 2024-06-05 23:24:25 +08:00
cf4c57298e Update index.html
Updated index slogan
2024-06-05 23:24:11 +08:00
d82a531a41 Update README.md
Added itsvmn's getting start blog post
2024-05-31 12:43:07 +08:00
7694e317f7 Merge pull request #175 from Morethanevil/main
Update CHANGELOG.md
2024-05-26 13:57:12 +08:00
ed4945ab7e Update CHANGELOG.md 2024-05-26 07:51:34 +02:00
ce8741bfc8 Merge pull request #174 from tobychui/v3.0.5
- Optimized uptime monitor error message
- Optimized detection logic for internal proxy target and header rewrite condition for HTTP_HOST
- Fixed ovh DNS challenge provider form generator bug
- Configuration for OVH DNS Challenge
- Added permission policy module (not enabled)
- Added single-use cookiejar to uptime monitor request client to handle cookie issues on some poorly written back-end server
2024-05-26 13:26:03 +08:00
7a3db09811 Updated acmedns generator results 2024-05-26 13:13:43 +08:00
e73f9b47d3 Update issue templates 2024-05-26 12:32:29 +08:00
c248dacccf Update uptime.go
+ Added cookiejar to request client #149
2024-05-25 14:44:48 +08:00
d596d6b843 v3.0.5 init commit
+ Added external domain name detection for PR #168
+ Updated uptime error message in 5xx range
+ Modernized reverse proxy error page template
+ Added wip permission policy module
2024-05-24 22:24:14 +08:00
6feb2d105d Merge pull request #168 from nettybun/issue-164-http-host-header
Use correct Host HTTP header
2024-05-24 20:13:33 +08:00
3a26a5b4d3 Use correct Host HTTP header 2024-05-23 12:03:00 -07:00
2cdd5654ed Update README.md
Fixed wordings
2024-05-21 15:23:54 +08:00
a0d362df4e Update README.md
Added getting started guide
2024-05-21 15:22:55 +08:00
334c1ab131 Updated provider dns credential fields 2024-05-20 21:56:40 +08:00
08d52024ab Fixed bug in generator
Fixed bug for acmedns module auto generation for ovh provider #161
2024-05-20 21:53:53 +08:00
a3e16594e8 Merge pull request #155 from Morethanevil/main
Update CHANGELOG.md
2024-05-18 21:30:42 +08:00
cced07ba2d Update CHANGELOG.md 2024-05-18 14:11:57 +02:00
2003992d75 Update README.md
Updated project desc
2024-05-18 15:30:21 +08:00
71423d98b1 Updated README banner
Updated readme banner
2024-05-18 15:27:53 +08:00
8ca716c59f Update README.md
Added DNS challenge in feature list
2024-05-18 15:12:25 +08:00
b1a14872c3 EAB implementation done 2023-09-02 18:56:04 -07:00
df9deb3fbb Merge remote-tracking branch 'upstream/main' 2023-09-02 18:03:49 -07:00
9369237229 updated EAB 2023-08-20 22:29:15 -07:00
154 changed files with 30506 additions and 34601 deletions

View File

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

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

View File

@ -33,7 +33,6 @@ jobs:
docker buildx create --name mainbuilder --driver docker-container --platform linux/amd64,linux/arm64 --use docker buildx create --name mainbuilder --driver docker-container --platform linux/amd64,linux/arm64 --use
docker buildx build --push \ docker buildx build --push \
--build-arg VERSION=${{ github.event.release.tag_name }} \
--provenance=false \ --provenance=false \
--platform linux/amd64,linux/arm64 \ --platform linux/amd64,linux/arm64 \
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \ --tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \

6
.gitignore vendored
View File

@ -34,3 +34,9 @@ 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/acmedns
/tools/dns_challenge_update/code-gen/lego /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/

View File

@ -1,3 +1,85 @@
# 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 # v3.0.3 Apr 30 2024
## Breaking Change ## Breaking Change

View File

@ -2,9 +2,8 @@
# Zoraxy # Zoraxy
General purpose request (reverse) proxy and forwarding tool for networking noobs. Now written in Go! A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
*Zoraxy v3 HTTP proxy config is not compatible with the older v2. If you are looking for the legacy version of Zoraxy, take a look at the [v2 branch](https://github.com/tobychui/zoraxy/tree/v2)*
### Features ### Features
@ -19,14 +18,17 @@ General purpose request (reverse) proxy and forwarding tool for networking noobs
- TLS / SSL setup and deploy - TLS / SSL setup and deploy
- ACME features like auto-renew to serve your sites in http**s** - ACME features like auto-renew to serve your sites in http**s**
- SNI support (and SAN certs) - 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) - Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)
- Global Area Network Controller Web UI (ZeroTier not included) - Global Area Network Controller Web UI (ZeroTier not included)
- TCP Tunneling / Proxy - Stream Proxy (TCP & UDP)
- Integrated Up-time Monitor - Integrated Up-time Monitor
- Web-SSH Terminal - Web-SSH Terminal
- Utilities - Utilities
- CIDR IP converters - CIDR IP converters
- mDNS Scanner - mDNS Scanner
- Wake-On-Lan
- Debug Forward Proxy
- IP Scanner - IP Scanner
- Others - Others
- Basic single-admin management mode - Basic single-admin management mode
@ -41,6 +43,12 @@ General purpose request (reverse) proxy and forwarding tool for networking noobs
For other systems or architectures, please see [Release](https://github.com/tobychui/zoraxy/releases/latest/) 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 ## Build from Source
Requires Go 1.22 or higher Requires Go 1.22 or higher
@ -60,7 +68,7 @@ Zoraxy provides basic authentication system for standalone mode. To use it in st
### Standalone Mode ### Standalone Mode
Standalone mode is the default mode for Zoraxy. This allows a single account to manage your reverse proxy server, just like a home router. This mode is suitable for new owners to homelabs or makers starting 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
@ -90,10 +98,12 @@ See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder fo
Usage of zoraxy: Usage of zoraxy:
-autorenew int -autorenew int
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400) ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
-cfgupgrade
Enable auto config upgrade if breaking change is detected (default true)
-docker
Run Zoraxy in docker compatibility mode
-fastgeoip -fastgeoip
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices) Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
-log
Log terminal output to file (default true)
-mdns -mdns
Enable mDNS scanner and transponder (default true) Enable mDNS scanner and transponder (default true)
-mdnsname string -mdnsname string
@ -182,4 +192,3 @@ If you like the project and want to support us, please consider a donation. You
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.** This project is open-sourced under AGPL. I open-sourced this project so everyone can check for security issues and benefit all users. **This software is intended to be free of charge. If you have acquired this software from a third-party seller, the authors of this repository bears no responsibility for any technical difficulties assistance or support.**

View File

@ -1,8 +1,4 @@
FROM docker.io/golang:alpine FROM docker.io/golang:alpine AS build
# VERSION comes from the main.yml workflow --build-arg
ARG VERSION
RUN apk add --no-cache bash netcat-openbsd sudo
RUN mkdir -p /opt/zoraxy/source/ &&\ RUN mkdir -p /opt/zoraxy/source/ &&\
mkdir -p /opt/zoraxy/config/ &&\ mkdir -p /opt/zoraxy/config/ &&\
@ -10,8 +6,6 @@ RUN mkdir -p /opt/zoraxy/source/ &&\
RUN chmod -R 770 /opt/zoraxy/ RUN chmod -R 770 /opt/zoraxy/
VOLUME [ "/opt/zoraxy/config/" ]
# If you build it yourself, you will need to add the src directory into the docker directory. # If you build it yourself, you will need to add the src directory into the docker directory.
COPY ./src/ /opt/zoraxy/source/ COPY ./src/ /opt/zoraxy/source/
@ -24,12 +18,31 @@ RUN go mod tidy &&\
RUN chmod 755 /usr/local/bin/zoraxy &&\ RUN chmod 755 /usr/local/bin/zoraxy &&\
chmod +x /usr/local/bin/zoraxy chmod +x /usr/local/bin/zoraxy
FROM docker.io/alpine: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 ARGS="-noauth=false" 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"
ENTRYPOINT "zoraxy" "-port=:8000" "${ARGS}" 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}"
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

View File

@ -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. |
| `-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. | | `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"
``` ```

View File

@ -80,7 +80,7 @@
<div class="bannerHeaderWrapper"> <div class="bannerHeaderWrapper">
<h1 class="bannerHeader">Zoraxy</h1> <h1 class="bannerHeader">Zoraxy</h1>
<div class="ui divider"></div><br> <div class="ui divider"></div><br>
<p class="bannerSubheader">All in one homelab network routing solution</p> <p class="bannerSubheader">Beyond Reverse Proxy: Your Ultimate Homelab Network Tool</p>
</div> </div>
<br><br> <br><br>
<a class="ui basic big button" style="background-color: white;" href="#features"><i class="ui blue arrow down icon"></i> 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>

View File

@ -15,4 +15,12 @@ The templates folder contains the template for overriding the build in error or
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). 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 It is worth mentioning that the uwu icons for not-found and access-denied are created by @SAWARATSUKI

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

View File

@ -19,7 +19,7 @@ clean:
$(PLATFORMS): $(PLATFORMS):
@echo "Building $(os)/$(arch)" @echo "Building $(os)/$(arch)"
GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) $(if $(filter linux/arm,$(os)/$(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 # GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) GOARM=6 go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath

View File

@ -22,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)
}, },
@ -37,12 +37,12 @@ func initAPIs() {
if development { if development {
fs = http.FileServer(http.Dir("web/")) fs = http.FileServer(http.Dir("web/"))
} }
//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)
@ -61,6 +61,12 @@ func initAPIs() {
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener) authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck) authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange) authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
//Reverse proxy upstream (load balance) APIs
authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList)
authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd)
authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority)
authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate)
authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete)
//Reverse proxy virtual directory APIs //Reverse proxy virtual directory APIs
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir) authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir) authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
@ -70,6 +76,10 @@ func initAPIs() {
authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList) authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList)
authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd) authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd)
authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove) 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)
@ -79,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)
@ -119,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)
@ -140,15 +151,14 @@ func initAPIs() {
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization) authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete) authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
//TCP Proxy //Stream (TCP / UDP) Proxy
authRouter.HandleFunc("/api/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)
@ -177,8 +187,8 @@ func initAPIs() {
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort) 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)
@ -213,31 +223,38 @@ func initAPIs() {
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete) 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)
@ -247,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) {
@ -263,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)

View File

@ -182,27 +182,28 @@ func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
sysdb.Read("settings", "usetls", &currentTlsSetting) sysdb.Read("settings", "usetls", &currentTlsSetting)
} }
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")
if err != nil {
utils.SendErrorResponse(w, "new state not set or invalid")
return
}
if newState {
sysdb.Write("settings", "usetls", true) sysdb.Write("settings", "usetls", true)
SystemWideLogger.Println("Enabling TLS mode on reverse proxy") SystemWideLogger.Println("Enabling TLS mode on reverse proxy")
dynamicProxyRouter.UpdateTLSSetting(true) dynamicProxyRouter.UpdateTLSSetting(true)
} else if newState == "false" { } else {
sysdb.Write("settings", "usetls", false) sysdb.Write("settings", "usetls", false)
SystemWideLogger.Println("Disabling TLS mode on reverse proxy") SystemWideLogger.Println("Disabling TLS mode on reverse proxy")
dynamicProxyRouter.UpdateTLSSetting(false) dynamicProxyRouter.UpdateTLSSetting(false)
} else {
utils.SendErrorResponse(w, "invalid state given. Only support true or false")
return
} }
utils.SendOK(w) utils.SendOK(w)
} else {
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
} }
} }
@ -233,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

View File

@ -14,6 +14,7 @@ import (
"time" "time"
"imuslab.com/zoraxy/mod/dynamicproxy" "imuslab.com/zoraxy/mod/dynamicproxy"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
) )
@ -79,7 +80,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
return errors.New("not supported proxy type") return errors.New("not supported proxy type")
} }
SystemWideLogger.PrintAndLog("Proxy", thisConfigEndpoint.RootOrMatchingDomain+" -> "+thisConfigEndpoint.Domain+" routing rule loaded", nil) SystemWideLogger.PrintAndLog("proxy-config", thisConfigEndpoint.RootOrMatchingDomain+" -> "+loadbalance.GetUpstreamsAsString(thisConfigEndpoint.ActiveOrigins)+" routing rule loaded", nil)
return nil return nil
} }
@ -130,12 +131,18 @@ func RemoveReverseProxyConfig(endpoint string) error {
func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) { func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
//Default settings //Default settings
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{ rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
ProxyType: dynamicproxy.ProxyType_Root, ProxyType: dynamicproxy.ProxyType_Root,
RootOrMatchingDomain: "/", RootOrMatchingDomain: "/",
Domain: "127.0.0.1:" + staticWebServer.GetListeningPort(), ActiveOrigins: []*loadbalance.Upstream{
RequireTLS: false, {
OriginIpOrDomain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
RequireTLS: false,
SkipCertValidations: false,
Weight: 0,
},
},
InactiveOrigins: []*loadbalance.Upstream{},
BypassGlobalTLS: false, BypassGlobalTLS: false,
SkipCertValidations: false,
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{}, VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
RequireBasicAuth: false, RequireBasicAuth: false,
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{}, BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},

View File

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

View File

@ -6,6 +6,7 @@ toolchain go1.22.2
require ( require (
github.com/boltdb/bolt v1.3.1 github.com/boltdb/bolt v1.3.1
github.com/docker/docker v27.0.0+incompatible
github.com/go-acme/lego/v4 v4.16.1 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.6.0 github.com/google/uuid v1.6.0
@ -14,13 +15,13 @@ require (
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.26 github.com/microcosm-cc/bluemonday v1.0.26
golang.org/x/net v0.23.0 golang.org/x/net v0.25.0
golang.org/x/sys v0.18.0 golang.org/x/sys v0.20.0
golang.org/x/text v0.14.0 golang.org/x/text v0.15.0
) )
require ( require (
cloud.google.com/go/compute v1.20.1 // indirect cloud.google.com/go/compute v1.25.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // 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 v68.0.0+incompatible // indirect
@ -39,8 +40,8 @@ require (
github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // 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/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/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // 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 v1.24.1 // indirect
@ -64,30 +65,37 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/civo/civogo v0.3.11 // indirect github.com/civo/civogo v0.3.11 // indirect
github.com/cloudflare/cloudflare-go v0.86.0 // indirect github.com/cloudflare/cloudflare-go v0.86.0 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect github.com/deepmap/oapi-codegen v1.9.1 // indirect
github.com/dimchansky/utfbom v1.1.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/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/exoscale/egoscale v0.102.3 // indirect
github.com/fatih/structs v1.1.0 // 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/fsnotify/fsnotify v1.7.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect github.com/go-errors/errors v1.0.1 // indirect
github.com/go-jose/go-jose/v4 v4.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-resty/resty/v2 v2.11.0 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // 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-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/s2a-go v0.1.4 // indirect github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/googleapis/gax-go/v2 v2.12.2 // indirect
github.com/gophercloud/gophercloud v1.0.0 // indirect github.com/gophercloud/gophercloud v1.0.0 // indirect
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect github.com/gorilla/csrf v1.7.2 // indirect
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect
@ -111,8 +119,11 @@ require (
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // 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/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/nrdcg/auroradns v1.1.0 // indirect github.com/nrdcg/auroradns v1.1.0 // indirect
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect
@ -125,9 +136,9 @@ require (
github.com/nrdcg/nodion v0.1.0 // indirect github.com/nrdcg/nodion v0.1.0 // indirect
github.com/nrdcg/porkbun v0.3.0 // indirect github.com/nrdcg/porkbun v0.3.0 // indirect
github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect
github.com/oracle/oci-go-sdk v24.3.0+incompatible // 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/ovh/go-ovh v1.4.3 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
@ -142,8 +153,8 @@ require (
github.com/softlayer/softlayer-go v1.1.3 // indirect github.com/softlayer/softlayer-go v1.1.3 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cast v1.3.1 // indirect
github.com/stretchr/objx v0.5.1 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.8.4 // 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/common v1.0.490 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod 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/transip/gotransip/v6 v6.23.0 // indirect
@ -153,22 +164,29 @@ require (
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // 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 github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
go.opencensus.io v0.24.0 // 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 go.uber.org/ratelimit v0.2.0 // indirect
golang.org/x/crypto v0.21.0 // indirect golang.org/x/crypto v0.23.0 // indirect
golang.org/x/mod v0.16.0 // indirect golang.org/x/mod v0.16.0 // indirect
golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sync v0.6.0 // indirect golang.org/x/sync v0.6.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.19.0 // indirect golang.org/x/tools v0.19.0 // indirect
google.golang.org/api v0.126.0 // indirect google.golang.org/api v0.169.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
google.golang.org/grpc v1.55.0 // indirect google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
) )

View File

@ -6,8 +6,8 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
@ -33,6 +33,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
@ -60,11 +62,11 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkM
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY=
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass=
@ -135,18 +137,15 @@ github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpX
github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw= github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
@ -161,10 +160,18 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dnsimple/dnsimple-go v1.2.0 h1:ddTGyLVKly5HKb5L65AkLqFqwZlWo3WnR0BlFZlIddM= github.com/dnsimple/dnsimple-go v1.2.0 h1:ddTGyLVKly5HKb5L65AkLqFqwZlWo3WnR0BlFZlIddM=
github.com/dnsimple/dnsimple-go v1.2.0/go.mod h1:z/cs26v/eiRvUyXsHQBLd8lWF8+cD6GbmkPH84plM4U= github.com/dnsimple/dnsimple-go v1.2.0/go.mod h1:z/cs26v/eiRvUyXsHQBLd8lWF8+cD6GbmkPH84plM4U=
github.com/docker/docker v27.0.0+incompatible h1:JRugTYuelmWlW0M3jakcIadDx2HUoUO6+Tf2C5jVfwA=
github.com/docker/docker v27.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -179,6 +186,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
@ -201,6 +210,11 @@ github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiK
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
@ -226,7 +240,10 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
@ -256,8 +273,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -284,26 +301,24 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA=
github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc=
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k= github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k=
github.com/gophercloud/gophercloud v1.0.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c= github.com/gophercloud/gophercloud v1.0.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c=
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15Kfovd8MTZrcana+UlQqNbOif8dLpA0=
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@ -320,9 +335,10 @@ github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfc
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
@ -345,7 +361,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -382,6 +397,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
@ -471,6 +487,10 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -478,10 +498,11 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo= github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo=
github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk=
@ -521,13 +542,13 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0= github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0=
github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY= github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
@ -582,6 +603,7 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22/go.mod h1:fCa7OJZ/9DRTnOKmxvT
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@ -615,8 +637,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -626,9 +648,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
@ -649,14 +670,12 @@ github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4
github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f h1:cG+ehPRJSlqljSufLf1KXeXpUd1dLNjnzA18mZcB/O0= github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f h1:cG+ehPRJSlqljSufLf1KXeXpUd1dLNjnzA18mZcB/O0=
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 h1:2wzke3JH7OtN20WsNDZx2VH/TCmsbqtDEbXzjF+i05E= github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 h1:2wzke3JH7OtN20WsNDZx2VH/TCmsbqtDEbXzjF+i05E=
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997/go.mod h1:2CHKs/YGbCcNn/BPaCkEBwKz/FNCELi+MLILjR9RaTA= github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997/go.mod h1:2CHKs/YGbCcNn/BPaCkEBwKz/FNCELi+MLILjR9RaTA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@ -665,7 +684,23 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY=
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI=
go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@ -683,18 +718,16 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -714,6 +747,7 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -739,6 +773,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@ -755,14 +790,14 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -828,16 +863,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -850,8 +885,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -879,8 +914,10 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@ -896,14 +933,14 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY=
google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -916,12 +953,12 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -933,9 +970,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -948,8 +984,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -959,10 +995,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
@ -985,6 +1018,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -12,24 +12,29 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/csrf"
"imuslab.com/zoraxy/mod/access" "imuslab.com/zoraxy/mod/access"
"imuslab.com/zoraxy/mod/acme" "imuslab.com/zoraxy/mod/acme"
"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/forwardproxy"
"imuslab.com/zoraxy/mod/ganserv" "imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb" "imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/info/logger"
"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" "imuslab.com/zoraxy/mod/webserv"
@ -44,17 +49,19 @@ var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transpo
var mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)") 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 staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder") var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
var logOutputToFile = flag.Bool("log", true, "Log terminal output to file") var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
var ( var (
name = "Zoraxy" name = "Zoraxy"
version = "3.0.4" 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()
/* /*
@ -66,29 +73,35 @@ var (
/* /*
Handler Modules Handler Modules
*/ */
sysdb *database.Database //System database sysdb *database.Database //System database
authAgent *auth.AuthAgent //Authentication agent authAgent *auth.AuthAgent //Authentication agent
tlsCertManager *tlscert.Manager //TLS / SSL management tlsCertManager *tlscert.Manager //TLS / SSL management
redirectTable *redirection.RuleTable //Handle special redirection rule sets redirectTable *redirection.RuleTable //Handle special redirection rule sets
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers webminPanelMux *http.ServeMux //Server mux for handling webmin panel APIs
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code csrfMiddleware func(http.Handler) http.Handler //CSRF protection middleware
accessController *access.Controller //Access controller, handle black list and white list
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
statisticCollector *statistic.Collector //Collecting statistic from visitors geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
uptimeMonitor *uptime.Monitor //Uptime monitor service worker accessController *access.Controller //Access controller, handle black list and white list
mdnsScanner *mdns.MDNSHost //mDNS discovery services netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
ganManager *ganserv.NetworkManager //Global Area Network Manager statisticCollector *statistic.Collector //Collecting statistic from visitors
webSshManager *sshprox.Manager //Web SSH connection service uptimeMonitor *uptime.Monitor //Uptime monitor service worker
tcpProxyManager *tcpprox.Manager //TCP Proxy Manager mdnsScanner *mdns.MDNSHost //mDNS discovery services
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew ganManager *ganserv.NetworkManager //Global Area Network Manager
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking webSshManager *sshprox.Manager //Web SSH connection service
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
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
SystemWideLogger *logger.Logger //Logger for Zoraxy 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.
@ -103,32 +116,34 @@ func SetupCloseHandler() {
} }
func ShutdownSeq() { func ShutdownSeq() {
fmt.Println("- Shutting down " + name) SystemWideLogger.Println("Shutting down " + name)
fmt.Println("- Closing GeoDB ") SystemWideLogger.Println("Closing GeoDB ")
geodbStore.Close() geodbStore.Close()
fmt.Println("- Closing Netstats Listener") SystemWideLogger.Println("Closing Netstats Listener")
netstatBuffers.Close() netstatBuffers.Close()
fmt.Println("- Closing Statistic Collector") SystemWideLogger.Println("Closing Statistic Collector")
statisticCollector.Close() statisticCollector.Close()
if mdnsTickerStop != nil { if mdnsTickerStop != nil {
fmt.Println("- Stopping mDNS Discoverer (might take a few minutes)") SystemWideLogger.Println("Stopping mDNS Discoverer (might take a few minutes)")
// Stop the mdns service // Stop the mdns service
mdnsTickerStop <- true mdnsTickerStop <- true
} }
mdnsScanner.Close() mdnsScanner.Close()
fmt.Println("- Closing Certificates Auto Renewer") SystemWideLogger.Println("Shutting down load balancer")
loadBalancer.Close()
SystemWideLogger.Println("Closing Certificates Auto Renewer")
acmeAutoRenewer.Close() acmeAutoRenewer.Close()
//Remove the tmp folder //Remove the tmp folder
fmt.Println("- Cleaning up tmp files") SystemWideLogger.Println("Cleaning up tmp files")
os.RemoveAll("./tmp") os.RemoveAll("./tmp")
fmt.Println("- Closing system wide logger") //Close database
SystemWideLogger.Close() SystemWideLogger.Println("Stopping system database")
//Close database, final
fmt.Println("- Stopping system database")
sysdb.Close() sysdb.Close()
//Close logger
SystemWideLogger.Println("Closing system wide logger")
SystemWideLogger.Close()
} }
func main() { func main() {
@ -139,6 +154,16 @@ func main() {
os.Exit(0) os.Exit(0)
} }
if !utils.ValidateListeningAddress(*webUIPort) {
fmt.Println("Malformed -port (listening address) paramter. Do you mean -port=:" + *webUIPort + "?")
os.Exit(0)
}
if *enableAutoUpdate {
fmt.Println("Checking required config update")
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version))
}
SetupCloseHandler() SetupCloseHandler()
//Read or create the system uuid //Read or create the system uuid
@ -154,12 +179,22 @@ func main() {
} }
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) 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() {
@ -172,7 +207,7 @@ func main() {
finalSequence() finalSequence()
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort) SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
err = http.ListenAndServe(*webUIPort, nil) err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -448,7 +448,12 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
} }
domains := strings.Split(domainPara, ",") domains := strings.Split(domainPara, ",")
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS, dns) //Clean spaces in front or behind each domain
cleanedDomains := []string{}
for _, domain := range domains {
cleanedDomains = append(cleanedDomains, strings.TrimSpace(domain))
}
result, err := a.ObtainCert(cleanedDomains, filename, email, ca, caUrl, skipTLS, dns)
if err != nil { if err != nil {
utils.SendErrorResponse(w, jsonEscape(err.Error())) utils.SendErrorResponse(w, jsonEscape(err.Error()))
return return

View File

@ -1,11 +1,6 @@
package acme package acme
import ( import (
"errors"
"log"
"os"
"strings"
"github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge"
"imuslab.com/zoraxy/mod/acme/acmedns" "imuslab.com/zoraxy/mod/acme/acmedns"
) )
@ -29,7 +24,7 @@ func GetDnsChallengeProviderByName(dnsProvider string, dnsCredentials string) (c
/* /*
Original implementation of DNS ACME using OS.Env as payload Original implementation of DNS ACME using OS.Env as payload
*/ */
/*
func setCredentialsIntoEnvironmentVariables(credentials map[string]string) { func setCredentialsIntoEnvironmentVariables(credentials map[string]string) {
for key, value := range credentials { for key, value := range credentials {
err := os.Setenv(key, value) err := os.Setenv(key, value)
@ -41,6 +36,7 @@ func setCredentialsIntoEnvironmentVariables(credentials map[string]string) {
} }
} }
func extractDnsCredentials(input string) (map[string]string, error) { func extractDnsCredentials(input string) (map[string]string, error) {
result := make(map[string]string) result := make(map[string]string)
@ -70,3 +66,5 @@ func extractDnsCredentials(input string) (map[string]string, error) {
return result, nil return result, nil
} }
*/

View File

@ -6,6 +6,7 @@ package acmedns
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"time"
"github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/alidns" "github.com/go-acme/lego/v4/providers/dns/alidns"
@ -142,6 +143,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return alidns.NewDNSProviderConfig(cfg) return alidns.NewDNSProviderConfig(cfg)
case "allinkl": case "allinkl":
cfg := allinkl.NewDefaultConfig() cfg := allinkl.NewDefaultConfig()
@ -149,6 +151,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return allinkl.NewDNSProviderConfig(cfg) return allinkl.NewDNSProviderConfig(cfg)
case "arvancloud": case "arvancloud":
cfg := arvancloud.NewDefaultConfig() cfg := arvancloud.NewDefaultConfig()
@ -156,6 +159,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return arvancloud.NewDNSProviderConfig(cfg) return arvancloud.NewDNSProviderConfig(cfg)
case "auroradns": case "auroradns":
cfg := auroradns.NewDefaultConfig() cfg := auroradns.NewDefaultConfig()
@ -163,6 +167,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return auroradns.NewDNSProviderConfig(cfg) return auroradns.NewDNSProviderConfig(cfg)
case "autodns": case "autodns":
cfg := autodns.NewDefaultConfig() cfg := autodns.NewDefaultConfig()
@ -170,6 +175,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return autodns.NewDNSProviderConfig(cfg) return autodns.NewDNSProviderConfig(cfg)
case "azure": case "azure":
cfg := azure.NewDefaultConfig() cfg := azure.NewDefaultConfig()
@ -177,6 +183,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return azure.NewDNSProviderConfig(cfg) return azure.NewDNSProviderConfig(cfg)
case "azuredns": case "azuredns":
cfg := azuredns.NewDefaultConfig() cfg := azuredns.NewDefaultConfig()
@ -184,6 +191,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return azuredns.NewDNSProviderConfig(cfg) return azuredns.NewDNSProviderConfig(cfg)
case "bindman": case "bindman":
cfg := bindman.NewDefaultConfig() cfg := bindman.NewDefaultConfig()
@ -191,6 +199,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return bindman.NewDNSProviderConfig(cfg) return bindman.NewDNSProviderConfig(cfg)
case "bluecat": case "bluecat":
cfg := bluecat.NewDefaultConfig() cfg := bluecat.NewDefaultConfig()
@ -198,6 +207,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return bluecat.NewDNSProviderConfig(cfg) return bluecat.NewDNSProviderConfig(cfg)
case "brandit": case "brandit":
cfg := brandit.NewDefaultConfig() cfg := brandit.NewDefaultConfig()
@ -205,6 +215,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return brandit.NewDNSProviderConfig(cfg) return brandit.NewDNSProviderConfig(cfg)
case "bunny": case "bunny":
cfg := bunny.NewDefaultConfig() cfg := bunny.NewDefaultConfig()
@ -212,6 +223,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return bunny.NewDNSProviderConfig(cfg) return bunny.NewDNSProviderConfig(cfg)
case "checkdomain": case "checkdomain":
cfg := checkdomain.NewDefaultConfig() cfg := checkdomain.NewDefaultConfig()
@ -219,6 +231,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return checkdomain.NewDNSProviderConfig(cfg) return checkdomain.NewDNSProviderConfig(cfg)
case "civo": case "civo":
cfg := civo.NewDefaultConfig() cfg := civo.NewDefaultConfig()
@ -226,6 +239,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return civo.NewDNSProviderConfig(cfg) return civo.NewDNSProviderConfig(cfg)
case "clouddns": case "clouddns":
cfg := clouddns.NewDefaultConfig() cfg := clouddns.NewDefaultConfig()
@ -233,6 +247,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return clouddns.NewDNSProviderConfig(cfg) return clouddns.NewDNSProviderConfig(cfg)
case "cloudflare": case "cloudflare":
cfg := cloudflare.NewDefaultConfig() cfg := cloudflare.NewDefaultConfig()
@ -240,6 +255,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return cloudflare.NewDNSProviderConfig(cfg) return cloudflare.NewDNSProviderConfig(cfg)
case "cloudns": case "cloudns":
cfg := cloudns.NewDefaultConfig() cfg := cloudns.NewDefaultConfig()
@ -247,6 +263,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return cloudns.NewDNSProviderConfig(cfg) return cloudns.NewDNSProviderConfig(cfg)
case "cloudru": case "cloudru":
cfg := cloudru.NewDefaultConfig() cfg := cloudru.NewDefaultConfig()
@ -254,6 +271,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return cloudru.NewDNSProviderConfig(cfg) return cloudru.NewDNSProviderConfig(cfg)
case "cloudxns": case "cloudxns":
cfg := cloudxns.NewDefaultConfig() cfg := cloudxns.NewDefaultConfig()
@ -261,6 +279,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return cloudxns.NewDNSProviderConfig(cfg) return cloudxns.NewDNSProviderConfig(cfg)
case "conoha": case "conoha":
cfg := conoha.NewDefaultConfig() cfg := conoha.NewDefaultConfig()
@ -268,6 +287,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return conoha.NewDNSProviderConfig(cfg) return conoha.NewDNSProviderConfig(cfg)
case "constellix": case "constellix":
cfg := constellix.NewDefaultConfig() cfg := constellix.NewDefaultConfig()
@ -275,6 +295,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return constellix.NewDNSProviderConfig(cfg) return constellix.NewDNSProviderConfig(cfg)
case "cpanel": case "cpanel":
cfg := cpanel.NewDefaultConfig() cfg := cpanel.NewDefaultConfig()
@ -282,6 +303,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return cpanel.NewDNSProviderConfig(cfg) return cpanel.NewDNSProviderConfig(cfg)
case "derak": case "derak":
cfg := derak.NewDefaultConfig() cfg := derak.NewDefaultConfig()
@ -289,6 +311,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return derak.NewDNSProviderConfig(cfg) return derak.NewDNSProviderConfig(cfg)
case "desec": case "desec":
cfg := desec.NewDefaultConfig() cfg := desec.NewDefaultConfig()
@ -296,6 +319,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return desec.NewDNSProviderConfig(cfg) return desec.NewDNSProviderConfig(cfg)
case "digitalocean": case "digitalocean":
cfg := digitalocean.NewDefaultConfig() cfg := digitalocean.NewDefaultConfig()
@ -303,6 +327,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return digitalocean.NewDNSProviderConfig(cfg) return digitalocean.NewDNSProviderConfig(cfg)
case "dnshomede": case "dnshomede":
cfg := dnshomede.NewDefaultConfig() cfg := dnshomede.NewDefaultConfig()
@ -310,6 +335,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return dnshomede.NewDNSProviderConfig(cfg) return dnshomede.NewDNSProviderConfig(cfg)
case "dnsimple": case "dnsimple":
cfg := dnsimple.NewDefaultConfig() cfg := dnsimple.NewDefaultConfig()
@ -317,6 +343,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return dnsimple.NewDNSProviderConfig(cfg) return dnsimple.NewDNSProviderConfig(cfg)
case "dnsmadeeasy": case "dnsmadeeasy":
cfg := dnsmadeeasy.NewDefaultConfig() cfg := dnsmadeeasy.NewDefaultConfig()
@ -324,6 +351,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return dnsmadeeasy.NewDNSProviderConfig(cfg) return dnsmadeeasy.NewDNSProviderConfig(cfg)
case "dnspod": case "dnspod":
cfg := dnspod.NewDefaultConfig() cfg := dnspod.NewDefaultConfig()
@ -331,6 +359,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return dnspod.NewDNSProviderConfig(cfg) return dnspod.NewDNSProviderConfig(cfg)
case "dode": case "dode":
cfg := dode.NewDefaultConfig() cfg := dode.NewDefaultConfig()
@ -338,6 +367,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return dode.NewDNSProviderConfig(cfg) return dode.NewDNSProviderConfig(cfg)
case "domeneshop": case "domeneshop":
cfg := domeneshop.NewDefaultConfig() cfg := domeneshop.NewDefaultConfig()
@ -345,6 +375,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return domeneshop.NewDNSProviderConfig(cfg) return domeneshop.NewDNSProviderConfig(cfg)
case "dreamhost": case "dreamhost":
cfg := dreamhost.NewDefaultConfig() cfg := dreamhost.NewDefaultConfig()
@ -352,6 +383,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return dreamhost.NewDNSProviderConfig(cfg) return dreamhost.NewDNSProviderConfig(cfg)
case "duckdns": case "duckdns":
cfg := duckdns.NewDefaultConfig() cfg := duckdns.NewDefaultConfig()
@ -359,6 +391,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return duckdns.NewDNSProviderConfig(cfg) return duckdns.NewDNSProviderConfig(cfg)
case "dyn": case "dyn":
cfg := dyn.NewDefaultConfig() cfg := dyn.NewDefaultConfig()
@ -366,6 +399,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return dyn.NewDNSProviderConfig(cfg) return dyn.NewDNSProviderConfig(cfg)
case "dynu": case "dynu":
cfg := dynu.NewDefaultConfig() cfg := dynu.NewDefaultConfig()
@ -373,6 +407,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return dynu.NewDNSProviderConfig(cfg) return dynu.NewDNSProviderConfig(cfg)
case "easydns": case "easydns":
cfg := easydns.NewDefaultConfig() cfg := easydns.NewDefaultConfig()
@ -380,6 +415,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return easydns.NewDNSProviderConfig(cfg) return easydns.NewDNSProviderConfig(cfg)
case "efficientip": case "efficientip":
cfg := efficientip.NewDefaultConfig() cfg := efficientip.NewDefaultConfig()
@ -387,6 +423,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return efficientip.NewDNSProviderConfig(cfg) return efficientip.NewDNSProviderConfig(cfg)
case "epik": case "epik":
cfg := epik.NewDefaultConfig() cfg := epik.NewDefaultConfig()
@ -394,6 +431,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return epik.NewDNSProviderConfig(cfg) return epik.NewDNSProviderConfig(cfg)
case "exoscale": case "exoscale":
cfg := exoscale.NewDefaultConfig() cfg := exoscale.NewDefaultConfig()
@ -401,6 +439,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return exoscale.NewDNSProviderConfig(cfg) return exoscale.NewDNSProviderConfig(cfg)
case "freemyip": case "freemyip":
cfg := freemyip.NewDefaultConfig() cfg := freemyip.NewDefaultConfig()
@ -408,6 +447,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return freemyip.NewDNSProviderConfig(cfg) return freemyip.NewDNSProviderConfig(cfg)
case "gandi": case "gandi":
cfg := gandi.NewDefaultConfig() cfg := gandi.NewDefaultConfig()
@ -415,6 +455,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return gandi.NewDNSProviderConfig(cfg) return gandi.NewDNSProviderConfig(cfg)
case "gandiv5": case "gandiv5":
cfg := gandiv5.NewDefaultConfig() cfg := gandiv5.NewDefaultConfig()
@ -422,6 +463,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return gandiv5.NewDNSProviderConfig(cfg) return gandiv5.NewDNSProviderConfig(cfg)
case "gcore": case "gcore":
cfg := gcore.NewDefaultConfig() cfg := gcore.NewDefaultConfig()
@ -429,6 +471,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return gcore.NewDNSProviderConfig(cfg) return gcore.NewDNSProviderConfig(cfg)
case "glesys": case "glesys":
cfg := glesys.NewDefaultConfig() cfg := glesys.NewDefaultConfig()
@ -436,6 +479,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return glesys.NewDNSProviderConfig(cfg) return glesys.NewDNSProviderConfig(cfg)
case "godaddy": case "godaddy":
cfg := godaddy.NewDefaultConfig() cfg := godaddy.NewDefaultConfig()
@ -443,6 +487,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return godaddy.NewDNSProviderConfig(cfg) return godaddy.NewDNSProviderConfig(cfg)
case "googledomains": case "googledomains":
cfg := googledomains.NewDefaultConfig() cfg := googledomains.NewDefaultConfig()
@ -450,6 +495,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return googledomains.NewDNSProviderConfig(cfg) return googledomains.NewDNSProviderConfig(cfg)
case "hetzner": case "hetzner":
cfg := hetzner.NewDefaultConfig() cfg := hetzner.NewDefaultConfig()
@ -457,6 +503,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return hetzner.NewDNSProviderConfig(cfg) return hetzner.NewDNSProviderConfig(cfg)
case "hostingde": case "hostingde":
cfg := hostingde.NewDefaultConfig() cfg := hostingde.NewDefaultConfig()
@ -464,6 +511,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return hostingde.NewDNSProviderConfig(cfg) return hostingde.NewDNSProviderConfig(cfg)
case "hosttech": case "hosttech":
cfg := hosttech.NewDefaultConfig() cfg := hosttech.NewDefaultConfig()
@ -471,6 +519,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return hosttech.NewDNSProviderConfig(cfg) return hosttech.NewDNSProviderConfig(cfg)
case "httpnet": case "httpnet":
cfg := httpnet.NewDefaultConfig() cfg := httpnet.NewDefaultConfig()
@ -478,6 +527,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return httpnet.NewDNSProviderConfig(cfg) return httpnet.NewDNSProviderConfig(cfg)
case "hyperone": case "hyperone":
cfg := hyperone.NewDefaultConfig() cfg := hyperone.NewDefaultConfig()
@ -485,6 +535,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return hyperone.NewDNSProviderConfig(cfg) return hyperone.NewDNSProviderConfig(cfg)
case "ibmcloud": case "ibmcloud":
cfg := ibmcloud.NewDefaultConfig() cfg := ibmcloud.NewDefaultConfig()
@ -492,6 +543,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return ibmcloud.NewDNSProviderConfig(cfg) return ibmcloud.NewDNSProviderConfig(cfg)
case "iij": case "iij":
cfg := iij.NewDefaultConfig() cfg := iij.NewDefaultConfig()
@ -499,6 +551,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return iij.NewDNSProviderConfig(cfg) return iij.NewDNSProviderConfig(cfg)
case "iijdpf": case "iijdpf":
cfg := iijdpf.NewDefaultConfig() cfg := iijdpf.NewDefaultConfig()
@ -506,6 +559,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return iijdpf.NewDNSProviderConfig(cfg) return iijdpf.NewDNSProviderConfig(cfg)
case "infoblox": case "infoblox":
cfg := infoblox.NewDefaultConfig() cfg := infoblox.NewDefaultConfig()
@ -513,6 +567,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return infoblox.NewDNSProviderConfig(cfg) return infoblox.NewDNSProviderConfig(cfg)
case "infomaniak": case "infomaniak":
cfg := infomaniak.NewDefaultConfig() cfg := infomaniak.NewDefaultConfig()
@ -520,6 +575,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return infomaniak.NewDNSProviderConfig(cfg) return infomaniak.NewDNSProviderConfig(cfg)
case "internetbs": case "internetbs":
cfg := internetbs.NewDefaultConfig() cfg := internetbs.NewDefaultConfig()
@ -527,6 +583,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return internetbs.NewDNSProviderConfig(cfg) return internetbs.NewDNSProviderConfig(cfg)
case "inwx": case "inwx":
cfg := inwx.NewDefaultConfig() cfg := inwx.NewDefaultConfig()
@ -534,6 +591,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return inwx.NewDNSProviderConfig(cfg) return inwx.NewDNSProviderConfig(cfg)
case "ionos": case "ionos":
cfg := ionos.NewDefaultConfig() cfg := ionos.NewDefaultConfig()
@ -541,6 +599,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return ionos.NewDNSProviderConfig(cfg) return ionos.NewDNSProviderConfig(cfg)
case "ipv64": case "ipv64":
cfg := ipv64.NewDefaultConfig() cfg := ipv64.NewDefaultConfig()
@ -548,6 +607,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return ipv64.NewDNSProviderConfig(cfg) return ipv64.NewDNSProviderConfig(cfg)
case "iwantmyname": case "iwantmyname":
cfg := iwantmyname.NewDefaultConfig() cfg := iwantmyname.NewDefaultConfig()
@ -555,6 +615,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return iwantmyname.NewDNSProviderConfig(cfg) return iwantmyname.NewDNSProviderConfig(cfg)
case "joker": case "joker":
cfg := joker.NewDefaultConfig() cfg := joker.NewDefaultConfig()
@ -562,6 +623,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return joker.NewDNSProviderConfig(cfg) return joker.NewDNSProviderConfig(cfg)
case "liara": case "liara":
cfg := liara.NewDefaultConfig() cfg := liara.NewDefaultConfig()
@ -569,6 +631,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return liara.NewDNSProviderConfig(cfg) return liara.NewDNSProviderConfig(cfg)
case "lightsail": case "lightsail":
cfg := lightsail.NewDefaultConfig() cfg := lightsail.NewDefaultConfig()
@ -576,6 +639,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return lightsail.NewDNSProviderConfig(cfg) return lightsail.NewDNSProviderConfig(cfg)
case "linode": case "linode":
cfg := linode.NewDefaultConfig() cfg := linode.NewDefaultConfig()
@ -583,6 +647,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return linode.NewDNSProviderConfig(cfg) return linode.NewDNSProviderConfig(cfg)
case "liquidweb": case "liquidweb":
cfg := liquidweb.NewDefaultConfig() cfg := liquidweb.NewDefaultConfig()
@ -590,6 +655,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return liquidweb.NewDNSProviderConfig(cfg) return liquidweb.NewDNSProviderConfig(cfg)
case "loopia": case "loopia":
cfg := loopia.NewDefaultConfig() cfg := loopia.NewDefaultConfig()
@ -597,6 +663,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return loopia.NewDNSProviderConfig(cfg) return loopia.NewDNSProviderConfig(cfg)
case "luadns": case "luadns":
cfg := luadns.NewDefaultConfig() cfg := luadns.NewDefaultConfig()
@ -604,6 +671,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return luadns.NewDNSProviderConfig(cfg) return luadns.NewDNSProviderConfig(cfg)
case "mailinabox": case "mailinabox":
cfg := mailinabox.NewDefaultConfig() cfg := mailinabox.NewDefaultConfig()
@ -611,6 +679,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return mailinabox.NewDNSProviderConfig(cfg) return mailinabox.NewDNSProviderConfig(cfg)
case "metaname": case "metaname":
cfg := metaname.NewDefaultConfig() cfg := metaname.NewDefaultConfig()
@ -618,6 +687,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return metaname.NewDNSProviderConfig(cfg) return metaname.NewDNSProviderConfig(cfg)
case "mydnsjp": case "mydnsjp":
cfg := mydnsjp.NewDefaultConfig() cfg := mydnsjp.NewDefaultConfig()
@ -625,6 +695,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return mydnsjp.NewDNSProviderConfig(cfg) return mydnsjp.NewDNSProviderConfig(cfg)
case "namecheap": case "namecheap":
cfg := namecheap.NewDefaultConfig() cfg := namecheap.NewDefaultConfig()
@ -632,6 +703,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return namecheap.NewDNSProviderConfig(cfg) return namecheap.NewDNSProviderConfig(cfg)
case "namedotcom": case "namedotcom":
cfg := namedotcom.NewDefaultConfig() cfg := namedotcom.NewDefaultConfig()
@ -639,6 +711,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return namedotcom.NewDNSProviderConfig(cfg) return namedotcom.NewDNSProviderConfig(cfg)
case "namesilo": case "namesilo":
cfg := namesilo.NewDefaultConfig() cfg := namesilo.NewDefaultConfig()
@ -646,6 +719,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return namesilo.NewDNSProviderConfig(cfg) return namesilo.NewDNSProviderConfig(cfg)
case "nearlyfreespeech": case "nearlyfreespeech":
cfg := nearlyfreespeech.NewDefaultConfig() cfg := nearlyfreespeech.NewDefaultConfig()
@ -653,20 +727,23 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return nearlyfreespeech.NewDNSProviderConfig(cfg) return nearlyfreespeech.NewDNSProviderConfig(cfg)
case "netcup": case "netcup":
cfg := netcup.NewDefaultConfig() cfg := netcup.NewDefaultConfig()
err := json.Unmarshal([]byte(js), &cfg) err := json.Unmarshal([]byte(js), &cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return netcup.NewDNSProviderConfig(cfg) cfg.PropagationTimeout = 20*time.Minute
return netcup.NewDNSProviderConfig(cfg)
case "netlify": case "netlify":
cfg := netlify.NewDefaultConfig() cfg := netlify.NewDefaultConfig()
err := json.Unmarshal([]byte(js), &cfg) err := json.Unmarshal([]byte(js), &cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return netlify.NewDNSProviderConfig(cfg) return netlify.NewDNSProviderConfig(cfg)
case "nicmanager": case "nicmanager":
cfg := nicmanager.NewDefaultConfig() cfg := nicmanager.NewDefaultConfig()
@ -674,6 +751,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return nicmanager.NewDNSProviderConfig(cfg) return nicmanager.NewDNSProviderConfig(cfg)
case "nifcloud": case "nifcloud":
cfg := nifcloud.NewDefaultConfig() cfg := nifcloud.NewDefaultConfig()
@ -681,6 +759,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return nifcloud.NewDNSProviderConfig(cfg) return nifcloud.NewDNSProviderConfig(cfg)
case "njalla": case "njalla":
cfg := njalla.NewDefaultConfig() cfg := njalla.NewDefaultConfig()
@ -688,6 +767,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return njalla.NewDNSProviderConfig(cfg) return njalla.NewDNSProviderConfig(cfg)
case "nodion": case "nodion":
cfg := nodion.NewDefaultConfig() cfg := nodion.NewDefaultConfig()
@ -695,6 +775,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return nodion.NewDNSProviderConfig(cfg) return nodion.NewDNSProviderConfig(cfg)
case "ns1": case "ns1":
cfg := ns1.NewDefaultConfig() cfg := ns1.NewDefaultConfig()
@ -702,6 +783,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return ns1.NewDNSProviderConfig(cfg) return ns1.NewDNSProviderConfig(cfg)
case "otc": case "otc":
cfg := otc.NewDefaultConfig() cfg := otc.NewDefaultConfig()
@ -709,6 +791,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return otc.NewDNSProviderConfig(cfg) return otc.NewDNSProviderConfig(cfg)
case "ovh": case "ovh":
cfg := ovh.NewDefaultConfig() cfg := ovh.NewDefaultConfig()
@ -716,6 +799,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return ovh.NewDNSProviderConfig(cfg) return ovh.NewDNSProviderConfig(cfg)
case "pdns": case "pdns":
cfg := pdns.NewDefaultConfig() cfg := pdns.NewDefaultConfig()
@ -723,6 +807,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return pdns.NewDNSProviderConfig(cfg) return pdns.NewDNSProviderConfig(cfg)
case "plesk": case "plesk":
cfg := plesk.NewDefaultConfig() cfg := plesk.NewDefaultConfig()
@ -730,6 +815,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return plesk.NewDNSProviderConfig(cfg) return plesk.NewDNSProviderConfig(cfg)
case "porkbun": case "porkbun":
cfg := porkbun.NewDefaultConfig() cfg := porkbun.NewDefaultConfig()
@ -737,6 +823,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return porkbun.NewDNSProviderConfig(cfg) return porkbun.NewDNSProviderConfig(cfg)
case "rackspace": case "rackspace":
cfg := rackspace.NewDefaultConfig() cfg := rackspace.NewDefaultConfig()
@ -744,6 +831,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return rackspace.NewDNSProviderConfig(cfg) return rackspace.NewDNSProviderConfig(cfg)
case "rcodezero": case "rcodezero":
cfg := rcodezero.NewDefaultConfig() cfg := rcodezero.NewDefaultConfig()
@ -751,6 +839,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return rcodezero.NewDNSProviderConfig(cfg) return rcodezero.NewDNSProviderConfig(cfg)
case "regru": case "regru":
cfg := regru.NewDefaultConfig() cfg := regru.NewDefaultConfig()
@ -758,6 +847,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return regru.NewDNSProviderConfig(cfg) return regru.NewDNSProviderConfig(cfg)
case "rfc2136": case "rfc2136":
cfg := rfc2136.NewDefaultConfig() cfg := rfc2136.NewDefaultConfig()
@ -765,6 +855,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return rfc2136.NewDNSProviderConfig(cfg) return rfc2136.NewDNSProviderConfig(cfg)
case "rimuhosting": case "rimuhosting":
cfg := rimuhosting.NewDefaultConfig() cfg := rimuhosting.NewDefaultConfig()
@ -772,6 +863,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return rimuhosting.NewDNSProviderConfig(cfg) return rimuhosting.NewDNSProviderConfig(cfg)
case "route53": case "route53":
cfg := route53.NewDefaultConfig() cfg := route53.NewDefaultConfig()
@ -779,6 +871,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return route53.NewDNSProviderConfig(cfg) return route53.NewDNSProviderConfig(cfg)
case "safedns": case "safedns":
cfg := safedns.NewDefaultConfig() cfg := safedns.NewDefaultConfig()
@ -786,6 +879,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return safedns.NewDNSProviderConfig(cfg) return safedns.NewDNSProviderConfig(cfg)
case "sakuracloud": case "sakuracloud":
cfg := sakuracloud.NewDefaultConfig() cfg := sakuracloud.NewDefaultConfig()
@ -793,6 +887,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return sakuracloud.NewDNSProviderConfig(cfg) return sakuracloud.NewDNSProviderConfig(cfg)
case "scaleway": case "scaleway":
cfg := scaleway.NewDefaultConfig() cfg := scaleway.NewDefaultConfig()
@ -800,6 +895,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return scaleway.NewDNSProviderConfig(cfg) return scaleway.NewDNSProviderConfig(cfg)
case "selectel": case "selectel":
cfg := selectel.NewDefaultConfig() cfg := selectel.NewDefaultConfig()
@ -807,6 +903,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return selectel.NewDNSProviderConfig(cfg) return selectel.NewDNSProviderConfig(cfg)
case "servercow": case "servercow":
cfg := servercow.NewDefaultConfig() cfg := servercow.NewDefaultConfig()
@ -814,6 +911,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return servercow.NewDNSProviderConfig(cfg) return servercow.NewDNSProviderConfig(cfg)
case "shellrent": case "shellrent":
cfg := shellrent.NewDefaultConfig() cfg := shellrent.NewDefaultConfig()
@ -821,6 +919,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return shellrent.NewDNSProviderConfig(cfg) return shellrent.NewDNSProviderConfig(cfg)
case "simply": case "simply":
cfg := simply.NewDefaultConfig() cfg := simply.NewDefaultConfig()
@ -828,6 +927,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return simply.NewDNSProviderConfig(cfg) return simply.NewDNSProviderConfig(cfg)
case "sonic": case "sonic":
cfg := sonic.NewDefaultConfig() cfg := sonic.NewDefaultConfig()
@ -835,6 +935,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return sonic.NewDNSProviderConfig(cfg) return sonic.NewDNSProviderConfig(cfg)
case "stackpath": case "stackpath":
cfg := stackpath.NewDefaultConfig() cfg := stackpath.NewDefaultConfig()
@ -842,6 +943,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return stackpath.NewDNSProviderConfig(cfg) return stackpath.NewDNSProviderConfig(cfg)
case "tencentcloud": case "tencentcloud":
cfg := tencentcloud.NewDefaultConfig() cfg := tencentcloud.NewDefaultConfig()
@ -849,6 +951,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return tencentcloud.NewDNSProviderConfig(cfg) return tencentcloud.NewDNSProviderConfig(cfg)
case "transip": case "transip":
cfg := transip.NewDefaultConfig() cfg := transip.NewDefaultConfig()
@ -856,6 +959,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return transip.NewDNSProviderConfig(cfg) return transip.NewDNSProviderConfig(cfg)
case "ultradns": case "ultradns":
cfg := ultradns.NewDefaultConfig() cfg := ultradns.NewDefaultConfig()
@ -863,6 +967,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return ultradns.NewDNSProviderConfig(cfg) return ultradns.NewDNSProviderConfig(cfg)
case "variomedia": case "variomedia":
cfg := variomedia.NewDefaultConfig() cfg := variomedia.NewDefaultConfig()
@ -870,6 +975,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return variomedia.NewDNSProviderConfig(cfg) return variomedia.NewDNSProviderConfig(cfg)
case "vegadns": case "vegadns":
cfg := vegadns.NewDefaultConfig() cfg := vegadns.NewDefaultConfig()
@ -877,6 +983,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return vegadns.NewDNSProviderConfig(cfg) return vegadns.NewDNSProviderConfig(cfg)
case "vercel": case "vercel":
cfg := vercel.NewDefaultConfig() cfg := vercel.NewDefaultConfig()
@ -884,6 +991,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return vercel.NewDNSProviderConfig(cfg) return vercel.NewDNSProviderConfig(cfg)
case "versio": case "versio":
cfg := versio.NewDefaultConfig() cfg := versio.NewDefaultConfig()
@ -891,6 +999,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return versio.NewDNSProviderConfig(cfg) return versio.NewDNSProviderConfig(cfg)
case "vinyldns": case "vinyldns":
cfg := vinyldns.NewDefaultConfig() cfg := vinyldns.NewDefaultConfig()
@ -898,6 +1007,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return vinyldns.NewDNSProviderConfig(cfg) return vinyldns.NewDNSProviderConfig(cfg)
case "vkcloud": case "vkcloud":
cfg := vkcloud.NewDefaultConfig() cfg := vkcloud.NewDefaultConfig()
@ -905,6 +1015,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return vkcloud.NewDNSProviderConfig(cfg) return vkcloud.NewDNSProviderConfig(cfg)
case "vscale": case "vscale":
cfg := vscale.NewDefaultConfig() cfg := vscale.NewDefaultConfig()
@ -912,6 +1023,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return vscale.NewDNSProviderConfig(cfg) return vscale.NewDNSProviderConfig(cfg)
case "vultr": case "vultr":
cfg := vultr.NewDefaultConfig() cfg := vultr.NewDefaultConfig()
@ -919,6 +1031,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return vultr.NewDNSProviderConfig(cfg) return vultr.NewDNSProviderConfig(cfg)
case "webnames": case "webnames":
cfg := webnames.NewDefaultConfig() cfg := webnames.NewDefaultConfig()
@ -926,6 +1039,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return webnames.NewDNSProviderConfig(cfg) return webnames.NewDNSProviderConfig(cfg)
case "websupport": case "websupport":
cfg := websupport.NewDefaultConfig() cfg := websupport.NewDefaultConfig()
@ -933,6 +1047,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return websupport.NewDNSProviderConfig(cfg) return websupport.NewDNSProviderConfig(cfg)
case "wedos": case "wedos":
cfg := wedos.NewDefaultConfig() cfg := wedos.NewDefaultConfig()
@ -940,6 +1055,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return wedos.NewDNSProviderConfig(cfg) return wedos.NewDNSProviderConfig(cfg)
case "yandex": case "yandex":
cfg := yandex.NewDefaultConfig() cfg := yandex.NewDefaultConfig()
@ -947,6 +1063,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return yandex.NewDNSProviderConfig(cfg) return yandex.NewDNSProviderConfig(cfg)
case "yandex360": case "yandex360":
cfg := yandex360.NewDefaultConfig() cfg := yandex360.NewDefaultConfig()
@ -954,6 +1071,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return yandex360.NewDNSProviderConfig(cfg) return yandex360.NewDNSProviderConfig(cfg)
case "yandexcloud": case "yandexcloud":
cfg := yandexcloud.NewDefaultConfig() cfg := yandexcloud.NewDefaultConfig()
@ -961,6 +1079,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return yandexcloud.NewDNSProviderConfig(cfg) return yandexcloud.NewDNSProviderConfig(cfg)
case "zoneee": case "zoneee":
cfg := zoneee.NewDefaultConfig() cfg := zoneee.NewDefaultConfig()
@ -968,6 +1087,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return zoneee.NewDNSProviderConfig(cfg) return zoneee.NewDNSProviderConfig(cfg)
case "zonomi": case "zonomi":
cfg := zonomi.NewDefaultConfig() cfg := zonomi.NewDefaultConfig()
@ -975,6 +1095,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.PropagationTimeout = 5*time.Minute
return zonomi.NewDNSProviderConfig(cfg) return zonomi.NewDNSProviderConfig(cfg)
default: default:
return nil, fmt.Errorf("unrecognized DNS provider: %s", name) return nil, fmt.Errorf("unrecognized DNS provider: %s", name)

View File

@ -153,6 +153,10 @@
"azure": { "azure": {
"Name": "azure", "Name": "azure",
"ConfigableFields": [ "ConfigableFields": [
{
"Title": "ZoneName",
"Datatype": "string"
},
{ {
"Title": "ClientID", "Title": "ClientID",
"Datatype": "string" "Datatype": "string"
@ -208,6 +212,10 @@
"azuredns": { "azuredns": {
"Name": "azuredns", "Name": "azuredns",
"ConfigableFields": [ "ConfigableFields": [
{
"Title": "ZoneName",
"Datatype": "string"
},
{ {
"Title": "SubscriptionID", "Title": "SubscriptionID",
"Datatype": "string" "Datatype": "string"
@ -343,6 +351,10 @@
{ {
"Title": "HTTPClient", "Title": "HTTPClient",
"Datatype": "*http.Client" "Datatype": "*http.Client"
},
{
"Title": "SkipDeploy",
"Datatype": "bool"
} }
] ]
}, },
@ -1214,10 +1226,6 @@
"gandi": { "gandi": {
"Name": "gandi", "Name": "gandi",
"ConfigableFields": [ "ConfigableFields": [
{
"Title": "BaseURL",
"Datatype": "string"
},
{ {
"Title": "APIKey", "Title": "APIKey",
"Datatype": "string" "Datatype": "string"
@ -1242,15 +1250,28 @@
"Name": "gandiv5", "Name": "gandiv5",
"ConfigableFields": [ "ConfigableFields": [
{ {
"Title": "fieldName", "Title": "APIKey",
"Datatype": "string" "Datatype": "string"
}, },
{ {
"Title": "authZone", "Title": "PersonalAccessToken",
"Datatype": "string" "Datatype": "string"
} }
], ],
"HiddenFields": [] "HiddenFields": [
{
"Title": "PropagationTimeout",
"Datatype": "time.Duration"
},
{
"Title": "PollingInterval",
"Datatype": "time.Duration"
},
{
"Title": "HTTPClient",
"Datatype": "*http.Client"
}
]
}, },
"gcore": { "gcore": {
"Name": "gcore", "Name": "gcore",
@ -2063,35 +2084,40 @@
"Name": "namecheap", "Name": "namecheap",
"ConfigableFields": [ "ConfigableFields": [
{ {
"Title": "domain", "Title": "Debug",
"Datatype": "bool"
},
{
"Title": "BaseURL",
"Datatype": "string" "Datatype": "string"
}, },
{ {
"Title": "key", "Title": "APIUser",
"Datatype": "string" "Datatype": "string"
}, },
{ {
"Title": "keyFqdn", "Title": "APIKey",
"Datatype": "string" "Datatype": "string"
}, },
{ {
"Title": "keyValue", "Title": "ClientIP",
"Datatype": "string"
},
{
"Title": "tld",
"Datatype": "string"
},
{
"Title": "sld",
"Datatype": "string"
},
{
"Title": "host",
"Datatype": "string" "Datatype": "string"
} }
], ],
"HiddenFields": [] "HiddenFields": [
{
"Title": "PropagationTimeout",
"Datatype": "time.Duration"
},
{
"Title": "PollingInterval",
"Datatype": "time.Duration"
},
{
"Title": "HTTPClient",
"Datatype": "*http.Client"
}
]
}, },
"namedotcom": { "namedotcom": {
"Name": "namedotcom", "Name": "namedotcom",
@ -2418,26 +2444,38 @@
"Name": "ovh", "Name": "ovh",
"ConfigableFields": [ "ConfigableFields": [
{ {
"Title": "FieldType", "Title": "APIEndpoint",
"Datatype": "string" "Datatype": "string"
}, },
{ {
"Title": "SubDomain", "Title": "ApplicationKey",
"Datatype": "string" "Datatype": "string"
}, },
{ {
"Title": "Target", "Title": "ApplicationSecret",
"Datatype": "string" "Datatype": "string"
}, },
{ {
"Title": "Zone", "Title": "ConsumerKey",
"Datatype": "string" "Datatype": "string"
} }
], ],
"HiddenFields": [ "HiddenFields": [
{ {
"Title": "ID", "Title": "OAuth2Config",
"Datatype": "int64" "Datatype": "*OAuth2Config"
},
{
"Title": "PropagationTimeout",
"Datatype": "time.Duration"
},
{
"Title": "PollingInterval",
"Datatype": "time.Duration"
},
{
"Title": "HTTPClient",
"Datatype": "*http.Client"
} }
] ]
}, },
@ -2875,15 +2913,28 @@
"Name": "shellrent", "Name": "shellrent",
"ConfigableFields": [ "ConfigableFields": [
{ {
"Title": "domainID", "Title": "Username",
"Datatype": "int" "Datatype": "string"
}, },
{ {
"Title": "recordID", "Title": "Token",
"Datatype": "int" "Datatype": "string"
} }
], ],
"HiddenFields": [] "HiddenFields": [
{
"Title": "PropagationTimeout",
"Datatype": "time.Duration"
},
{
"Title": "PollingInterval",
"Datatype": "time.Duration"
},
{
"Title": "HTTPClient",
"Datatype": "*http.Client"
}
]
}, },
"simply": { "simply": {
"Name": "simply", "Name": "simply",
@ -3034,15 +3085,28 @@
}, },
"ultradns": { "ultradns": {
"Name": "ultradns", "Name": "ultradns",
"ConfigableFields": [], "ConfigableFields": [
"HiddenFields": [
{ {
"Title": "config", "Title": "Username",
"Datatype": "*Config" "Datatype": "string"
}, },
{ {
"Title": "client", "Title": "Password",
"Datatype": "*client.Client" "Datatype": "string"
},
{
"Title": "Endpoint",
"Datatype": "string"
}
],
"HiddenFields": [
{
"Title": "PropagationTimeout",
"Datatype": "time.Duration"
},
{
"Title": "PollingInterval",
"Datatype": "time.Duration"
} }
] ]
}, },

View File

@ -34,6 +34,7 @@ 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
} }
@ -44,11 +45,15 @@ type ExpiredCerts struct {
// 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
@ -135,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
@ -165,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")
} }
} }
@ -208,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")
@ -231,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
@ -252,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
@ -277,7 +297,7 @@ 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
DNSName, err := ExtractDomains(certBytes) DNSName, err := ExtractDomains(certBytes)
@ -305,7 +325,7 @@ 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
DNSName, err := ExtractDomains(certBytes) DNSName, err := ExtractDomains(certBytes)

View File

@ -81,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 {

View File

@ -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 {
@ -37,12 +38,12 @@ type AuthEndpoints struct {
Autologin string Autologin string
} }
//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?")
@ -77,7 +79,7 @@ func GetSessionKey(sysdb *db.Database) (string, error) {
return sessionKey, nil return sessionKey, nil
} }
//This function will handle an http request and redirect to the given login address if not logged in // This function will handle an http request and redirect to the given login address if not logged in
func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) { func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
if a.CheckAuth(r) { if a.CheckAuth(r) {
//User already logged in //User already logged in
@ -88,14 +90,14 @@ func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, hand
} }
} }
//Handle login request, require POST username and password // Handle login request, require POST username and password
func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) { func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
//Get username from request using POST mode //Get username from request using POST mode
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
@ -140,14 +142,14 @@ func (a *AuthAgent) ValidateUsernameAndPassword(username string, password string
return succ return succ
} }
//validate the username and password, return reasons if the auth failed // validate the username and password, return reasons if the auth failed
func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, password string) (bool, string) { func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, password string) (bool, string) {
hashedPassword := Hash(password) hashedPassword := Hash(password)
var passwordInDB string var passwordInDB string
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"
} }
@ -158,7 +160,7 @@ func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, passw
} }
} }
//Login the user by creating a valid session for this user // Login the user by creating a valid session for this user
func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, username string, rememberme bool) { func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, username string, rememberme bool) {
session, _ := a.SessionStore.Get(r, a.SessionName) session, _ := a.SessionStore.Get(r, a.SessionName)
@ -181,11 +183,15 @@ func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, u
session.Save(r, w) session.Save(r, w)
} }
//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 {
@ -208,7 +214,7 @@ func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
return nil return nil
} }
//Get the current session username from request // Get the current session username from request
func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string, error) { func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string, error) {
if a.CheckAuth(r) { if a.CheckAuth(r) {
//This user has logged in. //This user has logged in.
@ -220,7 +226,7 @@ func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string,
} }
} }
//Get the current session user email from request // Get the current session user email from request
func (a *AuthAgent) GetUserEmail(w http.ResponseWriter, r *http.Request) (string, error) { func (a *AuthAgent) GetUserEmail(w http.ResponseWriter, r *http.Request) (string, error) {
if a.CheckAuth(r) { if a.CheckAuth(r) {
//This user has logged in. //This user has logged in.
@ -239,7 +245,7 @@ func (a *AuthAgent) GetUserEmail(w http.ResponseWriter, r *http.Request) (string
} }
} }
//Check if the user has logged in, return true / false in JSON // Check if the user has logged in, return true / false in JSON
func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) { func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
if a.CheckAuth(r) { if a.CheckAuth(r) {
utils.SendJSONResponse(w, "true") utils.SendJSONResponse(w, "true")
@ -248,7 +254,7 @@ func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
} }
} }
//Handle new user register. Require POST username, password, group. // Handle new user register. Require POST username, password, group.
func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callback func(string, string)) { func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
//Get username from request //Get username from request
newusername, err := utils.PostPara(r, "username") newusername, err := utils.PostPara(r, "username")
@ -291,10 +297,10 @@ 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.
func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Request, callback func(string, string)) { func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
//Get username from request //Get username from request
newusername, err := utils.PostPara(r, "username") newusername, err := utils.PostPara(r, "username")
@ -324,10 +330,10 @@ 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
func (a *AuthAgent) CheckAuth(r *http.Request) bool { func (a *AuthAgent) CheckAuth(r *http.Request) bool {
session, err := a.SessionStore.Get(r, a.SessionName) session, err := a.SessionStore.Get(r, a.SessionName)
if err != nil { if err != nil {
@ -340,8 +346,8 @@ func (a *AuthAgent) CheckAuth(r *http.Request) bool {
return true return true
} }
//Handle de-register of users. Require POST username. // Handle de-register of users. Require POST username.
//THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER // THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) { func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
//Check if the user is logged in //Check if the user is logged in
if !a.CheckAuth(r) { if !a.CheckAuth(r) {
@ -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 {
@ -381,7 +387,7 @@ func (a *AuthAgent) UnregisterUser(username string) error {
return nil return nil
} }
//Get the number of users in the system // Get the number of users in the system
func (a *AuthAgent) GetUserCounts() int { func (a *AuthAgent) GetUserCounts() int {
entries, _ := a.Database.ListTable("auth") entries, _ := a.Database.ListTable("auth")
usercount := 0 usercount := 0
@ -393,12 +399,12 @@ 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
} }
//List all username within the system // List all username within the system
func (a *AuthAgent) ListUsers() []string { func (a *AuthAgent) ListUsers() []string {
entries, _ := a.Database.ListTable("auth") entries, _ := a.Database.ListTable("auth")
results := []string{} results := []string{}
@ -411,7 +417,7 @@ func (a *AuthAgent) ListUsers() []string {
return results return results
} }
//Check if the given username exists // Check if the given username exists
func (a *AuthAgent) UserExists(username string) bool { func (a *AuthAgent) UserExists(username string) bool {
userpasswordhash := "" userpasswordhash := ""
err := a.Database.Read("auth", "passhash/"+username, &userpasswordhash) err := a.Database.Read("auth", "passhash/"+username, &userpasswordhash)
@ -421,7 +427,7 @@ func (a *AuthAgent) UserExists(username string) bool {
return true return true
} }
//Update the session expire time given the request header. // Update the session expire time given the request header.
func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Request) bool { func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Request) bool {
session, _ := a.SessionStore.Get(r, a.SessionName) session, _ := a.SessionStore.Get(r, a.SessionName)
if session.Values["authenticated"].(bool) { if session.Values["authenticated"].(bool) {
@ -446,7 +452,7 @@ func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Reque
} }
} }
//Create user account // Create user account
func (a *AuthAgent) CreateUserAccount(newusername string, password string, email string) error { func (a *AuthAgent) CreateUserAccount(newusername string, password string, email string) error {
//Check user already exists //Check user already exists
if a.UserExists(newusername) { if a.UserExists(newusername) {
@ -470,7 +476,7 @@ func (a *AuthAgent) CreateUserAccount(newusername string, password string, email
return nil return nil
} }
//Hash the given raw string into sha512 hash // Hash the given raw string into sha512 hash
func Hash(raw string) string { func Hash(raw string) string {
h := sha512.New() h := sha512.New()
h.Write([]byte(raw)) h.Write([]byte(raw))

View File

@ -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,24 +28,38 @@ 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
http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) { if router.option.TargetMux == nil {
//Check authentication of the user http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
if router.option.RequireAuth { //Check authentication of the user
authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) { if router.option.RequireAuth {
authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) {
handler(w, r)
})
} else {
handler(w, r) handler(w, r)
}) }
} else {
handler(w, r)
}
}) })
} 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

View File

@ -0,0 +1,57 @@
//go:build !windows
// +build !windows
package dockerux
import (
"context"
"encoding/json"
"net/http"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"imuslab.com/zoraxy/mod/utils"
)
func (d *UXOptimizer) HandleDockerAvailable(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(d.RunninInDocker)
utils.SendJSONResponse(w, string(js))
}
func (d *UXOptimizer) HandleDockerContainersList(w http.ResponseWriter, r *http.Request) {
apiClient, err := client.NewClientWithOpts(client.WithVersion("1.43"))
if err != nil {
d.SystemWideLogger.PrintAndLog("Docker", "Unable to create new docker client", err)
utils.SendErrorResponse(w, "Docker client initiation failed")
return
}
defer apiClient.Close()
containers, err := apiClient.ContainerList(context.Background(), container.ListOptions{All: true})
if err != nil {
d.SystemWideLogger.PrintAndLog("Docker", "List docker container failed", err)
utils.SendErrorResponse(w, "List docker container failed")
return
}
networks, err := apiClient.NetworkList(context.Background(), types.NetworkListOptions{})
if err != nil {
d.SystemWideLogger.PrintAndLog("Docker", "List docker network failed", err)
utils.SendErrorResponse(w, "List docker network failed")
return
}
result := map[string]interface{}{
"network": networks,
"containers": containers,
}
js, err := json.Marshal(result)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendJSONResponse(w, string(js))
}

View File

@ -0,0 +1,32 @@
//go:build windows
// +build windows
package dockerux
/*
Windows docker UX optimizer dummy
This is a dummy module for Windows as docker features
is useless on Windows and create a larger binary size
docker on Windows build are trimmed to reduce binary size
and make it compatibile with Windows 7
*/
import (
"encoding/json"
"net/http"
"imuslab.com/zoraxy/mod/utils"
)
// Windows build not support docker
func (d *UXOptimizer) HandleDockerAvailable(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(d.RunninInDocker)
utils.SendJSONResponse(w, string(js))
}
func (d *UXOptimizer) HandleDockerContainersList(w http.ResponseWriter, r *http.Request) {
utils.SendErrorResponse(w, "Platform not supported")
}

View File

@ -0,0 +1,24 @@
package dockerux
import "imuslab.com/zoraxy/mod/info/logger"
/*
Docker Optimizer
This script add support for optimizing docker user experience
Note that this module are community contribute only. For bug
report, please directly tag the Pull Request author.
*/
type UXOptimizer struct {
RunninInDocker bool
SystemWideLogger *logger.Logger
}
//Create a new docker optimizer
func NewDockerOptimizer(IsRunningInDocker bool, logger *logger.Logger) *UXOptimizer {
return &UXOptimizer{
RunninInDocker: IsRunningInDocker,
SystemWideLogger: logger,
}
}

View File

@ -14,18 +14,24 @@ import (
Main server for dynamic proxy core Main server for dynamic proxy core
Routing Handler Priority (High to Low) Routing Handler Priority (High to Low)
- Blacklist - Special Routing Rule (e.g. acme)
- Whitelist
- Redirectable - Redirectable
- Subdomain Routing - Subdomain Routing
- Vitrual Directory Routing - Access Router
- Blacklist
- Whitelist
- Rate Limitor
- Basic Auth
- Vitrual Directory Proxy
- Subdomain Proxy
- Root router (default site router)
*/ */
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
/* /*
Special Routing Rules, bypass most of the limitations Special Routing Rules, bypass most of the limitations
*/ */
//Check if there are external routing rule matches. //Check if there are external routing rule (rr) matches.
//If yes, route them via external rr //If yes, route them via external rr
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r) matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
if matchedRoutingRule != nil { if matchedRoutingRule != nil {
@ -34,16 +40,13 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
//Inject headers
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
/* /*
Redirection Routing Redirection Routing
*/ */
//Check if this is a redirection url //Check if this is a redirection url
if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) { if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r) statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
h.logRequest(r, statusCode != 500, statusCode, "redirect", "") h.Parent.logRequest(r, statusCode != 500, statusCode, "redirect", "")
return return
} }
@ -70,10 +73,20 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
// Rate Limit
if sep.RequireRateLimit {
err := h.handleRateLimitRouting(w, r, sep)
if err != nil {
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 429)
return
}
}
//Validate basic auth //Validate basic auth
if sep.RequireBasicAuth { if sep.RequireBasicAuth {
err := h.handleBasicAuthRouting(w, r, sep) err := h.handleBasicAuthRouting(w, r, sep)
if err != nil { if err != nil {
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401)
return return
} }
} }
@ -90,6 +103,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled { if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
//Missing tailing slash. Redirect to target proxy endpoint //Missing tailing slash. Redirect to target proxy endpoint
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect) http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
h.Parent.Option.Logger.LogHTTPRequest(r, "redirect", 307)
return return
} }
} }
@ -183,12 +197,12 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
} }
hostname := parsedURL.Hostname() hostname := parsedURL.Hostname()
if hostname == domainOnly { if hostname == domainOnly {
h.logRequest(r, false, 500, "root-redirect", domainOnly) h.Parent.logRequest(r, false, 500, "root-redirect", domainOnly)
http.Error(w, "Loopback redirects due to invalid settings", 500) http.Error(w, "Loopback redirects due to invalid settings", 500)
return return
} }
h.logRequest(r, false, 307, "root-redirect", domainOnly) h.Parent.logRequest(r, false, 307, "root-redirect", domainOnly)
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect) http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
case DefaultSite_NotFoundPage: case DefaultSite_NotFoundPage:
//Serve the not found page, use template if exists //Serve the not found page, use template if exists

View File

@ -24,7 +24,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter,
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r) isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
if isBlocked { if isBlocked {
h.logRequest(r, false, 403, blockedReason, "") h.Parent.logRequest(r, false, 403, blockedReason, "")
} }
return isBlocked return isBlocked
} }

View File

@ -18,7 +18,7 @@ import (
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error { func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
err := handleBasicAuth(w, r, pe) err := handleBasicAuth(w, r, pe)
if err != nil { if err != nil {
h.logRequest(r, false, 401, "host", pe.Domain) h.Parent.logRequest(r, false, 401, "host", r.URL.Hostname())
} }
return err return err
} }

View File

@ -0,0 +1,80 @@
package dynamicproxy
import (
"strconv"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
)
/*
CustomHeader.go
This script handle parsing and injecting custom headers
into the dpcore routing logic
*/
// SplitInboundOutboundHeaders split user defined headers into upstream and downstream headers
// return upstream header and downstream header key-value pairs
// if the header is expected to be deleted, the value will be set to empty string
func (ept *ProxyEndpoint) SplitInboundOutboundHeaders() ([][]string, [][]string) {
if len(ept.UserDefinedHeaders) == 0 && ept.HSTSMaxAge == 0 && !ept.EnablePermissionPolicyHeader {
//Early return if there are no defined headers
return [][]string{}, [][]string{}
}
//Use pre-allocation for faster performance
//Downstream +2 for Permission Policy and HSTS
upstreamHeaders := make([][]string, len(ept.UserDefinedHeaders))
downstreamHeaders := make([][]string, len(ept.UserDefinedHeaders)+2)
upstreamHeaderCounter := 0
downstreamHeaderCounter := 0
//Sort the headers into upstream or downstream
for _, customHeader := range ept.UserDefinedHeaders {
thisHeaderSet := make([]string, 2)
thisHeaderSet[0] = customHeader.Key
thisHeaderSet[1] = customHeader.Value
if customHeader.IsRemove {
//Prevent invalid config
thisHeaderSet[1] = ""
}
//Assign to slice
if customHeader.Direction == HeaderDirection_ZoraxyToUpstream {
upstreamHeaders[upstreamHeaderCounter] = thisHeaderSet
upstreamHeaderCounter++
} else if customHeader.Direction == HeaderDirection_ZoraxyToDownstream {
downstreamHeaders[downstreamHeaderCounter] = thisHeaderSet
downstreamHeaderCounter++
}
}
//Check if the endpoint require HSTS headers
if ept.HSTSMaxAge > 0 {
if ept.ContainsWildcardName(true) {
//Endpoint listening domain includes wildcards.
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(ept.HSTSMaxAge)) + "; includeSubdomains"}
} else {
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(ept.HSTSMaxAge))}
}
downstreamHeaderCounter++
}
//Check if the endpoint require Permission Policy
if ept.EnablePermissionPolicyHeader {
var usingPermissionPolicy *permissionpolicy.PermissionsPolicy
if ept.PermissionPolicy != nil {
//Custom permission policy
usingPermissionPolicy = ept.PermissionPolicy
} else {
//Permission policy is enabled but not customized. Use default
usingPermissionPolicy = permissionpolicy.GetDefaultPermissionPolicy()
}
downstreamHeaders[downstreamHeaderCounter] = usingPermissionPolicy.ToKeyValueHeader()
downstreamHeaderCounter++
}
return upstreamHeaders, downstreamHeaders
}

View File

@ -0,0 +1,21 @@
package domainsniff
import "net/http"
/*
Promox API sniffer
This handler sniff proxmox API endpoint and
adjust the request accordingly to fix shits
in the proxmox API server
*/
func IsProxmox(r *http.Request) bool {
// Check if any of the cookies is named PVEAuthCookie
for _, cookie := range r.Cookies() {
if cookie.Name == "PVEAuthCookie" {
return true
}
}
return false
}

View File

@ -10,6 +10,9 @@ import (
"net/url" "net/url"
"strings" "strings"
"time" "time"
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
) )
// ReverseProxy is an HTTP Handler that takes an incoming request and // ReverseProxy is an HTTP Handler that takes an incoming request and
@ -48,18 +51,27 @@ type ReverseProxy struct {
ModifyResponse func(*http.Response) error ModifyResponse func(*http.Response) error
//Prepender is an optional prepend text for URL rewrite //Prepender is an optional prepend text for URL rewrite
//
Prepender string Prepender string
Verbal bool Verbal bool
} }
type ResponseRewriteRuleSet struct { type ResponseRewriteRuleSet struct {
ProxyDomain string /* Basic Rewrite Rulesets */
OriginalHost string ProxyDomain string
UseTLS bool OriginalHost string
NoCache bool UseTLS bool
PathPrefix string //Vdir prefix for root, / will be rewrite to this NoCache bool
PathPrefix string //Vdir prefix for root, / will be rewrite to this
UpstreamHeaders [][]string
DownstreamHeaders [][]string
/* Advance Usecase Options */
HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase)
NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase)
/* System Information Payload */
Version string //Version number of Zoraxy, use for X-Proxy-By
} }
type requestCanceler interface { type requestCanceler interface {
@ -67,8 +79,8 @@ type requestCanceler interface {
} }
type DpcoreOptions struct { type DpcoreOptions struct {
IgnoreTLSVerification bool IgnoreTLSVerification bool //Disable all TLS verification when request pass through this proxy router
FlushInterval time.Duration FlushInterval time.Duration //Duration to flush in normal requests. Stream request or keep-alive request will always flush with interval of -1 (immediately)
} }
func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy { func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy {
@ -175,7 +187,7 @@ var hopHeaders = []string{
"Te", // canonicalized version of "TE" "Te", // canonicalized version of "TE"
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522 "Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
"Transfer-Encoding", "Transfer-Encoding",
//"Upgrade", //"Upgrade", // handled by websocket proxy in higher layer abstraction
} }
// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy // Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
@ -246,79 +258,7 @@ func (p *ReverseProxy) logf(format string, args ...interface{}) {
} }
} }
func removeHeaders(header http.Header, noCache bool) { func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) (int, error) {
// Remove hop-by-hop headers listed in the "Connection" header.
if c := header.Get("Connection"); c != "" {
for _, f := range strings.Split(c, ",") {
if f = strings.TrimSpace(f); f != "" {
header.Del(f)
}
}
}
// Remove hop-by-hop headers
for _, h := range hopHeaders {
if header.Get(h) != "" {
header.Del(h)
}
}
//Restore the Upgrade header if any
if header.Get("Zr-Origin-Upgrade") != "" {
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
header.Del("Zr-Origin-Upgrade")
}
//Disable cache if nocache is set
if noCache {
header.Del("Cache-Control")
header.Set("Cache-Control", "no-store")
}
//Hide Go-HTTP-Client UA if the client didnt sent us one
if _, ok := header["User-Agent"]; !ok {
// If the outbound request doesn't have a User-Agent header set,
// don't send the default Go HTTP client User-Agent.
header.Set("User-Agent", "")
}
}
func addXForwardedForHeader(req *http.Request) {
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := req.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
if req.TLS != nil {
req.Header.Set("X-Forwarded-Proto", "https")
} else {
req.Header.Set("X-Forwarded-Proto", "http")
}
if req.Header.Get("X-Real-Ip") == "" {
//Check if CF-Connecting-IP header exists
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
if CF_Connecting_IP != "" {
//Use CF Connecting IP
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
} else {
// Not exists. Fill it in with first entry in X-Forwarded-For
ips := strings.Split(clientIP, ",")
if len(ips) > 0 {
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
}
}
}
}
}
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error {
transport := p.Transport transport := p.Transport
outreq := new(http.Request) outreq := new(http.Request)
@ -346,9 +286,12 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
p.Director(outreq) p.Director(outreq)
outreq.Close = false outreq.Close = false
if !rrr.UseTLS { //Only skip origin rewrite iff proxy target require TLS and it is external domain name like github.com
//This seems to be routing to external sites if rrr.HostHeaderOverwrite != "" {
//Do not keep the original host //Use user defined overwrite header value, see issue #255
outreq.Host = rrr.HostHeaderOverwrite
} else if !(rrr.UseTLS && isExternalDomainName(rrr.ProxyDomain)) {
// Always use the original host, see issue #164
outreq.Host = rrr.OriginalHost outreq.Host = rrr.OriginalHost
} }
@ -356,12 +299,25 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
outreq.Header = make(http.Header) outreq.Header = make(http.Header)
copyHeader(outreq.Header, req.Header) copyHeader(outreq.Header, req.Header)
// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers. // Remove hop-by-hop headers.
removeHeaders(outreq.Header, rrr.NoCache) if !rrr.NoRemoveHopByHop {
removeHeaders(outreq.Header, rrr.NoCache)
}
// Add X-Forwarded-For Header. // Add X-Forwarded-For Header.
addXForwardedForHeader(outreq) addXForwardedForHeader(outreq)
// Add user defined headers (to upstream)
injectUserDefinedHeaders(outreq.Header, rrr.UpstreamHeaders)
// Rewrite outbound UA, must be after user headers
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)
//Fix proxmox transfer encoding bug if detected Proxmox Cookie
if domainsniff.IsProxmox(req) {
outreq.TransferEncoding = []string{"identity"}
}
res, err := transport.RoundTrip(outreq) res, err := transport.RoundTrip(outreq)
if err != nil { if err != nil {
if p.Verbal { if p.Verbal {
@ -369,11 +325,13 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
} }
//rw.WriteHeader(http.StatusBadGateway) //rw.WriteHeader(http.StatusBadGateway)
return err return http.StatusBadGateway, err
} }
// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers. // Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
removeHeaders(res.Header, rrr.NoCache) if !rrr.NoRemoveHopByHop {
removeHeaders(res.Header, rrr.NoCache)
}
//Remove the User-Agent header if exists //Remove the User-Agent header if exists
if _, ok := res.Header["User-Agent"]; ok { if _, ok := res.Header["User-Agent"]; ok {
@ -388,17 +346,21 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
} }
//rw.WriteHeader(http.StatusBadGateway) //rw.WriteHeader(http.StatusBadGateway)
return err return http.StatusBadGateway, err
} }
} }
//TODO: Figure out a way to proxy for proxmox
//if res.StatusCode == 501 || res.StatusCode == 500 { //if res.StatusCode == 501 || res.StatusCode == 500 {
// fmt.Println(outreq.Proto, outreq.RemoteAddr, outreq.RequestURI) // fmt.Println(outreq.Proto, outreq.RemoteAddr, outreq.RequestURI)
// fmt.Println(">>>", outreq.Method, res.Header, res.ContentLength, res.StatusCode) // fmt.Println(">>>", outreq.Method, res.Header, res.ContentLength, res.StatusCode)
// fmt.Println(outreq.Header, req.Host) // fmt.Println(outreq.Header, req.Host)
//} //}
//Custom header rewriter functions //Add debug X-Proxy-By tracker
res.Header.Set("x-proxy-by", "zoraxy/"+rrr.Version)
//Custom Location header rewriter functions
if res.Header.Get("Location") != "" { if res.Header.Get("Location") != "" {
locationRewrite := res.Header.Get("Location") locationRewrite := res.Header.Get("Location")
originLocation := res.Header.Get("Location") originLocation := res.Header.Get("Location")
@ -424,9 +386,15 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
res.Header.Set("Location", locationRewrite) res.Header.Set("Location", locationRewrite)
} }
// Add user defined headers (to downstream)
injectUserDefinedHeaders(res.Header, rrr.DownstreamHeaders)
// Copy header from response to client. // Copy header from response to client.
copyHeader(rw.Header(), res.Header) copyHeader(rw.Header(), res.Header)
// inject permission policy headers
permissionpolicy.InjectPermissionPolicyHeader(rw, nil)
// The "Trailer" header isn't included in the Transport's response, Build it up from Trailer. // The "Trailer" header isn't included in the Transport's response, Build it up from Trailer.
if len(res.Trailer) > 0 { if len(res.Trailer) > 0 {
trailerKeys := make([]string, 0, len(res.Trailer)) trailerKeys := make([]string, 0, len(res.Trailer))
@ -454,14 +422,14 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
res.Body.Close() res.Body.Close()
copyHeader(rw.Header(), res.Trailer) copyHeader(rw.Header(), res.Trailer)
return nil return res.StatusCode, nil
} }
func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) error { func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) (int, error) {
hij, ok := rw.(http.Hijacker) hij, ok := rw.(http.Hijacker)
if !ok { if !ok {
p.logf("http server does not support hijacker") p.logf("http server does not support hijacker")
return errors.New("http server does not support hijacker") return http.StatusNotImplemented, errors.New("http server does not support hijacker")
} }
clientConn, _, err := hij.Hijack() clientConn, _, err := hij.Hijack()
@ -469,7 +437,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
if p.Verbal { if p.Verbal {
p.logf("http: proxy error: %v", err) p.logf("http: proxy error: %v", err)
} }
return err return http.StatusInternalServerError, err
} }
proxyConn, err := net.Dial("tcp", req.URL.Host) proxyConn, err := net.Dial("tcp", req.URL.Host)
@ -478,7 +446,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
p.logf("http: proxy error: %v", err) p.logf("http: proxy error: %v", err)
} }
return err return http.StatusInternalServerError, err
} }
// The returned net.Conn may have read or write deadlines // The returned net.Conn may have read or write deadlines
@ -497,7 +465,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
if p.Verbal { if p.Verbal {
p.logf("http: proxy error: %v", err) p.logf("http: proxy error: %v", err)
} }
return err return http.StatusGatewayTimeout, err
} }
err = proxyConn.SetDeadline(deadline) err = proxyConn.SetDeadline(deadline)
@ -506,7 +474,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
p.logf("http: proxy error: %v", err) p.logf("http: proxy error: %v", err)
} }
return err return http.StatusGatewayTimeout, err
} }
_, err = clientConn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) _, err = clientConn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
@ -515,7 +483,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
p.logf("http: proxy error: %v", err) p.logf("http: proxy error: %v", err)
} }
return err return http.StatusInternalServerError, err
} }
go func() { go func() {
@ -528,15 +496,13 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
proxyConn.Close() proxyConn.Close()
clientConn.Close() clientConn.Close()
return nil return http.StatusOK, nil
} }
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error { func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) (int, error) {
if req.Method == "CONNECT" { if req.Method == "CONNECT" {
err := p.ProxyHTTPS(rw, req) return p.ProxyHTTPS(rw, req)
return err
} else { } else {
err := p.ProxyHTTP(rw, req, rrr) return p.ProxyHTTP(rw, req, rrr)
return err
} }
} }

View File

@ -3,6 +3,7 @@ package dpcore
import ( import (
"mime" "mime"
"net/http" "net/http"
"strings"
"time" "time"
) )
@ -17,6 +18,12 @@ func (p *ReverseProxy) getFlushInterval(req *http.Request, res *http.Response) t
return -1 return -1
} }
// Fixed issue #235: Added auto detection for ollama / llm output stream
connectionHeader := req.Header["Connection"]
if len(connectionHeader) > 0 && strings.Contains(strings.Join(connectionHeader, ","), "keep-alive") {
return -1
}
//Cannot sniff anything. Use default value //Cannot sniff anything. Use default value
return p.FlushInterval return p.FlushInterval

View File

@ -0,0 +1,120 @@
package dpcore
import (
"net"
"net/http"
"strings"
)
/*
Header.go
This script handles headers rewrite and remove
in dpcore.
Added in Zoraxy v3.0.6 by tobychui
*/
// removeHeaders Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
func removeHeaders(header http.Header, noCache bool) {
// Remove hop-by-hop headers listed in the "Connection" header.
if c := header.Get("Connection"); c != "" {
for _, f := range strings.Split(c, ",") {
if f = strings.TrimSpace(f); f != "" {
header.Del(f)
}
}
}
// Remove hop-by-hop headers
for _, h := range hopHeaders {
if header.Get(h) != "" {
header.Del(h)
}
}
//Restore the Upgrade header if any
if header.Get("Zr-Origin-Upgrade") != "" {
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
header.Del("Zr-Origin-Upgrade")
}
//Disable cache if nocache is set
if noCache {
header.Del("Cache-Control")
header.Set("Cache-Control", "no-store")
}
}
// rewriteUserAgent rewrite the user agent based on incoming request
func rewriteUserAgent(header http.Header, UA string) {
//Hide Go-HTTP-Client UA if the client didnt sent us one
if header.Get("User-Agent") == "" {
// If the outbound request doesn't have a User-Agent header set,
// don't send the default Go HTTP client User-Agent
header.Del("User-Agent")
header.Set("User-Agent", UA)
}
}
// Add X-Forwarded-For Header and rewrite X-Real-Ip according to sniffing logics
func addXForwardedForHeader(req *http.Request) {
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := req.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
if req.TLS != nil {
req.Header.Set("X-Forwarded-Proto", "https")
} else {
req.Header.Set("X-Forwarded-Proto", "http")
}
if req.Header.Get("X-Real-Ip") == "" {
//Check if CF-Connecting-IP header exists
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
Fastly_Client_IP := req.Header.Get("Fastly-Client-IP")
if CF_Connecting_IP != "" {
//Use CF Connecting IP
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
} else if Fastly_Client_IP != "" {
//Use Fastly Client IP
req.Header.Set("X-Real-Ip", Fastly_Client_IP)
} else {
// Not exists. Fill it in with first entry in X-Forwarded-For
ips := strings.Split(clientIP, ",")
if len(ips) > 0 {
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
}
}
}
}
}
// injectUserDefinedHeaders inject the user headers from slice
// if a value is empty string, the key will be removed from header.
// if a key is empty string, the function will return immediately
func injectUserDefinedHeaders(header http.Header, userHeaders [][]string) {
for _, userHeader := range userHeaders {
if len(userHeader) == 0 {
//End of header slice
return
}
headerKey := userHeader[0]
headerValue := userHeader[1]
if headerValue == "" {
//Remove header from head
header.Del(headerKey)
continue
}
//Default: Set header value
header.Del(headerKey) //Remove header if it already exists
header.Set(headerKey, headerValue)
}
}

View File

@ -1,6 +1,10 @@
package dpcore package dpcore
import ( import (
"bytes"
"io"
"net"
"net/http"
"net/url" "net/url"
"strings" "strings"
) )
@ -60,3 +64,94 @@ func replaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS b
func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) { func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
return replaceLocationHost(urlString, rrr, useTLS) return replaceLocationHost(urlString, rrr, useTLS)
} }
// isExternalDomainName check and return if the hostname is external domain name (e.g. github.com)
// instead of internal (like 192.168.1.202:8443 (ip address) or domains end with .local or .internal)
func isExternalDomainName(hostname string) bool {
host, _, err := net.SplitHostPort(hostname)
if err != nil {
//hostname doesnt contain port
ip := net.ParseIP(hostname)
if ip != nil {
//IP address, not a domain name
return false
}
} else {
//Hostname contain port, use hostname without port to check if it is ip
ip := net.ParseIP(host)
if ip != nil {
//IP address, not a domain name
return false
}
}
//Check if it is internal DNS assigned domains
internalDNSTLD := []string{".local", ".internal", ".localhost", ".home.arpa"}
for _, tld := range internalDNSTLD {
if strings.HasSuffix(strings.ToLower(hostname), tld) {
return false
}
}
return true
}
// DeepCopyRequest returns a deep copy of the given http.Request.
func DeepCopyRequest(req *http.Request) (*http.Request, error) {
// Copy the URL
urlCopy := *req.URL
// Copy the headers
headersCopy := make(http.Header, len(req.Header))
for k, vv := range req.Header {
vvCopy := make([]string, len(vv))
copy(vvCopy, vv)
headersCopy[k] = vvCopy
}
// Copy the cookies
cookiesCopy := make([]*http.Cookie, len(req.Cookies()))
for i, cookie := range req.Cookies() {
cookieCopy := *cookie
cookiesCopy[i] = &cookieCopy
}
// Copy the body, if present
var bodyCopy io.ReadCloser
if req.Body != nil {
var buf bytes.Buffer
if _, err := buf.ReadFrom(req.Body); err != nil {
return nil, err
}
// Reset the request body so it can be read again
if err := req.Body.Close(); err != nil {
return nil, err
}
req.Body = io.NopCloser(&buf)
bodyCopy = io.NopCloser(bytes.NewReader(buf.Bytes()))
}
// Create the new request
reqCopy := &http.Request{
Method: req.Method,
URL: &urlCopy,
Proto: req.Proto,
ProtoMajor: req.ProtoMajor,
ProtoMinor: req.ProtoMinor,
Header: headersCopy,
Body: bodyCopy,
ContentLength: req.ContentLength,
TransferEncoding: append([]string(nil), req.TransferEncoding...),
Close: req.Close,
Host: req.Host,
Form: req.Form,
PostForm: req.PostForm,
MultipartForm: req.MultipartForm,
Trailer: req.Trailer,
RemoteAddr: req.RemoteAddr,
TLS: req.TLS,
// Cancel and Context are not copied as it might cause issues
}
return reqCopy, nil
}

View File

@ -23,12 +23,13 @@ import (
func NewDynamicProxy(option RouterOption) (*Router, error) { func NewDynamicProxy(option RouterOption) (*Router, error) {
proxyMap := sync.Map{} proxyMap := sync.Map{}
thisRouter := Router{ thisRouter := Router{
Option: &option, Option: &option,
ProxyEndpoints: &proxyMap, ProxyEndpoints: &proxyMap,
Running: false, Running: false,
server: nil, server: nil,
routingRules: []*RoutingRule{}, routingRules: []*RoutingRule{},
tldMap: map[string]int{}, loadBalancer: option.LoadBalancer,
rateLimitCounter: RequestCountPerIpTable{},
} }
thisRouter.mux = &ProxyHandler{ thisRouter.mux = &ProxyHandler{
@ -85,6 +86,12 @@ func (router *Router) StartProxyService() error {
MinVersion: uint16(minVersion), MinVersion: uint16(minVersion),
} }
//Start rate limitor
err := router.startRateLimterCounterResetTicker()
if err != nil {
return err
}
if router.Option.UseTls { if router.Option.UseTls {
router.server = &http.Server{ router.server = &http.Server{
Addr: ":" + strconv.Itoa(router.Option.Port), Addr: ":" + strconv.Itoa(router.Option.Port),
@ -129,6 +136,13 @@ func (router *Router) StartProxyService() error {
} }
} }
// Rate Limit
if sep.RequireRateLimit {
if err := router.handleRateLimit(w, r, sep); err != nil {
return
}
}
//Validate basic auth //Validate basic auth
if sep.RequireBasicAuth { if sep.RequireBasicAuth {
err := handleBasicAuth(w, r, sep) err := handleBasicAuth(w, r, sep)
@ -137,11 +151,20 @@ func (router *Router) StartProxyService() error {
} }
} }
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ selectedUpstream, err := router.loadBalancer.GetRequestUpstreamTarget(w, r, sep.ActiveOrigins, sep.UseStickySession)
ProxyDomain: sep.Domain, if err != nil {
OriginalHost: originalHostHeader, http.ServeFile(w, r, "./web/hosterror.html")
UseTLS: sep.RequireTLS, router.Option.Logger.PrintAndLog("dprouter", "failed to get upstream for hostname", err)
PathPrefix: "", router.logRequest(r, false, 404, "vdir-http", r.Host)
}
selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: selectedUpstream.OriginIpOrDomain,
OriginalHost: originalHostHeader,
UseTLS: selectedUpstream.RequireTLS,
HostHeaderOverwrite: sep.RequestHostOverwrite,
NoRemoveHopByHop: sep.DisableHopByHopHeaderRemoval,
PathPrefix: "",
Version: sep.parent.Option.HostVersion,
}) })
return return
} }
@ -173,7 +196,7 @@ func (router *Router) StartProxyService() error {
IdleTimeout: 120 * time.Second, IdleTimeout: 120 * time.Second,
} }
log.Println("Starting HTTP-to-HTTPS redirector (port 80)") router.Option.Logger.PrintAndLog("dprouter", "Starting HTTP-to-HTTPS redirector (port 80)", nil)
//Create a redirection stop channel //Create a redirection stop channel
stopChan := make(chan bool) stopChan := make(chan bool)
@ -184,7 +207,7 @@ func (router *Router) StartProxyService() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
httpServer.Shutdown(ctx) httpServer.Shutdown(ctx)
log.Println("HTTP to HTTPS redirection listener stopped") router.Option.Logger.PrintAndLog("dprouter", "HTTP to HTTPS redirection listener stopped", nil)
}() }()
//Start the http server that listens to port 80 and redirect to 443 //Start the http server that listens to port 80 and redirect to 443
@ -199,10 +222,10 @@ func (router *Router) StartProxyService() error {
} }
//Start the TLS server //Start the TLS server
log.Println("Reverse proxy service started in the background (TLS mode)") router.Option.Logger.PrintAndLog("dprouter", "Reverse proxy service started in the background (TLS mode)", nil)
go func() { go func() {
if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
log.Fatalf("Could not start proxy server: %v\n", err) router.Option.Logger.PrintAndLog("dprouter", "Could not start proxy server", err)
} }
}() }()
} else { } else {
@ -210,10 +233,9 @@ func (router *Router) StartProxyService() error {
router.tlsListener = nil router.tlsListener = nil
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux} router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
router.Running = true router.Running = true
log.Println("Reverse proxy service started in the background (Plain HTTP mode)") router.Option.Logger.PrintAndLog("dprouter", "Reverse proxy service started in the background (Plain HTTP mode)", nil)
go func() { go func() {
router.server.ListenAndServe() router.server.ListenAndServe()
//log.Println("[DynamicProxy] " + err.Error())
}() }()
} }
@ -231,10 +253,23 @@ func (router *Router) StopProxyService() error {
return err return err
} }
//Stop TLS listener
if router.tlsListener != nil { if router.tlsListener != nil {
router.tlsListener.Close() router.tlsListener.Close()
} }
//Stop rate limiter
if router.rateLimterStop != nil {
go func() {
// As the rate timer loop has a 1 sec ticker
// stop the rate limiter in go routine can prevent
// front end from freezing for 1 sec
router.rateLimterStop <- true
}()
}
//Stop TLS redirection (from port 80)
if router.tlsRedirectStop != nil { if router.tlsRedirectStop != nil {
router.tlsRedirectStop <- true router.tlsRedirectStop <- true
} }

View File

@ -7,6 +7,7 @@ import (
"golang.org/x/text/cases" "golang.org/x/text/cases"
"golang.org/x/text/language" "golang.org/x/text/language"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
) )
/* /*
@ -30,7 +31,6 @@ func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
return true return true
} }
} }
return false return false
} }
@ -49,16 +49,13 @@ func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error {
} }
// Add a user defined header to the list, duplicates will be automatically removed // Add a user defined header to the list, duplicates will be automatically removed
func (ep *ProxyEndpoint) AddUserDefinedHeader(key string, value string) error { func (ep *ProxyEndpoint) AddUserDefinedHeader(newHeaderRule *UserDefinedHeader) error {
if ep.UserDefinedHeaderExists(key) { if ep.UserDefinedHeaderExists(newHeaderRule.Key) {
ep.RemoveUserDefinedHeader(key) ep.RemoveUserDefinedHeader(newHeaderRule.Key)
} }
ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, &UserDefinedHeader{ newHeaderRule.Key = cases.Title(language.Und, cases.NoLower).String(newHeaderRule.Key)
Key: cases.Title(language.Und, cases.NoLower).String(key), //e.g. x-proxy-by -> X-Proxy-By ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, newHeaderRule)
Value: value,
})
return nil return nil
} }
@ -137,6 +134,116 @@ func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint)
return readyRoutingRule, nil return readyRoutingRule, nil
} }
/* Upstream related wrapper functions */
//Check if there already exists another upstream with identical origin
func (ep *ProxyEndpoint) UpstreamOriginExists(originURL string) bool {
for _, origin := range ep.ActiveOrigins {
if origin.OriginIpOrDomain == originURL {
return true
}
}
for _, origin := range ep.InactiveOrigins {
if origin.OriginIpOrDomain == originURL {
return true
}
}
return false
}
// Get a upstream origin from given origin ip or domain
func (ep *ProxyEndpoint) GetUpstreamOriginByMatchingIP(originIpOrDomain string) (*loadbalance.Upstream, error) {
for _, origin := range ep.ActiveOrigins {
if origin.OriginIpOrDomain == originIpOrDomain {
return origin, nil
}
}
for _, origin := range ep.InactiveOrigins {
if origin.OriginIpOrDomain == originIpOrDomain {
return origin, nil
}
}
return nil, errors.New("target upstream origin not found")
}
// Add upstream to endpoint and update it to runtime
func (ep *ProxyEndpoint) AddUpstreamOrigin(newOrigin *loadbalance.Upstream, activate bool) error {
//Check if the upstream already exists
if ep.UpstreamOriginExists(newOrigin.OriginIpOrDomain) {
return errors.New("upstream with same origin already exists")
}
if activate {
//Add it to the active origin list
err := newOrigin.StartProxy()
if err != nil {
return err
}
ep.ActiveOrigins = append(ep.ActiveOrigins, newOrigin)
} else {
//Add to inactive origin list
ep.InactiveOrigins = append(ep.InactiveOrigins, newOrigin)
}
ep.UpdateToRuntime()
return nil
}
// Remove upstream from endpoint and update it to runtime
func (ep *ProxyEndpoint) RemoveUpstreamOrigin(originIpOrDomain string) error {
//Just to make sure there are no spaces
originIpOrDomain = strings.TrimSpace(originIpOrDomain)
//Check if the upstream already been removed
if !ep.UpstreamOriginExists(originIpOrDomain) {
//Not exists in the first place
return nil
}
newActiveOriginList := []*loadbalance.Upstream{}
for _, origin := range ep.ActiveOrigins {
if origin.OriginIpOrDomain != originIpOrDomain {
newActiveOriginList = append(newActiveOriginList, origin)
}
}
newInactiveOriginList := []*loadbalance.Upstream{}
for _, origin := range ep.InactiveOrigins {
if origin.OriginIpOrDomain != originIpOrDomain {
newInactiveOriginList = append(newInactiveOriginList, origin)
}
}
//Ok, set the origin list to the new one
ep.ActiveOrigins = newActiveOriginList
ep.InactiveOrigins = newInactiveOriginList
ep.UpdateToRuntime()
return nil
}
// Check if the proxy endpoint hostname or alias name contains subdomain wildcard
func (ep *ProxyEndpoint) ContainsWildcardName(skipAliasCheck bool) bool {
hostname := ep.RootOrMatchingDomain
aliasHostnames := ep.MatchingDomainAlias
wildcardCheck := func(hostname string) bool {
return len(hostname) > 0 && hostname[0] == '*'
}
if wildcardCheck(hostname) {
return true
}
if !skipAliasCheck {
for _, aliasHostname := range aliasHostnames {
if wildcardCheck(aliasHostname) {
return true
}
}
}
return false
}
// Create a deep clone object of the proxy endpoint // Create a deep clone object of the proxy endpoint
// Note the returned object is not activated. Call to prepare function before pushing into runtime // Note the returned object is not activated. Call to prepare function before pushing into runtime
func (ep *ProxyEndpoint) Clone() *ProxyEndpoint { func (ep *ProxyEndpoint) Clone() *ProxyEndpoint {

View File

@ -0,0 +1,99 @@
package loadbalance
import (
"strings"
"sync"
"github.com/google/uuid"
"github.com/gorilla/sessions"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
)
/*
Load Balancer
Handleing load balance request for upstream destinations
*/
type Options struct {
SystemUUID string //Use for the session store
UseActiveHealthCheck bool //Use active health check, default to false
Geodb *geodb.Store //GeoIP resolver for checking incoming request origin country
Logger *logger.Logger
}
type RouteManager struct {
SessionStore *sessions.CookieStore
LoadBalanceMap sync.Map //Sync map to store the last load balance state of a given node
OnlineStatusMap sync.Map //Sync map to store the online status of a given ip address or domain name
onlineStatusTickerStop chan bool //Stopping channel for the online status pinger
Options Options //Options for the load balancer
}
/* Upstream or Origin Server */
type Upstream struct {
//Upstream Proxy Configs
OriginIpOrDomain string //Target IP address or domain name with port
RequireTLS bool //Require TLS connection
SkipCertValidations bool //Set to true to accept self signed certs
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
//Load balancing configs
Weight int //Random weight for round robin, 0 for fallback only
MaxConn int //TODO: Maxmium connection to this server, 0 for unlimited
//currentConnectionCounts atomic.Uint64 //Counter for number of client currently connected
proxy *dpcore.ReverseProxy
}
// Create a new load balancer
func NewLoadBalancer(options *Options) *RouteManager {
if options.SystemUUID == "" {
//System UUID not passed in. Use random key
options.SystemUUID = uuid.New().String()
}
//Generate a session store for stickySession
store := sessions.NewCookieStore([]byte(options.SystemUUID))
return &RouteManager{
SessionStore: store,
LoadBalanceMap: sync.Map{},
OnlineStatusMap: sync.Map{},
onlineStatusTickerStop: nil,
Options: *options,
}
}
// UpstreamsReady checks if the group of upstreams contains at least one
// origin server that is ready
func (m *RouteManager) UpstreamsReady(upstreams []*Upstream) bool {
for _, upstream := range upstreams {
if upstream.IsReady() {
return true
}
}
return false
}
// String format and convert a list of upstream into a string representations
func GetUpstreamsAsString(upstreams []*Upstream) string {
targets := []string{}
for _, upstream := range upstreams {
targets = append(targets, upstream.String())
}
return strings.Join(targets, ", ")
}
func (m *RouteManager) Close() {
if m.onlineStatusTickerStop != nil {
m.onlineStatusTickerStop <- true
}
}
// Print debug message
func (m *RouteManager) debugPrint(message string, err error) {
m.Options.Logger.PrintAndLog("LoadBalancer", message, err)
}

View File

@ -0,0 +1,39 @@
package loadbalance
import (
"net/http"
"time"
)
// Return the last ping status to see if the target is online
func (m *RouteManager) IsTargetOnline(matchingDomainOrIp string) bool {
value, ok := m.LoadBalanceMap.Load(matchingDomainOrIp)
if !ok {
return false
}
isOnline, ok := value.(bool)
return ok && isOnline
}
// Ping a target to see if it is online
func PingTarget(targetMatchingDomainOrIp string, requireTLS bool) bool {
client := &http.Client{
Timeout: 10 * time.Second,
}
url := targetMatchingDomainOrIp
if requireTLS {
url = "https://" + url
} else {
url = "http://" + url
}
resp, err := client.Get(url)
if err != nil {
return false
}
defer resp.Body.Close()
return resp.StatusCode >= 200 && resp.StatusCode <= 600
}

View File

@ -0,0 +1,154 @@
package loadbalance
import (
"errors"
"fmt"
"log"
"math/rand"
"net/http"
)
/*
Origin Picker
This script contains the code to pick the best origin
by this request.
*/
// GetRequestUpstreamTarget return the upstream target where this
// request should be routed
func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.Request, origins []*Upstream, useStickySession bool) (*Upstream, error) {
if len(origins) == 0 {
return nil, errors.New("no upstream is defined for this host")
}
var targetOrigin = origins[0]
if useStickySession {
//Use stick session, check which origins this request previously used
targetOriginId, err := m.getSessionHandler(r, origins)
if err != nil {
//No valid session found. Assign a new upstream
targetOrigin, index, err := getRandomUpstreamByWeight(origins)
if err != nil {
fmt.Println("Oops. Unable to get random upstream")
targetOrigin = origins[0]
index = 0
}
m.setSessionHandler(w, r, targetOrigin.OriginIpOrDomain, index)
return targetOrigin, nil
}
//Valid session found. Resume the previous session
return origins[targetOriginId], nil
} else {
//Do not use stick session. Get a random one
var err error
targetOrigin, _, err = getRandomUpstreamByWeight(origins)
if err != nil {
log.Println(err)
targetOrigin = origins[0]
}
}
//fmt.Println("DEBUG: Picking origin " + targetOrigin.OriginIpOrDomain)
return targetOrigin, nil
}
/* Features related to session access */
//Set a new origin for this connection by session
func (m *RouteManager) setSessionHandler(w http.ResponseWriter, r *http.Request, originIpOrDomain string, index int) error {
session, err := m.SessionStore.Get(r, "STICKYSESSION")
if err != nil {
return err
}
session.Values["zr_sid_origin"] = originIpOrDomain
session.Values["zr_sid_index"] = index
session.Options.MaxAge = 86400 //1 day
session.Options.Path = "/"
err = session.Save(r, w)
if err != nil {
return err
}
return nil
}
// Get the previous connected origin from session
func (m *RouteManager) getSessionHandler(r *http.Request, upstreams []*Upstream) (int, error) {
// Get existing session
session, err := m.SessionStore.Get(r, "STICKYSESSION")
if err != nil {
return -1, err
}
// Retrieve session values for origin
originDomainRaw := session.Values["zr_sid_origin"]
originIDRaw := session.Values["zr_sid_index"]
if originDomainRaw == nil || originIDRaw == nil {
return -1, errors.New("no session has been set")
}
originDomain := originDomainRaw.(string)
originID := originIDRaw.(int)
//Check if it has been modified
if len(upstreams) < originID || upstreams[originID].OriginIpOrDomain != originDomain {
//Mismatch or upstreams has been updated
return -1, errors.New("upstreams has been changed")
}
return originID, nil
}
/* Functions related to random upstream picking */
// Get a random upstream by the weights defined in Upstream struct, return the upstream, index value and any error
func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) {
var ret *Upstream
sum := 0
for _, c := range upstreams {
sum += c.Weight
}
r, err := intRange(0, sum)
if err != nil {
return ret, -1, err
}
counter := 0
for _, c := range upstreams {
r -= c.Weight
if r < 0 {
return c, counter, nil
}
counter++
}
if ret == nil {
//All fallback
//use the first one that is with weight = 0
fallbackUpstreams := []*Upstream{}
fallbackUpstreamsOriginalID := []int{}
for ix, upstream := range upstreams {
if upstream.Weight == 0 {
fallbackUpstreams = append(fallbackUpstreams, upstream)
fallbackUpstreamsOriginalID = append(fallbackUpstreamsOriginalID, ix)
}
}
upstreamID := rand.Intn(len(fallbackUpstreams))
return fallbackUpstreams[upstreamID], fallbackUpstreamsOriginalID[upstreamID], nil
}
return ret, -1, errors.New("failed to pick an upstream origin server")
}
// IntRange returns a random integer in the range from min to max.
func intRange(min, max int) (int, error) {
var result int
switch {
case min > max:
// Fail with error
return result, errors.New("min is greater than max")
case max == min:
result = max
case max > min:
b := rand.Intn(max-min) + min
result = min + int(b)
}
return result, nil
}

View File

@ -0,0 +1,77 @@
package loadbalance
import (
"encoding/json"
"errors"
"net/http"
"net/url"
"strings"
"time"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
)
// StartProxy create and start a HTTP proxy using dpcore
// Example of webProxyEndpoint: https://example.com:443 or http://192.168.1.100:8080
func (u *Upstream) StartProxy() error {
//Filter the tailing slash if any
domain := u.OriginIpOrDomain
if len(domain) == 0 {
return errors.New("invalid endpoint config")
}
if domain[len(domain)-1:] == "/" {
domain = domain[:len(domain)-1]
}
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
//TLS is not hardcoded in proxy target domain
if u.RequireTLS {
domain = "https://" + domain
} else {
domain = "http://" + domain
}
}
//Create a new proxy agent for this upstream
path, err := url.Parse(domain)
if err != nil {
return err
}
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
IgnoreTLSVerification: u.SkipCertValidations,
FlushInterval: 100 * time.Millisecond,
})
u.proxy = proxy
return nil
}
// IsReady return the proxy ready state of the upstream server
// Return false if StartProxy() is not called on this upstream before
func (u *Upstream) IsReady() bool {
return u.proxy != nil
}
// Clone return a new deep copy object of the identical upstream
func (u *Upstream) Clone() *Upstream {
newUpstream := Upstream{}
js, _ := json.Marshal(u)
json.Unmarshal(js, &newUpstream)
return &newUpstream
}
// ServeHTTP uses this upstream proxy router to route the current request, return the status code and error if any
func (u *Upstream) ServeHTTP(w http.ResponseWriter, r *http.Request, rrr *dpcore.ResponseRewriteRuleSet) (int, error) {
//Auto rewrite to upstream origin if not set
if rrr.ProxyDomain == "" {
rrr.ProxyDomain = u.OriginIpOrDomain
}
return u.proxy.ServeHTTP(w, r, rrr)
}
// String return the string representations of endpoints in this upstream
func (u *Upstream) String() string {
return u.OriginIpOrDomain
}

View File

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

View File

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

View File

@ -16,6 +16,7 @@ import (
"imuslab.com/zoraxy/mod/websocketproxy" "imuslab.com/zoraxy/mod/websocketproxy"
) )
// Check if the request URI matches any of the proxy endpoint
func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint { func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
var targetProxyEndpoint *ProxyEndpoint = nil var targetProxyEndpoint *ProxyEndpoint = nil
router.ProxyEndpoints.Range(func(key, value interface{}) bool { router.ProxyEndpoints.Range(func(key, value interface{}) bool {
@ -30,6 +31,7 @@ func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *P
return targetProxyEndpoint return targetProxyEndpoint
} }
// Get the proxy endpoint from hostname, which might includes checking of wildcard certificates
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint { func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
var targetSubdomainEndpoint *ProxyEndpoint = nil var targetSubdomainEndpoint *ProxyEndpoint = nil
ep, ok := router.ProxyEndpoints.Load(hostname) ep, ok := router.ProxyEndpoints.Load(hostname)
@ -111,18 +113,21 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
r.Header.Set("X-Forwarded-Host", r.Host) r.Header.Set("X-Forwarded-Host", r.Host)
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID) r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
//Inject custom headers /* Load balancing */
if len(target.UserDefinedHeaders) > 0 { selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession)
for _, customHeader := range target.UserDefinedHeaders { if err != nil {
r.Header.Set(customHeader.Key, customHeader.Value) http.ServeFile(w, r, "./web/rperror.html")
} log.Println(err.Error())
h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname())
return
} }
/* WebSocket automatic proxy */
requestURL := r.URL.String() requestURL := r.URL.String()
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" { if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin //Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
r.Header.Set("Zr-Origin-Upgrade", "websocket") r.Header.Set("Zr-Origin-Upgrade", "websocket")
wsRedirectionEndpoint := target.Domain wsRedirectionEndpoint := selectedUpstream.OriginIpOrDomain
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" { if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
//Append / to the end of the redirection endpoint if not exists //Append / to the end of the redirection endpoint if not exists
wsRedirectionEndpoint = wsRedirectionEndpoint + "/" wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
@ -132,13 +137,13 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
requestURL = requestURL[1:] requestURL = requestURL[1:]
} }
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL) u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL)
if target.RequireTLS { if selectedUpstream.RequireTLS {
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL) u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
} }
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain) h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{ wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: target.SkipCertValidations, SkipTLSValidation: selectedUpstream.SkipCertValidations,
SkipOriginCheck: target.SkipWebSocketOriginCheck, SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
}) })
wspHandler.ServeHTTP(w, r) wspHandler.ServeHTTP(w, r)
return return
@ -152,12 +157,20 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
r.URL, _ = url.Parse(originalHostHeader) r.URL, _ = url.Parse(originalHostHeader)
} }
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ //Build downstream and upstream header rules
ProxyDomain: target.Domain, upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()
OriginalHost: originalHostHeader,
UseTLS: target.RequireTLS, statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
NoCache: h.Parent.Option.NoCache, ProxyDomain: selectedUpstream.OriginIpOrDomain,
PathPrefix: "", OriginalHost: originalHostHeader,
UseTLS: selectedUpstream.RequireTLS,
NoCache: h.Parent.Option.NoCache,
PathPrefix: "",
UpstreamHeaders: upstreamHeaders,
DownstreamHeaders: downstreamHeaders,
HostHeaderOverwrite: target.RequestHostOverwrite,
NoRemoveHopByHop: target.DisableHopByHopHeaderRemoval,
Version: target.parent.Option.HostVersion,
}) })
var dnsError *net.DNSError var dnsError *net.DNSError
@ -165,15 +178,15 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
if errors.As(err, &dnsError) { if errors.As(err, &dnsError) {
http.ServeFile(w, r, "./web/hosterror.html") http.ServeFile(w, r, "./web/hosterror.html")
log.Println(err.Error()) log.Println(err.Error())
h.logRequest(r, false, 404, "subdomain-http", target.Domain) h.Parent.logRequest(r, false, 404, "host-http", r.URL.Hostname())
} else { } else {
http.ServeFile(w, r, "./web/rperror.html") http.ServeFile(w, r, "./web/rperror.html")
log.Println(err.Error()) log.Println(err.Error())
h.logRequest(r, false, 521, "subdomain-http", target.Domain) h.Parent.logRequest(r, false, 521, "host-http", r.URL.Hostname())
} }
} }
h.logRequest(r, true, 200, "subdomain-http", target.Domain) h.Parent.logRequest(r, true, statusCode, "host-http", r.URL.Hostname())
} }
// Handle vdir type request // Handle vdir type request
@ -184,13 +197,6 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
r.Header.Set("X-Forwarded-Host", r.Host) r.Header.Set("X-Forwarded-Host", r.Host)
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID) r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
//Inject custom headers
if len(target.parent.UserDefinedHeaders) > 0 {
for _, customHeader := range target.parent.UserDefinedHeaders {
r.Header.Set(customHeader.Key, customHeader.Value)
}
}
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" { if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin //Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
r.Header.Set("Zr-Origin-Upgrade", "websocket") r.Header.Set("Zr-Origin-Upgrade", "websocket")
@ -202,10 +208,10 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
if target.RequireTLS { if target.RequireTLS {
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String()) u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
} }
h.logRequest(r, true, 101, "vdir-websocket", target.Domain) h.Parent.logRequest(r, true, 101, "vdir-websocket", target.Domain)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{ wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: target.SkipCertValidations, SkipTLSValidation: target.SkipCertValidations,
SkipOriginCheck: target.parent.SkipWebSocketOriginCheck, SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
}) })
wspHandler.ServeHTTP(w, r) wspHandler.ServeHTTP(w, r)
return return
@ -219,11 +225,18 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
r.URL, _ = url.Parse(originalHostHeader) r.URL, _ = url.Parse(originalHostHeader)
} }
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ //Build downstream and upstream header rules
ProxyDomain: target.Domain, upstreamHeaders, downstreamHeaders := target.parent.SplitInboundOutboundHeaders()
OriginalHost: originalHostHeader,
UseTLS: target.RequireTLS, statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
PathPrefix: target.MatchingPath, ProxyDomain: target.Domain,
OriginalHost: originalHostHeader,
UseTLS: target.RequireTLS,
PathPrefix: target.MatchingPath,
UpstreamHeaders: upstreamHeaders,
DownstreamHeaders: downstreamHeaders,
HostHeaderOverwrite: target.parent.RequestHostOverwrite,
Version: target.parent.parent.Option.HostVersion,
}) })
var dnsError *net.DNSError var dnsError *net.DNSError
@ -231,23 +244,24 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
if errors.As(err, &dnsError) { if errors.As(err, &dnsError) {
http.ServeFile(w, r, "./web/hosterror.html") http.ServeFile(w, r, "./web/hosterror.html")
log.Println(err.Error()) log.Println(err.Error())
h.logRequest(r, false, 404, "vdir-http", target.Domain) h.Parent.logRequest(r, false, 404, "vdir-http", target.Domain)
} else { } else {
http.ServeFile(w, r, "./web/rperror.html") http.ServeFile(w, r, "./web/rperror.html")
log.Println(err.Error()) log.Println(err.Error())
h.logRequest(r, false, 521, "vdir-http", target.Domain) h.Parent.logRequest(r, false, 521, "vdir-http", target.Domain)
} }
} }
h.logRequest(r, true, 200, "vdir-http", target.Domain) h.Parent.logRequest(r, true, statusCode, "vdir-http", target.Domain)
} }
func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) { // This logger collect data for the statistical analysis. For log to file logger, check the Logger and LogHTTPRequest handler
if h.Parent.Option.StatisticCollector != nil { func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) {
if router.Option.StatisticCollector != nil {
go func() { go func() {
requestInfo := statistic.RequestInfo{ requestInfo := statistic.RequestInfo{
IpAddr: netutils.GetRequesterIP(r), IpAddr: netutils.GetRequesterIP(r),
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r), RequestOriginalCountryISOCode: router.Option.GeodbStore.GetRequesterCountryISOCode(r),
Succ: succ, Succ: succ,
StatusCode: statusCode, StatusCode: statusCode,
ForwardType: forwardType, ForwardType: forwardType,
@ -256,7 +270,8 @@ func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, fo
RequestURL: r.Host + r.RequestURI, RequestURL: r.Host + r.RequestURI,
Target: target, Target: target,
} }
h.Parent.Option.StatisticCollector.RecordRequest(requestInfo) router.Option.StatisticCollector.RecordRequest(requestInfo)
}() }()
} }
router.Option.Logger.LogHTTPRequest(r, forwardType, statusCode)
} }

View File

@ -0,0 +1,119 @@
package dynamicproxy
import (
"errors"
"net"
"net/http"
"strings"
"sync"
"sync/atomic"
"time"
)
// IpTable is a rate limiter implementation using sync.Map with atomic int64
type RequestCountPerIpTable struct {
table sync.Map
}
// Increment the count of requests for a given IP
func (t *RequestCountPerIpTable) Increment(ip string) {
v, _ := t.table.LoadOrStore(ip, new(int64))
atomic.AddInt64(v.(*int64), 1)
}
// Check if the IP is in the table and if it is, check if the count is less than the limit
func (t *RequestCountPerIpTable) Exceeded(ip string, limit int64) bool {
v, ok := t.table.Load(ip)
if !ok {
return false
}
count := atomic.LoadInt64(v.(*int64))
return count >= limit
}
// Get the count of requests for a given IP
func (t *RequestCountPerIpTable) GetCount(ip string) int64 {
v, ok := t.table.Load(ip)
if !ok {
return 0
}
return atomic.LoadInt64(v.(*int64))
}
// Clear the IP table
func (t *RequestCountPerIpTable) Clear() {
t.table.Range(func(key, value interface{}) bool {
t.table.Delete(key)
return true
})
}
func (h *ProxyHandler) handleRateLimitRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
err := h.Parent.handleRateLimit(w, r, pe)
if err != nil {
h.Parent.logRequest(r, false, 429, "ratelimit", r.URL.Hostname())
}
return err
}
func (router *Router) handleRateLimit(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
//Get the real client-ip from request header
clientIP := r.RemoteAddr
if r.Header.Get("X-Real-Ip") == "" {
CF_Connecting_IP := r.Header.Get("CF-Connecting-IP")
Fastly_Client_IP := r.Header.Get("Fastly-Client-IP")
if CF_Connecting_IP != "" {
//Use CF Connecting IP
clientIP = CF_Connecting_IP
} else if Fastly_Client_IP != "" {
//Use Fastly Client IP
clientIP = Fastly_Client_IP
} else {
ips := strings.Split(clientIP, ",")
if len(ips) > 0 {
clientIP = strings.TrimSpace(ips[0])
}
}
}
ip, _, err := net.SplitHostPort(clientIP)
if err != nil {
//Default allow passthrough on error
return nil
}
router.rateLimitCounter.Increment(ip)
if router.rateLimitCounter.Exceeded(ip, int64(pe.RateLimit)) {
w.WriteHeader(429)
return errors.New("rate limit exceeded")
}
// log.Println("Rate limit check", ip, ipTable.GetCount(ip))
return nil
}
// Start the ticker routine for reseting the rate limit counter every seconds
func (r *Router) startRateLimterCounterResetTicker() error {
if r.rateLimterStop != nil {
return errors.New("another rate limiter ticker already running")
}
tickerStopChan := make(chan bool)
r.rateLimterStop = tickerStopChan
counterResetTicker := time.NewTicker(1 * time.Second)
go func() {
for {
select {
case <-tickerStopChan:
r.rateLimterStop = nil
return
case <-counterResetTicker.C:
r.rateLimitCounter.Clear()
}
}
}()
return nil
}

View File

@ -1,7 +1,7 @@
package redirection package redirection
import ( import (
"log" "errors"
"net/http" "net/http"
"strings" "strings"
) )
@ -52,7 +52,7 @@ func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) int {
//Invalid usage //Invalid usage
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Internal Server Error")) w.Write([]byte("500 - Internal Server Error"))
log.Println("Target request URL do not have matching redirect rule. Check with IsRedirectable before calling HandleRedirect!") t.log("Target request URL do not have matching redirect rule. Check with IsRedirectable before calling HandleRedirect!", errors.New("invalid usage"))
return 500 return 500
} }
} }

View File

@ -30,11 +30,12 @@ type RedirectRules struct {
StatusCode int //Status Code for redirection StatusCode int //Status Code for redirection
} }
func NewRuleTable(configPath string, allowRegex bool) (*RuleTable, error) { func NewRuleTable(configPath string, allowRegex bool, logger *logger.Logger) (*RuleTable, error) {
thisRuleTable := RuleTable{ thisRuleTable := RuleTable{
rules: sync.Map{}, rules: sync.Map{},
configPath: configPath, configPath: configPath,
AllowRegex: allowRegex, AllowRegex: allowRegex,
Logger: logger,
} }
//Load all the rules from the config path //Load all the rules from the config path
if !utils.FileExists(configPath) { if !utils.FileExists(configPath) {
@ -67,7 +68,7 @@ func NewRuleTable(configPath string, allowRegex bool) (*RuleTable, error) {
//Map the rules into the sync map //Map the rules into the sync map
for _, rule := range rules { for _, rule := range rules {
log.Println("Redirection rule added: " + rule.RedirectURL + " -> " + rule.TargetURL) thisRuleTable.log("Redirection rule added: "+rule.RedirectURL+" -> "+rule.TargetURL, nil)
thisRuleTable.rules.Store(rule.RedirectURL, rule) thisRuleTable.rules.Store(rule.RedirectURL, rule)
} }
@ -92,7 +93,7 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
// Create a new file for writing the JSON data // Create a new file for writing the JSON data
file, err := os.Create(filepath) file, err := os.Create(filepath)
if err != nil { if err != nil {
log.Printf("Error creating file %s: %s", filepath, err) t.log("Error creating file "+filepath, err)
return err return err
} }
defer file.Close() defer file.Close()
@ -100,7 +101,7 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
// Encode the RedirectRules object to JSON and write it to the file // Encode the RedirectRules object to JSON and write it to the file
err = json.NewEncoder(file).Encode(newRule) err = json.NewEncoder(file).Encode(newRule)
if err != nil { if err != nil {
log.Printf("Error encoding JSON to file %s: %s", filepath, err) t.log("Error encoding JSON to file "+filepath, err)
return err return err
} }
@ -125,7 +126,7 @@ func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
// Delete the file // Delete the file
if err := os.Remove(filepath); err != nil { if err := os.Remove(filepath); err != nil {
log.Printf("Error deleting file %s: %s", filepath, err) t.log("Error deleting file "+filepath, err)
return err return err
} }
@ -194,6 +195,6 @@ func (t *RuleTable) log(message string, err error) {
log.Println("[Redirect] " + message + ": " + err.Error()) log.Println("[Redirect] " + message + ": " + err.Error())
} }
} else { } else {
t.Logger.PrintAndLog("Redirect", message, err) t.Logger.PrintAndLog("redirect", message, err)
} }
} }

View File

@ -2,8 +2,10 @@ package dynamicproxy
import ( import (
"errors" "errors"
"log"
"net/url" "net/url"
"strings" "strings"
"time"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore" "imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
) )
@ -17,41 +19,18 @@ import (
// Prepare proxy route generate a proxy handler service object for your endpoint // Prepare proxy route generate a proxy handler service object for your endpoint
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) { func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
//Filter the tailing slash if any for _, thisOrigin := range endpoint.ActiveOrigins {
domain := endpoint.Domain //Create the proxy routing handler
if len(domain) == 0 { err := thisOrigin.StartProxy()
return nil, errors.New("invalid endpoint config") if err != nil {
} log.Println("Unable to setup upstream " + thisOrigin.OriginIpOrDomain + ": " + err.Error())
if domain[len(domain)-1:] == "/" { continue
domain = domain[:len(domain)-1]
}
endpoint.Domain = domain
//Parse the web proxy endpoint
webProxyEndpoint := domain
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
//TLS is not hardcoded in proxy target domain
if endpoint.RequireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
} }
} }
//Create a new proxy agent for this root
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return nil, err
}
//Create the proxy routing handler
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
IgnoreTLSVerification: endpoint.SkipCertValidations,
})
endpoint.proxy = proxy
endpoint.parent = router endpoint.parent = router
//Prepare proxy routing hjandler for each of the virtual directories //Prepare proxy routing handler for each of the virtual directories
for _, vdir := range endpoint.VirtualDirectories { for _, vdir := range endpoint.VirtualDirectories {
domain := vdir.Domain domain := vdir.Domain
if len(domain) == 0 { if len(domain) == 0 {
@ -63,7 +42,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
} }
//Parse the web proxy endpoint //Parse the web proxy endpoint
webProxyEndpoint = domain webProxyEndpoint := domain
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) { if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
//TLS is not hardcoded in proxy target domain //TLS is not hardcoded in proxy target domain
if vdir.RequireTLS { if vdir.RequireTLS {
@ -80,6 +59,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
proxy := dpcore.NewDynamicProxyCore(path, vdir.MatchingPath, &dpcore.DpcoreOptions{ proxy := dpcore.NewDynamicProxyCore(path, vdir.MatchingPath, &dpcore.DpcoreOptions{
IgnoreTLSVerification: vdir.SkipCertValidations, IgnoreTLSVerification: vdir.SkipCertValidations,
FlushInterval: 500 * time.Millisecond,
}) })
vdir.proxy = proxy vdir.proxy = proxy
vdir.parent = endpoint vdir.parent = endpoint
@ -90,7 +70,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime // Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error { func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
if endpoint.proxy == nil { if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
//This endpoint is not prepared //This endpoint is not prepared
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime") return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
} }
@ -101,7 +81,7 @@ func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
// Set given Proxy Route as Root. Call to PrepareProxyRoute before adding to runtime // Set given Proxy Route as Root. Call to PrepareProxyRoute before adding to runtime
func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error { func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error {
if endpoint.proxy == nil { if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
//This endpoint is not prepared //This endpoint is not prepared
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime") return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
} }

View File

@ -14,7 +14,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.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> <title>404 - Host Not Found</title>
<style> <style>
h1, h2, h3, h4, h5, p, a, span{ h1, h2, h3, h4, h5, p, a, span, .ui.list .item{
font-family: 'Noto Sans TC', sans-serif; font-family: 'Noto Sans TC', sans-serif;
font-weight: 300; font-weight: 300;
color: rgb(88, 88, 88) color: rgb(88, 88, 88)
@ -22,9 +22,6 @@
.diagram{ .diagram{
background-color: #ebebeb; background-color: #ebebeb;
box-shadow:
inset 0px 11px 8px -10px #CCC,
inset 0px -11px 8px -10px #CCC;
padding-bottom: 2em; padding-bottom: 2em;
} }

View File

@ -8,8 +8,11 @@ import (
"imuslab.com/zoraxy/mod/access" "imuslab.com/zoraxy/mod/access"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore" "imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection" "imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/geodb" "imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/statistic" "imuslab.com/zoraxy/mod/statistic"
"imuslab.com/zoraxy/mod/tlscert" "imuslab.com/zoraxy/mod/tlscert"
) )
@ -24,23 +27,27 @@ type ProxyHandler struct {
Parent *Router Parent *Router
} }
/* Router Object Options */
type RouterOption struct { type RouterOption struct {
HostUUID string //The UUID of Zoraxy, use for heading mod HostUUID string //The UUID of Zoraxy, use for heading mod
HostVersion string //The version of Zoraxy, use for heading mod HostVersion string //The version of Zoraxy, use for heading mod
Port int //Incoming port Port int //Incoming port
UseTls bool //Use TLS to serve incoming requsts UseTls bool //Use TLS to serve incoming requsts
ForceTLSLatest bool //Force TLS1.2 or above ForceTLSLatest bool //Force TLS1.2 or above
NoCache bool //Force set Cache-Control: no-store NoCache bool //Force set Cache-Control: no-store
ListenOnPort80 bool //Enable port 80 http listener ListenOnPort80 bool //Enable port 80 http listener
ForceHttpsRedirect bool //Force redirection of http to https endpoint ForceHttpsRedirect bool //Force redirection of http to https endpoint
TlsManager *tlscert.Manager TlsManager *tlscert.Manager //TLS manager for serving SAN certificates
RedirectRuleTable *redirection.RuleTable RedirectRuleTable *redirection.RuleTable //Redirection rules handler and table
GeodbStore *geodb.Store //GeoIP resolver GeodbStore *geodb.Store //GeoIP resolver
AccessController *access.Controller //Blacklist / whitelist controller AccessController *access.Controller //Blacklist / whitelist controller
StatisticCollector *statistic.Collector StatisticCollector *statistic.Collector //Statistic collector for storing stats on incoming visitors
WebDirectory string //The static web server directory containing the templates folder WebDirectory string //The static web server directory containing the templates folder
LoadBalancer *loadbalance.RouteManager //Load balancer that handle load balancing of proxy target
Logger *logger.Logger //Logger for reverse proxy requets
} }
/* Router Object */
type Router struct { type Router struct {
Option *RouterOption Option *RouterOption
ProxyEndpoints *sync.Map ProxyEndpoints *sync.Map
@ -49,12 +56,15 @@ type Router struct {
mux http.Handler mux http.Handler
server *http.Server server *http.Server
tlsListener net.Listener tlsListener net.Listener
loadBalancer *loadbalance.RouteManager //Load balancer routing manager
routingRules []*RoutingRule routingRules []*RoutingRule
tlsRedirectStop chan bool //Stop channel for tls redirection server tlsRedirectStop chan bool //Stop channel for tls redirection server
tldMap map[string]int //Top level domain map, see tld.json rateLimterStop chan bool //Stop channel for rate limiter
rateLimitCounter RequestCountPerIpTable //Request counter for rate limter
} }
/* Basic Auth Related Data structure*/
// Auth credential for basic auth on certain endpoints // Auth credential for basic auth on certain endpoints
type BasicAuthCredentials struct { type BasicAuthCredentials struct {
Username string Username string
@ -72,12 +82,25 @@ type BasicAuthExceptionRule struct {
PathPrefix string PathPrefix string
} }
/* Custom Header Related Data structure */
// Header injection direction type
type HeaderDirection int
const (
HeaderDirection_ZoraxyToUpstream HeaderDirection = 0 //Inject (or remove) header to request out-going from Zoraxy to backend server
HeaderDirection_ZoraxyToDownstream HeaderDirection = 1 //Inject (or remove) header to request out-going from Zoraxy to client (e.g. browser)
)
// User defined headers to add into a proxy endpoint // User defined headers to add into a proxy endpoint
type UserDefinedHeader struct { type UserDefinedHeader struct {
Key string Direction HeaderDirection
Value string Key string
Value string
IsRemove bool //Instead of set, remove this key instead
} }
/* Routing Rule Data Structures */
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better // A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
// program structure than directly using ProxyEndpoint // program structure than directly using ProxyEndpoint
type VirtualDirectoryEndpoint struct { type VirtualDirectoryEndpoint struct {
@ -92,40 +115,47 @@ type VirtualDirectoryEndpoint struct {
// A proxy endpoint record, a general interface for handling inbound routing // A proxy endpoint record, a general interface for handling inbound routing
type ProxyEndpoint struct { type ProxyEndpoint struct {
ProxyType int //The type of this proxy, see const def ProxyType int //The type of this proxy, see const def
RootOrMatchingDomain string //Matching domain for host, also act as key RootOrMatchingDomain string //Matching domain for host, also act as key
MatchingDomainAlias []string //A list of domains that alias to this rule MatchingDomainAlias []string //A list of domains that alias to this rule
Domain string //Domain or IP to proxy to ActiveOrigins []*loadbalance.Upstream //Activated Upstream or origin servers IP or domain to proxy to
InactiveOrigins []*loadbalance.Upstream //Disabled Upstream or origin servers IP or domain to proxy to
UseStickySession bool //Use stick session for load balancing
UseActiveLoadBalance bool //Use active loadbalancing, default passive
Disabled bool //If the rule is disabled
//TLS/SSL Related //Inbound TLS/SSL Related
RequireTLS bool //Target domain require TLS BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
SkipCertValidations bool //Set to true to accept self signed certs
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
//Virtual Directories //Virtual Directories
VirtualDirectories []*VirtualDirectoryEndpoint VirtualDirectories []*VirtualDirectoryEndpoint
//Custom Headers //Custom Headers
UserDefinedHeaders []*UserDefinedHeader //Custom headers to append when proxying requests from this endpoint UserDefinedHeaders []*UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
RequestHostOverwrite string //If not empty, this domain will be used to overwrite the Host field in request header
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header
DisableHopByHopHeaderRemoval bool //Do not remove hop-by-hop headers
//Authentication //Authentication
RequireBasicAuth bool //Set to true to request basic auth before proxy RequireBasicAuth bool //Set to true to request basic auth before proxy
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
// Rate Limiting
RequireRateLimit bool
RateLimit int64 // Rate limit in requests per second
//Access Control //Access Control
AccessFilterUUID string //Access filter ID AccessFilterUUID string //Access filter ID
Disabled bool //If the rule is disabled
//Fallback routing logic (Special Rule Sets Only) //Fallback routing logic (Special Rule Sets Only)
DefaultSiteOption int //Fallback routing logic options DefaultSiteOption int //Fallback routing logic options
DefaultSiteValue string //Fallback routing target, optional DefaultSiteValue string //Fallback routing target, optional
//Internal Logic Elements //Internal Logic Elements
parent *Router `json:"-"` parent *Router `json:"-"`
proxy *dpcore.ReverseProxy `json:"-"`
} }
/* /*

View File

@ -42,17 +42,22 @@ SendEmail(
) )
*/ */
func (s *Sender) SendEmail(to string, subject string, content string) error { func (s *Sender) SendEmail(to string, subject string, content string) error {
//Parse the email content // Parse the email content
msg := []byte("To: " + to + "\n" + msg := []byte("To: " + to + "\n" +
"From: Zoraxy <" + s.SenderAddr + ">\n" + "From: Zoraxy <" + s.SenderAddr + ">\n" +
"Subject: " + subject + "\n" + "Subject: " + subject + "\n" +
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" + "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" +
content + "\n\n") content + "\n\n")
//Login to the SMTP server // Initialize the auth variable
//Username can be username (e.g. admin) or email (e.g. admin@example.com), depending on SMTP service provider var auth smtp.Auth
auth := smtp.PlainAuth("", s.Username, s.Password, s.Hostname) if s.Password != "" {
// Login to the SMTP server
// Username can be username (e.g. admin) or email (e.g. admin@example.com), depending on SMTP service provider
auth = smtp.PlainAuth("", s.Username, s.Password, s.Hostname)
}
// Send the email
err := smtp.SendMail(s.Hostname+":"+strconv.Itoa(s.Port), auth, s.SenderAddr, []string{to}, msg) err := smtp.SendMail(s.Hostname+":"+strconv.Itoa(s.Port), auth, s.SenderAddr, []string{to}, msg)
if err != nil { if err != nil {
return err return err

View File

@ -19,9 +19,8 @@ type Store struct {
geodbIpv6 [][]string //Parsed geodb list for ipv6 geodbIpv6 [][]string //Parsed geodb list for ipv6
geotrie *trie geotrie *trie
geotrieIpv6 *trie geotrieIpv6 *trie
//geoipCache sync.Map sysdb *database.Database
sysdb *database.Database option *StoreOptions
option *StoreOptions
} }
type StoreOptions struct { type StoreOptions struct {

View File

@ -43,7 +43,7 @@ func TestResolveCountryCodeFromIP(t *testing.T) {
// Create a new store // Create a new store
store, err := geodb.NewGeoDb(nil, &geodb.StoreOptions{ store, err := geodb.NewGeoDb(nil, &geodb.StoreOptions{
false, false,
false, true,
}) })
if err != nil { if err != nil {
t.Errorf("error creating store: %v", err) t.Errorf("error creating store: %v", err)
@ -56,6 +56,7 @@ func TestResolveCountryCodeFromIP(t *testing.T) {
{"176.113.115.113", "RU"}, {"176.113.115.113", "RU"},
{"65.21.233.213", "FI"}, {"65.21.233.213", "FI"},
{"94.23.207.193", "FR"}, {"94.23.207.193", "FR"},
{"77.131.21.232", "FR"},
} }
for _, testcase := range knownIpCountryMap { for _, testcase := range knownIpCountryMap {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -16,13 +16,6 @@ func (s *Store) search(ip string) string {
ip = strings.Split(ip, ",")[0] ip = strings.Split(ip, ",")[0]
ip = strings.TrimSpace(ip) ip = strings.TrimSpace(ip)
} }
//See if there are cached country code for this ip
/*
ccc, ok := s.geoipCache.Load(ip)
if ok {
return ccc.(string)
}
*/
//Search in geotrie tree //Search in geotrie tree
cc := "" cc := ""

View File

@ -53,6 +53,9 @@ func isIPv6InRange(startIP, endIP, testIP string) (bool, error) {
// Slow country code lookup for // Slow country code lookup for
func (s *Store) slowSearchIpv4(ipAddr string) string { func (s *Store) slowSearchIpv4(ipAddr string) string {
if isReservedIP(ipAddr) {
return ""
}
for _, ipRange := range s.geodb { for _, ipRange := range s.geodb {
startIp := ipRange[0] startIp := ipRange[0]
endIp := ipRange[1] endIp := ipRange[1]
@ -67,6 +70,9 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
} }
func (s *Store) slowSearchIpv6(ipAddr string) string { func (s *Store) slowSearchIpv6(ipAddr string) string {
if isReservedIP(ipAddr) {
return ""
}
for _, ipRange := range s.geodbIpv6 { for _, ipRange := range s.geodbIpv6 {
startIp := ipRange[0] startIp := ipRange[0]
endIp := ipRange[1] endIp := ipRange[1]

View File

@ -1,7 +1,6 @@
package geodb package geodb
import ( import (
"math"
"net" "net"
) )
@ -41,14 +40,10 @@ func (t *trie) insert(ipAddr string, cc string) {
ipBytes := ipToBytes(ipAddr) ipBytes := ipToBytes(ipAddr)
current := t.root current := t.root
for _, b := range ipBytes { for _, b := range ipBytes {
//For each byte in the ip address //For each byte in the ip address (4 / 16 bytes)
//each byte is 8 bit //each byte is 8 bit
for j := 0; j < 8; j++ { for j := 7; j >= 0; j-- {
bitwise := (b&uint8(math.Pow(float64(2), float64(j))) > 0) bit := int(b >> j & 1)
bit := 0b0000
if bitwise {
bit = 0b0001
}
if current.childrens[bit] == nil { if current.childrens[bit] == nil {
current.childrens[bit] = &trie_Node{ current.childrens[bit] = &trie_Node{
childrens: [2]*trie_Node{}, childrens: [2]*trie_Node{},
@ -58,21 +53,9 @@ func (t *trie) insert(ipAddr string, cc string) {
current = current.childrens[bit] current = current.childrens[bit]
} }
} }
/*
for i := 63; i >= 0; i-- {
bit := (ipInt64 >> uint(i)) & 1
if current.childrens[bit] == nil {
current.childrens[bit] = &trie_Node{
childrens: [2]*trie_Node{},
cc: cc,
}
}
current = current.childrens[bit]
}
*/
} }
// isReservedIP check if the given ip address is NOT a public ip address
func isReservedIP(ip string) bool { func isReservedIP(ip string) bool {
parsedIP := net.ParseIP(ip) parsedIP := net.ParseIP(ip)
if parsedIP == nil { if parsedIP == nil {
@ -86,12 +69,10 @@ func isReservedIP(ip string) bool {
if parsedIP.IsLinkLocalUnicast() || parsedIP.IsLinkLocalMulticast() { if parsedIP.IsLinkLocalUnicast() || parsedIP.IsLinkLocalMulticast() {
return true return true
} }
//Check if the IP is in the reserved private range
if parsedIP.IsPrivate() { if parsedIP.IsPrivate() {
return true return true
} }
// If the IP address is not a reserved address, return false
return false return false
} }
@ -106,27 +87,15 @@ func (t *trie) search(ipAddr string) string {
for _, b := range ipBytes { for _, b := range ipBytes {
//For each byte in the ip address //For each byte in the ip address
//each byte is 8 bit //each byte is 8 bit
for j := 0; j < 8; j++ { for j := 7; j >= 0; j-- {
bitwise := (b&uint8(math.Pow(float64(2), float64(j))) > 0) bit := int(b >> j & 1)
bit := 0b0000
if bitwise {
bit = 0b0001
}
if current.childrens[bit] == nil { if current.childrens[bit] == nil {
return current.cc return current.cc
} }
current = current.childrens[bit] current = current.childrens[bit]
} }
} }
/*
for i := 63; i >= 0; i-- {
bit := (ipInt64 >> uint(i)) & 1
if current.childrens[bit] == nil {
return current.cc
}
current = current.childrens[bit]
}
*/
if len(current.childrens) == 0 { if len(current.childrens) == 0 {
return current.cc return current.cc
} }

View File

@ -13,29 +13,31 @@ import (
Zoraxy Logger Zoraxy Logger
This script is designed to make a managed log for the Zoraxy This script is designed to make a managed log for the Zoraxy
and replace the ton of log.Println in the system core and replace the ton of log.Println in the system core.
The core logger is based in golang's build-in log package
*/ */
type Logger struct { type Logger struct {
LogToFile bool //Set enable write to file
Prefix string //Prefix for log files Prefix string //Prefix for log files
LogFolder string //Folder to store the log file LogFolder string //Folder to store the log file
CurrentLogFile string //Current writing filename CurrentLogFile string //Current writing filename
logger *log.Logger
file *os.File file *os.File
} }
func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger, error) { // Create a new logger that log to files
func NewLogger(logFilePrefix string, logFolder string) (*Logger, error) {
err := os.MkdirAll(logFolder, 0775) err := os.MkdirAll(logFolder, 0775)
if err != nil { if err != nil {
return nil, err return nil, err
} }
thisLogger := Logger{ thisLogger := Logger{
LogToFile: logToFile,
Prefix: logFilePrefix, Prefix: logFilePrefix,
LogFolder: logFolder, LogFolder: logFolder,
} }
//Create the log file if not exists
logFilePath := thisLogger.getLogFilepath() logFilePath := thisLogger.getLogFilepath()
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
if err != nil { if err != nil {
@ -43,9 +45,26 @@ func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger,
} }
thisLogger.CurrentLogFile = logFilePath thisLogger.CurrentLogFile = logFilePath
thisLogger.file = f thisLogger.file = f
//Start the logger
logger := log.New(f, "", log.Flags()&^(log.Ldate|log.Ltime))
logger.SetFlags(0)
logger.SetOutput(f)
thisLogger.logger = logger
return &thisLogger, nil return &thisLogger, nil
} }
// Create a fmt logger that only log to STDOUT
func NewFmtLogger() (*Logger, error) {
return &Logger{
Prefix: "",
LogFolder: "",
CurrentLogFile: "",
logger: nil,
file: nil,
}, nil
}
func (l *Logger) getLogFilepath() string { func (l *Logger) getLogFilepath() string {
year, month, _ := time.Now().Date() year, month, _ := time.Now().Date()
return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log") return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log")
@ -54,9 +73,8 @@ func (l *Logger) getLogFilepath() string {
// PrintAndLog will log the message to file and print the log to STDOUT // PrintAndLog will log the message to file and print the log to STDOUT
func (l *Logger) PrintAndLog(title string, message string, originalError error) { func (l *Logger) PrintAndLog(title string, message string, originalError error) {
go func() { go func() {
l.Log(title, message, originalError) l.Log(title, message, originalError, true)
}() }()
log.Println("[" + title + "] " + message)
} }
// Println is a fast snap-in replacement for log.Println // Println is a fast snap-in replacement for log.Println
@ -64,18 +82,26 @@ func (l *Logger) Println(v ...interface{}) {
//Convert the array of interfaces into string //Convert the array of interfaces into string
message := fmt.Sprint(v...) message := fmt.Sprint(v...)
go func() { go func() {
l.Log("info", string(message), nil) l.Log("internal", string(message), nil, true)
}() }()
log.Println("[INFO] " + string(message))
} }
func (l *Logger) Log(title string, errorMessage string, originalError error) { func (l *Logger) Log(title string, errorMessage string, originalError error, copyToSTDOUT bool) {
l.ValidateAndUpdateLogFilepath() l.ValidateAndUpdateLogFilepath()
if l.LogToFile { if l.logger == nil || copyToSTDOUT {
//Use STDOUT instead of logger
if originalError == nil { if originalError == nil {
l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [INFO]" + errorMessage + "\n") fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
} else { } else {
l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [ERROR]" + errorMessage + " " + originalError.Error() + "\n") fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:error] " + errorMessage + ": " + originalError.Error())
}
}
if l.logger != nil {
if originalError == nil {
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
} else {
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:error] " + errorMessage + ": " + originalError.Error())
} }
} }
@ -83,18 +109,28 @@ func (l *Logger) Log(title string, errorMessage string, originalError error) {
// Validate if the logging target is still valid (detect any months change) // Validate if the logging target is still valid (detect any months change)
func (l *Logger) ValidateAndUpdateLogFilepath() { func (l *Logger) ValidateAndUpdateLogFilepath() {
if l.file == nil {
return
}
expectedCurrentLogFilepath := l.getLogFilepath() expectedCurrentLogFilepath := l.getLogFilepath()
if l.CurrentLogFile != expectedCurrentLogFilepath { if l.CurrentLogFile != expectedCurrentLogFilepath {
//Change of month. Update to a new log file //Change of month. Update to a new log file
l.file.Close() l.file.Close()
l.file = nil
//Create a new log file
f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
if err != nil { if err != nil {
log.Println("[Logger] Unable to create new log. Logging to file disabled.") log.Println("Unable to create new log. Logging is disabled: ", err.Error())
l.LogToFile = false l.logger = nil
return return
} }
l.CurrentLogFile = expectedCurrentLogFilepath l.CurrentLogFile = expectedCurrentLogFilepath
l.file = f l.file = f
//Start a new logger
logger := log.New(f, "", log.Default().Flags())
l.logger = logger
} }
} }

View File

@ -0,0 +1,32 @@
package logger
/*
Traffic Log
This script log the traffic of HTTP requests
*/
import (
"net/http"
"strconv"
"time"
"imuslab.com/zoraxy/mod/netutils"
)
// Log HTTP request. Note that this must run in go routine to prevent any blocking
// in reverse proxy router
func (l *Logger) LogHTTPRequest(r *http.Request, reqclass string, statusCode int) {
go func() {
l.ValidateAndUpdateLogFilepath()
if l.logger == nil || l.file == nil {
//logger is not initiated. Do not log http request
return
}
clientIP := netutils.GetRequesterIP(r)
requestURI := r.RequestURI
statusCodeString := strconv.Itoa(statusCode)
//fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [router:" + reqclass + "] [client " + clientIP + "] " + r.Method + " " + requestURI + " " + statusCodeString)
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [router:" + reqclass + "] [origin:" + r.URL.Hostname() + "] [client " + clientIP + "] " + r.Method + " " + requestURI + " " + statusCodeString)
}()
}

View File

@ -51,13 +51,7 @@ func (v *Viewer) HandleReadLog(w http.ResponseWriter, r *http.Request) {
return return
} }
catergory, err := utils.GetPara(r, "catergory") content, err := v.LoadLogFile(strings.TrimSpace(filepath.Base(filename)))
if err != nil {
utils.SendErrorResponse(w, "invalid catergory given")
return
}
content, err := v.LoadLogFile(strings.TrimSpace(filepath.Base(catergory)), strings.TrimSpace(filepath.Base(filename)))
if err != nil { if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return return
@ -106,8 +100,10 @@ func (v *Viewer) ListLogFiles(showFullpath bool) map[string][]*LogFile {
return result return result
} }
func (v *Viewer) LoadLogFile(catergory string, filename string) (string, error) { func (v *Viewer) LoadLogFile(filename string) (string, error) {
logFilepath := filepath.Join(v.option.RootFolder, catergory, filename) filename = filepath.ToSlash(filename)
filename = strings.ReplaceAll(filename, "../", "")
logFilepath := filepath.Join(v.option.RootFolder, filename)
if utils.FileExists(logFilepath) { if utils.FileExists(logFilepath) {
//Load it //Load it
content, err := os.ReadFile(logFilepath) content, err := os.ReadFile(logFilepath)

View File

@ -3,8 +3,6 @@ package netstat
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"log"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
@ -14,6 +12,7 @@ import (
"strings" "strings"
"time" "time"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
) )
@ -35,11 +34,11 @@ type NetStatBuffers struct {
Stats []*FlowStat //Statistic of the flow Stats []*FlowStat //Statistic of the flow
StopChan chan bool //Channel to stop the ticker StopChan chan bool //Channel to stop the ticker
EventTicker *time.Ticker //Ticker for event logging EventTicker *time.Ticker //Ticker for event logging
logger *logger.Logger
} }
// Get a new network statistic buffers // Get a new network statistic buffers
func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) { func NewNetStatBuffer(recordCount int, systemWideLogger *logger.Logger) (*NetStatBuffers, error) {
//Flood fill the stats with 0 //Flood fill the stats with 0
initialStats := []*FlowStat{} initialStats := []*FlowStat{}
for i := 0; i < recordCount; i++ { for i := 0; i < recordCount; i++ {
@ -66,21 +65,22 @@ func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
Stats: initialStats, Stats: initialStats,
StopChan: stopCh, StopChan: stopCh,
EventTicker: ticker, EventTicker: ticker,
logger: systemWideLogger,
} }
//Get the initial measurements of netstats //Get the initial measurements of netstats
rx, tx, err := GetNetworkInterfaceStats() rx, tx, err := thisNetBuffer.GetNetworkInterfaceStats()
if err != nil { if err != nil {
log.Println("Unable to get NIC stats: ", err.Error()) systemWideLogger.PrintAndLog("netstat", "Unable to get NIC stats: ", err)
} }
retryCount := 0 retryCount := 0
for rx == 0 && tx == 0 && retryCount < 10 { for rx == 0 && tx == 0 && retryCount < 10 {
//Strange. Retry //Strange. Retry
log.Println("NIC stats return all 0. Retrying...") systemWideLogger.PrintAndLog("netstat", "NIC stats return all 0. Retrying...", nil)
rx, tx, err = GetNetworkInterfaceStats() rx, tx, err = thisNetBuffer.GetNetworkInterfaceStats()
if err != nil { if err != nil {
log.Println("Unable to get NIC stats: ", err.Error()) systemWideLogger.PrintAndLog("netstat", "Unable to get NIC stats: ", err)
} }
retryCount++ retryCount++
} }
@ -95,20 +95,20 @@ func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
for { for {
select { select {
case <-n.StopChan: case <-n.StopChan:
fmt.Println("- Netstats listener stopped") systemWideLogger.PrintAndLog("netstat", "Netstats listener stopped", nil)
return return
case <-ticker.C: case <-ticker.C:
if n.PreviousStat.RX == 0 && n.PreviousStat.TX == 0 { if n.PreviousStat.RX == 0 && n.PreviousStat.TX == 0 {
//Initiation state is still not done. Ignore request //Initiation state is still not done. Ignore request
log.Println("No initial states. Waiting") systemWideLogger.PrintAndLog("netstat", "No initial states. Waiting", nil)
return return
} }
// Get the latest network interface stats // Get the latest network interface stats
rx, tx, err := GetNetworkInterfaceStats() rx, tx, err := thisNetBuffer.GetNetworkInterfaceStats()
if err != nil { if err != nil {
// Log the error, but don't stop the buffer // Log the error, but don't stop the buffer
log.Printf("Failed to get network interface stats: %v", err) systemWideLogger.PrintAndLog("netstat", "Failed to get network interface stats", err)
continue continue
} }
@ -174,8 +174,8 @@ func (n *NetStatBuffers) Close() {
n.EventTicker.Stop() n.EventTicker.Stop()
} }
func HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) { func (n *NetStatBuffers) HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
rx, tx, err := GetNetworkInterfaceStats() rx, tx, err := n.GetNetworkInterfaceStats()
if err != nil { if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return return
@ -194,7 +194,7 @@ func HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
} }
// Get network interface stats, return accumulated rx bits, tx bits and error if any // Get network interface stats, return accumulated rx bits, tx bits and error if any
func GetNetworkInterfaceStats() (int64, int64, error) { func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
//Windows wmic sometime freeze and not respond. //Windows wmic sometime freeze and not respond.
//The safer way is to make a bypass mechanism //The safer way is to make a bypass mechanism
@ -263,7 +263,7 @@ func GetNetworkInterfaceStats() (int64, int64, error) {
result = <-callbackChan result = <-callbackChan
cmd = nil cmd = nil
if result.Err != nil { if result.Err != nil {
log.Println("Unable to extract NIC info from wmic: " + result.Err.Error()) n.logger.PrintAndLog("netstat", "Unable to extract NIC info from wmic", result.Err)
} }
return result.RX, result.TX, result.Err return result.RX, result.TX, result.Err
} else if runtime.GOOS == "linux" { } else if runtime.GOOS == "linux" {

View File

@ -9,13 +9,13 @@ import (
"time" "time"
) )
//Rewrite url based on proxy root // Rewrite url based on proxy root (default site)
func RewriteURL(rooturl string, requestURL string) (*url.URL, error) { func RewriteURL(rooturl string, requestURL string) (*url.URL, error) {
rewrittenURL := strings.TrimPrefix(requestURL, rooturl) rewrittenURL := strings.TrimPrefix(requestURL, rooturl)
return url.Parse(rewrittenURL) return url.Parse(rewrittenURL)
} }
//Check if the current platform support web.ssh function // Check if the current platform support web.ssh function
func IsWebSSHSupported() bool { func IsWebSSHSupported() bool {
//Check if the binary exists in system/gotty/ //Check if the binary exists in system/gotty/
binary := "gotty_" + runtime.GOOS + "_" + runtime.GOARCH binary := "gotty_" + runtime.GOOS + "_" + runtime.GOARCH
@ -34,7 +34,7 @@ func IsWebSSHSupported() bool {
return true return true
} }
//Check if a given domain and port is a valid ssh server // Check if a given domain and port is a valid ssh server
func IsSSHConnectable(ipOrDomain string, port int) bool { func IsSSHConnectable(ipOrDomain string, port int) bool {
timeout := time.Second * 3 timeout := time.Second * 3
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ipOrDomain, port), timeout) conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ipOrDomain, port), timeout)
@ -60,7 +60,7 @@ func IsSSHConnectable(ipOrDomain string, port int) bool {
return string(buf[:7]) == "SSH-2.0" return string(buf[:7]) == "SSH-2.0"
} }
//Check if the port is used by other process or application // Check if the port is used by other process or application
func isPortInUse(port int) bool { func isPortInUse(port int) bool {
address := fmt.Sprintf(":%d", port) address := fmt.Sprintf(":%d", port)
listener, err := net.Listen("tcp", address) listener, err := net.Listen("tcp", address)

View File

@ -1,9 +1,10 @@
package tcpprox package streamproxy
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
) )
@ -22,13 +23,13 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) {
return return
} }
portA, err := utils.PostPara(r, "porta") listenAddr, err := utils.PostPara(r, "listenAddr")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "first address cannot be empty") utils.SendErrorResponse(w, "first address cannot be empty")
return return
} }
portB, err := utils.PostPara(r, "portb") proxyAddr, err := utils.PostPara(r, "proxyAddr")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "second address cannot be empty") utils.SendErrorResponse(w, "second address cannot be empty")
return return
@ -44,27 +45,17 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) {
} }
} }
modeValue := ProxyMode_Transport useTCP, _ := utils.PostBool(r, "useTCP")
mode, err := utils.PostPara(r, "mode") useUDP, _ := utils.PostBool(r, "useUDP")
if err != nil || mode == "" {
utils.SendErrorResponse(w, "no mode given")
} else if mode == "listen" {
modeValue = ProxyMode_Listen
} else if mode == "transport" {
modeValue = ProxyMode_Transport
} else if mode == "starter" {
modeValue = ProxyMode_Starter
} else {
utils.SendErrorResponse(w, "invalid mode given. Only support listen / transport / starter")
}
//Create the target config //Create the target config
newConfigUUID := m.NewConfig(&ProxyRelayOptions{ newConfigUUID := m.NewConfig(&ProxyRelayOptions{
Name: name, Name: name,
PortA: portA, ListeningAddr: strings.TrimSpace(listenAddr),
PortB: portB, ProxyAddr: strings.TrimSpace(proxyAddr),
Timeout: timeout, Timeout: timeout,
Mode: modeValue, UseTCP: useTCP,
UseUDP: useUDP,
}) })
js, _ := json.Marshal(newConfigUUID) js, _ := json.Marshal(newConfigUUID)
@ -80,22 +71,10 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
} }
newName, _ := utils.PostPara(r, "name") newName, _ := utils.PostPara(r, "name")
newPortA, _ := utils.PostPara(r, "porta") listenAddr, _ := utils.PostPara(r, "listenAddr")
newPortB, _ := utils.PostPara(r, "portb") proxyAddr, _ := utils.PostPara(r, "proxyAddr")
newModeStr, _ := utils.PostPara(r, "mode") useTCP, _ := utils.PostBool(r, "useTCP")
newMode := -1 useUDP, _ := utils.PostBool(r, "useUDP")
if newModeStr != "" {
if newModeStr == "listen" {
newMode = 0
} else if newModeStr == "transport" {
newMode = 1
} else if newModeStr == "starter" {
newMode = 2
} else {
utils.SendErrorResponse(w, "invalid new mode value")
return
}
}
newTimeoutStr, _ := utils.PostPara(r, "timeout") newTimeoutStr, _ := utils.PostPara(r, "timeout")
newTimeout := -1 newTimeout := -1
@ -108,7 +87,7 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
} }
// Call the EditConfig method to modify the configuration // Call the EditConfig method to modify the configuration
err = m.EditConfig(configUUID, newName, newPortA, newPortB, newMode, newTimeout) err = m.EditConfig(configUUID, newName, listenAddr, proxyAddr, useTCP, useUDP, newTimeout)
if err != nil { if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return return
@ -158,6 +137,7 @@ func (m *Manager) HandleStopProxy(w http.ResponseWriter, r *http.Request) {
} }
if !targetProxyConfig.IsRunning() { if !targetProxyConfig.IsRunning() {
targetProxyConfig.Running = false
utils.SendErrorResponse(w, "target proxy service is not running") utils.SendErrorResponse(w, "target proxy service is not running")
return return
} }
@ -180,6 +160,7 @@ func (m *Manager) HandleRemoveProxy(w http.ResponseWriter, r *http.Request) {
} }
if targetProxyConfig.IsRunning() { if targetProxyConfig.IsRunning() {
targetProxyConfig.Running = false
utils.SendErrorResponse(w, "Service is running") utils.SendErrorResponse(w, "Service is running")
return return
} }
@ -209,25 +190,3 @@ func (m *Manager) HandleGetProxyStatus(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(targetConfig) js, _ := json.Marshal(targetConfig)
utils.SendJSONResponse(w, string(js)) utils.SendJSONResponse(w, string(js))
} }
func (m *Manager) HandleConfigValidate(w http.ResponseWriter, r *http.Request) {
uuid, err := utils.GetPara(r, "uuid")
if err != nil {
utils.SendErrorResponse(w, "invalid uuid given")
return
}
targetConfig, err := m.GetConfigByUUID(uuid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
err = targetConfig.ValidateConfigs()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}

View File

@ -0,0 +1,281 @@
package streamproxy
import (
"errors"
"log"
"net"
"sync"
"sync/atomic"
"time"
"github.com/google/uuid"
"imuslab.com/zoraxy/mod/database"
)
/*
TCP Proxy
Forward port from one port to another
Also accept active connection and passive
connection
*/
type ProxyRelayOptions struct {
Name string
ListeningAddr string
ProxyAddr string
Timeout int
UseTCP bool
UseUDP bool
}
type ProxyRelayConfig struct {
UUID string //A UUIDv4 representing this config
Name string //Name of the config
Running bool //Status, read only
AutoStart bool //If the service suppose to started automatically
ListeningAddress string //Listening Address, usually 127.0.0.1:port
ProxyTargetAddr string //Proxy target address
UseTCP bool //Enable TCP proxy
UseUDP bool //Enable UDP proxy
Timeout int //Timeout for connection in sec
tcpStopChan chan bool //Stop channel for TCP listener
udpStopChan chan bool //Stop channel for UDP listener
aTobAccumulatedByteTransfer atomic.Int64 //Accumulated byte transfer from A to B
bToaAccumulatedByteTransfer atomic.Int64 //Accumulated byte transfer from B to A
udpClientMap sync.Map //map storing the UDP client-server connections
parent *Manager `json:"-"`
}
type Options struct {
Database *database.Database
DefaultTimeout int
AccessControlHandler func(net.Conn) bool
}
type Manager struct {
//Config and stores
Options *Options
Configs []*ProxyRelayConfig
//Realtime Statistics
Connections int //currently connected connect counts
}
func NewStreamProxy(options *Options) *Manager {
options.Database.NewTable("tcprox")
//Load relay configs from db
previousRules := []*ProxyRelayConfig{}
if options.Database.KeyExists("tcprox", "rules") {
options.Database.Read("tcprox", "rules", &previousRules)
}
//Check if the AccessControlHandler is empty. If yes, set it to always allow access
if options.AccessControlHandler == nil {
options.AccessControlHandler = func(conn net.Conn) bool {
//Always allow access
return true
}
}
//Create a new proxy manager for TCP
thisManager := Manager{
Options: options,
Connections: 0,
}
//Inject manager into the rules
for _, rule := range previousRules {
rule.parent = &thisManager
if rule.Running {
//This was previously running. Start it again
log.Println("[Stream Proxy] Resuming stream proxy rule " + rule.Name)
rule.Start()
}
}
thisManager.Configs = previousRules
return &thisManager
}
func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
//Generate two zero value for atomic int64
aAcc := atomic.Int64{}
bAcc := atomic.Int64{}
aAcc.Store(0)
bAcc.Store(0)
//Generate a new config from options
configUUID := uuid.New().String()
thisConfig := ProxyRelayConfig{
UUID: configUUID,
Name: config.Name,
ListeningAddress: config.ListeningAddr,
ProxyTargetAddr: config.ProxyAddr,
UseTCP: config.UseTCP,
UseUDP: config.UseUDP,
Timeout: config.Timeout,
tcpStopChan: nil,
udpStopChan: nil,
aTobAccumulatedByteTransfer: aAcc,
bToaAccumulatedByteTransfer: bAcc,
udpClientMap: sync.Map{},
parent: m,
}
m.Configs = append(m.Configs, &thisConfig)
m.SaveConfigToDatabase()
return configUUID
}
func (m *Manager) GetConfigByUUID(configUUID string) (*ProxyRelayConfig, error) {
// Find and return the config with the specified UUID
for _, config := range m.Configs {
if config.UUID == configUUID {
return config, nil
}
}
return nil, errors.New("config not found")
}
// Edit the config based on config UUID, leave empty for unchange fields
func (m *Manager) EditConfig(configUUID string, newName string, newListeningAddr string, newProxyAddr string, useTCP bool, useUDP bool, newTimeout int) error {
// Find the config with the specified UUID
foundConfig, err := m.GetConfigByUUID(configUUID)
if err != nil {
return err
}
// Validate and update the fields
if newName != "" {
foundConfig.Name = newName
}
if newListeningAddr != "" {
foundConfig.ListeningAddress = newListeningAddr
}
if newProxyAddr != "" {
foundConfig.ProxyTargetAddr = newProxyAddr
}
foundConfig.UseTCP = useTCP
foundConfig.UseUDP = useUDP
if newTimeout != -1 {
if newTimeout < 0 {
return errors.New("invalid timeout value given")
}
foundConfig.Timeout = newTimeout
}
m.SaveConfigToDatabase()
//Check if config is running. If yes, restart it
if foundConfig.IsRunning() {
foundConfig.Restart()
}
return nil
}
func (m *Manager) RemoveConfig(configUUID string) error {
// Find and remove the config with the specified UUID
for i, config := range m.Configs {
if config.UUID == configUUID {
m.Configs = append(m.Configs[:i], m.Configs[i+1:]...)
m.SaveConfigToDatabase()
return nil
}
}
return errors.New("config not found")
}
func (m *Manager) SaveConfigToDatabase() {
m.Options.Database.Write("tcprox", "rules", m.Configs)
}
/*
Config Functions
*/
// Start a proxy if stopped
func (c *ProxyRelayConfig) Start() error {
if c.IsRunning() {
c.Running = true
return errors.New("proxy already running")
}
// Create a stopChan to control the loop
tcpStopChan := make(chan bool)
udpStopChan := make(chan bool)
//Start the proxy service
if c.UseUDP {
c.udpStopChan = udpStopChan
go func() {
err := c.ForwardUDP(c.ListeningAddress, c.ProxyTargetAddr, udpStopChan)
if err != nil {
if !c.UseTCP {
c.Running = false
c.parent.SaveConfigToDatabase()
}
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
}
}()
}
if c.UseTCP {
c.tcpStopChan = tcpStopChan
go func() {
//Default to transport mode
err := c.Port2host(c.ListeningAddress, c.ProxyTargetAddr, tcpStopChan)
if err != nil {
c.Running = false
c.parent.SaveConfigToDatabase()
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
}
}()
}
//Successfully spawned off the proxy routine
c.Running = true
c.parent.SaveConfigToDatabase()
return nil
}
// Return if a proxy config is running
func (c *ProxyRelayConfig) IsRunning() bool {
return c.tcpStopChan != nil || c.udpStopChan != nil
}
// Restart a proxy config
func (c *ProxyRelayConfig) Restart() {
if c.IsRunning() {
c.Stop()
}
time.Sleep(300 * time.Millisecond)
c.Start()
}
// Stop a running proxy if running
func (c *ProxyRelayConfig) Stop() {
log.Println("[STREAM PROXY] Stopping Stream Proxy " + c.Name)
if c.udpStopChan != nil {
log.Println("[STREAM PROXY] Stopping UDP for " + c.Name)
c.udpStopChan <- true
c.udpStopChan = nil
}
if c.tcpStopChan != nil {
log.Println("[STREAM PROXY] Stopping TCP for " + c.Name)
c.tcpStopChan <- true
c.tcpStopChan = nil
}
log.Println("[STREAM PROXY] Stopped Stream Proxy " + c.Name)
c.Running = false
//Update the running status
c.parent.SaveConfigToDatabase()
}

View File

@ -1,10 +1,10 @@
package tcpprox_test package streamproxy_test
import ( import (
"testing" "testing"
"time" "time"
"imuslab.com/zoraxy/mod/tcpprox" "imuslab.com/zoraxy/mod/streamproxy"
) )
func TestPort2Port(t *testing.T) { func TestPort2Port(t *testing.T) {
@ -12,7 +12,7 @@ func TestPort2Port(t *testing.T) {
stopChan := make(chan bool) stopChan := make(chan bool)
// Create a ProxyRelayConfig with dummy values // Create a ProxyRelayConfig with dummy values
config := &tcpprox.ProxyRelayConfig{ config := &streamproxy.ProxyRelayConfig{
Timeout: 1, Timeout: 1,
} }
@ -36,7 +36,7 @@ func TestPort2Port(t *testing.T) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
// If the goroutine is still running, it means it did not stop as expected // If the goroutine is still running, it means it did not stop as expected
if config.Running { if config.IsRunning() {
t.Errorf("port2port did not stop as expected") t.Errorf("port2port did not stop as expected")
} }

View File

@ -0,0 +1,146 @@
package streamproxy
import (
"errors"
"io"
"log"
"net"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
func isValidIP(ip string) bool {
parsedIP := net.ParseIP(ip)
return parsedIP != nil
}
func isValidPort(port string) bool {
portInt, err := strconv.Atoi(port)
if err != nil {
return false
}
if portInt < 1 || portInt > 65535 {
return false
}
return true
}
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup, accumulator *atomic.Int64) {
n, err := io.Copy(conn1, conn2)
if err != nil {
return
}
accumulator.Add(n) //Add to accumulator
conn1.Close()
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
//conn2.Close()
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
wg.Done()
}
func forward(conn1 net.Conn, conn2 net.Conn, aTob *atomic.Int64, bToa *atomic.Int64) {
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
var wg sync.WaitGroup
// wait tow goroutines
wg.Add(2)
go connCopy(conn1, conn2, &wg, aTob)
go connCopy(conn2, conn1, &wg, bToa)
//blocking when the wg is locked
wg.Wait()
}
func (c *ProxyRelayConfig) accept(listener net.Listener) (net.Conn, error) {
conn, err := listener.Accept()
if err != nil {
return nil, err
}
//Check if connection in blacklist or whitelist
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
if !c.parent.Options.AccessControlHandler(conn) {
time.Sleep(300 * time.Millisecond)
conn.Close()
log.Println("[x]", "Connection from "+addr.IP.String()+" rejected by access control policy")
return nil, errors.New("Connection from " + addr.IP.String() + " rejected by access control policy")
}
}
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
return conn, err
}
func startListener(address string) (net.Listener, error) {
log.Println("[+]", "try to start server on:["+address+"]")
server, err := net.Listen("tcp", address)
if err != nil {
return nil, errors.New("listen address [" + address + "] faild")
}
log.Println("[√]", "start listen at address:["+address+"]")
return server, nil
}
/*
Forwarder Functions
*/
/*
portA -> server
server -> portB
*/
func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, stopChan chan bool) error {
listenerStartingAddr := allowPort
if isValidPort(allowPort) {
//number only, e.g. 8080
listenerStartingAddr = "0.0.0.0:" + allowPort
} else if strings.HasPrefix(allowPort, ":") && isValidPort(allowPort[1:]) {
//port number starting with :, e.g. :8080
listenerStartingAddr = "0.0.0.0" + allowPort
}
server, err := startListener(listenerStartingAddr)
if err != nil {
return err
}
targetAddress = strings.TrimSpace(targetAddress)
//Start stop handler
go func() {
<-stopChan
log.Println("[x]", "Received stop signal. Exiting Port to Host forwarder")
server.Close()
}()
//Start blocking loop for accepting connections
for {
conn, err := c.accept(server)
if err != nil {
if errors.Is(err, net.ErrClosed) {
//Terminate by stop chan. Exit listener loop
return nil
}
//Connection error. Retry
continue
}
go func(targetAddress string) {
log.Println("[+]", "start connect host:["+targetAddress+"]")
target, err := net.Dial("tcp", targetAddress)
if err != nil {
// temporarily unavailable, don't use fatal.
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", c.Timeout, "seconds. ")
conn.Close()
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
time.Sleep(time.Duration(c.Timeout) * time.Second)
return
}
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
}(targetAddress)
}
}

View File

@ -0,0 +1,157 @@
package streamproxy
import (
"errors"
"log"
"net"
"strings"
"time"
)
/*
UDP Proxy Module
*/
// Information maintained for each client/server connection
type udpClientServerConn struct {
ClientAddr *net.UDPAddr // Address of the client
ServerConn *net.UDPConn // UDP connection to server
}
// Generate a new connection by opening a UDP connection to the server
func createNewUDPConn(srvAddr, cliAddr *net.UDPAddr) *udpClientServerConn {
conn := new(udpClientServerConn)
conn.ClientAddr = cliAddr
srvudp, err := net.DialUDP("udp", nil, srvAddr)
if err != nil {
return nil
}
conn.ServerConn = srvudp
return conn
}
// Start listener, return inbound lisener and proxy target UDP address
func initUDPConnections(listenAddr string, targetAddress string) (*net.UDPConn, *net.UDPAddr, error) {
// Set up Proxy
saddr, err := net.ResolveUDPAddr("udp", listenAddr)
if err != nil {
return nil, nil, err
}
inboundConn, err := net.ListenUDP("udp", saddr)
if err != nil {
return nil, nil, err
}
log.Println("[UDP] Proxy listening on " + listenAddr)
outboundConn, err := net.ResolveUDPAddr("udp", targetAddress)
if err != nil {
return nil, nil, err
}
return inboundConn, outboundConn, nil
}
// Go routine which manages connection from server to single client
func (c *ProxyRelayConfig) RunUDPConnectionRelay(conn *udpClientServerConn, lisenter *net.UDPConn) {
var buffer [1500]byte
for {
// Read from server
n, err := conn.ServerConn.Read(buffer[0:])
if err != nil {
if errors.Is(err, net.ErrClosed) {
return
}
continue
}
// Relay it to client
_, err = lisenter.WriteToUDP(buffer[0:n], conn.ClientAddr)
if err != nil {
continue
}
}
}
// Close all connections that waiting for read from server
func (c *ProxyRelayConfig) CloseAllUDPConnections() {
c.udpClientMap.Range(func(clientAddr, clientServerConn interface{}) bool {
conn := clientServerConn.(*udpClientServerConn)
conn.ServerConn.Close()
return true
})
}
func (c *ProxyRelayConfig) ForwardUDP(address1, address2 string, stopChan chan bool) error {
//By default the incoming listen Address is int
//We need to add the loopback address into it
if isValidPort(address1) {
//Port number only. Missing the : in front
address1 = ":" + address1
}
if strings.HasPrefix(address1, ":") {
//Prepend 127.0.0.1 to the address
address1 = "127.0.0.1" + address1
}
lisener, targetAddr, err := initUDPConnections(address1, address2)
if err != nil {
return err
}
go func() {
//Stop channel receiver
for {
select {
case <-stopChan:
//Stop signal received
//Stop server -> client forwarder
c.CloseAllUDPConnections()
//Stop client -> server forwarder
//Force close, will terminate ReadFromUDP for inbound listener
lisener.Close()
return
default:
time.Sleep(100 * time.Millisecond)
}
}
}()
var buffer [1500]byte
for {
n, cliaddr, err := lisener.ReadFromUDP(buffer[0:])
if err != nil {
if errors.Is(err, net.ErrClosed) {
//Proxy stopped
return nil
}
continue
}
c.aTobAccumulatedByteTransfer.Add(int64(n))
saddr := cliaddr.String()
rawConn, found := c.udpClientMap.Load(saddr)
var conn *udpClientServerConn
if !found {
conn = createNewUDPConn(targetAddr, cliaddr)
if conn == nil {
continue
}
c.udpClientMap.Store(saddr, conn)
log.Println("[UDP] Created new connection for client " + saddr)
// Fire up routine to manage new connection
go c.RunUDPConnectionRelay(conn, lisener)
} else {
log.Println("[UDP] Found connection for client " + saddr)
conn = rawConn.(*udpClientServerConn)
}
// Relay to server
_, err = conn.ServerConn.Write(buffer[0:n])
if err != nil {
continue
}
}
}

View File

@ -1,341 +0,0 @@
package tcpprox
import (
"errors"
"io"
"log"
"net"
"strconv"
"sync"
"time"
)
func isValidIP(ip string) bool {
parsedIP := net.ParseIP(ip)
return parsedIP != nil
}
func isValidPort(port string) bool {
portInt, err := strconv.Atoi(port)
if err != nil {
return false
}
if portInt < 1 || portInt > 65535 {
return false
}
return true
}
func isReachable(target string) bool {
timeout := time.Duration(2 * time.Second) // Set the timeout value as per your requirement
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
return false
}
defer conn.Close()
return true
}
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup, accumulator *int64) {
io.Copy(conn1, conn2)
conn1.Close()
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
//conn2.Close()
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
wg.Done()
}
func forward(conn1 net.Conn, conn2 net.Conn, aTob *int64, bToa *int64) {
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
var wg sync.WaitGroup
// wait tow goroutines
wg.Add(2)
go connCopy(conn1, conn2, &wg, aTob)
go connCopy(conn2, conn1, &wg, bToa)
//blocking when the wg is locked
wg.Wait()
}
func (c *ProxyRelayConfig) accept(listener net.Listener) (net.Conn, error) {
conn, err := listener.Accept()
if err != nil {
return nil, err
}
//Check if connection in blacklist or whitelist
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
if !c.parent.Options.AccessControlHandler(conn) {
time.Sleep(300 * time.Millisecond)
conn.Close()
log.Println("[x]", "Connection from "+addr.IP.String()+" rejected by access control policy")
return nil, errors.New("Connection from " + addr.IP.String() + " rejected by access control policy")
}
}
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
return conn, err
}
func startListener(address string) (net.Listener, error) {
log.Println("[+]", "try to start server on:["+address+"]")
server, err := net.Listen("tcp", address)
if err != nil {
return nil, errors.New("listen address [" + address + "] faild")
}
log.Println("[√]", "start listen at address:["+address+"]")
return server, nil
}
/*
Config Functions
*/
// Config validator
func (c *ProxyRelayConfig) ValidateConfigs() error {
if c.Mode == ProxyMode_Transport {
//Port2Host: PortA int, PortB string
if !isValidPort(c.PortA) {
return errors.New("first address must be a valid port number")
}
if !isReachable(c.PortB) {
return errors.New("second address is unreachable")
}
return nil
} else if c.Mode == ProxyMode_Listen {
//Port2Port: Both port are port number
if !isValidPort(c.PortA) {
return errors.New("first address is not a valid port number")
}
if !isValidPort(c.PortB) {
return errors.New("second address is not a valid port number")
}
return nil
} else if c.Mode == ProxyMode_Starter {
//Host2Host: Both have to be hosts
if !isReachable(c.PortA) {
return errors.New("first address is unreachable")
}
if !isReachable(c.PortB) {
return errors.New("second address is unreachable")
}
return nil
} else {
return errors.New("invalid mode given")
}
}
// Start a proxy if stopped
func (c *ProxyRelayConfig) Start() error {
if c.Running {
return errors.New("proxy already running")
}
// Create a stopChan to control the loop
stopChan := make(chan bool)
c.stopChan = stopChan
//Validate configs
err := c.ValidateConfigs()
if err != nil {
return err
}
//Start the proxy service
go func() {
c.Running = true
if c.Mode == ProxyMode_Transport {
err = c.Port2host(c.PortA, c.PortB, stopChan)
} else if c.Mode == ProxyMode_Listen {
err = c.Port2port(c.PortA, c.PortB, stopChan)
} else if c.Mode == ProxyMode_Starter {
err = c.Host2host(c.PortA, c.PortB, stopChan)
}
if err != nil {
c.Running = false
log.Println("Error starting proxy service " + c.Name + "(" + c.UUID + "): " + err.Error())
}
}()
//Successfully spawned off the proxy routine
return nil
}
// Stop a running proxy if running
func (c *ProxyRelayConfig) IsRunning() bool {
return c.Running || c.stopChan != nil
}
// Stop a running proxy if running
func (c *ProxyRelayConfig) Stop() {
if c.Running || c.stopChan != nil {
c.stopChan <- true
time.Sleep(300 * time.Millisecond)
c.stopChan = nil
c.Running = false
}
}
/*
Forwarder Functions
*/
/*
portA -> server
portB -> server
*/
func (c *ProxyRelayConfig) Port2port(port1 string, port2 string, stopChan chan bool) error {
//Trim the Prefix of : if exists
listen1, err := startListener("0.0.0.0:" + port1)
if err != nil {
return err
}
listen2, err := startListener("0.0.0.0:" + port2)
if err != nil {
return err
}
log.Println("[√]", "listen port:", port1, "and", port2, "success. waiting for client...")
c.Running = true
go func() {
<-stopChan
log.Println("[x]", "Received stop signal. Exiting Port to Port forwarder")
c.Running = false
listen1.Close()
listen2.Close()
}()
for {
conn1, err := c.accept(listen1)
if err != nil {
if !c.Running {
return nil
}
continue
}
conn2, err := c.accept(listen2)
if err != nil {
if !c.Running {
return nil
}
continue
}
if conn1 == nil || conn2 == nil {
log.Println("[x]", "accept client faild. retry in ", c.Timeout, " seconds. ")
time.Sleep(time.Duration(c.Timeout) * time.Second)
continue
}
go forward(conn1, conn2, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
}
}
/*
portA -> server
server -> portB
*/
func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, stopChan chan bool) error {
server, err := startListener("0.0.0.0:" + allowPort)
if err != nil {
return err
}
//Start stop handler
go func() {
<-stopChan
log.Println("[x]", "Received stop signal. Exiting Port to Host forwarder")
c.Running = false
server.Close()
}()
//Start blocking loop for accepting connections
for {
conn, err := c.accept(server)
if conn == nil || err != nil {
if !c.Running {
//Terminate by stop chan. Exit listener loop
return nil
}
//Connection error. Retry
continue
}
go func(targetAddress string) {
log.Println("[+]", "start connect host:["+targetAddress+"]")
target, err := net.Dial("tcp", targetAddress)
if err != nil {
// temporarily unavailable, don't use fatal.
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", c.Timeout, "seconds. ")
conn.Close()
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
time.Sleep(time.Duration(c.Timeout) * time.Second)
return
}
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
}(targetAddress)
}
}
/*
server -> portA
server -> portB
*/
func (c *ProxyRelayConfig) Host2host(address1, address2 string, stopChan chan bool) error {
c.Running = true
go func() {
<-stopChan
log.Println("[x]", "Received stop signal. Exiting Host to Host forwarder")
c.Running = false
}()
for c.Running {
log.Println("[+]", "try to connect host:["+address1+"] and ["+address2+"]")
var host1, host2 net.Conn
var err error
for {
d := net.Dialer{Timeout: time.Duration(c.Timeout)}
host1, err = d.Dial("tcp", address1)
if err == nil {
log.Println("[→]", "connect ["+address1+"] success.")
break
} else {
log.Println("[x]", "connect target address ["+address1+"] faild. retry in ", c.Timeout, " seconds. ")
time.Sleep(time.Duration(c.Timeout) * time.Second)
}
if !c.Running {
return nil
}
}
for {
d := net.Dialer{Timeout: time.Duration(c.Timeout)}
host2, err = d.Dial("tcp", address2)
if err == nil {
log.Println("[→]", "connect ["+address2+"] success.")
break
} else {
log.Println("[x]", "connect target address ["+address2+"] faild. retry in ", c.Timeout, " seconds. ")
time.Sleep(time.Duration(c.Timeout) * time.Second)
}
if !c.Running {
return nil
}
}
go forward(host1, host2, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
}
return nil
}

View File

@ -1,289 +0,0 @@
package tcpprox
import (
"fmt"
"io"
"log"
"net"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
const timeout = 5
func main() {
//log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
log.SetFlags(log.Ldate | log.Lmicroseconds)
printWelcome()
args := os.Args
argc := len(os.Args)
if argc <= 2 {
printHelp()
os.Exit(0)
}
//TODO:support UDP protocol
/*var logFileError error
if argc > 5 && args[4] == "-log" {
logPath := args[5] + "/" + time.Now().Format("2006_01_02_15_04_05") // "2006-01-02 15:04:05"
logPath += args[1] + "-" + strings.Replace(args[2], ":", "_", -1) + "-" + args[3] + ".log"
logPath = strings.Replace(logPath, `\`, "/", -1)
logPath = strings.Replace(logPath, "//", "/", -1)
logFile, logFileError = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE, 0666)
if logFileError != nil {
log.Fatalln("[x]", "log file path error.", logFileError.Error())
}
log.Println("[√]", "open test log file success. path:", logPath)
}*/
switch args[1] {
case "-listen":
if argc < 3 {
log.Fatalln(`-listen need two arguments, like "nb -listen 1997 2017".`)
}
port1 := checkPort(args[2])
port2 := checkPort(args[3])
log.Println("[√]", "start to listen port:", port1, "and port:", port2)
port2port(port1, port2)
break
case "-tran":
if argc < 3 {
log.Fatalln(`-tran need two arguments, like "nb -tran 1997 192.168.1.2:3389".`)
}
port := checkPort(args[2])
var remoteAddress string
if checkIp(args[3]) {
remoteAddress = args[3]
}
split := strings.SplitN(remoteAddress, ":", 2)
log.Println("[√]", "start to transmit address:", remoteAddress, "to address:", split[0]+":"+port)
port2host(port, remoteAddress)
break
case "-slave":
if argc < 3 {
log.Fatalln(`-slave need two arguments, like "nb -slave 127.0.0.1:3389 8.8.8.8:1997".`)
}
var address1, address2 string
checkIp(args[2])
if checkIp(args[2]) {
address1 = args[2]
}
checkIp(args[3])
if checkIp(args[3]) {
address2 = args[3]
}
log.Println("[√]", "start to connect address:", address1, "and address:", address2)
host2host(address1, address2)
break
default:
printHelp()
}
}
func printWelcome() {
fmt.Println("+----------------------------------------------------------------+")
fmt.Println("| Welcome to use NATBypass Ver1.0.0 . |")
fmt.Println("| Code by cw1997 at 2017-10-19 03:59:51 |")
fmt.Println("| If you have some problem when you use the tool, |")
fmt.Println("| please submit issue at : https://github.com/cw1997/NATBypass . |")
fmt.Println("+----------------------------------------------------------------+")
fmt.Println()
// sleep one second because the fmt is not thread-safety.
// if not to do this, fmt.Print will print after the log.Print.
time.Sleep(time.Second)
}
func printHelp() {
fmt.Println(`usage: "-listen port1 port2" example: "nb -listen 1997 2017" `)
fmt.Println(` "-tran port1 ip:port2" example: "nb -tran 1997 192.168.1.2:3389" `)
fmt.Println(` "-slave ip1:port1 ip2:port2" example: "nb -slave 127.0.0.1:3389 8.8.8.8:1997" `)
fmt.Println(`============================================================`)
fmt.Println(`optional argument: "-log logpath" . example: "nb -listen 1997 2017 -log d:/nb" `)
fmt.Println(`log filename format: Y_m_d_H_i_s-agrs1-args2-args3.log`)
fmt.Println(`============================================================`)
fmt.Println(`if you want more help, please read "README.md". `)
}
func checkPort(port string) string {
PortNum, err := strconv.Atoi(port)
if err != nil {
log.Fatalln("[x]", "port should be a number")
}
if PortNum < 1 || PortNum > 65535 {
log.Fatalln("[x]", "port should be a number and the range is [1,65536)")
}
return port
}
func checkIp(address string) bool {
ipAndPort := strings.Split(address, ":")
if len(ipAndPort) != 2 {
log.Fatalln("[x]", "address error. should be a string like [ip:port]. ")
}
ip := ipAndPort[0]
port := ipAndPort[1]
checkPort(port)
pattern := `^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$`
ok, err := regexp.MatchString(pattern, ip)
if err != nil || !ok {
log.Fatalln("[x]", "ip error. ")
}
return ok
}
func port2port(port1 string, port2 string) {
listen1 := start_server("0.0.0.0:" + port1)
listen2 := start_server("0.0.0.0:" + port2)
log.Println("[√]", "listen port:", port1, "and", port2, "success. waiting for client...")
for {
conn1 := accept(listen1)
conn2 := accept(listen2)
if conn1 == nil || conn2 == nil {
log.Println("[x]", "accept client faild. retry in ", timeout, " seconds. ")
time.Sleep(timeout * time.Second)
continue
}
forward(conn1, conn2)
}
}
func port2host(allowPort string, targetAddress string) {
server := start_server("0.0.0.0:" + allowPort)
for {
conn := accept(server)
if conn == nil {
continue
}
//println(targetAddress)
go func(targetAddress string) {
log.Println("[+]", "start connect host:["+targetAddress+"]")
target, err := net.Dial("tcp", targetAddress)
if err != nil {
// temporarily unavailable, don't use fatal.
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", timeout, "seconds. ")
conn.Close()
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
time.Sleep(timeout * time.Second)
return
}
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
forward(target, conn)
}(targetAddress)
}
}
func host2host(address1, address2 string) {
for {
log.Println("[+]", "try to connect host:["+address1+"] and ["+address2+"]")
var host1, host2 net.Conn
var err error
for {
host1, err = net.Dial("tcp", address1)
if err == nil {
log.Println("[→]", "connect ["+address1+"] success.")
break
} else {
log.Println("[x]", "connect target address ["+address1+"] faild. retry in ", timeout, " seconds. ")
time.Sleep(timeout * time.Second)
}
}
for {
host2, err = net.Dial("tcp", address2)
if err == nil {
log.Println("[→]", "connect ["+address2+"] success.")
break
} else {
log.Println("[x]", "connect target address ["+address2+"] faild. retry in ", timeout, " seconds. ")
time.Sleep(timeout * time.Second)
}
}
forward(host1, host2)
}
}
func start_server(address string) net.Listener {
log.Println("[+]", "try to start server on:["+address+"]")
server, err := net.Listen("tcp", address)
if err != nil {
log.Fatalln("[x]", "listen address ["+address+"] faild.")
}
log.Println("[√]", "start listen at address:["+address+"]")
return server
/*defer server.Close()
for {
conn, err := server.Accept()
log.Println("accept a new client. remote address:[" + conn.RemoteAddr().String() +
"], local address:[" + conn.LocalAddr().String() + "]")
if err != nil {
log.Println("accept a new client faild.", err.Error())
continue
}
//go recvConnMsg(conn)
}*/
}
func accept(listener net.Listener) net.Conn {
conn, err := listener.Accept()
if err != nil {
log.Println("[x]", "accept connect ["+conn.RemoteAddr().String()+"] faild.", err.Error())
return nil
}
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
return conn
}
func forward(conn1 net.Conn, conn2 net.Conn) {
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
var wg sync.WaitGroup
// wait tow goroutines
wg.Add(2)
go connCopy(conn1, conn2, &wg)
go connCopy(conn2, conn1, &wg)
//blocking when the wg is locked
wg.Wait()
}
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup) {
//TODO:log, record the data from conn1 and conn2.
logFile := openLog(conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
if logFile != nil {
w := io.MultiWriter(conn1, logFile)
io.Copy(w, conn2)
} else {
io.Copy(conn1, conn2)
}
conn1.Close()
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
//conn2.Close()
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
wg.Done()
}
func openLog(address1, address2, address3, address4 string) *os.File {
args := os.Args
argc := len(os.Args)
var logFileError error
var logFile *os.File
if argc > 5 && args[4] == "-log" {
address1 = strings.Replace(address1, ":", "_", -1)
address2 = strings.Replace(address2, ":", "_", -1)
address3 = strings.Replace(address3, ":", "_", -1)
address4 = strings.Replace(address4, ":", "_", -1)
timeStr := time.Now().Format("2006_01_02_15_04_05") // "2006-01-02 15:04:05"
logPath := args[5] + "/" + timeStr + args[1] + "-" + address1 + "_" + address2 + "-" + address3 + "_" + address4 + ".log"
logPath = strings.Replace(logPath, `\`, "/", -1)
logPath = strings.Replace(logPath, "//", "/", -1)
logFile, logFileError = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE, 0666)
if logFileError != nil {
log.Fatalln("[x]", "log file path error.", logFileError.Error())
}
log.Println("[√]", "open test log file success. path:", logPath)
}
return logFile
}

View File

@ -1,185 +0,0 @@
package tcpprox
import (
"errors"
"net"
"github.com/google/uuid"
"imuslab.com/zoraxy/mod/database"
)
/*
TCP Proxy
Forward port from one port to another
Also accept active connection and passive
connection
*/
const (
ProxyMode_Listen = 0
ProxyMode_Transport = 1
ProxyMode_Starter = 2
)
type ProxyRelayOptions struct {
Name string
PortA string
PortB string
Timeout int
Mode int
}
type ProxyRelayConfig struct {
UUID string //A UUIDv4 representing this config
Name string //Name of the config
Running bool //If the service is running
PortA string //Ports A (config depends on mode)
PortB string //Ports B (config depends on mode)
Mode int //Operation Mode
Timeout int //Timeout for connection in sec
stopChan chan bool //Stop channel to stop the listener
aTobAccumulatedByteTransfer int64 //Accumulated byte transfer from A to B
bToaAccumulatedByteTransfer int64 //Accumulated byte transfer from B to A
parent *Manager `json:"-"`
}
type Options struct {
Database *database.Database
DefaultTimeout int
AccessControlHandler func(net.Conn) bool
}
type Manager struct {
//Config and stores
Options *Options
Configs []*ProxyRelayConfig
//Realtime Statistics
Connections int //currently connected connect counts
}
func NewTCProxy(options *Options) *Manager {
options.Database.NewTable("tcprox")
//Load relay configs from db
previousRules := []*ProxyRelayConfig{}
if options.Database.KeyExists("tcprox", "rules") {
options.Database.Read("tcprox", "rules", &previousRules)
}
//Check if the AccessControlHandler is empty. If yes, set it to always allow access
if options.AccessControlHandler == nil {
options.AccessControlHandler = func(conn net.Conn) bool {
//Always allow access
return true
}
}
//Create a new proxy manager for TCP
thisManager := Manager{
Options: options,
Connections: 0,
}
//Inject manager into the rules
for _, rule := range previousRules {
rule.parent = &thisManager
}
thisManager.Configs = previousRules
return &thisManager
}
func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
//Generate a new config from options
configUUID := uuid.New().String()
thisConfig := ProxyRelayConfig{
UUID: configUUID,
Name: config.Name,
Running: false,
PortA: config.PortA,
PortB: config.PortB,
Mode: config.Mode,
Timeout: config.Timeout,
stopChan: nil,
aTobAccumulatedByteTransfer: 0,
bToaAccumulatedByteTransfer: 0,
parent: m,
}
m.Configs = append(m.Configs, &thisConfig)
m.SaveConfigToDatabase()
return configUUID
}
func (m *Manager) GetConfigByUUID(configUUID string) (*ProxyRelayConfig, error) {
// Find and return the config with the specified UUID
for _, config := range m.Configs {
if config.UUID == configUUID {
return config, nil
}
}
return nil, errors.New("config not found")
}
// Edit the config based on config UUID, leave empty for unchange fields
func (m *Manager) EditConfig(configUUID string, newName string, newPortA string, newPortB string, newMode int, newTimeout int) error {
// Find the config with the specified UUID
foundConfig, err := m.GetConfigByUUID(configUUID)
if err != nil {
return err
}
// Validate and update the fields
if newName != "" {
foundConfig.Name = newName
}
if newPortA != "" {
foundConfig.PortA = newPortA
}
if newPortB != "" {
foundConfig.PortB = newPortB
}
if newMode != -1 {
if newMode > 2 || newMode < 0 {
return errors.New("invalid mode given")
}
foundConfig.Mode = newMode
}
if newTimeout != -1 {
if newTimeout < 0 {
return errors.New("invalid timeout value given")
}
foundConfig.Timeout = newTimeout
}
/*
err = foundConfig.ValidateConfigs()
if err != nil {
return err
}
*/
m.SaveConfigToDatabase()
return nil
}
func (m *Manager) RemoveConfig(configUUID string) error {
// Find and remove the config with the specified UUID
for i, config := range m.Configs {
if config.UUID == configUUID {
m.Configs = append(m.Configs[:i], m.Configs[i+1:]...)
m.SaveConfigToDatabase()
return nil
}
}
return errors.New("config not found")
}
func (m *Manager) SaveConfigToDatabase() {
m.Options.Database.Write("tcprox", "rules", m.Configs)
}

View File

@ -11,6 +11,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
) )
@ -21,15 +22,16 @@ type CertCache struct {
} }
type Manager struct { type Manager struct {
CertStore string //Path where all the certs are stored CertStore string //Path where all the certs are stored
LoadedCerts []*CertCache //A list of loaded certs LoadedCerts []*CertCache //A list of loaded certs
Logger *logger.Logger //System wide logger for debug mesage
verbal bool verbal bool
} }
//go:embed localhost.pem localhost.key //go:embed localhost.pem localhost.key
var buildinCertStore embed.FS var buildinCertStore embed.FS
func NewManager(certStore string, verbal bool) (*Manager, error) { func NewManager(certStore string, verbal bool, logger *logger.Logger) (*Manager, error) {
if !utils.FileExists(certStore) { if !utils.FileExists(certStore) {
os.MkdirAll(certStore, 0775) os.MkdirAll(certStore, 0775)
} }
@ -52,6 +54,7 @@ func NewManager(certStore string, verbal bool) (*Manager, error) {
CertStore: certStore, CertStore: certStore,
LoadedCerts: []*CertCache{}, LoadedCerts: []*CertCache{},
verbal: verbal, verbal: verbal,
Logger: logger,
} }
err := thisManager.UpdateLoadedCertList() err := thisManager.UpdateLoadedCertList()
@ -78,7 +81,7 @@ func (m *Manager) UpdateLoadedCertList() error {
priKey := filepath.Join(m.CertStore, certname+".key") priKey := filepath.Join(m.CertStore, certname+".key")
certificate, err := tls.LoadX509KeyPair(pubKey, priKey) certificate, err := tls.LoadX509KeyPair(pubKey, priKey)
if err != nil { if err != nil {
log.Println("Certificate loaded failed: " + certname) m.Logger.PrintAndLog("tls-router", "Certificate load failed: "+certname, err)
continue continue
} }
@ -86,6 +89,7 @@ func (m *Manager) UpdateLoadedCertList() error {
loadedCert, err := x509.ParseCertificate(thisCert) loadedCert, err := x509.ParseCertificate(thisCert)
if err != nil { if err != nil {
//Error pasring cert, skip this byte segment //Error pasring cert, skip this byte segment
m.Logger.PrintAndLog("tls-router", "Certificate parse failed: "+certname, err)
continue continue
} }
@ -171,37 +175,10 @@ func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, err
pubKey, priKey = m.GetCertByX509CNHostname(helloInfo.ServerName) pubKey, priKey = m.GetCertByX509CNHostname(helloInfo.ServerName)
} else { } else {
//Fallback to legacy method of matching certificates //Fallback to legacy method of matching certificates
/*
domainCerts, _ := m.ListCertDomains()
cloestDomainCert := matchClosestDomainCertificate(helloInfo.ServerName, domainCerts)
if cloestDomainCert != "" {
//There is a matching parent domain for this subdomain. Use this instead.
pubKey = filepath.Join(m.CertStore, cloestDomainCert+".pem")
priKey = filepath.Join(m.CertStore, cloestDomainCert+".key")
} else if m.DefaultCertExists() {
//Use default.pem and default.key
pubKey = filepath.Join(m.CertStore, "default.pem")
priKey = filepath.Join(m.CertStore, "default.key")
if m.verbal {
log.Println("No matching certificate found. Serving with default")
}
} else {
if m.verbal {
log.Println("Matching certificate not found. Serving with build-in certificate. Requesting server name: ", helloInfo.ServerName)
}
}*/
if m.DefaultCertExists() { if m.DefaultCertExists() {
//Use default.pem and default.key //Use default.pem and default.key
pubKey = filepath.Join(m.CertStore, "default.pem") pubKey = filepath.Join(m.CertStore, "default.pem")
priKey = filepath.Join(m.CertStore, "default.key") priKey = filepath.Join(m.CertStore, "default.key")
//if m.verbal {
// log.Println("No matching certificate found. Serving with default")
//}
} else {
//if m.verbal {
// log.Println("Matching certificate not found. Serving with build-in certificate. Requesting server name: ", helloInfo.ServerName)
//}
} }
} }

107
src/mod/update/update.go Normal file
View File

@ -0,0 +1,107 @@
package update
/*
Update.go
This module handle cross version updates that contains breaking changes
update command should always exit after the update is completed
*/
import (
"fmt"
"os"
"strconv"
"strings"
"imuslab.com/zoraxy/mod/utils"
)
// Run config update. Version numbers are int. For example
// to update 3.0.7 to 3.0.8, use RunConfigUpdate(307, 308)
// This function support cross versions updates (e.g. 307 -> 310)
func RunConfigUpdate(fromVersion int, toVersion int) {
versionFile := "./conf/version"
isFirstTimeInit, _ := isFirstTimeInitialize("./conf/proxy/")
if isFirstTimeInit {
//Create version file and exit
os.MkdirAll("./conf/", 0775)
os.WriteFile(versionFile, []byte(strconv.Itoa(toVersion)), 0775)
return
}
if fromVersion == 0 {
//Run auto previous version detection
fromVersion = 307
if utils.FileExists(versionFile) {
//Read the version file
previousVersionText, err := os.ReadFile(versionFile)
if err != nil {
panic("Unable to read version file at " + versionFile)
}
//Convert the version to int
versionInt, err := strconv.Atoi(strings.TrimSpace(string(previousVersionText)))
if err != nil {
panic("Unable to read version file at " + versionFile)
}
fromVersion = versionInt
}
if fromVersion == toVersion {
//No need to update
return
}
}
//Do iterate update
for i := fromVersion; i < toVersion; i++ {
oldVersion := i
newVersion := i + 1
fmt.Println("Updating from v", oldVersion, " to v", newVersion)
runUpdateRoutineWithVersion(oldVersion, newVersion)
//Write the updated version to file
os.WriteFile(versionFile, []byte(strconv.Itoa(newVersion)), 0775)
}
fmt.Println("Update completed")
}
func GetVersionIntFromVersionNumber(version string) int {
versionNumberOnly := strings.ReplaceAll(version, ".", "")
versionInt, _ := strconv.Atoi(versionNumberOnly)
return versionInt
}
// Check if the folder "./conf/proxy/" exists and contains files
func isFirstTimeInitialize(path string) (bool, error) {
// Check if the folder exists
info, err := os.Stat(path)
if os.IsNotExist(err) {
// The folder does not exist
return true, nil
}
if err != nil {
// Some other error occurred
return false, err
}
// Check if it is a directory
if !info.IsDir() {
// The path is not a directory
return false, fmt.Errorf("%s is not a directory", path)
}
// Read the directory contents
files, err := os.ReadDir(path)
if err != nil {
return false, err
}
// Check if the directory is empty
if len(files) == 0 {
// The folder exists but is empty
return true, nil
}
// The folder exists and contains files
return false, nil
}

View File

@ -0,0 +1,16 @@
package update
import v308 "imuslab.com/zoraxy/mod/update/v308"
// Updater Core logic
func runUpdateRoutineWithVersion(fromVersion int, toVersion int) {
if fromVersion == 307 && toVersion == 308 {
//Updating from v3.0.7 to v3.0.8
err := v308.UpdateFrom307To308()
if err != nil {
panic(err)
}
}
//ADD MORE VERSIONS HERE
}

View File

@ -0,0 +1,138 @@
package v308
/*
v307 type definations
This file wrap up the self-contained data structure
for v3.0.7 structure and allow automatic updates
for future releases if required
*/
type v307PermissionsPolicy struct {
Accelerometer []string `json:"accelerometer"`
AmbientLightSensor []string `json:"ambient_light_sensor"`
Autoplay []string `json:"autoplay"`
Battery []string `json:"battery"`
Camera []string `json:"camera"`
CrossOriginIsolated []string `json:"cross_origin_isolated"`
DisplayCapture []string `json:"display_capture"`
DocumentDomain []string `json:"document_domain"`
EncryptedMedia []string `json:"encrypted_media"`
ExecutionWhileNotRendered []string `json:"execution_while_not_rendered"`
ExecutionWhileOutOfView []string `json:"execution_while_out_of_viewport"`
Fullscreen []string `json:"fullscreen"`
Geolocation []string `json:"geolocation"`
Gyroscope []string `json:"gyroscope"`
KeyboardMap []string `json:"keyboard_map"`
Magnetometer []string `json:"magnetometer"`
Microphone []string `json:"microphone"`
Midi []string `json:"midi"`
NavigationOverride []string `json:"navigation_override"`
Payment []string `json:"payment"`
PictureInPicture []string `json:"picture_in_picture"`
PublicKeyCredentialsGet []string `json:"publickey_credentials_get"`
ScreenWakeLock []string `json:"screen_wake_lock"`
SyncXHR []string `json:"sync_xhr"`
USB []string `json:"usb"`
WebShare []string `json:"web_share"`
XRSpatialTracking []string `json:"xr_spatial_tracking"`
ClipboardRead []string `json:"clipboard_read"`
ClipboardWrite []string `json:"clipboard_write"`
Gamepad []string `json:"gamepad"`
SpeakerSelection []string `json:"speaker_selection"`
ConversionMeasurement []string `json:"conversion_measurement"`
FocusWithoutUserActivation []string `json:"focus_without_user_activation"`
HID []string `json:"hid"`
IdleDetection []string `json:"idle_detection"`
InterestCohort []string `json:"interest_cohort"`
Serial []string `json:"serial"`
SyncScript []string `json:"sync_script"`
TrustTokenRedemption []string `json:"trust_token_redemption"`
Unload []string `json:"unload"`
WindowPlacement []string `json:"window_placement"`
VerticalScroll []string `json:"vertical_scroll"`
}
// Auth credential for basic auth on certain endpoints
type v307BasicAuthCredentials struct {
Username string
PasswordHash string
}
// Auth credential for basic auth on certain endpoints
type v307BasicAuthUnhashedCredentials struct {
Username string
Password string
}
// Paths to exclude in basic auth enabled proxy handler
type v307BasicAuthExceptionRule struct {
PathPrefix string
}
// Header injection direction type
type v307HeaderDirection int
const (
HeaderDirection_ZoraxyToUpstream v307HeaderDirection = 0 //Inject (or remove) header to request out-going from Zoraxy to backend server
HeaderDirection_ZoraxyToDownstream v307HeaderDirection = 1 //Inject (or remove) header to request out-going from Zoraxy to client (e.g. browser)
)
// User defined headers to add into a proxy endpoint
type v307UserDefinedHeader struct {
Direction v307HeaderDirection
Key string
Value string
IsRemove bool //Instead of set, remove this key instead
}
// The original proxy endpoint structure from v3.0.7
type v307ProxyEndpoint struct {
ProxyType int //The type of this proxy, see const def
RootOrMatchingDomain string //Matching domain for host, also act as key
MatchingDomainAlias []string //A list of domains that alias to this rule
Domain string //Domain or IP to proxy to
//TLS/SSL Related
RequireTLS bool //Target domain require TLS
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
SkipCertValidations bool //Set to true to accept self signed certs
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
//Virtual Directories
VirtualDirectories []*v307VirtualDirectoryEndpoint
//Custom Headers
UserDefinedHeaders []*v307UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
PermissionPolicy *v307PermissionsPolicy //Permission policy header
//Authentication
RequireBasicAuth bool //Set to true to request basic auth before proxy
BasicAuthCredentials []*v307BasicAuthCredentials //Basic auth credentials
BasicAuthExceptionRules []*v307BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
// Rate Limiting
RequireRateLimit bool
RateLimit int64 // Rate limit in requests per second
//Access Control
AccessFilterUUID string //Access filter ID
Disabled bool //If the rule is disabled
//Fallback routing logic (Special Rule Sets Only)
DefaultSiteOption int //Fallback routing logic options
DefaultSiteValue string //Fallback routing target, optional
}
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
// program structure than directly using ProxyEndpoint
type v307VirtualDirectoryEndpoint struct {
MatchingPath string //Matching prefix of the request path, also act as key
Domain string //Domain or IP to proxy to
RequireTLS bool //Target domain require TLS
SkipCertValidations bool //Set to true to accept self signed certs
Disabled bool //If the rule is enabled
}

View File

@ -0,0 +1,63 @@
package v308
/*
v308 type definations
This file wrap up the self-contained data structure
for v3.0.8 structure and allow automatic updates
for future releases if required
Some struct are identical as v307 and hence it is not redefined here
*/
/* Upstream or Origin Server */
type v308Upstream struct {
//Upstream Proxy Configs
OriginIpOrDomain string //Target IP address or domain name with port
RequireTLS bool //Require TLS connection
SkipCertValidations bool //Set to true to accept self signed certs
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
//Load balancing configs
Weight int //Prirotiy of fallback, set all to 0 for round robin
MaxConn int //Maxmium connection to this server
}
// A proxy endpoint record, a general interface for handling inbound routing
type v308ProxyEndpoint struct {
ProxyType int //The type of this proxy, see const def
RootOrMatchingDomain string //Matching domain for host, also act as key
MatchingDomainAlias []string //A list of domains that alias to this rule
ActiveOrigins []*v308Upstream //Activated Upstream or origin servers IP or domain to proxy to
InactiveOrigins []*v308Upstream //Disabled Upstream or origin servers IP or domain to proxy to
UseStickySession bool //Use stick session for load balancing
Disabled bool //If the rule is disabled
//Inbound TLS/SSL Related
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
//Virtual Directories
VirtualDirectories []*v307VirtualDirectoryEndpoint
//Custom Headers
UserDefinedHeaders []*v307UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
PermissionPolicy *v307PermissionsPolicy //Permission policy header
//Authentication
RequireBasicAuth bool //Set to true to request basic auth before proxy
BasicAuthCredentials []*v307BasicAuthCredentials //Basic auth credentials
BasicAuthExceptionRules []*v307BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
// Rate Limiting
RequireRateLimit bool
RateLimit int64 // Rate limit in requests per second
//Access Control
AccessFilterUUID string //Access filter ID
//Fallback routing logic (Special Rule Sets Only)
DefaultSiteOption int //Fallback routing logic options
DefaultSiteValue string //Fallback routing target, optional
}

132
src/mod/update/v308/v308.go Normal file
View File

@ -0,0 +1,132 @@
package v308
import (
"encoding/json"
"io"
"log"
"os"
"path/filepath"
)
/*
v3.0.7 update to v3.0.8
This update function
*/
// Update proxy config files from v3.0.7 to v3.0.8
func UpdateFrom307To308() error {
//Load the configs
oldConfigFiles, err := filepath.Glob("./conf/proxy/*.config")
if err != nil {
return err
}
//Backup all the files
err = os.MkdirAll("./conf/proxy.old/", 0775)
if err != nil {
return err
}
for _, oldConfigFile := range oldConfigFiles {
// Extract the file name from the path
fileName := filepath.Base(oldConfigFile)
// Construct the backup file path
backupFile := filepath.Join("./conf/proxy.old/", fileName)
// Copy the file to the backup directory
err := copyFile(oldConfigFile, backupFile)
if err != nil {
return err
}
}
//read the config into the old struct
for _, oldConfigFile := range oldConfigFiles {
configContent, err := os.ReadFile(oldConfigFile)
if err != nil {
log.Println("Unable to read config file "+filepath.Base(oldConfigFile), err.Error())
continue
}
thisOldConfigStruct := v307ProxyEndpoint{}
err = json.Unmarshal(configContent, &thisOldConfigStruct)
if err != nil {
log.Println("Unable to parse file "+filepath.Base(oldConfigFile), err.Error())
continue
}
//Convert the old config to new config
newProxyStructure := convertV307ToV308(thisOldConfigStruct)
js, _ := json.MarshalIndent(newProxyStructure, "", " ")
err = os.WriteFile(oldConfigFile, js, 0775)
if err != nil {
log.Println(err.Error())
continue
}
}
return nil
}
func convertV307ToV308(old v307ProxyEndpoint) v308ProxyEndpoint {
// Create a new v308ProxyEndpoint instance
matchingDomainsSlice := old.MatchingDomainAlias
if matchingDomainsSlice == nil {
matchingDomainsSlice = []string{}
}
newEndpoint := v308ProxyEndpoint{
ProxyType: old.ProxyType,
RootOrMatchingDomain: old.RootOrMatchingDomain,
MatchingDomainAlias: matchingDomainsSlice,
ActiveOrigins: []*v308Upstream{{ // Mapping Domain field to v308Upstream struct
OriginIpOrDomain: old.Domain,
RequireTLS: old.RequireTLS,
SkipCertValidations: old.SkipCertValidations,
SkipWebSocketOriginCheck: old.SkipWebSocketOriginCheck,
Weight: 1,
MaxConn: 0,
}},
InactiveOrigins: []*v308Upstream{},
UseStickySession: false,
Disabled: old.Disabled,
BypassGlobalTLS: old.BypassGlobalTLS,
VirtualDirectories: old.VirtualDirectories,
UserDefinedHeaders: old.UserDefinedHeaders,
HSTSMaxAge: old.HSTSMaxAge,
EnablePermissionPolicyHeader: old.EnablePermissionPolicyHeader,
PermissionPolicy: old.PermissionPolicy,
RequireBasicAuth: old.RequireBasicAuth,
BasicAuthCredentials: old.BasicAuthCredentials,
BasicAuthExceptionRules: old.BasicAuthExceptionRules,
RequireRateLimit: old.RequireRateLimit,
RateLimit: old.RateLimit,
AccessFilterUUID: old.AccessFilterUUID,
DefaultSiteOption: old.DefaultSiteOption,
DefaultSiteValue: old.DefaultSiteValue,
}
return newEndpoint
}
// Helper function to copy files
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
destinationFile, err := os.Create(dst)
if err != nil {
return err
}
defer destinationFile.Close()
_, err = io.Copy(destinationFile, sourceFile)
return err
}

View File

@ -2,14 +2,23 @@ package uptime
import ( import (
"encoding/json" "encoding/json"
"errors"
"log" "log"
"net/http" "net/http"
"net/http/cookiejar"
"strconv"
"strings" "strings"
"time" "time"
"golang.org/x/net/publicsuffix"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
) )
const (
logModuleName = "uptime-monitor"
)
type Record struct { type Record struct {
Timestamp int64 Timestamp int64
ID string ID string
@ -21,17 +30,26 @@ type Record struct {
Latency int64 Latency int64
} }
type ProxyType string
const (
ProxyType_Host ProxyType = "Origin Server"
ProxyType_Vdir ProxyType = "Virtual Directory"
)
type Target struct { type Target struct {
ID string ID string
Name string Name string
URL string URL string
Protocol string Protocol string
ProxyType ProxyType
} }
type Config struct { type Config struct {
Targets []*Target Targets []*Target
Interval int Interval int
MaxRecordsStore int MaxRecordsStore int
Logger *logger.Logger
} }
type Monitor struct { type Monitor struct {
@ -54,6 +72,12 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
Config: config, Config: config,
OnlineStatusLog: map[string][]*Record{}, OnlineStatusLog: map[string][]*Record{},
} }
if config.Logger == nil {
//Use default fmt to log if logger is not provided
config.Logger, _ = logger.NewFmtLogger()
}
//Start the endpoint listener //Start the endpoint listener
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second) ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
done := make(chan bool) done := make(chan bool)
@ -67,7 +91,7 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
case <-done: case <-done:
return return
case t := <-ticker.C: case t := <-ticker.C:
log.Println("Uptime updated - ", t.Unix()) thisMonitor.Config.Logger.PrintAndLog(logModuleName, "Uptime updated - "+strconv.Itoa(int(t.Unix())), nil)
thisMonitor.ExecuteUptimeCheck() thisMonitor.ExecuteUptimeCheck()
} }
} }
@ -81,7 +105,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
//For each target to check online, do the following //For each target to check online, do the following
var thisRecord Record var thisRecord Record
if target.Protocol == "http" || target.Protocol == "https" { if target.Protocol == "http" || target.Protocol == "https" {
online, laterncy, statusCode := getWebsiteStatusWithLatency(target.URL) online, laterncy, statusCode := m.getWebsiteStatusWithLatency(target.URL)
thisRecord = Record{ thisRecord = Record{
Timestamp: time.Now().Unix(), Timestamp: time.Now().Unix(),
ID: target.ID, ID: target.ID,
@ -94,7 +118,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
} }
} else { } else {
log.Println("Unknown protocol: " + target.Protocol + ". Skipping") m.Config.Logger.PrintAndLog(logModuleName, "Unknown protocol: "+target.Protocol, errors.New("unsupported protocol"))
continue continue
} }
@ -114,8 +138,6 @@ func (m *Monitor) ExecuteUptimeCheck() {
m.OnlineStatusLog[target.ID] = thisRecords m.OnlineStatusLog[target.ID] = thisRecords
} }
} }
//TODO: Write results to db
} }
func (m *Monitor) AddTargetToMonitor(target *Target) { func (m *Monitor) AddTargetToMonitor(target *Target) {
@ -191,12 +213,12 @@ func (m *Monitor) HandleUptimeLogRead(w http.ResponseWriter, r *http.Request) {
*/ */
// Get website stauts with latency given URL, return is conn succ and its latency and status code // Get website stauts with latency given URL, return is conn succ and its latency and status code
func getWebsiteStatusWithLatency(url string) (bool, int64, int) { func (m *Monitor) getWebsiteStatusWithLatency(url string) (bool, int64, int) {
start := time.Now().UnixNano() / int64(time.Millisecond) start := time.Now().UnixNano() / int64(time.Millisecond)
statusCode, err := getWebsiteStatus(url) statusCode, err := getWebsiteStatus(url)
end := time.Now().UnixNano() / int64(time.Millisecond) end := time.Now().UnixNano() / int64(time.Millisecond)
if err != nil { if err != nil {
log.Println(err.Error()) m.Config.Logger.PrintAndLog(logModuleName, "Ping upstream timeout. Assume offline", err)
return false, 0, 0 return false, 0, 0
} else { } else {
diff := end - start diff := end - start
@ -217,11 +239,24 @@ func getWebsiteStatusWithLatency(url string) (bool, int64, int) {
} }
func getWebsiteStatus(url string) (int, error) { func getWebsiteStatus(url string) (int, error) {
client := http.Client{ // Create a one-time use cookie jar to store cookies
Timeout: 10 * time.Second, jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
log.Fatal(err)
} }
resp, err := client.Get(url) client := http.Client{
Jar: jar,
Timeout: 5 * time.Second,
}
req, _ := http.NewRequest("GET", url, nil)
req.Header = http.Header{
"User-Agent": {"zoraxy-uptime/1.1"},
}
resp, err := client.Do(req)
//resp, err := client.Get(url)
if err != nil { if err != nil {
//Try replace the http with https and vise versa //Try replace the http with https and vise versa
rewriteURL := "" rewriteURL := ""
@ -231,7 +266,12 @@ func getWebsiteStatus(url string) (int, error) {
rewriteURL = strings.ReplaceAll(url, "http://", "https://") rewriteURL = strings.ReplaceAll(url, "http://", "https://")
} }
resp, err = client.Get(rewriteURL) req, _ := http.NewRequest("GET", rewriteURL, nil)
req.Header = http.Header{
"User-Agent": {"zoraxy-uptime/1.1"},
}
resp, err := client.Do(req)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "http: server gave HTTP response to HTTPS client") { if strings.Contains(err.Error(), "http: server gave HTTP response to HTTPS client") {
//Invalid downstream reverse proxy settings, but it is online //Invalid downstream reverse proxy settings, but it is online

View File

@ -1,6 +1,10 @@
package utils package utils
import ( import (
"archive/zip"
"io"
"os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
) )
@ -50,3 +54,52 @@ func ReplaceSpecialCharacters(filename string) string {
return filename return filename
} }
/* Zip File Handler */
// zipFiles compresses multiple files into a single zip archive file
func ZipFiles(filename string, files ...string) error {
newZipFile, err := os.Create(filename)
if err != nil {
return err
}
defer newZipFile.Close()
zipWriter := zip.NewWriter(newZipFile)
defer zipWriter.Close()
for _, file := range files {
if err := addFileToZip(zipWriter, file); err != nil {
return err
}
}
return nil
}
// addFileToZip adds an individual file to a zip archive
func addFileToZip(zipWriter *zip.Writer, filename string) error {
fileToZip, err := os.Open(filename)
if err != nil {
return err
}
defer fileToZip.Close()
info, err := fileToZip.Stat()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = filepath.Base(filename)
header.Method = zip.Deflate
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(writer, fileToZip)
return err
}

View File

@ -3,6 +3,7 @@ package utils
import ( import (
"errors" "errors"
"log" "log"
"net"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
@ -48,6 +49,24 @@ func GetPara(r *http.Request, key string) (string, error) {
} }
} }
// Get GET paramter as boolean, accept 1 or true
func GetBool(r *http.Request, key string) (bool, error) {
x, err := GetPara(r, key)
if err != nil {
return false, err
}
x = strings.TrimSpace(x)
if x == "1" || strings.ToLower(x) == "true" || strings.ToLower(x) == "on" {
return true, nil
} else if x == "0" || strings.ToLower(x) == "false" || strings.ToLower(x) == "off" {
return false, nil
}
return false, errors.New("invalid boolean given")
}
// Get POST paramter // Get POST paramter
func PostPara(r *http.Request, key string) (string, error) { func PostPara(r *http.Request, key string) (string, error) {
r.ParseForm() r.ParseForm()
@ -68,9 +87,9 @@ func PostBool(r *http.Request, key string) (bool, error) {
x = strings.TrimSpace(x) x = strings.TrimSpace(x)
if x == "1" || strings.ToLower(x) == "true" { if x == "1" || strings.ToLower(x) == "true" || strings.ToLower(x) == "on" {
return true, nil return true, nil
} else if x == "0" || strings.ToLower(x) == "false" { } else if x == "0" || strings.ToLower(x) == "false" || strings.ToLower(x) == "off" {
return false, nil return false, nil
} }
@ -141,3 +160,35 @@ func StringInArrayIgnoreCase(arr []string, str string) bool {
return StringInArray(smallArray, strings.ToLower(str)) return StringInArray(smallArray, strings.ToLower(str))
} }
// Validate if the listening address is correct
func ValidateListeningAddress(address string) bool {
// Check if the address starts with a colon, indicating it's just a port
if strings.HasPrefix(address, ":") {
return true
}
// Split the address into host and port parts
host, port, err := net.SplitHostPort(address)
if err != nil {
// Try to parse it as just a port
if _, err := strconv.Atoi(address); err == nil {
return false // It's just a port number
}
return false // It's an invalid address
}
// Check if the port part is a valid number
if _, err := strconv.Atoi(port); err != nil {
return false
}
// Check if the host part is a valid IP address or empty (indicating any IP)
if host != "" {
if net.ParseIP(host) == nil {
return false
}
}
return true
}

View File

@ -173,7 +173,7 @@ func (fm *FileManager) HandleDownload(w http.ResponseWriter, r *http.Request) {
// HandleNewFolder creates a new folder in the specified directory // HandleNewFolder creates a new folder in the specified directory
func (fm *FileManager) HandleNewFolder(w http.ResponseWriter, r *http.Request) { func (fm *FileManager) HandleNewFolder(w http.ResponseWriter, r *http.Request) {
// Parse the directory name from the request // Parse the directory name from the request
dirName, err := utils.GetPara(r, "path") dirName, err := utils.PostPara(r, "path")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "invalid directory name") utils.SendErrorResponse(w, "invalid directory name")
return return
@ -268,13 +268,13 @@ func (fm *FileManager) HandleFileCopy(w http.ResponseWriter, r *http.Request) {
func (fm *FileManager) HandleFileMove(w http.ResponseWriter, r *http.Request) { func (fm *FileManager) HandleFileMove(w http.ResponseWriter, r *http.Request) {
// Parse the source and destination paths from the request // Parse the source and destination paths from the request
srcPath, err := utils.GetPara(r, "srcpath") srcPath, err := utils.PostPara(r, "srcpath")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "invalid source path") utils.SendErrorResponse(w, "invalid source path")
return return
} }
destPath, err := utils.GetPara(r, "destpath") destPath, err := utils.PostPara(r, "destpath")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "invalid destination path") utils.SendErrorResponse(w, "invalid destination path")
return return

View File

@ -5,13 +5,13 @@ import (
_ "embed" _ "embed"
"errors" "errors"
"fmt" "fmt"
"log"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"imuslab.com/zoraxy/mod/database" "imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
"imuslab.com/zoraxy/mod/webserv/filemanager" "imuslab.com/zoraxy/mod/webserv/filemanager"
) )
@ -30,6 +30,7 @@ type WebServerOptions struct {
EnableDirectoryListing bool //Enable listing of directory EnableDirectoryListing bool //Enable listing of directory
WebRoot string //Folder for stroing the static web folders WebRoot string //Folder for stroing the static web folders
EnableWebDirManager bool //Enable web file manager to handle files in web directory EnableWebDirManager bool //Enable web file manager to handle files in web directory
Logger *logger.Logger //System logger
Sysdb *database.Database //Database for storing configs Sysdb *database.Database //Database for storing configs
} }
@ -45,13 +46,16 @@ type WebServer struct {
// NewWebServer creates a new WebServer instance. One instance only // NewWebServer creates a new WebServer instance. One instance only
func NewWebServer(options *WebServerOptions) *WebServer { func NewWebServer(options *WebServerOptions) *WebServer {
if options.Logger == nil {
options.Logger, _ = logger.NewFmtLogger()
}
if !utils.FileExists(options.WebRoot) { if !utils.FileExists(options.WebRoot) {
//Web root folder not exists. Create one with default templates //Web root folder not exists. Create one with default templates
os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775) os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775)
os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775) os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775)
indexTemplate, err := templates.ReadFile("templates/index.html") indexTemplate, err := templates.ReadFile("templates/index.html")
if err != nil { if err != nil {
log.Println("Failed to read static wev server template file: ", err.Error()) options.Logger.PrintAndLog("static-webserv", "Failed to read static wev server template file: ", err)
} else { } else {
os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775) os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775)
} }
@ -102,7 +106,7 @@ func (ws *WebServer) RestorePreviousState() {
// ChangePort changes the server's port. // ChangePort changes the server's port.
func (ws *WebServer) ChangePort(port string) error { func (ws *WebServer) ChangePort(port string) error {
if IsPortInUse(port) { if IsPortInUse(port) {
return errors.New("Selected port is used by another process") return errors.New("selected port is used by another process")
} }
if ws.isRunning { if ws.isRunning {
@ -119,6 +123,7 @@ func (ws *WebServer) ChangePort(port string) error {
return err return err
} }
ws.option.Logger.PrintAndLog("static-webserv", "Listening port updated to "+port, nil)
ws.option.Sysdb.Write("webserv", "port", port) ws.option.Sysdb.Write("webserv", "port", port)
return nil return nil
@ -141,7 +146,7 @@ func (ws *WebServer) Start() error {
//Check if the port is usable //Check if the port is usable
if IsPortInUse(ws.option.Port) { if IsPortInUse(ws.option.Port) {
return errors.New("Port already in use or access denied by host OS") return errors.New("port already in use or access denied by host OS")
} }
//Dispose the old mux and create a new one //Dispose the old mux and create a new one
@ -159,12 +164,12 @@ func (ws *WebServer) Start() error {
go func() { go func() {
if err := ws.server.ListenAndServe(); err != nil { if err := ws.server.ListenAndServe(); err != nil {
if err != http.ErrServerClosed { if err != http.ErrServerClosed {
fmt.Printf("Web server error: %v\n", err) ws.option.Logger.PrintAndLog("static-webserv", "Web server failed to start", err)
} }
} }
}() }()
log.Println("Static Web Server started. Listeing on :" + ws.option.Port) ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server started. Listeing on :"+ws.option.Port, nil)
ws.isRunning = true ws.isRunning = true
ws.option.Sysdb.Write("webserv", "enabled", true) ws.option.Sysdb.Write("webserv", "enabled", true)
return nil return nil
@ -182,7 +187,7 @@ func (ws *WebServer) Stop() error {
if err := ws.server.Close(); err != nil { if err := ws.server.Close(); err != nil {
return err return err
} }
ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server stopped", nil)
ws.isRunning = false ws.isRunning = false
ws.option.Sysdb.Write("webserv", "enabled", false) ws.option.Sysdb.Write("webserv", "enabled", false)
return nil return nil

View File

@ -91,7 +91,7 @@ func handleToggleRedirectRegexpSupport(w http.ResponseWriter, r *http.Request) {
//Update the current regex support rule enable state //Update the current regex support rule enable state
enableRegexSupport := strings.EqualFold(strings.TrimSpace(enabled), "true") enableRegexSupport := strings.EqualFold(strings.TrimSpace(enabled), "true")
redirectTable.AllowRegex = enableRegexSupport redirectTable.AllowRegex = enableRegexSupport
err = sysdb.Write("Redirect", "regex", enableRegexSupport) err = sysdb.Write("redirect", "regex", enableRegexSupport)
if enableRegexSupport { if enableRegexSupport {
SystemWideLogger.PrintAndLog("redirect", "Regex redirect rule enabled", nil) SystemWideLogger.PrintAndLog("redirect", "Regex redirect rule enabled", nil)

View File

@ -11,6 +11,8 @@ import (
"imuslab.com/zoraxy/mod/auth" "imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/dynamicproxy" "imuslab.com/zoraxy/mod/dynamicproxy"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
"imuslab.com/zoraxy/mod/uptime" "imuslab.com/zoraxy/mod/uptime"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
) )
@ -95,9 +97,11 @@ func ReverseProxtInit() {
StatisticCollector: statisticCollector, StatisticCollector: statisticCollector,
WebDirectory: *staticWebServerRoot, WebDirectory: *staticWebServerRoot,
AccessController: accessController, AccessController: accessController,
LoadBalancer: loadBalancer,
Logger: SystemWideLogger,
}) })
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Unable to create dynamic proxy router", err) SystemWideLogger.PrintAndLog("proxy-config", "Unable to create dynamic proxy router", err)
return return
} }
@ -112,7 +116,7 @@ func ReverseProxtInit() {
for _, conf := range confs { for _, conf := range confs {
err := LoadReverseProxyConfig(conf) err := LoadReverseProxyConfig(conf)
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Failed to load config file: "+filepath.Base(conf), err) SystemWideLogger.PrintAndLog("proxy-config", "Failed to load config file: "+filepath.Base(conf), err)
return return
} }
} }
@ -121,7 +125,7 @@ func ReverseProxtInit() {
//Root config not set (new deployment?), use internal static web server as root //Root config not set (new deployment?), use internal static web server as root
defaultRootRouter, err := GetDefaultRootConfig() defaultRootRouter, err := GetDefaultRootConfig()
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Failed to generate default root routing", err) SystemWideLogger.PrintAndLog("proxy-config", "Failed to generate default root routing", err)
return return
} }
dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter) dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter)
@ -140,12 +144,13 @@ func ReverseProxtInit() {
//This must be done in go routine to prevent blocking on system startup //This must be done in go routine to prevent blocking on system startup
uptimeMonitor, _ = uptime.NewUptimeMonitor(&uptime.Config{ uptimeMonitor, _ = uptime.NewUptimeMonitor(&uptime.Config{
Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter), Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter),
Interval: 300, //5 minutes Interval: 300, //5 minutes
MaxRecordsStore: 288, //1 day MaxRecordsStore: 288, //1 day
Logger: SystemWideLogger, //Logger
}) })
SystemWideLogger.Println("Uptime Monitor background service started") SystemWideLogger.Println("Uptime Monitor background service started")
}() }()
} }
func ReverseProxyHandleOnOff(w http.ResponseWriter, r *http.Request) { func ReverseProxyHandleOnOff(w http.ResponseWriter, r *http.Request) {
@ -204,12 +209,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
useBypassGlobalTLS := bypassGlobalTLS == "true" useBypassGlobalTLS := bypassGlobalTLS == "true"
//Enable TLS validation? //Enable TLS validation?
stv, _ := utils.PostPara(r, "tlsval") skipTlsValidation, _ := utils.PostBool(r, "tlsval")
if stv == "" {
stv = "false"
}
skipTlsValidation := (stv == "true")
//Get access rule ID //Get access rule ID
accessRuleID, _ := utils.PostPara(r, "access") accessRuleID, _ := utils.PostPara(r, "access")
@ -221,13 +221,34 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
return return
} }
//Require basic auth? // Require basic auth?
rba, _ := utils.PostPara(r, "bauth") requireBasicAuth, _ := utils.PostBool(r, "bauth")
if rba == "" {
rba = "false"
}
requireBasicAuth := (rba == "true") //Use sticky session?
useStickySession, _ := utils.PostBool(r, "stickysess")
// Require Rate Limiting?
requireRateLimit := false
proxyRateLimit := 1000
requireRateLimit, err = utils.PostBool(r, "rate")
if err != nil {
requireRateLimit = false
}
if requireRateLimit {
proxyRateLimit, err = utils.PostInt(r, "ratenum")
if err != nil {
proxyRateLimit = 0
}
if err != nil {
utils.SendErrorResponse(w, "invalid rate limit number")
return
}
if proxyRateLimit <= 0 {
utils.SendErrorResponse(w, "rate limit number must be greater than 0")
return
}
}
// Bypass WebSocket Origin Check // Bypass WebSocket Origin Check
strbpwsorg, _ := utils.PostPara(r, "bpwsorg") strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
@ -292,13 +313,21 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
ProxyType: dynamicproxy.ProxyType_Host, ProxyType: dynamicproxy.ProxyType_Host,
RootOrMatchingDomain: rootOrMatchingDomain, RootOrMatchingDomain: rootOrMatchingDomain,
MatchingDomainAlias: aliasHostnames, MatchingDomainAlias: aliasHostnames,
Domain: endpoint, ActiveOrigins: []*loadbalance.Upstream{
{
OriginIpOrDomain: endpoint,
RequireTLS: useTLS,
SkipCertValidations: skipTlsValidation,
SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
Weight: 1,
},
},
InactiveOrigins: []*loadbalance.Upstream{},
UseStickySession: useStickySession,
//TLS //TLS
RequireTLS: useTLS, BypassGlobalTLS: useBypassGlobalTLS,
BypassGlobalTLS: useBypassGlobalTLS, AccessFilterUUID: accessRuleID,
SkipCertValidations: skipTlsValidation,
SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
AccessFilterUUID: accessRuleID,
//VDir //VDir
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{}, VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
//Custom headers //Custom headers
@ -309,6 +338,9 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{}, BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
DefaultSiteOption: 0, DefaultSiteOption: 0,
DefaultSiteValue: "", DefaultSiteValue: "",
// Rate Limit
RequireRateLimit: requireRateLimit,
RateLimit: int64(proxyRateLimit),
} }
preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisProxyEndpoint) preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisProxyEndpoint)
@ -345,14 +377,19 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
//Write the root options to file //Write the root options to file
rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{ rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{
ProxyType: dynamicproxy.ProxyType_Root, ProxyType: dynamicproxy.ProxyType_Root,
RootOrMatchingDomain: "/", RootOrMatchingDomain: "/",
Domain: endpoint, ActiveOrigins: []*loadbalance.Upstream{
RequireTLS: useTLS, {
BypassGlobalTLS: false, OriginIpOrDomain: endpoint,
SkipCertValidations: false, RequireTLS: useTLS,
SkipWebSocketOriginCheck: true, SkipCertValidations: true,
SkipWebSocketOriginCheck: true,
Weight: 1,
},
},
InactiveOrigins: []*loadbalance.Upstream{},
BypassGlobalTLS: false,
DefaultSiteOption: defaultSiteOption, DefaultSiteOption: defaultSiteOption,
DefaultSiteValue: dsVal, DefaultSiteValue: dsVal,
} }
@ -362,7 +399,11 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
return return
} }
dynamicProxyRouter.SetProxyRouteAsRoot(preparedRootProxyRoute) err = dynamicProxyRouter.SetProxyRouteAsRoot(preparedRootProxyRoute)
if err != nil {
utils.SendErrorResponse(w, "unable to update default site: "+err.Error())
return
}
proxyEndpointCreated = &rootRoutingEndpoint proxyEndpointCreated = &rootRoutingEndpoint
} else { } else {
//Invalid eptype //Invalid eptype
@ -373,7 +414,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
//Save the config to file //Save the config to file
err = SaveReverseProxyConfig(proxyEndpointCreated) err = SaveReverseProxyConfig(proxyEndpointCreated)
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Unable to save new proxy rule to file", err) SystemWideLogger.PrintAndLog("proxy-config", "Unable to save new proxy rule to file", err)
return return
} }
@ -396,24 +437,12 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
return return
} }
endpoint, err := utils.PostPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "endpoint not defined")
return
}
tls, _ := utils.PostPara(r, "tls") tls, _ := utils.PostPara(r, "tls")
if tls == "" { if tls == "" {
tls = "false" tls = "false"
} }
useTLS := (tls == "true") useStickySession, _ := utils.PostBool(r, "ss")
stv, _ := utils.PostPara(r, "tlsval")
if stv == "" {
stv = "false"
}
skipTlsValidation := (stv == "true")
//Load bypass TLS option //Load bypass TLS option
bpgtls, _ := utils.PostPara(r, "bpgtls") bpgtls, _ := utils.PostPara(r, "bpgtls")
@ -430,12 +459,28 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
requireBasicAuth := (rba == "true") requireBasicAuth := (rba == "true")
// Bypass WebSocket Origin Check // Rate Limiting?
strbpwsorg, _ := utils.PostPara(r, "bpwsorg") rl, _ := utils.PostPara(r, "rate")
if strbpwsorg == "" { if rl == "" {
strbpwsorg = "false" rl = "false"
}
requireRateLimit := (rl == "true")
rlnum, _ := utils.PostPara(r, "ratenum")
if rlnum == "" {
rlnum = "0"
}
proxyRateLimit, err := strconv.ParseInt(rlnum, 10, 64)
if err != nil {
utils.SendErrorResponse(w, "invalid rate limit number")
return
}
if requireRateLimit && proxyRateLimit <= 0 {
utils.SendErrorResponse(w, "rate limit number must be greater than 0")
return
} else if proxyRateLimit < 0 {
proxyRateLimit = 1000
} }
bypassWebsocketOriginCheck := (strbpwsorg == "true")
//Load the previous basic auth credentials from current proxy rules //Load the previous basic auth credentials from current proxy rules
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain) targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
@ -446,12 +491,11 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
//Generate a new proxyEndpoint from the new config //Generate a new proxyEndpoint from the new config
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry) newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
newProxyEndpoint.Domain = endpoint
newProxyEndpoint.RequireTLS = useTLS
newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
newProxyEndpoint.SkipCertValidations = skipTlsValidation
newProxyEndpoint.RequireBasicAuth = requireBasicAuth newProxyEndpoint.RequireBasicAuth = requireBasicAuth
newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck newProxyEndpoint.RequireRateLimit = requireRateLimit
newProxyEndpoint.RateLimit = proxyRateLimit
newProxyEndpoint.UseStickySession = useStickySession
//Prepare to replace the current routing rule //Prepare to replace the current routing rule
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint) readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
@ -465,7 +509,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
//Save it to file //Save it to file
SaveReverseProxyConfig(newProxyEndpoint) SaveReverseProxyConfig(newProxyEndpoint)
//Update uptime monitor //Update uptime monitor targets
UpdateUptimeMonitorTargets() UpdateUptimeMonitorTargets()
utils.SendOK(w) utils.SendOK(w)
@ -499,7 +543,7 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
newAlias := []string{} newAlias := []string{}
err = json.Unmarshal([]byte(newAliasJSON), &newAlias) err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Unable to parse new alias list", err) SystemWideLogger.PrintAndLog("proxy-config", "Unable to parse new alias list", err)
utils.SendErrorResponse(w, "Invalid alias list given") utils.SendErrorResponse(w, "Invalid alias list given")
return return
} }
@ -521,14 +565,14 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
err = SaveReverseProxyConfig(newProxyEndpoint) err = SaveReverseProxyConfig(newProxyEndpoint)
if err != nil { if err != nil {
utils.SendErrorResponse(w, "Alias update failed") utils.SendErrorResponse(w, "Alias update failed")
SystemWideLogger.PrintAndLog("Proxy", "Unable to save alias update", err) SystemWideLogger.PrintAndLog("proxy-config", "Unable to save alias update", err)
} }
utils.SendOK(w) utils.SendOK(w)
} }
func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) { func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
ep, err := utils.GetPara(r, "ep") ep, err := utils.PostPara(r, "ep")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "Invalid ep given") utils.SendErrorResponse(w, "Invalid ep given")
return return
@ -548,12 +592,6 @@ func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
return return
} }
//Update utm if exists
if uptimeMonitor != nil {
uptimeMonitor.Config.Targets = GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter)
uptimeMonitor.CleanRecords()
}
//Update uptime monitor //Update uptime monitor
UpdateUptimeMonitorTargets() UpdateUptimeMonitorTargets()
@ -825,6 +863,10 @@ func ReverseProxyToggleRuleSet(w http.ResponseWriter, r *http.Request) {
utils.SendErrorResponse(w, "unable to save updated rule") utils.SendErrorResponse(w, "unable to save updated rule")
return return
} }
//Update uptime monitor
UpdateUptimeMonitorTargets()
utils.SendOK(w) utils.SendOK(w)
} }
@ -884,7 +926,7 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
}) })
sort.Slice(results, func(i, j int) bool { sort.Slice(results, func(i, j int) bool {
return results[i].Domain < results[j].Domain return results[i].RootOrMatchingDomain < results[j].RootOrMatchingDomain
}) })
js, _ := json.Marshal(results) js, _ := json.Marshal(results)
@ -899,18 +941,22 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
// Handle port 80 incoming traffics // Handle port 80 incoming traffics
func HandleUpdatePort80Listener(w http.ResponseWriter, r *http.Request) { func HandleUpdatePort80Listener(w http.ResponseWriter, r *http.Request) {
enabled, err := utils.GetPara(r, "enable") if r.Method == http.MethodGet {
if err != nil {
//Load the current status //Load the current status
currentEnabled := false currentEnabled := false
err = sysdb.Read("settings", "listenP80", &currentEnabled) err := sysdb.Read("settings", "listenP80", &currentEnabled)
if err != nil { if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return return
} }
js, _ := json.Marshal(currentEnabled) js, _ := json.Marshal(currentEnabled)
utils.SendJSONResponse(w, string(js)) utils.SendJSONResponse(w, string(js))
} else { } else if r.Method == http.MethodPost {
enabled, err := utils.PostPara(r, "enable")
if err != nil {
utils.SendErrorResponse(w, "enable state not set")
return
}
if enabled == "true" { if enabled == "true" {
sysdb.Write("settings", "listenP80", true) sysdb.Write("settings", "listenP80", true)
SystemWideLogger.Println("Enabling port 80 listener") SystemWideLogger.Println("Enabling port 80 listener")
@ -923,38 +969,48 @@ func HandleUpdatePort80Listener(w http.ResponseWriter, r *http.Request) {
utils.SendErrorResponse(w, "invalid mode given: "+enabled) utils.SendErrorResponse(w, "invalid mode given: "+enabled)
} }
utils.SendOK(w) utils.SendOK(w)
} else {
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
} }
} }
// Handle https redirect // Handle https redirect
func HandleUpdateHttpsRedirect(w http.ResponseWriter, r *http.Request) { func HandleUpdateHttpsRedirect(w http.ResponseWriter, r *http.Request) {
useRedirect, err := utils.GetPara(r, "set") if r.Method == http.MethodGet {
if err != nil {
currentRedirectToHttps := false currentRedirectToHttps := false
//Load the current status //Load the current status
err = sysdb.Read("settings", "redirect", &currentRedirectToHttps) err := sysdb.Read("settings", "redirect", &currentRedirectToHttps)
if err != nil { if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return return
} }
js, _ := json.Marshal(currentRedirectToHttps) js, _ := json.Marshal(currentRedirectToHttps)
utils.SendJSONResponse(w, string(js)) utils.SendJSONResponse(w, string(js))
} else { } else if r.Method == http.MethodPost {
useRedirect, err := utils.PostBool(r, "set")
if err != nil {
utils.SendErrorResponse(w, "status not set")
return
}
if dynamicProxyRouter.Option.Port == 80 { if dynamicProxyRouter.Option.Port == 80 {
utils.SendErrorResponse(w, "This option is not available when listening on port 80") utils.SendErrorResponse(w, "This option is not available when listening on port 80")
return return
} }
if useRedirect == "true" { if useRedirect {
sysdb.Write("settings", "redirect", true) sysdb.Write("settings", "redirect", true)
SystemWideLogger.Println("Updating force HTTPS redirection to true") SystemWideLogger.Println("Updating force HTTPS redirection to true")
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true) dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
} else if useRedirect == "false" { } else {
sysdb.Write("settings", "redirect", false) sysdb.Write("settings", "redirect", false)
SystemWideLogger.Println("Updating force HTTPS redirection to false") SystemWideLogger.Println("Updating force HTTPS redirection to false")
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false) dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
} }
utils.SendOK(w) utils.SendOK(w)
} else {
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
} }
} }
@ -1004,15 +1060,20 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
return return
} }
rootProxyTargetOrigin := ""
if len(dynamicProxyRouter.Root.ActiveOrigins) > 0 {
rootProxyTargetOrigin = dynamicProxyRouter.Root.ActiveOrigins[0].OriginIpOrDomain
}
//Check if it is identical as proxy root (recursion!) //Check if it is identical as proxy root (recursion!)
if dynamicProxyRouter.Root == nil || dynamicProxyRouter.Root.Domain == "" { if dynamicProxyRouter.Root == nil || rootProxyTargetOrigin == "" {
//Check if proxy root is set before checking recursive listen //Check if proxy root is set before checking recursive listen
//Fixing issue #43 //Fixing issue #43
utils.SendErrorResponse(w, "Set Proxy Root before changing inbound port") utils.SendErrorResponse(w, "Set Proxy Root before changing inbound port")
return return
} }
proxyRoot := strings.TrimSuffix(dynamicProxyRouter.Root.Domain, "/") proxyRoot := strings.TrimSuffix(rootProxyTargetOrigin, "/")
if strings.EqualFold(proxyRoot, "localhost:"+strconv.Itoa(newIncomingPortInt)) || strings.EqualFold(proxyRoot, "127.0.0.1:"+strconv.Itoa(newIncomingPortInt)) { if strings.EqualFold(proxyRoot, "localhost:"+strconv.Itoa(newIncomingPortInt)) || strings.EqualFold(proxyRoot, "127.0.0.1:"+strconv.Itoa(newIncomingPortInt)) {
//Listening port is same as proxy root //Listening port is same as proxy root
//Not allow recursive settings //Not allow recursive settings
@ -1039,13 +1100,13 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
//List all the custom header defined in this proxy rule //List all the custom header defined in this proxy rule
func HandleCustomHeaderList(w http.ResponseWriter, r *http.Request) { func HandleCustomHeaderList(w http.ResponseWriter, r *http.Request) {
epType, err := utils.PostPara(r, "type") epType, err := utils.GetPara(r, "type")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "endpoint type not defined") utils.SendErrorResponse(w, "endpoint type not defined")
return return
} }
domain, err := utils.PostPara(r, "domain") domain, err := utils.GetPara(r, "domain")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "domain or matching rule not defined") utils.SendErrorResponse(w, "domain or matching rule not defined")
return return
@ -1076,9 +1137,9 @@ func HandleCustomHeaderList(w http.ResponseWriter, r *http.Request) {
// Add a new header to the target endpoint // Add a new header to the target endpoint
func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) { func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
epType, err := utils.PostPara(r, "type") rewriteType, err := utils.PostPara(r, "type")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "endpoint type not defined") utils.SendErrorResponse(w, "rewriteType not defined")
return return
} }
@ -1088,6 +1149,12 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
return return
} }
direction, err := utils.PostPara(r, "direction")
if err != nil {
utils.SendErrorResponse(w, "HTTP modifiy direction not set")
return
}
name, err := utils.PostPara(r, "name") name, err := utils.PostPara(r, "name")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "HTTP header name not set") utils.SendErrorResponse(w, "HTTP header name not set")
@ -1095,26 +1162,46 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
} }
value, err := utils.PostPara(r, "value") value, err := utils.PostPara(r, "value")
if err != nil { if err != nil && rewriteType == "add" {
utils.SendErrorResponse(w, "HTTP header value not set") utils.SendErrorResponse(w, "HTTP header value not set")
return return
} }
var targetProxyEndpoint *dynamicproxy.ProxyEndpoint targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
if epType == "root" { if err != nil {
targetProxyEndpoint = dynamicProxyRouter.Root utils.SendErrorResponse(w, "target endpoint not exists")
} else { return
ep, err := dynamicProxyRouter.LoadProxy(domain) }
if err != nil {
utils.SendErrorResponse(w, "target endpoint not exists")
return
}
targetProxyEndpoint = ep //Create a Custom Header Defination type
var rewriteDirection dynamicproxy.HeaderDirection
if direction == "toOrigin" {
rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToUpstream
} else if direction == "toClient" {
rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToDownstream
} else {
//Unknown direction
utils.SendErrorResponse(w, "header rewrite direction not supported")
return
}
isRemove := false
if rewriteType == "remove" {
isRemove = true
}
headerRewriteDefination := dynamicproxy.UserDefinedHeader{
Key: name,
Value: value,
Direction: rewriteDirection,
IsRemove: isRemove,
} }
//Create a new custom header object //Create a new custom header object
targetProxyEndpoint.AddUserDefinedHeader(name, value) err = targetProxyEndpoint.AddUserDefinedHeader(&headerRewriteDefination)
if err != nil {
utils.SendErrorResponse(w, "unable to add header rewrite rule: "+err.Error())
return
}
//Save it (no need reload as header are not handled by dpcore) //Save it (no need reload as header are not handled by dpcore)
err = SaveReverseProxyConfig(targetProxyEndpoint) err = SaveReverseProxyConfig(targetProxyEndpoint)
@ -1128,12 +1215,6 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
// Remove a header from the target endpoint // Remove a header from the target endpoint
func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) { func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) {
epType, err := utils.PostPara(r, "type")
if err != nil {
utils.SendErrorResponse(w, "endpoint type not defined")
return
}
domain, err := utils.PostPara(r, "domain") domain, err := utils.PostPara(r, "domain")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "domain or matching rule not defined") utils.SendErrorResponse(w, "domain or matching rule not defined")
@ -1146,20 +1227,17 @@ func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) {
return return
} }
var targetProxyEndpoint *dynamicproxy.ProxyEndpoint targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
if epType == "root" { if err != nil {
targetProxyEndpoint = dynamicProxyRouter.Root utils.SendErrorResponse(w, "target endpoint not exists")
} else { return
ep, err := dynamicProxyRouter.LoadProxy(domain)
if err != nil {
utils.SendErrorResponse(w, "target endpoint not exists")
return
}
targetProxyEndpoint = ep
} }
targetProxyEndpoint.RemoveUserDefinedHeader(name) err = targetProxyEndpoint.RemoveUserDefinedHeader(name)
if err != nil {
utils.SendErrorResponse(w, "unable to remove header rewrite rule: "+err.Error())
return
}
err = SaveReverseProxyConfig(targetProxyEndpoint) err = SaveReverseProxyConfig(targetProxyEndpoint)
if err != nil { if err != nil {
@ -1170,3 +1248,267 @@ func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) {
utils.SendOK(w) utils.SendOK(w)
} }
func HandleHostOverwrite(w http.ResponseWriter, r *http.Request) {
domain, err := utils.PostPara(r, "domain")
if err != nil {
domain, err = utils.GetPara(r, "domain")
if err != nil {
utils.SendErrorResponse(w, "domain or matching rule not defined")
return
}
}
//Get the proxy endpoint object dedicated to this domain
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
if err != nil {
utils.SendErrorResponse(w, "target endpoint not exists")
return
}
if r.Method == http.MethodGet {
//Get the current host header
js, _ := json.Marshal(targetProxyEndpoint.RequestHostOverwrite)
utils.SendJSONResponse(w, string(js))
} else if r.Method == http.MethodPost {
//Set the new host header
newHostname, _ := utils.PostPara(r, "hostname")
//As this will require change in the proxy instance we are running
//we need to clone and respawn this proxy endpoint
newProxyEndpoint := targetProxyEndpoint.Clone()
newProxyEndpoint.RequestHostOverwrite = newHostname
//Save proxy endpoint
err = SaveReverseProxyConfig(newProxyEndpoint)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Spawn a new endpoint with updated dpcore
preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Remove the old endpoint
err = targetProxyEndpoint.Remove()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Add the newly prepared endpoint to runtime
err = dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Print log message
if newHostname != "" {
SystemWideLogger.Println("Updated " + domain + " hostname overwrite to: " + newHostname)
} else {
SystemWideLogger.Println("Removed " + domain + " hostname overwrite")
}
utils.SendOK(w)
} else {
//Invalid method
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
}
}
// HandleHopByHop get and set the hop by hop remover state
// note that it shows the DISABLE STATE of hop-by-hop remover, not the enable state
func HandleHopByHop(w http.ResponseWriter, r *http.Request) {
domain, err := utils.PostPara(r, "domain")
if err != nil {
domain, err = utils.GetPara(r, "domain")
if err != nil {
utils.SendErrorResponse(w, "domain or matching rule not defined")
return
}
}
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
if err != nil {
utils.SendErrorResponse(w, "target endpoint not exists")
return
}
if r.Method == http.MethodGet {
//Get the current hop by hop header state
js, _ := json.Marshal(!targetProxyEndpoint.DisableHopByHopHeaderRemoval)
utils.SendJSONResponse(w, string(js))
} else if r.Method == http.MethodPost {
//Set the hop by hop header state
enableHopByHopRemover, _ := utils.PostBool(r, "removeHopByHop")
//As this will require change in the proxy instance we are running
//we need to clone and respawn this proxy endpoint
newProxyEndpoint := targetProxyEndpoint.Clone()
//Storage file use false as default, so disable removal = not enable remover
newProxyEndpoint.DisableHopByHopHeaderRemoval = !enableHopByHopRemover
//Save proxy endpoint
err = SaveReverseProxyConfig(newProxyEndpoint)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Spawn a new endpoint with updated dpcore
preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Remove the old endpoint
err = targetProxyEndpoint.Remove()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Add the newly prepared endpoint to runtime
err = dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Print log message
if enableHopByHopRemover {
SystemWideLogger.Println("Enabled hop-by-hop headers removal on " + domain)
} else {
SystemWideLogger.Println("Disabled hop-by-hop headers removal on " + domain)
}
utils.SendOK(w)
} else {
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
}
}
// Handle view or edit HSTS states
func HandleHSTSState(w http.ResponseWriter, r *http.Request) {
domain, err := utils.PostPara(r, "domain")
if err != nil {
domain, err = utils.GetPara(r, "domain")
if err != nil {
utils.SendErrorResponse(w, "domain or matching rule not defined")
return
}
}
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
if err != nil {
utils.SendErrorResponse(w, "target endpoint not exists")
return
}
if r.Method == http.MethodGet {
//Return current HSTS enable state
hstsAge := targetProxyEndpoint.HSTSMaxAge
js, _ := json.Marshal(hstsAge)
utils.SendJSONResponse(w, string(js))
return
} else if r.Method == http.MethodPost {
newMaxAge, err := utils.PostInt(r, "maxage")
if err != nil {
utils.SendErrorResponse(w, "maxage not defeined")
return
}
if newMaxAge == 0 || newMaxAge >= 31536000 {
targetProxyEndpoint.HSTSMaxAge = int64(newMaxAge)
SaveReverseProxyConfig(targetProxyEndpoint)
targetProxyEndpoint.UpdateToRuntime()
} else {
utils.SendErrorResponse(w, "invalid max age given")
return
}
utils.SendOK(w)
return
}
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
}
// HandlePermissionPolicy handle read or write to permission policy
func HandlePermissionPolicy(w http.ResponseWriter, r *http.Request) {
domain, err := utils.PostPara(r, "domain")
if err != nil {
domain, err = utils.GetPara(r, "domain")
if err != nil {
utils.SendErrorResponse(w, "domain or matching rule not defined")
return
}
}
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
if err != nil {
utils.SendErrorResponse(w, "target endpoint not exists")
return
}
if r.Method == http.MethodGet {
type CurrentPolicyState struct {
PPEnabled bool
CurrentPolicy *permissionpolicy.PermissionsPolicy
}
currentPolicy := permissionpolicy.GetDefaultPermissionPolicy()
if targetProxyEndpoint.PermissionPolicy != nil {
currentPolicy = targetProxyEndpoint.PermissionPolicy
}
result := CurrentPolicyState{
PPEnabled: targetProxyEndpoint.EnablePermissionPolicyHeader,
CurrentPolicy: currentPolicy,
}
js, _ := json.Marshal(result)
utils.SendJSONResponse(w, string(js))
return
} else if r.Method == http.MethodPost {
//Update the enable state of permission policy
enableState, err := utils.PostBool(r, "enable")
if err != nil {
utils.SendErrorResponse(w, "invalid enable state given")
return
}
targetProxyEndpoint.EnablePermissionPolicyHeader = enableState
SaveReverseProxyConfig(targetProxyEndpoint)
targetProxyEndpoint.UpdateToRuntime()
utils.SendOK(w)
return
} else if r.Method == http.MethodPut {
//Store the new permission policy
newPermissionPolicyJSONString, err := utils.PostPara(r, "pp")
if err != nil {
utils.SendErrorResponse(w, "missing pp (permission policy) paramter")
return
}
//Parse the permission policy from JSON string
newPermissionPolicy := permissionpolicy.GetDefaultPermissionPolicy()
err = json.Unmarshal([]byte(newPermissionPolicyJSONString), &newPermissionPolicy)
if err != nil {
utils.SendErrorResponse(w, "permission policy parse error: "+err.Error())
return
}
//Save it to file
targetProxyEndpoint.PermissionPolicy = newPermissionPolicy
SaveReverseProxyConfig(targetProxyEndpoint)
targetProxyEndpoint.UpdateToRuntime()
utils.SendOK(w)
return
}
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
}

View File

@ -4,9 +4,11 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/gorilla/csrf"
"imuslab.com/zoraxy/mod/sshprox" "imuslab.com/zoraxy/mod/sshprox"
) )
@ -42,11 +44,15 @@ func FSHandler(handler http.Handler) http.Handler {
// Allow access to /script/*, /img/pubic/* and /login.html without authentication // Allow access to /script/*, /img/pubic/* and /login.html without authentication
if strings.HasPrefix(r.URL.Path, ppf("/script/")) || strings.HasPrefix(r.URL.Path, ppf("/img/public/")) || r.URL.Path == ppf("/login.html") || r.URL.Path == ppf("/reset.html") || r.URL.Path == ppf("/favicon.png") { if strings.HasPrefix(r.URL.Path, ppf("/script/")) || strings.HasPrefix(r.URL.Path, ppf("/img/public/")) || r.URL.Path == ppf("/login.html") || r.URL.Path == ppf("/reset.html") || r.URL.Path == ppf("/favicon.png") {
if isHTMLFilePath(r.URL.Path) {
handleInjectHTML(w, r, r.URL.Path)
return
}
handler.ServeHTTP(w, r) handler.ServeHTTP(w, r)
return return
} }
// check authentication // Check authentication
if !authAgent.CheckAuth(r) && requireAuth { if !authAgent.CheckAuth(r) && requireAuth {
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect) http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
return return
@ -77,6 +83,10 @@ func FSHandler(handler http.Handler) http.Handler {
} }
//Authenticated //Authenticated
if isHTMLFilePath(r.URL.Path) {
handleInjectHTML(w, r, r.URL.Path)
return
}
handler.ServeHTTP(w, r) handler.ServeHTTP(w, r)
}) })
} }
@ -88,3 +98,53 @@ func ppf(relativeFilepath string) string {
} }
return relativeFilepath return relativeFilepath
} }
func isHTMLFilePath(requestURI string) bool {
return strings.HasSuffix(requestURI, ".html") || strings.HasSuffix(requestURI, "/")
}
// Serve the html file with template token injected
func handleInjectHTML(w http.ResponseWriter, r *http.Request, relativeFilepath string) {
// Read the HTML file
var content []byte
var err error
if len(relativeFilepath) > 0 && relativeFilepath[len(relativeFilepath)-1:] == "/" {
relativeFilepath = relativeFilepath + "index.html"
}
if development {
//Load from disk
targetFilePath := strings.ReplaceAll(filepath.Join("web/", relativeFilepath), "\\", "/")
content, err = os.ReadFile(targetFilePath)
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
} else {
//Load from embedded fs, require trimming off the prefix slash for relative path
relativeFilepath = strings.TrimPrefix(relativeFilepath, "/")
content, err = webres.ReadFile(relativeFilepath)
if err != nil {
SystemWideLogger.Println("load embedded web file failed: ", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}
// Convert the file content to a string
htmlContent := string(content)
//Defeine the system template for this request
templateStrings := map[string]string{
".csrfToken": csrf.Token(r),
}
// Replace template tokens in the HTML content
for key, value := range templateStrings {
placeholder := "{{" + key + "}}"
htmlContent = strings.ReplaceAll(htmlContent, placeholder, value)
}
// Write the modified HTML content to the response
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(htmlContent))
}

View File

@ -4,6 +4,7 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -12,18 +13,21 @@ import (
"imuslab.com/zoraxy/mod/acme" "imuslab.com/zoraxy/mod/acme"
"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/forwardproxy" "imuslab.com/zoraxy/mod/forwardproxy"
"imuslab.com/zoraxy/mod/ganserv" "imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb" "imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/info/logger"
"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/webserv" "imuslab.com/zoraxy/mod/webserv"
) )
@ -44,6 +48,18 @@ var (
) )
func startupSequence() { func startupSequence() {
//Start a system wide logger and log viewer
l, err := logger.NewLogger("zr", "./log")
if err == nil {
SystemWideLogger = l
} else {
panic(err)
}
LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
RootFolder: "./log",
Extension: ".log",
})
//Create database //Create database
db, err := database.NewDatabase("sys.db", false) db, err := database.NewDatabase("sys.db", false)
if err != nil { if err != nil {
@ -58,17 +74,17 @@ func startupSequence() {
os.MkdirAll("./conf/proxy/", 0775) os.MkdirAll("./conf/proxy/", 0775)
//Create an auth agent //Create an auth agent
sessionKey, err := auth.GetSessionKey(sysdb) sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, func(w http.ResponseWriter, r *http.Request) { authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, SystemWideLogger, func(w http.ResponseWriter, r *http.Request) {
//Not logged in. Redirecting to login page //Not logged in. Redirecting to login page
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect) http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
}) })
//Create a TLS certificate manager //Create a TLS certificate manager
tlsCertManager, err = tlscert.NewManager("./conf/certs", development) tlsCertManager, err = tlscert.NewManager("./conf/certs", development, SystemWideLogger)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -77,11 +93,10 @@ func startupSequence() {
db.NewTable("redirect") db.NewTable("redirect")
redirectAllowRegexp := false redirectAllowRegexp := false
db.Read("redirect", "regex", &redirectAllowRegexp) db.Read("redirect", "regex", &redirectAllowRegexp)
redirectTable, err = redirection.NewRuleTable("./conf/redirect", redirectAllowRegexp) redirectTable, err = redirection.NewRuleTable("./conf/redirect", redirectAllowRegexp, SystemWideLogger)
if err != nil { if err != nil {
panic(err) panic(err)
} }
redirectTable.Logger = SystemWideLogger
//Create a geodb store //Create a geodb store
geodbStore, err = geodb.NewGeoDb(sysdb, &geodb.StoreOptions{ geodbStore, err = geodb.NewGeoDb(sysdb, &geodb.StoreOptions{
@ -92,6 +107,13 @@ func startupSequence() {
panic(err) panic(err)
} }
//Create a load balancer
loadBalancer = loadbalance.NewLoadBalancer(&loadbalance.Options{
SystemUUID: nodeUUID,
Geodb: geodbStore,
Logger: SystemWideLogger,
})
//Create the access controller //Create the access controller
accessController, err = access.NewAccessController(&access.Options{ accessController, err = access.NewAccessController(&access.Options{
Database: sysdb, Database: sysdb,
@ -110,14 +132,6 @@ func startupSequence() {
panic(err) panic(err)
} }
//Create a system wide logger
l, err := logger.NewLogger("zr", "./log", *logOutputToFile)
if err == nil {
SystemWideLogger = l
} else {
panic(err)
}
//Start the static web server //Start the static web server
staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{ staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{
Sysdb: sysdb, Sysdb: sysdb,
@ -125,12 +139,13 @@ func startupSequence() {
WebRoot: *staticWebServerRoot, WebRoot: *staticWebServerRoot,
EnableDirectoryListing: true, EnableDirectoryListing: true,
EnableWebDirManager: *allowWebFileManager, EnableWebDirManager: *allowWebFileManager,
Logger: SystemWideLogger,
}) })
//Restore the web server to previous shutdown state //Restore the web server to previous shutdown state
staticWebServer.RestorePreviousState() staticWebServer.RestorePreviousState()
//Create a netstat buffer //Create a netstat buffer
netstatBuffers, err = netstat.NewNetStatBuffer(300) netstatBuffers, err = netstat.NewNetStatBuffer(300, SystemWideLogger)
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Network", "Failed to load network statistic info", err) SystemWideLogger.PrintAndLog("Network", "Failed to load network statistic info", err)
panic(err) panic(err)
@ -229,7 +244,7 @@ func startupSequence() {
webSshManager = sshprox.NewSSHProxyManager() webSshManager = sshprox.NewSSHProxyManager()
//Create TCP Proxy Manager //Create TCP Proxy Manager
tcpProxyManager = tcpprox.NewTCProxy(&tcpprox.Options{ streamProxyManager = streamproxy.NewStreamProxy(&streamproxy.Options{
Database: sysdb, Database: sysdb,
AccessControlHandler: accessController.DefaultAccessRule.AllowConnectionAccess, AccessControlHandler: accessController.DefaultAccessRule.AllowConnectionAccess,
}) })
@ -264,11 +279,23 @@ func startupSequence() {
//Create a table just to store acme related preferences //Create a table just to store acme related preferences
sysdb.NewTable("acmepref") sysdb.NewTable("acmepref")
acmeHandler = initACME() acmeHandler = initACME()
acmeAutoRenewer, err = acme.NewAutoRenewer("./conf/acme_conf.json", "./conf/certs/", int64(*acmeAutoRenewInterval), acmeHandler) acmeAutoRenewer, err = acme.NewAutoRenewer(
"./conf/acme_conf.json",
"./conf/certs/",
int64(*acmeAutoRenewInterval),
*acmeCertAutoRenewDays,
acmeHandler,
)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
/* Docker UX Optimizer */
if runtime.GOOS == "windows" && *runningInDocker {
SystemWideLogger.PrintAndLog("warning", "Invalid start flag combination: docker=true && runtime.GOOS == windows. Running in docker UX development mode.", nil)
}
DockerUXOptimizer = dockerux.NewDockerOptimizer(*runningInDocker, SystemWideLogger)
} }
// This sequence start after everything is initialized // This sequence start after everything is initialized
@ -278,5 +305,4 @@ func finalSequence() {
//Inject routing rules //Inject routing rules
registerBuildInRoutingRules() registerBuildInRoutingRules()
} }

283
src/upstreams.go Normal file
View File

@ -0,0 +1,283 @@
package main
import (
"encoding/json"
"net/http"
"sort"
"strings"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
"imuslab.com/zoraxy/mod/utils"
)
/*
Upstreams.go
This script handle upstream and load balancer
related API
*/
// List upstreams from a endpoint
func ReverseProxyUpstreamList(w http.ResponseWriter, r *http.Request) {
endpoint, err := utils.GetPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "endpoint not defined")
return
}
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
if err != nil {
utils.SendErrorResponse(w, "target endpoint not found")
return
}
activeUpstreams := targetEndpoint.ActiveOrigins
inactiveUpstreams := targetEndpoint.InactiveOrigins
// Sort the upstreams slice by weight, then by origin domain alphabetically
sort.Slice(activeUpstreams, func(i, j int) bool {
if activeUpstreams[i].Weight != activeUpstreams[j].Weight {
return activeUpstreams[i].Weight > activeUpstreams[j].Weight
}
return activeUpstreams[i].OriginIpOrDomain < activeUpstreams[j].OriginIpOrDomain
})
sort.Slice(inactiveUpstreams, func(i, j int) bool {
if inactiveUpstreams[i].Weight != inactiveUpstreams[j].Weight {
return inactiveUpstreams[i].Weight > inactiveUpstreams[j].Weight
}
return inactiveUpstreams[i].OriginIpOrDomain < inactiveUpstreams[j].OriginIpOrDomain
})
type UpstreamCombinedList struct {
ActiveOrigins []*loadbalance.Upstream
InactiveOrigins []*loadbalance.Upstream
}
js, _ := json.Marshal(UpstreamCombinedList{
ActiveOrigins: activeUpstreams,
InactiveOrigins: inactiveUpstreams,
})
utils.SendJSONResponse(w, string(js))
}
// Add an upstream to a given proxy upstream endpoint
func ReverseProxyUpstreamAdd(w http.ResponseWriter, r *http.Request) {
endpoint, err := utils.PostPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "endpoint not defined")
return
}
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
if err != nil {
utils.SendErrorResponse(w, "target endpoint not found")
return
}
upstreamOrigin, err := utils.PostPara(r, "origin")
if err != nil {
utils.SendErrorResponse(w, "upstream origin not set")
return
}
requireTLS, _ := utils.PostBool(r, "tls")
skipTlsValidation, _ := utils.PostBool(r, "tlsval")
bpwsorg, _ := utils.PostBool(r, "bpwsorg")
preactivate, _ := utils.PostBool(r, "active")
//Create a new upstream object
newUpstream := loadbalance.Upstream{
OriginIpOrDomain: upstreamOrigin,
RequireTLS: requireTLS,
SkipCertValidations: skipTlsValidation,
SkipWebSocketOriginCheck: bpwsorg,
Weight: 1,
MaxConn: 0,
}
//Add the new upstream to endpoint
err = targetEndpoint.AddUpstreamOrigin(&newUpstream, preactivate)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Save changes to configs
err = SaveReverseProxyConfig(targetEndpoint)
if err != nil {
SystemWideLogger.PrintAndLog("INFO", "Unable to save new upstream to proxy config", err)
utils.SendErrorResponse(w, "Failed to save new upstream config")
return
}
//Update Uptime Monitor
UpdateUptimeMonitorTargets()
utils.SendOK(w)
}
// Update the connection configuration of this origin
// pass in the whole new upstream origin json via "payload" POST variable
// for missing fields, original value will be used instead
func ReverseProxyUpstreamUpdate(w http.ResponseWriter, r *http.Request) {
endpoint, err := utils.PostPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "endpoint not defined")
return
}
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
if err != nil {
utils.SendErrorResponse(w, "target endpoint not found")
return
}
//Editing upstream origin IP
originIP, err := utils.PostPara(r, "origin")
if err != nil {
utils.SendErrorResponse(w, "origin ip or matching address not set")
return
}
originIP = strings.TrimSpace(originIP)
//Update content payload
payload, err := utils.PostPara(r, "payload")
if err != nil {
utils.SendErrorResponse(w, "update payload not set")
return
}
isActive, _ := utils.PostBool(r, "active")
targetUpstream, err := targetEndpoint.GetUpstreamOriginByMatchingIP(originIP)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Deep copy the upstream so other request handling goroutine won't be effected
newUpstream := targetUpstream.Clone()
//Overwrite the new value into the old upstream
err = json.Unmarshal([]byte(payload), &newUpstream)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Replace the old upstream with the new one
err = targetEndpoint.RemoveUpstreamOrigin(originIP)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
err = targetEndpoint.AddUpstreamOrigin(newUpstream, isActive)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Save changes to configs
err = SaveReverseProxyConfig(targetEndpoint)
if err != nil {
SystemWideLogger.PrintAndLog("INFO", "Unable to save upstream update to proxy config", err)
utils.SendErrorResponse(w, "Failed to save updated upstream config")
return
}
//Update Uptime Monitor
UpdateUptimeMonitorTargets()
utils.SendOK(w)
}
func ReverseProxyUpstreamSetPriority(w http.ResponseWriter, r *http.Request) {
endpoint, err := utils.PostPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "endpoint not defined")
return
}
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
if err != nil {
utils.SendErrorResponse(w, "target endpoint not found")
return
}
weight, err := utils.PostInt(r, "weight")
if err != nil {
utils.SendErrorResponse(w, "priority not defined")
return
}
if weight < 0 {
utils.SendErrorResponse(w, "invalid weight given")
return
}
//Editing upstream origin IP
originIP, err := utils.PostPara(r, "origin")
if err != nil {
utils.SendErrorResponse(w, "origin ip or matching address not set")
return
}
originIP = strings.TrimSpace(originIP)
editingUpstream, err := targetEndpoint.GetUpstreamOriginByMatchingIP(originIP)
editingUpstream.Weight = weight
// The editing upstream is a pointer to the runtime object
// and the change of weight do not requre a respawn of the proxy object
// so no need to remove & re-prepare the upstream on weight changes
err = SaveReverseProxyConfig(targetEndpoint)
if err != nil {
SystemWideLogger.PrintAndLog("INFO", "Unable to update upstream weight", err)
utils.SendErrorResponse(w, "Failed to update upstream weight")
return
}
utils.SendOK(w)
}
func ReverseProxyUpstreamDelete(w http.ResponseWriter, r *http.Request) {
endpoint, err := utils.PostPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "endpoint not defined")
return
}
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
if err != nil {
utils.SendErrorResponse(w, "target endpoint not found")
return
}
//Editing upstream origin IP
originIP, err := utils.PostPara(r, "origin")
if err != nil {
utils.SendErrorResponse(w, "origin ip or matching address not set")
return
}
originIP = strings.TrimSpace(originIP)
if !targetEndpoint.UpstreamOriginExists(originIP) {
utils.SendErrorResponse(w, "target upstream not found")
return
}
err = targetEndpoint.RemoveUpstreamOrigin(originIP)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Save changes to configs
err = SaveReverseProxyConfig(targetEndpoint)
if err != nil {
SystemWideLogger.PrintAndLog("INFO", "Unable to remove upstream", err)
utils.SendErrorResponse(w, "Failed to remove upstream from proxy rule")
return
}
//Update uptime monitor
UpdateUptimeMonitorTargets()
utils.SendOK(w)
}

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