Compare commits
94 Commits
Author | SHA1 | Date | |
---|---|---|---|
83536a83f7 | |||
1183b0ed55 | |||
b00e302f6d | |||
deddb17803 | |||
aa96d831e1 | |||
c6f7f37aaf | |||
63f12dedcf | |||
136d1ecafb | |||
7193defad7 | |||
cf4c57298e | |||
d82a531a41 | |||
7694e317f7 | |||
ed4945ab7e | |||
ce8741bfc8 | |||
7a3db09811 | |||
e73f9b47d3 | |||
c248dacccf | |||
d596d6b843 | |||
6feb2d105d | |||
3a26a5b4d3 | |||
2cdd5654ed | |||
a0d362df4e | |||
334c1ab131 | |||
08d52024ab | |||
a3e16594e8 | |||
cced07ba2d | |||
2003992d75 | |||
71423d98b1 | |||
8ca716c59f | |||
fe48a9a0c3 | |||
ec973eb3bc | |||
7b69b5fa63 | |||
ce4f46cb50 | |||
3454a9b975 | |||
55bc939a37 | |||
1d63b679dc | |||
3df96350a3 | |||
34fab7b3d0 | |||
46817d0664 | |||
1db2ca61fa | |||
0b601406de | |||
b4c771cdee | |||
a486d42351 | |||
90c2199a1b | |||
161c61fac7 | |||
5ffacb1d06 | |||
75ebd0ffbe | |||
dc069f3c57 | |||
e1b512f78f | |||
8854a38f49 | |||
7583a4628c | |||
73c0ea0896 | |||
7dad7c7305 | |||
faa95b4e21 | |||
cb0e13976d | |||
ccd8dcff56 | |||
750656fd7f | |||
d9f515fdba | |||
176249a7d9 | |||
e2a449a7bc | |||
a9695e969e | |||
7ba997dfc2 | |||
d00117e878 | |||
43a84a3f1c | |||
e24f31bdef | |||
fc9240fbac | |||
e0f5431215 | |||
de658a3c6c | |||
73276b1918 | |||
abdb7d4d75 | |||
72299ace15 | |||
4d6c79f51b | |||
2c045f4f40 | |||
b8cf046ca6 | |||
026dd6b89d | |||
5805fe6ed2 | |||
3c78211800 | |||
8e648a8e1f | |||
a000893dd1 | |||
db88bfb752 | |||
05297d854b | |||
0d7bce4d30 | |||
8db95dddc6 | |||
05daeded37 | |||
8ce6471be5 | |||
e242c9288f | |||
c55a29e7cf | |||
6af047430c | |||
200c924acd | |||
9b2168466c | |||
7ae48bf370 | |||
ee3d76fb96 | |||
40d192524b | |||
c659e05005 |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -33,6 +33,7 @@ If applicable, add screenshots to help explain your problem.
|
|||||||
- Device: [e.g. Bananapi R2 PRO]
|
- Device: [e.g. Bananapi R2 PRO]
|
||||||
- OS: [e.g. Armbian]
|
- OS: [e.g. Armbian]
|
||||||
- Version [e.g. 23.02 Bullseye ]
|
- Version [e.g. 23.02 Bullseye ]
|
||||||
|
- Docker Version (if you are running Zoraxy in docker): [e.g. 3.0.4]
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
25
.github/ISSUE_TEMPLATE/help-needed.md
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
name: Help Needed
|
||||||
|
about: Something went wrong but I don't know why
|
||||||
|
title: "[HELP]"
|
||||||
|
labels: help wanted
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**What happened?**
|
||||||
|
A clear and concise description of what the problem is. Ex. I tried to create a proxy rule but it doesn't work. When I connects to my domain, I see [...]
|
||||||
|
|
||||||
|
**Describe what have you tried**
|
||||||
|
A clear and concise description of what you expect to see and what you have tried to debug it.
|
||||||
|
|
||||||
|
**Describe the networking setup you are using**
|
||||||
|
Here are some example, commonly asked questions from our maintainers:
|
||||||
|
- Are you using the docker build of Zoraxy? [yes (with docker setup & networking config attach) /no]
|
||||||
|
- Your Zoraxy version? [e.g. 3.0.4]
|
||||||
|
- Are you using Cloudflare? [yes/no]
|
||||||
|
- Are your system hosted under a NAT router? [e.g. yes, with subnet is e.g. 192.168.0.0/24 and include port forwarding config if any]
|
||||||
|
- DNS record (if any)
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
4
.github/workflows/main.yml
vendored
@ -22,7 +22,6 @@ jobs:
|
|||||||
- name: Login to Docker & GHCR
|
- name: Login to Docker & GHCR
|
||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||||
#echo "${{ secrets.GHCR_PASSWORD }}" | docker login ghcr.io -u "${{ secrets.GHCR_USERNAME }}" --password-stdin
|
|
||||||
|
|
||||||
- name: Setup building file structure
|
- name: Setup building file structure
|
||||||
run: |
|
run: |
|
||||||
@ -39,7 +38,4 @@ jobs:
|
|||||||
--platform linux/amd64,linux/arm64 \
|
--platform linux/amd64,linux/arm64 \
|
||||||
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \
|
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \
|
||||||
--tag zoraxydocker/zoraxy:latest \
|
--tag zoraxydocker/zoraxy:latest \
|
||||||
# Since this is still undetermined, I will leave it commented
|
|
||||||
#--tag ghcr.io/zoraxydocker/zoraxy:${{ steps.get_latest_release_tag.outputs.latest_tag }} \
|
|
||||||
#--tag ghcr.io/zoraxydocker/zoraxy:latest \
|
|
||||||
.
|
.
|
||||||
|
4
.gitignore
vendored
@ -31,4 +31,6 @@ src/rules/*
|
|||||||
src/README.md
|
src/README.md
|
||||||
docker/ContainerTester.sh
|
docker/ContainerTester.sh
|
||||||
docker/ImagePublisher.sh
|
docker/ImagePublisher.sh
|
||||||
src/mod/acme/test/stackoverflow.pem
|
src/mod/acme/test/stackoverflow.pem
|
||||||
|
/tools/dns_challenge_update/code-gen/acmedns
|
||||||
|
/tools/dns_challenge_update/code-gen/lego
|
||||||
|
89
CHANGELOG.md
@ -1,3 +1,92 @@
|
|||||||
|
# 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
|
# v2.6.8 Nov 25 2023
|
||||||
|
|
||||||
+ Added opt-out for subdomains for global TLS settings: See [release notes](https://github.com/tobychui/zoraxy/releases/tag/2.6.8)
|
+ Added opt-out for subdomains for global TLS settings: See [release notes](https://github.com/tobychui/zoraxy/releases/tag/2.6.8)
|
||||||
|
45
README.md
@ -2,22 +2,23 @@
|
|||||||
|
|
||||||
# Zoraxy
|
# Zoraxy
|
||||||
|
|
||||||
General purpose request (reverse) proxy and forwarding tool for networking noobs. Now written in Go!
|
A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
|
||||||
|
|
||||||
*Zoraxy v3 HTTP proxy config is not compatible with the older v2. If you are looking for the legacy version of Zoraxy, take a look at the [v2 branch](https://github.com/tobychui/zoraxy/tree/v2)*
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- Simple to use interface with detail in-system instructions
|
- Simple to use interface with detail in-system instructions
|
||||||
- Reverse Proxy
|
- Reverse Proxy (HTTP/2)
|
||||||
- Virtual Directory
|
- Virtual Directory
|
||||||
|
- WebSocket Proxy (automatic, no set-up needed)
|
||||||
- Basic Auth
|
- Basic Auth
|
||||||
|
- Alias Hostnames
|
||||||
- Custom Headers
|
- Custom Headers
|
||||||
- Redirection Rules
|
- Redirection Rules
|
||||||
- TLS / SSL setup and deploy
|
- TLS / SSL setup and deploy
|
||||||
- ACME features like auto-renew to serve your sites in http**s**
|
- ACME features like auto-renew to serve your sites in http**s**
|
||||||
- SNI support (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)
|
- Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)
|
||||||
- Global Area Network Controller Web UI (ZeroTier not included)
|
- Global Area Network Controller Web UI (ZeroTier not included)
|
||||||
- TCP Tunneling / Proxy
|
- TCP Tunneling / Proxy
|
||||||
@ -32,8 +33,23 @@ General purpose request (reverse) proxy and forwarding tool for networking noobs
|
|||||||
- External permission management system for easy system integration
|
- External permission management system for easy system integration
|
||||||
- SMTP config for password reset
|
- SMTP config for password reset
|
||||||
|
|
||||||
|
## Downloads
|
||||||
|
|
||||||
|
[Windows](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe)
|
||||||
|
/[Linux (amd64)](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_amd64)
|
||||||
|
/[Linux (arm64)](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_arm64)
|
||||||
|
|
||||||
|
For other systems or architectures, please see [Release](https://github.com/tobychui/zoraxy/releases/latest/)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
[Installing Zoraxy Reverse Proxy: Your Gateway to Efficient Web Routing](https://geekscircuit.com/installing-zoraxy-reverse-proxy-your-gateway-to-efficient-web-routing/)
|
||||||
|
|
||||||
|
Thank you for the well written and easy to follow tutorial by Reddit users [itsvmn](https://www.reddit.com/user/itsvmn/)!
|
||||||
|
If you have no background in setting up reverse proxy or web routing, you should check this out before you start setting up your Zoraxy.
|
||||||
|
|
||||||
## Build from Source
|
## Build from Source
|
||||||
Requires Go 1.20 or higher
|
|
||||||
|
Requires Go 1.22 or higher
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/tobychui/zoraxy
|
git clone https://github.com/tobychui/zoraxy
|
||||||
@ -50,7 +66,7 @@ Zoraxy provides basic authentication system for standalone mode. To use it in st
|
|||||||
|
|
||||||
### Standalone Mode
|
### Standalone Mode
|
||||||
|
|
||||||
Standalone mode is the default mode for Zoraxy. This allows a single account to manage your reverse proxy server, just like a home router. This mode is suitable for new owners to homelabs or makers starting growing their web services into multiple servers.
|
Standalone mode is the default mode for Zoraxy. This allows a single account to manage your reverse proxy server just like a basic home router. This mode is suitable for new owners to homelabs or makers starting growing their web services into multiple servers. A full "Getting Started" guide can be found [here](https://github.com/tobychui/zoraxy/wiki/Getting-Started).
|
||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
@ -71,21 +87,23 @@ The installation method is same as Linux. If you are using a Raspberry Pi 4 or n
|
|||||||
The installation method is same as Linux. For other ARM SBCs, please refer to your SBC's CPU architecture and pick the one that is suitable for your device.
|
The installation method is same as Linux. For other ARM SBCs, please refer to your SBC's CPU architecture and pick the one that is suitable for your device.
|
||||||
|
|
||||||
#### Docker
|
#### Docker
|
||||||
|
|
||||||
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details.
|
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details.
|
||||||
|
|
||||||
### Start Paramters
|
### Start Paramters
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage of zoraxy:
|
Usage of zoraxy:
|
||||||
-autorenew int
|
-autorenew int
|
||||||
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
|
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
|
||||||
-fastgeoip
|
-fastgeoip
|
||||||
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
|
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
|
||||||
-info
|
|
||||||
Show information about this program in JSON
|
|
||||||
-log
|
-log
|
||||||
Log terminal output to file (default true)
|
Log terminal output to file (default true)
|
||||||
-mdns
|
-mdns
|
||||||
Enable mDNS scanner and transponder (default true)
|
Enable mDNS scanner and transponder (default true)
|
||||||
|
-mdnsname string
|
||||||
|
mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)
|
||||||
-noauth
|
-noauth
|
||||||
Disable authentication for management interface
|
Disable authentication for management interface
|
||||||
-port string
|
-port string
|
||||||
@ -145,12 +163,13 @@ This allows you to have an infinite number of network members in your Global Are
|
|||||||
## Web SSH
|
## Web SSH
|
||||||
|
|
||||||
Web SSH currently only supports Linux based OSes. The following platforms are supported:
|
Web SSH currently only supports Linux based OSes. The following platforms are supported:
|
||||||
|
|
||||||
- linux/amd64
|
- linux/amd64
|
||||||
- linux/arm64
|
- linux/arm64
|
||||||
- linux/armv6 (experimental)
|
- linux/armv6 (experimental)
|
||||||
- linux/386 (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:
|
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 +178,14 @@ Loopback web SSH connection, by default, is disabled. This means that if you are
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Sponsor This Project
|
## Sponsor This Project
|
||||||
|
|
||||||
If you like the project and want to support us, please consider a donation. You can use the links below
|
If you like the project and want to support us, please consider a donation. You can use the links below
|
||||||
|
|
||||||
- [tobychui (Primary author)](https://paypal.me/tobychui)
|
- [tobychui (Primary author)](https://paypal.me/tobychui)
|
||||||
- PassiveLemon (Docker compatibility maintainer)
|
- PassiveLemon (Docker compatibility maintainer)
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is open-sourced under AGPL. I open-sourced this project so everyone can check for security issues and benefit all users. **If you plan to use this project in a commercial environment (which violate the AGPL terms), please contact toby@imuslab.com for an alternative 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.**
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,10 +8,7 @@ RUN mkdir -p /opt/zoraxy/source/ &&\
|
|||||||
mkdir -p /opt/zoraxy/config/ &&\
|
mkdir -p /opt/zoraxy/config/ &&\
|
||||||
mkdir -p /usr/local/bin/
|
mkdir -p /usr/local/bin/
|
||||||
|
|
||||||
COPY entrypoint.sh /opt/zoraxy/
|
RUN chmod -R 770 /opt/zoraxy/
|
||||||
|
|
||||||
RUN chmod -R 755 /opt/zoraxy/ &&\
|
|
||||||
chmod +x /opt/zoraxy/entrypoint.sh
|
|
||||||
|
|
||||||
VOLUME [ "/opt/zoraxy/config/" ]
|
VOLUME [ "/opt/zoraxy/config/" ]
|
||||||
|
|
||||||
@ -24,15 +21,15 @@ RUN go mod tidy &&\
|
|||||||
go build -o /usr/local/bin/zoraxy &&\
|
go build -o /usr/local/bin/zoraxy &&\
|
||||||
rm -r /opt/zoraxy/source/
|
rm -r /opt/zoraxy/source/
|
||||||
|
|
||||||
RUN chmod +x /usr/local/bin/zoraxy
|
RUN chmod 755 /usr/local/bin/zoraxy &&\
|
||||||
|
chmod +x /usr/local/bin/zoraxy
|
||||||
|
|
||||||
WORKDIR /opt/zoraxy/config/
|
WORKDIR /opt/zoraxy/config/
|
||||||
|
|
||||||
ENV VERSION=$VERSION
|
ENV VERSION=$VERSION
|
||||||
|
|
||||||
ENV ARGS="-noauth=false"
|
ENV ARGS="-noauth=false"
|
||||||
|
|
||||||
ENTRYPOINT ["/opt/zoraxy/entrypoint.sh"]
|
ENTRYPOINT "zoraxy" "-port=:8000" "${ARGS}"
|
||||||
|
|
||||||
HEALTHCHECK --interval=5s --timeout=5s --retries=2 CMD nc -vz 127.0.0.1 8000 || exit 1
|
HEALTHCHECK --interval=5s --timeout=5s --retries=2 CMD nc -vz 127.0.0.1 8000 || exit 1
|
||||||
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
echo "Zoraxy version $VERSION"
|
|
||||||
|
|
||||||
zoraxy -port=:8000 ${ARGS}
|
|
BIN
docs/img/bg.png
Before Width: | Height: | Size: 4.5 MiB |
BIN
docs/img/bg2.png
Before Width: | Height: | Size: 9.4 MiB |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m772-635-43-100-104-46 104-45 43-95 43 95 104 45-104 46-43 100Zm0 595-43-96-104-45 104-45 43-101 43 101 104 45-104 45-43 96ZM333-194l-92-197-201-90 201-90 92-196 93 196 200 90-200 90-93 197Zm0-148 48-96 98-43-98-43-48-96-47 96-99 43 99 43 47 96Zm0-139Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m772-635-43-100-104-46 104-45 43-95 43 95 104 45-104 46-43 100Zm0 595-43-96-104-45 104-45 43-101 43 101 104 45-104 45-43 96ZM333-194l-92-197-201-90 201-90 92-196 93 196 200 90-200 90-93 197Zm0-148 48-96 98-43-98-43-48-96-47 96-99 43 99 43 47 96Zm0-139Z"/></svg>
|
Before Width: | Height: | Size: 358 B After Width: | Height: | Size: 377 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M280-453h400v-60H280v60ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>
|
<svg fill="#ff7a7a" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M280-453h400v-60H280v60ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>
|
Before Width: | Height: | Size: 433 B After Width: | Height: | Size: 448 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M320-242 80-482l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M320-242 80-482l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"/></svg>
|
Before Width: | Height: | Size: 209 B After Width: | Height: | Size: 227 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-80v-270h120v-160h210v-100H330v-270h300v270H510v100h210v160h120v270H540v-270h120v-100H300v100h120v270H120Zm270-590h180v-150H390v150ZM180-140h180v-150H180v150Zm420 0h180v-150H600v150ZM480-670ZM360-290Zm240 0Z"/></svg>
|
<svg fill="#919191" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-80v-270h120v-160h210v-100H330v-270h300v270H510v100h210v160h120v270H540v-270h120v-100H300v100h120v270H120Zm270-590h180v-150H390v150ZM180-140h180v-150H180v150Zm420 0h180v-150H600v150ZM480-670ZM360-290Zm240 0Z"/></svg>
|
Before Width: | Height: | Size: 317 B After Width: | Height: | Size: 332 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>
|
Before Width: | Height: | Size: 224 B After Width: | Height: | Size: 242 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M356-120H180q-24 0-42-18t-18-42v-176q44-5 75.5-34.5T227-463q0-43-31.5-72.5T120-570v-176q0-24 18-42t42-18h177q11-40 39.5-67t68.5-27q40 0 68.5 27t39.5 67h173q24 0 42 18t18 42v173q40 11 65.5 41.5T897-461q0 40-25.5 67T806-356v176q0 24-18 42t-42 18H570q-5-48-35.5-77.5T463-227q-41 0-71.5 29.5T356-120Zm-176-60h130q25-61 69.888-84 44.888-23 83-23T546-264q45 23 70 84h130v-235h45q20 0 33-13t13-33q0-20-13-33t-33-13h-45v-239H511v-48q0-20-13-33t-33-13q-20 0-33 13t-13 33v48H180v130q48.15 17.817 77.575 59.686Q287-514.445 287-462.777 287-412 257.5-370T180-310v130Zm329-330Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M356-120H180q-24 0-42-18t-18-42v-176q44-5 75.5-34.5T227-463q0-43-31.5-72.5T120-570v-176q0-24 18-42t42-18h177q11-40 39.5-67t68.5-27q40 0 68.5 27t39.5 67h173q24 0 42 18t18 42v173q40 11 65.5 41.5T897-461q0 40-25.5 67T806-356v176q0 24-18 42t-42 18H570q-5-48-35.5-77.5T463-227q-41 0-71.5 29.5T356-120Zm-176-60h130q25-61 69.888-84 44.888-23 83-23T546-264q45 23 70 84h130v-235h45q20 0 33-13t13-33q0-20-13-33t-33-13h-45v-239H511v-48q0-20-13-33t-33-13q-20 0-33 13t-13 33v48H180v130q48.15 17.817 77.575 59.686Q287-514.445 287-462.777 287-412 257.5-370T180-310v130Zm329-330Z"/></svg>
|
Before Width: | Height: | Size: 669 B After Width: | Height: | Size: 688 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M273-160 80-353l193-193 42 42-121 121h316v60H194l121 121-42 42Zm414-254-42-42 121-121H450v-60h316L645-758l42-42 193 193-193 193Z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48" fill="#fcba03"><path d="M273-160 80-353l193-193 42 42-121 121h316v60H194l121 121-42 42Zm414-254-42-42 121-121H450v-60h316L645-758l42-42 193 193-193 193Z"/></svg>
|
Before Width: | Height: | Size: 234 B After Width: | Height: | Size: 249 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M700-160v-410H275l153 153-42 43-226-226 226-226 42 42-153 154h485v470h-60Z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" fill="#0388fc" height="48" viewBox="0 -960 960 960" width="48"><path d="M700-160v-410H275l153 153-42 43-226-226 226-226 42 42-153 154h485v470h-60Z"/></svg>
|
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 195 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M197-197q-54-54-85.5-126.5T80-480q0-84 31.5-156.5T197-763l43 43q-46 46-73 107.5T140-480q0 71 26.5 132T240-240l-43 43Zm113-113q-32-32-51-75.5T240-480q0-51 19-94.5t51-75.5l43 43q-24 24-38.5 56.5T300-480q0 38 14 70t39 57l-43 43Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-43-43q24-24 38.5-56.5T660-480q0-38-14-70t-39-57l43-43q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-43-43q46-46 73-107.5T820-480q0-71-26.5-132T720-720l43-43q54 55 85.5 127.5T880-480q0 83-31.5 155.5T763-197Z"/></svg>
|
<svg fill="#83f2c4" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M197-197q-54-54-85.5-126.5T80-480q0-84 31.5-156.5T197-763l43 43q-46 46-73 107.5T140-480q0 71 26.5 132T240-240l-43 43Zm113-113q-32-32-51-75.5T240-480q0-51 19-94.5t51-75.5l43 43q-24 24-38.5 56.5T300-480q0 38 14 70t39 57l-43 43Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-43-43q24-24 38.5-56.5T660-480q0-38-14-70t-39-57l43-43q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-43-43q46-46 73-107.5T820-480q0-71-26.5-132T720-720l43-43q54 55 85.5 127.5T880-480q0 83-31.5 155.5T763-197Z"/></svg>
|
Before Width: | Height: | Size: 652 B After Width: | Height: | Size: 667 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M345-377h391L609-548 506-413l-68-87-93 123Zm-85 177q-24 0-42-18t-18-42v-560q0-24 18-42t42-18h560q24 0 42 18t18 42v560q0 24-18 42t-42 18H260Zm0-60h560v-560H260v560ZM140-80q-24 0-42-18t-18-42v-620h60v620h620v60H140Zm120-740v560-560Z"/></svg>
|
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M345-377h391L609-548 506-413l-68-87-93 123Zm-85 177q-24 0-42-18t-18-42v-560q0-24 18-42t42-18h560q24 0 42 18t18 42v560q0 24-18 42t-42 18H260Zm0-60h560v-560H260v560ZM140-80q-24 0-42-18t-18-42v-620h60v620h620v60H140Zm120-740v560-560Z"/></svg>
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 355 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg>
|
<svg fill="#edf230" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg>
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
BIN
docs/img/screenshots/1.png
Normal file
After Width: | Height: | Size: 202 KiB |
Before Width: | Height: | Size: 42 KiB |
BIN
docs/img/screenshots/10.png
Normal file
After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 62 KiB |
BIN
docs/img/screenshots/2.png
Normal file
After Width: | Height: | Size: 146 KiB |
Before Width: | Height: | Size: 32 KiB |
BIN
docs/img/screenshots/3.png
Normal file
After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 28 KiB |
BIN
docs/img/screenshots/4.png
Normal file
After Width: | Height: | Size: 203 KiB |
Before Width: | Height: | Size: 41 KiB |
BIN
docs/img/screenshots/5.png
Normal file
After Width: | Height: | Size: 123 KiB |
Before Width: | Height: | Size: 46 KiB |
BIN
docs/img/screenshots/6.png
Normal file
After Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 48 KiB |
BIN
docs/img/screenshots/7.png
Normal file
After Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 55 KiB |
BIN
docs/img/screenshots/8.png
Normal file
After Width: | Height: | Size: 185 KiB |
Before Width: | Height: | Size: 68 KiB |
BIN
docs/img/screenshots/9.png
Normal file
After Width: | Height: | Size: 867 KiB |
Before Width: | Height: | Size: 153 KiB |
@ -8,7 +8,7 @@
|
|||||||
<meta name="author" content="tobychui">
|
<meta name="author" content="tobychui">
|
||||||
|
|
||||||
<!-- HTML Meta Tags -->
|
<!-- HTML Meta Tags -->
|
||||||
<title>Cluster Proxy Gateway | Zoraxy</title>
|
<title>Reverse Proxy Server | Zoraxy</title>
|
||||||
<meta name="description" content="A reverse proxy server and cluster network gateway for noobs">
|
<meta name="description" content="A reverse proxy server and cluster network gateway for noobs">
|
||||||
|
|
||||||
<!-- Facebook Meta Tags -->
|
<!-- Facebook Meta Tags -->
|
||||||
@ -74,21 +74,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="right-content">
|
<div class="right-content">
|
||||||
<!-- Hero Banner Section -->
|
<!-- Hero Banner Section -->
|
||||||
<div class="dot-container">
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div class="dot"></div>
|
|
||||||
</div>
|
|
||||||
<div class="headbanner"></div>
|
<div class="headbanner"></div>
|
||||||
<div id="home" class="herotext">
|
<div id="home" class="herotext">
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
<div class="bannerHeaderWrapper">
|
<div class="bannerHeaderWrapper">
|
||||||
<h1 class="bannerHeader">Zoraxy</h1>
|
<h1 class="bannerHeader">Zoraxy</h1>
|
||||||
<p class="bannerSubheader">All in one homelab network routing solution</p>
|
<div class="ui divider"></div><br>
|
||||||
|
<p class="bannerSubheader">Beyond Reverse Proxy: Your Ultimate Homelab Network Tool</p>
|
||||||
</div>
|
</div>
|
||||||
<br><br>
|
<br><br>
|
||||||
<a class="ui black big button" href="#features">Learn More</a>
|
<a class="ui basic big button" style="background-color: white;" href="#features"><i class="ui blue arrow down icon"></i> Learn More</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
<table class="ui very basic collapsing unstackable celled table">
|
<table class="ui very basic collapsing unstackable celled table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -126,6 +121,22 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="wavesWrapper">
|
||||||
|
<!-- CSS waves-->
|
||||||
|
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
|
||||||
|
<defs>
|
||||||
|
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
|
||||||
|
</defs>
|
||||||
|
<g class="parallax">
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(255,255,255,0.7" />
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(255,255,255,0.5)" />
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(255,255,255,0.3)" />
|
||||||
|
<use xlink:href="#gentle-wave" x="48" y="7" fill="#fff" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Features -->
|
<!-- Features -->
|
||||||
@ -240,34 +251,34 @@
|
|||||||
|
|
||||||
<div class="ui three column stackable grid">
|
<div class="ui three column stackable grid">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/1.webp" target="_blank"><img src="img/screenshots/1.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/1.png" target="_blank"><img src="img/screenshots/1.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/2.webp" target="_blank"><img src="img/screenshots/2.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/2.png" target="_blank"><img src="img/screenshots/2.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/3.webp" target="_blank"><img src="img/screenshots/3.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/3.png" target="_blank"><img src="img/screenshots/3.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/4.webp" target="_blank"><img src="img/screenshots/4.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/4.png" target="_blank"><img src="img/screenshots/4.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/5.webp" target="_blank"><img src="img/screenshots/5.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/5.png" target="_blank"><img src="img/screenshots/5.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/6.webp" target="_blank"><img src="img/screenshots/6.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/6.png" target="_blank"><img src="img/screenshots/6.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/7.webp" target="_blank"><img src="img/screenshots/7.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/7.png" target="_blank"><img src="img/screenshots/7.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/8.webp" target="_blank"><img src="img/screenshots/8.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/8.png" target="_blank"><img src="img/screenshots/8.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/9.webp" target="_blank"><img src="img/screenshots/9.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/9.png" target="_blank"><img src="img/screenshots/9.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="img/screenshots/10.webp" target="_blank"><img src="img/screenshots/10.webp" class="ui fluid image screenshot"></a>
|
<a href="img/screenshots/10.png" target="_blank"><img src="img/screenshots/10.png" class="ui fluid image screenshot"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
135
docs/style.css
@ -1,5 +1,5 @@
|
|||||||
body{
|
body{
|
||||||
background: #f6f6f6 !important;
|
background: #ffffff !important;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
@ -18,7 +18,7 @@ body{
|
|||||||
.left-menu {
|
.left-menu {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
background-color: #ffffff;
|
background-color: #fcfcfc;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding-top: 1.5em;
|
padding-top: 1.5em;
|
||||||
}
|
}
|
||||||
@ -48,17 +48,19 @@ body{
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 1px solid #f6f6f6;
|
border-bottom: 1px solid #f6f6f6;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-right: 0.4em solid var(--themeTextColor);
|
|
||||||
transition: border-left ease-in-out 0.1s, background-color ease-in-out 0.1s;
|
transition: border-left ease-in-out 0.1s, background-color ease-in-out 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item.active{
|
.menu-item.active{
|
||||||
border-right: 0.4em solid var(--themeSkyblueColorDecondary);
|
background: linear-gradient(60deg, rgba(84, 58, 183, 0.3) 0%, rgba(0, 172, 193, 0.3) 100%);
|
||||||
background-color: #f0f8ff;
|
}
|
||||||
|
|
||||||
|
.menu-item .item-icon{
|
||||||
|
fill: #fcfcfc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item:hover{
|
.menu-item:hover{
|
||||||
border-right: 0.4em solid var(--themeSkyblueColorDecondary);
|
background: rgba(35,35,35,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item img{
|
.menu-item img{
|
||||||
@ -69,18 +71,6 @@ body{
|
|||||||
|
|
||||||
|
|
||||||
/* Head banner */
|
/* Head banner */
|
||||||
.headbanner{
|
|
||||||
background-image: url('img/bg.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: right center;
|
|
||||||
background-size: auto 100%;
|
|
||||||
position:absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100%;
|
|
||||||
z-index: -100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.herotext{
|
.herotext{
|
||||||
padding-top: 15em;
|
padding-top: 15em;
|
||||||
@ -91,11 +81,13 @@ body{
|
|||||||
.bannerHeader{
|
.bannerHeader{
|
||||||
font-size: 8em;
|
font-size: 8em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bannerSubheader{
|
.bannerSubheader{
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
|
color: #ebebeb;
|
||||||
margin-top: -20px;
|
margin-top: -20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +96,21 @@ body{
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#home{
|
||||||
|
background: linear-gradient(60deg, rgba(84,58,183,1) 0%, rgba(0,172,193,1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#home .table th, #home .table h4{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#home .table h4 .content, #home .table h4 .sub.header{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#home .table td a{
|
||||||
|
color: #d6ddff;
|
||||||
|
}
|
||||||
|
|
||||||
/* features */
|
/* features */
|
||||||
#features{
|
#features{
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
@ -173,56 +180,58 @@ body{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Decorative Animation */
|
/*
|
||||||
.dot-container {
|
Waves CSS
|
||||||
display: flex;
|
*/
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
#wavesWrapper{
|
||||||
height: 40px;
|
position: absolute;
|
||||||
position: absolute;
|
bottom: 0;
|
||||||
top: 2em;
|
width: 100%;
|
||||||
left: 2em;
|
left: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot:nth-child(1) {
|
.waves {
|
||||||
animation-delay: 0s;
|
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) {
|
.parallax > use {
|
||||||
animation-delay: 2s;
|
animation: move-forever 25s cubic-bezier(.55,.5,.45,.5) infinite;
|
||||||
}
|
}
|
||||||
|
.parallax > use:nth-child(1) {
|
||||||
.dot:nth-child(4) {
|
animation-delay: -8s;
|
||||||
animation-delay: 3s;
|
animation-duration: 28s;
|
||||||
}
|
}
|
||||||
|
.parallax > use:nth-child(2) {
|
||||||
@keyframes dot-animation {
|
animation-delay: -12s;
|
||||||
0% {
|
animation-duration: 40s;
|
||||||
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(3) {
|
||||||
|
animation-delay: -16s;
|
||||||
|
animation-duration: 52s;
|
||||||
|
}
|
||||||
|
.parallax > use:nth-child(4) {
|
||||||
|
animation-delay: -20s;
|
||||||
|
animation-duration: 80s;
|
||||||
|
}
|
||||||
|
@keyframes move-forever {
|
||||||
|
0% {
|
||||||
|
transform: translate3d(-90px,0,0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate3d(85px,0,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*Shrinking for mobile*/
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.waves {
|
||||||
|
height:40px;
|
||||||
|
min-height:40px;
|
||||||
|
}
|
||||||
|
}
|
18
example/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Example www Folder
|
||||||
|
|
||||||
|
This is an example www folder that contains two sub-folders.
|
||||||
|
|
||||||
|
- `html/`
|
||||||
|
- `templates/`
|
||||||
|
|
||||||
|
The html file contain static resources that will be served by Zoraxy build-in static web server. You can use it as a generic web server with a static site generator like [Hugo](https://gohugo.io/) or use it as a small CDN for serving your scripts / image that commonly use across many of your sites.
|
||||||
|
|
||||||
|
The templates folder contains the template for overriding the build in error or access denied pages. The following templates are supported
|
||||||
|
|
||||||
|
- notfound.html (Default site Not-Found error page)
|
||||||
|
- whitelist.html (Error page when client being blocked by whitelist rule)
|
||||||
|
- blacklist.html (Error page when client being blocked by blacklist rule)
|
||||||
|
|
||||||
|
To use the template, copy and paste the `wwww` folder to the same directory as zoraxy executable (aka the src/ file if you `go build` with the current folder tree).
|
||||||
|
|
||||||
|
It is worth mentioning that the uwu icons for not-found and access-denied are created by @SAWARATSUKI
|
229
example/www/html/index.html
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Zoraxy Firework!</title>
|
||||||
|
<style>
|
||||||
|
body{
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js" integrity="sha512-aNMyYYxdIxIaot0Y1/PLuEu3eipGCmsEUBrUq+7aVyPGMFH8z0eTP0tkqAvv34fzN6z+201d3T8HPb1svWSKHQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="c"></canvas>
|
||||||
|
<script>
|
||||||
|
var c = document.getElementById("c");
|
||||||
|
var ctx = c.getContext("2d");
|
||||||
|
var cH;
|
||||||
|
var cW;
|
||||||
|
var bgColor = "#FF6138";
|
||||||
|
var animations = [];
|
||||||
|
var circles = [];
|
||||||
|
|
||||||
|
var colorPicker = (function() {
|
||||||
|
var colors = ["#FF6138", "#FFBE53", "#2980B9", "#FCFCFC", "#282741"];
|
||||||
|
var index = 0;
|
||||||
|
function next() {
|
||||||
|
index = index++ < colors.length-1 ? index : 0;
|
||||||
|
return colors[index];
|
||||||
|
}
|
||||||
|
function current() {
|
||||||
|
return colors[index]
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
next: next,
|
||||||
|
current: current
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
function removeAnimation(animation) {
|
||||||
|
var index = animations.indexOf(animation);
|
||||||
|
if (index > -1) animations.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcPageFillRadius(x, y) {
|
||||||
|
var l = Math.max(x - 0, cW - x);
|
||||||
|
var h = Math.max(y - 0, cH - y);
|
||||||
|
return Math.sqrt(Math.pow(l, 2) + Math.pow(h, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addClickListeners() {
|
||||||
|
document.addEventListener("touchstart", handleEvent);
|
||||||
|
document.addEventListener("mousedown", handleEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleEvent(e) {
|
||||||
|
if (e.touches) {
|
||||||
|
e.preventDefault();
|
||||||
|
e = e.touches[0];
|
||||||
|
}
|
||||||
|
var currentColor = colorPicker.current();
|
||||||
|
var nextColor = colorPicker.next();
|
||||||
|
var targetR = calcPageFillRadius(e.pageX, e.pageY);
|
||||||
|
var rippleSize = Math.min(200, (cW * .4));
|
||||||
|
var minCoverDuration = 750;
|
||||||
|
|
||||||
|
var pageFill = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
r: 0,
|
||||||
|
fill: nextColor
|
||||||
|
});
|
||||||
|
var fillAnimation = anime({
|
||||||
|
targets: pageFill,
|
||||||
|
r: targetR,
|
||||||
|
duration: Math.max(targetR / 2 , minCoverDuration ),
|
||||||
|
easing: "easeOutQuart",
|
||||||
|
complete: function(){
|
||||||
|
bgColor = pageFill.fill;
|
||||||
|
removeAnimation(fillAnimation);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var ripple = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
r: 0,
|
||||||
|
fill: currentColor,
|
||||||
|
stroke: {
|
||||||
|
width: 3,
|
||||||
|
color: currentColor
|
||||||
|
},
|
||||||
|
opacity: 1
|
||||||
|
});
|
||||||
|
var rippleAnimation = anime({
|
||||||
|
targets: ripple,
|
||||||
|
r: rippleSize,
|
||||||
|
opacity: 0,
|
||||||
|
easing: "easeOutExpo",
|
||||||
|
duration: 900,
|
||||||
|
complete: removeAnimation
|
||||||
|
});
|
||||||
|
|
||||||
|
var particles = [];
|
||||||
|
for (var i=0; i<32; i++) {
|
||||||
|
var particle = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
fill: currentColor,
|
||||||
|
r: anime.random(24, 48)
|
||||||
|
})
|
||||||
|
particles.push(particle);
|
||||||
|
}
|
||||||
|
var particlesAnimation = anime({
|
||||||
|
targets: particles,
|
||||||
|
x: function(particle){
|
||||||
|
return particle.x + anime.random(rippleSize, -rippleSize);
|
||||||
|
},
|
||||||
|
y: function(particle){
|
||||||
|
return particle.y + anime.random(rippleSize * 1.15, -rippleSize * 1.15);
|
||||||
|
},
|
||||||
|
r: 0,
|
||||||
|
easing: "easeOutExpo",
|
||||||
|
duration: anime.random(1000,1300),
|
||||||
|
complete: removeAnimation
|
||||||
|
});
|
||||||
|
animations.push(fillAnimation, rippleAnimation, particlesAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extend(a, b){
|
||||||
|
for(var key in b) {
|
||||||
|
if(b.hasOwnProperty(key)) {
|
||||||
|
a[key] = b[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Circle = function(opts) {
|
||||||
|
extend(this, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
Circle.prototype.draw = function() {
|
||||||
|
ctx.globalAlpha = this.opacity || 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
|
||||||
|
if (this.stroke) {
|
||||||
|
ctx.strokeStyle = this.stroke.color;
|
||||||
|
ctx.lineWidth = this.stroke.width;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
if (this.fill) {
|
||||||
|
ctx.fillStyle = this.fill;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var animate = anime({
|
||||||
|
duration: Infinity,
|
||||||
|
update: function() {
|
||||||
|
ctx.fillStyle = bgColor;
|
||||||
|
ctx.fillRect(0, 0, cW, cH);
|
||||||
|
animations.forEach(function(anim) {
|
||||||
|
anim.animatables.forEach(function(animatable) {
|
||||||
|
animatable.target.draw();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var resizeCanvas = function() {
|
||||||
|
cW = window.innerWidth;
|
||||||
|
cH = window.innerHeight;
|
||||||
|
c.width = cW * devicePixelRatio;
|
||||||
|
c.height = cH * devicePixelRatio;
|
||||||
|
ctx.scale(devicePixelRatio, devicePixelRatio);
|
||||||
|
};
|
||||||
|
|
||||||
|
(function init() {
|
||||||
|
resizeCanvas();
|
||||||
|
if (window.CP) {
|
||||||
|
// CodePen's loop detection was causin' problems
|
||||||
|
// and I have no idea why, so...
|
||||||
|
window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 6000;
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", resizeCanvas);
|
||||||
|
addClickListeners();
|
||||||
|
if (!!window.location.pathname.match(/fullcpgrid/)) {
|
||||||
|
startFauxClicking();
|
||||||
|
}
|
||||||
|
handleInactiveUser();
|
||||||
|
})();
|
||||||
|
|
||||||
|
function handleInactiveUser() {
|
||||||
|
var inactive = setTimeout(function(){
|
||||||
|
fauxClick(cW/2, cH/2);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
function clearInactiveTimeout() {
|
||||||
|
clearTimeout(inactive);
|
||||||
|
document.removeEventListener("mousedown", clearInactiveTimeout);
|
||||||
|
document.removeEventListener("touchstart", clearInactiveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", clearInactiveTimeout);
|
||||||
|
document.addEventListener("touchstart", clearInactiveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startFauxClicking() {
|
||||||
|
setTimeout(function(){
|
||||||
|
fauxClick(anime.random( cW * .2, cW * .8), anime.random(cH * .2, cH * .8));
|
||||||
|
startFauxClicking();
|
||||||
|
}, anime.random(200, 900));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fauxClick(x, y) {
|
||||||
|
var fauxClick = new Event("mousedown");
|
||||||
|
fauxClick.pageX = x;
|
||||||
|
fauxClick.pageY = y;
|
||||||
|
document.dispatchEvent(fauxClick);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
52
example/www/templates/blacklist.html
Normal file
42
example/www/templates/notfound.html
Normal file
52
example/www/templates/whitelist.html
Normal file
Before Width: | Height: | Size: 390 KiB After Width: | Height: | Size: 75 KiB |
BIN
img/title.png
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 69 KiB |
BIN
img/title.psd
@ -3,9 +3,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
strip "github.com/grokify/html-strip-tags-go"
|
"github.com/google/uuid"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,6 +20,157 @@ import (
|
|||||||
banning / whitelist a specific IP address or country code
|
banning / whitelist a specific IP address or country code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
General Function
|
||||||
|
*/
|
||||||
|
|
||||||
|
func handleListAccessRules(w http.ResponseWriter, r *http.Request) {
|
||||||
|
allAccessRules := accessController.ListAllAccessRules()
|
||||||
|
js, _ := json.Marshal(allAccessRules)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAttachRuleToHost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleid, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
host, err := utils.PostPara(r, "host")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if access rule and proxy rule exists
|
||||||
|
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(host)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid host given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !accessController.AccessRuleExists(ruleid) {
|
||||||
|
utils.SendErrorResponse(w, "access rule not exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update the proxy host acess rule id
|
||||||
|
targetProxyEndpoint.AccessFilterUUID = ruleid
|
||||||
|
targetProxyEndpoint.UpdateToRuntime()
|
||||||
|
err = SaveReverseProxyConfig(targetProxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new access rule, require name and desc only
|
||||||
|
func handleCreateAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleName, err := utils.PostPara(r, "name")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleDesc, _ := utils.PostPara(r, "desc")
|
||||||
|
|
||||||
|
//Filter out injection if any
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
ruleName = p.Sanitize(ruleName)
|
||||||
|
ruleDesc = p.Sanitize(ruleDesc)
|
||||||
|
|
||||||
|
ruleUUID := uuid.New().String()
|
||||||
|
newAccessRule := access.AccessRule{
|
||||||
|
ID: ruleUUID,
|
||||||
|
Name: ruleName,
|
||||||
|
Desc: ruleDesc,
|
||||||
|
BlacklistEnabled: false,
|
||||||
|
WhitelistEnabled: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add it to runtime
|
||||||
|
err = accessController.AddNewAccessRule(&newAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle removing an access rule. All proxy endpoint using this rule will be
|
||||||
|
// set to use the default rule
|
||||||
|
func handleRemoveAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule id given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ruleID == "default" {
|
||||||
|
utils.SendErrorResponse(w, "default access rule cannot be removed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleID = strings.TrimSpace(ruleID)
|
||||||
|
|
||||||
|
//Set all proxy hosts that use this access rule back to using "default"
|
||||||
|
allProxyEndpoints := dynamicProxyRouter.GetProxyEndpointsAsMap()
|
||||||
|
for _, proxyEndpoint := range allProxyEndpoints {
|
||||||
|
if strings.EqualFold(proxyEndpoint.AccessFilterUUID, ruleID) {
|
||||||
|
//This proxy endpoint is using the current access filter.
|
||||||
|
//set it to default
|
||||||
|
proxyEndpoint.AccessFilterUUID = "default"
|
||||||
|
proxyEndpoint.UpdateToRuntime()
|
||||||
|
err = SaveReverseProxyConfig(proxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Unable to save updated proxy endpoint "+proxyEndpoint.RootOrMatchingDomain, err)
|
||||||
|
} else {
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Updated "+proxyEndpoint.RootOrMatchingDomain+" access filter to \"default\"", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove the access rule by ID
|
||||||
|
err = accessController.RemoveAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemWideLogger.PrintAndLog("Access", "Access Rule "+ruleID+" removed", nil)
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the name and desc, for other properties use blacklist / whitelist api
|
||||||
|
func handleUpadateAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleName, err := utils.PostPara(r, "name")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid rule name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleDesc, _ := utils.PostPara(r, "desc")
|
||||||
|
|
||||||
|
//Filter anything weird
|
||||||
|
p := bluemonday.StrictPolicy()
|
||||||
|
ruleName = p.Sanitize(ruleName)
|
||||||
|
ruleDesc = p.Sanitize(ruleDesc)
|
||||||
|
|
||||||
|
err = accessController.UpdateAccessRule(ruleID, ruleName, ruleDesc)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Blacklist Related
|
Blacklist Related
|
||||||
*/
|
*/
|
||||||
@ -28,11 +182,24 @@ func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
|
|||||||
bltype = "country"
|
bltype = "country"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ruleID, err := utils.GetPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
//Use default if not set
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
resulst := []string{}
|
resulst := []string{}
|
||||||
if bltype == "country" {
|
if bltype == "country" {
|
||||||
resulst = geodbStore.GetAllBlacklistedCountryCode()
|
resulst = rule.GetAllBlacklistedCountryCode()
|
||||||
} else if bltype == "ip" {
|
} else if bltype == "ip" {
|
||||||
resulst = geodbStore.GetAllBlacklistedIp()
|
resulst = rule.GetAllBlacklistedIp()
|
||||||
}
|
}
|
||||||
|
|
||||||
js, _ := json.Marshal(resulst)
|
js, _ := json.Marshal(resulst)
|
||||||
@ -47,7 +214,23 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddCountryCodeToBlackList(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.PostPara(r, "comment")
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.AddCountryCodeToBlackList(countryCode, comment)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -59,7 +242,19 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveCountryCodeFromBlackList(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveCountryCodeFromBlackList(countryCode)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -71,7 +266,24 @@ func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.AddIPToBlackList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _ := utils.GetPara(r, "comment")
|
||||||
|
p := bluemonday.StripTagsPolicy()
|
||||||
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
|
rule.AddIPToBlackList(ipAddr, comment)
|
||||||
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -81,23 +293,46 @@ func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveIPFromBlackList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target rule from access controller
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveIPFromBlackList(ipAddr)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
|
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
enable, err := utils.PostPara(r, "enable")
|
enable, _ := utils.PostPara(r, "enable")
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Return the current enabled state
|
ruleID = "default"
|
||||||
currentEnabled := geodbStore.BlacklistEnabled
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable == "" {
|
||||||
|
//enable paramter not set
|
||||||
|
currentEnabled := rule.BlacklistEnabled
|
||||||
js, _ := json.Marshal(currentEnabled)
|
js, _ := json.Marshal(currentEnabled)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else {
|
||||||
if enable == "true" {
|
if enable == "true" {
|
||||||
geodbStore.ToggleBlacklist(true)
|
rule.ToggleBlacklist(true)
|
||||||
} else if enable == "false" {
|
} else if enable == "false" {
|
||||||
geodbStore.ToggleBlacklist(false)
|
rule.ToggleBlacklist(false)
|
||||||
} else {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||||
return
|
return
|
||||||
@ -117,11 +352,22 @@ func handleListWhitelisted(w http.ResponseWriter, r *http.Request) {
|
|||||||
bltype = "country"
|
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" {
|
if bltype == "country" {
|
||||||
resulst = geodbStore.GetAllWhitelistedCountryCode()
|
resulst = rule.GetAllWhitelistedCountryCode()
|
||||||
} else if bltype == "ip" {
|
} else if bltype == "ip" {
|
||||||
resulst = geodbStore.GetAllWhitelistedIp()
|
resulst = rule.GetAllWhitelistedIp()
|
||||||
}
|
}
|
||||||
|
|
||||||
js, _ := json.Marshal(resulst)
|
js, _ := json.Marshal(resulst)
|
||||||
@ -136,10 +382,22 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
comment, _ := utils.PostPara(r, "comment")
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
comment = strip.StripTags(comment)
|
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)
|
||||||
|
|
||||||
|
rule.AddCountryCodeToWhitelist(countryCode, comment)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -151,7 +409,18 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveCountryCodeFromWhitelist(countryCode)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveCountryCodeFromWhitelist(countryCode)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -163,10 +432,23 @@ func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
comment, _ := utils.PostPara(r, "comment")
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
comment = strip.StripTags(comment)
|
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) {
|
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -176,23 +458,45 @@ func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
geodbStore.RemoveIPFromWhiteList(ipAddr)
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.RemoveIPFromWhiteList(ipAddr)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
|
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
enable, err := utils.PostPara(r, "enable")
|
enable, _ := utils.PostPara(r, "enable")
|
||||||
|
ruleID, err := utils.PostPara(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable == "" {
|
||||||
//Return the current enabled state
|
//Return the current enabled state
|
||||||
currentEnabled := geodbStore.WhitelistEnabled
|
currentEnabled := rule.WhitelistEnabled
|
||||||
js, _ := json.Marshal(currentEnabled)
|
js, _ := json.Marshal(currentEnabled)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else {
|
||||||
if enable == "true" {
|
if enable == "true" {
|
||||||
geodbStore.ToggleWhitelist(true)
|
rule.ToggleWhitelist(true)
|
||||||
} else if enable == "false" {
|
} else if enable == "false" {
|
||||||
geodbStore.ToggleWhitelist(false)
|
rule.ToggleWhitelist(false)
|
||||||
} else {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||||
return
|
return
|
||||||
|
40
src/acme.go
@ -38,7 +38,7 @@ func initACME() *acme.ACMEHandler {
|
|||||||
port = getRandomPort(30000)
|
port = getRandomPort(30000)
|
||||||
}
|
}
|
||||||
|
|
||||||
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port))
|
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the special routing rule for ACME
|
// create the special routing rule for ACME
|
||||||
@ -85,22 +85,26 @@ func acmeRegisterSpecialRoutingRule() {
|
|||||||
// This function check if the renew setup is satisfied. If not, toggle them automatically
|
// This function check if the renew setup is satisfied. If not, toggle them automatically
|
||||||
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
|
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
|
||||||
isForceHttpsRedirectEnabledOriginally := false
|
isForceHttpsRedirectEnabledOriginally := false
|
||||||
if dynamicProxyRouter.Option.Port == 443 {
|
dnsPara, _ := utils.PostBool(r, "dns")
|
||||||
//Enable port 80 to 443 redirect
|
if !dnsPara {
|
||||||
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
|
if dynamicProxyRouter.Option.Port == 443 {
|
||||||
SystemWideLogger.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests")
|
//Enable port 80 to 443 redirect
|
||||||
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
|
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 {
|
} else {
|
||||||
//Set this to true, so after renew, do not turn it off
|
//This port do not support ACME
|
||||||
isForceHttpsRedirectEnabledOriginally = true
|
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
|
//Add a 3 second delay to make sure everything is settle down
|
||||||
@ -109,7 +113,11 @@ func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request)
|
|||||||
// Pass over to the acmeHandler to deal with the communication
|
// Pass over to the acmeHandler to deal with the communication
|
||||||
acmeHandler.HandleRenewCertificate(w, r)
|
acmeHandler.HandleRenewCertificate(w, r)
|
||||||
|
|
||||||
if dynamicProxyRouter.Option.Port == 443 {
|
//Update the TLS cert store buffer
|
||||||
|
tlsCertManager.UpdateLoadedCertList()
|
||||||
|
|
||||||
|
//Restore original settings
|
||||||
|
if dynamicProxyRouter.Option.Port == 443 && !dnsPara {
|
||||||
if !isForceHttpsRedirectEnabledOriginally {
|
if !isForceHttpsRedirectEnabledOriginally {
|
||||||
//Default is off. Turn the redirection off
|
//Default is off. Turn the redirection off
|
||||||
SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil)
|
SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil)
|
||||||
|
32
src/api.go
@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
"net/http/pprof"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||||
"imuslab.com/zoraxy/mod/acme/acmewizard"
|
"imuslab.com/zoraxy/mod/acme/acmewizard"
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/netstat"
|
"imuslab.com/zoraxy/mod/netstat"
|
||||||
@ -47,8 +48,11 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
|
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
|
||||||
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
|
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
||||||
|
authRouter.HandleFunc("/api/proxy/toggle", ReverseProxyToggleRuleSet)
|
||||||
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
|
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
|
||||||
|
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
|
||||||
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
||||||
|
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
||||||
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
||||||
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
||||||
@ -84,7 +88,14 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
|
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
|
||||||
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
|
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
|
||||||
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
||||||
|
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
|
||||||
|
|
||||||
|
//Access Rules API
|
||||||
|
authRouter.HandleFunc("/api/access/list", handleListAccessRules)
|
||||||
|
authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
|
||||||
|
authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
|
||||||
|
authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
|
||||||
|
authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)
|
||||||
//Blacklist APIs
|
//Blacklist APIs
|
||||||
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
||||||
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
||||||
@ -92,7 +103,6 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
||||||
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
||||||
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
||||||
|
|
||||||
//Whitelist APIs
|
//Whitelist APIs
|
||||||
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
||||||
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
||||||
@ -131,14 +141,13 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
||||||
|
|
||||||
//TCP Proxy
|
//TCP Proxy
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/add", tcpProxyManager.HandleAddProxyConfig)
|
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/edit", tcpProxyManager.HandleEditProxyConfigs)
|
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/list", tcpProxyManager.HandleListConfigs)
|
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/start", tcpProxyManager.HandleStartProxy)
|
authRouter.HandleFunc("/api/streamprox/config/start", streamProxyManager.HandleStartProxy)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/stop", tcpProxyManager.HandleStopProxy)
|
authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/delete", tcpProxyManager.HandleRemoveProxy)
|
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/status", tcpProxyManager.HandleGetProxyStatus)
|
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus)
|
||||||
authRouter.HandleFunc("/api/tcpprox/config/validate", tcpProxyManager.HandleConfigValidate)
|
|
||||||
|
|
||||||
//mDNS APIs
|
//mDNS APIs
|
||||||
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
|
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
|
||||||
@ -163,6 +172,8 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet)
|
authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet)
|
||||||
authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet)
|
authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet)
|
||||||
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
|
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
|
||||||
|
authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle)
|
||||||
|
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
|
||||||
|
|
||||||
//Account Reset
|
//Account Reset
|
||||||
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
||||||
@ -175,9 +186,12 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HanldeSetDNS)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
||||||
|
authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson)
|
||||||
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard
|
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard
|
||||||
|
|
||||||
//Static Web Server
|
//Static Web Server
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -46,6 +47,7 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
|
|||||||
LastModifiedDate string
|
LastModifiedDate string
|
||||||
ExpireDate string
|
ExpireDate string
|
||||||
RemainingDays int
|
RemainingDays int
|
||||||
|
UseDNS bool
|
||||||
}
|
}
|
||||||
|
|
||||||
results := []*CertInfo{}
|
results := []*CertInfo{}
|
||||||
@ -81,12 +83,19 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
certInfoFilename := filepath.Join(tlsCertManager.CertStore, filename+".json")
|
||||||
|
useDNSValidation := false //Default to false for HTTP TLS certificates
|
||||||
|
certInfo, err := acme.LoadCertInfoJSON(certInfoFilename) //Note: Not all certs have info json
|
||||||
|
if err == nil {
|
||||||
|
useDNSValidation = certInfo.UseDNS
|
||||||
|
}
|
||||||
|
|
||||||
thisCertInfo := CertInfo{
|
thisCertInfo := CertInfo{
|
||||||
Domain: filename,
|
Domain: filename,
|
||||||
LastModifiedDate: modifiedTime,
|
LastModifiedDate: modifiedTime,
|
||||||
ExpireDate: certExpireTime,
|
ExpireDate: certExpireTime,
|
||||||
RemainingDays: expiredIn,
|
RemainingDays: expiredIn,
|
||||||
|
UseDNS: useDNSValidation,
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, &thisCertInfo)
|
results = append(results, &thisCertInfo)
|
||||||
|
@ -155,7 +155,7 @@ func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
||||||
includeSysDBRaw, err := utils.GetPara(r, "includeDB")
|
includeSysDBRaw, _ := utils.GetPara(r, "includeDB")
|
||||||
includeSysDB := false
|
includeSysDB := false
|
||||||
if includeSysDBRaw == "true" {
|
if includeSysDBRaw == "true" {
|
||||||
//Include the system database in backup snapshot
|
//Include the system database in backup snapshot
|
||||||
@ -177,7 +177,7 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer zipWriter.Close()
|
defer zipWriter.Close()
|
||||||
|
|
||||||
// Walk through the folder and add files to the zip
|
// Walk through the folder and add files to the zip
|
||||||
err = filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error {
|
err := filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domain, err := utils.PostPara(r, "domain")
|
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, "domain cannot be empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
portString, err := utils.PostPara(r, "port")
|
portString, err := utils.PostPara(r, "port")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "port must be a valid integer")
|
utils.SendErrorResponse(w, "port must be a valid integer")
|
||||||
@ -76,7 +70,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Set the email sender properties
|
//Set the email sender properties
|
||||||
thisEmailSender := email.Sender{
|
thisEmailSender := email.Sender{
|
||||||
Hostname: strings.TrimSpace(hostname),
|
Hostname: strings.TrimSpace(hostname),
|
||||||
Domain: strings.TrimSpace(domain),
|
|
||||||
Port: port,
|
Port: port,
|
||||||
Username: strings.TrimSpace(username),
|
Username: strings.TrimSpace(username),
|
||||||
Password: strings.TrimSpace(password),
|
Password: strings.TrimSpace(password),
|
||||||
@ -206,7 +199,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
|
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
|
||||||
if EmailSender.Username == "" || EmailSender.Domain == "" {
|
if EmailSender.Username == "" {
|
||||||
//Reset account not setup
|
//Reset account not setup
|
||||||
utils.SendErrorResponse(w, "Reset account not setup.")
|
utils.SendErrorResponse(w, "Reset account not setup.")
|
||||||
return
|
return
|
||||||
@ -279,17 +272,14 @@ func HandleNewPasswordSetup(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Delete the user account
|
// Un register the user account
|
||||||
authAgent.UnregisterUser(username)
|
if err := authAgent.UnregisterUser(username); err != nil {
|
||||||
|
|
||||||
//Ok. Set the new password
|
|
||||||
err = authAgent.CreateUserAccount(username, newPassword, "")
|
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, err.Error())
|
utils.SendErrorResponse(w, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
//Ok. Set the new password
|
||||||
|
if err := authAgent.CreateUserAccount(username, newPassword, ""); err != nil {
|
||||||
utils.SendErrorResponse(w, err.Error())
|
utils.SendErrorResponse(w, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
176
src/go.mod
@ -1,20 +1,174 @@
|
|||||||
module imuslab.com/zoraxy
|
module imuslab.com/zoraxy
|
||||||
|
|
||||||
go 1.16
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/boltdb/bolt v1.3.1
|
github.com/boltdb/bolt v1.3.1
|
||||||
github.com/go-acme/lego/v4 v4.14.0
|
github.com/go-acme/lego/v4 v4.16.1
|
||||||
github.com/go-ping/ping v1.1.0
|
github.com/go-ping/ping v1.1.0
|
||||||
github.com/google/uuid v1.3.1
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/sessions v1.2.1
|
github.com/gorilla/sessions v1.2.2
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.1
|
||||||
github.com/grandcat/zeroconf v1.0.0
|
github.com/grandcat/zeroconf v1.0.0
|
||||||
github.com/grokify/html-strip-tags-go v0.1.0
|
|
||||||
github.com/likexian/whois v1.15.1
|
github.com/likexian/whois v1.15.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.25
|
github.com/microcosm-cc/bluemonday v1.0.26
|
||||||
golang.org/x/net v0.14.0
|
golang.org/x/net v0.23.0
|
||||||
golang.org/x/sys v0.11.0
|
golang.org/x/sys v0.18.0
|
||||||
golang.org/x/text v0.12.0
|
golang.org/x/text v0.14.0
|
||||||
golang.org/x/tools v0.12.0 // indirect
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go/compute v1.20.1 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
|
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
||||||
|
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||||
|
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
|
||||||
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
|
||||||
|
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
||||||
|
github.com/aws/smithy-go v1.19.0 // indirect
|
||||||
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
|
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/civo/civogo v0.3.11 // indirect
|
||||||
|
github.com/cloudflare/cloudflare-go v0.86.0 // indirect
|
||||||
|
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
||||||
|
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||||
|
github.com/dnsimple/dnsimple-go v1.2.0 // indirect
|
||||||
|
github.com/exoscale/egoscale v0.102.3 // indirect
|
||||||
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
|
github.com/go-errors/errors v1.0.1 // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||||
|
github.com/go-resty/resty/v2 v2.11.0 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/google/s2a-go v0.1.4 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
|
||||||
|
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
||||||
|
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
||||||
|
github.com/gorilla/css v1.0.1 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||||
|
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||||
|
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||||
|
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||||
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
|
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||||
|
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||||
|
github.com/linode/linodego v1.28.0 // indirect
|
||||||
|
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
|
||||||
|
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/miekg/dns v1.1.58 // indirect
|
||||||
|
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||||
|
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||||
|
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect
|
||||||
|
github.com/nrdcg/desec v0.7.0 // indirect
|
||||||
|
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||||
|
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||||
|
github.com/nrdcg/goinwx v0.10.0 // indirect
|
||||||
|
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
||||||
|
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||||
|
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||||
|
github.com/nrdcg/porkbun v0.3.0 // indirect
|
||||||
|
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||||
|
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||||
|
github.com/ovh/go-ovh v1.4.3 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/pquerna/otp v1.4.0 // indirect
|
||||||
|
github.com/sacloud/api-client-go v0.2.8 // indirect
|
||||||
|
github.com/sacloud/go-http v0.1.6 // indirect
|
||||||
|
github.com/sacloud/iaas-api-go v1.11.1 // indirect
|
||||||
|
github.com/sacloud/packages-go v0.0.9 // indirect
|
||||||
|
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||||
|
github.com/softlayer/softlayer-go v1.1.3 // indirect
|
||||||
|
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||||
|
github.com/spf13/cast v1.3.1 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.1 // indirect
|
||||||
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||||
|
github.com/transip/gotransip/v6 v6.23.0 // indirect
|
||||||
|
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect
|
||||||
|
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||||
|
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
||||||
|
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
|
||||||
|
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
go.uber.org/ratelimit v0.2.0 // indirect
|
||||||
|
golang.org/x/crypto v0.21.0 // indirect
|
||||||
|
golang.org/x/mod v0.16.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.16.0 // indirect
|
||||||
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
|
golang.org/x/time v0.5.0 // indirect
|
||||||
|
golang.org/x/tools v0.19.0 // indirect
|
||||||
|
google.golang.org/api v0.126.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||||
|
google.golang.org/grpc v1.55.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
1349
src/go.sum
13
src/main.go
@ -12,11 +12,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/acme"
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||||
"imuslab.com/zoraxy/mod/email"
|
"imuslab.com/zoraxy/mod/email"
|
||||||
|
"imuslab.com/zoraxy/mod/forwardproxy"
|
||||||
"imuslab.com/zoraxy/mod/ganserv"
|
"imuslab.com/zoraxy/mod/ganserv"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
@ -26,7 +28,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/sshprox"
|
"imuslab.com/zoraxy/mod/sshprox"
|
||||||
"imuslab.com/zoraxy/mod/statistic"
|
"imuslab.com/zoraxy/mod/statistic"
|
||||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||||
"imuslab.com/zoraxy/mod/tcpprox"
|
"imuslab.com/zoraxy/mod/streamproxy"
|
||||||
"imuslab.com/zoraxy/mod/tlscert"
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
@ -39,6 +41,7 @@ var noauth = flag.Bool("noauth", false, "Disable authentication for management i
|
|||||||
var showver = flag.Bool("version", false, "Show version of this server")
|
var showver = flag.Bool("version", false, "Show version of this server")
|
||||||
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
||||||
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
||||||
|
var mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
|
||||||
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
||||||
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||||
var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
||||||
@ -49,7 +52,7 @@ var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
name = "Zoraxy"
|
name = "Zoraxy"
|
||||||
version = "3.0.0"
|
version = "3.0.6"
|
||||||
nodeUUID = "generic"
|
nodeUUID = "generic"
|
||||||
development = false //Set this to false to use embedded web fs
|
development = false //Set this to false to use embedded web fs
|
||||||
bootTime = time.Now().Unix()
|
bootTime = time.Now().Unix()
|
||||||
@ -68,17 +71,19 @@ var (
|
|||||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||||
geodbStore *geodb.Store //GeoIP database, also handle black list and whitelist features
|
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||||
|
accessController *access.Controller //Access controller, handle black list and white list
|
||||||
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||||
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||||
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
||||||
mdnsScanner *mdns.MDNSHost //mDNS discovery services
|
mdnsScanner *mdns.MDNSHost //mDNS discovery services
|
||||||
ganManager *ganserv.NetworkManager //Global Area Network Manager
|
ganManager *ganserv.NetworkManager //Global Area Network Manager
|
||||||
webSshManager *sshprox.Manager //Web SSH connection service
|
webSshManager *sshprox.Manager //Web SSH connection service
|
||||||
tcpProxyManager *tcpprox.Manager //TCP Proxy Manager
|
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
|
||||||
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
|
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
|
||||||
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
||||||
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
||||||
|
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
|
||||||
|
|
||||||
//Helper modules
|
//Helper modules
|
||||||
EmailSender *email.Sender //Email sender that handle email sending
|
EmailSender *email.Sender //Email sender that handle email sending
|
||||||
|
221
src/mod/access/access.go
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Access.go
|
||||||
|
|
||||||
|
This module is the new version of access control system
|
||||||
|
where now the blacklist / whitelist are seperated from
|
||||||
|
geodb module
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Create a new access controller to handle blacklist / whitelist
|
||||||
|
func NewAccessController(options *Options) (*Controller, error) {
|
||||||
|
sysdb := options.Database
|
||||||
|
if sysdb == nil {
|
||||||
|
return nil, errors.New("missing database access")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create the config folder if not exists
|
||||||
|
confFolder := options.ConfigFolder
|
||||||
|
if !utils.FileExists(confFolder) {
|
||||||
|
err := os.MkdirAll(confFolder, 0775)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the global access rule if not exists
|
||||||
|
var defaultAccessRule = AccessRule{
|
||||||
|
ID: "default",
|
||||||
|
Name: "Default",
|
||||||
|
Desc: "Default access rule for all HTTP proxy hosts",
|
||||||
|
BlacklistEnabled: false,
|
||||||
|
WhitelistEnabled: false,
|
||||||
|
WhiteListCountryCode: &map[string]string{},
|
||||||
|
WhiteListIP: &map[string]string{},
|
||||||
|
BlackListContryCode: &map[string]string{},
|
||||||
|
BlackListIP: &map[string]string{},
|
||||||
|
}
|
||||||
|
defaultRuleSettingFile := filepath.Join(confFolder, "default.json")
|
||||||
|
if utils.FileExists(defaultRuleSettingFile) {
|
||||||
|
//Load from file
|
||||||
|
defaultRuleBytes, err := os.ReadFile(defaultRuleSettingFile)
|
||||||
|
if err == nil {
|
||||||
|
err = json.Unmarshal(defaultRuleBytes, &defaultAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to parse default routing rule config file. Using default", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Create one
|
||||||
|
js, _ := json.MarshalIndent(defaultAccessRule, "", " ")
|
||||||
|
os.WriteFile(defaultRuleSettingFile, js, 0775)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate a controller object
|
||||||
|
thisController := Controller{
|
||||||
|
DefaultAccessRule: &defaultAccessRule,
|
||||||
|
ProxyAccessRule: &sync.Map{},
|
||||||
|
Options: options,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assign default access rule parent
|
||||||
|
thisController.DefaultAccessRule.parent = &thisController
|
||||||
|
|
||||||
|
//Load all acccess rules from file
|
||||||
|
configFiles, err := filepath.Glob(options.ConfigFolder + "/*.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ProxyAccessRules := sync.Map{}
|
||||||
|
for _, configFile := range configFiles {
|
||||||
|
if filepath.Base(configFile) == "default.json" {
|
||||||
|
//Skip this, as this was already loaded as default
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configContent, err := os.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to load config "+filepath.Base(configFile), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse the config file into AccessRule
|
||||||
|
thisAccessRule := AccessRule{}
|
||||||
|
err = json.Unmarshal(configContent, &thisAccessRule)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("Access", "Unable to parse config "+filepath.Base(configFile), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
thisAccessRule.parent = &thisController
|
||||||
|
ProxyAccessRules.Store(thisAccessRule.ID, &thisAccessRule)
|
||||||
|
}
|
||||||
|
thisController.ProxyAccessRule = &ProxyAccessRules
|
||||||
|
|
||||||
|
return &thisController, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the global access rule
|
||||||
|
func (c *Controller) GetGlobalAccessRule() (*AccessRule, error) {
|
||||||
|
if c.DefaultAccessRule == nil {
|
||||||
|
return nil, errors.New("global access rule is not set")
|
||||||
|
}
|
||||||
|
return c.DefaultAccessRule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load access rules to runtime, require rule ID
|
||||||
|
func (c *Controller) GetAccessRuleByID(accessRuleID string) (*AccessRule, error) {
|
||||||
|
if accessRuleID == "default" || accessRuleID == "" {
|
||||||
|
|
||||||
|
return c.DefaultAccessRule, nil
|
||||||
|
}
|
||||||
|
//Load from sync.Map, should be O(1)
|
||||||
|
targetRule, ok := c.ProxyAccessRule.Load(accessRuleID)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("target access rule not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
ar, ok := targetRule.(*AccessRule)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("assertion of access rule failed, version too old?")
|
||||||
|
}
|
||||||
|
return ar, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all the access rules currently in runtime, including default
|
||||||
|
func (c *Controller) ListAllAccessRules() []*AccessRule {
|
||||||
|
results := []*AccessRule{c.DefaultAccessRule}
|
||||||
|
c.ProxyAccessRule.Range(func(key, value interface{}) bool {
|
||||||
|
results = append(results, value.(*AccessRule))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an access rule exists given the rule id
|
||||||
|
func (c *Controller) AccessRuleExists(ruleID string) bool {
|
||||||
|
r, _ := c.GetAccessRuleByID(ruleID)
|
||||||
|
if r != nil {
|
||||||
|
//An access rule with identical ID exists
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new access rule to runtime and save it to file
|
||||||
|
func (c *Controller) AddNewAccessRule(newRule *AccessRule) error {
|
||||||
|
r, _ := c.GetAccessRuleByID(newRule.ID)
|
||||||
|
if r != nil {
|
||||||
|
//An access rule with identical ID exists
|
||||||
|
return errors.New("access rule already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the blacklist and whitelist are populated with empty map
|
||||||
|
if newRule.BlackListContryCode == nil {
|
||||||
|
newRule.BlackListContryCode = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.BlackListIP == nil {
|
||||||
|
newRule.BlackListIP = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.WhiteListCountryCode == nil {
|
||||||
|
newRule.WhiteListCountryCode = &map[string]string{}
|
||||||
|
}
|
||||||
|
if newRule.WhiteListIP == nil {
|
||||||
|
newRule.WhiteListIP = &map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add access rule to runtime
|
||||||
|
newRule.parent = c
|
||||||
|
c.ProxyAccessRule.Store(newRule.ID, newRule)
|
||||||
|
|
||||||
|
//Save rule to file
|
||||||
|
newRule.SaveChanges()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the access rule meta info.
|
||||||
|
func (c *Controller) UpdateAccessRule(ruleID string, name string, desc string) error {
|
||||||
|
targetAccessRule, err := c.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
///Update the name and desc
|
||||||
|
targetAccessRule.Name = name
|
||||||
|
targetAccessRule.Desc = desc
|
||||||
|
|
||||||
|
//Overwrite the rule currently in sync map
|
||||||
|
if ruleID == "default" {
|
||||||
|
c.DefaultAccessRule = targetAccessRule
|
||||||
|
} else {
|
||||||
|
c.ProxyAccessRule.Store(ruleID, targetAccessRule)
|
||||||
|
}
|
||||||
|
return targetAccessRule.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the access rule by its id
|
||||||
|
func (c *Controller) RemoveAccessRuleByID(ruleID string) error {
|
||||||
|
if !c.AccessRuleExists(ruleID) {
|
||||||
|
return errors.New("access rule not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Default cannot be removed
|
||||||
|
if ruleID == "default" {
|
||||||
|
return errors.New("default access rule cannot be removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove it
|
||||||
|
return c.DeleteAccessRuleByID(ruleID)
|
||||||
|
}
|
153
src/mod/access/accessRule.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check both blacklist and whitelist for access for both geoIP and ip / CIDR ranges
|
||||||
|
func (s *AccessRule) AllowIpAccess(ipaddr string) bool {
|
||||||
|
if s.IsBlacklisted(ipaddr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.IsWhitelisted(ipaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check both blacklist and whitelist for access using net.Conn
|
||||||
|
func (s *AccessRule) AllowConnectionAccess(conn net.Conn) bool {
|
||||||
|
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||||
|
return s.AllowIpAccess(addr.IP.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle black list
|
||||||
|
func (s *AccessRule) ToggleBlacklist(enabled bool) {
|
||||||
|
s.BlacklistEnabled = enabled
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggel white list
|
||||||
|
func (s *AccessRule) ToggleWhitelist(enabled bool) {
|
||||||
|
s.WhitelistEnabled = enabled
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if a IP address is blacklisted, in either country or IP blacklist
|
||||||
|
IsBlacklisted default return is false (allow access)
|
||||||
|
*/
|
||||||
|
func (s *AccessRule) IsBlacklisted(ipAddr string) bool {
|
||||||
|
if !s.BlacklistEnabled {
|
||||||
|
//Blacklist not enabled. Always return false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipAddr == "" {
|
||||||
|
//Unable to get the target IP address
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsCountryCodeBlacklisted(countryCode.CountryIsoCode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsIPBlacklisted(ipAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsWhitelisted check if a given IP address is in the current
|
||||||
|
server's white list.
|
||||||
|
|
||||||
|
Note that the Whitelist default result is true even
|
||||||
|
when encountered error
|
||||||
|
*/
|
||||||
|
func (s *AccessRule) IsWhitelisted(ipAddr string) bool {
|
||||||
|
if !s.WhitelistEnabled {
|
||||||
|
//Whitelist not enabled. Always return true (allow access)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipAddr == "" {
|
||||||
|
//Unable to get the target IP address, assume ok
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsCountryCodeWhitelisted(countryCode.CountryIsoCode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsIPWhitelisted(ipAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilities function */
|
||||||
|
|
||||||
|
// Update the current access rule to json file
|
||||||
|
func (s *AccessRule) SaveChanges() error {
|
||||||
|
if s.parent == nil {
|
||||||
|
return errors.New("save failed: access rule detached from controller")
|
||||||
|
}
|
||||||
|
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
|
||||||
|
js, err := json.MarshalIndent(s, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(saveTarget, js, 0775)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete this access rule, this will only delete the config file.
|
||||||
|
// for runtime delete, use DeleteAccessRuleByID from parent Controller
|
||||||
|
func (s *AccessRule) DeleteConfigFile() error {
|
||||||
|
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
|
||||||
|
return os.Remove(saveTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the access rule by given ID
|
||||||
|
func (c *Controller) DeleteAccessRuleByID(accessRuleID string) error {
|
||||||
|
targetAccessRule, err := c.GetAccessRuleByID(accessRuleID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete config file associated with this access rule
|
||||||
|
err = targetAccessRule.DeleteConfigFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete the access rule in runtime
|
||||||
|
c.ProxyAccessRule.Delete(accessRuleID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a deep copy object of the access rule list
|
||||||
|
func deepCopy(valueList map[string]string) map[string]string {
|
||||||
|
result := map[string]string{}
|
||||||
|
js, _ := json.Marshal(valueList)
|
||||||
|
json.Unmarshal(js, &result)
|
||||||
|
return result
|
||||||
|
}
|
94
src/mod/access/blacklist.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Blacklist.go
|
||||||
|
|
||||||
|
This script store the blacklist related functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Geo Blacklist
|
||||||
|
func (s *AccessRule) AddCountryCodeToBlackList(countryCode string, comment string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
|
||||||
|
newBlacklistCountryCode[countryCode] = comment
|
||||||
|
s.BlackListContryCode = &newBlacklistCountryCode
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveCountryCodeFromBlackList(countryCode string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
|
||||||
|
delete(newBlacklistCountryCode, countryCode)
|
||||||
|
s.BlackListContryCode = &newBlacklistCountryCode
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsCountryCodeBlacklisted(countryCode string) bool {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
blacklistMap := *s.BlackListContryCode
|
||||||
|
_, ok := blacklistMap[countryCode]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllBlacklistedCountryCode() []string {
|
||||||
|
bannedCountryCodes := []string{}
|
||||||
|
blacklistMap := *s.BlackListContryCode
|
||||||
|
for cc, _ := range blacklistMap {
|
||||||
|
bannedCountryCodes = append(bannedCountryCodes, cc)
|
||||||
|
}
|
||||||
|
return bannedCountryCodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP Blacklsits
|
||||||
|
func (s *AccessRule) AddIPToBlackList(ipAddr string, comment string) {
|
||||||
|
newBlackListIP := deepCopy(*s.BlackListIP)
|
||||||
|
newBlackListIP[ipAddr] = comment
|
||||||
|
s.BlackListIP = &newBlackListIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveIPFromBlackList(ipAddr string) {
|
||||||
|
newBlackListIP := deepCopy(*s.BlackListIP)
|
||||||
|
delete(newBlackListIP, ipAddr)
|
||||||
|
s.BlackListIP = &newBlackListIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllBlacklistedIp() []string {
|
||||||
|
bannedIps := []string{}
|
||||||
|
blacklistMap := *s.BlackListIP
|
||||||
|
for ip, _ := range blacklistMap {
|
||||||
|
bannedIps = append(bannedIps, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bannedIps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsIPBlacklisted(ipAddr string) bool {
|
||||||
|
IPBlacklist := *s.BlackListIP
|
||||||
|
_, ok := IPBlacklist[ipAddr]
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for CIDR
|
||||||
|
for ipOrCIDR, _ := range IPBlacklist {
|
||||||
|
wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR)
|
||||||
|
if wildcardMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR)
|
||||||
|
if cidrMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
38
src/mod/access/typedef.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Logger logger.Logger
|
||||||
|
ConfigFolder string //Path for storing config files
|
||||||
|
GeoDB *geodb.Store //For resolving country code
|
||||||
|
Database *database.Database //System key-value database
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessRule struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Desc string
|
||||||
|
BlacklistEnabled bool
|
||||||
|
WhitelistEnabled bool
|
||||||
|
|
||||||
|
/* Whitelist Blacklist Table, value is comment if supported */
|
||||||
|
WhiteListCountryCode *map[string]string
|
||||||
|
WhiteListIP *map[string]string
|
||||||
|
BlackListContryCode *map[string]string
|
||||||
|
BlackListIP *map[string]string
|
||||||
|
|
||||||
|
parent *Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
DefaultAccessRule *AccessRule
|
||||||
|
ProxyAccessRule *sync.Map
|
||||||
|
Options *Options
|
||||||
|
}
|
112
src/mod/access/whitelist.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Whitelist.go
|
||||||
|
|
||||||
|
This script handles whitelist related functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
EntryType_CountryCode int = 0
|
||||||
|
EntryType_IP int = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type WhitelistEntry struct {
|
||||||
|
EntryType int //Entry type of whitelist, Country Code or IP
|
||||||
|
CC string //ISO Country Code
|
||||||
|
IP string //IP address or range
|
||||||
|
Comment string //Comment for this entry
|
||||||
|
}
|
||||||
|
|
||||||
|
//Geo Whitelist
|
||||||
|
|
||||||
|
func (s *AccessRule) AddCountryCodeToWhitelist(countryCode string, comment string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
|
||||||
|
newWhitelistCC[countryCode] = comment
|
||||||
|
s.WhiteListCountryCode = &newWhitelistCC
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveCountryCodeFromWhitelist(countryCode string) {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
|
||||||
|
delete(newWhitelistCC, countryCode)
|
||||||
|
s.WhiteListCountryCode = &newWhitelistCC
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsCountryCodeWhitelisted(countryCode string) bool {
|
||||||
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
whitelistCC := *s.WhiteListCountryCode
|
||||||
|
_, ok := whitelistCC[countryCode]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllWhitelistedCountryCode() []*WhitelistEntry {
|
||||||
|
whitelistedCountryCode := []*WhitelistEntry{}
|
||||||
|
whitelistCC := *s.WhiteListCountryCode
|
||||||
|
for cc, comment := range whitelistCC {
|
||||||
|
whitelistedCountryCode = append(whitelistedCountryCode, &WhitelistEntry{
|
||||||
|
EntryType: EntryType_CountryCode,
|
||||||
|
CC: cc,
|
||||||
|
Comment: comment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return whitelistedCountryCode
|
||||||
|
}
|
||||||
|
|
||||||
|
//IP Whitelist
|
||||||
|
|
||||||
|
func (s *AccessRule) AddIPToWhiteList(ipAddr string, comment string) {
|
||||||
|
newWhitelistIP := deepCopy(*s.WhiteListIP)
|
||||||
|
newWhitelistIP[ipAddr] = comment
|
||||||
|
s.WhiteListIP = &newWhitelistIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) RemoveIPFromWhiteList(ipAddr string) {
|
||||||
|
newWhitelistIP := deepCopy(*s.WhiteListIP)
|
||||||
|
delete(newWhitelistIP, ipAddr)
|
||||||
|
s.WhiteListIP = &newWhitelistIP
|
||||||
|
s.SaveChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) IsIPWhitelisted(ipAddr string) bool {
|
||||||
|
//Check for IP wildcard and CIRD rules
|
||||||
|
WhitelistedIP := *s.WhiteListIP
|
||||||
|
for ipOrCIDR, _ := range WhitelistedIP {
|
||||||
|
wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR)
|
||||||
|
if wildcardMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR)
|
||||||
|
if cidrMatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccessRule) GetAllWhitelistedIp() []*WhitelistEntry {
|
||||||
|
whitelistedIp := []*WhitelistEntry{}
|
||||||
|
currentWhitelistedIP := *s.WhiteListIP
|
||||||
|
for ipOrCIDR, comment := range currentWhitelistedIP {
|
||||||
|
thisEntry := WhitelistEntry{
|
||||||
|
EntryType: EntryType_IP,
|
||||||
|
IP: ipOrCIDR,
|
||||||
|
Comment: comment,
|
||||||
|
}
|
||||||
|
whitelistedIp = append(whitelistedIp, &thisEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return whitelistedIp
|
||||||
|
}
|
@ -9,6 +9,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@ -24,6 +25,7 @@ import (
|
|||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,6 +33,7 @@ type CertificateInfoJSON struct {
|
|||||||
AcmeName string `json:"acme_name"`
|
AcmeName string `json:"acme_name"`
|
||||||
AcmeUrl string `json:"acme_url"`
|
AcmeUrl string `json:"acme_url"`
|
||||||
SkipTLS bool `json:"skip_tls"`
|
SkipTLS bool `json:"skip_tls"`
|
||||||
|
UseDNS bool `json:"dns"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACMEUser represents a user in the ACME system.
|
// ACMEUser represents a user in the ACME system.
|
||||||
@ -40,6 +43,11 @@ type ACMEUser struct {
|
|||||||
key crypto.PrivateKey
|
key crypto.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EABConfig struct {
|
||||||
|
Kid string `json:"kid"`
|
||||||
|
HmacKey string `json:"HmacKey"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetEmail returns the email of the ACMEUser.
|
// GetEmail returns the email of the ACMEUser.
|
||||||
func (u *ACMEUser) GetEmail() string {
|
func (u *ACMEUser) GetEmail() string {
|
||||||
return u.Email
|
return u.Email
|
||||||
@ -59,18 +67,20 @@ func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey {
|
|||||||
type ACMEHandler struct {
|
type ACMEHandler struct {
|
||||||
DefaultAcmeServer string
|
DefaultAcmeServer string
|
||||||
Port string
|
Port string
|
||||||
|
Database *database.Database
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewACME creates a new ACMEHandler instance.
|
// NewACME creates a new ACMEHandler instance.
|
||||||
func NewACME(acmeServer string, port string) *ACMEHandler {
|
func NewACME(acmeServer string, port string, database *database.Database) *ACMEHandler {
|
||||||
return &ACMEHandler{
|
return &ACMEHandler{
|
||||||
DefaultAcmeServer: acmeServer,
|
DefaultAcmeServer: acmeServer,
|
||||||
Port: port,
|
Port: port,
|
||||||
|
Database: database,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObtainCert obtains a certificate for the specified domains.
|
// ObtainCert obtains a certificate for the specified domains.
|
||||||
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool) (bool, error) {
|
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool, useDNS bool) (bool, error) {
|
||||||
log.Println("[ACME] Obtaining certificate...")
|
log.Println("[ACME] Obtaining certificate...")
|
||||||
|
|
||||||
// generate private key
|
// generate private key
|
||||||
@ -107,6 +117,11 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Fallback to Let's Encrypt if it is not set
|
||||||
|
if caName == "" {
|
||||||
|
caName = "Let's Encrypt"
|
||||||
|
}
|
||||||
|
|
||||||
// setup the custom ACME url endpoint.
|
// setup the custom ACME url endpoint.
|
||||||
if caUrl != "" {
|
if caUrl != "" {
|
||||||
config.CADirURL = caUrl
|
config.CADirURL = caUrl
|
||||||
@ -136,17 +151,107 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setup how to receive challenge
|
// setup how to receive challenge
|
||||||
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
|
if useDNS {
|
||||||
if err != nil {
|
if !a.Database.TableExists("acme") {
|
||||||
log.Println(err)
|
a.Database.NewTable("acme")
|
||||||
return false, err
|
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.Database.KeyExists("acme", certificateName+"_dns_provider") || !a.Database.KeyExists("acme", certificateName+"_dns_credentials") {
|
||||||
|
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -2)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var dnsCredentials string
|
||||||
|
err := a.Database.Read("acme", certificateName+"_dns_credentials", &dnsCredentials)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var dnsProvider string
|
||||||
|
err = a.Database.Read("acme", certificateName+"_dns_provider", &dnsProvider)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := GetDnsChallengeProviderByName(dnsProvider, dnsCredentials)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.Challenge.SetDNS01Provider(provider)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New users will need to register
|
// New users will need to register
|
||||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
/*
|
||||||
if err != nil {
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
log.Println(err)
|
if err != nil {
|
||||||
return false, err
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
var reg *registration.Resource
|
||||||
|
// New users will need to register
|
||||||
|
if client.GetExternalAccountRequired() {
|
||||||
|
log.Println("External Account Required for this ACME Provider.")
|
||||||
|
// IF KID and HmacEncoded is overidden
|
||||||
|
|
||||||
|
if !a.Database.TableExists("acme") {
|
||||||
|
a.Database.NewTable("acme")
|
||||||
|
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.Database.KeyExists("acme", config.CADirURL+"_kid") || !a.Database.KeyExists("acme", config.CADirURL+"_hmacEncoded") {
|
||||||
|
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -2)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var kid string
|
||||||
|
var hmacEncoded string
|
||||||
|
err := a.Database.Read("acme", config.CADirURL+"_kid", &kid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Database.Read("acme", config.CADirURL+"_hmacEncoded", &hmacEncoded)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("EAB Credential retrieved.", kid, hmacEncoded)
|
||||||
|
if kid != "" && hmacEncoded != "" {
|
||||||
|
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||||
|
TermsOfServiceAgreed: true,
|
||||||
|
Kid: kid,
|
||||||
|
HmacEncoded: hmacEncoded,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
//return false, errors.New("External Account Required for this ACME Provider.")
|
||||||
|
} else {
|
||||||
|
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
adminUser.Registration = reg
|
adminUser.Registration = reg
|
||||||
|
|
||||||
@ -179,6 +284,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
AcmeName: caName,
|
AcmeName: caName,
|
||||||
AcmeUrl: caUrl,
|
AcmeUrl: caUrl,
|
||||||
SkipTLS: skipTLS,
|
SkipTLS: skipTLS,
|
||||||
|
UseDNS: useDNS,
|
||||||
}
|
}
|
||||||
|
|
||||||
certInfoBytes, err := json.Marshal(certInfo)
|
certInfoBytes, err := json.Marshal(certInfo)
|
||||||
@ -291,6 +397,8 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//Make sure the wildcard * do not goes into the filename
|
||||||
|
filename = strings.ReplaceAll(filename, "*", "_")
|
||||||
|
|
||||||
email, err := utils.PostPara(r, "email")
|
email, err := utils.PostPara(r, "email")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -329,8 +437,18 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
skipTLS = true
|
skipTLS = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dns bool
|
||||||
|
|
||||||
|
if dnsString, err := utils.PostPara(r, "dns"); err != nil {
|
||||||
|
dns = false
|
||||||
|
} else if dnsString != "true" {
|
||||||
|
dns = false
|
||||||
|
} else {
|
||||||
|
dns = true
|
||||||
|
}
|
||||||
|
|
||||||
domains := strings.Split(domainPara, ",")
|
domains := strings.Split(domainPara, ",")
|
||||||
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS)
|
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS, dns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||||
return
|
return
|
||||||
@ -362,7 +480,7 @@ func IsPortInUse(port int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load cert information from json file
|
// Load cert information from json file
|
||||||
func loadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
|
func LoadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
|
||||||
certInfoBytes, err := os.ReadFile(filename)
|
certInfoBytes, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
72
src/mod/acme/acme_dns.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
|
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetDnsChallengeProviderByName(dnsProvider string, dnsCredentials string) (challenge.Provider, error) {
|
||||||
|
|
||||||
|
//Original Implementation
|
||||||
|
/*credentials, err := extractDnsCredentials(dnsCredentials)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
setCredentialsIntoEnvironmentVariables(credentials)
|
||||||
|
|
||||||
|
provider, err := dns.NewDNSChallengeProviderByName(dnsProvider)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//New implementation using acmedns CICD pipeline generated datatype
|
||||||
|
return acmedns.GetDNSProviderByJsonConfig(dnsProvider, dnsCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Original implementation of DNS ACME using OS.Env as payload
|
||||||
|
*/
|
||||||
|
|
||||||
|
func setCredentialsIntoEnvironmentVariables(credentials map[string]string) {
|
||||||
|
for key, value := range credentials {
|
||||||
|
err := os.Setenv(key, value)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERR] Failed to set environment variable %s: %v", key, err)
|
||||||
|
} else {
|
||||||
|
log.Println("[INFO] Environment variable %s set successfully", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractDnsCredentials(input string) (map[string]string, error) {
|
||||||
|
result := make(map[string]string)
|
||||||
|
|
||||||
|
// Split the input string by newline character
|
||||||
|
lines := strings.Split(input, "\n")
|
||||||
|
|
||||||
|
// Iterate over each line
|
||||||
|
for _, line := range lines {
|
||||||
|
// Split the line by "=" character
|
||||||
|
//use SpliyN to make sure not to split the value if the value is base64
|
||||||
|
parts := strings.SplitN(line, "=", 1)
|
||||||
|
|
||||||
|
// Check if the line is in the correct format
|
||||||
|
if len(parts) == 2 {
|
||||||
|
key := strings.TrimSpace(parts[0])
|
||||||
|
value := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
|
// Add the key-value pair to the map
|
||||||
|
result[key] = value
|
||||||
|
|
||||||
|
if value == "" || key == "" {
|
||||||
|
//invalid config
|
||||||
|
return result, errors.New("DNS credential extract failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
982
src/mod/acme/acmedns/acmedns.go
Normal file
@ -0,0 +1,982 @@
|
|||||||
|
package acmedns
|
||||||
|
/*
|
||||||
|
THIS MODULE IS GENERATED AUTOMATICALLY
|
||||||
|
DO NOT EDIT THIS FILE
|
||||||
|
*/
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/allinkl"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/arvancloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/auroradns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/autodns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/azure"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/azuredns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/bindman"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/bluecat"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/brandit"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/bunny"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/checkdomain"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/civo"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/clouddns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/cloudns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/cloudru"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/cloudxns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/conoha"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/constellix"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/cpanel"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/derak"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/desec"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/digitalocean"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dnshomede"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dnsimple"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dnsmadeeasy"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dnspod"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dode"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/domeneshop"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dreamhost"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/duckdns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dyn"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/dynu"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/easydns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/efficientip"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/epik"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/exoscale"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/freemyip"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/gandi"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/gandiv5"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/gcore"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/glesys"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/godaddy"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/googledomains"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/hetzner"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/hostingde"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/hosttech"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/httpnet"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/hyperone"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ibmcloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/iij"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/iijdpf"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/infoblox"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/infomaniak"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/internetbs"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/inwx"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ionos"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ipv64"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/iwantmyname"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/joker"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/liara"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/lightsail"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/linode"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/liquidweb"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/loopia"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/luadns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/mailinabox"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/metaname"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/mydnsjp"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/namecheap"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/namedotcom"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/namesilo"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/nearlyfreespeech"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/netcup"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/netlify"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/nicmanager"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/nifcloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/njalla"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/nodion"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ns1"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/otc"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ovh"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/pdns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/plesk"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/porkbun"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/rackspace"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/rcodezero"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/regru"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/rfc2136"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/rimuhosting"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/route53"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/safedns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/sakuracloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/scaleway"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/selectel"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/servercow"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/shellrent"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/simply"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/sonic"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/stackpath"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/transip"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/ultradns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/variomedia"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vegadns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vercel"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/versio"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vinyldns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vkcloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vscale"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/vultr"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/webnames"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/websupport"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/wedos"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/yandex"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/yandex360"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/yandexcloud"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/zoneee"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/zonomi"
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
//name is the DNS provider name, e.g. cloudflare or gandi
|
||||||
|
//JSON (js) must be in key-value string that match ConfigableFields Title in providers.json, e.g. {"Username":"far","Password":"boo"}
|
||||||
|
func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, error){
|
||||||
|
switch name {
|
||||||
|
|
||||||
|
case "alidns":
|
||||||
|
cfg := alidns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return alidns.NewDNSProviderConfig(cfg)
|
||||||
|
case "allinkl":
|
||||||
|
cfg := allinkl.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return allinkl.NewDNSProviderConfig(cfg)
|
||||||
|
case "arvancloud":
|
||||||
|
cfg := arvancloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return arvancloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "auroradns":
|
||||||
|
cfg := auroradns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return auroradns.NewDNSProviderConfig(cfg)
|
||||||
|
case "autodns":
|
||||||
|
cfg := autodns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return autodns.NewDNSProviderConfig(cfg)
|
||||||
|
case "azure":
|
||||||
|
cfg := azure.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return azure.NewDNSProviderConfig(cfg)
|
||||||
|
case "azuredns":
|
||||||
|
cfg := azuredns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return azuredns.NewDNSProviderConfig(cfg)
|
||||||
|
case "bindman":
|
||||||
|
cfg := bindman.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bindman.NewDNSProviderConfig(cfg)
|
||||||
|
case "bluecat":
|
||||||
|
cfg := bluecat.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bluecat.NewDNSProviderConfig(cfg)
|
||||||
|
case "brandit":
|
||||||
|
cfg := brandit.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return brandit.NewDNSProviderConfig(cfg)
|
||||||
|
case "bunny":
|
||||||
|
cfg := bunny.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bunny.NewDNSProviderConfig(cfg)
|
||||||
|
case "checkdomain":
|
||||||
|
cfg := checkdomain.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return checkdomain.NewDNSProviderConfig(cfg)
|
||||||
|
case "civo":
|
||||||
|
cfg := civo.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return civo.NewDNSProviderConfig(cfg)
|
||||||
|
case "clouddns":
|
||||||
|
cfg := clouddns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return clouddns.NewDNSProviderConfig(cfg)
|
||||||
|
case "cloudflare":
|
||||||
|
cfg := cloudflare.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cloudflare.NewDNSProviderConfig(cfg)
|
||||||
|
case "cloudns":
|
||||||
|
cfg := cloudns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cloudns.NewDNSProviderConfig(cfg)
|
||||||
|
case "cloudru":
|
||||||
|
cfg := cloudru.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cloudru.NewDNSProviderConfig(cfg)
|
||||||
|
case "cloudxns":
|
||||||
|
cfg := cloudxns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cloudxns.NewDNSProviderConfig(cfg)
|
||||||
|
case "conoha":
|
||||||
|
cfg := conoha.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conoha.NewDNSProviderConfig(cfg)
|
||||||
|
case "constellix":
|
||||||
|
cfg := constellix.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return constellix.NewDNSProviderConfig(cfg)
|
||||||
|
case "cpanel":
|
||||||
|
cfg := cpanel.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cpanel.NewDNSProviderConfig(cfg)
|
||||||
|
case "derak":
|
||||||
|
cfg := derak.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return derak.NewDNSProviderConfig(cfg)
|
||||||
|
case "desec":
|
||||||
|
cfg := desec.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return desec.NewDNSProviderConfig(cfg)
|
||||||
|
case "digitalocean":
|
||||||
|
cfg := digitalocean.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return digitalocean.NewDNSProviderConfig(cfg)
|
||||||
|
case "dnshomede":
|
||||||
|
cfg := dnshomede.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dnshomede.NewDNSProviderConfig(cfg)
|
||||||
|
case "dnsimple":
|
||||||
|
cfg := dnsimple.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dnsimple.NewDNSProviderConfig(cfg)
|
||||||
|
case "dnsmadeeasy":
|
||||||
|
cfg := dnsmadeeasy.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dnsmadeeasy.NewDNSProviderConfig(cfg)
|
||||||
|
case "dnspod":
|
||||||
|
cfg := dnspod.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dnspod.NewDNSProviderConfig(cfg)
|
||||||
|
case "dode":
|
||||||
|
cfg := dode.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dode.NewDNSProviderConfig(cfg)
|
||||||
|
case "domeneshop":
|
||||||
|
cfg := domeneshop.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return domeneshop.NewDNSProviderConfig(cfg)
|
||||||
|
case "dreamhost":
|
||||||
|
cfg := dreamhost.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dreamhost.NewDNSProviderConfig(cfg)
|
||||||
|
case "duckdns":
|
||||||
|
cfg := duckdns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return duckdns.NewDNSProviderConfig(cfg)
|
||||||
|
case "dyn":
|
||||||
|
cfg := dyn.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dyn.NewDNSProviderConfig(cfg)
|
||||||
|
case "dynu":
|
||||||
|
cfg := dynu.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dynu.NewDNSProviderConfig(cfg)
|
||||||
|
case "easydns":
|
||||||
|
cfg := easydns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return easydns.NewDNSProviderConfig(cfg)
|
||||||
|
case "efficientip":
|
||||||
|
cfg := efficientip.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return efficientip.NewDNSProviderConfig(cfg)
|
||||||
|
case "epik":
|
||||||
|
cfg := epik.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return epik.NewDNSProviderConfig(cfg)
|
||||||
|
case "exoscale":
|
||||||
|
cfg := exoscale.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return exoscale.NewDNSProviderConfig(cfg)
|
||||||
|
case "freemyip":
|
||||||
|
cfg := freemyip.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return freemyip.NewDNSProviderConfig(cfg)
|
||||||
|
case "gandi":
|
||||||
|
cfg := gandi.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return gandi.NewDNSProviderConfig(cfg)
|
||||||
|
case "gandiv5":
|
||||||
|
cfg := gandiv5.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return gandiv5.NewDNSProviderConfig(cfg)
|
||||||
|
case "gcore":
|
||||||
|
cfg := gcore.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return gcore.NewDNSProviderConfig(cfg)
|
||||||
|
case "glesys":
|
||||||
|
cfg := glesys.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return glesys.NewDNSProviderConfig(cfg)
|
||||||
|
case "godaddy":
|
||||||
|
cfg := godaddy.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return godaddy.NewDNSProviderConfig(cfg)
|
||||||
|
case "googledomains":
|
||||||
|
cfg := googledomains.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return googledomains.NewDNSProviderConfig(cfg)
|
||||||
|
case "hetzner":
|
||||||
|
cfg := hetzner.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hetzner.NewDNSProviderConfig(cfg)
|
||||||
|
case "hostingde":
|
||||||
|
cfg := hostingde.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hostingde.NewDNSProviderConfig(cfg)
|
||||||
|
case "hosttech":
|
||||||
|
cfg := hosttech.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hosttech.NewDNSProviderConfig(cfg)
|
||||||
|
case "httpnet":
|
||||||
|
cfg := httpnet.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return httpnet.NewDNSProviderConfig(cfg)
|
||||||
|
case "hyperone":
|
||||||
|
cfg := hyperone.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hyperone.NewDNSProviderConfig(cfg)
|
||||||
|
case "ibmcloud":
|
||||||
|
cfg := ibmcloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ibmcloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "iij":
|
||||||
|
cfg := iij.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return iij.NewDNSProviderConfig(cfg)
|
||||||
|
case "iijdpf":
|
||||||
|
cfg := iijdpf.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return iijdpf.NewDNSProviderConfig(cfg)
|
||||||
|
case "infoblox":
|
||||||
|
cfg := infoblox.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return infoblox.NewDNSProviderConfig(cfg)
|
||||||
|
case "infomaniak":
|
||||||
|
cfg := infomaniak.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return infomaniak.NewDNSProviderConfig(cfg)
|
||||||
|
case "internetbs":
|
||||||
|
cfg := internetbs.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return internetbs.NewDNSProviderConfig(cfg)
|
||||||
|
case "inwx":
|
||||||
|
cfg := inwx.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return inwx.NewDNSProviderConfig(cfg)
|
||||||
|
case "ionos":
|
||||||
|
cfg := ionos.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ionos.NewDNSProviderConfig(cfg)
|
||||||
|
case "ipv64":
|
||||||
|
cfg := ipv64.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ipv64.NewDNSProviderConfig(cfg)
|
||||||
|
case "iwantmyname":
|
||||||
|
cfg := iwantmyname.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return iwantmyname.NewDNSProviderConfig(cfg)
|
||||||
|
case "joker":
|
||||||
|
cfg := joker.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return joker.NewDNSProviderConfig(cfg)
|
||||||
|
case "liara":
|
||||||
|
cfg := liara.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return liara.NewDNSProviderConfig(cfg)
|
||||||
|
case "lightsail":
|
||||||
|
cfg := lightsail.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return lightsail.NewDNSProviderConfig(cfg)
|
||||||
|
case "linode":
|
||||||
|
cfg := linode.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return linode.NewDNSProviderConfig(cfg)
|
||||||
|
case "liquidweb":
|
||||||
|
cfg := liquidweb.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return liquidweb.NewDNSProviderConfig(cfg)
|
||||||
|
case "loopia":
|
||||||
|
cfg := loopia.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return loopia.NewDNSProviderConfig(cfg)
|
||||||
|
case "luadns":
|
||||||
|
cfg := luadns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return luadns.NewDNSProviderConfig(cfg)
|
||||||
|
case "mailinabox":
|
||||||
|
cfg := mailinabox.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mailinabox.NewDNSProviderConfig(cfg)
|
||||||
|
case "metaname":
|
||||||
|
cfg := metaname.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return metaname.NewDNSProviderConfig(cfg)
|
||||||
|
case "mydnsjp":
|
||||||
|
cfg := mydnsjp.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mydnsjp.NewDNSProviderConfig(cfg)
|
||||||
|
case "namecheap":
|
||||||
|
cfg := namecheap.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return namecheap.NewDNSProviderConfig(cfg)
|
||||||
|
case "namedotcom":
|
||||||
|
cfg := namedotcom.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return namedotcom.NewDNSProviderConfig(cfg)
|
||||||
|
case "namesilo":
|
||||||
|
cfg := namesilo.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return namesilo.NewDNSProviderConfig(cfg)
|
||||||
|
case "nearlyfreespeech":
|
||||||
|
cfg := nearlyfreespeech.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nearlyfreespeech.NewDNSProviderConfig(cfg)
|
||||||
|
case "netcup":
|
||||||
|
cfg := netcup.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return netcup.NewDNSProviderConfig(cfg)
|
||||||
|
case "netlify":
|
||||||
|
cfg := netlify.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return netlify.NewDNSProviderConfig(cfg)
|
||||||
|
case "nicmanager":
|
||||||
|
cfg := nicmanager.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nicmanager.NewDNSProviderConfig(cfg)
|
||||||
|
case "nifcloud":
|
||||||
|
cfg := nifcloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nifcloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "njalla":
|
||||||
|
cfg := njalla.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return njalla.NewDNSProviderConfig(cfg)
|
||||||
|
case "nodion":
|
||||||
|
cfg := nodion.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nodion.NewDNSProviderConfig(cfg)
|
||||||
|
case "ns1":
|
||||||
|
cfg := ns1.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ns1.NewDNSProviderConfig(cfg)
|
||||||
|
case "otc":
|
||||||
|
cfg := otc.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return otc.NewDNSProviderConfig(cfg)
|
||||||
|
case "ovh":
|
||||||
|
cfg := ovh.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ovh.NewDNSProviderConfig(cfg)
|
||||||
|
case "pdns":
|
||||||
|
cfg := pdns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pdns.NewDNSProviderConfig(cfg)
|
||||||
|
case "plesk":
|
||||||
|
cfg := plesk.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return plesk.NewDNSProviderConfig(cfg)
|
||||||
|
case "porkbun":
|
||||||
|
cfg := porkbun.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return porkbun.NewDNSProviderConfig(cfg)
|
||||||
|
case "rackspace":
|
||||||
|
cfg := rackspace.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rackspace.NewDNSProviderConfig(cfg)
|
||||||
|
case "rcodezero":
|
||||||
|
cfg := rcodezero.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rcodezero.NewDNSProviderConfig(cfg)
|
||||||
|
case "regru":
|
||||||
|
cfg := regru.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return regru.NewDNSProviderConfig(cfg)
|
||||||
|
case "rfc2136":
|
||||||
|
cfg := rfc2136.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rfc2136.NewDNSProviderConfig(cfg)
|
||||||
|
case "rimuhosting":
|
||||||
|
cfg := rimuhosting.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rimuhosting.NewDNSProviderConfig(cfg)
|
||||||
|
case "route53":
|
||||||
|
cfg := route53.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return route53.NewDNSProviderConfig(cfg)
|
||||||
|
case "safedns":
|
||||||
|
cfg := safedns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return safedns.NewDNSProviderConfig(cfg)
|
||||||
|
case "sakuracloud":
|
||||||
|
cfg := sakuracloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sakuracloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "scaleway":
|
||||||
|
cfg := scaleway.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return scaleway.NewDNSProviderConfig(cfg)
|
||||||
|
case "selectel":
|
||||||
|
cfg := selectel.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return selectel.NewDNSProviderConfig(cfg)
|
||||||
|
case "servercow":
|
||||||
|
cfg := servercow.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return servercow.NewDNSProviderConfig(cfg)
|
||||||
|
case "shellrent":
|
||||||
|
cfg := shellrent.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return shellrent.NewDNSProviderConfig(cfg)
|
||||||
|
case "simply":
|
||||||
|
cfg := simply.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return simply.NewDNSProviderConfig(cfg)
|
||||||
|
case "sonic":
|
||||||
|
cfg := sonic.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sonic.NewDNSProviderConfig(cfg)
|
||||||
|
case "stackpath":
|
||||||
|
cfg := stackpath.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stackpath.NewDNSProviderConfig(cfg)
|
||||||
|
case "tencentcloud":
|
||||||
|
cfg := tencentcloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tencentcloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "transip":
|
||||||
|
cfg := transip.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return transip.NewDNSProviderConfig(cfg)
|
||||||
|
case "ultradns":
|
||||||
|
cfg := ultradns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ultradns.NewDNSProviderConfig(cfg)
|
||||||
|
case "variomedia":
|
||||||
|
cfg := variomedia.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return variomedia.NewDNSProviderConfig(cfg)
|
||||||
|
case "vegadns":
|
||||||
|
cfg := vegadns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vegadns.NewDNSProviderConfig(cfg)
|
||||||
|
case "vercel":
|
||||||
|
cfg := vercel.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vercel.NewDNSProviderConfig(cfg)
|
||||||
|
case "versio":
|
||||||
|
cfg := versio.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return versio.NewDNSProviderConfig(cfg)
|
||||||
|
case "vinyldns":
|
||||||
|
cfg := vinyldns.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vinyldns.NewDNSProviderConfig(cfg)
|
||||||
|
case "vkcloud":
|
||||||
|
cfg := vkcloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vkcloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "vscale":
|
||||||
|
cfg := vscale.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vscale.NewDNSProviderConfig(cfg)
|
||||||
|
case "vultr":
|
||||||
|
cfg := vultr.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vultr.NewDNSProviderConfig(cfg)
|
||||||
|
case "webnames":
|
||||||
|
cfg := webnames.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webnames.NewDNSProviderConfig(cfg)
|
||||||
|
case "websupport":
|
||||||
|
cfg := websupport.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return websupport.NewDNSProviderConfig(cfg)
|
||||||
|
case "wedos":
|
||||||
|
cfg := wedos.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return wedos.NewDNSProviderConfig(cfg)
|
||||||
|
case "yandex":
|
||||||
|
cfg := yandex.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return yandex.NewDNSProviderConfig(cfg)
|
||||||
|
case "yandex360":
|
||||||
|
cfg := yandex360.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return yandex360.NewDNSProviderConfig(cfg)
|
||||||
|
case "yandexcloud":
|
||||||
|
cfg := yandexcloud.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return yandexcloud.NewDNSProviderConfig(cfg)
|
||||||
|
case "zoneee":
|
||||||
|
cfg := zoneee.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return zoneee.NewDNSProviderConfig(cfg)
|
||||||
|
case "zonomi":
|
||||||
|
cfg := zonomi.NewDefaultConfig()
|
||||||
|
err := json.Unmarshal([]byte(js), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return zonomi.NewDNSProviderConfig(cfg)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unrecognized DNS provider: %s", name)
|
||||||
|
}
|
||||||
|
}
|
27
src/mod/acme/acmedns/acmedns_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package acmedns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test if the structure of ACME DNS config can be reflected from lego source code definations
|
||||||
|
func TestACMEDNSConfigStructureReflector(t *testing.T) {
|
||||||
|
providers := []string{
|
||||||
|
"gandi",
|
||||||
|
"cloudflare",
|
||||||
|
"azure",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, provider := range providers {
|
||||||
|
strcture, err := acmedns.GetProviderConfigStructure(provider)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(strcture)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3553
src/mod/acme/acmedns/providers.json
Normal file
80
src/mod/acme/acmedns/providerutils.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package acmedns
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed providers.json
|
||||||
|
var providers []byte //A list of providers generated by acmedns code-generator
|
||||||
|
|
||||||
|
type ConfigTemplate struct {
|
||||||
|
Name string `json:"Name"`
|
||||||
|
ConfigableFields []struct {
|
||||||
|
Title string `json:"Title"`
|
||||||
|
Datatype string `json:"Datatype"`
|
||||||
|
} `json:"ConfigableFields"`
|
||||||
|
HiddenFields []struct {
|
||||||
|
Title string `json:"Title"`
|
||||||
|
Datatype string `json:"Datatype"`
|
||||||
|
} `json:"HiddenFields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a map of string => datatype
|
||||||
|
func GetProviderConfigStructure(providerName string) (map[string]string, error) {
|
||||||
|
//Load the target config template from embedded providers.json
|
||||||
|
configTemplateMap := map[string]ConfigTemplate{}
|
||||||
|
err := json.Unmarshal(providers, &configTemplateMap)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetConfigTemplate, ok := configTemplateMap[providerName]
|
||||||
|
if !ok {
|
||||||
|
return map[string]string{}, errors.New("provider not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
results := map[string]string{}
|
||||||
|
for _, field := range targetConfigTemplate.ConfigableFields {
|
||||||
|
results[field.Title] = field.Datatype
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleServeProvidersJson return the list of supported providers as json
|
||||||
|
func HandleServeProvidersJson(w http.ResponseWriter, r *http.Request) {
|
||||||
|
providerName, _ := utils.GetPara(r, "name")
|
||||||
|
if providerName == "" {
|
||||||
|
//Send the current list of providers
|
||||||
|
configTemplateMap := map[string]ConfigTemplate{}
|
||||||
|
err := json.Unmarshal(providers, &configTemplateMap)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "failed to load DNS provider")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse the provider names into an array
|
||||||
|
providers := []string{}
|
||||||
|
for providerName, _ := range configTemplateMap {
|
||||||
|
providers = append(providers, providerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(providers)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//Get the config for that provider
|
||||||
|
confTemplate, err := GetProviderConfigStructure(providerName)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(confTemplate)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
@ -344,7 +344,7 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
|||||||
|
|
||||||
// Load certificate info for ACME detail
|
// Load certificate info for ACME detail
|
||||||
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
|
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
|
||||||
certInfo, err := loadCertInfoJSON(certInfoFilename)
|
certInfo, err := LoadCertInfoJSON(certInfoFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Renew %s certificate error, can't get the ACME detail for cert: %v, trying org section as ca", certName, err)
|
log.Printf("Renew %s certificate error, can't get the ACME detail for cert: %v, trying org section as ca", certName, err)
|
||||||
|
|
||||||
@ -356,7 +356,7 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS)
|
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS, certInfo.UseDNS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
|
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
|
||||||
} else {
|
} else {
|
||||||
@ -373,3 +373,65 @@ func (a *AutoRenewer) saveRenewConfigToFile() error {
|
|||||||
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
|
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
|
||||||
return os.WriteFile(a.ConfigFilePath, js, 0775)
|
return os.WriteFile(a.ConfigFilePath, js, 0775)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle update auto renew EAD configuration
|
||||||
|
func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
|
||||||
|
kid, err := utils.GetPara(r, "kid")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "kid not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hmacEncoded, err := utils.GetPara(r, "hmacEncoded")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "hmacEncoded not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
acmeDirectoryURL, err := utils.GetPara(r, "acmeDirectoryURL")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "acmeDirectoryURL not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.AcmeHandler.Database.TableExists("acme") {
|
||||||
|
a.AcmeHandler.Database.NewTable("acme")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_kid", kid)
|
||||||
|
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_hmacEncoded", hmacEncoded)
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle update auto renew DNS configuration
|
||||||
|
func (a *AutoRenewer) HanldeSetDNS(w http.ResponseWriter, r *http.Request) {
|
||||||
|
dnsProvider, err := utils.PostPara(r, "dnsProvider")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "dnsProvider not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsCredentials, err := utils.PostPara(r, "dnsCredentials")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "dnsCredentials not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename, err := utils.PostPara(r, "filename")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "filename not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.AcmeHandler.Database.TableExists("acme") {
|
||||||
|
a.AcmeHandler.Database.NewTable("acme")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.AcmeHandler.Database.Write("acme", filename+"_dns_provider", dnsProvider)
|
||||||
|
a.AcmeHandler.Database.Write("acme", filename+"_dns_credentials", dnsCredentials)
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
package aroz
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
//To be used with arozos system
|
|
||||||
type ArozHandler struct {
|
|
||||||
Port string
|
|
||||||
restfulEndpoint string
|
|
||||||
}
|
|
||||||
|
|
||||||
//Information required for registering this subservice to arozos
|
|
||||||
type ServiceInfo struct {
|
|
||||||
Name string //Name of this module. e.g. "Audio"
|
|
||||||
Desc string //Description for this module
|
|
||||||
Group string //Group of the module, e.g. "system" / "media" etc
|
|
||||||
IconPath string //Module icon image path e.g. "Audio/img/function_icon.png"
|
|
||||||
Version string //Version of the module. Format: [0-9]*.[0-9][0-9].[0-9]
|
|
||||||
StartDir string //Default starting dir, e.g. "Audio/index.html"
|
|
||||||
SupportFW bool //Support floatWindow. If yes, floatWindow dir will be loaded
|
|
||||||
LaunchFWDir string //This link will be launched instead of 'StartDir' if fw mode
|
|
||||||
SupportEmb bool //Support embedded mode
|
|
||||||
LaunchEmb string //This link will be launched instead of StartDir / Fw if a file is opened with this module
|
|
||||||
InitFWSize []int //Floatwindow init size. [0] => Width, [1] => Height
|
|
||||||
InitEmbSize []int //Embedded mode init size. [0] => Width, [1] => Height
|
|
||||||
SupportedExt []string //Supported File Extensions. e.g. ".mp3", ".flac", ".wav"
|
|
||||||
}
|
|
||||||
|
|
||||||
//This function will request the required flag from the startup paramters and parse it to the need of the arozos.
|
|
||||||
func HandleFlagParse(info ServiceInfo) *ArozHandler {
|
|
||||||
var infoRequestMode = flag.Bool("info", false, "Show information about this program in JSON")
|
|
||||||
var port = flag.String("port", ":8000", "Management web interface listening port")
|
|
||||||
var restful = flag.String("rpt", "", "Reserved")
|
|
||||||
//Parse the flags
|
|
||||||
flag.Parse()
|
|
||||||
if *infoRequestMode {
|
|
||||||
//Information request mode
|
|
||||||
jsonString, _ := json.MarshalIndent(info, "", " ")
|
|
||||||
fmt.Println(string(jsonString))
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
return &ArozHandler{
|
|
||||||
Port: *port,
|
|
||||||
restfulEndpoint: *restful,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Get the username and resources access token from the request, return username, token
|
|
||||||
func (a *ArozHandler) GetUserInfoFromRequest(w http.ResponseWriter, r *http.Request) (string, string) {
|
|
||||||
username := r.Header.Get("aouser")
|
|
||||||
token := r.Header.Get("aotoken")
|
|
||||||
|
|
||||||
return username, token
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ArozHandler) IsUsingExternalPermissionManager() bool {
|
|
||||||
return !(a.restfulEndpoint == "")
|
|
||||||
}
|
|
||||||
|
|
||||||
//Request gateway interface for advance permission sandbox control
|
|
||||||
func (a *ArozHandler) RequestGatewayInterface(token string, script string) (*http.Response, error) {
|
|
||||||
resp, err := http.PostForm(a.restfulEndpoint,
|
|
||||||
url.Values{"token": {token}, "script": {script}})
|
|
||||||
if err != nil {
|
|
||||||
// handle error
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
@ -6,8 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -16,46 +14,31 @@ import (
|
|||||||
Main server for dynamic proxy core
|
Main server for dynamic proxy core
|
||||||
|
|
||||||
Routing Handler Priority (High to Low)
|
Routing Handler Priority (High to Low)
|
||||||
- Blacklist
|
- Special Routing Rule (e.g. acme)
|
||||||
- Whitelist
|
|
||||||
- Redirectable
|
- Redirectable
|
||||||
- Subdomain Routing
|
- Subdomain Routing
|
||||||
- Vitrual Directory Routing
|
- Access Router
|
||||||
|
- Blacklist
|
||||||
|
- Whitelist
|
||||||
|
- Basic Auth
|
||||||
|
- Vitrual Directory Proxy
|
||||||
|
- Subdomain Proxy
|
||||||
|
- Root router (default site router)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
/*
|
/*
|
||||||
Special Routing Rules, bypass most of the limitations
|
Special Routing Rules, bypass most of the limitations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//Check if there are external routing rule matches.
|
//Check if there are external routing rule matches.
|
||||||
//If yes, route them via external rr
|
//If yes, route them via external rr
|
||||||
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
||||||
if matchedRoutingRule != nil {
|
if matchedRoutingRule != nil {
|
||||||
//Matching routing rule found. Let the sub-router handle it
|
//Matching routing rule found. Let the sub-router handle it
|
||||||
if matchedRoutingRule.UseSystemAccessControl {
|
|
||||||
//This matching rule request system access control.
|
|
||||||
//check access logic
|
|
||||||
respWritten := h.handleAccessRouting(w, r)
|
|
||||||
if respWritten {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
matchedRoutingRule.Route(w, r)
|
matchedRoutingRule.Route(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Inject headers
|
|
||||||
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
|
|
||||||
|
|
||||||
/*
|
|
||||||
General Access Check
|
|
||||||
*/
|
|
||||||
respWritten := h.handleAccessRouting(w, r)
|
|
||||||
if respWritten {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Redirection Routing
|
Redirection Routing
|
||||||
*/
|
*/
|
||||||
@ -66,19 +49,30 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Extract request host to see if it is virtual directory or subdomain
|
/*
|
||||||
|
Host Routing
|
||||||
|
*/
|
||||||
|
//Extract request host to see if any proxy rule is matched
|
||||||
domainOnly := r.Host
|
domainOnly := r.Host
|
||||||
if strings.Contains(r.Host, ":") {
|
if strings.Contains(r.Host, ":") {
|
||||||
hostPath := strings.Split(r.Host, ":")
|
hostPath := strings.Split(r.Host, ":")
|
||||||
domainOnly = hostPath[0]
|
domainOnly = hostPath[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Host Routing
|
|
||||||
*/
|
|
||||||
|
|
||||||
sep := h.Parent.getProxyEndpointFromHostname(domainOnly)
|
sep := h.Parent.getProxyEndpointFromHostname(domainOnly)
|
||||||
if sep != nil && !sep.Disabled {
|
if sep != nil && !sep.Disabled {
|
||||||
|
//Matching proxy rule found
|
||||||
|
//Access Check (blacklist / whitelist)
|
||||||
|
ruleID := sep.AccessFilterUUID
|
||||||
|
if sep.AccessFilterUUID == "" {
|
||||||
|
//Use default rule
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
if h.handleAccessRouting(ruleID, w, r) {
|
||||||
|
//Request handled by subroute
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Validate basic auth
|
||||||
if sep.RequireBasicAuth {
|
if sep.RequireBasicAuth {
|
||||||
err := h.handleBasicAuthRouting(w, r, sep)
|
err := h.handleBasicAuthRouting(w, r, sep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -95,7 +89,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
} else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root {
|
} else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root {
|
||||||
potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
|
potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
|
||||||
if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
|
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
|
||||||
//Missing tailing slash. Redirect to target proxy endpoint
|
//Missing tailing slash. Redirect to target proxy endpoint
|
||||||
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
@ -110,6 +104,13 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
/*
|
/*
|
||||||
Root Router Handling
|
Root Router Handling
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//Root access control based on default rule
|
||||||
|
blocked := h.handleAccessRouting("default", w, r)
|
||||||
|
if blocked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//Clean up the request URI
|
//Clean up the request URI
|
||||||
proxyingPath := strings.TrimSpace(r.RequestURI)
|
proxyingPath := strings.TrimSpace(r.RequestURI)
|
||||||
if !strings.HasSuffix(proxyingPath, "/") {
|
if !strings.HasSuffix(proxyingPath, "/") {
|
||||||
@ -137,7 +138,6 @@ Once entered this routing segment, the root routing options will take over
|
|||||||
for the routing logic.
|
for the routing logic.
|
||||||
*/
|
*/
|
||||||
func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) {
|
func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
domainOnly := r.Host
|
domainOnly := r.Host
|
||||||
if strings.Contains(r.Host, ":") {
|
if strings.Contains(r.Host, ":") {
|
||||||
hostPath := strings.Split(r.Host, ":")
|
hostPath := strings.Split(r.Host, ":")
|
||||||
@ -193,41 +193,14 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
|||||||
h.logRequest(r, false, 307, "root-redirect", domainOnly)
|
h.logRequest(r, false, 307, "root-redirect", domainOnly)
|
||||||
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
|
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
|
||||||
case DefaultSite_NotFoundPage:
|
case DefaultSite_NotFoundPage:
|
||||||
http.NotFound(w, r)
|
//Serve the not found page, use template if exists
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle access routing logic. Return true if the request is handled or blocked by the access control logic
|
|
||||||
// if the return value is false, you can continue process the response writer
|
|
||||||
func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Request) bool {
|
|
||||||
//Check if this ip is in blacklist
|
|
||||||
clientIpAddr := geodb.GetRequesterIP(r)
|
|
||||||
if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/blacklist.html"))
|
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/notfound.html"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.Write(page_forbidden)
|
w.Write(page_hosterror)
|
||||||
} else {
|
} else {
|
||||||
w.Write(template)
|
w.Write(template)
|
||||||
}
|
}
|
||||||
h.logRequest(r, false, 403, "blacklist", "")
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if this ip is in whitelist
|
|
||||||
if !h.Parent.Option.GeodbStore.IsWhitelisted(clientIpAddr) {
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/whitelist.html"))
|
|
||||||
if err != nil {
|
|
||||||
w.Write(page_forbidden)
|
|
||||||
} else {
|
|
||||||
w.Write(template)
|
|
||||||
}
|
|
||||||
h.logRequest(r, false, 403, "whitelist", "")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
65
src/mod/dynamicproxy/access.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package dynamicproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle access check (blacklist / whitelist), return true if request is handled (aka blocked)
|
||||||
|
// if the return value is false, you can continue process the response writer
|
||||||
|
func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err != nil {
|
||||||
|
//Unable to load access rule. Target rule not found?
|
||||||
|
log.Println("[Proxy] Unable to load access rule: " + ruleID)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte("500 - Internal Server Error"))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
|
||||||
|
if isBlocked {
|
||||||
|
h.logRequest(r, false, 403, blockedReason, "")
|
||||||
|
}
|
||||||
|
return isBlocked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return boolean, return true if access is blocked
|
||||||
|
// For string, it will return the blocked reason (if any)
|
||||||
|
func accessRequestBlocked(accessRule *access.AccessRule, templateDirectory string, w http.ResponseWriter, r *http.Request) (bool, string) {
|
||||||
|
//Check if this ip is in blacklist
|
||||||
|
clientIpAddr := netutils.GetRequesterIP(r)
|
||||||
|
if accessRule.IsBlacklisted(clientIpAddr) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
template, err := os.ReadFile(filepath.Join(templateDirectory, "templates/blacklist.html"))
|
||||||
|
if err != nil {
|
||||||
|
w.Write(page_forbidden)
|
||||||
|
} else {
|
||||||
|
w.Write(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, "blacklist"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if this ip is in whitelist
|
||||||
|
if !accessRule.IsWhitelisted(clientIpAddr) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
template, err := os.ReadFile(filepath.Join(templateDirectory, "templates/whitelist.html"))
|
||||||
|
if err != nil {
|
||||||
|
w.Write(page_forbidden)
|
||||||
|
} else {
|
||||||
|
w.Write(template)
|
||||||
|
}
|
||||||
|
return true, "whitelist"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Not blocked.
|
||||||
|
return false, ""
|
||||||
|
}
|
@ -16,6 +16,16 @@ import (
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||||
|
err := handleBasicAuth(w, r, pe)
|
||||||
|
if err != nil {
|
||||||
|
h.logRequest(r, false, 401, "host", pe.Domain)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle basic auth logic
|
||||||
|
// do not write to http.ResponseWriter if err return is not nil (already handled by this function)
|
||||||
|
func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||||
if len(pe.BasicAuthExceptionRules) > 0 {
|
if len(pe.BasicAuthExceptionRules) > 0 {
|
||||||
//Check if the current path matches the exception rules
|
//Check if the current path matches the exception rules
|
||||||
for _, exceptionRule := range pe.BasicAuthExceptionRules {
|
for _, exceptionRule := range pe.BasicAuthExceptionRules {
|
||||||
@ -44,7 +54,6 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !matchingFound {
|
if !matchingFound {
|
||||||
h.logRequest(r, false, 401, "host", pe.Domain)
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
w.WriteHeader(401)
|
w.WriteHeader(401)
|
||||||
return errors.New("unauthorized")
|
return errors.New("unauthorized")
|
||||||
|
46
src/mod/dynamicproxy/customHeader.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package dynamicproxy
|
||||||
|
|
||||||
|
/*
|
||||||
|
CustomHeader.go
|
||||||
|
|
||||||
|
This script handle parsing and injecting custom headers
|
||||||
|
into the dpcore routing logic
|
||||||
|
*/
|
||||||
|
|
||||||
|
//SplitInboundOutboundHeaders split user defined headers into upstream and downstream headers
|
||||||
|
//return upstream header and downstream header key-value pairs
|
||||||
|
//if the header is expected to be deleted, the value will be set to empty string
|
||||||
|
func (ept *ProxyEndpoint) SplitInboundOutboundHeaders() ([][]string, [][]string) {
|
||||||
|
if len(ept.UserDefinedHeaders) == 0 {
|
||||||
|
//Early return if there are no defined headers
|
||||||
|
return [][]string{}, [][]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Use pre-allocation for faster performance
|
||||||
|
upstreamHeaders := make([][]string, len(ept.UserDefinedHeaders))
|
||||||
|
downstreamHeaders := make([][]string, len(ept.UserDefinedHeaders))
|
||||||
|
upstreamHeaderCounter := 0
|
||||||
|
downstreamHeaderCounter := 0
|
||||||
|
|
||||||
|
//Sort the headers into upstream or downstream
|
||||||
|
for _, customHeader := range ept.UserDefinedHeaders {
|
||||||
|
thisHeaderSet := make([]string, 2)
|
||||||
|
thisHeaderSet[0] = customHeader.Key
|
||||||
|
thisHeaderSet[1] = customHeader.Value
|
||||||
|
if customHeader.IsRemove {
|
||||||
|
//Prevent invalid config
|
||||||
|
thisHeaderSet[1] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assign to slice
|
||||||
|
if customHeader.Direction == HeaderDirection_ZoraxyToUpstream {
|
||||||
|
upstreamHeaders[upstreamHeaderCounter] = thisHeaderSet
|
||||||
|
upstreamHeaderCounter++
|
||||||
|
} else if customHeader.Direction == HeaderDirection_ZoraxyToDownstream {
|
||||||
|
downstreamHeaders[downstreamHeaderCounter] = thisHeaderSet
|
||||||
|
downstreamHeaderCounter++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return upstreamHeaders, downstreamHeaders
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package dpcore
|
package dpcore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@ -8,11 +9,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
|
||||||
|
|
||||||
var onExitFlushLoop func()
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
|
)
|
||||||
|
|
||||||
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
||||||
// sends it to another server, proxying the response back to the
|
// sends it to another server, proxying the response back to the
|
||||||
@ -57,18 +57,26 @@ type ReverseProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ResponseRewriteRuleSet struct {
|
type ResponseRewriteRuleSet struct {
|
||||||
ProxyDomain string
|
ProxyDomain string
|
||||||
OriginalHost string
|
OriginalHost string
|
||||||
UseTLS bool
|
UseTLS bool
|
||||||
NoCache bool
|
NoCache bool
|
||||||
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
||||||
|
UpstreamHeaders [][]string
|
||||||
|
DownstreamHeaders [][]string
|
||||||
|
Version string //Version number of Zoraxy, use for X-Proxy-By
|
||||||
}
|
}
|
||||||
|
|
||||||
type requestCanceler interface {
|
type requestCanceler interface {
|
||||||
CancelRequest(req *http.Request)
|
CancelRequest(req *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerification bool) *ReverseProxy {
|
type DpcoreOptions struct {
|
||||||
|
IgnoreTLSVerification bool
|
||||||
|
FlushInterval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy {
|
||||||
targetQuery := target.RawQuery
|
targetQuery := target.RawQuery
|
||||||
director := func(req *http.Request) {
|
director := func(req *http.Request) {
|
||||||
req.URL.Scheme = target.Scheme
|
req.URL.Scheme = target.Scheme
|
||||||
@ -80,10 +88,6 @@ func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerificatio
|
|||||||
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := req.Header["User-Agent"]; !ok {
|
|
||||||
req.Header.Set("User-Agent", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Hack the default transporter to handle more connections
|
//Hack the default transporter to handle more connections
|
||||||
@ -95,16 +99,17 @@ func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerificatio
|
|||||||
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
|
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
|
||||||
thisTransporter.(*http.Transport).DisableCompression = true
|
thisTransporter.(*http.Transport).DisableCompression = true
|
||||||
|
|
||||||
if ignoreTLSVerification {
|
if dpcOptions.IgnoreTLSVerification {
|
||||||
//Ignore TLS certificate validation error
|
//Ignore TLS certificate validation error
|
||||||
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
|
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ReverseProxy{
|
return &ReverseProxy{
|
||||||
Director: director,
|
Director: director,
|
||||||
Prepender: prepender,
|
Prepender: prepender,
|
||||||
Verbal: false,
|
FlushInterval: dpcOptions.FlushInterval,
|
||||||
Transport: thisTransporter,
|
Verbal: false,
|
||||||
|
Transport: thisTransporter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,64 +183,66 @@ var hopHeaders = []string{
|
|||||||
//"Upgrade",
|
//"Upgrade",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
|
// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
|
||||||
if p.FlushInterval != 0 {
|
func (p *ReverseProxy) copyResponse(dst http.ResponseWriter, src io.Reader, flushInterval time.Duration) error {
|
||||||
if wf, ok := dst.(writeFlusher); ok {
|
var w io.Writer = dst
|
||||||
mlw := &maxLatencyWriter{
|
if flushInterval != 0 {
|
||||||
dst: wf,
|
mlw := &maxLatencyWriter{
|
||||||
latency: p.FlushInterval,
|
dst: dst,
|
||||||
done: make(chan bool),
|
flush: http.NewResponseController(dst).Flush,
|
||||||
}
|
latency: flushInterval,
|
||||||
|
|
||||||
go mlw.flushLoop()
|
|
||||||
defer mlw.stop()
|
|
||||||
dst = mlw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer mlw.stop()
|
||||||
|
// set up initial timer so headers get flushed even if body writes are delayed
|
||||||
|
mlw.flushPending = true
|
||||||
|
mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
|
||||||
|
w = mlw
|
||||||
}
|
}
|
||||||
|
|
||||||
io.Copy(dst, src)
|
var buf []byte
|
||||||
|
_, err := p.copyBuffer(w, src, buf)
|
||||||
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type writeFlusher interface {
|
// Copy with given buffer size. Default to 64k
|
||||||
io.Writer
|
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
|
||||||
http.Flusher
|
if len(buf) == 0 {
|
||||||
}
|
buf = make([]byte, 64*1024)
|
||||||
|
}
|
||||||
|
|
||||||
type maxLatencyWriter struct {
|
var written int64
|
||||||
dst writeFlusher
|
|
||||||
latency time.Duration
|
|
||||||
mu sync.Mutex
|
|
||||||
done chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *maxLatencyWriter) Write(b []byte) (int, error) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
return m.dst.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *maxLatencyWriter) flushLoop() {
|
|
||||||
t := time.NewTicker(m.latency)
|
|
||||||
defer t.Stop()
|
|
||||||
for {
|
for {
|
||||||
select {
|
nr, rerr := src.Read(buf)
|
||||||
case <-m.done:
|
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
|
||||||
if onExitFlushLoop != nil {
|
p.logf("dpcore read error during body copy: %v", rerr)
|
||||||
onExitFlushLoop()
|
}
|
||||||
|
|
||||||
|
if nr > 0 {
|
||||||
|
nw, werr := dst.Write(buf[:nr])
|
||||||
|
if nw > 0 {
|
||||||
|
written += int64(nw)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
case <-t.C:
|
if werr != nil {
|
||||||
m.mu.Lock()
|
return written, werr
|
||||||
m.dst.Flush()
|
}
|
||||||
m.mu.Unlock()
|
|
||||||
|
if nr != nw {
|
||||||
|
return written, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rerr != nil {
|
||||||
|
if rerr == io.EOF {
|
||||||
|
rerr = nil
|
||||||
|
}
|
||||||
|
return written, rerr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *maxLatencyWriter) stop() {
|
|
||||||
m.done <- true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReverseProxy) logf(format string, args ...interface{}) {
|
func (p *ReverseProxy) logf(format string, args ...interface{}) {
|
||||||
if p.ErrorLog != nil {
|
if p.ErrorLog != nil {
|
||||||
p.ErrorLog.Printf(format, args...)
|
p.ErrorLog.Printf(format, args...)
|
||||||
@ -244,59 +251,6 @@ func (p *ReverseProxy) logf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeHeaders(header http.Header, noCache bool) {
|
|
||||||
// Remove hop-by-hop headers listed in the "Connection" header.
|
|
||||||
if c := header.Get("Connection"); c != "" {
|
|
||||||
for _, f := range strings.Split(c, ",") {
|
|
||||||
if f = strings.TrimSpace(f); f != "" {
|
|
||||||
header.Del(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove hop-by-hop headers
|
|
||||||
for _, h := range hopHeaders {
|
|
||||||
if header.Get(h) != "" {
|
|
||||||
header.Del(h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Restore the Upgrade header if any
|
|
||||||
if header.Get("Zr-Origin-Upgrade") != "" {
|
|
||||||
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
|
|
||||||
header.Del("Zr-Origin-Upgrade")
|
|
||||||
}
|
|
||||||
|
|
||||||
//Disable cache if nocache is set
|
|
||||||
if noCache {
|
|
||||||
header.Del("Cache-Control")
|
|
||||||
header.Set("Cache-Control", "no-store")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addXForwardedForHeader(req *http.Request) {
|
|
||||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
|
||||||
// If we aren't the first proxy retain prior
|
|
||||||
// X-Forwarded-For information as a comma+space
|
|
||||||
// separated list and fold multiple headers into one.
|
|
||||||
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
|
||||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
|
||||||
}
|
|
||||||
req.Header.Set("X-Forwarded-For", clientIP)
|
|
||||||
if req.TLS != nil {
|
|
||||||
req.Header.Set("X-Forwarded-Proto", "https")
|
|
||||||
} else {
|
|
||||||
req.Header.Set("X-Forwarded-Proto", "http")
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Header.Get("X-Real-Ip") == "" {
|
|
||||||
//Not exists. Fill it in with client IP
|
|
||||||
req.Header.Set("X-Real-Ip", clientIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error {
|
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error {
|
||||||
transport := p.Transport
|
transport := p.Transport
|
||||||
|
|
||||||
@ -325,9 +279,9 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
p.Director(outreq)
|
p.Director(outreq)
|
||||||
outreq.Close = false
|
outreq.Close = false
|
||||||
|
|
||||||
if !rrr.UseTLS {
|
//Only skip origin rewrite iff proxy target require TLS and it is external domain name like github.com
|
||||||
//This seems to be routing to external sites
|
if !(rrr.UseTLS && isExternalDomainName(rrr.ProxyDomain)) {
|
||||||
//Do not keep the original host
|
// Always use the original host, see issue #164
|
||||||
outreq.Host = rrr.OriginalHost
|
outreq.Host = rrr.OriginalHost
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,12 +289,18 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
outreq.Header = make(http.Header)
|
outreq.Header = make(http.Header)
|
||||||
copyHeader(outreq.Header, req.Header)
|
copyHeader(outreq.Header, req.Header)
|
||||||
|
|
||||||
// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
|
// Remove hop-by-hop headers.
|
||||||
removeHeaders(outreq.Header, rrr.NoCache)
|
removeHeaders(outreq.Header, rrr.NoCache)
|
||||||
|
|
||||||
// Add X-Forwarded-For Header.
|
// Add X-Forwarded-For Header.
|
||||||
addXForwardedForHeader(outreq)
|
addXForwardedForHeader(outreq)
|
||||||
|
|
||||||
|
// Add user defined headers (to upstream)
|
||||||
|
injectUserDefinedHeaders(outreq.Header, rrr.UpstreamHeaders)
|
||||||
|
|
||||||
|
// Rewrite outbound UA, must be after user headers
|
||||||
|
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)
|
||||||
|
|
||||||
res, err := transport.RoundTrip(outreq)
|
res, err := transport.RoundTrip(outreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if p.Verbal {
|
if p.Verbal {
|
||||||
@ -354,6 +314,12 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
|
// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
|
||||||
removeHeaders(res.Header, rrr.NoCache)
|
removeHeaders(res.Header, rrr.NoCache)
|
||||||
|
|
||||||
|
//Remove the User-Agent header if exists
|
||||||
|
if _, ok := res.Header["User-Agent"]; ok {
|
||||||
|
//Server to client request should not contains a User-Agent header
|
||||||
|
res.Header.Del("User-Agent")
|
||||||
|
}
|
||||||
|
|
||||||
if p.ModifyResponse != nil {
|
if p.ModifyResponse != nil {
|
||||||
if err := p.ModifyResponse(res); err != nil {
|
if err := p.ModifyResponse(res); err != nil {
|
||||||
if p.Verbal {
|
if p.Verbal {
|
||||||
@ -365,7 +331,17 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Custom header rewriter functions
|
//TODO: Figure out a way to proxy for proxmox
|
||||||
|
//if res.StatusCode == 501 || res.StatusCode == 500 {
|
||||||
|
// fmt.Println(outreq.Proto, outreq.RemoteAddr, outreq.RequestURI)
|
||||||
|
// fmt.Println(">>>", outreq.Method, res.Header, res.ContentLength, res.StatusCode)
|
||||||
|
// fmt.Println(outreq.Header, req.Host)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//Add debug X-Proxy-By tracker
|
||||||
|
res.Header.Set("x-proxy-by", "zoraxy/"+rrr.Version)
|
||||||
|
|
||||||
|
//Custom Location header rewriter functions
|
||||||
if res.Header.Get("Location") != "" {
|
if res.Header.Get("Location") != "" {
|
||||||
locationRewrite := res.Header.Get("Location")
|
locationRewrite := res.Header.Get("Location")
|
||||||
originLocation := res.Header.Get("Location")
|
originLocation := res.Header.Get("Location")
|
||||||
@ -391,9 +367,16 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
res.Header.Set("Location", locationRewrite)
|
res.Header.Set("Location", locationRewrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add user defined headers (to downstream)
|
||||||
|
injectUserDefinedHeaders(res.Header, rrr.DownstreamHeaders)
|
||||||
|
|
||||||
// Copy header from response to client.
|
// Copy header from response to client.
|
||||||
copyHeader(rw.Header(), res.Header)
|
copyHeader(rw.Header(), res.Header)
|
||||||
|
|
||||||
|
// inject permission policy headers
|
||||||
|
//TODO: Load permission policy from rrr
|
||||||
|
permissionpolicy.InjectPermissionPolicyHeader(rw, nil)
|
||||||
|
|
||||||
// The "Trailer" header isn't included in the Transport's response, Build it up from Trailer.
|
// The "Trailer" header isn't included in the Transport's response, Build it up from Trailer.
|
||||||
if len(res.Trailer) > 0 {
|
if len(res.Trailer) > 0 {
|
||||||
trailerKeys := make([]string, 0, len(res.Trailer))
|
trailerKeys := make([]string, 0, len(res.Trailer))
|
||||||
@ -413,7 +396,10 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.copyResponse(rw, res.Body)
|
//Get flush interval in real time and start copying the request
|
||||||
|
flushInterval := p.getFlushInterval(req, res)
|
||||||
|
p.copyResponse(rw, res.Body, flushInterval)
|
||||||
|
|
||||||
// close now, instead of defer, to populate res.Trailer
|
// close now, instead of defer, to populate res.Trailer
|
||||||
res.Body.Close()
|
res.Body.Close()
|
||||||
copyHeader(rw.Header(), res.Trailer)
|
copyHeader(rw.Header(), res.Trailer)
|
||||||
|
38
src/mod/dynamicproxy/dpcore/flush.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package dpcore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auto sniff of flush interval from header
|
||||||
|
func (p *ReverseProxy) getFlushInterval(req *http.Request, res *http.Response) time.Duration {
|
||||||
|
contentType := req.Header.Get("Content-Type")
|
||||||
|
if actualContentType, _, _ := mime.ParseMediaType(contentType); actualContentType == "text/event-stream" {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ContentLength == -1 || p.isBidirectionalStream(req, res) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
//Cannot sniff anything. Use default value
|
||||||
|
return p.FlushInterval
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for bidirectional stream, copy from Caddy :D
|
||||||
|
func (p *ReverseProxy) isBidirectionalStream(req *http.Request, res *http.Response) bool {
|
||||||
|
// We have to check the encoding here; only flush headers with identity encoding.
|
||||||
|
// Non-identity encoding might combine with "encode" directive, and in that case,
|
||||||
|
// if body size larger than enc.MinLength, upper level encode handle might have
|
||||||
|
// Content-Encoding header to write.
|
||||||
|
// (see https://github.com/caddyserver/caddy/issues/3606 for use case)
|
||||||
|
ae := req.Header.Get("Accept-Encoding")
|
||||||
|
|
||||||
|
return req.ProtoMajor == 2 &&
|
||||||
|
res.ProtoMajor == 2 &&
|
||||||
|
res.ContentLength == -1 &&
|
||||||
|
(ae == "identity" || ae == "")
|
||||||
|
}
|
121
src/mod/dynamicproxy/dpcore/header.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package dpcore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Header.go
|
||||||
|
|
||||||
|
This script handles headers rewrite and remove
|
||||||
|
in dpcore.
|
||||||
|
|
||||||
|
Added in Zoraxy v3.0.6 by tobychui
|
||||||
|
*/
|
||||||
|
|
||||||
|
// removeHeaders Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
|
||||||
|
func removeHeaders(header http.Header, noCache bool) {
|
||||||
|
// Remove hop-by-hop headers listed in the "Connection" header.
|
||||||
|
if c := header.Get("Connection"); c != "" {
|
||||||
|
for _, f := range strings.Split(c, ",") {
|
||||||
|
if f = strings.TrimSpace(f); f != "" {
|
||||||
|
header.Del(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove hop-by-hop headers
|
||||||
|
for _, h := range hopHeaders {
|
||||||
|
if header.Get(h) != "" {
|
||||||
|
header.Del(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Restore the Upgrade header if any
|
||||||
|
if header.Get("Zr-Origin-Upgrade") != "" {
|
||||||
|
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
|
||||||
|
header.Del("Zr-Origin-Upgrade")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Disable cache if nocache is set
|
||||||
|
if noCache {
|
||||||
|
header.Del("Cache-Control")
|
||||||
|
header.Set("Cache-Control", "no-store")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewriteUserAgent rewrite the user agent based on incoming request
|
||||||
|
func rewriteUserAgent(header http.Header, UA string) {
|
||||||
|
//Hide Go-HTTP-Client UA if the client didnt sent us one
|
||||||
|
if header.Get("User-Agent") == "" {
|
||||||
|
// If the outbound request doesn't have a User-Agent header set,
|
||||||
|
// don't send the default Go HTTP client User-Agent
|
||||||
|
header.Del("User-Agent")
|
||||||
|
header.Set("User-Agent", UA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add X-Forwarded-For Header and rewrite X-Real-Ip according to sniffing logics
|
||||||
|
func addXForwardedForHeader(req *http.Request) {
|
||||||
|
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||||
|
// If we aren't the first proxy retain prior
|
||||||
|
// X-Forwarded-For information as a comma+space
|
||||||
|
// separated list and fold multiple headers into one.
|
||||||
|
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
||||||
|
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||||
|
}
|
||||||
|
req.Header.Set("X-Forwarded-For", clientIP)
|
||||||
|
if req.TLS != nil {
|
||||||
|
req.Header.Set("X-Forwarded-Proto", "https")
|
||||||
|
} else {
|
||||||
|
req.Header.Set("X-Forwarded-Proto", "http")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Header.Get("X-Real-Ip") == "" {
|
||||||
|
//Check if CF-Connecting-IP header exists
|
||||||
|
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
|
||||||
|
Fastly_Client_IP := req.Header.Get("Fastly-Client-IP")
|
||||||
|
if CF_Connecting_IP != "" {
|
||||||
|
//Use CF Connecting IP
|
||||||
|
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
|
||||||
|
} else if Fastly_Client_IP != "" {
|
||||||
|
//Use Fastly Client IP
|
||||||
|
req.Header.Set("X-Real-Ip", Fastly_Client_IP)
|
||||||
|
} else {
|
||||||
|
// Not exists. Fill it in with first entry in X-Forwarded-For
|
||||||
|
ips := strings.Split(clientIP, ",")
|
||||||
|
if len(ips) > 0 {
|
||||||
|
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// injectUserDefinedHeaders inject the user headers from slice
|
||||||
|
// if a value is empty string, the key will be removed from header.
|
||||||
|
// if a key is empty string, the function will return immediately
|
||||||
|
func injectUserDefinedHeaders(header http.Header, userHeaders [][]string) {
|
||||||
|
for _, userHeader := range userHeaders {
|
||||||
|
if len(userHeader) == 0 {
|
||||||
|
//End of header slice
|
||||||
|
return
|
||||||
|
}
|
||||||
|
headerKey := userHeader[0]
|
||||||
|
headerValue := userHeader[1]
|
||||||
|
if headerValue == "" {
|
||||||
|
//Remove header from head
|
||||||
|
header.Del(headerKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Default: Set header value
|
||||||
|
header.Del(headerKey) //Remove header if it already exists
|
||||||
|
header.Set(headerKey, headerValue)
|
||||||
|
}
|
||||||
|
}
|
73
src/mod/dynamicproxy/dpcore/maxLatencyWriter.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package dpcore
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Max Latency Writer
|
||||||
|
|
||||||
|
This script implements a io writer with periodic flushing base on a ticker
|
||||||
|
Mostly based on httputil.ReverseProxy
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type maxLatencyWriter struct {
|
||||||
|
dst io.Writer
|
||||||
|
flush func() error
|
||||||
|
latency time.Duration // non-zero; negative means to flush immediately
|
||||||
|
mu sync.Mutex // protects t, flushPending, and dst.Flush
|
||||||
|
t *time.Timer
|
||||||
|
flushPending bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
n, err = m.dst.Write(p)
|
||||||
|
if m.latency < 0 {
|
||||||
|
//Flush immediately
|
||||||
|
m.flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.flushPending {
|
||||||
|
//Flush in next tick cycle
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.t == nil {
|
||||||
|
m.t = time.AfterFunc(m.latency, m.delayedFlush)
|
||||||
|
} else {
|
||||||
|
m.t.Reset(m.latency)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.flushPending = true
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *maxLatencyWriter) delayedFlush() {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
if !m.flushPending {
|
||||||
|
// if stop was called but AfterFunc already started this goroutine
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.flush()
|
||||||
|
m.flushPending = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *maxLatencyWriter) stop() {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
m.flushPending = false
|
||||||
|
if m.t != nil {
|
||||||
|
m.t.Stop()
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package dpcore
|
package dpcore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -60,3 +61,34 @@ func replaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS b
|
|||||||
func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
|
func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
|
||||||
return replaceLocationHost(urlString, rrr, useTLS)
|
return replaceLocationHost(urlString, rrr, useTLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isExternalDomainName check and return if the hostname is external domain name (e.g. github.com)
|
||||||
|
// instead of internal (like 192.168.1.202:8443 (ip address) or domains end with .local or .internal)
|
||||||
|
func isExternalDomainName(hostname string) bool {
|
||||||
|
host, _, err := net.SplitHostPort(hostname)
|
||||||
|
if err != nil {
|
||||||
|
//hostname doesnt contain port
|
||||||
|
ip := net.ParseIP(hostname)
|
||||||
|
if ip != nil {
|
||||||
|
//IP address, not a domain name
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Hostname contain port, use hostname without port to check if it is ip
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
if ip != nil {
|
||||||
|
//IP address, not a domain name
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if it is internal DNS assigned domains
|
||||||
|
internalDNSTLD := []string{".local", ".internal", ".localhost", ".home.arpa"}
|
||||||
|
for _, tld := range internalDNSTLD {
|
||||||
|
if strings.HasSuffix(strings.ToLower(hostname), tld) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -115,11 +115,34 @@ func (router *Router) StartProxyService() error {
|
|||||||
r.URL, _ = url.Parse(originalHostHeader)
|
r.URL, _ = url.Parse(originalHostHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Access Check (blacklist / whitelist)
|
||||||
|
ruleID := sep.AccessFilterUUID
|
||||||
|
if sep.AccessFilterUUID == "" {
|
||||||
|
//Use default rule
|
||||||
|
ruleID = "default"
|
||||||
|
}
|
||||||
|
accessRule, err := router.Option.AccessController.GetAccessRuleByID(ruleID)
|
||||||
|
if err == nil {
|
||||||
|
isBlocked, _ := accessRequestBlocked(accessRule, router.Option.WebDirectory, w, r)
|
||||||
|
if isBlocked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Validate basic auth
|
||||||
|
if sep.RequireBasicAuth {
|
||||||
|
err := handleBasicAuth(w, r, sep)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: sep.Domain,
|
ProxyDomain: sep.Domain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: sep.RequireTLS,
|
UseTLS: sep.RequireTLS,
|
||||||
PathPrefix: "",
|
PathPrefix: "",
|
||||||
|
Version: sep.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,16 +48,13 @@ func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add a user defined header to the list, duplicates will be automatically removed
|
// Add a user defined header to the list, duplicates will be automatically removed
|
||||||
func (ep *ProxyEndpoint) AddUserDefinedHeader(key string, value string) error {
|
func (ep *ProxyEndpoint) AddUserDefinedHeader(newHeaderRule *UserDefinedHeader) error {
|
||||||
if ep.UserDefinedHeaderExists(key) {
|
if ep.UserDefinedHeaderExists(newHeaderRule.Key) {
|
||||||
ep.RemoveUserDefinedHeader(key)
|
ep.RemoveUserDefinedHeader(newHeaderRule.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, &UserDefinedHeader{
|
newHeaderRule.Key = cases.Title(language.Und, cases.NoLower).String(newHeaderRule.Key)
|
||||||
Key: cases.Title(language.Und, cases.NoLower).String(key), //e.g. x-proxy-by -> X-Proxy-By
|
ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, newHeaderRule)
|
||||||
Value: value,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +66,8 @@ func (ep *ProxyEndpoint) AddUserDefinedHeader(key string, value string) error {
|
|||||||
func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI string) *VirtualDirectoryEndpoint {
|
func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI string) *VirtualDirectoryEndpoint {
|
||||||
for _, vdir := range ep.VirtualDirectories {
|
for _, vdir := range ep.VirtualDirectories {
|
||||||
if strings.HasPrefix(requestURI, vdir.MatchingPath) {
|
if strings.HasPrefix(requestURI, vdir.MatchingPath) {
|
||||||
return vdir
|
thisVdir := vdir
|
||||||
|
return thisVdir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -80,7 +77,8 @@ func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI str
|
|||||||
func (ep *ProxyEndpoint) GetVirtualDirectoryRuleByMatchingPath(matchingPath string) *VirtualDirectoryEndpoint {
|
func (ep *ProxyEndpoint) GetVirtualDirectoryRuleByMatchingPath(matchingPath string) *VirtualDirectoryEndpoint {
|
||||||
for _, vdir := range ep.VirtualDirectories {
|
for _, vdir := range ep.VirtualDirectories {
|
||||||
if vdir.MatchingPath == matchingPath {
|
if vdir.MatchingPath == matchingPath {
|
||||||
return vdir
|
thisVdir := vdir
|
||||||
|
return thisVdir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
193
src/mod/dynamicproxy/permissionpolicy/permissionpolicy.go
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package permissionpolicy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Permisson Policy
|
||||||
|
|
||||||
|
This is a permission policy header modifier that changes
|
||||||
|
the request permission related policy fields
|
||||||
|
|
||||||
|
author: tobychui
|
||||||
|
*/
|
||||||
|
|
||||||
|
type PermissionsPolicy struct {
|
||||||
|
Accelerometer []string `json:"accelerometer"`
|
||||||
|
AmbientLightSensor []string `json:"ambient_light_sensor"`
|
||||||
|
Autoplay []string `json:"autoplay"`
|
||||||
|
Battery []string `json:"battery"`
|
||||||
|
Camera []string `json:"camera"`
|
||||||
|
CrossOriginIsolated []string `json:"cross_origin_isolated"`
|
||||||
|
DisplayCapture []string `json:"display_capture"`
|
||||||
|
DocumentDomain []string `json:"document_domain"`
|
||||||
|
EncryptedMedia []string `json:"encrypted_media"`
|
||||||
|
ExecutionWhileNotRendered []string `json:"execution_while_not_rendered"`
|
||||||
|
ExecutionWhileOutOfView []string `json:"execution_while_out_of_viewport"`
|
||||||
|
Fullscreen []string `json:"fullscreen"`
|
||||||
|
Geolocation []string `json:"geolocation"`
|
||||||
|
Gyroscope []string `json:"gyroscope"`
|
||||||
|
KeyboardMap []string `json:"keyboard_map"`
|
||||||
|
Magnetometer []string `json:"magnetometer"`
|
||||||
|
Microphone []string `json:"microphone"`
|
||||||
|
Midi []string `json:"midi"`
|
||||||
|
NavigationOverride []string `json:"navigation_override"`
|
||||||
|
Payment []string `json:"payment"`
|
||||||
|
PictureInPicture []string `json:"picture_in_picture"`
|
||||||
|
PublicKeyCredentialsGet []string `json:"publickey_credentials_get"`
|
||||||
|
ScreenWakeLock []string `json:"screen_wake_lock"`
|
||||||
|
SyncXHR []string `json:"sync_xhr"`
|
||||||
|
USB []string `json:"usb"`
|
||||||
|
WebShare []string `json:"web_share"`
|
||||||
|
XRSpatialTracking []string `json:"xr_spatial_tracking"`
|
||||||
|
ClipboardRead []string `json:"clipboard_read"`
|
||||||
|
ClipboardWrite []string `json:"clipboard_write"`
|
||||||
|
Gamepad []string `json:"gamepad"`
|
||||||
|
SpeakerSelection []string `json:"speaker_selection"`
|
||||||
|
ConversionMeasurement []string `json:"conversion_measurement"`
|
||||||
|
FocusWithoutUserActivation []string `json:"focus_without_user_activation"`
|
||||||
|
HID []string `json:"hid"`
|
||||||
|
IdleDetection []string `json:"idle_detection"`
|
||||||
|
InterestCohort []string `json:"interest_cohort"`
|
||||||
|
Serial []string `json:"serial"`
|
||||||
|
SyncScript []string `json:"sync_script"`
|
||||||
|
TrustTokenRedemption []string `json:"trust_token_redemption"`
|
||||||
|
Unload []string `json:"unload"`
|
||||||
|
WindowPlacement []string `json:"window_placement"`
|
||||||
|
VerticalScroll []string `json:"vertical_scroll"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaultPermissionPolicy returns a PermissionsPolicy struct with all policies set to *
|
||||||
|
func GetDefaultPermissionPolicy() *PermissionsPolicy {
|
||||||
|
return &PermissionsPolicy{
|
||||||
|
Accelerometer: []string{"*"},
|
||||||
|
AmbientLightSensor: []string{"*"},
|
||||||
|
Autoplay: []string{"*"},
|
||||||
|
Battery: []string{"*"},
|
||||||
|
Camera: []string{"*"},
|
||||||
|
CrossOriginIsolated: []string{"*"},
|
||||||
|
DisplayCapture: []string{"*"},
|
||||||
|
DocumentDomain: []string{"*"},
|
||||||
|
EncryptedMedia: []string{"*"},
|
||||||
|
ExecutionWhileNotRendered: []string{"*"},
|
||||||
|
ExecutionWhileOutOfView: []string{"*"},
|
||||||
|
Fullscreen: []string{"*"},
|
||||||
|
Geolocation: []string{"*"},
|
||||||
|
Gyroscope: []string{"*"},
|
||||||
|
KeyboardMap: []string{"*"},
|
||||||
|
Magnetometer: []string{"*"},
|
||||||
|
Microphone: []string{"*"},
|
||||||
|
Midi: []string{"*"},
|
||||||
|
NavigationOverride: []string{"*"},
|
||||||
|
Payment: []string{"*"},
|
||||||
|
PictureInPicture: []string{"*"},
|
||||||
|
PublicKeyCredentialsGet: []string{"*"},
|
||||||
|
ScreenWakeLock: []string{"*"},
|
||||||
|
SyncXHR: []string{"*"},
|
||||||
|
USB: []string{"*"},
|
||||||
|
WebShare: []string{"*"},
|
||||||
|
XRSpatialTracking: []string{"*"},
|
||||||
|
ClipboardRead: []string{"*"},
|
||||||
|
ClipboardWrite: []string{"*"},
|
||||||
|
Gamepad: []string{"*"},
|
||||||
|
SpeakerSelection: []string{"*"},
|
||||||
|
ConversionMeasurement: []string{"*"},
|
||||||
|
FocusWithoutUserActivation: []string{"*"},
|
||||||
|
HID: []string{"*"},
|
||||||
|
IdleDetection: []string{"*"},
|
||||||
|
InterestCohort: []string{"*"},
|
||||||
|
Serial: []string{"*"},
|
||||||
|
SyncScript: []string{"*"},
|
||||||
|
TrustTokenRedemption: []string{"*"},
|
||||||
|
Unload: []string{"*"},
|
||||||
|
WindowPlacement: []string{"*"},
|
||||||
|
VerticalScroll: []string{"*"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InjectPermissionPolicyHeader inject the permission policy into headers
|
||||||
|
func InjectPermissionPolicyHeader(w http.ResponseWriter, policy *PermissionsPolicy) {
|
||||||
|
//Keep the original Permission Policy if exists, or there are no policy given
|
||||||
|
if policy == nil || w.Header().Get("Permissions-Policy") != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
policyHeader := []string{}
|
||||||
|
|
||||||
|
// Helper function to add policy directives
|
||||||
|
addDirective := func(name string, sources []string) {
|
||||||
|
if len(sources) > 0 {
|
||||||
|
if sources[0] == "*" {
|
||||||
|
//Allow all
|
||||||
|
policyHeader = append(policyHeader, fmt.Sprintf("%s=%s", name, "*"))
|
||||||
|
} else {
|
||||||
|
//Other than "self" which do not need double quote, others domain need double quote in place
|
||||||
|
formatedSources := []string{}
|
||||||
|
for _, source := range sources {
|
||||||
|
if source == "self" {
|
||||||
|
formatedSources = append(formatedSources, "self")
|
||||||
|
} else {
|
||||||
|
formatedSources = append(formatedSources, "\""+source+"\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
policyHeader = append(policyHeader, fmt.Sprintf("%s=(%s)", name, strings.Join(formatedSources, " ")))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//There are no setting for this field. Assume no permission
|
||||||
|
policyHeader = append(policyHeader, fmt.Sprintf("%s=()", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add each policy directive to the header
|
||||||
|
addDirective("accelerometer", policy.Accelerometer)
|
||||||
|
addDirective("ambient-light-sensor", policy.AmbientLightSensor)
|
||||||
|
addDirective("autoplay", policy.Autoplay)
|
||||||
|
addDirective("battery", policy.Battery)
|
||||||
|
addDirective("camera", policy.Camera)
|
||||||
|
addDirective("cross-origin-isolated", policy.CrossOriginIsolated)
|
||||||
|
addDirective("display-capture", policy.DisplayCapture)
|
||||||
|
addDirective("document-domain", policy.DocumentDomain)
|
||||||
|
addDirective("encrypted-media", policy.EncryptedMedia)
|
||||||
|
addDirective("execution-while-not-rendered", policy.ExecutionWhileNotRendered)
|
||||||
|
addDirective("execution-while-out-of-viewport", policy.ExecutionWhileOutOfView)
|
||||||
|
addDirective("fullscreen", policy.Fullscreen)
|
||||||
|
addDirective("geolocation", policy.Geolocation)
|
||||||
|
addDirective("gyroscope", policy.Gyroscope)
|
||||||
|
addDirective("keyboard-map", policy.KeyboardMap)
|
||||||
|
addDirective("magnetometer", policy.Magnetometer)
|
||||||
|
addDirective("microphone", policy.Microphone)
|
||||||
|
addDirective("midi", policy.Midi)
|
||||||
|
addDirective("navigation-override", policy.NavigationOverride)
|
||||||
|
addDirective("payment", policy.Payment)
|
||||||
|
addDirective("picture-in-picture", policy.PictureInPicture)
|
||||||
|
addDirective("publickey-credentials-get", policy.PublicKeyCredentialsGet)
|
||||||
|
addDirective("screen-wake-lock", policy.ScreenWakeLock)
|
||||||
|
addDirective("sync-xhr", policy.SyncXHR)
|
||||||
|
addDirective("usb", policy.USB)
|
||||||
|
addDirective("web-share", policy.WebShare)
|
||||||
|
addDirective("xr-spatial-tracking", policy.XRSpatialTracking)
|
||||||
|
addDirective("clipboard-read", policy.ClipboardRead)
|
||||||
|
addDirective("clipboard-write", policy.ClipboardWrite)
|
||||||
|
addDirective("gamepad", policy.Gamepad)
|
||||||
|
addDirective("speaker-selection", policy.SpeakerSelection)
|
||||||
|
addDirective("conversion-measurement", policy.ConversionMeasurement)
|
||||||
|
addDirective("focus-without-user-activation", policy.FocusWithoutUserActivation)
|
||||||
|
addDirective("hid", policy.HID)
|
||||||
|
addDirective("idle-detection", policy.IdleDetection)
|
||||||
|
addDirective("interest-cohort", policy.InterestCohort)
|
||||||
|
addDirective("serial", policy.Serial)
|
||||||
|
addDirective("sync-script", policy.SyncScript)
|
||||||
|
addDirective("trust-token-redemption", policy.TrustTokenRedemption)
|
||||||
|
addDirective("unload", policy.Unload)
|
||||||
|
addDirective("window-placement", policy.WindowPlacement)
|
||||||
|
addDirective("vertical-scroll", policy.VerticalScroll)
|
||||||
|
|
||||||
|
// Join the directives and set the header
|
||||||
|
policyHeaderValue := strings.Join(policyHeader, ", ")
|
||||||
|
|
||||||
|
//Inject the new policy into the header
|
||||||
|
w.Header().Set("Permissions-Policy", policyHeaderValue)
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package permissionpolicy_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInjectPermissionPolicyHeader(t *testing.T) {
|
||||||
|
//Prepare the data for permission policy
|
||||||
|
testPermissionPolicy := permissionpolicy.GetDefaultPermissionPolicy()
|
||||||
|
testPermissionPolicy.Geolocation = []string{"self"}
|
||||||
|
testPermissionPolicy.Microphone = []string{"self", "https://example.com"}
|
||||||
|
testPermissionPolicy.Camera = []string{"*"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
existingHeader string
|
||||||
|
policy *permissionpolicy.PermissionsPolicy
|
||||||
|
expectedHeader string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Default policy with a few limitations",
|
||||||
|
existingHeader: "",
|
||||||
|
policy: testPermissionPolicy,
|
||||||
|
expectedHeader: `accelerometer=*, ambient-light-sensor=*, autoplay=*, battery=*, camera=*, cross-origin-isolated=*, display-capture=*, document-domain=*, encrypted-media=*, execution-while-not-rendered=*, execution-while-out-of-viewport=*, fullscreen=*, geolocation=(self), gyroscope=*, keyboard-map=*, magnetometer=*, microphone=(self "https://example.com"), midi=*, navigation-override=*, payment=*, picture-in-picture=*, publickey-credentials-get=*, screen-wake-lock=*, sync-xhr=*, usb=*, web-share=*, xr-spatial-tracking=*, clipboard-read=*, clipboard-write=*, gamepad=*, speaker-selection=*, conversion-measurement=*, focus-without-user-activation=*, hid=*, idle-detection=*, interest-cohort=*, serial=*, sync-script=*, trust-token-redemption=*, unload=*, window-placement=*, vertical-scroll=*`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
if tt.existingHeader != "" {
|
||||||
|
rr.Header().Set("Permissions-Policy", tt.existingHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionpolicy.InjectPermissionPolicyHeader(rr, tt.policy)
|
||||||
|
|
||||||
|
gotHeader := rr.Header().Get("Permissions-Policy")
|
||||||
|
if !strings.Contains(gotHeader, tt.expectedHeader) {
|
||||||
|
t.Errorf("got header %s, want %s", gotHeader, tt.expectedHeader)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
"imuslab.com/zoraxy/mod/statistic"
|
"imuslab.com/zoraxy/mod/statistic"
|
||||||
"imuslab.com/zoraxy/mod/websocketproxy"
|
"imuslab.com/zoraxy/mod/websocketproxy"
|
||||||
)
|
)
|
||||||
@ -34,23 +34,45 @@ func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoi
|
|||||||
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
||||||
ep, ok := router.ProxyEndpoints.Load(hostname)
|
ep, ok := router.ProxyEndpoints.Load(hostname)
|
||||||
if ok {
|
if ok {
|
||||||
|
//Exact hit
|
||||||
targetSubdomainEndpoint = ep.(*ProxyEndpoint)
|
targetSubdomainEndpoint = ep.(*ProxyEndpoint)
|
||||||
|
if !targetSubdomainEndpoint.Disabled {
|
||||||
|
return targetSubdomainEndpoint
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//No hit. Try with wildcard
|
//No hit. Try with wildcard and alias
|
||||||
matchProxyEndpoints := []*ProxyEndpoint{}
|
matchProxyEndpoints := []*ProxyEndpoint{}
|
||||||
router.ProxyEndpoints.Range(func(k, v interface{}) bool {
|
router.ProxyEndpoints.Range(func(k, v interface{}) bool {
|
||||||
ep := v.(*ProxyEndpoint)
|
ep := v.(*ProxyEndpoint)
|
||||||
match, err := filepath.Match(ep.RootOrMatchingDomain, hostname)
|
match, err := filepath.Match(ep.RootOrMatchingDomain, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Continue
|
//Bad pattern. Skip this rule
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if match {
|
if match {
|
||||||
//targetSubdomainEndpoint = ep
|
//Wildcard matches. Skip checking alias
|
||||||
matchProxyEndpoints = append(matchProxyEndpoints, ep)
|
matchProxyEndpoints = append(matchProxyEndpoints, ep)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Wildcard not match. Check for alias
|
||||||
|
if ep.MatchingDomainAlias != nil && len(ep.MatchingDomainAlias) > 0 {
|
||||||
|
for _, aliasDomain := range ep.MatchingDomainAlias {
|
||||||
|
match, err := filepath.Match(aliasDomain, hostname)
|
||||||
|
if err != nil {
|
||||||
|
//Bad pattern. Skip this alias
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
//This alias match
|
||||||
|
matchProxyEndpoints = append(matchProxyEndpoints, ep)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -89,13 +111,6 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||||
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
|
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
|
||||||
|
|
||||||
//Inject custom headers
|
|
||||||
if len(target.UserDefinedHeaders) > 0 {
|
|
||||||
for _, customHeader := range target.UserDefinedHeaders {
|
|
||||||
r.Header.Set(customHeader.Key, customHeader.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestURL := r.URL.String()
|
requestURL := r.URL.String()
|
||||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||||
@ -114,7 +129,10 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
||||||
}
|
}
|
||||||
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
|
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
|
||||||
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
|
SkipTLSValidation: target.SkipCertValidations,
|
||||||
|
SkipOriginCheck: target.SkipWebSocketOriginCheck,
|
||||||
|
})
|
||||||
wspHandler.ServeHTTP(w, r)
|
wspHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -127,12 +145,18 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
r.URL, _ = url.Parse(originalHostHeader)
|
r.URL, _ = url.Parse(originalHostHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Build downstream and upstream header rules
|
||||||
|
upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()
|
||||||
|
|
||||||
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: target.Domain,
|
ProxyDomain: target.Domain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: target.RequireTLS,
|
UseTLS: target.RequireTLS,
|
||||||
NoCache: h.Parent.Option.NoCache,
|
NoCache: h.Parent.Option.NoCache,
|
||||||
PathPrefix: "",
|
PathPrefix: "",
|
||||||
|
UpstreamHeaders: upstreamHeaders,
|
||||||
|
DownstreamHeaders: downstreamHeaders,
|
||||||
|
Version: target.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
|
|
||||||
var dnsError *net.DNSError
|
var dnsError *net.DNSError
|
||||||
@ -159,13 +183,6 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||||
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
|
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
|
||||||
|
|
||||||
//Inject custom headers
|
|
||||||
if len(target.parent.UserDefinedHeaders) > 0 {
|
|
||||||
for _, customHeader := range target.parent.UserDefinedHeaders {
|
|
||||||
r.Header.Set(customHeader.Key, customHeader.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||||
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||||
@ -178,7 +195,10 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
||||||
}
|
}
|
||||||
h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||||
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
|
SkipTLSValidation: target.SkipCertValidations,
|
||||||
|
SkipOriginCheck: target.parent.SkipWebSocketOriginCheck,
|
||||||
|
})
|
||||||
wspHandler.ServeHTTP(w, r)
|
wspHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -191,11 +211,17 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
r.URL, _ = url.Parse(originalHostHeader)
|
r.URL, _ = url.Parse(originalHostHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Build downstream and upstream header rules
|
||||||
|
upstreamHeaders, downstreamHeaders := target.parent.SplitInboundOutboundHeaders()
|
||||||
|
|
||||||
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: target.Domain,
|
ProxyDomain: target.Domain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: target.RequireTLS,
|
UseTLS: target.RequireTLS,
|
||||||
PathPrefix: target.MatchingPath,
|
PathPrefix: target.MatchingPath,
|
||||||
|
UpstreamHeaders: upstreamHeaders,
|
||||||
|
DownstreamHeaders: downstreamHeaders,
|
||||||
|
Version: target.parent.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
|
|
||||||
var dnsError *net.DNSError
|
var dnsError *net.DNSError
|
||||||
@ -218,7 +244,7 @@ func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, fo
|
|||||||
if h.Parent.Option.StatisticCollector != nil {
|
if h.Parent.Option.StatisticCollector != nil {
|
||||||
go func() {
|
go func() {
|
||||||
requestInfo := statistic.RequestInfo{
|
requestInfo := statistic.RequestInfo{
|
||||||
IpAddr: geodb.GetRequesterIP(r),
|
IpAddr: netutils.GetRequesterIP(r),
|
||||||
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||||
Succ: succ,
|
Succ: succ,
|
||||||
StatusCode: statusCode,
|
StatusCode: statusCode,
|
||||||
|
@ -2,19 +2,25 @@ package redirection
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RuleTable struct {
|
type RuleTable struct {
|
||||||
|
AllowRegex bool //Allow regular expression to be used in rule matching. Require up to O(n^m) time complexity
|
||||||
|
Logger *logger.Logger
|
||||||
configPath string //The location where the redirection rules is stored
|
configPath string //The location where the redirection rules is stored
|
||||||
rules sync.Map //Store the redirection rules for this reverse proxy instance
|
rules sync.Map //Store the redirection rules for this reverse proxy instance
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedirectRules struct {
|
type RedirectRules struct {
|
||||||
@ -24,10 +30,11 @@ type RedirectRules struct {
|
|||||||
StatusCode int //Status Code for redirection
|
StatusCode int //Status Code for redirection
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRuleTable(configPath string) (*RuleTable, error) {
|
func NewRuleTable(configPath string, allowRegex bool) (*RuleTable, error) {
|
||||||
thisRuleTable := RuleTable{
|
thisRuleTable := RuleTable{
|
||||||
rules: sync.Map{},
|
rules: sync.Map{},
|
||||||
configPath: configPath,
|
configPath: configPath,
|
||||||
|
AllowRegex: allowRegex,
|
||||||
}
|
}
|
||||||
//Load all the rules from the config path
|
//Load all the rules from the config path
|
||||||
if !utils.FileExists(configPath) {
|
if !utils.FileExists(configPath) {
|
||||||
@ -77,7 +84,7 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
||||||
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
|
filename := utils.ReplaceSpecialCharacters(redirectURL) + ".json"
|
||||||
|
|
||||||
// Create the full file path by joining the t.configPath with the filename
|
// Create the full file path by joining the t.configPath with the filename
|
||||||
filepath := path.Join(t.configPath, filename)
|
filepath := path.Join(t.configPath, filename)
|
||||||
@ -105,11 +112,12 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
|
|||||||
|
|
||||||
func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
|
func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
|
||||||
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
||||||
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
|
filename := utils.ReplaceSpecialCharacters(redirectURL) + ".json"
|
||||||
|
|
||||||
// Create the full file path by joining the t.configPath with the filename
|
// Create the full file path by joining the t.configPath with the filename
|
||||||
filepath := path.Join(t.configPath, filename)
|
filepath := path.Join(t.configPath, filename)
|
||||||
|
|
||||||
|
fmt.Println(redirectURL, filename, filepath)
|
||||||
// Check if the file exists
|
// Check if the file exists
|
||||||
if _, err := os.Stat(filepath); os.IsNotExist(err) {
|
if _, err := os.Stat(filepath); os.IsNotExist(err) {
|
||||||
return nil // File doesn't exist, nothing to delete
|
return nil // File doesn't exist, nothing to delete
|
||||||
@ -145,18 +153,47 @@ func (t *RuleTable) MatchRedirectRule(requestedURL string) *RedirectRules {
|
|||||||
// Iterate through all the keys in the rules map
|
// Iterate through all the keys in the rules map
|
||||||
var targetRedirectionRule *RedirectRules = nil
|
var targetRedirectionRule *RedirectRules = nil
|
||||||
var maxMatch int = 0
|
var maxMatch int = 0
|
||||||
|
|
||||||
t.rules.Range(func(key interface{}, value interface{}) bool {
|
t.rules.Range(func(key interface{}, value interface{}) bool {
|
||||||
// Check if the requested URL starts with the key as a prefix
|
// Check if the requested URL starts with the key as a prefix
|
||||||
if strings.HasPrefix(requestedURL, key.(string)) {
|
if t.AllowRegex {
|
||||||
// This request URL matched the domain
|
//Regexp matching rule
|
||||||
if len(key.(string)) > maxMatch {
|
matched, err := regexp.MatchString(key.(string), requestedURL)
|
||||||
|
if err != nil {
|
||||||
|
//Something wrong with the regex?
|
||||||
|
t.log("Unable to match regex", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
maxMatch = len(key.(string))
|
maxMatch = len(key.(string))
|
||||||
targetRedirectionRule = value.(*RedirectRules)
|
targetRedirectionRule = value.(*RedirectRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//Default: prefix matching redirect
|
||||||
|
if strings.HasPrefix(requestedURL, key.(string)) {
|
||||||
|
// This request URL matched the domain
|
||||||
|
if len(key.(string)) > maxMatch {
|
||||||
|
maxMatch = len(key.(string))
|
||||||
|
targetRedirectionRule = value.(*RedirectRules)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
return targetRedirectionRule
|
return targetRedirectionRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the message to log file, use STDOUT if logger not set
|
||||||
|
func (t *RuleTable) log(message string, err error) {
|
||||||
|
if t.Logger == nil {
|
||||||
|
if err == nil {
|
||||||
|
log.Println("[Redirect] " + message)
|
||||||
|
} else {
|
||||||
|
log.Println("[Redirect] " + message + ": " + err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Logger.PrintAndLog("Redirect", message, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,6 +19,9 @@ import (
|
|||||||
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
||||||
//Filter the tailing slash if any
|
//Filter the tailing slash if any
|
||||||
domain := endpoint.Domain
|
domain := endpoint.Domain
|
||||||
|
if len(domain) == 0 {
|
||||||
|
return nil, errors.New("invalid endpoint config")
|
||||||
|
}
|
||||||
if domain[len(domain)-1:] == "/" {
|
if domain[len(domain)-1:] == "/" {
|
||||||
domain = domain[:len(domain)-1]
|
domain = domain[:len(domain)-1]
|
||||||
}
|
}
|
||||||
@ -42,13 +45,19 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Create the proxy routing handler
|
//Create the proxy routing handler
|
||||||
proxy := dpcore.NewDynamicProxyCore(path, "", endpoint.SkipCertValidations)
|
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
|
||||||
|
IgnoreTLSVerification: endpoint.SkipCertValidations,
|
||||||
|
})
|
||||||
endpoint.proxy = proxy
|
endpoint.proxy = proxy
|
||||||
endpoint.parent = router
|
endpoint.parent = router
|
||||||
|
|
||||||
//Prepare proxy routing hjandler for each of the virtual directories
|
//Prepare proxy routing hjandler for each of the virtual directories
|
||||||
for _, vdir := range endpoint.VirtualDirectories {
|
for _, vdir := range endpoint.VirtualDirectories {
|
||||||
domain := vdir.Domain
|
domain := vdir.Domain
|
||||||
|
if len(domain) == 0 {
|
||||||
|
//invalid vdir
|
||||||
|
continue
|
||||||
|
}
|
||||||
if domain[len(domain)-1:] == "/" {
|
if domain[len(domain)-1:] == "/" {
|
||||||
domain = domain[:len(domain)-1]
|
domain = domain[:len(domain)-1]
|
||||||
}
|
}
|
||||||
@ -69,7 +78,9 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := dpcore.NewDynamicProxyCore(path, vdir.MatchingPath, vdir.SkipCertValidations)
|
proxy := dpcore.NewDynamicProxyCore(path, vdir.MatchingPath, &dpcore.DpcoreOptions{
|
||||||
|
IgnoreTLSVerification: vdir.SkipCertValidations,
|
||||||
|
})
|
||||||
vdir.proxy = proxy
|
vdir.proxy = proxy
|
||||||
vdir.parent = endpoint
|
vdir.parent = endpoint
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
<h3 style="margin-top: 1em;">403 - Forbidden</h3>
|
<h3 style="margin-top: 1em;">403 - Forbidden</h3>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<p>You do not have permission to view this directory or page. <br>
|
<p>You do not have permission to view this directory or page. <br>
|
||||||
This might cause by the region limit setting of this site.</p>
|
This might be caused by the region limit setting of this site.</p>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div style="text-align: left;">
|
<div style="text-align: left;">
|
||||||
<small>Request time: <span id="reqtime"></span></small><br>
|
<small>Request time: <span id="reqtime"></span></small><br>
|
||||||
|
154
src/mod/dynamicproxy/templates/hosterror.html
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="theme-color" content="#4b75ff">
|
||||||
|
<link rel="icon" type="image/png" href="img/small_icon.png"/>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.css">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
|
||||||
|
<title>404 - Host Not Found</title>
|
||||||
|
<style>
|
||||||
|
h1, h2, h3, h4, h5, p, a, span, .ui.list .item{
|
||||||
|
font-family: 'Noto Sans TC', sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
color: rgb(88, 88, 88)
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram{
|
||||||
|
background-color: #ebebeb;
|
||||||
|
padding-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagramHeader{
|
||||||
|
margin-top: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:512px) {
|
||||||
|
.widescreenOnly{
|
||||||
|
display: none !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.four.wide.column:not(.widescreenOnly){
|
||||||
|
width: 50% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.grid{
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<br><br>
|
||||||
|
<div class="ui container">
|
||||||
|
<h1 style="font-size: 4rem;">Error 404</h1>
|
||||||
|
<p style="font-size: 2rem; margin-bottom: 0.4em;">Target Host Not Found</p>
|
||||||
|
<small id="timestamp"></small>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
</div>
|
||||||
|
<div class="diagram">
|
||||||
|
<div class="ui text container">
|
||||||
|
<div class="ui grid">
|
||||||
|
<div class="four wide column widescreenOnly" align="center">
|
||||||
|
<svg version="1.1" id="client_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||||
|
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
|
||||||
|
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
|
||||||
|
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
|
||||||
|
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
|
||||||
|
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
|
||||||
|
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
|
||||||
|
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
|
||||||
|
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
|
||||||
|
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
|
||||||
|
82.989,143.204 "/>
|
||||||
|
</svg>
|
||||||
|
<small>You</small>
|
||||||
|
<h2 class="diagramHeader">Browser</h2>
|
||||||
|
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||||
|
</div>
|
||||||
|
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||||
|
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||||
|
</div>
|
||||||
|
<div class="four wide column widescreenOnly" align="center">
|
||||||
|
<svg version="1.1" id="cloud_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||||
|
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
|
||||||
|
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
|
||||||
|
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
|
||||||
|
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
|
||||||
|
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
|
||||||
|
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
|
||||||
|
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
|
||||||
|
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
|
||||||
|
83.971,148.123 "/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<small>Gateway Node</small>
|
||||||
|
<h2 class="diagramHeader">Reverse Proxy</h2>
|
||||||
|
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||||
|
</div>
|
||||||
|
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||||
|
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||||
|
</div>
|
||||||
|
<div class="four wide column" align="center">
|
||||||
|
<svg version="1.1" id="host_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||||
|
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
|
||||||
|
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
|
||||||
|
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
|
||||||
|
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
|
||||||
|
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
|
||||||
|
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
|
||||||
|
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
|
||||||
|
</svg>
|
||||||
|
<small id="host"></small>
|
||||||
|
<h2 class="diagramHeader">Host</h2>
|
||||||
|
<p style="font-weight: 500; color: #bd2426;">Not Found</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui stackable grid">
|
||||||
|
<div class="eight wide column">
|
||||||
|
<h1>What happend?</h1>
|
||||||
|
<p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p>
|
||||||
|
</div>
|
||||||
|
<div class="eight wide column">
|
||||||
|
<h1>What can I do?</h1>
|
||||||
|
<h5 style="font-weight: 500;">If you are a visitor of this website: </h5>
|
||||||
|
<p>Please try again in a few minutes</p>
|
||||||
|
<h5 style="font-weight: 500;">If you are the owner of this website:</h5>
|
||||||
|
<div class="ui bulleted list">
|
||||||
|
<div class="item">Check if the proxy rules that match this hostname exists</div>
|
||||||
|
<div class="item">Visit the Reverse Proxy management interface to correct any setting errors</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui container" style="color: grey; font-size: 90%">
|
||||||
|
<p>Powered by Zoraxy</p>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$("#timestamp").text(new Date());
|
||||||
|
$("#host").text(location.href);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
@ -34,7 +35,8 @@ type RouterOption struct {
|
|||||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||||
TlsManager *tlscert.Manager
|
TlsManager *tlscert.Manager
|
||||||
RedirectRuleTable *redirection.RuleTable
|
RedirectRuleTable *redirection.RuleTable
|
||||||
GeodbStore *geodb.Store //GeoIP blacklist and whitelist
|
GeodbStore *geodb.Store //GeoIP resolver
|
||||||
|
AccessController *access.Controller //Blacklist / whitelist controller
|
||||||
StatisticCollector *statistic.Collector
|
StatisticCollector *statistic.Collector
|
||||||
WebDirectory string //The static web server directory containing the templates folder
|
WebDirectory string //The static web server directory containing the templates folder
|
||||||
}
|
}
|
||||||
@ -70,10 +72,20 @@ type BasicAuthExceptionRule struct {
|
|||||||
PathPrefix string
|
PathPrefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Header injection direction type
|
||||||
|
type HeaderDirection int
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderDirection_ZoraxyToUpstream HeaderDirection = 0 //Inject (or remove) header to request out-going from Zoraxy to backend server
|
||||||
|
HeaderDirection_ZoraxyToDownstream HeaderDirection = 1 //Inject (or remove) header to request out-going from Zoraxy to client (e.g. browser)
|
||||||
|
)
|
||||||
|
|
||||||
// User defined headers to add into a proxy endpoint
|
// User defined headers to add into a proxy endpoint
|
||||||
type UserDefinedHeader struct {
|
type UserDefinedHeader struct {
|
||||||
Key string
|
Direction HeaderDirection
|
||||||
Value string
|
Key string
|
||||||
|
Value string
|
||||||
|
IsRemove bool //Instead of set, remove this key instead
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||||
@ -90,14 +102,16 @@ type VirtualDirectoryEndpoint struct {
|
|||||||
|
|
||||||
// A proxy endpoint record, a general interface for handling inbound routing
|
// A proxy endpoint record, a general interface for handling inbound routing
|
||||||
type ProxyEndpoint struct {
|
type ProxyEndpoint struct {
|
||||||
ProxyType int //The type of this proxy, see const def
|
ProxyType int //The type of this proxy, see const def
|
||||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||||
Domain string //Domain or IP to proxy to
|
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||||
|
Domain string //Domain or IP to proxy to
|
||||||
|
|
||||||
//TLS/SSL Related
|
//TLS/SSL Related
|
||||||
RequireTLS bool //Target domain require TLS
|
RequireTLS bool //Target domain require TLS
|
||||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||||
SkipCertValidations bool //Set to true to accept self signed certs
|
SkipCertValidations bool //Set to true to accept self signed certs
|
||||||
|
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||||
|
|
||||||
//Virtual Directories
|
//Virtual Directories
|
||||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||||
@ -110,13 +124,17 @@ type ProxyEndpoint struct {
|
|||||||
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
|
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
|
||||||
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||||
|
|
||||||
//Fallback routing logic
|
//Access Control
|
||||||
|
AccessFilterUUID string //Access filter ID
|
||||||
|
|
||||||
|
Disabled bool //If the rule is disabled
|
||||||
|
|
||||||
|
//Fallback routing logic (Special Rule Sets Only)
|
||||||
DefaultSiteOption int //Fallback routing logic options
|
DefaultSiteOption int //Fallback routing logic options
|
||||||
DefaultSiteValue string //Fallback routing target, optional
|
DefaultSiteValue string //Fallback routing target, optional
|
||||||
|
|
||||||
Disabled bool //If the rule is disabled
|
|
||||||
//Internal Logic Elements
|
//Internal Logic Elements
|
||||||
parent *Router
|
parent *Router `json:"-"`
|
||||||
proxy *dpcore.ReverseProxy `json:"-"`
|
proxy *dpcore.ReverseProxy `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,4 +159,6 @@ Web Templates
|
|||||||
var (
|
var (
|
||||||
//go:embed templates/forbidden.html
|
//go:embed templates/forbidden.html
|
||||||
page_forbidden []byte
|
page_forbidden []byte
|
||||||
|
//go:embed templates/hosterror.html
|
||||||
|
page_hosterror []byte
|
||||||
)
|
)
|
||||||
|
@ -13,18 +13,16 @@ import (
|
|||||||
|
|
||||||
type Sender struct {
|
type Sender struct {
|
||||||
Hostname string //E.g. mail.gandi.net
|
Hostname string //E.g. mail.gandi.net
|
||||||
Domain string //E.g. arozos.com
|
|
||||||
Port int //E.g. 587
|
Port int //E.g. 587
|
||||||
Username string //Username of the email account
|
Username string //Username of the email account
|
||||||
Password string //Password of the email account
|
Password string //Password of the email account
|
||||||
SenderAddr string //e.g. admin@arozos.com
|
SenderAddr string //e.g. admin@arozos.com
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create a new email sender object
|
// Create a new email sender object
|
||||||
func NewEmailSender(hostname string, domain string, port int, username string, password string, senderAddr string) *Sender {
|
func NewEmailSender(hostname string, port int, username string, password string, senderAddr string) *Sender {
|
||||||
return &Sender{
|
return &Sender{
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Domain: domain,
|
|
||||||
Port: port,
|
Port: port,
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
@ -33,24 +31,33 @@ func NewEmailSender(hostname string, domain string, port int, username string, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Send a email to a reciving addr
|
Send a email to a reciving addr
|
||||||
Example Usage:
|
Example Usage:
|
||||||
SendEmail(
|
SendEmail(
|
||||||
test@example.com,
|
|
||||||
"Free donuts",
|
test@example.com,
|
||||||
"Come get your free donuts on this Sunday!"
|
"Free donuts",
|
||||||
)
|
"Come get your free donuts on this Sunday!"
|
||||||
|
|
||||||
|
)
|
||||||
*/
|
*/
|
||||||
func (s *Sender) SendEmail(to string, subject string, content string) error {
|
func (s *Sender) SendEmail(to string, subject string, content string) error {
|
||||||
//Parse the email content
|
// Parse the email content
|
||||||
msg := []byte("To: " + to + "\n" +
|
msg := []byte("To: " + to + "\n" +
|
||||||
"From: Zoraxy <" + s.SenderAddr + ">\n" +
|
"From: Zoraxy <" + s.SenderAddr + ">\n" +
|
||||||
"Subject: " + subject + "\n" +
|
"Subject: " + subject + "\n" +
|
||||||
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" +
|
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" +
|
||||||
content + "\n\n")
|
content + "\n\n")
|
||||||
|
|
||||||
//Login to the SMTP server
|
// Initialize the auth variable
|
||||||
auth := smtp.PlainAuth("", s.Username+"@"+s.Domain, s.Password, s.Hostname)
|
var auth smtp.Auth
|
||||||
|
if s.Password != "" {
|
||||||
|
// Login to the SMTP server
|
||||||
|
// Username can be username (e.g. admin) or email (e.g. admin@example.com), depending on SMTP service provider
|
||||||
|
auth = smtp.PlainAuth("", s.Username, s.Password, s.Hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the email
|
||||||
err := smtp.SendMail(s.Hostname+":"+strconv.Itoa(s.Port), auth, s.SenderAddr, []string{to}, msg)
|
err := smtp.SendMail(s.Hostname+":"+strconv.Itoa(s.Port), auth, s.SenderAddr, []string{to}, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
25
src/mod/forwardproxy/cproxy/LICENSE.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Smarty
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
NOTE: Various optional and subordinate components carry their own licensing
|
||||||
|
requirements and restrictions. Use of those components is subject to the terms
|
||||||
|
and conditions outlined the respective license of each component.
|
109
src/mod/forwardproxy/cproxy/config.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package cproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(options ...option) http.Handler {
|
||||||
|
var this configuration
|
||||||
|
Options.apply(options...)(&this)
|
||||||
|
return newHandler(this.Filter, this.ClientConnector, this.ServerConnector, this.Monitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
var Options singleton
|
||||||
|
|
||||||
|
type singleton struct{}
|
||||||
|
type option func(*configuration)
|
||||||
|
|
||||||
|
type configuration struct {
|
||||||
|
DialTimeout time.Duration
|
||||||
|
Filter Filter
|
||||||
|
DialAddress string
|
||||||
|
Dialer Dialer
|
||||||
|
LogConnections bool
|
||||||
|
ProxyProtocol bool
|
||||||
|
Initializer initializer
|
||||||
|
ClientConnector clientConnector
|
||||||
|
ServerConnector serverConnector
|
||||||
|
Monitor monitor
|
||||||
|
Logger logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (singleton) DialTimeout(value time.Duration) option {
|
||||||
|
return func(this *configuration) { this.DialTimeout = value }
|
||||||
|
}
|
||||||
|
func (singleton) Filter(value Filter) option {
|
||||||
|
return func(this *configuration) { this.Filter = value }
|
||||||
|
}
|
||||||
|
func (singleton) ClientConnector(value clientConnector) option {
|
||||||
|
return func(this *configuration) { this.ClientConnector = value }
|
||||||
|
}
|
||||||
|
func (singleton) DialAddress(value string) option {
|
||||||
|
return func(this *configuration) { this.DialAddress = value }
|
||||||
|
}
|
||||||
|
func (singleton) Dialer(value Dialer) option {
|
||||||
|
return func(this *configuration) { this.Dialer = value }
|
||||||
|
}
|
||||||
|
func (singleton) LogConnections(value bool) option {
|
||||||
|
return func(this *configuration) { this.LogConnections = value }
|
||||||
|
}
|
||||||
|
func (singleton) ProxyProtocol(value bool) option {
|
||||||
|
return func(this *configuration) { this.ProxyProtocol = value }
|
||||||
|
}
|
||||||
|
func (singleton) Initializer(value initializer) option {
|
||||||
|
return func(this *configuration) { this.Initializer = value }
|
||||||
|
}
|
||||||
|
func (singleton) ServerConnector(value serverConnector) option {
|
||||||
|
return func(this *configuration) { this.ServerConnector = value }
|
||||||
|
}
|
||||||
|
func (singleton) Monitor(value monitor) option {
|
||||||
|
return func(this *configuration) { this.Monitor = value }
|
||||||
|
}
|
||||||
|
func (singleton) Logger(value logger) option {
|
||||||
|
return func(this *configuration) { this.Logger = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
func (singleton) apply(options ...option) option {
|
||||||
|
return func(this *configuration) {
|
||||||
|
for _, item := range Options.defaults(options...) {
|
||||||
|
item(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.Dialer == nil {
|
||||||
|
this.Dialer = newDialer(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Dialer = newRoutingDialer(this)
|
||||||
|
|
||||||
|
if this.ProxyProtocol {
|
||||||
|
this.Initializer = newProxyProtocolInitializer()
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.Initializer == nil {
|
||||||
|
this.Initializer = nop{}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Initializer = newLoggingInitializer(this)
|
||||||
|
|
||||||
|
if this.ServerConnector == nil {
|
||||||
|
this.ServerConnector = newServerConnector(this.Dialer, this.Initializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (singleton) defaults(options ...option) []option {
|
||||||
|
return append([]option{
|
||||||
|
Options.DialTimeout(time.Second * 10),
|
||||||
|
Options.Filter(newFilter()),
|
||||||
|
Options.ClientConnector(newClientConnector()),
|
||||||
|
Options.Initializer(nop{}),
|
||||||
|
Options.Monitor(nop{}),
|
||||||
|
Options.Logger(nop{}),
|
||||||
|
}, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type nop struct{}
|
||||||
|
|
||||||
|
func (nop) Measure(int) {}
|
||||||
|
func (nop) Printf(string, ...interface{}) {}
|
||||||
|
func (nop) Initialize(Socket, Socket) bool { return true }
|
19
src/mod/forwardproxy/cproxy/default_client_connector.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package cproxy
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type defaultClientConnector struct{}
|
||||||
|
|
||||||
|
func newClientConnector() *defaultClientConnector {
|
||||||
|
return &defaultClientConnector{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *defaultClientConnector) Connect(response http.ResponseWriter) Socket {
|
||||||
|
if hijacker, ok := response.(http.Hijacker); !ok {
|
||||||
|
return nil
|
||||||
|
} else if socket, _, _ := hijacker.Hijack(); socket == nil {
|
||||||
|
return nil // this 'else if' exists to avoid the pointer nil != interface nil issue
|
||||||
|
} else {
|
||||||
|
return socket
|
||||||
|
}
|
||||||
|
}
|
25
src/mod/forwardproxy/cproxy/default_dialer.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package cproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type defaultDialer struct {
|
||||||
|
timeout time.Duration
|
||||||
|
logger logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDialer(config *configuration) *defaultDialer {
|
||||||
|
return &defaultDialer{timeout: config.DialTimeout, logger: config.Logger}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *defaultDialer) Dial(address string) Socket {
|
||||||
|
if socket, err := net.DialTimeout("tcp", address, this.timeout); err == nil {
|
||||||
|
return socket
|
||||||
|
} else {
|
||||||
|
this.logger.Printf("[INFO] Unable to establish connection to [%s]: %s", address, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
9
src/mod/forwardproxy/cproxy/default_filter.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package cproxy
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type defaultFilter struct{}
|
||||||
|
|
||||||
|
func newFilter() *defaultFilter { return &defaultFilter{} }
|
||||||
|
|
||||||
|
func (this *defaultFilter) IsAuthorized(http.ResponseWriter, *http.Request) bool { return true }
|