268 Commits

Author SHA1 Message Date
0eb0696670 Merge pull request #399 from tobychui/v3.1.4
V3.1.4
2024-11-24 14:47:53 +08:00
9fca2354c6 Update darktheme.css
Fixed docker container list text theme color
2024-11-24 14:41:01 +08:00
e56b045689 Added dark theme to docker container list 2024-11-24 13:58:46 +08:00
763ccb4d60 Remove deprecated ZeroTier config directory from Docker readme 2024-11-24 00:44:52 -05:00
4d4492069d Merge branch 'main' into v3.1.4 2024-11-24 12:37:58 +08:00
f3591aa171 Update dockerContainersList.html
Merged PR into dark theme branch
2024-11-24 12:35:26 +08:00
2dcf578cbe Update README.md 2024-11-24 11:52:57 +08:00
23a5c6ceb0 Updated geoIP database 2024-11-24 11:46:49 +08:00
015889851a Optimized UX and code structure
+ Added automatic self-sign certificate sniffing
+ Moved all constant into def.go
+ Added auto restart on port change when proxy server is running
+ Optimized slow search geoIP resolver by introducing new cache mechanism
+ Updated default incoming port to HTTPS instead of HTTP
2024-11-24 11:38:01 +08:00
093ed9c212 Merge pull request #395 from eyerrock/container-searchbar
search bar for Docker container list
2024-11-21 21:47:38 +08:00
0af8c67346 Updated API register function
- Seperated different register for APIs
2024-11-19 21:13:02 +08:00
c5170bcb94 Refactorized main entry function
- Moved constants to def.go
- Added acme close function (not used for now)
- Added robots.txt to prevent webmin panel being scanned by search engine
2024-11-19 20:30:36 +08:00
cd48388c02 refactored docker container list 2024-11-18 21:01:54 +01:00
373845f8fd added searchbar to docker container list 2024-11-18 18:16:07 +01:00
293a527ffc Completed dark theme 2024-11-18 21:04:25 +08:00
e4facbc7b6 Added more dark themes
- Added wrappers for snippet dark theme
- Optimized color pallets
2024-11-17 17:41:22 +08:00
1c79fa4e96 Fixed #394 2024-11-17 08:38:13 +08:00
6515eb99e3 Fixed #393
Updated version code manually
2024-11-15 06:48:35 +08:00
ec5c24b9b8 Added more darktheme
- Added more dark theme css
- Merged main branch fixes and new features
- Added todo tag for custom timeout
2024-11-14 21:18:05 +08:00
df88084375 Merge pull request #391 from eyerrock/list-containers-with-unexposed-ports
list containers with unexposed ports
2024-11-14 20:06:31 +08:00
74017baecf Merge pull request #392 from PassiveLemon/zoraxy-volume
Symlink ZeroTier var to Zoraxy config
2024-11-13 18:42:35 +08:00
294d504ee6 Symlink ZeroTier var to Zoraxy config 2024-11-12 12:40:08 -05:00
477429900e list containers with unexposed ports 2024-11-11 21:07:07 +01:00
2e9bc77a5d Merge commit from fork
Fixed web ssh security bug
2024-11-10 13:57:01 +08:00
ed178d857a Fixed web ssh security bug 2024-11-10 13:22:32 +08:00
4cf5d29692 Added more dark theme 2024-11-09 16:12:41 +08:00
634e9c9855 v3.1.3 init commit
- Fixed #378
- Added wip dark theme
- Fixed in code typo
- Fixed int conversion bug in some DNS challenge supplier
2024-11-08 22:24:07 +08:00
e79a70b7ac Merge pull request #376 from PassiveLemon/actions-cache
Add layer caching to Docker action
2024-11-06 06:58:52 +08:00
779115d06b Add layer caching to Docker action 2024-11-04 20:39:47 -05:00
9cb315ea67 Merge pull request #373 from Morethanevil/main
Update CHANGELOG.md
2024-11-03 17:41:49 +08:00
43ba00ec8d Update CHANGELOG.md
Thanks for your work :)
2024-11-03 10:11:20 +01:00
4577fb1f2f Merge pull request #368 from tobychui/v3.1.2
v3.1.2
2024-11-03 10:57:06 +08:00
f877bf9eda Update reverseproxy.go
Fixed typo
2024-11-03 09:31:24 +08:00
363b9b6d94 Merge branch 'main' into v3.1.2 2024-11-02 15:26:41 +08:00
c5ca68868b Optimized ACME logic
- Added automatic port 80 listener enable for those who don't read our wiki
- Reduced default interval for polling and propagation timeout
2024-10-28 21:40:58 +08:00
f927bb539a Updated geodb
- Updated geoip table
- Fixed bug in streamproxy delete in dev version
- Commented SSO related features (WIP) for release
2024-10-27 16:35:59 +08:00
5f64b622b5 Fixed #353 and #327
- Added user defined polling and propagation timeout option in ACME
- Updated lego and added a few new DNS challenge providers
- Updated code gen to support new parameters
2024-10-27 16:17:44 +08:00
9a371f5bcb Updated code generator for lego
- Removed windows 7 support
2024-10-27 15:40:53 +08:00
172c5afa60 Added support for custom header variables
- Added support for using nginx-like variables in custom headers
- Supported variables includes: $host, $remote_addr, $request_uri, $request_method, $content_length, $content_type, $uri, $args, $scheme, $query_string, $http_user_agent and $http_referer
- Added test case for custom header variable rewriter
2024-10-27 14:47:01 +08:00
f98e04a9fc Fixed #318
- Added support for automatic X-Remote-User header when basic auth is enabled
- Moved header logic to rewrite module (new module)
- Added default site automatic fix for URL missing http:// or https:// prefix
2024-10-26 22:21:49 +08:00
99295cad86 Fixed #342
- Added port scanner
- Moved handlers for IP scanner into ipscan module
-Minor code optimization
2024-10-26 19:41:43 +08:00
95d0a98576 Merge pull request #358 from eltociear/patch-1
Fixed typo in reverseproxy.go
2024-10-26 18:46:51 +08:00
00bfa262cb docs: update reverseproxy.go
Defination -> Definition
2024-10-26 18:46:42 +09:00
528be69fe0 Optimized stream proxy codebase
- Moved stream proxy config from database to file based conf
- Optimized implementation for detecting proxy rule running
- Fixed #320 (hopefully)
2024-10-25 23:30:44 +08:00
6923f0d200 Fixed #328
- Fixed register enter not working
- Updated all link to new project domain (aroz.org)
2024-10-23 21:31:06 +08:00
7255b62e31 Merge pull request #344 from tobychui/main
Update development branch to match new project URL and docker config
2024-10-23 21:11:09 +08:00
cf14d12c31 Update index.html
Updated all links to aroz.org
2024-10-20 17:36:51 +08:00
90cf26306a Update CNAME 2024-10-20 17:25:28 +08:00
cab2f4e63a Fixed #316
Fixed early renew day not passed into auto renewer config bug
2024-09-26 22:57:49 +08:00
75d773887c Merge pull request #308 from PassiveLemon/Fix_307
Fix #307
2024-09-17 10:58:27 +08:00
a944c3ff36 Fix #307 2024-09-16 13:09:37 -04:00
465f332dfc Merge pull request #305 from PassiveLemon/ZeroTierFix
Fix: Build older version of ZeroTier
2024-09-15 23:43:49 +08:00
dfda3fe94b Fix: Build older version of ZeroTier
Anything from 1.12.0+ just doesn't work on Zoraxy
2024-09-14 01:23:17 -04:00
5c56da1180 Added basic oauth module structure (wip)
- Added struct for oauth
- Added interception handler for Zoraxy SSO
- Added user structure for SSO
2024-09-12 10:55:01 +08:00
3392013a5c Fixed #297
- Added UI to showcase ZeroSSL do not support DNS challenge
- Added test case for origin picker
- Updated zerotier struct info (wip)
2024-09-09 21:12:12 +08:00
8b4c601d50 Merge pull request #298 from Morethanevil/main
Update CHANGELOG.md
2024-09-05 08:18:09 +08:00
3a2eaf8766 Update CHANGELOG.md 2024-09-04 17:44:52 +02:00
a45092a449 Patched #274 2024-09-04 22:05:54 +08:00
d5315e5b8e Merge pull request #289 from tobychui/v3.1.1
v3.1.1 update
2024-09-04 21:35:21 +08:00
31cc1a69a1 Merge pull request #295 from PassiveLemon/zerotier
Add ZeroTier to Docker container
2024-09-01 23:01:20 +08:00
d348cbf48b Update Docker README 2024-08-30 09:47:02 -04:00
f6339868ac Refactor Dockerfile and bundle ZeroTier 2024-08-30 09:47:02 -04:00
af10f2a644 Fix typos and inconsistencies in README 2024-08-28 18:27:49 -04:00
3b247c31da Fixed typo in README 2024-08-27 10:18:08 +08:00
d74e8badb9 Fixed #287
- Removed unusded tab switch in quicksetup.js
- Changed Macedonia to North Macedonia
2024-08-25 13:12:07 +08:00
b40131d212 Updated geodb and merged PR from main 2024-08-23 17:52:36 +08:00
563a12c860 Merge pull request #286 from ahmadsyamim/patch-1
Fix typo remvoeClass to removeClass
2024-08-23 17:37:52 +08:00
8b2c3b7e03 Fix typo remvoeClass to removeClass 2024-08-23 09:51:34 +08:00
608cc0c523 Optimized upstream & loadbalancer
- Test and optimized load balancer origin picker
- Fixed no active origin cannot load proxy rule bug
- Implemented logger design in websocket proxy module
- Added more quickstart tours
- Fixed #270 (I guess)
- Fixed #90 (I guess)
2024-08-19 16:10:35 +08:00
b558bcbfcf Merge pull request #258 from bouroo/perf/upstreams-sortfunc
weighted random upstream
2024-08-19 15:39:22 +08:00
9ea3fa2542 Added tour for setup https 2024-08-16 22:28:21 +08:00
01f68c5ef5 Added tour for basic operations
- added static website setup tour
- added subdomain setup tour
2024-08-15 22:35:43 +08:00
a7f89086d4 Restructured log format in acme module
- Replaced all log.Println in acme module to system wide logger
- Fixed file manager path escape bug #274
2024-08-13 21:56:23 +08:00
a5ef6456c6 v3.1.1 init
- Fixed path traverse bug in web server file manager
- Merged docker container list from main
- Updated version code
- Merged network status fix from PR
- Removed unused comments in dpcore
-
2024-08-07 13:53:43 +08:00
87659b43bd Merge pull request #278 from JokerQyou/fix/network-io-chart-not-rendering
Fix network I/O chart not rendering.
2024-08-07 13:49:02 +08:00
ddbecf7b68 Merge pull request #280 from 7brend7/fix-added-containers-list
Fix existings containers list in docker popup
2024-08-07 13:40:24 +08:00
1b3a9de378 Fix existings containers list in docker popup 2024-08-04 00:25:13 +03:00
6dd62f509d Update network data instead of assigning new variables. 2024-08-02 22:00:51 +08:00
d5cc6a6859 Fix network I/O chart not rendering.
Close #200.
2024-08-02 00:07:12 +08:00
1d965da7d0 Merge pull request #277 from Morethanevil/main
Update CHANGELOG.md
2024-08-01 08:43:46 +08:00
3567c70bab Update CHANGELOG.md 2024-07-31 19:52:31 +02:00
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
8a8ec1cb0b 📝 randIndex for fallbackUpstreams random 2024-07-24 14:59:48 +07:00
e53c3cf3c4 ️ fallbackUpstreams with preserve index 2024-07-24 14:47:33 +07:00
d17de5c200 weighted random upstream 2024-07-23 08:50:10 +07:00
97ff48ee70 🔥 origins already checked before getRandomUpstreamByWeight 2024-07-23 08:31:59 +07:00
d64b1174af keep compatible with go 1.20 2024-07-23 08:31:59 +07:00
bec363abab ️ immediate return if single upstream 2024-07-23 08:31:59 +07:00
0dddd1f9e3 📝 discribe for upstream sort func 2024-07-23 08:31:59 +07:00
6bfcb2e1f5 ️ slices.SortFunc for upstreams 2024-07-23 08:31:59 +07: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
fe48a9a0c3 Updated acmedns tool
Add support for automatically generating acmedns module for Windows 7 using older version of lego
2024-05-18 14:48:55 +08:00
ec973eb3bc Merge pull request #152 from tobychui/v3.0.4
V3.0.4 Updates

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

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

Pre-checks on git.hkwtc is working and approved

