mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-07-01 11:51:44 +02:00
Compare commits
129 Commits
Author | SHA1 | Date | |
---|---|---|---|
d1e5581eea | |||
be5797c8a5 | |||
ebd316a7f1 | |||
84aec4387a | |||
30dfb9cb65 | |||
0b1768ab5b | |||
ad4721820b | |||
1d4c275db3 | |||
b3ad97743c | |||
1a6a87e79b | |||
749fd4b7af | |||
85422c0a74 | |||
73999c1ae9 | |||
0ad84b3415 | |||
64b6769695 | |||
e72b2f9e09 | |||
992dd231f2 | |||
49555c1191 | |||
2fca458bd0 | |||
2423d0fb3a | |||
bb0f55018c | |||
9e95d84627 | |||
e73841786b | |||
d5449c947a | |||
8ff51044bb | |||
cc08c704de | |||
2f1a6b5ba4 | |||
4d163fe80f | |||
24371ed22e | |||
12358d3522 | |||
c39af1ff8e | |||
6bf944e13c | |||
b653b805b8 | |||
eb91865b70 | |||
57e72a8a90 | |||
4dbf110edc | |||
1eefa99b72 | |||
e6b2d458f7 | |||
4a4483e09d | |||
4485d1f811 | |||
0eb0696670 | |||
9fca2354c6 | |||
e56b045689 | |||
763ccb4d60 | |||
4d4492069d | |||
f3591aa171 | |||
2dcf578cbe | |||
23a5c6ceb0 | |||
015889851a | |||
093ed9c212 | |||
0af8c67346 | |||
c5170bcb94 | |||
cd48388c02 | |||
373845f8fd | |||
293a527ffc | |||
e4facbc7b6 | |||
1c79fa4e96 | |||
6515eb99e3 | |||
ec5c24b9b8 | |||
df88084375 | |||
74017baecf | |||
294d504ee6 | |||
477429900e | |||
2e9bc77a5d | |||
ed178d857a | |||
4cf5d29692 | |||
634e9c9855 | |||
e79a70b7ac | |||
779115d06b | |||
9cb315ea67 | |||
43ba00ec8d | |||
4577fb1f2f | |||
f877bf9eda | |||
363b9b6d94 | |||
c5ca68868b | |||
f927bb539a | |||
5f64b622b5 | |||
9a371f5bcb | |||
172c5afa60 | |||
f98e04a9fc | |||
99295cad86 | |||
95d0a98576 | |||
00bfa262cb | |||
528be69fe0 | |||
6923f0d200 | |||
7255b62e31 | |||
cf14d12c31 | |||
90cf26306a | |||
cab2f4e63a | |||
75d773887c | |||
a944c3ff36 | |||
465f332dfc | |||
dfda3fe94b | |||
5c56da1180 | |||
3392013a5c | |||
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 | |||
8a8ec1cb0b | |||
e53c3cf3c4 | |||
d17de5c200 | |||
97ff48ee70 | |||
d64b1174af | |||
bec363abab | |||
0dddd1f9e3 | |||
6bfcb2e1f5 |
43
.github/workflows/docker.yml
vendored
Normal file
43
.github/workflows/docker.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
name: Build and push Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [ published ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup-build-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Setup building file structure
|
||||||
|
run: |
|
||||||
|
cp -lr $GITHUB_WORKSPACE/src/ $GITHUB_WORKSPACE/docker/
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./docker
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags: |
|
||||||
|
zoraxydocker/zoraxy:latest
|
||||||
|
zoraxydocker/zoraxy:${{ github.event.release.tag_name }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
40
.github/workflows/main.yml
vendored
40
.github/workflows/main.yml
vendored
@ -1,40 +0,0 @@
|
|||||||
name: Image Publisher
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [ published ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
setup-build-push:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.release.tag_name }}
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to Docker & GHCR
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
|
||||||
|
|
||||||
- name: Setup building file structure
|
|
||||||
run: |
|
|
||||||
cp -r $GITHUB_WORKSPACE/src/ $GITHUB_WORKSPACE/docker/
|
|
||||||
|
|
||||||
- name: Build the image
|
|
||||||
run: |
|
|
||||||
cd $GITHUB_WORKSPACE/docker/
|
|
||||||
docker buildx create --name mainbuilder --driver docker-container --platform linux/amd64,linux/arm64 --use
|
|
||||||
|
|
||||||
docker buildx build --push \
|
|
||||||
--provenance=false \
|
|
||||||
--platform linux/amd64,linux/arm64 \
|
|
||||||
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \
|
|
||||||
--tag zoraxydocker/zoraxy:latest \
|
|
||||||
.
|
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -30,7 +30,7 @@ src/certs/*
|
|||||||
src/rules/*
|
src/rules/*
|
||||||
src/README.md
|
src/README.md
|
||||||
docker/ContainerTester.sh
|
docker/ContainerTester.sh
|
||||||
docker/ImagePublisher.sh
|
docker/docker-compose.yaml
|
||||||
src/mod/acme/test/stackoverflow.pem
|
src/mod/acme/test/stackoverflow.pem
|
||||||
/tools/dns_challenge_update/code-gen/acmedns
|
/tools/dns_challenge_update/code-gen/acmedns
|
||||||
/tools/dns_challenge_update/code-gen/lego
|
/tools/dns_challenge_update/code-gen/lego
|
||||||
|
75
CHANGELOG.md
75
CHANGELOG.md
@ -1,3 +1,78 @@
|
|||||||
|
# v3.1.5 28 Dec 2024
|
||||||
|
|
||||||
|
+ Fixed hostname case sensitive bug [#435](https://github.com/tobychui/zoraxy/issues/435)
|
||||||
|
+ Fixed ACME table too wide css bug [#422](https://github.com/tobychui/zoraxy/issues/422)
|
||||||
|
+ Fixed HSTS toggle button bug [#415](https://github.com/tobychui/zoraxy/issues/415)
|
||||||
|
+ Fixed slow GeoIP resolve mode concurrent r/w bug [#401](https://github.com/tobychui/zoraxy/issues/401)
|
||||||
|
+ Added close connection as default site option [#430](https://github.com/tobychui/zoraxy/issues/430)
|
||||||
|
+ Added experimental authelia support [#384](https://github.com/tobychui/zoraxy/issues/384)
|
||||||
|
+ Added custom header support to websocket [#426](https://github.com/tobychui/zoraxy/issues/426)
|
||||||
|
+ Added levelDB as database implementation (not currently used)
|
||||||
|
+ Added external GeoIP db loading support
|
||||||
|
+ Restructured a lot of modules
|
||||||
|
|
||||||
|
# v3.1.4 24 Nov 2024
|
||||||
|
|
||||||
|
+ **Added Dark Theme Mode** [#390](https://github.com/tobychui/zoraxy/issues/390) [#82](https://github.com/tobychui/zoraxy/issues/82)
|
||||||
|
+ Added an auto sniffer for self-signed certificates
|
||||||
|
+ Added robots.txt to the project
|
||||||
|
+ Introduced an EU wrapper in the front-end for automatic registration of 26 countries [#378](https://github.com/tobychui/zoraxy/issues/378)
|
||||||
|
+ Moved all hard-coded values to a dedicated def.go file
|
||||||
|
+ Fixed a panic issue occurring on unsupported platform exits
|
||||||
|
+ Integrated fixes for SSH proxy and Docker snippet updates [#330](https://github.com/tobychui/zoraxy/issues/330) [#348](https://github.com/tobychui/zoraxy/issues/348)
|
||||||
|
+ **Changed the default listening port to 443 and enable TLS by default**
|
||||||
|
+ Optimized GeoIP database slow-search mode CPU usage
|
||||||
|
|
||||||
|
|
||||||
|
# v3.1.3 12 Nov 2024
|
||||||
|
|
||||||
|
+ Fixed a critical security bug [CVE-2024-52010](https://github.com/advisories/GHSA-7hpf-g48v-hw3j)
|
||||||
|
|
||||||
|
# v3.1.2 03 Nov 2024
|
||||||
|
|
||||||
|
+ Added auto start port 80 listener on acme certificate generator
|
||||||
|
+ Added polling interval and propagation timeout option in ACME module [#300](https://github.com/tobychui/zoraxy/issues/300)
|
||||||
|
+ Added support for custom header variables [#318](https://github.com/tobychui/zoraxy/issues/318)
|
||||||
|
+ Added support for X-Remote-User
|
||||||
|
+ Added port scanner [#342](https://github.com/tobychui/zoraxy/issues/342)
|
||||||
|
+ Optimized code base for stream proxy and config file storage [#320](https://github.com/tobychui/zoraxy/issues/320)
|
||||||
|
+ Removed sorting on cert list
|
||||||
|
+ Fixed request certificate button bug
|
||||||
|
+ Fixed cert auto renew logic [#316](https://github.com/tobychui/zoraxy/issues/316)
|
||||||
|
+ Fixed unable to remove new stream proxy bug
|
||||||
|
+ Fixed many other minor bugs [#328](https://github.com/tobychui/zoraxy/issues/328) [#297](https://github.com/tobychui/zoraxy/issues/297)
|
||||||
|
+ Added more code to SSO system (disabled in release)
|
||||||
|
|
||||||
|
|
||||||
|
# v3.1.1. 09 Sep 2024
|
||||||
|
|
||||||
|
+ Updated country name in access list [#287](https://github.com/tobychui/zoraxy/issues/287)
|
||||||
|
+ Added tour for basic operations
|
||||||
|
+ Updated acme log to system wide logger implementation
|
||||||
|
+ Fixed path traversal in file manager [#274](https://github.com/tobychui/zoraxy/issues/274)
|
||||||
|
+ Removed Proxmox debug code
|
||||||
|
+ Fixed trie tree implementations
|
||||||
|
|
||||||
|
**Thanks to all contributors**
|
||||||
|
|
||||||
|
+ Fix existing containers list in docker popup [7brend7](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3A7brend7)
|
||||||
|
+ Fix network I/O chart not rendering [JokerQyou](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3AJokerQyou)
|
||||||
|
+ Fix typo remvoeClass to removeClass [Aahmadsyamim](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3Aahmadsyamim)
|
||||||
|
+ Updated weighted random upstream implementation [bouroo](https://github.com/tobychui/zoraxy/issues?q=is%3Apr+author%3Abouroo)
|
||||||
|
|
||||||
|
# v3.1.0 31 Jul 2024
|
||||||
|
|
||||||
|
+ Updated log viewer with filter and auto refresh [#243](https://github.com/tobychui/zoraxy/issues/243)
|
||||||
|
+ Fixed csrf vulnerability [#267](https://github.com/tobychui/zoraxy/issues/267)
|
||||||
|
+ Fixed promox issue
|
||||||
|
+ Fixed status code bug in upstream log [#254](https://github.com/tobychui/zoraxy/issues/254)
|
||||||
|
+ Added host overwrite and hop-by-hop header remover
|
||||||
|
+ Added early renew days settings [#256](https://github.com/tobychui/zoraxy/issues/256)
|
||||||
|
+ Updated make file to force no CGO in cicd process
|
||||||
|
+ Fixed bug in updater
|
||||||
|
+ Fixed wildcard certificate renew bug [#249](https://github.com/tobychui/zoraxy/issues/249)
|
||||||
|
+ Added certificate download function [#227](https://github.com/tobychui/zoraxy/issues/227)
|
||||||
|
|
||||||
# v3.0.9 16 Jul 2024
|
# v3.0.9 16 Jul 2024
|
||||||
|
|
||||||
+ Added certificate download [#227](https://github.com/tobychui/zoraxy/issues/227)
|
+ Added certificate download [#227](https://github.com/tobychui/zoraxy/issues/227)
|
||||||
|
26
README.md
26
README.md
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
|
A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- Simple to use interface with detail in-system instructions
|
- Simple to use interface with detail in-system instructions
|
||||||
@ -34,24 +33,26 @@ A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
|
|||||||
- Basic single-admin management mode
|
- Basic single-admin management mode
|
||||||
- External permission management system for easy system integration
|
- External permission management system for easy system integration
|
||||||
- SMTP config for password reset
|
- SMTP config for password reset
|
||||||
|
- Dark Theme Mode
|
||||||
|
|
||||||
## Downloads
|
## Downloads
|
||||||
|
|
||||||
[Windows](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe)
|
[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 (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 (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
|
## 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/)
|
[Installing Zoraxy Reverse Proxy: Your Gateway to Efficient Web Routing](https://geekscircuit.com/installing-zoraxy-reverse-proxy-your-gateway-to-efficient-web-routing/)
|
||||||
|
|
||||||
Thank you for the well written and easy to follow tutorial by Reddit users [itsvmn](https://www.reddit.com/user/itsvmn/)!
|
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.
|
If you have no background in setting up reverse proxy or web routing, you should check this out before you start setting up your Zoraxy.
|
||||||
|
|
||||||
## Build from Source
|
## Build from Source
|
||||||
|
|
||||||
Requires Go 1.22 or higher
|
Requires Go 1.23 or higher
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/tobychui/zoraxy
|
git clone https://github.com/tobychui/zoraxy
|
||||||
@ -64,7 +65,7 @@ sudo ./zoraxy -port=:8000
|
|||||||
|
|
||||||
## Usage
|
## 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
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ 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.
|
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details.
|
||||||
|
|
||||||
### Start Paramters
|
### Start Parameters
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage of zoraxy:
|
Usage of zoraxy:
|
||||||
@ -102,6 +103,8 @@ Usage of zoraxy:
|
|||||||
Enable auto config upgrade if breaking change is detected (default true)
|
Enable auto config upgrade if breaking change is detected (default true)
|
||||||
-docker
|
-docker
|
||||||
Run Zoraxy in docker compatibility mode
|
Run Zoraxy in docker compatibility mode
|
||||||
|
-earlyrenew int
|
||||||
|
Number of days to early renew a soon expiring certificate (days) (default 30)
|
||||||
-fastgeoip
|
-fastgeoip
|
||||||
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
|
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
|
||||||
-mdns
|
-mdns
|
||||||
@ -134,7 +137,8 @@ If you already have an upstream reverse proxy server in place with permission ma
|
|||||||
./zoraxy -noauth=true
|
./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.*
|
> [!WARNING]
|
||||||
|
> 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
|
## Screenshots
|
||||||
|
|
||||||
@ -154,7 +158,7 @@ This project also compatible with [ZeroTier](https://www.zerotier.com/). However
|
|||||||
|
|
||||||
To use Zoraxy with ZeroTier, assuming you already have a valid license, install ZeroTier on your host and then run Zoraxy in sudo mode (or Run As Administrator if you are on Windows). The program will automatically grab the authtoken in the correct location on your host.
|
To use Zoraxy with ZeroTier, assuming you already have a valid license, install ZeroTier on your host and then run Zoraxy in sudo mode (or Run As Administrator if you are on Windows). The program will automatically grab the authtoken in the correct location on your host.
|
||||||
|
|
||||||
If you prefer not to run Zoraxy in sudo mode or you have some weird installation profile, you can also pass in the ZeroTier auth token using the following flags::
|
If you prefer not to run Zoraxy in sudo mode or you have some weird installation profile, you can also pass in the ZeroTier auth token using the following flags:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./zoraxy -ztauth="your_zerotier_authtoken" -ztport=9993
|
./zoraxy -ztauth="your_zerotier_authtoken" -ztport=9993
|
||||||
@ -175,7 +179,7 @@ Web SSH currently only supports Linux based OSes. The following platforms are su
|
|||||||
|
|
||||||
### Loopback Connection
|
### Loopback Connection
|
||||||
|
|
||||||
Loopback web SSH connection, by default, is disabled. This means that if you are trying to connect to an address like 127.0.0.1 or localhost, the system will reject your connection for security reasons. To enable loopback for testing or development purpose, use the following flags to override the loopback checking:
|
Loopback web SSH connections, by default, are disabled. This means that if you are trying to connect to an address like 127.0.0.1 or localhost, the system will reject your connection for security reasons. To enable loopback for testing or development purpose, use the following flags to override the loopback checking:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./zoraxy -sshlb=true
|
./zoraxy -sshlb=true
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
FROM docker.io/golang:alpine AS build
|
FROM docker.io/golang:alpine AS build-zoraxy
|
||||||
|
|
||||||
RUN mkdir -p /opt/zoraxy/source/ &&\
|
RUN mkdir -p /opt/zoraxy/source/ &&\
|
||||||
mkdir -p /opt/zoraxy/config/ &&\
|
|
||||||
mkdir -p /usr/local/bin/
|
mkdir -p /usr/local/bin/
|
||||||
|
|
||||||
RUN chmod -R 770 /opt/zoraxy/
|
|
||||||
|
|
||||||
# If you build it yourself, you will need to add the src directory into the docker directory.
|
# If you build it yourself, you will need to add the src directory into the docker directory.
|
||||||
COPY ./src/ /opt/zoraxy/source/
|
COPY ./src/ /opt/zoraxy/source/
|
||||||
|
|
||||||
@ -13,23 +10,42 @@ WORKDIR /opt/zoraxy/source/
|
|||||||
|
|
||||||
RUN go mod tidy &&\
|
RUN go mod tidy &&\
|
||||||
go build -o /usr/local/bin/zoraxy &&\
|
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 &&\
|
FROM docker.io/ubuntu:latest AS build-zerotier
|
||||||
chmod +x /usr/local/bin/zoraxy
|
|
||||||
|
|
||||||
FROM docker.io/alpine:latest
|
RUN mkdir -p /opt/zerotier/source/ &&\
|
||||||
|
mkdir -p /usr/local/bin/
|
||||||
|
|
||||||
RUN apk add --no-cache bash netcat-openbsd sudo
|
WORKDIR /opt/zerotier/source/
|
||||||
|
|
||||||
COPY --from=build /usr/local/bin/zoraxy /usr/local/bin/zoraxy
|
RUN apt-get update -y &&\
|
||||||
COPY --from=build /opt/zoraxy/config/ /opt/zoraxy/config
|
apt-get install -y curl jq build-essential pkg-config clang cargo libssl-dev
|
||||||
|
|
||||||
VOLUME [ "/opt/zoraxy/config/" ]
|
RUN curl -Lo ZeroTierOne.tar.gz https://codeload.github.com/zerotier/ZeroTierOne/tar.gz/refs/tags/1.10.6 &&\
|
||||||
|
tar -xzvf ZeroTierOne.tar.gz &&\
|
||||||
|
cd ZeroTierOne-* &&\
|
||||||
|
make &&\
|
||||||
|
mv ./zerotier-one /usr/local/bin/zerotier-one &&\
|
||||||
|
chmod 755 /usr/local/bin/zerotier-one
|
||||||
|
|
||||||
|
FROM docker.io/ubuntu:latest
|
||||||
|
|
||||||
|
RUN apt-get update -y &&\
|
||||||
|
apt-get install -y bash sudo netcat-openbsd libssl-dev ca-certificates
|
||||||
|
|
||||||
|
COPY --chmod=700 ./entrypoint.sh /opt/zoraxy/
|
||||||
|
COPY --from=build-zoraxy /usr/local/bin/zoraxy /usr/local/bin/zoraxy
|
||||||
|
COPY --from=build-zerotier /usr/local/bin/zerotier-one /usr/local/bin/zerotier-one
|
||||||
|
|
||||||
WORKDIR /opt/zoraxy/config/
|
WORKDIR /opt/zoraxy/config/
|
||||||
|
|
||||||
|
ENV ZEROTIER="false"
|
||||||
|
|
||||||
ENV AUTORENEW="86400"
|
ENV AUTORENEW="86400"
|
||||||
|
ENV CFGUPGRADE="true"
|
||||||
|
ENV DB="auto"
|
||||||
|
ENV DOCKER="true"
|
||||||
ENV EARLYRENEW="30"
|
ENV EARLYRENEW="30"
|
||||||
ENV FASTGEOIP="false"
|
ENV FASTGEOIP="false"
|
||||||
ENV MDNS="true"
|
ENV MDNS="true"
|
||||||
@ -37,12 +53,16 @@ ENV MDNSNAME="''"
|
|||||||
ENV NOAUTH="false"
|
ENV NOAUTH="false"
|
||||||
ENV PORT="8000"
|
ENV PORT="8000"
|
||||||
ENV SSHLB="false"
|
ENV SSHLB="false"
|
||||||
|
ENV UPDATE_GEOIP="false"
|
||||||
ENV VERSION="false"
|
ENV VERSION="false"
|
||||||
ENV WEBFM="true"
|
ENV WEBFM="true"
|
||||||
ENV WEBROOT="./www"
|
ENV WEBROOT="./www"
|
||||||
ENV ZTAUTH="''"
|
ENV ZTAUTH=""
|
||||||
ENV ZTPORT="9993"
|
ENV ZTPORT="9993"
|
||||||
|
|
||||||
ENTRYPOINT "zoraxy" "-docker=true" "-autorenew=${AUTORENEW}" "-earlyrenew=${EARLYRENEW}" "-fastgeoip=${FASTGEOIP}" "-mdns=${MDNS}" "-mdnsname=${MDNSNAME}" "-noauth=${NOAUTH}" "-port=:${PORT}" "-sshlb=${SSHLB}" "-version=${VERSION}" "-webfm=${WEBFM}" "-webroot=${WEBROOT}" "-ztauth=${ZTAUTH}" "-ztport=${ZTPORT}"
|
VOLUME [ "/opt/zoraxy/config/" ]
|
||||||
|
|
||||||
|
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
|
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 CMD nc -vz 127.0.0.1 $PORT || exit 1
|
||||||
|
|
||||||
|
127
docker/README.md
127
docker/README.md
@ -1,73 +1,104 @@
|
|||||||
# [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)
|
||||||
[](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>
|
## Usage
|
||||||
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>
|
|
||||||
|
|
||||||
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/).
|
||||||
|
|
||||||
The examples below are not exactly how it should be set up, rather they give a general idea of usage.
|
In the examples below, make sure to update `/path/to/zoraxy/config/`. 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 or a named Docker volume.
|
||||||
|
|
||||||
|
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 80:80 -p 443:443 -p (management external):(management internal) -v (path to storage directory):/opt/zoraxy/data/ -e (flag)="(value)" zoraxydocker/zoraxy:latest
|
docker run -d \
|
||||||
|
--name zoraxy \
|
||||||
|
--restart unless-stopped \
|
||||||
|
-p 80:80 \
|
||||||
|
-p 443:443 \
|
||||||
|
-p 8000:8000 \
|
||||||
|
-v /path/to/zoraxy/config/:/opt/zoraxy/config/ \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-v /etc/localtime:/etc/localtime \
|
||||||
|
-e FASTGEOIP="true" \
|
||||||
|
zoraxydocker/zoraxy:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Docker Compose </br>
|
### Docker Compose
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
services:
|
services:
|
||||||
zoraxy-docker:
|
zoraxy:
|
||||||
image: zoraxydocker/zoraxy:latest
|
|
||||||
container_name: (container name)
|
|
||||||
ports:
|
|
||||||
- 80:80
|
|
||||||
- 443:443
|
|
||||||
- (management external):(management internal)
|
|
||||||
volumes:
|
|
||||||
- (path to storage directory):/opt/zoraxy/config/
|
|
||||||
environment:
|
|
||||||
(flag): "(value)"
|
|
||||||
```
|
|
||||||
|
|
||||||
| 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. |
|
|
||||||
| `-v /var/run/docker.sock:/var/run/docker.sock` | No | Used for autodiscovery. |
|
|
||||||
| `-v /etc/localtime:/etc/localtime` | No | Uses the hosts time in the container. |
|
|
||||||
| `-e (flag)="(value)"` | No | Arguments to run Zoraxy with. They are simply just capitalized Zoraxy flags. `-docker=true` is always set by default. See examples below. |
|
|
||||||
| `zoraxydocker/zoraxy:latest` | Yes | The repository on Docker hub. By default, it is the latest version that is published. |
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> Docker usage of the port flag should not include the colon. Ex: `-e PORT="8000"` for Docker run and `PORT: "8000"` for Docker compose.
|
|
||||||
|
|
||||||
## Examples: </br>
|
|
||||||
### Docker Run </br>
|
|
||||||
```
|
|
||||||
docker run -d --name zoraxy -p 80:80 -p 443:443 -p 8005:8005 -v /home/docker/Containers/Zoraxy:/opt/zoraxy/config/ -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime -e PORT="8005" -e FASTGEOIP="true" zoraxydocker/zoraxy:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker Compose </br>
|
|
||||||
```yml
|
|
||||||
services:
|
|
||||||
zoraxy-docker:
|
|
||||||
image: zoraxydocker/zoraxy:latest
|
image: zoraxydocker/zoraxy:latest
|
||||||
container_name: zoraxy
|
container_name: zoraxy
|
||||||
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
- 443:443
|
- 443:443
|
||||||
- 8005:8005
|
- 8000:8000
|
||||||
volumes:
|
volumes:
|
||||||
- /home/docker/Containers/Zoraxy:/opt/zoraxy/config/
|
- /path/to/zoraxy/config/:/opt/zoraxy/config/
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- /etc/localtime:/etc/localtime
|
- /etc/localtime:/etc/localtime
|
||||||
environment:
|
environment:
|
||||||
PORT: "8005"
|
|
||||||
FASTGEOIP: "true"
|
FASTGEOIP: "true"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Ports
|
||||||
|
|
||||||
|
| Port | Details |
|
||||||
|
|:-|:-|
|
||||||
|
| `80` | HTTP traffic. |
|
||||||
|
| `443` | HTTPS traffic. |
|
||||||
|
| `8000` | Management interface. Can be changed with the `PORT` env. |
|
||||||
|
|
||||||
|
### Volumes
|
||||||
|
|
||||||
|
| Volume | Details |
|
||||||
|
|:-|:-|
|
||||||
|
| `/opt/zoraxy/config/` | Zoraxy configuration. |
|
||||||
|
| `/var/run/docker.sock` | Docker socket. Used for additional functionality with Zoraxy. |
|
||||||
|
| `/etc/localtime` | Localtime. Set to ensure the host and container are synchronized. |
|
||||||
|
|
||||||
|
### Environment
|
||||||
|
|
||||||
|
Variables are the same as those in [Start Parameters](https://github.com/tobychui/zoraxy?tab=readme-ov-file#start-paramters).
|
||||||
|
|
||||||
|
| Variable | Default | Details |
|
||||||
|
|:-|:-|:-|
|
||||||
|
| `AUTORENEW` | `86400` (Integer) | ACME auto TLS/SSL certificate renew check interval. |
|
||||||
|
| `CFGUPGRADE` | `true` (Boolean) | Enable auto config upgrade if breaking change is detected. |
|
||||||
|
| `DB` | `auto` (String) | Database backend to use (leveldb, boltdb, auto) Note that fsdb will be used on unsupported platforms like RISCV (default "auto"). |
|
||||||
|
| `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). |
|
||||||
|
| `UPDATE_GEOIP` | `false` (Boolean) | Download the latest GeoIP data and exit. |
|
||||||
|
| `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.
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
To build the Docker image:
|
||||||
|
- Check out the repository/branch.
|
||||||
|
- Copy the Zoraxy `src/` directory into the `docker/` (here) directory.
|
||||||
|
- Run the build command with `docker build -t zoraxy_build .`
|
||||||
|
- You can now use the image `zoraxy_build`
|
||||||
|
- If you wish to change the image name, then modify`zoraxy_build` in the previous step and then build again.
|
||||||
|
|
||||||
|
37
docker/entrypoint.sh
Normal file
37
docker/entrypoint.sh
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
update-ca-certificates
|
||||||
|
echo "CA certificates updated."
|
||||||
|
|
||||||
|
zoraxy -update_geoip=true
|
||||||
|
echo "Updated GeoIP data."
|
||||||
|
|
||||||
|
if [ "$ZEROTIER" = "true" ]; then
|
||||||
|
if [ ! -d "/opt/zoraxy/config/zerotier/" ]; then
|
||||||
|
mkdir -p /opt/zoraxy/config/zerotier/
|
||||||
|
fi
|
||||||
|
ln -s /opt/zoraxy/config/zerotier/ /var/lib/zerotier-one
|
||||||
|
zerotier-one -d
|
||||||
|
echo "ZeroTier daemon started."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting Zoraxy..."
|
||||||
|
exec zoraxy \
|
||||||
|
-autorenew="$AUTORENEW" \
|
||||||
|
-cfgupgrade="$CFGUPGRADE" \
|
||||||
|
-db="$DB" \
|
||||||
|
-docker="$DOCKER" \
|
||||||
|
-earlyrenew="$EARLYRENEW" \
|
||||||
|
-fastgeoip="$FASTGEOIP" \
|
||||||
|
-mdns="$MDNS" \
|
||||||
|
-mdnsname="$MDNSNAME" \
|
||||||
|
-noauth="$NOAUTH" \
|
||||||
|
-port=:"$PORT" \
|
||||||
|
-sshlb="$SSHLB" \
|
||||||
|
-update_geoip="$UPDATE_GEOIP" \
|
||||||
|
-version="$VERSION" \
|
||||||
|
-webfm="$WEBFM" \
|
||||||
|
-webroot="$WEBROOT" \
|
||||||
|
-ztauth="$ZTAUTH" \
|
||||||
|
-ztport="$ZTPORT"
|
||||||
|
|
@ -1 +1 @@
|
|||||||
zoraxy.arozos.com
|
zoraxy.aroz.org
|
@ -12,19 +12,19 @@
|
|||||||
<meta name="description" content="A reverse proxy server and cluster network gateway for noobs">
|
<meta name="description" content="A reverse proxy server and cluster network gateway for noobs">
|
||||||
|
|
||||||
<!-- Facebook Meta Tags -->
|
<!-- Facebook Meta Tags -->
|
||||||
<meta property="og:url" content="https://zoraxy.arozos.com/">
|
<meta property="og:url" content="https://zoraxy.aroz.org/">
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:title" content="Cluster Proxy Gateway | Zoraxy">
|
<meta property="og:title" content="Cluster Proxy Gateway | Zoraxy">
|
||||||
<meta property="og:description" content="A reverse proxy server and cluster network gateway for noobs">
|
<meta property="og:description" content="A reverse proxy server and cluster network gateway for noobs">
|
||||||
<meta property="og:image" content="https://zoraxy.arozos.com/img/og.png">
|
<meta property="og:image" content="https://zoraxy.aroz.org/img/og.png">
|
||||||
|
|
||||||
<!-- Twitter Meta Tags -->
|
<!-- Twitter Meta Tags -->
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
<meta property="twitter:domain" content="arozos.com">
|
<meta property="twitter:domain" content="aroz.org">
|
||||||
<meta property="twitter:url" content="https://zoraxy.arozos.com/">
|
<meta property="twitter:url" content="https://zoraxy.aroz.org/">
|
||||||
<meta name="twitter:title" content="Cluster Proxy Gateway | Zoraxy">
|
<meta name="twitter:title" content="Cluster Proxy Gateway | Zoraxy">
|
||||||
<meta name="twitter:description" content="A reverse proxy server and cluster network gateway for noobs">
|
<meta name="twitter:description" content="A reverse proxy server and cluster network gateway for noobs">
|
||||||
<meta name="twitter:image" content="https://zoraxy.arozos.com/img/og.png">
|
<meta name="twitter:image" content="https://zoraxy.aroz.org/img/og.png">
|
||||||
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
<link href="favicon.png" rel="icon">
|
<link href="favicon.png" rel="icon">
|
||||||
|
@ -230,7 +230,17 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rule.AddCountryCodeToBlackList(countryCode, comment)
|
//Check if the country code contains comma, if yes, split it
|
||||||
|
if strings.Contains(countryCode, ",") {
|
||||||
|
codes := strings.Split(countryCode, ",")
|
||||||
|
for _, code := range codes {
|
||||||
|
code = strings.TrimSpace(code)
|
||||||
|
rule.AddCountryCodeToBlackList(code, comment)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
countryCode = strings.TrimSpace(countryCode)
|
||||||
|
rule.AddCountryCodeToBlackList(countryCode, comment)
|
||||||
|
}
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -254,7 +264,17 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rule.RemoveCountryCodeFromBlackList(countryCode)
|
//Check if the country code contains comma, if yes, split it
|
||||||
|
if strings.Contains(countryCode, ",") {
|
||||||
|
codes := strings.Split(countryCode, ",")
|
||||||
|
for _, code := range codes {
|
||||||
|
code = strings.TrimSpace(code)
|
||||||
|
rule.RemoveCountryCodeFromBlackList(code)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
countryCode = strings.TrimSpace(countryCode)
|
||||||
|
rule.RemoveCountryCodeFromBlackList(countryCode)
|
||||||
|
}
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -397,7 +417,17 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
p := bluemonday.StrictPolicy()
|
p := bluemonday.StrictPolicy()
|
||||||
comment = p.Sanitize(comment)
|
comment = p.Sanitize(comment)
|
||||||
|
|
||||||
rule.AddCountryCodeToWhitelist(countryCode, comment)
|
//Check if the country code contains comma, if yes, split it
|
||||||
|
if strings.Contains(countryCode, ",") {
|
||||||
|
codes := strings.Split(countryCode, ",")
|
||||||
|
for _, code := range codes {
|
||||||
|
code = strings.TrimSpace(code)
|
||||||
|
rule.AddCountryCodeToWhitelist(code, comment)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
countryCode = strings.TrimSpace(countryCode)
|
||||||
|
rule.AddCountryCodeToWhitelist(countryCode, comment)
|
||||||
|
}
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
@ -420,7 +450,17 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rule.RemoveCountryCodeFromWhitelist(countryCode)
|
//Check if the country code contains comma, if yes, split it
|
||||||
|
if strings.Contains(countryCode, ",") {
|
||||||
|
codes := strings.Split(countryCode, ",")
|
||||||
|
for _, code := range codes {
|
||||||
|
code = strings.TrimSpace(code)
|
||||||
|
rule.RemoveCountryCodeFromWhitelist(code)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
countryCode = strings.TrimSpace(countryCode)
|
||||||
|
rule.RemoveCountryCodeFromWhitelist(countryCode)
|
||||||
|
}
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
53
src/acme.go
53
src/acme.go
@ -38,7 +38,21 @@ func initACME() *acme.ACMEHandler {
|
|||||||
port = getRandomPort(30000)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart ACME handler and auto renewer
|
||||||
|
func restartACMEHandler() {
|
||||||
|
SystemWideLogger.Println("Restarting ACME handler")
|
||||||
|
//Clos the current handler and auto renewer
|
||||||
|
acmeHandler.Close()
|
||||||
|
acmeAutoRenewer.Close()
|
||||||
|
acmeDeregisterSpecialRoutingRule()
|
||||||
|
|
||||||
|
//Reinit the handler with a new random port
|
||||||
|
acmeHandler = initACME()
|
||||||
|
|
||||||
|
acmeRegisterSpecialRoutingRule()
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the special routing rule for ACME
|
// create the special routing rule for ACME
|
||||||
@ -82,12 +96,29 @@ func acmeRegisterSpecialRoutingRule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove the special routing rule for ACME
|
||||||
|
func acmeDeregisterSpecialRoutingRule() {
|
||||||
|
SystemWideLogger.Println("Removing ACME routing rule")
|
||||||
|
dynamicProxyRouter.RemoveRoutingRule("acme-autorenew")
|
||||||
|
}
|
||||||
|
|
||||||
// This function check if the renew setup is satisfied. If not, toggle them automatically
|
// This function check if the renew setup is satisfied. If not, toggle them automatically
|
||||||
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
|
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
|
||||||
isForceHttpsRedirectEnabledOriginally := false
|
isForceHttpsRedirectEnabledOriginally := false
|
||||||
|
requireRestorePort80 := false
|
||||||
dnsPara, _ := utils.PostBool(r, "dns")
|
dnsPara, _ := utils.PostBool(r, "dns")
|
||||||
if !dnsPara {
|
if !dnsPara {
|
||||||
|
|
||||||
if dynamicProxyRouter.Option.Port == 443 {
|
if dynamicProxyRouter.Option.Port == 443 {
|
||||||
|
//Check if port 80 is enabled
|
||||||
|
if !dynamicProxyRouter.Option.ListenOnPort80 {
|
||||||
|
//Enable port 80 temporarily
|
||||||
|
SystemWideLogger.PrintAndLog("ACME", "Temporarily enabling port 80 listener to handle ACME request ", nil)
|
||||||
|
dynamicProxyRouter.UpdatePort80ListenerState(true)
|
||||||
|
requireRestorePort80 = true
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
//Enable port 80 to 443 redirect
|
//Enable port 80 to 443 redirect
|
||||||
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
|
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
|
||||||
SystemWideLogger.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests")
|
SystemWideLogger.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests")
|
||||||
@ -107,8 +138,8 @@ func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add a 3 second delay to make sure everything is settle down
|
//Add a 2 second delay to make sure everything is settle down
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
// Pass over to the acmeHandler to deal with the communication
|
// Pass over to the acmeHandler to deal with the communication
|
||||||
acmeHandler.HandleRenewCertificate(w, r)
|
acmeHandler.HandleRenewCertificate(w, r)
|
||||||
@ -117,13 +148,17 @@ func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request)
|
|||||||
tlsCertManager.UpdateLoadedCertList()
|
tlsCertManager.UpdateLoadedCertList()
|
||||||
|
|
||||||
//Restore original settings
|
//Restore original settings
|
||||||
if dynamicProxyRouter.Option.Port == 443 && !dnsPara {
|
if requireRestorePort80 {
|
||||||
if !isForceHttpsRedirectEnabledOriginally {
|
//Restore port 80 listener
|
||||||
//Default is off. Turn the redirection off
|
SystemWideLogger.PrintAndLog("ACME", "Restoring previous port 80 listener settings", nil)
|
||||||
SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil)
|
dynamicProxyRouter.UpdatePort80ListenerState(false)
|
||||||
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if !isForceHttpsRedirectEnabledOriginally {
|
||||||
|
//Default is off. Turn the redirection off
|
||||||
|
SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil)
|
||||||
|
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleACMEPreferredCA return the user preferred / default CA for new subdomain auto creation
|
// HandleACMEPreferredCA return the user preferred / default CA for new subdomain auto creation
|
||||||
|
270
src/api.go
270
src/api.go
@ -8,6 +8,8 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/acme/acmedns"
|
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||||
"imuslab.com/zoraxy/mod/acme/acmewizard"
|
"imuslab.com/zoraxy/mod/acme/acmewizard"
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
|
||||||
|
"imuslab.com/zoraxy/mod/ipscan"
|
||||||
"imuslab.com/zoraxy/mod/netstat"
|
"imuslab.com/zoraxy/mod/netstat"
|
||||||
"imuslab.com/zoraxy/mod/netutils"
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
@ -17,34 +19,11 @@ import (
|
|||||||
API.go
|
API.go
|
||||||
|
|
||||||
This file contains all the API called by the web management interface
|
This file contains all the API called by the web management interface
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var requireAuth = true
|
// Register the APIs for HTTP proxy management functions
|
||||||
|
func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
|
||||||
func initAPIs(targetMux *http.ServeMux) {
|
/* Reverse Proxy Settings & Status */
|
||||||
authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
|
|
||||||
AuthAgent: authAgent,
|
|
||||||
RequireAuth: requireAuth,
|
|
||||||
TargetMux: targetMux,
|
|
||||||
DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
//Register the standard web services urls
|
|
||||||
fs := http.FileServer(http.FS(webres))
|
|
||||||
if development {
|
|
||||||
fs = http.FileServer(http.Dir("web/"))
|
|
||||||
}
|
|
||||||
//Add a layer of middleware for advance control
|
|
||||||
advHandler := FSHandler(fs)
|
|
||||||
targetMux.Handle("/", advHandler)
|
|
||||||
|
|
||||||
//Authentication APIs
|
|
||||||
registerAuthAPIs(requireAuth, targetMux)
|
|
||||||
|
|
||||||
//Reverse proxy
|
|
||||||
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
|
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
|
||||||
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
|
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
||||||
@ -55,24 +34,24 @@ func initAPIs(targetMux *http.ServeMux) {
|
|||||||
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
||||||
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
||||||
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
authRouter.HandleFunc("/api/proxy/tlscheck", domainsniff.HandleCheckSiteSupportTLS)
|
||||||
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
|
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
|
||||||
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
|
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
|
||||||
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
||||||
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
||||||
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
||||||
//Reverse proxy upstream (load balance) APIs
|
/* Reverse proxy upstream (load balance) */
|
||||||
authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList)
|
authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList)
|
||||||
authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd)
|
authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd)
|
||||||
authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority)
|
authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority)
|
||||||
authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate)
|
authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate)
|
||||||
authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete)
|
authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete)
|
||||||
//Reverse proxy virtual directory APIs
|
/* Reverse proxy virtual directory */
|
||||||
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
|
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
|
||||||
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
|
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
|
||||||
authRouter.HandleFunc("/api/proxy/vdir/del", ReverseProxyDeleteVdir)
|
authRouter.HandleFunc("/api/proxy/vdir/del", ReverseProxyDeleteVdir)
|
||||||
authRouter.HandleFunc("/api/proxy/vdir/edit", ReverseProxyEditVdir)
|
authRouter.HandleFunc("/api/proxy/vdir/edit", ReverseProxyEditVdir)
|
||||||
//Reverse proxy user define header apis
|
/* Reverse proxy user-defined header */
|
||||||
authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList)
|
authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList)
|
||||||
authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd)
|
authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd)
|
||||||
authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove)
|
authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove)
|
||||||
@ -80,12 +59,15 @@ func initAPIs(targetMux *http.ServeMux) {
|
|||||||
authRouter.HandleFunc("/api/proxy/header/handleHopByHop", HandleHopByHop)
|
authRouter.HandleFunc("/api/proxy/header/handleHopByHop", HandleHopByHop)
|
||||||
authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite)
|
authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite)
|
||||||
authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy)
|
authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy)
|
||||||
//Reverse proxy auth related APIs
|
authRouter.HandleFunc("/api/proxy/header/handleWsHeaderBehavior", HandleWsHeaderBehavior)
|
||||||
|
/* Reverse proxy auth related */
|
||||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
|
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
|
||||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
|
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
|
||||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/delete", RemoveProxyBasicAuthExceptionPaths)
|
authRouter.HandleFunc("/api/proxy/auth/exceptions/delete", RemoveProxyBasicAuthExceptionPaths)
|
||||||
|
}
|
||||||
|
|
||||||
//TLS / SSL config
|
// Register the APIs for TLS / SSL certificate management functions
|
||||||
|
func RegisterTLSAPIs(authRouter *auth.RouterDef) {
|
||||||
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
|
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
|
||||||
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
|
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
|
||||||
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
|
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
|
||||||
@ -94,48 +76,72 @@ func initAPIs(targetMux *http.ServeMux) {
|
|||||||
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
||||||
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
||||||
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
|
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
|
||||||
|
}
|
||||||
|
|
||||||
//Redirection config
|
// Register the APIs for Authentication handlers like Authelia and OAUTH2
|
||||||
|
func RegisterAuthenticationHandlerAPIs(authRouter *auth.RouterDef) {
|
||||||
|
authRouter.HandleFunc("/api/sso/Authelia", autheliaRouter.HandleSetAutheliaURLAndHTTPS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the APIs for redirection rules management functions
|
||||||
|
func RegisterRedirectionAPIs(authRouter *auth.RouterDef) {
|
||||||
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
|
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
|
||||||
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
|
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
|
||||||
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
||||||
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
|
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
|
||||||
|
}
|
||||||
|
|
||||||
//Access Rules API
|
// Register the APIs for access rules management functions
|
||||||
|
func RegisterAccessRuleAPIs(authRouter *auth.RouterDef) {
|
||||||
|
/* Access Rules Settings & Status */
|
||||||
authRouter.HandleFunc("/api/access/list", handleListAccessRules)
|
authRouter.HandleFunc("/api/access/list", handleListAccessRules)
|
||||||
authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
|
authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
|
||||||
authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
|
authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
|
||||||
authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
|
authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
|
||||||
authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)
|
authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)
|
||||||
//Blacklist APIs
|
/* Blacklist */
|
||||||
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
||||||
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
||||||
authRouter.HandleFunc("/api/blacklist/country/remove", handleCountryBlacklistRemove)
|
authRouter.HandleFunc("/api/blacklist/country/remove", handleCountryBlacklistRemove)
|
||||||
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
||||||
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
||||||
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
||||||
//Whitelist APIs
|
/* Whitelist */
|
||||||
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
||||||
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
||||||
authRouter.HandleFunc("/api/whitelist/country/remove", handleCountryWhitelistRemove)
|
authRouter.HandleFunc("/api/whitelist/country/remove", handleCountryWhitelistRemove)
|
||||||
authRouter.HandleFunc("/api/whitelist/ip/add", handleIpWhitelistAdd)
|
authRouter.HandleFunc("/api/whitelist/ip/add", handleIpWhitelistAdd)
|
||||||
authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove)
|
authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove)
|
||||||
authRouter.HandleFunc("/api/whitelist/enable", handleWhitelistEnable)
|
authRouter.HandleFunc("/api/whitelist/enable", handleWhitelistEnable)
|
||||||
|
}
|
||||||
|
|
||||||
//Path Blocker APIs
|
// Register the APIs for path blocking rules management functions, WIP
|
||||||
|
func RegisterPathRuleAPIs(authRouter *auth.RouterDef) {
|
||||||
authRouter.HandleFunc("/api/pathrule/add", pathRuleHandler.HandleAddBlockingPath)
|
authRouter.HandleFunc("/api/pathrule/add", pathRuleHandler.HandleAddBlockingPath)
|
||||||
authRouter.HandleFunc("/api/pathrule/list", pathRuleHandler.HandleListBlockingPath)
|
authRouter.HandleFunc("/api/pathrule/list", pathRuleHandler.HandleListBlockingPath)
|
||||||
authRouter.HandleFunc("/api/pathrule/remove", pathRuleHandler.HandleRemoveBlockingPath)
|
authRouter.HandleFunc("/api/pathrule/remove", pathRuleHandler.HandleRemoveBlockingPath)
|
||||||
|
}
|
||||||
|
|
||||||
//Statistic & uptime monitoring API
|
// Register the APIs statistic anlysis and uptime monitoring functions
|
||||||
|
func RegisterStatisticalAPIs(authRouter *auth.RouterDef) {
|
||||||
|
/* Traffic Summary */
|
||||||
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
|
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
|
||||||
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
|
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
|
||||||
authRouter.HandleFunc("/api/stats/netstat", netstatBuffers.HandleGetNetworkInterfaceStats)
|
authRouter.HandleFunc("/api/stats/netstat", netstatBuffers.HandleGetNetworkInterfaceStats)
|
||||||
authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats)
|
authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats)
|
||||||
authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces)
|
authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces)
|
||||||
|
/* Zoraxy Analytic */
|
||||||
|
authRouter.HandleFunc("/api/analytic/list", AnalyticLoader.HandleSummaryList)
|
||||||
|
authRouter.HandleFunc("/api/analytic/load", AnalyticLoader.HandleLoadTargetDaySummary)
|
||||||
|
authRouter.HandleFunc("/api/analytic/loadRange", AnalyticLoader.HandleLoadTargetRangeSummary)
|
||||||
|
authRouter.HandleFunc("/api/analytic/exportRange", AnalyticLoader.HandleRangeExport)
|
||||||
|
authRouter.HandleFunc("/api/analytic/resetRange", AnalyticLoader.HandleRangeReset)
|
||||||
|
/* UpTime Monitor */
|
||||||
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
|
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
|
||||||
|
}
|
||||||
|
|
||||||
//Global Area Network APIs
|
// Register the APIs for Global Area Network management functions, Will be moving to plugin soon
|
||||||
|
func RegisterGANAPIs(authRouter *auth.RouterDef) {
|
||||||
authRouter.HandleFunc("/api/gan/network/info", ganManager.HandleGetNodeID)
|
authRouter.HandleFunc("/api/gan/network/info", ganManager.HandleGetNodeID)
|
||||||
authRouter.HandleFunc("/api/gan/network/add", ganManager.HandleAddNetwork)
|
authRouter.HandleFunc("/api/gan/network/add", ganManager.HandleAddNetwork)
|
||||||
authRouter.HandleFunc("/api/gan/network/remove", ganManager.HandleRemoveNetwork)
|
authRouter.HandleFunc("/api/gan/network/remove", ganManager.HandleRemoveNetwork)
|
||||||
@ -150,8 +156,10 @@ func initAPIs(targetMux *http.ServeMux) {
|
|||||||
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
|
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
|
||||||
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
|
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
|
||||||
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
||||||
|
}
|
||||||
|
|
||||||
//Stream (TCP / UDP) Proxy
|
// Register the APIs for Stream (TCP / UDP) Proxy management functions
|
||||||
|
func RegisterStreamProxyAPIs(authRouter *auth.RouterDef) {
|
||||||
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
||||||
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
|
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
|
||||||
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
|
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
|
||||||
@ -159,20 +167,59 @@ func initAPIs(targetMux *http.ServeMux) {
|
|||||||
authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy)
|
authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy)
|
||||||
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
|
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
|
||||||
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus)
|
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus)
|
||||||
|
}
|
||||||
|
|
||||||
//mDNS APIs
|
// Register the APIs for mDNS service management functions
|
||||||
|
func RegisterMDNSAPIs(authRouter *auth.RouterDef) {
|
||||||
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
|
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
|
||||||
authRouter.HandleFunc("/api/mdns/discover", HandleMdnsScanning)
|
authRouter.HandleFunc("/api/mdns/discover", HandleMdnsScanning)
|
||||||
|
}
|
||||||
|
|
||||||
//Zoraxy Analytic
|
// Register the APIs for ACME and Auto Renewer management functions
|
||||||
authRouter.HandleFunc("/api/analytic/list", AnalyticLoader.HandleSummaryList)
|
func RegisterACMEAndAutoRenewerAPIs(authRouter *auth.RouterDef) {
|
||||||
authRouter.HandleFunc("/api/analytic/load", AnalyticLoader.HandleLoadTargetDaySummary)
|
/* ACME Core */
|
||||||
authRouter.HandleFunc("/api/analytic/loadRange", AnalyticLoader.HandleLoadTargetRangeSummary)
|
authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
|
||||||
authRouter.HandleFunc("/api/analytic/exportRange", AnalyticLoader.HandleRangeExport)
|
authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
|
||||||
authRouter.HandleFunc("/api/analytic/resetRange", AnalyticLoader.HandleRangeReset)
|
/* Auto Renewer */
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HandleSetDNS)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
||||||
|
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
||||||
|
authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson)
|
||||||
|
/* ACME Wizard */
|
||||||
|
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck)
|
||||||
|
}
|
||||||
|
|
||||||
//Network utilities
|
// Register the APIs for Static Web Server management functions
|
||||||
authRouter.HandleFunc("/api/tools/ipscan", HandleIpScan)
|
func RegisterStaticWebServerAPIs(authRouter *auth.RouterDef) {
|
||||||
|
/* Static Web Server Controls */
|
||||||
|
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
|
||||||
|
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
|
||||||
|
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
|
||||||
|
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
|
||||||
|
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
|
||||||
|
/* File Manager */
|
||||||
|
if *allowWebFileManager {
|
||||||
|
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
|
||||||
|
authRouter.HandleFunc("/api/fs/upload", staticWebServer.FileManager.HandleUpload)
|
||||||
|
authRouter.HandleFunc("/api/fs/download", staticWebServer.FileManager.HandleDownload)
|
||||||
|
authRouter.HandleFunc("/api/fs/newFolder", staticWebServer.FileManager.HandleNewFolder)
|
||||||
|
authRouter.HandleFunc("/api/fs/copy", staticWebServer.FileManager.HandleFileCopy)
|
||||||
|
authRouter.HandleFunc("/api/fs/move", staticWebServer.FileManager.HandleFileMove)
|
||||||
|
authRouter.HandleFunc("/api/fs/properties", staticWebServer.FileManager.HandleFileProperties)
|
||||||
|
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the APIs for Network Utilities functions
|
||||||
|
func RegisterNetworkUtilsAPIs(authRouter *auth.RouterDef) {
|
||||||
|
authRouter.HandleFunc("/api/tools/ipscan", ipscan.HandleIpScan)
|
||||||
|
authRouter.HandleFunc("/api/tools/portscan", ipscan.HandleScanPort)
|
||||||
authRouter.HandleFunc("/api/tools/traceroute", netutils.HandleTraceRoute)
|
authRouter.HandleFunc("/api/tools/traceroute", netutils.HandleTraceRoute)
|
||||||
authRouter.HandleFunc("/api/tools/ping", netutils.HandlePing)
|
authRouter.HandleFunc("/api/tools/ping", netutils.HandlePing)
|
||||||
authRouter.HandleFunc("/api/tools/whois", netutils.HandleWhois)
|
authRouter.HandleFunc("/api/tools/whois", netutils.HandleWhois)
|
||||||
@ -185,66 +232,10 @@ func initAPIs(targetMux *http.ServeMux) {
|
|||||||
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
|
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
|
||||||
authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle)
|
authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle)
|
||||||
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
|
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
|
||||||
|
|
||||||
//Account Reset
|
|
||||||
targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
|
||||||
targetMux.HandleFunc("/api/account/new", HandleNewPasswordSetup)
|
|
||||||
|
|
||||||
//ACME & Auto Renewer
|
|
||||||
authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
|
|
||||||
authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
|
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable)
|
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
|
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HanldeSetDNS)
|
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
|
||||||
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
|
||||||
authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson)
|
|
||||||
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard
|
|
||||||
|
|
||||||
//Static Web Server
|
|
||||||
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
|
|
||||||
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
|
|
||||||
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
|
|
||||||
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
|
|
||||||
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
|
|
||||||
if *allowWebFileManager {
|
|
||||||
//Web Directory Manager file operation functions
|
|
||||||
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
|
|
||||||
authRouter.HandleFunc("/api/fs/upload", staticWebServer.FileManager.HandleUpload)
|
|
||||||
authRouter.HandleFunc("/api/fs/download", staticWebServer.FileManager.HandleDownload)
|
|
||||||
authRouter.HandleFunc("/api/fs/newFolder", staticWebServer.FileManager.HandleNewFolder)
|
|
||||||
authRouter.HandleFunc("/api/fs/copy", staticWebServer.FileManager.HandleFileCopy)
|
|
||||||
authRouter.HandleFunc("/api/fs/move", staticWebServer.FileManager.HandleFileMove)
|
|
||||||
authRouter.HandleFunc("/api/fs/properties", staticWebServer.FileManager.HandleFileProperties)
|
|
||||||
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Docker UX Optimizations
|
|
||||||
authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable)
|
|
||||||
authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList)
|
|
||||||
|
|
||||||
//Others
|
|
||||||
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
|
// Register the APIs for Auth functions, due to scoping issue some functions are defined here
|
||||||
func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
func RegisterAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
||||||
//Auth APIs
|
|
||||||
targetMux.HandleFunc("/api/auth/login", authAgent.HandleLogin)
|
targetMux.HandleFunc("/api/auth/login", authAgent.HandleLogin)
|
||||||
targetMux.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
|
targetMux.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
|
||||||
targetMux.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
|
targetMux.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -260,21 +251,17 @@ func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
|||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
js, _ := json.Marshal(username)
|
js, _ := json.Marshal(username)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
})
|
})
|
||||||
targetMux.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(authAgent.GetUserCounts())
|
||||||
js, _ := json.Marshal(uc)
|
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
})
|
})
|
||||||
targetMux.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) {
|
targetMux.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if authAgent.GetUserCounts() == 0 {
|
if authAgent.GetUserCounts() == 0 {
|
||||||
//Allow register root admin
|
//Allow register root admin
|
||||||
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {
|
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {})
|
||||||
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
//This function is disabled
|
//This function is disabled
|
||||||
utils.SendErrorResponse(w, "Root management account already exists")
|
utils.SendErrorResponse(w, "Root management account already exists")
|
||||||
@ -315,5 +302,60 @@ func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
|||||||
authAgent.UnregisterUser(username)
|
authAgent.UnregisterUser(username)
|
||||||
authAgent.CreateUserAccount(username, newPassword, "")
|
authAgent.CreateUserAccount(username, newPassword, "")
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register all the APIs */
|
||||||
|
func initAPIs(targetMux *http.ServeMux) {
|
||||||
|
authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
|
||||||
|
AuthAgent: authAgent,
|
||||||
|
RequireAuth: requireAuth,
|
||||||
|
TargetMux: targetMux,
|
||||||
|
DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
//Register the standard web services urls
|
||||||
|
fs := http.FileServer(http.FS(webres))
|
||||||
|
if DEVELOPMENT_BUILD {
|
||||||
|
fs = http.FileServer(http.Dir("web/"))
|
||||||
|
}
|
||||||
|
//Add a layer of middleware for advance control
|
||||||
|
advHandler := FSHandler(fs)
|
||||||
|
targetMux.Handle("/", advHandler)
|
||||||
|
|
||||||
|
//Register the APIs
|
||||||
|
RegisterAuthAPIs(requireAuth, targetMux)
|
||||||
|
RegisterHTTPProxyAPIs(authRouter)
|
||||||
|
RegisterTLSAPIs(authRouter)
|
||||||
|
RegisterAuthenticationHandlerAPIs(authRouter)
|
||||||
|
RegisterRedirectionAPIs(authRouter)
|
||||||
|
RegisterAccessRuleAPIs(authRouter)
|
||||||
|
RegisterPathRuleAPIs(authRouter)
|
||||||
|
RegisterStatisticalAPIs(authRouter)
|
||||||
|
RegisterGANAPIs(authRouter)
|
||||||
|
RegisterStreamProxyAPIs(authRouter)
|
||||||
|
RegisterMDNSAPIs(authRouter)
|
||||||
|
RegisterNetworkUtilsAPIs(authRouter)
|
||||||
|
RegisterACMEAndAutoRenewerAPIs(authRouter)
|
||||||
|
RegisterStaticWebServerAPIs(authRouter)
|
||||||
|
|
||||||
|
//Account Reset
|
||||||
|
targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
||||||
|
targetMux.HandleFunc("/api/account/new", HandleNewPasswordSetup)
|
||||||
|
|
||||||
|
//Docker UX Optimizations
|
||||||
|
authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable)
|
||||||
|
authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList)
|
||||||
|
|
||||||
|
//Others
|
||||||
|
targetMux.HandleFunc("/api/info/x", HandleZoraxyInfo)
|
||||||
|
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
|
||||||
|
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
|
||||||
|
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
|
||||||
|
authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog)
|
||||||
|
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
|
||||||
|
|
||||||
|
//Debug
|
||||||
|
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
|
||||||
}
|
}
|
||||||
|
@ -177,7 +177,10 @@ func handleListDomains(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Handle front-end toggling TLS mode
|
// Handle front-end toggling TLS mode
|
||||||
func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
|
func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
currentTlsSetting := false
|
currentTlsSetting := true //Default to true
|
||||||
|
if dynamicProxyRouter.Option != nil {
|
||||||
|
currentTlsSetting = dynamicProxyRouter.Option.UseTls
|
||||||
|
}
|
||||||
if sysdb.KeyExists("settings", "usetls") {
|
if sysdb.KeyExists("settings", "usetls") {
|
||||||
sysdb.Read("settings", "usetls", ¤tTlsSetting)
|
sysdb.Read("settings", "usetls", ¤tTlsSetting)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Parse it into dynamic proxy endpoint
|
//Parse it into dynamic proxy endpoint
|
||||||
thisConfigEndpoint := dynamicproxy.ProxyEndpoint{}
|
thisConfigEndpoint := dynamicproxy.GetDefaultProxyEndpoint()
|
||||||
err = json.Unmarshal(endpointConfig, &thisConfigEndpoint)
|
err = json.Unmarshal(endpointConfig, &thisConfigEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -59,7 +59,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
|||||||
thisConfigEndpoint.RootOrMatchingDomain = "/"
|
thisConfigEndpoint.RootOrMatchingDomain = "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Root {
|
if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyTypeRoot {
|
||||||
//This is a root config file
|
//This is a root config file
|
||||||
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -68,7 +68,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
|||||||
|
|
||||||
dynamicProxyRouter.SetProxyRouteAsRoot(rootProxyEndpoint)
|
dynamicProxyRouter.SetProxyRouteAsRoot(rootProxyEndpoint)
|
||||||
|
|
||||||
} else if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Host {
|
} else if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyTypeHost {
|
||||||
//This is a host config file
|
//This is a host config file
|
||||||
readyProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
readyProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -97,7 +97,7 @@ func filterProxyConfigFilename(filename string) string {
|
|||||||
func SaveReverseProxyConfig(endpoint *dynamicproxy.ProxyEndpoint) error {
|
func SaveReverseProxyConfig(endpoint *dynamicproxy.ProxyEndpoint) error {
|
||||||
//Get filename for saving
|
//Get filename for saving
|
||||||
filename := filepath.Join("./conf/proxy/", endpoint.RootOrMatchingDomain+".config")
|
filename := filepath.Join("./conf/proxy/", endpoint.RootOrMatchingDomain+".config")
|
||||||
if endpoint.ProxyType == dynamicproxy.ProxyType_Root {
|
if endpoint.ProxyType == dynamicproxy.ProxyTypeRoot {
|
||||||
filename = "./conf/proxy/root.config"
|
filename = "./conf/proxy/root.config"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,27 +129,23 @@ func RemoveReverseProxyConfig(endpoint string) error {
|
|||||||
// Get the default root config that point to the internal static web server
|
// Get the default root config that point to the internal static web server
|
||||||
// this will be used if root config is not found (new deployment / missing root.config file)
|
// this will be used if root config is not found (new deployment / missing root.config file)
|
||||||
func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
|
func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
|
||||||
//Default settings
|
//Get the default proxy endpoint
|
||||||
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
|
rootProxyEndpointConfig := dynamicproxy.GetDefaultProxyEndpoint()
|
||||||
ProxyType: dynamicproxy.ProxyType_Root,
|
rootProxyEndpointConfig.ProxyType = dynamicproxy.ProxyTypeRoot
|
||||||
RootOrMatchingDomain: "/",
|
rootProxyEndpointConfig.RootOrMatchingDomain = "/"
|
||||||
ActiveOrigins: []*loadbalance.Upstream{
|
rootProxyEndpointConfig.ActiveOrigins = []*loadbalance.Upstream{
|
||||||
{
|
{
|
||||||
OriginIpOrDomain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
OriginIpOrDomain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
||||||
RequireTLS: false,
|
RequireTLS: false,
|
||||||
SkipCertValidations: false,
|
SkipCertValidations: false,
|
||||||
Weight: 0,
|
Weight: 0,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
InactiveOrigins: []*loadbalance.Upstream{},
|
}
|
||||||
BypassGlobalTLS: false,
|
rootProxyEndpointConfig.DefaultSiteOption = dynamicproxy.DefaultSite_InternalStaticWebServer
|
||||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
rootProxyEndpointConfig.DefaultSiteValue = ""
|
||||||
RequireBasicAuth: false,
|
|
||||||
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
//Default settings
|
||||||
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
|
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&rootProxyEndpointConfig)
|
||||||
DefaultSiteOption: dynamicproxy.DefaultSite_InternalStaticWebServer,
|
|
||||||
DefaultSiteValue: "",
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -167,12 +163,15 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
if includeSysDBRaw == "true" {
|
if includeSysDBRaw == "true" {
|
||||||
//Include the system database in backup snapshot
|
//Include the system database in backup snapshot
|
||||||
//Temporary set it to read only
|
//Temporary set it to read only
|
||||||
sysdb.ReadOnly = true
|
|
||||||
includeSysDB = true
|
includeSysDB = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the folder path to be zipped
|
// Specify the folder path to be zipped
|
||||||
folderPath := "./conf/"
|
if !utils.FileExists("./conf") {
|
||||||
|
SystemWideLogger.PrintAndLog("Backup", "Configuration folder not found", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
folderPath := "./conf"
|
||||||
|
|
||||||
// Set the Content-Type header to indicate it's a zip file
|
// Set the Content-Type header to indicate it's a zip file
|
||||||
w.Header().Set("Content-Type", "application/zip")
|
w.Header().Set("Content-Type", "application/zip")
|
||||||
@ -227,7 +226,7 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Open the file on disk
|
// Open the file on disk
|
||||||
file, err := os.Open("sys.db")
|
file, err := os.Open("./sys.db")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Backup", "Unable to open sysdb", err)
|
SystemWideLogger.PrintAndLog("Backup", "Unable to open sysdb", err)
|
||||||
return
|
return
|
||||||
@ -241,8 +240,6 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Restore sysdb state
|
|
||||||
sysdb.ReadOnly = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -278,6 +275,8 @@ func ImportConfigFromZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
targetDir := "./conf"
|
targetDir := "./conf"
|
||||||
if utils.FileExists(targetDir) {
|
if utils.FileExists(targetDir) {
|
||||||
//Backup the old config to old
|
//Backup the old config to old
|
||||||
|
//backupPath := filepath.Dir(*path_conf) + filepath.Base(*path_conf) + ".old_" + strconv.Itoa(int(time.Now().Unix()))
|
||||||
|
//os.Rename(*path_conf, backupPath)
|
||||||
os.Rename("./conf", "./conf.old_"+strconv.Itoa(int(time.Now().Unix())))
|
os.Rename("./conf", "./conf.old_"+strconv.Itoa(int(time.Now().Unix())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
148
src/def.go
Normal file
148
src/def.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
Type and flag definations
|
||||||
|
|
||||||
|
This file contains all the type and flag definations
|
||||||
|
Author: tobychui
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"flag"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/access"
|
||||||
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
|
"imuslab.com/zoraxy/mod/auth/sso/authelia"
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/dockerux"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||||
|
"imuslab.com/zoraxy/mod/email"
|
||||||
|
"imuslab.com/zoraxy/mod/forwardproxy"
|
||||||
|
"imuslab.com/zoraxy/mod/ganserv"
|
||||||
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logviewer"
|
||||||
|
"imuslab.com/zoraxy/mod/mdns"
|
||||||
|
"imuslab.com/zoraxy/mod/netstat"
|
||||||
|
"imuslab.com/zoraxy/mod/pathrule"
|
||||||
|
"imuslab.com/zoraxy/mod/sshprox"
|
||||||
|
"imuslab.com/zoraxy/mod/statistic"
|
||||||
|
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||||
|
"imuslab.com/zoraxy/mod/streamproxy"
|
||||||
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
|
"imuslab.com/zoraxy/mod/webserv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
/* Build Constants */
|
||||||
|
SYSTEM_NAME = "Zoraxy"
|
||||||
|
SYSTEM_VERSION = "3.1.6"
|
||||||
|
DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */
|
||||||
|
|
||||||
|
/* System Constants */
|
||||||
|
TMP_FOLDER = "./tmp"
|
||||||
|
WEBSERV_DEFAULT_PORT = 5487
|
||||||
|
MDNS_HOSTNAME_PREFIX = "zoraxy_" /* Follow by node UUID */
|
||||||
|
MDNS_IDENTIFY_DEVICE_TYPE = "Network Gateway"
|
||||||
|
MDNS_IDENTIFY_DOMAIN = "zoraxy.aroz.org"
|
||||||
|
MDNS_IDENTIFY_VENDOR = "imuslab.com"
|
||||||
|
MDNS_SCAN_TIMEOUT = 30 /* Seconds */
|
||||||
|
MDNS_SCAN_UPDATE_INTERVAL = 15 /* Minutes */
|
||||||
|
GEODB_CACHE_CLEAR_INTERVAL = 15 /* Minutes */
|
||||||
|
ACME_AUTORENEW_CONFIG_PATH = "./conf/acme_conf.json"
|
||||||
|
CSRF_COOKIENAME = "zoraxy_csrf"
|
||||||
|
LOG_PREFIX = "zr"
|
||||||
|
LOG_EXTENSION = ".log"
|
||||||
|
|
||||||
|
/* Configuration Folder Storage Path Constants */
|
||||||
|
CONF_HTTP_PROXY = "./conf/proxy"
|
||||||
|
CONF_STREAM_PROXY = "./conf/streamproxy"
|
||||||
|
CONF_CERT_STORE = "./conf/certs"
|
||||||
|
CONF_REDIRECTION = "./conf/redirect"
|
||||||
|
CONF_ACCESS_RULE = "./conf/access"
|
||||||
|
CONF_PATH_RULE = "./conf/rules/pathrules"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* System Startup Flags */
|
||||||
|
var (
|
||||||
|
webUIPort = flag.String("port", ":8000", "Management web interface listening port")
|
||||||
|
databaseBackend = flag.String("db", "auto", "Database backend to use (leveldb, boltdb, auto) Note that fsdb will be used on unsupported platforms like RISCV")
|
||||||
|
noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
|
||||||
|
showver = flag.Bool("version", false, "Show version of this server")
|
||||||
|
allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
||||||
|
allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
||||||
|
mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
|
||||||
|
ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
||||||
|
ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||||
|
runningInDocker = flag.Bool("docker", false, "Run Zoraxy in docker compatibility mode")
|
||||||
|
acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
||||||
|
acmeCertAutoRenewDays = flag.Int("earlyrenew", 30, "Number of days to early renew a soon expiring certificate (days)")
|
||||||
|
enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
|
||||||
|
allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
|
||||||
|
enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
|
||||||
|
|
||||||
|
/* Path Configuration Flags */
|
||||||
|
//path_database = flag.String("dbpath", "./sys.db", "Database path")
|
||||||
|
//path_conf = flag.String("conf", "./conf", "Configuration folder path")
|
||||||
|
path_uuid = flag.String("uuid", "./sys.uuid", "sys.uuid file path")
|
||||||
|
path_logFile = flag.String("log", "./log", "Log folder path")
|
||||||
|
path_webserver = flag.String("webroot", "./www", "Static web server root folder. Only allow change in start paramters")
|
||||||
|
|
||||||
|
/* Maintaince Function Flags */
|
||||||
|
geoDbUpdate = flag.Bool("update_geoip", false, "Download the latest GeoIP data and exit")
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Global Variables and Handlers */
|
||||||
|
var (
|
||||||
|
nodeUUID = "generic" //System uuid in uuidv4 format, load from database on startup
|
||||||
|
bootTime = time.Now().Unix()
|
||||||
|
requireAuth = true //Require authentication for webmin panel, override from flag
|
||||||
|
|
||||||
|
/*
|
||||||
|
Binary Embedding File System
|
||||||
|
*/
|
||||||
|
//go:embed web/*
|
||||||
|
webres embed.FS
|
||||||
|
|
||||||
|
/*
|
||||||
|
Handler Modules
|
||||||
|
*/
|
||||||
|
sysdb *database.Database //System database
|
||||||
|
authAgent *auth.AuthAgent //Authentication agent
|
||||||
|
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||||
|
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||||
|
webminPanelMux *http.ServeMux //Server mux for handling webmin panel APIs
|
||||||
|
csrfMiddleware func(http.Handler) http.Handler //CSRF protection middleware
|
||||||
|
|
||||||
|
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||||
|
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||||
|
accessController *access.Controller //Access controller, handle black list and white list
|
||||||
|
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||||
|
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||||
|
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
||||||
|
mdnsScanner *mdns.MDNSHost //mDNS discovery services
|
||||||
|
ganManager *ganserv.NetworkManager //Global Area Network Manager
|
||||||
|
webSshManager *sshprox.Manager //Web SSH connection service
|
||||||
|
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
|
||||||
|
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
|
||||||
|
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
||||||
|
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
||||||
|
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
|
||||||
|
loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing
|
||||||
|
|
||||||
|
//Authentication Provider
|
||||||
|
autheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication
|
||||||
|
|
||||||
|
//Helper modules
|
||||||
|
EmailSender *email.Sender //Email sender that handle email sending
|
||||||
|
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
||||||
|
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
|
||||||
|
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
||||||
|
LogViewer *logviewer.Viewer //Log viewer HTTP handlers
|
||||||
|
)
|
206
src/go.mod
206
src/go.mod
@ -1,107 +1,129 @@
|
|||||||
module imuslab.com/zoraxy
|
module imuslab.com/zoraxy
|
||||||
|
|
||||||
go 1.21
|
go 1.22.0
|
||||||
|
|
||||||
toolchain go1.22.2
|
toolchain go1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/boltdb/bolt v1.3.1
|
github.com/boltdb/bolt v1.3.1
|
||||||
github.com/docker/docker v27.0.0+incompatible
|
github.com/docker/docker v27.0.0+incompatible
|
||||||
github.com/go-acme/lego/v4 v4.16.1
|
github.com/go-acme/lego/v4 v4.19.2
|
||||||
github.com/go-ping/ping v1.1.0
|
github.com/go-ping/ping v1.1.0
|
||||||
|
github.com/go-session/session v3.1.2+incompatible
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/sessions v1.2.2
|
github.com/gorilla/sessions v1.2.2
|
||||||
github.com/gorilla/websocket v1.5.1
|
github.com/gorilla/websocket v1.5.1
|
||||||
github.com/grandcat/zeroconf v1.0.0
|
github.com/grandcat/zeroconf v1.0.0
|
||||||
github.com/likexian/whois v1.15.1
|
github.com/likexian/whois v1.15.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26
|
github.com/microcosm-cc/bluemonday v1.0.26
|
||||||
golang.org/x/net v0.25.0
|
golang.org/x/net v0.29.0
|
||||||
golang.org/x/sys v0.20.0
|
golang.org/x/sys v0.25.0
|
||||||
golang.org/x/text v0.15.0
|
golang.org/x/text v0.18.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.25.1 // indirect
|
cloud.google.com/go/auth v0.9.3 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||||
|
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
|
github.com/golang/snappy v0.0.1 // indirect
|
||||||
|
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114 // indirect
|
||||||
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||||
|
github.com/shopspring/decimal v1.3.1 // indirect
|
||||||
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
|
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 // indirect
|
||||||
|
github.com/tidwall/buntdb v1.1.2 // indirect
|
||||||
|
github.com/tidwall/gjson v1.12.1 // indirect
|
||||||
|
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
|
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e // indirect
|
||||||
|
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect
|
||||||
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
|
github.com/vultr/govultr/v3 v3.9.1 // indirect
|
||||||
|
go.mongodb.org/mongo-driver v1.12.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go/compute/metadata v0.5.1 // indirect
|
||||||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0 // indirect
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
|
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
|
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
||||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
|
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 // indirect
|
||||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
|
github.com/aws/aws-sdk-go-v2/config v1.27.33 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.32 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
github.com/aws/aws-sdk-go-v2/service/lightsail v1.40.6 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0 // indirect
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
github.com/aws/smithy-go v1.20.4 // indirect
|
||||||
github.com/aws/smithy-go v1.19.0 // indirect
|
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/civo/civogo v0.3.11 // indirect
|
github.com/civo/civogo v0.3.11 // indirect
|
||||||
github.com/cloudflare/cloudflare-go v0.86.0 // indirect
|
github.com/cloudflare/cloudflare-go v0.104.0 // indirect
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
|
||||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
github.com/dnsimple/dnsimple-go v1.2.0 // indirect
|
github.com/dnsimple/dnsimple-go v1.7.0 // indirect
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
github.com/docker/go-units 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/fatih/structs v1.1.0 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-errors/errors v1.0.1 // indirect
|
github.com/go-errors/errors v1.0.1 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-resty/resty/v2 v2.11.0 // indirect
|
github.com/go-oauth2/oauth2/v4 v4.5.2
|
||||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
github.com/go-resty/resty/v2 v2.13.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
|
github.com/gofrs/uuid v4.4.0+incompatible
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
github.com/google/s2a-go v0.1.8 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.12.2 // indirect
|
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
|
||||||
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
github.com/gophercloud/gophercloud v1.14.0 // indirect
|
||||||
github.com/gorilla/csrf v1.7.2 // indirect
|
github.com/gorilla/csrf v1.7.2
|
||||||
github.com/gorilla/css v1.0.1 // indirect
|
github.com/gorilla/css v1.0.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||||
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
@ -111,11 +133,11 @@ require (
|
|||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||||
github.com/labbsr0x/goh v1.0.1 // indirect
|
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||||
github.com/linode/linodego v1.28.0 // indirect
|
github.com/linode/linodego v1.40.0 // indirect
|
||||||
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
|
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
|
||||||
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/miekg/dns v1.1.58 // indirect
|
github.com/miekg/dns v1.1.62 // indirect
|
||||||
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
@ -126,66 +148,64 @@ require (
|
|||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||||
github.com/nrdcg/auroradns v1.1.0 // indirect
|
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect
|
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
|
||||||
github.com/nrdcg/desec v0.7.0 // indirect
|
github.com/nrdcg/desec v0.8.0 // indirect
|
||||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||||
github.com/nrdcg/freemyip v0.2.0 // indirect
|
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||||
github.com/nrdcg/goinwx v0.10.0 // indirect
|
github.com/nrdcg/goinwx v0.10.0 // indirect
|
||||||
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
||||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||||
github.com/nrdcg/nodion v0.1.0 // indirect
|
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||||
github.com/nrdcg/porkbun v0.3.0 // indirect
|
github.com/nrdcg/porkbun v0.4.0 // indirect
|
||||||
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||||
github.com/ovh/go-ovh v1.4.3 // indirect
|
github.com/ovh/go-ovh v1.6.0 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/pquerna/otp v1.4.0 // indirect
|
github.com/pquerna/otp v1.4.0 // indirect
|
||||||
github.com/sacloud/api-client-go v0.2.8 // indirect
|
github.com/sacloud/api-client-go v0.2.10 // indirect
|
||||||
github.com/sacloud/go-http v0.1.6 // indirect
|
github.com/sacloud/go-http v0.1.8 // indirect
|
||||||
github.com/sacloud/iaas-api-go v1.11.1 // indirect
|
github.com/sacloud/iaas-api-go v1.12.0 // indirect
|
||||||
github.com/sacloud/packages-go v0.0.9 // indirect
|
github.com/sacloud/packages-go v0.0.10 // indirect
|
||||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22 // indirect
|
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||||
github.com/softlayer/softlayer-go v1.1.3 // indirect
|
github.com/softlayer/softlayer-go v1.1.5 // indirect
|
||||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
|
||||||
github.com/stretchr/testify v1.9.0 // indirect
|
github.com/stretchr/testify v1.9.0 // indirect
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1002 // indirect
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 // indirect
|
||||||
github.com/transip/gotransip/v6 v6.23.0 // indirect
|
github.com/transip/gotransip/v6 v6.26.0 // indirect
|
||||||
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect
|
github.com/ultradns/ultradns-go-sdk v1.7.0-20240913052650-970ca9a // indirect
|
||||||
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||||
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
github.com/xlzd/gotp v0.1.0
|
||||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
|
github.com/yandex-cloud/go-genproto v0.0.0-20240911120709-1fa0cb6f47c2 // indirect
|
||||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
|
github.com/yandex-cloud/go-sdk v0.0.0-20240911121212-e4e74d0d02f5 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.27.0 // indirect
|
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||||
go.uber.org/ratelimit v0.2.0 // indirect
|
go.uber.org/ratelimit v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.23.0 // indirect
|
golang.org/x/crypto v0.27.0 // indirect
|
||||||
golang.org/x/mod v0.16.0 // indirect
|
golang.org/x/mod v0.21.0 // indirect
|
||||||
golang.org/x/oauth2 v0.18.0 // indirect
|
golang.org/x/oauth2 v0.23.0 // indirect
|
||||||
golang.org/x/sync v0.6.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.6.0 // indirect
|
||||||
golang.org/x/tools v0.19.0 // indirect
|
golang.org/x/tools v0.25.0 // indirect
|
||||||
google.golang.org/api v0.169.0 // indirect
|
google.golang.org/api v0.197.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
google.golang.org/grpc v1.66.1 // indirect
|
||||||
google.golang.org/grpc v1.64.0 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect
|
gopkg.in/ns1/ns1-go.v2 v2.12.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gotest.tools/v3 v3.5.1 // indirect
|
gotest.tools/v3 v3.5.1 // indirect
|
||||||
|
603
src/go.sum
603
src/go.sum
File diff suppressed because it is too large
Load Diff
170
src/main.go
170
src/main.go
@ -1,7 +1,36 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
______
|
||||||
|
|___ /
|
||||||
|
/ / ___ _ __ __ ___ ___ _
|
||||||
|
/ / / _ \| '__/ _` \ \/ / | | |
|
||||||
|
/ /_| (_) | | | (_| |> <| |_| |
|
||||||
|
/_____\___/|_| \__,_/_/\_\\__, |
|
||||||
|
__/ |
|
||||||
|
|___/
|
||||||
|
|
||||||
|
Zoraxy - A general purpose HTTP reverse proxy and forwarding tool
|
||||||
|
Author: tobychui
|
||||||
|
License: AGPLv3
|
||||||
|
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, version 3 of the License or any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -13,98 +42,12 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/csrf"
|
"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/geodb"
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
|
||||||
"imuslab.com/zoraxy/mod/info/logviewer"
|
|
||||||
"imuslab.com/zoraxy/mod/mdns"
|
|
||||||
"imuslab.com/zoraxy/mod/netstat"
|
|
||||||
"imuslab.com/zoraxy/mod/pathrule"
|
|
||||||
"imuslab.com/zoraxy/mod/sshprox"
|
|
||||||
"imuslab.com/zoraxy/mod/statistic"
|
|
||||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
|
||||||
"imuslab.com/zoraxy/mod/streamproxy"
|
|
||||||
"imuslab.com/zoraxy/mod/tlscert"
|
|
||||||
"imuslab.com/zoraxy/mod/update"
|
"imuslab.com/zoraxy/mod/update"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
"imuslab.com/zoraxy/mod/webserv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// General flags
|
/* SIGTERM handler, do shutdown sequences before closing */
|
||||||
var webUIPort = flag.String("port", ":8000", "Management web interface listening port")
|
|
||||||
var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
|
|
||||||
var showver = flag.Bool("version", false, "Show version of this server")
|
|
||||||
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
|
||||||
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
|
||||||
var 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 enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
|
|
||||||
|
|
||||||
var (
|
|
||||||
name = "Zoraxy"
|
|
||||||
version = "3.1.0"
|
|
||||||
nodeUUID = "generic" //System uuid, in uuidv4 format
|
|
||||||
development = false //Set this to false to use embedded web fs
|
|
||||||
bootTime = time.Now().Unix()
|
|
||||||
|
|
||||||
/*
|
|
||||||
Binary Embedding File System
|
|
||||||
*/
|
|
||||||
//go:embed web/*
|
|
||||||
webres embed.FS
|
|
||||||
|
|
||||||
/*
|
|
||||||
Handler Modules
|
|
||||||
*/
|
|
||||||
sysdb *database.Database //System database
|
|
||||||
authAgent *auth.AuthAgent //Authentication agent
|
|
||||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
|
||||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
|
||||||
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
|
|
||||||
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.
|
|
||||||
func SetupCloseHandler() {
|
func SetupCloseHandler() {
|
||||||
c := make(chan os.Signal, 2)
|
c := make(chan os.Signal, 2)
|
||||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||||
@ -115,45 +58,21 @@ func SetupCloseHandler() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ShutdownSeq() {
|
|
||||||
SystemWideLogger.Println("Shutting down " + name)
|
|
||||||
SystemWideLogger.Println("Closing GeoDB ")
|
|
||||||
geodbStore.Close()
|
|
||||||
SystemWideLogger.Println("Closing Netstats Listener")
|
|
||||||
netstatBuffers.Close()
|
|
||||||
SystemWideLogger.Println("Closing Statistic Collector")
|
|
||||||
statisticCollector.Close()
|
|
||||||
if mdnsTickerStop != nil {
|
|
||||||
SystemWideLogger.Println("Stopping mDNS Discoverer (might take a few minutes)")
|
|
||||||
// Stop the mdns service
|
|
||||||
mdnsTickerStop <- true
|
|
||||||
}
|
|
||||||
mdnsScanner.Close()
|
|
||||||
SystemWideLogger.Println("Shutting down load balancer")
|
|
||||||
loadBalancer.Close()
|
|
||||||
SystemWideLogger.Println("Closing Certificates Auto Renewer")
|
|
||||||
acmeAutoRenewer.Close()
|
|
||||||
//Remove the tmp folder
|
|
||||||
SystemWideLogger.Println("Cleaning up tmp files")
|
|
||||||
os.RemoveAll("./tmp")
|
|
||||||
|
|
||||||
//Close database
|
|
||||||
SystemWideLogger.Println("Stopping system database")
|
|
||||||
sysdb.Close()
|
|
||||||
|
|
||||||
//Close logger
|
|
||||||
SystemWideLogger.Println("Closing system wide logger")
|
|
||||||
SystemWideLogger.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
//Parse startup flags
|
//Parse startup flags
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
/* Maintaince Function Modes */
|
||||||
if *showver {
|
if *showver {
|
||||||
fmt.Println(name + " - Version " + version)
|
fmt.Println(SYSTEM_NAME + " - Version " + SYSTEM_VERSION)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
if *geoDbUpdate {
|
||||||
|
geodb.DownloadGeoDBUpdate("./conf/geodb")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Main Zoraxy Routines */
|
||||||
if !utils.ValidateListeningAddress(*webUIPort) {
|
if !utils.ValidateListeningAddress(*webUIPort) {
|
||||||
fmt.Println("Malformed -port (listening address) paramter. Do you mean -port=:" + *webUIPort + "?")
|
fmt.Println("Malformed -port (listening address) paramter. Do you mean -port=:" + *webUIPort + "?")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@ -161,13 +80,13 @@ func main() {
|
|||||||
|
|
||||||
if *enableAutoUpdate {
|
if *enableAutoUpdate {
|
||||||
fmt.Println("Checking required config update")
|
fmt.Println("Checking required config update")
|
||||||
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version))
|
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(SYSTEM_VERSION))
|
||||||
}
|
}
|
||||||
|
|
||||||
SetupCloseHandler()
|
SetupCloseHandler()
|
||||||
|
|
||||||
//Read or create the system uuid
|
//Read or create the system uuid
|
||||||
uuidRecord := "./sys.uuid"
|
uuidRecord := *path_uuid
|
||||||
if !utils.FileExists(uuidRecord) {
|
if !utils.FileExists(uuidRecord) {
|
||||||
newSystemUUID := uuid.New().String()
|
newSystemUUID := uuid.New().String()
|
||||||
os.WriteFile(uuidRecord, []byte(newSystemUUID), 0775)
|
os.WriteFile(uuidRecord, []byte(newSystemUUID), 0775)
|
||||||
@ -183,13 +102,13 @@ func main() {
|
|||||||
webminPanelMux = http.NewServeMux()
|
webminPanelMux = http.NewServeMux()
|
||||||
csrfMiddleware = csrf.Protect(
|
csrfMiddleware = csrf.Protect(
|
||||||
[]byte(nodeUUID),
|
[]byte(nodeUUID),
|
||||||
csrf.CookieName("zoraxy-csrf"),
|
csrf.CookieName(CSRF_COOKIENAME),
|
||||||
csrf.Secure(false),
|
csrf.Secure(false),
|
||||||
csrf.Path("/"),
|
csrf.Path("/"),
|
||||||
csrf.SameSite(csrf.SameSiteLaxMode),
|
csrf.SameSite(csrf.SameSiteLaxMode),
|
||||||
)
|
)
|
||||||
|
|
||||||
//Startup all modules
|
//Startup all modules, see start.go
|
||||||
startupSequence()
|
startupSequence()
|
||||||
|
|
||||||
//Initiate management interface APIs
|
//Initiate management interface APIs
|
||||||
@ -206,11 +125,10 @@ func main() {
|
|||||||
//Start the finalize sequences
|
//Start the finalize sequences
|
||||||
finalSequence()
|
finalSequence()
|
||||||
|
|
||||||
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
|
SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://localhost" + *webUIPort)
|
||||||
err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
|
err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -22,18 +21,29 @@ import (
|
|||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var defaultNameservers = []string{
|
||||||
|
"8.8.8.8:53", // Google DNS
|
||||||
|
"8.8.4.4:53", // Google DNS
|
||||||
|
"1.1.1.1:53", // Cloudflare DNS
|
||||||
|
"1.0.0.1:53", // Cloudflare DNS
|
||||||
|
}
|
||||||
|
|
||||||
type CertificateInfoJSON struct {
|
type CertificateInfoJSON struct {
|
||||||
AcmeName string `json:"acme_name"`
|
AcmeName string `json:"acme_name"` //ACME provider name
|
||||||
AcmeUrl string `json:"acme_url"`
|
AcmeUrl string `json:"acme_url"` //Custom ACME URL (if any)
|
||||||
SkipTLS bool `json:"skip_tls"`
|
SkipTLS bool `json:"skip_tls"` //Skip TLS verification of upstream
|
||||||
UseDNS bool `json:"dns"`
|
UseDNS bool `json:"dns"` //Use DNS challenge
|
||||||
|
PropTimeout int `json:"prop_time"` //Propagation timeout
|
||||||
|
DNSServers []string `json:"dnsServers"` // DNS servers
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACMEUser represents a user in the ACME system.
|
// ACMEUser represents a user in the ACME system.
|
||||||
@ -68,25 +78,38 @@ type ACMEHandler struct {
|
|||||||
DefaultAcmeServer string
|
DefaultAcmeServer string
|
||||||
Port string
|
Port string
|
||||||
Database *database.Database
|
Database *database.Database
|
||||||
|
Logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewACME creates a new ACMEHandler instance.
|
// 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{
|
return &ACMEHandler{
|
||||||
DefaultAcmeServer: acmeServer,
|
DefaultAcmeServer: defaultAcmeServer,
|
||||||
Port: port,
|
Port: port,
|
||||||
Database: database,
|
Database: database,
|
||||||
|
Logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ACMEHandler) Logf(message string, err error) {
|
||||||
|
a.Logger.PrintAndLog("ACME", message, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the ACMEHandler.
|
||||||
|
// ACME Handler does not need to close anything
|
||||||
|
// Function defined for future compatibility
|
||||||
|
func (a *ACMEHandler) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ObtainCert obtains a certificate for the specified domains.
|
// 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) {
|
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool, useDNS bool, propagationTimeout int, dnsServers string) (bool, error) {
|
||||||
log.Println("[ACME] Obtaining certificate...")
|
a.Logf("Obtaining certificate for: "+strings.Join(domains, ", "), nil)
|
||||||
|
|
||||||
// generate private key
|
// generate private key
|
||||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Private key generation failed", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +125,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
// skip TLS verify if need
|
// skip TLS verify if need
|
||||||
// Ref: https://github.com/go-acme/lego/blob/6af2c756ac73a9cb401621afca722d0f4112b1b8/lego/client_config.go#L74
|
// Ref: https://github.com/go-acme/lego/blob/6af2c756ac73a9cb401621afca722d0f4112b1b8/lego/client_config.go#L74
|
||||||
if skipTLS {
|
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{
|
config.HTTPClient.Transport = &http.Transport{
|
||||||
Proxy: http.ProxyFromEnvironment,
|
Proxy: http.ProxyFromEnvironment,
|
||||||
DialContext: (&net.Dialer{
|
DialContext: (&net.Dialer{
|
||||||
@ -129,16 +152,16 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
|
|
||||||
// if not custom ACME url, load it from ca.json
|
// if not custom ACME url, load it from ca.json
|
||||||
if caName == "custom" {
|
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 {
|
} else {
|
||||||
caLinkOverwrite, err := loadCAApiServerFromName(caName)
|
caLinkOverwrite, err := loadCAApiServerFromName(caName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
config.CADirURL = caLinkOverwrite
|
config.CADirURL = caLinkOverwrite
|
||||||
log.Println("[INFO] Using " + caLinkOverwrite + " for CA Directory URL")
|
a.Logf("Using "+caLinkOverwrite+" for CA Directory URL", nil)
|
||||||
} else {
|
} else {
|
||||||
// (caName == "" || caUrl == "") will use default acme
|
// (caName == "" || caUrl == "") will use default acme
|
||||||
config.CADirURL = a.DefaultAcmeServer
|
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,50 +169,72 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
|
|
||||||
client, err := lego.NewClient(config)
|
client, err := lego.NewClient(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Failed to spawn new ACME client from current config", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load certificate info from JSON file
|
||||||
|
certInfo, err := LoadCertInfoJSON(fmt.Sprintf("./conf/certs/%s.json", certificateName))
|
||||||
|
if err == nil {
|
||||||
|
useDNS = certInfo.UseDNS
|
||||||
|
if dnsServers == "" && certInfo.DNSServers != nil && len(certInfo.DNSServers) > 0 {
|
||||||
|
dnsServers = strings.Join(certInfo.DNSServers, ",")
|
||||||
|
}
|
||||||
|
propagationTimeout = certInfo.PropTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean DNS servers
|
||||||
|
dnsNameservers := strings.Split(dnsServers, ",")
|
||||||
|
for i := range dnsNameservers {
|
||||||
|
dnsNameservers[i] = strings.TrimSpace(dnsNameservers[i])
|
||||||
|
}
|
||||||
|
|
||||||
// setup how to receive challenge
|
// setup how to receive challenge
|
||||||
if useDNS {
|
if useDNS {
|
||||||
if !a.Database.TableExists("acme") {
|
if !a.Database.TableExists("acme") {
|
||||||
a.Database.NewTable("acme")
|
a.Database.NewTable("acme")
|
||||||
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -1)")
|
return false, errors.New("DNS Provider and DNS Credential configuration required for ACME Provider (Error -1)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.Database.KeyExists("acme", certificateName+"_dns_provider") || !a.Database.KeyExists("acme", certificateName+"_dns_credentials") {
|
if !a.Database.KeyExists("acme", certificateName+"_dns_provider") || !a.Database.KeyExists("acme", certificateName+"_dns_credentials") {
|
||||||
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -2)")
|
return false, errors.New("DNS Provider and DNS Credential configuration required for ACME Provider (Error -2)")
|
||||||
}
|
}
|
||||||
|
|
||||||
var dnsCredentials string
|
var dnsCredentials string
|
||||||
err := a.Database.Read("acme", certificateName+"_dns_credentials", &dnsCredentials)
|
err := a.Database.Read("acme", certificateName+"_dns_credentials", &dnsCredentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Read DNS credential failed", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var dnsProvider string
|
var dnsProvider string
|
||||||
err = a.Database.Read("acme", certificateName+"_dns_provider", &dnsProvider)
|
err = a.Database.Read("acme", certificateName+"_dns_provider", &dnsProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Read DNS Provider failed", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := GetDnsChallengeProviderByName(dnsProvider, dnsCredentials)
|
provider, err := GetDnsChallengeProviderByName(dnsProvider, dnsCredentials, propagationTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Unable to resolve DNS challenge provider", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.Challenge.SetDNS01Provider(provider)
|
if len(dnsNameservers) > 0 && dnsNameservers[0] != "" {
|
||||||
|
a.Logf("Using DNS servers: "+strings.Join(dnsNameservers, ", "), nil)
|
||||||
|
err = client.Challenge.SetDNS01Provider(provider, dns01.AddRecursiveNameservers(dnsNameservers))
|
||||||
|
} else {
|
||||||
|
// Use default DNS-01 nameservers if dnsServers is empty
|
||||||
|
err = client.Challenge.SetDNS01Provider(provider, dns01.AddRecursiveNameservers(defaultNameservers))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Failed to resolve DNS01 Provider", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
|
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Failed to resolve HTTP01 Provider", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,7 +250,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
var reg *registration.Resource
|
var reg *registration.Resource
|
||||||
// New users will need to register
|
// New users will need to register
|
||||||
if client.GetExternalAccountRequired() {
|
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 KID and HmacEncoded is overidden
|
||||||
|
|
||||||
if !a.Database.TableExists("acme") {
|
if !a.Database.TableExists("acme") {
|
||||||
@ -220,20 +265,18 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
var kid string
|
var kid string
|
||||||
var hmacEncoded string
|
var hmacEncoded string
|
||||||
err := a.Database.Read("acme", config.CADirURL+"_kid", &kid)
|
err := a.Database.Read("acme", config.CADirURL+"_kid", &kid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Failed to read kid from database", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.Database.Read("acme", config.CADirURL+"_hmacEncoded", &hmacEncoded)
|
err = a.Database.Read("acme", config.CADirURL+"_hmacEncoded", &hmacEncoded)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Failed to read HMAC from database", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("EAB Credential retrieved.", kid, hmacEncoded)
|
a.Logf("EAB Credential retrieved: "+kid+" / "+hmacEncoded, nil)
|
||||||
if kid != "" && hmacEncoded != "" {
|
if kid != "" && hmacEncoded != "" {
|
||||||
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||||
TermsOfServiceAgreed: true,
|
TermsOfServiceAgreed: true,
|
||||||
@ -242,14 +285,14 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Register with external account binder failed", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
//return false, errors.New("External Account Required for this ACME Provider.")
|
//return false, errors.New("External Account Required for this ACME Provider.")
|
||||||
} else {
|
} else {
|
||||||
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Unable to register client", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,7 +305,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
}
|
}
|
||||||
certificates, err := client.Certificate.Obtain(request)
|
certificates, err := client.Certificate.Obtain(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Obtain certificate failed", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,32 +313,34 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
// private key, and a certificate URL.
|
// private key, and a certificate URL.
|
||||||
err = os.WriteFile("./conf/certs/"+certificateName+".pem", certificates.Certificate, 0777)
|
err = os.WriteFile("./conf/certs/"+certificateName+".pem", certificates.Certificate, 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Failed to write public key to disk", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
err = os.WriteFile("./conf/certs/"+certificateName+".key", certificates.PrivateKey, 0777)
|
err = os.WriteFile("./conf/certs/"+certificateName+".key", certificates.PrivateKey, 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Failed to write private key to disk", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save certificate's ACME info for renew usage
|
// Save certificate's ACME info for renew usage
|
||||||
certInfo := &CertificateInfoJSON{
|
certInfo = &CertificateInfoJSON{
|
||||||
AcmeName: caName,
|
AcmeName: caName,
|
||||||
AcmeUrl: caUrl,
|
AcmeUrl: caUrl,
|
||||||
SkipTLS: skipTLS,
|
SkipTLS: skipTLS,
|
||||||
UseDNS: useDNS,
|
UseDNS: useDNS,
|
||||||
|
PropTimeout: propagationTimeout,
|
||||||
|
DNSServers: dnsNameservers,
|
||||||
}
|
}
|
||||||
|
|
||||||
certInfoBytes, err := json.Marshal(certInfo)
|
certInfoBytes, err := json.Marshal(certInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Marshal certificate renew config failed", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile("./conf/certs/"+certificateName+".json", certInfoBytes, 0777)
|
err = os.WriteFile("./conf/certs/"+certificateName+".json", certInfoBytes, 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Failed to write certificate renew config to file", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +358,7 @@ func (a *ACMEHandler) CheckCertificate() []string {
|
|||||||
expiredCerts := []string{}
|
expiredCerts := []string{}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
a.Logf("Failed to load certificate folder", err)
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,14 +455,14 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
ca, err := utils.PostPara(r, "ca")
|
ca, err := utils.PostPara(r, "ca")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("[INFO] CA not set. Using default")
|
a.Logf("CA not set. Using default", nil)
|
||||||
ca, caUrl = "", ""
|
ca, caUrl = "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if ca == "custom" {
|
if ca == "custom" {
|
||||||
caUrl, err = utils.PostPara(r, "caURL")
|
caUrl, err = utils.PostPara(r, "caURL")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("[INFO] Custom CA set but no URL provide, Using default")
|
a.Logf("Custom CA set but no URL provide, Using default", nil)
|
||||||
ca, caUrl = "", ""
|
ca, caUrl = "", ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -448,12 +493,44 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
domains := strings.Split(domainPara, ",")
|
domains := strings.Split(domainPara, ",")
|
||||||
|
|
||||||
|
// Default propagation timeout is 300 seconds
|
||||||
|
propagationTimeout := 300
|
||||||
|
if dns {
|
||||||
|
ppgTimeout, err := utils.PostPara(r, "ppgTimeout")
|
||||||
|
if err == nil {
|
||||||
|
propagationTimeout, err = strconv.Atoi(ppgTimeout)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Invalid propagation timeout value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if propagationTimeout < 60 {
|
||||||
|
//Minimum propagation timeout is 60 seconds
|
||||||
|
propagationTimeout = 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Clean spaces in front or behind each domain
|
//Clean spaces in front or behind each domain
|
||||||
cleanedDomains := []string{}
|
cleanedDomains := []string{}
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
cleanedDomains = append(cleanedDomains, strings.TrimSpace(domain))
|
cleanedDomains = append(cleanedDomains, strings.TrimSpace(domain))
|
||||||
}
|
}
|
||||||
result, err := a.ObtainCert(cleanedDomains, filename, email, ca, caUrl, skipTLS, dns)
|
|
||||||
|
// Extract DNS servers from the request
|
||||||
|
var dnsServers []string
|
||||||
|
dnsServersPara, err := utils.PostPara(r, "dnsServers")
|
||||||
|
if err == nil && dnsServersPara != "" {
|
||||||
|
dnsServers = strings.Split(dnsServersPara, ",")
|
||||||
|
for i := range dnsServers {
|
||||||
|
dnsServers[i] = strings.TrimSpace(dnsServers[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert DNS servers slice to a single string
|
||||||
|
dnsServersString := strings.Join(dnsServers, ",")
|
||||||
|
|
||||||
|
result, err := a.ObtainCert(cleanedDomains, filename, email, ca, caUrl, skipTLS, dns, propagationTimeout, dnsServersString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||||
return
|
return
|
||||||
@ -465,7 +542,7 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
func jsonEscape(i string) string {
|
func jsonEscape(i string) string {
|
||||||
b, err := json.Marshal(i)
|
b, err := json.Marshal(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Unable to escape json data: " + err.Error())
|
//log.Println("Unable to escape json data: " + err.Error())
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
s := string(b)
|
s := string(b)
|
||||||
@ -496,5 +573,10 @@ func LoadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean DNS servers
|
||||||
|
for i := range certInfo.DNSServers {
|
||||||
|
certInfo.DNSServers[i] = strings.TrimSpace(certInfo.DNSServers[i])
|
||||||
|
}
|
||||||
|
|
||||||
return certInfo, nil
|
return certInfo, nil
|
||||||
}
|
}
|
||||||
|
@ -1,70 +1,56 @@
|
|||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
"imuslab.com/zoraxy/mod/acme/acmedns"
|
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetDnsChallengeProviderByName(dnsProvider string, dnsCredentials string) (challenge.Provider, error) {
|
// Preprocessor function to get DNS challenge provider by name
|
||||||
|
func GetDnsChallengeProviderByName(dnsProvider string, dnsCredentials string, ppgTimeout int) (challenge.Provider, error) {
|
||||||
//Original Implementation
|
//Unpack the dnsCredentials (json string) to map
|
||||||
/*credentials, err := extractDnsCredentials(dnsCredentials)
|
var dnsCredentialsMap map[string]interface{}
|
||||||
|
err := json.Unmarshal([]byte(dnsCredentials), &dnsCredentialsMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
setCredentialsIntoEnvironmentVariables(credentials)
|
|
||||||
|
|
||||||
provider, err := dns.NewDNSChallengeProviderByName(dnsProvider)
|
//Clear the PollingInterval and PropagationTimeout field and conert to int
|
||||||
*/
|
userDefinedPollingInterval := 2
|
||||||
|
if dnsCredentialsMap["PollingInterval"] != nil {
|
||||||
//New implementation using acmedns CICD pipeline generated datatype
|
userDefinedPollingIntervalRaw := dnsCredentialsMap["PollingInterval"].(string)
|
||||||
return acmedns.GetDNSProviderByJsonConfig(dnsProvider, dnsCredentials)
|
delete(dnsCredentialsMap, "PollingInterval")
|
||||||
}
|
convertedPollingInterval, err := strconv.Atoi(userDefinedPollingIntervalRaw)
|
||||||
|
if err == nil {
|
||||||
/*
|
userDefinedPollingInterval = convertedPollingInterval
|
||||||
Original implementation of DNS ACME using OS.Env as payload
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
func setCredentialsIntoEnvironmentVariables(credentials map[string]string) {
|
|
||||||
for key, value := range credentials {
|
|
||||||
err := os.Setenv(key, value)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("[ERR] Failed to set environment variable %s: %v", key, err)
|
|
||||||
} else {
|
|
||||||
log.Println("[INFO] Environment variable %s set successfully", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func extractDnsCredentials(input string) (map[string]string, error) {
|
|
||||||
result := make(map[string]string)
|
|
||||||
|
|
||||||
// Split the input string by newline character
|
|
||||||
lines := strings.Split(input, "\n")
|
|
||||||
|
|
||||||
// Iterate over each line
|
|
||||||
for _, line := range lines {
|
|
||||||
// Split the line by "=" character
|
|
||||||
//use SpliyN to make sure not to split the value if the value is base64
|
|
||||||
parts := strings.SplitN(line, "=", 1)
|
|
||||||
|
|
||||||
// Check if the line is in the correct format
|
|
||||||
if len(parts) == 2 {
|
|
||||||
key := strings.TrimSpace(parts[0])
|
|
||||||
value := strings.TrimSpace(parts[1])
|
|
||||||
|
|
||||||
// Add the key-value pair to the map
|
|
||||||
result[key] = value
|
|
||||||
|
|
||||||
if value == "" || key == "" {
|
|
||||||
//invalid config
|
|
||||||
return result, errors.New("DNS credential extract failed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
userDefinedPropagationTimeout := ppgTimeout
|
||||||
}
|
if dnsCredentialsMap["PropagationTimeout"] != nil {
|
||||||
|
userDefinedPropagationTimeoutRaw := dnsCredentialsMap["PropagationTimeout"].(string)
|
||||||
|
delete(dnsCredentialsMap, "PropagationTimeout")
|
||||||
|
convertedPropagationTimeout, err := strconv.Atoi(userDefinedPropagationTimeoutRaw)
|
||||||
|
if err == nil {
|
||||||
|
//Overwrite the default propagation timeout if it is requeted from UI
|
||||||
|
userDefinedPropagationTimeout = convertedPropagationTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*/
|
//Restructure dnsCredentials string from map
|
||||||
|
dnsCredentialsBytes, err := json.Marshal(dnsCredentialsMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dnsCredentials = string(dnsCredentialsBytes)
|
||||||
|
|
||||||
|
//Using acmedns CICD pipeline generated datatype to optain the DNS provider
|
||||||
|
return acmedns.GetDNSProviderByJsonConfig(
|
||||||
|
dnsProvider,
|
||||||
|
dnsCredentials,
|
||||||
|
int64(userDefinedPropagationTimeout),
|
||||||
|
int64(userDefinedPollingInterval),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -75,6 +75,15 @@ func HandleGuidedStepCheck(w http.ResponseWriter, r *http.Request) {
|
|||||||
httpServerReachable := isHTTPServerAvailable(domain)
|
httpServerReachable := isHTTPServerAvailable(domain)
|
||||||
js, _ := json.Marshal(httpServerReachable)
|
js, _ := json.Marshal(httpServerReachable)
|
||||||
utils.SendJSONResponse(w, string(js))
|
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 {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid step number")
|
utils.SendErrorResponse(w, "invalid step number")
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"os"
|
"os"
|
||||||
@ -12,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,6 +26,7 @@ type AutoRenewConfig struct {
|
|||||||
Email string //Email for acme
|
Email string //Email for acme
|
||||||
RenewAll bool //Renew all or selective renew with the slice below
|
RenewAll bool //Renew all or selective renew with the slice below
|
||||||
FilesToRenew []string //If RenewAll is false, renew these certificate files
|
FilesToRenew []string //If RenewAll is false, renew these certificate files
|
||||||
|
DNSServers string // DNS servers
|
||||||
}
|
}
|
||||||
|
|
||||||
type AutoRenewer struct {
|
type AutoRenewer struct {
|
||||||
@ -36,6 +37,7 @@ type AutoRenewer struct {
|
|||||||
RenewTickInterval int64
|
RenewTickInterval int64
|
||||||
EarlyRenewDays int //How many days before cert expire to renew certificate
|
EarlyRenewDays int //How many days before cert expire to renew certificate
|
||||||
TickerstopChan chan bool
|
TickerstopChan chan bool
|
||||||
|
Logger *logger.Logger //System wide logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpiredCerts struct {
|
type ExpiredCerts struct {
|
||||||
@ -45,7 +47,7 @@ type ExpiredCerts struct {
|
|||||||
|
|
||||||
// Create an auto renew agent, require config filepath and auto scan & renew interval (seconds)
|
// Create an auto renew agent, require config filepath and auto scan & renew interval (seconds)
|
||||||
// Set renew check interval to 0 for auto (1 day)
|
// Set renew check interval to 0 for auto (1 day)
|
||||||
func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64, earlyRenewDays int, AcmeHandler *ACMEHandler) (*AutoRenewer, error) {
|
func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64, earlyRenewDays int, AcmeHandler *ACMEHandler, logger *logger.Logger) (*AutoRenewer, error) {
|
||||||
if renewCheckInterval == 0 {
|
if renewCheckInterval == 0 {
|
||||||
renewCheckInterval = 86400 //1 day
|
renewCheckInterval = 86400 //1 day
|
||||||
}
|
}
|
||||||
@ -87,8 +89,12 @@ func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64,
|
|||||||
AcmeHandler: AcmeHandler,
|
AcmeHandler: AcmeHandler,
|
||||||
RenewerConfig: &renewerConfig,
|
RenewerConfig: &renewerConfig,
|
||||||
RenewTickInterval: renewCheckInterval,
|
RenewTickInterval: renewCheckInterval,
|
||||||
|
EarlyRenewDays: earlyRenewDays,
|
||||||
|
Logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thisRenewer.Logf("ACME early renew set to "+fmt.Sprint(earlyRenewDays)+" days and check interval set to "+fmt.Sprint(renewCheckInterval)+" seconds", nil)
|
||||||
|
|
||||||
if thisRenewer.RenewerConfig.Enabled {
|
if thisRenewer.RenewerConfig.Enabled {
|
||||||
//Start the renew ticker
|
//Start the renew ticker
|
||||||
thisRenewer.StartAutoRenewTicker()
|
thisRenewer.StartAutoRenewTicker()
|
||||||
@ -100,6 +106,10 @@ func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64,
|
|||||||
return &thisRenewer, nil
|
return &thisRenewer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AutoRenewer) Logf(message string, err error) {
|
||||||
|
a.Logger.PrintAndLog("cert-renew", message, err)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AutoRenewer) StartAutoRenewTicker() {
|
func (a *AutoRenewer) StartAutoRenewTicker() {
|
||||||
//Stop the previous ticker if still running
|
//Stop the previous ticker if still running
|
||||||
if a.TickerstopChan != nil {
|
if a.TickerstopChan != nil {
|
||||||
@ -118,7 +128,7 @@ func (a *AutoRenewer) StartAutoRenewTicker() {
|
|||||||
case <-done:
|
case <-done:
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
log.Println("Check and renew certificates in progress")
|
a.Logf("Check and renew certificates in progress", nil)
|
||||||
a.CheckAndRenewCertificates()
|
a.CheckAndRenewCertificates()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,12 +243,12 @@ func (a *AutoRenewer) HandleAutoRenewEnable(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
a.RenewerConfig.Enabled = true
|
a.RenewerConfig.Enabled = true
|
||||||
a.saveRenewConfigToFile()
|
a.saveRenewConfigToFile()
|
||||||
log.Println("[ACME] ACME auto renew enabled")
|
a.Logf("ACME auto renew enabled", nil)
|
||||||
a.StartAutoRenewTicker()
|
a.StartAutoRenewTicker()
|
||||||
} else {
|
} else {
|
||||||
a.RenewerConfig.Enabled = false
|
a.RenewerConfig.Enabled = false
|
||||||
a.saveRenewConfigToFile()
|
a.saveRenewConfigToFile()
|
||||||
log.Println("[ACME] ACME auto renew disabled")
|
a.Logf("ACME auto renew disabled", nil)
|
||||||
a.StopAutoRenewTicker()
|
a.StopAutoRenewTicker()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -283,7 +293,7 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
|||||||
certFolder := a.CertFolder
|
certFolder := a.CertFolder
|
||||||
files, err := os.ReadDir(certFolder)
|
files, err := os.ReadDir(certFolder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Unable to renew certificates: " + err.Error())
|
a.Logf("Read certificate store failed", err)
|
||||||
return []string{}, err
|
return []string{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,11 +309,10 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
|||||||
}
|
}
|
||||||
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
|
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
|
||||||
//This cert is expired
|
//This cert is expired
|
||||||
|
|
||||||
DNSName, err := ExtractDomains(certBytes)
|
DNSName, err := ExtractDomains(certBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Maybe self signed. Ignore this
|
//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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,11 +336,10 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
|||||||
}
|
}
|
||||||
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
|
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
|
||||||
//This cert is expired
|
//This cert is expired
|
||||||
|
|
||||||
DNSName, err := ExtractDomains(certBytes)
|
DNSName, err := ExtractDomains(certBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Maybe self signed. Ignore this
|
//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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,6 +355,7 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
|||||||
return a.renewExpiredDomains(expiredCertList)
|
return a.renewExpiredDomains(expiredCertList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close the auto renewer
|
||||||
func (a *AutoRenewer) Close() {
|
func (a *AutoRenewer) Close() {
|
||||||
if a.TickerstopChan != nil {
|
if a.TickerstopChan != nil {
|
||||||
a.TickerstopChan <- true
|
a.TickerstopChan <- true
|
||||||
@ -358,7 +367,7 @@ func (a *AutoRenewer) Close() {
|
|||||||
func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, error) {
|
func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, error) {
|
||||||
renewedCertFiles := []string{}
|
renewedCertFiles := []string{}
|
||||||
for _, expiredCert := range certs {
|
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)
|
fileName := filepath.Base(expiredCert.Filepath)
|
||||||
certName := fileName[:len(fileName)-len(filepath.Ext(fileName))]
|
certName := fileName[:len(fileName)-len(filepath.Ext(fileName))]
|
||||||
|
|
||||||
@ -366,21 +375,33 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
|||||||
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
|
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
|
||||||
certInfo, err := LoadCertInfoJSON(certInfoFilename)
|
certInfo, err := LoadCertInfoJSON(certInfoFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Renew %s certificate error, can't get the ACME detail for cert: %v, trying org section as ca", certName, err)
|
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 {
|
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{}
|
certInfo = &CertificateInfoJSON{}
|
||||||
} else {
|
} else {
|
||||||
certInfo = &CertificateInfoJSON{AcmeName: CAName}
|
certInfo = &CertificateInfoJSON{AcmeName: CAName}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS, certInfo.UseDNS)
|
//For upgrading config from older version of Zoraxy which don't have timeout
|
||||||
|
if certInfo.PropTimeout == 0 {
|
||||||
|
//Set default timeout
|
||||||
|
certInfo.PropTimeout = 300
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract DNS servers from the certificate info if available
|
||||||
|
var dnsServers string
|
||||||
|
if len(certInfo.DNSServers) > 0 {
|
||||||
|
dnsServers = strings.Join(certInfo.DNSServers, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS, certInfo.UseDNS, certInfo.PropTimeout, dnsServers)
|
||||||
if err != nil {
|
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 {
|
} 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))
|
renewedCertFiles = append(renewedCertFiles, filepath.Base(expiredCert.Filepath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -426,7 +447,7 @@ func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle update auto renew DNS configuration
|
// Handle update auto renew DNS configuration
|
||||||
func (a *AutoRenewer) HanldeSetDNS(w http.ResponseWriter, r *http.Request) {
|
func (a *AutoRenewer) HandleSetDNS(w http.ResponseWriter, r *http.Request) {
|
||||||
dnsProvider, err := utils.PostPara(r, "dnsProvider")
|
dnsProvider, err := utils.PostPara(r, "dnsProvider")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "dnsProvider not set")
|
utils.SendErrorResponse(w, "dnsProvider not set")
|
||||||
@ -445,12 +466,18 @@ func (a *AutoRenewer) HanldeSetDNS(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dnsServers, err := utils.PostPara(r, "dnsServers")
|
||||||
|
if err != nil {
|
||||||
|
dnsServers = ""
|
||||||
|
}
|
||||||
|
|
||||||
if !a.AcmeHandler.Database.TableExists("acme") {
|
if !a.AcmeHandler.Database.TableExists("acme") {
|
||||||
a.AcmeHandler.Database.NewTable("acme")
|
a.AcmeHandler.Database.NewTable("acme")
|
||||||
}
|
}
|
||||||
|
|
||||||
a.AcmeHandler.Database.Write("acme", filename+"_dns_provider", dnsProvider)
|
a.AcmeHandler.Database.Write("acme", filename+"_dns_provider", dnsProvider)
|
||||||
a.AcmeHandler.Database.Write("acme", filename+"_dns_credentials", dnsCredentials)
|
a.AcmeHandler.Database.Write("acme", filename+"_dns_credentials", dnsCredentials)
|
||||||
|
a.AcmeHandler.Database.Write("acme", filename+"_dns_servers", dnsServers)
|
||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ package acme
|
|||||||
/*
|
/*
|
||||||
CA.go
|
CA.go
|
||||||
|
|
||||||
This script load CA defination from embedded ca.json
|
This script load CA definition from embedded ca.json
|
||||||
*/
|
*/
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
@ -13,7 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CA Defination, load from embeded json when startup
|
// CA definition, load from embeded json when startup
|
||||||
type CaDef struct {
|
type CaDef struct {
|
||||||
Production map[string]string
|
Production map[string]string
|
||||||
Test map[string]string
|
Test map[string]string
|
||||||
|
@ -5,14 +5,14 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get the issuer name from pem file
|
// Get the issuer name from pem file
|
||||||
func ExtractIssuerNameFromPEM(pemFilePath string) (string, error) {
|
func ExtractIssuerNameFromPEM(pemFilePath string) (string, error) {
|
||||||
// Read the PEM file
|
// Read the PEM file
|
||||||
pemData, err := ioutil.ReadFile(pemFilePath)
|
pemData, err := os.ReadFile(pemFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -210,8 +210,8 @@ func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
|
|||||||
}
|
}
|
||||||
session.Values["authenticated"] = false
|
session.Values["authenticated"] = false
|
||||||
session.Values["username"] = nil
|
session.Values["username"] = nil
|
||||||
session.Save(r, w)
|
session.Options.MaxAge = -1
|
||||||
return nil
|
return session.Save(r, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current session username from request
|
// Get the current session username from request
|
||||||
@ -339,6 +339,7 @@ func (a *AuthAgent) CheckAuth(r *http.Request) bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user is authenticated
|
// Check if user is authenticated
|
||||||
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
|
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
|
||||||
return false
|
return false
|
||||||
|
136
src/mod/auth/sso/authelia/authelia.go
Normal file
136
src/mod/auth/sso/authelia/authelia.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package authelia
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AutheliaRouterOptions struct {
|
||||||
|
UseHTTPS bool //If the Authelia server is using HTTPS
|
||||||
|
AutheliaURL string //The URL of the Authelia server
|
||||||
|
Logger *logger.Logger
|
||||||
|
Database *database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
type AutheliaRouter struct {
|
||||||
|
options *AutheliaRouterOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAutheliaRouter creates a new AutheliaRouter object
|
||||||
|
func NewAutheliaRouter(options *AutheliaRouterOptions) *AutheliaRouter {
|
||||||
|
options.Database.NewTable("authelia")
|
||||||
|
|
||||||
|
//Read settings from database, if exists
|
||||||
|
options.Database.Read("authelia", "autheliaURL", &options.AutheliaURL)
|
||||||
|
options.Database.Read("authelia", "useHTTPS", &options.UseHTTPS)
|
||||||
|
|
||||||
|
return &AutheliaRouter{
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleSetAutheliaURLAndHTTPS is the internal handler for setting the Authelia URL and HTTPS
|
||||||
|
func (ar *AutheliaRouter) HandleSetAutheliaURLAndHTTPS(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
//Return the current settings
|
||||||
|
js, _ := json.Marshal(map[string]interface{}{
|
||||||
|
"useHTTPS": ar.options.UseHTTPS,
|
||||||
|
"autheliaURL": ar.options.AutheliaURL,
|
||||||
|
})
|
||||||
|
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
} else if r.Method == http.MethodPost {
|
||||||
|
//Update the settings
|
||||||
|
autheliaURL, err := utils.PostPara(r, "autheliaURL")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "autheliaURL not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
useHTTPS, err := utils.PostBool(r, "useHTTPS")
|
||||||
|
if err != nil {
|
||||||
|
useHTTPS = false
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write changes to runtime
|
||||||
|
ar.options.AutheliaURL = autheliaURL
|
||||||
|
ar.options.UseHTTPS = useHTTPS
|
||||||
|
|
||||||
|
//Write changes to database
|
||||||
|
ar.options.Database.Write("authelia", "autheliaURL", autheliaURL)
|
||||||
|
ar.options.Database.Write("authelia", "useHTTPS", useHTTPS)
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleAutheliaAuth is the internal handler for Authelia authentication
|
||||||
|
// Set useHTTPS to true if your authelia server is using HTTPS
|
||||||
|
// Set autheliaURL to the URL of the Authelia server, e.g. authelia.example.com
|
||||||
|
func (ar *AutheliaRouter) HandleAutheliaAuth(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
if ar.options.AutheliaURL == "" {
|
||||||
|
ar.options.Logger.PrintAndLog("Authelia", "Authelia URL not set", nil)
|
||||||
|
w.WriteHeader(500)
|
||||||
|
w.Write([]byte("500 - Internal Server Error"))
|
||||||
|
return errors.New("authelia URL not set")
|
||||||
|
}
|
||||||
|
protocol := "http"
|
||||||
|
if ar.options.UseHTTPS {
|
||||||
|
protocol = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
autheliaBaseURL := protocol + "://" + ar.options.AutheliaURL
|
||||||
|
//Remove tailing slash if any
|
||||||
|
if autheliaBaseURL[len(autheliaBaseURL)-1] == '/' {
|
||||||
|
autheliaBaseURL = autheliaBaseURL[:len(autheliaBaseURL)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
//Make a request to Authelia to verify the request
|
||||||
|
req, err := http.NewRequest("POST", autheliaBaseURL+"/api/verify", nil)
|
||||||
|
if err != nil {
|
||||||
|
ar.options.Logger.PrintAndLog("Authelia", "Unable to create request", err)
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme := "http"
|
||||||
|
if r.TLS != nil {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
req.Header.Add("X-Original-URL", fmt.Sprintf("%s://%s", scheme, r.Host))
|
||||||
|
|
||||||
|
// Copy cookies from the incoming request
|
||||||
|
for _, cookie := range r.Cookies() {
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Making the verification request
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
ar.options.Logger.PrintAndLog("Authelia", "Unable to verify", err)
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
redirectURL := autheliaBaseURL + "/?rd=" + url.QueryEscape(scheme+"://"+r.Host+r.URL.String()) + "&rm=" + r.Method
|
||||||
|
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -9,17 +9,39 @@ package database
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"log"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/database/dbinc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
Db interface{} //This will be nil on openwrt and *bolt.DB in the rest of the systems
|
Db interface{} //This will be nil on openwrt, leveldb.DB on x64 platforms or bolt.DB on other platforms
|
||||||
Tables sync.Map
|
BackendType dbinc.BackendType
|
||||||
ReadOnly bool
|
Backend dbinc.Backend
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDatabase(dbfile string, readOnlyMode bool) (*Database, error) {
|
func NewDatabase(dbfile string, backendType dbinc.BackendType) (*Database, error) {
|
||||||
return newDatabase(dbfile, readOnlyMode)
|
if runtime.GOARCH == "riscv64" {
|
||||||
|
log.Println("RISCV hardware detected, ignoring the backend type and using FS emulated database")
|
||||||
|
}
|
||||||
|
return newDatabase(dbfile, backendType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the recommended backend type for the current system
|
||||||
|
func GetRecommendedBackendType() dbinc.BackendType {
|
||||||
|
//Check if the system is running on RISCV hardware
|
||||||
|
if runtime.GOARCH == "riscv64" {
|
||||||
|
//RISCV hardware, currently only support FS emulated database
|
||||||
|
return dbinc.BackendFSOnly
|
||||||
|
} else if runtime.GOOS == "windows" || (runtime.GOOS == "linux" && runtime.GOARCH == "amd64") {
|
||||||
|
//Powerful hardware
|
||||||
|
return dbinc.BackendBoltDB
|
||||||
|
//return dbinc.BackendLevelDB
|
||||||
|
}
|
||||||
|
|
||||||
|
//Default to BoltDB, the safest option
|
||||||
|
return dbinc.BackendBoltDB
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -29,39 +51,33 @@ func NewDatabase(dbfile string, readOnlyMode bool) (*Database, error) {
|
|||||||
err := sysdb.DropTable("MyTable")
|
err := sysdb.DropTable("MyTable")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (d *Database) UpdateReadWriteMode(readOnly bool) {
|
// Create a new table
|
||||||
d.ReadOnly = readOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
//Dump the whole db into a log file
|
|
||||||
func (d *Database) Dump(filename string) ([]string, error) {
|
|
||||||
return d.dump(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create a new table
|
|
||||||
func (d *Database) NewTable(tableName string) error {
|
func (d *Database) NewTable(tableName string) error {
|
||||||
return d.newTable(tableName)
|
return d.newTable(tableName)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check is table exists
|
// Check is table exists
|
||||||
func (d *Database) TableExists(tableName string) bool {
|
func (d *Database) TableExists(tableName string) bool {
|
||||||
return d.tableExists(tableName)
|
return d.tableExists(tableName)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Drop the given table
|
// Drop the given table
|
||||||
func (d *Database) DropTable(tableName string) error {
|
func (d *Database) DropTable(tableName string) error {
|
||||||
return d.dropTable(tableName)
|
return d.dropTable(tableName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Write to database with given tablename and key. Example Usage:
|
Write to database with given tablename and key. Example Usage:
|
||||||
|
|
||||||
type demo struct{
|
type demo struct{
|
||||||
content string
|
content string
|
||||||
}
|
}
|
||||||
|
|
||||||
thisDemo := demo{
|
thisDemo := demo{
|
||||||
content: "Hello World",
|
content: "Hello World",
|
||||||
}
|
}
|
||||||
err := sysdb.Write("MyTable", "username/message",thisDemo);
|
|
||||||
|
err := sysdb.Write("MyTable", "username/message",thisDemo);
|
||||||
*/
|
*/
|
||||||
func (d *Database) Write(tableName string, key string, value interface{}) error {
|
func (d *Database) Write(tableName string, key string, value interface{}) error {
|
||||||
return d.write(tableName, key, value)
|
return d.write(tableName, key, value)
|
||||||
@ -81,14 +97,21 @@ func (d *Database) Read(tableName string, key string, assignee interface{}) erro
|
|||||||
return d.read(tableName, key, assignee)
|
return d.read(tableName, key, assignee)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if a key exists in the database table given tablename and key
|
||||||
|
|
||||||
|
if sysdb.KeyExists("MyTable", "username/message"){
|
||||||
|
log.Println("Key exists")
|
||||||
|
}
|
||||||
|
*/
|
||||||
func (d *Database) KeyExists(tableName string, key string) bool {
|
func (d *Database) KeyExists(tableName string, key string) bool {
|
||||||
return d.keyExists(tableName, key)
|
return d.keyExists(tableName, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Delete a value from the database table given tablename and key
|
Delete a value from the database table given tablename and key
|
||||||
|
|
||||||
err := sysdb.Delete("MyTable", "username/message");
|
err := sysdb.Delete("MyTable", "username/message");
|
||||||
*/
|
*/
|
||||||
func (d *Database) Delete(tableName string, key string) error {
|
func (d *Database) Delete(tableName string, key string) error {
|
||||||
return d.delete(tableName, key)
|
return d.delete(tableName, key)
|
||||||
@ -115,6 +138,9 @@ func (d *Database) ListTable(tableName string) ([][][]byte, error) {
|
|||||||
return d.listTable(tableName)
|
return d.listTable(tableName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Close the database connection
|
||||||
|
*/
|
||||||
func (d *Database) Close() {
|
func (d *Database) Close() {
|
||||||
d.close()
|
d.close()
|
||||||
}
|
}
|
||||||
|
@ -4,183 +4,67 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"imuslab.com/zoraxy/mod/database/dbbolt"
|
||||||
|
"imuslab.com/zoraxy/mod/database/dbinc"
|
||||||
|
"imuslab.com/zoraxy/mod/database/dbleveldb"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDatabase(dbfile string, readOnlyMode bool) (*Database, error) {
|
func newDatabase(dbfile string, backendType dbinc.BackendType) (*Database, error) {
|
||||||
db, err := bolt.Open(dbfile, 0600, nil)
|
if backendType == dbinc.BackendFSOnly {
|
||||||
if err != nil {
|
return nil, errors.New("Unsupported backend type for this platform")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tableMap := sync.Map{}
|
if backendType == dbinc.BackendLevelDB {
|
||||||
//Build the table list from database
|
db, err := dbleveldb.NewDB(dbfile)
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
return &Database{
|
||||||
return tx.ForEach(func(name []byte, _ *bolt.Bucket) error {
|
Db: nil,
|
||||||
tableMap.Store(string(name), "")
|
BackendType: backendType,
|
||||||
return nil
|
Backend: db,
|
||||||
})
|
}, err
|
||||||
})
|
}
|
||||||
|
|
||||||
|
db, err := dbbolt.NewBoltDatabase(dbfile)
|
||||||
return &Database{
|
return &Database{
|
||||||
Db: db,
|
Db: nil,
|
||||||
Tables: tableMap,
|
BackendType: backendType,
|
||||||
ReadOnly: readOnlyMode,
|
Backend: db,
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//Dump the whole db into a log file
|
|
||||||
func (d *Database) dump(filename string) ([]string, error) {
|
|
||||||
results := []string{}
|
|
||||||
|
|
||||||
d.Tables.Range(func(tableName, v interface{}) bool {
|
|
||||||
entries, err := d.ListTable(tableName.(string))
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Reading table " + tableName.(string) + " failed: " + err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, keypairs := range entries {
|
|
||||||
results = append(results, string(keypairs[0])+":"+string(keypairs[1])+"\n")
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create a new table
|
|
||||||
func (d *Database) newTable(tableName string) error {
|
func (d *Database) newTable(tableName string) error {
|
||||||
if d.ReadOnly == true {
|
return d.Backend.NewTable(tableName)
|
||||||
return errors.New("Operation rejected in ReadOnly mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
|
|
||||||
_, err := tx.CreateBucketIfNotExists([]byte(tableName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
d.Tables.Store(tableName, "")
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check is table exists
|
|
||||||
func (d *Database) tableExists(tableName string) bool {
|
func (d *Database) tableExists(tableName string) bool {
|
||||||
if _, ok := d.Tables.Load(tableName); ok {
|
return d.Backend.TableExists(tableName)
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Drop the given table
|
|
||||||
func (d *Database) dropTable(tableName string) error {
|
func (d *Database) dropTable(tableName string) error {
|
||||||
if d.ReadOnly == true {
|
return d.Backend.DropTable(tableName)
|
||||||
return errors.New("Operation rejected in ReadOnly mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
|
|
||||||
err := tx.DeleteBucket([]byte(tableName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Write to table
|
|
||||||
func (d *Database) write(tableName string, key string, value interface{}) error {
|
func (d *Database) write(tableName string, key string, value interface{}) error {
|
||||||
if d.ReadOnly {
|
return d.Backend.Write(tableName, key, value)
|
||||||
return errors.New("Operation rejected in ReadOnly mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonString, err := json.Marshal(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
|
|
||||||
_, err := tx.CreateBucketIfNotExists([]byte(tableName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b := tx.Bucket([]byte(tableName))
|
|
||||||
err = b.Put([]byte(key), jsonString)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) read(tableName string, key string, assignee interface{}) error {
|
func (d *Database) read(tableName string, key string, assignee interface{}) error {
|
||||||
err := d.Db.(*bolt.DB).View(func(tx *bolt.Tx) error {
|
return d.Backend.Read(tableName, key, assignee)
|
||||||
b := tx.Bucket([]byte(tableName))
|
|
||||||
v := b.Get([]byte(key))
|
|
||||||
json.Unmarshal(v, &assignee)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) keyExists(tableName string, key string) bool {
|
func (d *Database) keyExists(tableName string, key string) bool {
|
||||||
resultIsNil := false
|
return d.Backend.KeyExists(tableName, key)
|
||||||
if !d.TableExists(tableName) {
|
|
||||||
//Table not exists. Do not proceed accessing key
|
|
||||||
log.Println("[DB] ERROR: Requesting key from table that didn't exist!!!")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
err := d.Db.(*bolt.DB).View(func(tx *bolt.Tx) error {
|
|
||||||
b := tx.Bucket([]byte(tableName))
|
|
||||||
v := b.Get([]byte(key))
|
|
||||||
if v == nil {
|
|
||||||
resultIsNil = true
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
if resultIsNil {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) delete(tableName string, key string) error {
|
func (d *Database) delete(tableName string, key string) error {
|
||||||
if d.ReadOnly {
|
return d.Backend.Delete(tableName, key)
|
||||||
return errors.New("Operation rejected in ReadOnly mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
|
|
||||||
tx.Bucket([]byte(tableName)).Delete([]byte(key))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) listTable(tableName string) ([][][]byte, error) {
|
func (d *Database) listTable(tableName string) ([][][]byte, error) {
|
||||||
var results [][][]byte
|
return d.Backend.ListTable(tableName)
|
||||||
err := d.Db.(*bolt.DB).View(func(tx *bolt.Tx) error {
|
|
||||||
b := tx.Bucket([]byte(tableName))
|
|
||||||
c := b.Cursor()
|
|
||||||
|
|
||||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
|
||||||
results = append(results, [][]byte{k, v})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return results, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) close() {
|
func (d *Database) close() {
|
||||||
d.Db.(*bolt.DB).Close()
|
d.Backend.Close()
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,19 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
"imuslab.com/zoraxy/mod/database/dbinc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDatabase(dbfile string, readOnlyMode bool) (*Database, error) {
|
/*
|
||||||
|
OpenWRT or RISCV backend
|
||||||
|
|
||||||
|
For OpenWRT or RISCV platform, we will use the filesystem as the database backend
|
||||||
|
as boltdb or leveldb is not supported on these platforms, including boltDB and LevelDB
|
||||||
|
in conditional compilation will create a build error on these platforms
|
||||||
|
*/
|
||||||
|
|
||||||
|
func newDatabase(dbfile string, backendType dbinc.BackendType) (*Database, error) {
|
||||||
dbRootPath := filepath.ToSlash(filepath.Clean(dbfile))
|
dbRootPath := filepath.ToSlash(filepath.Clean(dbfile))
|
||||||
dbRootPath = "fsdb/" + dbRootPath
|
dbRootPath = "fsdb/" + dbRootPath
|
||||||
err := os.MkdirAll(dbRootPath, 0755)
|
err := os.MkdirAll(dbRootPath, 0755)
|
||||||
@ -21,24 +30,11 @@ func newDatabase(dbfile string, readOnlyMode bool) (*Database, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tableMap := sync.Map{}
|
|
||||||
//build the table list from file system
|
|
||||||
files, err := filepath.Glob(filepath.Join(dbRootPath, "/*"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
if isDirectory(file) {
|
|
||||||
tableMap.Store(filepath.Base(file), "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Filesystem Emulated Key-value Database Service Started: " + dbRootPath)
|
log.Println("Filesystem Emulated Key-value Database Service Started: " + dbRootPath)
|
||||||
return &Database{
|
return &Database{
|
||||||
Db: dbRootPath,
|
Db: dbRootPath,
|
||||||
Tables: tableMap,
|
BackendType: dbinc.BackendFSOnly,
|
||||||
ReadOnly: readOnlyMode,
|
Backend: nil,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,9 +57,7 @@ func (d *Database) dump(filename string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) newTable(tableName string) error {
|
func (d *Database) newTable(tableName string) error {
|
||||||
if d.ReadOnly {
|
|
||||||
return errors.New("Operation rejected in ReadOnly mode")
|
|
||||||
}
|
|
||||||
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
|
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
|
||||||
if !fileExists(tablePath) {
|
if !fileExists(tablePath) {
|
||||||
return os.MkdirAll(tablePath, 0755)
|
return os.MkdirAll(tablePath, 0755)
|
||||||
@ -85,9 +79,7 @@ func (d *Database) tableExists(tableName string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) dropTable(tableName string) error {
|
func (d *Database) dropTable(tableName string) error {
|
||||||
if d.ReadOnly {
|
|
||||||
return errors.New("Operation rejected in ReadOnly mode")
|
|
||||||
}
|
|
||||||
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
|
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
|
||||||
if d.tableExists(tableName) {
|
if d.tableExists(tableName) {
|
||||||
return os.RemoveAll(tablePath)
|
return os.RemoveAll(tablePath)
|
||||||
@ -98,9 +90,7 @@ func (d *Database) dropTable(tableName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) write(tableName string, key string, value interface{}) error {
|
func (d *Database) write(tableName string, key string, value interface{}) error {
|
||||||
if d.ReadOnly {
|
|
||||||
return errors.New("Operation rejected in ReadOnly mode")
|
|
||||||
}
|
|
||||||
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
|
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
|
||||||
js, err := json.Marshal(value)
|
js, err := json.Marshal(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -138,9 +128,7 @@ func (d *Database) keyExists(tableName string, key string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) delete(tableName string, key string) error {
|
func (d *Database) delete(tableName string, key string) error {
|
||||||
if d.ReadOnly {
|
|
||||||
return errors.New("Operation rejected in ReadOnly mode")
|
|
||||||
}
|
|
||||||
if !d.keyExists(tableName, key) {
|
if !d.keyExists(tableName, key) {
|
||||||
return errors.New("key not exists")
|
return errors.New("key not exists")
|
||||||
}
|
}
|
||||||
|
141
src/mod/database/dbbolt/dbbolt.go
Normal file
141
src/mod/database/dbbolt/dbbolt.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package dbbolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/boltdb/bolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
|
Db interface{} //This is the bolt database object
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBoltDatabase(dbfile string) (*Database, error) {
|
||||||
|
db, err := bolt.Open(dbfile, 0600, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Database{
|
||||||
|
Db: db,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new table
|
||||||
|
func (d *Database) NewTable(tableName string) error {
|
||||||
|
err := d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
|
||||||
|
_, err := tx.CreateBucketIfNotExists([]byte(tableName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check is table exists
|
||||||
|
func (d *Database) TableExists(tableName string) bool {
|
||||||
|
return d.Db.(*bolt.DB).View(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte(tableName))
|
||||||
|
if b == nil {
|
||||||
|
return errors.New("table not exists")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the given table
|
||||||
|
func (d *Database) DropTable(tableName string) error {
|
||||||
|
err := d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
|
||||||
|
err := tx.DeleteBucket([]byte(tableName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to table
|
||||||
|
func (d *Database) Write(tableName string, key string, value interface{}) error {
|
||||||
|
jsonString, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
|
||||||
|
_, err := tx.CreateBucketIfNotExists([]byte(tableName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b := tx.Bucket([]byte(tableName))
|
||||||
|
err = b.Put([]byte(key), jsonString)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) Read(tableName string, key string, assignee interface{}) error {
|
||||||
|
err := d.Db.(*bolt.DB).View(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte(tableName))
|
||||||
|
v := b.Get([]byte(key))
|
||||||
|
json.Unmarshal(v, &assignee)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) KeyExists(tableName string, key string) bool {
|
||||||
|
resultIsNil := false
|
||||||
|
if !d.TableExists(tableName) {
|
||||||
|
//Table not exists. Do not proceed accessing key
|
||||||
|
//log.Println("[DB] ERROR: Requesting key from table that didn't exist!!!")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
err := d.Db.(*bolt.DB).View(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte(tableName))
|
||||||
|
v := b.Get([]byte(key))
|
||||||
|
if v == nil {
|
||||||
|
resultIsNil = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
if resultIsNil {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) Delete(tableName string, key string) error {
|
||||||
|
err := d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
|
||||||
|
tx.Bucket([]byte(tableName)).Delete([]byte(key))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) ListTable(tableName string) ([][][]byte, error) {
|
||||||
|
var results [][][]byte
|
||||||
|
err := d.Db.(*bolt.DB).View(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte(tableName))
|
||||||
|
c := b.Cursor()
|
||||||
|
|
||||||
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||||
|
results = append(results, [][]byte{k, v})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) Close() {
|
||||||
|
d.Db.(*bolt.DB).Close()
|
||||||
|
}
|
67
src/mod/database/dbbolt/dbbolt_test.go
Normal file
67
src/mod/database/dbbolt/dbbolt_test.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package dbbolt_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/database/dbbolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewBoltDatabase(t *testing.T) {
|
||||||
|
dbfile := "test.db"
|
||||||
|
defer os.Remove(dbfile)
|
||||||
|
|
||||||
|
db, err := dbbolt.NewBoltDatabase(dbfile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new Bolt database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if db.Db == nil {
|
||||||
|
t.Fatalf("Expected non-nil database object")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTable(t *testing.T) {
|
||||||
|
dbfile := "test.db"
|
||||||
|
defer os.Remove(dbfile)
|
||||||
|
|
||||||
|
db, err := dbbolt.NewBoltDatabase(dbfile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new Bolt database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
err = db.NewTable("testTable")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new table: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTableExists(t *testing.T) {
|
||||||
|
dbfile := "test.db"
|
||||||
|
defer os.Remove(dbfile)
|
||||||
|
|
||||||
|
db, err := dbbolt.NewBoltDatabase(dbfile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new Bolt database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
tableName := "testTable"
|
||||||
|
err = db.NewTable(tableName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new table: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exists := db.TableExists(tableName)
|
||||||
|
if !exists {
|
||||||
|
t.Fatalf("Expected table %s to exist", tableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonExistentTable := "nonExistentTable"
|
||||||
|
exists = db.TableExists(nonExistentTable)
|
||||||
|
if exists {
|
||||||
|
t.Fatalf("Expected table %s to not exist", nonExistentTable)
|
||||||
|
}
|
||||||
|
}
|
39
src/mod/database/dbinc/dbinc.go
Normal file
39
src/mod/database/dbinc/dbinc.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package dbinc
|
||||||
|
|
||||||
|
/*
|
||||||
|
dbinc is the interface for all database backend
|
||||||
|
*/
|
||||||
|
type BackendType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
BackendBoltDB BackendType = iota //Default backend
|
||||||
|
BackendFSOnly //OpenWRT or RISCV backend
|
||||||
|
BackendLevelDB //LevelDB backend
|
||||||
|
|
||||||
|
BackEndAuto = BackendBoltDB
|
||||||
|
)
|
||||||
|
|
||||||
|
type Backend interface {
|
||||||
|
NewTable(tableName string) error
|
||||||
|
TableExists(tableName string) bool
|
||||||
|
DropTable(tableName string) error
|
||||||
|
Write(tableName string, key string, value interface{}) error
|
||||||
|
Read(tableName string, key string, assignee interface{}) error
|
||||||
|
KeyExists(tableName string, key string) bool
|
||||||
|
Delete(tableName string, key string) error
|
||||||
|
ListTable(tableName string) ([][][]byte, error)
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BackendType) String() string {
|
||||||
|
switch b {
|
||||||
|
case BackendBoltDB:
|
||||||
|
return "BoltDB"
|
||||||
|
case BackendFSOnly:
|
||||||
|
return "File System Emulated Key-Value Store"
|
||||||
|
case BackendLevelDB:
|
||||||
|
return "LevelDB"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
152
src/mod/database/dbleveldb/dbleveldb.go
Normal file
152
src/mod/database/dbleveldb/dbleveldb.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package dbleveldb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
|
"github.com/syndtr/goleveldb/leveldb/util"
|
||||||
|
"imuslab.com/zoraxy/mod/database/dbinc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure the DB struct implements the Backend interface
|
||||||
|
var _ dbinc.Backend = (*DB)(nil)
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
db *leveldb.DB
|
||||||
|
Table sync.Map //For emulating table creation
|
||||||
|
batch leveldb.Batch //Batch write
|
||||||
|
writeFlushTicker *time.Ticker //Ticker for flushing data into disk
|
||||||
|
writeFlushStop chan bool //Stop channel for write flush ticker
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDB(path string) (*DB, error) {
|
||||||
|
//If the path is not a directory (e.g. /tmp/dbfile.db), convert the filename to directory
|
||||||
|
if filepath.Ext(path) != "" {
|
||||||
|
path = strings.ReplaceAll(path, ".", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := leveldb.OpenFile(path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
thisDB := &DB{
|
||||||
|
db: db,
|
||||||
|
Table: sync.Map{},
|
||||||
|
batch: leveldb.Batch{},
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create a ticker to flush data into disk every 1 seconds
|
||||||
|
writeFlushTicker := time.NewTicker(1 * time.Second)
|
||||||
|
writeFlushStop := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-writeFlushTicker.C:
|
||||||
|
if thisDB.batch.Len() == 0 {
|
||||||
|
//No flushing needed
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = db.Write(&thisDB.batch, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[LevelDB] Failed to flush data into disk: ", err)
|
||||||
|
}
|
||||||
|
thisDB.batch.Reset()
|
||||||
|
case <-writeFlushStop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
thisDB.writeFlushTicker = writeFlushTicker
|
||||||
|
thisDB.writeFlushStop = writeFlushStop
|
||||||
|
|
||||||
|
return thisDB, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) NewTable(tableName string) error {
|
||||||
|
//Create a table entry in the sync.Map
|
||||||
|
d.Table.Store(tableName, true)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) TableExists(tableName string) bool {
|
||||||
|
_, ok := d.Table.Load(tableName)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) DropTable(tableName string) error {
|
||||||
|
d.Table.Delete(tableName)
|
||||||
|
iter := d.db.NewIterator(nil, nil)
|
||||||
|
defer iter.Release()
|
||||||
|
|
||||||
|
for iter.Next() {
|
||||||
|
key := iter.Key()
|
||||||
|
if filepath.Dir(string(key)) == tableName {
|
||||||
|
err := d.db.Delete(key, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Write(tableName string, key string, value interface{}) error {
|
||||||
|
data, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.batch.Put([]byte(filepath.ToSlash(filepath.Join(tableName, key))), data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Read(tableName string, key string, assignee interface{}) error {
|
||||||
|
data, err := d.db.Get([]byte(filepath.ToSlash(filepath.Join(tableName, key))), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, assignee)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) KeyExists(tableName string, key string) bool {
|
||||||
|
_, err := d.db.Get([]byte(filepath.ToSlash(filepath.Join(tableName, key))), nil)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Delete(tableName string, key string) error {
|
||||||
|
return d.db.Delete([]byte(filepath.ToSlash(filepath.Join(tableName, key))), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) ListTable(tableName string) ([][][]byte, error) {
|
||||||
|
iter := d.db.NewIterator(util.BytesPrefix([]byte(tableName+"/")), nil)
|
||||||
|
defer iter.Release()
|
||||||
|
|
||||||
|
var result [][][]byte
|
||||||
|
for iter.Next() {
|
||||||
|
key := iter.Key()
|
||||||
|
//The key contains the table name as prefix. Trim it before returning
|
||||||
|
value := iter.Value()
|
||||||
|
result = append(result, [][]byte{[]byte(strings.TrimPrefix(string(key), tableName+"/")), value})
|
||||||
|
}
|
||||||
|
|
||||||
|
err := iter.Error()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Close() {
|
||||||
|
//Write the remaining data in batch back into disk
|
||||||
|
d.writeFlushStop <- true
|
||||||
|
d.writeFlushTicker.Stop()
|
||||||
|
d.db.Write(&d.batch, nil)
|
||||||
|
d.db.Close()
|
||||||
|
}
|
141
src/mod/database/dbleveldb/dbleveldb_test.go
Normal file
141
src/mod/database/dbleveldb/dbleveldb_test.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package dbleveldb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/database/dbleveldb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewDB(t *testing.T) {
|
||||||
|
path := "/tmp/testdb"
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
db, err := dbleveldb.NewDB(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new DB: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTable(t *testing.T) {
|
||||||
|
path := "/tmp/testdb"
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
db, err := dbleveldb.NewDB(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new DB: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
err = db.NewTable("testTable")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new table: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTableExists(t *testing.T) {
|
||||||
|
path := "/tmp/testdb"
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
db, err := dbleveldb.NewDB(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new DB: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
db.NewTable("testTable")
|
||||||
|
if !db.TableExists("testTable") {
|
||||||
|
t.Fatalf("Table should exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDropTable(t *testing.T) {
|
||||||
|
path := "/tmp/testdb"
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
db, err := dbleveldb.NewDB(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new DB: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
db.NewTable("testTable")
|
||||||
|
err = db.DropTable("testTable")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to drop table: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if db.TableExists("testTable") {
|
||||||
|
t.Fatalf("Table should not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteAndRead(t *testing.T) {
|
||||||
|
path := "/tmp/testdb"
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
db, err := dbleveldb.NewDB(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new DB: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
db.NewTable("testTable")
|
||||||
|
err = db.Write("testTable", "testKey", "testValue")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to write to table: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var value string
|
||||||
|
err = db.Read("testTable", "testKey", &value)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read from table: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value != "testValue" {
|
||||||
|
t.Fatalf("Expected 'testValue', got '%v'", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestListTable(t *testing.T) {
|
||||||
|
path := "/tmp/testdb"
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
db, err := dbleveldb.NewDB(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new DB: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
db.NewTable("testTable")
|
||||||
|
err = db.Write("testTable", "testKey1", "testValue1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to write to table: %v", err)
|
||||||
|
}
|
||||||
|
err = db.Write("testTable", "testKey2", "testValue2")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to write to table: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := db.ListTable("testTable")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to list table: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) != 2 {
|
||||||
|
t.Fatalf("Expected 2 entries, got %v", len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := map[string]string{
|
||||||
|
"testTable/testKey1": "\"testValue1\"",
|
||||||
|
"testTable/testKey2": "\"testValue2\"",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range result {
|
||||||
|
key := string(entry[0])
|
||||||
|
value := string(entry[1])
|
||||||
|
if expected[key] != value {
|
||||||
|
t.Fatalf("Expected value '%v' for key '%v', got '%v'", expected[key], key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ import (
|
|||||||
- Blacklist
|
- Blacklist
|
||||||
- Whitelist
|
- Whitelist
|
||||||
- Rate Limitor
|
- Rate Limitor
|
||||||
|
- SSO Auth
|
||||||
- Basic Auth
|
- Basic Auth
|
||||||
- Vitrual Directory Proxy
|
- Vitrual Directory Proxy
|
||||||
- Subdomain Proxy
|
- Subdomain Proxy
|
||||||
@ -77,18 +78,16 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if sep.RequireRateLimit {
|
if sep.RequireRateLimit {
|
||||||
err := h.handleRateLimitRouting(w, r, sep)
|
err := h.handleRateLimitRouting(w, r, sep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 429)
|
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 307)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Validate basic auth
|
//Validate basic auth
|
||||||
if sep.RequireBasicAuth {
|
respWritten := handleAuthProviderRouting(sep, w, r, h)
|
||||||
err := h.handleBasicAuthRouting(w, r, sep)
|
if respWritten {
|
||||||
if err != nil {
|
//Request handled by subroute
|
||||||
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if any virtual directory rules matches
|
//Check if any virtual directory rules matches
|
||||||
@ -98,7 +97,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Virtual directory routing rule found. Route via vdir mode
|
//Virtual directory routing rule found. Route via vdir mode
|
||||||
h.vdirRequest(w, r, targetProxyEndpoint)
|
h.vdirRequest(w, r, targetProxyEndpoint)
|
||||||
return
|
return
|
||||||
} else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root {
|
} else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyTypeRoot {
|
||||||
potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
|
potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
|
||||||
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
|
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
|
||||||
//Missing tailing slash. Redirect to target proxy endpoint
|
//Missing tailing slash. Redirect to target proxy endpoint
|
||||||
@ -143,7 +142,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
/*
|
/*
|
||||||
handleRootRouting
|
handleRootRouting
|
||||||
|
|
||||||
This function handle root routing situations where there are no subdomain
|
This function handle root routing (aka default sites) situations where there are no subdomain
|
||||||
, vdir or special routing rule matches the requested URI.
|
, vdir or special routing rule matches the requested URI.
|
||||||
|
|
||||||
Once entered this routing segment, the root routing options will take over
|
Once entered this routing segment, the root routing options will take over
|
||||||
@ -163,7 +162,6 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
|||||||
fallthrough
|
fallthrough
|
||||||
case DefaultSite_ReverseProxy:
|
case DefaultSite_ReverseProxy:
|
||||||
//They both share the same behavior
|
//They both share the same behavior
|
||||||
|
|
||||||
//Check if any virtual directory rules matches
|
//Check if any virtual directory rules matches
|
||||||
proxyingPath := strings.TrimSpace(r.RequestURI)
|
proxyingPath := strings.TrimSpace(r.RequestURI)
|
||||||
targetProxyEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath)
|
targetProxyEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath)
|
||||||
@ -171,7 +169,7 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
|||||||
//Virtual directory routing rule found. Route via vdir mode
|
//Virtual directory routing rule found. Route via vdir mode
|
||||||
h.vdirRequest(w, r, targetProxyEndpoint)
|
h.vdirRequest(w, r, targetProxyEndpoint)
|
||||||
return
|
return
|
||||||
} else if !strings.HasSuffix(proxyingPath, "/") && proot.ProxyType != ProxyType_Root {
|
} else if !strings.HasSuffix(proxyingPath, "/") && proot.ProxyType != ProxyTypeRoot {
|
||||||
potentialProxtEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
|
potentialProxtEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
|
||||||
if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
|
if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
|
||||||
//Missing tailing slash. Redirect to target proxy endpoint
|
//Missing tailing slash. Redirect to target proxy endpoint
|
||||||
@ -188,8 +186,13 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
|||||||
redirectTarget = "about:blank"
|
redirectTarget = "about:blank"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check if the default site values start with http or https
|
||||||
|
if !strings.HasPrefix(redirectTarget, "http://") && !strings.HasPrefix(redirectTarget, "https://") {
|
||||||
|
redirectTarget = "http://" + redirectTarget
|
||||||
|
}
|
||||||
|
|
||||||
//Check if it is an infinite loopback redirect
|
//Check if it is an infinite loopback redirect
|
||||||
parsedURL, err := url.Parse(proot.DefaultSiteValue)
|
parsedURL, err := url.Parse(redirectTarget)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Error when parsing target. Send to root
|
//Error when parsing target. Send to root
|
||||||
h.hostRequest(w, r, h.Parent.Root)
|
h.hostRequest(w, r, h.Parent.Root)
|
||||||
@ -214,5 +217,27 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
|||||||
} else {
|
} else {
|
||||||
w.Write(template)
|
w.Write(template)
|
||||||
}
|
}
|
||||||
|
case DefaultSite_NoResponse:
|
||||||
|
//No response. Just close the connection
|
||||||
|
h.Parent.logRequest(r, false, 444, "root-noresponse", domainOnly)
|
||||||
|
hijacker, ok := w.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
w.Header().Set("Connection", "close")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, _, err := hijacker.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
w.Header().Set("Connection", "close")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
case DefaultSite_TeaPot:
|
||||||
|
//I'm a teapot
|
||||||
|
h.Parent.logRequest(r, false, 418, "root-teapot", domainOnly)
|
||||||
|
http.Error(w, "I'm a teapot", http.StatusTeapot)
|
||||||
|
default:
|
||||||
|
//Unknown routing option. Send empty response
|
||||||
|
h.Parent.logRequest(r, false, 544, "root-unknown", domainOnly)
|
||||||
|
http.Error(w, "544 - No Route Defined", 544)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package dynamicproxy
|
package dynamicproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -16,7 +15,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter,
|
|||||||
accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
|
accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Unable to load access rule. Target rule not found?
|
//Unable to load access rule. Target rule not found?
|
||||||
log.Println("[Proxy] Unable to load access rule: " + ruleID)
|
h.Parent.Option.Logger.PrintAndLog("proxy-access", "Unable to load access rule: "+ruleID, err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte("500 - Internal Server Error"))
|
w.Write([]byte("500 - Internal Server Error"))
|
||||||
return true
|
return true
|
||||||
|
108
src/mod/dynamicproxy/authProviders.go
Normal file
108
src/mod/dynamicproxy/authProviders.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package dynamicproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
authProviders.go
|
||||||
|
|
||||||
|
This script handle authentication providers
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Central Authentication Provider Router
|
||||||
|
|
||||||
|
This function will route the request to the correct authentication provider
|
||||||
|
if the return value is true, do not continue to the next handler
|
||||||
|
|
||||||
|
handleAuthProviderRouting takes in 4 parameters:
|
||||||
|
- sep: the ProxyEndpoint object
|
||||||
|
- w: the http.ResponseWriter object
|
||||||
|
- r: the http.Request object
|
||||||
|
- h: the ProxyHandler object
|
||||||
|
|
||||||
|
and return a boolean indicate if the request is written to http.ResponseWriter
|
||||||
|
- true: the request is handled, do not write to http.ResponseWriter
|
||||||
|
- false: the request is not handled (usually means auth ok), continue to the next handler
|
||||||
|
*/
|
||||||
|
func handleAuthProviderRouting(sep *ProxyEndpoint, w http.ResponseWriter, r *http.Request, h *ProxyHandler) bool {
|
||||||
|
if sep.AuthenticationProvider.AuthMethod == AuthMethodBasic {
|
||||||
|
err := h.handleBasicAuthRouting(w, r, sep)
|
||||||
|
if err != nil {
|
||||||
|
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if sep.AuthenticationProvider.AuthMethod == AuthMethodAuthelia {
|
||||||
|
err := h.handleAutheliaAuth(w, r)
|
||||||
|
if err != nil {
|
||||||
|
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//No authentication provider, do not need to handle
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Basic Auth */
|
||||||
|
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||||
|
err := handleBasicAuth(w, r, pe)
|
||||||
|
if err != nil {
|
||||||
|
h.Parent.logRequest(r, false, 401, "host", r.URL.Hostname())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle basic auth logic
|
||||||
|
// do not write to http.ResponseWriter if err return is not nil (already handled by this function)
|
||||||
|
func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||||
|
if len(pe.AuthenticationProvider.BasicAuthExceptionRules) > 0 {
|
||||||
|
//Check if the current path matches the exception rules
|
||||||
|
for _, exceptionRule := range pe.AuthenticationProvider.BasicAuthExceptionRules {
|
||||||
|
if strings.HasPrefix(r.RequestURI, exceptionRule.PathPrefix) {
|
||||||
|
//This path is excluded from basic auth
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u, p, ok := r.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for the credentials to see if there is one matching
|
||||||
|
hashedPassword := auth.Hash(p)
|
||||||
|
matchingFound := false
|
||||||
|
for _, cred := range pe.AuthenticationProvider.BasicAuthCredentials {
|
||||||
|
if u == cred.Username && hashedPassword == cred.PasswordHash {
|
||||||
|
matchingFound = true
|
||||||
|
|
||||||
|
//Set the X-Remote-User header
|
||||||
|
r.Header.Set("X-Remote-User", u)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matchingFound {
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Authelia */
|
||||||
|
|
||||||
|
// Handle authelia auth routing
|
||||||
|
func (h *ProxyHandler) handleAutheliaAuth(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return h.Parent.Option.AutheliaRouter.HandleAutheliaAuth(w, r)
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
package dynamicproxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
BasicAuth.go
|
|
||||||
|
|
||||||
This file handles the basic auth on proxy endpoints
|
|
||||||
if RequireBasicAuth is set to true
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
|
||||||
err := handleBasicAuth(w, r, pe)
|
|
||||||
if err != nil {
|
|
||||||
h.Parent.logRequest(r, false, 401, "host", r.URL.Hostname())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle basic auth logic
|
|
||||||
// do not write to http.ResponseWriter if err return is not nil (already handled by this function)
|
|
||||||
func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
|
||||||
if len(pe.BasicAuthExceptionRules) > 0 {
|
|
||||||
//Check if the current path matches the exception rules
|
|
||||||
for _, exceptionRule := range pe.BasicAuthExceptionRules {
|
|
||||||
if strings.HasPrefix(r.RequestURI, exceptionRule.PathPrefix) {
|
|
||||||
//This path is excluded from basic auth
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u, p, ok := r.BasicAuth()
|
|
||||||
if !ok {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
|
||||||
w.WriteHeader(401)
|
|
||||||
return errors.New("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check for the credentials to see if there is one matching
|
|
||||||
hashedPassword := auth.Hash(p)
|
|
||||||
matchingFound := false
|
|
||||||
for _, cred := range pe.BasicAuthCredentials {
|
|
||||||
if u == cred.Username && hashedPassword == cred.PasswordHash {
|
|
||||||
matchingFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !matchingFound {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
|
||||||
w.WriteHeader(401)
|
|
||||||
return errors.New("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,80 +1,12 @@
|
|||||||
package dynamicproxy
|
package dynamicproxy
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
CustomHeader.go
|
CustomHeader.go
|
||||||
|
|
||||||
This script handle parsing and injecting custom headers
|
This script handle parsing and injecting custom headers
|
||||||
into the dpcore routing logic
|
into the dpcore routing logic
|
||||||
|
|
||||||
|
Updates: 2024-10-26
|
||||||
|
Contents from this file has been moved to rewrite/rewrite.go
|
||||||
|
This file is kept for contributors to understand the structure
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
65
src/mod/dynamicproxy/default.go
Normal file
65
src/mod/dynamicproxy/default.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package dynamicproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Default Provider
|
||||||
|
|
||||||
|
This script provide the default options for all datatype
|
||||||
|
provided by dynamicproxy module
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// GetDefaultAuthenticationProvider return a default authentication provider
|
||||||
|
func GetDefaultAuthenticationProvider() *AuthenticationProvider {
|
||||||
|
return &AuthenticationProvider{
|
||||||
|
AuthMethod: AuthMethodNone,
|
||||||
|
BasicAuthCredentials: []*BasicAuthCredentials{},
|
||||||
|
BasicAuthExceptionRules: []*BasicAuthExceptionRule{},
|
||||||
|
BasicAuthGroupIDs: []string{},
|
||||||
|
AutheliaURL: "",
|
||||||
|
UseHTTPS: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaultHeaderRewriteRules return a default header rewrite rules
|
||||||
|
func GetDefaultHeaderRewriteRules() *HeaderRewriteRules {
|
||||||
|
return &HeaderRewriteRules{
|
||||||
|
UserDefinedHeaders: []*rewrite.UserDefinedHeader{},
|
||||||
|
RequestHostOverwrite: "",
|
||||||
|
HSTSMaxAge: 0,
|
||||||
|
EnablePermissionPolicyHeader: false,
|
||||||
|
PermissionPolicy: nil,
|
||||||
|
DisableHopByHopHeaderRemoval: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaultProxyEndpoint return a default proxy endpoint
|
||||||
|
func GetDefaultProxyEndpoint() ProxyEndpoint {
|
||||||
|
randomPrefix := uuid.New().String()
|
||||||
|
return ProxyEndpoint{
|
||||||
|
ProxyType: ProxyTypeHost,
|
||||||
|
RootOrMatchingDomain: randomPrefix + ".internal",
|
||||||
|
MatchingDomainAlias: []string{},
|
||||||
|
ActiveOrigins: []*loadbalance.Upstream{},
|
||||||
|
InactiveOrigins: []*loadbalance.Upstream{},
|
||||||
|
UseStickySession: false,
|
||||||
|
UseActiveLoadBalance: false,
|
||||||
|
Disabled: false,
|
||||||
|
BypassGlobalTLS: false,
|
||||||
|
VirtualDirectories: []*VirtualDirectoryEndpoint{},
|
||||||
|
HeaderRewriteRules: GetDefaultHeaderRewriteRules(),
|
||||||
|
EnableWebsocketCustomHeaders: false,
|
||||||
|
AuthenticationProvider: GetDefaultAuthenticationProvider(),
|
||||||
|
RequireRateLimit: false,
|
||||||
|
RateLimit: 0,
|
||||||
|
DisableUptimeMonitor: false,
|
||||||
|
AccessFilterUUID: "default",
|
||||||
|
DefaultSiteOption: DefaultSite_InternalStaticWebServer,
|
||||||
|
DefaultSiteValue: "",
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,26 @@
|
|||||||
package domainsniff
|
package domainsniff
|
||||||
|
|
||||||
|
/*
|
||||||
|
Domainsniff
|
||||||
|
|
||||||
|
This package contain codes that perform project / domain specific behavior in Zoraxy
|
||||||
|
If you want Zoraxy to handle a particular domain or open source project in a special way,
|
||||||
|
you can add the checking logic here.
|
||||||
|
|
||||||
|
*/
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Check if the domain is reachable and return err if not reachable
|
// Check if the domain is reachable and return err if not reachable
|
||||||
func DomainReachableWithError(domain string) error {
|
func DomainReachableWithError(domain string) error {
|
||||||
timeout := 1 * time.Second
|
timeout := 1 * time.Second
|
||||||
conn, err := net.DialTimeout("tcp", domain, timeout)
|
conn, err := net.DialTimeout("tcp", domain, timeout)
|
||||||
@ -17,7 +32,132 @@ func DomainReachableWithError(domain string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if domain reachable
|
// Check if a domain have TLS but it is self-signed or expired
|
||||||
|
// Return false if sniff error
|
||||||
|
func DomainIsSelfSigned(domain string) bool {
|
||||||
|
//Extract the domain from URl in case the user input the full URL
|
||||||
|
host, port, err := net.SplitHostPort(domain)
|
||||||
|
if err != nil {
|
||||||
|
host = domain
|
||||||
|
} else {
|
||||||
|
domain = host + ":" + port
|
||||||
|
}
|
||||||
|
if !strings.Contains(domain, ":") {
|
||||||
|
domain = domain + ":443"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get the certificate
|
||||||
|
conn, err := net.Dial("tcp", domain)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
//Connect with TLS using secure verify
|
||||||
|
tlsConn := tls.Client(conn, nil)
|
||||||
|
err = tlsConn.Handshake()
|
||||||
|
if err == nil {
|
||||||
|
//This is a valid certificate
|
||||||
|
fmt.Println()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//Connect with TLS using insecure skip verify
|
||||||
|
config := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
tlsConn = tls.Client(conn, config)
|
||||||
|
err = tlsConn.Handshake()
|
||||||
|
//If the handshake is successful, this is a self-signed certificate
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if domain reachable
|
||||||
func DomainReachable(domain string) bool {
|
func DomainReachable(domain string) bool {
|
||||||
return DomainReachableWithError(domain) == nil
|
return DomainReachableWithError(domain) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if domain is served by a web server using HTTPS
|
||||||
|
func DomainUsesTLS(targetURL string) bool {
|
||||||
|
//Check if the site support https
|
||||||
|
httpsUrl := fmt.Sprintf("https://%s", targetURL)
|
||||||
|
httpUrl := fmt.Sprintf("http://%s", targetURL)
|
||||||
|
|
||||||
|
client := http.Client{Timeout: 5 * time.Second}
|
||||||
|
|
||||||
|
resp, err := client.Head(httpsUrl)
|
||||||
|
if err == nil && resp.StatusCode == http.StatusOK {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = client.Head(httpUrl)
|
||||||
|
if err == nil && resp.StatusCode == http.StatusOK {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//If the site is not reachable, return false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
WebSocket Header Sniff
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Check if the requst is a special case where
|
||||||
|
// user defined header shall not be passed over
|
||||||
|
|
||||||
|
func RequireWebsocketHeaderCopy(r *http.Request) bool {
|
||||||
|
//Return false for proxmox
|
||||||
|
if IsProxmox(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add more edge cases here
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Request Handlers
|
||||||
|
*/
|
||||||
|
//Check if site support TLS
|
||||||
|
//Pass in ?selfsignchk=true to also check for self-signed certificate
|
||||||
|
func HandleCheckSiteSupportTLS(w http.ResponseWriter, r *http.Request) {
|
||||||
|
targetURL, err := utils.PostPara(r, "url")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid url given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//If the selfsign flag is set, also chec for self-signed certificate
|
||||||
|
_, err = utils.PostBool(r, "selfsignchk")
|
||||||
|
if err == nil {
|
||||||
|
//Return the https and selfsign status
|
||||||
|
type result struct {
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
SelfSign bool `json:"selfsign"`
|
||||||
|
}
|
||||||
|
|
||||||
|
scanResult := result{Protocol: "http", SelfSign: false}
|
||||||
|
|
||||||
|
if DomainUsesTLS(targetURL) {
|
||||||
|
scanResult.Protocol = "https"
|
||||||
|
if DomainIsSelfSigned(targetURL) {
|
||||||
|
scanResult.SelfSign = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(scanResult)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if DomainUsesTLS(targetURL) {
|
||||||
|
js, _ := json.Marshal("https")
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
js, _ := json.Marshal("http")
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -54,6 +54,9 @@ type ReverseProxy struct {
|
|||||||
Prepender string
|
Prepender string
|
||||||
|
|
||||||
Verbal bool
|
Verbal bool
|
||||||
|
|
||||||
|
//Appended by Zoraxy project
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseRewriteRuleSet struct {
|
type ResponseRewriteRuleSet struct {
|
||||||
@ -106,6 +109,8 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp
|
|||||||
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
|
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
|
||||||
thisTransporter.(*http.Transport).DisableCompression = true
|
thisTransporter.(*http.Transport).DisableCompression = true
|
||||||
|
|
||||||
|
//TODO: Add user adjustable timeout option here
|
||||||
|
|
||||||
if dpcOptions.IgnoreTLSVerification {
|
if dpcOptions.IgnoreTLSVerification {
|
||||||
//Ignore TLS certificate validation error
|
//Ignore TLS certificate validation error
|
||||||
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
|
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
|
||||||
@ -350,13 +355,6 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Figure out a way to proxy for proxmox
|
|
||||||
//if res.StatusCode == 501 || res.StatusCode == 500 {
|
|
||||||
// fmt.Println(outreq.Proto, outreq.RemoteAddr, outreq.RequestURI)
|
|
||||||
// fmt.Println(">>>", outreq.Method, res.Header, res.ContentLength, res.StatusCode)
|
|
||||||
// fmt.Println(outreq.Header, req.Host)
|
|
||||||
//}
|
|
||||||
|
|
||||||
//Add debug X-Proxy-By tracker
|
//Add debug X-Proxy-By tracker
|
||||||
res.Header.Set("x-proxy-by", "zoraxy/"+rrr.Version)
|
res.Header.Set("x-proxy-by", "zoraxy/"+rrr.Version)
|
||||||
|
|
||||||
|
@ -1,29 +1,67 @@
|
|||||||
package dpcore_test
|
package dpcore_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReplaceLocationHost(t *testing.T) {
|
func TestReplaceLocationHost(t *testing.T) {
|
||||||
urlString := "http://private.com/test/newtarget/"
|
tests := []struct {
|
||||||
rrr := &dpcore.ResponseRewriteRuleSet{
|
name string
|
||||||
OriginalHost: "test.example.com",
|
urlString string
|
||||||
ProxyDomain: "private.com/test",
|
rrr *dpcore.ResponseRewriteRuleSet
|
||||||
UseTLS: true,
|
useTLS bool
|
||||||
}
|
expectedResult string
|
||||||
useTLS := true
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Basic HTTP to HTTPS redirection",
|
||||||
|
urlString: "http://example.com/resource",
|
||||||
|
rrr: &dpcore.ResponseRewriteRuleSet{ProxyDomain: "example.com", OriginalHost: "proxy.example.com", UseTLS: true},
|
||||||
|
useTLS: true,
|
||||||
|
expectedResult: "https://proxy.example.com/resource",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
|
||||||
expectedResult := "https://test.example.com/newtarget/"
|
{
|
||||||
|
name: "Basic HTTPS to HTTP redirection",
|
||||||
result, err := dpcore.ReplaceLocationHost(urlString, rrr, useTLS)
|
urlString: "https://proxy.example.com/resource",
|
||||||
if err != nil {
|
rrr: &dpcore.ResponseRewriteRuleSet{ProxyDomain: "proxy.example.com", OriginalHost: "proxy.example.com", UseTLS: false},
|
||||||
t.Errorf("Error occurred: %v", err)
|
useTLS: false,
|
||||||
|
expectedResult: "http://proxy.example.com/resource",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No rewrite on mismatched domain",
|
||||||
|
urlString: "http://anotherdomain.com/resource",
|
||||||
|
rrr: &dpcore.ResponseRewriteRuleSet{ProxyDomain: "proxy.example.com", OriginalHost: "proxy.example.com", UseTLS: true},
|
||||||
|
useTLS: true,
|
||||||
|
expectedResult: "http://anotherdomain.com/resource",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Subpath trimming with HTTPS",
|
||||||
|
urlString: "https://blog.example.com/post?id=1",
|
||||||
|
rrr: &dpcore.ResponseRewriteRuleSet{ProxyDomain: "blog.example.com", OriginalHost: "proxy.example.com/blog", UseTLS: true},
|
||||||
|
useTLS: true,
|
||||||
|
expectedResult: "https://proxy.example.com/blog/post?id=1",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if result != expectedResult {
|
for _, tt := range tests {
|
||||||
t.Errorf("Expected: %s, but got: %s", expectedResult, result)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := dpcore.ReplaceLocationHost(tt.urlString, tt.rrr, tt.useTLS)
|
||||||
|
if (err != nil) != tt.expectError {
|
||||||
|
t.Errorf("Expected error: %v, got: %v", tt.expectError, err)
|
||||||
|
}
|
||||||
|
if result != tt.expectedResult {
|
||||||
|
result, _ = url.QueryUnescape(result)
|
||||||
|
t.Errorf("Expected result: %s, got: %s", tt.expectedResult, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +74,7 @@ func TestReplaceLocationHostRelative(t *testing.T) {
|
|||||||
}
|
}
|
||||||
useTLS := true
|
useTLS := true
|
||||||
|
|
||||||
expectedResult := "https://test.example.com/api/"
|
expectedResult := "api/"
|
||||||
|
|
||||||
result, err := dpcore.ReplaceLocationHost(urlString, rrr, useTLS)
|
result, err := dpcore.ReplaceLocationHost(urlString, rrr, useTLS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -60,7 +60,7 @@ func replaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS b
|
|||||||
return u.String(), nil
|
return u.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug functions
|
// Debug functions for replaceLocationHost
|
||||||
func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
|
func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
|
||||||
return replaceLocationHost(urlString, rrr, useTLS)
|
return replaceLocationHost(urlString, rrr, useTLS)
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ func (router *Router) StartProxyService() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Validate basic auth
|
//Validate basic auth
|
||||||
if sep.RequireBasicAuth {
|
if sep.AuthenticationProvider.AuthMethod == AuthMethodBasic {
|
||||||
err := handleBasicAuth(w, r, sep)
|
err := handleBasicAuth(w, r, sep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -157,12 +157,18 @@ func (router *Router) StartProxyService() error {
|
|||||||
router.Option.Logger.PrintAndLog("dprouter", "failed to get upstream for hostname", err)
|
router.Option.Logger.PrintAndLog("dprouter", "failed to get upstream for hostname", err)
|
||||||
router.logRequest(r, false, 404, "vdir-http", r.Host)
|
router.logRequest(r, false, 404, "vdir-http", r.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
endpointProxyRewriteRules := GetDefaultHeaderRewriteRules()
|
||||||
|
if sep.HeaderRewriteRules != nil {
|
||||||
|
endpointProxyRewriteRules = sep.HeaderRewriteRules
|
||||||
|
}
|
||||||
|
|
||||||
selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
UseTLS: selectedUpstream.RequireTLS,
|
UseTLS: selectedUpstream.RequireTLS,
|
||||||
HostHeaderOverwrite: sep.RequestHostOverwrite,
|
HostHeaderOverwrite: endpointProxyRewriteRules.RequestHostOverwrite,
|
||||||
NoRemoveHopByHop: sep.DisableHopByHopHeaderRemoval,
|
NoRemoveHopByHop: endpointProxyRewriteRules.DisableHopByHopHeaderRemoval,
|
||||||
PathPrefix: "",
|
PathPrefix: "",
|
||||||
Version: sep.parent.Option.HostVersion,
|
Version: sep.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
@ -291,7 +297,7 @@ func (router *Router) Restart() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(300 * time.Millisecond)
|
time.Sleep(800 * time.Millisecond)
|
||||||
// Start the server
|
// Start the server
|
||||||
err = router.StartProxyService()
|
err = router.StartProxyService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -26,7 +27,12 @@ import (
|
|||||||
|
|
||||||
// Check if a user define header exists in this endpoint, ignore case
|
// Check if a user define header exists in this endpoint, ignore case
|
||||||
func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
|
func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
|
||||||
for _, header := range ep.UserDefinedHeaders {
|
endpointProxyRewriteRules := GetDefaultHeaderRewriteRules()
|
||||||
|
if ep.HeaderRewriteRules != nil {
|
||||||
|
endpointProxyRewriteRules = ep.HeaderRewriteRules
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, header := range endpointProxyRewriteRules.UserDefinedHeaders {
|
||||||
if strings.EqualFold(header.Key, key) {
|
if strings.EqualFold(header.Key, key) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -36,26 +42,32 @@ func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
|
|||||||
|
|
||||||
// Remvoe a user defined header from the list
|
// Remvoe a user defined header from the list
|
||||||
func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error {
|
func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error {
|
||||||
newHeaderList := []*UserDefinedHeader{}
|
newHeaderList := []*rewrite.UserDefinedHeader{}
|
||||||
for _, header := range ep.UserDefinedHeaders {
|
if ep.HeaderRewriteRules == nil {
|
||||||
|
ep.HeaderRewriteRules = GetDefaultHeaderRewriteRules()
|
||||||
|
}
|
||||||
|
for _, header := range ep.HeaderRewriteRules.UserDefinedHeaders {
|
||||||
if !strings.EqualFold(header.Key, key) {
|
if !strings.EqualFold(header.Key, key) {
|
||||||
newHeaderList = append(newHeaderList, header)
|
newHeaderList = append(newHeaderList, header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ep.UserDefinedHeaders = newHeaderList
|
ep.HeaderRewriteRules.UserDefinedHeaders = newHeaderList
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a user defined header to the list, duplicates will be automatically removed
|
// Add a user defined header to the list, duplicates will be automatically removed
|
||||||
func (ep *ProxyEndpoint) AddUserDefinedHeader(newHeaderRule *UserDefinedHeader) error {
|
func (ep *ProxyEndpoint) AddUserDefinedHeader(newHeaderRule *rewrite.UserDefinedHeader) error {
|
||||||
if ep.UserDefinedHeaderExists(newHeaderRule.Key) {
|
if ep.UserDefinedHeaderExists(newHeaderRule.Key) {
|
||||||
ep.RemoveUserDefinedHeader(newHeaderRule.Key)
|
ep.RemoveUserDefinedHeader(newHeaderRule.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ep.HeaderRewriteRules == nil {
|
||||||
|
ep.HeaderRewriteRules = GetDefaultHeaderRewriteRules()
|
||||||
|
}
|
||||||
newHeaderRule.Key = cases.Title(language.Und, cases.NoLower).String(newHeaderRule.Key)
|
newHeaderRule.Key = cases.Title(language.Und, cases.NoLower).String(newHeaderRule.Key)
|
||||||
ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, newHeaderRule)
|
ep.HeaderRewriteRules.UserDefinedHeaders = append(ep.HeaderRewriteRules.UserDefinedHeaders, newHeaderRule)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +117,7 @@ func (ep *ProxyEndpoint) RemoveVirtualDirectoryRuleByMatchingPath(matchingPath s
|
|||||||
return errors.New("target virtual directory routing rule not found")
|
return errors.New("target virtual directory routing rule not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete a vdir rule by its matching path
|
// Add a vdir rule by its matching path
|
||||||
func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint) (*ProxyEndpoint, error) {
|
func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint) (*ProxyEndpoint, error) {
|
||||||
//Check for matching path duplicate
|
//Check for matching path duplicate
|
||||||
if ep.GetVirtualDirectoryRuleByMatchingPath(vdir.MatchingPath) != nil {
|
if ep.GetVirtualDirectoryRuleByMatchingPath(vdir.MatchingPath) != nil {
|
||||||
@ -122,9 +134,9 @@ func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ep.ProxyType == ProxyType_Root {
|
if ep.ProxyType == ProxyTypeRoot {
|
||||||
parentRouter.Root = readyRoutingRule
|
parentRouter.Root = readyRoutingRule
|
||||||
} else if ep.ProxyType == ProxyType_Host {
|
} else if ep.ProxyType == ProxyTypeHost {
|
||||||
ep.Remove()
|
ep.Remove()
|
||||||
parentRouter.AddProxyRouteToRuntime(readyRoutingRule)
|
parentRouter.AddProxyRouteToRuntime(readyRoutingRule)
|
||||||
} else {
|
} else {
|
||||||
@ -263,5 +275,6 @@ func (ep *ProxyEndpoint) Remove() error {
|
|||||||
// use prepare -> remove -> add if you change anything in the endpoint
|
// use prepare -> remove -> add if you change anything in the endpoint
|
||||||
// that effects the proxy routing src / dest
|
// that effects the proxy routing src / dest
|
||||||
func (ep *ProxyEndpoint) UpdateToRuntime() {
|
func (ep *ProxyEndpoint) UpdateToRuntime() {
|
||||||
ep.parent.ProxyEndpoints.Store(ep.RootOrMatchingDomain, ep)
|
lookupHostname := strings.ToLower(ep.RootOrMatchingDomain)
|
||||||
|
ep.parent.ProxyEndpoints.Store(lookupHostname, ep)
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,10 @@ func GetUpstreamsAsString(upstreams []*Upstream) string {
|
|||||||
for _, upstream := range upstreams {
|
for _, upstream := range upstreams {
|
||||||
targets = append(targets, upstream.String())
|
targets = append(targets, upstream.String())
|
||||||
}
|
}
|
||||||
|
if len(targets) == 0 {
|
||||||
|
//No upstream
|
||||||
|
return "(no upstream config)"
|
||||||
|
}
|
||||||
return strings.Join(targets, ", ")
|
return strings.Join(targets, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +97,7 @@ func (m *RouteManager) Close() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print debug message
|
// Log Println, replace all log.Println or fmt.Println with this
|
||||||
func (m *RouteManager) debugPrint(message string, err error) {
|
func (m *RouteManager) println(message string, err error) {
|
||||||
m.Options.Logger.PrintAndLog("LoadBalancer", message, err)
|
m.Options.Logger.PrintAndLog("LoadBalancer", message, err)
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@ package loadbalance
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
@ -29,7 +27,7 @@ func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.R
|
|||||||
//No valid session found. Assign a new upstream
|
//No valid session found. Assign a new upstream
|
||||||
targetOrigin, index, err := getRandomUpstreamByWeight(origins)
|
targetOrigin, index, err := getRandomUpstreamByWeight(origins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Oops. Unable to get random upstream")
|
m.println("Unable to get random upstream", err)
|
||||||
targetOrigin = origins[0]
|
targetOrigin = origins[0]
|
||||||
index = 0
|
index = 0
|
||||||
}
|
}
|
||||||
@ -44,7 +42,7 @@ func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.R
|
|||||||
var err error
|
var err error
|
||||||
targetOrigin, _, err = getRandomUpstreamByWeight(origins)
|
targetOrigin, _, err = getRandomUpstreamByWeight(origins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
m.println("Failed to get next origin", err)
|
||||||
targetOrigin = origins[0]
|
targetOrigin = origins[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,42 +100,66 @@ func (m *RouteManager) getSessionHandler(r *http.Request, upstreams []*Upstream)
|
|||||||
/* Functions related to random upstream picking */
|
/* 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
|
// 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) {
|
func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) {
|
||||||
var ret *Upstream
|
// If there is only one upstream, return it
|
||||||
sum := 0
|
if len(upstreams) == 1 {
|
||||||
for _, c := range upstreams {
|
return upstreams[0], 0, nil
|
||||||
sum += c.Weight
|
|
||||||
}
|
|
||||||
r, err := intRange(0, sum)
|
|
||||||
if err != nil {
|
|
||||||
return ret, -1, err
|
|
||||||
}
|
|
||||||
counter := 0
|
|
||||||
for _, c := range upstreams {
|
|
||||||
r -= c.Weight
|
|
||||||
if r < 0 {
|
|
||||||
return c, counter, nil
|
|
||||||
}
|
|
||||||
counter++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ret == nil {
|
// Preserve the index with upstreams
|
||||||
//All fallback
|
type upstreamWithIndex struct {
|
||||||
//use the first one that is with weight = 0
|
Upstream *Upstream
|
||||||
fallbackUpstreams := []*Upstream{}
|
Index int
|
||||||
fallbackUpstreamsOriginalID := []int{}
|
|
||||||
for ix, upstream := range upstreams {
|
|
||||||
if upstream.Weight == 0 {
|
|
||||||
fallbackUpstreams = append(fallbackUpstreams, upstream)
|
|
||||||
fallbackUpstreamsOriginalID = append(fallbackUpstreamsOriginalID, ix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
upstreamID := rand.Intn(len(fallbackUpstreams))
|
|
||||||
return fallbackUpstreams[upstreamID], fallbackUpstreamsOriginalID[upstreamID], nil
|
|
||||||
}
|
}
|
||||||
return ret, -1, errors.New("failed to pick an upstream origin server")
|
|
||||||
|
// 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.
|
// IntRange returns a random integer in the range from min to max.
|
||||||
|
/*
|
||||||
func intRange(min, max int) (int, error) {
|
func intRange(min, max int) (int, error) {
|
||||||
var result int
|
var result int
|
||||||
switch {
|
switch {
|
||||||
@ -152,3 +174,4 @@ func intRange(min, max int) (int, error) {
|
|||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
100
src/mod/dynamicproxy/loadbalance/originPicker_test.go
Normal file
100
src/mod/dynamicproxy/loadbalance/originPicker_test.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package loadbalance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) { ... }
|
||||||
|
func TestRandomUpstreamSelection(t *testing.T) {
|
||||||
|
rand.Seed(time.Now().UnixNano()) // Seed for randomness
|
||||||
|
|
||||||
|
// Define some test upstreams
|
||||||
|
upstreams := []*Upstream{
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "192.168.1.1:8080",
|
||||||
|
RequireTLS: false,
|
||||||
|
SkipCertValidations: false,
|
||||||
|
SkipWebSocketOriginCheck: false,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0, // No connection limit for now
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "192.168.1.2:8080",
|
||||||
|
RequireTLS: false,
|
||||||
|
SkipCertValidations: false,
|
||||||
|
SkipWebSocketOriginCheck: false,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "192.168.1.3:8080",
|
||||||
|
RequireTLS: true,
|
||||||
|
SkipCertValidations: true,
|
||||||
|
SkipWebSocketOriginCheck: true,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "192.168.1.4:8080",
|
||||||
|
RequireTLS: true,
|
||||||
|
SkipCertValidations: true,
|
||||||
|
SkipWebSocketOriginCheck: true,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track how many times each upstream is selected
|
||||||
|
selectionCount := make(map[string]int)
|
||||||
|
totalPicks := 10000 // Number of times to call getRandomUpstreamByWeight
|
||||||
|
//expectedPickCount := totalPicks / len(upstreams) // Ideal count for each upstream
|
||||||
|
|
||||||
|
// Pick upstreams and record their selection count
|
||||||
|
for i := 0; i < totalPicks; i++ {
|
||||||
|
upstream, _, err := getRandomUpstreamByWeight(upstreams)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error getting random upstream: %v", err)
|
||||||
|
}
|
||||||
|
selectionCount[upstream.OriginIpOrDomain]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition 1: Ensure every upstream has been picked at least once
|
||||||
|
for _, upstream := range upstreams {
|
||||||
|
if selectionCount[upstream.OriginIpOrDomain] == 0 {
|
||||||
|
t.Errorf("Upstream %s was never selected", upstream.OriginIpOrDomain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition 2: Check that the distribution is within 1-2 standard deviations
|
||||||
|
counts := make([]float64, len(upstreams))
|
||||||
|
for i, upstream := range upstreams {
|
||||||
|
counts[i] = float64(selectionCount[upstream.OriginIpOrDomain])
|
||||||
|
}
|
||||||
|
|
||||||
|
mean := float64(totalPicks) / float64(len(upstreams))
|
||||||
|
stddev := calculateStdDev(counts, mean)
|
||||||
|
|
||||||
|
tolerance := 2 * stddev // Allowing up to 2 standard deviations
|
||||||
|
for i, count := range counts {
|
||||||
|
if math.Abs(count-mean) > tolerance {
|
||||||
|
t.Errorf("Selection of upstream %s is outside acceptable range: %v picks (mean: %v, stddev: %v)", upstreams[i].OriginIpOrDomain, count, mean, stddev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Selection count:", selectionCount)
|
||||||
|
fmt.Printf("Mean: %.2f, StdDev: %.2f\n", mean, stddev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to calculate standard deviation
|
||||||
|
func calculateStdDev(data []float64, mean float64) float64 {
|
||||||
|
var sumOfSquares float64
|
||||||
|
for _, value := range data {
|
||||||
|
sumOfSquares += (value - mean) * (value - mean)
|
||||||
|
}
|
||||||
|
variance := sumOfSquares / float64(len(data))
|
||||||
|
return math.Sqrt(variance)
|
||||||
|
}
|
@ -10,7 +10,9 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||||
"imuslab.com/zoraxy/mod/netutils"
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
"imuslab.com/zoraxy/mod/statistic"
|
"imuslab.com/zoraxy/mod/statistic"
|
||||||
"imuslab.com/zoraxy/mod/websocketproxy"
|
"imuslab.com/zoraxy/mod/websocketproxy"
|
||||||
@ -34,6 +36,7 @@ func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *P
|
|||||||
// Get the proxy endpoint from hostname, which might includes checking of wildcard certificates
|
// Get the proxy endpoint from hostname, which might includes checking of wildcard certificates
|
||||||
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
|
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
|
||||||
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
||||||
|
hostname = strings.ToLower(hostname)
|
||||||
ep, ok := router.ProxyEndpoints.Load(hostname)
|
ep, ok := router.ProxyEndpoints.Load(hostname)
|
||||||
if ok {
|
if ok {
|
||||||
//Exact hit
|
//Exact hit
|
||||||
@ -117,7 +120,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession)
|
selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.ServeFile(w, r, "./web/rperror.html")
|
http.ServeFile(w, r, "./web/rperror.html")
|
||||||
log.Println(err.Error())
|
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())
|
h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -141,9 +144,17 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
||||||
}
|
}
|
||||||
h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain)
|
h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain)
|
||||||
|
|
||||||
|
if target.HeaderRewriteRules == nil {
|
||||||
|
target.HeaderRewriteRules = GetDefaultHeaderRewriteRules()
|
||||||
|
}
|
||||||
|
|
||||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
SkipTLSValidation: selectedUpstream.SkipCertValidations,
|
SkipTLSValidation: selectedUpstream.SkipCertValidations,
|
||||||
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
|
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
|
||||||
|
CopyAllHeaders: target.EnableWebsocketCustomHeaders,
|
||||||
|
UserDefinedHeaders: target.HeaderRewriteRules.UserDefinedHeaders,
|
||||||
|
Logger: h.Parent.Option.Logger,
|
||||||
})
|
})
|
||||||
wspHandler.ServeHTTP(w, r)
|
wspHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
@ -157,9 +168,23 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
r.URL, _ = url.Parse(originalHostHeader)
|
r.URL, _ = url.Parse(originalHostHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Build downstream and upstream header rules
|
//Populate the user-defined headers with the values from the request
|
||||||
upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()
|
headerRewriteOptions := GetDefaultHeaderRewriteRules()
|
||||||
|
if target.HeaderRewriteRules != nil {
|
||||||
|
headerRewriteOptions = target.HeaderRewriteRules
|
||||||
|
}
|
||||||
|
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, headerRewriteOptions.UserDefinedHeaders)
|
||||||
|
|
||||||
|
//Build downstream and upstream header rules
|
||||||
|
upstreamHeaders, downstreamHeaders := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{
|
||||||
|
UserDefinedHeaders: rewrittenUserDefinedHeaders,
|
||||||
|
HSTSMaxAge: headerRewriteOptions.HSTSMaxAge,
|
||||||
|
HSTSIncludeSubdomains: target.ContainsWildcardName(true),
|
||||||
|
EnablePermissionPolicyHeader: headerRewriteOptions.EnablePermissionPolicyHeader,
|
||||||
|
PermissionPolicy: headerRewriteOptions.PermissionPolicy,
|
||||||
|
})
|
||||||
|
|
||||||
|
//Handle the request reverse proxy
|
||||||
statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
@ -168,8 +193,8 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
PathPrefix: "",
|
PathPrefix: "",
|
||||||
UpstreamHeaders: upstreamHeaders,
|
UpstreamHeaders: upstreamHeaders,
|
||||||
DownstreamHeaders: downstreamHeaders,
|
DownstreamHeaders: downstreamHeaders,
|
||||||
HostHeaderOverwrite: target.RequestHostOverwrite,
|
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
||||||
NoRemoveHopByHop: target.DisableHopByHopHeaderRemoval,
|
NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval,
|
||||||
Version: target.parent.Option.HostVersion,
|
Version: target.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -177,11 +202,10 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.As(err, &dnsError) {
|
if errors.As(err, &dnsError) {
|
||||||
http.ServeFile(w, r, "./web/hosterror.html")
|
http.ServeFile(w, r, "./web/hosterror.html")
|
||||||
log.Println(err.Error())
|
|
||||||
h.Parent.logRequest(r, false, 404, "host-http", r.URL.Hostname())
|
h.Parent.logRequest(r, false, 404, "host-http", r.URL.Hostname())
|
||||||
} else {
|
} else {
|
||||||
http.ServeFile(w, r, "./web/rperror.html")
|
http.ServeFile(w, r, "./web/rperror.html")
|
||||||
log.Println(err.Error())
|
//TODO: Take this upstream offline automatically
|
||||||
h.Parent.logRequest(r, false, 521, "host-http", r.URL.Hostname())
|
h.Parent.logRequest(r, false, 521, "host-http", r.URL.Hostname())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,10 +232,18 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
if target.RequireTLS {
|
if target.RequireTLS {
|
||||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if target.parent.HeaderRewriteRules != nil {
|
||||||
|
target.parent.HeaderRewriteRules = GetDefaultHeaderRewriteRules()
|
||||||
|
}
|
||||||
|
|
||||||
h.Parent.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
h.Parent.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
SkipTLSValidation: target.SkipCertValidations,
|
SkipTLSValidation: target.SkipCertValidations,
|
||||||
SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
|
SkipOriginCheck: target.parent.EnableWebsocketCustomHeaders, //You should not use websocket via virtual directory. But keep this to true for compatibility
|
||||||
|
CopyAllHeaders: domainsniff.RequireWebsocketHeaderCopy(r), //Left this as default to prevent nginx user setting / as vdir
|
||||||
|
UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders,
|
||||||
|
Logger: h.Parent.Option.Logger,
|
||||||
})
|
})
|
||||||
wspHandler.ServeHTTP(w, r)
|
wspHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
@ -225,9 +257,24 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
r.URL, _ = url.Parse(originalHostHeader)
|
r.URL, _ = url.Parse(originalHostHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Build downstream and upstream header rules
|
//Populate the user-defined headers with the values from the request
|
||||||
upstreamHeaders, downstreamHeaders := target.parent.SplitInboundOutboundHeaders()
|
headerRewriteOptions := GetDefaultHeaderRewriteRules()
|
||||||
|
if target.parent.HeaderRewriteRules != nil {
|
||||||
|
headerRewriteOptions = target.parent.HeaderRewriteRules
|
||||||
|
}
|
||||||
|
|
||||||
|
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, headerRewriteOptions.UserDefinedHeaders)
|
||||||
|
|
||||||
|
//Build downstream and upstream header rules, use the parent (subdomain) endpoint's headers
|
||||||
|
upstreamHeaders, downstreamHeaders := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{
|
||||||
|
UserDefinedHeaders: rewrittenUserDefinedHeaders,
|
||||||
|
HSTSMaxAge: headerRewriteOptions.HSTSMaxAge,
|
||||||
|
HSTSIncludeSubdomains: target.parent.ContainsWildcardName(true),
|
||||||
|
EnablePermissionPolicyHeader: headerRewriteOptions.EnablePermissionPolicyHeader,
|
||||||
|
PermissionPolicy: headerRewriteOptions.PermissionPolicy,
|
||||||
|
})
|
||||||
|
|
||||||
|
//Handle the virtual directory reverse proxy request
|
||||||
statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
ProxyDomain: target.Domain,
|
ProxyDomain: target.Domain,
|
||||||
OriginalHost: originalHostHeader,
|
OriginalHost: originalHostHeader,
|
||||||
@ -235,7 +282,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
|||||||
PathPrefix: target.MatchingPath,
|
PathPrefix: target.MatchingPath,
|
||||||
UpstreamHeaders: upstreamHeaders,
|
UpstreamHeaders: upstreamHeaders,
|
||||||
DownstreamHeaders: downstreamHeaders,
|
DownstreamHeaders: downstreamHeaders,
|
||||||
HostHeaderOverwrite: target.parent.RequestHostOverwrite,
|
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
||||||
Version: target.parent.parent.Option.HostVersion,
|
Version: target.parent.parent.Option.HostVersion,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
63
src/mod/dynamicproxy/rewrite/headervars.go
Normal file
63
src/mod/dynamicproxy/rewrite/headervars.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package rewrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetHeaderVariableValuesFromRequest returns a map of header variables and their values
|
||||||
|
// note that variables behavior is not exactly identical to nginx variables
|
||||||
|
func GetHeaderVariableValuesFromRequest(r *http.Request) map[string]string {
|
||||||
|
vars := make(map[string]string)
|
||||||
|
|
||||||
|
// Request-specific variables
|
||||||
|
vars["$host"] = r.Host
|
||||||
|
vars["$remote_addr"] = r.RemoteAddr
|
||||||
|
vars["$request_uri"] = r.RequestURI
|
||||||
|
vars["$request_method"] = r.Method
|
||||||
|
vars["$content_length"] = fmt.Sprintf("%d", r.ContentLength)
|
||||||
|
vars["$content_type"] = r.Header.Get("Content-Type")
|
||||||
|
|
||||||
|
// Parsed URI elements
|
||||||
|
vars["$uri"] = r.URL.Path
|
||||||
|
vars["$args"] = r.URL.RawQuery
|
||||||
|
vars["$scheme"] = r.URL.Scheme
|
||||||
|
vars["$query_string"] = r.URL.RawQuery
|
||||||
|
|
||||||
|
// User agent and referer
|
||||||
|
vars["$http_user_agent"] = r.UserAgent()
|
||||||
|
vars["$http_referer"] = r.Referer()
|
||||||
|
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomHeadersIncludeDynamicVariables checks if the user-defined headers contain dynamic variables
|
||||||
|
// use for early exit when processing the headers
|
||||||
|
func CustomHeadersIncludeDynamicVariables(userDefinedHeaders []*UserDefinedHeader) bool {
|
||||||
|
for _, header := range userDefinedHeaders {
|
||||||
|
if strings.Contains(header.Value, "$") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopulateRequestHeaderVariables populates the user-defined headers with the values from the request
|
||||||
|
func PopulateRequestHeaderVariables(r *http.Request, userDefinedHeaders []*UserDefinedHeader) []*UserDefinedHeader {
|
||||||
|
if !CustomHeadersIncludeDynamicVariables(userDefinedHeaders) {
|
||||||
|
// Early exit if there are no dynamic variables
|
||||||
|
return userDefinedHeaders
|
||||||
|
}
|
||||||
|
vars := GetHeaderVariableValuesFromRequest(r)
|
||||||
|
populatedHeaders := []*UserDefinedHeader{}
|
||||||
|
// Populate the user-defined headers with the values from the request
|
||||||
|
for _, header := range userDefinedHeaders {
|
||||||
|
thisHeader := header.Copy()
|
||||||
|
for key, value := range vars {
|
||||||
|
thisHeader.Value = strings.ReplaceAll(thisHeader.Value, key, value)
|
||||||
|
}
|
||||||
|
populatedHeaders = append(populatedHeaders, thisHeader)
|
||||||
|
}
|
||||||
|
return populatedHeaders
|
||||||
|
}
|
172
src/mod/dynamicproxy/rewrite/headervars_test.go
Normal file
172
src/mod/dynamicproxy/rewrite/headervars_test.go
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package rewrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetHeaderVariableValuesFromRequest(t *testing.T) {
|
||||||
|
// Create a sample request
|
||||||
|
req := httptest.NewRequest("GET", "https://example.com/test?foo=bar", nil)
|
||||||
|
req.Host = "example.com"
|
||||||
|
req.RemoteAddr = "192.168.1.1:12345"
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("User-Agent", "TestAgent")
|
||||||
|
req.Header.Set("Referer", "https://referer.com")
|
||||||
|
|
||||||
|
// Call the function
|
||||||
|
vars := GetHeaderVariableValuesFromRequest(req)
|
||||||
|
|
||||||
|
// Expected results
|
||||||
|
expected := map[string]string{
|
||||||
|
"$host": "example.com",
|
||||||
|
"$remote_addr": "192.168.1.1:12345",
|
||||||
|
"$request_uri": "https://example.com/test?foo=bar",
|
||||||
|
"$request_method": "GET",
|
||||||
|
"$content_length": "0", // ContentLength is 0 because there's no body in the request
|
||||||
|
"$content_type": "application/json",
|
||||||
|
"$uri": "/test",
|
||||||
|
"$args": "foo=bar",
|
||||||
|
"$scheme": "https",
|
||||||
|
"$query_string": "foo=bar",
|
||||||
|
"$http_user_agent": "TestAgent",
|
||||||
|
"$http_referer": "https://referer.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each expected variable
|
||||||
|
for key, expectedValue := range expected {
|
||||||
|
if vars[key] != expectedValue {
|
||||||
|
t.Errorf("Expected %s to be %s, but got %s", key, expectedValue, vars[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomHeadersIncludeDynamicVariables(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
headers []*UserDefinedHeader
|
||||||
|
expectedHasVar bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "No headers",
|
||||||
|
headers: []*UserDefinedHeader{},
|
||||||
|
expectedHasVar: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Headers without dynamic variables",
|
||||||
|
headers: []*UserDefinedHeader{
|
||||||
|
{
|
||||||
|
Direction: HeaderDirection_ZoraxyToUpstream,
|
||||||
|
Key: "X-Custom-Header",
|
||||||
|
Value: "staticValue",
|
||||||
|
IsRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Direction: HeaderDirection_ZoraxyToDownstream,
|
||||||
|
Key: "X-Another-Header",
|
||||||
|
Value: "staticValue",
|
||||||
|
IsRemove: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedHasVar: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Headers with one dynamic variable",
|
||||||
|
headers: []*UserDefinedHeader{
|
||||||
|
{
|
||||||
|
Direction: HeaderDirection_ZoraxyToUpstream,
|
||||||
|
Key: "X-Custom-Header",
|
||||||
|
Value: "$dynamicValue",
|
||||||
|
IsRemove: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedHasVar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Headers with multiple dynamic variables",
|
||||||
|
headers: []*UserDefinedHeader{
|
||||||
|
{
|
||||||
|
Direction: HeaderDirection_ZoraxyToUpstream,
|
||||||
|
Key: "X-Custom-Header",
|
||||||
|
Value: "$dynamicValue1",
|
||||||
|
IsRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Direction: HeaderDirection_ZoraxyToDownstream,
|
||||||
|
Key: "X-Another-Header",
|
||||||
|
Value: "$dynamicValue2",
|
||||||
|
IsRemove: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedHasVar: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
hasVar := CustomHeadersIncludeDynamicVariables(tt.headers)
|
||||||
|
if hasVar != tt.expectedHasVar {
|
||||||
|
t.Errorf("Expected %v, but got %v", tt.expectedHasVar, hasVar)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPopulateRequestHeaderVariables(t *testing.T) {
|
||||||
|
// Create a sample request with specific values
|
||||||
|
req := httptest.NewRequest("GET", "https://example.com/test?foo=bar", nil)
|
||||||
|
req.Host = "example.com"
|
||||||
|
req.RemoteAddr = "192.168.1.1:12345"
|
||||||
|
req.Header.Set("User-Agent", "TestAgent")
|
||||||
|
req.Header.Set("Referer", "https://referer.com")
|
||||||
|
|
||||||
|
// Define user-defined headers with dynamic variables
|
||||||
|
userDefinedHeaders := []*UserDefinedHeader{
|
||||||
|
{
|
||||||
|
Direction: HeaderDirection_ZoraxyToUpstream,
|
||||||
|
Key: "X-Forwarded-Host",
|
||||||
|
Value: "$host",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Direction: HeaderDirection_ZoraxyToDownstream,
|
||||||
|
Key: "X-Client-IP",
|
||||||
|
Value: "$remote_addr",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Direction: HeaderDirection_ZoraxyToDownstream,
|
||||||
|
Key: "X-Custom-Header",
|
||||||
|
Value: "$request_uri",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the function with the test data
|
||||||
|
resultHeaders := PopulateRequestHeaderVariables(req, userDefinedHeaders)
|
||||||
|
|
||||||
|
// Expected results after variable substitution
|
||||||
|
expectedHeaders := []*UserDefinedHeader{
|
||||||
|
{
|
||||||
|
Direction: HeaderDirection_ZoraxyToUpstream,
|
||||||
|
Key: "X-Forwarded-Host",
|
||||||
|
Value: "example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Direction: HeaderDirection_ZoraxyToDownstream,
|
||||||
|
Key: "X-Client-IP",
|
||||||
|
Value: "192.168.1.1:12345",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Direction: HeaderDirection_ZoraxyToDownstream,
|
||||||
|
Key: "X-Custom-Header",
|
||||||
|
Value: "https://example.com/test?foo=bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate results
|
||||||
|
for i, expected := range expectedHeaders {
|
||||||
|
if resultHeaders[i].Direction != expected.Direction ||
|
||||||
|
resultHeaders[i].Key != expected.Key ||
|
||||||
|
resultHeaders[i].Value != expected.Value {
|
||||||
|
t.Errorf("Expected header %v, but got %v", expected, resultHeaders[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
src/mod/dynamicproxy/rewrite/rewrite.go
Normal file
79
src/mod/dynamicproxy/rewrite/rewrite.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package rewrite
|
||||||
|
|
||||||
|
/*
|
||||||
|
rewrite.go
|
||||||
|
|
||||||
|
This script handle the rewrite logic for custom headers
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 SplitUpDownStreamHeaders(rewriteOptions *HeaderRewriteOptions) ([][]string, [][]string) {
|
||||||
|
if len(rewriteOptions.UserDefinedHeaders) == 0 && rewriteOptions.HSTSMaxAge == 0 && !rewriteOptions.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(rewriteOptions.UserDefinedHeaders))
|
||||||
|
downstreamHeaders := make([][]string, len(rewriteOptions.UserDefinedHeaders)+2)
|
||||||
|
upstreamHeaderCounter := 0
|
||||||
|
downstreamHeaderCounter := 0
|
||||||
|
|
||||||
|
//Sort the headers into upstream or downstream
|
||||||
|
for _, customHeader := range rewriteOptions.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 rewriteOptions.HSTSMaxAge > 0 {
|
||||||
|
if rewriteOptions.HSTSIncludeSubdomains {
|
||||||
|
//Endpoint listening domain includes wildcards.
|
||||||
|
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(rewriteOptions.HSTSMaxAge)) + "; includeSubdomains"}
|
||||||
|
} else {
|
||||||
|
downstreamHeaders[downstreamHeaderCounter] = []string{"Strict-Transport-Security", "max-age=" + strconv.Itoa(int(rewriteOptions.HSTSMaxAge))}
|
||||||
|
}
|
||||||
|
|
||||||
|
downstreamHeaderCounter++
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the endpoint require Permission Policy
|
||||||
|
if rewriteOptions.EnablePermissionPolicyHeader {
|
||||||
|
var usingPermissionPolicy *permissionpolicy.PermissionsPolicy
|
||||||
|
if rewriteOptions.PermissionPolicy != nil {
|
||||||
|
//Custom permission policy
|
||||||
|
usingPermissionPolicy = rewriteOptions.PermissionPolicy
|
||||||
|
} else {
|
||||||
|
//Permission policy is enabled but not customized. Use default
|
||||||
|
usingPermissionPolicy = permissionpolicy.GetDefaultPermissionPolicy()
|
||||||
|
}
|
||||||
|
|
||||||
|
downstreamHeaders[downstreamHeaderCounter] = usingPermissionPolicy.ToKeyValueHeader()
|
||||||
|
downstreamHeaderCounter++
|
||||||
|
}
|
||||||
|
|
||||||
|
return upstreamHeaders, downstreamHeaders
|
||||||
|
}
|
51
src/mod/dynamicproxy/rewrite/typedef.go
Normal file
51
src/mod/dynamicproxy/rewrite/typedef.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package rewrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
typdef.go
|
||||||
|
|
||||||
|
This script handle the type definition for custom headers
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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 {
|
||||||
|
Direction HeaderDirection
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
IsRemove bool //Instead of set, remove this key instead
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeaderRewriteOptions struct {
|
||||||
|
UserDefinedHeaders []*UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||||
|
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||||
|
HSTSIncludeSubdomains bool //Include subdomains in HSTS header
|
||||||
|
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||||
|
PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utilities for header rewrite
|
||||||
|
func (h *UserDefinedHeader) GetDirection() HeaderDirection {
|
||||||
|
return h.Direction
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy eturns a deep copy of the UserDefinedHeader
|
||||||
|
func (h *UserDefinedHeader) Copy() *UserDefinedHeader {
|
||||||
|
result := UserDefinedHeader{}
|
||||||
|
js, _ := json.Marshal(h)
|
||||||
|
json.Unmarshal(js, &result)
|
||||||
|
return &result
|
||||||
|
}
|
@ -70,12 +70,18 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
|||||||
|
|
||||||
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
|
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
|
||||||
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
||||||
|
lookupHostname := strings.ToLower(endpoint.RootOrMatchingDomain)
|
||||||
|
if len(endpoint.ActiveOrigins) == 0 {
|
||||||
|
//There are no active origins. No need to check for ready
|
||||||
|
router.ProxyEndpoints.Store(lookupHostname, endpoint)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
|
if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
|
||||||
//This endpoint is not prepared
|
//This endpoint is not prepared
|
||||||
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
||||||
}
|
}
|
||||||
// Push record into running subdomain endpoints
|
// Push record into running subdomain endpoints
|
||||||
router.ProxyEndpoints.Store(endpoint.RootOrMatchingDomain, endpoint)
|
router.ProxyEndpoints.Store(lookupHostname, endpoint)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
package dynamicproxy
|
package dynamicproxy
|
||||||
|
|
||||||
|
/*
|
||||||
|
typdef.go
|
||||||
|
|
||||||
|
This script handle the type definition for dynamic proxy and endpoints
|
||||||
|
|
||||||
|
If you are looking for the default object initailization, please refer to default.go
|
||||||
|
*/
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"net"
|
"net"
|
||||||
@ -7,20 +14,24 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/access"
|
"imuslab.com/zoraxy/mod/access"
|
||||||
|
"imuslab.com/zoraxy/mod/auth/sso/authelia"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/statistic"
|
"imuslab.com/zoraxy/mod/statistic"
|
||||||
"imuslab.com/zoraxy/mod/tlscert"
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ProxyType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ProxyType_Root = 0
|
ProxyTypeRoot ProxyType = iota //Root Proxy, everything not matching will be routed here
|
||||||
ProxyType_Host = 1
|
ProxyTypeHost //Host Proxy, match by host (domain) name
|
||||||
ProxyType_Vdir = 2
|
ProxyTypeVdir //Virtual Directory Proxy, match by path prefix
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyHandler struct {
|
type ProxyHandler struct {
|
||||||
@ -29,14 +40,17 @@ type ProxyHandler struct {
|
|||||||
|
|
||||||
/* Router Object Options */
|
/* Router Object Options */
|
||||||
type RouterOption struct {
|
type RouterOption struct {
|
||||||
HostUUID string //The UUID of Zoraxy, use for heading mod
|
/* Basic Settings */
|
||||||
HostVersion string //The version of Zoraxy, use for heading mod
|
HostUUID string //The UUID of Zoraxy, use for heading mod
|
||||||
Port int //Incoming port
|
HostVersion string //The version of Zoraxy, use for heading mod
|
||||||
UseTls bool //Use TLS to serve incoming requsts
|
Port int //Incoming port
|
||||||
ForceTLSLatest bool //Force TLS1.2 or above
|
UseTls bool //Use TLS to serve incoming requsts
|
||||||
NoCache bool //Force set Cache-Control: no-store
|
ForceTLSLatest bool //Force TLS1.2 or above
|
||||||
ListenOnPort80 bool //Enable port 80 http listener
|
NoCache bool //Force set Cache-Control: no-store
|
||||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
ListenOnPort80 bool //Enable port 80 http listener
|
||||||
|
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||||
|
|
||||||
|
/* Routing Service Managers */
|
||||||
TlsManager *tlscert.Manager //TLS manager for serving SAN certificates
|
TlsManager *tlscert.Manager //TLS manager for serving SAN certificates
|
||||||
RedirectRuleTable *redirection.RuleTable //Redirection rules handler and table
|
RedirectRuleTable *redirection.RuleTable //Redirection rules handler and table
|
||||||
GeodbStore *geodb.Store //GeoIP resolver
|
GeodbStore *geodb.Store //GeoIP resolver
|
||||||
@ -44,20 +58,25 @@ type RouterOption struct {
|
|||||||
StatisticCollector *statistic.Collector //Statistic collector for storing stats on incoming visitors
|
StatisticCollector *statistic.Collector //Statistic collector for storing stats on incoming visitors
|
||||||
WebDirectory string //The static web server directory containing the templates folder
|
WebDirectory string //The static web server directory containing the templates folder
|
||||||
LoadBalancer *loadbalance.RouteManager //Load balancer that handle load balancing of proxy target
|
LoadBalancer *loadbalance.RouteManager //Load balancer that handle load balancing of proxy target
|
||||||
Logger *logger.Logger //Logger for reverse proxy requets
|
|
||||||
|
/* Authentication Providers */
|
||||||
|
AutheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication
|
||||||
|
|
||||||
|
/* Utilities */
|
||||||
|
Logger *logger.Logger //Logger for reverse proxy requets
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Router Object */
|
/* Router Object */
|
||||||
type Router struct {
|
type Router struct {
|
||||||
Option *RouterOption
|
Option *RouterOption
|
||||||
ProxyEndpoints *sync.Map
|
ProxyEndpoints *sync.Map //Map of ProxyEndpoint objects, each ProxyEndpoint object is a routing rule that handle incoming requests
|
||||||
Running bool
|
Running bool //If the router is running
|
||||||
Root *ProxyEndpoint
|
Root *ProxyEndpoint //Root proxy endpoint, default site
|
||||||
mux http.Handler
|
mux http.Handler //HTTP handler
|
||||||
server *http.Server
|
server *http.Server //HTTP server
|
||||||
tlsListener net.Listener
|
tlsListener net.Listener //TLS listener, handle SNI routing
|
||||||
loadBalancer *loadbalance.RouteManager //Load balancer routing manager
|
loadBalancer *loadbalance.RouteManager //Load balancer routing manager
|
||||||
routingRules []*RoutingRule
|
routingRules []*RoutingRule //Special routing rules, handle high priority routing like ACME request handling
|
||||||
|
|
||||||
tlsRedirectStop chan bool //Stop channel for tls redirection server
|
tlsRedirectStop chan bool //Stop channel for tls redirection server
|
||||||
rateLimterStop chan bool //Stop channel for rate limiter
|
rateLimterStop chan bool //Stop channel for rate limiter
|
||||||
@ -82,23 +101,6 @@ type BasicAuthExceptionRule struct {
|
|||||||
PathPrefix string
|
PathPrefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom Header Related Data structure */
|
|
||||||
// Header injection direction type
|
|
||||||
type HeaderDirection int
|
|
||||||
|
|
||||||
const (
|
|
||||||
HeaderDirection_ZoraxyToUpstream HeaderDirection = 0 //Inject (or remove) header to request out-going from Zoraxy to backend server
|
|
||||||
HeaderDirection_ZoraxyToDownstream HeaderDirection = 1 //Inject (or remove) header to request out-going from Zoraxy to client (e.g. browser)
|
|
||||||
)
|
|
||||||
|
|
||||||
// User defined headers to add into a proxy endpoint
|
|
||||||
type UserDefinedHeader struct {
|
|
||||||
Direction HeaderDirection
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
IsRemove bool //Instead of set, remove this key instead
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Routing Rule Data Structures */
|
/* Routing Rule Data Structures */
|
||||||
|
|
||||||
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||||
@ -113,9 +115,48 @@ type VirtualDirectoryEndpoint struct {
|
|||||||
parent *ProxyEndpoint `json:"-"`
|
parent *ProxyEndpoint `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rules and settings for header rewriting
|
||||||
|
type HeaderRewriteRules struct {
|
||||||
|
UserDefinedHeaders []*rewrite.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 Provider
|
||||||
|
|
||||||
|
TODO: Move these into a dedicated module
|
||||||
|
*/
|
||||||
|
|
||||||
|
type AuthMethod int
|
||||||
|
|
||||||
|
const (
|
||||||
|
AuthMethodNone AuthMethod = iota //No authentication required
|
||||||
|
AuthMethodBasic //Basic Auth
|
||||||
|
AuthMethodAuthelia //Authelia
|
||||||
|
AuthMethodOauth2 //Oauth2
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthenticationProvider struct {
|
||||||
|
AuthMethod AuthMethod //The authentication method to use
|
||||||
|
/* Basic Auth Settings */
|
||||||
|
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
|
||||||
|
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||||
|
BasicAuthGroupIDs []string //Group IDs that are allowed to access this endpoint
|
||||||
|
|
||||||
|
/* Authelia Settings */
|
||||||
|
AutheliaURL string //URL of the Authelia server, leave empty to use global settings e.g. authelia.example.com
|
||||||
|
UseHTTPS bool //Whether to use HTTPS for the Authelia server
|
||||||
|
}
|
||||||
|
|
||||||
// A proxy endpoint record, a general interface for handling inbound routing
|
// A proxy endpoint record, a general interface for handling inbound routing
|
||||||
type ProxyEndpoint struct {
|
type ProxyEndpoint struct {
|
||||||
ProxyType int //The type of this proxy, see const def
|
ProxyType ProxyType //The type of this proxy, see const def
|
||||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||||
ActiveOrigins []*loadbalance.Upstream //Activated Upstream or origin servers IP or domain to proxy to
|
ActiveOrigins []*loadbalance.Upstream //Activated Upstream or origin servers IP or domain to proxy to
|
||||||
@ -131,22 +172,19 @@ type ProxyEndpoint struct {
|
|||||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||||
|
|
||||||
//Custom Headers
|
//Custom Headers
|
||||||
UserDefinedHeaders []*UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
HeaderRewriteRules *HeaderRewriteRules
|
||||||
RequestHostOverwrite string //If not empty, this domain will be used to overwrite the Host field in request header
|
EnableWebsocketCustomHeaders bool //Enable custom headers for websocket connections as well (default only http reqiests)
|
||||||
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
|
||||||
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
|
||||||
PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header
|
|
||||||
DisableHopByHopHeaderRemoval bool //Do not remove hop-by-hop headers
|
|
||||||
|
|
||||||
//Authentication
|
//Authentication
|
||||||
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
AuthenticationProvider *AuthenticationProvider
|
||||||
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
|
|
||||||
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
|
||||||
|
|
||||||
// Rate Limiting
|
// Rate Limiting
|
||||||
RequireRateLimit bool
|
RequireRateLimit bool
|
||||||
RateLimit int64 // Rate limit in requests per second
|
RateLimit int64 // Rate limit in requests per second
|
||||||
|
|
||||||
|
//Uptime Monitor
|
||||||
|
DisableUptimeMonitor bool //Disable uptime monitor for this endpoint
|
||||||
|
|
||||||
//Access Control
|
//Access Control
|
||||||
AccessFilterUUID string //Access filter ID
|
AccessFilterUUID string //Access filter ID
|
||||||
|
|
||||||
@ -171,6 +209,9 @@ const (
|
|||||||
DefaultSite_ReverseProxy = 1
|
DefaultSite_ReverseProxy = 1
|
||||||
DefaultSite_Redirect = 2
|
DefaultSite_Redirect = 2
|
||||||
DefaultSite_NotFoundPage = 3
|
DefaultSite_NotFoundPage = 3
|
||||||
|
DefaultSite_NoResponse = 4
|
||||||
|
|
||||||
|
DefaultSite_TeaPot = 418 //I'm a teapot
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -16,7 +16,7 @@ type Sender struct {
|
|||||||
Port int //E.g. 587
|
Port int //E.g. 587
|
||||||
Username string //Username of the email account
|
Username string //Username of the email account
|
||||||
Password string //Password of the email account
|
Password string //Password of the email account
|
||||||
SenderAddr string //e.g. admin@arozos.com
|
SenderAddr string //e.g. admin@aroz.org
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new email sender object
|
// Create a new email sender object
|
||||||
|
@ -18,7 +18,7 @@ func (this *defaultDialer) Dial(address string) Socket {
|
|||||||
if socket, err := net.DialTimeout("tcp", address, this.timeout); err == nil {
|
if socket, err := net.DialTimeout("tcp", address, this.timeout); err == nil {
|
||||||
return socket
|
return socket
|
||||||
} else {
|
} 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
|
return nil
|
||||||
|
@ -17,7 +17,7 @@ func (this *loggingInitializer) Initialize(client, server Socket) bool {
|
|||||||
result := this.inner.Initialize(client, server)
|
result := this.inner.Initialize(client, server)
|
||||||
|
|
||||||
if !result {
|
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
|
return result
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ganserv
|
package ganserv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
@ -85,6 +86,7 @@ func NewNetworkManager(option *NetworkManagerOptions) *NetworkManager {
|
|||||||
//Get controller info
|
//Get controller info
|
||||||
instanceInfo, err := getControllerInfo(option.AuthToken, option.ApiPort)
|
instanceInfo, err := getControllerInfo(option.AuthToken, option.ApiPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Println("ZeroTier connection failed: ", err.Error())
|
||||||
return &NetworkManager{
|
return &NetworkManager{
|
||||||
authToken: option.AuthToken,
|
authToken: option.AuthToken,
|
||||||
apiPort: option.ApiPort,
|
apiPort: option.ApiPort,
|
||||||
|
@ -28,11 +28,17 @@ type NodeInfo struct {
|
|||||||
Clock int64 `json:"clock"`
|
Clock int64 `json:"clock"`
|
||||||
Config struct {
|
Config struct {
|
||||||
Settings struct {
|
Settings struct {
|
||||||
AllowTCPFallbackRelay bool `json:"allowTcpFallbackRelay"`
|
AllowTCPFallbackRelay bool `json:"allowTcpFallbackRelay,omitempty"`
|
||||||
PortMappingEnabled bool `json:"portMappingEnabled"`
|
ForceTCPRelay bool `json:"forceTcpRelay,omitempty"`
|
||||||
PrimaryPort int `json:"primaryPort"`
|
HomeDir string `json:"homeDir,omitempty"`
|
||||||
SoftwareUpdate string `json:"softwareUpdate"`
|
ListeningOn []string `json:"listeningOn,omitempty"`
|
||||||
SoftwareUpdateChannel string `json:"softwareUpdateChannel"`
|
PortMappingEnabled bool `json:"portMappingEnabled,omitempty"`
|
||||||
|
PrimaryPort int `json:"primaryPort,omitempty"`
|
||||||
|
SecondaryPort int `json:"secondaryPort,omitempty"`
|
||||||
|
SoftwareUpdate string `json:"softwareUpdate,omitempty"`
|
||||||
|
SoftwareUpdateChannel string `json:"softwareUpdateChannel,omitempty"`
|
||||||
|
SurfaceAddresses []string `json:"surfaceAddresses,omitempty"`
|
||||||
|
TertiaryPort int `json:"tertiaryPort,omitempty"`
|
||||||
} `json:"settings"`
|
} `json:"settings"`
|
||||||
} `json:"config"`
|
} `json:"config"`
|
||||||
Online bool `json:"online"`
|
Online bool `json:"online"`
|
||||||
@ -46,7 +52,6 @@ type NodeInfo struct {
|
|||||||
VersionMinor int `json:"versionMinor"`
|
VersionMinor int `json:"versionMinor"`
|
||||||
VersionRev int `json:"versionRev"`
|
VersionRev int `json:"versionRev"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrResp struct {
|
type ErrResp struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,14 @@ package geodb
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/netutils"
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed geoipv4.csv
|
//go:embed geoipv4.csv
|
||||||
@ -15,17 +20,23 @@ var geoipv4 []byte //Geodb dataset for ipv4
|
|||||||
var geoipv6 []byte //Geodb dataset for ipv6
|
var geoipv6 []byte //Geodb dataset for ipv6
|
||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
geodb [][]string //Parsed geodb list
|
geodb [][]string //Parsed geodb list
|
||||||
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
||||||
geotrie *trie
|
geotrie *trie
|
||||||
geotrieIpv6 *trie
|
geotrieIpv6 *trie
|
||||||
sysdb *database.Database
|
sysdb *database.Database
|
||||||
option *StoreOptions
|
slowLookupCacheIpv4 sync.Map //Cache for slow lookup, ip -> cc
|
||||||
|
slowLookupCacheIpv6 sync.Map //Cache for slow lookup ipv6, ip -> cc
|
||||||
|
cacheClearTicker *time.Ticker //Ticker for clearing cache
|
||||||
|
cacheClearTickerStopChan chan bool //Stop channel for cache clear ticker
|
||||||
|
option *StoreOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
type StoreOptions struct {
|
type StoreOptions struct {
|
||||||
AllowSlowIpv4LookUp bool
|
AllowSlowIpv4LookUp bool
|
||||||
AllowSloeIpv6Lookup bool
|
AllowSlowIpv6Lookup bool
|
||||||
|
Logger *logger.Logger
|
||||||
|
SlowLookupCacheClearInterval time.Duration //Clear slow lookup cache interval
|
||||||
}
|
}
|
||||||
|
|
||||||
type CountryInfo struct {
|
type CountryInfo struct {
|
||||||
@ -34,6 +45,23 @@ type CountryInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
|
func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
|
||||||
|
//Check if external geoDB data is available
|
||||||
|
if utils.FileExists("./conf/geodb/geoipv4.csv") {
|
||||||
|
externalV4Db, err := os.ReadFile("./conf/geodb/geoipv4.csv")
|
||||||
|
if err == nil {
|
||||||
|
option.Logger.PrintAndLog("GeoDB", "External GeoDB data found, using external IPv4 GeoIP data", nil)
|
||||||
|
geoipv4 = externalV4Db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.FileExists("./conf/geodb/geoipv6.csv") {
|
||||||
|
externalV6Db, err := os.ReadFile("./conf/geodb/geoipv6.csv")
|
||||||
|
if err == nil {
|
||||||
|
option.Logger.PrintAndLog("GeoDB", "External GeoDB data found, using external IPv6 GeoIP data", nil)
|
||||||
|
geoipv6 = externalV6Db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parsedGeoData, err := parseCSV(geoipv4)
|
parsedGeoData, err := parseCSV(geoipv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -50,18 +78,44 @@ func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ipv6Trie *trie
|
var ipv6Trie *trie
|
||||||
if !option.AllowSloeIpv6Lookup {
|
if !option.AllowSlowIpv6Lookup {
|
||||||
ipv6Trie = constrctTrieTree(parsedGeoDataIpv6)
|
ipv6Trie = constrctTrieTree(parsedGeoDataIpv6)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Store{
|
if option.SlowLookupCacheClearInterval == 0 {
|
||||||
geodb: parsedGeoData,
|
option.SlowLookupCacheClearInterval = 30 * time.Minute
|
||||||
geotrie: ipv4Trie,
|
}
|
||||||
geodbIpv6: parsedGeoDataIpv6,
|
|
||||||
geotrieIpv6: ipv6Trie,
|
//Create a new store
|
||||||
sysdb: sysdb,
|
thisGeoDBStore := &Store{
|
||||||
option: option,
|
geodb: parsedGeoData,
|
||||||
}, nil
|
geotrie: ipv4Trie,
|
||||||
|
geodbIpv6: parsedGeoDataIpv6,
|
||||||
|
geotrieIpv6: ipv6Trie,
|
||||||
|
sysdb: sysdb,
|
||||||
|
slowLookupCacheIpv4: sync.Map{},
|
||||||
|
slowLookupCacheIpv6: sync.Map{},
|
||||||
|
cacheClearTicker: time.NewTicker(option.SlowLookupCacheClearInterval),
|
||||||
|
cacheClearTickerStopChan: make(chan bool),
|
||||||
|
option: option,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Start cache clear ticker
|
||||||
|
if option.AllowSlowIpv4LookUp || option.AllowSlowIpv6Lookup {
|
||||||
|
go func(store *Store) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-store.cacheClearTickerStopChan:
|
||||||
|
return
|
||||||
|
case <-thisGeoDBStore.cacheClearTicker.C:
|
||||||
|
thisGeoDBStore.slowLookupCacheIpv4 = sync.Map{}
|
||||||
|
thisGeoDBStore.slowLookupCacheIpv6 = sync.Map{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(thisGeoDBStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
return thisGeoDBStore, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) {
|
func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) {
|
||||||
@ -73,8 +127,12 @@ func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close the store
|
||||||
func (s *Store) Close() {
|
func (s *Store) Close() {
|
||||||
|
if s.option.AllowSlowIpv4LookUp || s.option.AllowSlowIpv6Lookup {
|
||||||
|
//Stop cache clear ticker
|
||||||
|
s.cacheClearTickerStopChan <- true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
|
func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/geodb"
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -42,8 +43,10 @@ func TestTrieConstruct(t *testing.T) {
|
|||||||
func TestResolveCountryCodeFromIP(t *testing.T) {
|
func TestResolveCountryCodeFromIP(t *testing.T) {
|
||||||
// Create a new store
|
// Create a new store
|
||||||
store, err := geodb.NewGeoDb(nil, &geodb.StoreOptions{
|
store, err := geodb.NewGeoDb(nil, &geodb.StoreOptions{
|
||||||
false,
|
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
|
&logger.Logger{},
|
||||||
|
0,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error creating store: %v", err)
|
t.Errorf("error creating store: %v", err)
|
||||||
@ -83,4 +86,24 @@ func TestResolveCountryCodeFromIP(t *testing.T) {
|
|||||||
if info.CountryIsoCode != expected {
|
if info.CountryIsoCode != expected {
|
||||||
t.Errorf("expected country code %s, but got %s for IP %s", expected, info.CountryIsoCode, ip)
|
t.Errorf("expected country code %s, but got %s for IP %s", expected, info.CountryIsoCode, ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test for issue #401
|
||||||
|
// Create 100 concurrent goroutines to resolve country code for random IP addresses in the test cases above
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
go func() {
|
||||||
|
for _, testcase := range knownIpCountryMap {
|
||||||
|
ip := testcase[0]
|
||||||
|
expected := testcase[1]
|
||||||
|
info, err := store.ResolveCountryCodeFromIP(ip)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error resolving country code for IP %s: %v", ip, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if info.CountryIsoCode != expected {
|
||||||
|
t.Errorf("expected country code %s, but got %s for IP %s", expected, info.CountryIsoCode, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
133777
src/mod/geodb/geoipv4.csv
133777
src/mod/geodb/geoipv4.csv
File diff suppressed because it is too large
Load Diff
106276
src/mod/geodb/geoipv6.csv
106276
src/mod/geodb/geoipv6.csv
File diff suppressed because it is too large
Load Diff
67
src/mod/geodb/locale.go
Normal file
67
src/mod/geodb/locale.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package geodb
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// GetRequesterCountryISOCode get the locale of the requester
|
||||||
|
func (s *Store) GetLocaleFromRequest(r *http.Request) (string, error) {
|
||||||
|
cc := s.GetRequesterCountryISOCode(r)
|
||||||
|
return GetLocaleFromCountryCode(cc), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocaleFromCountryCode get the locale given the country code
|
||||||
|
func GetLocaleFromCountryCode(cc string) string {
|
||||||
|
//If you find your country is not in the list, please add it here
|
||||||
|
mapCountryToLocale := map[string]string{
|
||||||
|
"aa": "ar_AA",
|
||||||
|
"by": "be_BY",
|
||||||
|
"bg": "bg_BG",
|
||||||
|
"es": "ca_ES",
|
||||||
|
"cz": "cs_CZ",
|
||||||
|
"dk": "da_DK",
|
||||||
|
"ch": "de_CH",
|
||||||
|
"de": "de_DE",
|
||||||
|
"gr": "el_GR",
|
||||||
|
"au": "en_AU",
|
||||||
|
"be": "en_BE",
|
||||||
|
"gb": "en_GB",
|
||||||
|
"jp": "en_JP",
|
||||||
|
"us": "en_US",
|
||||||
|
"za": "en_ZA",
|
||||||
|
"fi": "fi_FI",
|
||||||
|
"ca": "fr_CA",
|
||||||
|
"fr": "fr_FR",
|
||||||
|
"hr": "hr_HR",
|
||||||
|
"hu": "hu_HU",
|
||||||
|
"is": "is_IS",
|
||||||
|
"it": "it_IT",
|
||||||
|
"il": "iw_IL",
|
||||||
|
"kr": "ko_KR",
|
||||||
|
"lt": "lt_LT",
|
||||||
|
"lv": "lv_LV",
|
||||||
|
"mk": "mk_MK",
|
||||||
|
"nl": "nl_NL",
|
||||||
|
"no": "no_NO",
|
||||||
|
"pl": "pl_PL",
|
||||||
|
"br": "pt_BR",
|
||||||
|
"pt": "pt_PT",
|
||||||
|
"ro": "ro_RO",
|
||||||
|
"ru": "ru_RU",
|
||||||
|
"sp": "sh_SP",
|
||||||
|
"sk": "sk_SK",
|
||||||
|
"sl": "sl_SL",
|
||||||
|
"al": "sq_AL",
|
||||||
|
"se": "sv_SE",
|
||||||
|
"th": "th_TH",
|
||||||
|
"tr": "tr_TR",
|
||||||
|
"ua": "uk_UA",
|
||||||
|
"cn": "zh_CN",
|
||||||
|
"tw": "zh_TW",
|
||||||
|
"hk": "zh_HK",
|
||||||
|
}
|
||||||
|
locale, ok := mapCountryToLocale[cc]
|
||||||
|
if !ok {
|
||||||
|
return "en-US"
|
||||||
|
}
|
||||||
|
|
||||||
|
return locale
|
||||||
|
}
|
@ -56,6 +56,13 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
|
|||||||
if isReservedIP(ipAddr) {
|
if isReservedIP(ipAddr) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check if already in cache
|
||||||
|
cc := s.GetSlowSearchCachedIpv4(ipAddr)
|
||||||
|
if cc != "" {
|
||||||
|
return cc
|
||||||
|
}
|
||||||
|
|
||||||
for _, ipRange := range s.geodb {
|
for _, ipRange := range s.geodb {
|
||||||
startIp := ipRange[0]
|
startIp := ipRange[0]
|
||||||
endIp := ipRange[1]
|
endIp := ipRange[1]
|
||||||
@ -63,6 +70,8 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
|
|||||||
|
|
||||||
inRange, _ := isIPv4InRange(startIp, endIp, ipAddr)
|
inRange, _ := isIPv4InRange(startIp, endIp, ipAddr)
|
||||||
if inRange {
|
if inRange {
|
||||||
|
//Add to cache
|
||||||
|
s.slowLookupCacheIpv4.Store(ipAddr, cc)
|
||||||
return cc
|
return cc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,6 +82,13 @@ func (s *Store) slowSearchIpv6(ipAddr string) string {
|
|||||||
if isReservedIP(ipAddr) {
|
if isReservedIP(ipAddr) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check if already in cache
|
||||||
|
cc := s.GetSlowSearchCachedIpv6(ipAddr)
|
||||||
|
if cc != "" {
|
||||||
|
return cc
|
||||||
|
}
|
||||||
|
|
||||||
for _, ipRange := range s.geodbIpv6 {
|
for _, ipRange := range s.geodbIpv6 {
|
||||||
startIp := ipRange[0]
|
startIp := ipRange[0]
|
||||||
endIp := ipRange[1]
|
endIp := ipRange[1]
|
||||||
@ -80,8 +96,28 @@ func (s *Store) slowSearchIpv6(ipAddr string) string {
|
|||||||
|
|
||||||
inRange, _ := isIPv6InRange(startIp, endIp, ipAddr)
|
inRange, _ := isIPv6InRange(startIp, endIp, ipAddr)
|
||||||
if inRange {
|
if inRange {
|
||||||
|
//Add to cache
|
||||||
|
s.slowLookupCacheIpv6.Store(ipAddr, cc)
|
||||||
return cc
|
return cc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSlowSearchCachedIpv4 return the country code for the given ipv4 address, return empty string if not found
|
||||||
|
func (s *Store) GetSlowSearchCachedIpv4(ipAddr string) string {
|
||||||
|
cc, ok := s.slowLookupCacheIpv4.Load(ipAddr)
|
||||||
|
if ok {
|
||||||
|
return cc.(string)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSlowSearchCachedIpv6 return the country code for the given ipv6 address, return empty string if not found
|
||||||
|
func (s *Store) GetSlowSearchCachedIpv6(ipAddr string) string {
|
||||||
|
cc, ok := s.slowLookupCacheIpv6.Load(ipAddr)
|
||||||
|
if ok {
|
||||||
|
return cc.(string)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
56
src/mod/geodb/updater.go
Normal file
56
src/mod/geodb/updater.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package geodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ipv4UpdateSource = "https://cdn.jsdelivr.net/npm/@ip-location-db/geo-whois-asn-country/geo-whois-asn-country-ipv4.csv"
|
||||||
|
ipv6UpdateSource = "https://cdn.jsdelivr.net/npm/@ip-location-db/geo-whois-asn-country/geo-whois-asn-country-ipv6.csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DownloadGeoDBUpdate download the latest geodb update
|
||||||
|
func DownloadGeoDBUpdate(externalGeoDBStoragePath string) {
|
||||||
|
//Create the storage path if not exist
|
||||||
|
if !utils.FileExists(externalGeoDBStoragePath) {
|
||||||
|
os.MkdirAll(externalGeoDBStoragePath, 0755)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Download the update
|
||||||
|
log.Println("Downloading IPv4 database update...")
|
||||||
|
err := downloadFile(ipv4UpdateSource, externalGeoDBStoragePath+"/geoipv4.csv")
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Downloading IPv6 database update...")
|
||||||
|
err = downloadFile(ipv6UpdateSource, externalGeoDBStoragePath+"/geoipv6.csv")
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("GeoDB update stored at: " + externalGeoDBStoragePath)
|
||||||
|
log.Println("Exiting...")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
func downloadFile(url string, savepath string) error {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
fileContent, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(savepath, fileContent, 0644)
|
||||||
|
}
|
79
src/mod/ipscan/handlers.go
Normal file
79
src/mod/ipscan/handlers.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package ipscan
|
||||||
|
|
||||||
|
/*
|
||||||
|
ipscan http handlers
|
||||||
|
|
||||||
|
This script provide http handlers for ipscan module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleScanPort is the HTTP handler for scanning opened ports on a given IP address
|
||||||
|
func HandleScanPort(w http.ResponseWriter, r *http.Request) {
|
||||||
|
targetIp, err := utils.GetPara(r, "ip")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target IP address not given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the IP is a valid IP address
|
||||||
|
ip := net.ParseIP(targetIp)
|
||||||
|
if ip == nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid IP address")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan the ports
|
||||||
|
openPorts := ScanPorts(targetIp)
|
||||||
|
jsonData, err := json.Marshal(openPorts)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "failed to marshal JSON")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendJSONResponse(w, string(jsonData))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleIpScan is the HTTP handler for scanning IP addresses in a given range or CIDR
|
||||||
|
func HandleIpScan(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cidr, err := utils.PostPara(r, "cidr")
|
||||||
|
if err != nil {
|
||||||
|
//Ip range mode
|
||||||
|
start, err := utils.PostPara(r, "start")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "missing start ip")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
end, err := utils.PostPara(r, "end")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "missing end ip")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
discoveredHosts, err := ScanIpRange(start, end)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(discoveredHosts)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
} else {
|
||||||
|
//CIDR mode
|
||||||
|
discoveredHosts, err := ScanCIDRRange(cidr)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(discoveredHosts)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,7 @@ type DiscoveredHost struct {
|
|||||||
HttpsPortDetected bool
|
HttpsPortDetected bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//Scan an IP range given the start and ending ip address
|
// Scan an IP range given the start and ending ip address
|
||||||
func ScanIpRange(start, end string) ([]*DiscoveredHost, error) {
|
func ScanIpRange(start, end string) ([]*DiscoveredHost, error) {
|
||||||
ipStart := net.ParseIP(start)
|
ipStart := net.ParseIP(start)
|
||||||
ipEnd := net.ParseIP(end)
|
ipEnd := net.ParseIP(end)
|
||||||
@ -57,7 +57,6 @@ func ScanIpRange(start, end string) ([]*DiscoveredHost, error) {
|
|||||||
host.CheckHostname()
|
host.CheckHostname()
|
||||||
host.CheckPort("http", 80, &host.HttpPortDetected)
|
host.CheckPort("http", 80, &host.HttpPortDetected)
|
||||||
host.CheckPort("https", 443, &host.HttpsPortDetected)
|
host.CheckPort("https", 443, &host.HttpsPortDetected)
|
||||||
fmt.Println("OK", host)
|
|
||||||
hosts = append(hosts, host)
|
hosts = append(hosts, host)
|
||||||
|
|
||||||
}(thisIp)
|
}(thisIp)
|
||||||
@ -118,7 +117,7 @@ func (host *DiscoveredHost) CheckPing() error {
|
|||||||
func (host *DiscoveredHost) CheckHostname() {
|
func (host *DiscoveredHost) CheckHostname() {
|
||||||
// lookup the hostname for the IP address
|
// lookup the hostname for the IP address
|
||||||
names, err := net.LookupAddr(host.IP)
|
names, err := net.LookupAddr(host.IP)
|
||||||
fmt.Println(names, err)
|
//fmt.Println(names, err)
|
||||||
if err == nil && len(names) > 0 {
|
if err == nil && len(names) > 0 {
|
||||||
host.Hostname = names[0]
|
host.Hostname = names[0]
|
||||||
}
|
}
|
||||||
|
48
src/mod/ipscan/portscan.go
Normal file
48
src/mod/ipscan/portscan.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package ipscan
|
||||||
|
|
||||||
|
/*
|
||||||
|
Port Scanner
|
||||||
|
|
||||||
|
This module scan the given IP address and scan all the opened port
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenedPort holds information about an open port and its service type
|
||||||
|
type OpenedPort struct {
|
||||||
|
Port int
|
||||||
|
IsTCP bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScanPorts scans all the opened ports on a given host IP (both IPv4 and IPv6)
|
||||||
|
func ScanPorts(host string) []*OpenedPort {
|
||||||
|
var openPorts []*OpenedPort
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
for port := 1; port <= 65535; port++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(port int) {
|
||||||
|
defer wg.Done()
|
||||||
|
address := fmt.Sprintf("%s:%d", host, port)
|
||||||
|
|
||||||
|
// Check TCP
|
||||||
|
conn, err := net.DialTimeout("tcp", address, 5*time.Second)
|
||||||
|
if err == nil {
|
||||||
|
mu.Lock()
|
||||||
|
openPorts = append(openPorts, &OpenedPort{Port: port, IsTCP: true})
|
||||||
|
mu.Unlock()
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
return openPorts
|
||||||
|
}
|
@ -169,9 +169,16 @@ func (n *NetStatBuffers) HandleGetBufferedNetworkInterfaceStats(w http.ResponseW
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *NetStatBuffers) Close() {
|
func (n *NetStatBuffers) Close() {
|
||||||
n.StopChan <- true
|
//Fixed issue #394 for stopping netstat listener on platforms not supported platforms
|
||||||
time.Sleep(300 * time.Millisecond)
|
if n.StopChan != nil {
|
||||||
n.EventTicker.Stop()
|
n.StopChan <- true
|
||||||
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.EventTicker != nil {
|
||||||
|
n.EventTicker.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NetStatBuffers) HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
|
func (n *NetStatBuffers) HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -270,11 +277,11 @@ func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) {
|
|||||||
allIfaceRxByteFiles, err := filepath.Glob("/sys/class/net/*/statistics/rx_bytes")
|
allIfaceRxByteFiles, err := filepath.Glob("/sys/class/net/*/statistics/rx_bytes")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//Permission denied
|
//Permission denied
|
||||||
return 0, 0, errors.New("Access denied")
|
return 0, 0, errors.New("access denied")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(allIfaceRxByteFiles) == 0 {
|
if len(allIfaceRxByteFiles) == 0 {
|
||||||
return 0, 0, errors.New("No valid iface found")
|
return 0, 0, errors.New("no valid iface found")
|
||||||
}
|
}
|
||||||
|
|
||||||
rxSum := int64(0)
|
rxSum := int64(0)
|
||||||
@ -334,5 +341,5 @@ func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) {
|
|||||||
return 0, 0, nil //no ethernet adapters with en*/<Link#*>
|
return 0, 0, nil //no ethernet adapters with en*/<Link#*>
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, 0, errors.New("Platform not supported")
|
return 0, 0, errors.New("platform not supported")
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,15 @@ import (
|
|||||||
func GetRequesterIP(r *http.Request) string {
|
func GetRequesterIP(r *http.Request) string {
|
||||||
ip := r.Header.Get("X-Real-Ip")
|
ip := r.Header.Get("X-Real-Ip")
|
||||||
if ip == "" {
|
if 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
|
||||||
|
return CF_Connecting_IP
|
||||||
|
} else if Fastly_Client_IP != "" {
|
||||||
|
//Use Fastly Client IP
|
||||||
|
return Fastly_Client_IP
|
||||||
|
}
|
||||||
ip = r.Header.Get("X-Forwarded-For")
|
ip = r.Header.Get("X-Forwarded-For")
|
||||||
}
|
}
|
||||||
if ip == "" {
|
if ip == "" {
|
||||||
|
@ -50,21 +50,6 @@ func NewSSHProxyManager() *Manager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the next free port in the list
|
|
||||||
func (m *Manager) GetNextPort() int {
|
|
||||||
nextPort := m.StartingPort
|
|
||||||
occupiedPort := make(map[int]bool)
|
|
||||||
for _, instance := range m.Instances {
|
|
||||||
occupiedPort[instance.AssignedPort] = true
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
if !occupiedPort[nextPort] {
|
|
||||||
return nextPort
|
|
||||||
}
|
|
||||||
nextPort++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWriter, r *http.Request) {
|
func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWriter, r *http.Request) {
|
||||||
targetInstance, err := m.GetInstanceById(instanceId)
|
targetInstance, err := m.GetInstanceById(instanceId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -88,6 +73,7 @@ func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWrite
|
|||||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||||
SkipTLSValidation: false,
|
SkipTLSValidation: false,
|
||||||
SkipOriginCheck: false,
|
SkipOriginCheck: false,
|
||||||
|
Logger: nil,
|
||||||
})
|
})
|
||||||
wspHandler.ServeHTTP(w, r)
|
wspHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
@ -167,6 +153,17 @@ func (i *Instance) CreateNewConnection(listenPort int, username string, remoteIp
|
|||||||
if username != "" {
|
if username != "" {
|
||||||
connAddr = username + "@" + remoteIpAddr
|
connAddr = username + "@" + remoteIpAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Trim the space in the username and remote address
|
||||||
|
username = strings.TrimSpace(username)
|
||||||
|
remoteIpAddr = strings.TrimSpace(remoteIpAddr)
|
||||||
|
|
||||||
|
//Validate the username and remote address
|
||||||
|
err := ValidateUsernameAndRemoteAddr(username, remoteIpAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
configPath := filepath.Join(filepath.Dir(i.ExecPath), ".gotty")
|
configPath := filepath.Join(filepath.Dir(i.ExecPath), ".gotty")
|
||||||
title := username + "@" + remoteIpAddr
|
title := username + "@" + remoteIpAddr
|
||||||
if remotePort != 22 {
|
if remotePort != 22 {
|
||||||
|
66
src/mod/sshprox/sshprox_test.go
Normal file
66
src/mod/sshprox/sshprox_test.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package sshprox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInstance_Destroy(t *testing.T) {
|
||||||
|
manager := NewSSHProxyManager()
|
||||||
|
instance, err := manager.NewSSHProxy("/tmp")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create new SSH proxy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.Destroy()
|
||||||
|
|
||||||
|
if len(manager.Instances) != 0 {
|
||||||
|
t.Errorf("Expected Instances to be empty, got %d", len(manager.Instances))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstance_ValidateUsernameAndRemoteAddr(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
username string
|
||||||
|
remoteAddr string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"validuser", "127.0.0.1", false},
|
||||||
|
{"valid.user", "example.com", false},
|
||||||
|
{"; bash ;", "example.com", true},
|
||||||
|
{"valid-user", "example.com", false},
|
||||||
|
{"invalid user", "127.0.0.1", true},
|
||||||
|
{"validuser", "invalid address", true},
|
||||||
|
{"invalid@user", "127.0.0.1", true},
|
||||||
|
{"validuser", "invalid@address", true},
|
||||||
|
{"injection; rm -rf /", "127.0.0.1", true},
|
||||||
|
{"validuser", "127.0.0.1; rm -rf /", true},
|
||||||
|
{"$(reboot)", "127.0.0.1", true},
|
||||||
|
{"validuser", "$(reboot)", true},
|
||||||
|
{"validuser", "127.0.0.1; $(reboot)", true},
|
||||||
|
{"validuser", "127.0.0.1 | ls", true},
|
||||||
|
{"validuser", "127.0.0.1 & ls", true},
|
||||||
|
{"validuser", "127.0.0.1 && ls", true},
|
||||||
|
{"validuser", "127.0.0.1 |& ls", true},
|
||||||
|
{"validuser", "127.0.0.1 ; ls", true},
|
||||||
|
{"validuser", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", false},
|
||||||
|
{"validuser", "2001:db8::ff00:42:8329", false},
|
||||||
|
{"validuser", "2001:db8:0:1234:0:567:8:1", false},
|
||||||
|
{"validuser", "2001:db8::1234:0:567:8:1", false},
|
||||||
|
{"validuser", "2001:db8:0:0:0:0:2:1", false},
|
||||||
|
{"validuser", "2001:db8::2:1", false},
|
||||||
|
{"validuser", "2001:db8:0:0:8:800:200c:417a", false},
|
||||||
|
{"validuser", "2001:db8::8:800:200c:417a", false},
|
||||||
|
{"validuser", "2001:db8:0:0:8:800:200c:417a; rm -rf /", true},
|
||||||
|
{"validuser", "2001:db8::8:800:200c:417a; rm -rf /", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
err := ValidateUsernameAndRemoteAddr(test.username, test.remoteAddr)
|
||||||
|
if test.expectError && err == nil {
|
||||||
|
t.Errorf("Expected error for username %s and remoteAddr %s, but got none", test.username, test.remoteAddr)
|
||||||
|
}
|
||||||
|
if !test.expectError && err != nil {
|
||||||
|
t.Errorf("Did not expect error for username %s and remoteAddr %s, but got %v", test.username, test.remoteAddr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
package sshprox
|
package sshprox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -34,6 +36,21 @@ func IsWebSSHSupported() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the next free port in the list
|
||||||
|
func (m *Manager) GetNextPort() int {
|
||||||
|
nextPort := m.StartingPort
|
||||||
|
occupiedPort := make(map[int]bool)
|
||||||
|
for _, instance := range m.Instances {
|
||||||
|
occupiedPort[instance.AssignedPort] = true
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if !occupiedPort[nextPort] {
|
||||||
|
return nextPort
|
||||||
|
}
|
||||||
|
nextPort++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if a given domain and port is a valid ssh server
|
// Check if a given domain and port is a valid ssh server
|
||||||
func IsSSHConnectable(ipOrDomain string, port int) bool {
|
func IsSSHConnectable(ipOrDomain string, port int) bool {
|
||||||
timeout := time.Second * 3
|
timeout := time.Second * 3
|
||||||
@ -60,13 +77,47 @@ func IsSSHConnectable(ipOrDomain string, port int) bool {
|
|||||||
return string(buf[:7]) == "SSH-2.0"
|
return string(buf[:7]) == "SSH-2.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the port is used by other process or application
|
// Validate the username and remote address to prevent injection
|
||||||
func isPortInUse(port int) bool {
|
func ValidateUsernameAndRemoteAddr(username string, remoteIpAddr string) error {
|
||||||
address := fmt.Sprintf(":%d", port)
|
// Validate and sanitize the username to prevent ssh injection
|
||||||
listener, err := net.Listen("tcp", address)
|
validUsername := regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
|
||||||
if err != nil {
|
if !validUsername.MatchString(username) {
|
||||||
|
return errors.New("invalid username, only alphanumeric characters, dots, underscores and dashes are allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the remoteIpAddr is a valid ipv4 or ipv6 address
|
||||||
|
if net.ParseIP(remoteIpAddr) != nil {
|
||||||
|
//A valid IP address do not need further validation
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and sanitize the remote domain to prevent injection
|
||||||
|
validRemoteAddr := regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
|
||||||
|
if !validRemoteAddr.MatchString(remoteIpAddr) {
|
||||||
|
return errors.New("invalid remote address, only alphanumeric characters, dots, underscores and dashes are allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the given ip or domain is a loopback address
|
||||||
|
// or resolves to a loopback address
|
||||||
|
func IsLoopbackIPOrDomain(ipOrDomain string) bool {
|
||||||
|
if strings.EqualFold(strings.TrimSpace(ipOrDomain), "localhost") || strings.TrimSpace(ipOrDomain) == "127.0.0.1" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
listener.Close()
|
|
||||||
|
//Check if the ipOrDomain resolves to a loopback address
|
||||||
|
ips, err := net.LookupIP(ipOrDomain)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if ip.IsLoopback() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -33,15 +33,15 @@ type DailySummary struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RequestInfo struct {
|
type RequestInfo struct {
|
||||||
IpAddr string
|
IpAddr string //IP address of the downstream request
|
||||||
RequestOriginalCountryISOCode string
|
RequestOriginalCountryISOCode string //ISO code of the country where the request originated
|
||||||
Succ bool
|
Succ bool //If the request is successful and resp generated by upstream instead of Zoraxy (except static web server)
|
||||||
StatusCode int
|
StatusCode int //HTTP status code of the request
|
||||||
ForwardType string
|
ForwardType string //Forward type of the request, usually the proxy type (e.g. host-http, subdomain-websocket or vdir-http or any of the combination)
|
||||||
Referer string
|
Referer string //Referer of the downstream request
|
||||||
UserAgent string
|
UserAgent string //UserAgent of the downstream request
|
||||||
RequestURL string
|
RequestURL string //Request URL
|
||||||
Target string
|
Target string //Target domain or hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
type CollectorOption struct {
|
type CollectorOption struct {
|
||||||
@ -59,7 +59,7 @@ func NewStatisticCollector(option CollectorOption) (*Collector, error) {
|
|||||||
|
|
||||||
//Create the collector object
|
//Create the collector object
|
||||||
thisCollector := Collector{
|
thisCollector := Collector{
|
||||||
DailySummary: newDailySummary(),
|
DailySummary: NewDailySummary(),
|
||||||
Option: &option,
|
Option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +87,11 @@ func (c *Collector) SaveSummaryOfDay() {
|
|||||||
c.Option.Database.Write("stats", summaryKey, saveData)
|
c.Option.Database.Write("stats", summaryKey, saveData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the daily summary up until now
|
||||||
|
func (c *Collector) GetCurrentDailySummary() *DailySummary {
|
||||||
|
return c.DailySummary
|
||||||
|
}
|
||||||
|
|
||||||
// Load the summary of a day given
|
// Load the summary of a day given
|
||||||
func (c *Collector) LoadSummaryOfDay(year int, month time.Month, day int) *DailySummary {
|
func (c *Collector) LoadSummaryOfDay(year int, month time.Month, day int) *DailySummary {
|
||||||
date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
|
date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
|
||||||
@ -99,7 +104,7 @@ func (c *Collector) LoadSummaryOfDay(year int, month time.Month, day int) *Daily
|
|||||||
|
|
||||||
// Reset today summary, for debug or restoring injections
|
// Reset today summary, for debug or restoring injections
|
||||||
func (c *Collector) ResetSummaryOfDay() {
|
func (c *Collector) ResetSummaryOfDay() {
|
||||||
c.DailySummary = newDailySummary()
|
c.DailySummary = NewDailySummary()
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function gives the current slot in the 288- 5 minutes interval of the day
|
// This function gives the current slot in the 288- 5 minutes interval of the day
|
||||||
@ -185,8 +190,6 @@ func (c *Collector) RecordRequest(ri RequestInfo) {
|
|||||||
c.DailySummary.UserAgent.Store(ri.UserAgent, ua.(int)+1)
|
c.DailySummary.UserAgent.Store(ri.UserAgent, ua.(int)+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
//ADD MORE HERE IF NEEDED
|
|
||||||
|
|
||||||
//Record request URL, if it is a page
|
//Record request URL, if it is a page
|
||||||
ext := filepath.Ext(ri.RequestURL)
|
ext := filepath.Ext(ri.RequestURL)
|
||||||
|
|
||||||
@ -201,6 +204,8 @@ func (c *Collector) RecordRequest(ri RequestInfo) {
|
|||||||
c.DailySummary.RequestURL.Store(ri.RequestURL, ru.(int)+1)
|
c.DailySummary.RequestURL.Store(ri.RequestURL, ru.(int)+1)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
//ADD MORE HERE IF NEEDED
|
||||||
}
|
}
|
||||||
|
|
||||||
// nightly task
|
// nightly task
|
||||||
@ -223,7 +228,7 @@ func (c *Collector) ScheduleResetRealtimeStats() chan bool {
|
|||||||
case <-time.After(duration):
|
case <-time.After(duration):
|
||||||
// store daily summary to database and reset summary
|
// store daily summary to database and reset summary
|
||||||
c.SaveSummaryOfDay()
|
c.SaveSummaryOfDay()
|
||||||
c.DailySummary = newDailySummary()
|
c.DailySummary = NewDailySummary()
|
||||||
case <-doneCh:
|
case <-doneCh:
|
||||||
// stop the routine
|
// stop the routine
|
||||||
return
|
return
|
||||||
@ -234,7 +239,7 @@ func (c *Collector) ScheduleResetRealtimeStats() chan bool {
|
|||||||
return doneCh
|
return doneCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDailySummary() *DailySummary {
|
func NewDailySummary() *DailySummary {
|
||||||
return &DailySummary{
|
return &DailySummary{
|
||||||
TotalRequest: 0,
|
TotalRequest: 0,
|
||||||
ErrorRequest: 0,
|
ErrorRequest: 0,
|
||||||
@ -247,3 +252,30 @@ func newDailySummary() *DailySummary {
|
|||||||
RequestURL: &sync.Map{},
|
RequestURL: &sync.Map{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PrintDailySummary(summary *DailySummary) {
|
||||||
|
summary.ForwardTypes.Range(func(key, value interface{}) bool {
|
||||||
|
println(key.(string), value.(int))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
summary.RequestOrigin.Range(func(key, value interface{}) bool {
|
||||||
|
println(key.(string), value.(int))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
summary.RequestClientIp.Range(func(key, value interface{}) bool {
|
||||||
|
println(key.(string), value.(int))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
summary.Referer.Range(func(key, value interface{}) bool {
|
||||||
|
println(key.(string), value.(int))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
summary.UserAgent.Range(func(key, value interface{}) bool {
|
||||||
|
println(key.(string), value.(int))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
summary.RequestURL.Range(func(key, value interface{}) bool {
|
||||||
|
println(key.(string), value.(int))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
215
src/mod/statistic/statistic_test.go
Normal file
215
src/mod/statistic/statistic_test.go
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
package statistic_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/database/dbinc"
|
||||||
|
"imuslab.com/zoraxy/mod/geodb"
|
||||||
|
"imuslab.com/zoraxy/mod/statistic"
|
||||||
|
)
|
||||||
|
|
||||||
|
const test_db_path = "test_db"
|
||||||
|
|
||||||
|
func getNewDatabase() *database.Database {
|
||||||
|
db, err := database.NewDatabase(test_db_path, dbinc.BackendLevelDB)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
db.NewTable("stats")
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearDatabase(db *database.Database) {
|
||||||
|
db.Close()
|
||||||
|
os.RemoveAll(test_db_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewStatisticCollector(t *testing.T) {
|
||||||
|
db := getNewDatabase()
|
||||||
|
defer clearDatabase(db)
|
||||||
|
option := statistic.CollectorOption{Database: db}
|
||||||
|
collector, err := statistic.NewStatisticCollector(option)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if collector == nil {
|
||||||
|
t.Fatalf("Expected collector, got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveSummaryOfDay(t *testing.T) {
|
||||||
|
db := getNewDatabase()
|
||||||
|
defer clearDatabase(db)
|
||||||
|
option := statistic.CollectorOption{Database: db}
|
||||||
|
collector, _ := statistic.NewStatisticCollector(option)
|
||||||
|
collector.SaveSummaryOfDay()
|
||||||
|
// Add assertions to check if data is saved correctly
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadSummaryOfDay(t *testing.T) {
|
||||||
|
db := getNewDatabase()
|
||||||
|
defer clearDatabase(db)
|
||||||
|
option := statistic.CollectorOption{Database: db}
|
||||||
|
collector, _ := statistic.NewStatisticCollector(option)
|
||||||
|
year, month, day := time.Now().Date()
|
||||||
|
summary := collector.LoadSummaryOfDay(year, month, day)
|
||||||
|
if summary == nil {
|
||||||
|
t.Fatalf("Expected summary, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResetSummaryOfDay(t *testing.T) {
|
||||||
|
db := getNewDatabase()
|
||||||
|
defer clearDatabase(db)
|
||||||
|
option := statistic.CollectorOption{Database: db}
|
||||||
|
collector, _ := statistic.NewStatisticCollector(option)
|
||||||
|
collector.ResetSummaryOfDay()
|
||||||
|
if collector.DailySummary.TotalRequest != 0 {
|
||||||
|
t.Fatalf("Expected TotalRequest to be 0, got %v", collector.DailySummary.TotalRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCurrentRealtimeStatIntervalId(t *testing.T) {
|
||||||
|
db := getNewDatabase()
|
||||||
|
defer clearDatabase(db)
|
||||||
|
option := statistic.CollectorOption{Database: db}
|
||||||
|
collector, _ := statistic.NewStatisticCollector(option)
|
||||||
|
intervalId := collector.GetCurrentRealtimeStatIntervalId()
|
||||||
|
if intervalId < 0 || intervalId > 287 {
|
||||||
|
t.Fatalf("Expected intervalId to be between 0 and 287, got %v", intervalId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecordRequest(t *testing.T) {
|
||||||
|
db := getNewDatabase()
|
||||||
|
defer clearDatabase(db)
|
||||||
|
option := statistic.CollectorOption{Database: db}
|
||||||
|
collector, _ := statistic.NewStatisticCollector(option)
|
||||||
|
requestInfo := statistic.RequestInfo{
|
||||||
|
IpAddr: "127.0.0.1",
|
||||||
|
RequestOriginalCountryISOCode: "US",
|
||||||
|
Succ: true,
|
||||||
|
StatusCode: 200,
|
||||||
|
ForwardType: "type1",
|
||||||
|
Referer: "http://example.com",
|
||||||
|
UserAgent: "Mozilla/5.0",
|
||||||
|
RequestURL: "/test",
|
||||||
|
Target: "target1",
|
||||||
|
}
|
||||||
|
collector.RecordRequest(requestInfo)
|
||||||
|
time.Sleep(1 * time.Second) // Wait for the goroutine to finish
|
||||||
|
if collector.DailySummary.TotalRequest != 1 {
|
||||||
|
t.Fatalf("Expected TotalRequest to be 1, got %v", collector.DailySummary.TotalRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScheduleResetRealtimeStats(t *testing.T) {
|
||||||
|
db := getNewDatabase()
|
||||||
|
defer clearDatabase(db)
|
||||||
|
option := statistic.CollectorOption{Database: db}
|
||||||
|
collector, _ := statistic.NewStatisticCollector(option)
|
||||||
|
stopChan := collector.ScheduleResetRealtimeStats()
|
||||||
|
if stopChan == nil {
|
||||||
|
t.Fatalf("Expected stopChan, got nil")
|
||||||
|
}
|
||||||
|
collector.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDailySummary(t *testing.T) {
|
||||||
|
summary := statistic.NewDailySummary()
|
||||||
|
if summary.TotalRequest != 0 {
|
||||||
|
t.Fatalf("Expected TotalRequest to be 0, got %v", summary.TotalRequest)
|
||||||
|
}
|
||||||
|
if summary.ForwardTypes == nil {
|
||||||
|
t.Fatalf("Expected ForwardTypes to be initialized, got nil")
|
||||||
|
}
|
||||||
|
if summary.RequestOrigin == nil {
|
||||||
|
t.Fatalf("Expected RequestOrigin to be initialized, got nil")
|
||||||
|
}
|
||||||
|
if summary.RequestClientIp == nil {
|
||||||
|
t.Fatalf("Expected RequestClientIp to be initialized, got nil")
|
||||||
|
}
|
||||||
|
if summary.Referer == nil {
|
||||||
|
t.Fatalf("Expected Referer to be initialized, got nil")
|
||||||
|
}
|
||||||
|
if summary.UserAgent == nil {
|
||||||
|
t.Fatalf("Expected UserAgent to be initialized, got nil")
|
||||||
|
}
|
||||||
|
if summary.RequestURL == nil {
|
||||||
|
t.Fatalf("Expected RequestURL to be initialized, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateTestRequestInfo(db *database.Database) statistic.RequestInfo {
|
||||||
|
//Generate a random IPv4 address
|
||||||
|
randomIpAddr := ""
|
||||||
|
for {
|
||||||
|
ip := net.IPv4(byte(rand.Intn(256)), byte(rand.Intn(256)), byte(rand.Intn(256)), byte(rand.Intn(256)))
|
||||||
|
if !ip.IsPrivate() && !ip.IsLoopback() && !ip.IsMulticast() && !ip.IsUnspecified() {
|
||||||
|
randomIpAddr = ip.String()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Resolve the country code for this IP
|
||||||
|
ipLocation := "unknown"
|
||||||
|
geoIpResolver, err := geodb.NewGeoDb(db, &geodb.StoreOptions{
|
||||||
|
AllowSlowIpv4LookUp: false,
|
||||||
|
AllowSlowIpv6Lookup: true, //Just to save some RAM
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
ipInfo, _ := geoIpResolver.ResolveCountryCodeFromIP(randomIpAddr)
|
||||||
|
ipLocation = ipInfo.CountryIsoCode
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardType := "host-http"
|
||||||
|
//Generate a random forward type between "subdomain-http" and "host-https"
|
||||||
|
if rand.Intn(2) == 1 {
|
||||||
|
forwardType = "subdomain-http"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate 5 random refers URL and pick from there
|
||||||
|
referers := []string{"https://example.com", "https://example.org", "https://example.net", "https://example.io", "https://example.co"}
|
||||||
|
referer := referers[rand.Intn(5)]
|
||||||
|
|
||||||
|
return statistic.RequestInfo{
|
||||||
|
IpAddr: randomIpAddr,
|
||||||
|
RequestOriginalCountryISOCode: ipLocation,
|
||||||
|
Succ: true,
|
||||||
|
StatusCode: 200,
|
||||||
|
ForwardType: forwardType,
|
||||||
|
Referer: referer,
|
||||||
|
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
|
||||||
|
RequestURL: "/benchmark",
|
||||||
|
Target: "test.imuslab.internal",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRecordRequest(b *testing.B) {
|
||||||
|
db := getNewDatabase()
|
||||||
|
defer clearDatabase(db)
|
||||||
|
|
||||||
|
option := statistic.CollectorOption{Database: db}
|
||||||
|
collector, _ := statistic.NewStatisticCollector(option)
|
||||||
|
var requestInfo statistic.RequestInfo = generateTestRequestInfo(db)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
collector.RecordRequest(requestInfo)
|
||||||
|
collector.SaveSummaryOfDay()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write the current in-memory summary to database file
|
||||||
|
b.StopTimer()
|
||||||
|
|
||||||
|
//Print the generated summary
|
||||||
|
//testSummary := collector.GetCurrentDailySummary()
|
||||||
|
//statistic.PrintDailySummary(testSummary)
|
||||||
|
}
|
@ -1,15 +1,18 @@
|
|||||||
package streamproxy
|
package streamproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -48,9 +51,10 @@ type ProxyRelayConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Database *database.Database
|
|
||||||
DefaultTimeout int
|
DefaultTimeout int
|
||||||
AccessControlHandler func(net.Conn) bool
|
AccessControlHandler func(net.Conn) bool
|
||||||
|
ConfigStore string //Folder to store the config files, will be created if not exists
|
||||||
|
Logger *logger.Logger //Logger for the stream proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@ -63,13 +67,37 @@ type Manager struct {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStreamProxy(options *Options) *Manager {
|
func NewStreamProxy(options *Options) (*Manager, error) {
|
||||||
options.Database.NewTable("tcprox")
|
if !utils.FileExists(options.ConfigStore) {
|
||||||
|
err := os.MkdirAll(options.ConfigStore, 0775)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Load relay configs from db
|
//Load relay configs from db
|
||||||
previousRules := []*ProxyRelayConfig{}
|
previousRules := []*ProxyRelayConfig{}
|
||||||
if options.Database.KeyExists("tcprox", "rules") {
|
streamProxyConfigFiles, err := filepath.Glob(options.ConfigStore + "/*.config")
|
||||||
options.Database.Read("tcprox", "rules", &previousRules)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, configFile := range streamProxyConfigFiles {
|
||||||
|
//Read file into bytes
|
||||||
|
configBytes, err := os.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("stream-prox", "Read stream proxy config failed", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
thisRelayConfig := &ProxyRelayConfig{}
|
||||||
|
err = json.Unmarshal(configBytes, thisRelayConfig)
|
||||||
|
if err != nil {
|
||||||
|
options.Logger.PrintAndLog("stream-prox", "Unmarshal stream proxy config failed", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Append the config to the list
|
||||||
|
previousRules = append(previousRules, thisRelayConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if the AccessControlHandler is empty. If yes, set it to always allow access
|
//Check if the AccessControlHandler is empty. If yes, set it to always allow access
|
||||||
@ -91,14 +119,27 @@ func NewStreamProxy(options *Options) *Manager {
|
|||||||
rule.parent = &thisManager
|
rule.parent = &thisManager
|
||||||
if rule.Running {
|
if rule.Running {
|
||||||
//This was previously running. Start it again
|
//This was previously running. Start it again
|
||||||
log.Println("[Stream Proxy] Resuming stream proxy rule " + rule.Name)
|
thisManager.logf("Resuming stream proxy rule "+rule.Name, nil)
|
||||||
rule.Start()
|
rule.Start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thisManager.Configs = previousRules
|
thisManager.Configs = previousRules
|
||||||
|
|
||||||
return &thisManager
|
return &thisManager, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper function to log error
|
||||||
|
func (m *Manager) logf(message string, originalError error) {
|
||||||
|
if m.Options.Logger == nil {
|
||||||
|
//Print to fmt
|
||||||
|
if originalError != nil {
|
||||||
|
message += ": " + originalError.Error()
|
||||||
|
}
|
||||||
|
println(message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Options.Logger.PrintAndLog("stream-prox", message, originalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
|
func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
|
||||||
@ -179,6 +220,11 @@ func (m *Manager) EditConfig(configUUID string, newName string, newListeningAddr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) RemoveConfig(configUUID string) error {
|
func (m *Manager) RemoveConfig(configUUID string) error {
|
||||||
|
//Remove the config from file
|
||||||
|
err := os.Remove(filepath.Join(m.Options.ConfigStore, configUUID+".config"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Find and remove the config with the specified UUID
|
// Find and remove the config with the specified UUID
|
||||||
for i, config := range m.Configs {
|
for i, config := range m.Configs {
|
||||||
if config.UUID == configUUID {
|
if config.UUID == configUUID {
|
||||||
@ -190,8 +236,19 @@ func (m *Manager) RemoveConfig(configUUID string) error {
|
|||||||
return errors.New("config not found")
|
return errors.New("config not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save all configs to ConfigStore folder
|
||||||
func (m *Manager) SaveConfigToDatabase() {
|
func (m *Manager) SaveConfigToDatabase() {
|
||||||
m.Options.Database.Write("tcprox", "rules", m.Configs)
|
for _, config := range m.Configs {
|
||||||
|
configBytes, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
m.logf("Failed to marshal stream proxy config", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = os.WriteFile(m.Options.ConfigStore+"/"+config.UUID+".config", configBytes, 0775)
|
||||||
|
if err != nil {
|
||||||
|
m.logf("Failed to save stream proxy config", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -217,9 +274,10 @@ func (c *ProxyRelayConfig) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if !c.UseTCP {
|
if !c.UseTCP {
|
||||||
c.Running = false
|
c.Running = false
|
||||||
|
c.udpStopChan = nil
|
||||||
c.parent.SaveConfigToDatabase()
|
c.parent.SaveConfigToDatabase()
|
||||||
}
|
}
|
||||||
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
|
c.parent.logf("[proto:udp] Error starting stream proxy "+c.Name+"("+c.UUID+")", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -231,8 +289,9 @@ func (c *ProxyRelayConfig) Start() error {
|
|||||||
err := c.Port2host(c.ListeningAddress, c.ProxyTargetAddr, tcpStopChan)
|
err := c.Port2host(c.ListeningAddress, c.ProxyTargetAddr, tcpStopChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Running = false
|
c.Running = false
|
||||||
|
c.tcpStopChan = nil
|
||||||
c.parent.SaveConfigToDatabase()
|
c.parent.SaveConfigToDatabase()
|
||||||
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
|
c.parent.logf("[proto:tcp] Error starting stream proxy "+c.Name+"("+c.UUID+")", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -253,27 +312,27 @@ func (c *ProxyRelayConfig) Restart() {
|
|||||||
if c.IsRunning() {
|
if c.IsRunning() {
|
||||||
c.Stop()
|
c.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(300 * time.Millisecond)
|
time.Sleep(3000 * time.Millisecond)
|
||||||
c.Start()
|
c.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop a running proxy if running
|
// Stop a running proxy if running
|
||||||
func (c *ProxyRelayConfig) Stop() {
|
func (c *ProxyRelayConfig) Stop() {
|
||||||
log.Println("[STREAM PROXY] Stopping Stream Proxy " + c.Name)
|
c.parent.logf("Stopping Stream Proxy "+c.Name, nil)
|
||||||
|
|
||||||
if c.udpStopChan != nil {
|
if c.udpStopChan != nil {
|
||||||
log.Println("[STREAM PROXY] Stopping UDP for " + c.Name)
|
c.parent.logf("Stopping UDP for "+c.Name, nil)
|
||||||
c.udpStopChan <- true
|
c.udpStopChan <- true
|
||||||
c.udpStopChan = nil
|
c.udpStopChan = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.tcpStopChan != nil {
|
if c.tcpStopChan != nil {
|
||||||
log.Println("[STREAM PROXY] Stopping TCP for " + c.Name)
|
c.parent.logf("Stopping TCP for "+c.Name, nil)
|
||||||
c.tcpStopChan <- true
|
c.tcpStopChan <- true
|
||||||
c.tcpStopChan = nil
|
c.tcpStopChan = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("[STREAM PROXY] Stopped Stream Proxy " + c.Name)
|
c.parent.logf("Stopped Stream Proxy "+c.Name, nil)
|
||||||
c.Running = false
|
c.Running = false
|
||||||
|
|
||||||
//Update the running status
|
//Update the running status
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -185,7 +184,6 @@ func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, err
|
|||||||
//Load the cert and serve it
|
//Load the cert and serve it
|
||||||
cer, err := tls.LoadX509KeyPair(pubKey, priKey)
|
cer, err := tls.LoadX509KeyPair(pubKey, priKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package update
|
package update
|
||||||
|
|
||||||
import v308 "imuslab.com/zoraxy/mod/update/v308"
|
import (
|
||||||
|
v308 "imuslab.com/zoraxy/mod/update/v308"
|
||||||
|
v315 "imuslab.com/zoraxy/mod/update/v315"
|
||||||
|
)
|
||||||
|
|
||||||
// Updater Core logic
|
// Updater Core logic
|
||||||
func runUpdateRoutineWithVersion(fromVersion int, toVersion int) {
|
func runUpdateRoutineWithVersion(fromVersion int, toVersion int) {
|
||||||
@ -10,6 +13,12 @@ func runUpdateRoutineWithVersion(fromVersion int, toVersion int) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
} else if fromVersion == 314 && toVersion == 315 {
|
||||||
|
//Updating from v3.1.4 to v3.1.5
|
||||||
|
err := v315.UpdateFrom314To315()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//ADD MORE VERSIONS HERE
|
//ADD MORE VERSIONS HERE
|
||||||
|
24
src/mod/update/updateutil/updateutil.go
Normal file
24
src/mod/update/updateutil/updateutil.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package updateutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package v308
|
package v308
|
||||||
|
|
||||||
/*
|
/*
|
||||||
v307 type definations
|
v307 type definitions
|
||||||
|
|
||||||
This file wrap up the self-contained data structure
|
This file wrap up the self-contained data structure
|
||||||
for v3.0.7 structure and allow automatic updates
|
for v3.0.7 structure and allow automatic updates
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package v308
|
package v308
|
||||||
|
|
||||||
/*
|
/*
|
||||||
v308 type definations
|
v308 type definition
|
||||||
|
|
||||||
This file wrap up the self-contained data structure
|
This file wrap up the self-contained data structure
|
||||||
for v3.0.8 structure and allow automatic updates
|
for v3.0.8 structure and allow automatic updates
|
||||||
|
50
src/mod/update/v315/typedef314.go
Normal file
50
src/mod/update/v315/typedef314.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package v315
|
||||||
|
|
||||||
|
import (
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A proxy endpoint record, a general interface for handling inbound routing
|
||||||
|
type v314ProxyEndpoint 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 []*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
|
||||||
|
|
||||||
|
//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 []*rewrite.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
|
||||||
|
UseSSOIntercept bool //Allow SSO to intercept this endpoint and provide authentication via Oauth2 credentials
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
106
src/mod/update/v315/typedef315.go
Normal file
106
src/mod/update/v315/typedef315.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package v315
|
||||||
|
|
||||||
|
import (
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProxyTypeRoot ProxyType = iota //Root Proxy, everything not matching will be routed here
|
||||||
|
ProxyTypeHost //Host Proxy, match by host (domain) name
|
||||||
|
ProxyTypeVdir //Virtual Directory Proxy, match by path prefix
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Basic Auth Related Data structure*/
|
||||||
|
// Auth credential for basic auth on certain endpoints
|
||||||
|
type BasicAuthCredentials struct {
|
||||||
|
Username string
|
||||||
|
PasswordHash string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth credential for basic auth on certain endpoints
|
||||||
|
type BasicAuthUnhashedCredentials struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paths to exclude in basic auth enabled proxy handler
|
||||||
|
type BasicAuthExceptionRule struct {
|
||||||
|
PathPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Routing Rule Data Structures */
|
||||||
|
|
||||||
|
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||||
|
// program structure than directly using ProxyEndpoint
|
||||||
|
type VirtualDirectoryEndpoint 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rules and settings for header rewriting
|
||||||
|
type HeaderRewriteRules struct {
|
||||||
|
UserDefinedHeaders []*rewrite.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
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthProvider int
|
||||||
|
|
||||||
|
const (
|
||||||
|
AuthProviderNone AuthProvider = iota
|
||||||
|
AuthProviderBasicAuth
|
||||||
|
AuthProviderAuthelia
|
||||||
|
AuthProviderOauth2
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthenticationProvider struct {
|
||||||
|
AuthProvider AuthProvider //The type of authentication provider
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// A proxy endpoint record, a general interface for handling inbound routing
|
||||||
|
type v315ProxyEndpoint struct {
|
||||||
|
ProxyType ProxyType //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
|
||||||
|
|
||||||
|
//Inbound TLS/SSL Related
|
||||||
|
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||||
|
|
||||||
|
//Virtual Directories
|
||||||
|
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||||
|
|
||||||
|
//Custom Headers
|
||||||
|
HeaderRewriteRules *HeaderRewriteRules
|
||||||
|
|
||||||
|
//Authentication
|
||||||
|
AuthenticationProvider *AuthenticationProvider
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
124
src/mod/update/v315/v315.go
Normal file
124
src/mod/update/v315/v315.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package v315
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/update/updateutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UpdateFrom314To315() 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-314.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-314.old/", fileName)
|
||||||
|
|
||||||
|
// Copy the file to the backup directory
|
||||||
|
err := updateutil.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 := v314ProxyEndpoint{}
|
||||||
|
err = json.Unmarshal(configContent, &thisOldConfigStruct)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to parse file "+filepath.Base(oldConfigFile), err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert the old struct to the new struct
|
||||||
|
thisNewConfigStruct := convertV314ToV315(thisOldConfigStruct)
|
||||||
|
|
||||||
|
//Write the new config to file
|
||||||
|
newConfigContent, err := json.MarshalIndent(thisNewConfigStruct, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to marshal new config "+filepath.Base(oldConfigFile), err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(oldConfigFile, newConfigContent, 0664)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to write new config "+filepath.Base(oldConfigFile), err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertV314ToV315(thisOldConfigStruct v314ProxyEndpoint) v315ProxyEndpoint {
|
||||||
|
//Move old header and auth configs into struct
|
||||||
|
newHeaderRewriteRules := HeaderRewriteRules{
|
||||||
|
UserDefinedHeaders: thisOldConfigStruct.UserDefinedHeaders,
|
||||||
|
RequestHostOverwrite: thisOldConfigStruct.RequestHostOverwrite,
|
||||||
|
HSTSMaxAge: thisOldConfigStruct.HSTSMaxAge,
|
||||||
|
EnablePermissionPolicyHeader: thisOldConfigStruct.EnablePermissionPolicyHeader,
|
||||||
|
PermissionPolicy: thisOldConfigStruct.PermissionPolicy,
|
||||||
|
DisableHopByHopHeaderRemoval: thisOldConfigStruct.DisableHopByHopHeaderRemoval,
|
||||||
|
}
|
||||||
|
|
||||||
|
newAuthenticationProvider := AuthenticationProvider{
|
||||||
|
RequireBasicAuth: thisOldConfigStruct.RequireBasicAuth,
|
||||||
|
BasicAuthCredentials: thisOldConfigStruct.BasicAuthCredentials,
|
||||||
|
BasicAuthExceptionRules: thisOldConfigStruct.BasicAuthExceptionRules,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert proxy type int to enum
|
||||||
|
var newConfigProxyType ProxyType
|
||||||
|
if thisOldConfigStruct.ProxyType == 0 {
|
||||||
|
newConfigProxyType = ProxyTypeRoot
|
||||||
|
} else if thisOldConfigStruct.ProxyType == 1 {
|
||||||
|
newConfigProxyType = ProxyTypeHost
|
||||||
|
} else if thisOldConfigStruct.ProxyType == 2 {
|
||||||
|
newConfigProxyType = ProxyTypeVdir
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update the config struct
|
||||||
|
thisNewConfigStruct := v315ProxyEndpoint{
|
||||||
|
ProxyType: newConfigProxyType,
|
||||||
|
RootOrMatchingDomain: thisOldConfigStruct.RootOrMatchingDomain,
|
||||||
|
MatchingDomainAlias: thisOldConfigStruct.MatchingDomainAlias,
|
||||||
|
ActiveOrigins: thisOldConfigStruct.ActiveOrigins,
|
||||||
|
InactiveOrigins: thisOldConfigStruct.InactiveOrigins,
|
||||||
|
UseStickySession: thisOldConfigStruct.UseStickySession,
|
||||||
|
UseActiveLoadBalance: thisOldConfigStruct.UseActiveLoadBalance,
|
||||||
|
Disabled: thisOldConfigStruct.Disabled,
|
||||||
|
BypassGlobalTLS: thisOldConfigStruct.BypassGlobalTLS,
|
||||||
|
VirtualDirectories: thisOldConfigStruct.VirtualDirectories,
|
||||||
|
RequireRateLimit: thisOldConfigStruct.RequireRateLimit,
|
||||||
|
RateLimit: thisOldConfigStruct.RateLimit,
|
||||||
|
AccessFilterUUID: thisOldConfigStruct.AccessFilterUUID,
|
||||||
|
DefaultSiteOption: thisOldConfigStruct.DefaultSiteOption,
|
||||||
|
DefaultSiteValue: thisOldConfigStruct.DefaultSiteValue,
|
||||||
|
|
||||||
|
//Append the new struct into the new config
|
||||||
|
HeaderRewriteRules: &newHeaderRewriteRules,
|
||||||
|
AuthenticationProvider: &newAuthenticationProvider,
|
||||||
|
}
|
||||||
|
|
||||||
|
return thisNewConfigStruct
|
||||||
|
}
|
@ -3,7 +3,6 @@ package uptime
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -242,7 +241,7 @@ func getWebsiteStatus(url string) (int, error) {
|
|||||||
// Create a one-time use cookie jar to store cookies
|
// Create a one-time use cookie jar to store cookies
|
||||||
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := http.Client{
|
client := http.Client{
|
||||||
|
@ -41,12 +41,12 @@ func SendOK(w http.ResponseWriter) {
|
|||||||
|
|
||||||
// Get GET parameter
|
// Get GET parameter
|
||||||
func GetPara(r *http.Request, key string) (string, error) {
|
func GetPara(r *http.Request, key string) (string, error) {
|
||||||
keys, ok := r.URL.Query()[key]
|
// Get first value from the URL query
|
||||||
if !ok || len(keys[0]) < 1 {
|
value := r.URL.Query().Get(key)
|
||||||
|
if len(value) == 0 {
|
||||||
return "", errors.New("invalid " + key + " given")
|
return "", errors.New("invalid " + key + " given")
|
||||||
} else {
|
|
||||||
return keys[0], nil
|
|
||||||
}
|
}
|
||||||
|
return value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get GET paramter as boolean, accept 1 or true
|
// Get GET paramter as boolean, accept 1 or true
|
||||||
@ -56,26 +56,29 @@ func GetBool(r *http.Request, key string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
x = strings.TrimSpace(x)
|
// Convert to lowercase and trim spaces just once to compare
|
||||||
|
switch strings.ToLower(strings.TrimSpace(x)) {
|
||||||
if x == "1" || strings.ToLower(x) == "true" || strings.ToLower(x) == "on" {
|
case "1", "true", "on":
|
||||||
return true, nil
|
return true, nil
|
||||||
} else if x == "0" || strings.ToLower(x) == "false" || strings.ToLower(x) == "off" {
|
case "0", "false", "off":
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, errors.New("invalid boolean given")
|
return false, errors.New("invalid boolean given")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get POST paramter
|
// Get POST parameter
|
||||||
func PostPara(r *http.Request, key string) (string, error) {
|
func PostPara(r *http.Request, key string) (string, error) {
|
||||||
r.ParseForm()
|
// Try to parse the form
|
||||||
x := r.Form.Get(key)
|
if err := r.ParseForm(); err != nil {
|
||||||
if x == "" {
|
return "", err
|
||||||
return "", errors.New("invalid " + key + " given")
|
|
||||||
} else {
|
|
||||||
return x, nil
|
|
||||||
}
|
}
|
||||||
|
// 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
|
// Get POST paramter as boolean, accept 1 or true
|
||||||
@ -85,11 +88,11 @@ func PostBool(r *http.Request, key string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
x = strings.TrimSpace(x)
|
// Convert to lowercase and trim spaces just once to compare
|
||||||
|
switch strings.ToLower(strings.TrimSpace(x)) {
|
||||||
if x == "1" || strings.ToLower(x) == "true" || strings.ToLower(x) == "on" {
|
case "1", "true", "on":
|
||||||
return true, nil
|
return true, nil
|
||||||
} else if x == "0" || strings.ToLower(x) == "false" || strings.ToLower(x) == "off" {
|
case "0", "false", "off":
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,14 +117,19 @@ func PostInt(r *http.Request, key string) (int, error) {
|
|||||||
|
|
||||||
func FileExists(filename string) bool {
|
func FileExists(filename string) bool {
|
||||||
_, err := os.Stat(filename)
|
_, 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 false
|
||||||
}
|
}
|
||||||
return true
|
// Some other error
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsDir(path string) bool {
|
func IsDir(path string) bool {
|
||||||
if FileExists(path) == false {
|
if !FileExists(path) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
fi, err := os.Stat(path)
|
fi, err := os.Stat(path)
|
||||||
@ -191,4 +199,4 @@ func ValidateListeningAddress(address string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
@ -42,6 +42,13 @@ func (fm *FileManager) HandleList(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Construct the absolute path to the target directory
|
// Construct the absolute path to the target directory
|
||||||
targetDir := filepath.Join(fm.Directory, 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
|
// Open the target directory
|
||||||
dirEntries, err := os.ReadDir(targetDir)
|
dirEntries, err := os.ReadDir(targetDir)
|
||||||
if err != nil {
|
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
|
// Specify the directory where you want to save the uploaded file
|
||||||
uploadDir := filepath.Join(fm.Directory, dir)
|
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) {
|
if !utils.FileExists(uploadDir) {
|
||||||
utils.SendErrorResponse(w, "upload target directory not exists")
|
utils.SendErrorResponse(w, "upload target directory not exists")
|
||||||
return
|
return
|
||||||
@ -157,14 +172,20 @@ func (fm *FileManager) HandleDownload(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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")
|
previewMode, _ := utils.GetPara(r, "preview")
|
||||||
if previewMode == "true" {
|
if previewMode == "true" {
|
||||||
// Serve the file using http.ServeFile
|
// Serve the file using http.ServeFile
|
||||||
filePath := filepath.Join(fm.Directory, filename)
|
|
||||||
http.ServeFile(w, r, filePath)
|
http.ServeFile(w, r, filePath)
|
||||||
} else {
|
} else {
|
||||||
// Trigger a download with content disposition headers
|
// Trigger a download with content disposition headers
|
||||||
filePath := filepath.Join(fm.Directory, filename)
|
|
||||||
w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(filename))
|
w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(filename))
|
||||||
http.ServeFile(w, r, filePath)
|
http.ServeFile(w, r, filePath)
|
||||||
}
|
}
|
||||||
@ -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
|
// Specify the directory where you want to create the new folder
|
||||||
newFolderPath := filepath.Join(fm.Directory, dirName)
|
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
|
// Check if the folder already exists
|
||||||
if _, err := os.Stat(newFolderPath); os.IsNotExist(err) {
|
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)
|
absSrcPath := filepath.Join(fm.Directory, srcPath)
|
||||||
absDestPath := filepath.Join(fm.Directory, destPath)
|
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
|
// Check if the source path exists
|
||||||
if _, err := os.Stat(absSrcPath); os.IsNotExist(err) {
|
if _, err := os.Stat(absSrcPath); os.IsNotExist(err) {
|
||||||
utils.SendErrorResponse(w, "source path does not exist")
|
utils.SendErrorResponse(w, "source path does not exist")
|
||||||
@ -288,6 +326,18 @@ func (fm *FileManager) HandleFileMove(w http.ResponseWriter, r *http.Request) {
|
|||||||
absSrcPath := filepath.Join(fm.Directory, srcPath)
|
absSrcPath := filepath.Join(fm.Directory, srcPath)
|
||||||
absDestPath := filepath.Join(fm.Directory, destPath)
|
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
|
// Check if the source path exists
|
||||||
if _, err := os.Stat(absSrcPath); os.IsNotExist(err) {
|
if _, err := os.Stat(absSrcPath); os.IsNotExist(err) {
|
||||||
utils.SendErrorResponse(w, "source path does not exist")
|
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
|
// Construct the absolute path to the target file or directory
|
||||||
absPath := filepath.Join(fm.Directory, filePath)
|
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
|
// Check if the target path exists
|
||||||
_, err = os.Stat(absPath)
|
_, 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
|
// Construct the absolute path to the target file or directory
|
||||||
absPath := filepath.Join(fm.Directory, filePath)
|
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
|
// Check if the target path exists
|
||||||
_, err = os.Stat(absPath)
|
_, 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
|
// Respond with a success message or appropriate response
|
||||||
utils.SendOK(w)
|
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
|
||||||
|
}
|
||||||
|
@ -83,7 +83,11 @@ func (ws *WebServer) SetEnableDirectoryListing(w http.ResponseWriter, r *http.Re
|
|||||||
utils.SendErrorResponse(w, "invalid setting given")
|
utils.SendErrorResponse(w, "invalid setting given")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = ws.option.Sysdb.Write("webserv", "dirlist", enableList)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "unable to save setting")
|
||||||
|
return
|
||||||
|
}
|
||||||
ws.option.EnableDirectoryListing = enableList
|
ws.option.EnableDirectoryListing = enableList
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
You can upload your html files to your web directory via the <b>Web Directory Manager</b>.
|
You can upload your html files to your web directory via the <b>Web Directory Manager</b>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
For online documentation, please refer to <a href="//zoraxy.arozos.com">zoraxy.arozos.com</a> or the <a href="https://github.com/tobychui/zoraxy/wiki">project wiki</a>.<br>
|
For online documentation, please refer to <a href="//zoraxy.aroz.org">zoraxy.aroz.org</a> or the <a href="https://github.com/tobychui/zoraxy/wiki">project wiki</a>.<br>
|
||||||
Thank you for using Zoraxy!
|
Thank you for using Zoraxy!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,7 @@ package websocketproxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@ -12,6 +13,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -54,8 +57,11 @@ type WebsocketProxy struct {
|
|||||||
|
|
||||||
// Additional options for websocket proxy runtime
|
// Additional options for websocket proxy runtime
|
||||||
type Options struct {
|
type Options struct {
|
||||||
SkipTLSValidation bool //Skip backend TLS validation
|
SkipTLSValidation bool //Skip backend TLS validation
|
||||||
SkipOriginCheck bool //Skip origin check
|
SkipOriginCheck bool //Skip origin check
|
||||||
|
CopyAllHeaders bool //Copy all headers from incoming request to backend request
|
||||||
|
UserDefinedHeaders []*rewrite.UserDefinedHeader //User defined headers
|
||||||
|
Logger *logger.Logger //Logger, can be nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyHandler returns a new http.Handler interface that reverse proxies the
|
// ProxyHandler returns a new http.Handler interface that reverse proxies the
|
||||||
@ -75,20 +81,65 @@ func NewProxy(target *url.URL, options Options) *WebsocketProxy {
|
|||||||
u.RawQuery = r.URL.RawQuery
|
u.RawQuery = r.URL.RawQuery
|
||||||
return &u
|
return &u
|
||||||
}
|
}
|
||||||
return &WebsocketProxy{Backend: backend, Verbal: false, Options: options}
|
|
||||||
|
// Create a new websocket proxy
|
||||||
|
wsprox := &WebsocketProxy{Backend: backend, Verbal: false, Options: options}
|
||||||
|
if options.CopyAllHeaders {
|
||||||
|
wsprox.Director = DefaultDirector
|
||||||
|
}
|
||||||
|
|
||||||
|
return wsprox
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utilities function for log printing
|
||||||
|
func (w *WebsocketProxy) Println(messsage string, err error) {
|
||||||
|
if w.Options.Logger != nil {
|
||||||
|
w.Options.Logger.PrintAndLog("websocket", messsage, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("[websocketproxy] [system:info]"+messsage, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultDirector is the default implementation of Director, which copies
|
||||||
|
// all headers from the incoming request to the outgoing request.
|
||||||
|
func DefaultDirector(r *http.Request, h http.Header) {
|
||||||
|
//Copy all header values from request to target header
|
||||||
|
for k, vv := range r.Header {
|
||||||
|
for _, v := range vv {
|
||||||
|
h.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove hop-by-hop headers
|
||||||
|
for _, removePendingHeader := range []string{
|
||||||
|
"Connection",
|
||||||
|
"Keep-Alive",
|
||||||
|
"Proxy-Authenticate",
|
||||||
|
"Proxy-Authorization",
|
||||||
|
"Te",
|
||||||
|
"Trailers",
|
||||||
|
"Transfer-Encoding",
|
||||||
|
"Sec-WebSocket-Extensions",
|
||||||
|
"Sec-WebSocket-Key",
|
||||||
|
"Sec-WebSocket-Protocol",
|
||||||
|
"Sec-WebSocket-Version",
|
||||||
|
"Upgrade",
|
||||||
|
} {
|
||||||
|
h.Del(removePendingHeader)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP implements the http.Handler that proxies WebSocket connections.
|
// ServeHTTP implements the http.Handler that proxies WebSocket connections.
|
||||||
func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
if w.Backend == nil {
|
if w.Backend == nil {
|
||||||
log.Println("websocketproxy: backend function is not defined")
|
w.Println("Invalid websocket backend configuration", errors.New("backend function not found"))
|
||||||
http.Error(rw, "internal server error (code: 1)", http.StatusInternalServerError)
|
http.Error(rw, "internal server error (code: 1)", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backendURL := w.Backend(req)
|
backendURL := w.Backend(req)
|
||||||
if backendURL == nil {
|
if backendURL == nil {
|
||||||
log.Println("websocketproxy: backend URL is nil")
|
w.Println("Invalid websocket backend configuration", errors.New("backend URL is nil"))
|
||||||
http.Error(rw, "internal server error (code: 2)", http.StatusInternalServerError)
|
http.Error(rw, "internal server error (code: 2)", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -121,6 +172,11 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
if req.Host != "" {
|
if req.Host != "" {
|
||||||
requestHeader.Set("Host", req.Host)
|
requestHeader.Set("Host", req.Host)
|
||||||
}
|
}
|
||||||
|
if userAgent := req.Header.Get("User-Agent"); userAgent != "" {
|
||||||
|
requestHeader.Set("User-Agent", userAgent)
|
||||||
|
} else {
|
||||||
|
requestHeader.Set("User-Agent", "zoraxy-wsproxy/1.1")
|
||||||
|
}
|
||||||
|
|
||||||
// Pass X-Forwarded-For headers too, code below is a part of
|
// Pass X-Forwarded-For headers too, code below is a part of
|
||||||
// httputil.ReverseProxy. See http://en.wikipedia.org/wiki/X-Forwarded-For
|
// httputil.ReverseProxy. See http://en.wikipedia.org/wiki/X-Forwarded-For
|
||||||
@ -144,10 +200,29 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
requestHeader.Set("X-Forwarded-Proto", "https")
|
requestHeader.Set("X-Forwarded-Proto", "https")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable the director to copy any additional headers it desires for
|
// Replace header variables and copy user-defined headers
|
||||||
// forwarding to the remote server.
|
if w.Options.CopyAllHeaders {
|
||||||
if w.Director != nil {
|
// Rewrite the user defined headers
|
||||||
w.Director(req, requestHeader)
|
// This is reported to be not compatible with Proxmox and Home Assistant
|
||||||
|
// but required by some other projects like MeshCentral
|
||||||
|
// we will make this optional
|
||||||
|
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(req, w.Options.UserDefinedHeaders)
|
||||||
|
upstreamHeaders, _ := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{
|
||||||
|
UserDefinedHeaders: rewrittenUserDefinedHeaders,
|
||||||
|
})
|
||||||
|
for _, headerValuePair := range upstreamHeaders {
|
||||||
|
//Do not copy Upgrade and Connection headers, it will be handled by the upgrader
|
||||||
|
if strings.EqualFold(headerValuePair[0], "Upgrade") || strings.EqualFold(headerValuePair[0], "Connection") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
requestHeader.Set(headerValuePair[0], headerValuePair[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable the director to copy any additional headers it desires for
|
||||||
|
// forwarding to the remote server.
|
||||||
|
if w.Director != nil {
|
||||||
|
w.Director(req, requestHeader)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to the backend URL, also pass the headers we get from the requst
|
// Connect to the backend URL, also pass the headers we get from the requst
|
||||||
@ -158,13 +233,13 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
// http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-01
|
// http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-01
|
||||||
connBackend, resp, err := dialer.Dial(backendURL.String(), requestHeader)
|
connBackend, resp, err := dialer.Dial(backendURL.String(), requestHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("websocketproxy: couldn't dial to remote backend url %s", err)
|
w.Println("Couldn't dial to remote backend url "+backendURL.String(), err)
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
// If the WebSocket handshake fails, ErrBadHandshake is returned
|
// If the WebSocket handshake fails, ErrBadHandshake is returned
|
||||||
// along with a non-nil *http.Response so that callers can handle
|
// along with a non-nil *http.Response so that callers can handle
|
||||||
// redirects, authentication, etcetera.
|
// redirects, authentication, etcetera.
|
||||||
if err := copyResponse(rw, resp); err != nil {
|
if err := copyResponse(rw, resp); err != nil {
|
||||||
log.Printf("websocketproxy: couldn't write response after failed remote backend handshake: %s", err)
|
w.Println("Couldn't write response after failed remote backend handshake to "+backendURL.String(), err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
http.Error(rw, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
|
http.Error(rw, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
|
||||||
@ -198,7 +273,7 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
// Also pass the header that we gathered from the Dial handshake.
|
// Also pass the header that we gathered from the Dial handshake.
|
||||||
connPub, err := upgrader.Upgrade(rw, req, upgradeHeader)
|
connPub, err := upgrader.Upgrade(rw, req, upgradeHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("websocketproxy: couldn't upgrade %s", err)
|
w.Println("Couldn't upgrade incoming request", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer connPub.Close()
|
defer connPub.Close()
|
||||||
|
@ -31,6 +31,7 @@ func TestProxy(t *testing.T) {
|
|||||||
proxy := NewProxy(u, Options{
|
proxy := NewProxy(u, Options{
|
||||||
SkipTLSValidation: false,
|
SkipTLSValidation: false,
|
||||||
SkipOriginCheck: false,
|
SkipOriginCheck: false,
|
||||||
|
Logger: nil,
|
||||||
})
|
})
|
||||||
proxy.Upgrader = upgrader
|
proxy.Upgrader = upgrader
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
@ -26,18 +27,18 @@ func ReverseProxtInit() {
|
|||||||
/*
|
/*
|
||||||
Load Reverse Proxy Global Settings
|
Load Reverse Proxy Global Settings
|
||||||
*/
|
*/
|
||||||
inboundPort := 80
|
inboundPort := 443
|
||||||
if sysdb.KeyExists("settings", "inbound") {
|
if sysdb.KeyExists("settings", "inbound") {
|
||||||
sysdb.Read("settings", "inbound", &inboundPort)
|
sysdb.Read("settings", "inbound", &inboundPort)
|
||||||
SystemWideLogger.Println("Serving inbound port ", inboundPort)
|
SystemWideLogger.Println("Serving inbound port ", inboundPort)
|
||||||
} else {
|
} else {
|
||||||
SystemWideLogger.Println("Inbound port not set. Using default (80)")
|
SystemWideLogger.Println("Inbound port not set. Using default (443)")
|
||||||
}
|
}
|
||||||
|
|
||||||
useTls := false
|
useTls := true
|
||||||
sysdb.Read("settings", "usetls", &useTls)
|
sysdb.Read("settings", "usetls", &useTls)
|
||||||
if useTls {
|
if useTls {
|
||||||
SystemWideLogger.Println("TLS mode enabled. Serving proxxy request with TLS")
|
SystemWideLogger.Println("TLS mode enabled. Serving proxy request with TLS")
|
||||||
} else {
|
} else {
|
||||||
SystemWideLogger.Println("TLS mode disabled. Serving proxy request with plain http")
|
SystemWideLogger.Println("TLS mode disabled. Serving proxy request with plain http")
|
||||||
}
|
}
|
||||||
@ -58,7 +59,7 @@ func ReverseProxtInit() {
|
|||||||
SystemWideLogger.Println("Development mode disabled. Proxying with default Cache Control policy")
|
SystemWideLogger.Println("Development mode disabled. Proxying with default Cache Control policy")
|
||||||
}
|
}
|
||||||
|
|
||||||
listenOnPort80 := false
|
listenOnPort80 := true
|
||||||
sysdb.Read("settings", "listenP80", &listenOnPort80)
|
sysdb.Read("settings", "listenP80", &listenOnPort80)
|
||||||
if listenOnPort80 {
|
if listenOnPort80 {
|
||||||
SystemWideLogger.Println("Port 80 listener enabled")
|
SystemWideLogger.Println("Port 80 listener enabled")
|
||||||
@ -66,7 +67,7 @@ func ReverseProxtInit() {
|
|||||||
SystemWideLogger.Println("Port 80 listener disabled")
|
SystemWideLogger.Println("Port 80 listener disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
forceHttpsRedirect := false
|
forceHttpsRedirect := true
|
||||||
sysdb.Read("settings", "redirect", &forceHttpsRedirect)
|
sysdb.Read("settings", "redirect", &forceHttpsRedirect)
|
||||||
if forceHttpsRedirect {
|
if forceHttpsRedirect {
|
||||||
SystemWideLogger.Println("Force HTTPS mode enabled")
|
SystemWideLogger.Println("Force HTTPS mode enabled")
|
||||||
@ -84,7 +85,7 @@ func ReverseProxtInit() {
|
|||||||
|
|
||||||
dprouter, err := dynamicproxy.NewDynamicProxy(dynamicproxy.RouterOption{
|
dprouter, err := dynamicproxy.NewDynamicProxy(dynamicproxy.RouterOption{
|
||||||
HostUUID: nodeUUID,
|
HostUUID: nodeUUID,
|
||||||
HostVersion: version,
|
HostVersion: SYSTEM_VERSION,
|
||||||
Port: inboundPort,
|
Port: inboundPort,
|
||||||
UseTls: useTls,
|
UseTls: useTls,
|
||||||
ForceTLSLatest: forceLatestTLSVersion,
|
ForceTLSLatest: forceLatestTLSVersion,
|
||||||
@ -95,8 +96,9 @@ func ReverseProxtInit() {
|
|||||||
RedirectRuleTable: redirectTable,
|
RedirectRuleTable: redirectTable,
|
||||||
GeodbStore: geodbStore,
|
GeodbStore: geodbStore,
|
||||||
StatisticCollector: statisticCollector,
|
StatisticCollector: statisticCollector,
|
||||||
WebDirectory: *staticWebServerRoot,
|
WebDirectory: *path_webserver,
|
||||||
AccessController: accessController,
|
AccessController: accessController,
|
||||||
|
AutheliaRouter: autheliaRouter,
|
||||||
LoadBalancer: loadBalancer,
|
LoadBalancer: loadBalancer,
|
||||||
Logger: SystemWideLogger,
|
Logger: SystemWideLogger,
|
||||||
})
|
})
|
||||||
@ -307,10 +309,21 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Generate a default authenticaion provider
|
||||||
|
authMethod := dynamicproxy.AuthMethodNone
|
||||||
|
if requireBasicAuth {
|
||||||
|
authMethod = dynamicproxy.AuthMethodBasic
|
||||||
|
}
|
||||||
|
thisAuthenticationProvider := dynamicproxy.AuthenticationProvider{
|
||||||
|
AuthMethod: authMethod,
|
||||||
|
BasicAuthCredentials: basicAuthCredentials,
|
||||||
|
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
|
||||||
|
}
|
||||||
|
|
||||||
//Generate a proxy endpoint object
|
//Generate a proxy endpoint object
|
||||||
thisProxyEndpoint := dynamicproxy.ProxyEndpoint{
|
thisProxyEndpoint := dynamicproxy.ProxyEndpoint{
|
||||||
//I/O
|
//I/O
|
||||||
ProxyType: dynamicproxy.ProxyType_Host,
|
ProxyType: dynamicproxy.ProxyTypeHost,
|
||||||
RootOrMatchingDomain: rootOrMatchingDomain,
|
RootOrMatchingDomain: rootOrMatchingDomain,
|
||||||
MatchingDomainAlias: aliasHostnames,
|
MatchingDomainAlias: aliasHostnames,
|
||||||
ActiveOrigins: []*loadbalance.Upstream{
|
ActiveOrigins: []*loadbalance.Upstream{
|
||||||
@ -331,13 +344,16 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
//VDir
|
//VDir
|
||||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||||
//Custom headers
|
//Custom headers
|
||||||
UserDefinedHeaders: []*dynamicproxy.UserDefinedHeader{},
|
|
||||||
//Auth
|
//Auth
|
||||||
RequireBasicAuth: requireBasicAuth,
|
AuthenticationProvider: &thisAuthenticationProvider,
|
||||||
BasicAuthCredentials: basicAuthCredentials,
|
|
||||||
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
|
//Header Rewrite
|
||||||
DefaultSiteOption: 0,
|
HeaderRewriteRules: dynamicproxy.GetDefaultHeaderRewriteRules(),
|
||||||
DefaultSiteValue: "",
|
|
||||||
|
//Default Site
|
||||||
|
DefaultSiteOption: 0,
|
||||||
|
DefaultSiteValue: "",
|
||||||
// Rate Limit
|
// Rate Limit
|
||||||
RequireRateLimit: requireRateLimit,
|
RequireRateLimit: requireRateLimit,
|
||||||
RateLimit: int64(proxyRateLimit),
|
RateLimit: int64(proxyRateLimit),
|
||||||
@ -377,7 +393,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
//Write the root options to file
|
//Write the root options to file
|
||||||
rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{
|
rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{
|
||||||
ProxyType: dynamicproxy.ProxyType_Root,
|
ProxyType: dynamicproxy.ProxyTypeRoot,
|
||||||
RootOrMatchingDomain: "/",
|
RootOrMatchingDomain: "/",
|
||||||
ActiveOrigins: []*loadbalance.Upstream{
|
ActiveOrigins: []*loadbalance.Upstream{
|
||||||
{
|
{
|
||||||
@ -451,13 +467,23 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
bypassGlobalTLS := (bpgtls == "true")
|
bypassGlobalTLS := (bpgtls == "true")
|
||||||
|
|
||||||
// Basic Auth
|
//Disable uptime monitor
|
||||||
rba, _ := utils.PostPara(r, "bauth")
|
disbleUtm, err := utils.PostBool(r, "dutm")
|
||||||
if rba == "" {
|
if err != nil {
|
||||||
rba = "false"
|
disbleUtm = false
|
||||||
}
|
}
|
||||||
|
|
||||||
requireBasicAuth := (rba == "true")
|
// Auth Provider
|
||||||
|
authProviderTypeStr, _ := utils.PostPara(r, "authprovider")
|
||||||
|
if authProviderTypeStr == "" {
|
||||||
|
authProviderTypeStr = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
authProviderType, err := strconv.Atoi(authProviderTypeStr)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Invalid auth provider type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Rate Limiting?
|
// Rate Limiting?
|
||||||
rl, _ := utils.PostPara(r, "rate")
|
rl, _ := utils.PostPara(r, "rate")
|
||||||
@ -492,10 +518,27 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
//Generate a new proxyEndpoint from the new config
|
//Generate a new proxyEndpoint from the new config
|
||||||
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
|
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
|
||||||
newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
|
newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
|
||||||
newProxyEndpoint.RequireBasicAuth = requireBasicAuth
|
if newProxyEndpoint.AuthenticationProvider == nil {
|
||||||
|
newProxyEndpoint.AuthenticationProvider = &dynamicproxy.AuthenticationProvider{
|
||||||
|
AuthMethod: dynamicproxy.AuthMethodNone,
|
||||||
|
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
||||||
|
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if authProviderType == 1 {
|
||||||
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodBasic
|
||||||
|
} else if authProviderType == 2 {
|
||||||
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodAuthelia
|
||||||
|
} else if authProviderType == 3 {
|
||||||
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOauth2
|
||||||
|
} else {
|
||||||
|
newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodNone
|
||||||
|
}
|
||||||
|
|
||||||
newProxyEndpoint.RequireRateLimit = requireRateLimit
|
newProxyEndpoint.RequireRateLimit = requireRateLimit
|
||||||
newProxyEndpoint.RateLimit = proxyRateLimit
|
newProxyEndpoint.RateLimit = proxyRateLimit
|
||||||
newProxyEndpoint.UseStickySession = useStickySession
|
newProxyEndpoint.UseStickySession = useStickySession
|
||||||
|
newProxyEndpoint.DisableUptimeMonitor = disbleUtm
|
||||||
|
|
||||||
//Prepare to replace the current routing rule
|
//Prepare to replace the current routing rule
|
||||||
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
|
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
|
||||||
@ -622,7 +665,7 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
usernames := []string{}
|
usernames := []string{}
|
||||||
for _, cred := range targetProxy.BasicAuthCredentials {
|
for _, cred := range targetProxy.AuthenticationProvider.BasicAuthCredentials {
|
||||||
usernames = append(usernames, cred.Username)
|
usernames = append(usernames, cred.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -666,7 +709,7 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
|
|||||||
if credential.Password == "" {
|
if credential.Password == "" {
|
||||||
//Check if exists in the old credential files
|
//Check if exists in the old credential files
|
||||||
keepUnchange := false
|
keepUnchange := false
|
||||||
for _, oldCredEntry := range targetProxy.BasicAuthCredentials {
|
for _, oldCredEntry := range targetProxy.AuthenticationProvider.BasicAuthCredentials {
|
||||||
if oldCredEntry.Username == credential.Username {
|
if oldCredEntry.Username == credential.Username {
|
||||||
//Exists! Reuse the old hash
|
//Exists! Reuse the old hash
|
||||||
mergedCredentials = append(mergedCredentials, &dynamicproxy.BasicAuthCredentials{
|
mergedCredentials = append(mergedCredentials, &dynamicproxy.BasicAuthCredentials{
|
||||||
@ -691,7 +734,7 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
targetProxy.BasicAuthCredentials = mergedCredentials
|
targetProxy.AuthenticationProvider.BasicAuthCredentials = mergedCredentials
|
||||||
|
|
||||||
//Save it to file
|
//Save it to file
|
||||||
SaveReverseProxyConfig(targetProxy)
|
SaveReverseProxyConfig(targetProxy)
|
||||||
@ -725,7 +768,7 @@ func ListProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//List all the exception paths for this proxy
|
//List all the exception paths for this proxy
|
||||||
results := targetProxy.BasicAuthExceptionRules
|
results := targetProxy.AuthenticationProvider.BasicAuthExceptionRules
|
||||||
if results == nil {
|
if results == nil {
|
||||||
//It is a config from a really old version of zoraxy. Overwrite it with empty array
|
//It is a config from a really old version of zoraxy. Overwrite it with empty array
|
||||||
results = []*dynamicproxy.BasicAuthExceptionRule{}
|
results = []*dynamicproxy.BasicAuthExceptionRule{}
|
||||||
@ -762,7 +805,7 @@ func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
//Add a new exception rule if it is not already exists
|
//Add a new exception rule if it is not already exists
|
||||||
alreadyExists := false
|
alreadyExists := false
|
||||||
for _, thisExceptionRule := range targetProxy.BasicAuthExceptionRules {
|
for _, thisExceptionRule := range targetProxy.AuthenticationProvider.BasicAuthExceptionRules {
|
||||||
if thisExceptionRule.PathPrefix == matchingPrefix {
|
if thisExceptionRule.PathPrefix == matchingPrefix {
|
||||||
alreadyExists = true
|
alreadyExists = true
|
||||||
break
|
break
|
||||||
@ -772,7 +815,7 @@ func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendErrorResponse(w, "This matching path already exists")
|
utils.SendErrorResponse(w, "This matching path already exists")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
targetProxy.BasicAuthExceptionRules = append(targetProxy.BasicAuthExceptionRules, &dynamicproxy.BasicAuthExceptionRule{
|
targetProxy.AuthenticationProvider.BasicAuthExceptionRules = append(targetProxy.AuthenticationProvider.BasicAuthExceptionRules, &dynamicproxy.BasicAuthExceptionRule{
|
||||||
PathPrefix: strings.TrimSpace(matchingPrefix),
|
PathPrefix: strings.TrimSpace(matchingPrefix),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -806,7 +849,7 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
newExceptionRuleList := []*dynamicproxy.BasicAuthExceptionRule{}
|
newExceptionRuleList := []*dynamicproxy.BasicAuthExceptionRule{}
|
||||||
matchingExists := false
|
matchingExists := false
|
||||||
for _, thisExceptionalRule := range targetProxy.BasicAuthExceptionRules {
|
for _, thisExceptionalRule := range targetProxy.AuthenticationProvider.BasicAuthExceptionRules {
|
||||||
if thisExceptionalRule.PathPrefix != matchingPrefix {
|
if thisExceptionalRule.PathPrefix != matchingPrefix {
|
||||||
newExceptionRuleList = append(newExceptionRuleList, thisExceptionalRule)
|
newExceptionRuleList = append(newExceptionRuleList, thisExceptionalRule)
|
||||||
} else {
|
} else {
|
||||||
@ -819,7 +862,7 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetProxy.BasicAuthExceptionRules = newExceptionRuleList
|
targetProxy.AuthenticationProvider.BasicAuthExceptionRules = newExceptionRuleList
|
||||||
|
|
||||||
// Save configs to runtime and file
|
// Save configs to runtime and file
|
||||||
targetProxy.UpdateToRuntime()
|
targetProxy.UpdateToRuntime()
|
||||||
@ -883,6 +926,7 @@ func ReverseProxyListDetail(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendErrorResponse(w, "epname not defined")
|
utils.SendErrorResponse(w, "epname not defined")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
epname = strings.ToLower(strings.TrimSpace(epname))
|
||||||
endpointRaw, ok := dynamicProxyRouter.ProxyEndpoints.Load(epname)
|
endpointRaw, ok := dynamicProxyRouter.ProxyEndpoints.Load(epname)
|
||||||
if !ok {
|
if !ok {
|
||||||
utils.SendErrorResponse(w, "proxy rule not found")
|
utils.SendErrorResponse(w, "proxy rule not found")
|
||||||
@ -910,17 +954,15 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
|
|||||||
results := []*dynamicproxy.ProxyEndpoint{}
|
results := []*dynamicproxy.ProxyEndpoint{}
|
||||||
dynamicProxyRouter.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
dynamicProxyRouter.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||||
thisEndpoint := dynamicproxy.CopyEndpoint(value.(*dynamicproxy.ProxyEndpoint))
|
thisEndpoint := dynamicproxy.CopyEndpoint(value.(*dynamicproxy.ProxyEndpoint))
|
||||||
|
|
||||||
//Clear the auth passwords before showing to front-end
|
//Clear the auth passwords before showing to front-end
|
||||||
cleanedCredentials := []*dynamicproxy.BasicAuthCredentials{}
|
cleanedCredentials := []*dynamicproxy.BasicAuthCredentials{}
|
||||||
for _, user := range thisEndpoint.BasicAuthCredentials {
|
for _, user := range thisEndpoint.AuthenticationProvider.BasicAuthCredentials {
|
||||||
cleanedCredentials = append(cleanedCredentials, &dynamicproxy.BasicAuthCredentials{
|
cleanedCredentials = append(cleanedCredentials, &dynamicproxy.BasicAuthCredentials{
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
PasswordHash: "",
|
PasswordHash: "",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
thisEndpoint.AuthenticationProvider.BasicAuthCredentials = cleanedCredentials
|
||||||
thisEndpoint.BasicAuthCredentials = cleanedCredentials
|
|
||||||
results = append(results, thisEndpoint)
|
results = append(results, thisEndpoint)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@ -1085,6 +1127,7 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
if dynamicProxyRouter.Running {
|
if dynamicProxyRouter.Running {
|
||||||
dynamicProxyRouter.StopProxyService()
|
dynamicProxyRouter.StopProxyService()
|
||||||
dynamicProxyRouter.Option.Port = newIncomingPortInt
|
dynamicProxyRouter.Option.Port = newIncomingPortInt
|
||||||
|
time.Sleep(1 * time.Second) //Fixed start fail issue
|
||||||
dynamicProxyRouter.StartProxyService()
|
dynamicProxyRouter.StartProxyService()
|
||||||
} else {
|
} else {
|
||||||
//Only change setting but not starting the proxy service
|
//Only change setting but not starting the proxy service
|
||||||
@ -1126,9 +1169,9 @@ func HandleCustomHeaderList(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//List all custom headers
|
//List all custom headers
|
||||||
customHeaderList := targetProxyEndpoint.UserDefinedHeaders
|
customHeaderList := targetProxyEndpoint.HeaderRewriteRules.UserDefinedHeaders
|
||||||
if customHeaderList == nil {
|
if customHeaderList == nil {
|
||||||
customHeaderList = []*dynamicproxy.UserDefinedHeader{}
|
customHeaderList = []*rewrite.UserDefinedHeader{}
|
||||||
}
|
}
|
||||||
js, _ := json.Marshal(customHeaderList)
|
js, _ := json.Marshal(customHeaderList)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
@ -1173,12 +1216,12 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create a Custom Header Defination type
|
//Create a Custom Header Definition type
|
||||||
var rewriteDirection dynamicproxy.HeaderDirection
|
var rewriteDirection rewrite.HeaderDirection
|
||||||
if direction == "toOrigin" {
|
if direction == "toOrigin" {
|
||||||
rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToUpstream
|
rewriteDirection = rewrite.HeaderDirection_ZoraxyToUpstream
|
||||||
} else if direction == "toClient" {
|
} else if direction == "toClient" {
|
||||||
rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToDownstream
|
rewriteDirection = rewrite.HeaderDirection_ZoraxyToDownstream
|
||||||
} else {
|
} else {
|
||||||
//Unknown direction
|
//Unknown direction
|
||||||
utils.SendErrorResponse(w, "header rewrite direction not supported")
|
utils.SendErrorResponse(w, "header rewrite direction not supported")
|
||||||
@ -1189,7 +1232,8 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
if rewriteType == "remove" {
|
if rewriteType == "remove" {
|
||||||
isRemove = true
|
isRemove = true
|
||||||
}
|
}
|
||||||
headerRewriteDefination := dynamicproxy.UserDefinedHeader{
|
|
||||||
|
headerRewriteDefinition := rewrite.UserDefinedHeader{
|
||||||
Key: name,
|
Key: name,
|
||||||
Value: value,
|
Value: value,
|
||||||
Direction: rewriteDirection,
|
Direction: rewriteDirection,
|
||||||
@ -1197,7 +1241,7 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Create a new custom header object
|
//Create a new custom header object
|
||||||
err = targetProxyEndpoint.AddUserDefinedHeader(&headerRewriteDefination)
|
err = targetProxyEndpoint.AddUserDefinedHeader(&headerRewriteDefinition)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "unable to add header rewrite rule: "+err.Error())
|
utils.SendErrorResponse(w, "unable to add header rewrite rule: "+err.Error())
|
||||||
return
|
return
|
||||||
@ -1267,7 +1311,7 @@ func HandleHostOverwrite(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
//Get the current host header
|
//Get the current host header
|
||||||
js, _ := json.Marshal(targetProxyEndpoint.RequestHostOverwrite)
|
js, _ := json.Marshal(targetProxyEndpoint.HeaderRewriteRules.RequestHostOverwrite)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else if r.Method == http.MethodPost {
|
} else if r.Method == http.MethodPost {
|
||||||
//Set the new host header
|
//Set the new host header
|
||||||
@ -1276,7 +1320,7 @@ func HandleHostOverwrite(w http.ResponseWriter, r *http.Request) {
|
|||||||
//As this will require change in the proxy instance we are running
|
//As this will require change in the proxy instance we are running
|
||||||
//we need to clone and respawn this proxy endpoint
|
//we need to clone and respawn this proxy endpoint
|
||||||
newProxyEndpoint := targetProxyEndpoint.Clone()
|
newProxyEndpoint := targetProxyEndpoint.Clone()
|
||||||
newProxyEndpoint.RequestHostOverwrite = newHostname
|
newProxyEndpoint.HeaderRewriteRules.RequestHostOverwrite = newHostname
|
||||||
//Save proxy endpoint
|
//Save proxy endpoint
|
||||||
err = SaveReverseProxyConfig(newProxyEndpoint)
|
err = SaveReverseProxyConfig(newProxyEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1339,7 +1383,7 @@ func HandleHopByHop(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
//Get the current hop by hop header state
|
//Get the current hop by hop header state
|
||||||
js, _ := json.Marshal(!targetProxyEndpoint.DisableHopByHopHeaderRemoval)
|
js, _ := json.Marshal(!targetProxyEndpoint.HeaderRewriteRules.DisableHopByHopHeaderRemoval)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else if r.Method == http.MethodPost {
|
} else if r.Method == http.MethodPost {
|
||||||
//Set the hop by hop header state
|
//Set the hop by hop header state
|
||||||
@ -1349,7 +1393,7 @@ func HandleHopByHop(w http.ResponseWriter, r *http.Request) {
|
|||||||
//we need to clone and respawn this proxy endpoint
|
//we need to clone and respawn this proxy endpoint
|
||||||
newProxyEndpoint := targetProxyEndpoint.Clone()
|
newProxyEndpoint := targetProxyEndpoint.Clone()
|
||||||
//Storage file use false as default, so disable removal = not enable remover
|
//Storage file use false as default, so disable removal = not enable remover
|
||||||
newProxyEndpoint.DisableHopByHopHeaderRemoval = !enableHopByHopRemover
|
newProxyEndpoint.HeaderRewriteRules.DisableHopByHopHeaderRemoval = !enableHopByHopRemover
|
||||||
|
|
||||||
//Save proxy endpoint
|
//Save proxy endpoint
|
||||||
err = SaveReverseProxyConfig(newProxyEndpoint)
|
err = SaveReverseProxyConfig(newProxyEndpoint)
|
||||||
@ -1412,7 +1456,7 @@ func HandleHSTSState(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
//Return current HSTS enable state
|
//Return current HSTS enable state
|
||||||
hstsAge := targetProxyEndpoint.HSTSMaxAge
|
hstsAge := targetProxyEndpoint.HeaderRewriteRules.HSTSMaxAge
|
||||||
js, _ := json.Marshal(hstsAge)
|
js, _ := json.Marshal(hstsAge)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
return
|
return
|
||||||
@ -1424,8 +1468,12 @@ func HandleHSTSState(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if newMaxAge == 0 || newMaxAge >= 31536000 {
|
if newMaxAge == 0 || newMaxAge >= 31536000 {
|
||||||
targetProxyEndpoint.HSTSMaxAge = int64(newMaxAge)
|
targetProxyEndpoint.HeaderRewriteRules.HSTSMaxAge = int64(newMaxAge)
|
||||||
SaveReverseProxyConfig(targetProxyEndpoint)
|
err = SaveReverseProxyConfig(targetProxyEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "save HSTS state failed: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
targetProxyEndpoint.UpdateToRuntime()
|
targetProxyEndpoint.UpdateToRuntime()
|
||||||
} else {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid max age given")
|
utils.SendErrorResponse(w, "invalid max age given")
|
||||||
@ -1462,11 +1510,11 @@ func HandlePermissionPolicy(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentPolicy := permissionpolicy.GetDefaultPermissionPolicy()
|
currentPolicy := permissionpolicy.GetDefaultPermissionPolicy()
|
||||||
if targetProxyEndpoint.PermissionPolicy != nil {
|
if targetProxyEndpoint.HeaderRewriteRules.PermissionPolicy != nil {
|
||||||
currentPolicy = targetProxyEndpoint.PermissionPolicy
|
currentPolicy = targetProxyEndpoint.HeaderRewriteRules.PermissionPolicy
|
||||||
}
|
}
|
||||||
result := CurrentPolicyState{
|
result := CurrentPolicyState{
|
||||||
PPEnabled: targetProxyEndpoint.EnablePermissionPolicyHeader,
|
PPEnabled: targetProxyEndpoint.HeaderRewriteRules.EnablePermissionPolicyHeader,
|
||||||
CurrentPolicy: currentPolicy,
|
CurrentPolicy: currentPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1481,7 +1529,7 @@ func HandlePermissionPolicy(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetProxyEndpoint.EnablePermissionPolicyHeader = enableState
|
targetProxyEndpoint.HeaderRewriteRules.EnablePermissionPolicyHeader = enableState
|
||||||
SaveReverseProxyConfig(targetProxyEndpoint)
|
SaveReverseProxyConfig(targetProxyEndpoint)
|
||||||
targetProxyEndpoint.UpdateToRuntime()
|
targetProxyEndpoint.UpdateToRuntime()
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
@ -1503,7 +1551,7 @@ func HandlePermissionPolicy(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Save it to file
|
//Save it to file
|
||||||
targetProxyEndpoint.PermissionPolicy = newPermissionPolicy
|
targetProxyEndpoint.HeaderRewriteRules.PermissionPolicy = newPermissionPolicy
|
||||||
SaveReverseProxyConfig(targetProxyEndpoint)
|
SaveReverseProxyConfig(targetProxyEndpoint)
|
||||||
targetProxyEndpoint.UpdateToRuntime()
|
targetProxyEndpoint.UpdateToRuntime()
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
@ -1512,3 +1560,39 @@ func HandlePermissionPolicy(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HandleWsHeaderBehavior(w http.ResponseWriter, r *http.Request) {
|
||||||
|
domain, err := utils.PostPara(r, "domain")
|
||||||
|
if err != nil {
|
||||||
|
domain, err = utils.GetPara(r, "domain")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "domain or matching rule not defined")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "target endpoint not exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
js, _ := json.Marshal(targetProxyEndpoint.EnableWebsocketCustomHeaders)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
} else if r.Method == http.MethodPost {
|
||||||
|
enableWsHeader, err := utils.PostBool(r, "enable")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid enable state given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetProxyEndpoint.EnableWebsocketCustomHeaders = enableWsHeader
|
||||||
|
SaveReverseProxyConfig(targetProxyEndpoint)
|
||||||
|
targetProxyEndpoint.UpdateToRuntime()
|
||||||
|
utils.SendOK(w)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -27,7 +27,7 @@ func FSHandler(handler http.Handler) http.Handler {
|
|||||||
Development Mode Override
|
Development Mode Override
|
||||||
=> Web root is located in /
|
=> Web root is located in /
|
||||||
*/
|
*/
|
||||||
if development && strings.HasPrefix(r.URL.Path, "/web/") {
|
if DEVELOPMENT_BUILD && strings.HasPrefix(r.URL.Path, "/web/") {
|
||||||
u, _ := url.Parse(strings.TrimPrefix(r.URL.Path, "/web"))
|
u, _ := url.Parse(strings.TrimPrefix(r.URL.Path, "/web"))
|
||||||
r.URL = u
|
r.URL = u
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ func FSHandler(handler http.Handler) http.Handler {
|
|||||||
Production Mode Override
|
Production Mode Override
|
||||||
=> Web root is located in /web
|
=> Web root is located in /web
|
||||||
*/
|
*/
|
||||||
if !development && r.URL.Path == "/" {
|
if !DEVELOPMENT_BUILD && r.URL.Path == "/" {
|
||||||
//Redirect to web UI
|
//Redirect to web UI
|
||||||
http.Redirect(w, r, "/web/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/web/", http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
@ -93,7 +93,7 @@ func FSHandler(handler http.Handler) http.Handler {
|
|||||||
|
|
||||||
// Production path fix wrapper. Fix the path on production or development environment
|
// Production path fix wrapper. Fix the path on production or development environment
|
||||||
func ppf(relativeFilepath string) string {
|
func ppf(relativeFilepath string) string {
|
||||||
if !development {
|
if !DEVELOPMENT_BUILD {
|
||||||
return strings.ReplaceAll(filepath.Join("/web/", relativeFilepath), "\\", "/")
|
return strings.ReplaceAll(filepath.Join("/web/", relativeFilepath), "\\", "/")
|
||||||
}
|
}
|
||||||
return relativeFilepath
|
return relativeFilepath
|
||||||
@ -111,7 +111,7 @@ func handleInjectHTML(w http.ResponseWriter, r *http.Request, relativeFilepath s
|
|||||||
if len(relativeFilepath) > 0 && relativeFilepath[len(relativeFilepath)-1:] == "/" {
|
if len(relativeFilepath) > 0 && relativeFilepath[len(relativeFilepath)-1:] == "/" {
|
||||||
relativeFilepath = relativeFilepath + "index.html"
|
relativeFilepath = relativeFilepath + "index.html"
|
||||||
}
|
}
|
||||||
if development {
|
if DEVELOPMENT_BUILD {
|
||||||
//Load from disk
|
//Load from disk
|
||||||
targetFilePath := strings.ReplaceAll(filepath.Join("web/", relativeFilepath), "\\", "/")
|
targetFilePath := strings.ReplaceAll(filepath.Join("web/", relativeFilepath), "\\", "/")
|
||||||
content, err = os.ReadFile(targetFilePath)
|
content, err = os.ReadFile(targetFilePath)
|
||||||
|
126
src/start.go
126
src/start.go
@ -12,7 +12,9 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/access"
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/acme"
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
"imuslab.com/zoraxy/mod/auth"
|
"imuslab.com/zoraxy/mod/auth"
|
||||||
|
"imuslab.com/zoraxy/mod/auth/sso/authelia"
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/database/dbinc"
|
||||||
"imuslab.com/zoraxy/mod/dockerux"
|
"imuslab.com/zoraxy/mod/dockerux"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||||
@ -36,7 +38,10 @@ import (
|
|||||||
Startup Sequence
|
Startup Sequence
|
||||||
|
|
||||||
This function starts the startup sequence of all
|
This function starts the startup sequence of all
|
||||||
required modules
|
required modules. Their startup sequences are inter-dependent
|
||||||
|
and must be started in a specific order.
|
||||||
|
|
||||||
|
Don't touch this function unless you know what you are doing
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -49,19 +54,26 @@ var (
|
|||||||
|
|
||||||
func startupSequence() {
|
func startupSequence() {
|
||||||
//Start a system wide logger and log viewer
|
//Start a system wide logger and log viewer
|
||||||
l, err := logger.NewLogger("zr", "./log")
|
l, err := logger.NewLogger(LOG_PREFIX, *path_logFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
SystemWideLogger = l
|
SystemWideLogger = l
|
||||||
} else {
|
} else {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
|
LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
|
||||||
RootFolder: "./log",
|
RootFolder: *path_logFile,
|
||||||
Extension: ".log",
|
Extension: LOG_EXTENSION,
|
||||||
})
|
})
|
||||||
|
|
||||||
//Create database
|
//Create database
|
||||||
db, err := database.NewDatabase("sys.db", false)
|
backendType := database.GetRecommendedBackendType()
|
||||||
|
if *databaseBackend == "leveldb" {
|
||||||
|
backendType = dbinc.BackendLevelDB
|
||||||
|
} else if *databaseBackend == "boltdb" {
|
||||||
|
backendType = dbinc.BackendBoltDB
|
||||||
|
}
|
||||||
|
l.PrintAndLog("database", "Using "+backendType.String()+" as the database backend", nil)
|
||||||
|
db, err := database.NewDatabase("./sys.db", backendType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -70,21 +82,21 @@ func startupSequence() {
|
|||||||
sysdb.NewTable("settings")
|
sysdb.NewTable("settings")
|
||||||
|
|
||||||
//Create tmp folder and conf folder
|
//Create tmp folder and conf folder
|
||||||
os.MkdirAll("./tmp", 0775)
|
os.MkdirAll(TMP_FOLDER, 0775)
|
||||||
os.MkdirAll("./conf/proxy/", 0775)
|
os.MkdirAll(CONF_HTTP_PROXY, 0775)
|
||||||
|
|
||||||
//Create an auth agent
|
//Create an auth agent
|
||||||
sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger)
|
sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, SystemWideLogger, func(w http.ResponseWriter, r *http.Request) {
|
authAgent = auth.NewAuthenticationAgent(SYSTEM_NAME, []byte(sessionKey), sysdb, true, SystemWideLogger, func(w http.ResponseWriter, r *http.Request) {
|
||||||
//Not logged in. Redirecting to login page
|
//Not logged in. Redirecting to login page
|
||||||
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
|
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
|
||||||
})
|
})
|
||||||
|
|
||||||
//Create a TLS certificate manager
|
//Create a TLS certificate manager
|
||||||
tlsCertManager, err = tlscert.NewManager("./conf/certs", development, SystemWideLogger)
|
tlsCertManager, err = tlscert.NewManager(CONF_CERT_STORE, DEVELOPMENT_BUILD, SystemWideLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -93,15 +105,17 @@ func startupSequence() {
|
|||||||
db.NewTable("redirect")
|
db.NewTable("redirect")
|
||||||
redirectAllowRegexp := false
|
redirectAllowRegexp := false
|
||||||
db.Read("redirect", "regex", &redirectAllowRegexp)
|
db.Read("redirect", "regex", &redirectAllowRegexp)
|
||||||
redirectTable, err = redirection.NewRuleTable("./conf/redirect", redirectAllowRegexp, SystemWideLogger)
|
redirectTable, err = redirection.NewRuleTable(CONF_REDIRECTION, redirectAllowRegexp, SystemWideLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create a geodb store
|
//Create a geodb store
|
||||||
geodbStore, err = geodb.NewGeoDb(sysdb, &geodb.StoreOptions{
|
geodbStore, err = geodb.NewGeoDb(sysdb, &geodb.StoreOptions{
|
||||||
AllowSlowIpv4LookUp: !*enableHighSpeedGeoIPLookup,
|
AllowSlowIpv4LookUp: !*enableHighSpeedGeoIPLookup,
|
||||||
AllowSloeIpv6Lookup: !*enableHighSpeedGeoIPLookup,
|
AllowSlowIpv6Lookup: !*enableHighSpeedGeoIPLookup,
|
||||||
|
Logger: SystemWideLogger,
|
||||||
|
SlowLookupCacheClearInterval: GEODB_CACHE_CLEAR_INTERVAL * time.Minute,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -118,12 +132,20 @@ func startupSequence() {
|
|||||||
accessController, err = access.NewAccessController(&access.Options{
|
accessController, err = access.NewAccessController(&access.Options{
|
||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
GeoDB: geodbStore,
|
GeoDB: geodbStore,
|
||||||
ConfigFolder: "./conf/access",
|
ConfigFolder: CONF_ACCESS_RULE,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Create authentication providers
|
||||||
|
autheliaRouter = authelia.NewAutheliaRouter(&authelia.AutheliaRouterOptions{
|
||||||
|
UseHTTPS: false, // Automatic populate in router initiation
|
||||||
|
AutheliaURL: "", // Automatic populate in router initiation
|
||||||
|
Logger: SystemWideLogger,
|
||||||
|
Database: sysdb,
|
||||||
|
})
|
||||||
|
|
||||||
//Create a statistic collector
|
//Create a statistic collector
|
||||||
statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{
|
statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{
|
||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
@ -135,8 +157,8 @@ func startupSequence() {
|
|||||||
//Start the static web server
|
//Start the static web server
|
||||||
staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{
|
staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{
|
||||||
Sysdb: sysdb,
|
Sysdb: sysdb,
|
||||||
Port: "5487", //Default Port
|
Port: strconv.Itoa(WEBSERV_DEFAULT_PORT), //Default Port
|
||||||
WebRoot: *staticWebServerRoot,
|
WebRoot: *path_webserver,
|
||||||
EnableDirectoryListing: true,
|
EnableDirectoryListing: true,
|
||||||
EnableWebDirManager: *allowWebFileManager,
|
EnableWebDirManager: *allowWebFileManager,
|
||||||
Logger: SystemWideLogger,
|
Logger: SystemWideLogger,
|
||||||
@ -160,7 +182,7 @@ func startupSequence() {
|
|||||||
|
|
||||||
pathRuleHandler = pathrule.NewPathRuleHandler(&pathrule.Options{
|
pathRuleHandler = pathrule.NewPathRuleHandler(&pathrule.Options{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
ConfigFolder: "./conf/rules/pathrules",
|
ConfigFolder: CONF_PATH_RULE,
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -178,7 +200,7 @@ func startupSequence() {
|
|||||||
|
|
||||||
hostName := *mdnsName
|
hostName := *mdnsName
|
||||||
if hostName == "" {
|
if hostName == "" {
|
||||||
hostName = "zoraxy_" + nodeUUID
|
hostName = MDNS_HOSTNAME_PREFIX + nodeUUID
|
||||||
} else {
|
} else {
|
||||||
//Trim off the suffix
|
//Trim off the suffix
|
||||||
hostName = strings.TrimSuffix(hostName, ".local")
|
hostName = strings.TrimSuffix(hostName, ".local")
|
||||||
@ -187,24 +209,24 @@ func startupSequence() {
|
|||||||
mdnsScanner, err = mdns.NewMDNS(mdns.NetworkHost{
|
mdnsScanner, err = mdns.NewMDNS(mdns.NetworkHost{
|
||||||
HostName: hostName,
|
HostName: hostName,
|
||||||
Port: portInt,
|
Port: portInt,
|
||||||
Domain: "zoraxy.arozos.com",
|
Domain: MDNS_IDENTIFY_DOMAIN,
|
||||||
Model: "Network Gateway",
|
Model: MDNS_IDENTIFY_DEVICE_TYPE,
|
||||||
UUID: nodeUUID,
|
UUID: nodeUUID,
|
||||||
Vendor: "imuslab.com",
|
Vendor: MDNS_IDENTIFY_VENDOR,
|
||||||
BuildVersion: version,
|
BuildVersion: SYSTEM_VERSION,
|
||||||
}, "")
|
}, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.Println("Unable to startup mDNS service. Disabling mDNS services")
|
SystemWideLogger.Println("Unable to startup mDNS service. Disabling mDNS services")
|
||||||
} else {
|
} else {
|
||||||
//Start initial scanning
|
//Start initial scanning
|
||||||
go func() {
|
go func() {
|
||||||
hosts := mdnsScanner.Scan(30, "")
|
hosts := mdnsScanner.Scan(MDNS_SCAN_TIMEOUT, "")
|
||||||
previousmdnsScanResults = hosts
|
previousmdnsScanResults = hosts
|
||||||
SystemWideLogger.Println("mDNS Startup scan completed")
|
SystemWideLogger.Println("mDNS Startup scan completed")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
//Create a ticker to update mDNS results every 5 minutes
|
//Create a ticker to update mDNS results every 5 minutes
|
||||||
ticker := time.NewTicker(15 * time.Minute)
|
ticker := time.NewTicker(MDNS_SCAN_UPDATE_INTERVAL * time.Minute)
|
||||||
stopChan := make(chan bool)
|
stopChan := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
@ -212,7 +234,7 @@ func startupSequence() {
|
|||||||
case <-stopChan:
|
case <-stopChan:
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
hosts := mdnsScanner.Scan(30, "")
|
hosts := mdnsScanner.Scan(MDNS_SCAN_TIMEOUT, "")
|
||||||
previousmdnsScanResults = hosts
|
previousmdnsScanResults = hosts
|
||||||
SystemWideLogger.Println("mDNS scan result updated")
|
SystemWideLogger.Println("mDNS scan result updated")
|
||||||
}
|
}
|
||||||
@ -244,10 +266,14 @@ func startupSequence() {
|
|||||||
webSshManager = sshprox.NewSSHProxyManager()
|
webSshManager = sshprox.NewSSHProxyManager()
|
||||||
|
|
||||||
//Create TCP Proxy Manager
|
//Create TCP Proxy Manager
|
||||||
streamProxyManager = streamproxy.NewStreamProxy(&streamproxy.Options{
|
streamProxyManager, err = streamproxy.NewStreamProxy(&streamproxy.Options{
|
||||||
Database: sysdb,
|
|
||||||
AccessControlHandler: accessController.DefaultAccessRule.AllowConnectionAccess,
|
AccessControlHandler: accessController.DefaultAccessRule.AllowConnectionAccess,
|
||||||
|
ConfigStore: CONF_STREAM_PROXY,
|
||||||
|
Logger: SystemWideLogger,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
//Create WoL MAC storage table
|
//Create WoL MAC storage table
|
||||||
sysdb.NewTable("wolmac")
|
sysdb.NewTable("wolmac")
|
||||||
@ -280,11 +306,12 @@ func startupSequence() {
|
|||||||
sysdb.NewTable("acmepref")
|
sysdb.NewTable("acmepref")
|
||||||
acmeHandler = initACME()
|
acmeHandler = initACME()
|
||||||
acmeAutoRenewer, err = acme.NewAutoRenewer(
|
acmeAutoRenewer, err = acme.NewAutoRenewer(
|
||||||
"./conf/acme_conf.json",
|
ACME_AUTORENEW_CONFIG_PATH,
|
||||||
"./conf/certs/",
|
CONF_CERT_STORE,
|
||||||
int64(*acmeAutoRenewInterval),
|
int64(*acmeAutoRenewInterval),
|
||||||
*acmeCertAutoRenewDays,
|
*acmeCertAutoRenewDays,
|
||||||
acmeHandler,
|
acmeHandler,
|
||||||
|
SystemWideLogger,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -298,6 +325,7 @@ func startupSequence() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Finalize Startup Sequence */
|
||||||
// This sequence start after everything is initialized
|
// This sequence start after everything is initialized
|
||||||
func finalSequence() {
|
func finalSequence() {
|
||||||
//Start ACME renew agent
|
//Start ACME renew agent
|
||||||
@ -306,3 +334,45 @@ func finalSequence() {
|
|||||||
//Inject routing rules
|
//Inject routing rules
|
||||||
registerBuildInRoutingRules()
|
registerBuildInRoutingRules()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Shutdown Sequence */
|
||||||
|
func ShutdownSeq() {
|
||||||
|
SystemWideLogger.Println("Shutting down " + SYSTEM_NAME)
|
||||||
|
SystemWideLogger.Println("Closing Netstats Listener")
|
||||||
|
if netstatBuffers != nil {
|
||||||
|
netstatBuffers.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemWideLogger.Println("Closing Statistic Collector")
|
||||||
|
if statisticCollector != nil {
|
||||||
|
statisticCollector.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if mdnsTickerStop != nil {
|
||||||
|
SystemWideLogger.Println("Stopping mDNS Discoverer (might take a few minutes)")
|
||||||
|
// Stop the mdns service
|
||||||
|
mdnsTickerStop <- true
|
||||||
|
}
|
||||||
|
if mdnsScanner != nil {
|
||||||
|
mdnsScanner.Close()
|
||||||
|
}
|
||||||
|
SystemWideLogger.Println("Shutting down load balancer")
|
||||||
|
if loadBalancer != nil {
|
||||||
|
loadBalancer.Close()
|
||||||
|
}
|
||||||
|
SystemWideLogger.Println("Closing Certificates Auto Renewer")
|
||||||
|
if acmeAutoRenewer != nil {
|
||||||
|
acmeAutoRenewer.Close()
|
||||||
|
}
|
||||||
|
//Remove the tmp folder
|
||||||
|
SystemWideLogger.Println("Cleaning up tmp files")
|
||||||
|
os.RemoveAll("./tmp")
|
||||||
|
|
||||||
|
//Close database
|
||||||
|
SystemWideLogger.Println("Stopping system database")
|
||||||
|
sysdb.Close()
|
||||||
|
|
||||||
|
//Close logger
|
||||||
|
SystemWideLogger.Println("Closing system wide logger")
|
||||||
|
SystemWideLogger.Close()
|
||||||
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user