mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-28 18:31:45 +02:00
Compare commits
150 Commits
Author | SHA1 | Date | |
---|---|---|---|
465f332dfc | |||
dfda3fe94b | |||
8b4c601d50 | |||
3a2eaf8766 | |||
a45092a449 | |||
d5315e5b8e | |||
31cc1a69a1 | |||
d348cbf48b | |||
f6339868ac | |||
af10f2a644 | |||
3b247c31da | |||
d74e8badb9 | |||
b40131d212 | |||
563a12c860 | |||
8b2c3b7e03 | |||
608cc0c523 | |||
b558bcbfcf | |||
9ea3fa2542 | |||
01f68c5ef5 | |||
a7f89086d4 | |||
a5ef6456c6 | |||
87659b43bd | |||
ddbecf7b68 | |||
1b3a9de378 | |||
6dd62f509d | |||
d5cc6a6859 | |||
1d965da7d0 | |||
3567c70bab | |||
0a734e0bd3 | |||
f4fa92635c | |||
7d5151bb00 | |||
54475e4b99 | |||
6ac16caf37 | |||
97502db607 | |||
0747cf4b0f | |||
94483acc92 | |||
7626857c02 | |||
0f772a715b | |||
fd1439f746 | |||
ca37bfbfa6 | |||
c1e16d55ab | |||
f595da92a1 | |||
8a8ec1cb0b | |||
e53c3cf3c4 | |||
d17de5c200 | |||
97ff48ee70 | |||
d64b1174af | |||
bec363abab | |||
0dddd1f9e3 | |||
6bfcb2e1f5 | |||
02ff288280 | |||
b1c5bc2963 | |||
d3dbbf9052 | |||
f4a5c905e7 | |||
245379e91f | |||
955a2232df | |||
7eb7ae7ced | |||
3aa0f2d914 | |||
39b0c8c674 | |||
bddeae8365 | |||
8e0e9531e7 | |||
6ff22865e0 | |||
0828fd1958 | |||
82f84470f7 | |||
cf9a05f130 | |||
301072db90 | |||
cfcd10d64f | |||
c85760c73a | |||
b7bb918aa3 | |||
962f3e0566 | |||
0bcf2b2ae3 | |||
6bfeb8cf3d | |||
33def66386 | |||
cb469f28d2 | |||
8239f4cb53 | |||
e410b92e34 | |||
aca6e44b35 | |||
2aa35cbe6d | |||
745a54605f | |||
e3b61868a1 | |||
764b1944be | |||
100cd727fc | |||
7e62fef879 | |||
1a4a55721f | |||
bb9deccff6 | |||
a18413dd03 | |||
2cd1b1de3c | |||
3a2db63d61 | |||
123d3bcf3f | |||
3ec1d9c888 | |||
5785261c7e | |||
89e60649e5 | |||
5423b82858 | |||
57135a867e | |||
547855f30f | |||
05b477e90a | |||
3519c7841c | |||
e7b4054248 | |||
973d0b3372 | |||
704980d4f8 | |||
03974163d4 | |||
dfb81513b1 | |||
b604c66a2f | |||
dd84864dd4 | |||
443cd961d2 | |||
10048150bb | |||
85f9b297c4 | |||
07e524a007 | |||
25c7e8ac1a | |||
49babbd60f | |||
fa11422748 | |||
bb1b161ae2 | |||
9545343151 | |||
61e4d45430 | |||
6026c4fd53 | |||
e3f8c99ed3 | |||
fc88dfe72e | |||
d43322f7a5 | |||
83536a83f7 | |||
1183b0ed55 | |||
b00e302f6d | |||
deddb17803 | |||
aa96d831e1 | |||
c6f7f37aaf | |||
63f12dedcf | |||
136d1ecafb | |||
7193defad7 | |||
cf4c57298e | |||
d82a531a41 | |||
7694e317f7 | |||
ed4945ab7e | |||
ce8741bfc8 | |||
7a3db09811 | |||
e73f9b47d3 | |||
c248dacccf | |||
d596d6b843 | |||
6feb2d105d | |||
3a26a5b4d3 | |||
2cdd5654ed | |||
a0d362df4e | |||
334c1ab131 | |||
08d52024ab | |||
a3e16594e8 | |||
cced07ba2d | |||
2003992d75 | |||
71423d98b1 | |||
8ca716c59f | |||
b1a14872c3 | |||
df9deb3fbb | |||
9369237229 |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
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]
|
||||
- OS: [e.g. Armbian]
|
||||
- Version [e.g. 23.02 Bullseye ]
|
||||
- Docker Version (if you are running Zoraxy in docker): [e.g. 3.0.4]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
25
.github/ISSUE_TEMPLATE/help-needed.md
vendored
Normal file
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.
|
1
.github/workflows/main.yml
vendored
1
.github/workflows/main.yml
vendored
@ -33,7 +33,6 @@ jobs:
|
||||
docker buildx create --name mainbuilder --driver docker-container --platform linux/amd64,linux/arm64 --use
|
||||
|
||||
docker buildx build --push \
|
||||
--build-arg VERSION=${{ github.event.release.tag_name }} \
|
||||
--provenance=false \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -34,3 +34,9 @@ docker/ImagePublisher.sh
|
||||
src/mod/acme/test/stackoverflow.pem
|
||||
/tools/dns_challenge_update/code-gen/acmedns
|
||||
/tools/dns_challenge_update/code-gen/lego
|
||||
src/tmp/localhost.key
|
||||
src/tmp/localhost.pem
|
||||
src/www/html/index.html
|
||||
src/sys.uuid
|
||||
src/zoraxy
|
||||
src/log/
|
111
CHANGELOG.md
111
CHANGELOG.md
@ -1,3 +1,114 @@
|
||||
# v3.1.1. 09 Sep 2024
|
||||
|
||||
+ Updated country name in access list [#287](https://github.com/tobychui/zoraxy/issues/287)
|
||||
+ Added tour for basic operations
|
||||
+ Updated acme log to system wide logger implementation
|
||||
+ Fixed path traversal in file manager [#274](https://github.com/tobychui/zoraxy/issues/274)
|
||||
+ Removed Proxmox debug code
|
||||
+ Fixed trie tree implementations
|
||||
|
||||
**Thanks to all contributors**
|
||||
|
||||
+ Fix existing containers list in docker popup [7brend7](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3A7brend7)
|
||||
+ Fix network I/O chart not rendering [JokerQyou](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3AJokerQyou)
|
||||
+ Fix typo remvoeClass to removeClass [Aahmadsyamim](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3Aahmadsyamim)
|
||||
+ Updated weighted random upstream implementation [bouroo](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3Abouroo)
|
||||
|
||||
# v3.1.0 31 Jul 2024
|
||||
|
||||
+ Updated log viewer with filter and auto refresh [#243](https://github.com/tobychui/zoraxy/issues/243)
|
||||
+ Fixed csrf vulnerability [#267](https://github.com/tobychui/zoraxy/issues/267)
|
||||
+ Fixed promox issue
|
||||
+ Fixed status code bug in upstream log [#254](https://github.com/tobychui/zoraxy/issues/254)
|
||||
+ Added host overwrite and hop-by-hop header remover
|
||||
+ Added early renew days settings [#256](https://github.com/tobychui/zoraxy/issues/256)
|
||||
+ Updated make file to force no CGO in cicd process
|
||||
+ Fixed bug in updater
|
||||
+ Fixed wildcard certificate renew bug [#249](https://github.com/tobychui/zoraxy/issues/249)
|
||||
+ Added certificate download function [#227](https://github.com/tobychui/zoraxy/issues/227)
|
||||
|
||||
# v3.0.9 16 Jul 2024
|
||||
|
||||
+ Added certificate download [#227](https://github.com/tobychui/zoraxy/issues/227)
|
||||
+ Updated netcup timeout value [#231](https://github.com/tobychui/zoraxy/issues/231)
|
||||
+ Updated geoip db
|
||||
+ Removed debug print from log viewer
|
||||
+ Upgraded netstat log printing to new log formatter
|
||||
+ Improved update module implementation
|
||||
|
||||
# v3.0.8 15 Jul 2024
|
||||
|
||||
+ Added apache style logging mechanism (and build-in log viewer) [#218](https://github.com/tobychui/zoraxy/issues/218)
|
||||
+ Fixed keep alive flushing issues [#235](https://github.com/tobychui/zoraxy/issues/235)
|
||||
+ Added multi-upstream supports [#100](https://github.com/tobychui/zoraxy/issues/100)
|
||||
+ Added stick session load balancer
|
||||
+ Added weighted random load balancer
|
||||
+ Added domain cleaning logic to domain / IP input fields
|
||||
+ Added HSTS "include subdomain" auto injector
|
||||
+ Added work-in-progress SSO / Oauth Server UI
|
||||
+ Fixed uptime monitor not updating on proxy rule change bug
|
||||
+ Optimized UI for create new proxy rule
|
||||
+ Removed service expose proxy feature
|
||||
|
||||
# v3.0.7 20 Jun 2024
|
||||
|
||||
+ Fixed redirection enable bug [#199](https://github.com/tobychui/zoraxy/issues/199)
|
||||
+ Fixed header tool user agent rewrite sequence
|
||||
+ Optimized rate limit UI
|
||||
+ Added HSTS and Permission Policy Editor [#163](https://github.com/tobychui/zoraxy/issues/163)
|
||||
+ Docker UX optimization start parameter `-docker`
|
||||
+ Docker container selector implementation for conditional compilations for Windows
|
||||
|
||||
From contributors:
|
||||
|
||||
+ Add Rate Limits Limits to Zoraxy fixes [185](https://github.com/tobychui/zoraxy/issues/185) by [Kirari04](https://github.com/Kirari04)
|
||||
+ Add docker containers list to set rule by [7brend7](https://github.com/7brend7) [PR202](https://github.com/tobychui/zoraxy/pull/202)
|
||||
|
||||
# v3.0.6 10 Jun 2024
|
||||
|
||||
+ Added fastly_client_ip to X-Real-IP auto rewrite
|
||||
+ Added atomic accumulator to TCP proxy
|
||||
+ Added white logo for future dark theme
|
||||
+ Added multi selection for white / blacklist [#176](https://github.com/tobychui/zoraxy/issues/176)
|
||||
+ Moved custom header rewrite to dpcore
|
||||
+ Restructure dpcore header rewrite sequence
|
||||
+ Added advance custom header settings (zoraxy to upstream and zoraxy to downstream mode)
|
||||
+ Added header remove feature
|
||||
+ Removed password requirement for SMTP [#162](https://github.com/tobychui/zoraxy/issues/162) [#80](https://github.com/tobychui/zoraxy/issues/80)
|
||||
+ Restructured TCP proxy into Stream Proxy (Support both TCP and UDP) [#147](https://github.com/tobychui/zoraxy/issues/147)
|
||||
+ Added stream proxy auto start [#169](https://github.com/tobychui/zoraxy/issues/169)
|
||||
+ Optimized UX for reminding user to click Apply after port change
|
||||
+ Added version number to footer [#160](https://github.com/tobychui/zoraxy/issues/160)
|
||||
|
||||
From contributors:
|
||||
|
||||
+ Fixed missing / unnecessary error check [PR187](https://github.com/tobychui/zoraxy/pull/187) by [Kirari04](https://github.com/Kirari04)
|
||||
|
||||
# v3.0.5 May 26 2024
|
||||
|
||||
|
||||
+ Optimized uptime monitor error message [#121](https://github.com/tobychui/zoraxy/issues/121)
|
||||
+ Optimized detection logic for internal proxy target and header rewrite condition for HTTP_HOST [#164](https://github.com/tobychui/zoraxy/issues/164)
|
||||
+ Fixed ovh DNS challenge provider form generator bug [#161](https://github.com/tobychui/zoraxy/issues/161)
|
||||
+ Added permission policy module (not enabled)
|
||||
+ Added single-use cookiejar to uptime monitor request client to handle cookie issues on some poorly written back-end server [#149](https://github.com/tobychui/zoraxy/issues/149)
|
||||
|
||||
|
||||
# v3.0.4 May 18 2024
|
||||
|
||||
## This release tidied up the contribution by [Teifun2](https://github.com/Teifun2) and added a new way to generate DNS challenge based certificate (e.g. wildcards) from Let's Encrypt without changing any environment variables. This also fixes a few previous ACME module EAB settings bug related to concurrent save.
|
||||
|
||||
You can find the DNS challenge settings under TLS / SSL > ACME snippet > Generate New Certificate > (Check the "Use a DNS Challenge" checkbox)
|
||||
|
||||
+ Optimized DNS challenge implementation [thanks to Teifun2](https://github.com/Teifun2) / Issues [#49](https://github.com/tobychui/zoraxy/issues/49) [#79](https://github.com/tobychui/zoraxy/issues/79)
|
||||
+ Removed dependencies on environment variable write and keep all data contained
|
||||
+ Fixed panic on loading certificate generated by Zoraxy v2
|
||||
+ Added automatic form generator for DNS challenge / providers
|
||||
+ Added CA name default value
|
||||
+ Added code generator for acmedns module (storing the DNS challenge provider contents extracted from lego)
|
||||
+ Fixed ACME snippet "Obtain Certificate" concurrent issues in save EAB and DNS credentials
|
||||
|
||||
|
||||
# v3.0.3 Apr 30 2024
|
||||
## Breaking Change
|
||||
|
||||
|
39
README.md
39
README.md
@ -2,9 +2,7 @@
|
||||
|
||||
# Zoraxy
|
||||
|
||||
General purpose request (reverse) proxy and forwarding tool for networking noobs. Now written in Go!
|
||||
|
||||
*Zoraxy v3 HTTP proxy config is not compatible with the older v2. If you are looking for the legacy version of Zoraxy, take a look at the [v2 branch](https://github.com/tobychui/zoraxy/tree/v2)*
|
||||
A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
|
||||
|
||||
### Features
|
||||
|
||||
@ -19,14 +17,17 @@ General purpose request (reverse) proxy and forwarding tool for networking noobs
|
||||
- TLS / SSL setup and deploy
|
||||
- ACME features like auto-renew to serve your sites in http**s**
|
||||
- SNI support (and SAN certs)
|
||||
- DNS Challenge for Let's Encrypt and [these DNS providers](https://go-acme.github.io/lego/dns/)
|
||||
- Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)
|
||||
- Global Area Network Controller Web UI (ZeroTier not included)
|
||||
- TCP Tunneling / Proxy
|
||||
- Stream Proxy (TCP & UDP)
|
||||
- Integrated Up-time Monitor
|
||||
- Web-SSH Terminal
|
||||
- Utilities
|
||||
- CIDR IP converters
|
||||
- mDNS Scanner
|
||||
- Wake-On-Lan
|
||||
- Debug Forward Proxy
|
||||
- IP Scanner
|
||||
- Others
|
||||
- Basic single-admin management mode
|
||||
@ -36,10 +37,17 @@ General purpose request (reverse) proxy and forwarding tool for networking noobs
|
||||
## 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)
|
||||
/ [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/)
|
||||
For other systems or architectures, please see [Releases](https://github.com/tobychui/zoraxy/releases/latest/)
|
||||
|
||||
## Getting Started
|
||||
|
||||
[Installing Zoraxy Reverse Proxy: Your Gateway to Efficient Web Routing](https://geekscircuit.com/installing-zoraxy-reverse-proxy-your-gateway-to-efficient-web-routing/)
|
||||
|
||||
Thank you for the well written and easy to follow tutorial by Reddit user [itsvmn](https://www.reddit.com/user/itsvmn/)!
|
||||
If you have no background in setting up reverse proxy or web routing, you should check this out before you start setting up your Zoraxy.
|
||||
|
||||
## Build from Source
|
||||
|
||||
@ -56,11 +64,11 @@ sudo ./zoraxy -port=:8000
|
||||
|
||||
## Usage
|
||||
|
||||
Zoraxy provides basic authentication system for standalone mode. To use it in standalone mode, follow the instructionss below for your desired deployment platform.
|
||||
Zoraxy provides basic authentication system for standalone mode. To use it in standalone mode, follow the instructions below for your desired deployment platform.
|
||||
|
||||
### Standalone Mode
|
||||
|
||||
Standalone mode is the default mode for Zoraxy. This allows a single account to manage your reverse proxy server, just like a home router. This mode is suitable for new owners to homelabs or makers starting growing their web services into multiple servers.
|
||||
Standalone mode is the default mode for Zoraxy. This allows a single account to manage your reverse proxy server just like a basic home router. This mode is suitable for new owners to homelabs or makers starting growing their web services into multiple servers. A full "Getting Started" guide can be found [here](https://github.com/tobychui/zoraxy/wiki/Getting-Started).
|
||||
|
||||
#### Linux
|
||||
|
||||
@ -84,16 +92,18 @@ The installation method is same as Linux. For other ARM SBCs, please refer to yo
|
||||
|
||||
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details.
|
||||
|
||||
### Start Paramters
|
||||
### Start Parameters
|
||||
|
||||
```
|
||||
Usage of zoraxy:
|
||||
-autorenew int
|
||||
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
|
||||
-cfgupgrade
|
||||
Enable auto config upgrade if breaking change is detected (default true)
|
||||
-docker
|
||||
Run Zoraxy in docker compatibility mode
|
||||
-fastgeoip
|
||||
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
|
||||
-log
|
||||
Log terminal output to file (default true)
|
||||
-mdns
|
||||
Enable mDNS scanner and transponder (default true)
|
||||
-mdnsname string
|
||||
@ -109,7 +119,7 @@ Usage of zoraxy:
|
||||
-webfm
|
||||
Enable web file manager for static web server root folder (default true)
|
||||
-webroot string
|
||||
Static web server root folder. Only allow chnage in start paramters (default "./www")
|
||||
Static web server root folder. Only allow change in start parameters (default "./www")
|
||||
-ztauth string
|
||||
ZeroTier authtoken for the local node
|
||||
-ztport int
|
||||
@ -124,7 +134,7 @@ If you already have an upstream reverse proxy server in place with permission ma
|
||||
./zoraxy -noauth=true
|
||||
```
|
||||
|
||||
*Note: For security reaons, you should only enable no-auth if you are running Zoraxy in a trusted environment or with another authentication management proxy in front.*
|
||||
*Note: For security reasons, you should only enable no-auth if you are running Zoraxy in a trusted environment or with another authentication management proxy in front.*
|
||||
|
||||
## Screenshots
|
||||
|
||||
@ -182,4 +192,3 @@ If you like the project and want to support us, please consider a donation. You
|
||||
|
||||
This project is open-sourced under AGPL. I open-sourced this project so everyone can check for security issues and benefit all users. **This software is intended to be free of charge. If you have acquired this software from a third-party seller, the authors of this repository bears no responsibility for any technical difficulties assistance or support.**
|
||||
|
||||
|
||||
|
@ -1,17 +1,8 @@
|
||||
FROM docker.io/golang:alpine
|
||||
# VERSION comes from the main.yml workflow --build-arg
|
||||
ARG VERSION
|
||||
|
||||
RUN apk add --no-cache bash netcat-openbsd sudo
|
||||
FROM docker.io/golang:alpine AS build-zoraxy
|
||||
|
||||
RUN mkdir -p /opt/zoraxy/source/ &&\
|
||||
mkdir -p /opt/zoraxy/config/ &&\
|
||||
mkdir -p /usr/local/bin/
|
||||
|
||||
RUN chmod -R 770 /opt/zoraxy/
|
||||
|
||||
VOLUME [ "/opt/zoraxy/config/" ]
|
||||
|
||||
# If you build it yourself, you will need to add the src directory into the docker directory.
|
||||
COPY ./src/ /opt/zoraxy/source/
|
||||
|
||||
@ -19,17 +10,57 @@ WORKDIR /opt/zoraxy/source/
|
||||
|
||||
RUN go mod tidy &&\
|
||||
go build -o /usr/local/bin/zoraxy &&\
|
||||
rm -r /opt/zoraxy/source/
|
||||
chmod 755 /usr/local/bin/zoraxy
|
||||
|
||||
RUN chmod 755 /usr/local/bin/zoraxy &&\
|
||||
chmod +x /usr/local/bin/zoraxy
|
||||
FROM docker.io/ubuntu:latest AS build-zerotier
|
||||
|
||||
RUN mkdir -p /opt/zerotier/source/ &&\
|
||||
mkdir -p /usr/local/bin/
|
||||
|
||||
WORKDIR /opt/zerotier/source/
|
||||
|
||||
RUN apt-get update -y &&\
|
||||
apt-get install -y curl jq build-essential pkg-config clang cargo libssl-dev
|
||||
|
||||
RUN curl -Lo ZeroTierOne.tar.gz https://codeload.github.com/zerotier/ZeroTierOne/tar.gz/refs/tags/1.10.6 &&\
|
||||
tar -xzvf ZeroTierOne.tar.gz &&\
|
||||
cd ZeroTierOne-* &&\
|
||||
make &&\
|
||||
mv ./zerotier-one /usr/local/bin/zerotier-one &&\
|
||||
chmod 755 /usr/local/bin/zerotier-one
|
||||
|
||||
FROM docker.io/ubuntu:latest
|
||||
|
||||
RUN apt-get update -y &&\
|
||||
apt-get install -y bash sudo netcat-openbsd libssl-dev
|
||||
|
||||
COPY --chmod=700 ./entrypoint.sh /opt/zoraxy/
|
||||
COPY --from=build-zoraxy /usr/local/bin/zoraxy /usr/local/bin/zoraxy
|
||||
COPY --from=build-zerotier /usr/local/bin/zerotier-one /usr/local/bin/zerotier-one
|
||||
|
||||
WORKDIR /opt/zoraxy/config/
|
||||
|
||||
ENV VERSION=$VERSION
|
||||
ENV ARGS="-noauth=false"
|
||||
ENV ZEROTIER="false"
|
||||
|
||||
ENTRYPOINT "zoraxy" "-port=:8000" "${ARGS}"
|
||||
ENV AUTORENEW="86400"
|
||||
ENV CFGUPGRADE="true"
|
||||
ENV DOCKER="true"
|
||||
ENV EARLYRENEW="30"
|
||||
ENV FASTGEOIP="false"
|
||||
ENV MDNS="true"
|
||||
ENV MDNSNAME="''"
|
||||
ENV NOAUTH="false"
|
||||
ENV PORT="8000"
|
||||
ENV SSHLB="false"
|
||||
ENV VERSION="false"
|
||||
ENV WEBFM="true"
|
||||
ENV WEBROOT="./www"
|
||||
ENV ZTAUTH=""
|
||||
ENV ZTPORT="9993"
|
||||
|
||||
HEALTHCHECK --interval=5s --timeout=5s --retries=2 CMD nc -vz 127.0.0.1 8000 || exit 1
|
||||
VOLUME [ "/opt/zoraxy/config/", "/var/lib/zerotier-one/" ]
|
||||
|
||||
ENTRYPOINT [ "/opt/zoraxy/entrypoint.sh" ]
|
||||
|
||||
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 CMD nc -vz 127.0.0.1 $PORT || exit 1
|
||||
|
||||
|
121
docker/README.md
121
docker/README.md
@ -1,65 +1,98 @@
|
||||
# [zoraxy](https://github.com/tobychui/zoraxy/) </br>
|
||||
# Zoraxy Docker
|
||||
|
||||
[](https://hub.docker.com/r/zoraxydocker/zoraxy)
|
||||
[](https://hub.docker.com/r/zoraxydocker/zoraxy)
|
||||
[](https://hub.docker.com/r/zoraxydocker/zoraxy)
|
||||
[](https://hub.docker.com/r/zoraxydocker/zoraxy)
|
||||
|
||||
## Setup: </br>
|
||||
Although not required, it is recommended to give Zoraxy a dedicated location on the host to mount the container. That way, the host/user can access them whenever needed. A volume will be created automatically within Docker if a location is not specified. </br>
|
||||
## Usage
|
||||
|
||||
You may also need to portforward your 80/443 to allow http and https traffic. If you are accessing the interface from outside of the local network, you may also need to forward your management port. If you know how to do this, great! If not, find the manufacturer of your router and search on how to do that. There are too many to be listed here. </br>
|
||||
If you are attempting to access your service from outside your network, make sure to forward ports 80 and 443 to the Zoraxy host to allow web traffic. If you know how to do this, great! If not, find the manufacturer of your router and search on how to do that. There are too many to be listed here. Read more about it from [whatismyip](https://www.whatismyip.com/port-forwarding/).
|
||||
|
||||
In the examples below, make sure to update `/path/to/zoraxy/config/` with your actual path. If a path is not provided, a Docker volume will be created at the location but it is recommended to store the data at a defined host location.
|
||||
|
||||
Once setup, access the webui at `http://<host-ip>:8000` to configure Zoraxy. Change the port in the URL if you changed the management port.
|
||||
|
||||
### Docker Run
|
||||
|
||||
### Using Docker run </br>
|
||||
```
|
||||
docker run -d --name (container name) -p (ports) -v (path to storage directory):/opt/zoraxy/data/ -e ARGS='(your arguments)' zoraxydocker/zoraxy:latest
|
||||
docker run -d \
|
||||
--name zoraxy \
|
||||
--restart unless-stopped \
|
||||
-p 80:80 \
|
||||
-p 443:443 \
|
||||
-p 8000:8000 \
|
||||
-v /path/to/zoraxy/config/:/opt/zoraxy/config/ \
|
||||
-v /path/to/zerotier/config/:/var/lib/zerotier-one/ \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /etc/localtime:/etc/localtime \
|
||||
-e FASTGEOIP="true" \
|
||||
-e ZEROTIER="true" \
|
||||
zoraxydocker/zoraxy:latest
|
||||
```
|
||||
|
||||
### Using Docker Compose </br>
|
||||
### Docker Compose
|
||||
|
||||
```yml
|
||||
version: '3.3'
|
||||
services:
|
||||
zoraxy-docker:
|
||||
image: zoraxydocker/zoraxy:latest
|
||||
container_name: (container name)
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
- (external):8000
|
||||
volumes:
|
||||
- (path to storage directory):/opt/zoraxy/config/
|
||||
environment:
|
||||
ARGS: '(your arguments)'
|
||||
```
|
||||
|
||||
| Operator | Need | Details |
|
||||
|:-|:-|:-|
|
||||
| `-d` | Yes | will run the container in the background. |
|
||||
| `--name (container name)` | No | Sets the name of the container to the following word. You can change this to whatever you want. |
|
||||
| `-p (ports)` | Yes | Depending on how your network is setup, you may need to portforward 80, 443, and the management port. |
|
||||
| `-v (path to storage directory):/opt/zoraxy/config/` | Recommend | Sets the folder that holds your files. This should be the place you just chose. By default, it will create a Docker volume for the files for persistency but they will not be accessible. |
|
||||
| `-e ARGS='(your arguments)'` | No | Sets the arguments to run Zoraxy with. Enter them as you would normally. By default, it is ran with `-noauth=false` but <b>you cannot change the management port.</b> This is required for the healthcheck to work. |
|
||||
| `zoraxydocker/zoraxy:latest` | Yes | The repository on Docker hub. By default, it is the latest version that is published. |
|
||||
|
||||
## Examples: </br>
|
||||
### Docker Run </br>
|
||||
```
|
||||
docker run -d --name zoraxy -p 80:80 -p 443:443 -p 8005:8000/tcp -v /home/docker/Containers/Zoraxy:/opt/zoraxy/config/ -e ARGS='-noauth=false' zoraxydocker/zoraxy:latest
|
||||
```
|
||||
|
||||
### Docker Compose </br>
|
||||
```yml
|
||||
version: '3.3'
|
||||
services:
|
||||
zoraxy-docker:
|
||||
zoraxy:
|
||||
image: zoraxydocker/zoraxy:latest
|
||||
container_name: zoraxy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
- 8005:8000/tcp
|
||||
- 8000:8000
|
||||
volumes:
|
||||
- /home/docker/Containers/Zoraxy:/opt/zoraxy/config/
|
||||
- /path/to/zoraxy/config/:/opt/zoraxy/config/
|
||||
- /path/to/zerotier/config/:/var/lib/zerotier-one/
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /etc/localtime:/etc/localtime
|
||||
environment:
|
||||
ARGS: '-noauth=false'
|
||||
FASTGEOIP: "true"
|
||||
ZEROTIER: "true"
|
||||
```
|
||||
|
||||
### Ports
|
||||
|
||||
| Port | Details |
|
||||
|:-|:-|
|
||||
| `80` | HTTP traffic. |
|
||||
| `443` | HTTPS traffic. |
|
||||
| `8000` | Management interface. Can be changed with the `PORT` env. |
|
||||
|
||||
### Volumes
|
||||
|
||||
| Volume | Details |
|
||||
|:-|:-|
|
||||
| `/opt/zoraxy/config/` | Zoraxy configuration. |
|
||||
| `/var/lib/zerotier-one/` | ZeroTier configuration. Only required if you wish to use ZeroTier. |
|
||||
| `/var/run/docker.sock` | Docker socket. Used for additional functionality with Zoraxy. |
|
||||
| `/etc/localtime` | Localtime. Set to ensure the host and container are synchronized. |
|
||||
|
||||
### Environment
|
||||
|
||||
Variables are the same as those in [Start Parameters](https://github.com/tobychui/zoraxy?tab=readme-ov-file#start-paramters).
|
||||
|
||||
| Variable | Default | Details |
|
||||
|:-|:-|:-|
|
||||
| `AUTORENEW` | `86400` (Integer) | ACME auto TLS/SSL certificate renew check interval. |
|
||||
| `CFGUPGRADE` | `true` (Boolean) | Enable auto config upgrade if breaking change is detected. |
|
||||
| `DOCKER` | `true` (Boolean) | Run Zoraxy in docker compatibility mode. |
|
||||
| `EARLYRENEW` | `30` (Integer) | Number of days to early renew a soon expiring certificate. |
|
||||
| `FASTGEOIP` | `false` (Boolean) | Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices). |
|
||||
| `MDNS` | `true` (Boolean) | Enable mDNS scanner and transponder. |
|
||||
| `MDNSNAME` | `''` (String) | mDNS name, leave empty to use default (zoraxy_{node-uuid}.local). |
|
||||
| `NOAUTH` | `false` (Boolean) | Disable authentication for management interface. |
|
||||
| `PORT` | `8000` (Integer) | Management web interface listening port |
|
||||
| `SSHLB` | `false` (Boolean) | Allow loopback web ssh connection (DANGER). |
|
||||
| `VERSION` | `false` (Boolean) | Show version of this server. |
|
||||
| `WEBFM` | `true` (Boolean) | Enable web file manager for static web server root folder. |
|
||||
| `WEBROOT` | `./www` (String) | Static web server root folder. Only allow change in start parameters. |
|
||||
| `ZEROTIER` | `false` (Boolean) | Enable ZeroTier functionality for GAN. |
|
||||
| `ZTAUTH` | `""` (String) | ZeroTier authtoken for the local node. |
|
||||
| `ZTPORT` | `9993` (Integer) | ZeroTier controller API port. |
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Contrary to the Zoraxy README, Docker usage of the port flag should NOT include the colon. Ex: `-e PORT="8000"` for Docker run and `PORT: "8000"` for Docker compose.
|
||||
|
||||
|
25
docker/entrypoint.sh
Normal file
25
docker/entrypoint.sh
Normal file
@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ "$ZEROTIER" = "true" ]; then
|
||||
echo "Starting ZeroTier daemon..."
|
||||
zerotier-one -d
|
||||
fi
|
||||
|
||||
echo "Starting Zoraxy..."
|
||||
exec zoraxy \
|
||||
-autorenew="$AUTORENEW" \
|
||||
-cfgupgrade="$CFGUPGRADE" \
|
||||
-docker="$DOCKER" \
|
||||
-earlyrenew="$EARLYRENEW" \
|
||||
-fastgeoip="$FASTGEOIP" \
|
||||
-mdns="$MDNS" \
|
||||
-mdnsname="$MDNSNAME" \
|
||||
-noauth="$NOAUTH" \
|
||||
-port=:"$PORT" \
|
||||
-sshlb="$SSHLB" \
|
||||
-version="$VERSION" \
|
||||
-webfm="$WEBFM" \
|
||||
-webroot="$WEBROOT" \
|
||||
-ztauth="$ZTAUTH" \
|
||||
-ztport="$ZTPORT"
|
||||
|
@ -80,7 +80,7 @@
|
||||
<div class="bannerHeaderWrapper">
|
||||
<h1 class="bannerHeader">Zoraxy</h1>
|
||||
<div class="ui divider"></div><br>
|
||||
<p class="bannerSubheader">All in one homelab network routing solution</p>
|
||||
<p class="bannerSubheader">Beyond Reverse Proxy: Your Ultimate Homelab Network Tool</p>
|
||||
</div>
|
||||
<br><br>
|
||||
<a class="ui basic big button" style="background-color: white;" href="#features"><i class="ui blue arrow down icon"></i> Learn More</a>
|
||||
|
@ -15,4 +15,12 @@ The templates folder contains the template for overriding the build in error or
|
||||
|
||||
To use the template, copy and paste the `wwww` folder to the same directory as zoraxy executable (aka the src/ file if you `go build` with the current folder tree).
|
||||
|
||||
|
||||
|
||||
### Other Templates
|
||||
|
||||
There are a few pre-built templates that works with Zoraxy where you can find in the `other-templates` folder. Copy the folder into `www` and rename the folder to `templates` to active them.
|
||||
|
||||
|
||||
|
||||
It is worth mentioning that the uwu icons for not-found and access-denied are created by @SAWARATSUKI
|
||||
|
185
example/other-templates/templates_cf/blacklist.html
Normal file
185
example/other-templates/templates_cf/blacklist.html
Normal file
File diff suppressed because one or more lines are too long
154
example/other-templates/templates_cf/notfound.html
Normal file
154
example/other-templates/templates_cf/notfound.html
Normal file
@ -0,0 +1,154 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="theme-color" content="#4b75ff">
|
||||
<link rel="icon" type="image/png" href="img/small_icon.png"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
|
||||
<title>404 - Host Not Found</title>
|
||||
<style>
|
||||
h1, h2, h3, h4, h5, p, a, span, .ui.list .item{
|
||||
font-family: 'Noto Sans TC', sans-serif;
|
||||
font-weight: 300;
|
||||
color: rgb(88, 88, 88)
|
||||
}
|
||||
|
||||
.diagram{
|
||||
background-color: #ebebeb;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
.diagramHeader{
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
@media (max-width:512px) {
|
||||
.widescreenOnly{
|
||||
display: none !important;
|
||||
|
||||
}
|
||||
|
||||
.four.wide.column:not(.widescreenOnly){
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.ui.grid{
|
||||
justify-content: center !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<br><br>
|
||||
<div class="ui container">
|
||||
<h1 style="font-size: 4rem;">Error 404</h1>
|
||||
<p style="font-size: 2rem; margin-bottom: 0.4em;">Target Host Not Found</p>
|
||||
<small id="timestamp"></small>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="diagram">
|
||||
<div class="ui text container">
|
||||
<div class="ui grid">
|
||||
<div class="four wide column widescreenOnly" align="center">
|
||||
<svg version="1.1" id="client_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
|
||||
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
|
||||
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
|
||||
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
|
||||
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
|
||||
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
|
||||
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
|
||||
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
|
||||
82.989,143.204 "/>
|
||||
</svg>
|
||||
<small>You</small>
|
||||
<h2 class="diagramHeader">Browser</h2>
|
||||
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||
</div>
|
||||
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||
</div>
|
||||
<div class="four wide column widescreenOnly" align="center">
|
||||
<svg version="1.1" id="cloud_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
|
||||
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
|
||||
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
|
||||
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
|
||||
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
|
||||
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
|
||||
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
|
||||
83.971,148.123 "/>
|
||||
</svg>
|
||||
|
||||
<small>Gateway Node</small>
|
||||
<h2 class="diagramHeader">Reverse Proxy</h2>
|
||||
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||
</div>
|
||||
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||
</div>
|
||||
<div class="four wide column" align="center">
|
||||
<svg version="1.1" id="host_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
|
||||
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
|
||||
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
|
||||
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
|
||||
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
|
||||
</svg>
|
||||
<small id="host"></small>
|
||||
<h2 class="diagramHeader">Host</h2>
|
||||
<p style="font-weight: 500; color: #bd2426;">Not Found</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui stackable grid">
|
||||
<div class="eight wide column">
|
||||
<h1>What happend?</h1>
|
||||
<p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<h1>What can I do?</h1>
|
||||
<h5 style="font-weight: 500;">If you are a visitor of this website: </h5>
|
||||
<p>Please try again in a few minutes</p>
|
||||
<h5 style="font-weight: 500;">If you are the owner of this website:</h5>
|
||||
<div class="ui bulleted list">
|
||||
<div class="item">Check if the proxy rules that match this hostname exists</div>
|
||||
<div class="item">Visit the Reverse Proxy management interface to correct any setting errors</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui container" style="color: grey; font-size: 90%">
|
||||
<p>Powered by Zoraxy</p>
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
<script>
|
||||
$("#timestamp").text(new Date());
|
||||
$("#host").text(location.href);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
185
example/other-templates/templates_cf/whitelist.html
Normal file
185
example/other-templates/templates_cf/whitelist.html
Normal file
File diff suppressed because one or more lines are too long
52
example/other-templates/templates_uwu/blacklist.html
Normal file
52
example/other-templates/templates_uwu/blacklist.html
Normal file
File diff suppressed because one or more lines are too long
42
example/other-templates/templates_uwu/notfound.html
Normal file
42
example/other-templates/templates_uwu/notfound.html
Normal file
File diff suppressed because one or more lines are too long
52
example/other-templates/templates_uwu/whitelist.html
Normal file
52
example/other-templates/templates_uwu/whitelist.html
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
Binary file not shown.
BIN
img/title.png
BIN
img/title.png
Binary file not shown.
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 69 KiB |
BIN
img/title.psd
BIN
img/title.psd
Binary file not shown.
@ -19,7 +19,7 @@ clean:
|
||||
|
||||
$(PLATFORMS):
|
||||
@echo "Building $(os)/$(arch)"
|
||||
GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) $(if $(filter linux/arm,$(os)/$(arch)),GOARM=6,) go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath
|
||||
GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) $(if $(filter linux/arm,$(os)/$(arch)),GOARM=6,) CGO_ENABLED="0" go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath
|
||||
# GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) GOARM=6 go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath
|
||||
|
||||
|
||||
|
@ -38,7 +38,7 @@ func initACME() *acme.ACMEHandler {
|
||||
port = getRandomPort(30000)
|
||||
}
|
||||
|
||||
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb)
|
||||
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb, SystemWideLogger)
|
||||
}
|
||||
|
||||
// create the special routing rule for ACME
|
||||
|
69
src/api.go
69
src/api.go
@ -22,11 +22,11 @@ import (
|
||||
|
||||
var requireAuth = true
|
||||
|
||||
func initAPIs() {
|
||||
|
||||
func initAPIs(targetMux *http.ServeMux) {
|
||||
authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
|
||||
AuthAgent: authAgent,
|
||||
RequireAuth: requireAuth,
|
||||
TargetMux: targetMux,
|
||||
DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
|
||||
},
|
||||
@ -37,12 +37,12 @@ func initAPIs() {
|
||||
if development {
|
||||
fs = http.FileServer(http.Dir("web/"))
|
||||
}
|
||||
//Add a layer of middleware for advance control
|
||||
//Add a layer of middleware for advance control
|
||||
advHandler := FSHandler(fs)
|
||||
http.Handle("/", advHandler)
|
||||
targetMux.Handle("/", advHandler)
|
||||
|
||||
//Authentication APIs
|
||||
registerAuthAPIs(requireAuth)
|
||||
registerAuthAPIs(requireAuth, targetMux)
|
||||
|
||||
//Reverse proxy
|
||||
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
|
||||
@ -61,6 +61,12 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
||||
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
||||
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
||||
//Reverse proxy upstream (load balance) APIs
|
||||
authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete)
|
||||
//Reverse proxy virtual directory APIs
|
||||
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
|
||||
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
|
||||
@ -70,6 +76,10 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList)
|
||||
authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd)
|
||||
authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove)
|
||||
authRouter.HandleFunc("/api/proxy/header/handleHSTS", HandleHSTSState)
|
||||
authRouter.HandleFunc("/api/proxy/header/handleHopByHop", HandleHopByHop)
|
||||
authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite)
|
||||
authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy)
|
||||
//Reverse proxy auth related APIs
|
||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
|
||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
|
||||
@ -79,6 +89,7 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
|
||||
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
|
||||
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
|
||||
authRouter.HandleFunc("/api/cert/download", handleCertDownload)
|
||||
authRouter.HandleFunc("/api/cert/list", handleListCertificate)
|
||||
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
||||
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
||||
@ -119,7 +130,7 @@ func initAPIs() {
|
||||
//Statistic & uptime monitoring API
|
||||
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
|
||||
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
|
||||
authRouter.HandleFunc("/api/stats/netstat", netstat.HandleGetNetworkInterfaceStats)
|
||||
authRouter.HandleFunc("/api/stats/netstat", netstatBuffers.HandleGetNetworkInterfaceStats)
|
||||
authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats)
|
||||
authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces)
|
||||
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
|
||||
@ -140,15 +151,14 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
|
||||
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
||||
|
||||
//TCP Proxy
|
||||
authRouter.HandleFunc("/api/tcpprox/config/add", tcpProxyManager.HandleAddProxyConfig)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/edit", tcpProxyManager.HandleEditProxyConfigs)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/list", tcpProxyManager.HandleListConfigs)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/start", tcpProxyManager.HandleStartProxy)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/stop", tcpProxyManager.HandleStopProxy)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/delete", tcpProxyManager.HandleRemoveProxy)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/status", tcpProxyManager.HandleGetProxyStatus)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/validate", tcpProxyManager.HandleConfigValidate)
|
||||
//Stream (TCP / UDP) Proxy
|
||||
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
||||
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
|
||||
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
|
||||
authRouter.HandleFunc("/api/streamprox/config/start", streamProxyManager.HandleStartProxy)
|
||||
authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy)
|
||||
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
|
||||
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus)
|
||||
|
||||
//mDNS APIs
|
||||
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
|
||||
@ -177,8 +187,8 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
|
||||
|
||||
//Account Reset
|
||||
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
||||
http.HandleFunc("/api/account/new", HandleNewPasswordSetup)
|
||||
targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
||||
targetMux.HandleFunc("/api/account/new", HandleNewPasswordSetup)
|
||||
|
||||
//ACME & Auto Renewer
|
||||
authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
|
||||
@ -213,31 +223,38 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete)
|
||||
}
|
||||
|
||||
//Docker UX Optimizations
|
||||
authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable)
|
||||
authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList)
|
||||
|
||||
//Others
|
||||
http.HandleFunc("/api/info/x", HandleZoraxyInfo)
|
||||
targetMux.HandleFunc("/api/info/x", HandleZoraxyInfo)
|
||||
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
|
||||
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
|
||||
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
|
||||
authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog)
|
||||
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
|
||||
|
||||
//Debug
|
||||
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
|
||||
|
||||
//If you got APIs to add, append them here
|
||||
|
||||
}
|
||||
|
||||
// Function to renders Auth related APIs
|
||||
func registerAuthAPIs(requireAuth bool) {
|
||||
func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
||||
//Auth APIs
|
||||
http.HandleFunc("/api/auth/login", authAgent.HandleLogin)
|
||||
http.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
|
||||
http.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
|
||||
targetMux.HandleFunc("/api/auth/login", authAgent.HandleLogin)
|
||||
targetMux.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
|
||||
targetMux.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
|
||||
if requireAuth {
|
||||
authAgent.CheckLogin(w, r)
|
||||
} else {
|
||||
utils.SendJSONResponse(w, "true")
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/api/auth/username", func(w http.ResponseWriter, r *http.Request) {
|
||||
targetMux.HandleFunc("/api/auth/username", func(w http.ResponseWriter, r *http.Request) {
|
||||
username, err := authAgent.GetUserName(w, r)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
@ -247,12 +264,12 @@ func registerAuthAPIs(requireAuth bool) {
|
||||
js, _ := json.Marshal(username)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
})
|
||||
http.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
|
||||
targetMux.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
|
||||
uc := authAgent.GetUserCounts()
|
||||
js, _ := json.Marshal(uc)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
})
|
||||
http.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) {
|
||||
targetMux.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) {
|
||||
if authAgent.GetUserCounts() == 0 {
|
||||
//Allow register root admin
|
||||
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {
|
||||
@ -263,7 +280,7 @@ func registerAuthAPIs(requireAuth bool) {
|
||||
utils.SendErrorResponse(w, "Root management account already exists")
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/api/auth/changePassword", func(w http.ResponseWriter, r *http.Request) {
|
||||
targetMux.HandleFunc("/api/auth/changePassword", func(w http.ResponseWriter, r *http.Request) {
|
||||
username, err := authAgent.GetUserName(w, r)
|
||||
if err != nil {
|
||||
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
|
||||
|
68
src/cert.go
68
src/cert.go
@ -182,27 +182,28 @@ func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
|
||||
sysdb.Read("settings", "usetls", ¤tTlsSetting)
|
||||
}
|
||||
|
||||
newState, err := utils.PostPara(r, "set")
|
||||
if err != nil {
|
||||
//No setting. Get the current status
|
||||
if r.Method == http.MethodGet {
|
||||
//Get the current status
|
||||
js, _ := json.Marshal(currentTlsSetting)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
if newState == "true" {
|
||||
} else if r.Method == http.MethodPost {
|
||||
newState, err := utils.PostBool(r, "set")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "new state not set or invalid")
|
||||
return
|
||||
}
|
||||
if newState {
|
||||
sysdb.Write("settings", "usetls", true)
|
||||
SystemWideLogger.Println("Enabling TLS mode on reverse proxy")
|
||||
dynamicProxyRouter.UpdateTLSSetting(true)
|
||||
} else if newState == "false" {
|
||||
} else {
|
||||
sysdb.Write("settings", "usetls", false)
|
||||
SystemWideLogger.Println("Disabling TLS mode on reverse proxy")
|
||||
dynamicProxyRouter.UpdateTLSSetting(false)
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid state given. Only support true or false")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
|
||||
} else {
|
||||
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,6 +234,51 @@ func handleSetTlsRequireLatest(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle download of the selected certificate
|
||||
func handleCertDownload(w http.ResponseWriter, r *http.Request) {
|
||||
// get the certificate name
|
||||
certname, err := utils.GetPara(r, "certname")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid certname given")
|
||||
return
|
||||
}
|
||||
certname = filepath.Base(certname) //prevent path escape
|
||||
|
||||
// check if the cert exists
|
||||
pubKey := filepath.Join(filepath.Join("./conf/certs"), certname+".key")
|
||||
priKey := filepath.Join(filepath.Join("./conf/certs"), certname+".pem")
|
||||
|
||||
if utils.FileExists(pubKey) && utils.FileExists(priKey) {
|
||||
//Zip them and serve them via http download
|
||||
seeking, _ := utils.GetBool(r, "seek")
|
||||
if seeking {
|
||||
//This request only check if the key exists. Do not provide download
|
||||
utils.SendOK(w)
|
||||
return
|
||||
}
|
||||
|
||||
//Serve both file in zip
|
||||
zipTmpFolder := "./tmp/download"
|
||||
os.MkdirAll(zipTmpFolder, 0775)
|
||||
zipFileName := filepath.Join(zipTmpFolder, certname+".zip")
|
||||
err := utils.ZipFiles(zipFileName, pubKey, priKey)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to create zip file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer os.Remove(zipFileName) // Clean up the zip file after serving
|
||||
|
||||
// Serve the zip file
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=\""+certname+"_export.zip\"")
|
||||
w.Header().Set("Content-Type", "application/zip")
|
||||
http.ServeFile(w, r, zipFileName)
|
||||
} else {
|
||||
//Not both key exists
|
||||
utils.SendErrorResponse(w, "invalid key-pairs: private key or public key not found in key store")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Handle upload of the certificate
|
||||
func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
||||
// check if request method is POST
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -79,7 +80,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
||||
return errors.New("not supported proxy type")
|
||||
}
|
||||
|
||||
SystemWideLogger.PrintAndLog("Proxy", thisConfigEndpoint.RootOrMatchingDomain+" -> "+thisConfigEndpoint.Domain+" routing rule loaded", nil)
|
||||
SystemWideLogger.PrintAndLog("proxy-config", thisConfigEndpoint.RootOrMatchingDomain+" -> "+loadbalance.GetUpstreamsAsString(thisConfigEndpoint.ActiveOrigins)+" routing rule loaded", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -130,12 +131,18 @@ func RemoveReverseProxyConfig(endpoint string) error {
|
||||
func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
|
||||
//Default settings
|
||||
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
|
||||
ProxyType: dynamicproxy.ProxyType_Root,
|
||||
RootOrMatchingDomain: "/",
|
||||
Domain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
||||
RequireTLS: false,
|
||||
ProxyType: dynamicproxy.ProxyType_Root,
|
||||
RootOrMatchingDomain: "/",
|
||||
ActiveOrigins: []*loadbalance.Upstream{
|
||||
{
|
||||
OriginIpOrDomain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
||||
RequireTLS: false,
|
||||
SkipCertValidations: false,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
InactiveOrigins: []*loadbalance.Upstream{},
|
||||
BypassGlobalTLS: false,
|
||||
SkipCertValidations: false,
|
||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||
RequireBasicAuth: false,
|
||||
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
||||
|
@ -272,17 +272,14 @@ func HandleNewPasswordSetup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Delete the user account
|
||||
authAgent.UnregisterUser(username)
|
||||
|
||||
//Ok. Set the new password
|
||||
err = authAgent.CreateUserAccount(username, newPassword, "")
|
||||
if err != nil {
|
||||
// Un register the user account
|
||||
if err := authAgent.UnregisterUser(username); err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
//Ok. Set the new password
|
||||
if err := authAgent.CreateUserAccount(username, newPassword, ""); err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
66
src/go.mod
66
src/go.mod
@ -6,6 +6,7 @@ toolchain go1.22.2
|
||||
|
||||
require (
|
||||
github.com/boltdb/bolt v1.3.1
|
||||
github.com/docker/docker v27.0.0+incompatible
|
||||
github.com/go-acme/lego/v4 v4.16.1
|
||||
github.com/go-ping/ping v1.1.0
|
||||
github.com/google/uuid v1.6.0
|
||||
@ -14,13 +15,13 @@ require (
|
||||
github.com/grandcat/zeroconf v1.0.0
|
||||
github.com/likexian/whois v1.15.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
golang.org/x/net v0.23.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/text v0.15.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.20.1 // indirect
|
||||
cloud.google.com/go/compute v1.25.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||
@ -39,8 +40,8 @@ require (
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||
github.com/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
|
||||
@ -64,30 +65,37 @@ require (
|
||||
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/containerd/log v0.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/dnsimple/dnsimple-go v1.2.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/exoscale/egoscale v0.102.3 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-resty/resty/v2 v2.11.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/s2a-go v0.1.4 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.2 // indirect
|
||||
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
||||
github.com/gorilla/csrf v1.7.2 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
@ -111,8 +119,11 @@ require (
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect
|
||||
@ -125,9 +136,9 @@ require (
|
||||
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/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/ovh/go-ovh v1.4.3 // indirect
|
||||
github.com/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
|
||||
@ -142,8 +153,8 @@ require (
|
||||
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/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||
github.com/transip/gotransip/v6 v6.23.0 // indirect
|
||||
@ -153,22 +164,29 @@ require (
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
|
||||
go.opentelemetry.io/otel v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
||||
go.uber.org/ratelimit v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/oauth2 v0.16.0 // indirect
|
||||
golang.org/x/oauth2 v0.18.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/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
|
||||
google.golang.org/api v0.169.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.5.1 // indirect
|
||||
)
|
||||
|
173
src/go.sum
173
src/go.sum
@ -6,8 +6,8 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
|
||||
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
|
||||
cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
@ -33,6 +33,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
|
||||
@ -60,11 +62,11 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkM
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass=
|
||||
@ -135,18 +137,15 @@ github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpX
|
||||
github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
|
||||
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||
@ -161,10 +160,18 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/dnsimple/dnsimple-go v1.2.0 h1:ddTGyLVKly5HKb5L65AkLqFqwZlWo3WnR0BlFZlIddM=
|
||||
github.com/dnsimple/dnsimple-go v1.2.0/go.mod h1:z/cs26v/eiRvUyXsHQBLd8lWF8+cD6GbmkPH84plM4U=
|
||||
github.com/docker/docker v27.0.0+incompatible h1:JRugTYuelmWlW0M3jakcIadDx2HUoUO6+Tf2C5jVfwA=
|
||||
github.com/docker/docker v27.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@ -179,6 +186,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
@ -201,6 +210,11 @@ github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiK
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
|
||||
@ -226,7 +240,10 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
@ -256,8 +273,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@ -284,26 +301,24 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
||||
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
|
||||
github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
|
||||
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
|
||||
github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc=
|
||||
github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k=
|
||||
github.com/gophercloud/gophercloud v1.0.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c=
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15Kfovd8MTZrcana+UlQqNbOif8dLpA0=
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
|
||||
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
@ -320,9 +335,10 @@ github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfc
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
@ -345,7 +361,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
@ -382,6 +397,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
|
||||
@ -471,6 +487,10 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@ -478,10 +498,11 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo=
|
||||
github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk=
|
||||
@ -521,13 +542,13 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU=
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0=
|
||||
github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
@ -582,6 +603,7 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22/go.mod h1:fCa7OJZ/9DRTnOKmxvT
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
@ -615,8 +637,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
|
||||
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@ -626,9 +648,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
@ -649,14 +670,12 @@ github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4
|
||||
github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
|
||||
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
|
||||
github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f h1:cG+ehPRJSlqljSufLf1KXeXpUd1dLNjnzA18mZcB/O0=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 h1:2wzke3JH7OtN20WsNDZx2VH/TCmsbqtDEbXzjF+i05E=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997/go.mod h1:2CHKs/YGbCcNn/BPaCkEBwKz/FNCELi+MLILjR9RaTA=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@ -665,7 +684,23 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
|
||||
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
|
||||
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY=
|
||||
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
|
||||
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
|
||||
go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI=
|
||||
go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
|
||||
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
|
||||
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
@ -683,18 +718,16 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -714,6 +747,7 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@ -739,6 +773,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
@ -755,14 +790,14 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -828,16 +863,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@ -850,8 +885,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -879,8 +914,10 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
@ -896,14 +933,14 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o=
|
||||
google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw=
|
||||
google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY=
|
||||
google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@ -916,12 +953,12 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao=
|
||||
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@ -933,9 +970,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -948,8 +984,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@ -959,10 +995,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
@ -985,6 +1018,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
119
src/main.go
119
src/main.go
@ -12,24 +12,29 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/csrf"
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/acme"
|
||||
"imuslab.com/zoraxy/mod/auth"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/dockerux"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||
"imuslab.com/zoraxy/mod/email"
|
||||
"imuslab.com/zoraxy/mod/forwardproxy"
|
||||
"imuslab.com/zoraxy/mod/ganserv"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/info/logviewer"
|
||||
"imuslab.com/zoraxy/mod/mdns"
|
||||
"imuslab.com/zoraxy/mod/netstat"
|
||||
"imuslab.com/zoraxy/mod/pathrule"
|
||||
"imuslab.com/zoraxy/mod/sshprox"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||
"imuslab.com/zoraxy/mod/tcpprox"
|
||||
"imuslab.com/zoraxy/mod/streamproxy"
|
||||
"imuslab.com/zoraxy/mod/tlscert"
|
||||
"imuslab.com/zoraxy/mod/update"
|
||||
"imuslab.com/zoraxy/mod/uptime"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
"imuslab.com/zoraxy/mod/webserv"
|
||||
@ -44,17 +49,19 @@ var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transpo
|
||||
var mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
|
||||
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
||||
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||
var runningInDocker = flag.Bool("docker", false, "Run Zoraxy in docker compatibility mode")
|
||||
var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
||||
var acmeCertAutoRenewDays = flag.Int("earlyrenew", 30, "Number of days to early renew a soon expiring certificate (days)")
|
||||
var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
|
||||
var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
|
||||
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
|
||||
var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
|
||||
var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
|
||||
|
||||
var (
|
||||
name = "Zoraxy"
|
||||
version = "3.0.4"
|
||||
nodeUUID = "generic"
|
||||
development = false //Set this to false to use embedded web fs
|
||||
version = "3.1.1"
|
||||
nodeUUID = "generic" //System uuid, in uuidv4 format
|
||||
development = false //Set this to false to use embedded web fs
|
||||
bootTime = time.Now().Unix()
|
||||
|
||||
/*
|
||||
@ -66,29 +73,35 @@ var (
|
||||
/*
|
||||
Handler Modules
|
||||
*/
|
||||
sysdb *database.Database //System database
|
||||
authAgent *auth.AuthAgent //Authentication agent
|
||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||
accessController *access.Controller //Access controller, handle black list and white list
|
||||
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
||||
mdnsScanner *mdns.MDNSHost //mDNS discovery services
|
||||
ganManager *ganserv.NetworkManager //Global Area Network Manager
|
||||
webSshManager *sshprox.Manager //Web SSH connection service
|
||||
tcpProxyManager *tcpprox.Manager //TCP Proxy Manager
|
||||
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
|
||||
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
||||
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
||||
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
|
||||
sysdb *database.Database //System database
|
||||
authAgent *auth.AuthAgent //Authentication agent
|
||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||
webminPanelMux *http.ServeMux //Server mux for handling webmin panel APIs
|
||||
csrfMiddleware func(http.Handler) http.Handler //CSRF protection middleware
|
||||
|
||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||
accessController *access.Controller //Access controller, handle black list and white list
|
||||
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
||||
mdnsScanner *mdns.MDNSHost //mDNS discovery services
|
||||
ganManager *ganserv.NetworkManager //Global Area Network Manager
|
||||
webSshManager *sshprox.Manager //Web SSH connection service
|
||||
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
|
||||
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
|
||||
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
||||
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
||||
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
|
||||
loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing
|
||||
|
||||
//Helper modules
|
||||
EmailSender *email.Sender //Email sender that handle email sending
|
||||
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
||||
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
||||
EmailSender *email.Sender //Email sender that handle email sending
|
||||
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
||||
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
|
||||
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
||||
LogViewer *logviewer.Viewer
|
||||
)
|
||||
|
||||
// Kill signal handler. Do something before the system the core terminate.
|
||||
@ -103,32 +116,34 @@ func SetupCloseHandler() {
|
||||
}
|
||||
|
||||
func ShutdownSeq() {
|
||||
fmt.Println("- Shutting down " + name)
|
||||
fmt.Println("- Closing GeoDB ")
|
||||
geodbStore.Close()
|
||||
fmt.Println("- Closing Netstats Listener")
|
||||
SystemWideLogger.Println("Shutting down " + name)
|
||||
//SystemWideLogger.Println("Closing GeoDB")
|
||||
//geodbStore.Close()
|
||||
SystemWideLogger.Println("Closing Netstats Listener")
|
||||
netstatBuffers.Close()
|
||||
fmt.Println("- Closing Statistic Collector")
|
||||
SystemWideLogger.Println("Closing Statistic Collector")
|
||||
statisticCollector.Close()
|
||||
if mdnsTickerStop != nil {
|
||||
fmt.Println("- Stopping mDNS Discoverer (might take a few minutes)")
|
||||
SystemWideLogger.Println("Stopping mDNS Discoverer (might take a few minutes)")
|
||||
// Stop the mdns service
|
||||
mdnsTickerStop <- true
|
||||
}
|
||||
|
||||
mdnsScanner.Close()
|
||||
fmt.Println("- Closing Certificates Auto Renewer")
|
||||
SystemWideLogger.Println("Shutting down load balancer")
|
||||
loadBalancer.Close()
|
||||
SystemWideLogger.Println("Closing Certificates Auto Renewer")
|
||||
acmeAutoRenewer.Close()
|
||||
//Remove the tmp folder
|
||||
fmt.Println("- Cleaning up tmp files")
|
||||
SystemWideLogger.Println("Cleaning up tmp files")
|
||||
os.RemoveAll("./tmp")
|
||||
|
||||
fmt.Println("- Closing system wide logger")
|
||||
SystemWideLogger.Close()
|
||||
|
||||
//Close database, final
|
||||
fmt.Println("- Stopping system database")
|
||||
//Close database
|
||||
SystemWideLogger.Println("Stopping system database")
|
||||
sysdb.Close()
|
||||
|
||||
//Close logger
|
||||
SystemWideLogger.Println("Closing system wide logger")
|
||||
SystemWideLogger.Close()
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -139,6 +154,16 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if !utils.ValidateListeningAddress(*webUIPort) {
|
||||
fmt.Println("Malformed -port (listening address) paramter. Do you mean -port=:" + *webUIPort + "?")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *enableAutoUpdate {
|
||||
fmt.Println("Checking required config update")
|
||||
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version))
|
||||
}
|
||||
|
||||
SetupCloseHandler()
|
||||
|
||||
//Read or create the system uuid
|
||||
@ -154,12 +179,22 @@ func main() {
|
||||
}
|
||||
nodeUUID = string(uuidBytes)
|
||||
|
||||
//Create a new webmin mux and csrf middleware layer
|
||||
webminPanelMux = http.NewServeMux()
|
||||
csrfMiddleware = csrf.Protect(
|
||||
[]byte(nodeUUID),
|
||||
csrf.CookieName("zoraxy-csrf"),
|
||||
csrf.Secure(false),
|
||||
csrf.Path("/"),
|
||||
csrf.SameSite(csrf.SameSiteLaxMode),
|
||||
)
|
||||
|
||||
//Startup all modules
|
||||
startupSequence()
|
||||
|
||||
//Initiate management interface APIs
|
||||
requireAuth = !(*noauth)
|
||||
initAPIs()
|
||||
initAPIs(webminPanelMux)
|
||||
|
||||
//Start the reverse proxy server in go routine
|
||||
go func() {
|
||||
@ -172,7 +207,7 @@ func main() {
|
||||
finalSequence()
|
||||
|
||||
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
|
||||
err = http.ListenAndServe(*webUIPort, nil)
|
||||
err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -26,6 +25,7 @@ import (
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -68,25 +68,31 @@ type ACMEHandler struct {
|
||||
DefaultAcmeServer string
|
||||
Port string
|
||||
Database *database.Database
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
// NewACME creates a new ACMEHandler instance.
|
||||
func NewACME(acmeServer string, port string, database *database.Database) *ACMEHandler {
|
||||
func NewACME(defaultAcmeServer string, port string, database *database.Database, logger *logger.Logger) *ACMEHandler {
|
||||
return &ACMEHandler{
|
||||
DefaultAcmeServer: acmeServer,
|
||||
DefaultAcmeServer: defaultAcmeServer,
|
||||
Port: port,
|
||||
Database: database,
|
||||
Logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ACMEHandler) Logf(message string, err error) {
|
||||
a.Logger.PrintAndLog("ACME", message, err)
|
||||
}
|
||||
|
||||
// ObtainCert obtains a certificate for the specified domains.
|
||||
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...")
|
||||
a.Logf("Obtaining certificate for: "+strings.Join(domains, ", "), nil)
|
||||
|
||||
// generate private key
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Private key generation failed", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -102,7 +108,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
// skip TLS verify if need
|
||||
// Ref: https://github.com/go-acme/lego/blob/6af2c756ac73a9cb401621afca722d0f4112b1b8/lego/client_config.go#L74
|
||||
if skipTLS {
|
||||
log.Println("[INFO] Ignore TLS/SSL Verification Error for ACME Server")
|
||||
a.Logf("Ignoring TLS/SSL Verification Error for ACME Server", nil)
|
||||
config.HTTPClient.Transport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
@ -129,16 +135,16 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
|
||||
// if not custom ACME url, load it from ca.json
|
||||
if caName == "custom" {
|
||||
log.Println("[INFO] Using Custom ACME " + caUrl + " for CA Directory URL")
|
||||
a.Logf("Using Custom ACME "+caUrl+" for CA Directory URL", nil)
|
||||
} else {
|
||||
caLinkOverwrite, err := loadCAApiServerFromName(caName)
|
||||
if err == nil {
|
||||
config.CADirURL = caLinkOverwrite
|
||||
log.Println("[INFO] Using " + caLinkOverwrite + " for CA Directory URL")
|
||||
a.Logf("Using "+caLinkOverwrite+" for CA Directory URL", nil)
|
||||
} else {
|
||||
// (caName == "" || caUrl == "") will use default acme
|
||||
config.CADirURL = a.DefaultAcmeServer
|
||||
log.Println("[INFO] Using Default ACME " + a.DefaultAcmeServer + " for CA Directory URL")
|
||||
a.Logf("Using Default ACME "+a.DefaultAcmeServer+" for CA Directory URL", nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,7 +152,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Failed to spawn new ACME client from current config", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -164,32 +170,32 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
var dnsCredentials string
|
||||
err := a.Database.Read("acme", certificateName+"_dns_credentials", &dnsCredentials)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Read DNS credential failed", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
var dnsProvider string
|
||||
err = a.Database.Read("acme", certificateName+"_dns_provider", &dnsProvider)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Read DNS Provider failed", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
provider, err := GetDnsChallengeProviderByName(dnsProvider, dnsCredentials)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Unable to resolve DNS challenge provider", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = client.Challenge.SetDNS01Provider(provider)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Failed to resolve DNS01 Provider", err)
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Failed to resolve HTTP01 Provider", err)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
@ -205,7 +211,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
var reg *registration.Resource
|
||||
// New users will need to register
|
||||
if client.GetExternalAccountRequired() {
|
||||
log.Println("External Account Required for this ACME Provider.")
|
||||
a.Logf("External Account Required for this ACME Provider", nil)
|
||||
// IF KID and HmacEncoded is overidden
|
||||
|
||||
if !a.Database.TableExists("acme") {
|
||||
@ -220,20 +226,18 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
var kid string
|
||||
var hmacEncoded string
|
||||
err := a.Database.Read("acme", config.CADirURL+"_kid", &kid)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Failed to read kid from database", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = a.Database.Read("acme", config.CADirURL+"_hmacEncoded", &hmacEncoded)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Failed to read HMAC from database", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
log.Println("EAB Credential retrieved.", kid, hmacEncoded)
|
||||
a.Logf("EAB Credential retrieved: "+kid+" / "+hmacEncoded, nil)
|
||||
if kid != "" && hmacEncoded != "" {
|
||||
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
@ -242,14 +246,14 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Register with external account binder failed", err)
|
||||
return false, err
|
||||
}
|
||||
//return false, errors.New("External Account Required for this ACME Provider.")
|
||||
} else {
|
||||
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Unable to register client", err)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
@ -262,7 +266,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
}
|
||||
certificates, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Obtain certificate failed", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -270,12 +274,12 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
// private key, and a certificate URL.
|
||||
err = os.WriteFile("./conf/certs/"+certificateName+".pem", certificates.Certificate, 0777)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Failed to write public key to disk", err)
|
||||
return false, err
|
||||
}
|
||||
err = os.WriteFile("./conf/certs/"+certificateName+".key", certificates.PrivateKey, 0777)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Failed to write private key to disk", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -289,13 +293,13 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
|
||||
certInfoBytes, err := json.Marshal(certInfo)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Marshal certificate renew config failed", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = os.WriteFile("./conf/certs/"+certificateName+".json", certInfoBytes, 0777)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Failed to write certificate renew config to file", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -313,7 +317,7 @@ func (a *ACMEHandler) CheckCertificate() []string {
|
||||
expiredCerts := []string{}
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.Logf("Failed to load certificate folder", err)
|
||||
return []string{}
|
||||
}
|
||||
|
||||
@ -410,14 +414,14 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
ca, err := utils.PostPara(r, "ca")
|
||||
if err != nil {
|
||||
log.Println("[INFO] CA not set. Using default")
|
||||
a.Logf("CA not set. Using default", nil)
|
||||
ca, caUrl = "", ""
|
||||
}
|
||||
|
||||
if ca == "custom" {
|
||||
caUrl, err = utils.PostPara(r, "caURL")
|
||||
if err != nil {
|
||||
log.Println("[INFO] Custom CA set but no URL provide, Using default")
|
||||
a.Logf("Custom CA set but no URL provide, Using default", nil)
|
||||
ca, caUrl = "", ""
|
||||
}
|
||||
}
|
||||
@ -448,7 +452,12 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
domains := strings.Split(domainPara, ",")
|
||||
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS, dns)
|
||||
//Clean spaces in front or behind each domain
|
||||
cleanedDomains := []string{}
|
||||
for _, domain := range domains {
|
||||
cleanedDomains = append(cleanedDomains, strings.TrimSpace(domain))
|
||||
}
|
||||
result, err := a.ObtainCert(cleanedDomains, filename, email, ca, caUrl, skipTLS, dns)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||
return
|
||||
@ -460,7 +469,7 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
||||
func jsonEscape(i string) string {
|
||||
b, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
log.Println("Unable to escape json data: " + err.Error())
|
||||
//log.Println("Unable to escape json data: " + err.Error())
|
||||
return i
|
||||
}
|
||||
s := string(b)
|
||||
|
@ -1,11 +1,6 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||
)
|
||||
@ -29,7 +24,7 @@ func GetDnsChallengeProviderByName(dnsProvider string, dnsCredentials string) (c
|
||||
/*
|
||||
Original implementation of DNS ACME using OS.Env as payload
|
||||
*/
|
||||
|
||||
/*
|
||||
func setCredentialsIntoEnvironmentVariables(credentials map[string]string) {
|
||||
for key, value := range credentials {
|
||||
err := os.Setenv(key, value)
|
||||
@ -41,6 +36,7 @@ func setCredentialsIntoEnvironmentVariables(credentials map[string]string) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func extractDnsCredentials(input string) (map[string]string, error) {
|
||||
result := make(map[string]string)
|
||||
|
||||
@ -70,3 +66,5 @@ func extractDnsCredentials(input string) (map[string]string, error) {
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
*/
|
||||
|
@ -6,6 +6,7 @@ package acmedns
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||
@ -142,6 +143,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return alidns.NewDNSProviderConfig(cfg)
|
||||
case "allinkl":
|
||||
cfg := allinkl.NewDefaultConfig()
|
||||
@ -149,6 +151,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return allinkl.NewDNSProviderConfig(cfg)
|
||||
case "arvancloud":
|
||||
cfg := arvancloud.NewDefaultConfig()
|
||||
@ -156,6 +159,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return arvancloud.NewDNSProviderConfig(cfg)
|
||||
case "auroradns":
|
||||
cfg := auroradns.NewDefaultConfig()
|
||||
@ -163,6 +167,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return auroradns.NewDNSProviderConfig(cfg)
|
||||
case "autodns":
|
||||
cfg := autodns.NewDefaultConfig()
|
||||
@ -170,6 +175,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return autodns.NewDNSProviderConfig(cfg)
|
||||
case "azure":
|
||||
cfg := azure.NewDefaultConfig()
|
||||
@ -177,6 +183,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return azure.NewDNSProviderConfig(cfg)
|
||||
case "azuredns":
|
||||
cfg := azuredns.NewDefaultConfig()
|
||||
@ -184,6 +191,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return azuredns.NewDNSProviderConfig(cfg)
|
||||
case "bindman":
|
||||
cfg := bindman.NewDefaultConfig()
|
||||
@ -191,6 +199,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return bindman.NewDNSProviderConfig(cfg)
|
||||
case "bluecat":
|
||||
cfg := bluecat.NewDefaultConfig()
|
||||
@ -198,6 +207,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return bluecat.NewDNSProviderConfig(cfg)
|
||||
case "brandit":
|
||||
cfg := brandit.NewDefaultConfig()
|
||||
@ -205,6 +215,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return brandit.NewDNSProviderConfig(cfg)
|
||||
case "bunny":
|
||||
cfg := bunny.NewDefaultConfig()
|
||||
@ -212,6 +223,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return bunny.NewDNSProviderConfig(cfg)
|
||||
case "checkdomain":
|
||||
cfg := checkdomain.NewDefaultConfig()
|
||||
@ -219,6 +231,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return checkdomain.NewDNSProviderConfig(cfg)
|
||||
case "civo":
|
||||
cfg := civo.NewDefaultConfig()
|
||||
@ -226,6 +239,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return civo.NewDNSProviderConfig(cfg)
|
||||
case "clouddns":
|
||||
cfg := clouddns.NewDefaultConfig()
|
||||
@ -233,6 +247,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return clouddns.NewDNSProviderConfig(cfg)
|
||||
case "cloudflare":
|
||||
cfg := cloudflare.NewDefaultConfig()
|
||||
@ -240,6 +255,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return cloudflare.NewDNSProviderConfig(cfg)
|
||||
case "cloudns":
|
||||
cfg := cloudns.NewDefaultConfig()
|
||||
@ -247,6 +263,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return cloudns.NewDNSProviderConfig(cfg)
|
||||
case "cloudru":
|
||||
cfg := cloudru.NewDefaultConfig()
|
||||
@ -254,6 +271,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return cloudru.NewDNSProviderConfig(cfg)
|
||||
case "cloudxns":
|
||||
cfg := cloudxns.NewDefaultConfig()
|
||||
@ -261,6 +279,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return cloudxns.NewDNSProviderConfig(cfg)
|
||||
case "conoha":
|
||||
cfg := conoha.NewDefaultConfig()
|
||||
@ -268,6 +287,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return conoha.NewDNSProviderConfig(cfg)
|
||||
case "constellix":
|
||||
cfg := constellix.NewDefaultConfig()
|
||||
@ -275,6 +295,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return constellix.NewDNSProviderConfig(cfg)
|
||||
case "cpanel":
|
||||
cfg := cpanel.NewDefaultConfig()
|
||||
@ -282,6 +303,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return cpanel.NewDNSProviderConfig(cfg)
|
||||
case "derak":
|
||||
cfg := derak.NewDefaultConfig()
|
||||
@ -289,6 +311,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return derak.NewDNSProviderConfig(cfg)
|
||||
case "desec":
|
||||
cfg := desec.NewDefaultConfig()
|
||||
@ -296,6 +319,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return desec.NewDNSProviderConfig(cfg)
|
||||
case "digitalocean":
|
||||
cfg := digitalocean.NewDefaultConfig()
|
||||
@ -303,6 +327,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return digitalocean.NewDNSProviderConfig(cfg)
|
||||
case "dnshomede":
|
||||
cfg := dnshomede.NewDefaultConfig()
|
||||
@ -310,6 +335,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return dnshomede.NewDNSProviderConfig(cfg)
|
||||
case "dnsimple":
|
||||
cfg := dnsimple.NewDefaultConfig()
|
||||
@ -317,6 +343,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return dnsimple.NewDNSProviderConfig(cfg)
|
||||
case "dnsmadeeasy":
|
||||
cfg := dnsmadeeasy.NewDefaultConfig()
|
||||
@ -324,6 +351,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return dnsmadeeasy.NewDNSProviderConfig(cfg)
|
||||
case "dnspod":
|
||||
cfg := dnspod.NewDefaultConfig()
|
||||
@ -331,6 +359,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return dnspod.NewDNSProviderConfig(cfg)
|
||||
case "dode":
|
||||
cfg := dode.NewDefaultConfig()
|
||||
@ -338,6 +367,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return dode.NewDNSProviderConfig(cfg)
|
||||
case "domeneshop":
|
||||
cfg := domeneshop.NewDefaultConfig()
|
||||
@ -345,6 +375,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return domeneshop.NewDNSProviderConfig(cfg)
|
||||
case "dreamhost":
|
||||
cfg := dreamhost.NewDefaultConfig()
|
||||
@ -352,6 +383,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return dreamhost.NewDNSProviderConfig(cfg)
|
||||
case "duckdns":
|
||||
cfg := duckdns.NewDefaultConfig()
|
||||
@ -359,6 +391,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return duckdns.NewDNSProviderConfig(cfg)
|
||||
case "dyn":
|
||||
cfg := dyn.NewDefaultConfig()
|
||||
@ -366,6 +399,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return dyn.NewDNSProviderConfig(cfg)
|
||||
case "dynu":
|
||||
cfg := dynu.NewDefaultConfig()
|
||||
@ -373,6 +407,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return dynu.NewDNSProviderConfig(cfg)
|
||||
case "easydns":
|
||||
cfg := easydns.NewDefaultConfig()
|
||||
@ -380,6 +415,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return easydns.NewDNSProviderConfig(cfg)
|
||||
case "efficientip":
|
||||
cfg := efficientip.NewDefaultConfig()
|
||||
@ -387,6 +423,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return efficientip.NewDNSProviderConfig(cfg)
|
||||
case "epik":
|
||||
cfg := epik.NewDefaultConfig()
|
||||
@ -394,6 +431,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return epik.NewDNSProviderConfig(cfg)
|
||||
case "exoscale":
|
||||
cfg := exoscale.NewDefaultConfig()
|
||||
@ -401,6 +439,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return exoscale.NewDNSProviderConfig(cfg)
|
||||
case "freemyip":
|
||||
cfg := freemyip.NewDefaultConfig()
|
||||
@ -408,6 +447,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return freemyip.NewDNSProviderConfig(cfg)
|
||||
case "gandi":
|
||||
cfg := gandi.NewDefaultConfig()
|
||||
@ -415,6 +455,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return gandi.NewDNSProviderConfig(cfg)
|
||||
case "gandiv5":
|
||||
cfg := gandiv5.NewDefaultConfig()
|
||||
@ -422,6 +463,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return gandiv5.NewDNSProviderConfig(cfg)
|
||||
case "gcore":
|
||||
cfg := gcore.NewDefaultConfig()
|
||||
@ -429,6 +471,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return gcore.NewDNSProviderConfig(cfg)
|
||||
case "glesys":
|
||||
cfg := glesys.NewDefaultConfig()
|
||||
@ -436,6 +479,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return glesys.NewDNSProviderConfig(cfg)
|
||||
case "godaddy":
|
||||
cfg := godaddy.NewDefaultConfig()
|
||||
@ -443,6 +487,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return godaddy.NewDNSProviderConfig(cfg)
|
||||
case "googledomains":
|
||||
cfg := googledomains.NewDefaultConfig()
|
||||
@ -450,6 +495,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return googledomains.NewDNSProviderConfig(cfg)
|
||||
case "hetzner":
|
||||
cfg := hetzner.NewDefaultConfig()
|
||||
@ -457,6 +503,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return hetzner.NewDNSProviderConfig(cfg)
|
||||
case "hostingde":
|
||||
cfg := hostingde.NewDefaultConfig()
|
||||
@ -464,6 +511,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return hostingde.NewDNSProviderConfig(cfg)
|
||||
case "hosttech":
|
||||
cfg := hosttech.NewDefaultConfig()
|
||||
@ -471,6 +519,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return hosttech.NewDNSProviderConfig(cfg)
|
||||
case "httpnet":
|
||||
cfg := httpnet.NewDefaultConfig()
|
||||
@ -478,6 +527,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return httpnet.NewDNSProviderConfig(cfg)
|
||||
case "hyperone":
|
||||
cfg := hyperone.NewDefaultConfig()
|
||||
@ -485,6 +535,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return hyperone.NewDNSProviderConfig(cfg)
|
||||
case "ibmcloud":
|
||||
cfg := ibmcloud.NewDefaultConfig()
|
||||
@ -492,6 +543,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return ibmcloud.NewDNSProviderConfig(cfg)
|
||||
case "iij":
|
||||
cfg := iij.NewDefaultConfig()
|
||||
@ -499,6 +551,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return iij.NewDNSProviderConfig(cfg)
|
||||
case "iijdpf":
|
||||
cfg := iijdpf.NewDefaultConfig()
|
||||
@ -506,6 +559,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return iijdpf.NewDNSProviderConfig(cfg)
|
||||
case "infoblox":
|
||||
cfg := infoblox.NewDefaultConfig()
|
||||
@ -513,6 +567,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return infoblox.NewDNSProviderConfig(cfg)
|
||||
case "infomaniak":
|
||||
cfg := infomaniak.NewDefaultConfig()
|
||||
@ -520,6 +575,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return infomaniak.NewDNSProviderConfig(cfg)
|
||||
case "internetbs":
|
||||
cfg := internetbs.NewDefaultConfig()
|
||||
@ -527,6 +583,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return internetbs.NewDNSProviderConfig(cfg)
|
||||
case "inwx":
|
||||
cfg := inwx.NewDefaultConfig()
|
||||
@ -534,6 +591,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return inwx.NewDNSProviderConfig(cfg)
|
||||
case "ionos":
|
||||
cfg := ionos.NewDefaultConfig()
|
||||
@ -541,6 +599,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return ionos.NewDNSProviderConfig(cfg)
|
||||
case "ipv64":
|
||||
cfg := ipv64.NewDefaultConfig()
|
||||
@ -548,6 +607,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return ipv64.NewDNSProviderConfig(cfg)
|
||||
case "iwantmyname":
|
||||
cfg := iwantmyname.NewDefaultConfig()
|
||||
@ -555,6 +615,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return iwantmyname.NewDNSProviderConfig(cfg)
|
||||
case "joker":
|
||||
cfg := joker.NewDefaultConfig()
|
||||
@ -562,6 +623,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return joker.NewDNSProviderConfig(cfg)
|
||||
case "liara":
|
||||
cfg := liara.NewDefaultConfig()
|
||||
@ -569,6 +631,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return liara.NewDNSProviderConfig(cfg)
|
||||
case "lightsail":
|
||||
cfg := lightsail.NewDefaultConfig()
|
||||
@ -576,6 +639,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return lightsail.NewDNSProviderConfig(cfg)
|
||||
case "linode":
|
||||
cfg := linode.NewDefaultConfig()
|
||||
@ -583,6 +647,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return linode.NewDNSProviderConfig(cfg)
|
||||
case "liquidweb":
|
||||
cfg := liquidweb.NewDefaultConfig()
|
||||
@ -590,6 +655,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return liquidweb.NewDNSProviderConfig(cfg)
|
||||
case "loopia":
|
||||
cfg := loopia.NewDefaultConfig()
|
||||
@ -597,6 +663,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return loopia.NewDNSProviderConfig(cfg)
|
||||
case "luadns":
|
||||
cfg := luadns.NewDefaultConfig()
|
||||
@ -604,6 +671,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return luadns.NewDNSProviderConfig(cfg)
|
||||
case "mailinabox":
|
||||
cfg := mailinabox.NewDefaultConfig()
|
||||
@ -611,6 +679,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return mailinabox.NewDNSProviderConfig(cfg)
|
||||
case "metaname":
|
||||
cfg := metaname.NewDefaultConfig()
|
||||
@ -618,6 +687,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return metaname.NewDNSProviderConfig(cfg)
|
||||
case "mydnsjp":
|
||||
cfg := mydnsjp.NewDefaultConfig()
|
||||
@ -625,6 +695,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return mydnsjp.NewDNSProviderConfig(cfg)
|
||||
case "namecheap":
|
||||
cfg := namecheap.NewDefaultConfig()
|
||||
@ -632,6 +703,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return namecheap.NewDNSProviderConfig(cfg)
|
||||
case "namedotcom":
|
||||
cfg := namedotcom.NewDefaultConfig()
|
||||
@ -639,6 +711,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return namedotcom.NewDNSProviderConfig(cfg)
|
||||
case "namesilo":
|
||||
cfg := namesilo.NewDefaultConfig()
|
||||
@ -646,6 +719,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return namesilo.NewDNSProviderConfig(cfg)
|
||||
case "nearlyfreespeech":
|
||||
cfg := nearlyfreespeech.NewDefaultConfig()
|
||||
@ -653,20 +727,23 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
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 "netcup":
|
||||
cfg := netcup.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 20*time.Minute
|
||||
return netcup.NewDNSProviderConfig(cfg)
|
||||
case "netlify":
|
||||
cfg := netlify.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return netlify.NewDNSProviderConfig(cfg)
|
||||
case "nicmanager":
|
||||
cfg := nicmanager.NewDefaultConfig()
|
||||
@ -674,6 +751,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return nicmanager.NewDNSProviderConfig(cfg)
|
||||
case "nifcloud":
|
||||
cfg := nifcloud.NewDefaultConfig()
|
||||
@ -681,6 +759,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return nifcloud.NewDNSProviderConfig(cfg)
|
||||
case "njalla":
|
||||
cfg := njalla.NewDefaultConfig()
|
||||
@ -688,6 +767,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return njalla.NewDNSProviderConfig(cfg)
|
||||
case "nodion":
|
||||
cfg := nodion.NewDefaultConfig()
|
||||
@ -695,6 +775,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return nodion.NewDNSProviderConfig(cfg)
|
||||
case "ns1":
|
||||
cfg := ns1.NewDefaultConfig()
|
||||
@ -702,6 +783,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return ns1.NewDNSProviderConfig(cfg)
|
||||
case "otc":
|
||||
cfg := otc.NewDefaultConfig()
|
||||
@ -709,6 +791,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return otc.NewDNSProviderConfig(cfg)
|
||||
case "ovh":
|
||||
cfg := ovh.NewDefaultConfig()
|
||||
@ -716,6 +799,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return ovh.NewDNSProviderConfig(cfg)
|
||||
case "pdns":
|
||||
cfg := pdns.NewDefaultConfig()
|
||||
@ -723,6 +807,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return pdns.NewDNSProviderConfig(cfg)
|
||||
case "plesk":
|
||||
cfg := plesk.NewDefaultConfig()
|
||||
@ -730,6 +815,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return plesk.NewDNSProviderConfig(cfg)
|
||||
case "porkbun":
|
||||
cfg := porkbun.NewDefaultConfig()
|
||||
@ -737,6 +823,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return porkbun.NewDNSProviderConfig(cfg)
|
||||
case "rackspace":
|
||||
cfg := rackspace.NewDefaultConfig()
|
||||
@ -744,6 +831,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return rackspace.NewDNSProviderConfig(cfg)
|
||||
case "rcodezero":
|
||||
cfg := rcodezero.NewDefaultConfig()
|
||||
@ -751,6 +839,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return rcodezero.NewDNSProviderConfig(cfg)
|
||||
case "regru":
|
||||
cfg := regru.NewDefaultConfig()
|
||||
@ -758,6 +847,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return regru.NewDNSProviderConfig(cfg)
|
||||
case "rfc2136":
|
||||
cfg := rfc2136.NewDefaultConfig()
|
||||
@ -765,6 +855,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return rfc2136.NewDNSProviderConfig(cfg)
|
||||
case "rimuhosting":
|
||||
cfg := rimuhosting.NewDefaultConfig()
|
||||
@ -772,6 +863,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return rimuhosting.NewDNSProviderConfig(cfg)
|
||||
case "route53":
|
||||
cfg := route53.NewDefaultConfig()
|
||||
@ -779,6 +871,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return route53.NewDNSProviderConfig(cfg)
|
||||
case "safedns":
|
||||
cfg := safedns.NewDefaultConfig()
|
||||
@ -786,6 +879,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return safedns.NewDNSProviderConfig(cfg)
|
||||
case "sakuracloud":
|
||||
cfg := sakuracloud.NewDefaultConfig()
|
||||
@ -793,6 +887,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return sakuracloud.NewDNSProviderConfig(cfg)
|
||||
case "scaleway":
|
||||
cfg := scaleway.NewDefaultConfig()
|
||||
@ -800,6 +895,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return scaleway.NewDNSProviderConfig(cfg)
|
||||
case "selectel":
|
||||
cfg := selectel.NewDefaultConfig()
|
||||
@ -807,6 +903,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return selectel.NewDNSProviderConfig(cfg)
|
||||
case "servercow":
|
||||
cfg := servercow.NewDefaultConfig()
|
||||
@ -814,6 +911,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return servercow.NewDNSProviderConfig(cfg)
|
||||
case "shellrent":
|
||||
cfg := shellrent.NewDefaultConfig()
|
||||
@ -821,6 +919,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return shellrent.NewDNSProviderConfig(cfg)
|
||||
case "simply":
|
||||
cfg := simply.NewDefaultConfig()
|
||||
@ -828,6 +927,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return simply.NewDNSProviderConfig(cfg)
|
||||
case "sonic":
|
||||
cfg := sonic.NewDefaultConfig()
|
||||
@ -835,6 +935,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return sonic.NewDNSProviderConfig(cfg)
|
||||
case "stackpath":
|
||||
cfg := stackpath.NewDefaultConfig()
|
||||
@ -842,6 +943,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return stackpath.NewDNSProviderConfig(cfg)
|
||||
case "tencentcloud":
|
||||
cfg := tencentcloud.NewDefaultConfig()
|
||||
@ -849,6 +951,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return tencentcloud.NewDNSProviderConfig(cfg)
|
||||
case "transip":
|
||||
cfg := transip.NewDefaultConfig()
|
||||
@ -856,6 +959,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return transip.NewDNSProviderConfig(cfg)
|
||||
case "ultradns":
|
||||
cfg := ultradns.NewDefaultConfig()
|
||||
@ -863,6 +967,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return ultradns.NewDNSProviderConfig(cfg)
|
||||
case "variomedia":
|
||||
cfg := variomedia.NewDefaultConfig()
|
||||
@ -870,6 +975,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return variomedia.NewDNSProviderConfig(cfg)
|
||||
case "vegadns":
|
||||
cfg := vegadns.NewDefaultConfig()
|
||||
@ -877,6 +983,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return vegadns.NewDNSProviderConfig(cfg)
|
||||
case "vercel":
|
||||
cfg := vercel.NewDefaultConfig()
|
||||
@ -884,6 +991,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return vercel.NewDNSProviderConfig(cfg)
|
||||
case "versio":
|
||||
cfg := versio.NewDefaultConfig()
|
||||
@ -891,6 +999,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return versio.NewDNSProviderConfig(cfg)
|
||||
case "vinyldns":
|
||||
cfg := vinyldns.NewDefaultConfig()
|
||||
@ -898,6 +1007,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return vinyldns.NewDNSProviderConfig(cfg)
|
||||
case "vkcloud":
|
||||
cfg := vkcloud.NewDefaultConfig()
|
||||
@ -905,6 +1015,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return vkcloud.NewDNSProviderConfig(cfg)
|
||||
case "vscale":
|
||||
cfg := vscale.NewDefaultConfig()
|
||||
@ -912,6 +1023,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return vscale.NewDNSProviderConfig(cfg)
|
||||
case "vultr":
|
||||
cfg := vultr.NewDefaultConfig()
|
||||
@ -919,6 +1031,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return vultr.NewDNSProviderConfig(cfg)
|
||||
case "webnames":
|
||||
cfg := webnames.NewDefaultConfig()
|
||||
@ -926,6 +1039,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return webnames.NewDNSProviderConfig(cfg)
|
||||
case "websupport":
|
||||
cfg := websupport.NewDefaultConfig()
|
||||
@ -933,6 +1047,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return websupport.NewDNSProviderConfig(cfg)
|
||||
case "wedos":
|
||||
cfg := wedos.NewDefaultConfig()
|
||||
@ -940,6 +1055,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return wedos.NewDNSProviderConfig(cfg)
|
||||
case "yandex":
|
||||
cfg := yandex.NewDefaultConfig()
|
||||
@ -947,6 +1063,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return yandex.NewDNSProviderConfig(cfg)
|
||||
case "yandex360":
|
||||
cfg := yandex360.NewDefaultConfig()
|
||||
@ -954,6 +1071,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return yandex360.NewDNSProviderConfig(cfg)
|
||||
case "yandexcloud":
|
||||
cfg := yandexcloud.NewDefaultConfig()
|
||||
@ -961,6 +1079,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return yandexcloud.NewDNSProviderConfig(cfg)
|
||||
case "zoneee":
|
||||
cfg := zoneee.NewDefaultConfig()
|
||||
@ -968,6 +1087,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return zoneee.NewDNSProviderConfig(cfg)
|
||||
case "zonomi":
|
||||
cfg := zonomi.NewDefaultConfig()
|
||||
@ -975,6 +1095,7 @@ func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.PropagationTimeout = 5*time.Minute
|
||||
return zonomi.NewDNSProviderConfig(cfg)
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized DNS provider: %s", name)
|
||||
|
@ -153,6 +153,10 @@
|
||||
"azure": {
|
||||
"Name": "azure",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "ZoneName",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "ClientID",
|
||||
"Datatype": "string"
|
||||
@ -208,6 +212,10 @@
|
||||
"azuredns": {
|
||||
"Name": "azuredns",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "ZoneName",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "SubscriptionID",
|
||||
"Datatype": "string"
|
||||
@ -343,6 +351,10 @@
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
},
|
||||
{
|
||||
"Title": "SkipDeploy",
|
||||
"Datatype": "bool"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1214,10 +1226,6 @@
|
||||
"gandi": {
|
||||
"Name": "gandi",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "BaseURL",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "APIKey",
|
||||
"Datatype": "string"
|
||||
@ -1242,15 +1250,28 @@
|
||||
"Name": "gandiv5",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "fieldName",
|
||||
"Title": "APIKey",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "authZone",
|
||||
"Title": "PersonalAccessToken",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": []
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gcore": {
|
||||
"Name": "gcore",
|
||||
@ -2063,35 +2084,40 @@
|
||||
"Name": "namecheap",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "domain",
|
||||
"Title": "Debug",
|
||||
"Datatype": "bool"
|
||||
},
|
||||
{
|
||||
"Title": "BaseURL",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "key",
|
||||
"Title": "APIUser",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "keyFqdn",
|
||||
"Title": "APIKey",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "keyValue",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "tld",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "sld",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "host",
|
||||
"Title": "ClientIP",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": []
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
"namedotcom": {
|
||||
"Name": "namedotcom",
|
||||
@ -2418,26 +2444,38 @@
|
||||
"Name": "ovh",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "FieldType",
|
||||
"Title": "APIEndpoint",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "SubDomain",
|
||||
"Title": "ApplicationKey",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "Target",
|
||||
"Title": "ApplicationSecret",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "Zone",
|
||||
"Title": "ConsumerKey",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "ID",
|
||||
"Datatype": "int64"
|
||||
"Title": "OAuth2Config",
|
||||
"Datatype": "*OAuth2Config"
|
||||
},
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -2875,15 +2913,28 @@
|
||||
"Name": "shellrent",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "domainID",
|
||||
"Datatype": "int"
|
||||
"Title": "Username",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "recordID",
|
||||
"Datatype": "int"
|
||||
"Title": "Token",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": []
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
"simply": {
|
||||
"Name": "simply",
|
||||
@ -3034,15 +3085,28 @@
|
||||
},
|
||||
"ultradns": {
|
||||
"Name": "ultradns",
|
||||
"ConfigableFields": [],
|
||||
"HiddenFields": [
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "config",
|
||||
"Datatype": "*Config"
|
||||
"Title": "Username",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "client",
|
||||
"Datatype": "*client.Client"
|
||||
"Title": "Password",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "Endpoint",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -75,6 +75,15 @@ func HandleGuidedStepCheck(w http.ResponseWriter, r *http.Request) {
|
||||
httpServerReachable := isHTTPServerAvailable(domain)
|
||||
js, _ := json.Marshal(httpServerReachable)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else if stepNo == 10 {
|
||||
//Resolve public Ip address for tour
|
||||
publicIp, err := getPublicIPAddress()
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
js, _ := json.Marshal(publicIp)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid step number")
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"os"
|
||||
@ -12,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -34,7 +34,9 @@ type AutoRenewer struct {
|
||||
AcmeHandler *ACMEHandler
|
||||
RenewerConfig *AutoRenewConfig
|
||||
RenewTickInterval int64
|
||||
EarlyRenewDays int //How many days before cert expire to renew certificate
|
||||
TickerstopChan chan bool
|
||||
Logger *logger.Logger //System wide logger
|
||||
}
|
||||
|
||||
type ExpiredCerts struct {
|
||||
@ -44,11 +46,15 @@ type ExpiredCerts struct {
|
||||
|
||||
// Create an auto renew agent, require config filepath and auto scan & renew interval (seconds)
|
||||
// Set renew check interval to 0 for auto (1 day)
|
||||
func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64, AcmeHandler *ACMEHandler) (*AutoRenewer, error) {
|
||||
func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64, earlyRenewDays int, AcmeHandler *ACMEHandler, logger *logger.Logger) (*AutoRenewer, error) {
|
||||
if renewCheckInterval == 0 {
|
||||
renewCheckInterval = 86400 //1 day
|
||||
}
|
||||
|
||||
if earlyRenewDays == 0 {
|
||||
earlyRenewDays = 30
|
||||
}
|
||||
|
||||
//Load the config file. If not found, create one
|
||||
if !utils.FileExists(config) {
|
||||
//Create one
|
||||
@ -82,6 +88,7 @@ func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64,
|
||||
AcmeHandler: AcmeHandler,
|
||||
RenewerConfig: &renewerConfig,
|
||||
RenewTickInterval: renewCheckInterval,
|
||||
Logger: logger,
|
||||
}
|
||||
|
||||
if thisRenewer.RenewerConfig.Enabled {
|
||||
@ -95,6 +102,10 @@ func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64,
|
||||
return &thisRenewer, nil
|
||||
}
|
||||
|
||||
func (a *AutoRenewer) Logf(message string, err error) {
|
||||
a.Logger.PrintAndLog("CertRenew", message, err)
|
||||
}
|
||||
|
||||
func (a *AutoRenewer) StartAutoRenewTicker() {
|
||||
//Stop the previous ticker if still running
|
||||
if a.TickerstopChan != nil {
|
||||
@ -113,7 +124,7 @@ func (a *AutoRenewer) StartAutoRenewTicker() {
|
||||
case <-done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
log.Println("Check and renew certificates in progress")
|
||||
a.Logf("Check and renew certificates in progress", nil)
|
||||
a.CheckAndRenewCertificates()
|
||||
}
|
||||
}
|
||||
@ -135,7 +146,7 @@ func (a *AutoRenewer) StopAutoRenewTicker() {
|
||||
// opr = setSelected -> Enter a list of file names (or matching rules) for auto renew
|
||||
// opr = setAuto -> Set to use auto detect certificates and renew
|
||||
func (a *AutoRenewer) HandleSetAutoRenewDomains(w http.ResponseWriter, r *http.Request) {
|
||||
opr, err := utils.GetPara(r, "opr")
|
||||
opr, err := utils.PostPara(r, "opr")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Operation not set")
|
||||
return
|
||||
@ -165,6 +176,8 @@ func (a *AutoRenewer) HandleSetAutoRenewDomains(w http.ResponseWriter, r *http.R
|
||||
a.RenewerConfig.RenewAll = true
|
||||
a.saveRenewConfigToFile()
|
||||
utils.SendOK(w)
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid operation given")
|
||||
}
|
||||
|
||||
}
|
||||
@ -208,42 +221,52 @@ func (a *AutoRenewer) HandleRenewNow(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
// HandleAutoRenewEnable get and set the auto renew enable state
|
||||
func (a *AutoRenewer) HandleAutoRenewEnable(w http.ResponseWriter, r *http.Request) {
|
||||
val, err := utils.PostPara(r, "enable")
|
||||
if err != nil {
|
||||
if r.Method == http.MethodGet {
|
||||
js, _ := json.Marshal(a.RenewerConfig.Enabled)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
if val == "true" {
|
||||
} else if r.Method == http.MethodPost {
|
||||
val, err := utils.PostBool(r, "enable")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty enable state")
|
||||
}
|
||||
if val {
|
||||
//Check if the email is not empty
|
||||
if a.RenewerConfig.Email == "" {
|
||||
utils.SendErrorResponse(w, "Email is not set")
|
||||
return
|
||||
}
|
||||
|
||||
a.RenewerConfig.Enabled = true
|
||||
a.saveRenewConfigToFile()
|
||||
log.Println("[ACME] ACME auto renew enabled")
|
||||
a.Logf("ACME auto renew enabled", nil)
|
||||
a.StartAutoRenewTicker()
|
||||
} else {
|
||||
a.RenewerConfig.Enabled = false
|
||||
a.saveRenewConfigToFile()
|
||||
log.Println("[ACME] ACME auto renew disabled")
|
||||
a.Logf("ACME auto renew disabled", nil)
|
||||
a.StopAutoRenewTicker()
|
||||
}
|
||||
} else {
|
||||
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (a *AutoRenewer) HandleACMEEmail(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
email, err := utils.PostPara(r, "set")
|
||||
if err != nil {
|
||||
if r.Method == http.MethodGet {
|
||||
//Return the current email to user
|
||||
js, _ := json.Marshal(a.RenewerConfig.Email)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
} else if r.Method == http.MethodPost {
|
||||
email, err := utils.PostPara(r, "set")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty email given")
|
||||
return
|
||||
}
|
||||
|
||||
//Check if the email is valid
|
||||
_, err := mail.ParseAddress(email)
|
||||
_, err = mail.ParseAddress(email)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
@ -252,8 +275,11 @@ func (a *AutoRenewer) HandleACMEEmail(w http.ResponseWriter, r *http.Request) {
|
||||
//Set the new config
|
||||
a.RenewerConfig.Email = email
|
||||
a.saveRenewConfigToFile()
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
} else {
|
||||
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
// Check and renew certificates. This check all the certificates in the
|
||||
@ -263,7 +289,7 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
||||
certFolder := a.CertFolder
|
||||
files, err := os.ReadDir(certFolder)
|
||||
if err != nil {
|
||||
log.Println("Unable to renew certificates: " + err.Error())
|
||||
a.Logf("Read certificate store failed", err)
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
@ -277,13 +303,13 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if CertExpireSoon(certBytes) || CertIsExpired(certBytes) {
|
||||
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
|
||||
//This cert is expired
|
||||
|
||||
DNSName, err := ExtractDomains(certBytes)
|
||||
if err != nil {
|
||||
//Maybe self signed. Ignore this
|
||||
log.Println("Encounted error when trying to resolve DNS name for cert " + file.Name())
|
||||
a.Logf("Encounted error when trying to resolve DNS name for cert "+file.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -305,13 +331,12 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if CertExpireSoon(certBytes) || CertIsExpired(certBytes) {
|
||||
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
|
||||
//This cert is expired
|
||||
|
||||
DNSName, err := ExtractDomains(certBytes)
|
||||
if err != nil {
|
||||
//Maybe self signed. Ignore this
|
||||
log.Println("Encounted error when trying to resolve DNS name for cert " + file.Name())
|
||||
a.Logf("Encounted error when trying to resolve DNS name for cert "+file.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -338,7 +363,7 @@ func (a *AutoRenewer) Close() {
|
||||
func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, error) {
|
||||
renewedCertFiles := []string{}
|
||||
for _, expiredCert := range certs {
|
||||
log.Println("Renewing " + expiredCert.Filepath + " (Might take a few minutes)")
|
||||
a.Logf("Renewing "+expiredCert.Filepath+" (Might take a few minutes)", nil)
|
||||
fileName := filepath.Base(expiredCert.Filepath)
|
||||
certName := fileName[:len(fileName)-len(filepath.Ext(fileName))]
|
||||
|
||||
@ -346,10 +371,10 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
||||
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
|
||||
certInfo, err := LoadCertInfoJSON(certInfoFilename)
|
||||
if err != nil {
|
||||
log.Printf("Renew %s certificate error, can't get the ACME detail for cert: %v, trying org section as ca", certName, err)
|
||||
a.Logf("Renew "+certName+"certificate error, can't get the ACME detail for certificate, trying org section as ca", err)
|
||||
|
||||
if CAName, extractErr := ExtractIssuerNameFromPEM(expiredCert.Filepath); extractErr != nil {
|
||||
log.Printf("extract issuer name for cert error: %v, using default ca", extractErr)
|
||||
a.Logf("Extract issuer name for cert error, using default ca", err)
|
||||
certInfo = &CertificateInfoJSON{}
|
||||
} else {
|
||||
certInfo = &CertificateInfoJSON{AcmeName: CAName}
|
||||
@ -358,9 +383,9 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
||||
|
||||
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS, certInfo.UseDNS)
|
||||
if err != nil {
|
||||
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
|
||||
a.Logf("Renew "+fileName+"("+strings.Join(expiredCert.Domains, ",")+") failed", err)
|
||||
} else {
|
||||
log.Println("Successfully renewed " + filepath.Base(expiredCert.Filepath))
|
||||
a.Logf("Successfully renewed "+filepath.Base(expiredCert.Filepath), nil)
|
||||
renewedCertFiles = append(renewedCertFiles, filepath.Base(expiredCert.Filepath))
|
||||
}
|
||||
}
|
||||
|
@ -81,13 +81,14 @@ func CertIsExpired(certBytes []byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func CertExpireSoon(certBytes []byte) bool {
|
||||
// CertExpireSoon check if the given cert bytes will expires within the given number of days from now
|
||||
func CertExpireSoon(certBytes []byte, numberOfDays int) bool {
|
||||
block, _ := pem.Decode(certBytes)
|
||||
if block != nil {
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err == nil {
|
||||
expirationDate := cert.NotAfter
|
||||
threshold := 14 * 24 * time.Hour // 14 days
|
||||
threshold := time.Duration(numberOfDays) * 24 * time.Hour
|
||||
|
||||
timeRemaining := time.Until(expirationDate)
|
||||
if timeRemaining <= threshold {
|
||||
|
@ -14,10 +14,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"encoding/hex"
|
||||
"log"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
db "imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -27,6 +27,7 @@ type AuthAgent struct {
|
||||
SessionStore *sessions.CookieStore
|
||||
Database *db.Database
|
||||
LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
type AuthEndpoints struct {
|
||||
@ -37,12 +38,12 @@ type AuthEndpoints struct {
|
||||
Autologin string
|
||||
}
|
||||
|
||||
//Constructor
|
||||
func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
|
||||
// Constructor
|
||||
func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, systemLogger *logger.Logger, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
|
||||
store := sessions.NewCookieStore(key)
|
||||
err := sysdb.NewTable("auth")
|
||||
if err != nil {
|
||||
log.Println("Failed to create auth database. Terminating.")
|
||||
systemLogger.Println("Failed to create auth database. Terminating.")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@ -52,13 +53,14 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database,
|
||||
SessionStore: store,
|
||||
Database: sysdb,
|
||||
LoginRedirectionHandler: loginRedirectionHandler,
|
||||
Logger: systemLogger,
|
||||
}
|
||||
|
||||
//Return the authAgent
|
||||
return &newAuthAgent
|
||||
}
|
||||
|
||||
func GetSessionKey(sysdb *db.Database) (string, error) {
|
||||
func GetSessionKey(sysdb *db.Database, logger *logger.Logger) (string, error) {
|
||||
sysdb.NewTable("auth")
|
||||
sessionKey := ""
|
||||
if !sysdb.KeyExists("auth", "sessionkey") {
|
||||
@ -66,9 +68,9 @@ func GetSessionKey(sysdb *db.Database) (string, error) {
|
||||
rand.Read(key)
|
||||
sessionKey = string(key)
|
||||
sysdb.Write("auth", "sessionkey", sessionKey)
|
||||
log.Println("[Auth] New authentication session key generated")
|
||||
logger.PrintAndLog("auth", "New authentication session key generated", nil)
|
||||
} else {
|
||||
log.Println("[Auth] Authentication session key loaded from database")
|
||||
logger.PrintAndLog("auth", "Authentication session key loaded from database", nil)
|
||||
err := sysdb.Read("auth", "sessionkey", &sessionKey)
|
||||
if err != nil {
|
||||
return "", errors.New("database read error. Is the database file corrupted?")
|
||||
@ -77,7 +79,7 @@ func GetSessionKey(sysdb *db.Database) (string, error) {
|
||||
return sessionKey, nil
|
||||
}
|
||||
|
||||
//This function will handle an http request and redirect to the given login address if not logged in
|
||||
// This function will handle an http request and redirect to the given login address if not logged in
|
||||
func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
|
||||
if a.CheckAuth(r) {
|
||||
//User already logged in
|
||||
@ -88,14 +90,14 @@ func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, hand
|
||||
}
|
||||
}
|
||||
|
||||
//Handle login request, require POST username and password
|
||||
// Handle login request, require POST username and password
|
||||
func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
//Get username from request using POST mode
|
||||
username, err := utils.PostPara(r, "username")
|
||||
if err != nil {
|
||||
//Username not defined
|
||||
log.Println("[Auth] " + r.RemoteAddr + " trying to login with username: " + username)
|
||||
a.Logger.PrintAndLog("auth", r.RemoteAddr+" trying to login with username: "+username, nil)
|
||||
utils.SendErrorResponse(w, "Username not defined or empty.")
|
||||
return
|
||||
}
|
||||
@ -124,11 +126,11 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
a.LoginUserByRequest(w, r, username, rememberme)
|
||||
|
||||
//Print the login message to console
|
||||
log.Println(username + " logged in.")
|
||||
a.Logger.PrintAndLog("auth", username+" logged in.", nil)
|
||||
utils.SendOK(w)
|
||||
} else {
|
||||
//Password incorrect
|
||||
log.Println(username + " login request rejected: " + rejectionReason)
|
||||
a.Logger.PrintAndLog("auth", username+" login request rejected: "+rejectionReason, nil)
|
||||
|
||||
utils.SendErrorResponse(w, rejectionReason)
|
||||
return
|
||||
@ -140,14 +142,14 @@ func (a *AuthAgent) ValidateUsernameAndPassword(username string, password string
|
||||
return succ
|
||||
}
|
||||
|
||||
//validate the username and password, return reasons if the auth failed
|
||||
// validate the username and password, return reasons if the auth failed
|
||||
func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, password string) (bool, string) {
|
||||
hashedPassword := Hash(password)
|
||||
var passwordInDB string
|
||||
err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
|
||||
if err != nil {
|
||||
//User not found or db exception
|
||||
log.Println("[Auth] " + username + " login with incorrect password")
|
||||
a.Logger.PrintAndLog("auth", username+" login with incorrect password", nil)
|
||||
return false, "Invalid username or password"
|
||||
}
|
||||
|
||||
@ -158,7 +160,7 @@ func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, passw
|
||||
}
|
||||
}
|
||||
|
||||
//Login the user by creating a valid session for this user
|
||||
// Login the user by creating a valid session for this user
|
||||
func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, username string, rememberme bool) {
|
||||
session, _ := a.SessionStore.Get(r, a.SessionName)
|
||||
|
||||
@ -181,11 +183,15 @@ func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, u
|
||||
session.Save(r, w)
|
||||
}
|
||||
|
||||
//Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
|
||||
// Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
|
||||
func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
username, err := a.GetUserName(w, r)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "user not logged in")
|
||||
return
|
||||
}
|
||||
if username != "" {
|
||||
log.Println(username + " logged out.")
|
||||
a.Logger.PrintAndLog("auth", username+" logged out", nil)
|
||||
}
|
||||
// Revoke users authentication
|
||||
err = a.Logout(w, r)
|
||||
@ -194,7 +200,7 @@ func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte("OK"))
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
|
||||
@ -208,7 +214,7 @@ func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//Get the current session username from request
|
||||
// Get the current session username from request
|
||||
func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string, error) {
|
||||
if a.CheckAuth(r) {
|
||||
//This user has logged in.
|
||||
@ -220,7 +226,7 @@ func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string,
|
||||
}
|
||||
}
|
||||
|
||||
//Get the current session user email from request
|
||||
// Get the current session user email from request
|
||||
func (a *AuthAgent) GetUserEmail(w http.ResponseWriter, r *http.Request) (string, error) {
|
||||
if a.CheckAuth(r) {
|
||||
//This user has logged in.
|
||||
@ -239,7 +245,7 @@ func (a *AuthAgent) GetUserEmail(w http.ResponseWriter, r *http.Request) (string
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the user has logged in, return true / false in JSON
|
||||
// Check if the user has logged in, return true / false in JSON
|
||||
func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if a.CheckAuth(r) {
|
||||
utils.SendJSONResponse(w, "true")
|
||||
@ -248,7 +254,7 @@ func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
//Handle new user register. Require POST username, password, group.
|
||||
// Handle new user register. Require POST username, password, group.
|
||||
func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
|
||||
//Get username from request
|
||||
newusername, err := utils.PostPara(r, "username")
|
||||
@ -291,10 +297,10 @@ func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callb
|
||||
|
||||
//Return to the client with OK
|
||||
utils.SendOK(w)
|
||||
log.Println("[Auth] New user " + newusername + " added to system.")
|
||||
a.Logger.PrintAndLog("auth", "New user "+newusername+" added to system.", nil)
|
||||
}
|
||||
|
||||
//Handle new user register without confirmation email. Require POST username, password, group.
|
||||
// Handle new user register without confirmation email. Require POST username, password, group.
|
||||
func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
|
||||
//Get username from request
|
||||
newusername, err := utils.PostPara(r, "username")
|
||||
@ -324,10 +330,10 @@ func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Re
|
||||
|
||||
//Return to the client with OK
|
||||
utils.SendOK(w)
|
||||
log.Println("[Auth] Admin account created: " + newusername)
|
||||
a.Logger.PrintAndLog("auth", "Admin account created: "+newusername, nil)
|
||||
}
|
||||
|
||||
//Check authentication from request header's session value
|
||||
// Check authentication from request header's session value
|
||||
func (a *AuthAgent) CheckAuth(r *http.Request) bool {
|
||||
session, err := a.SessionStore.Get(r, a.SessionName)
|
||||
if err != nil {
|
||||
@ -340,8 +346,8 @@ func (a *AuthAgent) CheckAuth(r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
//Handle de-register of users. Require POST username.
|
||||
//THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
|
||||
// Handle de-register of users. Require POST username.
|
||||
// THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
|
||||
func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
|
||||
//Check if the user is logged in
|
||||
if !a.CheckAuth(r) {
|
||||
@ -365,7 +371,7 @@ func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
//Return to the client with OK
|
||||
utils.SendOK(w)
|
||||
log.Println("[Auth] User " + username + " has been removed from the system.")
|
||||
a.Logger.PrintAndLog("auth", "User "+username+" has been removed from the system", nil)
|
||||
}
|
||||
|
||||
func (a *AuthAgent) UnregisterUser(username string) error {
|
||||
@ -381,7 +387,7 @@ func (a *AuthAgent) UnregisterUser(username string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//Get the number of users in the system
|
||||
// Get the number of users in the system
|
||||
func (a *AuthAgent) GetUserCounts() int {
|
||||
entries, _ := a.Database.ListTable("auth")
|
||||
usercount := 0
|
||||
@ -393,12 +399,12 @@ func (a *AuthAgent) GetUserCounts() int {
|
||||
}
|
||||
|
||||
if usercount == 0 {
|
||||
log.Println("There are no user in the database.")
|
||||
a.Logger.PrintAndLog("auth", "There are no user in the database", nil)
|
||||
}
|
||||
return usercount
|
||||
}
|
||||
|
||||
//List all username within the system
|
||||
// List all username within the system
|
||||
func (a *AuthAgent) ListUsers() []string {
|
||||
entries, _ := a.Database.ListTable("auth")
|
||||
results := []string{}
|
||||
@ -411,7 +417,7 @@ func (a *AuthAgent) ListUsers() []string {
|
||||
return results
|
||||
}
|
||||
|
||||
//Check if the given username exists
|
||||
// Check if the given username exists
|
||||
func (a *AuthAgent) UserExists(username string) bool {
|
||||
userpasswordhash := ""
|
||||
err := a.Database.Read("auth", "passhash/"+username, &userpasswordhash)
|
||||
@ -421,7 +427,7 @@ func (a *AuthAgent) UserExists(username string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
//Update the session expire time given the request header.
|
||||
// Update the session expire time given the request header.
|
||||
func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Request) bool {
|
||||
session, _ := a.SessionStore.Get(r, a.SessionName)
|
||||
if session.Values["authenticated"].(bool) {
|
||||
@ -446,7 +452,7 @@ func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
}
|
||||
|
||||
//Create user account
|
||||
// Create user account
|
||||
func (a *AuthAgent) CreateUserAccount(newusername string, password string, email string) error {
|
||||
//Check user already exists
|
||||
if a.UserExists(newusername) {
|
||||
@ -470,7 +476,7 @@ func (a *AuthAgent) CreateUserAccount(newusername string, password string, email
|
||||
return nil
|
||||
}
|
||||
|
||||
//Hash the given raw string into sha512 hash
|
||||
// Hash the given raw string into sha512 hash
|
||||
func Hash(raw string) string {
|
||||
h := sha512.New()
|
||||
h.Write([]byte(raw))
|
||||
|
@ -2,7 +2,7 @@ package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -10,7 +10,7 @@ type RouterOption struct {
|
||||
AuthAgent *AuthAgent
|
||||
RequireAuth bool //This router require authentication
|
||||
DeniedHandler func(http.ResponseWriter, *http.Request) //Things to do when request is rejected
|
||||
|
||||
TargetMux *http.ServeMux
|
||||
}
|
||||
|
||||
type RouterDef struct {
|
||||
@ -28,24 +28,38 @@ func NewManagedHTTPRouter(option RouterOption) *RouterDef {
|
||||
func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error {
|
||||
//Check if the endpoint already registered
|
||||
if _, exist := router.endpoints[endpoint]; exist {
|
||||
log.Println("WARNING! Duplicated registering of web endpoint: " + endpoint)
|
||||
fmt.Println("WARNING! Duplicated registering of web endpoint: " + endpoint)
|
||||
return errors.New("endpoint register duplicated")
|
||||
}
|
||||
|
||||
authAgent := router.option.AuthAgent
|
||||
|
||||
//OK. Register handler
|
||||
http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||
//Check authentication of the user
|
||||
if router.option.RequireAuth {
|
||||
authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) {
|
||||
if router.option.TargetMux == nil {
|
||||
http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||
//Check authentication of the user
|
||||
if router.option.RequireAuth {
|
||||
authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) {
|
||||
handler(w, r)
|
||||
})
|
||||
} else {
|
||||
handler(w, r)
|
||||
})
|
||||
} else {
|
||||
handler(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
} else {
|
||||
router.option.TargetMux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||
//Check authentication of the user
|
||||
if router.option.RequireAuth {
|
||||
authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) {
|
||||
handler(w, r)
|
||||
})
|
||||
} else {
|
||||
handler(w, r)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
router.endpoints[endpoint] = handler
|
||||
|
||||
|
57
src/mod/dockerux/docker.go
Normal file
57
src/mod/dockerux/docker.go
Normal file
@ -0,0 +1,57 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package dockerux
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
func (d *UXOptimizer) HandleDockerAvailable(w http.ResponseWriter, r *http.Request) {
|
||||
js, _ := json.Marshal(d.RunninInDocker)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (d *UXOptimizer) HandleDockerContainersList(w http.ResponseWriter, r *http.Request) {
|
||||
apiClient, err := client.NewClientWithOpts(client.WithVersion("1.43"))
|
||||
if err != nil {
|
||||
d.SystemWideLogger.PrintAndLog("Docker", "Unable to create new docker client", err)
|
||||
utils.SendErrorResponse(w, "Docker client initiation failed")
|
||||
return
|
||||
}
|
||||
defer apiClient.Close()
|
||||
|
||||
containers, err := apiClient.ContainerList(context.Background(), container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
d.SystemWideLogger.PrintAndLog("Docker", "List docker container failed", err)
|
||||
utils.SendErrorResponse(w, "List docker container failed")
|
||||
return
|
||||
}
|
||||
|
||||
networks, err := apiClient.NetworkList(context.Background(), types.NetworkListOptions{})
|
||||
if err != nil {
|
||||
d.SystemWideLogger.PrintAndLog("Docker", "List docker network failed", err)
|
||||
utils.SendErrorResponse(w, "List docker network failed")
|
||||
return
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"network": networks,
|
||||
"containers": containers,
|
||||
}
|
||||
|
||||
js, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
32
src/mod/dockerux/docker_windows.go
Normal file
32
src/mod/dockerux/docker_windows.go
Normal file
@ -0,0 +1,32 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package dockerux
|
||||
|
||||
/*
|
||||
|
||||
Windows docker UX optimizer dummy
|
||||
|
||||
This is a dummy module for Windows as docker features
|
||||
is useless on Windows and create a larger binary size
|
||||
|
||||
docker on Windows build are trimmed to reduce binary size
|
||||
and make it compatibile with Windows 7
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
// Windows build not support docker
|
||||
func (d *UXOptimizer) HandleDockerAvailable(w http.ResponseWriter, r *http.Request) {
|
||||
js, _ := json.Marshal(d.RunninInDocker)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (d *UXOptimizer) HandleDockerContainersList(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendErrorResponse(w, "Platform not supported")
|
||||
}
|
24
src/mod/dockerux/dockerux.go
Normal file
24
src/mod/dockerux/dockerux.go
Normal file
@ -0,0 +1,24 @@
|
||||
package dockerux
|
||||
|
||||
import "imuslab.com/zoraxy/mod/info/logger"
|
||||
|
||||
/*
|
||||
Docker Optimizer
|
||||
|
||||
This script add support for optimizing docker user experience
|
||||
Note that this module are community contribute only. For bug
|
||||
report, please directly tag the Pull Request author.
|
||||
*/
|
||||
|
||||
type UXOptimizer struct {
|
||||
RunninInDocker bool
|
||||
SystemWideLogger *logger.Logger
|
||||
}
|
||||
|
||||
//Create a new docker optimizer
|
||||
func NewDockerOptimizer(IsRunningInDocker bool, logger *logger.Logger) *UXOptimizer {
|
||||
return &UXOptimizer{
|
||||
RunninInDocker: IsRunningInDocker,
|
||||
SystemWideLogger: logger,
|
||||
}
|
||||
}
|
@ -14,18 +14,24 @@ import (
|
||||
Main server for dynamic proxy core
|
||||
|
||||
Routing Handler Priority (High to Low)
|
||||
- Blacklist
|
||||
- Whitelist
|
||||
- Special Routing Rule (e.g. acme)
|
||||
- Redirectable
|
||||
- Subdomain Routing
|
||||
- Vitrual Directory Routing
|
||||
- Access Router
|
||||
- Blacklist
|
||||
- Whitelist
|
||||
- Rate Limitor
|
||||
- Basic Auth
|
||||
- Vitrual Directory Proxy
|
||||
- Subdomain Proxy
|
||||
- Root router (default site router)
|
||||
*/
|
||||
|
||||
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
Special Routing Rules, bypass most of the limitations
|
||||
*/
|
||||
//Check if there are external routing rule matches.
|
||||
//Check if there are external routing rule (rr) matches.
|
||||
//If yes, route them via external rr
|
||||
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
||||
if matchedRoutingRule != nil {
|
||||
@ -34,16 +40,13 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Inject headers
|
||||
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
|
||||
|
||||
/*
|
||||
Redirection Routing
|
||||
*/
|
||||
//Check if this is a redirection url
|
||||
if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
|
||||
statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
|
||||
h.logRequest(r, statusCode != 500, statusCode, "redirect", "")
|
||||
h.Parent.logRequest(r, statusCode != 500, statusCode, "redirect", "")
|
||||
return
|
||||
}
|
||||
|
||||
@ -70,10 +73,20 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Rate Limit
|
||||
if sep.RequireRateLimit {
|
||||
err := h.handleRateLimitRouting(w, r, sep)
|
||||
if err != nil {
|
||||
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 429)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//Validate basic auth
|
||||
if sep.RequireBasicAuth {
|
||||
err := h.handleBasicAuthRouting(w, r, sep)
|
||||
if err != nil {
|
||||
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -90,6 +103,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
|
||||
//Missing tailing slash. Redirect to target proxy endpoint
|
||||
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
||||
h.Parent.Option.Logger.LogHTTPRequest(r, "redirect", 307)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -183,12 +197,12 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
hostname := parsedURL.Hostname()
|
||||
if hostname == domainOnly {
|
||||
h.logRequest(r, false, 500, "root-redirect", domainOnly)
|
||||
h.Parent.logRequest(r, false, 500, "root-redirect", domainOnly)
|
||||
http.Error(w, "Loopback redirects due to invalid settings", 500)
|
||||
return
|
||||
}
|
||||
|
||||
h.logRequest(r, false, 307, "root-redirect", domainOnly)
|
||||
h.Parent.logRequest(r, false, 307, "root-redirect", domainOnly)
|
||||
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
|
||||
case DefaultSite_NotFoundPage:
|
||||
//Serve the not found page, use template if exists
|
||||
|
@ -24,7 +24,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter,
|
||||
|
||||
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
|
||||
if isBlocked {
|
||||
h.logRequest(r, false, 403, blockedReason, "")
|
||||
h.Parent.logRequest(r, false, 403, blockedReason, "")
|
||||
}
|
||||
return isBlocked
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||
err := handleBasicAuth(w, r, pe)
|
||||
if err != nil {
|
||||
h.logRequest(r, false, 401, "host", pe.Domain)
|
||||
h.Parent.logRequest(r, false, 401, "host", r.URL.Hostname())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
80
src/mod/dynamicproxy/customHeader.go
Normal file
80
src/mod/dynamicproxy/customHeader.go
Normal file
@ -0,0 +1,80 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
)
|
||||
|
||||
/*
|
||||
CustomHeader.go
|
||||
|
||||
This script handle parsing and injecting custom headers
|
||||
into the dpcore routing logic
|
||||
*/
|
||||
|
||||
// SplitInboundOutboundHeaders split user defined headers into upstream and downstream headers
|
||||
// return upstream header and downstream header key-value pairs
|
||||
// if the header is expected to be deleted, the value will be set to empty string
|
||||
func (ept *ProxyEndpoint) SplitInboundOutboundHeaders() ([][]string, [][]string) {
|
||||
if len(ept.UserDefinedHeaders) == 0 && ept.HSTSMaxAge == 0 && !ept.EnablePermissionPolicyHeader {
|
||||
//Early return if there are no defined headers
|
||||
return [][]string{}, [][]string{}
|
||||
}
|
||||
|
||||
//Use pre-allocation for faster performance
|
||||
//Downstream +2 for Permission Policy and HSTS
|
||||
upstreamHeaders := make([][]string, len(ept.UserDefinedHeaders))
|
||||
downstreamHeaders := make([][]string, len(ept.UserDefinedHeaders)+2)
|
||||
upstreamHeaderCounter := 0
|
||||
downstreamHeaderCounter := 0
|
||||
|
||||
//Sort the headers into upstream or downstream
|
||||
for _, customHeader := range ept.UserDefinedHeaders {
|
||||
thisHeaderSet := make([]string, 2)
|
||||
thisHeaderSet[0] = customHeader.Key
|
||||
thisHeaderSet[1] = customHeader.Value
|
||||
if customHeader.IsRemove {
|
||||
//Prevent invalid config
|
||||
thisHeaderSet[1] = ""
|
||||
}
|
||||
|
||||
//Assign to slice
|
||||
if customHeader.Direction == HeaderDirection_ZoraxyToUpstream {
|
||||
upstreamHeaders[upstreamHeaderCounter] = thisHeaderSet
|
||||
upstreamHeaderCounter++
|
||||
} else if customHeader.Direction == HeaderDirection_ZoraxyToDownstream {
|
||||
downstreamHeaders[downstreamHeaderCounter] = thisHeaderSet
|
||||
downstreamHeaderCounter++
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the endpoint require HSTS headers
|
||||
if ept.HSTSMaxAge > 0 {
|
||||
if ept.ContainsWildcardName(true) {
|
||||
//Endpoint listening domain includes wildcards.
|
||||
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(ept.HSTSMaxAge)) + "; includeSubdomains"}
|
||||
} else {
|
||||
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(ept.HSTSMaxAge))}
|
||||
}
|
||||
|
||||
downstreamHeaderCounter++
|
||||
}
|
||||
|
||||
//Check if the endpoint require Permission Policy
|
||||
if ept.EnablePermissionPolicyHeader {
|
||||
var usingPermissionPolicy *permissionpolicy.PermissionsPolicy
|
||||
if ept.PermissionPolicy != nil {
|
||||
//Custom permission policy
|
||||
usingPermissionPolicy = ept.PermissionPolicy
|
||||
} else {
|
||||
//Permission policy is enabled but not customized. Use default
|
||||
usingPermissionPolicy = permissionpolicy.GetDefaultPermissionPolicy()
|
||||
}
|
||||
|
||||
downstreamHeaders[downstreamHeaderCounter] = usingPermissionPolicy.ToKeyValueHeader()
|
||||
downstreamHeaderCounter++
|
||||
}
|
||||
|
||||
return upstreamHeaders, downstreamHeaders
|
||||
}
|
21
src/mod/dynamicproxy/domainsniff/proxmox.go
Normal file
21
src/mod/dynamicproxy/domainsniff/proxmox.go
Normal file
@ -0,0 +1,21 @@
|
||||
package domainsniff
|
||||
|
||||
import "net/http"
|
||||
|
||||
/*
|
||||
Promox API sniffer
|
||||
|
||||
This handler sniff proxmox API endpoint and
|
||||
adjust the request accordingly to fix shits
|
||||
in the proxmox API server
|
||||
*/
|
||||
|
||||
func IsProxmox(r *http.Request) bool {
|
||||
// Check if any of the cookies is named PVEAuthCookie
|
||||
for _, cookie := range r.Cookies() {
|
||||
if cookie.Name == "PVEAuthCookie" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -10,6 +10,9 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
)
|
||||
|
||||
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
||||
@ -48,18 +51,30 @@ type ReverseProxy struct {
|
||||
ModifyResponse func(*http.Response) error
|
||||
|
||||
//Prepender is an optional prepend text for URL rewrite
|
||||
//
|
||||
Prepender string
|
||||
|
||||
Verbal bool
|
||||
|
||||
//Appended by Zoraxy project
|
||||
|
||||
}
|
||||
|
||||
type ResponseRewriteRuleSet struct {
|
||||
ProxyDomain string
|
||||
OriginalHost string
|
||||
UseTLS bool
|
||||
NoCache bool
|
||||
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
||||
/* Basic Rewrite Rulesets */
|
||||
ProxyDomain string
|
||||
OriginalHost string
|
||||
UseTLS bool
|
||||
NoCache bool
|
||||
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
||||
UpstreamHeaders [][]string
|
||||
DownstreamHeaders [][]string
|
||||
|
||||
/* Advance Usecase Options */
|
||||
HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase)
|
||||
NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase)
|
||||
|
||||
/* System Information Payload */
|
||||
Version string //Version number of Zoraxy, use for X-Proxy-By
|
||||
}
|
||||
|
||||
type requestCanceler interface {
|
||||
@ -67,8 +82,8 @@ type requestCanceler interface {
|
||||
}
|
||||
|
||||
type DpcoreOptions struct {
|
||||
IgnoreTLSVerification bool
|
||||
FlushInterval time.Duration
|
||||
IgnoreTLSVerification bool //Disable all TLS verification when request pass through this proxy router
|
||||
FlushInterval time.Duration //Duration to flush in normal requests. Stream request or keep-alive request will always flush with interval of -1 (immediately)
|
||||
}
|
||||
|
||||
func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy {
|
||||
@ -175,7 +190,7 @@ var hopHeaders = []string{
|
||||
"Te", // canonicalized version of "TE"
|
||||
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
|
||||
"Transfer-Encoding",
|
||||
//"Upgrade",
|
||||
//"Upgrade", // handled by websocket proxy in higher layer abstraction
|
||||
}
|
||||
|
||||
// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
|
||||
@ -246,79 +261,7 @@ 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")
|
||||
}
|
||||
|
||||
//Hide Go-HTTP-Client UA if the client didnt sent us one
|
||||
if _, ok := header["User-Agent"]; !ok {
|
||||
// If the outbound request doesn't have a User-Agent header set,
|
||||
// don't send the default Go HTTP client User-Agent.
|
||||
header.Set("User-Agent", "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func addXForwardedForHeader(req *http.Request) {
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
// If we aren't the first proxy retain prior
|
||||
// X-Forwarded-For information as a comma+space
|
||||
// separated list and fold multiple headers into one.
|
||||
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
req.Header.Set("X-Forwarded-For", clientIP)
|
||||
if req.TLS != nil {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
} else {
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
}
|
||||
|
||||
if req.Header.Get("X-Real-Ip") == "" {
|
||||
//Check if CF-Connecting-IP header exists
|
||||
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
|
||||
if CF_Connecting_IP != "" {
|
||||
//Use CF Connecting IP
|
||||
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
|
||||
} else {
|
||||
// Not exists. Fill it in with first entry in X-Forwarded-For
|
||||
ips := strings.Split(clientIP, ",")
|
||||
if len(ips) > 0 {
|
||||
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error {
|
||||
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) (int, error) {
|
||||
transport := p.Transport
|
||||
|
||||
outreq := new(http.Request)
|
||||
@ -346,9 +289,12 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
p.Director(outreq)
|
||||
outreq.Close = false
|
||||
|
||||
if !rrr.UseTLS {
|
||||
//This seems to be routing to external sites
|
||||
//Do not keep the original host
|
||||
//Only skip origin rewrite iff proxy target require TLS and it is external domain name like github.com
|
||||
if rrr.HostHeaderOverwrite != "" {
|
||||
//Use user defined overwrite header value, see issue #255
|
||||
outreq.Host = rrr.HostHeaderOverwrite
|
||||
} else if !(rrr.UseTLS && isExternalDomainName(rrr.ProxyDomain)) {
|
||||
// Always use the original host, see issue #164
|
||||
outreq.Host = rrr.OriginalHost
|
||||
}
|
||||
|
||||
@ -356,12 +302,25 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
outreq.Header = make(http.Header)
|
||||
copyHeader(outreq.Header, req.Header)
|
||||
|
||||
// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
|
||||
removeHeaders(outreq.Header, rrr.NoCache)
|
||||
// Remove hop-by-hop headers.
|
||||
if !rrr.NoRemoveHopByHop {
|
||||
removeHeaders(outreq.Header, rrr.NoCache)
|
||||
}
|
||||
|
||||
// Add X-Forwarded-For Header.
|
||||
addXForwardedForHeader(outreq)
|
||||
|
||||
// Add user defined headers (to upstream)
|
||||
injectUserDefinedHeaders(outreq.Header, rrr.UpstreamHeaders)
|
||||
|
||||
// Rewrite outbound UA, must be after user headers
|
||||
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)
|
||||
|
||||
//Fix proxmox transfer encoding bug if detected Proxmox Cookie
|
||||
if domainsniff.IsProxmox(req) {
|
||||
outreq.TransferEncoding = []string{"identity"}
|
||||
}
|
||||
|
||||
res, err := transport.RoundTrip(outreq)
|
||||
if err != nil {
|
||||
if p.Verbal {
|
||||
@ -369,11 +328,13 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
}
|
||||
|
||||
//rw.WriteHeader(http.StatusBadGateway)
|
||||
return err
|
||||
return http.StatusBadGateway, err
|
||||
}
|
||||
|
||||
// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
|
||||
removeHeaders(res.Header, rrr.NoCache)
|
||||
if !rrr.NoRemoveHopByHop {
|
||||
removeHeaders(res.Header, rrr.NoCache)
|
||||
}
|
||||
|
||||
//Remove the User-Agent header if exists
|
||||
if _, ok := res.Header["User-Agent"]; ok {
|
||||
@ -388,17 +349,14 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
}
|
||||
|
||||
//rw.WriteHeader(http.StatusBadGateway)
|
||||
return err
|
||||
return http.StatusBadGateway, err
|
||||
}
|
||||
}
|
||||
|
||||
//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 header rewriter functions
|
||||
//Custom Location header rewriter functions
|
||||
if res.Header.Get("Location") != "" {
|
||||
locationRewrite := res.Header.Get("Location")
|
||||
originLocation := res.Header.Get("Location")
|
||||
@ -424,9 +382,15 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
res.Header.Set("Location", locationRewrite)
|
||||
}
|
||||
|
||||
// Add user defined headers (to downstream)
|
||||
injectUserDefinedHeaders(res.Header, rrr.DownstreamHeaders)
|
||||
|
||||
// Copy header from response to client.
|
||||
copyHeader(rw.Header(), res.Header)
|
||||
|
||||
// inject permission policy headers
|
||||
permissionpolicy.InjectPermissionPolicyHeader(rw, nil)
|
||||
|
||||
// The "Trailer" header isn't included in the Transport's response, Build it up from Trailer.
|
||||
if len(res.Trailer) > 0 {
|
||||
trailerKeys := make([]string, 0, len(res.Trailer))
|
||||
@ -454,14 +418,14 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
res.Body.Close()
|
||||
copyHeader(rw.Header(), res.Trailer)
|
||||
|
||||
return nil
|
||||
return res.StatusCode, nil
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) error {
|
||||
func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) (int, error) {
|
||||
hij, ok := rw.(http.Hijacker)
|
||||
if !ok {
|
||||
p.logf("http server does not support hijacker")
|
||||
return errors.New("http server does not support hijacker")
|
||||
return http.StatusNotImplemented, errors.New("http server does not support hijacker")
|
||||
}
|
||||
|
||||
clientConn, _, err := hij.Hijack()
|
||||
@ -469,7 +433,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
|
||||
if p.Verbal {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
return err
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
proxyConn, err := net.Dial("tcp", req.URL.Host)
|
||||
@ -478,7 +442,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// The returned net.Conn may have read or write deadlines
|
||||
@ -497,7 +461,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
|
||||
if p.Verbal {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
return err
|
||||
return http.StatusGatewayTimeout, err
|
||||
}
|
||||
|
||||
err = proxyConn.SetDeadline(deadline)
|
||||
@ -506,7 +470,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
return http.StatusGatewayTimeout, err
|
||||
}
|
||||
|
||||
_, err = clientConn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
|
||||
@ -515,7 +479,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
@ -528,15 +492,13 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
|
||||
proxyConn.Close()
|
||||
clientConn.Close()
|
||||
|
||||
return nil
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error {
|
||||
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) (int, error) {
|
||||
if req.Method == "CONNECT" {
|
||||
err := p.ProxyHTTPS(rw, req)
|
||||
return err
|
||||
return p.ProxyHTTPS(rw, req)
|
||||
} else {
|
||||
err := p.ProxyHTTP(rw, req, rrr)
|
||||
return err
|
||||
return p.ProxyHTTP(rw, req, rrr)
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package dpcore
|
||||
import (
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -17,6 +18,12 @@ func (p *ReverseProxy) getFlushInterval(req *http.Request, res *http.Response) t
|
||||
return -1
|
||||
}
|
||||
|
||||
// Fixed issue #235: Added auto detection for ollama / llm output stream
|
||||
connectionHeader := req.Header["Connection"]
|
||||
if len(connectionHeader) > 0 && strings.Contains(strings.Join(connectionHeader, ","), "keep-alive") {
|
||||
return -1
|
||||
}
|
||||
|
||||
//Cannot sniff anything. Use default value
|
||||
return p.FlushInterval
|
||||
|
||||
|
120
src/mod/dynamicproxy/dpcore/header.go
Normal file
120
src/mod/dynamicproxy/dpcore/header.go
Normal file
@ -0,0 +1,120 @@
|
||||
package dpcore
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Header.go
|
||||
|
||||
This script handles headers rewrite and remove
|
||||
in dpcore.
|
||||
|
||||
Added in Zoraxy v3.0.6 by tobychui
|
||||
*/
|
||||
|
||||
// removeHeaders Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
|
||||
func removeHeaders(header http.Header, noCache bool) {
|
||||
// Remove hop-by-hop headers listed in the "Connection" header.
|
||||
if c := header.Get("Connection"); c != "" {
|
||||
for _, f := range strings.Split(c, ",") {
|
||||
if f = strings.TrimSpace(f); f != "" {
|
||||
header.Del(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove hop-by-hop headers
|
||||
for _, h := range hopHeaders {
|
||||
if header.Get(h) != "" {
|
||||
header.Del(h)
|
||||
}
|
||||
}
|
||||
|
||||
//Restore the Upgrade header if any
|
||||
if header.Get("Zr-Origin-Upgrade") != "" {
|
||||
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
|
||||
header.Del("Zr-Origin-Upgrade")
|
||||
}
|
||||
|
||||
//Disable cache if nocache is set
|
||||
if noCache {
|
||||
header.Del("Cache-Control")
|
||||
header.Set("Cache-Control", "no-store")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// rewriteUserAgent rewrite the user agent based on incoming request
|
||||
func rewriteUserAgent(header http.Header, UA string) {
|
||||
//Hide Go-HTTP-Client UA if the client didnt sent us one
|
||||
if header.Get("User-Agent") == "" {
|
||||
// If the outbound request doesn't have a User-Agent header set,
|
||||
// don't send the default Go HTTP client User-Agent
|
||||
header.Del("User-Agent")
|
||||
header.Set("User-Agent", UA)
|
||||
}
|
||||
}
|
||||
|
||||
// Add X-Forwarded-For Header and rewrite X-Real-Ip according to sniffing logics
|
||||
func addXForwardedForHeader(req *http.Request) {
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
// If we aren't the first proxy retain prior
|
||||
// X-Forwarded-For information as a comma+space
|
||||
// separated list and fold multiple headers into one.
|
||||
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
req.Header.Set("X-Forwarded-For", clientIP)
|
||||
if req.TLS != nil {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
} else {
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
}
|
||||
|
||||
if req.Header.Get("X-Real-Ip") == "" {
|
||||
//Check if CF-Connecting-IP header exists
|
||||
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
|
||||
Fastly_Client_IP := req.Header.Get("Fastly-Client-IP")
|
||||
if CF_Connecting_IP != "" {
|
||||
//Use CF Connecting IP
|
||||
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
|
||||
} else if Fastly_Client_IP != "" {
|
||||
//Use Fastly Client IP
|
||||
req.Header.Set("X-Real-Ip", Fastly_Client_IP)
|
||||
} else {
|
||||
// Not exists. Fill it in with first entry in X-Forwarded-For
|
||||
ips := strings.Split(clientIP, ",")
|
||||
if len(ips) > 0 {
|
||||
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// injectUserDefinedHeaders inject the user headers from slice
|
||||
// if a value is empty string, the key will be removed from header.
|
||||
// if a key is empty string, the function will return immediately
|
||||
func injectUserDefinedHeaders(header http.Header, userHeaders [][]string) {
|
||||
for _, userHeader := range userHeaders {
|
||||
if len(userHeader) == 0 {
|
||||
//End of header slice
|
||||
return
|
||||
}
|
||||
headerKey := userHeader[0]
|
||||
headerValue := userHeader[1]
|
||||
if headerValue == "" {
|
||||
//Remove header from head
|
||||
header.Del(headerKey)
|
||||
continue
|
||||
}
|
||||
|
||||
//Default: Set header value
|
||||
header.Del(headerKey) //Remove header if it already exists
|
||||
header.Set(headerKey, headerValue)
|
||||
}
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
package dpcore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
@ -60,3 +64,94 @@ func replaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS b
|
||||
func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
|
||||
return replaceLocationHost(urlString, rrr, useTLS)
|
||||
}
|
||||
|
||||
// isExternalDomainName check and return if the hostname is external domain name (e.g. github.com)
|
||||
// instead of internal (like 192.168.1.202:8443 (ip address) or domains end with .local or .internal)
|
||||
func isExternalDomainName(hostname string) bool {
|
||||
host, _, err := net.SplitHostPort(hostname)
|
||||
if err != nil {
|
||||
//hostname doesnt contain port
|
||||
ip := net.ParseIP(hostname)
|
||||
if ip != nil {
|
||||
//IP address, not a domain name
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
//Hostname contain port, use hostname without port to check if it is ip
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
//IP address, not a domain name
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//Check if it is internal DNS assigned domains
|
||||
internalDNSTLD := []string{".local", ".internal", ".localhost", ".home.arpa"}
|
||||
for _, tld := range internalDNSTLD {
|
||||
if strings.HasSuffix(strings.ToLower(hostname), tld) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// DeepCopyRequest returns a deep copy of the given http.Request.
|
||||
func DeepCopyRequest(req *http.Request) (*http.Request, error) {
|
||||
// Copy the URL
|
||||
urlCopy := *req.URL
|
||||
|
||||
// Copy the headers
|
||||
headersCopy := make(http.Header, len(req.Header))
|
||||
for k, vv := range req.Header {
|
||||
vvCopy := make([]string, len(vv))
|
||||
copy(vvCopy, vv)
|
||||
headersCopy[k] = vvCopy
|
||||
}
|
||||
|
||||
// Copy the cookies
|
||||
cookiesCopy := make([]*http.Cookie, len(req.Cookies()))
|
||||
for i, cookie := range req.Cookies() {
|
||||
cookieCopy := *cookie
|
||||
cookiesCopy[i] = &cookieCopy
|
||||
}
|
||||
|
||||
// Copy the body, if present
|
||||
var bodyCopy io.ReadCloser
|
||||
if req.Body != nil {
|
||||
var buf bytes.Buffer
|
||||
if _, err := buf.ReadFrom(req.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Reset the request body so it can be read again
|
||||
if err := req.Body.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Body = io.NopCloser(&buf)
|
||||
bodyCopy = io.NopCloser(bytes.NewReader(buf.Bytes()))
|
||||
}
|
||||
|
||||
// Create the new request
|
||||
reqCopy := &http.Request{
|
||||
Method: req.Method,
|
||||
URL: &urlCopy,
|
||||
Proto: req.Proto,
|
||||
ProtoMajor: req.ProtoMajor,
|
||||
ProtoMinor: req.ProtoMinor,
|
||||
Header: headersCopy,
|
||||
Body: bodyCopy,
|
||||
ContentLength: req.ContentLength,
|
||||
TransferEncoding: append([]string(nil), req.TransferEncoding...),
|
||||
Close: req.Close,
|
||||
Host: req.Host,
|
||||
Form: req.Form,
|
||||
PostForm: req.PostForm,
|
||||
MultipartForm: req.MultipartForm,
|
||||
Trailer: req.Trailer,
|
||||
RemoteAddr: req.RemoteAddr,
|
||||
TLS: req.TLS,
|
||||
// Cancel and Context are not copied as it might cause issues
|
||||
}
|
||||
|
||||
return reqCopy, nil
|
||||
}
|
||||
|
@ -23,12 +23,13 @@ import (
|
||||
func NewDynamicProxy(option RouterOption) (*Router, error) {
|
||||
proxyMap := sync.Map{}
|
||||
thisRouter := Router{
|
||||
Option: &option,
|
||||
ProxyEndpoints: &proxyMap,
|
||||
Running: false,
|
||||
server: nil,
|
||||
routingRules: []*RoutingRule{},
|
||||
tldMap: map[string]int{},
|
||||
Option: &option,
|
||||
ProxyEndpoints: &proxyMap,
|
||||
Running: false,
|
||||
server: nil,
|
||||
routingRules: []*RoutingRule{},
|
||||
loadBalancer: option.LoadBalancer,
|
||||
rateLimitCounter: RequestCountPerIpTable{},
|
||||
}
|
||||
|
||||
thisRouter.mux = &ProxyHandler{
|
||||
@ -85,6 +86,12 @@ func (router *Router) StartProxyService() error {
|
||||
MinVersion: uint16(minVersion),
|
||||
}
|
||||
|
||||
//Start rate limitor
|
||||
err := router.startRateLimterCounterResetTicker()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if router.Option.UseTls {
|
||||
router.server = &http.Server{
|
||||
Addr: ":" + strconv.Itoa(router.Option.Port),
|
||||
@ -129,6 +136,13 @@ func (router *Router) StartProxyService() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Rate Limit
|
||||
if sep.RequireRateLimit {
|
||||
if err := router.handleRateLimit(w, r, sep); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//Validate basic auth
|
||||
if sep.RequireBasicAuth {
|
||||
err := handleBasicAuth(w, r, sep)
|
||||
@ -137,11 +151,20 @@ func (router *Router) StartProxyService() error {
|
||||
}
|
||||
}
|
||||
|
||||
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: sep.Domain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: sep.RequireTLS,
|
||||
PathPrefix: "",
|
||||
selectedUpstream, err := router.loadBalancer.GetRequestUpstreamTarget(w, r, sep.ActiveOrigins, sep.UseStickySession)
|
||||
if err != nil {
|
||||
http.ServeFile(w, r, "./web/hosterror.html")
|
||||
router.Option.Logger.PrintAndLog("dprouter", "failed to get upstream for hostname", err)
|
||||
router.logRequest(r, false, 404, "vdir-http", r.Host)
|
||||
}
|
||||
selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: selectedUpstream.RequireTLS,
|
||||
HostHeaderOverwrite: sep.RequestHostOverwrite,
|
||||
NoRemoveHopByHop: sep.DisableHopByHopHeaderRemoval,
|
||||
PathPrefix: "",
|
||||
Version: sep.parent.Option.HostVersion,
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -173,7 +196,7 @@ func (router *Router) StartProxyService() error {
|
||||
IdleTimeout: 120 * time.Second,
|
||||
}
|
||||
|
||||
log.Println("Starting HTTP-to-HTTPS redirector (port 80)")
|
||||
router.Option.Logger.PrintAndLog("dprouter", "Starting HTTP-to-HTTPS redirector (port 80)", nil)
|
||||
|
||||
//Create a redirection stop channel
|
||||
stopChan := make(chan bool)
|
||||
@ -184,7 +207,7 @@ func (router *Router) StartProxyService() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
httpServer.Shutdown(ctx)
|
||||
log.Println("HTTP to HTTPS redirection listener stopped")
|
||||
router.Option.Logger.PrintAndLog("dprouter", "HTTP to HTTPS redirection listener stopped", nil)
|
||||
}()
|
||||
|
||||
//Start the http server that listens to port 80 and redirect to 443
|
||||
@ -199,10 +222,10 @@ func (router *Router) StartProxyService() error {
|
||||
}
|
||||
|
||||
//Start the TLS server
|
||||
log.Println("Reverse proxy service started in the background (TLS mode)")
|
||||
router.Option.Logger.PrintAndLog("dprouter", "Reverse proxy service started in the background (TLS mode)", nil)
|
||||
go func() {
|
||||
if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("Could not start proxy server: %v\n", err)
|
||||
router.Option.Logger.PrintAndLog("dprouter", "Could not start proxy server", err)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
@ -210,10 +233,9 @@ func (router *Router) StartProxyService() error {
|
||||
router.tlsListener = nil
|
||||
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
|
||||
router.Running = true
|
||||
log.Println("Reverse proxy service started in the background (Plain HTTP mode)")
|
||||
router.Option.Logger.PrintAndLog("dprouter", "Reverse proxy service started in the background (Plain HTTP mode)", nil)
|
||||
go func() {
|
||||
router.server.ListenAndServe()
|
||||
//log.Println("[DynamicProxy] " + err.Error())
|
||||
}()
|
||||
}
|
||||
|
||||
@ -231,10 +253,23 @@ func (router *Router) StopProxyService() error {
|
||||
return err
|
||||
}
|
||||
|
||||
//Stop TLS listener
|
||||
if router.tlsListener != nil {
|
||||
router.tlsListener.Close()
|
||||
}
|
||||
|
||||
//Stop rate limiter
|
||||
if router.rateLimterStop != nil {
|
||||
go func() {
|
||||
// As the rate timer loop has a 1 sec ticker
|
||||
// stop the rate limiter in go routine can prevent
|
||||
// front end from freezing for 1 sec
|
||||
router.rateLimterStop <- true
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
//Stop TLS redirection (from port 80)
|
||||
if router.tlsRedirectStop != nil {
|
||||
router.tlsRedirectStop <- true
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -30,7 +31,6 @@ func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@ -49,16 +49,13 @@ func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error {
|
||||
}
|
||||
|
||||
// Add a user defined header to the list, duplicates will be automatically removed
|
||||
func (ep *ProxyEndpoint) AddUserDefinedHeader(key string, value string) error {
|
||||
if ep.UserDefinedHeaderExists(key) {
|
||||
ep.RemoveUserDefinedHeader(key)
|
||||
func (ep *ProxyEndpoint) AddUserDefinedHeader(newHeaderRule *UserDefinedHeader) error {
|
||||
if ep.UserDefinedHeaderExists(newHeaderRule.Key) {
|
||||
ep.RemoveUserDefinedHeader(newHeaderRule.Key)
|
||||
}
|
||||
|
||||
ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, &UserDefinedHeader{
|
||||
Key: cases.Title(language.Und, cases.NoLower).String(key), //e.g. x-proxy-by -> X-Proxy-By
|
||||
Value: value,
|
||||
})
|
||||
|
||||
newHeaderRule.Key = cases.Title(language.Und, cases.NoLower).String(newHeaderRule.Key)
|
||||
ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, newHeaderRule)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -137,6 +134,116 @@ func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint)
|
||||
return readyRoutingRule, nil
|
||||
}
|
||||
|
||||
/* Upstream related wrapper functions */
|
||||
//Check if there already exists another upstream with identical origin
|
||||
func (ep *ProxyEndpoint) UpstreamOriginExists(originURL string) bool {
|
||||
for _, origin := range ep.ActiveOrigins {
|
||||
if origin.OriginIpOrDomain == originURL {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, origin := range ep.InactiveOrigins {
|
||||
if origin.OriginIpOrDomain == originURL {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get a upstream origin from given origin ip or domain
|
||||
func (ep *ProxyEndpoint) GetUpstreamOriginByMatchingIP(originIpOrDomain string) (*loadbalance.Upstream, error) {
|
||||
for _, origin := range ep.ActiveOrigins {
|
||||
if origin.OriginIpOrDomain == originIpOrDomain {
|
||||
return origin, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, origin := range ep.InactiveOrigins {
|
||||
if origin.OriginIpOrDomain == originIpOrDomain {
|
||||
return origin, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("target upstream origin not found")
|
||||
}
|
||||
|
||||
// Add upstream to endpoint and update it to runtime
|
||||
func (ep *ProxyEndpoint) AddUpstreamOrigin(newOrigin *loadbalance.Upstream, activate bool) error {
|
||||
//Check if the upstream already exists
|
||||
if ep.UpstreamOriginExists(newOrigin.OriginIpOrDomain) {
|
||||
return errors.New("upstream with same origin already exists")
|
||||
}
|
||||
|
||||
if activate {
|
||||
//Add it to the active origin list
|
||||
err := newOrigin.StartProxy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ep.ActiveOrigins = append(ep.ActiveOrigins, newOrigin)
|
||||
} else {
|
||||
//Add to inactive origin list
|
||||
ep.InactiveOrigins = append(ep.InactiveOrigins, newOrigin)
|
||||
}
|
||||
|
||||
ep.UpdateToRuntime()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove upstream from endpoint and update it to runtime
|
||||
func (ep *ProxyEndpoint) RemoveUpstreamOrigin(originIpOrDomain string) error {
|
||||
//Just to make sure there are no spaces
|
||||
originIpOrDomain = strings.TrimSpace(originIpOrDomain)
|
||||
|
||||
//Check if the upstream already been removed
|
||||
if !ep.UpstreamOriginExists(originIpOrDomain) {
|
||||
//Not exists in the first place
|
||||
return nil
|
||||
}
|
||||
|
||||
newActiveOriginList := []*loadbalance.Upstream{}
|
||||
for _, origin := range ep.ActiveOrigins {
|
||||
if origin.OriginIpOrDomain != originIpOrDomain {
|
||||
newActiveOriginList = append(newActiveOriginList, origin)
|
||||
}
|
||||
}
|
||||
|
||||
newInactiveOriginList := []*loadbalance.Upstream{}
|
||||
for _, origin := range ep.InactiveOrigins {
|
||||
if origin.OriginIpOrDomain != originIpOrDomain {
|
||||
newInactiveOriginList = append(newInactiveOriginList, origin)
|
||||
}
|
||||
}
|
||||
//Ok, set the origin list to the new one
|
||||
ep.ActiveOrigins = newActiveOriginList
|
||||
ep.InactiveOrigins = newInactiveOriginList
|
||||
ep.UpdateToRuntime()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the proxy endpoint hostname or alias name contains subdomain wildcard
|
||||
func (ep *ProxyEndpoint) ContainsWildcardName(skipAliasCheck bool) bool {
|
||||
hostname := ep.RootOrMatchingDomain
|
||||
aliasHostnames := ep.MatchingDomainAlias
|
||||
|
||||
wildcardCheck := func(hostname string) bool {
|
||||
return len(hostname) > 0 && hostname[0] == '*'
|
||||
}
|
||||
|
||||
if wildcardCheck(hostname) {
|
||||
return true
|
||||
}
|
||||
|
||||
if !skipAliasCheck {
|
||||
for _, aliasHostname := range aliasHostnames {
|
||||
if wildcardCheck(aliasHostname) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Create a deep clone object of the proxy endpoint
|
||||
// Note the returned object is not activated. Call to prepare function before pushing into runtime
|
||||
func (ep *ProxyEndpoint) Clone() *ProxyEndpoint {
|
||||
|
103
src/mod/dynamicproxy/loadbalance/loadbalance.go
Normal file
103
src/mod/dynamicproxy/loadbalance/loadbalance.go
Normal file
@ -0,0 +1,103 @@
|
||||
package loadbalance
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/sessions"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
)
|
||||
|
||||
/*
|
||||
Load Balancer
|
||||
|
||||
Handleing load balance request for upstream destinations
|
||||
*/
|
||||
|
||||
type Options struct {
|
||||
SystemUUID string //Use for the session store
|
||||
UseActiveHealthCheck bool //Use active health check, default to false
|
||||
Geodb *geodb.Store //GeoIP resolver for checking incoming request origin country
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
type RouteManager struct {
|
||||
SessionStore *sessions.CookieStore
|
||||
LoadBalanceMap sync.Map //Sync map to store the last load balance state of a given node
|
||||
OnlineStatusMap sync.Map //Sync map to store the online status of a given ip address or domain name
|
||||
onlineStatusTickerStop chan bool //Stopping channel for the online status pinger
|
||||
Options Options //Options for the load balancer
|
||||
}
|
||||
|
||||
/* Upstream or Origin Server */
|
||||
type Upstream struct {
|
||||
//Upstream Proxy Configs
|
||||
OriginIpOrDomain string //Target IP address or domain name with port
|
||||
RequireTLS bool //Require TLS connection
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||
|
||||
//Load balancing configs
|
||||
Weight int //Random weight for round robin, 0 for fallback only
|
||||
MaxConn int //TODO: Maxmium connection to this server, 0 for unlimited
|
||||
|
||||
//currentConnectionCounts atomic.Uint64 //Counter for number of client currently connected
|
||||
proxy *dpcore.ReverseProxy
|
||||
}
|
||||
|
||||
// Create a new load balancer
|
||||
func NewLoadBalancer(options *Options) *RouteManager {
|
||||
if options.SystemUUID == "" {
|
||||
//System UUID not passed in. Use random key
|
||||
options.SystemUUID = uuid.New().String()
|
||||
}
|
||||
|
||||
//Generate a session store for stickySession
|
||||
store := sessions.NewCookieStore([]byte(options.SystemUUID))
|
||||
return &RouteManager{
|
||||
SessionStore: store,
|
||||
LoadBalanceMap: sync.Map{},
|
||||
OnlineStatusMap: sync.Map{},
|
||||
onlineStatusTickerStop: nil,
|
||||
Options: *options,
|
||||
}
|
||||
}
|
||||
|
||||
// UpstreamsReady checks if the group of upstreams contains at least one
|
||||
// origin server that is ready
|
||||
func (m *RouteManager) UpstreamsReady(upstreams []*Upstream) bool {
|
||||
for _, upstream := range upstreams {
|
||||
if upstream.IsReady() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String format and convert a list of upstream into a string representations
|
||||
func GetUpstreamsAsString(upstreams []*Upstream) string {
|
||||
targets := []string{}
|
||||
for _, upstream := range upstreams {
|
||||
targets = append(targets, upstream.String())
|
||||
}
|
||||
if len(targets) == 0 {
|
||||
//No upstream
|
||||
return "(no upstream config)"
|
||||
}
|
||||
return strings.Join(targets, ", ")
|
||||
}
|
||||
|
||||
func (m *RouteManager) Close() {
|
||||
if m.onlineStatusTickerStop != nil {
|
||||
m.onlineStatusTickerStop <- true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Log Println, replace all log.Println or fmt.Println with this
|
||||
func (m *RouteManager) println(message string, err error) {
|
||||
m.Options.Logger.PrintAndLog("LoadBalancer", message, err)
|
||||
}
|
39
src/mod/dynamicproxy/loadbalance/onlineStatus.go
Normal file
39
src/mod/dynamicproxy/loadbalance/onlineStatus.go
Normal file
@ -0,0 +1,39 @@
|
||||
package loadbalance
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Return the last ping status to see if the target is online
|
||||
func (m *RouteManager) IsTargetOnline(matchingDomainOrIp string) bool {
|
||||
value, ok := m.LoadBalanceMap.Load(matchingDomainOrIp)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
isOnline, ok := value.(bool)
|
||||
return ok && isOnline
|
||||
}
|
||||
|
||||
// Ping a target to see if it is online
|
||||
func PingTarget(targetMatchingDomainOrIp string, requireTLS bool) bool {
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
url := targetMatchingDomainOrIp
|
||||
if requireTLS {
|
||||
url = "https://" + url
|
||||
} else {
|
||||
url = "http://" + url
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.StatusCode >= 200 && resp.StatusCode <= 600
|
||||
}
|
177
src/mod/dynamicproxy/loadbalance/originPicker.go
Normal file
177
src/mod/dynamicproxy/loadbalance/originPicker.go
Normal file
@ -0,0 +1,177 @@
|
||||
package loadbalance
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
/*
|
||||
Origin Picker
|
||||
|
||||
This script contains the code to pick the best origin
|
||||
by this request.
|
||||
*/
|
||||
|
||||
// GetRequestUpstreamTarget return the upstream target where this
|
||||
// request should be routed
|
||||
func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.Request, origins []*Upstream, useStickySession bool) (*Upstream, error) {
|
||||
if len(origins) == 0 {
|
||||
return nil, errors.New("no upstream is defined for this host")
|
||||
}
|
||||
var targetOrigin = origins[0]
|
||||
if useStickySession {
|
||||
//Use stick session, check which origins this request previously used
|
||||
targetOriginId, err := m.getSessionHandler(r, origins)
|
||||
if err != nil {
|
||||
//No valid session found. Assign a new upstream
|
||||
targetOrigin, index, err := getRandomUpstreamByWeight(origins)
|
||||
if err != nil {
|
||||
m.println("Unable to get random upstream", err)
|
||||
targetOrigin = origins[0]
|
||||
index = 0
|
||||
}
|
||||
m.setSessionHandler(w, r, targetOrigin.OriginIpOrDomain, index)
|
||||
return targetOrigin, nil
|
||||
}
|
||||
|
||||
//Valid session found. Resume the previous session
|
||||
return origins[targetOriginId], nil
|
||||
} else {
|
||||
//Do not use stick session. Get a random one
|
||||
var err error
|
||||
targetOrigin, _, err = getRandomUpstreamByWeight(origins)
|
||||
if err != nil {
|
||||
m.println("Failed to get next origin", err)
|
||||
targetOrigin = origins[0]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//fmt.Println("DEBUG: Picking origin " + targetOrigin.OriginIpOrDomain)
|
||||
return targetOrigin, nil
|
||||
}
|
||||
|
||||
/* Features related to session access */
|
||||
//Set a new origin for this connection by session
|
||||
func (m *RouteManager) setSessionHandler(w http.ResponseWriter, r *http.Request, originIpOrDomain string, index int) error {
|
||||
session, err := m.SessionStore.Get(r, "STICKYSESSION")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
session.Values["zr_sid_origin"] = originIpOrDomain
|
||||
session.Values["zr_sid_index"] = index
|
||||
session.Options.MaxAge = 86400 //1 day
|
||||
session.Options.Path = "/"
|
||||
err = session.Save(r, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the previous connected origin from session
|
||||
func (m *RouteManager) getSessionHandler(r *http.Request, upstreams []*Upstream) (int, error) {
|
||||
// Get existing session
|
||||
session, err := m.SessionStore.Get(r, "STICKYSESSION")
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Retrieve session values for origin
|
||||
originDomainRaw := session.Values["zr_sid_origin"]
|
||||
originIDRaw := session.Values["zr_sid_index"]
|
||||
|
||||
if originDomainRaw == nil || originIDRaw == nil {
|
||||
return -1, errors.New("no session has been set")
|
||||
}
|
||||
originDomain := originDomainRaw.(string)
|
||||
originID := originIDRaw.(int)
|
||||
|
||||
//Check if it has been modified
|
||||
if len(upstreams) < originID || upstreams[originID].OriginIpOrDomain != originDomain {
|
||||
//Mismatch or upstreams has been updated
|
||||
return -1, errors.New("upstreams has been changed")
|
||||
}
|
||||
|
||||
return originID, nil
|
||||
}
|
||||
|
||||
/* Functions related to random upstream picking */
|
||||
// Get a random upstream by the weights defined in Upstream struct, return the upstream, index value and any error
|
||||
func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) {
|
||||
// If there is only one upstream, return it
|
||||
if len(upstreams) == 1 {
|
||||
return upstreams[0], 0, nil
|
||||
}
|
||||
|
||||
// Preserve the index with upstreams
|
||||
type upstreamWithIndex struct {
|
||||
Upstream *Upstream
|
||||
Index int
|
||||
}
|
||||
|
||||
// Calculate total weight for upstreams with weight > 0
|
||||
totalWeight := 0
|
||||
fallbackUpstreams := make([]upstreamWithIndex, 0, len(upstreams))
|
||||
|
||||
for index, upstream := range upstreams {
|
||||
if upstream.Weight > 0 {
|
||||
totalWeight += upstream.Weight
|
||||
} else {
|
||||
// Collect fallback upstreams
|
||||
fallbackUpstreams = append(fallbackUpstreams, upstreamWithIndex{upstream, index})
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no upstreams with weight > 0, return a fallback upstream if available
|
||||
if totalWeight == 0 {
|
||||
if len(fallbackUpstreams) > 0 {
|
||||
// Randomly select one of the fallback upstreams
|
||||
randIndex := rand.Intn(len(fallbackUpstreams))
|
||||
return fallbackUpstreams[randIndex].Upstream, fallbackUpstreams[randIndex].Index, nil
|
||||
}
|
||||
// No upstreams available at all
|
||||
return nil, -1, errors.New("no valid upstream servers available")
|
||||
}
|
||||
|
||||
// Random weight between 0 and total weight
|
||||
randomWeight := rand.Intn(totalWeight)
|
||||
|
||||
// Select an upstream based on the random weight
|
||||
for index, upstream := range upstreams {
|
||||
if upstream.Weight > 0 { // Only consider upstreams with weight > 0
|
||||
if randomWeight < upstream.Weight {
|
||||
// Return the selected upstream and its index
|
||||
return upstream, index, nil
|
||||
}
|
||||
randomWeight -= upstream.Weight
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, it means we should return a fallback upstream if available
|
||||
if len(fallbackUpstreams) > 0 {
|
||||
randIndex := rand.Intn(len(fallbackUpstreams))
|
||||
return fallbackUpstreams[randIndex].Upstream, fallbackUpstreams[randIndex].Index, nil
|
||||
}
|
||||
|
||||
return nil, -1, errors.New("failed to pick an upstream origin server")
|
||||
}
|
||||
|
||||
// IntRange returns a random integer in the range from min to max.
|
||||
/*
|
||||
func intRange(min, max int) (int, error) {
|
||||
var result int
|
||||
switch {
|
||||
case min > max:
|
||||
// Fail with error
|
||||
return result, errors.New("min is greater than max")
|
||||
case max == min:
|
||||
result = max
|
||||
case max > min:
|
||||
b := rand.Intn(max-min) + min
|
||||
result = min + int(b)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
*/
|
77
src/mod/dynamicproxy/loadbalance/upstream.go
Normal file
77
src/mod/dynamicproxy/loadbalance/upstream.go
Normal file
@ -0,0 +1,77 @@
|
||||
package loadbalance
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
)
|
||||
|
||||
// StartProxy create and start a HTTP proxy using dpcore
|
||||
// Example of webProxyEndpoint: https://example.com:443 or http://192.168.1.100:8080
|
||||
func (u *Upstream) StartProxy() error {
|
||||
//Filter the tailing slash if any
|
||||
domain := u.OriginIpOrDomain
|
||||
if len(domain) == 0 {
|
||||
return errors.New("invalid endpoint config")
|
||||
}
|
||||
if domain[len(domain)-1:] == "/" {
|
||||
domain = domain[:len(domain)-1]
|
||||
}
|
||||
|
||||
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
||||
//TLS is not hardcoded in proxy target domain
|
||||
if u.RequireTLS {
|
||||
domain = "https://" + domain
|
||||
} else {
|
||||
domain = "http://" + domain
|
||||
}
|
||||
}
|
||||
|
||||
//Create a new proxy agent for this upstream
|
||||
path, err := url.Parse(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
|
||||
IgnoreTLSVerification: u.SkipCertValidations,
|
||||
FlushInterval: 100 * time.Millisecond,
|
||||
})
|
||||
|
||||
u.proxy = proxy
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsReady return the proxy ready state of the upstream server
|
||||
// Return false if StartProxy() is not called on this upstream before
|
||||
func (u *Upstream) IsReady() bool {
|
||||
return u.proxy != nil
|
||||
}
|
||||
|
||||
// Clone return a new deep copy object of the identical upstream
|
||||
func (u *Upstream) Clone() *Upstream {
|
||||
newUpstream := Upstream{}
|
||||
js, _ := json.Marshal(u)
|
||||
json.Unmarshal(js, &newUpstream)
|
||||
return &newUpstream
|
||||
}
|
||||
|
||||
// ServeHTTP uses this upstream proxy router to route the current request, return the status code and error if any
|
||||
func (u *Upstream) ServeHTTP(w http.ResponseWriter, r *http.Request, rrr *dpcore.ResponseRewriteRuleSet) (int, error) {
|
||||
//Auto rewrite to upstream origin if not set
|
||||
if rrr.ProxyDomain == "" {
|
||||
rrr.ProxyDomain = u.OriginIpOrDomain
|
||||
}
|
||||
|
||||
return u.proxy.ServeHTTP(w, r, rrr)
|
||||
}
|
||||
|
||||
// String return the string representations of endpoints in this upstream
|
||||
func (u *Upstream) String() string {
|
||||
return u.OriginIpOrDomain
|
||||
}
|
197
src/mod/dynamicproxy/permissionpolicy/permissionpolicy.go
Normal file
197
src/mod/dynamicproxy/permissionpolicy/permissionpolicy.go
Normal file
@ -0,0 +1,197 @@
|
||||
package permissionpolicy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Permisson Policy
|
||||
|
||||
This is a permission policy header modifier that changes
|
||||
the request permission related policy fields
|
||||
|
||||
author: tobychui
|
||||
*/
|
||||
|
||||
type PermissionsPolicy struct {
|
||||
Accelerometer []string `json:"accelerometer"`
|
||||
AmbientLightSensor []string `json:"ambient_light_sensor"`
|
||||
Autoplay []string `json:"autoplay"`
|
||||
Battery []string `json:"battery"`
|
||||
Camera []string `json:"camera"`
|
||||
CrossOriginIsolated []string `json:"cross_origin_isolated"`
|
||||
DisplayCapture []string `json:"display_capture"`
|
||||
DocumentDomain []string `json:"document_domain"`
|
||||
EncryptedMedia []string `json:"encrypted_media"`
|
||||
ExecutionWhileNotRendered []string `json:"execution_while_not_rendered"`
|
||||
ExecutionWhileOutOfView []string `json:"execution_while_out_of_viewport"`
|
||||
Fullscreen []string `json:"fullscreen"`
|
||||
Geolocation []string `json:"geolocation"`
|
||||
Gyroscope []string `json:"gyroscope"`
|
||||
KeyboardMap []string `json:"keyboard_map"`
|
||||
Magnetometer []string `json:"magnetometer"`
|
||||
Microphone []string `json:"microphone"`
|
||||
Midi []string `json:"midi"`
|
||||
NavigationOverride []string `json:"navigation_override"`
|
||||
Payment []string `json:"payment"`
|
||||
PictureInPicture []string `json:"picture_in_picture"`
|
||||
PublicKeyCredentialsGet []string `json:"publickey_credentials_get"`
|
||||
ScreenWakeLock []string `json:"screen_wake_lock"`
|
||||
SyncXHR []string `json:"sync_xhr"`
|
||||
USB []string `json:"usb"`
|
||||
WebShare []string `json:"web_share"`
|
||||
XRSpatialTracking []string `json:"xr_spatial_tracking"`
|
||||
ClipboardRead []string `json:"clipboard_read"`
|
||||
ClipboardWrite []string `json:"clipboard_write"`
|
||||
Gamepad []string `json:"gamepad"`
|
||||
SpeakerSelection []string `json:"speaker_selection"`
|
||||
ConversionMeasurement []string `json:"conversion_measurement"`
|
||||
FocusWithoutUserActivation []string `json:"focus_without_user_activation"`
|
||||
HID []string `json:"hid"`
|
||||
IdleDetection []string `json:"idle_detection"`
|
||||
InterestCohort []string `json:"interest_cohort"`
|
||||
Serial []string `json:"serial"`
|
||||
SyncScript []string `json:"sync_script"`
|
||||
TrustTokenRedemption []string `json:"trust_token_redemption"`
|
||||
Unload []string `json:"unload"`
|
||||
WindowPlacement []string `json:"window_placement"`
|
||||
VerticalScroll []string `json:"vertical_scroll"`
|
||||
}
|
||||
|
||||
// GetDefaultPermissionPolicy returns a PermissionsPolicy struct with all policies set to *
|
||||
func GetDefaultPermissionPolicy() *PermissionsPolicy {
|
||||
return &PermissionsPolicy{
|
||||
Accelerometer: []string{"*"},
|
||||
AmbientLightSensor: []string{"*"},
|
||||
Autoplay: []string{"*"},
|
||||
Battery: []string{"*"},
|
||||
Camera: []string{"*"},
|
||||
CrossOriginIsolated: []string{"*"},
|
||||
DisplayCapture: []string{"*"},
|
||||
DocumentDomain: []string{"*"},
|
||||
EncryptedMedia: []string{"*"},
|
||||
ExecutionWhileNotRendered: []string{"*"},
|
||||
ExecutionWhileOutOfView: []string{"*"},
|
||||
Fullscreen: []string{"*"},
|
||||
Geolocation: []string{"*"},
|
||||
Gyroscope: []string{"*"},
|
||||
KeyboardMap: []string{"*"},
|
||||
Magnetometer: []string{"*"},
|
||||
Microphone: []string{"*"},
|
||||
Midi: []string{"*"},
|
||||
NavigationOverride: []string{"*"},
|
||||
Payment: []string{"*"},
|
||||
PictureInPicture: []string{"*"},
|
||||
PublicKeyCredentialsGet: []string{"*"},
|
||||
ScreenWakeLock: []string{"*"},
|
||||
SyncXHR: []string{"*"},
|
||||
USB: []string{"*"},
|
||||
WebShare: []string{"*"},
|
||||
XRSpatialTracking: []string{"*"},
|
||||
ClipboardRead: []string{"*"},
|
||||
ClipboardWrite: []string{"*"},
|
||||
Gamepad: []string{"*"},
|
||||
SpeakerSelection: []string{"*"},
|
||||
ConversionMeasurement: []string{"*"},
|
||||
FocusWithoutUserActivation: []string{"*"},
|
||||
HID: []string{"*"},
|
||||
IdleDetection: []string{"*"},
|
||||
InterestCohort: []string{"*"},
|
||||
Serial: []string{"*"},
|
||||
SyncScript: []string{"*"},
|
||||
TrustTokenRedemption: []string{"*"},
|
||||
Unload: []string{"*"},
|
||||
WindowPlacement: []string{"*"},
|
||||
VerticalScroll: []string{"*"},
|
||||
}
|
||||
}
|
||||
|
||||
// ToKeyValueHeader convert a permission policy struct into a key value string header
|
||||
func (policy *PermissionsPolicy) ToKeyValueHeader() []string {
|
||||
policyHeader := []string{}
|
||||
|
||||
// Helper function to add policy directives
|
||||
addDirective := func(name string, sources []string) {
|
||||
if len(sources) > 0 {
|
||||
if sources[0] == "*" {
|
||||
//Allow all
|
||||
policyHeader = append(policyHeader, fmt.Sprintf("%s=%s", name, "*"))
|
||||
} else {
|
||||
//Other than "self" which do not need double quote, others domain need double quote in place
|
||||
formatedSources := []string{}
|
||||
for _, source := range sources {
|
||||
if source == "self" {
|
||||
formatedSources = append(formatedSources, "self")
|
||||
} else {
|
||||
formatedSources = append(formatedSources, "\""+source+"\"")
|
||||
}
|
||||
}
|
||||
policyHeader = append(policyHeader, fmt.Sprintf("%s=(%s)", name, strings.Join(formatedSources, " ")))
|
||||
}
|
||||
} else {
|
||||
//There are no setting for this field. Assume no permission
|
||||
policyHeader = append(policyHeader, fmt.Sprintf("%s=()", name))
|
||||
}
|
||||
}
|
||||
|
||||
// Add each policy directive to the header
|
||||
addDirective("accelerometer", policy.Accelerometer)
|
||||
addDirective("ambient-light-sensor", policy.AmbientLightSensor)
|
||||
addDirective("autoplay", policy.Autoplay)
|
||||
addDirective("battery", policy.Battery)
|
||||
addDirective("camera", policy.Camera)
|
||||
addDirective("cross-origin-isolated", policy.CrossOriginIsolated)
|
||||
addDirective("display-capture", policy.DisplayCapture)
|
||||
addDirective("document-domain", policy.DocumentDomain)
|
||||
addDirective("encrypted-media", policy.EncryptedMedia)
|
||||
addDirective("execution-while-not-rendered", policy.ExecutionWhileNotRendered)
|
||||
addDirective("execution-while-out-of-viewport", policy.ExecutionWhileOutOfView)
|
||||
addDirective("fullscreen", policy.Fullscreen)
|
||||
addDirective("geolocation", policy.Geolocation)
|
||||
addDirective("gyroscope", policy.Gyroscope)
|
||||
addDirective("keyboard-map", policy.KeyboardMap)
|
||||
addDirective("magnetometer", policy.Magnetometer)
|
||||
addDirective("microphone", policy.Microphone)
|
||||
addDirective("midi", policy.Midi)
|
||||
addDirective("navigation-override", policy.NavigationOverride)
|
||||
addDirective("payment", policy.Payment)
|
||||
addDirective("picture-in-picture", policy.PictureInPicture)
|
||||
addDirective("publickey-credentials-get", policy.PublicKeyCredentialsGet)
|
||||
addDirective("screen-wake-lock", policy.ScreenWakeLock)
|
||||
addDirective("sync-xhr", policy.SyncXHR)
|
||||
addDirective("usb", policy.USB)
|
||||
addDirective("web-share", policy.WebShare)
|
||||
addDirective("xr-spatial-tracking", policy.XRSpatialTracking)
|
||||
addDirective("clipboard-read", policy.ClipboardRead)
|
||||
addDirective("clipboard-write", policy.ClipboardWrite)
|
||||
addDirective("gamepad", policy.Gamepad)
|
||||
addDirective("speaker-selection", policy.SpeakerSelection)
|
||||
addDirective("conversion-measurement", policy.ConversionMeasurement)
|
||||
addDirective("focus-without-user-activation", policy.FocusWithoutUserActivation)
|
||||
addDirective("hid", policy.HID)
|
||||
addDirective("idle-detection", policy.IdleDetection)
|
||||
addDirective("interest-cohort", policy.InterestCohort)
|
||||
addDirective("serial", policy.Serial)
|
||||
addDirective("sync-script", policy.SyncScript)
|
||||
addDirective("trust-token-redemption", policy.TrustTokenRedemption)
|
||||
addDirective("unload", policy.Unload)
|
||||
addDirective("window-placement", policy.WindowPlacement)
|
||||
addDirective("vertical-scroll", policy.VerticalScroll)
|
||||
|
||||
// Join the directives and set the header
|
||||
policyHeaderValue := strings.Join(policyHeader, ", ")
|
||||
return []string{"Permissions-Policy", policyHeaderValue}
|
||||
}
|
||||
|
||||
// InjectPermissionPolicyHeader inject the permission policy into headers
|
||||
func InjectPermissionPolicyHeader(w http.ResponseWriter, policy *PermissionsPolicy) {
|
||||
//Keep the original Permission Policy if exists, or there are no policy given
|
||||
if policy == nil || w.Header().Get("Permissions-Policy") != "" {
|
||||
return
|
||||
}
|
||||
headerKV := policy.ToKeyValueHeader()
|
||||
//Inject the new policy into the header
|
||||
w.Header().Set(headerKV[0], headerKV[1])
|
||||
}
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import (
|
||||
"imuslab.com/zoraxy/mod/websocketproxy"
|
||||
)
|
||||
|
||||
// Check if the request URI matches any of the proxy endpoint
|
||||
func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
|
||||
var targetProxyEndpoint *ProxyEndpoint = nil
|
||||
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||
@ -30,6 +31,7 @@ func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *P
|
||||
return targetProxyEndpoint
|
||||
}
|
||||
|
||||
// Get the proxy endpoint from hostname, which might includes checking of wildcard certificates
|
||||
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
|
||||
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
||||
ep, ok := router.ProxyEndpoints.Load(hostname)
|
||||
@ -111,18 +113,21 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||
r.Header.Set("X-Forwarded-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)
|
||||
}
|
||||
/* Load balancing */
|
||||
selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession)
|
||||
if err != nil {
|
||||
http.ServeFile(w, r, "./web/rperror.html")
|
||||
h.Parent.Option.Logger.PrintAndLog("proxy", "Failed to assign an upstream for this request", err)
|
||||
h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname())
|
||||
return
|
||||
}
|
||||
|
||||
/* WebSocket automatic proxy */
|
||||
requestURL := r.URL.String()
|
||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||
wsRedirectionEndpoint := target.Domain
|
||||
wsRedirectionEndpoint := selectedUpstream.OriginIpOrDomain
|
||||
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
||||
//Append / to the end of the redirection endpoint if not exists
|
||||
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
|
||||
@ -132,13 +137,14 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
requestURL = requestURL[1:]
|
||||
}
|
||||
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL)
|
||||
if target.RequireTLS {
|
||||
if selectedUpstream.RequireTLS {
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
||||
}
|
||||
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
|
||||
h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain)
|
||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||
SkipTLSValidation: target.SkipCertValidations,
|
||||
SkipOriginCheck: target.SkipWebSocketOriginCheck,
|
||||
SkipTLSValidation: selectedUpstream.SkipCertValidations,
|
||||
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
|
||||
Logger: h.Parent.Option.Logger,
|
||||
})
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
@ -152,28 +158,35 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
r.URL, _ = url.Parse(originalHostHeader)
|
||||
}
|
||||
|
||||
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: target.Domain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: target.RequireTLS,
|
||||
NoCache: h.Parent.Option.NoCache,
|
||||
PathPrefix: "",
|
||||
//Build downstream and upstream header rules
|
||||
upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()
|
||||
|
||||
statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: selectedUpstream.RequireTLS,
|
||||
NoCache: h.Parent.Option.NoCache,
|
||||
PathPrefix: "",
|
||||
UpstreamHeaders: upstreamHeaders,
|
||||
DownstreamHeaders: downstreamHeaders,
|
||||
HostHeaderOverwrite: target.RequestHostOverwrite,
|
||||
NoRemoveHopByHop: target.DisableHopByHopHeaderRemoval,
|
||||
Version: target.parent.Option.HostVersion,
|
||||
})
|
||||
|
||||
var dnsError *net.DNSError
|
||||
if err != nil {
|
||||
if errors.As(err, &dnsError) {
|
||||
http.ServeFile(w, r, "./web/hosterror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 404, "subdomain-http", target.Domain)
|
||||
h.Parent.logRequest(r, false, 404, "host-http", r.URL.Hostname())
|
||||
} else {
|
||||
http.ServeFile(w, r, "./web/rperror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 521, "subdomain-http", target.Domain)
|
||||
//TODO: Take this upstream offline automatically
|
||||
h.Parent.logRequest(r, false, 521, "host-http", r.URL.Hostname())
|
||||
}
|
||||
}
|
||||
|
||||
h.logRequest(r, true, 200, "subdomain-http", target.Domain)
|
||||
h.Parent.logRequest(r, true, statusCode, "host-http", r.URL.Hostname())
|
||||
}
|
||||
|
||||
// Handle vdir type request
|
||||
@ -184,13 +197,6 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||
r.Header.Set("X-Forwarded-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" {
|
||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||
@ -202,10 +208,11 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
if target.RequireTLS {
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
||||
}
|
||||
h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||
h.Parent.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||
SkipTLSValidation: target.SkipCertValidations,
|
||||
SkipOriginCheck: target.parent.SkipWebSocketOriginCheck,
|
||||
SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
|
||||
Logger: h.Parent.Option.Logger,
|
||||
})
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
@ -219,11 +226,18 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
r.URL, _ = url.Parse(originalHostHeader)
|
||||
}
|
||||
|
||||
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: target.Domain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: target.RequireTLS,
|
||||
PathPrefix: target.MatchingPath,
|
||||
//Build downstream and upstream header rules
|
||||
upstreamHeaders, downstreamHeaders := target.parent.SplitInboundOutboundHeaders()
|
||||
|
||||
statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: target.Domain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: target.RequireTLS,
|
||||
PathPrefix: target.MatchingPath,
|
||||
UpstreamHeaders: upstreamHeaders,
|
||||
DownstreamHeaders: downstreamHeaders,
|
||||
HostHeaderOverwrite: target.parent.RequestHostOverwrite,
|
||||
Version: target.parent.parent.Option.HostVersion,
|
||||
})
|
||||
|
||||
var dnsError *net.DNSError
|
||||
@ -231,23 +245,24 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
if errors.As(err, &dnsError) {
|
||||
http.ServeFile(w, r, "./web/hosterror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 404, "vdir-http", target.Domain)
|
||||
h.Parent.logRequest(r, false, 404, "vdir-http", target.Domain)
|
||||
} else {
|
||||
http.ServeFile(w, r, "./web/rperror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 521, "vdir-http", target.Domain)
|
||||
h.Parent.logRequest(r, false, 521, "vdir-http", target.Domain)
|
||||
}
|
||||
}
|
||||
h.logRequest(r, true, 200, "vdir-http", target.Domain)
|
||||
h.Parent.logRequest(r, true, statusCode, "vdir-http", target.Domain)
|
||||
|
||||
}
|
||||
|
||||
func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) {
|
||||
if h.Parent.Option.StatisticCollector != nil {
|
||||
// This logger collect data for the statistical analysis. For log to file logger, check the Logger and LogHTTPRequest handler
|
||||
func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) {
|
||||
if router.Option.StatisticCollector != nil {
|
||||
go func() {
|
||||
requestInfo := statistic.RequestInfo{
|
||||
IpAddr: netutils.GetRequesterIP(r),
|
||||
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||
RequestOriginalCountryISOCode: router.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||
Succ: succ,
|
||||
StatusCode: statusCode,
|
||||
ForwardType: forwardType,
|
||||
@ -256,7 +271,8 @@ func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, fo
|
||||
RequestURL: r.Host + r.RequestURI,
|
||||
Target: target,
|
||||
}
|
||||
h.Parent.Option.StatisticCollector.RecordRequest(requestInfo)
|
||||
router.Option.StatisticCollector.RecordRequest(requestInfo)
|
||||
}()
|
||||
}
|
||||
router.Option.Logger.LogHTTPRequest(r, forwardType, statusCode)
|
||||
}
|
||||
|
119
src/mod/dynamicproxy/ratelimit.go
Normal file
119
src/mod/dynamicproxy/ratelimit.go
Normal file
@ -0,0 +1,119 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IpTable is a rate limiter implementation using sync.Map with atomic int64
|
||||
type RequestCountPerIpTable struct {
|
||||
table sync.Map
|
||||
}
|
||||
|
||||
// Increment the count of requests for a given IP
|
||||
func (t *RequestCountPerIpTable) Increment(ip string) {
|
||||
v, _ := t.table.LoadOrStore(ip, new(int64))
|
||||
atomic.AddInt64(v.(*int64), 1)
|
||||
}
|
||||
|
||||
// Check if the IP is in the table and if it is, check if the count is less than the limit
|
||||
func (t *RequestCountPerIpTable) Exceeded(ip string, limit int64) bool {
|
||||
v, ok := t.table.Load(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
count := atomic.LoadInt64(v.(*int64))
|
||||
return count >= limit
|
||||
}
|
||||
|
||||
// Get the count of requests for a given IP
|
||||
func (t *RequestCountPerIpTable) GetCount(ip string) int64 {
|
||||
v, ok := t.table.Load(ip)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return atomic.LoadInt64(v.(*int64))
|
||||
}
|
||||
|
||||
// Clear the IP table
|
||||
func (t *RequestCountPerIpTable) Clear() {
|
||||
t.table.Range(func(key, value interface{}) bool {
|
||||
t.table.Delete(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ProxyHandler) handleRateLimitRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||
err := h.Parent.handleRateLimit(w, r, pe)
|
||||
if err != nil {
|
||||
h.Parent.logRequest(r, false, 429, "ratelimit", r.URL.Hostname())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (router *Router) handleRateLimit(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||
//Get the real client-ip from request header
|
||||
clientIP := r.RemoteAddr
|
||||
if r.Header.Get("X-Real-Ip") == "" {
|
||||
CF_Connecting_IP := r.Header.Get("CF-Connecting-IP")
|
||||
Fastly_Client_IP := r.Header.Get("Fastly-Client-IP")
|
||||
if CF_Connecting_IP != "" {
|
||||
//Use CF Connecting IP
|
||||
clientIP = CF_Connecting_IP
|
||||
} else if Fastly_Client_IP != "" {
|
||||
//Use Fastly Client IP
|
||||
clientIP = Fastly_Client_IP
|
||||
} else {
|
||||
ips := strings.Split(clientIP, ",")
|
||||
if len(ips) > 0 {
|
||||
clientIP = strings.TrimSpace(ips[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ip, _, err := net.SplitHostPort(clientIP)
|
||||
if err != nil {
|
||||
//Default allow passthrough on error
|
||||
return nil
|
||||
}
|
||||
|
||||
router.rateLimitCounter.Increment(ip)
|
||||
|
||||
if router.rateLimitCounter.Exceeded(ip, int64(pe.RateLimit)) {
|
||||
w.WriteHeader(429)
|
||||
return errors.New("rate limit exceeded")
|
||||
}
|
||||
|
||||
// log.Println("Rate limit check", ip, ipTable.GetCount(ip))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start the ticker routine for reseting the rate limit counter every seconds
|
||||
func (r *Router) startRateLimterCounterResetTicker() error {
|
||||
if r.rateLimterStop != nil {
|
||||
return errors.New("another rate limiter ticker already running")
|
||||
}
|
||||
tickerStopChan := make(chan bool)
|
||||
r.rateLimterStop = tickerStopChan
|
||||
|
||||
counterResetTicker := time.NewTicker(1 * time.Second)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-tickerStopChan:
|
||||
r.rateLimterStop = nil
|
||||
return
|
||||
case <-counterResetTicker.C:
|
||||
r.rateLimitCounter.Clear()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package redirection
|
||||
|
||||
import (
|
||||
"log"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
@ -52,7 +52,7 @@ func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) int {
|
||||
//Invalid usage
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("500 - Internal Server Error"))
|
||||
log.Println("Target request URL do not have matching redirect rule. Check with IsRedirectable before calling HandleRedirect!")
|
||||
t.log("Target request URL do not have matching redirect rule. Check with IsRedirectable before calling HandleRedirect!", errors.New("invalid usage"))
|
||||
return 500
|
||||
}
|
||||
}
|
||||
|
@ -30,11 +30,12 @@ type RedirectRules struct {
|
||||
StatusCode int //Status Code for redirection
|
||||
}
|
||||
|
||||
func NewRuleTable(configPath string, allowRegex bool) (*RuleTable, error) {
|
||||
func NewRuleTable(configPath string, allowRegex bool, logger *logger.Logger) (*RuleTable, error) {
|
||||
thisRuleTable := RuleTable{
|
||||
rules: sync.Map{},
|
||||
configPath: configPath,
|
||||
AllowRegex: allowRegex,
|
||||
Logger: logger,
|
||||
}
|
||||
//Load all the rules from the config path
|
||||
if !utils.FileExists(configPath) {
|
||||
@ -67,7 +68,7 @@ func NewRuleTable(configPath string, allowRegex bool) (*RuleTable, error) {
|
||||
|
||||
//Map the rules into the sync map
|
||||
for _, rule := range rules {
|
||||
log.Println("Redirection rule added: " + rule.RedirectURL + " -> " + rule.TargetURL)
|
||||
thisRuleTable.log("Redirection rule added: "+rule.RedirectURL+" -> "+rule.TargetURL, nil)
|
||||
thisRuleTable.rules.Store(rule.RedirectURL, rule)
|
||||
}
|
||||
|
||||
@ -92,7 +93,7 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
|
||||
// Create a new file for writing the JSON data
|
||||
file, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
log.Printf("Error creating file %s: %s", filepath, err)
|
||||
t.log("Error creating file "+filepath, err)
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
@ -100,7 +101,7 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
|
||||
// Encode the RedirectRules object to JSON and write it to the file
|
||||
err = json.NewEncoder(file).Encode(newRule)
|
||||
if err != nil {
|
||||
log.Printf("Error encoding JSON to file %s: %s", filepath, err)
|
||||
t.log("Error encoding JSON to file "+filepath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -125,7 +126,7 @@ func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
|
||||
|
||||
// Delete the file
|
||||
if err := os.Remove(filepath); err != nil {
|
||||
log.Printf("Error deleting file %s: %s", filepath, err)
|
||||
t.log("Error deleting file "+filepath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -194,6 +195,6 @@ func (t *RuleTable) log(message string, err error) {
|
||||
log.Println("[Redirect] " + message + ": " + err.Error())
|
||||
}
|
||||
} else {
|
||||
t.Logger.PrintAndLog("Redirect", message, err)
|
||||
t.Logger.PrintAndLog("redirect", message, err)
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,10 @@ package dynamicproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
)
|
||||
@ -17,41 +19,18 @@ import (
|
||||
|
||||
// Prepare proxy route generate a proxy handler service object for your endpoint
|
||||
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
||||
//Filter the tailing slash if any
|
||||
domain := endpoint.Domain
|
||||
if len(domain) == 0 {
|
||||
return nil, errors.New("invalid endpoint config")
|
||||
}
|
||||
if domain[len(domain)-1:] == "/" {
|
||||
domain = domain[:len(domain)-1]
|
||||
}
|
||||
endpoint.Domain = domain
|
||||
|
||||
//Parse the web proxy endpoint
|
||||
webProxyEndpoint := domain
|
||||
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
||||
//TLS is not hardcoded in proxy target domain
|
||||
if endpoint.RequireTLS {
|
||||
webProxyEndpoint = "https://" + webProxyEndpoint
|
||||
} else {
|
||||
webProxyEndpoint = "http://" + webProxyEndpoint
|
||||
for _, thisOrigin := range endpoint.ActiveOrigins {
|
||||
//Create the proxy routing handler
|
||||
err := thisOrigin.StartProxy()
|
||||
if err != nil {
|
||||
log.Println("Unable to setup upstream " + thisOrigin.OriginIpOrDomain + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//Create a new proxy agent for this root
|
||||
path, err := url.Parse(webProxyEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Create the proxy routing handler
|
||||
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
|
||||
IgnoreTLSVerification: endpoint.SkipCertValidations,
|
||||
})
|
||||
endpoint.proxy = proxy
|
||||
endpoint.parent = router
|
||||
|
||||
//Prepare proxy routing hjandler for each of the virtual directories
|
||||
//Prepare proxy routing handler for each of the virtual directories
|
||||
for _, vdir := range endpoint.VirtualDirectories {
|
||||
domain := vdir.Domain
|
||||
if len(domain) == 0 {
|
||||
@ -63,7 +42,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
||||
}
|
||||
|
||||
//Parse the web proxy endpoint
|
||||
webProxyEndpoint = domain
|
||||
webProxyEndpoint := domain
|
||||
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
||||
//TLS is not hardcoded in proxy target domain
|
||||
if vdir.RequireTLS {
|
||||
@ -80,6 +59,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
||||
|
||||
proxy := dpcore.NewDynamicProxyCore(path, vdir.MatchingPath, &dpcore.DpcoreOptions{
|
||||
IgnoreTLSVerification: vdir.SkipCertValidations,
|
||||
FlushInterval: 500 * time.Millisecond,
|
||||
})
|
||||
vdir.proxy = proxy
|
||||
vdir.parent = endpoint
|
||||
@ -90,7 +70,12 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
||||
|
||||
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
|
||||
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
||||
if endpoint.proxy == nil {
|
||||
if len(endpoint.ActiveOrigins) == 0 {
|
||||
//There are no active origins. No need to check for ready
|
||||
router.ProxyEndpoints.Store(endpoint.RootOrMatchingDomain, endpoint)
|
||||
return nil
|
||||
}
|
||||
if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
|
||||
//This endpoint is not prepared
|
||||
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
||||
}
|
||||
@ -101,7 +86,7 @@ func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
||||
|
||||
// Set given Proxy Route as Root. Call to PrepareProxyRoute before adding to runtime
|
||||
func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error {
|
||||
if endpoint.proxy == nil {
|
||||
if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
|
||||
//This endpoint is not prepared
|
||||
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
<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{
|
||||
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)
|
||||
@ -22,9 +22,6 @@
|
||||
|
||||
.diagram{
|
||||
background-color: #ebebeb;
|
||||
box-shadow:
|
||||
inset 0px 11px 8px -10px #CCC,
|
||||
inset 0px -11px 8px -10px #CCC;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,11 @@ import (
|
||||
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
"imuslab.com/zoraxy/mod/tlscert"
|
||||
)
|
||||
@ -24,23 +27,27 @@ type ProxyHandler struct {
|
||||
Parent *Router
|
||||
}
|
||||
|
||||
/* Router Object Options */
|
||||
type RouterOption struct {
|
||||
HostUUID string //The UUID of Zoraxy, use for heading mod
|
||||
HostVersion string //The version of Zoraxy, use for heading mod
|
||||
Port int //Incoming port
|
||||
UseTls bool //Use TLS to serve incoming requsts
|
||||
ForceTLSLatest bool //Force TLS1.2 or above
|
||||
NoCache bool //Force set Cache-Control: no-store
|
||||
ListenOnPort80 bool //Enable port 80 http listener
|
||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||
TlsManager *tlscert.Manager
|
||||
RedirectRuleTable *redirection.RuleTable
|
||||
GeodbStore *geodb.Store //GeoIP resolver
|
||||
AccessController *access.Controller //Blacklist / whitelist controller
|
||||
StatisticCollector *statistic.Collector
|
||||
WebDirectory string //The static web server directory containing the templates folder
|
||||
HostUUID string //The UUID of Zoraxy, use for heading mod
|
||||
HostVersion string //The version of Zoraxy, use for heading mod
|
||||
Port int //Incoming port
|
||||
UseTls bool //Use TLS to serve incoming requsts
|
||||
ForceTLSLatest bool //Force TLS1.2 or above
|
||||
NoCache bool //Force set Cache-Control: no-store
|
||||
ListenOnPort80 bool //Enable port 80 http listener
|
||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||
TlsManager *tlscert.Manager //TLS manager for serving SAN certificates
|
||||
RedirectRuleTable *redirection.RuleTable //Redirection rules handler and table
|
||||
GeodbStore *geodb.Store //GeoIP resolver
|
||||
AccessController *access.Controller //Blacklist / whitelist controller
|
||||
StatisticCollector *statistic.Collector //Statistic collector for storing stats on incoming visitors
|
||||
WebDirectory string //The static web server directory containing the templates folder
|
||||
LoadBalancer *loadbalance.RouteManager //Load balancer that handle load balancing of proxy target
|
||||
Logger *logger.Logger //Logger for reverse proxy requets
|
||||
}
|
||||
|
||||
/* Router Object */
|
||||
type Router struct {
|
||||
Option *RouterOption
|
||||
ProxyEndpoints *sync.Map
|
||||
@ -49,12 +56,15 @@ type Router struct {
|
||||
mux http.Handler
|
||||
server *http.Server
|
||||
tlsListener net.Listener
|
||||
loadBalancer *loadbalance.RouteManager //Load balancer routing manager
|
||||
routingRules []*RoutingRule
|
||||
|
||||
tlsRedirectStop chan bool //Stop channel for tls redirection server
|
||||
tldMap map[string]int //Top level domain map, see tld.json
|
||||
tlsRedirectStop chan bool //Stop channel for tls redirection server
|
||||
rateLimterStop chan bool //Stop channel for rate limiter
|
||||
rateLimitCounter RequestCountPerIpTable //Request counter for rate limter
|
||||
}
|
||||
|
||||
/* Basic Auth Related Data structure*/
|
||||
// Auth credential for basic auth on certain endpoints
|
||||
type BasicAuthCredentials struct {
|
||||
Username string
|
||||
@ -72,12 +82,25 @@ type BasicAuthExceptionRule struct {
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
/* Custom Header Related Data structure */
|
||||
// Header injection direction type
|
||||
type HeaderDirection int
|
||||
|
||||
const (
|
||||
HeaderDirection_ZoraxyToUpstream HeaderDirection = 0 //Inject (or remove) header to request out-going from Zoraxy to backend server
|
||||
HeaderDirection_ZoraxyToDownstream HeaderDirection = 1 //Inject (or remove) header to request out-going from Zoraxy to client (e.g. browser)
|
||||
)
|
||||
|
||||
// User defined headers to add into a proxy endpoint
|
||||
type UserDefinedHeader struct {
|
||||
Key string
|
||||
Value string
|
||||
Direction HeaderDirection
|
||||
Key string
|
||||
Value string
|
||||
IsRemove bool //Instead of set, remove this key instead
|
||||
}
|
||||
|
||||
/* Routing Rule Data Structures */
|
||||
|
||||
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||
// program structure than directly using ProxyEndpoint
|
||||
type VirtualDirectoryEndpoint struct {
|
||||
@ -92,40 +115,47 @@ type VirtualDirectoryEndpoint struct {
|
||||
|
||||
// A proxy endpoint record, a general interface for handling inbound routing
|
||||
type ProxyEndpoint struct {
|
||||
ProxyType int //The type of this proxy, see const def
|
||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||
Domain string //Domain or IP to proxy to
|
||||
ProxyType int //The type of this proxy, see const def
|
||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||
ActiveOrigins []*loadbalance.Upstream //Activated Upstream or origin servers IP or domain to proxy to
|
||||
InactiveOrigins []*loadbalance.Upstream //Disabled Upstream or origin servers IP or domain to proxy to
|
||||
UseStickySession bool //Use stick session for load balancing
|
||||
UseActiveLoadBalance bool //Use active loadbalancing, default passive
|
||||
Disabled bool //If the rule is disabled
|
||||
|
||||
//TLS/SSL Related
|
||||
RequireTLS bool //Target domain require TLS
|
||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||
//Inbound TLS/SSL Related
|
||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||
|
||||
//Virtual Directories
|
||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||
|
||||
//Custom Headers
|
||||
UserDefinedHeaders []*UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||
UserDefinedHeaders []*UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||
RequestHostOverwrite string //If not empty, this domain will be used to overwrite the Host field in request header
|
||||
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||
PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header
|
||||
DisableHopByHopHeaderRemoval bool //Do not remove hop-by-hop headers
|
||||
|
||||
//Authentication
|
||||
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
|
||||
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||
|
||||
// Rate Limiting
|
||||
RequireRateLimit bool
|
||||
RateLimit int64 // Rate limit in requests per second
|
||||
|
||||
//Access Control
|
||||
AccessFilterUUID string //Access filter ID
|
||||
|
||||
Disabled bool //If the rule is disabled
|
||||
|
||||
//Fallback routing logic (Special Rule Sets Only)
|
||||
DefaultSiteOption int //Fallback routing logic options
|
||||
DefaultSiteValue string //Fallback routing target, optional
|
||||
|
||||
//Internal Logic Elements
|
||||
parent *Router `json:"-"`
|
||||
proxy *dpcore.ReverseProxy `json:"-"`
|
||||
parent *Router `json:"-"`
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -42,17 +42,22 @@ SendEmail(
|
||||
)
|
||||
*/
|
||||
func (s *Sender) SendEmail(to string, subject string, content string) error {
|
||||
//Parse the email content
|
||||
// Parse the email content
|
||||
msg := []byte("To: " + to + "\n" +
|
||||
"From: Zoraxy <" + s.SenderAddr + ">\n" +
|
||||
"Subject: " + subject + "\n" +
|
||||
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" +
|
||||
content + "\n\n")
|
||||
|
||||
//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)
|
||||
// Initialize the auth variable
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -18,7 +18,7 @@ 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)
|
||||
this.logger.Printf("Unable to establish connection to [%s]: %s", address, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -17,7 +17,7 @@ func (this *loggingInitializer) Initialize(client, server Socket) bool {
|
||||
result := this.inner.Initialize(client, server)
|
||||
|
||||
if !result {
|
||||
this.logger.Printf("[INFO] Connection failed [%s] -> [%s]", client.RemoteAddr(), server.RemoteAddr())
|
||||
this.logger.Printf("Connection failed [%s] -> [%s]", client.RemoteAddr(), server.RemoteAddr())
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -19,9 +19,8 @@ type Store struct {
|
||||
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
||||
geotrie *trie
|
||||
geotrieIpv6 *trie
|
||||
//geoipCache sync.Map
|
||||
sysdb *database.Database
|
||||
option *StoreOptions
|
||||
sysdb *database.Database
|
||||
option *StoreOptions
|
||||
}
|
||||
|
||||
type StoreOptions struct {
|
||||
|
@ -43,7 +43,7 @@ func TestResolveCountryCodeFromIP(t *testing.T) {
|
||||
// Create a new store
|
||||
store, err := geodb.NewGeoDb(nil, &geodb.StoreOptions{
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("error creating store: %v", err)
|
||||
@ -56,6 +56,7 @@ func TestResolveCountryCodeFromIP(t *testing.T) {
|
||||
{"176.113.115.113", "RU"},
|
||||
{"65.21.233.213", "FI"},
|
||||
{"94.23.207.193", "FR"},
|
||||
{"77.131.21.232", "FR"},
|
||||
}
|
||||
|
||||
for _, testcase := range knownIpCountryMap {
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -16,13 +16,6 @@ func (s *Store) search(ip string) string {
|
||||
ip = strings.Split(ip, ",")[0]
|
||||
ip = strings.TrimSpace(ip)
|
||||
}
|
||||
//See if there are cached country code for this ip
|
||||
/*
|
||||
ccc, ok := s.geoipCache.Load(ip)
|
||||
if ok {
|
||||
return ccc.(string)
|
||||
}
|
||||
*/
|
||||
|
||||
//Search in geotrie tree
|
||||
cc := ""
|
||||
|
@ -53,6 +53,9 @@ func isIPv6InRange(startIP, endIP, testIP string) (bool, error) {
|
||||
|
||||
// Slow country code lookup for
|
||||
func (s *Store) slowSearchIpv4(ipAddr string) string {
|
||||
if isReservedIP(ipAddr) {
|
||||
return ""
|
||||
}
|
||||
for _, ipRange := range s.geodb {
|
||||
startIp := ipRange[0]
|
||||
endIp := ipRange[1]
|
||||
@ -67,6 +70,9 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
|
||||
}
|
||||
|
||||
func (s *Store) slowSearchIpv6(ipAddr string) string {
|
||||
if isReservedIP(ipAddr) {
|
||||
return ""
|
||||
}
|
||||
for _, ipRange := range s.geodbIpv6 {
|
||||
startIp := ipRange[0]
|
||||
endIp := ipRange[1]
|
||||
|
@ -1,7 +1,6 @@
|
||||
package geodb
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net"
|
||||
)
|
||||
|
||||
@ -41,14 +40,10 @@ func (t *trie) insert(ipAddr string, cc string) {
|
||||
ipBytes := ipToBytes(ipAddr)
|
||||
current := t.root
|
||||
for _, b := range ipBytes {
|
||||
//For each byte in the ip address
|
||||
//For each byte in the ip address (4 / 16 bytes)
|
||||
//each byte is 8 bit
|
||||
for j := 0; j < 8; j++ {
|
||||
bitwise := (b&uint8(math.Pow(float64(2), float64(j))) > 0)
|
||||
bit := 0b0000
|
||||
if bitwise {
|
||||
bit = 0b0001
|
||||
}
|
||||
for j := 7; j >= 0; j-- {
|
||||
bit := int(b >> j & 1)
|
||||
if current.childrens[bit] == nil {
|
||||
current.childrens[bit] = &trie_Node{
|
||||
childrens: [2]*trie_Node{},
|
||||
@ -58,21 +53,9 @@ func (t *trie) insert(ipAddr string, cc string) {
|
||||
current = current.childrens[bit]
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
for i := 63; i >= 0; i-- {
|
||||
bit := (ipInt64 >> uint(i)) & 1
|
||||
if current.childrens[bit] == nil {
|
||||
current.childrens[bit] = &trie_Node{
|
||||
childrens: [2]*trie_Node{},
|
||||
cc: cc,
|
||||
}
|
||||
}
|
||||
current = current.childrens[bit]
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// isReservedIP check if the given ip address is NOT a public ip address
|
||||
func isReservedIP(ip string) bool {
|
||||
parsedIP := net.ParseIP(ip)
|
||||
if parsedIP == nil {
|
||||
@ -86,12 +69,10 @@ func isReservedIP(ip string) bool {
|
||||
if parsedIP.IsLinkLocalUnicast() || parsedIP.IsLinkLocalMulticast() {
|
||||
return true
|
||||
}
|
||||
|
||||
//Check if the IP is in the reserved private range
|
||||
if parsedIP.IsPrivate() {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the IP address is not a reserved address, return false
|
||||
return false
|
||||
}
|
||||
|
||||
@ -106,27 +87,15 @@ func (t *trie) search(ipAddr string) string {
|
||||
for _, b := range ipBytes {
|
||||
//For each byte in the ip address
|
||||
//each byte is 8 bit
|
||||
for j := 0; j < 8; j++ {
|
||||
bitwise := (b&uint8(math.Pow(float64(2), float64(j))) > 0)
|
||||
bit := 0b0000
|
||||
if bitwise {
|
||||
bit = 0b0001
|
||||
}
|
||||
for j := 7; j >= 0; j-- {
|
||||
bit := int(b >> j & 1)
|
||||
if current.childrens[bit] == nil {
|
||||
return current.cc
|
||||
}
|
||||
current = current.childrens[bit]
|
||||
}
|
||||
}
|
||||
/*
|
||||
for i := 63; i >= 0; i-- {
|
||||
bit := (ipInt64 >> uint(i)) & 1
|
||||
if current.childrens[bit] == nil {
|
||||
return current.cc
|
||||
}
|
||||
current = current.childrens[bit]
|
||||
}
|
||||
*/
|
||||
|
||||
if len(current.childrens) == 0 {
|
||||
return current.cc
|
||||
}
|
||||
|
@ -13,29 +13,31 @@ import (
|
||||
Zoraxy Logger
|
||||
|
||||
This script is designed to make a managed log for the Zoraxy
|
||||
and replace the ton of log.Println in the system core
|
||||
and replace the ton of log.Println in the system core.
|
||||
The core logger is based in golang's build-in log package
|
||||
*/
|
||||
|
||||
type Logger struct {
|
||||
LogToFile bool //Set enable write to file
|
||||
Prefix string //Prefix for log files
|
||||
LogFolder string //Folder to store the log file
|
||||
CurrentLogFile string //Current writing filename
|
||||
logger *log.Logger
|
||||
file *os.File
|
||||
}
|
||||
|
||||
func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger, error) {
|
||||
// Create a new logger that log to files
|
||||
func NewLogger(logFilePrefix string, logFolder string) (*Logger, error) {
|
||||
err := os.MkdirAll(logFolder, 0775)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
thisLogger := Logger{
|
||||
LogToFile: logToFile,
|
||||
Prefix: logFilePrefix,
|
||||
LogFolder: logFolder,
|
||||
}
|
||||
|
||||
//Create the log file if not exists
|
||||
logFilePath := thisLogger.getLogFilepath()
|
||||
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
|
||||
if err != nil {
|
||||
@ -43,9 +45,26 @@ func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger,
|
||||
}
|
||||
thisLogger.CurrentLogFile = logFilePath
|
||||
thisLogger.file = f
|
||||
|
||||
//Start the logger
|
||||
logger := log.New(f, "", log.Flags()&^(log.Ldate|log.Ltime))
|
||||
logger.SetFlags(0)
|
||||
logger.SetOutput(f)
|
||||
thisLogger.logger = logger
|
||||
return &thisLogger, nil
|
||||
}
|
||||
|
||||
// Create a fmt logger that only log to STDOUT
|
||||
func NewFmtLogger() (*Logger, error) {
|
||||
return &Logger{
|
||||
Prefix: "",
|
||||
LogFolder: "",
|
||||
CurrentLogFile: "",
|
||||
logger: nil,
|
||||
file: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *Logger) getLogFilepath() string {
|
||||
year, month, _ := time.Now().Date()
|
||||
return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log")
|
||||
@ -54,9 +73,8 @@ func (l *Logger) getLogFilepath() string {
|
||||
// PrintAndLog will log the message to file and print the log to STDOUT
|
||||
func (l *Logger) PrintAndLog(title string, message string, originalError error) {
|
||||
go func() {
|
||||
l.Log(title, message, originalError)
|
||||
l.Log(title, message, originalError, true)
|
||||
}()
|
||||
log.Println("[" + title + "] " + message)
|
||||
}
|
||||
|
||||
// Println is a fast snap-in replacement for log.Println
|
||||
@ -64,18 +82,26 @@ func (l *Logger) Println(v ...interface{}) {
|
||||
//Convert the array of interfaces into string
|
||||
message := fmt.Sprint(v...)
|
||||
go func() {
|
||||
l.Log("info", string(message), nil)
|
||||
l.Log("internal", string(message), nil, true)
|
||||
}()
|
||||
log.Println("[INFO] " + string(message))
|
||||
}
|
||||
|
||||
func (l *Logger) Log(title string, errorMessage string, originalError error) {
|
||||
func (l *Logger) Log(title string, errorMessage string, originalError error, copyToSTDOUT bool) {
|
||||
l.ValidateAndUpdateLogFilepath()
|
||||
if l.LogToFile {
|
||||
if l.logger == nil || copyToSTDOUT {
|
||||
//Use STDOUT instead of logger
|
||||
if originalError == nil {
|
||||
l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [INFO]" + errorMessage + "\n")
|
||||
fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
|
||||
} else {
|
||||
l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [ERROR]" + errorMessage + " " + originalError.Error() + "\n")
|
||||
fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:error] " + errorMessage + ": " + originalError.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if l.logger != nil {
|
||||
if originalError == nil {
|
||||
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
|
||||
} else {
|
||||
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:error] " + errorMessage + ": " + originalError.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,18 +109,28 @@ func (l *Logger) Log(title string, errorMessage string, originalError error) {
|
||||
|
||||
// Validate if the logging target is still valid (detect any months change)
|
||||
func (l *Logger) ValidateAndUpdateLogFilepath() {
|
||||
if l.file == nil {
|
||||
return
|
||||
}
|
||||
expectedCurrentLogFilepath := l.getLogFilepath()
|
||||
if l.CurrentLogFile != expectedCurrentLogFilepath {
|
||||
//Change of month. Update to a new log file
|
||||
l.file.Close()
|
||||
l.file = nil
|
||||
|
||||
//Create a new log file
|
||||
f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
|
||||
if err != nil {
|
||||
log.Println("[Logger] Unable to create new log. Logging to file disabled.")
|
||||
l.LogToFile = false
|
||||
log.Println("Unable to create new log. Logging is disabled: ", err.Error())
|
||||
l.logger = nil
|
||||
return
|
||||
}
|
||||
l.CurrentLogFile = expectedCurrentLogFilepath
|
||||
l.file = f
|
||||
|
||||
//Start a new logger
|
||||
logger := log.New(f, "", log.Default().Flags())
|
||||
l.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
|
32
src/mod/info/logger/trafficlog.go
Normal file
32
src/mod/info/logger/trafficlog.go
Normal file
@ -0,0 +1,32 @@
|
||||
package logger
|
||||
|
||||
/*
|
||||
Traffic Log
|
||||
|
||||
This script log the traffic of HTTP requests
|
||||
|
||||
*/
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
)
|
||||
|
||||
// Log HTTP request. Note that this must run in go routine to prevent any blocking
|
||||
// in reverse proxy router
|
||||
func (l *Logger) LogHTTPRequest(r *http.Request, reqclass string, statusCode int) {
|
||||
go func() {
|
||||
l.ValidateAndUpdateLogFilepath()
|
||||
if l.logger == nil || l.file == nil {
|
||||
//logger is not initiated. Do not log http request
|
||||
return
|
||||
}
|
||||
clientIP := netutils.GetRequesterIP(r)
|
||||
requestURI := r.RequestURI
|
||||
statusCodeString := strconv.Itoa(statusCode)
|
||||
//fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [router:" + reqclass + "] [client " + clientIP + "] " + r.Method + " " + requestURI + " " + statusCodeString)
|
||||
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [router:" + reqclass + "] [origin:" + r.URL.Hostname() + "] [client " + clientIP + "] " + r.Method + " " + requestURI + " " + statusCodeString)
|
||||
}()
|
||||
}
|
@ -51,13 +51,7 @@ func (v *Viewer) HandleReadLog(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
catergory, err := utils.GetPara(r, "catergory")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid catergory given")
|
||||
return
|
||||
}
|
||||
|
||||
content, err := v.LoadLogFile(strings.TrimSpace(filepath.Base(catergory)), strings.TrimSpace(filepath.Base(filename)))
|
||||
content, err := v.LoadLogFile(strings.TrimSpace(filepath.Base(filename)))
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
@ -106,8 +100,10 @@ func (v *Viewer) ListLogFiles(showFullpath bool) map[string][]*LogFile {
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *Viewer) LoadLogFile(catergory string, filename string) (string, error) {
|
||||
logFilepath := filepath.Join(v.option.RootFolder, catergory, filename)
|
||||
func (v *Viewer) LoadLogFile(filename string) (string, error) {
|
||||
filename = filepath.ToSlash(filename)
|
||||
filename = strings.ReplaceAll(filename, "../", "")
|
||||
logFilepath := filepath.Join(v.option.RootFolder, filename)
|
||||
if utils.FileExists(logFilepath) {
|
||||
//Load it
|
||||
content, err := os.ReadFile(logFilepath)
|
||||
|
@ -3,8 +3,6 @@ package netstat
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
@ -14,6 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -35,11 +34,11 @@ type NetStatBuffers struct {
|
||||
Stats []*FlowStat //Statistic of the flow
|
||||
StopChan chan bool //Channel to stop the ticker
|
||||
EventTicker *time.Ticker //Ticker for event logging
|
||||
logger *logger.Logger
|
||||
}
|
||||
|
||||
// Get a new network statistic buffers
|
||||
func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
|
||||
|
||||
func NewNetStatBuffer(recordCount int, systemWideLogger *logger.Logger) (*NetStatBuffers, error) {
|
||||
//Flood fill the stats with 0
|
||||
initialStats := []*FlowStat{}
|
||||
for i := 0; i < recordCount; i++ {
|
||||
@ -66,21 +65,22 @@ func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
|
||||
Stats: initialStats,
|
||||
StopChan: stopCh,
|
||||
EventTicker: ticker,
|
||||
logger: systemWideLogger,
|
||||
}
|
||||
|
||||
//Get the initial measurements of netstats
|
||||
rx, tx, err := GetNetworkInterfaceStats()
|
||||
rx, tx, err := thisNetBuffer.GetNetworkInterfaceStats()
|
||||
if err != nil {
|
||||
log.Println("Unable to get NIC stats: ", err.Error())
|
||||
systemWideLogger.PrintAndLog("netstat", "Unable to get NIC stats: ", err)
|
||||
}
|
||||
|
||||
retryCount := 0
|
||||
for rx == 0 && tx == 0 && retryCount < 10 {
|
||||
//Strange. Retry
|
||||
log.Println("NIC stats return all 0. Retrying...")
|
||||
rx, tx, err = GetNetworkInterfaceStats()
|
||||
systemWideLogger.PrintAndLog("netstat", "NIC stats return all 0. Retrying...", nil)
|
||||
rx, tx, err = thisNetBuffer.GetNetworkInterfaceStats()
|
||||
if err != nil {
|
||||
log.Println("Unable to get NIC stats: ", err.Error())
|
||||
systemWideLogger.PrintAndLog("netstat", "Unable to get NIC stats: ", err)
|
||||
}
|
||||
retryCount++
|
||||
}
|
||||
@ -95,20 +95,20 @@ func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
|
||||
for {
|
||||
select {
|
||||
case <-n.StopChan:
|
||||
fmt.Println("- Netstats listener stopped")
|
||||
systemWideLogger.PrintAndLog("netstat", "Netstats listener stopped", nil)
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
if n.PreviousStat.RX == 0 && n.PreviousStat.TX == 0 {
|
||||
//Initiation state is still not done. Ignore request
|
||||
log.Println("No initial states. Waiting")
|
||||
systemWideLogger.PrintAndLog("netstat", "No initial states. Waiting", nil)
|
||||
return
|
||||
}
|
||||
// Get the latest network interface stats
|
||||
rx, tx, err := GetNetworkInterfaceStats()
|
||||
rx, tx, err := thisNetBuffer.GetNetworkInterfaceStats()
|
||||
if err != nil {
|
||||
// Log the error, but don't stop the buffer
|
||||
log.Printf("Failed to get network interface stats: %v", err)
|
||||
systemWideLogger.PrintAndLog("netstat", "Failed to get network interface stats", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -174,8 +174,8 @@ func (n *NetStatBuffers) Close() {
|
||||
n.EventTicker.Stop()
|
||||
}
|
||||
|
||||
func HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
|
||||
rx, tx, err := GetNetworkInterfaceStats()
|
||||
func (n *NetStatBuffers) HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
|
||||
rx, tx, err := n.GetNetworkInterfaceStats()
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
@ -194,7 +194,7 @@ func HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Get network interface stats, return accumulated rx bits, tx bits and error if any
|
||||
func GetNetworkInterfaceStats() (int64, int64, error) {
|
||||
func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
//Windows wmic sometime freeze and not respond.
|
||||
//The safer way is to make a bypass mechanism
|
||||
@ -263,7 +263,7 @@ func GetNetworkInterfaceStats() (int64, int64, error) {
|
||||
result = <-callbackChan
|
||||
cmd = nil
|
||||
if result.Err != nil {
|
||||
log.Println("Unable to extract NIC info from wmic: " + result.Err.Error())
|
||||
n.logger.PrintAndLog("netstat", "Unable to extract NIC info from wmic", result.Err)
|
||||
}
|
||||
return result.RX, result.TX, result.Err
|
||||
} else if runtime.GOOS == "linux" {
|
||||
|
@ -88,6 +88,7 @@ func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWrite
|
||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||
SkipTLSValidation: false,
|
||||
SkipOriginCheck: false,
|
||||
Logger: nil,
|
||||
})
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
|
@ -9,13 +9,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//Rewrite url based on proxy root
|
||||
// Rewrite url based on proxy root (default site)
|
||||
func RewriteURL(rooturl string, requestURL string) (*url.URL, error) {
|
||||
rewrittenURL := strings.TrimPrefix(requestURL, rooturl)
|
||||
return url.Parse(rewrittenURL)
|
||||
}
|
||||
|
||||
//Check if the current platform support web.ssh function
|
||||
// Check if the current platform support web.ssh function
|
||||
func IsWebSSHSupported() bool {
|
||||
//Check if the binary exists in system/gotty/
|
||||
binary := "gotty_" + runtime.GOOS + "_" + runtime.GOARCH
|
||||
@ -34,7 +34,7 @@ func IsWebSSHSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
//Check if a given domain and port is a valid ssh server
|
||||
// Check if a given domain and port is a valid ssh server
|
||||
func IsSSHConnectable(ipOrDomain string, port int) bool {
|
||||
timeout := time.Second * 3
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ipOrDomain, port), timeout)
|
||||
@ -60,7 +60,7 @@ func IsSSHConnectable(ipOrDomain string, port int) bool {
|
||||
return string(buf[:7]) == "SSH-2.0"
|
||||
}
|
||||
|
||||
//Check if the port is used by other process or application
|
||||
// Check if the port is used by other process or application
|
||||
func isPortInUse(port int) bool {
|
||||
address := fmt.Sprintf(":%d", port)
|
||||
listener, err := net.Listen("tcp", address)
|
||||
|
@ -1,9 +1,10 @@
|
||||
package tcpprox
|
||||
package streamproxy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
@ -22,13 +23,13 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
portA, err := utils.PostPara(r, "porta")
|
||||
listenAddr, err := utils.PostPara(r, "listenAddr")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "first address cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
portB, err := utils.PostPara(r, "portb")
|
||||
proxyAddr, err := utils.PostPara(r, "proxyAddr")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "second address cannot be empty")
|
||||
return
|
||||
@ -44,27 +45,17 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
modeValue := ProxyMode_Transport
|
||||
mode, err := utils.PostPara(r, "mode")
|
||||
if err != nil || mode == "" {
|
||||
utils.SendErrorResponse(w, "no mode given")
|
||||
} else if mode == "listen" {
|
||||
modeValue = ProxyMode_Listen
|
||||
} else if mode == "transport" {
|
||||
modeValue = ProxyMode_Transport
|
||||
} else if mode == "starter" {
|
||||
modeValue = ProxyMode_Starter
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid mode given. Only support listen / transport / starter")
|
||||
}
|
||||
useTCP, _ := utils.PostBool(r, "useTCP")
|
||||
useUDP, _ := utils.PostBool(r, "useUDP")
|
||||
|
||||
//Create the target config
|
||||
newConfigUUID := m.NewConfig(&ProxyRelayOptions{
|
||||
Name: name,
|
||||
PortA: portA,
|
||||
PortB: portB,
|
||||
Timeout: timeout,
|
||||
Mode: modeValue,
|
||||
Name: name,
|
||||
ListeningAddr: strings.TrimSpace(listenAddr),
|
||||
ProxyAddr: strings.TrimSpace(proxyAddr),
|
||||
Timeout: timeout,
|
||||
UseTCP: useTCP,
|
||||
UseUDP: useUDP,
|
||||
})
|
||||
|
||||
js, _ := json.Marshal(newConfigUUID)
|
||||
@ -80,22 +71,10 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
newName, _ := utils.PostPara(r, "name")
|
||||
newPortA, _ := utils.PostPara(r, "porta")
|
||||
newPortB, _ := utils.PostPara(r, "portb")
|
||||
newModeStr, _ := utils.PostPara(r, "mode")
|
||||
newMode := -1
|
||||
if newModeStr != "" {
|
||||
if newModeStr == "listen" {
|
||||
newMode = 0
|
||||
} else if newModeStr == "transport" {
|
||||
newMode = 1
|
||||
} else if newModeStr == "starter" {
|
||||
newMode = 2
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid new mode value")
|
||||
return
|
||||
}
|
||||
}
|
||||
listenAddr, _ := utils.PostPara(r, "listenAddr")
|
||||
proxyAddr, _ := utils.PostPara(r, "proxyAddr")
|
||||
useTCP, _ := utils.PostBool(r, "useTCP")
|
||||
useUDP, _ := utils.PostBool(r, "useUDP")
|
||||
|
||||
newTimeoutStr, _ := utils.PostPara(r, "timeout")
|
||||
newTimeout := -1
|
||||
@ -108,7 +87,7 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
// Call the EditConfig method to modify the configuration
|
||||
err = m.EditConfig(configUUID, newName, newPortA, newPortB, newMode, newTimeout)
|
||||
err = m.EditConfig(configUUID, newName, listenAddr, proxyAddr, useTCP, useUDP, newTimeout)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
@ -158,6 +137,7 @@ func (m *Manager) HandleStopProxy(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if !targetProxyConfig.IsRunning() {
|
||||
targetProxyConfig.Running = false
|
||||
utils.SendErrorResponse(w, "target proxy service is not running")
|
||||
return
|
||||
}
|
||||
@ -180,6 +160,7 @@ func (m *Manager) HandleRemoveProxy(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if targetProxyConfig.IsRunning() {
|
||||
targetProxyConfig.Running = false
|
||||
utils.SendErrorResponse(w, "Service is running")
|
||||
return
|
||||
}
|
||||
@ -209,25 +190,3 @@ func (m *Manager) HandleGetProxyStatus(w http.ResponseWriter, r *http.Request) {
|
||||
js, _ := json.Marshal(targetConfig)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (m *Manager) HandleConfigValidate(w http.ResponseWriter, r *http.Request) {
|
||||
uuid, err := utils.GetPara(r, "uuid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid uuid given")
|
||||
return
|
||||
}
|
||||
|
||||
targetConfig, err := m.GetConfigByUUID(uuid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = targetConfig.ValidateConfigs()
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
281
src/mod/streamproxy/streamproxy.go
Normal file
281
src/mod/streamproxy/streamproxy.go
Normal file
@ -0,0 +1,281 @@
|
||||
package streamproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
)
|
||||
|
||||
/*
|
||||
TCP Proxy
|
||||
|
||||
Forward port from one port to another
|
||||
Also accept active connection and passive
|
||||
connection
|
||||
*/
|
||||
|
||||
type ProxyRelayOptions struct {
|
||||
Name string
|
||||
ListeningAddr string
|
||||
ProxyAddr string
|
||||
Timeout int
|
||||
UseTCP bool
|
||||
UseUDP bool
|
||||
}
|
||||
|
||||
type ProxyRelayConfig struct {
|
||||
UUID string //A UUIDv4 representing this config
|
||||
Name string //Name of the config
|
||||
Running bool //Status, read only
|
||||
AutoStart bool //If the service suppose to started automatically
|
||||
ListeningAddress string //Listening Address, usually 127.0.0.1:port
|
||||
ProxyTargetAddr string //Proxy target address
|
||||
UseTCP bool //Enable TCP proxy
|
||||
UseUDP bool //Enable UDP proxy
|
||||
Timeout int //Timeout for connection in sec
|
||||
tcpStopChan chan bool //Stop channel for TCP listener
|
||||
udpStopChan chan bool //Stop channel for UDP listener
|
||||
aTobAccumulatedByteTransfer atomic.Int64 //Accumulated byte transfer from A to B
|
||||
bToaAccumulatedByteTransfer atomic.Int64 //Accumulated byte transfer from B to A
|
||||
udpClientMap sync.Map //map storing the UDP client-server connections
|
||||
parent *Manager `json:"-"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Database *database.Database
|
||||
DefaultTimeout int
|
||||
AccessControlHandler func(net.Conn) bool
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
//Config and stores
|
||||
Options *Options
|
||||
Configs []*ProxyRelayConfig
|
||||
|
||||
//Realtime Statistics
|
||||
Connections int //currently connected connect counts
|
||||
|
||||
}
|
||||
|
||||
func NewStreamProxy(options *Options) *Manager {
|
||||
options.Database.NewTable("tcprox")
|
||||
|
||||
//Load relay configs from db
|
||||
previousRules := []*ProxyRelayConfig{}
|
||||
if options.Database.KeyExists("tcprox", "rules") {
|
||||
options.Database.Read("tcprox", "rules", &previousRules)
|
||||
}
|
||||
|
||||
//Check if the AccessControlHandler is empty. If yes, set it to always allow access
|
||||
if options.AccessControlHandler == nil {
|
||||
options.AccessControlHandler = func(conn net.Conn) bool {
|
||||
//Always allow access
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
//Create a new proxy manager for TCP
|
||||
thisManager := Manager{
|
||||
Options: options,
|
||||
Connections: 0,
|
||||
}
|
||||
|
||||
//Inject manager into the rules
|
||||
for _, rule := range previousRules {
|
||||
rule.parent = &thisManager
|
||||
if rule.Running {
|
||||
//This was previously running. Start it again
|
||||
log.Println("[Stream Proxy] Resuming stream proxy rule " + rule.Name)
|
||||
rule.Start()
|
||||
}
|
||||
}
|
||||
|
||||
thisManager.Configs = previousRules
|
||||
|
||||
return &thisManager
|
||||
}
|
||||
|
||||
func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
|
||||
//Generate two zero value for atomic int64
|
||||
aAcc := atomic.Int64{}
|
||||
bAcc := atomic.Int64{}
|
||||
aAcc.Store(0)
|
||||
bAcc.Store(0)
|
||||
//Generate a new config from options
|
||||
configUUID := uuid.New().String()
|
||||
thisConfig := ProxyRelayConfig{
|
||||
UUID: configUUID,
|
||||
Name: config.Name,
|
||||
ListeningAddress: config.ListeningAddr,
|
||||
ProxyTargetAddr: config.ProxyAddr,
|
||||
UseTCP: config.UseTCP,
|
||||
UseUDP: config.UseUDP,
|
||||
Timeout: config.Timeout,
|
||||
tcpStopChan: nil,
|
||||
udpStopChan: nil,
|
||||
aTobAccumulatedByteTransfer: aAcc,
|
||||
bToaAccumulatedByteTransfer: bAcc,
|
||||
udpClientMap: sync.Map{},
|
||||
parent: m,
|
||||
}
|
||||
m.Configs = append(m.Configs, &thisConfig)
|
||||
m.SaveConfigToDatabase()
|
||||
return configUUID
|
||||
}
|
||||
|
||||
func (m *Manager) GetConfigByUUID(configUUID string) (*ProxyRelayConfig, error) {
|
||||
// Find and return the config with the specified UUID
|
||||
for _, config := range m.Configs {
|
||||
if config.UUID == configUUID {
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("config not found")
|
||||
}
|
||||
|
||||
// Edit the config based on config UUID, leave empty for unchange fields
|
||||
func (m *Manager) EditConfig(configUUID string, newName string, newListeningAddr string, newProxyAddr string, useTCP bool, useUDP bool, newTimeout int) error {
|
||||
// Find the config with the specified UUID
|
||||
foundConfig, err := m.GetConfigByUUID(configUUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate and update the fields
|
||||
if newName != "" {
|
||||
foundConfig.Name = newName
|
||||
}
|
||||
if newListeningAddr != "" {
|
||||
foundConfig.ListeningAddress = newListeningAddr
|
||||
}
|
||||
if newProxyAddr != "" {
|
||||
foundConfig.ProxyTargetAddr = newProxyAddr
|
||||
}
|
||||
|
||||
foundConfig.UseTCP = useTCP
|
||||
foundConfig.UseUDP = useUDP
|
||||
|
||||
if newTimeout != -1 {
|
||||
if newTimeout < 0 {
|
||||
return errors.New("invalid timeout value given")
|
||||
}
|
||||
foundConfig.Timeout = newTimeout
|
||||
}
|
||||
|
||||
m.SaveConfigToDatabase()
|
||||
|
||||
//Check if config is running. If yes, restart it
|
||||
if foundConfig.IsRunning() {
|
||||
foundConfig.Restart()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) RemoveConfig(configUUID string) error {
|
||||
// Find and remove the config with the specified UUID
|
||||
for i, config := range m.Configs {
|
||||
if config.UUID == configUUID {
|
||||
m.Configs = append(m.Configs[:i], m.Configs[i+1:]...)
|
||||
m.SaveConfigToDatabase()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("config not found")
|
||||
}
|
||||
|
||||
func (m *Manager) SaveConfigToDatabase() {
|
||||
m.Options.Database.Write("tcprox", "rules", m.Configs)
|
||||
}
|
||||
|
||||
/*
|
||||
Config Functions
|
||||
*/
|
||||
|
||||
// Start a proxy if stopped
|
||||
func (c *ProxyRelayConfig) Start() error {
|
||||
if c.IsRunning() {
|
||||
c.Running = true
|
||||
return errors.New("proxy already running")
|
||||
}
|
||||
|
||||
// Create a stopChan to control the loop
|
||||
tcpStopChan := make(chan bool)
|
||||
udpStopChan := make(chan bool)
|
||||
|
||||
//Start the proxy service
|
||||
if c.UseUDP {
|
||||
c.udpStopChan = udpStopChan
|
||||
go func() {
|
||||
err := c.ForwardUDP(c.ListeningAddress, c.ProxyTargetAddr, udpStopChan)
|
||||
if err != nil {
|
||||
if !c.UseTCP {
|
||||
c.Running = false
|
||||
c.parent.SaveConfigToDatabase()
|
||||
}
|
||||
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if c.UseTCP {
|
||||
c.tcpStopChan = tcpStopChan
|
||||
go func() {
|
||||
//Default to transport mode
|
||||
err := c.Port2host(c.ListeningAddress, c.ProxyTargetAddr, tcpStopChan)
|
||||
if err != nil {
|
||||
c.Running = false
|
||||
c.parent.SaveConfigToDatabase()
|
||||
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//Successfully spawned off the proxy routine
|
||||
c.Running = true
|
||||
c.parent.SaveConfigToDatabase()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return if a proxy config is running
|
||||
func (c *ProxyRelayConfig) IsRunning() bool {
|
||||
return c.tcpStopChan != nil || c.udpStopChan != nil
|
||||
}
|
||||
|
||||
// Restart a proxy config
|
||||
func (c *ProxyRelayConfig) Restart() {
|
||||
if c.IsRunning() {
|
||||
c.Stop()
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
c.Start()
|
||||
}
|
||||
|
||||
// Stop a running proxy if running
|
||||
func (c *ProxyRelayConfig) Stop() {
|
||||
log.Println("[STREAM PROXY] Stopping Stream Proxy " + c.Name)
|
||||
|
||||
if c.udpStopChan != nil {
|
||||
log.Println("[STREAM PROXY] Stopping UDP for " + c.Name)
|
||||
c.udpStopChan <- true
|
||||
c.udpStopChan = nil
|
||||
}
|
||||
|
||||
if c.tcpStopChan != nil {
|
||||
log.Println("[STREAM PROXY] Stopping TCP for " + c.Name)
|
||||
c.tcpStopChan <- true
|
||||
c.tcpStopChan = nil
|
||||
}
|
||||
|
||||
log.Println("[STREAM PROXY] Stopped Stream Proxy " + c.Name)
|
||||
c.Running = false
|
||||
|
||||
//Update the running status
|
||||
c.parent.SaveConfigToDatabase()
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
package tcpprox_test
|
||||
package streamproxy_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/tcpprox"
|
||||
"imuslab.com/zoraxy/mod/streamproxy"
|
||||
)
|
||||
|
||||
func TestPort2Port(t *testing.T) {
|
||||
@ -12,7 +12,7 @@ func TestPort2Port(t *testing.T) {
|
||||
stopChan := make(chan bool)
|
||||
|
||||
// Create a ProxyRelayConfig with dummy values
|
||||
config := &tcpprox.ProxyRelayConfig{
|
||||
config := &streamproxy.ProxyRelayConfig{
|
||||
Timeout: 1,
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ func TestPort2Port(t *testing.T) {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// If the goroutine is still running, it means it did not stop as expected
|
||||
if config.Running {
|
||||
if config.IsRunning() {
|
||||
t.Errorf("port2port did not stop as expected")
|
||||
}
|
||||
|
146
src/mod/streamproxy/tcpprox.go
Normal file
146
src/mod/streamproxy/tcpprox.go
Normal file
@ -0,0 +1,146 @@
|
||||
package streamproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
func isValidIP(ip string) bool {
|
||||
parsedIP := net.ParseIP(ip)
|
||||
return parsedIP != nil
|
||||
}
|
||||
|
||||
func isValidPort(port string) bool {
|
||||
portInt, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if portInt < 1 || portInt > 65535 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup, accumulator *atomic.Int64) {
|
||||
n, err := io.Copy(conn1, conn2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
accumulator.Add(n) //Add to accumulator
|
||||
conn1.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
|
||||
//conn2.Close()
|
||||
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func forward(conn1 net.Conn, conn2 net.Conn, aTob *atomic.Int64, bToa *atomic.Int64) {
|
||||
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
|
||||
var wg sync.WaitGroup
|
||||
// wait tow goroutines
|
||||
wg.Add(2)
|
||||
go connCopy(conn1, conn2, &wg, aTob)
|
||||
go connCopy(conn2, conn1, &wg, bToa)
|
||||
//blocking when the wg is locked
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (c *ProxyRelayConfig) accept(listener net.Listener) (net.Conn, error) {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Check if connection in blacklist or whitelist
|
||||
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||
if !c.parent.Options.AccessControlHandler(conn) {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
conn.Close()
|
||||
log.Println("[x]", "Connection from "+addr.IP.String()+" rejected by access control policy")
|
||||
return nil, errors.New("Connection from " + addr.IP.String() + " rejected by access control policy")
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func startListener(address string) (net.Listener, error) {
|
||||
log.Println("[+]", "try to start server on:["+address+"]")
|
||||
server, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return nil, errors.New("listen address [" + address + "] faild")
|
||||
}
|
||||
log.Println("[√]", "start listen at address:["+address+"]")
|
||||
return server, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Forwarder Functions
|
||||
*/
|
||||
|
||||
/*
|
||||
portA -> server
|
||||
server -> portB
|
||||
*/
|
||||
func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, stopChan chan bool) error {
|
||||
listenerStartingAddr := allowPort
|
||||
if isValidPort(allowPort) {
|
||||
//number only, e.g. 8080
|
||||
listenerStartingAddr = "0.0.0.0:" + allowPort
|
||||
} else if strings.HasPrefix(allowPort, ":") && isValidPort(allowPort[1:]) {
|
||||
//port number starting with :, e.g. :8080
|
||||
listenerStartingAddr = "0.0.0.0" + allowPort
|
||||
}
|
||||
|
||||
server, err := startListener(listenerStartingAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetAddress = strings.TrimSpace(targetAddress)
|
||||
|
||||
//Start stop handler
|
||||
go func() {
|
||||
<-stopChan
|
||||
log.Println("[x]", "Received stop signal. Exiting Port to Host forwarder")
|
||||
server.Close()
|
||||
}()
|
||||
|
||||
//Start blocking loop for accepting connections
|
||||
for {
|
||||
conn, err := c.accept(server)
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
//Terminate by stop chan. Exit listener loop
|
||||
return nil
|
||||
}
|
||||
//Connection error. Retry
|
||||
continue
|
||||
}
|
||||
|
||||
go func(targetAddress string) {
|
||||
log.Println("[+]", "start connect host:["+targetAddress+"]")
|
||||
target, err := net.Dial("tcp", targetAddress)
|
||||
if err != nil {
|
||||
// temporarily unavailable, don't use fatal.
|
||||
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", c.Timeout, "seconds. ")
|
||||
conn.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
|
||||
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||
return
|
||||
}
|
||||
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
|
||||
forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
|
||||
}(targetAddress)
|
||||
}
|
||||
}
|
157
src/mod/streamproxy/udpprox.go
Normal file
157
src/mod/streamproxy/udpprox.go
Normal file
@ -0,0 +1,157 @@
|
||||
package streamproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
UDP Proxy Module
|
||||
*/
|
||||
|
||||
// Information maintained for each client/server connection
|
||||
type udpClientServerConn struct {
|
||||
ClientAddr *net.UDPAddr // Address of the client
|
||||
ServerConn *net.UDPConn // UDP connection to server
|
||||
}
|
||||
|
||||
// Generate a new connection by opening a UDP connection to the server
|
||||
func createNewUDPConn(srvAddr, cliAddr *net.UDPAddr) *udpClientServerConn {
|
||||
conn := new(udpClientServerConn)
|
||||
conn.ClientAddr = cliAddr
|
||||
srvudp, err := net.DialUDP("udp", nil, srvAddr)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
conn.ServerConn = srvudp
|
||||
return conn
|
||||
}
|
||||
|
||||
// Start listener, return inbound lisener and proxy target UDP address
|
||||
func initUDPConnections(listenAddr string, targetAddress string) (*net.UDPConn, *net.UDPAddr, error) {
|
||||
// Set up Proxy
|
||||
saddr, err := net.ResolveUDPAddr("udp", listenAddr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
inboundConn, err := net.ListenUDP("udp", saddr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Println("[UDP] Proxy listening on " + listenAddr)
|
||||
|
||||
outboundConn, err := net.ResolveUDPAddr("udp", targetAddress)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return inboundConn, outboundConn, nil
|
||||
}
|
||||
|
||||
// Go routine which manages connection from server to single client
|
||||
func (c *ProxyRelayConfig) RunUDPConnectionRelay(conn *udpClientServerConn, lisenter *net.UDPConn) {
|
||||
var buffer [1500]byte
|
||||
for {
|
||||
// Read from server
|
||||
n, err := conn.ServerConn.Read(buffer[0:])
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Relay it to client
|
||||
_, err = lisenter.WriteToUDP(buffer[0:n], conn.ClientAddr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Close all connections that waiting for read from server
|
||||
func (c *ProxyRelayConfig) CloseAllUDPConnections() {
|
||||
c.udpClientMap.Range(func(clientAddr, clientServerConn interface{}) bool {
|
||||
conn := clientServerConn.(*udpClientServerConn)
|
||||
conn.ServerConn.Close()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ProxyRelayConfig) ForwardUDP(address1, address2 string, stopChan chan bool) error {
|
||||
//By default the incoming listen Address is int
|
||||
//We need to add the loopback address into it
|
||||
if isValidPort(address1) {
|
||||
//Port number only. Missing the : in front
|
||||
address1 = ":" + address1
|
||||
}
|
||||
if strings.HasPrefix(address1, ":") {
|
||||
//Prepend 127.0.0.1 to the address
|
||||
address1 = "127.0.0.1" + address1
|
||||
}
|
||||
|
||||
lisener, targetAddr, err := initUDPConnections(address1, address2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
//Stop channel receiver
|
||||
for {
|
||||
select {
|
||||
case <-stopChan:
|
||||
//Stop signal received
|
||||
//Stop server -> client forwarder
|
||||
c.CloseAllUDPConnections()
|
||||
//Stop client -> server forwarder
|
||||
//Force close, will terminate ReadFromUDP for inbound listener
|
||||
lisener.Close()
|
||||
return
|
||||
default:
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
var buffer [1500]byte
|
||||
for {
|
||||
n, cliaddr, err := lisener.ReadFromUDP(buffer[0:])
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
//Proxy stopped
|
||||
return nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
c.aTobAccumulatedByteTransfer.Add(int64(n))
|
||||
saddr := cliaddr.String()
|
||||
rawConn, found := c.udpClientMap.Load(saddr)
|
||||
var conn *udpClientServerConn
|
||||
if !found {
|
||||
conn = createNewUDPConn(targetAddr, cliaddr)
|
||||
if conn == nil {
|
||||
continue
|
||||
}
|
||||
c.udpClientMap.Store(saddr, conn)
|
||||
log.Println("[UDP] Created new connection for client " + saddr)
|
||||
// Fire up routine to manage new connection
|
||||
go c.RunUDPConnectionRelay(conn, lisener)
|
||||
|
||||
} else {
|
||||
log.Println("[UDP] Found connection for client " + saddr)
|
||||
conn = rawConn.(*udpClientServerConn)
|
||||
}
|
||||
|
||||
// Relay to server
|
||||
_, err = conn.ServerConn.Write(buffer[0:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,341 +0,0 @@
|
||||
package tcpprox
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func isValidIP(ip string) bool {
|
||||
parsedIP := net.ParseIP(ip)
|
||||
return parsedIP != nil
|
||||
}
|
||||
|
||||
func isValidPort(port string) bool {
|
||||
portInt, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if portInt < 1 || portInt > 65535 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isReachable(target string) bool {
|
||||
timeout := time.Duration(2 * time.Second) // Set the timeout value as per your requirement
|
||||
conn, err := net.DialTimeout("tcp", target, timeout)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup, accumulator *int64) {
|
||||
io.Copy(conn1, conn2)
|
||||
conn1.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
|
||||
//conn2.Close()
|
||||
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func forward(conn1 net.Conn, conn2 net.Conn, aTob *int64, bToa *int64) {
|
||||
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
|
||||
var wg sync.WaitGroup
|
||||
// wait tow goroutines
|
||||
wg.Add(2)
|
||||
go connCopy(conn1, conn2, &wg, aTob)
|
||||
go connCopy(conn2, conn1, &wg, bToa)
|
||||
//blocking when the wg is locked
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (c *ProxyRelayConfig) accept(listener net.Listener) (net.Conn, error) {
|
||||
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Check if connection in blacklist or whitelist
|
||||
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||
if !c.parent.Options.AccessControlHandler(conn) {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
conn.Close()
|
||||
log.Println("[x]", "Connection from "+addr.IP.String()+" rejected by access control policy")
|
||||
return nil, errors.New("Connection from " + addr.IP.String() + " rejected by access control policy")
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func startListener(address string) (net.Listener, error) {
|
||||
log.Println("[+]", "try to start server on:["+address+"]")
|
||||
server, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return nil, errors.New("listen address [" + address + "] faild")
|
||||
}
|
||||
log.Println("[√]", "start listen at address:["+address+"]")
|
||||
return server, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Config Functions
|
||||
*/
|
||||
|
||||
// Config validator
|
||||
func (c *ProxyRelayConfig) ValidateConfigs() error {
|
||||
if c.Mode == ProxyMode_Transport {
|
||||
//Port2Host: PortA int, PortB string
|
||||
if !isValidPort(c.PortA) {
|
||||
return errors.New("first address must be a valid port number")
|
||||
}
|
||||
|
||||
if !isReachable(c.PortB) {
|
||||
return errors.New("second address is unreachable")
|
||||
}
|
||||
return nil
|
||||
|
||||
} else if c.Mode == ProxyMode_Listen {
|
||||
//Port2Port: Both port are port number
|
||||
if !isValidPort(c.PortA) {
|
||||
return errors.New("first address is not a valid port number")
|
||||
}
|
||||
|
||||
if !isValidPort(c.PortB) {
|
||||
return errors.New("second address is not a valid port number")
|
||||
}
|
||||
|
||||
return nil
|
||||
} else if c.Mode == ProxyMode_Starter {
|
||||
//Host2Host: Both have to be hosts
|
||||
if !isReachable(c.PortA) {
|
||||
return errors.New("first address is unreachable")
|
||||
}
|
||||
|
||||
if !isReachable(c.PortB) {
|
||||
return errors.New("second address is unreachable")
|
||||
}
|
||||
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("invalid mode given")
|
||||
}
|
||||
}
|
||||
|
||||
// Start a proxy if stopped
|
||||
func (c *ProxyRelayConfig) Start() error {
|
||||
if c.Running {
|
||||
return errors.New("proxy already running")
|
||||
}
|
||||
|
||||
// Create a stopChan to control the loop
|
||||
stopChan := make(chan bool)
|
||||
c.stopChan = stopChan
|
||||
|
||||
//Validate configs
|
||||
err := c.ValidateConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Start the proxy service
|
||||
go func() {
|
||||
c.Running = true
|
||||
if c.Mode == ProxyMode_Transport {
|
||||
err = c.Port2host(c.PortA, c.PortB, stopChan)
|
||||
} else if c.Mode == ProxyMode_Listen {
|
||||
err = c.Port2port(c.PortA, c.PortB, stopChan)
|
||||
} else if c.Mode == ProxyMode_Starter {
|
||||
err = c.Host2host(c.PortA, c.PortB, stopChan)
|
||||
}
|
||||
if err != nil {
|
||||
c.Running = false
|
||||
log.Println("Error starting proxy service " + c.Name + "(" + c.UUID + "): " + err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
//Successfully spawned off the proxy routine
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop a running proxy if running
|
||||
func (c *ProxyRelayConfig) IsRunning() bool {
|
||||
return c.Running || c.stopChan != nil
|
||||
}
|
||||
|
||||
// Stop a running proxy if running
|
||||
func (c *ProxyRelayConfig) Stop() {
|
||||
if c.Running || c.stopChan != nil {
|
||||
c.stopChan <- true
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
c.stopChan = nil
|
||||
c.Running = false
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Forwarder Functions
|
||||
*/
|
||||
|
||||
/*
|
||||
portA -> server
|
||||
portB -> server
|
||||
*/
|
||||
func (c *ProxyRelayConfig) Port2port(port1 string, port2 string, stopChan chan bool) error {
|
||||
//Trim the Prefix of : if exists
|
||||
listen1, err := startListener("0.0.0.0:" + port1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listen2, err := startListener("0.0.0.0:" + port2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("[√]", "listen port:", port1, "and", port2, "success. waiting for client...")
|
||||
c.Running = true
|
||||
|
||||
go func() {
|
||||
<-stopChan
|
||||
log.Println("[x]", "Received stop signal. Exiting Port to Port forwarder")
|
||||
c.Running = false
|
||||
listen1.Close()
|
||||
listen2.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
conn1, err := c.accept(listen1)
|
||||
if err != nil {
|
||||
if !c.Running {
|
||||
return nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
conn2, err := c.accept(listen2)
|
||||
if err != nil {
|
||||
if !c.Running {
|
||||
return nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if conn1 == nil || conn2 == nil {
|
||||
log.Println("[x]", "accept client faild. retry in ", c.Timeout, " seconds. ")
|
||||
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||
continue
|
||||
}
|
||||
go forward(conn1, conn2, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
portA -> server
|
||||
server -> portB
|
||||
*/
|
||||
func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, stopChan chan bool) error {
|
||||
server, err := startListener("0.0.0.0:" + allowPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Start stop handler
|
||||
go func() {
|
||||
<-stopChan
|
||||
log.Println("[x]", "Received stop signal. Exiting Port to Host forwarder")
|
||||
c.Running = false
|
||||
server.Close()
|
||||
}()
|
||||
|
||||
//Start blocking loop for accepting connections
|
||||
for {
|
||||
conn, err := c.accept(server)
|
||||
if conn == nil || err != nil {
|
||||
if !c.Running {
|
||||
//Terminate by stop chan. Exit listener loop
|
||||
return nil
|
||||
}
|
||||
|
||||
//Connection error. Retry
|
||||
continue
|
||||
}
|
||||
|
||||
go func(targetAddress string) {
|
||||
log.Println("[+]", "start connect host:["+targetAddress+"]")
|
||||
target, err := net.Dial("tcp", targetAddress)
|
||||
if err != nil {
|
||||
// temporarily unavailable, don't use fatal.
|
||||
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", c.Timeout, "seconds. ")
|
||||
conn.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
|
||||
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||
return
|
||||
}
|
||||
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
|
||||
forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
|
||||
}(targetAddress)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
server -> portA
|
||||
server -> portB
|
||||
*/
|
||||
func (c *ProxyRelayConfig) Host2host(address1, address2 string, stopChan chan bool) error {
|
||||
c.Running = true
|
||||
go func() {
|
||||
<-stopChan
|
||||
log.Println("[x]", "Received stop signal. Exiting Host to Host forwarder")
|
||||
c.Running = false
|
||||
}()
|
||||
|
||||
for c.Running {
|
||||
log.Println("[+]", "try to connect host:["+address1+"] and ["+address2+"]")
|
||||
var host1, host2 net.Conn
|
||||
var err error
|
||||
for {
|
||||
d := net.Dialer{Timeout: time.Duration(c.Timeout)}
|
||||
host1, err = d.Dial("tcp", address1)
|
||||
if err == nil {
|
||||
log.Println("[→]", "connect ["+address1+"] success.")
|
||||
break
|
||||
} else {
|
||||
log.Println("[x]", "connect target address ["+address1+"] faild. retry in ", c.Timeout, " seconds. ")
|
||||
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||
}
|
||||
|
||||
if !c.Running {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
for {
|
||||
d := net.Dialer{Timeout: time.Duration(c.Timeout)}
|
||||
host2, err = d.Dial("tcp", address2)
|
||||
if err == nil {
|
||||
log.Println("[→]", "connect ["+address2+"] success.")
|
||||
break
|
||||
} else {
|
||||
log.Println("[x]", "connect target address ["+address2+"] faild. retry in ", c.Timeout, " seconds. ")
|
||||
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||
}
|
||||
|
||||
if !c.Running {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
go forward(host1, host2, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,289 +0,0 @@
|
||||
package tcpprox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const timeout = 5
|
||||
|
||||
func main() {
|
||||
//log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
|
||||
log.SetFlags(log.Ldate | log.Lmicroseconds)
|
||||
|
||||
printWelcome()
|
||||
|
||||
args := os.Args
|
||||
argc := len(os.Args)
|
||||
if argc <= 2 {
|
||||
printHelp()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
//TODO:support UDP protocol
|
||||
|
||||
/*var logFileError error
|
||||
if argc > 5 && args[4] == "-log" {
|
||||
logPath := args[5] + "/" + time.Now().Format("2006_01_02_15_04_05") // "2006-01-02 15:04:05"
|
||||
logPath += args[1] + "-" + strings.Replace(args[2], ":", "_", -1) + "-" + args[3] + ".log"
|
||||
logPath = strings.Replace(logPath, `\`, "/", -1)
|
||||
logPath = strings.Replace(logPath, "//", "/", -1)
|
||||
logFile, logFileError = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE, 0666)
|
||||
if logFileError != nil {
|
||||
log.Fatalln("[x]", "log file path error.", logFileError.Error())
|
||||
}
|
||||
log.Println("[√]", "open test log file success. path:", logPath)
|
||||
}*/
|
||||
|
||||
switch args[1] {
|
||||
case "-listen":
|
||||
if argc < 3 {
|
||||
log.Fatalln(`-listen need two arguments, like "nb -listen 1997 2017".`)
|
||||
}
|
||||
port1 := checkPort(args[2])
|
||||
port2 := checkPort(args[3])
|
||||
log.Println("[√]", "start to listen port:", port1, "and port:", port2)
|
||||
port2port(port1, port2)
|
||||
break
|
||||
case "-tran":
|
||||
if argc < 3 {
|
||||
log.Fatalln(`-tran need two arguments, like "nb -tran 1997 192.168.1.2:3389".`)
|
||||
}
|
||||
port := checkPort(args[2])
|
||||
var remoteAddress string
|
||||
if checkIp(args[3]) {
|
||||
remoteAddress = args[3]
|
||||
}
|
||||
split := strings.SplitN(remoteAddress, ":", 2)
|
||||
log.Println("[√]", "start to transmit address:", remoteAddress, "to address:", split[0]+":"+port)
|
||||
port2host(port, remoteAddress)
|
||||
break
|
||||
case "-slave":
|
||||
if argc < 3 {
|
||||
log.Fatalln(`-slave need two arguments, like "nb -slave 127.0.0.1:3389 8.8.8.8:1997".`)
|
||||
}
|
||||
var address1, address2 string
|
||||
checkIp(args[2])
|
||||
if checkIp(args[2]) {
|
||||
address1 = args[2]
|
||||
}
|
||||
checkIp(args[3])
|
||||
if checkIp(args[3]) {
|
||||
address2 = args[3]
|
||||
}
|
||||
log.Println("[√]", "start to connect address:", address1, "and address:", address2)
|
||||
host2host(address1, address2)
|
||||
break
|
||||
default:
|
||||
printHelp()
|
||||
}
|
||||
}
|
||||
|
||||
func printWelcome() {
|
||||
fmt.Println("+----------------------------------------------------------------+")
|
||||
fmt.Println("| Welcome to use NATBypass Ver1.0.0 . |")
|
||||
fmt.Println("| Code by cw1997 at 2017-10-19 03:59:51 |")
|
||||
fmt.Println("| If you have some problem when you use the tool, |")
|
||||
fmt.Println("| please submit issue at : https://github.com/cw1997/NATBypass . |")
|
||||
fmt.Println("+----------------------------------------------------------------+")
|
||||
fmt.Println()
|
||||
// sleep one second because the fmt is not thread-safety.
|
||||
// if not to do this, fmt.Print will print after the log.Print.
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
func printHelp() {
|
||||
fmt.Println(`usage: "-listen port1 port2" example: "nb -listen 1997 2017" `)
|
||||
fmt.Println(` "-tran port1 ip:port2" example: "nb -tran 1997 192.168.1.2:3389" `)
|
||||
fmt.Println(` "-slave ip1:port1 ip2:port2" example: "nb -slave 127.0.0.1:3389 8.8.8.8:1997" `)
|
||||
fmt.Println(`============================================================`)
|
||||
fmt.Println(`optional argument: "-log logpath" . example: "nb -listen 1997 2017 -log d:/nb" `)
|
||||
fmt.Println(`log filename format: Y_m_d_H_i_s-agrs1-args2-args3.log`)
|
||||
fmt.Println(`============================================================`)
|
||||
fmt.Println(`if you want more help, please read "README.md". `)
|
||||
}
|
||||
|
||||
func checkPort(port string) string {
|
||||
PortNum, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
log.Fatalln("[x]", "port should be a number")
|
||||
}
|
||||
if PortNum < 1 || PortNum > 65535 {
|
||||
log.Fatalln("[x]", "port should be a number and the range is [1,65536)")
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
func checkIp(address string) bool {
|
||||
ipAndPort := strings.Split(address, ":")
|
||||
if len(ipAndPort) != 2 {
|
||||
log.Fatalln("[x]", "address error. should be a string like [ip:port]. ")
|
||||
}
|
||||
ip := ipAndPort[0]
|
||||
port := ipAndPort[1]
|
||||
checkPort(port)
|
||||
pattern := `^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$`
|
||||
ok, err := regexp.MatchString(pattern, ip)
|
||||
if err != nil || !ok {
|
||||
log.Fatalln("[x]", "ip error. ")
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func port2port(port1 string, port2 string) {
|
||||
listen1 := start_server("0.0.0.0:" + port1)
|
||||
listen2 := start_server("0.0.0.0:" + port2)
|
||||
log.Println("[√]", "listen port:", port1, "and", port2, "success. waiting for client...")
|
||||
for {
|
||||
conn1 := accept(listen1)
|
||||
conn2 := accept(listen2)
|
||||
if conn1 == nil || conn2 == nil {
|
||||
log.Println("[x]", "accept client faild. retry in ", timeout, " seconds. ")
|
||||
time.Sleep(timeout * time.Second)
|
||||
continue
|
||||
}
|
||||
forward(conn1, conn2)
|
||||
}
|
||||
}
|
||||
|
||||
func port2host(allowPort string, targetAddress string) {
|
||||
server := start_server("0.0.0.0:" + allowPort)
|
||||
for {
|
||||
conn := accept(server)
|
||||
if conn == nil {
|
||||
continue
|
||||
}
|
||||
//println(targetAddress)
|
||||
go func(targetAddress string) {
|
||||
log.Println("[+]", "start connect host:["+targetAddress+"]")
|
||||
target, err := net.Dial("tcp", targetAddress)
|
||||
if err != nil {
|
||||
// temporarily unavailable, don't use fatal.
|
||||
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", timeout, "seconds. ")
|
||||
conn.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
|
||||
time.Sleep(timeout * time.Second)
|
||||
return
|
||||
}
|
||||
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
|
||||
forward(target, conn)
|
||||
}(targetAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func host2host(address1, address2 string) {
|
||||
for {
|
||||
log.Println("[+]", "try to connect host:["+address1+"] and ["+address2+"]")
|
||||
var host1, host2 net.Conn
|
||||
var err error
|
||||
for {
|
||||
host1, err = net.Dial("tcp", address1)
|
||||
if err == nil {
|
||||
log.Println("[→]", "connect ["+address1+"] success.")
|
||||
break
|
||||
} else {
|
||||
log.Println("[x]", "connect target address ["+address1+"] faild. retry in ", timeout, " seconds. ")
|
||||
time.Sleep(timeout * time.Second)
|
||||
}
|
||||
}
|
||||
for {
|
||||
host2, err = net.Dial("tcp", address2)
|
||||
if err == nil {
|
||||
log.Println("[→]", "connect ["+address2+"] success.")
|
||||
break
|
||||
} else {
|
||||
log.Println("[x]", "connect target address ["+address2+"] faild. retry in ", timeout, " seconds. ")
|
||||
time.Sleep(timeout * time.Second)
|
||||
}
|
||||
}
|
||||
forward(host1, host2)
|
||||
}
|
||||
}
|
||||
|
||||
func start_server(address string) net.Listener {
|
||||
log.Println("[+]", "try to start server on:["+address+"]")
|
||||
server, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
log.Fatalln("[x]", "listen address ["+address+"] faild.")
|
||||
}
|
||||
log.Println("[√]", "start listen at address:["+address+"]")
|
||||
return server
|
||||
/*defer server.Close()
|
||||
|
||||
for {
|
||||
conn, err := server.Accept()
|
||||
log.Println("accept a new client. remote address:[" + conn.RemoteAddr().String() +
|
||||
"], local address:[" + conn.LocalAddr().String() + "]")
|
||||
if err != nil {
|
||||
log.Println("accept a new client faild.", err.Error())
|
||||
continue
|
||||
}
|
||||
//go recvConnMsg(conn)
|
||||
}*/
|
||||
}
|
||||
|
||||
func accept(listener net.Listener) net.Conn {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Println("[x]", "accept connect ["+conn.RemoteAddr().String()+"] faild.", err.Error())
|
||||
return nil
|
||||
}
|
||||
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
|
||||
return conn
|
||||
}
|
||||
|
||||
func forward(conn1 net.Conn, conn2 net.Conn) {
|
||||
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
|
||||
var wg sync.WaitGroup
|
||||
// wait tow goroutines
|
||||
wg.Add(2)
|
||||
go connCopy(conn1, conn2, &wg)
|
||||
go connCopy(conn2, conn1, &wg)
|
||||
//blocking when the wg is locked
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup) {
|
||||
//TODO:log, record the data from conn1 and conn2.
|
||||
logFile := openLog(conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
|
||||
if logFile != nil {
|
||||
w := io.MultiWriter(conn1, logFile)
|
||||
io.Copy(w, conn2)
|
||||
} else {
|
||||
io.Copy(conn1, conn2)
|
||||
}
|
||||
conn1.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
|
||||
//conn2.Close()
|
||||
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
|
||||
wg.Done()
|
||||
}
|
||||
func openLog(address1, address2, address3, address4 string) *os.File {
|
||||
args := os.Args
|
||||
argc := len(os.Args)
|
||||
var logFileError error
|
||||
var logFile *os.File
|
||||
if argc > 5 && args[4] == "-log" {
|
||||
address1 = strings.Replace(address1, ":", "_", -1)
|
||||
address2 = strings.Replace(address2, ":", "_", -1)
|
||||
address3 = strings.Replace(address3, ":", "_", -1)
|
||||
address4 = strings.Replace(address4, ":", "_", -1)
|
||||
timeStr := time.Now().Format("2006_01_02_15_04_05") // "2006-01-02 15:04:05"
|
||||
logPath := args[5] + "/" + timeStr + args[1] + "-" + address1 + "_" + address2 + "-" + address3 + "_" + address4 + ".log"
|
||||
logPath = strings.Replace(logPath, `\`, "/", -1)
|
||||
logPath = strings.Replace(logPath, "//", "/", -1)
|
||||
logFile, logFileError = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE, 0666)
|
||||
if logFileError != nil {
|
||||
log.Fatalln("[x]", "log file path error.", logFileError.Error())
|
||||
}
|
||||
log.Println("[√]", "open test log file success. path:", logPath)
|
||||
}
|
||||
return logFile
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
package tcpprox
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
)
|
||||
|
||||
/*
|
||||
TCP Proxy
|
||||
|
||||
Forward port from one port to another
|
||||
Also accept active connection and passive
|
||||
connection
|
||||
*/
|
||||
|
||||
const (
|
||||
ProxyMode_Listen = 0
|
||||
ProxyMode_Transport = 1
|
||||
ProxyMode_Starter = 2
|
||||
)
|
||||
|
||||
type ProxyRelayOptions struct {
|
||||
Name string
|
||||
PortA string
|
||||
PortB string
|
||||
Timeout int
|
||||
Mode int
|
||||
}
|
||||
|
||||
type ProxyRelayConfig struct {
|
||||
UUID string //A UUIDv4 representing this config
|
||||
Name string //Name of the config
|
||||
Running bool //If the service is running
|
||||
PortA string //Ports A (config depends on mode)
|
||||
PortB string //Ports B (config depends on mode)
|
||||
Mode int //Operation Mode
|
||||
Timeout int //Timeout for connection in sec
|
||||
stopChan chan bool //Stop channel to stop the listener
|
||||
aTobAccumulatedByteTransfer int64 //Accumulated byte transfer from A to B
|
||||
bToaAccumulatedByteTransfer int64 //Accumulated byte transfer from B to A
|
||||
|
||||
parent *Manager `json:"-"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Database *database.Database
|
||||
DefaultTimeout int
|
||||
AccessControlHandler func(net.Conn) bool
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
//Config and stores
|
||||
Options *Options
|
||||
Configs []*ProxyRelayConfig
|
||||
|
||||
//Realtime Statistics
|
||||
Connections int //currently connected connect counts
|
||||
}
|
||||
|
||||
func NewTCProxy(options *Options) *Manager {
|
||||
options.Database.NewTable("tcprox")
|
||||
|
||||
//Load relay configs from db
|
||||
previousRules := []*ProxyRelayConfig{}
|
||||
if options.Database.KeyExists("tcprox", "rules") {
|
||||
options.Database.Read("tcprox", "rules", &previousRules)
|
||||
}
|
||||
|
||||
//Check if the AccessControlHandler is empty. If yes, set it to always allow access
|
||||
if options.AccessControlHandler == nil {
|
||||
options.AccessControlHandler = func(conn net.Conn) bool {
|
||||
//Always allow access
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
//Create a new proxy manager for TCP
|
||||
thisManager := Manager{
|
||||
Options: options,
|
||||
Connections: 0,
|
||||
}
|
||||
|
||||
//Inject manager into the rules
|
||||
for _, rule := range previousRules {
|
||||
rule.parent = &thisManager
|
||||
}
|
||||
|
||||
thisManager.Configs = previousRules
|
||||
|
||||
return &thisManager
|
||||
}
|
||||
|
||||
func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
|
||||
//Generate a new config from options
|
||||
configUUID := uuid.New().String()
|
||||
thisConfig := ProxyRelayConfig{
|
||||
UUID: configUUID,
|
||||
Name: config.Name,
|
||||
Running: false,
|
||||
PortA: config.PortA,
|
||||
PortB: config.PortB,
|
||||
Mode: config.Mode,
|
||||
Timeout: config.Timeout,
|
||||
stopChan: nil,
|
||||
aTobAccumulatedByteTransfer: 0,
|
||||
bToaAccumulatedByteTransfer: 0,
|
||||
|
||||
parent: m,
|
||||
}
|
||||
m.Configs = append(m.Configs, &thisConfig)
|
||||
m.SaveConfigToDatabase()
|
||||
return configUUID
|
||||
}
|
||||
|
||||
func (m *Manager) GetConfigByUUID(configUUID string) (*ProxyRelayConfig, error) {
|
||||
// Find and return the config with the specified UUID
|
||||
for _, config := range m.Configs {
|
||||
if config.UUID == configUUID {
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("config not found")
|
||||
}
|
||||
|
||||
// Edit the config based on config UUID, leave empty for unchange fields
|
||||
func (m *Manager) EditConfig(configUUID string, newName string, newPortA string, newPortB string, newMode int, newTimeout int) error {
|
||||
// Find the config with the specified UUID
|
||||
foundConfig, err := m.GetConfigByUUID(configUUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate and update the fields
|
||||
if newName != "" {
|
||||
foundConfig.Name = newName
|
||||
}
|
||||
if newPortA != "" {
|
||||
foundConfig.PortA = newPortA
|
||||
}
|
||||
if newPortB != "" {
|
||||
foundConfig.PortB = newPortB
|
||||
}
|
||||
if newMode != -1 {
|
||||
if newMode > 2 || newMode < 0 {
|
||||
return errors.New("invalid mode given")
|
||||
}
|
||||
foundConfig.Mode = newMode
|
||||
}
|
||||
if newTimeout != -1 {
|
||||
if newTimeout < 0 {
|
||||
return errors.New("invalid timeout value given")
|
||||
}
|
||||
foundConfig.Timeout = newTimeout
|
||||
}
|
||||
|
||||
/*
|
||||
err = foundConfig.ValidateConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*/
|
||||
|
||||
m.SaveConfigToDatabase()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) RemoveConfig(configUUID string) error {
|
||||
// Find and remove the config with the specified UUID
|
||||
for i, config := range m.Configs {
|
||||
if config.UUID == configUUID {
|
||||
m.Configs = append(m.Configs[:i], m.Configs[i+1:]...)
|
||||
m.SaveConfigToDatabase()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("config not found")
|
||||
}
|
||||
|
||||
func (m *Manager) SaveConfigToDatabase() {
|
||||
m.Options.Database.Write("tcprox", "rules", m.Configs)
|
||||
}
|
@ -6,11 +6,11 @@ import (
|
||||
"embed"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -21,15 +21,16 @@ type CertCache struct {
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
CertStore string //Path where all the certs are stored
|
||||
LoadedCerts []*CertCache //A list of loaded certs
|
||||
CertStore string //Path where all the certs are stored
|
||||
LoadedCerts []*CertCache //A list of loaded certs
|
||||
Logger *logger.Logger //System wide logger for debug mesage
|
||||
verbal bool
|
||||
}
|
||||
|
||||
//go:embed localhost.pem localhost.key
|
||||
var buildinCertStore embed.FS
|
||||
|
||||
func NewManager(certStore string, verbal bool) (*Manager, error) {
|
||||
func NewManager(certStore string, verbal bool, logger *logger.Logger) (*Manager, error) {
|
||||
if !utils.FileExists(certStore) {
|
||||
os.MkdirAll(certStore, 0775)
|
||||
}
|
||||
@ -52,6 +53,7 @@ func NewManager(certStore string, verbal bool) (*Manager, error) {
|
||||
CertStore: certStore,
|
||||
LoadedCerts: []*CertCache{},
|
||||
verbal: verbal,
|
||||
Logger: logger,
|
||||
}
|
||||
|
||||
err := thisManager.UpdateLoadedCertList()
|
||||
@ -78,7 +80,7 @@ func (m *Manager) UpdateLoadedCertList() error {
|
||||
priKey := filepath.Join(m.CertStore, certname+".key")
|
||||
certificate, err := tls.LoadX509KeyPair(pubKey, priKey)
|
||||
if err != nil {
|
||||
log.Println("Certificate loaded failed: " + certname)
|
||||
m.Logger.PrintAndLog("tls-router", "Certificate load failed: "+certname, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -86,6 +88,7 @@ func (m *Manager) UpdateLoadedCertList() error {
|
||||
loadedCert, err := x509.ParseCertificate(thisCert)
|
||||
if err != nil {
|
||||
//Error pasring cert, skip this byte segment
|
||||
m.Logger.PrintAndLog("tls-router", "Certificate parse failed: "+certname, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -171,44 +174,16 @@ func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, err
|
||||
pubKey, priKey = m.GetCertByX509CNHostname(helloInfo.ServerName)
|
||||
} else {
|
||||
//Fallback to legacy method of matching certificates
|
||||
/*
|
||||
domainCerts, _ := m.ListCertDomains()
|
||||
cloestDomainCert := matchClosestDomainCertificate(helloInfo.ServerName, domainCerts)
|
||||
if cloestDomainCert != "" {
|
||||
//There is a matching parent domain for this subdomain. Use this instead.
|
||||
pubKey = filepath.Join(m.CertStore, cloestDomainCert+".pem")
|
||||
priKey = filepath.Join(m.CertStore, cloestDomainCert+".key")
|
||||
} else if m.DefaultCertExists() {
|
||||
//Use default.pem and default.key
|
||||
pubKey = filepath.Join(m.CertStore, "default.pem")
|
||||
priKey = filepath.Join(m.CertStore, "default.key")
|
||||
if m.verbal {
|
||||
log.Println("No matching certificate found. Serving with default")
|
||||
}
|
||||
} else {
|
||||
if m.verbal {
|
||||
log.Println("Matching certificate not found. Serving with build-in certificate. Requesting server name: ", helloInfo.ServerName)
|
||||
}
|
||||
}*/
|
||||
|
||||
if m.DefaultCertExists() {
|
||||
//Use default.pem and default.key
|
||||
pubKey = filepath.Join(m.CertStore, "default.pem")
|
||||
priKey = filepath.Join(m.CertStore, "default.key")
|
||||
//if m.verbal {
|
||||
// log.Println("No matching certificate found. Serving with default")
|
||||
//}
|
||||
} else {
|
||||
//if m.verbal {
|
||||
// log.Println("Matching certificate not found. Serving with build-in certificate. Requesting server name: ", helloInfo.ServerName)
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
//Load the cert and serve it
|
||||
cer, err := tls.LoadX509KeyPair(pubKey, priKey)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
107
src/mod/update/update.go
Normal file
107
src/mod/update/update.go
Normal file
@ -0,0 +1,107 @@
|
||||
package update
|
||||
|
||||
/*
|
||||
Update.go
|
||||
|
||||
This module handle cross version updates that contains breaking changes
|
||||
update command should always exit after the update is completed
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
// Run config update. Version numbers are int. For example
|
||||
// to update 3.0.7 to 3.0.8, use RunConfigUpdate(307, 308)
|
||||
// This function support cross versions updates (e.g. 307 -> 310)
|
||||
func RunConfigUpdate(fromVersion int, toVersion int) {
|
||||
versionFile := "./conf/version"
|
||||
isFirstTimeInit, _ := isFirstTimeInitialize("./conf/proxy/")
|
||||
if isFirstTimeInit {
|
||||
//Create version file and exit
|
||||
os.MkdirAll("./conf/", 0775)
|
||||
os.WriteFile(versionFile, []byte(strconv.Itoa(toVersion)), 0775)
|
||||
return
|
||||
}
|
||||
if fromVersion == 0 {
|
||||
//Run auto previous version detection
|
||||
fromVersion = 307
|
||||
if utils.FileExists(versionFile) {
|
||||
//Read the version file
|
||||
previousVersionText, err := os.ReadFile(versionFile)
|
||||
if err != nil {
|
||||
panic("Unable to read version file at " + versionFile)
|
||||
}
|
||||
|
||||
//Convert the version to int
|
||||
versionInt, err := strconv.Atoi(strings.TrimSpace(string(previousVersionText)))
|
||||
if err != nil {
|
||||
panic("Unable to read version file at " + versionFile)
|
||||
}
|
||||
|
||||
fromVersion = versionInt
|
||||
}
|
||||
|
||||
if fromVersion == toVersion {
|
||||
//No need to update
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//Do iterate update
|
||||
for i := fromVersion; i < toVersion; i++ {
|
||||
oldVersion := i
|
||||
newVersion := i + 1
|
||||
fmt.Println("Updating from v", oldVersion, " to v", newVersion)
|
||||
runUpdateRoutineWithVersion(oldVersion, newVersion)
|
||||
//Write the updated version to file
|
||||
os.WriteFile(versionFile, []byte(strconv.Itoa(newVersion)), 0775)
|
||||
}
|
||||
fmt.Println("Update completed")
|
||||
}
|
||||
|
||||
func GetVersionIntFromVersionNumber(version string) int {
|
||||
versionNumberOnly := strings.ReplaceAll(version, ".", "")
|
||||
versionInt, _ := strconv.Atoi(versionNumberOnly)
|
||||
return versionInt
|
||||
}
|
||||
|
||||
// Check if the folder "./conf/proxy/" exists and contains files
|
||||
func isFirstTimeInitialize(path string) (bool, error) {
|
||||
// Check if the folder exists
|
||||
info, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
// The folder does not exist
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
// Some other error occurred
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if it is a directory
|
||||
if !info.IsDir() {
|
||||
// The path is not a directory
|
||||
return false, fmt.Errorf("%s is not a directory", path)
|
||||
}
|
||||
|
||||
// Read the directory contents
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if the directory is empty
|
||||
if len(files) == 0 {
|
||||
// The folder exists but is empty
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// The folder exists and contains files
|
||||
return false, nil
|
||||
}
|
16
src/mod/update/updatelogic.go
Normal file
16
src/mod/update/updatelogic.go
Normal file
@ -0,0 +1,16 @@
|
||||
package update
|
||||
|
||||
import v308 "imuslab.com/zoraxy/mod/update/v308"
|
||||
|
||||
// Updater Core logic
|
||||
func runUpdateRoutineWithVersion(fromVersion int, toVersion int) {
|
||||
if fromVersion == 307 && toVersion == 308 {
|
||||
//Updating from v3.0.7 to v3.0.8
|
||||
err := v308.UpdateFrom307To308()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
//ADD MORE VERSIONS HERE
|
||||
}
|
138
src/mod/update/v308/typedef307.go
Normal file
138
src/mod/update/v308/typedef307.go
Normal file
@ -0,0 +1,138 @@
|
||||
package v308
|
||||
|
||||
/*
|
||||
v307 type definations
|
||||
|
||||
This file wrap up the self-contained data structure
|
||||
for v3.0.7 structure and allow automatic updates
|
||||
for future releases if required
|
||||
*/
|
||||
|
||||
type v307PermissionsPolicy struct {
|
||||
Accelerometer []string `json:"accelerometer"`
|
||||
AmbientLightSensor []string `json:"ambient_light_sensor"`
|
||||
Autoplay []string `json:"autoplay"`
|
||||
Battery []string `json:"battery"`
|
||||
Camera []string `json:"camera"`
|
||||
CrossOriginIsolated []string `json:"cross_origin_isolated"`
|
||||
DisplayCapture []string `json:"display_capture"`
|
||||
DocumentDomain []string `json:"document_domain"`
|
||||
EncryptedMedia []string `json:"encrypted_media"`
|
||||
ExecutionWhileNotRendered []string `json:"execution_while_not_rendered"`
|
||||
ExecutionWhileOutOfView []string `json:"execution_while_out_of_viewport"`
|
||||
Fullscreen []string `json:"fullscreen"`
|
||||
Geolocation []string `json:"geolocation"`
|
||||
Gyroscope []string `json:"gyroscope"`
|
||||
KeyboardMap []string `json:"keyboard_map"`
|
||||
Magnetometer []string `json:"magnetometer"`
|
||||
Microphone []string `json:"microphone"`
|
||||
Midi []string `json:"midi"`
|
||||
NavigationOverride []string `json:"navigation_override"`
|
||||
Payment []string `json:"payment"`
|
||||
PictureInPicture []string `json:"picture_in_picture"`
|
||||
PublicKeyCredentialsGet []string `json:"publickey_credentials_get"`
|
||||
ScreenWakeLock []string `json:"screen_wake_lock"`
|
||||
SyncXHR []string `json:"sync_xhr"`
|
||||
USB []string `json:"usb"`
|
||||
WebShare []string `json:"web_share"`
|
||||
XRSpatialTracking []string `json:"xr_spatial_tracking"`
|
||||
ClipboardRead []string `json:"clipboard_read"`
|
||||
ClipboardWrite []string `json:"clipboard_write"`
|
||||
Gamepad []string `json:"gamepad"`
|
||||
SpeakerSelection []string `json:"speaker_selection"`
|
||||
ConversionMeasurement []string `json:"conversion_measurement"`
|
||||
FocusWithoutUserActivation []string `json:"focus_without_user_activation"`
|
||||
HID []string `json:"hid"`
|
||||
IdleDetection []string `json:"idle_detection"`
|
||||
InterestCohort []string `json:"interest_cohort"`
|
||||
Serial []string `json:"serial"`
|
||||
SyncScript []string `json:"sync_script"`
|
||||
TrustTokenRedemption []string `json:"trust_token_redemption"`
|
||||
Unload []string `json:"unload"`
|
||||
WindowPlacement []string `json:"window_placement"`
|
||||
VerticalScroll []string `json:"vertical_scroll"`
|
||||
}
|
||||
|
||||
// Auth credential for basic auth on certain endpoints
|
||||
type v307BasicAuthCredentials struct {
|
||||
Username string
|
||||
PasswordHash string
|
||||
}
|
||||
|
||||
// Auth credential for basic auth on certain endpoints
|
||||
type v307BasicAuthUnhashedCredentials struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Paths to exclude in basic auth enabled proxy handler
|
||||
type v307BasicAuthExceptionRule struct {
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
// Header injection direction type
|
||||
type v307HeaderDirection int
|
||||
|
||||
const (
|
||||
HeaderDirection_ZoraxyToUpstream v307HeaderDirection = 0 //Inject (or remove) header to request out-going from Zoraxy to backend server
|
||||
HeaderDirection_ZoraxyToDownstream v307HeaderDirection = 1 //Inject (or remove) header to request out-going from Zoraxy to client (e.g. browser)
|
||||
)
|
||||
|
||||
// User defined headers to add into a proxy endpoint
|
||||
type v307UserDefinedHeader struct {
|
||||
Direction v307HeaderDirection
|
||||
Key string
|
||||
Value string
|
||||
IsRemove bool //Instead of set, remove this key instead
|
||||
}
|
||||
|
||||
// The original proxy endpoint structure from v3.0.7
|
||||
type v307ProxyEndpoint struct {
|
||||
ProxyType int //The type of this proxy, see const def
|
||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||
Domain string //Domain or IP to proxy to
|
||||
|
||||
//TLS/SSL Related
|
||||
RequireTLS bool //Target domain require TLS
|
||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||
|
||||
//Virtual Directories
|
||||
VirtualDirectories []*v307VirtualDirectoryEndpoint
|
||||
|
||||
//Custom Headers
|
||||
UserDefinedHeaders []*v307UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||
PermissionPolicy *v307PermissionsPolicy //Permission policy header
|
||||
|
||||
//Authentication
|
||||
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||
BasicAuthCredentials []*v307BasicAuthCredentials //Basic auth credentials
|
||||
BasicAuthExceptionRules []*v307BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||
|
||||
// Rate Limiting
|
||||
RequireRateLimit bool
|
||||
RateLimit int64 // Rate limit in requests per second
|
||||
|
||||
//Access Control
|
||||
AccessFilterUUID string //Access filter ID
|
||||
|
||||
Disabled bool //If the rule is disabled
|
||||
|
||||
//Fallback routing logic (Special Rule Sets Only)
|
||||
DefaultSiteOption int //Fallback routing logic options
|
||||
DefaultSiteValue string //Fallback routing target, optional
|
||||
}
|
||||
|
||||
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||
// program structure than directly using ProxyEndpoint
|
||||
type v307VirtualDirectoryEndpoint struct {
|
||||
MatchingPath string //Matching prefix of the request path, also act as key
|
||||
Domain string //Domain or IP to proxy to
|
||||
RequireTLS bool //Target domain require TLS
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
Disabled bool //If the rule is enabled
|
||||
}
|
63
src/mod/update/v308/typedef308.go
Normal file
63
src/mod/update/v308/typedef308.go
Normal file
@ -0,0 +1,63 @@
|
||||
package v308
|
||||
|
||||
/*
|
||||
v308 type definations
|
||||
|
||||
This file wrap up the self-contained data structure
|
||||
for v3.0.8 structure and allow automatic updates
|
||||
for future releases if required
|
||||
|
||||
Some struct are identical as v307 and hence it is not redefined here
|
||||
*/
|
||||
|
||||
/* Upstream or Origin Server */
|
||||
type v308Upstream struct {
|
||||
//Upstream Proxy Configs
|
||||
OriginIpOrDomain string //Target IP address or domain name with port
|
||||
RequireTLS bool //Require TLS connection
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||
|
||||
//Load balancing configs
|
||||
Weight int //Prirotiy of fallback, set all to 0 for round robin
|
||||
MaxConn int //Maxmium connection to this server
|
||||
}
|
||||
|
||||
// A proxy endpoint record, a general interface for handling inbound routing
|
||||
type v308ProxyEndpoint struct {
|
||||
ProxyType int //The type of this proxy, see const def
|
||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||
ActiveOrigins []*v308Upstream //Activated Upstream or origin servers IP or domain to proxy to
|
||||
InactiveOrigins []*v308Upstream //Disabled Upstream or origin servers IP or domain to proxy to
|
||||
UseStickySession bool //Use stick session for load balancing
|
||||
Disabled bool //If the rule is disabled
|
||||
|
||||
//Inbound TLS/SSL Related
|
||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||
|
||||
//Virtual Directories
|
||||
VirtualDirectories []*v307VirtualDirectoryEndpoint
|
||||
|
||||
//Custom Headers
|
||||
UserDefinedHeaders []*v307UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||
PermissionPolicy *v307PermissionsPolicy //Permission policy header
|
||||
|
||||
//Authentication
|
||||
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||
BasicAuthCredentials []*v307BasicAuthCredentials //Basic auth credentials
|
||||
BasicAuthExceptionRules []*v307BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||
|
||||
// Rate Limiting
|
||||
RequireRateLimit bool
|
||||
RateLimit int64 // Rate limit in requests per second
|
||||
|
||||
//Access Control
|
||||
AccessFilterUUID string //Access filter ID
|
||||
|
||||
//Fallback routing logic (Special Rule Sets Only)
|
||||
DefaultSiteOption int //Fallback routing logic options
|
||||
DefaultSiteValue string //Fallback routing target, optional
|
||||
}
|
132
src/mod/update/v308/v308.go
Normal file
132
src/mod/update/v308/v308.go
Normal file
@ -0,0 +1,132 @@
|
||||
package v308
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
v3.0.7 update to v3.0.8
|
||||
|
||||
This update function
|
||||
*/
|
||||
|
||||
// Update proxy config files from v3.0.7 to v3.0.8
|
||||
func UpdateFrom307To308() error {
|
||||
|
||||
//Load the configs
|
||||
oldConfigFiles, err := filepath.Glob("./conf/proxy/*.config")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Backup all the files
|
||||
err = os.MkdirAll("./conf/proxy.old/", 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, oldConfigFile := range oldConfigFiles {
|
||||
// Extract the file name from the path
|
||||
fileName := filepath.Base(oldConfigFile)
|
||||
// Construct the backup file path
|
||||
backupFile := filepath.Join("./conf/proxy.old/", fileName)
|
||||
|
||||
// Copy the file to the backup directory
|
||||
err := copyFile(oldConfigFile, backupFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//read the config into the old struct
|
||||
for _, oldConfigFile := range oldConfigFiles {
|
||||
configContent, err := os.ReadFile(oldConfigFile)
|
||||
if err != nil {
|
||||
log.Println("Unable to read config file "+filepath.Base(oldConfigFile), err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
thisOldConfigStruct := v307ProxyEndpoint{}
|
||||
err = json.Unmarshal(configContent, &thisOldConfigStruct)
|
||||
if err != nil {
|
||||
log.Println("Unable to parse file "+filepath.Base(oldConfigFile), err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
//Convert the old config to new config
|
||||
newProxyStructure := convertV307ToV308(thisOldConfigStruct)
|
||||
js, _ := json.MarshalIndent(newProxyStructure, "", " ")
|
||||
err = os.WriteFile(oldConfigFile, js, 0775)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertV307ToV308(old v307ProxyEndpoint) v308ProxyEndpoint {
|
||||
// Create a new v308ProxyEndpoint instance
|
||||
|
||||
matchingDomainsSlice := old.MatchingDomainAlias
|
||||
if matchingDomainsSlice == nil {
|
||||
matchingDomainsSlice = []string{}
|
||||
}
|
||||
|
||||
newEndpoint := v308ProxyEndpoint{
|
||||
ProxyType: old.ProxyType,
|
||||
RootOrMatchingDomain: old.RootOrMatchingDomain,
|
||||
MatchingDomainAlias: matchingDomainsSlice,
|
||||
ActiveOrigins: []*v308Upstream{{ // Mapping Domain field to v308Upstream struct
|
||||
OriginIpOrDomain: old.Domain,
|
||||
RequireTLS: old.RequireTLS,
|
||||
SkipCertValidations: old.SkipCertValidations,
|
||||
SkipWebSocketOriginCheck: old.SkipWebSocketOriginCheck,
|
||||
Weight: 1,
|
||||
MaxConn: 0,
|
||||
}},
|
||||
InactiveOrigins: []*v308Upstream{},
|
||||
UseStickySession: false,
|
||||
Disabled: old.Disabled,
|
||||
BypassGlobalTLS: old.BypassGlobalTLS,
|
||||
VirtualDirectories: old.VirtualDirectories,
|
||||
UserDefinedHeaders: old.UserDefinedHeaders,
|
||||
HSTSMaxAge: old.HSTSMaxAge,
|
||||
EnablePermissionPolicyHeader: old.EnablePermissionPolicyHeader,
|
||||
PermissionPolicy: old.PermissionPolicy,
|
||||
RequireBasicAuth: old.RequireBasicAuth,
|
||||
BasicAuthCredentials: old.BasicAuthCredentials,
|
||||
BasicAuthExceptionRules: old.BasicAuthExceptionRules,
|
||||
RequireRateLimit: old.RequireRateLimit,
|
||||
RateLimit: old.RateLimit,
|
||||
AccessFilterUUID: old.AccessFilterUUID,
|
||||
DefaultSiteOption: old.DefaultSiteOption,
|
||||
DefaultSiteValue: old.DefaultSiteValue,
|
||||
}
|
||||
|
||||
return newEndpoint
|
||||
}
|
||||
|
||||
// Helper function to copy files
|
||||
func copyFile(src, dst string) error {
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
destinationFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destinationFile.Close()
|
||||
|
||||
_, err = io.Copy(destinationFile, sourceFile)
|
||||
return err
|
||||
}
|
@ -2,14 +2,22 @@ package uptime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
logModuleName = "uptime-monitor"
|
||||
)
|
||||
|
||||
type Record struct {
|
||||
Timestamp int64
|
||||
ID string
|
||||
@ -21,17 +29,26 @@ type Record struct {
|
||||
Latency int64
|
||||
}
|
||||
|
||||
type ProxyType string
|
||||
|
||||
const (
|
||||
ProxyType_Host ProxyType = "Origin Server"
|
||||
ProxyType_Vdir ProxyType = "Virtual Directory"
|
||||
)
|
||||
|
||||
type Target struct {
|
||||
ID string
|
||||
Name string
|
||||
URL string
|
||||
Protocol string
|
||||
ID string
|
||||
Name string
|
||||
URL string
|
||||
Protocol string
|
||||
ProxyType ProxyType
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Targets []*Target
|
||||
Interval int
|
||||
MaxRecordsStore int
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
type Monitor struct {
|
||||
@ -54,6 +71,12 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
|
||||
Config: config,
|
||||
OnlineStatusLog: map[string][]*Record{},
|
||||
}
|
||||
|
||||
if config.Logger == nil {
|
||||
//Use default fmt to log if logger is not provided
|
||||
config.Logger, _ = logger.NewFmtLogger()
|
||||
}
|
||||
|
||||
//Start the endpoint listener
|
||||
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
|
||||
done := make(chan bool)
|
||||
@ -67,7 +90,7 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
|
||||
case <-done:
|
||||
return
|
||||
case t := <-ticker.C:
|
||||
log.Println("Uptime updated - ", t.Unix())
|
||||
thisMonitor.Config.Logger.PrintAndLog(logModuleName, "Uptime updated - "+strconv.Itoa(int(t.Unix())), nil)
|
||||
thisMonitor.ExecuteUptimeCheck()
|
||||
}
|
||||
}
|
||||
@ -81,7 +104,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
|
||||
//For each target to check online, do the following
|
||||
var thisRecord Record
|
||||
if target.Protocol == "http" || target.Protocol == "https" {
|
||||
online, laterncy, statusCode := getWebsiteStatusWithLatency(target.URL)
|
||||
online, laterncy, statusCode := m.getWebsiteStatusWithLatency(target.URL)
|
||||
thisRecord = Record{
|
||||
Timestamp: time.Now().Unix(),
|
||||
ID: target.ID,
|
||||
@ -94,7 +117,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Println("Unknown protocol: " + target.Protocol + ". Skipping")
|
||||
m.Config.Logger.PrintAndLog(logModuleName, "Unknown protocol: "+target.Protocol, errors.New("unsupported protocol"))
|
||||
continue
|
||||
}
|
||||
|
||||
@ -114,8 +137,6 @@ func (m *Monitor) ExecuteUptimeCheck() {
|
||||
m.OnlineStatusLog[target.ID] = thisRecords
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Write results to db
|
||||
}
|
||||
|
||||
func (m *Monitor) AddTargetToMonitor(target *Target) {
|
||||
@ -191,12 +212,12 @@ func (m *Monitor) HandleUptimeLogRead(w http.ResponseWriter, r *http.Request) {
|
||||
*/
|
||||
|
||||
// Get website stauts with latency given URL, return is conn succ and its latency and status code
|
||||
func getWebsiteStatusWithLatency(url string) (bool, int64, int) {
|
||||
func (m *Monitor) getWebsiteStatusWithLatency(url string) (bool, int64, int) {
|
||||
start := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
statusCode, err := getWebsiteStatus(url)
|
||||
end := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
m.Config.Logger.PrintAndLog(logModuleName, "Ping upstream timeout. Assume offline", err)
|
||||
return false, 0, 0
|
||||
} else {
|
||||
diff := end - start
|
||||
@ -217,11 +238,24 @@ func getWebsiteStatusWithLatency(url string) (bool, int64, int) {
|
||||
}
|
||||
|
||||
func getWebsiteStatus(url string) (int, error) {
|
||||
client := http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
// Create a one-time use cookie jar to store cookies
|
||||
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
client := http.Client{
|
||||
Jar: jar,
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header = http.Header{
|
||||
"User-Agent": {"zoraxy-uptime/1.1"},
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
//resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
//Try replace the http with https and vise versa
|
||||
rewriteURL := ""
|
||||
@ -231,7 +265,12 @@ func getWebsiteStatus(url string) (int, error) {
|
||||
rewriteURL = strings.ReplaceAll(url, "http://", "https://")
|
||||
}
|
||||
|
||||
resp, err = client.Get(rewriteURL)
|
||||
req, _ := http.NewRequest("GET", rewriteURL, nil)
|
||||
req.Header = http.Header{
|
||||
"User-Agent": {"zoraxy-uptime/1.1"},
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "http: server gave HTTP response to HTTPS client") {
|
||||
//Invalid downstream reverse proxy settings, but it is online
|
||||
|
@ -1,6 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@ -50,3 +54,52 @@ func ReplaceSpecialCharacters(filename string) string {
|
||||
|
||||
return filename
|
||||
}
|
||||
|
||||
/* Zip File Handler */
|
||||
// zipFiles compresses multiple files into a single zip archive file
|
||||
func ZipFiles(filename string, files ...string) error {
|
||||
newZipFile, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer newZipFile.Close()
|
||||
|
||||
zipWriter := zip.NewWriter(newZipFile)
|
||||
defer zipWriter.Close()
|
||||
|
||||
for _, file := range files {
|
||||
if err := addFileToZip(zipWriter, file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// addFileToZip adds an individual file to a zip archive
|
||||
func addFileToZip(zipWriter *zip.Writer, filename string) error {
|
||||
fileToZip, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileToZip.Close()
|
||||
|
||||
info, err := fileToZip.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header.Name = filepath.Base(filename)
|
||||
header.Method = zip.Deflate
|
||||
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(writer, fileToZip)
|
||||
return err
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package utils
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
@ -40,23 +41,44 @@ func SendOK(w http.ResponseWriter) {
|
||||
|
||||
// Get GET parameter
|
||||
func GetPara(r *http.Request, key string) (string, error) {
|
||||
keys, ok := r.URL.Query()[key]
|
||||
if !ok || len(keys[0]) < 1 {
|
||||
// Get first value from the URL query
|
||||
value := r.URL.Query().Get(key)
|
||||
if len(value) == 0 {
|
||||
return "", errors.New("invalid " + key + " given")
|
||||
} else {
|
||||
return keys[0], nil
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Get POST paramter
|
||||
func PostPara(r *http.Request, key string) (string, error) {
|
||||
r.ParseForm()
|
||||
x := r.Form.Get(key)
|
||||
if x == "" {
|
||||
return "", errors.New("invalid " + key + " given")
|
||||
} else {
|
||||
return x, nil
|
||||
// Get GET paramter as boolean, accept 1 or true
|
||||
func GetBool(r *http.Request, key string) (bool, error) {
|
||||
x, err := GetPara(r, key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Convert to lowercase and trim spaces just once to compare
|
||||
switch strings.ToLower(strings.TrimSpace(x)) {
|
||||
case "1", "true", "on":
|
||||
return true, nil
|
||||
case "0", "false", "off":
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, errors.New("invalid boolean given")
|
||||
}
|
||||
|
||||
// Get POST parameter
|
||||
func PostPara(r *http.Request, key string) (string, error) {
|
||||
// Try to parse the form
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Get first value from the form
|
||||
x := r.Form.Get(key)
|
||||
if len(x) == 0 {
|
||||
return "", errors.New("invalid " + key + " given")
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// Get POST paramter as boolean, accept 1 or true
|
||||
@ -66,11 +88,11 @@ func PostBool(r *http.Request, key string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
x = strings.TrimSpace(x)
|
||||
|
||||
if x == "1" || strings.ToLower(x) == "true" {
|
||||
// Convert to lowercase and trim spaces just once to compare
|
||||
switch strings.ToLower(strings.TrimSpace(x)) {
|
||||
case "1", "true", "on":
|
||||
return true, nil
|
||||
} else if x == "0" || strings.ToLower(x) == "false" {
|
||||
case "0", "false", "off":
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -95,14 +117,19 @@ func PostInt(r *http.Request, key string) (int, error) {
|
||||
|
||||
func FileExists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
if err == nil {
|
||||
// File exists
|
||||
return true
|
||||
} else if errors.Is(err, os.ErrNotExist) {
|
||||
// File does not exist
|
||||
return false
|
||||
}
|
||||
return true
|
||||
// Some other error
|
||||
return false
|
||||
}
|
||||
|
||||
func IsDir(path string) bool {
|
||||
if FileExists(path) == false {
|
||||
if !FileExists(path) {
|
||||
return false
|
||||
}
|
||||
fi, err := os.Stat(path)
|
||||
@ -141,3 +168,35 @@ func StringInArrayIgnoreCase(arr []string, str string) bool {
|
||||
|
||||
return StringInArray(smallArray, strings.ToLower(str))
|
||||
}
|
||||
|
||||
// Validate if the listening address is correct
|
||||
func ValidateListeningAddress(address string) bool {
|
||||
// Check if the address starts with a colon, indicating it's just a port
|
||||
if strings.HasPrefix(address, ":") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Split the address into host and port parts
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
// Try to parse it as just a port
|
||||
if _, err := strconv.Atoi(address); err == nil {
|
||||
return false // It's just a port number
|
||||
}
|
||||
return false // It's an invalid address
|
||||
}
|
||||
|
||||
// Check if the port part is a valid number
|
||||
if _, err := strconv.Atoi(port); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the host part is a valid IP address or empty (indicating any IP)
|
||||
if host != "" {
|
||||
if net.ParseIP(host) == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@ -42,6 +42,13 @@ func (fm *FileManager) HandleList(w http.ResponseWriter, r *http.Request) {
|
||||
// Construct the absolute path to the target directory
|
||||
targetDir := filepath.Join(fm.Directory, directory)
|
||||
|
||||
// Clean path to prevent path escape #274
|
||||
isValidRequest := validatePathEscape(targetDir, fm.Directory)
|
||||
if !isValidRequest {
|
||||
http.Error(w, "403 - Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Open the target directory
|
||||
dirEntries, err := os.ReadDir(targetDir)
|
||||
if err != nil {
|
||||
@ -118,6 +125,14 @@ func (fm *FileManager) HandleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Specify the directory where you want to save the uploaded file
|
||||
uploadDir := filepath.Join(fm.Directory, dir)
|
||||
|
||||
// Clean path to prevent path escape #274
|
||||
isValidRequest := validatePathEscape(uploadDir, fm.Directory)
|
||||
if !isValidRequest {
|
||||
http.Error(w, "403 - Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.FileExists(uploadDir) {
|
||||
utils.SendErrorResponse(w, "upload target directory not exists")
|
||||
return
|
||||
@ -157,14 +172,20 @@ func (fm *FileManager) HandleDownload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
filePath := filepath.Join(fm.Directory, filename)
|
||||
// Clean path to prevent path escape #274
|
||||
isValidRequest := validatePathEscape(filePath, fm.Directory)
|
||||
if !isValidRequest {
|
||||
http.Error(w, "403 - Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
previewMode, _ := utils.GetPara(r, "preview")
|
||||
if previewMode == "true" {
|
||||
// Serve the file using http.ServeFile
|
||||
filePath := filepath.Join(fm.Directory, filename)
|
||||
http.ServeFile(w, r, filePath)
|
||||
} else {
|
||||
// Trigger a download with content disposition headers
|
||||
filePath := filepath.Join(fm.Directory, filename)
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(filename))
|
||||
http.ServeFile(w, r, filePath)
|
||||
}
|
||||
@ -173,7 +194,7 @@ func (fm *FileManager) HandleDownload(w http.ResponseWriter, r *http.Request) {
|
||||
// HandleNewFolder creates a new folder in the specified directory
|
||||
func (fm *FileManager) HandleNewFolder(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the directory name from the request
|
||||
dirName, err := utils.GetPara(r, "path")
|
||||
dirName, err := utils.PostPara(r, "path")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid directory name")
|
||||
return
|
||||
@ -185,6 +206,11 @@ func (fm *FileManager) HandleNewFolder(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Specify the directory where you want to create the new folder
|
||||
newFolderPath := filepath.Join(fm.Directory, dirName)
|
||||
isValidRequest := validatePathEscape(newFolderPath, fm.Directory)
|
||||
if !isValidRequest {
|
||||
http.Error(w, "403 - Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the folder already exists
|
||||
if _, err := os.Stat(newFolderPath); os.IsNotExist(err) {
|
||||
@ -226,6 +252,18 @@ func (fm *FileManager) HandleFileCopy(w http.ResponseWriter, r *http.Request) {
|
||||
absSrcPath := filepath.Join(fm.Directory, srcPath)
|
||||
absDestPath := filepath.Join(fm.Directory, destPath)
|
||||
|
||||
//Make sure the copy source and dest are within web directory folder
|
||||
isValidRequest := validatePathEscape(absSrcPath, fm.Directory)
|
||||
if !isValidRequest {
|
||||
http.Error(w, "403 - Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
isValidRequest = validatePathEscape(absDestPath, fm.Directory)
|
||||
if !isValidRequest {
|
||||
http.Error(w, "403 - Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the source path exists
|
||||
if _, err := os.Stat(absSrcPath); os.IsNotExist(err) {
|
||||
utils.SendErrorResponse(w, "source path does not exist")
|
||||
@ -268,13 +306,13 @@ func (fm *FileManager) HandleFileCopy(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (fm *FileManager) HandleFileMove(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the source and destination paths from the request
|
||||
srcPath, err := utils.GetPara(r, "srcpath")
|
||||
srcPath, err := utils.PostPara(r, "srcpath")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid source path")
|
||||
return
|
||||
}
|
||||
|
||||
destPath, err := utils.GetPara(r, "destpath")
|
||||
destPath, err := utils.PostPara(r, "destpath")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid destination path")
|
||||
return
|
||||
@ -288,6 +326,18 @@ func (fm *FileManager) HandleFileMove(w http.ResponseWriter, r *http.Request) {
|
||||
absSrcPath := filepath.Join(fm.Directory, srcPath)
|
||||
absDestPath := filepath.Join(fm.Directory, destPath)
|
||||
|
||||
//Make sure move source and target are within web server directory
|
||||
isValidRequest := validatePathEscape(absSrcPath, fm.Directory)
|
||||
if !isValidRequest {
|
||||
http.Error(w, "403 - Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
isValidRequest = validatePathEscape(absDestPath, fm.Directory)
|
||||
if !isValidRequest {
|
||||
http.Error(w, "403 - Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the source path exists
|
||||
if _, err := os.Stat(absSrcPath); os.IsNotExist(err) {
|
||||
utils.SendErrorResponse(w, "source path does not exist")
|
||||
@ -319,6 +369,11 @@ func (fm *FileManager) HandleFileProperties(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
// Construct the absolute path to the target file or directory
|
||||
absPath := filepath.Join(fm.Directory, filePath)
|
||||
isValidRequest := validatePathEscape(absPath, fm.Directory)
|
||||
if !isValidRequest {
|
||||
http.Error(w, "403 - Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the target path exists
|
||||
_, err = os.Stat(absPath)
|
||||
@ -386,6 +441,11 @@ func (fm *FileManager) HandleFileDelete(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// Construct the absolute path to the target file or directory
|
||||
absPath := filepath.Join(fm.Directory, filePath)
|
||||
isValidRequest := validatePathEscape(absPath, fm.Directory)
|
||||
if !isValidRequest {
|
||||
http.Error(w, "403 - Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the target path exists
|
||||
_, err = os.Stat(absPath)
|
||||
@ -404,3 +464,25 @@ func (fm *FileManager) HandleFileDelete(w http.ResponseWriter, r *http.Request)
|
||||
// Respond with a success message or appropriate response
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
// Return true if the path is within the root path
|
||||
func validatePathEscape(reqestPath string, rootPath string) bool {
|
||||
reqestPath = filepath.ToSlash(filepath.Clean(reqestPath))
|
||||
rootPath = filepath.ToSlash(filepath.Clean(rootPath))
|
||||
|
||||
requestPathAbs, err := filepath.Abs(reqestPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
rootPathAbs, err := filepath.Abs(rootPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasPrefix(requestPathAbs, rootPathAbs) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user