- Added alias for HTTP proxy host names
- Added separator support for create new proxy rules (use "," to add alias when creating new proxy rule)
- Added HTTP proxy host based access rules
- Added EAD Configuration for ACME (by @yeungalan )
- Fixed bug for bypassGlobalTLS endpoint do not support basic-auth
- Removed dependencies on management panel css for online font files
2024-04-24 16:15:53 +08:00
b8cf046ca6 Fixed offline font bug
- Fixed offline font bug
- Set to pre-release embedded webui
2024-04-24 11:34:00 +08:00
026dd6b89d Update README.md
Added more info
2024-04-19 09:57:34 +08:00
5805fe6ed2 Update README.md
Added more info
2024-04-19 09:56:58 +08:00
3c78211800 Added alias support
+ Added alias support (use , when adding a new proxy target to automatically add alias hostnames)
+ Fixed some UI issues
2024-04-16 23:33:24 +08:00
8e648a8e1f v3.0.2 init commit
+ Fixed zeroSSL bug (said by @yeungalan ) #45
+ Fixed manual renew button bug
+ Seperated geodb module with access controller
+ Added per hosts access control (experimental) #69
+ Fixed basic auth not working on TLS bypass mode bug
+ Fixed empty domain crash bug #120
2024-04-14 19:37:01 +08:00
a000893dd1 Merge pull request #118 from Morethanevil/main
Update CHANGELOG.md
2024-04-04 18:44:09 +08:00
db88bfb752 Update CHANGELOG.md
Thanks again for your hard work
2024-04-04 11:54:38 +02:00
05297d854b Merge pull request #117 from tobychui/v3.0.1
V3.0.1 Updates
- Added regex support for redirect (slow, don't use it unless you really needs it)
- Added new dpcore implementations for faster proxy speed
- Added support for CF-Connecting-IP to X-Real-IP auto rewrite
- Added better 404 page
- Added option to bypass websocket origin check
- Updated project homepage design
- Fixed recursive port detection logic
- Fixed UserAgent in resp bug
- Updated minimum required Go version to v1.22 (Notes: Windows 7 support is dropped)
2024-04-04 15:02:10 +08:00
0d7bce4d30 Updated to go 1.22 2024-04-04 14:48:17 +08:00
8db95dddc6 Added regexp redirect support 2024-04-04 14:24:38 +08:00
05daeded37 Updated dpcore
- Added immediate flush for buffer (i.e. no chunked encoding)
- Added support for stream mode proxy sniffing logic
2024-04-01 21:36:11 +08:00
8ce6471be5 Updated homepage design 2024-03-30 16:26:03 +08:00
e242c9288f Fixed recursive port detection logic
+ Fixed recursive port detection logic
+ Added support for CF-Connecting-IP #114
2024-03-30 15:52:10 +08:00
c55a29e7cf Better 404 page and rules disable toggle 2024-03-16 20:45:05 +08:00
6af047430c Added UI for WebSocket Origin Check bypass 2024-03-12 14:03:31 +08:00
200c924acd 3.0.1 init commit
- Removed Go HTTP client UA
- Added optional bypass of websocket origin check #107
- Added basic forward proxy for debug
- Fixed UI error in network utils tab
2024-03-10 14:49:18 +08:00
9b2168466c Update access.html
Fixed issue #103
2024-02-24 14:04:33 +08:00
7ae48bf370 Update README.md
Added quick download links for Github noobs
2024-02-20 22:00:58 +08:00
ee3d76fb96 Fix: Failure to build upon release
If we need these back, we can just re-add them.
2024-02-18 14:20:34 -05:00
40d192524b Merge pull request #102 from Morethanevil/main
Update CHANGELOG.md
2024-02-18 21:08:50 +08:00
c659e05005 Update CHANGELOG.md
Updated Changelog
2024-02-18 13:36:35 +01:00
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
291 changed files with 268584 additions and 117770 deletions

View File

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

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

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

43
.github/workflows/docker.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: Build and push Docker image
on:
release:
types: [ published ]
jobs:
setup-build-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Setup building file structure
run: |
cp -lr $GITHUB_WORKSPACE/src/ $GITHUB_WORKSPACE/docker/
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: ./docker
push: true
platforms: linux/amd64,linux/arm64
tags: |
zoraxydocker/zoraxy:latest
zoraxydocker/zoraxy:${{ github.event.release.tag_name }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -1,45 +0,0 @@
name: Image Publisher
on:
release:
types: [ published ]
jobs:
setup-build-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker & GHCR
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
#echo "${{ secrets.GHCR_PASSWORD }}" | docker login ghcr.io -u "${{ secrets.GHCR_USERNAME }}" --password-stdin
- name: Setup building file structure
run: |
cp -r $GITHUB_WORKSPACE/src/ $GITHUB_WORKSPACE/docker/
- name: Build the image
run: |
cd $GITHUB_WORKSPACE/docker/
docker buildx create --name mainbuilder --driver docker-container --platform linux/amd64,linux/arm64 --use
docker buildx build --push \
--build-arg VERSION=${{ github.event.release.tag_name }} \
--provenance=false \
--platform linux/amd64,linux/arm64 \
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \
--tag zoraxydocker/zoraxy:latest \
# Since this is still undetermined, I will leave it commented
#--tag ghcr.io/zoraxydocker/zoraxy:${{ steps.get_latest_release_tag.outputs.latest_tag }} \
#--tag ghcr.io/zoraxydocker/zoraxy:latest \
.

10
.gitignore vendored
View File

@ -31,4 +31,12 @@ src/rules/*
src/README.md
docker/ContainerTester.sh
docker/ImagePublisher.sh
src/mod/acme/test/stackoverflow.pem
src/mod/acme/test/stackoverflow.pem
/tools/dns_challenge_update/code-gen/acmedns
/tools/dns_challenge_update/code-gen/lego
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,194 @@
# v3.1.2 03 Nov 2024
+ Added auto start port 80 listener on acme certificate generator
+ Added polling interval and propagation timeout option in ACME module [#300](https://github.com/tobychui/zoraxy/issues/300)
+ Added support for custom header variables [#318](https://github.com/tobychui/zoraxy/issues/318)
+ Added support for X-Remote-User
+ Added port scanner [#342](https://github.com/tobychui/zoraxy/issues/342)
+ Optimized code base for stream proxy and config file storage [#320](https://github.com/tobychui/zoraxy/issues/320)
+ Removed sorting on cert list
+ Fixed request certificate button bug
+ Fixed cert auto renew logic [#316](https://github.com/tobychui/zoraxy/issues/316)
+ Fixed unable to remove new stream proxy bug
+ Fixed many other minor bugs [#328](https://github.com/tobychui/zoraxy/issues/328) [#297](https://github.com/tobychui/zoraxy/issues/297)
+ Added more code to SSO system (disabled in release)
# v3.1.1. 09 Sep 2024
+ Updated country name in access list [#287](https://github.com/tobychui/zoraxy/issues/287)
+ Added tour for basic operations
+ Updated acme log to system wide logger implementation
+ Fixed path traversal in file manager [#274](https://github.com/tobychui/zoraxy/issues/274)
+ Removed Proxmox debug code
+ Fixed trie tree implementations
**Thanks to all contributors**
+ Fix existing containers list in docker popup [7brend7](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3A7brend7)
+ Fix network I/O chart not rendering [JokerQyou](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3AJokerQyou)
+ Fix typo remvoeClass to removeClass [Aahmadsyamim](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3Aahmadsyamim)
+ Updated weighted random upstream implementation [bouroo](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3Abouroo)
# v3.1.0 31 Jul 2024
+ Updated log viewer with filter and auto refresh [#243](https://github.com/tobychui/zoraxy/issues/243)
+ Fixed csrf vulnerability [#267](https://github.com/tobychui/zoraxy/issues/267)
+ Fixed promox issue
+ Fixed status code bug in upstream log [#254](https://github.com/tobychui/zoraxy/issues/254)
+ Added host overwrite and hop-by-hop header remover
+ Added early renew days settings [#256](https://github.com/tobychui/zoraxy/issues/256)
+ Updated make file to force no CGO in cicd process
+ Fixed bug in updater
+ Fixed wildcard certificate renew bug [#249](https://github.com/tobychui/zoraxy/issues/249)
+ Added certificate download function [#227](https://github.com/tobychui/zoraxy/issues/227)
# v3.0.9 16 Jul 2024
+ Added certificate download [#227](https://github.com/tobychui/zoraxy/issues/227)
+ Updated netcup timeout value [#231](https://github.com/tobychui/zoraxy/issues/231)
+ Updated geoip db
+ Removed debug print from log viewer
+ Upgraded netstat log printing to new log formatter
+ Improved update module implementation
# v3.0.8 15 Jul 2024
+ Added apache style logging mechanism (and build-in log viewer) [#218](https://github.com/tobychui/zoraxy/issues/218)
+ Fixed keep alive flushing issues [#235](https://github.com/tobychui/zoraxy/issues/235)
+ Added multi-upstream supports [#100](https://github.com/tobychui/zoraxy/issues/100)
+ Added stick session load balancer
+ Added weighted random load balancer
+ Added domain cleaning logic to domain / IP input fields
+ Added HSTS "include subdomain" auto injector
+ Added work-in-progress SSO / Oauth Server UI
+ Fixed uptime monitor not updating on proxy rule change bug
+ Optimized UI for create new proxy rule
+ Removed service expose proxy feature
# v3.0.7 20 Jun 2024
+ Fixed redirection enable bug [#199](https://github.com/tobychui/zoraxy/issues/199)
+ Fixed header tool user agent rewrite sequence
+ Optimized rate limit UI
+ Added HSTS and Permission Policy Editor [#163](https://github.com/tobychui/zoraxy/issues/163)
+ Docker UX optimization start parameter `-docker`
+ Docker container selector implementation for conditional compilations for Windows
From contributors:
+ Add Rate Limits Limits to Zoraxy fixes [185](https://github.com/tobychui/zoraxy/issues/185) by [Kirari04](https://github.com/Kirari04)
+ Add docker containers list to set rule by [7brend7](https://github.com/7brend7) [PR202](https://github.com/tobychui/zoraxy/pull/202)
# v3.0.6 10 Jun 2024
+ Added fastly_client_ip to X-Real-IP auto rewrite
+ Added atomic accumulator to TCP proxy
+ Added white logo for future dark theme
+ Added multi selection for white / blacklist [#176](https://github.com/tobychui/zoraxy/issues/176)
+ Moved custom header rewrite to dpcore
+ Restructure dpcore header rewrite sequence
+ Added advance custom header settings (zoraxy to upstream and zoraxy to downstream mode)
+ Added header remove feature
+ Removed password requirement for SMTP [#162](https://github.com/tobychui/zoraxy/issues/162) [#80](https://github.com/tobychui/zoraxy/issues/80)
+ Restructured TCP proxy into Stream Proxy (Support both TCP and UDP) [#147](https://github.com/tobychui/zoraxy/issues/147)
+ Added stream proxy auto start [#169](https://github.com/tobychui/zoraxy/issues/169)
+ Optimized UX for reminding user to click Apply after port change
+ Added version number to footer [#160](https://github.com/tobychui/zoraxy/issues/160)
From contributors:
+ Fixed missing / unnecessary error check [PR187](https://github.com/tobychui/zoraxy/pull/187) by [Kirari04](https://github.com/Kirari04)
# v3.0.5 May 26 2024
+ Optimized uptime monitor error message [#121](https://github.com/tobychui/zoraxy/issues/121)
+ Optimized detection logic for internal proxy target and header rewrite condition for HTTP_HOST [#164](https://github.com/tobychui/zoraxy/issues/164)
+ Fixed ovh DNS challenge provider form generator bug [#161](https://github.com/tobychui/zoraxy/issues/161)
+ Added permission policy module (not enabled)
+ Added single-use cookiejar to uptime monitor request client to handle cookie issues on some poorly written back-end server [#149](https://github.com/tobychui/zoraxy/issues/149)
# v3.0.4 May 18 2024
## This release tidied up the contribution by [Teifun2](https://github.com/Teifun2) and added a new way to generate DNS challenge based certificate (e.g. wildcards) from Let's Encrypt without changing any environment variables. This also fixes a few previous ACME module EAB settings bug related to concurrent save.
You can find the DNS challenge settings under TLS / SSL > ACME snippet > Generate New Certificate > (Check the "Use a DNS Challenge" checkbox)
+ Optimized DNS challenge implementation [thanks to Teifun2](https://github.com/Teifun2) / Issues [#49](https://github.com/tobychui/zoraxy/issues/49) [#79](https://github.com/tobychui/zoraxy/issues/79)
+ Removed dependencies on environment variable write and keep all data contained
+ Fixed panic on loading certificate generated by Zoraxy v2
+ Added automatic form generator for DNS challenge / providers
+ Added CA name default value
+ Added code generator for acmedns module (storing the DNS challenge provider contents extracted from lego)
+ Fixed ACME snippet "Obtain Certificate" concurrent issues in save EAB and DNS credentials
# v3.0.3 Apr 30 2024
## Breaking Change
For users using SMTP with older versions, you might need to update the settings by moving the domains (the part after @ in the username and domain setup field) into the username field.
+ Updated SMTP UI for non email login username [#129](https://github.com/tobychui/zoraxy/issues/129)
+ Fixed ACME cert store reload after cert request [#126](https://github.com/tobychui/zoraxy/issues/126)
+ Fixed default rule not applying to default site when default site is set to proxy target [#130](https://github.com/tobychui/zoraxy/issues/130)
+ Fixed blacklist-ip not working with CIDR bug
+ Fixed minor vdir bug in tailing slash detection and redirect logic
+ Added custom mdns name support (-mdnsname flag)
+ Added LAN tag in statistic [#131](https://github.com/tobychui/zoraxy/issues/131)
# v3.0.2 Apr 24 2024
+ Added alias for HTTP proxy host names [#76](https://github.com/tobychui/zoraxy/issues/76)
+ Added separator support for create new proxy rules (use "," to add alias when creating new proxy rule)
+ Added HTTP proxy host based access rules [#69](https://github.com/tobychui/zoraxy/issues/69)
+ Added EAD Configuration for ACME (by [yeungalan](https://github.com/yeungalan)) [#45](https://github.com/tobychui/zoraxy/issues/45)
+ Fixed bug for bypassGlobalTLS endpoint do not support basic-auth
+ Fixed panic due to empty domain field in json config [#120](https://github.com/tobychui/zoraxy/issues/120)
+ Removed dependencies on management panel css for online font files
# v3.0.1 Apr 04 2024
## Bugfixupdate for big release of V3, read update notes from V3 if you are still on V2
+ Added regex support for redirect (slow, don't use it unless you really needs it) [#42](https://github.com/tobychui/zoraxy/issues/42)
+ Added new dpcore implementations for faster proxy speed
+ Added support for CF-Connecting-IP to X-Real-IP auto rewrite [#114](https://github.com/tobychui/zoraxy/issues/114)
+ Added enable / disable of HTTP proxy rules in runtime via slider [#108](https://github.com/tobychui/zoraxy/issues/108)
+ Added better 404 page
+ Added option to bypass websocket origin check [#107](https://github.com/tobychui/zoraxy/issues/107)
+ Updated project homepage design
+ Fixed recursive port detection logic
+ Fixed UserAgent in resp bug
+ Updated minimum required Go version to v1.22 (Notes: Windows 7 support is dropped) [#112](https://github.com/tobychui/zoraxy/issues/112)
# v3.0.0 Feb 18 2024
## IMPORTANT: V3 is a big rewrite and it is incompatible with V2! There is NO migration, if you want to stay on V2, please use V2 branch!
+ Added comments for whitelist [#97](https://github.com/tobychui/zoraxy/issues/97)
+ Added force-renew for certificates [#92](https://github.com/tobychui/zoraxy/issues/92)
+ Added automatic cert pick for multi-host certs (SNI)
+ Renamed .crt to .pem for cert store
+ Added best-fit selection for wildcard matching rules
+ Added x-proxy-by header / Added X-real-Ip header [#93](https://github.com/tobychui/zoraxy/issues/93)
+ Added Development Mode (Cache-Control: no-store)
+ Updated utm timeout to 10 seconds instead of 90
+ Added "Add controller as member" feature to Global Area Network editor
+ Added custom header
+ Deprecated aroz subservice support
+ Updated visuals, improving logical structure, less depressing colors [#95](https://github.com/tobychui/zoraxy/issues/95)
+ Added virtual directory into host routing object (each host now got its own sets of virtual directories)
+ Added support for wildcard host names (e.g. *.example.com)
+ Added best-fit selection for wildcard matching rules (e.g. *.a.example.com > *.example.com in routing)
+ Generalized root and hosts routing struct (no more conversion between runtime & save record object
+ Added "Default Site" to replace "Proxy Root" interface
+ Added Redirect & 404 page for "Default Site"
# v2.6.8 Nov 25 2023
+ Added opt-out for subdomains for global TLS settings: See [release notes](https://github.com/tobychui/zoraxy/releases/tag/2.6.8)

View File

@ -2,38 +2,57 @@
# Zoraxy
General purpose request (reverse) proxy and forwarding tool for networking noobs. Now written in Go!
*Zoraxy v3 HTTP proxy config is not compatible with the older v2. If you are looking for the legacy version of Zoraxy, take a look at the [v2 branch](https://github.com/tobychui/zoraxy/tree/v2)*
A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
### Features
- Simple to use interface with detail in-system instructions
- Reverse Proxy
- Reverse Proxy (HTTP/2)
- Virtual Directory
- WebSocket Proxy (automatic, no set-up needed)
- Basic Auth
- Alias Hostnames
- Custom Headers
- Redirection Rules
- TLS / SSL setup and deploy
- ACME features like auto-renew to serve your sites in http**s**
- SNI support (one certificate contains multiple host names)
- SNI support (and SAN certs)
- DNS Challenge for Let's Encrypt and [these DNS providers](https://go-acme.github.io/lego/dns/)
- Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)
- Global Area Network Controller Web UI (ZeroTier not included)
- TCP Tunneling / Proxy
- Stream Proxy (TCP & UDP)
- Integrated Up-time Monitor
- Web-SSH Terminal
- Utilities
- CIDR IP converters
- mDNS Scanner
- Wake-On-Lan
- Debug Forward Proxy
- IP Scanner
- Others
- Basic single-admin management mode
- External permission management system for easy system integration
- SMTP config for password reset
- Dark Theme Mode
## Downloads
[Windows](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe)
/ [Linux (amd64)](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_amd64)
/ [Linux (arm64)](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_arm64)
For other systems or architectures, please see [Releases](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 user [itsvmn](https://www.reddit.com/user/itsvmn/)!
If you have no background in setting up reverse proxy or web routing, you should check this out before you start setting up your Zoraxy.
## Build from Source
Requires Go 1.20 or higher
Requires Go 1.23 or higher
```bash
git clone https://github.com/tobychui/zoraxy
@ -46,11 +65,11 @@ sudo ./zoraxy -port=:8000
## Usage
Zoraxy provides basic authentication system for standalone mode. To use it in standalone mode, follow the instructionss below for your desired deployment platform.
Zoraxy provides basic authentication system for standalone mode. To use it in standalone mode, follow the instructions below for your desired deployment platform.
### 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
@ -71,21 +90,27 @@ The installation method is same as Linux. If you are using a Raspberry Pi 4 or n
The installation method is same as Linux. For other ARM SBCs, please refer to your SBC's CPU architecture and pick the one that is suitable for your device.
#### Docker
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details.
### Start Paramters
### Start Parameters
```
Usage of zoraxy:
-autorenew int
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
-cfgupgrade
Enable auto config upgrade if breaking change is detected (default true)
-docker
Run Zoraxy in docker compatibility mode
-earlyrenew int
Number of days to early renew a soon expiring certificate (days) (default 30)
-fastgeoip
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
-info
Show information about this program in JSON
-log
Log terminal output to file (default true)
-mdns
Enable mDNS scanner and transponder (default true)
-mdnsname string
mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)
-noauth
Disable authentication for management interface
-port string
@ -112,7 +137,7 @@ If you already have an upstream reverse proxy server in place with permission ma
./zoraxy -noauth=true
```
*Note: For security reaons, you should only enable no-auth if you are running Zoraxy in a trusted environment or with another authentication management proxy in front.*
*Note: For security reasons, you should only enable no-auth if you are running Zoraxy in a trusted environment or with another authentication management proxy in front.*
## Screenshots
@ -145,12 +170,13 @@ This allows you to have an infinite number of network members in your Global Are
## Web SSH
Web SSH currently only supports Linux based OSes. The following platforms are supported:
- linux/amd64
- linux/arm64
- linux/armv6 (experimental)
- linux/386 (experimental)
### Loopback Connection
### Loopback Connection
Loopback web SSH connection, by default, is disabled. This means that if you are trying to connect to an address like 127.0.0.1 or localhost, the system will reject your connection for security reasons. To enable loopback for testing or development purpose, use the following flags to override the loopback checking:
@ -159,12 +185,13 @@ Loopback web SSH connection, by default, is disabled. This means that if you are
```
## Sponsor This Project
If you like the project and want to support us, please consider a donation. You can use the links below
- [tobychui (Primary author)](https://paypal.me/tobychui)
- PassiveLemon (Docker compatibility maintainer)
## License
This project is open-sourced under AGPL. I open-sourced this project so everyone can check for security issues and benefit all users. **If you plan to use this project in a commercial environment (which violate the AGPL terms), please contact toby@imuslab.com for an alternative license.**
This project is open-sourced under AGPL. I open-sourced this project so everyone can check for security issues and benefit all users. **This software is intended to be free of charge. If you have acquired this software from a third-party seller, the authors of this repository bears no responsibility for any technical difficulties assistance or support.**

View File

@ -1,20 +1,8 @@
FROM docker.io/golang:alpine
# VERSION comes from the main.yml workflow --build-arg
ARG VERSION
RUN apk add --no-cache bash netcat-openbsd sudo
FROM docker.io/golang:alpine AS build-zoraxy
RUN mkdir -p /opt/zoraxy/source/ &&\
mkdir -p /opt/zoraxy/config/ &&\
mkdir -p /usr/local/bin/
COPY entrypoint.sh /opt/zoraxy/
RUN chmod -R 755 /opt/zoraxy/ &&\
chmod +x /opt/zoraxy/entrypoint.sh
VOLUME [ "/opt/zoraxy/config/" ]
# If you build it yourself, you will need to add the src directory into the docker directory.
COPY ./src/ /opt/zoraxy/source/
@ -22,17 +10,57 @@ WORKDIR /opt/zoraxy/source/
RUN go mod tidy &&\
go build -o /usr/local/bin/zoraxy &&\
rm -r /opt/zoraxy/source/
chmod 755 /usr/local/bin/zoraxy
RUN chmod +x /usr/local/bin/zoraxy
FROM docker.io/ubuntu:latest AS build-zerotier
RUN mkdir -p /opt/zerotier/source/ &&\
mkdir -p /usr/local/bin/
WORKDIR /opt/zerotier/source/
RUN apt-get update -y &&\
apt-get install -y curl jq build-essential pkg-config clang cargo libssl-dev
RUN curl -Lo ZeroTierOne.tar.gz https://codeload.github.com/zerotier/ZeroTierOne/tar.gz/refs/tags/1.10.6 &&\
tar -xzvf ZeroTierOne.tar.gz &&\
cd ZeroTierOne-* &&\
make &&\
mv ./zerotier-one /usr/local/bin/zerotier-one &&\
chmod 755 /usr/local/bin/zerotier-one
FROM docker.io/ubuntu:latest
RUN apt-get update -y &&\
apt-get install -y bash sudo netcat-openbsd libssl-dev ca-certificates
COPY --chmod=700 ./entrypoint.sh /opt/zoraxy/
COPY --from=build-zoraxy /usr/local/bin/zoraxy /usr/local/bin/zoraxy
COPY --from=build-zerotier /usr/local/bin/zerotier-one /usr/local/bin/zerotier-one
WORKDIR /opt/zoraxy/config/
ENV VERSION=$VERSION
ENV ZEROTIER="false"
ENV ARGS="-noauth=false"
ENV AUTORENEW="86400"
ENV CFGUPGRADE="true"
ENV DOCKER="true"
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 ["/opt/zoraxy/entrypoint.sh"]
VOLUME [ "/opt/zoraxy/config/" ]
HEALTHCHECK --interval=5s --timeout=5s --retries=2 CMD nc -vz 127.0.0.1 8000 || exit 1
ENTRYPOINT [ "/opt/zoraxy/entrypoint.sh" ]
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 CMD nc -vz 127.0.0.1 $PORT || exit 1

View File

@ -1,65 +1,95 @@
# [zoraxy](https://github.com/tobychui/zoraxy/) </br>
# Zoraxy Docker
[![Repo](https://img.shields.io/badge/Docker-Repo-007EC6?labelColor-555555&color-007EC6&logo=docker&logoColor=fff&style=flat-square)](https://hub.docker.com/r/zoraxydocker/zoraxy)
[![Version](https://img.shields.io/docker/v/zoraxydocker/zoraxy/latest?labelColor-555555&color-007EC6&style=flat-square)](https://hub.docker.com/r/zoraxydocker/zoraxy)
[![Size](https://img.shields.io/docker/image-size/zoraxydocker/zoraxy/latest?sort=semver&labelColor-555555&color-007EC6&style=flat-square)](https://hub.docker.com/r/zoraxydocker/zoraxy)
[![Pulls](https://img.shields.io/docker/pulls/zoraxydocker/zoraxy?labelColor-555555&color-007EC6&style=flat-square)](https://hub.docker.com/r/zoraxydocker/zoraxy)
## Setup: </br>
Although not required, it is recommended to give Zoraxy a dedicated location on the host to mount the container. That way, the host/user can access them whenever needed. A volume will be created automatically within Docker if a location is not specified. </br>
## Usage
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>
If you are attempting to access your service from outside your network, make sure to forward ports 80 and 443 to the Zoraxy host to allow web traffic. 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. Read more about it from [whatismyip](https://www.whatismyip.com/port-forwarding/).
In the examples below, make sure to update `/path/to/zoraxy/config/` with your actual path. If a path is not provided, a Docker volume will be created at the location but it is recommended to store the data at a defined host location.
Once setup, access the webui at `http://<host-ip>:8000` to configure Zoraxy. Change the port in the URL if you changed the management port.
### Docker Run
### 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 zoraxy \
--restart unless-stopped \
-p 80:80 \
-p 443:443 \
-p 8000:8000 \
-v /path/to/zoraxy/config/:/opt/zoraxy/config/ \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc/localtime:/etc/localtime \
-e FASTGEOIP="true" \
-e ZEROTIER="true" \
zoraxydocker/zoraxy:latest
```
### Using Docker Compose </br>
### Docker Compose
```yml
version: '3.3'
services:
zoraxy-docker:
image: zoraxydocker/zoraxy:latest
container_name: (container name)
ports:
- 80:80
- 443:443
- (external):8000
volumes:
- (path to storage directory):/opt/zoraxy/config/
environment:
ARGS: '(your arguments)'
```
| Operator | Need | Details |
|:-|:-|:-|
| `-d` | Yes | will run the container in the background. |
| `--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. |
| `-v (path to storage directory):/opt/zoraxy/config/` | Recommend | Sets the folder that holds your files. This should be the place you just chose. By default, it will create a Docker volume for the files for persistency but they will not be accessible. |
| `-e ARGS='(your arguments)'` | No | Sets the arguments to run Zoraxy with. Enter them as you would normally. By default, it is ran with `-noauth=false` but <b>you cannot change the management port.</b> This is required for the healthcheck to work. |
| `zoraxydocker/zoraxy:latest` | Yes | The repository on Docker hub. By default, it is the latest version that is published. |
## Examples: </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 Compose </br>
```yml
version: '3.3'
services:
zoraxy-docker:
zoraxy:
image: zoraxydocker/zoraxy:latest
container_name: zoraxy
restart: unless-stopped
ports:
- 80:80
- 443:443
- 8005:8000/tcp
- 8000:8000
volumes:
- /home/docker/Containers/Zoraxy:/opt/zoraxy/config/
- /path/to/zoraxy/config/:/opt/zoraxy/config/
- /var/run/docker.sock:/var/run/docker.sock
- /etc/localtime:/etc/localtime
environment:
ARGS: '-noauth=false'
FASTGEOIP: "true"
ZEROTIER: "true"
```
### Ports
| Port | Details |
|:-|:-|
| `80` | HTTP traffic. |
| `443` | HTTPS traffic. |
| `8000` | Management interface. Can be changed with the `PORT` env. |
### Volumes
| Volume | Details |
|:-|:-|
| `/opt/zoraxy/config/` | Zoraxy configuration. |
| `/var/run/docker.sock` | Docker socket. Used for additional functionality with Zoraxy. |
| `/etc/localtime` | Localtime. Set to ensure the host and container are synchronized. |
### Environment
Variables are the same as those in [Start Parameters](https://github.com/tobychui/zoraxy?tab=readme-ov-file#start-paramters).
| Variable | Default | Details |
|:-|:-|:-|
| `AUTORENEW` | `86400` (Integer) | ACME auto TLS/SSL certificate renew check interval. |
| `CFGUPGRADE` | `true` (Boolean) | Enable auto config upgrade if breaking change is detected. |
| `DOCKER` | `true` (Boolean) | Run Zoraxy in docker compatibility mode. |
| `EARLYRENEW` | `30` (Integer) | Number of days to early renew a soon expiring certificate. |
| `FASTGEOIP` | `false` (Boolean) | Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices). |
| `MDNS` | `true` (Boolean) | Enable mDNS scanner and transponder. |
| `MDNSNAME` | `''` (String) | mDNS name, leave empty to use default (zoraxy_{node-uuid}.local). |
| `NOAUTH` | `false` (Boolean) | Disable authentication for management interface. |
| `PORT` | `8000` (Integer) | Management web interface listening port |
| `SSHLB` | `false` (Boolean) | Allow loopback web ssh connection (DANGER). |
| `VERSION` | `false` (Boolean) | Show version of this server. |
| `WEBFM` | `true` (Boolean) | Enable web file manager for static web server root folder. |
| `WEBROOT` | `./www` (String) | Static web server root folder. Only allow change in start parameters. |
| `ZEROTIER` | `false` (Boolean) | Enable ZeroTier functionality for GAN. |
| `ZTAUTH` | `""` (String) | ZeroTier authtoken for the local node. |
| `ZTPORT` | `9993` (Integer) | ZeroTier controller API port. |
> [!IMPORTANT]
> Contrary to the Zoraxy README, Docker usage of the port flag should NOT include the colon. Ex: `-e PORT="8000"` for Docker run and `PORT: "8000"` for Docker compose.

View File

@ -1,4 +1,32 @@
#!/usr/bin/env bash
echo "Zoraxy version $VERSION"
zoraxy -port=:8000 ${ARGS}
update-ca-certificates
echo "CA certificates updated"
if [ "$ZEROTIER" = "true" ]; then
if [ ! -d "/opt/zoraxy/config/zerotier/" ]; then
mkdir -p /opt/zoraxy/config/zerotier/
fi
ln -s /opt/zoraxy/config/zerotier/ /var/lib/zerotier-one
zerotier-one -d
echo "ZeroTier daemon started"
fi
echo "Starting Zoraxy..."
exec zoraxy \
-autorenew="$AUTORENEW" \
-cfgupgrade="$CFGUPGRADE" \
-docker="$DOCKER" \
-earlyrenew="$EARLYRENEW" \
-fastgeoip="$FASTGEOIP" \
-mdns="$MDNS" \
-mdnsname="$MDNSNAME" \
-noauth="$NOAUTH" \
-port=:"$PORT" \
-sshlb="$SSHLB" \
-version="$VERSION" \
-webfm="$WEBFM" \
-webroot="$WEBROOT" \
-ztauth="$ZTAUTH" \
-ztport="$ZTPORT"

View File

@ -1 +1 @@
zoraxy.arozos.com
zoraxy.aroz.org

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 MiB

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m772-635-43-100-104-46 104-45 43-95 43 95 104 45-104 46-43 100Zm0 595-43-96-104-45 104-45 43-101 43 101 104 45-104 45-43 96ZM333-194l-92-197-201-90 201-90 92-196 93 196 200 90-200 90-93 197Zm0-148 48-96 98-43-98-43-48-96-47 96-99 43 99 43 47 96Zm0-139Z"/></svg>
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m772-635-43-100-104-46 104-45 43-95 43 95 104 45-104 46-43 100Zm0 595-43-96-104-45 104-45 43-101 43 101 104 45-104 45-43 96ZM333-194l-92-197-201-90 201-90 92-196 93 196 200 90-200 90-93 197Zm0-148 48-96 98-43-98-43-48-96-47 96-99 43 99 43 47 96Zm0-139Z"/></svg>

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 377 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M280-453h400v-60H280v60ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>
<svg fill="#ff7a7a" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M280-453h400v-60H280v60ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>

Before

Width:  |  Height:  |  Size: 433 B

After

Width:  |  Height:  |  Size: 448 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M320-242 80-482l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"/></svg>
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M320-242 80-482l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"/></svg>

Before

Width:  |  Height:  |  Size: 209 B

After

Width:  |  Height:  |  Size: 227 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-80v-270h120v-160h210v-100H330v-270h300v270H510v100h210v160h120v270H540v-270h120v-100H300v100h120v270H120Zm270-590h180v-150H390v150ZM180-140h180v-150H180v150Zm420 0h180v-150H600v150ZM480-670ZM360-290Zm240 0Z"/></svg>
<svg fill="#919191" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-80v-270h120v-160h210v-100H330v-270h300v270H510v100h210v160h120v270H540v-270h120v-100H300v100h120v270H120Zm270-590h180v-150H390v150ZM180-140h180v-150H180v150Zm420 0h180v-150H600v150ZM480-670ZM360-290Zm240 0Z"/></svg>

Before

Width:  |  Height:  |  Size: 317 B

After

Width:  |  Height:  |  Size: 332 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>

Before

Width:  |  Height:  |  Size: 224 B

After

Width:  |  Height:  |  Size: 242 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M356-120H180q-24 0-42-18t-18-42v-176q44-5 75.5-34.5T227-463q0-43-31.5-72.5T120-570v-176q0-24 18-42t42-18h177q11-40 39.5-67t68.5-27q40 0 68.5 27t39.5 67h173q24 0 42 18t18 42v173q40 11 65.5 41.5T897-461q0 40-25.5 67T806-356v176q0 24-18 42t-42 18H570q-5-48-35.5-77.5T463-227q-41 0-71.5 29.5T356-120Zm-176-60h130q25-61 69.888-84 44.888-23 83-23T546-264q45 23 70 84h130v-235h45q20 0 33-13t13-33q0-20-13-33t-33-13h-45v-239H511v-48q0-20-13-33t-33-13q-20 0-33 13t-13 33v48H180v130q48.15 17.817 77.575 59.686Q287-514.445 287-462.777 287-412 257.5-370T180-310v130Zm329-330Z"/></svg>
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M356-120H180q-24 0-42-18t-18-42v-176q44-5 75.5-34.5T227-463q0-43-31.5-72.5T120-570v-176q0-24 18-42t42-18h177q11-40 39.5-67t68.5-27q40 0 68.5 27t39.5 67h173q24 0 42 18t18 42v173q40 11 65.5 41.5T897-461q0 40-25.5 67T806-356v176q0 24-18 42t-42 18H570q-5-48-35.5-77.5T463-227q-41 0-71.5 29.5T356-120Zm-176-60h130q25-61 69.888-84 44.888-23 83-23T546-264q45 23 70 84h130v-235h45q20 0 33-13t13-33q0-20-13-33t-33-13h-45v-239H511v-48q0-20-13-33t-33-13q-20 0-33 13t-13 33v48H180v130q48.15 17.817 77.575 59.686Q287-514.445 287-462.777 287-412 257.5-370T180-310v130Zm329-330Z"/></svg>

Before

Width:  |  Height:  |  Size: 669 B

After

Width:  |  Height:  |  Size: 688 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M273-160 80-353l193-193 42 42-121 121h316v60H194l121 121-42 42Zm414-254-42-42 121-121H450v-60h316L645-758l42-42 193 193-193 193Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48" fill="#fcba03"><path d="M273-160 80-353l193-193 42 42-121 121h316v60H194l121 121-42 42Zm414-254-42-42 121-121H450v-60h316L645-758l42-42 193 193-193 193Z"/></svg>

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 249 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M700-160v-410H275l153 153-42 43-226-226 226-226 42 42-153 154h485v470h-60Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="#0388fc" height="48" viewBox="0 -960 960 960" width="48"><path d="M700-160v-410H275l153 153-42 43-226-226 226-226 42 42-153 154h485v470h-60Z"/></svg>

Before

Width:  |  Height:  |  Size: 180 B

After

Width:  |  Height:  |  Size: 195 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M197-197q-54-54-85.5-126.5T80-480q0-84 31.5-156.5T197-763l43 43q-46 46-73 107.5T140-480q0 71 26.5 132T240-240l-43 43Zm113-113q-32-32-51-75.5T240-480q0-51 19-94.5t51-75.5l43 43q-24 24-38.5 56.5T300-480q0 38 14 70t39 57l-43 43Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-43-43q24-24 38.5-56.5T660-480q0-38-14-70t-39-57l43-43q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-43-43q46-46 73-107.5T820-480q0-71-26.5-132T720-720l43-43q54 55 85.5 127.5T880-480q0 83-31.5 155.5T763-197Z"/></svg>
<svg fill="#83f2c4" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M197-197q-54-54-85.5-126.5T80-480q0-84 31.5-156.5T197-763l43 43q-46 46-73 107.5T140-480q0 71 26.5 132T240-240l-43 43Zm113-113q-32-32-51-75.5T240-480q0-51 19-94.5t51-75.5l43 43q-24 24-38.5 56.5T300-480q0 38 14 70t39 57l-43 43Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-43-43q24-24 38.5-56.5T660-480q0-38-14-70t-39-57l43-43q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-43-43q46-46 73-107.5T820-480q0-71-26.5-132T720-720l43-43q54 55 85.5 127.5T880-480q0 83-31.5 155.5T763-197Z"/></svg>

Before

Width:  |  Height:  |  Size: 652 B

After

Width:  |  Height:  |  Size: 667 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M345-377h391L609-548 506-413l-68-87-93 123Zm-85 177q-24 0-42-18t-18-42v-560q0-24 18-42t42-18h560q24 0 42 18t18 42v560q0 24-18 42t-42 18H260Zm0-60h560v-560H260v560ZM140-80q-24 0-42-18t-18-42v-620h60v620h620v60H140Zm120-740v560-560Z"/></svg>
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M345-377h391L609-548 506-413l-68-87-93 123Zm-85 177q-24 0-42-18t-18-42v-560q0-24 18-42t42-18h560q24 0 42 18t18 42v560q0 24-18 42t-42 18H260Zm0-60h560v-560H260v560ZM140-80q-24 0-42-18t-18-42v-620h60v620h620v60H140Zm120-740v560-560Z"/></svg>

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 355 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg>
<svg fill="#edf230" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

View File

@ -8,23 +8,23 @@
<meta name="author" content="tobychui">
<!-- HTML Meta Tags -->
<title>Cluster Proxy Gateway | Zoraxy</title>
<title>Reverse Proxy Server | Zoraxy</title>
<meta name="description" content="A reverse proxy server and cluster network gateway for noobs">
<!-- Facebook Meta Tags -->
<meta property="og:url" content="https://zoraxy.arozos.com/">
<meta property="og:url" content="https://zoraxy.aroz.org/">
<meta property="og:type" content="website">
<meta property="og:title" content="Cluster Proxy Gateway | Zoraxy">
<meta property="og:description" content="A reverse proxy server and cluster network gateway for noobs">
<meta property="og:image" content="https://zoraxy.arozos.com/img/og.png">
<meta property="og:image" content="https://zoraxy.aroz.org/img/og.png">
<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image">
<meta property="twitter:domain" content="arozos.com">
<meta property="twitter:url" content="https://zoraxy.arozos.com/">
<meta property="twitter:domain" content="aroz.org">
<meta property="twitter:url" content="https://zoraxy.aroz.org/">
<meta name="twitter:title" content="Cluster Proxy Gateway | Zoraxy">
<meta name="twitter:description" content="A reverse proxy server and cluster network gateway for noobs">
<meta name="twitter:image" content="https://zoraxy.arozos.com/img/og.png">
<meta name="twitter:image" content="https://zoraxy.aroz.org/img/og.png">
<!-- Favicons -->
<link href="favicon.png" rel="icon">
@ -74,21 +74,16 @@
</div>
<div class="right-content">
<!-- Hero Banner Section -->
<div class="dot-container">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
<div class="headbanner"></div>
<div id="home" class="herotext">
<div class="ui basic segment">
<div class="bannerHeaderWrapper">
<h1 class="bannerHeader">Zoraxy</h1>
<p class="bannerSubheader">All in one homelab network routing solution</p>
<div class="ui divider"></div><br>
<p class="bannerSubheader">Beyond Reverse Proxy: Your Ultimate Homelab Network Tool</p>
</div>
<br><br>
<a class="ui black big button" href="#features">Learn More</a>
<a class="ui basic big button" style="background-color: white;" href="#features"><i class="ui blue arrow down icon"></i> Learn More</a>
<br><br>
<table class="ui very basic collapsing unstackable celled table">
<thead>
@ -126,6 +121,22 @@
</tr>
</table>
</div>
<div id="wavesWrapper">
<!-- CSS waves-->
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
<defs>
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
</defs>
<g class="parallax">
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(255,255,255,0.7" />
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(255,255,255,0.5)" />
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(255,255,255,0.3)" />
<use xlink:href="#gentle-wave" x="48" y="7" fill="#fff" />
</g>
</svg>
</div>
</div>
<!-- Features -->
@ -240,34 +251,34 @@
<div class="ui three column stackable grid">
<div class="column">
<a href="img/screenshots/1.webp" target="_blank"><img src="img/screenshots/1.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/1.png" target="_blank"><img src="img/screenshots/1.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/2.webp" target="_blank"><img src="img/screenshots/2.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/2.png" target="_blank"><img src="img/screenshots/2.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/3.webp" target="_blank"><img src="img/screenshots/3.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/3.png" target="_blank"><img src="img/screenshots/3.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/4.webp" target="_blank"><img src="img/screenshots/4.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/4.png" target="_blank"><img src="img/screenshots/4.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/5.webp" target="_blank"><img src="img/screenshots/5.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/5.png" target="_blank"><img src="img/screenshots/5.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/6.webp" target="_blank"><img src="img/screenshots/6.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/6.png" target="_blank"><img src="img/screenshots/6.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/7.webp" target="_blank"><img src="img/screenshots/7.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/7.png" target="_blank"><img src="img/screenshots/7.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/8.webp" target="_blank"><img src="img/screenshots/8.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/8.png" target="_blank"><img src="img/screenshots/8.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/9.webp" target="_blank"><img src="img/screenshots/9.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/9.png" target="_blank"><img src="img/screenshots/9.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/10.webp" target="_blank"><img src="img/screenshots/10.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/10.png" target="_blank"><img src="img/screenshots/10.png" class="ui fluid image screenshot"></a>
</div>
</div>
</div>

View File

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

26
example/README.md Normal file
View File

@ -0,0 +1,26 @@
# Example www Folder
This is an example www folder that contains two sub-folders.
- `html/`
- `templates/`
The html file contain static resources that will be served by Zoraxy build-in static web server. You can use it as a generic web server with a static site generator like [Hugo](https://gohugo.io/) or use it as a small CDN for serving your scripts / image that commonly use across many of your sites.
The templates folder contains the template for overriding the build in error or access denied pages. The following templates are supported
- notfound.html (Default site Not-Found error page)
- whitelist.html (Error page when client being blocked by whitelist rule)
- blacklist.html (Error page when client being blocked by blacklist rule)
To use the template, copy and paste the `wwww` folder to the same directory as zoraxy executable (aka the src/ file if you `go build` with the current folder tree).
### Other Templates
There are a few pre-built templates that works with Zoraxy where you can find in the `other-templates` folder. Copy the folder into `www` and rename the folder to `templates` to active them.
It is worth mentioning that the uwu icons for not-found and access-denied are created by @SAWARATSUKI

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

229
example/www/html/index.html Normal file
View File

@ -0,0 +1,229 @@
<html>
<head>
<title>Zoraxy Firework!</title>
<style>
body{
margin: 0 !important;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js" integrity="sha512-aNMyYYxdIxIaot0Y1/PLuEu3eipGCmsEUBrUq+7aVyPGMFH8z0eTP0tkqAvv34fzN6z+201d3T8HPb1svWSKHQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<canvas id="c"></canvas>
<script>
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cH;
var cW;
var bgColor = "#FF6138";
var animations = [];
var circles = [];
var colorPicker = (function() {
var colors = ["#FF6138", "#FFBE53", "#2980B9", "#FCFCFC", "#282741"];
var index = 0;
function next() {
index = index++ < colors.length-1 ? index : 0;
return colors[index];
}
function current() {
return colors[index]
}
return {
next: next,
current: current
}
})();
function removeAnimation(animation) {
var index = animations.indexOf(animation);
if (index > -1) animations.splice(index, 1);
}
function calcPageFillRadius(x, y) {
var l = Math.max(x - 0, cW - x);
var h = Math.max(y - 0, cH - y);
return Math.sqrt(Math.pow(l, 2) + Math.pow(h, 2));
}
function addClickListeners() {
document.addEventListener("touchstart", handleEvent);
document.addEventListener("mousedown", handleEvent);
};
function handleEvent(e) {
if (e.touches) {
e.preventDefault();
e = e.touches[0];
}
var currentColor = colorPicker.current();
var nextColor = colorPicker.next();
var targetR = calcPageFillRadius(e.pageX, e.pageY);
var rippleSize = Math.min(200, (cW * .4));
var minCoverDuration = 750;
var pageFill = new Circle({
x: e.pageX,
y: e.pageY,
r: 0,
fill: nextColor
});
var fillAnimation = anime({
targets: pageFill,
r: targetR,
duration: Math.max(targetR / 2 , minCoverDuration ),
easing: "easeOutQuart",
complete: function(){
bgColor = pageFill.fill;
removeAnimation(fillAnimation);
}
});
var ripple = new Circle({
x: e.pageX,
y: e.pageY,
r: 0,
fill: currentColor,
stroke: {
width: 3,
color: currentColor
},
opacity: 1
});
var rippleAnimation = anime({
targets: ripple,
r: rippleSize,
opacity: 0,
easing: "easeOutExpo",
duration: 900,
complete: removeAnimation
});
var particles = [];
for (var i=0; i<32; i++) {
var particle = new Circle({
x: e.pageX,
y: e.pageY,
fill: currentColor,
r: anime.random(24, 48)
})
particles.push(particle);
}
var particlesAnimation = anime({
targets: particles,
x: function(particle){
return particle.x + anime.random(rippleSize, -rippleSize);
},
y: function(particle){
return particle.y + anime.random(rippleSize * 1.15, -rippleSize * 1.15);
},
r: 0,
easing: "easeOutExpo",
duration: anime.random(1000,1300),
complete: removeAnimation
});
animations.push(fillAnimation, rippleAnimation, particlesAnimation);
}
function extend(a, b){
for(var key in b) {
if(b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
return a;
}
var Circle = function(opts) {
extend(this, opts);
}
Circle.prototype.draw = function() {
ctx.globalAlpha = this.opacity || 1;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
if (this.stroke) {
ctx.strokeStyle = this.stroke.color;
ctx.lineWidth = this.stroke.width;
ctx.stroke();
}
if (this.fill) {
ctx.fillStyle = this.fill;
ctx.fill();
}
ctx.closePath();
ctx.globalAlpha = 1;
}
var animate = anime({
duration: Infinity,
update: function() {
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, cW, cH);
animations.forEach(function(anim) {
anim.animatables.forEach(function(animatable) {
animatable.target.draw();
});
});
}
});
var resizeCanvas = function() {
cW = window.innerWidth;
cH = window.innerHeight;
c.width = cW * devicePixelRatio;
c.height = cH * devicePixelRatio;
ctx.scale(devicePixelRatio, devicePixelRatio);
};
(function init() {
resizeCanvas();
if (window.CP) {
// CodePen's loop detection was causin' problems
// and I have no idea why, so...
window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 6000;
}
window.addEventListener("resize", resizeCanvas);
addClickListeners();
if (!!window.location.pathname.match(/fullcpgrid/)) {
startFauxClicking();
}
handleInactiveUser();
})();
function handleInactiveUser() {
var inactive = setTimeout(function(){
fauxClick(cW/2, cH/2);
}, 2000);
function clearInactiveTimeout() {
clearTimeout(inactive);
document.removeEventListener("mousedown", clearInactiveTimeout);
document.removeEventListener("touchstart", clearInactiveTimeout);
}
document.addEventListener("mousedown", clearInactiveTimeout);
document.addEventListener("touchstart", clearInactiveTimeout);
}
function startFauxClicking() {
setTimeout(function(){
fauxClick(anime.random( cW * .2, cW * .8), anime.random(cH * .2, cH * .8));
startFauxClicking();
}, anime.random(200, 900));
}
function fauxClick(x, y) {
var fauxClick = new Event("mousedown");
fauxClick.pageX = x;
fauxClick.pageY = y;
document.dispatchEvent(fauxClick);
}
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

View File

@ -19,7 +19,7 @@ clean:
$(PLATFORMS):
@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

View File

@ -3,9 +3,12 @@ package main
import (
"encoding/json"
"net/http"
"strings"
strip "github.com/grokify/html-strip-tags-go"
"imuslab.com/zoraxy/mod/geodb"
"github.com/google/uuid"
"github.com/microcosm-cc/bluemonday"
"imuslab.com/zoraxy/mod/access"
"imuslab.com/zoraxy/mod/utils"
)
@ -17,6 +20,157 @@ import (
banning / whitelist a specific IP address or country code
*/
/*
General Function
*/
func handleListAccessRules(w http.ResponseWriter, r *http.Request) {
allAccessRules := accessController.ListAllAccessRules()
js, _ := json.Marshal(allAccessRules)
utils.SendJSONResponse(w, string(js))
}
func handleAttachRuleToHost(w http.ResponseWriter, r *http.Request) {
ruleid, err := utils.PostPara(r, "id")
if err != nil {
utils.SendErrorResponse(w, "invalid rule name")
return
}
host, err := utils.PostPara(r, "host")
if err != nil {
utils.SendErrorResponse(w, "invalid rule name")
return
}
//Check if access rule and proxy rule exists
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(host)
if err != nil {
utils.SendErrorResponse(w, "invalid host given")
return
}
if !accessController.AccessRuleExists(ruleid) {
utils.SendErrorResponse(w, "access rule not exists")
return
}
//Update the proxy host acess rule id
targetProxyEndpoint.AccessFilterUUID = ruleid
targetProxyEndpoint.UpdateToRuntime()
err = SaveReverseProxyConfig(targetProxyEndpoint)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
// Create a new access rule, require name and desc only
func handleCreateAccessRule(w http.ResponseWriter, r *http.Request) {
ruleName, err := utils.PostPara(r, "name")
if err != nil {
utils.SendErrorResponse(w, "invalid rule name")
return
}
ruleDesc, _ := utils.PostPara(r, "desc")
//Filter out injection if any
p := bluemonday.StripTagsPolicy()
ruleName = p.Sanitize(ruleName)
ruleDesc = p.Sanitize(ruleDesc)
ruleUUID := uuid.New().String()
newAccessRule := access.AccessRule{
ID: ruleUUID,
Name: ruleName,
Desc: ruleDesc,
BlacklistEnabled: false,
WhitelistEnabled: false,
}
//Add it to runtime
err = accessController.AddNewAccessRule(&newAccessRule)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
// Handle removing an access rule. All proxy endpoint using this rule will be
// set to use the default rule
func handleRemoveAccessRule(w http.ResponseWriter, r *http.Request) {
ruleID, err := utils.PostPara(r, "id")
if err != nil {
utils.SendErrorResponse(w, "invalid rule id given")
return
}
if ruleID == "default" {
utils.SendErrorResponse(w, "default access rule cannot be removed")
return
}
ruleID = strings.TrimSpace(ruleID)
//Set all proxy hosts that use this access rule back to using "default"
allProxyEndpoints := dynamicProxyRouter.GetProxyEndpointsAsMap()
for _, proxyEndpoint := range allProxyEndpoints {
if strings.EqualFold(proxyEndpoint.AccessFilterUUID, ruleID) {
//This proxy endpoint is using the current access filter.
//set it to default
proxyEndpoint.AccessFilterUUID = "default"
proxyEndpoint.UpdateToRuntime()
err = SaveReverseProxyConfig(proxyEndpoint)
if err != nil {
SystemWideLogger.PrintAndLog("Access", "Unable to save updated proxy endpoint "+proxyEndpoint.RootOrMatchingDomain, err)
} else {
SystemWideLogger.PrintAndLog("Access", "Updated "+proxyEndpoint.RootOrMatchingDomain+" access filter to \"default\"", nil)
}
}
}
//Remove the access rule by ID
err = accessController.RemoveAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
SystemWideLogger.PrintAndLog("Access", "Access Rule "+ruleID+" removed", nil)
utils.SendOK(w)
}
// Only the name and desc, for other properties use blacklist / whitelist api
func handleUpadateAccessRule(w http.ResponseWriter, r *http.Request) {
ruleID, err := utils.PostPara(r, "id")
if err != nil {
utils.SendErrorResponse(w, "invalid rule id")
return
}
ruleName, err := utils.PostPara(r, "name")
if err != nil {
utils.SendErrorResponse(w, "invalid rule name")
return
}
ruleDesc, _ := utils.PostPara(r, "desc")
//Filter anything weird
p := bluemonday.StrictPolicy()
ruleName = p.Sanitize(ruleName)
ruleDesc = p.Sanitize(ruleDesc)
err = accessController.UpdateAccessRule(ruleID, ruleName, ruleDesc)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
/*
Blacklist Related
*/
@ -28,11 +182,24 @@ func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
bltype = "country"
}
ruleID, err := utils.GetPara(r, "id")
if err != nil {
//Use default if not set
ruleID = "default"
}
//Load the target rule from access controller
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
resulst := []string{}
if bltype == "country" {
resulst = geodbStore.GetAllBlacklistedCountryCode()
resulst = rule.GetAllBlacklistedCountryCode()
} else if bltype == "ip" {
resulst = geodbStore.GetAllBlacklistedIp()
resulst = rule.GetAllBlacklistedIp()
}
js, _ := json.Marshal(resulst)
@ -47,7 +214,33 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
return
}
geodbStore.AddCountryCodeToBlackList(countryCode)
ruleID, err := utils.PostPara(r, "id")
if err != nil {
ruleID = "default"
}
comment, _ := utils.PostPara(r, "comment")
p := bluemonday.StripTagsPolicy()
comment = p.Sanitize(comment)
//Load the target rule from access controller
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Check if the country code contains comma, if yes, split it
if strings.Contains(countryCode, ",") {
codes := strings.Split(countryCode, ",")
for _, code := range codes {
code = strings.TrimSpace(code)
rule.AddCountryCodeToBlackList(code, comment)
}
} else {
countryCode = strings.TrimSpace(countryCode)
rule.AddCountryCodeToBlackList(countryCode, comment)
}
utils.SendOK(w)
}
@ -59,7 +252,29 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
return
}
geodbStore.RemoveCountryCodeFromBlackList(countryCode)
ruleID, err := utils.PostPara(r, "id")
if err != nil {
ruleID = "default"
}
//Load the target rule from access controller
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Check if the country code contains comma, if yes, split it
if strings.Contains(countryCode, ",") {
codes := strings.Split(countryCode, ",")
for _, code := range codes {
code = strings.TrimSpace(code)
rule.RemoveCountryCodeFromBlackList(code)
}
} else {
countryCode = strings.TrimSpace(countryCode)
rule.RemoveCountryCodeFromBlackList(countryCode)
}
utils.SendOK(w)
}
@ -71,7 +286,24 @@ func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
return
}
geodbStore.AddIPToBlackList(ipAddr)
ruleID, err := utils.PostPara(r, "id")
if err != nil {
ruleID = "default"
}
//Load the target rule from access controller
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
comment, _ := utils.GetPara(r, "comment")
p := bluemonday.StripTagsPolicy()
comment = p.Sanitize(comment)
rule.AddIPToBlackList(ipAddr, comment)
utils.SendOK(w)
}
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
@ -81,23 +313,46 @@ func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
return
}
geodbStore.RemoveIPFromBlackList(ipAddr)
ruleID, err := utils.PostPara(r, "id")
if err != nil {
ruleID = "default"
}
//Load the target rule from access controller
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
rule.RemoveIPFromBlackList(ipAddr)
utils.SendOK(w)
}
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
enable, err := utils.PostPara(r, "enable")
enable, _ := utils.PostPara(r, "enable")
ruleID, err := utils.PostPara(r, "id")
if err != nil {
//Return the current enabled state
currentEnabled := geodbStore.BlacklistEnabled
ruleID = "default"
}
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
if enable == "" {
//enable paramter not set
currentEnabled := rule.BlacklistEnabled
js, _ := json.Marshal(currentEnabled)
utils.SendJSONResponse(w, string(js))
} else {
if enable == "true" {
geodbStore.ToggleBlacklist(true)
rule.ToggleBlacklist(true)
} else if enable == "false" {
geodbStore.ToggleBlacklist(false)
rule.ToggleBlacklist(false)
} else {
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
return
@ -117,11 +372,22 @@ func handleListWhitelisted(w http.ResponseWriter, r *http.Request) {
bltype = "country"
}
resulst := []*geodb.WhitelistEntry{}
ruleID, err := utils.GetPara(r, "id")
if err != nil {
ruleID = "default"
}
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
resulst := []*access.WhitelistEntry{}
if bltype == "country" {
resulst = geodbStore.GetAllWhitelistedCountryCode()
resulst = rule.GetAllWhitelistedCountryCode()
} else if bltype == "ip" {
resulst = geodbStore.GetAllWhitelistedIp()
resulst = rule.GetAllWhitelistedIp()
}
js, _ := json.Marshal(resulst)
@ -136,10 +402,32 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
return
}
comment, _ := utils.PostPara(r, "comment")
comment = strip.StripTags(comment)
ruleID, err := utils.PostPara(r, "id")
if err != nil {
ruleID = "default"
}
geodbStore.AddCountryCodeToWhitelist(countryCode, comment)
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
comment, _ := utils.PostPara(r, "comment")
p := bluemonday.StrictPolicy()
comment = p.Sanitize(comment)
//Check if the country code contains comma, if yes, split it
if strings.Contains(countryCode, ",") {
codes := strings.Split(countryCode, ",")
for _, code := range codes {
code = strings.TrimSpace(code)
rule.AddCountryCodeToWhitelist(code, comment)
}
} else {
countryCode = strings.TrimSpace(countryCode)
rule.AddCountryCodeToWhitelist(countryCode, comment)
}
utils.SendOK(w)
}
@ -151,7 +439,28 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
return
}
geodbStore.RemoveCountryCodeFromWhitelist(countryCode)
ruleID, err := utils.PostPara(r, "id")
if err != nil {
ruleID = "default"
}
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Check if the country code contains comma, if yes, split it
if strings.Contains(countryCode, ",") {
codes := strings.Split(countryCode, ",")
for _, code := range codes {
code = strings.TrimSpace(code)
rule.RemoveCountryCodeFromWhitelist(code)
}
} else {
countryCode = strings.TrimSpace(countryCode)
rule.RemoveCountryCodeFromWhitelist(countryCode)
}
utils.SendOK(w)
}
@ -163,10 +472,23 @@ func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
return
}
comment, _ := utils.PostPara(r, "comment")
comment = strip.StripTags(comment)
ruleID, err := utils.PostPara(r, "id")
if err != nil {
ruleID = "default"
}
geodbStore.AddIPToWhiteList(ipAddr, comment)
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
comment, _ := utils.PostPara(r, "comment")
p := bluemonday.StrictPolicy()
comment = p.Sanitize(comment)
rule.AddIPToWhiteList(ipAddr, comment)
utils.SendOK(w)
}
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
@ -176,23 +498,45 @@ func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
return
}
geodbStore.RemoveIPFromWhiteList(ipAddr)
ruleID, err := utils.PostPara(r, "id")
if err != nil {
ruleID = "default"
}
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
rule.RemoveIPFromWhiteList(ipAddr)
utils.SendOK(w)
}
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
enable, err := utils.PostPara(r, "enable")
enable, _ := utils.PostPara(r, "enable")
ruleID, err := utils.PostPara(r, "id")
if err != nil {
ruleID = "default"
}
rule, err := accessController.GetAccessRuleByID(ruleID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
if enable == "" {
//Return the current enabled state
currentEnabled := geodbStore.WhitelistEnabled
currentEnabled := rule.WhitelistEnabled
js, _ := json.Marshal(currentEnabled)
utils.SendJSONResponse(w, string(js))
} else {
if enable == "true" {
geodbStore.ToggleWhitelist(true)
rule.ToggleWhitelist(true)
} else if enable == "false" {
geodbStore.ToggleWhitelist(false)
rule.ToggleWhitelist(false)
} else {
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
return

View File

@ -38,7 +38,21 @@ func initACME() *acme.ACMEHandler {
port = getRandomPort(30000)
}
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port))
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb, SystemWideLogger)
}
// Restart ACME handler and auto renewer
func restartACMEHandler() {
SystemWideLogger.Println("Restarting ACME handler")
//Clos the current handler and auto renewer
acmeHandler.Close()
acmeAutoRenewer.Close()
acmeDeregisterSpecialRoutingRule()
//Reinit the handler with a new random port
acmeHandler = initACME()
acmeRegisterSpecialRoutingRule()
}
// create the special routing rule for ACME
@ -82,40 +96,69 @@ func acmeRegisterSpecialRoutingRule() {
}
}
// remove the special routing rule for ACME
func acmeDeregisterSpecialRoutingRule() {
SystemWideLogger.Println("Removing ACME routing rule")
dynamicProxyRouter.RemoveRoutingRule("acme-autorenew")
}
// This function check if the renew setup is satisfied. If not, toggle them automatically
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
isForceHttpsRedirectEnabledOriginally := false
if dynamicProxyRouter.Option.Port == 443 {
//Enable port 80 to 443 redirect
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
SystemWideLogger.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests")
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
requireRestorePort80 := false
dnsPara, _ := utils.PostBool(r, "dns")
if !dnsPara {
if dynamicProxyRouter.Option.Port == 443 {
//Check if port 80 is enabled
if !dynamicProxyRouter.Option.ListenOnPort80 {
//Enable port 80 temporarily
SystemWideLogger.PrintAndLog("ACME", "Temporarily enabling port 80 listener to handle ACME request ", nil)
dynamicProxyRouter.UpdatePort80ListenerState(true)
requireRestorePort80 = true
time.Sleep(2 * time.Second)
}
//Enable port 80 to 443 redirect
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
SystemWideLogger.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests")
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
} else {
//Set this to true, so after renew, do not turn it off
isForceHttpsRedirectEnabledOriginally = true
}
} else if dynamicProxyRouter.Option.Port == 80 {
//Go ahead
} else {
//Set this to true, so after renew, do not turn it off
isForceHttpsRedirectEnabledOriginally = true
//This port do not support ACME
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
return
}
} else if dynamicProxyRouter.Option.Port == 80 {
//Go ahead
} else {
//This port do not support ACME
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
}
//Add a 3 second delay to make sure everything is settle down
time.Sleep(3 * time.Second)
//Add a 2 second delay to make sure everything is settle down
time.Sleep(2 * time.Second)
// Pass over to the acmeHandler to deal with the communication
acmeHandler.HandleRenewCertificate(w, r)
if dynamicProxyRouter.Option.Port == 443 {
if !isForceHttpsRedirectEnabledOriginally {
//Default is off. Turn the redirection off
SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil)
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
}
//Update the TLS cert store buffer
tlsCertManager.UpdateLoadedCertList()
//Restore original settings
if requireRestorePort80 {
//Restore port 80 listener
SystemWideLogger.PrintAndLog("ACME", "Restoring previous port 80 listener settings", nil)
dynamicProxyRouter.UpdatePort80ListenerState(false)
}
if !isForceHttpsRedirectEnabledOriginally {
//Default is off. Turn the redirection off
SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil)
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
}
}
// HandleACMEPreferredCA return the user preferred / default CA for new subdomain auto creation

View File

@ -5,8 +5,11 @@ import (
"net/http"
"net/http/pprof"
"imuslab.com/zoraxy/mod/acme/acmedns"
"imuslab.com/zoraxy/mod/acme/acmewizard"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
"imuslab.com/zoraxy/mod/ipscan"
"imuslab.com/zoraxy/mod/netstat"
"imuslab.com/zoraxy/mod/netutils"
"imuslab.com/zoraxy/mod/utils"
@ -16,105 +19,140 @@ import (
API.go
This file contains all the API called by the web management interface
*/
var requireAuth = true
func initAPIs() {
authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
AuthAgent: authAgent,
RequireAuth: requireAuth,
DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
},
})
//Register the standard web services urls
fs := http.FileServer(http.FS(webres))
if development {
fs = http.FileServer(http.Dir("web/"))
}
//Add a layer of middleware for advance control
advHandler := FSHandler(fs)
http.Handle("/", advHandler)
//Authentication APIs
registerAuthAPIs(requireAuth)
//Reverse proxy
// Register the APIs for HTTP proxy management functions
func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
/* Reverse Proxy Settings & Status */
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
authRouter.HandleFunc("/api/proxy/toggle", ReverseProxyToggleRuleSet)
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
authRouter.HandleFunc("/api/proxy/tlscheck", domainsniff.HandleCheckSiteSupportTLS)
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
//Reverse proxy virtual directory APIs
/* Reverse proxy upstream (load balance) */
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 */
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
authRouter.HandleFunc("/api/proxy/vdir/del", ReverseProxyDeleteVdir)
authRouter.HandleFunc("/api/proxy/vdir/edit", ReverseProxyEditVdir)
//Reverse proxy user define header apis
/* Reverse proxy user-defined header */
authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList)
authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd)
authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove)
//Reverse proxy auth related APIs
authRouter.HandleFunc("/api/proxy/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 */
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
authRouter.HandleFunc("/api/proxy/auth/exceptions/delete", RemoveProxyBasicAuthExceptionPaths)
}
//TLS / SSL config
// Register the APIs for TLS / SSL certificate management functions
func RegisterTLSAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
authRouter.HandleFunc("/api/cert/download", handleCertDownload)
authRouter.HandleFunc("/api/cert/list", handleListCertificate)
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
}
//Redirection config
// Register the APIs for SSO and Oauth functions, WIP
func RegisterSSOAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/sso/status", ssoHandler.HandleSSOStatus)
authRouter.HandleFunc("/api/sso/enable", ssoHandler.HandleSSOEnable)
authRouter.HandleFunc("/api/sso/setPort", ssoHandler.HandlePortChange)
authRouter.HandleFunc("/api/sso/setAuthURL", ssoHandler.HandleSetAuthURL)
authRouter.HandleFunc("/api/sso/app/register", ssoHandler.HandleRegisterApp)
//authRouter.HandleFunc("/api/sso/app/list", ssoHandler.HandleListApp)
//authRouter.HandleFunc("/api/sso/app/remove", ssoHandler.HandleRemoveApp)
authRouter.HandleFunc("/api/sso/user/list", ssoHandler.HandleListUser)
authRouter.HandleFunc("/api/sso/user/add", ssoHandler.HandleAddUser)
authRouter.HandleFunc("/api/sso/user/edit", ssoHandler.HandleEditUser)
authRouter.HandleFunc("/api/sso/user/remove", ssoHandler.HandleRemoveUser)
}
// Register the APIs for redirection rules management functions
func RegisterRedirectionAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
}
//Blacklist APIs
// Register the APIs for access rules management functions
func RegisterAccessRuleAPIs(authRouter *auth.RouterDef) {
/* Access Rules Settings & Status */
authRouter.HandleFunc("/api/access/list", handleListAccessRules)
authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)
/* Blacklist */
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
authRouter.HandleFunc("/api/blacklist/country/remove", handleCountryBlacklistRemove)
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
//Whitelist APIs
/* Whitelist */
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
authRouter.HandleFunc("/api/whitelist/country/remove", handleCountryWhitelistRemove)
authRouter.HandleFunc("/api/whitelist/ip/add", handleIpWhitelistAdd)
authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove)
authRouter.HandleFunc("/api/whitelist/enable", handleWhitelistEnable)
}
//Path Blocker APIs
// Register the APIs for path blocking rules management functions, WIP
func RegisterPathRuleAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/pathrule/add", pathRuleHandler.HandleAddBlockingPath)
authRouter.HandleFunc("/api/pathrule/list", pathRuleHandler.HandleListBlockingPath)
authRouter.HandleFunc("/api/pathrule/remove", pathRuleHandler.HandleRemoveBlockingPath)
}
//Statistic & uptime monitoring API
// Register the APIs statistic anlysis and uptime monitoring functions
func RegisterStatisticalAPIs(authRouter *auth.RouterDef) {
/* Traffic Summary */
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
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/listnic", netstat.HandleListNetworkInterfaces)
/* Zoraxy Analytic */
authRouter.HandleFunc("/api/analytic/list", AnalyticLoader.HandleSummaryList)
authRouter.HandleFunc("/api/analytic/load", AnalyticLoader.HandleLoadTargetDaySummary)
authRouter.HandleFunc("/api/analytic/loadRange", AnalyticLoader.HandleLoadTargetRangeSummary)
authRouter.HandleFunc("/api/analytic/exportRange", AnalyticLoader.HandleRangeExport)
authRouter.HandleFunc("/api/analytic/resetRange", AnalyticLoader.HandleRangeReset)
/* UpTime Monitor */
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
}
//Global Area Network APIs
// Register the APIs for Global Area Network management functions, Will be moving to plugin soon
func RegisterGANAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/gan/network/info", ganManager.HandleGetNodeID)
authRouter.HandleFunc("/api/gan/network/add", ganManager.HandleAddNetwork)
authRouter.HandleFunc("/api/gan/network/remove", ganManager.HandleRemoveNetwork)
@ -129,30 +167,70 @@ func initAPIs() {
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
}
//TCP Proxy
authRouter.HandleFunc("/api/tcpprox/config/add", tcpProxyManager.HandleAddProxyConfig)
authRouter.HandleFunc("/api/tcpprox/config/edit", tcpProxyManager.HandleEditProxyConfigs)
authRouter.HandleFunc("/api/tcpprox/config/list", tcpProxyManager.HandleListConfigs)
authRouter.HandleFunc("/api/tcpprox/config/start", tcpProxyManager.HandleStartProxy)
authRouter.HandleFunc("/api/tcpprox/config/stop", tcpProxyManager.HandleStopProxy)
authRouter.HandleFunc("/api/tcpprox/config/delete", tcpProxyManager.HandleRemoveProxy)
authRouter.HandleFunc("/api/tcpprox/config/status", tcpProxyManager.HandleGetProxyStatus)
authRouter.HandleFunc("/api/tcpprox/config/validate", tcpProxyManager.HandleConfigValidate)
// Register the APIs for Stream (TCP / UDP) Proxy management functions
func RegisterStreamProxyAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
authRouter.HandleFunc("/api/streamprox/config/start", streamProxyManager.HandleStartProxy)
authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy)
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus)
}
//mDNS APIs
// Register the APIs for mDNS service management functions
func RegisterMDNSAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
authRouter.HandleFunc("/api/mdns/discover", HandleMdnsScanning)
}
//Zoraxy Analytic
authRouter.HandleFunc("/api/analytic/list", AnalyticLoader.HandleSummaryList)
authRouter.HandleFunc("/api/analytic/load", AnalyticLoader.HandleLoadTargetDaySummary)
authRouter.HandleFunc("/api/analytic/loadRange", AnalyticLoader.HandleLoadTargetRangeSummary)
authRouter.HandleFunc("/api/analytic/exportRange", AnalyticLoader.HandleRangeExport)
authRouter.HandleFunc("/api/analytic/resetRange", AnalyticLoader.HandleRangeReset)
// Register the APIs for ACME and Auto Renewer management functions
func RegisterACMEAndAutoRenewerAPIs(authRouter *auth.RouterDef) {
/* ACME Core */
authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
/* Auto Renewer */
authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable)
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HandleSetDNS)
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson)
/* ACME Wizard */
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck)
}
//Network utilities
authRouter.HandleFunc("/api/tools/ipscan", HandleIpScan)
// Register the APIs for Static Web Server management functions
func RegisterStaticWebServerAPIs(authRouter *auth.RouterDef) {
/* Static Web Server Controls */
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
/* File Manager */
if *allowWebFileManager {
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
authRouter.HandleFunc("/api/fs/upload", staticWebServer.FileManager.HandleUpload)
authRouter.HandleFunc("/api/fs/download", staticWebServer.FileManager.HandleDownload)
authRouter.HandleFunc("/api/fs/newFolder", staticWebServer.FileManager.HandleNewFolder)
authRouter.HandleFunc("/api/fs/copy", staticWebServer.FileManager.HandleFileCopy)
authRouter.HandleFunc("/api/fs/move", staticWebServer.FileManager.HandleFileMove)
authRouter.HandleFunc("/api/fs/properties", staticWebServer.FileManager.HandleFileProperties)
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete)
}
}
// Register the APIs for Network Utilities functions
func RegisterNetworkUtilsAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/tools/ipscan", ipscan.HandleIpScan)
authRouter.HandleFunc("/api/tools/portscan", ipscan.HandleScanPort)
authRouter.HandleFunc("/api/tools/traceroute", netutils.HandleTraceRoute)
authRouter.HandleFunc("/api/tools/ping", netutils.HandlePing)
authRouter.HandleFunc("/api/tools/whois", netutils.HandleWhois)
@ -163,92 +241,44 @@ func initAPIs() {
authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet)
authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet)
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
//Account Reset
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
http.HandleFunc("/api/account/new", HandleNewPasswordSetup)
//ACME & Auto Renewer
authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable)
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard
//Static Web Server
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
if *allowWebFileManager {
//Web Directory Manager file operation functions
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
authRouter.HandleFunc("/api/fs/upload", staticWebServer.FileManager.HandleUpload)
authRouter.HandleFunc("/api/fs/download", staticWebServer.FileManager.HandleDownload)
authRouter.HandleFunc("/api/fs/newFolder", staticWebServer.FileManager.HandleNewFolder)
authRouter.HandleFunc("/api/fs/copy", staticWebServer.FileManager.HandleFileCopy)
authRouter.HandleFunc("/api/fs/move", staticWebServer.FileManager.HandleFileMove)
authRouter.HandleFunc("/api/fs/properties", staticWebServer.FileManager.HandleFileProperties)
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete)
}
//Others
http.HandleFunc("/api/info/x", HandleZoraxyInfo)
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
//Debug
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
//If you got APIs to add, append them here
authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle)
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
}
// Function to renders Auth related APIs
func registerAuthAPIs(requireAuth bool) {
//Auth APIs
http.HandleFunc("/api/auth/login", authAgent.HandleLogin)
http.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
http.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
// Register the APIs for Auth functions, due to scoping issue some functions are defined here
func RegisterAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
targetMux.HandleFunc("/api/auth/login", authAgent.HandleLogin)
targetMux.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
targetMux.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
if requireAuth {
authAgent.CheckLogin(w, r)
} else {
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)
if err != nil {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
js, _ := json.Marshal(username)
utils.SendJSONResponse(w, string(js))
})
http.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
uc := authAgent.GetUserCounts()
js, _ := json.Marshal(uc)
targetMux.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(authAgent.GetUserCounts())
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 {
//Allow register root admin
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {
})
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {})
} else {
//This function is disabled
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)
if err != nil {
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
@ -283,5 +313,60 @@ func registerAuthAPIs(requireAuth bool) {
authAgent.UnregisterUser(username)
authAgent.CreateUserAccount(username, newPassword, "")
})
}
/* Register all the APIs */
func initAPIs(targetMux *http.ServeMux) {
authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
AuthAgent: authAgent,
RequireAuth: requireAuth,
TargetMux: targetMux,
DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
},
})
//Register the standard web services urls
fs := http.FileServer(http.FS(webres))
if DEVELOPMENT_BUILD {
fs = http.FileServer(http.Dir("web/"))
}
//Add a layer of middleware for advance control
advHandler := FSHandler(fs)
targetMux.Handle("/", advHandler)
//Register the APIs
RegisterAuthAPIs(requireAuth, targetMux)
RegisterHTTPProxyAPIs(authRouter)
RegisterTLSAPIs(authRouter)
//RegisterSSOAPIs(authRouter)
RegisterRedirectionAPIs(authRouter)
RegisterAccessRuleAPIs(authRouter)
RegisterPathRuleAPIs(authRouter)
RegisterStatisticalAPIs(authRouter)
RegisterGANAPIs(authRouter)
RegisterStreamProxyAPIs(authRouter)
RegisterMDNSAPIs(authRouter)
RegisterNetworkUtilsAPIs(authRouter)
RegisterACMEAndAutoRenewerAPIs(authRouter)
RegisterStaticWebServerAPIs(authRouter)
//Account Reset
targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
targetMux.HandleFunc("/api/account/new", HandleNewPasswordSetup)
//Docker UX Optimizations
authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable)
authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList)
//Others
targetMux.HandleFunc("/api/info/x", HandleZoraxyInfo)
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog)
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
//Debug
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
}

View File

@ -12,6 +12,7 @@ import (
"strings"
"time"
"imuslab.com/zoraxy/mod/acme"
"imuslab.com/zoraxy/mod/utils"
)
@ -46,6 +47,7 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
LastModifiedDate string
ExpireDate string
RemainingDays int
UseDNS bool
}
results := []*CertInfo{}
@ -81,12 +83,19 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
}
}
}
certInfoFilename := filepath.Join(tlsCertManager.CertStore, filename+".json")
useDNSValidation := false //Default to false for HTTP TLS certificates
certInfo, err := acme.LoadCertInfoJSON(certInfoFilename) //Note: Not all certs have info json
if err == nil {
useDNSValidation = certInfo.UseDNS
}
thisCertInfo := CertInfo{
Domain: filename,
LastModifiedDate: modifiedTime,
ExpireDate: certExpireTime,
RemainingDays: expiredIn,
UseDNS: useDNSValidation,
}
results = append(results, &thisCertInfo)
@ -168,32 +177,36 @@ func handleListDomains(w http.ResponseWriter, r *http.Request) {
// Handle front-end toggling TLS mode
func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
currentTlsSetting := false
currentTlsSetting := true //Default to true
if dynamicProxyRouter.Option != nil {
currentTlsSetting = dynamicProxyRouter.Option.UseTls
}
if sysdb.KeyExists("settings", "usetls") {
sysdb.Read("settings", "usetls", &currentTlsSetting)
}
newState, err := utils.PostPara(r, "set")
if err != nil {
//No setting. Get the current status
if r.Method == http.MethodGet {
//Get the current status
js, _ := json.Marshal(currentTlsSetting)
utils.SendJSONResponse(w, string(js))
} else {
if newState == "true" {
} else if r.Method == http.MethodPost {
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)
SystemWideLogger.Println("Enabling TLS mode on reverse proxy")
dynamicProxyRouter.UpdateTLSSetting(true)
} else if newState == "false" {
} else {
sysdb.Write("settings", "usetls", false)
SystemWideLogger.Println("Disabling TLS mode on reverse proxy")
dynamicProxyRouter.UpdateTLSSetting(false)
} else {
utils.SendErrorResponse(w, "invalid state given. Only support true or false")
return
}
utils.SendOK(w)
} else {
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
}
}
@ -224,6 +237,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
func handleCertUpload(w http.ResponseWriter, r *http.Request) {
// check if request method is POST

View File

@ -14,6 +14,7 @@ import (
"time"
"imuslab.com/zoraxy/mod/dynamicproxy"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
"imuslab.com/zoraxy/mod/utils"
)
@ -79,7 +80,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
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
}
@ -130,12 +131,18 @@ func RemoveReverseProxyConfig(endpoint string) error {
func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
//Default settings
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
ProxyType: dynamicproxy.ProxyType_Root,
RootOrMatchingDomain: "/",
Domain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
RequireTLS: false,
ProxyType: dynamicproxy.ProxyType_Root,
RootOrMatchingDomain: "/",
ActiveOrigins: []*loadbalance.Upstream{
{
OriginIpOrDomain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
RequireTLS: false,
SkipCertValidations: false,
Weight: 0,
},
},
InactiveOrigins: []*loadbalance.Upstream{},
BypassGlobalTLS: false,
SkipCertValidations: false,
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
RequireBasicAuth: false,
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
@ -155,7 +162,7 @@ func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
*/
func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
includeSysDBRaw, err := utils.GetPara(r, "includeDB")
includeSysDBRaw, _ := utils.GetPara(r, "includeDB")
includeSysDB := false
if includeSysDBRaw == "true" {
//Include the system database in backup snapshot
@ -177,7 +184,7 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
defer zipWriter.Close()
// Walk through the folder and add files to the zip
err = filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error {
err := filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error {
if err != nil {
return err
}

138
src/def.go Normal file
View File

@ -0,0 +1,138 @@
package main
/*
Type and flag definations
This file contains all the type and flag definations
Author: tobychui
*/
import (
"embed"
"flag"
"net/http"
"time"
"imuslab.com/zoraxy/mod/access"
"imuslab.com/zoraxy/mod/acme"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/auth/sso"
"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/email"
"imuslab.com/zoraxy/mod/forwardproxy"
"imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/info/logviewer"
"imuslab.com/zoraxy/mod/mdns"
"imuslab.com/zoraxy/mod/netstat"
"imuslab.com/zoraxy/mod/pathrule"
"imuslab.com/zoraxy/mod/sshprox"
"imuslab.com/zoraxy/mod/statistic"
"imuslab.com/zoraxy/mod/statistic/analytic"
"imuslab.com/zoraxy/mod/streamproxy"
"imuslab.com/zoraxy/mod/tlscert"
"imuslab.com/zoraxy/mod/uptime"
"imuslab.com/zoraxy/mod/webserv"
)
const (
/* Build Constants */
SYSTEM_NAME = "Zoraxy"
SYSTEM_VERSION = "3.1.4"
DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */
/* System Constants */
DATABASE_PATH = "sys.db"
TMP_FOLDER = "./tmp"
WEBSERV_DEFAULT_PORT = 5487
MDNS_HOSTNAME_PREFIX = "zoraxy_" /* Follow by node UUID */
MDNS_IDENTIFY_DEVICE_TYPE = "Network Gateway"
MDNS_IDENTIFY_DOMAIN = "zoraxy.aroz.org"
MDNS_IDENTIFY_VENDOR = "imuslab.com"
MDNS_SCAN_TIMEOUT = 30 /* Seconds */
MDNS_SCAN_UPDATE_INTERVAL = 15 /* Minutes */
GEODB_CACHE_CLEAR_INTERVAL = 15 /* Minutes */
ACME_AUTORENEW_CONFIG_PATH = "./conf/acme_conf.json"
CSRF_COOKIENAME = "zoraxy_csrf"
LOG_PREFIX = "zr"
LOG_FOLDER = "./log"
LOG_EXTENSION = ".log"
/* Configuration Folder Storage Path Constants */
CONF_HTTP_PROXY = "./conf/proxy"
CONF_STREAM_PROXY = "./conf/streamproxy"
CONF_CERT_STORE = "./conf/certs"
CONF_REDIRECTION = "./conf/redirect"
CONF_ACCESS_RULE = "./conf/access"
CONF_PATH_RULE = "./conf/rules/pathrules"
)
/* System Startup Flags */
var (
webUIPort = flag.String("port", ":8000", "Management web interface listening port")
noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
showver = flag.Bool("version", false, "Show version of this server")
allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
runningInDocker = flag.Bool("docker", false, "Run Zoraxy in docker compatibility mode")
acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
acmeCertAutoRenewDays = flag.Int("earlyrenew", 30, "Number of days to early renew a soon expiring certificate (days)")
enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
)
/* Global Variables and Handlers */
var (
nodeUUID = "generic" //System uuid in uuidv4 format, load from database on startup
bootTime = time.Now().Unix()
requireAuth = true //Require authentication for webmin panel, override from flag
/*
Binary Embedding File System
*/
//go:embed web/*
webres embed.FS
/*
Handler Modules
*/
sysdb *database.Database //System database
authAgent *auth.AuthAgent //Authentication agent
tlsCertManager *tlscert.Manager //TLS / SSL management
redirectTable *redirection.RuleTable //Handle special redirection rule sets
webminPanelMux *http.ServeMux //Server mux for handling webmin panel APIs
csrfMiddleware func(http.Handler) http.Handler //CSRF protection middleware
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
accessController *access.Controller //Access controller, handle black list and white list
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
statisticCollector *statistic.Collector //Collecting statistic from visitors
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
mdnsScanner *mdns.MDNSHost //mDNS discovery services
ganManager *ganserv.NetworkManager //Global Area Network Manager
webSshManager *sshprox.Manager //Web SSH connection service
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
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
ssoHandler *sso.SSOHandler //Single Sign On handler
//Helper modules
EmailSender *email.Sender //Email sender that handle email sending
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
SystemWideLogger *logger.Logger //Logger for Zoraxy
LogViewer *logviewer.Viewer //Log viewer HTTP handlers
)

View File

@ -25,12 +25,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
return
}
domain, err := utils.PostPara(r, "domain")
if err != nil {
utils.SendErrorResponse(w, "domain cannot be empty")
return
}
portString, err := utils.PostPara(r, "port")
if err != nil {
utils.SendErrorResponse(w, "port must be a valid integer")
@ -76,7 +70,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
//Set the email sender properties
thisEmailSender := email.Sender{
Hostname: strings.TrimSpace(hostname),
Domain: strings.TrimSpace(domain),
Port: port,
Username: strings.TrimSpace(username),
Password: strings.TrimSpace(password),
@ -206,7 +199,7 @@ var (
)
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
if EmailSender.Username == "" || EmailSender.Domain == "" {
if EmailSender.Username == "" {
//Reset account not setup
utils.SendErrorResponse(w, "Reset account not setup.")
return
@ -279,17 +272,14 @@ func HandleNewPasswordSetup(w http.ResponseWriter, r *http.Request) {
return
}
//Delete the user account
authAgent.UnregisterUser(username)
//Ok. Set the new password
err = authAgent.CreateUserAccount(username, newPassword, "")
if err != nil {
// Un register the user account
if err := authAgent.UnregisterUser(username); err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
if err != nil {
//Ok. Set the new password
if err := authAgent.CreateUserAccount(username, newPassword, ""); err != nil {
utils.SendErrorResponse(w, err.Error())
return
}

View File

@ -1,20 +1,210 @@
module imuslab.com/zoraxy
go 1.16
go 1.22.0
toolchain go1.22.2
require (
github.com/boltdb/bolt v1.3.1
github.com/go-acme/lego/v4 v4.14.0
github.com/docker/docker v27.0.0+incompatible
github.com/go-acme/lego/v4 v4.19.2
github.com/go-ping/ping v1.1.0
github.com/google/uuid v1.3.1
github.com/gorilla/sessions v1.2.1
github.com/gorilla/websocket v1.5.0
github.com/go-session/session v3.1.2+incompatible
github.com/google/uuid v1.6.0
github.com/gorilla/sessions v1.2.2
github.com/gorilla/websocket v1.5.1
github.com/grandcat/zeroconf v1.0.0
github.com/grokify/html-strip-tags-go v0.1.0
github.com/likexian/whois v1.15.1
github.com/microcosm-cc/bluemonday v1.0.25
golang.org/x/net v0.14.0
golang.org/x/sys v0.11.0
golang.org/x/text v0.12.0
golang.org/x/tools v0.12.0 // indirect
github.com/microcosm-cc/bluemonday v1.0.26
golang.org/x/net v0.29.0
golang.org/x/sys v0.25.0
golang.org/x/text v0.18.0
)
require (
cloud.google.com/go/auth v0.9.3 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 // indirect
github.com/tidwall/buntdb v1.1.2 // indirect
github.com/tidwall/gjson v1.12.1 // indirect
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e // indirect
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/vultr/govultr/v3 v3.9.1 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect
)
require (
cloud.google.com/go/compute/metadata v0.5.1 // indirect
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.33 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.32 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect
github.com/aws/aws-sdk-go-v2/service/lightsail v1.40.6 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect
github.com/aws/smithy-go v1.20.4 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/civo/civogo v0.3.11 // indirect
github.com/cloudflare/cloudflare-go v0.104.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/dnsimple/dnsimple-go v1.7.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.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/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-oauth2/oauth2/v4 v4.5.2
github.com/go-resty/resty/v2 v2.13.1 // indirect
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gofrs/uuid v4.4.0+incompatible
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/gophercloud/gophercloud v1.14.0 // indirect
github.com/gorilla/csrf v1.7.2
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
github.com/labbsr0x/goh v1.0.1 // indirect
github.com/linode/linodego v1.40.0 // indirect
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/nrdcg/auroradns v1.1.0 // indirect
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
github.com/nrdcg/desec v0.8.0 // indirect
github.com/nrdcg/dnspod-go v0.4.0 // indirect
github.com/nrdcg/freemyip v0.2.0 // indirect
github.com/nrdcg/goinwx v0.10.0 // indirect
github.com/nrdcg/mailinabox v0.2.0 // indirect
github.com/nrdcg/namesilo v0.2.1 // indirect
github.com/nrdcg/nodion v0.1.0 // indirect
github.com/nrdcg/porkbun v0.4.0 // indirect
github.com/nzdjb/go-metaname v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/ovh/go-ovh v1.6.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pquerna/otp v1.4.0 // indirect
github.com/sacloud/api-client-go v0.2.10 // indirect
github.com/sacloud/go-http v0.1.8 // indirect
github.com/sacloud/iaas-api-go v1.12.0 // indirect
github.com/sacloud/packages-go v0.0.10 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/softlayer/softlayer-go v1.1.5 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1002 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 // indirect
github.com/transip/gotransip/v6 v6.26.0 // indirect
github.com/ultradns/ultradns-go-sdk v1.7.0-20240913052650-970ca9a // indirect
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
github.com/xlzd/gotp v0.1.0
github.com/yandex-cloud/go-genproto v0.0.0-20240911120709-1fa0cb6f47c2 // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20240911121212-e4e74d0d02f5 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.uber.org/ratelimit v0.3.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.25.0 // indirect
google.golang.org/api v0.197.0 // indirect
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.66.1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.12.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
)

1669
src/go.sum

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,36 @@
package main
/*
______
|___ /
/ / ___ _ __ __ ___ ___ _
/ / / _ \| '__/ _` \ \/ / | | |
/ /_| (_) | | | (_| |> <| |_| |
/_____\___/|_| \__,_/_/\_\\__, |
__/ |
|___/
Zoraxy - A general purpose HTTP reverse proxy and forwarding tool
Author: tobychui
License: AGPLv3
--------------------------------------------
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License or any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"embed"
"flag"
"fmt"
"log"
@ -12,81 +41,13 @@ import (
"time"
"github.com/google/uuid"
"imuslab.com/zoraxy/mod/acme"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/email"
"imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/mdns"
"imuslab.com/zoraxy/mod/netstat"
"imuslab.com/zoraxy/mod/pathrule"
"imuslab.com/zoraxy/mod/sshprox"
"imuslab.com/zoraxy/mod/statistic"
"imuslab.com/zoraxy/mod/statistic/analytic"
"imuslab.com/zoraxy/mod/tcpprox"
"imuslab.com/zoraxy/mod/tlscert"
"imuslab.com/zoraxy/mod/uptime"
"github.com/gorilla/csrf"
"imuslab.com/zoraxy/mod/update"
"imuslab.com/zoraxy/mod/utils"
"imuslab.com/zoraxy/mod/webserv"
)
// General flags
var webUIPort = flag.String("port", ":8000", "Management web interface listening port")
var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
var showver = flag.Bool("version", false, "Show version of this server")
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
var (
name = "Zoraxy"
version = "3.0.0"
nodeUUID = "generic"
development = false //Set this to false to use embedded web fs
bootTime = time.Now().Unix()
/*
Binary Embedding File System
*/
//go:embed web/*
webres embed.FS
/*
Handler Modules
*/
sysdb *database.Database //System database
authAgent *auth.AuthAgent //Authentication agent
tlsCertManager *tlscert.Manager //TLS / SSL management
redirectTable *redirection.RuleTable //Handle special redirection rule sets
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
geodbStore *geodb.Store //GeoIP database, also handle black list and whitelist features
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
statisticCollector *statistic.Collector //Collecting statistic from visitors
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
mdnsScanner *mdns.MDNSHost //mDNS discovery services
ganManager *ganserv.NetworkManager //Global Area Network Manager
webSshManager *sshprox.Manager //Web SSH connection service
tcpProxyManager *tcpprox.Manager //TCP Proxy Manager
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
//Helper modules
EmailSender *email.Sender //Email sender that handle email sending
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
SystemWideLogger *logger.Logger //Logger for Zoraxy
)
// Kill signal handler. Do something before the system the core terminate.
/* SIGTERM handler, do shutdown sequences before closing */
func SetupCloseHandler() {
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
@ -98,42 +59,52 @@ func SetupCloseHandler() {
}
func ShutdownSeq() {
fmt.Println("- Shutting down " + name)
fmt.Println("- Closing GeoDB ")
geodbStore.Close()
fmt.Println("- Closing Netstats Listener")
SystemWideLogger.Println("Shutting down " + SYSTEM_NAME)
SystemWideLogger.Println("Closing Netstats Listener")
netstatBuffers.Close()
fmt.Println("- Closing Statistic Collector")
SystemWideLogger.Println("Closing Statistic Collector")
statisticCollector.Close()
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
mdnsTickerStop <- true
}
mdnsScanner.Close()
fmt.Println("- Closing Certificates Auto Renewer")
SystemWideLogger.Println("Shutting down load balancer")
loadBalancer.Close()
SystemWideLogger.Println("Closing Certificates Auto Renewer")
acmeAutoRenewer.Close()
//Remove the tmp folder
fmt.Println("- Cleaning up tmp files")
SystemWideLogger.Println("Cleaning up tmp files")
os.RemoveAll("./tmp")
fmt.Println("- Closing system wide logger")
SystemWideLogger.Close()
//Close database, final
fmt.Println("- Stopping system database")
//Close database
SystemWideLogger.Println("Stopping system database")
sysdb.Close()
//Close logger
SystemWideLogger.Println("Closing system wide logger")
SystemWideLogger.Close()
}
func main() {
//Parse startup flags
flag.Parse()
if *showver {
fmt.Println(name + " - Version " + version)
fmt.Println(SYSTEM_NAME + " - Version " + SYSTEM_VERSION)
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(SYSTEM_VERSION))
}
SetupCloseHandler()
//Read or create the system uuid
@ -149,12 +120,22 @@ func main() {
}
nodeUUID = string(uuidBytes)
//Create a new webmin mux and csrf middleware layer
webminPanelMux = http.NewServeMux()
csrfMiddleware = csrf.Protect(
[]byte(nodeUUID),
csrf.CookieName(CSRF_COOKIENAME),
csrf.Secure(false),
csrf.Path("/"),
csrf.SameSite(csrf.SameSiteLaxMode),
)
//Startup all modules
startupSequence()
//Initiate management interface APIs
requireAuth = !(*noauth)
initAPIs()
initAPIs(webminPanelMux)
//Start the reverse proxy server in go routine
go func() {
@ -166,11 +147,10 @@ func main() {
//Start the finalize sequences
finalSequence()
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
err = http.ListenAndServe(*webUIPort, nil)
SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://localhost" + *webUIPort)
err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
if err != nil {
log.Fatal(err)
}
}

221
src/mod/access/access.go Normal file
View File

@ -0,0 +1,221 @@
package access
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"sync"
"imuslab.com/zoraxy/mod/utils"
)
/*
Access.go
This module is the new version of access control system
where now the blacklist / whitelist are seperated from
geodb module
*/
// Create a new access controller to handle blacklist / whitelist
func NewAccessController(options *Options) (*Controller, error) {
sysdb := options.Database
if sysdb == nil {
return nil, errors.New("missing database access")
}
//Create the config folder if not exists
confFolder := options.ConfigFolder
if !utils.FileExists(confFolder) {
err := os.MkdirAll(confFolder, 0775)
if err != nil {
return nil, err
}
}
// Create the global access rule if not exists
var defaultAccessRule = AccessRule{
ID: "default",
Name: "Default",
Desc: "Default access rule for all HTTP proxy hosts",
BlacklistEnabled: false,
WhitelistEnabled: false,
WhiteListCountryCode: &map[string]string{},
WhiteListIP: &map[string]string{},
BlackListContryCode: &map[string]string{},
BlackListIP: &map[string]string{},
}
defaultRuleSettingFile := filepath.Join(confFolder, "default.json")
if utils.FileExists(defaultRuleSettingFile) {
//Load from file
defaultRuleBytes, err := os.ReadFile(defaultRuleSettingFile)
if err == nil {
err = json.Unmarshal(defaultRuleBytes, &defaultAccessRule)
if err != nil {
options.Logger.PrintAndLog("Access", "Unable to parse default routing rule config file. Using default", err)
}
}
} else {
//Create one
js, _ := json.MarshalIndent(defaultAccessRule, "", " ")
os.WriteFile(defaultRuleSettingFile, js, 0775)
}
//Generate a controller object
thisController := Controller{
DefaultAccessRule: &defaultAccessRule,
ProxyAccessRule: &sync.Map{},
Options: options,
}
//Assign default access rule parent
thisController.DefaultAccessRule.parent = &thisController
//Load all acccess rules from file
configFiles, err := filepath.Glob(options.ConfigFolder + "/*.json")
if err != nil {
return nil, err
}
ProxyAccessRules := sync.Map{}
for _, configFile := range configFiles {
if filepath.Base(configFile) == "default.json" {
//Skip this, as this was already loaded as default
continue
}
configContent, err := os.ReadFile(configFile)
if err != nil {
options.Logger.PrintAndLog("Access", "Unable to load config "+filepath.Base(configFile), err)
continue
}
//Parse the config file into AccessRule
thisAccessRule := AccessRule{}
err = json.Unmarshal(configContent, &thisAccessRule)
if err != nil {
options.Logger.PrintAndLog("Access", "Unable to parse config "+filepath.Base(configFile), err)
continue
}
thisAccessRule.parent = &thisController
ProxyAccessRules.Store(thisAccessRule.ID, &thisAccessRule)
}
thisController.ProxyAccessRule = &ProxyAccessRules
return &thisController, nil
}
// Get the global access rule
func (c *Controller) GetGlobalAccessRule() (*AccessRule, error) {
if c.DefaultAccessRule == nil {
return nil, errors.New("global access rule is not set")
}
return c.DefaultAccessRule, nil
}
// Load access rules to runtime, require rule ID
func (c *Controller) GetAccessRuleByID(accessRuleID string) (*AccessRule, error) {
if accessRuleID == "default" || accessRuleID == "" {
return c.DefaultAccessRule, nil
}
//Load from sync.Map, should be O(1)
targetRule, ok := c.ProxyAccessRule.Load(accessRuleID)
if !ok {
return nil, errors.New("target access rule not exists")
}
ar, ok := targetRule.(*AccessRule)
if !ok {
return nil, errors.New("assertion of access rule failed, version too old?")
}
return ar, nil
}
// Return all the access rules currently in runtime, including default
func (c *Controller) ListAllAccessRules() []*AccessRule {
results := []*AccessRule{c.DefaultAccessRule}
c.ProxyAccessRule.Range(func(key, value interface{}) bool {
results = append(results, value.(*AccessRule))
return true
})
return results
}
// Check if an access rule exists given the rule id
func (c *Controller) AccessRuleExists(ruleID string) bool {
r, _ := c.GetAccessRuleByID(ruleID)
if r != nil {
//An access rule with identical ID exists
return true
}
return false
}
// Add a new access rule to runtime and save it to file
func (c *Controller) AddNewAccessRule(newRule *AccessRule) error {
r, _ := c.GetAccessRuleByID(newRule.ID)
if r != nil {
//An access rule with identical ID exists
return errors.New("access rule already exists")
}
//Check if the blacklist and whitelist are populated with empty map
if newRule.BlackListContryCode == nil {
newRule.BlackListContryCode = &map[string]string{}
}
if newRule.BlackListIP == nil {
newRule.BlackListIP = &map[string]string{}
}
if newRule.WhiteListCountryCode == nil {
newRule.WhiteListCountryCode = &map[string]string{}
}
if newRule.WhiteListIP == nil {
newRule.WhiteListIP = &map[string]string{}
}
//Add access rule to runtime
newRule.parent = c
c.ProxyAccessRule.Store(newRule.ID, newRule)
//Save rule to file
newRule.SaveChanges()
return nil
}
// Update the access rule meta info.
func (c *Controller) UpdateAccessRule(ruleID string, name string, desc string) error {
targetAccessRule, err := c.GetAccessRuleByID(ruleID)
if err != nil {
return err
}
///Update the name and desc
targetAccessRule.Name = name
targetAccessRule.Desc = desc
//Overwrite the rule currently in sync map
if ruleID == "default" {
c.DefaultAccessRule = targetAccessRule
} else {
c.ProxyAccessRule.Store(ruleID, targetAccessRule)
}
return targetAccessRule.SaveChanges()
}
// Remove the access rule by its id
func (c *Controller) RemoveAccessRuleByID(ruleID string) error {
if !c.AccessRuleExists(ruleID) {
return errors.New("access rule not exists")
}
//Default cannot be removed
if ruleID == "default" {
return errors.New("default access rule cannot be removed")
}
//Remove it
return c.DeleteAccessRuleByID(ruleID)
}

View File

@ -0,0 +1,153 @@
package access
import (
"encoding/json"
"errors"
"net"
"os"
"path/filepath"
)
// Check both blacklist and whitelist for access for both geoIP and ip / CIDR ranges
func (s *AccessRule) AllowIpAccess(ipaddr string) bool {
if s.IsBlacklisted(ipaddr) {
return false
}
return s.IsWhitelisted(ipaddr)
}
// Check both blacklist and whitelist for access using net.Conn
func (s *AccessRule) AllowConnectionAccess(conn net.Conn) bool {
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
return s.AllowIpAccess(addr.IP.String())
}
return true
}
// Toggle black list
func (s *AccessRule) ToggleBlacklist(enabled bool) {
s.BlacklistEnabled = enabled
s.SaveChanges()
}
// Toggel white list
func (s *AccessRule) ToggleWhitelist(enabled bool) {
s.WhitelistEnabled = enabled
s.SaveChanges()
}
/*
Check if a IP address is blacklisted, in either country or IP blacklist
IsBlacklisted default return is false (allow access)
*/
func (s *AccessRule) IsBlacklisted(ipAddr string) bool {
if !s.BlacklistEnabled {
//Blacklist not enabled. Always return false
return false
}
if ipAddr == "" {
//Unable to get the target IP address
return false
}
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
if err != nil {
return false
}
if s.IsCountryCodeBlacklisted(countryCode.CountryIsoCode) {
return true
}
if s.IsIPBlacklisted(ipAddr) {
return true
}
return false
}
/*
IsWhitelisted check if a given IP address is in the current
server's white list.
Note that the Whitelist default result is true even
when encountered error
*/
func (s *AccessRule) IsWhitelisted(ipAddr string) bool {
if !s.WhitelistEnabled {
//Whitelist not enabled. Always return true (allow access)
return true
}
if ipAddr == "" {
//Unable to get the target IP address, assume ok
return true
}
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
if err != nil {
return true
}
if s.IsCountryCodeWhitelisted(countryCode.CountryIsoCode) {
return true
}
if s.IsIPWhitelisted(ipAddr) {
return true
}
return false
}
/* Utilities function */
// Update the current access rule to json file
func (s *AccessRule) SaveChanges() error {
if s.parent == nil {
return errors.New("save failed: access rule detached from controller")
}
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
js, err := json.MarshalIndent(s, "", " ")
if err != nil {
return err
}
err = os.WriteFile(saveTarget, js, 0775)
return err
}
// Delete this access rule, this will only delete the config file.
// for runtime delete, use DeleteAccessRuleByID from parent Controller
func (s *AccessRule) DeleteConfigFile() error {
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
return os.Remove(saveTarget)
}
// Delete the access rule by given ID
func (c *Controller) DeleteAccessRuleByID(accessRuleID string) error {
targetAccessRule, err := c.GetAccessRuleByID(accessRuleID)
if err != nil {
return err
}
//Delete config file associated with this access rule
err = targetAccessRule.DeleteConfigFile()
if err != nil {
return err
}
//Delete the access rule in runtime
c.ProxyAccessRule.Delete(accessRuleID)
return nil
}
// Create a deep copy object of the access rule list
func deepCopy(valueList map[string]string) map[string]string {
result := map[string]string{}
js, _ := json.Marshal(valueList)
json.Unmarshal(js, &result)
return result
}

View File

@ -0,0 +1,94 @@
package access
import (
"strings"
"imuslab.com/zoraxy/mod/netutils"
)
/*
Blacklist.go
This script store the blacklist related functions
*/
// Geo Blacklist
func (s *AccessRule) AddCountryCodeToBlackList(countryCode string, comment string) {
countryCode = strings.ToLower(countryCode)
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
newBlacklistCountryCode[countryCode] = comment
s.BlackListContryCode = &newBlacklistCountryCode
s.SaveChanges()
}
func (s *AccessRule) RemoveCountryCodeFromBlackList(countryCode string) {
countryCode = strings.ToLower(countryCode)
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
delete(newBlacklistCountryCode, countryCode)
s.BlackListContryCode = &newBlacklistCountryCode
s.SaveChanges()
}
func (s *AccessRule) IsCountryCodeBlacklisted(countryCode string) bool {
countryCode = strings.ToLower(countryCode)
blacklistMap := *s.BlackListContryCode
_, ok := blacklistMap[countryCode]
return ok
}
func (s *AccessRule) GetAllBlacklistedCountryCode() []string {
bannedCountryCodes := []string{}
blacklistMap := *s.BlackListContryCode
for cc, _ := range blacklistMap {
bannedCountryCodes = append(bannedCountryCodes, cc)
}
return bannedCountryCodes
}
// IP Blacklsits
func (s *AccessRule) AddIPToBlackList(ipAddr string, comment string) {
newBlackListIP := deepCopy(*s.BlackListIP)
newBlackListIP[ipAddr] = comment
s.BlackListIP = &newBlackListIP
s.SaveChanges()
}
func (s *AccessRule) RemoveIPFromBlackList(ipAddr string) {
newBlackListIP := deepCopy(*s.BlackListIP)
delete(newBlackListIP, ipAddr)
s.BlackListIP = &newBlackListIP
s.SaveChanges()
}
func (s *AccessRule) GetAllBlacklistedIp() []string {
bannedIps := []string{}
blacklistMap := *s.BlackListIP
for ip, _ := range blacklistMap {
bannedIps = append(bannedIps, ip)
}
return bannedIps
}
func (s *AccessRule) IsIPBlacklisted(ipAddr string) bool {
IPBlacklist := *s.BlackListIP
_, ok := IPBlacklist[ipAddr]
if ok {
return true
}
//Check for CIDR
for ipOrCIDR, _ := range IPBlacklist {
wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR)
if wildcardMatch {
return true
}
cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR)
if cidrMatch {
return true
}
}
return false
}

38
src/mod/access/typedef.go Normal file
View File

@ -0,0 +1,38 @@
package access
import (
"sync"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
)
type Options struct {
Logger logger.Logger
ConfigFolder string //Path for storing config files
GeoDB *geodb.Store //For resolving country code
Database *database.Database //System key-value database
}
type AccessRule struct {
ID string
Name string
Desc string
BlacklistEnabled bool
WhitelistEnabled bool
/* Whitelist Blacklist Table, value is comment if supported */
WhiteListCountryCode *map[string]string
WhiteListIP *map[string]string
BlackListContryCode *map[string]string
BlackListIP *map[string]string
parent *Controller
}
type Controller struct {
DefaultAccessRule *AccessRule
ProxyAccessRule *sync.Map
Options *Options
}

112
src/mod/access/whitelist.go Normal file
View File

@ -0,0 +1,112 @@
package access
import (
"strings"
"imuslab.com/zoraxy/mod/netutils"
)
/*
Whitelist.go
This script handles whitelist related functions
*/
const (
EntryType_CountryCode int = 0
EntryType_IP int = 1
)
type WhitelistEntry struct {
EntryType int //Entry type of whitelist, Country Code or IP
CC string //ISO Country Code
IP string //IP address or range
Comment string //Comment for this entry
}
//Geo Whitelist
func (s *AccessRule) AddCountryCodeToWhitelist(countryCode string, comment string) {
countryCode = strings.ToLower(countryCode)
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
newWhitelistCC[countryCode] = comment
s.WhiteListCountryCode = &newWhitelistCC
s.SaveChanges()
}
func (s *AccessRule) RemoveCountryCodeFromWhitelist(countryCode string) {
countryCode = strings.ToLower(countryCode)
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
delete(newWhitelistCC, countryCode)
s.WhiteListCountryCode = &newWhitelistCC
s.SaveChanges()
}
func (s *AccessRule) IsCountryCodeWhitelisted(countryCode string) bool {
countryCode = strings.ToLower(countryCode)
whitelistCC := *s.WhiteListCountryCode
_, ok := whitelistCC[countryCode]
return ok
}
func (s *AccessRule) GetAllWhitelistedCountryCode() []*WhitelistEntry {
whitelistedCountryCode := []*WhitelistEntry{}
whitelistCC := *s.WhiteListCountryCode
for cc, comment := range whitelistCC {
whitelistedCountryCode = append(whitelistedCountryCode, &WhitelistEntry{
EntryType: EntryType_CountryCode,
CC: cc,
Comment: comment,
})
}
return whitelistedCountryCode
}
//IP Whitelist
func (s *AccessRule) AddIPToWhiteList(ipAddr string, comment string) {
newWhitelistIP := deepCopy(*s.WhiteListIP)
newWhitelistIP[ipAddr] = comment
s.WhiteListIP = &newWhitelistIP
s.SaveChanges()
}
func (s *AccessRule) RemoveIPFromWhiteList(ipAddr string) {
newWhitelistIP := deepCopy(*s.WhiteListIP)
delete(newWhitelistIP, ipAddr)
s.WhiteListIP = &newWhitelistIP
s.SaveChanges()
}
func (s *AccessRule) IsIPWhitelisted(ipAddr string) bool {
//Check for IP wildcard and CIRD rules
WhitelistedIP := *s.WhiteListIP
for ipOrCIDR, _ := range WhitelistedIP {
wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR)
if wildcardMatch {
return true
}
cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR)
if cidrMatch {
return true
}
}
return false
}
func (s *AccessRule) GetAllWhitelistedIp() []*WhitelistEntry {
whitelistedIp := []*WhitelistEntry{}
currentWhitelistedIP := *s.WhiteListIP
for ipOrCIDR, comment := range currentWhitelistedIP {
thisEntry := WhitelistEntry{
EntryType: EntryType_IP,
IP: ipOrCIDR,
Comment: comment,
}
whitelistedIp = append(whitelistedIp, &thisEntry)
}
return whitelistedIp
}

View File

@ -9,8 +9,8 @@ import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
@ -24,13 +24,17 @@ import (
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils"
)
type CertificateInfoJSON struct {
AcmeName string `json:"acme_name"`
AcmeUrl string `json:"acme_url"`
SkipTLS bool `json:"skip_tls"`
AcmeName string `json:"acme_name"` //ACME provider name
AcmeUrl string `json:"acme_url"` //Custom ACME URL (if any)
SkipTLS bool `json:"skip_tls"` //Skip TLS verification of upstream
UseDNS bool `json:"dns"` //Use DNS challenge
PropTimeout int `json:"prop_time"` //Propagation timeout
}
// ACMEUser represents a user in the ACME system.
@ -40,6 +44,11 @@ type ACMEUser struct {
key crypto.PrivateKey
}
type EABConfig struct {
Kid string `json:"kid"`
HmacKey string `json:"HmacKey"`
}
// GetEmail returns the email of the ACMEUser.
func (u *ACMEUser) GetEmail() string {
return u.Email
@ -59,24 +68,39 @@ func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey {
type ACMEHandler struct {
DefaultAcmeServer string
Port string
Database *database.Database
Logger *logger.Logger
}
// NewACME creates a new ACMEHandler instance.
func NewACME(acmeServer string, port string) *ACMEHandler {
func NewACME(defaultAcmeServer string, port string, database *database.Database, logger *logger.Logger) *ACMEHandler {
return &ACMEHandler{
DefaultAcmeServer: acmeServer,
DefaultAcmeServer: defaultAcmeServer,
Port: port,
Database: database,
Logger: logger,
}
}
func (a *ACMEHandler) Logf(message string, err error) {
a.Logger.PrintAndLog("ACME", message, err)
}
// Close closes the ACMEHandler.
// ACME Handler does not need to close anything
// Function defined for future compatibility
func (a *ACMEHandler) Close() error {
return nil
}
// ObtainCert obtains a certificate for the specified domains.
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool) (bool, error) {
log.Println("[ACME] Obtaining certificate...")
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool, useDNS bool, propagationTimeout int) (bool, error) {
a.Logf("Obtaining certificate for: "+strings.Join(domains, ", "), nil)
// generate private key
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Println(err)
a.Logf("Private key generation failed", err)
return false, err
}
@ -92,7 +116,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
// skip TLS verify if need
// Ref: https://github.com/go-acme/lego/blob/6af2c756ac73a9cb401621afca722d0f4112b1b8/lego/client_config.go#L74
if skipTLS {
log.Println("[INFO] Ignore TLS/SSL Verification Error for ACME Server")
a.Logf("Ignoring TLS/SSL Verification Error for ACME Server", nil)
config.HTTPClient.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
@ -107,6 +131,11 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
}
}
//Fallback to Let's Encrypt if it is not set
if caName == "" {
caName = "Let's Encrypt"
}
// setup the custom ACME url endpoint.
if caUrl != "" {
config.CADirURL = caUrl
@ -114,16 +143,16 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
// if not custom ACME url, load it from ca.json
if caName == "custom" {
log.Println("[INFO] Using Custom ACME " + caUrl + " for CA Directory URL")
a.Logf("Using Custom ACME "+caUrl+" for CA Directory URL", nil)
} else {
caLinkOverwrite, err := loadCAApiServerFromName(caName)
if err == nil {
config.CADirURL = caLinkOverwrite
log.Println("[INFO] Using " + caLinkOverwrite + " for CA Directory URL")
a.Logf("Using "+caLinkOverwrite+" for CA Directory URL", nil)
} else {
// (caName == "" || caUrl == "") will use default acme
config.CADirURL = a.DefaultAcmeServer
log.Println("[INFO] Using Default ACME " + a.DefaultAcmeServer + " for CA Directory URL")
a.Logf("Using Default ACME "+a.DefaultAcmeServer+" for CA Directory URL", nil)
}
}
@ -131,22 +160,110 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
client, err := lego.NewClient(config)
if err != nil {
log.Println(err)
a.Logf("Failed to spawn new ACME client from current config", err)
return false, err
}
// setup how to receive challenge
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
if err != nil {
log.Println(err)
return false, err
if useDNS {
if !a.Database.TableExists("acme") {
a.Database.NewTable("acme")
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -1)")
}
if !a.Database.KeyExists("acme", certificateName+"_dns_provider") || !a.Database.KeyExists("acme", certificateName+"_dns_credentials") {
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -2)")
}
var dnsCredentials string
err := a.Database.Read("acme", certificateName+"_dns_credentials", &dnsCredentials)
if err != nil {
a.Logf("Read DNS credential failed", err)
return false, err
}
var dnsProvider string
err = a.Database.Read("acme", certificateName+"_dns_provider", &dnsProvider)
if err != nil {
a.Logf("Read DNS Provider failed", err)
return false, err
}
provider, err := GetDnsChallengeProviderByName(dnsProvider, dnsCredentials, propagationTimeout)
if err != nil {
a.Logf("Unable to resolve DNS challenge provider", err)
return false, err
}
err = client.Challenge.SetDNS01Provider(provider)
if err != nil {
a.Logf("Failed to resolve DNS01 Provider", err)
return false, err
}
} else {
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
if err != nil {
a.Logf("Failed to resolve HTTP01 Provider", err)
return false, err
}
}
// New users will need to register
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
log.Println(err)
return false, err
/*
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
log.Println(err)
return false, err
}
*/
var reg *registration.Resource
// New users will need to register
if client.GetExternalAccountRequired() {
a.Logf("External Account Required for this ACME Provider", nil)
// IF KID and HmacEncoded is overidden
if !a.Database.TableExists("acme") {
a.Database.NewTable("acme")
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -1)")
}
if !a.Database.KeyExists("acme", config.CADirURL+"_kid") || !a.Database.KeyExists("acme", config.CADirURL+"_hmacEncoded") {
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -2)")
}
var kid string
var hmacEncoded string
err := a.Database.Read("acme", config.CADirURL+"_kid", &kid)
if err != nil {
a.Logf("Failed to read kid from database", err)
return false, err
}
err = a.Database.Read("acme", config.CADirURL+"_hmacEncoded", &hmacEncoded)
if err != nil {
a.Logf("Failed to read HMAC from database", err)
return false, err
}
a.Logf("EAB Credential retrieved: "+kid+" / "+hmacEncoded, nil)
if kid != "" && hmacEncoded != "" {
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: kid,
HmacEncoded: hmacEncoded,
})
}
if err != nil {
a.Logf("Register with external account binder failed", err)
return false, err
}
//return false, errors.New("External Account Required for this ACME Provider.")
} else {
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
a.Logf("Unable to register client", err)
return false, err
}
}
adminUser.Registration = reg
@ -157,7 +274,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
}
certificates, err := client.Certificate.Obtain(request)
if err != nil {
log.Println(err)
a.Logf("Obtain certificate failed", err)
return false, err
}
@ -165,31 +282,33 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
// private key, and a certificate URL.
err = os.WriteFile("./conf/certs/"+certificateName+".pem", certificates.Certificate, 0777)
if err != nil {
log.Println(err)
a.Logf("Failed to write public key to disk", err)
return false, err
}
err = os.WriteFile("./conf/certs/"+certificateName+".key", certificates.PrivateKey, 0777)
if err != nil {
log.Println(err)
a.Logf("Failed to write private key to disk", err)
return false, err
}
// Save certificate's ACME info for renew usage
certInfo := &CertificateInfoJSON{
AcmeName: caName,
AcmeUrl: caUrl,
SkipTLS: skipTLS,
AcmeName: caName,
AcmeUrl: caUrl,
SkipTLS: skipTLS,
UseDNS: useDNS,
PropTimeout: propagationTimeout,
}
certInfoBytes, err := json.Marshal(certInfo)
if err != nil {
log.Println(err)
a.Logf("Marshal certificate renew config failed", err)
return false, err
}
err = os.WriteFile("./conf/certs/"+certificateName+".json", certInfoBytes, 0777)
if err != nil {
log.Println(err)
a.Logf("Failed to write certificate renew config to file", err)
return false, err
}
@ -207,7 +326,7 @@ func (a *ACMEHandler) CheckCertificate() []string {
expiredCerts := []string{}
if err != nil {
log.Println(err)
a.Logf("Failed to load certificate folder", err)
return []string{}
}
@ -291,6 +410,8 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
utils.SendErrorResponse(w, jsonEscape(err.Error()))
return
}
//Make sure the wildcard * do not goes into the filename
filename = strings.ReplaceAll(filename, "*", "_")
email, err := utils.PostPara(r, "email")
if err != nil {
@ -302,14 +423,14 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
ca, err := utils.PostPara(r, "ca")
if err != nil {
log.Println("[INFO] CA not set. Using default")
a.Logf("CA not set. Using default", nil)
ca, caUrl = "", ""
}
if ca == "custom" {
caUrl, err = utils.PostPara(r, "caURL")
if err != nil {
log.Println("[INFO] Custom CA set but no URL provide, Using default")
a.Logf("Custom CA set but no URL provide, Using default", nil)
ca, caUrl = "", ""
}
}
@ -329,8 +450,41 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
skipTLS = true
}
var dns bool
if dnsString, err := utils.PostPara(r, "dns"); err != nil {
dns = false
} else if dnsString != "true" {
dns = false
} else {
dns = true
}
domains := strings.Split(domainPara, ",")
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS)
// Default propagation timeout is 300 seconds
propagationTimeout := 300
if dns {
ppgTimeout, err := utils.PostPara(r, "ppgTimeout")
if err == nil {
propagationTimeout, err = strconv.Atoi(ppgTimeout)
if err != nil {
utils.SendErrorResponse(w, "Invalid propagation timeout value")
return
}
if propagationTimeout < 60 {
//Minimum propagation timeout is 60 seconds
propagationTimeout = 60
}
}
}
//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, propagationTimeout)
if err != nil {
utils.SendErrorResponse(w, jsonEscape(err.Error()))
return
@ -342,7 +496,7 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
func jsonEscape(i string) string {
b, err := json.Marshal(i)
if err != nil {
log.Println("Unable to escape json data: " + err.Error())
//log.Println("Unable to escape json data: " + err.Error())
return i
}
s := string(b)
@ -362,7 +516,7 @@ func IsPortInUse(port int) bool {
}
// Load cert information from json file
func loadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
func LoadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
certInfoBytes, err := os.ReadFile(filename)
if err != nil {
return nil, err

56
src/mod/acme/acme_dns.go Normal file
View File

@ -0,0 +1,56 @@
package acme
import (
"encoding/json"
"strconv"
"github.com/go-acme/lego/v4/challenge"
"imuslab.com/zoraxy/mod/acme/acmedns"
)
// Preprocessor function to get DNS challenge provider by name
func GetDnsChallengeProviderByName(dnsProvider string, dnsCredentials string, ppgTimeout int) (challenge.Provider, error) {
//Unpack the dnsCredentials (json string) to map
var dnsCredentialsMap map[string]interface{}
err := json.Unmarshal([]byte(dnsCredentials), &dnsCredentialsMap)
if err != nil {
return nil, err
}
//Clear the PollingInterval and PropagationTimeout field and conert to int
userDefinedPollingInterval := 2
if dnsCredentialsMap["PollingInterval"] != nil {
userDefinedPollingIntervalRaw := dnsCredentialsMap["PollingInterval"].(string)
delete(dnsCredentialsMap, "PollingInterval")
convertedPollingInterval, err := strconv.Atoi(userDefinedPollingIntervalRaw)
if err == nil {
userDefinedPollingInterval = convertedPollingInterval
}
}
userDefinedPropagationTimeout := ppgTimeout
if dnsCredentialsMap["PropagationTimeout"] != nil {
userDefinedPropagationTimeoutRaw := dnsCredentialsMap["PropagationTimeout"].(string)
delete(dnsCredentialsMap, "PropagationTimeout")
convertedPropagationTimeout, err := strconv.Atoi(userDefinedPropagationTimeoutRaw)
if err == nil {
//Overwrite the default propagation timeout if it is requeted from UI
userDefinedPropagationTimeout = convertedPropagationTimeout
}
}
//Restructure dnsCredentials string from map
dnsCredentialsBytes, err := json.Marshal(dnsCredentialsMap)
if err != nil {
return nil, err
}
dnsCredentials = string(dnsCredentialsBytes)
//Using acmedns CICD pipeline generated datatype to optain the DNS provider
return acmedns.GetDNSProviderByJsonConfig(
dnsProvider,
dnsCredentials,
int64(userDefinedPropagationTimeout),
int64(userDefinedPollingInterval),
)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
package acmedns_test
import (
"fmt"
"testing"
"imuslab.com/zoraxy/mod/acme/acmedns"
)
// Test if the structure of ACME DNS config can be reflected from lego source code definations
func TestACMEDNSConfigStructureReflector(t *testing.T) {
providers := []string{
"gandi",
"cloudflare",
"azure",
}
for _, provider := range providers {
strcture, err := acmedns.GetProviderConfigStructure(provider)
if err != nil {
panic(err)
}
fmt.Println(strcture)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
package acmedns
import (
_ "embed"
"encoding/json"
"errors"
"net/http"
"imuslab.com/zoraxy/mod/utils"
)
//go:embed providers.json
var providers []byte //A list of providers generated by acmedns code-generator
type ConfigTemplate struct {
Name string `json:"Name"`
ConfigableFields []struct {
Title string `json:"Title"`
Datatype string `json:"Datatype"`
} `json:"ConfigableFields"`
HiddenFields []struct {
Title string `json:"Title"`
Datatype string `json:"Datatype"`
} `json:"HiddenFields"`
}
// Return a map of string => datatype
func GetProviderConfigStructure(providerName string) (map[string]string, error) {
//Load the target config template from embedded providers.json
configTemplateMap := map[string]ConfigTemplate{}
err := json.Unmarshal(providers, &configTemplateMap)
if err != nil {
return map[string]string{}, err
}
targetConfigTemplate, ok := configTemplateMap[providerName]
if !ok {
return map[string]string{}, errors.New("provider not supported")
}
results := map[string]string{}
for _, field := range targetConfigTemplate.ConfigableFields {
results[field.Title] = field.Datatype
}
return results, nil
}
// HandleServeProvidersJson return the list of supported providers as json
func HandleServeProvidersJson(w http.ResponseWriter, r *http.Request) {
providerName, _ := utils.GetPara(r, "name")
if providerName == "" {
//Send the current list of providers
configTemplateMap := map[string]ConfigTemplate{}
err := json.Unmarshal(providers, &configTemplateMap)
if err != nil {
utils.SendErrorResponse(w, "failed to load DNS provider")
return
}
//Parse the provider names into an array
providers := []string{}
for providerName, _ := range configTemplateMap {
providers = append(providers, providerName)
}
js, _ := json.Marshal(providers)
utils.SendJSONResponse(w, string(js))
return
}
//Get the config for that provider
confTemplate, err := GetProviderConfigStructure(providerName)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
js, _ := json.Marshal(confTemplate)
utils.SendJSONResponse(w, string(js))
}

View File

@ -75,6 +75,15 @@ func HandleGuidedStepCheck(w http.ResponseWriter, r *http.Request) {
httpServerReachable := isHTTPServerAvailable(domain)
js, _ := json.Marshal(httpServerReachable)
utils.SendJSONResponse(w, string(js))
} else if stepNo == 10 {
//Resolve public Ip address for tour
publicIp, err := getPublicIPAddress()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
js, _ := json.Marshal(publicIp)
utils.SendJSONResponse(w, string(js))
} else {
utils.SendErrorResponse(w, "invalid step number")
}

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"net/mail"
"os"
@ -12,6 +11,7 @@ import (
"strings"
"time"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils"
)
@ -34,7 +34,9 @@ type AutoRenewer struct {
AcmeHandler *ACMEHandler
RenewerConfig *AutoRenewConfig
RenewTickInterval int64
EarlyRenewDays int //How many days before cert expire to renew certificate
TickerstopChan chan bool
Logger *logger.Logger //System wide logger
}
type ExpiredCerts struct {
@ -44,11 +46,15 @@ type ExpiredCerts struct {
// Create an auto renew agent, require config filepath and auto scan & renew interval (seconds)
// 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, logger *logger.Logger) (*AutoRenewer, error) {
if renewCheckInterval == 0 {
renewCheckInterval = 86400 //1 day
}
if earlyRenewDays == 0 {
earlyRenewDays = 30
}
//Load the config file. If not found, create one
if !utils.FileExists(config) {
//Create one
@ -82,8 +88,12 @@ func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64,
AcmeHandler: AcmeHandler,
RenewerConfig: &renewerConfig,
RenewTickInterval: renewCheckInterval,
EarlyRenewDays: earlyRenewDays,
Logger: logger,
}
thisRenewer.Logf("ACME early renew set to "+fmt.Sprint(earlyRenewDays)+" days and check interval set to "+fmt.Sprint(renewCheckInterval)+" seconds", nil)
if thisRenewer.RenewerConfig.Enabled {
//Start the renew ticker
thisRenewer.StartAutoRenewTicker()
@ -95,6 +105,10 @@ func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64,
return &thisRenewer, nil
}
func (a *AutoRenewer) Logf(message string, err error) {
a.Logger.PrintAndLog("cert-renew", message, err)
}
func (a *AutoRenewer) StartAutoRenewTicker() {
//Stop the previous ticker if still running
if a.TickerstopChan != nil {
@ -113,7 +127,7 @@ func (a *AutoRenewer) StartAutoRenewTicker() {
case <-done:
return
case <-ticker.C:
log.Println("Check and renew certificates in progress")
a.Logf("Check and renew certificates in progress", nil)
a.CheckAndRenewCertificates()
}
}
@ -135,7 +149,7 @@ func (a *AutoRenewer) StopAutoRenewTicker() {
// opr = setSelected -> Enter a list of file names (or matching rules) for auto renew
// opr = setAuto -> Set to use auto detect certificates and renew
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 {
utils.SendErrorResponse(w, "Operation not set")
return
@ -165,6 +179,8 @@ func (a *AutoRenewer) HandleSetAutoRenewDomains(w http.ResponseWriter, r *http.R
a.RenewerConfig.RenewAll = true
a.saveRenewConfigToFile()
utils.SendOK(w)
} else {
utils.SendErrorResponse(w, "invalid operation given")
}
}
@ -208,42 +224,52 @@ func (a *AutoRenewer) HandleRenewNow(w http.ResponseWriter, r *http.Request) {
utils.SendJSONResponse(w, string(js))
}
// HandleAutoRenewEnable get and set the auto renew enable state
func (a *AutoRenewer) HandleAutoRenewEnable(w http.ResponseWriter, r *http.Request) {
val, err := utils.PostPara(r, "enable")
if err != nil {
if r.Method == http.MethodGet {
js, _ := json.Marshal(a.RenewerConfig.Enabled)
utils.SendJSONResponse(w, string(js))
} else {
if val == "true" {
} else if r.Method == http.MethodPost {
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
if a.RenewerConfig.Email == "" {
utils.SendErrorResponse(w, "Email is not set")
return
}
a.RenewerConfig.Enabled = true
a.saveRenewConfigToFile()
log.Println("[ACME] ACME auto renew enabled")
a.Logf("ACME auto renew enabled", nil)
a.StartAutoRenewTicker()
} else {
a.RenewerConfig.Enabled = false
a.saveRenewConfigToFile()
log.Println("[ACME] ACME auto renew disabled")
a.Logf("ACME auto renew disabled", nil)
a.StopAutoRenewTicker()
}
} else {
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
}
}
func (a *AutoRenewer) HandleACMEEmail(w http.ResponseWriter, r *http.Request) {
email, err := utils.PostPara(r, "set")
if err != nil {
if r.Method == http.MethodGet {
//Return the current email to user
js, _ := json.Marshal(a.RenewerConfig.Email)
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
_, err := mail.ParseAddress(email)
_, err = mail.ParseAddress(email)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
@ -252,8 +278,11 @@ func (a *AutoRenewer) HandleACMEEmail(w http.ResponseWriter, r *http.Request) {
//Set the new config
a.RenewerConfig.Email = email
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
@ -263,7 +292,7 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
certFolder := a.CertFolder
files, err := os.ReadDir(certFolder)
if err != nil {
log.Println("Unable to renew certificates: " + err.Error())
a.Logf("Read certificate store failed", err)
return []string{}, err
}
@ -277,13 +306,12 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
if err != nil {
continue
}
if CertExpireSoon(certBytes) || CertIsExpired(certBytes) {
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
//This cert is expired
DNSName, err := ExtractDomains(certBytes)
if err != nil {
//Maybe self signed. Ignore this
log.Println("Encounted error when trying to resolve DNS name for cert " + file.Name())
a.Logf("Encounted error when trying to resolve DNS name for cert "+file.Name(), err)
continue
}
@ -305,13 +333,12 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
if err != nil {
continue
}
if CertExpireSoon(certBytes) || CertIsExpired(certBytes) {
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
//This cert is expired
DNSName, err := ExtractDomains(certBytes)
if err != nil {
//Maybe self signed. Ignore this
log.Println("Encounted error when trying to resolve DNS name for cert " + file.Name())
a.Logf("Encounted error when trying to resolve DNS name for cert "+file.Name(), err)
continue
}
@ -327,6 +354,7 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
return a.renewExpiredDomains(expiredCertList)
}
// Close the auto renewer
func (a *AutoRenewer) Close() {
if a.TickerstopChan != nil {
a.TickerstopChan <- true
@ -338,29 +366,35 @@ func (a *AutoRenewer) Close() {
func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, error) {
renewedCertFiles := []string{}
for _, expiredCert := range certs {
log.Println("Renewing " + expiredCert.Filepath + " (Might take a few minutes)")
a.Logf("Renewing "+expiredCert.Filepath+" (Might take a few minutes)", nil)
fileName := filepath.Base(expiredCert.Filepath)
certName := fileName[:len(fileName)-len(filepath.Ext(fileName))]
// Load certificate info for ACME detail
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
certInfo, err := loadCertInfoJSON(certInfoFilename)
certInfo, err := LoadCertInfoJSON(certInfoFilename)
if err != nil {
log.Printf("Renew %s certificate error, can't get the ACME detail for cert: %v, trying org section as ca", certName, err)
a.Logf("Renew "+certName+"certificate error, can't get the ACME detail for certificate, trying org section as ca", err)
if CAName, extractErr := ExtractIssuerNameFromPEM(expiredCert.Filepath); extractErr != nil {
log.Printf("extract issuer name for cert error: %v, using default ca", extractErr)
a.Logf("Extract issuer name for cert error, using default ca", err)
certInfo = &CertificateInfoJSON{}
} else {
certInfo = &CertificateInfoJSON{AcmeName: CAName}
}
}
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS)
//For upgrading config from older version of Zoraxy which don't have timeout
if certInfo.PropTimeout == 0 {
//Set default timeout
certInfo.PropTimeout = 300
}
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS, certInfo.UseDNS, certInfo.PropTimeout)
if err != nil {
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
a.Logf("Renew "+fileName+"("+strings.Join(expiredCert.Domains, ",")+") failed", err)
} else {
log.Println("Successfully renewed " + filepath.Base(expiredCert.Filepath))
a.Logf("Successfully renewed "+filepath.Base(expiredCert.Filepath), nil)
renewedCertFiles = append(renewedCertFiles, filepath.Base(expiredCert.Filepath))
}
}
@ -373,3 +407,65 @@ func (a *AutoRenewer) saveRenewConfigToFile() error {
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
return os.WriteFile(a.ConfigFilePath, js, 0775)
}
// Handle update auto renew EAD configuration
func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
kid, err := utils.GetPara(r, "kid")
if err != nil {
utils.SendErrorResponse(w, "kid not set")
return
}
hmacEncoded, err := utils.GetPara(r, "hmacEncoded")
if err != nil {
utils.SendErrorResponse(w, "hmacEncoded not set")
return
}
acmeDirectoryURL, err := utils.GetPara(r, "acmeDirectoryURL")
if err != nil {
utils.SendErrorResponse(w, "acmeDirectoryURL not set")
return
}
if !a.AcmeHandler.Database.TableExists("acme") {
a.AcmeHandler.Database.NewTable("acme")
}
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_kid", kid)
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_hmacEncoded", hmacEncoded)
utils.SendOK(w)
}
// Handle update auto renew DNS configuration
func (a *AutoRenewer) HandleSetDNS(w http.ResponseWriter, r *http.Request) {
dnsProvider, err := utils.PostPara(r, "dnsProvider")
if err != nil {
utils.SendErrorResponse(w, "dnsProvider not set")
return
}
dnsCredentials, err := utils.PostPara(r, "dnsCredentials")
if err != nil {
utils.SendErrorResponse(w, "dnsCredentials not set")
return
}
filename, err := utils.PostPara(r, "filename")
if err != nil {
utils.SendErrorResponse(w, "filename not set")
return
}
if !a.AcmeHandler.Database.TableExists("acme") {
a.AcmeHandler.Database.NewTable("acme")
}
a.AcmeHandler.Database.Write("acme", filename+"_dns_provider", dnsProvider)
a.AcmeHandler.Database.Write("acme", filename+"_dns_credentials", dnsCredentials)
utils.SendOK(w)
}

View File

@ -3,7 +3,7 @@ package acme
/*
CA.go
This script load CA defination from embedded ca.json
This script load CA definition from embedded ca.json
*/
import (
_ "embed"
@ -13,7 +13,7 @@ import (
"strings"
)
// CA Defination, load from embeded json when startup
// CA definition, load from embeded json when startup
type CaDef struct {
Production map[string]string
Test map[string]string

View File

@ -5,14 +5,14 @@ import (
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"os"
"time"
)
// Get the issuer name from pem file
func ExtractIssuerNameFromPEM(pemFilePath string) (string, error) {
// Read the PEM file
pemData, err := ioutil.ReadFile(pemFilePath)
pemData, err := os.ReadFile(pemFilePath)
if err != nil {
return "", err
}
@ -81,13 +81,14 @@ func CertIsExpired(certBytes []byte) bool {
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)
if block != nil {
cert, err := x509.ParseCertificate(block.Bytes)
if err == nil {
expirationDate := cert.NotAfter
threshold := 14 * 24 * time.Hour // 14 days
threshold := time.Duration(numberOfDays) * 24 * time.Hour
timeRemaining := time.Until(expirationDate)
if timeRemaining <= threshold {

View File

@ -1,76 +0,0 @@
package aroz
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"net/url"
"os"
)
//To be used with arozos system
type ArozHandler struct {
Port string
restfulEndpoint string
}
//Information required for registering this subservice to arozos
type ServiceInfo struct {
Name string //Name of this module. e.g. "Audio"
Desc string //Description for this module
Group string //Group of the module, e.g. "system" / "media" etc
IconPath string //Module icon image path e.g. "Audio/img/function_icon.png"
Version string //Version of the module. Format: [0-9]*.[0-9][0-9].[0-9]
StartDir string //Default starting dir, e.g. "Audio/index.html"
SupportFW bool //Support floatWindow. If yes, floatWindow dir will be loaded
LaunchFWDir string //This link will be launched instead of 'StartDir' if fw mode
SupportEmb bool //Support embedded mode
LaunchEmb string //This link will be launched instead of StartDir / Fw if a file is opened with this module
InitFWSize []int //Floatwindow init size. [0] => Width, [1] => Height
InitEmbSize []int //Embedded mode init size. [0] => Width, [1] => Height
SupportedExt []string //Supported File Extensions. e.g. ".mp3", ".flac", ".wav"
}
//This function will request the required flag from the startup paramters and parse it to the need of the arozos.
func HandleFlagParse(info ServiceInfo) *ArozHandler {
var infoRequestMode = flag.Bool("info", false, "Show information about this program in JSON")
var port = flag.String("port", ":8000", "Management web interface listening port")
var restful = flag.String("rpt", "", "Reserved")
//Parse the flags
flag.Parse()
if *infoRequestMode {
//Information request mode
jsonString, _ := json.MarshalIndent(info, "", " ")
fmt.Println(string(jsonString))
os.Exit(0)
}
return &ArozHandler{
Port: *port,
restfulEndpoint: *restful,
}
}
//Get the username and resources access token from the request, return username, token
func (a *ArozHandler) GetUserInfoFromRequest(w http.ResponseWriter, r *http.Request) (string, string) {
username := r.Header.Get("aouser")
token := r.Header.Get("aotoken")
return username, token
}
func (a *ArozHandler) IsUsingExternalPermissionManager() bool {
return !(a.restfulEndpoint == "")
}
//Request gateway interface for advance permission sandbox control
func (a *ArozHandler) RequestGatewayInterface(token string, script string) (*http.Response, error) {
resp, err := http.PostForm(a.restfulEndpoint,
url.Values{"token": {token}, "script": {script}})
if err != nil {
// handle error
return nil, err
}
return resp, nil
}

Binary file not shown.

View File

@ -14,10 +14,10 @@ import (
"strings"
"encoding/hex"
"log"
"github.com/gorilla/sessions"
db "imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils"
)
@ -27,6 +27,7 @@ type AuthAgent struct {
SessionStore *sessions.CookieStore
Database *db.Database
LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
Logger *logger.Logger
}
type AuthEndpoints struct {
@ -37,12 +38,12 @@ type AuthEndpoints struct {
Autologin string
}
//Constructor
func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
// Constructor
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)
err := sysdb.NewTable("auth")
if err != nil {
log.Println("Failed to create auth database. Terminating.")
systemLogger.Println("Failed to create auth database. Terminating.")
panic(err)
}
@ -52,13 +53,14 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database,
SessionStore: store,
Database: sysdb,
LoginRedirectionHandler: loginRedirectionHandler,
Logger: systemLogger,
}
//Return the authAgent
return &newAuthAgent
}
func GetSessionKey(sysdb *db.Database) (string, error) {
func GetSessionKey(sysdb *db.Database, logger *logger.Logger) (string, error) {
sysdb.NewTable("auth")
sessionKey := ""
if !sysdb.KeyExists("auth", "sessionkey") {
@ -66,9 +68,9 @@ func GetSessionKey(sysdb *db.Database) (string, error) {
rand.Read(key)
sessionKey = string(key)
sysdb.Write("auth", "sessionkey", sessionKey)
log.Println("[Auth] New authentication session key generated")
logger.PrintAndLog("auth", "New authentication session key generated", nil)
} 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)
if err != nil {
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
}
//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)) {
if a.CheckAuth(r) {
//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) {
//Get username from request using POST mode
username, err := utils.PostPara(r, "username")
if err != nil {
//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.")
return
}
@ -124,11 +126,11 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
a.LoginUserByRequest(w, r, username, rememberme)
//Print the login message to console
log.Println(username + " logged in.")
a.Logger.PrintAndLog("auth", username+" logged in.", nil)
utils.SendOK(w)
} else {
//Password incorrect
log.Println(username + " login request rejected: " + rejectionReason)
a.Logger.PrintAndLog("auth", username+" login request rejected: "+rejectionReason, nil)
utils.SendErrorResponse(w, rejectionReason)
return
@ -140,14 +142,14 @@ func (a *AuthAgent) ValidateUsernameAndPassword(username string, password string
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) {
hashedPassword := Hash(password)
var passwordInDB string
err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
if err != nil {
//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"
}
@ -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) {
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)
}
//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) {
username, err := a.GetUserName(w, r)
if err != nil {
utils.SendErrorResponse(w, "user not logged in")
return
}
if username != "" {
log.Println(username + " logged out.")
a.Logger.PrintAndLog("auth", username+" logged out", nil)
}
// Revoke users authentication
err = a.Logout(w, r)
@ -194,7 +200,7 @@ func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
return
}
w.Write([]byte("OK"))
utils.SendOK(w)
}
func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
@ -204,11 +210,11 @@ func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
}
session.Values["authenticated"] = false
session.Values["username"] = nil
session.Save(r, w)
return nil
session.Options.MaxAge = -1
return session.Save(r, w)
}
//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) {
if a.CheckAuth(r) {
//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) {
if a.CheckAuth(r) {
//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) {
if a.CheckAuth(r) {
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)) {
//Get username from request
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
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)) {
//Get username from request
newusername, err := utils.PostPara(r, "username")
@ -324,15 +330,16 @@ func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Re
//Return to the client with OK
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 {
session, err := a.SessionStore.Get(r, a.SessionName)
if err != nil {
return false
}
// Check if user is authenticated
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
return false
@ -340,8 +347,8 @@ func (a *AuthAgent) CheckAuth(r *http.Request) bool {
return true
}
//Handle de-register of users. Require POST username.
//THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
// Handle de-register of users. Require POST username.
// THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
//Check if the user is logged in
if !a.CheckAuth(r) {
@ -365,7 +372,7 @@ func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
//Return to the client with OK
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 {
@ -381,7 +388,7 @@ func (a *AuthAgent) UnregisterUser(username string) error {
return nil
}
//Get the number of users in the system
// Get the number of users in the system
func (a *AuthAgent) GetUserCounts() int {
entries, _ := a.Database.ListTable("auth")
usercount := 0
@ -393,12 +400,12 @@ func (a *AuthAgent) GetUserCounts() int {
}
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
}
//List all username within the system
// List all username within the system
func (a *AuthAgent) ListUsers() []string {
entries, _ := a.Database.ListTable("auth")
results := []string{}
@ -411,7 +418,7 @@ func (a *AuthAgent) ListUsers() []string {
return results
}
//Check if the given username exists
// Check if the given username exists
func (a *AuthAgent) UserExists(username string) bool {
userpasswordhash := ""
err := a.Database.Read("auth", "passhash/"+username, &userpasswordhash)
@ -421,7 +428,7 @@ func (a *AuthAgent) UserExists(username string) bool {
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 {
session, _ := a.SessionStore.Get(r, a.SessionName)
if session.Values["authenticated"].(bool) {
@ -446,7 +453,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 {
//Check user already exists
if a.UserExists(newusername) {
@ -470,7 +477,7 @@ func (a *AuthAgent) CreateUserAccount(newusername string, password string, email
return nil
}
//Hash the given raw string into sha512 hash
// Hash the given raw string into sha512 hash
func Hash(raw string) string {
h := sha512.New()
h.Write([]byte(raw))

View File

@ -2,7 +2,7 @@ package auth
import (
"errors"
"log"
"fmt"
"net/http"
)
@ -10,7 +10,7 @@ type RouterOption struct {
AuthAgent *AuthAgent
RequireAuth bool //This router require authentication
DeniedHandler func(http.ResponseWriter, *http.Request) //Things to do when request is rejected
TargetMux *http.ServeMux
}
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 {
//Check if the endpoint already registered
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")
}
authAgent := router.option.AuthAgent
//OK. Register handler
http.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) {
if router.option.TargetMux == nil {
http.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)
})
} 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

34
src/mod/auth/sso/app.go Normal file
View File

@ -0,0 +1,34 @@
package sso
/*
app.go
This file contains the app structure and app management
functions for the SSO module.
*/
// RegisteredUpstreamApp is a structure that contains the information of an
// upstream app that is registered with the SSO server
type RegisteredUpstreamApp struct {
ID string
Secret string
Domain []string
Scopes []string
SessionDuration int //in seconds, default to 1 hour
}
// RegisterUpstreamApp registers an upstream app with the SSO server
func (s *SSOHandler) ListRegisteredApps() []*RegisteredUpstreamApp {
apps := make([]*RegisteredUpstreamApp, 0)
for _, app := range s.Apps {
apps = append(apps, &app)
}
return apps
}
// RegisterUpstreamApp registers an upstream app with the SSO server
func (s *SSOHandler) GetAppByID(appID string) (*RegisteredUpstreamApp, bool) {
app, ok := s.Apps[appID]
return &app, ok
}

View File

@ -0,0 +1,271 @@
package sso
/*
handlers.go
This file contains the handlers for the SSO module.
If you are looking for handlers for SSO user management,
please refer to userHandlers.go.
*/
import (
"encoding/json"
"net/http"
"strings"
"github.com/gofrs/uuid"
"imuslab.com/zoraxy/mod/utils"
)
// HandleSSOStatus handle the request to get the status of the SSO portal server
func (s *SSOHandler) HandleSSOStatus(w http.ResponseWriter, r *http.Request) {
type SSOStatus struct {
Enabled bool
SSOInterceptEnabled bool
ListeningPort int
AuthURL string
}
status := SSOStatus{
Enabled: s.ssoPortalServer != nil,
//SSOInterceptEnabled: s.ssoInterceptEnabled,
ListeningPort: s.Config.PortalServerPort,
AuthURL: s.Config.AuthURL,
}
js, _ := json.Marshal(status)
utils.SendJSONResponse(w, string(js))
}
// Wrapper for starting and stopping the SSO portal server
// require POST request with key "enable" and value "true" or "false"
func (s *SSOHandler) HandleSSOEnable(w http.ResponseWriter, r *http.Request) {
enable, err := utils.PostBool(r, "enable")
if err != nil {
utils.SendErrorResponse(w, "invalid enable value")
return
}
if enable {
s.HandleStartSSOPortal(w, r)
} else {
s.HandleStopSSOPortal(w, r)
}
}
// HandleStartSSOPortal handle the request to start the SSO portal server
func (s *SSOHandler) HandleStartSSOPortal(w http.ResponseWriter, r *http.Request) {
if s.ssoPortalServer != nil {
//Already enabled. Do restart instead.
err := s.RestartSSOServer()
if err != nil {
utils.SendErrorResponse(w, "failed to start SSO server")
return
}
utils.SendOK(w)
return
}
//Check if the authURL is set correctly. If not, return error
if s.Config.AuthURL == "" {
utils.SendErrorResponse(w, "auth URL not set")
return
}
//Start the SSO portal server in go routine
go s.StartSSOPortal()
//Write current state to database
err := s.Config.Database.Write("sso_conf", "enabled", true)
if err != nil {
utils.SendErrorResponse(w, "failed to update SSO state")
return
}
utils.SendOK(w)
}
// HandleStopSSOPortal handle the request to stop the SSO portal server
func (s *SSOHandler) HandleStopSSOPortal(w http.ResponseWriter, r *http.Request) {
if s.ssoPortalServer == nil {
//Already disabled
utils.SendOK(w)
return
}
err := s.ssoPortalServer.Close()
if err != nil {
s.Log("Failed to stop SSO portal server", err)
utils.SendErrorResponse(w, "failed to stop SSO portal server")
return
}
s.ssoPortalServer = nil
//Write current state to database
err = s.Config.Database.Write("sso_conf", "enabled", false)
if err != nil {
utils.SendErrorResponse(w, "failed to update SSO state")
return
}
utils.SendOK(w)
}
// HandlePortChange handle the request to change the SSO portal server port
func (s *SSOHandler) HandlePortChange(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
//Return the current port
js, _ := json.Marshal(s.Config.PortalServerPort)
utils.SendJSONResponse(w, string(js))
return
}
port, err := utils.PostInt(r, "port")
if err != nil {
utils.SendErrorResponse(w, "invalid port given")
return
}
s.Config.PortalServerPort = port
//Write to the database
err = s.Config.Database.Write("sso_conf", "port", port)
if err != nil {
utils.SendErrorResponse(w, "failed to update port")
return
}
if s.IsRunning() {
//Restart the server if it is running
err = s.RestartSSOServer()
if err != nil {
utils.SendErrorResponse(w, "failed to restart SSO server")
return
}
}
utils.SendOK(w)
}
// HandleSetAuthURL handle the request to change the SSO auth URL
// This is the URL that the SSO portal server will redirect to for authentication
// e.g. auth.yourdomain.com
func (s *SSOHandler) HandleSetAuthURL(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
//Return the current auth URL
js, _ := json.Marshal(s.Config.AuthURL)
utils.SendJSONResponse(w, string(js))
return
}
//Get the auth URL
authURL, err := utils.PostPara(r, "auth_url")
if err != nil {
utils.SendErrorResponse(w, "invalid auth URL given")
return
}
s.Config.AuthURL = authURL
//Write to the database
err = s.Config.Database.Write("sso_conf", "authurl", authURL)
if err != nil {
utils.SendErrorResponse(w, "failed to update auth URL")
return
}
//Clear the cookie store and restart the server
err = s.RestartSSOServer()
if err != nil {
utils.SendErrorResponse(w, "failed to restart SSO server")
return
}
utils.SendOK(w)
}
// HandleRegisterApp handle the request to register a new app to the SSO portal
func (s *SSOHandler) HandleRegisterApp(w http.ResponseWriter, r *http.Request) {
appName, err := utils.PostPara(r, "app_name")
if err != nil {
utils.SendErrorResponse(w, "invalid app name given")
return
}
id, err := utils.PostPara(r, "app_id")
if err != nil {
//If id is not given, use the app name with a random UUID
newID, err := uuid.NewV4()
if err != nil {
utils.SendErrorResponse(w, "failed to generate new app ID")
return
}
id = strings.ReplaceAll(appName, " ", "") + "-" + newID.String()
}
//Check if the given appid is already in use
if _, ok := s.Apps[id]; ok {
utils.SendErrorResponse(w, "app ID already in use")
return
}
/*
Process the app domain
An app can have multiple domains, separated by commas
Usually the app domain is the proxy rule that points to the app
For example, if the app is hosted at app.yourdomain.com, the app domain is app.yourdomain.com
*/
appDomain, err := utils.PostPara(r, "app_domain")
if err != nil {
utils.SendErrorResponse(w, "invalid app URL given")
return
}
appURLs := strings.Split(appDomain, ",")
//Remove padding and trailing spaces in each URL
for i := range appURLs {
appURLs[i] = strings.TrimSpace(appURLs[i])
}
//Create a new app entry
thisAppEntry := RegisteredUpstreamApp{
ID: id,
Secret: "",
Domain: appURLs,
Scopes: []string{},
SessionDuration: 3600,
}
js, _ := json.Marshal(thisAppEntry)
//Create a new app in the database
err = s.Config.Database.Write("sso_apps", appName, string(js))
if err != nil {
utils.SendErrorResponse(w, "failed to create new app")
return
}
//Also add the app to runtime config
s.Apps[appName] = thisAppEntry
utils.SendOK(w)
}
// HandleAppRemove handle the request to remove an app from the SSO portal
func (s *SSOHandler) HandleAppRemove(w http.ResponseWriter, r *http.Request) {
appID, err := utils.PostPara(r, "app_id")
if err != nil {
utils.SendErrorResponse(w, "invalid app ID given")
return
}
//Check if the app actually exists
if _, ok := s.Apps[appID]; !ok {
utils.SendErrorResponse(w, "app not found")
return
}
delete(s.Apps, appID)
//Also remove it from the database
err = s.Config.Database.Delete("sso_apps", appID)
if err != nil {
s.Log("Failed to remove app from database", err)
}
}

295
src/mod/auth/sso/oauth2.go Normal file
View File

@ -0,0 +1,295 @@
package sso
import (
"context"
_ "embed"
"encoding/json"
"log"
"net/http"
"net/url"
"time"
"github.com/go-oauth2/oauth2/v4/errors"
"github.com/go-oauth2/oauth2/v4/generates"
"github.com/go-oauth2/oauth2/v4/manage"
"github.com/go-oauth2/oauth2/v4/models"
"github.com/go-oauth2/oauth2/v4/server"
"github.com/go-oauth2/oauth2/v4/store"
"github.com/go-session/session"
"imuslab.com/zoraxy/mod/utils"
)
const (
SSO_SESSION_NAME = "ZoraxySSO"
)
type OAuth2Server struct {
srv *server.Server //oAuth server instance
config *SSOConfig
parent *SSOHandler
}
//go:embed static/auth.html
var authHtml []byte
//go:embed static/login.html
var loginHtml []byte
// NewOAuth2Server creates a new OAuth2 server instance
func NewOAuth2Server(config *SSOConfig, parent *SSOHandler) (*OAuth2Server, error) {
manager := manage.NewDefaultManager()
manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
// token store
manager.MustTokenStorage(store.NewFileTokenStore("./conf/sso.db"))
// generate jwt access token
manager.MapAccessGenerate(generates.NewAccessGenerate())
//Load the information of registered app within the OAuth2 server
clientStore := store.NewClientStore()
clientStore.Set("myapp", &models.Client{
ID: "myapp",
Secret: "verysecurepassword",
Domain: "localhost:9094",
})
//TODO: LOAD THIS DYNAMICALLY FROM DATABASE
manager.MapClientStorage(clientStore)
thisServer := OAuth2Server{
config: config,
parent: parent,
}
//Create a new oauth server
srv := server.NewServer(server.NewConfig(), manager)
srv.SetPasswordAuthorizationHandler(thisServer.PasswordAuthorizationHandler)
srv.SetUserAuthorizationHandler(thisServer.UserAuthorizeHandler)
srv.SetInternalErrorHandler(func(err error) (re *errors.Response) {
log.Println("Internal Error:", err.Error())
return
})
srv.SetResponseErrorHandler(func(re *errors.Response) {
log.Println("Response Error:", re.Error.Error())
})
//Set the access scope handler
srv.SetAuthorizeScopeHandler(thisServer.AuthorizationScopeHandler)
//Set the access token expiration handler based on requesting domain / hostname
srv.SetAccessTokenExpHandler(thisServer.ExpireHandler)
thisServer.srv = srv
return &thisServer, nil
}
// Password handler, validate if the given username and password are correct
func (oas *OAuth2Server) PasswordAuthorizationHandler(ctx context.Context, clientID, username, password string) (userID string, err error) {
//TODO: LOAD THIS DYNAMICALLY FROM DATABASE
if username == "test" && password == "test" {
userID = "test"
}
return
}
// User Authorization Handler, handle auth request from user
func (oas *OAuth2Server) UserAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string, err error) {
store, err := session.Start(r.Context(), w, r)
if err != nil {
return
}
uid, ok := store.Get(SSO_SESSION_NAME)
if !ok {
if r.Form == nil {
r.ParseForm()
}
store.Set("ReturnUri", r.Form)
store.Save()
w.Header().Set("Location", "/oauth2/login")
w.WriteHeader(http.StatusFound)
return
}
userID = uid.(string)
store.Delete(SSO_SESSION_NAME)
store.Save()
return
}
// AccessTokenExpHandler, set the SSO session length default value
func (oas *OAuth2Server) ExpireHandler(w http.ResponseWriter, r *http.Request) (exp time.Duration, err error) {
requestHostname := r.Host
if requestHostname == "" {
//Use default value
return time.Hour, nil
}
//Get the Registered App Config from parent
appConfig, ok := oas.parent.Apps[requestHostname]
if !ok {
//Use default value
return time.Hour, nil
}
//Use the app's session length
return time.Second * time.Duration(appConfig.SessionDuration), nil
}
// AuthorizationScopeHandler, handle the scope of the request
func (oas *OAuth2Server) AuthorizationScopeHandler(w http.ResponseWriter, r *http.Request) (scope string, err error) {
//Get the scope from post or GEt request
if r.Form == nil {
if err := r.ParseForm(); err != nil {
return "none", err
}
}
//Get the hostname of the request
requestHostname := r.Host
if requestHostname == "" {
//No rule set. Use default
return "none", nil
}
//Get the Registered App Config from parent
appConfig, ok := oas.parent.Apps[requestHostname]
if !ok {
//No rule set. Use default
return "none", nil
}
//Check if the scope is set in the request
if v, ok := r.Form["scope"]; ok {
//Check if the requested scope is in the appConfig scope
if utils.StringInArray(appConfig.Scopes, v[0]) {
return v[0], nil
}
return "none", nil
}
return "none", nil
}
/* SSO Web Server Toggle Functions */
func (oas *OAuth2Server) RegisterOauthEndpoints(primaryMux *http.ServeMux) {
primaryMux.HandleFunc("/oauth2/login", oas.loginHandler)
primaryMux.HandleFunc("/oauth2/auth", oas.authHandler)
primaryMux.HandleFunc("/oauth2/authorize", func(w http.ResponseWriter, r *http.Request) {
store, err := session.Start(r.Context(), w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var form url.Values
if v, ok := store.Get("ReturnUri"); ok {
form = v.(url.Values)
}
r.Form = form
store.Delete("ReturnUri")
store.Save()
err = oas.srv.HandleAuthorizeRequest(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
})
primaryMux.HandleFunc("/oauth2/token", func(w http.ResponseWriter, r *http.Request) {
err := oas.srv.HandleTokenRequest(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
primaryMux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
token, err := oas.srv.ValidationBearerToken(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
data := map[string]interface{}{
"expires_in": int64(time.Until(token.GetAccessCreateAt().Add(token.GetAccessExpiresIn())).Seconds()),
"client_id": token.GetClientID(),
"user_id": token.GetUserID(),
}
e := json.NewEncoder(w)
e.SetIndent("", " ")
e.Encode(data)
})
}
func (oas *OAuth2Server) loginHandler(w http.ResponseWriter, r *http.Request) {
store, err := session.Start(r.Context(), w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if r.Method == "POST" {
if r.Form == nil {
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
//Load username and password from form post
username, err := utils.PostPara(r, "username")
if err != nil {
w.Write([]byte("invalid username or password"))
return
}
password, err := utils.PostPara(r, "password")
if err != nil {
w.Write([]byte("invalid username or password"))
return
}
//Validate the user
if !oas.parent.ValidateUsernameAndPassword(username, password) {
//Wrong password
w.Write([]byte("invalid username or password"))
return
}
store.Set(SSO_SESSION_NAME, r.Form.Get("username"))
store.Save()
w.Header().Set("Location", "/oauth2/auth")
w.WriteHeader(http.StatusFound)
return
} else if r.Method == "GET" {
//Check if the user is logged in
if _, ok := store.Get(SSO_SESSION_NAME); ok {
w.Header().Set("Location", "/oauth2/auth")
w.WriteHeader(http.StatusFound)
return
}
}
//User not logged in. Show login page
w.Write(loginHtml)
}
func (oas *OAuth2Server) authHandler(w http.ResponseWriter, r *http.Request) {
store, err := session.Start(context.TODO(), w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, ok := store.Get(SSO_SESSION_NAME); !ok {
w.Header().Set("Location", "/oauth2/login")
w.WriteHeader(http.StatusFound)
return
}
//User logged in. Check if this user have previously authorized the app
//TODO: Check if the user have previously authorized the app
//User have not authorized the app. Show the authorization page
w.Write(authHtml)
}

View File

@ -0,0 +1 @@
package sso

View File

@ -0,0 +1,58 @@
package sso
import (
"encoding/json"
"net/http"
"strings"
)
type OpenIDConfiguration struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
JwksUri string `json:"jwks_uri"`
ResponseTypesSupported []string `json:"response_types_supported"`
SubjectTypesSupported []string `json:"subject_types_supported"`
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
ClaimsSupported []string `json:"claims_supported"`
}
func (h *SSOHandler) HandleDiscoveryRequest(w http.ResponseWriter, r *http.Request) {
//Prepend https:// if not present
authBaseURL := h.Config.AuthURL
if !strings.HasPrefix(authBaseURL, "http://") && !strings.HasPrefix(authBaseURL, "https://") {
authBaseURL = "https://" + authBaseURL
}
//Handle the discovery request
discovery := OpenIDConfiguration{
Issuer: authBaseURL,
AuthorizationEndpoint: authBaseURL + "/oauth2/authorize",
TokenEndpoint: authBaseURL + "/oauth2/token",
JwksUri: authBaseURL + "/jwks.json",
ResponseTypesSupported: []string{"code", "token"},
SubjectTypesSupported: []string{"public"},
IDTokenSigningAlgValuesSupported: []string{
"RS256",
},
ClaimsSupported: []string{
"sub", //Subject, usually the user ID
"iss", //Issuer, usually the server URL
"aud", //Audience, usually the client ID
"exp", //Expiration Time
"iat", //Issued At
"email", //Email
"locale", //Locale
"name", //Full Name
"nickname", //Nickname
"preferred_username", //Preferred Username
"website", //Website
},
}
//Write the response
js, _ := json.Marshal(discovery)
w.Header().Set("Content-Type", "application/json")
w.Write(js)
}

132
src/mod/auth/sso/server.go Normal file
View File

@ -0,0 +1,132 @@
package sso
import (
"context"
"net/http"
"strconv"
"time"
"github.com/go-oauth2/oauth2/v4/errors"
"imuslab.com/zoraxy/mod/utils"
)
/*
server.go
This is the web server for the SSO portal. It contains the
HTTP server and the handlers for the SSO portal.
If you are looking for handlers that changes the settings
of the SSO portale or user management, please refer to
handlers.go.
*/
func (h *SSOHandler) InitSSOPortal(portalServerPort int) {
//Create a new web server for the SSO portal
pmux := http.NewServeMux()
fs := http.FileServer(http.FS(staticFiles))
pmux.Handle("/", fs)
//Register API endpoint for the SSO portal
pmux.HandleFunc("/sso/login", h.HandleLogin)
//Register API endpoint for autodiscovery
pmux.HandleFunc("/.well-known/openid-configuration", h.HandleDiscoveryRequest)
//Register OAuth2 endpoints
h.Oauth2Server.RegisterOauthEndpoints(pmux)
h.ssoPortalMux = pmux
}
// StartSSOPortal start the SSO portal server
// This function will block the main thread, call it in a goroutine
func (h *SSOHandler) StartSSOPortal() error {
if h.ssoPortalServer != nil {
return errors.New("SSO portal server already running")
}
h.ssoPortalServer = &http.Server{
Addr: ":" + strconv.Itoa(h.Config.PortalServerPort),
Handler: h.ssoPortalMux,
}
err := h.ssoPortalServer.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
h.Log("Failed to start SSO portal server", err)
}
return err
}
// StopSSOPortal stop the SSO portal server
func (h *SSOHandler) StopSSOPortal() error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := h.ssoPortalServer.Shutdown(ctx)
if err != nil {
h.Log("Failed to stop SSO portal server", err)
return err
}
h.ssoPortalServer = nil
return nil
}
// StartSSOPortal start the SSO portal server
func (h *SSOHandler) RestartSSOServer() error {
if h.ssoPortalServer != nil {
err := h.StopSSOPortal()
if err != nil {
return err
}
}
go h.StartSSOPortal()
return nil
}
func (h *SSOHandler) IsRunning() bool {
return h.ssoPortalServer != nil
}
// HandleLogin handle the login request
func (h *SSOHandler) HandleLogin(w http.ResponseWriter, r *http.Request) {
//Handle the login request
username, err := utils.PostPara(r, "username")
if err != nil {
utils.SendErrorResponse(w, "invalid username or password")
return
}
password, err := utils.PostPara(r, "password")
if err != nil {
utils.SendErrorResponse(w, "invalid username or password")
return
}
rememberMe, err := utils.PostBool(r, "remember_me")
if err != nil {
rememberMe = false
}
//Check if the user exists
userEntry, err := h.GetSSOUser(username)
if err != nil {
utils.SendErrorResponse(w, "user not found")
return
}
//Check if the password is correct
if !userEntry.VerifyPassword(password) {
utils.SendErrorResponse(w, "incorrect password")
return
}
//Create a new session for the user
session, _ := h.cookieStore.Get(r, "Zoraxy-SSO")
session.Values["username"] = username
if rememberMe {
session.Options.MaxAge = 86400 * 15 //15 days
} else {
session.Options.MaxAge = 3600 //1 hour
}
session.Save(r, w) //Save the session
utils.SendOK(w)
}

158
src/mod/auth/sso/sso.go Normal file
View File

@ -0,0 +1,158 @@
package sso
import (
"embed"
"net/http"
"github.com/gorilla/sessions"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/info/logger"
)
/*
sso.go
This file contains the main SSO handler and the SSO configuration
structure. It also contains the main SSO handler functions.
SSO web interface are stored in the static folder, which is embedded
into the binary.
*/
//go:embed static/*
var staticFiles embed.FS //Static files for the SSO portal
type SSOConfig struct {
SystemUUID string //System UUID, should be passed in from main scope
AuthURL string //Authentication subdomain URL, e.g. auth.example.com
PortalServerPort int //SSO portal server port
Database *database.Database //System master key-value database
Logger *logger.Logger
}
// SSOHandler is the main SSO handler structure
type SSOHandler struct {
cookieStore *sessions.CookieStore
ssoPortalServer *http.Server
ssoPortalMux *http.ServeMux
Oauth2Server *OAuth2Server
Config *SSOConfig
Apps map[string]RegisteredUpstreamApp
}
// Create a new Zoraxy SSO handler
func NewSSOHandler(config *SSOConfig) (*SSOHandler, error) {
//Create a cookie store for the SSO handler
cookieStore := sessions.NewCookieStore([]byte(config.SystemUUID))
cookieStore.Options = &sessions.Options{
Path: "",
Domain: "",
MaxAge: 0,
Secure: false,
HttpOnly: false,
SameSite: 0,
}
config.Database.NewTable("sso_users") //For storing user information
config.Database.NewTable("sso_conf") //For storing SSO configuration
config.Database.NewTable("sso_apps") //For storing registered apps
//Create the SSO Handler
thisHandler := SSOHandler{
cookieStore: cookieStore,
Config: config,
}
//Read the app info from database
thisHandler.Apps = make(map[string]RegisteredUpstreamApp)
//Create an oauth2 server
oauth2Server, err := NewOAuth2Server(config, &thisHandler)
if err != nil {
return nil, err
}
//Register endpoints
thisHandler.Oauth2Server = oauth2Server
thisHandler.InitSSOPortal(config.PortalServerPort)
return &thisHandler, nil
}
func (h *SSOHandler) RestorePreviousRunningState() {
//Load the previous SSO state
ssoEnabled := false
ssoPort := 5488
ssoAuthURL := ""
h.Config.Database.Read("sso_conf", "enabled", &ssoEnabled)
h.Config.Database.Read("sso_conf", "port", &ssoPort)
h.Config.Database.Read("sso_conf", "authurl", &ssoAuthURL)
if ssoAuthURL == "" {
//Cannot enable SSO without auth URL
ssoEnabled = false
}
h.Config.PortalServerPort = ssoPort
h.Config.AuthURL = ssoAuthURL
if ssoEnabled {
go h.StartSSOPortal()
}
}
// ServeForwardAuth handle the SSO request in interception mode
// Suppose to be called in dynamicproxy.
// Return true if the request is allowed to pass, false if the request is blocked and shall not be further processed
func (h *SSOHandler) ServeForwardAuth(w http.ResponseWriter, r *http.Request) bool {
//Get the current uri for appending to the auth subdomain
originalRequestURL := r.RequestURI
redirectAuthURL := h.Config.AuthURL
if redirectAuthURL == "" || !h.IsRunning() {
//Redirect not set or auth server is offlined
w.Write([]byte("SSO auth URL not set or SSO server offline."))
//TODO: Use better looking template if exists
return false
}
//Check if the user have the cookie "Zoraxy-SSO" set
session, err := h.cookieStore.Get(r, "Zoraxy-SSO")
if err != nil {
//Redirect to auth subdomain
http.Redirect(w, r, redirectAuthURL+"/sso/login?m=new&t="+originalRequestURL, http.StatusFound)
return false
}
//Check if the user is logged in
if session.Values["username"] != true {
//Redirect to auth subdomain
http.Redirect(w, r, redirectAuthURL+"/sso/login?m=expired&t="+originalRequestURL, http.StatusFound)
return false
}
//Check if the current request subdomain is allowed
userName := session.Values["username"].(string)
user, err := h.GetSSOUser(userName)
if err != nil {
//User might have been removed from SSO. Redirect to auth subdomain
http.Redirect(w, r, redirectAuthURL, http.StatusFound)
return false
}
//Check if the user have access to the current subdomain
if !user.Subdomains[r.Host].AllowAccess {
//User is not allowed to access the current subdomain. Sent 403
http.Error(w, "Forbidden", http.StatusForbidden)
//TODO: Use better looking template if exists
return false
}
//User is logged in, continue to the next handler
return true
}
// Log a message with the SSO module tag
func (h *SSOHandler) Log(message string, err error) {
h.Config.Logger.PrintAndLog("SSO", message, err)
}

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Auth</title>
<link
rel="stylesheet"
href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
/>
<script src="//code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="jumbotron">
<form action="/oauth2/authorize" method="POST">
<h1>Authorize</h1>
<p>The client would like to perform actions on your behalf.</p>
<p>
<button
type="submit"
class="btn btn-primary btn-lg"
style="width:200px;"
>
Allow
</button>
</p>
</form>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>Login Page</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css">
</head>
<body>
<div class="ui container">
<div class="ui middle aligned center aligned grid">
<div class="column">
<h2 class="ui teal image header">
<div class="content">
Log in to your account
</div>
</h2>
<form class="ui large form">
<div class="ui stacked segment">
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" name="username" placeholder="Username">
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input type="password" name="password" placeholder="Password">
</div>
</div>
<div class="ui fluid large teal submit button">Login</div>
</div>
<div class="ui error message"></div>
</form>
<div class="ui message">
New to us? <a href="#">Sign Up</a>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js"></script>
</body>
</html>

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