mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-30 11:21:44 +02:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
57135a867e | |||
547855f30f | |||
05b477e90a | |||
3519c7841c | |||
e7b4054248 | |||
973d0b3372 | |||
704980d4f8 | |||
03974163d4 | |||
dfb81513b1 | |||
b604c66a2f | |||
dd84864dd4 | |||
443cd961d2 | |||
10048150bb | |||
85f9b297c4 | |||
07e524a007 | |||
25c7e8ac1a | |||
49babbd60f | |||
fa11422748 | |||
bb1b161ae2 | |||
9545343151 | |||
61e4d45430 | |||
6026c4fd53 | |||
e3f8c99ed3 | |||
fc88dfe72e | |||
d43322f7a5 | |||
83536a83f7 | |||
1183b0ed55 | |||
b00e302f6d | |||
deddb17803 | |||
aa96d831e1 | |||
c6f7f37aaf | |||
63f12dedcf | |||
136d1ecafb | |||
7193defad7 | |||
cf4c57298e | |||
d82a531a41 | |||
7694e317f7 | |||
ed4945ab7e | |||
ce8741bfc8 | |||
7a3db09811 | |||
e73f9b47d3 | |||
c248dacccf | |||
d596d6b843 | |||
6feb2d105d | |||
3a26a5b4d3 | |||
2cdd5654ed | |||
a0d362df4e | |||
334c1ab131 | |||
08d52024ab | |||
a3e16594e8 | |||
cced07ba2d | |||
2003992d75 | |||
71423d98b1 | |||
8ca716c59f | |||
b1a14872c3 | |||
df9deb3fbb | |||
9369237229 |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -33,6 +33,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
- Device: [e.g. Bananapi R2 PRO]
|
||||
- OS: [e.g. Armbian]
|
||||
- Version [e.g. 23.02 Bullseye ]
|
||||
- Docker Version (if you are running Zoraxy in docker): [e.g. 3.0.4]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
25
.github/ISSUE_TEMPLATE/help-needed.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/help-needed.md
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
name: Help Needed
|
||||
about: Something went wrong but I don't know why
|
||||
title: "[HELP]"
|
||||
labels: help wanted
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What happened?**
|
||||
A clear and concise description of what the problem is. Ex. I tried to create a proxy rule but it doesn't work. When I connects to my domain, I see [...]
|
||||
|
||||
**Describe what have you tried**
|
||||
A clear and concise description of what you expect to see and what you have tried to debug it.
|
||||
|
||||
**Describe the networking setup you are using**
|
||||
Here are some example, commonly asked questions from our maintainers:
|
||||
- Are you using the docker build of Zoraxy? [yes (with docker setup & networking config attach) /no]
|
||||
- Your Zoraxy version? [e.g. 3.0.4]
|
||||
- Are you using Cloudflare? [yes/no]
|
||||
- Are your system hosted under a NAT router? [e.g. yes, with subnet is e.g. 192.168.0.0/24 and include port forwarding config if any]
|
||||
- DNS record (if any)
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -34,3 +34,9 @@ docker/ImagePublisher.sh
|
||||
src/mod/acme/test/stackoverflow.pem
|
||||
/tools/dns_challenge_update/code-gen/acmedns
|
||||
/tools/dns_challenge_update/code-gen/lego
|
||||
src/tmp/localhost.key
|
||||
src/tmp/localhost.pem
|
||||
src/www/html/index.html
|
||||
src/sys.uuid
|
||||
src/zoraxy
|
||||
src/log/
|
42
CHANGELOG.md
42
CHANGELOG.md
@ -1,3 +1,45 @@
|
||||
# v3.0.6 10 Jun 2024
|
||||
|
||||
+ Added fastly_client_ip to X-Real-IP auto rewrite
|
||||
+ Added atomic accumulator to TCP proxy
|
||||
+ Added white logo for future dark theme
|
||||
+ Added multi selection for white / blacklist [#176](https://github.com/tobychui/zoraxy/issues/176)
|
||||
+ Moved custom header rewrite to dpcore
|
||||
+ Restructure dpcore header rewrite sequence
|
||||
+ Added advance custom header settings (zoraxy to upstream and zoraxy to downstream mode)
|
||||
+ Added header remove feature
|
||||
+ Removed password requirement for SMTP [#162](https://github.com/tobychui/zoraxy/issues/162) [#80](https://github.com/tobychui/zoraxy/issues/80)
|
||||
+ Restructured TCP proxy into Stream Proxy (Support both TCP and UDP) [#147](https://github.com/tobychui/zoraxy/issues/147)
|
||||
+ Added stream proxy auto start [#169](https://github.com/tobychui/zoraxy/issues/169)
|
||||
+ Optimized UX for reminding user to click Apply after port change
|
||||
+ Added version number to footer [#160](https://github.com/tobychui/zoraxy/issues/160)
|
||||
+ Fixed missing / unnecessary error check [PR187](https://github.com/tobychui/zoraxy/pull/187) by [Kirari04](https://github.com/Kirari04)
|
||||
|
||||
# v3.0.5 May 26 2024
|
||||
|
||||
|
||||
+ Optimized uptime monitor error message [#121](https://github.com/tobychui/zoraxy/issues/121)
|
||||
+ Optimized detection logic for internal proxy target and header rewrite condition for HTTP_HOST [#164](https://github.com/tobychui/zoraxy/issues/164)
|
||||
+ Fixed ovh DNS challenge provider form generator bug [#161](https://github.com/tobychui/zoraxy/issues/161)
|
||||
+ Added permission policy module (not enabled)
|
||||
+ Added single-use cookiejar to uptime monitor request client to handle cookie issues on some poorly written back-end server [#149](https://github.com/tobychui/zoraxy/issues/149)
|
||||
|
||||
|
||||
# v3.0.4 May 18 2024
|
||||
|
||||
## This release tidied up the contribution by [Teifun2](https://github.com/Teifun2) and added a new way to generate DNS challenge based certificate (e.g. wildcards) from Let's Encrypt without changing any environment variables. This also fixes a few previous ACME module EAB settings bug related to concurrent save.
|
||||
|
||||
You can find the DNS challenge settings under TLS / SSL > ACME snippet > Generate New Certificate > (Check the "Use a DNS Challenge" checkbox)
|
||||
|
||||
+ Optimized DNS challenge implementation [thanks to Teifun2](https://github.com/Teifun2) / Issues [#49](https://github.com/tobychui/zoraxy/issues/49) [#79](https://github.com/tobychui/zoraxy/issues/79)
|
||||
+ Removed dependencies on environment variable write and keep all data contained
|
||||
+ Fixed panic on loading certificate generated by Zoraxy v2
|
||||
+ Added automatic form generator for DNS challenge / providers
|
||||
+ Added CA name default value
|
||||
+ Added code generator for acmedns module (storing the DNS challenge provider contents extracted from lego)
|
||||
+ Fixed ACME snippet "Obtain Certificate" concurrent issues in save EAB and DNS credentials
|
||||
|
||||
|
||||
# v3.0.3 Apr 30 2024
|
||||
## Breaking Change
|
||||
|
||||
|
15
README.md
15
README.md
@ -2,9 +2,8 @@
|
||||
|
||||
# Zoraxy
|
||||
|
||||
General purpose request (reverse) proxy and forwarding tool for networking noobs. Now written in Go!
|
||||
A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
|
||||
|
||||
*Zoraxy v3 HTTP proxy config is not compatible with the older v2. If you are looking for the legacy version of Zoraxy, take a look at the [v2 branch](https://github.com/tobychui/zoraxy/tree/v2)*
|
||||
|
||||
### Features
|
||||
|
||||
@ -19,6 +18,7 @@ General purpose request (reverse) proxy and forwarding tool for networking noobs
|
||||
- TLS / SSL setup and deploy
|
||||
- ACME features like auto-renew to serve your sites in http**s**
|
||||
- SNI support (and SAN certs)
|
||||
- DNS Challenge for Let's Encrypt and [these DNS providers](https://go-acme.github.io/lego/dns/)
|
||||
- Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)
|
||||
- Global Area Network Controller Web UI (ZeroTier not included)
|
||||
- TCP Tunneling / Proxy
|
||||
@ -41,6 +41,12 @@ General purpose request (reverse) proxy and forwarding tool for networking noobs
|
||||
|
||||
For other systems or architectures, please see [Release](https://github.com/tobychui/zoraxy/releases/latest/)
|
||||
|
||||
## Getting Started
|
||||
[Installing Zoraxy Reverse Proxy: Your Gateway to Efficient Web Routing](https://geekscircuit.com/installing-zoraxy-reverse-proxy-your-gateway-to-efficient-web-routing/)
|
||||
|
||||
Thank you for the well written and easy to follow tutorial by Reddit users [itsvmn](https://www.reddit.com/user/itsvmn/)!
|
||||
If you have no background in setting up reverse proxy or web routing, you should check this out before you start setting up your Zoraxy.
|
||||
|
||||
## Build from Source
|
||||
|
||||
Requires Go 1.22 or higher
|
||||
@ -60,7 +66,7 @@ Zoraxy provides basic authentication system for standalone mode. To use it in st
|
||||
|
||||
### Standalone Mode
|
||||
|
||||
Standalone mode is the default mode for Zoraxy. This allows a single account to manage your reverse proxy server, just like a home router. This mode is suitable for new owners to homelabs or makers starting growing their web services into multiple servers.
|
||||
Standalone mode is the default mode for Zoraxy. This allows a single account to manage your reverse proxy server just like a basic home router. This mode is suitable for new owners to homelabs or makers starting growing their web services into multiple servers. A full "Getting Started" guide can be found [here](https://github.com/tobychui/zoraxy/wiki/Getting-Started).
|
||||
|
||||
#### Linux
|
||||
|
||||
@ -90,6 +96,8 @@ See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder fo
|
||||
Usage of zoraxy:
|
||||
-autorenew int
|
||||
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
|
||||
-docker
|
||||
Run Zoraxy in docker compatibility mode
|
||||
-fastgeoip
|
||||
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
|
||||
-log
|
||||
@ -182,4 +190,3 @@ If you like the project and want to support us, please consider a donation. You
|
||||
|
||||
This project is open-sourced under AGPL. I open-sourced this project so everyone can check for security issues and benefit all users. **This software is intended to be free of charge. If you have acquired this software from a third-party seller, the authors of this repository bears no responsibility for any technical difficulties assistance or support.**
|
||||
|
||||
|
||||
|
@ -29,7 +29,7 @@ WORKDIR /opt/zoraxy/config/
|
||||
ENV VERSION=$VERSION
|
||||
ENV ARGS="-noauth=false"
|
||||
|
||||
ENTRYPOINT "zoraxy" "-port=:8000" "${ARGS}"
|
||||
ENTRYPOINT "zoraxy" "-port=:8000" "-docker=true" "${ARGS}"
|
||||
|
||||
HEALTHCHECK --interval=5s --timeout=5s --retries=2 CMD nc -vz 127.0.0.1 8000 || exit 1
|
||||
|
||||
|
@ -80,7 +80,7 @@
|
||||
<div class="bannerHeaderWrapper">
|
||||
<h1 class="bannerHeader">Zoraxy</h1>
|
||||
<div class="ui divider"></div><br>
|
||||
<p class="bannerSubheader">All in one homelab network routing solution</p>
|
||||
<p class="bannerSubheader">Beyond Reverse Proxy: Your Ultimate Homelab Network Tool</p>
|
||||
</div>
|
||||
<br><br>
|
||||
<a class="ui basic big button" style="background-color: white;" href="#features"><i class="ui blue arrow down icon"></i> Learn More</a>
|
||||
|
@ -15,4 +15,12 @@ The templates folder contains the template for overriding the build in error or
|
||||
|
||||
To use the template, copy and paste the `wwww` folder to the same directory as zoraxy executable (aka the src/ file if you `go build` with the current folder tree).
|
||||
|
||||
|
||||
|
||||
### Other Templates
|
||||
|
||||
There are a few pre-built templates that works with Zoraxy where you can find in the `other-templates` folder. Copy the folder into `www` and rename the folder to `templates` to active them.
|
||||
|
||||
|
||||
|
||||
It is worth mentioning that the uwu icons for not-found and access-denied are created by @SAWARATSUKI
|
||||
|
185
example/other-templates/templates_cf/blacklist.html
Normal file
185
example/other-templates/templates_cf/blacklist.html
Normal file
File diff suppressed because one or more lines are too long
154
example/other-templates/templates_cf/notfound.html
Normal file
154
example/other-templates/templates_cf/notfound.html
Normal file
@ -0,0 +1,154 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="theme-color" content="#4b75ff">
|
||||
<link rel="icon" type="image/png" href="img/small_icon.png"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
|
||||
<title>404 - Host Not Found</title>
|
||||
<style>
|
||||
h1, h2, h3, h4, h5, p, a, span, .ui.list .item{
|
||||
font-family: 'Noto Sans TC', sans-serif;
|
||||
font-weight: 300;
|
||||
color: rgb(88, 88, 88)
|
||||
}
|
||||
|
||||
.diagram{
|
||||
background-color: #ebebeb;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
.diagramHeader{
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
@media (max-width:512px) {
|
||||
.widescreenOnly{
|
||||
display: none !important;
|
||||
|
||||
}
|
||||
|
||||
.four.wide.column:not(.widescreenOnly){
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.ui.grid{
|
||||
justify-content: center !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<br><br>
|
||||
<div class="ui container">
|
||||
<h1 style="font-size: 4rem;">Error 404</h1>
|
||||
<p style="font-size: 2rem; margin-bottom: 0.4em;">Target Host Not Found</p>
|
||||
<small id="timestamp"></small>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="diagram">
|
||||
<div class="ui text container">
|
||||
<div class="ui grid">
|
||||
<div class="four wide column widescreenOnly" align="center">
|
||||
<svg version="1.1" id="client_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
|
||||
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
|
||||
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
|
||||
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
|
||||
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
|
||||
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
|
||||
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
|
||||
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
|
||||
82.989,143.204 "/>
|
||||
</svg>
|
||||
<small>You</small>
|
||||
<h2 class="diagramHeader">Browser</h2>
|
||||
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||
</div>
|
||||
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||
</div>
|
||||
<div class="four wide column widescreenOnly" align="center">
|
||||
<svg version="1.1" id="cloud_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
|
||||
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
|
||||
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
|
||||
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
|
||||
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
|
||||
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
|
||||
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
|
||||
83.971,148.123 "/>
|
||||
</svg>
|
||||
|
||||
<small>Gateway Node</small>
|
||||
<h2 class="diagramHeader">Reverse Proxy</h2>
|
||||
<p style="font-weight: 500; color: #9bca3e;">Working</p>
|
||||
</div>
|
||||
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
|
||||
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
|
||||
</div>
|
||||
<div class="four wide column" align="center">
|
||||
<svg version="1.1" id="host_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
|
||||
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
|
||||
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
|
||||
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
|
||||
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
|
||||
</svg>
|
||||
<small id="host"></small>
|
||||
<h2 class="diagramHeader">Host</h2>
|
||||
<p style="font-weight: 500; color: #bd2426;">Not Found</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui stackable grid">
|
||||
<div class="eight wide column">
|
||||
<h1>What happend?</h1>
|
||||
<p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<h1>What can I do?</h1>
|
||||
<h5 style="font-weight: 500;">If you are a visitor of this website: </h5>
|
||||
<p>Please try again in a few minutes</p>
|
||||
<h5 style="font-weight: 500;">If you are the owner of this website:</h5>
|
||||
<div class="ui bulleted list">
|
||||
<div class="item">Check if the proxy rules that match this hostname exists</div>
|
||||
<div class="item">Visit the Reverse Proxy management interface to correct any setting errors</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui container" style="color: grey; font-size: 90%">
|
||||
<p>Powered by Zoraxy</p>
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
<script>
|
||||
$("#timestamp").text(new Date());
|
||||
$("#host").text(location.href);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
185
example/other-templates/templates_cf/whitelist.html
Normal file
185
example/other-templates/templates_cf/whitelist.html
Normal file
File diff suppressed because one or more lines are too long
52
example/other-templates/templates_uwu/blacklist.html
Normal file
52
example/other-templates/templates_uwu/blacklist.html
Normal file
File diff suppressed because one or more lines are too long
42
example/other-templates/templates_uwu/notfound.html
Normal file
42
example/other-templates/templates_uwu/notfound.html
Normal file
File diff suppressed because one or more lines are too long
52
example/other-templates/templates_uwu/whitelist.html
Normal file
52
example/other-templates/templates_uwu/whitelist.html
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
Binary file not shown.
BIN
img/title.png
BIN
img/title.png
Binary file not shown.
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 69 KiB |
BIN
img/title.psd
BIN
img/title.psd
Binary file not shown.
23
src/api.go
23
src/api.go
@ -70,6 +70,8 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList)
|
||||
authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd)
|
||||
authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove)
|
||||
authRouter.HandleFunc("/api/proxy/header/handleHSTS", HandleHSTSState)
|
||||
authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy)
|
||||
//Reverse proxy auth related APIs
|
||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
|
||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
|
||||
@ -141,14 +143,13 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
||||
|
||||
//TCP Proxy
|
||||
authRouter.HandleFunc("/api/tcpprox/config/add", tcpProxyManager.HandleAddProxyConfig)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/edit", tcpProxyManager.HandleEditProxyConfigs)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/list", tcpProxyManager.HandleListConfigs)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/start", tcpProxyManager.HandleStartProxy)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/stop", tcpProxyManager.HandleStopProxy)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/delete", tcpProxyManager.HandleRemoveProxy)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/status", tcpProxyManager.HandleGetProxyStatus)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/validate", tcpProxyManager.HandleConfigValidate)
|
||||
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
||||
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
|
||||
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
|
||||
authRouter.HandleFunc("/api/streamprox/config/start", streamProxyManager.HandleStartProxy)
|
||||
authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy)
|
||||
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
|
||||
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus)
|
||||
|
||||
//mDNS APIs
|
||||
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
|
||||
@ -213,6 +214,10 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete)
|
||||
}
|
||||
|
||||
//Docker UX Optimizations
|
||||
authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable)
|
||||
authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList)
|
||||
|
||||
//Others
|
||||
http.HandleFunc("/api/info/x", HandleZoraxyInfo)
|
||||
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
|
||||
@ -223,6 +228,8 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
|
||||
|
||||
//If you got APIs to add, append them here
|
||||
// get available docker containers
|
||||
|
||||
}
|
||||
|
||||
// Function to renders Auth related APIs
|
||||
|
@ -272,17 +272,14 @@ func HandleNewPasswordSetup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Delete the user account
|
||||
authAgent.UnregisterUser(username)
|
||||
|
||||
//Ok. Set the new password
|
||||
err = authAgent.CreateUserAccount(username, newPassword, "")
|
||||
if err != nil {
|
||||
// Un register the user account
|
||||
if err := authAgent.UnregisterUser(username); err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
//Ok. Set the new password
|
||||
if err := authAgent.CreateUserAccount(username, newPassword, ""); err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
65
src/go.mod
65
src/go.mod
@ -6,6 +6,7 @@ toolchain go1.22.2
|
||||
|
||||
require (
|
||||
github.com/boltdb/bolt v1.3.1
|
||||
github.com/docker/docker v27.0.0+incompatible
|
||||
github.com/go-acme/lego/v4 v4.16.1
|
||||
github.com/go-ping/ping v1.1.0
|
||||
github.com/google/uuid v1.6.0
|
||||
@ -14,13 +15,13 @@ require (
|
||||
github.com/grandcat/zeroconf v1.0.0
|
||||
github.com/likexian/whois v1.15.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
golang.org/x/net v0.23.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/text v0.15.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.20.1 // indirect
|
||||
cloud.google.com/go/compute v1.25.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||
@ -39,8 +40,8 @@ require (
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
|
||||
@ -64,30 +65,36 @@ require (
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/civo/civogo v0.3.11 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.86.0 // indirect
|
||||
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/dnsimple/dnsimple-go v1.2.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/exoscale/egoscale v0.102.3 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-resty/resty/v2 v2.11.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/s2a-go v0.1.4 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.2 // indirect
|
||||
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
@ -111,8 +118,11 @@ require (
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect
|
||||
@ -125,9 +135,9 @@ require (
|
||||
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||
github.com/nrdcg/porkbun v0.3.0 // indirect
|
||||
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/ovh/go-ovh v1.4.3 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@ -142,8 +152,8 @@ require (
|
||||
github.com/softlayer/softlayer-go v1.1.3 // indirect
|
||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/stretchr/objx v0.5.1 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||
github.com/transip/gotransip/v6 v6.23.0 // indirect
|
||||
@ -153,22 +163,29 @@ require (
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
|
||||
go.opentelemetry.io/otel v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
||||
go.uber.org/ratelimit v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/oauth2 v0.16.0 // indirect
|
||||
golang.org/x/oauth2 v0.18.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/api v0.126.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
google.golang.org/api v0.169.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.5.1 // indirect
|
||||
)
|
||||
|
171
src/go.sum
171
src/go.sum
@ -6,8 +6,8 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
|
||||
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
|
||||
cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
@ -33,6 +33,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
|
||||
@ -60,11 +62,11 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkM
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass=
|
||||
@ -135,18 +137,15 @@ github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpX
|
||||
github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
|
||||
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||
@ -161,10 +160,18 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/dnsimple/dnsimple-go v1.2.0 h1:ddTGyLVKly5HKb5L65AkLqFqwZlWo3WnR0BlFZlIddM=
|
||||
github.com/dnsimple/dnsimple-go v1.2.0/go.mod h1:z/cs26v/eiRvUyXsHQBLd8lWF8+cD6GbmkPH84plM4U=
|
||||
github.com/docker/docker v27.0.0+incompatible h1:JRugTYuelmWlW0M3jakcIadDx2HUoUO6+Tf2C5jVfwA=
|
||||
github.com/docker/docker v27.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@ -179,6 +186,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
@ -201,6 +210,11 @@ github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiK
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
|
||||
@ -226,7 +240,10 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
@ -256,8 +273,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@ -284,25 +301,21 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
||||
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
|
||||
github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
|
||||
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
|
||||
github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc=
|
||||
github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k=
|
||||
github.com/gophercloud/gophercloud v1.0.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c=
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15Kfovd8MTZrcana+UlQqNbOif8dLpA0=
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
@ -320,9 +333,10 @@ github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfc
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
@ -345,7 +359,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
@ -382,6 +395,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
|
||||
@ -471,6 +485,10 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@ -478,10 +496,11 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo=
|
||||
github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk=
|
||||
@ -521,13 +540,13 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU=
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0=
|
||||
github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
@ -582,6 +601,7 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22/go.mod h1:fCa7OJZ/9DRTnOKmxvT
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
@ -615,8 +635,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
|
||||
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@ -626,9 +646,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
@ -649,14 +668,12 @@ github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4
|
||||
github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
|
||||
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
|
||||
github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f h1:cG+ehPRJSlqljSufLf1KXeXpUd1dLNjnzA18mZcB/O0=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 h1:2wzke3JH7OtN20WsNDZx2VH/TCmsbqtDEbXzjF+i05E=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997/go.mod h1:2CHKs/YGbCcNn/BPaCkEBwKz/FNCELi+MLILjR9RaTA=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@ -665,7 +682,23 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
|
||||
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
|
||||
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY=
|
||||
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
|
||||
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
|
||||
go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI=
|
||||
go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
|
||||
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
|
||||
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
@ -683,18 +716,16 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -714,6 +745,7 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@ -739,6 +771,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
@ -755,14 +788,14 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -828,16 +861,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@ -850,8 +883,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -879,8 +912,10 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
@ -896,14 +931,14 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o=
|
||||
google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw=
|
||||
google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY=
|
||||
google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@ -916,12 +951,12 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao=
|
||||
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@ -933,9 +968,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -948,8 +982,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@ -959,10 +993,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
@ -985,6 +1016,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
51
src/main.go
51
src/main.go
@ -16,6 +16,8 @@ import (
|
||||
"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"
|
||||
@ -28,7 +30,7 @@ import (
|
||||
"imuslab.com/zoraxy/mod/sshprox"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||
"imuslab.com/zoraxy/mod/tcpprox"
|
||||
"imuslab.com/zoraxy/mod/streamproxy"
|
||||
"imuslab.com/zoraxy/mod/tlscert"
|
||||
"imuslab.com/zoraxy/mod/uptime"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
@ -44,6 +46,7 @@ var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transpo
|
||||
var mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
|
||||
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
||||
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||
var runningInDocker = flag.Bool("docker", false, "Run Zoraxy in docker compatibility mode")
|
||||
var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
||||
var 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")
|
||||
@ -52,7 +55,7 @@ var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
|
||||
|
||||
var (
|
||||
name = "Zoraxy"
|
||||
version = "3.0.4"
|
||||
version = "3.0.7"
|
||||
nodeUUID = "generic"
|
||||
development = false //Set this to false to use embedded web fs
|
||||
bootTime = time.Now().Unix()
|
||||
@ -66,29 +69,31 @@ var (
|
||||
/*
|
||||
Handler Modules
|
||||
*/
|
||||
sysdb *database.Database //System database
|
||||
authAgent *auth.AuthAgent //Authentication agent
|
||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||
accessController *access.Controller //Access controller, handle black list and white list
|
||||
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
||||
mdnsScanner *mdns.MDNSHost //mDNS discovery services
|
||||
ganManager *ganserv.NetworkManager //Global Area Network Manager
|
||||
webSshManager *sshprox.Manager //Web SSH connection service
|
||||
tcpProxyManager *tcpprox.Manager //TCP Proxy Manager
|
||||
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
|
||||
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
||||
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
||||
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
|
||||
sysdb *database.Database //System database
|
||||
authAgent *auth.AuthAgent //Authentication agent
|
||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||
loadbalancer *loadbalance.RouteManager //Load balancer manager to get routing targets from proxy rules
|
||||
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
|
||||
|
||||
//Helper modules
|
||||
EmailSender *email.Sender //Email sender that handle email sending
|
||||
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
||||
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
||||
EmailSender *email.Sender //Email sender that handle email sending
|
||||
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
||||
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
|
||||
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
||||
)
|
||||
|
||||
// Kill signal handler. Do something before the system the core terminate.
|
||||
|
@ -1242,15 +1242,32 @@
|
||||
"Name": "gandiv5",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "fieldName",
|
||||
"Title": "BaseURL",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "authZone",
|
||||
"Title": "APIKey",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "PersonalAccessToken",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": []
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gcore": {
|
||||
"Name": "gcore",
|
||||
@ -2063,35 +2080,40 @@
|
||||
"Name": "namecheap",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "domain",
|
||||
"Title": "Debug",
|
||||
"Datatype": "bool"
|
||||
},
|
||||
{
|
||||
"Title": "BaseURL",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "key",
|
||||
"Title": "APIUser",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "keyFqdn",
|
||||
"Title": "APIKey",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "keyValue",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "tld",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "sld",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "host",
|
||||
"Title": "ClientIP",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": []
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
"namedotcom": {
|
||||
"Name": "namedotcom",
|
||||
@ -2418,26 +2440,38 @@
|
||||
"Name": "ovh",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "FieldType",
|
||||
"Title": "APIEndpoint",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "SubDomain",
|
||||
"Title": "ApplicationKey",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "Target",
|
||||
"Title": "ApplicationSecret",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "Zone",
|
||||
"Title": "ConsumerKey",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "ID",
|
||||
"Datatype": "int64"
|
||||
"Title": "OAuth2Config",
|
||||
"Datatype": "*OAuth2Config"
|
||||
},
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -2875,15 +2909,28 @@
|
||||
"Name": "shellrent",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "domainID",
|
||||
"Datatype": "int"
|
||||
"Title": "Username",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "recordID",
|
||||
"Datatype": "int"
|
||||
"Title": "Token",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": []
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
"simply": {
|
||||
"Name": "simply",
|
||||
@ -3034,15 +3081,28 @@
|
||||
},
|
||||
"ultradns": {
|
||||
"Name": "ultradns",
|
||||
"ConfigableFields": [],
|
||||
"HiddenFields": [
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "config",
|
||||
"Datatype": "*Config"
|
||||
"Title": "Username",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "client",
|
||||
"Datatype": "*client.Client"
|
||||
"Title": "Password",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "Endpoint",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
60
src/mod/dockerux/docker.go
Normal file
60
src/mod/dockerux/docker.go
Normal file
@ -0,0 +1,60 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package dockerux
|
||||
|
||||
/* Windows docker optimizer*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
// Windows build not support docker
|
||||
func (d *UXOptimizer) HandleDockerAvailable(w http.ResponseWriter, r *http.Request) {
|
||||
js, _ := json.Marshal(d.RunninInDocker)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (d *UXOptimizer) HandleDockerContainersList(w http.ResponseWriter, r *http.Request) {
|
||||
apiClient, err := client.NewClientWithOpts(client.WithVersion("1.43"))
|
||||
if err != nil {
|
||||
d.SystemWideLogger.PrintAndLog("Docker", "Unable to create new docker client", err)
|
||||
utils.SendErrorResponse(w, "Docker client initiation failed")
|
||||
return
|
||||
}
|
||||
defer apiClient.Close()
|
||||
|
||||
containers, err := apiClient.ContainerList(context.Background(), container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
d.SystemWideLogger.PrintAndLog("Docker", "List docker container failed", err)
|
||||
utils.SendErrorResponse(w, "List docker container failed")
|
||||
return
|
||||
}
|
||||
|
||||
networks, err := apiClient.NetworkList(context.Background(), types.NetworkListOptions{})
|
||||
if err != nil {
|
||||
d.SystemWideLogger.PrintAndLog("Docker", "List docker network failed", err)
|
||||
utils.SendErrorResponse(w, "List docker network failed")
|
||||
return
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"network": networks,
|
||||
"containers": containers,
|
||||
}
|
||||
|
||||
js, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
32
src/mod/dockerux/docker_windows.go
Normal file
32
src/mod/dockerux/docker_windows.go
Normal file
@ -0,0 +1,32 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package dockerux
|
||||
|
||||
/*
|
||||
|
||||
Windows docker UX optimizer dummy
|
||||
|
||||
This is a dummy module for Windows as docker features
|
||||
is useless on Windows and create a larger binary size
|
||||
|
||||
docker on Windows build are trimmed to reduce binary size
|
||||
and make it compatibile with Windows 7
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
// Windows build not support docker
|
||||
func (d *UXOptimizer) HandleDockerAvailable(w http.ResponseWriter, r *http.Request) {
|
||||
js, _ := json.Marshal(d.RunninInDocker)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (d *UXOptimizer) HandleDockerContainersList(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendErrorResponse(w, "Platform not supported")
|
||||
}
|
24
src/mod/dockerux/dockerux.go
Normal file
24
src/mod/dockerux/dockerux.go
Normal file
@ -0,0 +1,24 @@
|
||||
package dockerux
|
||||
|
||||
import "imuslab.com/zoraxy/mod/info/logger"
|
||||
|
||||
/*
|
||||
Docker Optimizer
|
||||
|
||||
This script add support for optimizing docker user experience
|
||||
Note that this module are community contribute only. For bug
|
||||
report, please directly tag the Pull Request author.
|
||||
*/
|
||||
|
||||
type UXOptimizer struct {
|
||||
RunninInDocker bool
|
||||
SystemWideLogger *logger.Logger
|
||||
}
|
||||
|
||||
//Create a new docker optimizer
|
||||
func NewDockerOptimizer(IsRunningInDocker bool, logger *logger.Logger) *UXOptimizer {
|
||||
return &UXOptimizer{
|
||||
RunninInDocker: IsRunningInDocker,
|
||||
SystemWideLogger: logger,
|
||||
}
|
||||
}
|
@ -14,11 +14,16 @@ import (
|
||||
Main server for dynamic proxy core
|
||||
|
||||
Routing Handler Priority (High to Low)
|
||||
- Blacklist
|
||||
- Whitelist
|
||||
- Special Routing Rule (e.g. acme)
|
||||
- Redirectable
|
||||
- Subdomain Routing
|
||||
- Vitrual Directory Routing
|
||||
- Access Router
|
||||
- Blacklist
|
||||
- Whitelist
|
||||
- Basic Auth
|
||||
- Vitrual Directory Proxy
|
||||
- Subdomain Proxy
|
||||
- Root router (default site router)
|
||||
*/
|
||||
|
||||
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
@ -34,9 +39,6 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Inject headers
|
||||
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
|
||||
|
||||
/*
|
||||
Redirection Routing
|
||||
*/
|
||||
@ -70,6 +72,14 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Rate Limit
|
||||
if sep.RequireRateLimit {
|
||||
err := h.handleRateLimitRouting(w, r, sep)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//Validate basic auth
|
||||
if sep.RequireBasicAuth {
|
||||
err := h.handleBasicAuthRouting(w, r, sep)
|
||||
|
74
src/mod/dynamicproxy/customHeader.go
Normal file
74
src/mod/dynamicproxy/customHeader.go
Normal file
@ -0,0 +1,74 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
)
|
||||
|
||||
/*
|
||||
CustomHeader.go
|
||||
|
||||
This script handle parsing and injecting custom headers
|
||||
into the dpcore routing logic
|
||||
*/
|
||||
|
||||
// SplitInboundOutboundHeaders split user defined headers into upstream and downstream headers
|
||||
// return upstream header and downstream header key-value pairs
|
||||
// if the header is expected to be deleted, the value will be set to empty string
|
||||
func (ept *ProxyEndpoint) SplitInboundOutboundHeaders() ([][]string, [][]string) {
|
||||
if len(ept.UserDefinedHeaders) == 0 && ept.HSTSMaxAge == 0 && !ept.EnablePermissionPolicyHeader {
|
||||
//Early return if there are no defined headers
|
||||
return [][]string{}, [][]string{}
|
||||
}
|
||||
|
||||
//Use pre-allocation for faster performance
|
||||
//Downstream +2 for Permission Policy and HSTS
|
||||
upstreamHeaders := make([][]string, len(ept.UserDefinedHeaders))
|
||||
downstreamHeaders := make([][]string, len(ept.UserDefinedHeaders)+2)
|
||||
upstreamHeaderCounter := 0
|
||||
downstreamHeaderCounter := 0
|
||||
|
||||
//Sort the headers into upstream or downstream
|
||||
for _, customHeader := range ept.UserDefinedHeaders {
|
||||
thisHeaderSet := make([]string, 2)
|
||||
thisHeaderSet[0] = customHeader.Key
|
||||
thisHeaderSet[1] = customHeader.Value
|
||||
if customHeader.IsRemove {
|
||||
//Prevent invalid config
|
||||
thisHeaderSet[1] = ""
|
||||
}
|
||||
|
||||
//Assign to slice
|
||||
if customHeader.Direction == HeaderDirection_ZoraxyToUpstream {
|
||||
upstreamHeaders[upstreamHeaderCounter] = thisHeaderSet
|
||||
upstreamHeaderCounter++
|
||||
} else if customHeader.Direction == HeaderDirection_ZoraxyToDownstream {
|
||||
downstreamHeaders[downstreamHeaderCounter] = thisHeaderSet
|
||||
downstreamHeaderCounter++
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the endpoint require HSTS headers
|
||||
if ept.HSTSMaxAge > 0 {
|
||||
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
|
||||
}
|
@ -10,6 +10,8 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
)
|
||||
|
||||
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
||||
@ -55,11 +57,14 @@ type ReverseProxy struct {
|
||||
}
|
||||
|
||||
type ResponseRewriteRuleSet struct {
|
||||
ProxyDomain string
|
||||
OriginalHost string
|
||||
UseTLS bool
|
||||
NoCache bool
|
||||
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
||||
ProxyDomain string
|
||||
OriginalHost string
|
||||
UseTLS bool
|
||||
NoCache bool
|
||||
PathPrefix string //Vdir prefix for root, / will be rewrite to this
|
||||
UpstreamHeaders [][]string
|
||||
DownstreamHeaders [][]string
|
||||
Version string //Version number of Zoraxy, use for X-Proxy-By
|
||||
}
|
||||
|
||||
type requestCanceler interface {
|
||||
@ -246,78 +251,6 @@ func (p *ReverseProxy) logf(format string, args ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func removeHeaders(header http.Header, noCache bool) {
|
||||
// Remove hop-by-hop headers listed in the "Connection" header.
|
||||
if c := header.Get("Connection"); c != "" {
|
||||
for _, f := range strings.Split(c, ",") {
|
||||
if f = strings.TrimSpace(f); f != "" {
|
||||
header.Del(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove hop-by-hop headers
|
||||
for _, h := range hopHeaders {
|
||||
if header.Get(h) != "" {
|
||||
header.Del(h)
|
||||
}
|
||||
}
|
||||
|
||||
//Restore the Upgrade header if any
|
||||
if header.Get("Zr-Origin-Upgrade") != "" {
|
||||
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
|
||||
header.Del("Zr-Origin-Upgrade")
|
||||
}
|
||||
|
||||
//Disable cache if nocache is set
|
||||
if noCache {
|
||||
header.Del("Cache-Control")
|
||||
header.Set("Cache-Control", "no-store")
|
||||
}
|
||||
|
||||
//Hide Go-HTTP-Client UA if the client didnt sent us one
|
||||
if _, ok := header["User-Agent"]; !ok {
|
||||
// If the outbound request doesn't have a User-Agent header set,
|
||||
// don't send the default Go HTTP client User-Agent.
|
||||
header.Set("User-Agent", "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func addXForwardedForHeader(req *http.Request) {
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
// If we aren't the first proxy retain prior
|
||||
// X-Forwarded-For information as a comma+space
|
||||
// separated list and fold multiple headers into one.
|
||||
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
req.Header.Set("X-Forwarded-For", clientIP)
|
||||
if req.TLS != nil {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
} else {
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
}
|
||||
|
||||
if req.Header.Get("X-Real-Ip") == "" {
|
||||
//Check if CF-Connecting-IP header exists
|
||||
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
|
||||
if CF_Connecting_IP != "" {
|
||||
//Use CF Connecting IP
|
||||
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
|
||||
} else {
|
||||
// Not exists. Fill it in with first entry in X-Forwarded-For
|
||||
ips := strings.Split(clientIP, ",")
|
||||
if len(ips) > 0 {
|
||||
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error {
|
||||
transport := p.Transport
|
||||
|
||||
@ -346,9 +279,9 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
p.Director(outreq)
|
||||
outreq.Close = false
|
||||
|
||||
if !rrr.UseTLS {
|
||||
//This seems to be routing to external sites
|
||||
//Do not keep the original host
|
||||
//Only skip origin rewrite iff proxy target require TLS and it is external domain name like github.com
|
||||
if !(rrr.UseTLS && isExternalDomainName(rrr.ProxyDomain)) {
|
||||
// Always use the original host, see issue #164
|
||||
outreq.Host = rrr.OriginalHost
|
||||
}
|
||||
|
||||
@ -356,12 +289,18 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
outreq.Header = make(http.Header)
|
||||
copyHeader(outreq.Header, req.Header)
|
||||
|
||||
// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
|
||||
// Remove hop-by-hop headers.
|
||||
removeHeaders(outreq.Header, rrr.NoCache)
|
||||
|
||||
// Add X-Forwarded-For Header.
|
||||
addXForwardedForHeader(outreq)
|
||||
|
||||
// Add user defined headers (to upstream)
|
||||
injectUserDefinedHeaders(outreq.Header, rrr.UpstreamHeaders)
|
||||
|
||||
// Rewrite outbound UA, must be after user headers
|
||||
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)
|
||||
|
||||
res, err := transport.RoundTrip(outreq)
|
||||
if err != nil {
|
||||
if p.Verbal {
|
||||
@ -392,13 +331,17 @@ 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)
|
||||
//}
|
||||
|
||||
//Custom header rewriter functions
|
||||
//Add debug X-Proxy-By tracker
|
||||
res.Header.Set("x-proxy-by", "zoraxy/"+rrr.Version)
|
||||
|
||||
//Custom Location header rewriter functions
|
||||
if res.Header.Get("Location") != "" {
|
||||
locationRewrite := res.Header.Get("Location")
|
||||
originLocation := res.Header.Get("Location")
|
||||
@ -424,9 +367,16 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
res.Header.Set("Location", locationRewrite)
|
||||
}
|
||||
|
||||
// Add user defined headers (to downstream)
|
||||
injectUserDefinedHeaders(res.Header, rrr.DownstreamHeaders)
|
||||
|
||||
// Copy header from response to client.
|
||||
copyHeader(rw.Header(), res.Header)
|
||||
|
||||
// inject permission policy headers
|
||||
//TODO: Load permission policy from rrr
|
||||
permissionpolicy.InjectPermissionPolicyHeader(rw, nil)
|
||||
|
||||
// The "Trailer" header isn't included in the Transport's response, Build it up from Trailer.
|
||||
if len(res.Trailer) > 0 {
|
||||
trailerKeys := make([]string, 0, len(res.Trailer))
|
||||
|
120
src/mod/dynamicproxy/dpcore/header.go
Normal file
120
src/mod/dynamicproxy/dpcore/header.go
Normal file
@ -0,0 +1,120 @@
|
||||
package dpcore
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Header.go
|
||||
|
||||
This script handles headers rewrite and remove
|
||||
in dpcore.
|
||||
|
||||
Added in Zoraxy v3.0.6 by tobychui
|
||||
*/
|
||||
|
||||
// removeHeaders Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
|
||||
func removeHeaders(header http.Header, noCache bool) {
|
||||
// Remove hop-by-hop headers listed in the "Connection" header.
|
||||
if c := header.Get("Connection"); c != "" {
|
||||
for _, f := range strings.Split(c, ",") {
|
||||
if f = strings.TrimSpace(f); f != "" {
|
||||
header.Del(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove hop-by-hop headers
|
||||
for _, h := range hopHeaders {
|
||||
if header.Get(h) != "" {
|
||||
header.Del(h)
|
||||
}
|
||||
}
|
||||
|
||||
//Restore the Upgrade header if any
|
||||
if header.Get("Zr-Origin-Upgrade") != "" {
|
||||
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
|
||||
header.Del("Zr-Origin-Upgrade")
|
||||
}
|
||||
|
||||
//Disable cache if nocache is set
|
||||
if noCache {
|
||||
header.Del("Cache-Control")
|
||||
header.Set("Cache-Control", "no-store")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// rewriteUserAgent rewrite the user agent based on incoming request
|
||||
func rewriteUserAgent(header http.Header, UA string) {
|
||||
//Hide Go-HTTP-Client UA if the client didnt sent us one
|
||||
if header.Get("User-Agent") == "" {
|
||||
// If the outbound request doesn't have a User-Agent header set,
|
||||
// don't send the default Go HTTP client User-Agent
|
||||
header.Del("User-Agent")
|
||||
header.Set("User-Agent", UA)
|
||||
}
|
||||
}
|
||||
|
||||
// Add X-Forwarded-For Header and rewrite X-Real-Ip according to sniffing logics
|
||||
func addXForwardedForHeader(req *http.Request) {
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
// If we aren't the first proxy retain prior
|
||||
// X-Forwarded-For information as a comma+space
|
||||
// separated list and fold multiple headers into one.
|
||||
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
req.Header.Set("X-Forwarded-For", clientIP)
|
||||
if req.TLS != nil {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
} else {
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
}
|
||||
|
||||
if req.Header.Get("X-Real-Ip") == "" {
|
||||
//Check if CF-Connecting-IP header exists
|
||||
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
|
||||
Fastly_Client_IP := req.Header.Get("Fastly-Client-IP")
|
||||
if CF_Connecting_IP != "" {
|
||||
//Use CF Connecting IP
|
||||
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
|
||||
} else if Fastly_Client_IP != "" {
|
||||
//Use Fastly Client IP
|
||||
req.Header.Set("X-Real-Ip", Fastly_Client_IP)
|
||||
} else {
|
||||
// Not exists. Fill it in with first entry in X-Forwarded-For
|
||||
ips := strings.Split(clientIP, ",")
|
||||
if len(ips) > 0 {
|
||||
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// injectUserDefinedHeaders inject the user headers from slice
|
||||
// if a value is empty string, the key will be removed from header.
|
||||
// if a key is empty string, the function will return immediately
|
||||
func injectUserDefinedHeaders(header http.Header, userHeaders [][]string) {
|
||||
for _, userHeader := range userHeaders {
|
||||
if len(userHeader) == 0 {
|
||||
//End of header slice
|
||||
return
|
||||
}
|
||||
headerKey := userHeader[0]
|
||||
headerValue := userHeader[1]
|
||||
if headerValue == "" {
|
||||
//Remove header from head
|
||||
header.Del(headerKey)
|
||||
continue
|
||||
}
|
||||
|
||||
//Default: Set header value
|
||||
header.Del(headerKey) //Remove header if it already exists
|
||||
header.Set(headerKey, headerValue)
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package dpcore
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
@ -60,3 +61,34 @@ func replaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS b
|
||||
func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
|
||||
return replaceLocationHost(urlString, rrr, useTLS)
|
||||
}
|
||||
|
||||
// isExternalDomainName check and return if the hostname is external domain name (e.g. github.com)
|
||||
// instead of internal (like 192.168.1.202:8443 (ip address) or domains end with .local or .internal)
|
||||
func isExternalDomainName(hostname string) bool {
|
||||
host, _, err := net.SplitHostPort(hostname)
|
||||
if err != nil {
|
||||
//hostname doesnt contain port
|
||||
ip := net.ParseIP(hostname)
|
||||
if ip != nil {
|
||||
//IP address, not a domain name
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
//Hostname contain port, use hostname without port to check if it is ip
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
//IP address, not a domain name
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//Check if it is internal DNS assigned domains
|
||||
internalDNSTLD := []string{".local", ".internal", ".localhost", ".home.arpa"}
|
||||
for _, tld := range internalDNSTLD {
|
||||
if strings.HasSuffix(strings.ToLower(hostname), tld) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -23,12 +23,12 @@ import (
|
||||
func NewDynamicProxy(option RouterOption) (*Router, error) {
|
||||
proxyMap := sync.Map{}
|
||||
thisRouter := Router{
|
||||
Option: &option,
|
||||
ProxyEndpoints: &proxyMap,
|
||||
Running: false,
|
||||
server: nil,
|
||||
routingRules: []*RoutingRule{},
|
||||
tldMap: map[string]int{},
|
||||
Option: &option,
|
||||
ProxyEndpoints: &proxyMap,
|
||||
Running: false,
|
||||
server: nil,
|
||||
routingRules: []*RoutingRule{},
|
||||
rateLimitCounter: RequestCountPerIpTable{},
|
||||
}
|
||||
|
||||
thisRouter.mux = &ProxyHandler{
|
||||
@ -85,6 +85,12 @@ func (router *Router) StartProxyService() error {
|
||||
MinVersion: uint16(minVersion),
|
||||
}
|
||||
|
||||
//Start rate limitor
|
||||
err := router.startRateLimterCounterResetTicker()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if router.Option.UseTls {
|
||||
router.server = &http.Server{
|
||||
Addr: ":" + strconv.Itoa(router.Option.Port),
|
||||
@ -129,6 +135,13 @@ func (router *Router) StartProxyService() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Rate Limit
|
||||
if sep.RequireRateLimit {
|
||||
if err := router.handleRateLimit(w, r, sep); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//Validate basic auth
|
||||
if sep.RequireBasicAuth {
|
||||
err := handleBasicAuth(w, r, sep)
|
||||
@ -142,6 +155,7 @@ func (router *Router) StartProxyService() error {
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: sep.RequireTLS,
|
||||
PathPrefix: "",
|
||||
Version: sep.parent.Option.HostVersion,
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -231,10 +245,23 @@ func (router *Router) StopProxyService() error {
|
||||
return err
|
||||
}
|
||||
|
||||
//Stop TLS listener
|
||||
if router.tlsListener != nil {
|
||||
router.tlsListener.Close()
|
||||
}
|
||||
|
||||
//Stop rate limiter
|
||||
if router.rateLimterStop != nil {
|
||||
go func() {
|
||||
// As the rate timer loop has a 1 sec ticker
|
||||
// stop the rate limiter in go routine can prevent
|
||||
// front end from freezing for 1 sec
|
||||
router.rateLimterStop <- true
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
//Stop TLS redirection (from port 80)
|
||||
if router.tlsRedirectStop != nil {
|
||||
router.tlsRedirectStop <- true
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@ -49,16 +48,13 @@ func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error {
|
||||
}
|
||||
|
||||
// Add a user defined header to the list, duplicates will be automatically removed
|
||||
func (ep *ProxyEndpoint) AddUserDefinedHeader(key string, value string) error {
|
||||
if ep.UserDefinedHeaderExists(key) {
|
||||
ep.RemoveUserDefinedHeader(key)
|
||||
func (ep *ProxyEndpoint) AddUserDefinedHeader(newHeaderRule *UserDefinedHeader) error {
|
||||
if ep.UserDefinedHeaderExists(newHeaderRule.Key) {
|
||||
ep.RemoveUserDefinedHeader(newHeaderRule.Key)
|
||||
}
|
||||
|
||||
ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, &UserDefinedHeader{
|
||||
Key: cases.Title(language.Und, cases.NoLower).String(key), //e.g. x-proxy-by -> X-Proxy-By
|
||||
Value: value,
|
||||
})
|
||||
|
||||
newHeaderRule.Key = cases.Title(language.Und, cases.NoLower).String(newHeaderRule.Key)
|
||||
ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, newHeaderRule)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
60
src/mod/dynamicproxy/loadbalance/loadbalance.go
Normal file
60
src/mod/dynamicproxy/loadbalance/loadbalance.go
Normal file
@ -0,0 +1,60 @@
|
||||
package loadbalance
|
||||
|
||||
import (
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/uptime"
|
||||
)
|
||||
|
||||
/*
|
||||
Load Balancer
|
||||
|
||||
Handleing load balance request for upstream destinations
|
||||
*/
|
||||
|
||||
type BalancePolicy int
|
||||
|
||||
const (
|
||||
BalancePolicy_RoundRobin BalancePolicy = 0 //Round robin, will ignore upstream if down
|
||||
BalancePolicy_Fallback BalancePolicy = 1 //Fallback only. Will only switch to next node if the first one failed
|
||||
BalancePolicy_Random BalancePolicy = 2 //Random, randomly pick one from the list that is online
|
||||
BalancePolicy_GeoRegion BalancePolicy = 3 //Use the one defined for this geo-location, when down, pick the next avaible node
|
||||
)
|
||||
|
||||
type LoadBalanceRule struct {
|
||||
Upstreams []string //Reverse proxy upstream servers
|
||||
LoadBalancePolicy BalancePolicy //Policy in deciding which target IP to proxy
|
||||
UseRegionLock bool //If this is enabled with BalancePolicy_Geo, when the main site failed, it will not pick another node
|
||||
UseStickySession bool //Use sticky session, if you are serving EU countries, make sure to add the "Do you want cookie" warning
|
||||
|
||||
parent *RouteManager
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Geodb *geodb.Store //GeoIP resolver for checking incoming request origin country
|
||||
UptimeMonitor *uptime.Monitor //For checking if the target is online, this might be nil when the module starts
|
||||
}
|
||||
|
||||
type RouteManager struct {
|
||||
Options Options
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
// Create a new load balance route manager
|
||||
func NewRouteManager(options *Options, logger *logger.Logger) *RouteManager {
|
||||
newManager := RouteManager{
|
||||
Options: *options,
|
||||
Logger: logger,
|
||||
}
|
||||
logger.PrintAndLog("INFO", "Load Balance Route Manager started", nil)
|
||||
return &newManager
|
||||
}
|
||||
|
||||
func (b *LoadBalanceRule) GetProxyTargetIP() {
|
||||
//TODO: Implement get proxy target IP logic here
|
||||
}
|
||||
|
||||
// Print debug message
|
||||
func (m *RouteManager) debugPrint(message string, err error) {
|
||||
m.Logger.PrintAndLog("LB", message, err)
|
||||
}
|
197
src/mod/dynamicproxy/permissionpolicy/permissionpolicy.go
Normal file
197
src/mod/dynamicproxy/permissionpolicy/permissionpolicy.go
Normal file
@ -0,0 +1,197 @@
|
||||
package permissionpolicy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Permisson Policy
|
||||
|
||||
This is a permission policy header modifier that changes
|
||||
the request permission related policy fields
|
||||
|
||||
author: tobychui
|
||||
*/
|
||||
|
||||
type PermissionsPolicy struct {
|
||||
Accelerometer []string `json:"accelerometer"`
|
||||
AmbientLightSensor []string `json:"ambient_light_sensor"`
|
||||
Autoplay []string `json:"autoplay"`
|
||||
Battery []string `json:"battery"`
|
||||
Camera []string `json:"camera"`
|
||||
CrossOriginIsolated []string `json:"cross_origin_isolated"`
|
||||
DisplayCapture []string `json:"display_capture"`
|
||||
DocumentDomain []string `json:"document_domain"`
|
||||
EncryptedMedia []string `json:"encrypted_media"`
|
||||
ExecutionWhileNotRendered []string `json:"execution_while_not_rendered"`
|
||||
ExecutionWhileOutOfView []string `json:"execution_while_out_of_viewport"`
|
||||
Fullscreen []string `json:"fullscreen"`
|
||||
Geolocation []string `json:"geolocation"`
|
||||
Gyroscope []string `json:"gyroscope"`
|
||||
KeyboardMap []string `json:"keyboard_map"`
|
||||
Magnetometer []string `json:"magnetometer"`
|
||||
Microphone []string `json:"microphone"`
|
||||
Midi []string `json:"midi"`
|
||||
NavigationOverride []string `json:"navigation_override"`
|
||||
Payment []string `json:"payment"`
|
||||
PictureInPicture []string `json:"picture_in_picture"`
|
||||
PublicKeyCredentialsGet []string `json:"publickey_credentials_get"`
|
||||
ScreenWakeLock []string `json:"screen_wake_lock"`
|
||||
SyncXHR []string `json:"sync_xhr"`
|
||||
USB []string `json:"usb"`
|
||||
WebShare []string `json:"web_share"`
|
||||
XRSpatialTracking []string `json:"xr_spatial_tracking"`
|
||||
ClipboardRead []string `json:"clipboard_read"`
|
||||
ClipboardWrite []string `json:"clipboard_write"`
|
||||
Gamepad []string `json:"gamepad"`
|
||||
SpeakerSelection []string `json:"speaker_selection"`
|
||||
ConversionMeasurement []string `json:"conversion_measurement"`
|
||||
FocusWithoutUserActivation []string `json:"focus_without_user_activation"`
|
||||
HID []string `json:"hid"`
|
||||
IdleDetection []string `json:"idle_detection"`
|
||||
InterestCohort []string `json:"interest_cohort"`
|
||||
Serial []string `json:"serial"`
|
||||
SyncScript []string `json:"sync_script"`
|
||||
TrustTokenRedemption []string `json:"trust_token_redemption"`
|
||||
Unload []string `json:"unload"`
|
||||
WindowPlacement []string `json:"window_placement"`
|
||||
VerticalScroll []string `json:"vertical_scroll"`
|
||||
}
|
||||
|
||||
// GetDefaultPermissionPolicy returns a PermissionsPolicy struct with all policies set to *
|
||||
func GetDefaultPermissionPolicy() *PermissionsPolicy {
|
||||
return &PermissionsPolicy{
|
||||
Accelerometer: []string{"*"},
|
||||
AmbientLightSensor: []string{"*"},
|
||||
Autoplay: []string{"*"},
|
||||
Battery: []string{"*"},
|
||||
Camera: []string{"*"},
|
||||
CrossOriginIsolated: []string{"*"},
|
||||
DisplayCapture: []string{"*"},
|
||||
DocumentDomain: []string{"*"},
|
||||
EncryptedMedia: []string{"*"},
|
||||
ExecutionWhileNotRendered: []string{"*"},
|
||||
ExecutionWhileOutOfView: []string{"*"},
|
||||
Fullscreen: []string{"*"},
|
||||
Geolocation: []string{"*"},
|
||||
Gyroscope: []string{"*"},
|
||||
KeyboardMap: []string{"*"},
|
||||
Magnetometer: []string{"*"},
|
||||
Microphone: []string{"*"},
|
||||
Midi: []string{"*"},
|
||||
NavigationOverride: []string{"*"},
|
||||
Payment: []string{"*"},
|
||||
PictureInPicture: []string{"*"},
|
||||
PublicKeyCredentialsGet: []string{"*"},
|
||||
ScreenWakeLock: []string{"*"},
|
||||
SyncXHR: []string{"*"},
|
||||
USB: []string{"*"},
|
||||
WebShare: []string{"*"},
|
||||
XRSpatialTracking: []string{"*"},
|
||||
ClipboardRead: []string{"*"},
|
||||
ClipboardWrite: []string{"*"},
|
||||
Gamepad: []string{"*"},
|
||||
SpeakerSelection: []string{"*"},
|
||||
ConversionMeasurement: []string{"*"},
|
||||
FocusWithoutUserActivation: []string{"*"},
|
||||
HID: []string{"*"},
|
||||
IdleDetection: []string{"*"},
|
||||
InterestCohort: []string{"*"},
|
||||
Serial: []string{"*"},
|
||||
SyncScript: []string{"*"},
|
||||
TrustTokenRedemption: []string{"*"},
|
||||
Unload: []string{"*"},
|
||||
WindowPlacement: []string{"*"},
|
||||
VerticalScroll: []string{"*"},
|
||||
}
|
||||
}
|
||||
|
||||
// ToKeyValueHeader convert a permission policy struct into a key value string header
|
||||
func (policy *PermissionsPolicy) ToKeyValueHeader() []string {
|
||||
policyHeader := []string{}
|
||||
|
||||
// Helper function to add policy directives
|
||||
addDirective := func(name string, sources []string) {
|
||||
if len(sources) > 0 {
|
||||
if sources[0] == "*" {
|
||||
//Allow all
|
||||
policyHeader = append(policyHeader, fmt.Sprintf("%s=%s", name, "*"))
|
||||
} else {
|
||||
//Other than "self" which do not need double quote, others domain need double quote in place
|
||||
formatedSources := []string{}
|
||||
for _, source := range sources {
|
||||
if source == "self" {
|
||||
formatedSources = append(formatedSources, "self")
|
||||
} else {
|
||||
formatedSources = append(formatedSources, "\""+source+"\"")
|
||||
}
|
||||
}
|
||||
policyHeader = append(policyHeader, fmt.Sprintf("%s=(%s)", name, strings.Join(formatedSources, " ")))
|
||||
}
|
||||
} else {
|
||||
//There are no setting for this field. Assume no permission
|
||||
policyHeader = append(policyHeader, fmt.Sprintf("%s=()", name))
|
||||
}
|
||||
}
|
||||
|
||||
// Add each policy directive to the header
|
||||
addDirective("accelerometer", policy.Accelerometer)
|
||||
addDirective("ambient-light-sensor", policy.AmbientLightSensor)
|
||||
addDirective("autoplay", policy.Autoplay)
|
||||
addDirective("battery", policy.Battery)
|
||||
addDirective("camera", policy.Camera)
|
||||
addDirective("cross-origin-isolated", policy.CrossOriginIsolated)
|
||||
addDirective("display-capture", policy.DisplayCapture)
|
||||
addDirective("document-domain", policy.DocumentDomain)
|
||||
addDirective("encrypted-media", policy.EncryptedMedia)
|
||||
addDirective("execution-while-not-rendered", policy.ExecutionWhileNotRendered)
|
||||
addDirective("execution-while-out-of-viewport", policy.ExecutionWhileOutOfView)
|
||||
addDirective("fullscreen", policy.Fullscreen)
|
||||
addDirective("geolocation", policy.Geolocation)
|
||||
addDirective("gyroscope", policy.Gyroscope)
|
||||
addDirective("keyboard-map", policy.KeyboardMap)
|
||||
addDirective("magnetometer", policy.Magnetometer)
|
||||
addDirective("microphone", policy.Microphone)
|
||||
addDirective("midi", policy.Midi)
|
||||
addDirective("navigation-override", policy.NavigationOverride)
|
||||
addDirective("payment", policy.Payment)
|
||||
addDirective("picture-in-picture", policy.PictureInPicture)
|
||||
addDirective("publickey-credentials-get", policy.PublicKeyCredentialsGet)
|
||||
addDirective("screen-wake-lock", policy.ScreenWakeLock)
|
||||
addDirective("sync-xhr", policy.SyncXHR)
|
||||
addDirective("usb", policy.USB)
|
||||
addDirective("web-share", policy.WebShare)
|
||||
addDirective("xr-spatial-tracking", policy.XRSpatialTracking)
|
||||
addDirective("clipboard-read", policy.ClipboardRead)
|
||||
addDirective("clipboard-write", policy.ClipboardWrite)
|
||||
addDirective("gamepad", policy.Gamepad)
|
||||
addDirective("speaker-selection", policy.SpeakerSelection)
|
||||
addDirective("conversion-measurement", policy.ConversionMeasurement)
|
||||
addDirective("focus-without-user-activation", policy.FocusWithoutUserActivation)
|
||||
addDirective("hid", policy.HID)
|
||||
addDirective("idle-detection", policy.IdleDetection)
|
||||
addDirective("interest-cohort", policy.InterestCohort)
|
||||
addDirective("serial", policy.Serial)
|
||||
addDirective("sync-script", policy.SyncScript)
|
||||
addDirective("trust-token-redemption", policy.TrustTokenRedemption)
|
||||
addDirective("unload", policy.Unload)
|
||||
addDirective("window-placement", policy.WindowPlacement)
|
||||
addDirective("vertical-scroll", policy.VerticalScroll)
|
||||
|
||||
// Join the directives and set the header
|
||||
policyHeaderValue := strings.Join(policyHeader, ", ")
|
||||
return []string{"Permissions-Policy", policyHeaderValue}
|
||||
}
|
||||
|
||||
// InjectPermissionPolicyHeader inject the permission policy into headers
|
||||
func InjectPermissionPolicyHeader(w http.ResponseWriter, policy *PermissionsPolicy) {
|
||||
//Keep the original Permission Policy if exists, or there are no policy given
|
||||
if policy == nil || w.Header().Get("Permissions-Policy") != "" {
|
||||
return
|
||||
}
|
||||
headerKV := policy.ToKeyValueHeader()
|
||||
//Inject the new policy into the header
|
||||
w.Header().Set(headerKV[0], headerKV[1])
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package permissionpolicy_test
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
)
|
||||
|
||||
func TestInjectPermissionPolicyHeader(t *testing.T) {
|
||||
//Prepare the data for permission policy
|
||||
testPermissionPolicy := permissionpolicy.GetDefaultPermissionPolicy()
|
||||
testPermissionPolicy.Geolocation = []string{"self"}
|
||||
testPermissionPolicy.Microphone = []string{"self", "https://example.com"}
|
||||
testPermissionPolicy.Camera = []string{"*"}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
existingHeader string
|
||||
policy *permissionpolicy.PermissionsPolicy
|
||||
expectedHeader string
|
||||
}{
|
||||
{
|
||||
name: "Default policy with a few limitations",
|
||||
existingHeader: "",
|
||||
policy: testPermissionPolicy,
|
||||
expectedHeader: `accelerometer=*, ambient-light-sensor=*, autoplay=*, battery=*, camera=*, cross-origin-isolated=*, display-capture=*, document-domain=*, encrypted-media=*, execution-while-not-rendered=*, execution-while-out-of-viewport=*, fullscreen=*, geolocation=(self), gyroscope=*, keyboard-map=*, magnetometer=*, microphone=(self "https://example.com"), midi=*, navigation-override=*, payment=*, picture-in-picture=*, publickey-credentials-get=*, screen-wake-lock=*, sync-xhr=*, usb=*, web-share=*, xr-spatial-tracking=*, clipboard-read=*, clipboard-write=*, gamepad=*, speaker-selection=*, conversion-measurement=*, focus-without-user-activation=*, hid=*, idle-detection=*, interest-cohort=*, serial=*, sync-script=*, trust-token-redemption=*, unload=*, window-placement=*, vertical-scroll=*`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
if tt.existingHeader != "" {
|
||||
rr.Header().Set("Permissions-Policy", tt.existingHeader)
|
||||
}
|
||||
|
||||
permissionpolicy.InjectPermissionPolicyHeader(rr, tt.policy)
|
||||
|
||||
gotHeader := rr.Header().Get("Permissions-Policy")
|
||||
if !strings.Contains(gotHeader, tt.expectedHeader) {
|
||||
t.Errorf("got header %s, want %s", gotHeader, tt.expectedHeader)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -111,13 +111,6 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
|
||||
|
||||
//Inject custom headers
|
||||
if len(target.UserDefinedHeaders) > 0 {
|
||||
for _, customHeader := range target.UserDefinedHeaders {
|
||||
r.Header.Set(customHeader.Key, customHeader.Value)
|
||||
}
|
||||
}
|
||||
|
||||
requestURL := r.URL.String()
|
||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||
@ -152,12 +145,18 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
r.URL, _ = url.Parse(originalHostHeader)
|
||||
}
|
||||
|
||||
//Build downstream and upstream header rules
|
||||
upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()
|
||||
|
||||
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: target.Domain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: target.RequireTLS,
|
||||
NoCache: h.Parent.Option.NoCache,
|
||||
PathPrefix: "",
|
||||
ProxyDomain: target.Domain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: target.RequireTLS,
|
||||
NoCache: h.Parent.Option.NoCache,
|
||||
PathPrefix: "",
|
||||
UpstreamHeaders: upstreamHeaders,
|
||||
DownstreamHeaders: downstreamHeaders,
|
||||
Version: target.parent.Option.HostVersion,
|
||||
})
|
||||
|
||||
var dnsError *net.DNSError
|
||||
@ -184,13 +183,6 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
|
||||
|
||||
//Inject custom headers
|
||||
if len(target.parent.UserDefinedHeaders) > 0 {
|
||||
for _, customHeader := range target.parent.UserDefinedHeaders {
|
||||
r.Header.Set(customHeader.Key, customHeader.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||
@ -219,11 +211,17 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
r.URL, _ = url.Parse(originalHostHeader)
|
||||
}
|
||||
|
||||
//Build downstream and upstream header rules
|
||||
upstreamHeaders, downstreamHeaders := target.parent.SplitInboundOutboundHeaders()
|
||||
|
||||
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: target.Domain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: target.RequireTLS,
|
||||
PathPrefix: target.MatchingPath,
|
||||
ProxyDomain: target.Domain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: target.RequireTLS,
|
||||
PathPrefix: target.MatchingPath,
|
||||
UpstreamHeaders: upstreamHeaders,
|
||||
DownstreamHeaders: downstreamHeaders,
|
||||
Version: target.parent.parent.Option.HostVersion,
|
||||
})
|
||||
|
||||
var dnsError *net.DNSError
|
||||
|
119
src/mod/dynamicproxy/ratelimit.go
Normal file
119
src/mod/dynamicproxy/ratelimit.go
Normal file
@ -0,0 +1,119 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IpTable is a rate limiter implementation using sync.Map with atomic int64
|
||||
type RequestCountPerIpTable struct {
|
||||
table sync.Map
|
||||
}
|
||||
|
||||
// Increment the count of requests for a given IP
|
||||
func (t *RequestCountPerIpTable) Increment(ip string) {
|
||||
v, _ := t.table.LoadOrStore(ip, new(int64))
|
||||
atomic.AddInt64(v.(*int64), 1)
|
||||
}
|
||||
|
||||
// Check if the IP is in the table and if it is, check if the count is less than the limit
|
||||
func (t *RequestCountPerIpTable) Exceeded(ip string, limit int64) bool {
|
||||
v, ok := t.table.Load(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
count := atomic.LoadInt64(v.(*int64))
|
||||
return count >= limit
|
||||
}
|
||||
|
||||
// Get the count of requests for a given IP
|
||||
func (t *RequestCountPerIpTable) GetCount(ip string) int64 {
|
||||
v, ok := t.table.Load(ip)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return atomic.LoadInt64(v.(*int64))
|
||||
}
|
||||
|
||||
// Clear the IP table
|
||||
func (t *RequestCountPerIpTable) Clear() {
|
||||
t.table.Range(func(key, value interface{}) bool {
|
||||
t.table.Delete(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ProxyHandler) handleRateLimitRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||
err := h.Parent.handleRateLimit(w, r, pe)
|
||||
if err != nil {
|
||||
h.logRequest(r, false, 429, "ratelimit", pe.Domain)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (router *Router) handleRateLimit(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||
//Get the real client-ip from request header
|
||||
clientIP := r.RemoteAddr
|
||||
if r.Header.Get("X-Real-Ip") == "" {
|
||||
CF_Connecting_IP := r.Header.Get("CF-Connecting-IP")
|
||||
Fastly_Client_IP := r.Header.Get("Fastly-Client-IP")
|
||||
if CF_Connecting_IP != "" {
|
||||
//Use CF Connecting IP
|
||||
clientIP = CF_Connecting_IP
|
||||
} else if Fastly_Client_IP != "" {
|
||||
//Use Fastly Client IP
|
||||
clientIP = Fastly_Client_IP
|
||||
} else {
|
||||
ips := strings.Split(clientIP, ",")
|
||||
if len(ips) > 0 {
|
||||
clientIP = strings.TrimSpace(ips[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ip, _, err := net.SplitHostPort(clientIP)
|
||||
if err != nil {
|
||||
//Default allow passthrough on error
|
||||
return nil
|
||||
}
|
||||
|
||||
router.rateLimitCounter.Increment(ip)
|
||||
|
||||
if router.rateLimitCounter.Exceeded(ip, int64(pe.RateLimit)) {
|
||||
w.WriteHeader(429)
|
||||
return errors.New("rate limit exceeded")
|
||||
}
|
||||
|
||||
// log.Println("Rate limit check", ip, ipTable.GetCount(ip))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start the ticker routine for reseting the rate limit counter every seconds
|
||||
func (r *Router) startRateLimterCounterResetTicker() error {
|
||||
if r.rateLimterStop != nil {
|
||||
return errors.New("another rate limiter ticker already running")
|
||||
}
|
||||
tickerStopChan := make(chan bool)
|
||||
r.rateLimterStop = tickerStopChan
|
||||
|
||||
counterResetTicker := time.NewTicker(1 * time.Second)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-tickerStopChan:
|
||||
r.rateLimterStop = nil
|
||||
return
|
||||
case <-counterResetTicker.C:
|
||||
r.rateLimitCounter.Clear()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package redirection
|
||||
|
||||
import (
|
||||
"log"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
@ -52,7 +52,7 @@ func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) int {
|
||||
//Invalid usage
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("500 - Internal Server Error"))
|
||||
log.Println("Target request URL do not have matching redirect rule. Check with IsRedirectable before calling HandleRedirect!")
|
||||
t.log("Target request URL do not have matching redirect rule. Check with IsRedirectable before calling HandleRedirect!", errors.New("invalid usage"))
|
||||
return 500
|
||||
}
|
||||
}
|
||||
|
@ -30,11 +30,12 @@ type RedirectRules struct {
|
||||
StatusCode int //Status Code for redirection
|
||||
}
|
||||
|
||||
func NewRuleTable(configPath string, allowRegex bool) (*RuleTable, error) {
|
||||
func NewRuleTable(configPath string, allowRegex bool, logger *logger.Logger) (*RuleTable, error) {
|
||||
thisRuleTable := RuleTable{
|
||||
rules: sync.Map{},
|
||||
configPath: configPath,
|
||||
AllowRegex: allowRegex,
|
||||
Logger: logger,
|
||||
}
|
||||
//Load all the rules from the config path
|
||||
if !utils.FileExists(configPath) {
|
||||
@ -67,7 +68,7 @@ func NewRuleTable(configPath string, allowRegex bool) (*RuleTable, error) {
|
||||
|
||||
//Map the rules into the sync map
|
||||
for _, rule := range rules {
|
||||
log.Println("Redirection rule added: " + rule.RedirectURL + " -> " + rule.TargetURL)
|
||||
thisRuleTable.log("Redirection rule added: "+rule.RedirectURL+" -> "+rule.TargetURL, nil)
|
||||
thisRuleTable.rules.Store(rule.RedirectURL, rule)
|
||||
}
|
||||
|
||||
@ -92,7 +93,7 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
|
||||
// Create a new file for writing the JSON data
|
||||
file, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
log.Printf("Error creating file %s: %s", filepath, err)
|
||||
t.log("Error creating file "+filepath, err)
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
@ -100,7 +101,7 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
|
||||
// Encode the RedirectRules object to JSON and write it to the file
|
||||
err = json.NewEncoder(file).Encode(newRule)
|
||||
if err != nil {
|
||||
log.Printf("Error encoding JSON to file %s: %s", filepath, err)
|
||||
t.log("Error encoding JSON to file "+filepath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -125,7 +126,7 @@ func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
|
||||
|
||||
// Delete the file
|
||||
if err := os.Remove(filepath); err != nil {
|
||||
log.Printf("Error deleting file %s: %s", filepath, err)
|
||||
t.log("Error deleting file "+filepath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
|
||||
<title>404 - Host Not Found</title>
|
||||
<style>
|
||||
h1, h2, h3, h4, h5, p, a, span{
|
||||
h1, h2, h3, h4, h5, p, a, span, .ui.list .item{
|
||||
font-family: 'Noto Sans TC', sans-serif;
|
||||
font-weight: 300;
|
||||
color: rgb(88, 88, 88)
|
||||
@ -22,9 +22,6 @@
|
||||
|
||||
.diagram{
|
||||
background-color: #ebebeb;
|
||||
box-shadow:
|
||||
inset 0px 11px 8px -10px #CCC,
|
||||
inset 0px -11px 8px -10px #CCC;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
@ -51,8 +52,9 @@ type Router struct {
|
||||
tlsListener net.Listener
|
||||
routingRules []*RoutingRule
|
||||
|
||||
tlsRedirectStop chan bool //Stop channel for tls redirection server
|
||||
tldMap map[string]int //Top level domain map, see tld.json
|
||||
tlsRedirectStop chan bool //Stop channel for tls redirection server
|
||||
rateLimterStop chan bool //Stop channel for rate limiter
|
||||
rateLimitCounter RequestCountPerIpTable //Request counter for rate limter
|
||||
}
|
||||
|
||||
// Auth credential for basic auth on certain endpoints
|
||||
@ -72,10 +74,20 @@ type BasicAuthExceptionRule struct {
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
// Header injection direction type
|
||||
type HeaderDirection int
|
||||
|
||||
const (
|
||||
HeaderDirection_ZoraxyToUpstream HeaderDirection = 0 //Inject (or remove) header to request out-going from Zoraxy to backend server
|
||||
HeaderDirection_ZoraxyToDownstream HeaderDirection = 1 //Inject (or remove) header to request out-going from Zoraxy to client (e.g. browser)
|
||||
)
|
||||
|
||||
// User defined headers to add into a proxy endpoint
|
||||
type UserDefinedHeader struct {
|
||||
Key string
|
||||
Value string
|
||||
Direction HeaderDirection
|
||||
Key string
|
||||
Value string
|
||||
IsRemove bool //Instead of set, remove this key instead
|
||||
}
|
||||
|
||||
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||
@ -107,13 +119,20 @@ type ProxyEndpoint struct {
|
||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||
|
||||
//Custom Headers
|
||||
UserDefinedHeaders []*UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||
UserDefinedHeaders []*UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||
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
|
||||
|
||||
//Authentication
|
||||
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
|
||||
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||
|
||||
// Rate Limiting
|
||||
RequireRateLimit bool
|
||||
RateLimit int64 // Rate limit in requests per second
|
||||
|
||||
//Access Control
|
||||
AccessFilterUUID string //Access filter ID
|
||||
|
||||
|
@ -42,17 +42,22 @@ SendEmail(
|
||||
)
|
||||
*/
|
||||
func (s *Sender) SendEmail(to string, subject string, content string) error {
|
||||
//Parse the email content
|
||||
// Parse the email content
|
||||
msg := []byte("To: " + to + "\n" +
|
||||
"From: Zoraxy <" + s.SenderAddr + ">\n" +
|
||||
"Subject: " + subject + "\n" +
|
||||
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" +
|
||||
content + "\n\n")
|
||||
|
||||
//Login to the SMTP server
|
||||
//Username can be username (e.g. admin) or email (e.g. admin@example.com), depending on SMTP service provider
|
||||
auth := smtp.PlainAuth("", s.Username, s.Password, s.Hostname)
|
||||
// Initialize the auth variable
|
||||
var auth smtp.Auth
|
||||
if s.Password != "" {
|
||||
// Login to the SMTP server
|
||||
// Username can be username (e.g. admin) or email (e.g. admin@example.com), depending on SMTP service provider
|
||||
auth = smtp.PlainAuth("", s.Username, s.Password, s.Hostname)
|
||||
}
|
||||
|
||||
// Send the email
|
||||
err := smtp.SendMail(s.Hostname+":"+strconv.Itoa(s.Port), auth, s.SenderAddr, []string{to}, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -53,6 +53,9 @@ func isIPv6InRange(startIP, endIP, testIP string) (bool, error) {
|
||||
|
||||
// Slow country code lookup for
|
||||
func (s *Store) slowSearchIpv4(ipAddr string) string {
|
||||
if isReservedIP(ipAddr) {
|
||||
return ""
|
||||
}
|
||||
for _, ipRange := range s.geodb {
|
||||
startIp := ipRange[0]
|
||||
endIp := ipRange[1]
|
||||
@ -67,6 +70,9 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
|
||||
}
|
||||
|
||||
func (s *Store) slowSearchIpv6(ipAddr string) string {
|
||||
if isReservedIP(ipAddr) {
|
||||
return ""
|
||||
}
|
||||
for _, ipRange := range s.geodbIpv6 {
|
||||
startIp := ipRange[0]
|
||||
endIp := ipRange[1]
|
||||
|
@ -9,13 +9,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//Rewrite url based on proxy root
|
||||
// Rewrite url based on proxy root (default site)
|
||||
func RewriteURL(rooturl string, requestURL string) (*url.URL, error) {
|
||||
rewrittenURL := strings.TrimPrefix(requestURL, rooturl)
|
||||
return url.Parse(rewrittenURL)
|
||||
}
|
||||
|
||||
//Check if the current platform support web.ssh function
|
||||
// Check if the current platform support web.ssh function
|
||||
func IsWebSSHSupported() bool {
|
||||
//Check if the binary exists in system/gotty/
|
||||
binary := "gotty_" + runtime.GOOS + "_" + runtime.GOARCH
|
||||
@ -34,7 +34,7 @@ func IsWebSSHSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
//Check if a given domain and port is a valid ssh server
|
||||
// Check if a given domain and port is a valid ssh server
|
||||
func IsSSHConnectable(ipOrDomain string, port int) bool {
|
||||
timeout := time.Second * 3
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ipOrDomain, port), timeout)
|
||||
@ -60,7 +60,7 @@ func IsSSHConnectable(ipOrDomain string, port int) bool {
|
||||
return string(buf[:7]) == "SSH-2.0"
|
||||
}
|
||||
|
||||
//Check if the port is used by other process or application
|
||||
// Check if the port is used by other process or application
|
||||
func isPortInUse(port int) bool {
|
||||
address := fmt.Sprintf(":%d", port)
|
||||
listener, err := net.Listen("tcp", address)
|
||||
|
@ -1,9 +1,10 @@
|
||||
package tcpprox
|
||||
package streamproxy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
@ -22,13 +23,13 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
portA, err := utils.PostPara(r, "porta")
|
||||
listenAddr, err := utils.PostPara(r, "listenAddr")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "first address cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
portB, err := utils.PostPara(r, "portb")
|
||||
proxyAddr, err := utils.PostPara(r, "proxyAddr")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "second address cannot be empty")
|
||||
return
|
||||
@ -44,27 +45,17 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
modeValue := ProxyMode_Transport
|
||||
mode, err := utils.PostPara(r, "mode")
|
||||
if err != nil || mode == "" {
|
||||
utils.SendErrorResponse(w, "no mode given")
|
||||
} else if mode == "listen" {
|
||||
modeValue = ProxyMode_Listen
|
||||
} else if mode == "transport" {
|
||||
modeValue = ProxyMode_Transport
|
||||
} else if mode == "starter" {
|
||||
modeValue = ProxyMode_Starter
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid mode given. Only support listen / transport / starter")
|
||||
}
|
||||
useTCP, _ := utils.PostBool(r, "useTCP")
|
||||
useUDP, _ := utils.PostBool(r, "useUDP")
|
||||
|
||||
//Create the target config
|
||||
newConfigUUID := m.NewConfig(&ProxyRelayOptions{
|
||||
Name: name,
|
||||
PortA: portA,
|
||||
PortB: portB,
|
||||
Timeout: timeout,
|
||||
Mode: modeValue,
|
||||
Name: name,
|
||||
ListeningAddr: strings.TrimSpace(listenAddr),
|
||||
ProxyAddr: strings.TrimSpace(proxyAddr),
|
||||
Timeout: timeout,
|
||||
UseTCP: useTCP,
|
||||
UseUDP: useUDP,
|
||||
})
|
||||
|
||||
js, _ := json.Marshal(newConfigUUID)
|
||||
@ -80,22 +71,10 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
newName, _ := utils.PostPara(r, "name")
|
||||
newPortA, _ := utils.PostPara(r, "porta")
|
||||
newPortB, _ := utils.PostPara(r, "portb")
|
||||
newModeStr, _ := utils.PostPara(r, "mode")
|
||||
newMode := -1
|
||||
if newModeStr != "" {
|
||||
if newModeStr == "listen" {
|
||||
newMode = 0
|
||||
} else if newModeStr == "transport" {
|
||||
newMode = 1
|
||||
} else if newModeStr == "starter" {
|
||||
newMode = 2
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid new mode value")
|
||||
return
|
||||
}
|
||||
}
|
||||
listenAddr, _ := utils.PostPara(r, "listenAddr")
|
||||
proxyAddr, _ := utils.PostPara(r, "proxyAddr")
|
||||
useTCP, _ := utils.PostBool(r, "useTCP")
|
||||
useUDP, _ := utils.PostBool(r, "useUDP")
|
||||
|
||||
newTimeoutStr, _ := utils.PostPara(r, "timeout")
|
||||
newTimeout := -1
|
||||
@ -108,7 +87,7 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
// Call the EditConfig method to modify the configuration
|
||||
err = m.EditConfig(configUUID, newName, newPortA, newPortB, newMode, newTimeout)
|
||||
err = m.EditConfig(configUUID, newName, listenAddr, proxyAddr, useTCP, useUDP, newTimeout)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
@ -158,6 +137,7 @@ func (m *Manager) HandleStopProxy(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if !targetProxyConfig.IsRunning() {
|
||||
targetProxyConfig.Running = false
|
||||
utils.SendErrorResponse(w, "target proxy service is not running")
|
||||
return
|
||||
}
|
||||
@ -180,6 +160,7 @@ func (m *Manager) HandleRemoveProxy(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if targetProxyConfig.IsRunning() {
|
||||
targetProxyConfig.Running = false
|
||||
utils.SendErrorResponse(w, "Service is running")
|
||||
return
|
||||
}
|
||||
@ -209,25 +190,3 @@ func (m *Manager) HandleGetProxyStatus(w http.ResponseWriter, r *http.Request) {
|
||||
js, _ := json.Marshal(targetConfig)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (m *Manager) HandleConfigValidate(w http.ResponseWriter, r *http.Request) {
|
||||
uuid, err := utils.GetPara(r, "uuid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid uuid given")
|
||||
return
|
||||
}
|
||||
|
||||
targetConfig, err := m.GetConfigByUUID(uuid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = targetConfig.ValidateConfigs()
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
281
src/mod/streamproxy/streamproxy.go
Normal file
281
src/mod/streamproxy/streamproxy.go
Normal file
@ -0,0 +1,281 @@
|
||||
package streamproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
)
|
||||
|
||||
/*
|
||||
TCP Proxy
|
||||
|
||||
Forward port from one port to another
|
||||
Also accept active connection and passive
|
||||
connection
|
||||
*/
|
||||
|
||||
type ProxyRelayOptions struct {
|
||||
Name string
|
||||
ListeningAddr string
|
||||
ProxyAddr string
|
||||
Timeout int
|
||||
UseTCP bool
|
||||
UseUDP bool
|
||||
}
|
||||
|
||||
type ProxyRelayConfig struct {
|
||||
UUID string //A UUIDv4 representing this config
|
||||
Name string //Name of the config
|
||||
Running bool //Status, read only
|
||||
AutoStart bool //If the service suppose to started automatically
|
||||
ListeningAddress string //Listening Address, usually 127.0.0.1:port
|
||||
ProxyTargetAddr string //Proxy target address
|
||||
UseTCP bool //Enable TCP proxy
|
||||
UseUDP bool //Enable UDP proxy
|
||||
Timeout int //Timeout for connection in sec
|
||||
tcpStopChan chan bool //Stop channel for TCP listener
|
||||
udpStopChan chan bool //Stop channel for UDP listener
|
||||
aTobAccumulatedByteTransfer atomic.Int64 //Accumulated byte transfer from A to B
|
||||
bToaAccumulatedByteTransfer atomic.Int64 //Accumulated byte transfer from B to A
|
||||
udpClientMap sync.Map //map storing the UDP client-server connections
|
||||
parent *Manager `json:"-"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Database *database.Database
|
||||
DefaultTimeout int
|
||||
AccessControlHandler func(net.Conn) bool
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
//Config and stores
|
||||
Options *Options
|
||||
Configs []*ProxyRelayConfig
|
||||
|
||||
//Realtime Statistics
|
||||
Connections int //currently connected connect counts
|
||||
|
||||
}
|
||||
|
||||
func NewStreamProxy(options *Options) *Manager {
|
||||
options.Database.NewTable("tcprox")
|
||||
|
||||
//Load relay configs from db
|
||||
previousRules := []*ProxyRelayConfig{}
|
||||
if options.Database.KeyExists("tcprox", "rules") {
|
||||
options.Database.Read("tcprox", "rules", &previousRules)
|
||||
}
|
||||
|
||||
//Check if the AccessControlHandler is empty. If yes, set it to always allow access
|
||||
if options.AccessControlHandler == nil {
|
||||
options.AccessControlHandler = func(conn net.Conn) bool {
|
||||
//Always allow access
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
//Create a new proxy manager for TCP
|
||||
thisManager := Manager{
|
||||
Options: options,
|
||||
Connections: 0,
|
||||
}
|
||||
|
||||
//Inject manager into the rules
|
||||
for _, rule := range previousRules {
|
||||
rule.parent = &thisManager
|
||||
if rule.Running {
|
||||
//This was previously running. Start it again
|
||||
log.Println("[Stream Proxy] Resuming stream proxy rule " + rule.Name)
|
||||
rule.Start()
|
||||
}
|
||||
}
|
||||
|
||||
thisManager.Configs = previousRules
|
||||
|
||||
return &thisManager
|
||||
}
|
||||
|
||||
func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
|
||||
//Generate two zero value for atomic int64
|
||||
aAcc := atomic.Int64{}
|
||||
bAcc := atomic.Int64{}
|
||||
aAcc.Store(0)
|
||||
bAcc.Store(0)
|
||||
//Generate a new config from options
|
||||
configUUID := uuid.New().String()
|
||||
thisConfig := ProxyRelayConfig{
|
||||
UUID: configUUID,
|
||||
Name: config.Name,
|
||||
ListeningAddress: config.ListeningAddr,
|
||||
ProxyTargetAddr: config.ProxyAddr,
|
||||
UseTCP: config.UseTCP,
|
||||
UseUDP: config.UseUDP,
|
||||
Timeout: config.Timeout,
|
||||
tcpStopChan: nil,
|
||||
udpStopChan: nil,
|
||||
aTobAccumulatedByteTransfer: aAcc,
|
||||
bToaAccumulatedByteTransfer: bAcc,
|
||||
udpClientMap: sync.Map{},
|
||||
parent: m,
|
||||
}
|
||||
m.Configs = append(m.Configs, &thisConfig)
|
||||
m.SaveConfigToDatabase()
|
||||
return configUUID
|
||||
}
|
||||
|
||||
func (m *Manager) GetConfigByUUID(configUUID string) (*ProxyRelayConfig, error) {
|
||||
// Find and return the config with the specified UUID
|
||||
for _, config := range m.Configs {
|
||||
if config.UUID == configUUID {
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("config not found")
|
||||
}
|
||||
|
||||
// Edit the config based on config UUID, leave empty for unchange fields
|
||||
func (m *Manager) EditConfig(configUUID string, newName string, newListeningAddr string, newProxyAddr string, useTCP bool, useUDP bool, newTimeout int) error {
|
||||
// Find the config with the specified UUID
|
||||
foundConfig, err := m.GetConfigByUUID(configUUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate and update the fields
|
||||
if newName != "" {
|
||||
foundConfig.Name = newName
|
||||
}
|
||||
if newListeningAddr != "" {
|
||||
foundConfig.ListeningAddress = newListeningAddr
|
||||
}
|
||||
if newProxyAddr != "" {
|
||||
foundConfig.ProxyTargetAddr = newProxyAddr
|
||||
}
|
||||
|
||||
foundConfig.UseTCP = useTCP
|
||||
foundConfig.UseUDP = useUDP
|
||||
|
||||
if newTimeout != -1 {
|
||||
if newTimeout < 0 {
|
||||
return errors.New("invalid timeout value given")
|
||||
}
|
||||
foundConfig.Timeout = newTimeout
|
||||
}
|
||||
|
||||
m.SaveConfigToDatabase()
|
||||
|
||||
//Check if config is running. If yes, restart it
|
||||
if foundConfig.IsRunning() {
|
||||
foundConfig.Restart()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) RemoveConfig(configUUID string) error {
|
||||
// Find and remove the config with the specified UUID
|
||||
for i, config := range m.Configs {
|
||||
if config.UUID == configUUID {
|
||||
m.Configs = append(m.Configs[:i], m.Configs[i+1:]...)
|
||||
m.SaveConfigToDatabase()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("config not found")
|
||||
}
|
||||
|
||||
func (m *Manager) SaveConfigToDatabase() {
|
||||
m.Options.Database.Write("tcprox", "rules", m.Configs)
|
||||
}
|
||||
|
||||
/*
|
||||
Config Functions
|
||||
*/
|
||||
|
||||
// Start a proxy if stopped
|
||||
func (c *ProxyRelayConfig) Start() error {
|
||||
if c.IsRunning() {
|
||||
c.Running = true
|
||||
return errors.New("proxy already running")
|
||||
}
|
||||
|
||||
// Create a stopChan to control the loop
|
||||
tcpStopChan := make(chan bool)
|
||||
udpStopChan := make(chan bool)
|
||||
|
||||
//Start the proxy service
|
||||
if c.UseUDP {
|
||||
c.udpStopChan = udpStopChan
|
||||
go func() {
|
||||
err := c.ForwardUDP(c.ListeningAddress, c.ProxyTargetAddr, udpStopChan)
|
||||
if err != nil {
|
||||
if !c.UseTCP {
|
||||
c.Running = false
|
||||
c.parent.SaveConfigToDatabase()
|
||||
}
|
||||
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if c.UseTCP {
|
||||
c.tcpStopChan = tcpStopChan
|
||||
go func() {
|
||||
//Default to transport mode
|
||||
err := c.Port2host(c.ListeningAddress, c.ProxyTargetAddr, tcpStopChan)
|
||||
if err != nil {
|
||||
c.Running = false
|
||||
c.parent.SaveConfigToDatabase()
|
||||
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//Successfully spawned off the proxy routine
|
||||
c.Running = true
|
||||
c.parent.SaveConfigToDatabase()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return if a proxy config is running
|
||||
func (c *ProxyRelayConfig) IsRunning() bool {
|
||||
return c.tcpStopChan != nil || c.udpStopChan != nil
|
||||
}
|
||||
|
||||
// Restart a proxy config
|
||||
func (c *ProxyRelayConfig) Restart() {
|
||||
if c.IsRunning() {
|
||||
c.Stop()
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
c.Start()
|
||||
}
|
||||
|
||||
// Stop a running proxy if running
|
||||
func (c *ProxyRelayConfig) Stop() {
|
||||
log.Println("[STREAM PROXY] Stopping Stream Proxy " + c.Name)
|
||||
|
||||
if c.udpStopChan != nil {
|
||||
log.Println("[STREAM PROXY] Stopping UDP for " + c.Name)
|
||||
c.udpStopChan <- true
|
||||
c.udpStopChan = nil
|
||||
}
|
||||
|
||||
if c.tcpStopChan != nil {
|
||||
log.Println("[STREAM PROXY] Stopping TCP for " + c.Name)
|
||||
c.tcpStopChan <- true
|
||||
c.tcpStopChan = nil
|
||||
}
|
||||
|
||||
log.Println("[STREAM PROXY] Stopped Stream Proxy " + c.Name)
|
||||
c.Running = false
|
||||
|
||||
//Update the running status
|
||||
c.parent.SaveConfigToDatabase()
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
package tcpprox_test
|
||||
package streamproxy_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/tcpprox"
|
||||
"imuslab.com/zoraxy/mod/streamproxy"
|
||||
)
|
||||
|
||||
func TestPort2Port(t *testing.T) {
|
||||
@ -12,7 +12,7 @@ func TestPort2Port(t *testing.T) {
|
||||
stopChan := make(chan bool)
|
||||
|
||||
// Create a ProxyRelayConfig with dummy values
|
||||
config := &tcpprox.ProxyRelayConfig{
|
||||
config := &streamproxy.ProxyRelayConfig{
|
||||
Timeout: 1,
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ func TestPort2Port(t *testing.T) {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// If the goroutine is still running, it means it did not stop as expected
|
||||
if config.Running {
|
||||
if config.IsRunning() {
|
||||
t.Errorf("port2port did not stop as expected")
|
||||
}
|
||||
|
146
src/mod/streamproxy/tcpprox.go
Normal file
146
src/mod/streamproxy/tcpprox.go
Normal file
@ -0,0 +1,146 @@
|
||||
package streamproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
func isValidIP(ip string) bool {
|
||||
parsedIP := net.ParseIP(ip)
|
||||
return parsedIP != nil
|
||||
}
|
||||
|
||||
func isValidPort(port string) bool {
|
||||
portInt, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if portInt < 1 || portInt > 65535 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup, accumulator *atomic.Int64) {
|
||||
n, err := io.Copy(conn1, conn2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
accumulator.Add(n) //Add to accumulator
|
||||
conn1.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
|
||||
//conn2.Close()
|
||||
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func forward(conn1 net.Conn, conn2 net.Conn, aTob *atomic.Int64, bToa *atomic.Int64) {
|
||||
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
|
||||
var wg sync.WaitGroup
|
||||
// wait tow goroutines
|
||||
wg.Add(2)
|
||||
go connCopy(conn1, conn2, &wg, aTob)
|
||||
go connCopy(conn2, conn1, &wg, bToa)
|
||||
//blocking when the wg is locked
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (c *ProxyRelayConfig) accept(listener net.Listener) (net.Conn, error) {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Check if connection in blacklist or whitelist
|
||||
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||
if !c.parent.Options.AccessControlHandler(conn) {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
conn.Close()
|
||||
log.Println("[x]", "Connection from "+addr.IP.String()+" rejected by access control policy")
|
||||
return nil, errors.New("Connection from " + addr.IP.String() + " rejected by access control policy")
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func startListener(address string) (net.Listener, error) {
|
||||
log.Println("[+]", "try to start server on:["+address+"]")
|
||||
server, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return nil, errors.New("listen address [" + address + "] faild")
|
||||
}
|
||||
log.Println("[√]", "start listen at address:["+address+"]")
|
||||
return server, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Forwarder Functions
|
||||
*/
|
||||
|
||||
/*
|
||||
portA -> server
|
||||
server -> portB
|
||||
*/
|
||||
func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, stopChan chan bool) error {
|
||||
listenerStartingAddr := allowPort
|
||||
if isValidPort(allowPort) {
|
||||
//number only, e.g. 8080
|
||||
listenerStartingAddr = "0.0.0.0:" + allowPort
|
||||
} else if strings.HasPrefix(allowPort, ":") && isValidPort(allowPort[1:]) {
|
||||
//port number starting with :, e.g. :8080
|
||||
listenerStartingAddr = "0.0.0.0" + allowPort
|
||||
}
|
||||
|
||||
server, err := startListener(listenerStartingAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetAddress = strings.TrimSpace(targetAddress)
|
||||
|
||||
//Start stop handler
|
||||
go func() {
|
||||
<-stopChan
|
||||
log.Println("[x]", "Received stop signal. Exiting Port to Host forwarder")
|
||||
server.Close()
|
||||
}()
|
||||
|
||||
//Start blocking loop for accepting connections
|
||||
for {
|
||||
conn, err := c.accept(server)
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
//Terminate by stop chan. Exit listener loop
|
||||
return nil
|
||||
}
|
||||
//Connection error. Retry
|
||||
continue
|
||||
}
|
||||
|
||||
go func(targetAddress string) {
|
||||
log.Println("[+]", "start connect host:["+targetAddress+"]")
|
||||
target, err := net.Dial("tcp", targetAddress)
|
||||
if err != nil {
|
||||
// temporarily unavailable, don't use fatal.
|
||||
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", c.Timeout, "seconds. ")
|
||||
conn.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
|
||||
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||
return
|
||||
}
|
||||
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
|
||||
forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
|
||||
}(targetAddress)
|
||||
}
|
||||
}
|
157
src/mod/streamproxy/udpprox.go
Normal file
157
src/mod/streamproxy/udpprox.go
Normal file
@ -0,0 +1,157 @@
|
||||
package streamproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
UDP Proxy Module
|
||||
*/
|
||||
|
||||
// Information maintained for each client/server connection
|
||||
type udpClientServerConn struct {
|
||||
ClientAddr *net.UDPAddr // Address of the client
|
||||
ServerConn *net.UDPConn // UDP connection to server
|
||||
}
|
||||
|
||||
// Generate a new connection by opening a UDP connection to the server
|
||||
func createNewUDPConn(srvAddr, cliAddr *net.UDPAddr) *udpClientServerConn {
|
||||
conn := new(udpClientServerConn)
|
||||
conn.ClientAddr = cliAddr
|
||||
srvudp, err := net.DialUDP("udp", nil, srvAddr)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
conn.ServerConn = srvudp
|
||||
return conn
|
||||
}
|
||||
|
||||
// Start listener, return inbound lisener and proxy target UDP address
|
||||
func initUDPConnections(listenAddr string, targetAddress string) (*net.UDPConn, *net.UDPAddr, error) {
|
||||
// Set up Proxy
|
||||
saddr, err := net.ResolveUDPAddr("udp", listenAddr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
inboundConn, err := net.ListenUDP("udp", saddr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Println("[UDP] Proxy listening on " + listenAddr)
|
||||
|
||||
outboundConn, err := net.ResolveUDPAddr("udp", targetAddress)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return inboundConn, outboundConn, nil
|
||||
}
|
||||
|
||||
// Go routine which manages connection from server to single client
|
||||
func (c *ProxyRelayConfig) RunUDPConnectionRelay(conn *udpClientServerConn, lisenter *net.UDPConn) {
|
||||
var buffer [1500]byte
|
||||
for {
|
||||
// Read from server
|
||||
n, err := conn.ServerConn.Read(buffer[0:])
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Relay it to client
|
||||
_, err = lisenter.WriteToUDP(buffer[0:n], conn.ClientAddr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Close all connections that waiting for read from server
|
||||
func (c *ProxyRelayConfig) CloseAllUDPConnections() {
|
||||
c.udpClientMap.Range(func(clientAddr, clientServerConn interface{}) bool {
|
||||
conn := clientServerConn.(*udpClientServerConn)
|
||||
conn.ServerConn.Close()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ProxyRelayConfig) ForwardUDP(address1, address2 string, stopChan chan bool) error {
|
||||
//By default the incoming listen Address is int
|
||||
//We need to add the loopback address into it
|
||||
if isValidPort(address1) {
|
||||
//Port number only. Missing the : in front
|
||||
address1 = ":" + address1
|
||||
}
|
||||
if strings.HasPrefix(address1, ":") {
|
||||
//Prepend 127.0.0.1 to the address
|
||||
address1 = "127.0.0.1" + address1
|
||||
}
|
||||
|
||||
lisener, targetAddr, err := initUDPConnections(address1, address2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
//Stop channel receiver
|
||||
for {
|
||||
select {
|
||||
case <-stopChan:
|
||||
//Stop signal received
|
||||
//Stop server -> client forwarder
|
||||
c.CloseAllUDPConnections()
|
||||
//Stop client -> server forwarder
|
||||
//Force close, will terminate ReadFromUDP for inbound listener
|
||||
lisener.Close()
|
||||
return
|
||||
default:
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
var buffer [1500]byte
|
||||
for {
|
||||
n, cliaddr, err := lisener.ReadFromUDP(buffer[0:])
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
//Proxy stopped
|
||||
return nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
c.aTobAccumulatedByteTransfer.Add(int64(n))
|
||||
saddr := cliaddr.String()
|
||||
rawConn, found := c.udpClientMap.Load(saddr)
|
||||
var conn *udpClientServerConn
|
||||
if !found {
|
||||
conn = createNewUDPConn(targetAddr, cliaddr)
|
||||
if conn == nil {
|
||||
continue
|
||||
}
|
||||
c.udpClientMap.Store(saddr, conn)
|
||||
log.Println("[UDP] Created new connection for client " + saddr)
|
||||
// Fire up routine to manage new connection
|
||||
go c.RunUDPConnectionRelay(conn, lisener)
|
||||
|
||||
} else {
|
||||
log.Println("[UDP] Found connection for client " + saddr)
|
||||
conn = rawConn.(*udpClientServerConn)
|
||||
}
|
||||
|
||||
// Relay to server
|
||||
_, err = conn.ServerConn.Write(buffer[0:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,341 +0,0 @@
|
||||
package tcpprox
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func isValidIP(ip string) bool {
|
||||
parsedIP := net.ParseIP(ip)
|
||||
return parsedIP != nil
|
||||
}
|
||||
|
||||
func isValidPort(port string) bool {
|
||||
portInt, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if portInt < 1 || portInt > 65535 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isReachable(target string) bool {
|
||||
timeout := time.Duration(2 * time.Second) // Set the timeout value as per your requirement
|
||||
conn, err := net.DialTimeout("tcp", target, timeout)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup, accumulator *int64) {
|
||||
io.Copy(conn1, conn2)
|
||||
conn1.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
|
||||
//conn2.Close()
|
||||
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func forward(conn1 net.Conn, conn2 net.Conn, aTob *int64, bToa *int64) {
|
||||
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
|
||||
var wg sync.WaitGroup
|
||||
// wait tow goroutines
|
||||
wg.Add(2)
|
||||
go connCopy(conn1, conn2, &wg, aTob)
|
||||
go connCopy(conn2, conn1, &wg, bToa)
|
||||
//blocking when the wg is locked
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (c *ProxyRelayConfig) accept(listener net.Listener) (net.Conn, error) {
|
||||
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Check if connection in blacklist or whitelist
|
||||
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||
if !c.parent.Options.AccessControlHandler(conn) {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
conn.Close()
|
||||
log.Println("[x]", "Connection from "+addr.IP.String()+" rejected by access control policy")
|
||||
return nil, errors.New("Connection from " + addr.IP.String() + " rejected by access control policy")
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func startListener(address string) (net.Listener, error) {
|
||||
log.Println("[+]", "try to start server on:["+address+"]")
|
||||
server, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return nil, errors.New("listen address [" + address + "] faild")
|
||||
}
|
||||
log.Println("[√]", "start listen at address:["+address+"]")
|
||||
return server, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Config Functions
|
||||
*/
|
||||
|
||||
// Config validator
|
||||
func (c *ProxyRelayConfig) ValidateConfigs() error {
|
||||
if c.Mode == ProxyMode_Transport {
|
||||
//Port2Host: PortA int, PortB string
|
||||
if !isValidPort(c.PortA) {
|
||||
return errors.New("first address must be a valid port number")
|
||||
}
|
||||
|
||||
if !isReachable(c.PortB) {
|
||||
return errors.New("second address is unreachable")
|
||||
}
|
||||
return nil
|
||||
|
||||
} else if c.Mode == ProxyMode_Listen {
|
||||
//Port2Port: Both port are port number
|
||||
if !isValidPort(c.PortA) {
|
||||
return errors.New("first address is not a valid port number")
|
||||
}
|
||||
|
||||
if !isValidPort(c.PortB) {
|
||||
return errors.New("second address is not a valid port number")
|
||||
}
|
||||
|
||||
return nil
|
||||
} else if c.Mode == ProxyMode_Starter {
|
||||
//Host2Host: Both have to be hosts
|
||||
if !isReachable(c.PortA) {
|
||||
return errors.New("first address is unreachable")
|
||||
}
|
||||
|
||||
if !isReachable(c.PortB) {
|
||||
return errors.New("second address is unreachable")
|
||||
}
|
||||
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("invalid mode given")
|
||||
}
|
||||
}
|
||||
|
||||
// Start a proxy if stopped
|
||||
func (c *ProxyRelayConfig) Start() error {
|
||||
if c.Running {
|
||||
return errors.New("proxy already running")
|
||||
}
|
||||
|
||||
// Create a stopChan to control the loop
|
||||
stopChan := make(chan bool)
|
||||
c.stopChan = stopChan
|
||||
|
||||
//Validate configs
|
||||
err := c.ValidateConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Start the proxy service
|
||||
go func() {
|
||||
c.Running = true
|
||||
if c.Mode == ProxyMode_Transport {
|
||||
err = c.Port2host(c.PortA, c.PortB, stopChan)
|
||||
} else if c.Mode == ProxyMode_Listen {
|
||||
err = c.Port2port(c.PortA, c.PortB, stopChan)
|
||||
} else if c.Mode == ProxyMode_Starter {
|
||||
err = c.Host2host(c.PortA, c.PortB, stopChan)
|
||||
}
|
||||
if err != nil {
|
||||
c.Running = false
|
||||
log.Println("Error starting proxy service " + c.Name + "(" + c.UUID + "): " + err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
//Successfully spawned off the proxy routine
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop a running proxy if running
|
||||
func (c *ProxyRelayConfig) IsRunning() bool {
|
||||
return c.Running || c.stopChan != nil
|
||||
}
|
||||
|
||||
// Stop a running proxy if running
|
||||
func (c *ProxyRelayConfig) Stop() {
|
||||
if c.Running || c.stopChan != nil {
|
||||
c.stopChan <- true
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
c.stopChan = nil
|
||||
c.Running = false
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Forwarder Functions
|
||||
*/
|
||||
|
||||
/*
|
||||
portA -> server
|
||||
portB -> server
|
||||
*/
|
||||
func (c *ProxyRelayConfig) Port2port(port1 string, port2 string, stopChan chan bool) error {
|
||||
//Trim the Prefix of : if exists
|
||||
listen1, err := startListener("0.0.0.0:" + port1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listen2, err := startListener("0.0.0.0:" + port2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("[√]", "listen port:", port1, "and", port2, "success. waiting for client...")
|
||||
c.Running = true
|
||||
|
||||
go func() {
|
||||
<-stopChan
|
||||
log.Println("[x]", "Received stop signal. Exiting Port to Port forwarder")
|
||||
c.Running = false
|
||||
listen1.Close()
|
||||
listen2.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
conn1, err := c.accept(listen1)
|
||||
if err != nil {
|
||||
if !c.Running {
|
||||
return nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
conn2, err := c.accept(listen2)
|
||||
if err != nil {
|
||||
if !c.Running {
|
||||
return nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if conn1 == nil || conn2 == nil {
|
||||
log.Println("[x]", "accept client faild. retry in ", c.Timeout, " seconds. ")
|
||||
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||
continue
|
||||
}
|
||||
go forward(conn1, conn2, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
portA -> server
|
||||
server -> portB
|
||||
*/
|
||||
func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, stopChan chan bool) error {
|
||||
server, err := startListener("0.0.0.0:" + allowPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Start stop handler
|
||||
go func() {
|
||||
<-stopChan
|
||||
log.Println("[x]", "Received stop signal. Exiting Port to Host forwarder")
|
||||
c.Running = false
|
||||
server.Close()
|
||||
}()
|
||||
|
||||
//Start blocking loop for accepting connections
|
||||
for {
|
||||
conn, err := c.accept(server)
|
||||
if conn == nil || err != nil {
|
||||
if !c.Running {
|
||||
//Terminate by stop chan. Exit listener loop
|
||||
return nil
|
||||
}
|
||||
|
||||
//Connection error. Retry
|
||||
continue
|
||||
}
|
||||
|
||||
go func(targetAddress string) {
|
||||
log.Println("[+]", "start connect host:["+targetAddress+"]")
|
||||
target, err := net.Dial("tcp", targetAddress)
|
||||
if err != nil {
|
||||
// temporarily unavailable, don't use fatal.
|
||||
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", c.Timeout, "seconds. ")
|
||||
conn.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
|
||||
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||
return
|
||||
}
|
||||
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
|
||||
forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
|
||||
}(targetAddress)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
server -> portA
|
||||
server -> portB
|
||||
*/
|
||||
func (c *ProxyRelayConfig) Host2host(address1, address2 string, stopChan chan bool) error {
|
||||
c.Running = true
|
||||
go func() {
|
||||
<-stopChan
|
||||
log.Println("[x]", "Received stop signal. Exiting Host to Host forwarder")
|
||||
c.Running = false
|
||||
}()
|
||||
|
||||
for c.Running {
|
||||
log.Println("[+]", "try to connect host:["+address1+"] and ["+address2+"]")
|
||||
var host1, host2 net.Conn
|
||||
var err error
|
||||
for {
|
||||
d := net.Dialer{Timeout: time.Duration(c.Timeout)}
|
||||
host1, err = d.Dial("tcp", address1)
|
||||
if err == nil {
|
||||
log.Println("[→]", "connect ["+address1+"] success.")
|
||||
break
|
||||
} else {
|
||||
log.Println("[x]", "connect target address ["+address1+"] faild. retry in ", c.Timeout, " seconds. ")
|
||||
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||
}
|
||||
|
||||
if !c.Running {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
for {
|
||||
d := net.Dialer{Timeout: time.Duration(c.Timeout)}
|
||||
host2, err = d.Dial("tcp", address2)
|
||||
if err == nil {
|
||||
log.Println("[→]", "connect ["+address2+"] success.")
|
||||
break
|
||||
} else {
|
||||
log.Println("[x]", "connect target address ["+address2+"] faild. retry in ", c.Timeout, " seconds. ")
|
||||
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||
}
|
||||
|
||||
if !c.Running {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
go forward(host1, host2, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,289 +0,0 @@
|
||||
package tcpprox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const timeout = 5
|
||||
|
||||
func main() {
|
||||
//log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
|
||||
log.SetFlags(log.Ldate | log.Lmicroseconds)
|
||||
|
||||
printWelcome()
|
||||
|
||||
args := os.Args
|
||||
argc := len(os.Args)
|
||||
if argc <= 2 {
|
||||
printHelp()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
//TODO:support UDP protocol
|
||||
|
||||
/*var logFileError error
|
||||
if argc > 5 && args[4] == "-log" {
|
||||
logPath := args[5] + "/" + time.Now().Format("2006_01_02_15_04_05") // "2006-01-02 15:04:05"
|
||||
logPath += args[1] + "-" + strings.Replace(args[2], ":", "_", -1) + "-" + args[3] + ".log"
|
||||
logPath = strings.Replace(logPath, `\`, "/", -1)
|
||||
logPath = strings.Replace(logPath, "//", "/", -1)
|
||||
logFile, logFileError = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE, 0666)
|
||||
if logFileError != nil {
|
||||
log.Fatalln("[x]", "log file path error.", logFileError.Error())
|
||||
}
|
||||
log.Println("[√]", "open test log file success. path:", logPath)
|
||||
}*/
|
||||
|
||||
switch args[1] {
|
||||
case "-listen":
|
||||
if argc < 3 {
|
||||
log.Fatalln(`-listen need two arguments, like "nb -listen 1997 2017".`)
|
||||
}
|
||||
port1 := checkPort(args[2])
|
||||
port2 := checkPort(args[3])
|
||||
log.Println("[√]", "start to listen port:", port1, "and port:", port2)
|
||||
port2port(port1, port2)
|
||||
break
|
||||
case "-tran":
|
||||
if argc < 3 {
|
||||
log.Fatalln(`-tran need two arguments, like "nb -tran 1997 192.168.1.2:3389".`)
|
||||
}
|
||||
port := checkPort(args[2])
|
||||
var remoteAddress string
|
||||
if checkIp(args[3]) {
|
||||
remoteAddress = args[3]
|
||||
}
|
||||
split := strings.SplitN(remoteAddress, ":", 2)
|
||||
log.Println("[√]", "start to transmit address:", remoteAddress, "to address:", split[0]+":"+port)
|
||||
port2host(port, remoteAddress)
|
||||
break
|
||||
case "-slave":
|
||||
if argc < 3 {
|
||||
log.Fatalln(`-slave need two arguments, like "nb -slave 127.0.0.1:3389 8.8.8.8:1997".`)
|
||||
}
|
||||
var address1, address2 string
|
||||
checkIp(args[2])
|
||||
if checkIp(args[2]) {
|
||||
address1 = args[2]
|
||||
}
|
||||
checkIp(args[3])
|
||||
if checkIp(args[3]) {
|
||||
address2 = args[3]
|
||||
}
|
||||
log.Println("[√]", "start to connect address:", address1, "and address:", address2)
|
||||
host2host(address1, address2)
|
||||
break
|
||||
default:
|
||||
printHelp()
|
||||
}
|
||||
}
|
||||
|
||||
func printWelcome() {
|
||||
fmt.Println("+----------------------------------------------------------------+")
|
||||
fmt.Println("| Welcome to use NATBypass Ver1.0.0 . |")
|
||||
fmt.Println("| Code by cw1997 at 2017-10-19 03:59:51 |")
|
||||
fmt.Println("| If you have some problem when you use the tool, |")
|
||||
fmt.Println("| please submit issue at : https://github.com/cw1997/NATBypass . |")
|
||||
fmt.Println("+----------------------------------------------------------------+")
|
||||
fmt.Println()
|
||||
// sleep one second because the fmt is not thread-safety.
|
||||
// if not to do this, fmt.Print will print after the log.Print.
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
func printHelp() {
|
||||
fmt.Println(`usage: "-listen port1 port2" example: "nb -listen 1997 2017" `)
|
||||
fmt.Println(` "-tran port1 ip:port2" example: "nb -tran 1997 192.168.1.2:3389" `)
|
||||
fmt.Println(` "-slave ip1:port1 ip2:port2" example: "nb -slave 127.0.0.1:3389 8.8.8.8:1997" `)
|
||||
fmt.Println(`============================================================`)
|
||||
fmt.Println(`optional argument: "-log logpath" . example: "nb -listen 1997 2017 -log d:/nb" `)
|
||||
fmt.Println(`log filename format: Y_m_d_H_i_s-agrs1-args2-args3.log`)
|
||||
fmt.Println(`============================================================`)
|
||||
fmt.Println(`if you want more help, please read "README.md". `)
|
||||
}
|
||||
|
||||
func checkPort(port string) string {
|
||||
PortNum, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
log.Fatalln("[x]", "port should be a number")
|
||||
}
|
||||
if PortNum < 1 || PortNum > 65535 {
|
||||
log.Fatalln("[x]", "port should be a number and the range is [1,65536)")
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
func checkIp(address string) bool {
|
||||
ipAndPort := strings.Split(address, ":")
|
||||
if len(ipAndPort) != 2 {
|
||||
log.Fatalln("[x]", "address error. should be a string like [ip:port]. ")
|
||||
}
|
||||
ip := ipAndPort[0]
|
||||
port := ipAndPort[1]
|
||||
checkPort(port)
|
||||
pattern := `^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$`
|
||||
ok, err := regexp.MatchString(pattern, ip)
|
||||
if err != nil || !ok {
|
||||
log.Fatalln("[x]", "ip error. ")
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func port2port(port1 string, port2 string) {
|
||||
listen1 := start_server("0.0.0.0:" + port1)
|
||||
listen2 := start_server("0.0.0.0:" + port2)
|
||||
log.Println("[√]", "listen port:", port1, "and", port2, "success. waiting for client...")
|
||||
for {
|
||||
conn1 := accept(listen1)
|
||||
conn2 := accept(listen2)
|
||||
if conn1 == nil || conn2 == nil {
|
||||
log.Println("[x]", "accept client faild. retry in ", timeout, " seconds. ")
|
||||
time.Sleep(timeout * time.Second)
|
||||
continue
|
||||
}
|
||||
forward(conn1, conn2)
|
||||
}
|
||||
}
|
||||
|
||||
func port2host(allowPort string, targetAddress string) {
|
||||
server := start_server("0.0.0.0:" + allowPort)
|
||||
for {
|
||||
conn := accept(server)
|
||||
if conn == nil {
|
||||
continue
|
||||
}
|
||||
//println(targetAddress)
|
||||
go func(targetAddress string) {
|
||||
log.Println("[+]", "start connect host:["+targetAddress+"]")
|
||||
target, err := net.Dial("tcp", targetAddress)
|
||||
if err != nil {
|
||||
// temporarily unavailable, don't use fatal.
|
||||
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", timeout, "seconds. ")
|
||||
conn.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
|
||||
time.Sleep(timeout * time.Second)
|
||||
return
|
||||
}
|
||||
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
|
||||
forward(target, conn)
|
||||
}(targetAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func host2host(address1, address2 string) {
|
||||
for {
|
||||
log.Println("[+]", "try to connect host:["+address1+"] and ["+address2+"]")
|
||||
var host1, host2 net.Conn
|
||||
var err error
|
||||
for {
|
||||
host1, err = net.Dial("tcp", address1)
|
||||
if err == nil {
|
||||
log.Println("[→]", "connect ["+address1+"] success.")
|
||||
break
|
||||
} else {
|
||||
log.Println("[x]", "connect target address ["+address1+"] faild. retry in ", timeout, " seconds. ")
|
||||
time.Sleep(timeout * time.Second)
|
||||
}
|
||||
}
|
||||
for {
|
||||
host2, err = net.Dial("tcp", address2)
|
||||
if err == nil {
|
||||
log.Println("[→]", "connect ["+address2+"] success.")
|
||||
break
|
||||
} else {
|
||||
log.Println("[x]", "connect target address ["+address2+"] faild. retry in ", timeout, " seconds. ")
|
||||
time.Sleep(timeout * time.Second)
|
||||
}
|
||||
}
|
||||
forward(host1, host2)
|
||||
}
|
||||
}
|
||||
|
||||
func start_server(address string) net.Listener {
|
||||
log.Println("[+]", "try to start server on:["+address+"]")
|
||||
server, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
log.Fatalln("[x]", "listen address ["+address+"] faild.")
|
||||
}
|
||||
log.Println("[√]", "start listen at address:["+address+"]")
|
||||
return server
|
||||
/*defer server.Close()
|
||||
|
||||
for {
|
||||
conn, err := server.Accept()
|
||||
log.Println("accept a new client. remote address:[" + conn.RemoteAddr().String() +
|
||||
"], local address:[" + conn.LocalAddr().String() + "]")
|
||||
if err != nil {
|
||||
log.Println("accept a new client faild.", err.Error())
|
||||
continue
|
||||
}
|
||||
//go recvConnMsg(conn)
|
||||
}*/
|
||||
}
|
||||
|
||||
func accept(listener net.Listener) net.Conn {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Println("[x]", "accept connect ["+conn.RemoteAddr().String()+"] faild.", err.Error())
|
||||
return nil
|
||||
}
|
||||
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
|
||||
return conn
|
||||
}
|
||||
|
||||
func forward(conn1 net.Conn, conn2 net.Conn) {
|
||||
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
|
||||
var wg sync.WaitGroup
|
||||
// wait tow goroutines
|
||||
wg.Add(2)
|
||||
go connCopy(conn1, conn2, &wg)
|
||||
go connCopy(conn2, conn1, &wg)
|
||||
//blocking when the wg is locked
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup) {
|
||||
//TODO:log, record the data from conn1 and conn2.
|
||||
logFile := openLog(conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
|
||||
if logFile != nil {
|
||||
w := io.MultiWriter(conn1, logFile)
|
||||
io.Copy(w, conn2)
|
||||
} else {
|
||||
io.Copy(conn1, conn2)
|
||||
}
|
||||
conn1.Close()
|
||||
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
|
||||
//conn2.Close()
|
||||
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
|
||||
wg.Done()
|
||||
}
|
||||
func openLog(address1, address2, address3, address4 string) *os.File {
|
||||
args := os.Args
|
||||
argc := len(os.Args)
|
||||
var logFileError error
|
||||
var logFile *os.File
|
||||
if argc > 5 && args[4] == "-log" {
|
||||
address1 = strings.Replace(address1, ":", "_", -1)
|
||||
address2 = strings.Replace(address2, ":", "_", -1)
|
||||
address3 = strings.Replace(address3, ":", "_", -1)
|
||||
address4 = strings.Replace(address4, ":", "_", -1)
|
||||
timeStr := time.Now().Format("2006_01_02_15_04_05") // "2006-01-02 15:04:05"
|
||||
logPath := args[5] + "/" + timeStr + args[1] + "-" + address1 + "_" + address2 + "-" + address3 + "_" + address4 + ".log"
|
||||
logPath = strings.Replace(logPath, `\`, "/", -1)
|
||||
logPath = strings.Replace(logPath, "//", "/", -1)
|
||||
logFile, logFileError = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE, 0666)
|
||||
if logFileError != nil {
|
||||
log.Fatalln("[x]", "log file path error.", logFileError.Error())
|
||||
}
|
||||
log.Println("[√]", "open test log file success. path:", logPath)
|
||||
}
|
||||
return logFile
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
package tcpprox
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
)
|
||||
|
||||
/*
|
||||
TCP Proxy
|
||||
|
||||
Forward port from one port to another
|
||||
Also accept active connection and passive
|
||||
connection
|
||||
*/
|
||||
|
||||
const (
|
||||
ProxyMode_Listen = 0
|
||||
ProxyMode_Transport = 1
|
||||
ProxyMode_Starter = 2
|
||||
)
|
||||
|
||||
type ProxyRelayOptions struct {
|
||||
Name string
|
||||
PortA string
|
||||
PortB string
|
||||
Timeout int
|
||||
Mode int
|
||||
}
|
||||
|
||||
type ProxyRelayConfig struct {
|
||||
UUID string //A UUIDv4 representing this config
|
||||
Name string //Name of the config
|
||||
Running bool //If the service is running
|
||||
PortA string //Ports A (config depends on mode)
|
||||
PortB string //Ports B (config depends on mode)
|
||||
Mode int //Operation Mode
|
||||
Timeout int //Timeout for connection in sec
|
||||
stopChan chan bool //Stop channel to stop the listener
|
||||
aTobAccumulatedByteTransfer int64 //Accumulated byte transfer from A to B
|
||||
bToaAccumulatedByteTransfer int64 //Accumulated byte transfer from B to A
|
||||
|
||||
parent *Manager `json:"-"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Database *database.Database
|
||||
DefaultTimeout int
|
||||
AccessControlHandler func(net.Conn) bool
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
//Config and stores
|
||||
Options *Options
|
||||
Configs []*ProxyRelayConfig
|
||||
|
||||
//Realtime Statistics
|
||||
Connections int //currently connected connect counts
|
||||
}
|
||||
|
||||
func NewTCProxy(options *Options) *Manager {
|
||||
options.Database.NewTable("tcprox")
|
||||
|
||||
//Load relay configs from db
|
||||
previousRules := []*ProxyRelayConfig{}
|
||||
if options.Database.KeyExists("tcprox", "rules") {
|
||||
options.Database.Read("tcprox", "rules", &previousRules)
|
||||
}
|
||||
|
||||
//Check if the AccessControlHandler is empty. If yes, set it to always allow access
|
||||
if options.AccessControlHandler == nil {
|
||||
options.AccessControlHandler = func(conn net.Conn) bool {
|
||||
//Always allow access
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
//Create a new proxy manager for TCP
|
||||
thisManager := Manager{
|
||||
Options: options,
|
||||
Connections: 0,
|
||||
}
|
||||
|
||||
//Inject manager into the rules
|
||||
for _, rule := range previousRules {
|
||||
rule.parent = &thisManager
|
||||
}
|
||||
|
||||
thisManager.Configs = previousRules
|
||||
|
||||
return &thisManager
|
||||
}
|
||||
|
||||
func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
|
||||
//Generate a new config from options
|
||||
configUUID := uuid.New().String()
|
||||
thisConfig := ProxyRelayConfig{
|
||||
UUID: configUUID,
|
||||
Name: config.Name,
|
||||
Running: false,
|
||||
PortA: config.PortA,
|
||||
PortB: config.PortB,
|
||||
Mode: config.Mode,
|
||||
Timeout: config.Timeout,
|
||||
stopChan: nil,
|
||||
aTobAccumulatedByteTransfer: 0,
|
||||
bToaAccumulatedByteTransfer: 0,
|
||||
|
||||
parent: m,
|
||||
}
|
||||
m.Configs = append(m.Configs, &thisConfig)
|
||||
m.SaveConfigToDatabase()
|
||||
return configUUID
|
||||
}
|
||||
|
||||
func (m *Manager) GetConfigByUUID(configUUID string) (*ProxyRelayConfig, error) {
|
||||
// Find and return the config with the specified UUID
|
||||
for _, config := range m.Configs {
|
||||
if config.UUID == configUUID {
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("config not found")
|
||||
}
|
||||
|
||||
// Edit the config based on config UUID, leave empty for unchange fields
|
||||
func (m *Manager) EditConfig(configUUID string, newName string, newPortA string, newPortB string, newMode int, newTimeout int) error {
|
||||
// Find the config with the specified UUID
|
||||
foundConfig, err := m.GetConfigByUUID(configUUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate and update the fields
|
||||
if newName != "" {
|
||||
foundConfig.Name = newName
|
||||
}
|
||||
if newPortA != "" {
|
||||
foundConfig.PortA = newPortA
|
||||
}
|
||||
if newPortB != "" {
|
||||
foundConfig.PortB = newPortB
|
||||
}
|
||||
if newMode != -1 {
|
||||
if newMode > 2 || newMode < 0 {
|
||||
return errors.New("invalid mode given")
|
||||
}
|
||||
foundConfig.Mode = newMode
|
||||
}
|
||||
if newTimeout != -1 {
|
||||
if newTimeout < 0 {
|
||||
return errors.New("invalid timeout value given")
|
||||
}
|
||||
foundConfig.Timeout = newTimeout
|
||||
}
|
||||
|
||||
/*
|
||||
err = foundConfig.ValidateConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*/
|
||||
|
||||
m.SaveConfigToDatabase()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) RemoveConfig(configUUID string) error {
|
||||
// Find and remove the config with the specified UUID
|
||||
for i, config := range m.Configs {
|
||||
if config.UUID == configUUID {
|
||||
m.Configs = append(m.Configs[:i], m.Configs[i+1:]...)
|
||||
m.SaveConfigToDatabase()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("config not found")
|
||||
}
|
||||
|
||||
func (m *Manager) SaveConfigToDatabase() {
|
||||
m.Options.Database.Write("tcprox", "rules", m.Configs)
|
||||
}
|
@ -4,9 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -217,11 +219,24 @@ func getWebsiteStatusWithLatency(url string) (bool, int64, int) {
|
||||
}
|
||||
|
||||
func getWebsiteStatus(url string) (int, error) {
|
||||
client := http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
// Create a one-time use cookie jar to store cookies
|
||||
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
client := http.Client{
|
||||
Jar: jar,
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header = http.Header{
|
||||
"User-Agent": {"zoraxy-uptime/1.1"},
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
//resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
//Try replace the http with https and vise versa
|
||||
rewriteURL := ""
|
||||
@ -231,7 +246,12 @@ func getWebsiteStatus(url string) (int, error) {
|
||||
rewriteURL = strings.ReplaceAll(url, "http://", "https://")
|
||||
}
|
||||
|
||||
resp, err = client.Get(rewriteURL)
|
||||
req, _ := http.NewRequest("GET", rewriteURL, nil)
|
||||
req.Header = http.Header{
|
||||
"User-Agent": {"zoraxy-uptime/1.1"},
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "http: server gave HTTP response to HTTPS client") {
|
||||
//Invalid downstream reverse proxy settings, but it is online
|
||||
|
@ -68,9 +68,9 @@ func PostBool(r *http.Request, key string) (bool, error) {
|
||||
|
||||
x = strings.TrimSpace(x)
|
||||
|
||||
if x == "1" || strings.ToLower(x) == "true" {
|
||||
if x == "1" || strings.ToLower(x) == "true" || strings.ToLower(x) == "on" {
|
||||
return true, nil
|
||||
} else if x == "0" || strings.ToLower(x) == "false" {
|
||||
} else if x == "0" || strings.ToLower(x) == "false" || strings.ToLower(x) == "off" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ func handleToggleRedirectRegexpSupport(w http.ResponseWriter, r *http.Request) {
|
||||
//Update the current regex support rule enable state
|
||||
enableRegexSupport := strings.EqualFold(strings.TrimSpace(enabled), "true")
|
||||
redirectTable.AllowRegex = enableRegexSupport
|
||||
err = sysdb.Write("Redirect", "regex", enableRegexSupport)
|
||||
err = sysdb.Write("redirect", "regex", enableRegexSupport)
|
||||
|
||||
if enableRegexSupport {
|
||||
SystemWideLogger.PrintAndLog("redirect", "Regex redirect rule enabled", nil)
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"imuslab.com/zoraxy/mod/auth"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
"imuslab.com/zoraxy/mod/uptime"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
@ -143,9 +144,12 @@ func ReverseProxtInit() {
|
||||
Interval: 300, //5 minutes
|
||||
MaxRecordsStore: 288, //1 day
|
||||
})
|
||||
|
||||
//Pass the pointer of this uptime monitor into the load balancer
|
||||
loadbalancer.Options.UptimeMonitor = uptimeMonitor
|
||||
|
||||
SystemWideLogger.Println("Uptime Monitor background service started")
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
func ReverseProxyHandleOnOff(w http.ResponseWriter, r *http.Request) {
|
||||
@ -221,7 +225,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Require basic auth?
|
||||
// Require basic auth?
|
||||
rba, _ := utils.PostPara(r, "bauth")
|
||||
if rba == "" {
|
||||
rba = "false"
|
||||
@ -229,6 +233,29 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
requireBasicAuth := (rba == "true")
|
||||
|
||||
// Require Rate Limiting?
|
||||
requireRateLimit := false
|
||||
proxyRateLimit := 1000
|
||||
|
||||
requireRateLimit, err = utils.PostBool(r, "rate")
|
||||
if err != nil {
|
||||
requireRateLimit = false
|
||||
}
|
||||
if requireRateLimit {
|
||||
proxyRateLimit, err = utils.PostInt(r, "ratenum")
|
||||
if err != nil {
|
||||
proxyRateLimit = 0
|
||||
}
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid rate limit number")
|
||||
return
|
||||
}
|
||||
if proxyRateLimit <= 0 {
|
||||
utils.SendErrorResponse(w, "rate limit number must be greater than 0")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Bypass WebSocket Origin Check
|
||||
strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
|
||||
if strbpwsorg == "" {
|
||||
@ -309,6 +336,9 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
|
||||
DefaultSiteOption: 0,
|
||||
DefaultSiteValue: "",
|
||||
// Rate Limit
|
||||
RequireRateLimit: requireRateLimit,
|
||||
RateLimit: int64(proxyRateLimit),
|
||||
}
|
||||
|
||||
preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisProxyEndpoint)
|
||||
@ -430,6 +460,26 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
requireBasicAuth := (rba == "true")
|
||||
|
||||
// Rate Limiting?
|
||||
rl, _ := utils.PostPara(r, "rate")
|
||||
if rl == "" {
|
||||
rl = "false"
|
||||
}
|
||||
requireRateLimit := (rl == "true")
|
||||
rlnum, _ := utils.PostPara(r, "ratenum")
|
||||
if rlnum == "" {
|
||||
rlnum = "0"
|
||||
}
|
||||
proxyRateLimit, err := strconv.ParseInt(rlnum, 10, 64)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid rate limit number")
|
||||
return
|
||||
}
|
||||
if proxyRateLimit <= 0 {
|
||||
utils.SendErrorResponse(w, "rate limit number must be greater than 0")
|
||||
return
|
||||
}
|
||||
|
||||
// Bypass WebSocket Origin Check
|
||||
strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
|
||||
if strbpwsorg == "" {
|
||||
@ -451,6 +501,8 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
|
||||
newProxyEndpoint.SkipCertValidations = skipTlsValidation
|
||||
newProxyEndpoint.RequireBasicAuth = requireBasicAuth
|
||||
newProxyEndpoint.RequireRateLimit = requireRateLimit
|
||||
newProxyEndpoint.RateLimit = proxyRateLimit
|
||||
newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck
|
||||
|
||||
//Prepare to replace the current routing rule
|
||||
@ -1076,9 +1128,9 @@ func HandleCustomHeaderList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Add a new header to the target endpoint
|
||||
func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
|
||||
epType, err := utils.PostPara(r, "type")
|
||||
rewriteType, err := utils.PostPara(r, "type")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "endpoint type not defined")
|
||||
utils.SendErrorResponse(w, "rewriteType not defined")
|
||||
return
|
||||
}
|
||||
|
||||
@ -1088,6 +1140,12 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
direction, err := utils.PostPara(r, "direction")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "HTTP modifiy direction not set")
|
||||
return
|
||||
}
|
||||
|
||||
name, err := utils.PostPara(r, "name")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "HTTP header name not set")
|
||||
@ -1095,26 +1153,46 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
value, err := utils.PostPara(r, "value")
|
||||
if err != nil {
|
||||
if err != nil && rewriteType == "add" {
|
||||
utils.SendErrorResponse(w, "HTTP header value not set")
|
||||
return
|
||||
}
|
||||
|
||||
var targetProxyEndpoint *dynamicproxy.ProxyEndpoint
|
||||
if epType == "root" {
|
||||
targetProxyEndpoint = dynamicProxyRouter.Root
|
||||
} else {
|
||||
ep, err := dynamicProxyRouter.LoadProxy(domain)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not exists")
|
||||
return
|
||||
}
|
||||
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not exists")
|
||||
return
|
||||
}
|
||||
|
||||
targetProxyEndpoint = ep
|
||||
//Create a Custom Header Defination type
|
||||
var rewriteDirection dynamicproxy.HeaderDirection
|
||||
if direction == "toOrigin" {
|
||||
rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToUpstream
|
||||
} else if direction == "toClient" {
|
||||
rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToDownstream
|
||||
} else {
|
||||
//Unknown direction
|
||||
utils.SendErrorResponse(w, "header rewrite direction not supported")
|
||||
return
|
||||
}
|
||||
|
||||
isRemove := false
|
||||
if rewriteType == "remove" {
|
||||
isRemove = true
|
||||
}
|
||||
headerRewriteDefination := dynamicproxy.UserDefinedHeader{
|
||||
Key: name,
|
||||
Value: value,
|
||||
Direction: rewriteDirection,
|
||||
IsRemove: isRemove,
|
||||
}
|
||||
|
||||
//Create a new custom header object
|
||||
targetProxyEndpoint.AddUserDefinedHeader(name, value)
|
||||
err = targetProxyEndpoint.AddUserDefinedHeader(&headerRewriteDefination)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to add header rewrite rule: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//Save it (no need reload as header are not handled by dpcore)
|
||||
err = SaveReverseProxyConfig(targetProxyEndpoint)
|
||||
@ -1128,12 +1206,6 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Remove a header from the target endpoint
|
||||
func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) {
|
||||
epType, err := utils.PostPara(r, "type")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "endpoint type not defined")
|
||||
return
|
||||
}
|
||||
|
||||
domain, err := utils.PostPara(r, "domain")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "domain or matching rule not defined")
|
||||
@ -1146,20 +1218,17 @@ func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var targetProxyEndpoint *dynamicproxy.ProxyEndpoint
|
||||
if epType == "root" {
|
||||
targetProxyEndpoint = dynamicProxyRouter.Root
|
||||
} else {
|
||||
ep, err := dynamicProxyRouter.LoadProxy(domain)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not exists")
|
||||
return
|
||||
}
|
||||
|
||||
targetProxyEndpoint = ep
|
||||
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not exists")
|
||||
return
|
||||
}
|
||||
|
||||
targetProxyEndpoint.RemoveUserDefinedHeader(name)
|
||||
err = targetProxyEndpoint.RemoveUserDefinedHeader(name)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to remove header rewrite rule: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = SaveReverseProxyConfig(targetProxyEndpoint)
|
||||
if err != nil {
|
||||
@ -1170,3 +1239,123 @@ func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendOK(w)
|
||||
|
||||
}
|
||||
|
||||
// Handle view or edit HSTS states
|
||||
func HandleHSTSState(w http.ResponseWriter, r *http.Request) {
|
||||
domain, err := utils.PostPara(r, "domain")
|
||||
if err != nil {
|
||||
domain, err = utils.GetPara(r, "domain")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "domain or matching rule not defined")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not exists")
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
//Return current HSTS enable state
|
||||
hstsAge := targetProxyEndpoint.HSTSMaxAge
|
||||
js, _ := json.Marshal(hstsAge)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
return
|
||||
} else if r.Method == http.MethodPost {
|
||||
newMaxAge, err := utils.PostInt(r, "maxage")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "maxage not defeined")
|
||||
return
|
||||
}
|
||||
|
||||
if newMaxAge == 0 || newMaxAge >= 31536000 {
|
||||
targetProxyEndpoint.HSTSMaxAge = int64(newMaxAge)
|
||||
SaveReverseProxyConfig(targetProxyEndpoint)
|
||||
targetProxyEndpoint.UpdateToRuntime()
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid max age given")
|
||||
return
|
||||
}
|
||||
utils.SendOK(w)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
// HandlePermissionPolicy handle read or write to permission policy
|
||||
func HandlePermissionPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
domain, err := utils.PostPara(r, "domain")
|
||||
if err != nil {
|
||||
domain, err = utils.GetPara(r, "domain")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "domain or matching rule not defined")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not exists")
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
type CurrentPolicyState struct {
|
||||
PPEnabled bool
|
||||
CurrentPolicy *permissionpolicy.PermissionsPolicy
|
||||
}
|
||||
|
||||
currentPolicy := permissionpolicy.GetDefaultPermissionPolicy()
|
||||
if targetProxyEndpoint.PermissionPolicy != nil {
|
||||
currentPolicy = targetProxyEndpoint.PermissionPolicy
|
||||
}
|
||||
result := CurrentPolicyState{
|
||||
PPEnabled: targetProxyEndpoint.EnablePermissionPolicyHeader,
|
||||
CurrentPolicy: currentPolicy,
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(result)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
return
|
||||
} else if r.Method == http.MethodPost {
|
||||
//Update the enable state of permission policy
|
||||
enableState, err := utils.PostBool(r, "enable")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid enable state given")
|
||||
return
|
||||
}
|
||||
|
||||
targetProxyEndpoint.EnablePermissionPolicyHeader = enableState
|
||||
SaveReverseProxyConfig(targetProxyEndpoint)
|
||||
targetProxyEndpoint.UpdateToRuntime()
|
||||
utils.SendOK(w)
|
||||
return
|
||||
} else if r.Method == http.MethodPut {
|
||||
//Store the new permission policy
|
||||
newPermissionPolicyJSONString, err := utils.PostPara(r, "pp")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "missing pp (permission policy) paramter")
|
||||
return
|
||||
}
|
||||
|
||||
//Parse the permission policy from JSON string
|
||||
newPermissionPolicy := permissionpolicy.GetDefaultPermissionPolicy()
|
||||
err = json.Unmarshal([]byte(newPermissionPolicyJSONString), &newPermissionPolicy)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "permission policy parse error: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//Save it to file
|
||||
targetProxyEndpoint.PermissionPolicy = newPermissionPolicy
|
||||
SaveReverseProxyConfig(targetProxyEndpoint)
|
||||
targetProxyEndpoint.UpdateToRuntime()
|
||||
utils.SendOK(w)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
37
src/start.go
37
src/start.go
@ -4,6 +4,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -12,6 +13,8 @@ import (
|
||||
"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/forwardproxy"
|
||||
"imuslab.com/zoraxy/mod/ganserv"
|
||||
@ -23,7 +26,7 @@ import (
|
||||
"imuslab.com/zoraxy/mod/sshprox"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||
"imuslab.com/zoraxy/mod/tcpprox"
|
||||
"imuslab.com/zoraxy/mod/streamproxy"
|
||||
"imuslab.com/zoraxy/mod/tlscert"
|
||||
"imuslab.com/zoraxy/mod/webserv"
|
||||
)
|
||||
@ -73,15 +76,22 @@ func startupSequence() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//Create a system wide logger
|
||||
l, err := logger.NewLogger("zr", "./log", *logOutputToFile)
|
||||
if err == nil {
|
||||
SystemWideLogger = l
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//Create a redirection rule table
|
||||
db.NewTable("redirect")
|
||||
redirectAllowRegexp := false
|
||||
db.Read("redirect", "regex", &redirectAllowRegexp)
|
||||
redirectTable, err = redirection.NewRuleTable("./conf/redirect", redirectAllowRegexp)
|
||||
redirectTable, err = redirection.NewRuleTable("./conf/redirect", redirectAllowRegexp, SystemWideLogger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
redirectTable.Logger = SystemWideLogger
|
||||
|
||||
//Create a geodb store
|
||||
geodbStore, err = geodb.NewGeoDb(sysdb, &geodb.StoreOptions{
|
||||
@ -92,6 +102,11 @@ func startupSequence() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//Create a load balance route manager
|
||||
loadbalancer = loadbalance.NewRouteManager(&loadbalance.Options{
|
||||
Geodb: geodbStore,
|
||||
}, SystemWideLogger)
|
||||
|
||||
//Create the access controller
|
||||
accessController, err = access.NewAccessController(&access.Options{
|
||||
Database: sysdb,
|
||||
@ -110,14 +125,6 @@ func startupSequence() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//Create a system wide logger
|
||||
l, err := logger.NewLogger("zr", "./log", *logOutputToFile)
|
||||
if err == nil {
|
||||
SystemWideLogger = l
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//Start the static web server
|
||||
staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{
|
||||
Sysdb: sysdb,
|
||||
@ -229,7 +236,7 @@ func startupSequence() {
|
||||
webSshManager = sshprox.NewSSHProxyManager()
|
||||
|
||||
//Create TCP Proxy Manager
|
||||
tcpProxyManager = tcpprox.NewTCProxy(&tcpprox.Options{
|
||||
streamProxyManager = streamproxy.NewStreamProxy(&streamproxy.Options{
|
||||
Database: sysdb,
|
||||
AccessControlHandler: accessController.DefaultAccessRule.AllowConnectionAccess,
|
||||
})
|
||||
@ -269,6 +276,12 @@ func startupSequence() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
/* Docker UX Optimizer */
|
||||
if runtime.GOOS == "windows" && *runningInDocker {
|
||||
SystemWideLogger.PrintAndLog("WARNING", "Invalid start flag combination: docker=true && runtime.GOOS == windows. Running in docker UX development mode.", nil)
|
||||
}
|
||||
DockerUXOptimizer = dockerux.NewDockerOptimizer(*runningInDocker, SystemWideLogger)
|
||||
|
||||
}
|
||||
|
||||
// This sequence start after everything is initialized
|
||||
|
@ -65,7 +65,7 @@
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Select Country</label>
|
||||
<div id="countrySelector" class="ui fluid search selection dropdown">
|
||||
<div id="countrySelector" class="ui fluid search multiple selection dropdown">
|
||||
<input type="hidden" name="country">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text">Select Country</div>
|
||||
@ -382,7 +382,7 @@
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Select Country</label>
|
||||
<div id="countrySelectorWhitelist" class="ui fluid search selection dropdown">
|
||||
<div id="countrySelectorWhitelist" class="ui fluid search multiple selection dropdown">
|
||||
<input type="hidden" name="country">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text">Select Country</div>
|
||||
@ -1018,42 +1018,71 @@
|
||||
|
||||
function addCountryToBlacklist() {
|
||||
var countryCode = $("#countrySelector").dropdown("get value").toLowerCase();
|
||||
$('#countrySelector').dropdown('clear');
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/blacklist/country/add",
|
||||
data: { cc: countryCode, id: currentEditingAccessRule},
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false);
|
||||
}
|
||||
initBannedCountryList();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// handle error response
|
||||
}
|
||||
});
|
||||
}
|
||||
let ccs = [countryCode];
|
||||
if (countryCode.includes(",")){
|
||||
//Multiple country codes selected
|
||||
//Usually just a few countries a for loop will get the job done
|
||||
ccs = countryCode.split(",");
|
||||
}
|
||||
|
||||
function removeFromBannedList(countryCode){
|
||||
if (confirm("Confirm removing " + getCountryName(countryCode) + " from blacklist?")){
|
||||
countryCode = countryCode.toLowerCase();
|
||||
let counter = 0;
|
||||
for(var i = 0; i < ccs.length; i++){
|
||||
let thisCountryCode = ccs[i];
|
||||
$.ajax({
|
||||
url: "/api/blacklist/country/remove",
|
||||
method: "POST",
|
||||
data: { cc: countryCode, id: currentEditingAccessRule},
|
||||
type: "POST",
|
||||
url: "/api/blacklist/country/add",
|
||||
data: { cc: thisCountryCode, id: currentEditingAccessRule},
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false);
|
||||
}
|
||||
initBannedCountryList();
|
||||
|
||||
if (counter == (ccs.length - 1)){
|
||||
//Last item
|
||||
setTimeout(function(){
|
||||
initBannedCountryList();
|
||||
if (ccs.length == 1){
|
||||
//Single country
|
||||
msgbox(`Added ${getCountryName(ccs[0])} to blacklist`);
|
||||
}else{
|
||||
msgbox(ccs.length + " countries added to blacklist");
|
||||
}
|
||||
|
||||
}, (ccs.length==1)?0:100);
|
||||
}
|
||||
counter++;
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Error removing country from blacklist: " + error);
|
||||
// Handle error response
|
||||
// handle error response
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$('#countrySelector').dropdown('clear');
|
||||
|
||||
}
|
||||
|
||||
function removeFromBannedList(countryCode){
|
||||
countryCode = countryCode.toLowerCase();
|
||||
let countryName = getCountryName(countryCode);
|
||||
$.ajax({
|
||||
url: "/api/blacklist/country/remove",
|
||||
method: "POST",
|
||||
data: { cc: countryCode, id: currentEditingAccessRule},
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false);
|
||||
}else{
|
||||
msgbox(countryName + " removed from blacklist");
|
||||
}
|
||||
initBannedCountryList();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Error removing country from blacklist: " + error);
|
||||
// Handle error response
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addIpBlacklist(){
|
||||
@ -1126,21 +1155,45 @@
|
||||
|
||||
function addCountryToWhitelist() {
|
||||
var countryCode = $("#countrySelectorWhitelist").dropdown("get value").toLowerCase();
|
||||
$('#countrySelectorWhitelist').dropdown('clear');
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/whitelist/country/add",
|
||||
data: { cc: countryCode , id: currentEditingAccessRule},
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false);
|
||||
let ccs = [countryCode];
|
||||
if (countryCode.includes(",")){
|
||||
//Multiple country codes selected
|
||||
//Usually just a few countries a for loop will get the job done
|
||||
ccs = countryCode.split(",");
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
for(var i = 0; i < ccs.length; i++){
|
||||
let thisCountryCode = ccs[i];
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/whitelist/country/add",
|
||||
data: { cc: thisCountryCode , id: currentEditingAccessRule},
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false);
|
||||
}
|
||||
|
||||
if (counter == (ccs.length - 1)){
|
||||
setTimeout(function(){
|
||||
initWhitelistCountryList();
|
||||
if (ccs.length == 1){
|
||||
//Single country
|
||||
msgbox(`Added ${getCountryName(ccs[0])} to whitelist`);
|
||||
}else{
|
||||
msgbox(ccs.length + " countries added to whitelist");
|
||||
}
|
||||
}, (ccs.length==1)?0:100);
|
||||
}
|
||||
counter++;
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// handle error response
|
||||
}
|
||||
initWhitelistCountryList();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// handle error response
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('#countrySelectorWhitelist').dropdown('clear');
|
||||
}
|
||||
|
||||
function removeFromWhiteList(countryCode){
|
||||
|
@ -19,7 +19,7 @@
|
||||
<th>Host</th>
|
||||
<th>Destination</th>
|
||||
<th>Virtual Directory</th>
|
||||
<th>Basic Auth</th>
|
||||
<th style="max-width: 300px;">Advanced Settings</th>
|
||||
<th class="no-sort" style="min-width:150px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -78,7 +78,7 @@
|
||||
vdList += `</div>`;
|
||||
|
||||
if (subd.VirtualDirectories.length == 0){
|
||||
vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;"><i class="check icon"></i> No Virtual Directory</small>`;
|
||||
vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Virtual Directory</small>`;
|
||||
}
|
||||
|
||||
let enableChecked = "checked";
|
||||
@ -104,8 +104,11 @@
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
|
||||
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
||||
<td data-label="" editable="true" datatype="basicauth">
|
||||
${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}
|
||||
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
||||
${subd.RequireBasicAuth?`<i class="ui green check icon"></i> Basic Auth`:``}
|
||||
${subd.RequireBasicAuth && subd.RequireRateLimit?"<br>":""}
|
||||
${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``}
|
||||
${!subd.RequireBasicAuth && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""}
|
||||
</td>
|
||||
<td class="center aligned ignoremw" editable="true" datatype="action" data-label="">
|
||||
<div class="ui toggle tiny fitted checkbox" style="margin-bottom: -0.5em; margin-right: 0.4em;" title="Enable / Disable Rule">
|
||||
@ -243,9 +246,9 @@
|
||||
|
||||
input = `
|
||||
<div class="ui mini fluid input">
|
||||
<input type="text" class="Domain" value="${domain}">
|
||||
<input type="text" class="Domain" onchange="cleanProxyTargetValue(this)" value="${domain}">
|
||||
</div>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<div class="ui checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox" class="RequireTLS" ${tls}>
|
||||
<label>Require TLS<br>
|
||||
<small>Proxy target require HTTPS connection</small></label>
|
||||
@ -254,7 +257,8 @@
|
||||
<input type="checkbox" class="SkipCertValidations" ${checkstate}>
|
||||
<label>Skip Verification<br>
|
||||
<small>Check this if proxy target is using self signed certificates</small></label>
|
||||
</div>
|
||||
</div><br>
|
||||
<!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="purple server icon"></i> Load Balance</button> -->
|
||||
`;
|
||||
column.empty().append(input);
|
||||
}else if (datatype == "vdir"){
|
||||
@ -263,11 +267,11 @@
|
||||
<i class="ui yellow folder icon"></i> Edit Virtual Directories
|
||||
</button>`);
|
||||
|
||||
}else if (datatype == "basicauth"){
|
||||
}else if (datatype == "advanced"){
|
||||
let requireBasicAuth = payload.RequireBasicAuth;
|
||||
let checkstate = "";
|
||||
let basicAuthCheckstate = "";
|
||||
if (requireBasicAuth){
|
||||
checkstate = "checked";
|
||||
basicAuthCheckstate = "checked";
|
||||
}
|
||||
|
||||
let skipWebSocketOriginCheck = payload.SkipWebSocketOriginCheck;
|
||||
@ -276,16 +280,35 @@
|
||||
wsCheckstate = "checked";
|
||||
}
|
||||
|
||||
let requireRateLimit = payload.RequireRateLimit;
|
||||
let rateLimitCheckState = "";
|
||||
if (requireRateLimit){
|
||||
rateLimitCheckState = "checked";
|
||||
}
|
||||
let rateLimit = payload.RateLimit;
|
||||
if (rateLimit == 0){
|
||||
//This value is not set. Make it default to 100
|
||||
rateLimit = 100;
|
||||
}
|
||||
let rateLimitDisableState = "";
|
||||
if (!payload.RequireRateLimit){
|
||||
rateLimitDisableState = "disabled";
|
||||
}
|
||||
|
||||
column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="RequireBasicAuth" ${checkstate}>
|
||||
<input type="checkbox" class="RequireBasicAuth" ${basicAuthCheckstate}>
|
||||
<label>Require Basic Auth</label>
|
||||
</div>
|
||||
<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button>
|
||||
<br>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button>
|
||||
<br>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>
|
||||
|
||||
<div class="ui basic advance segment" style="padding: 0.4em !important; border-radius: 0.4em;">
|
||||
<div class="ui endpointAdvanceConfig accordion" style="padding-right: 0.6em;">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
Advance Configs
|
||||
Security Options
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
@ -294,13 +317,34 @@
|
||||
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||
</div>
|
||||
<br>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>
|
||||
<!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="blue server icon"></i> Load Balance</button> -->
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
|
||||
<label>Require Rate Limit<br>
|
||||
<small>Check this to enable rate limit on this inbound hostname</small></label>
|
||||
</div><br>
|
||||
<div class="ui mini right labeled fluid input ${rateLimitDisableState}" style="margin-top: 0.4em;">
|
||||
<input type="number" class="RateLimit" value="${rateLimit}" min="1" >
|
||||
<label class="ui basic label">
|
||||
req / sec / IP
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
`);
|
||||
|
||||
} else if (datatype == "ratelimit"){
|
||||
|
||||
column.empty().append(`
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="RequireRateLimit" ${checkstate}>
|
||||
<label>Require Rate Limit</label>
|
||||
</div>
|
||||
<div class="ui mini fluid input">
|
||||
<input type="number" class="RateLimit" value="${rateLimit}" placeholder="100" min="1" max="1000" >
|
||||
</div>
|
||||
`);
|
||||
|
||||
}else if (datatype == 'action'){
|
||||
column.empty().append(`
|
||||
<button title="Save" onclick="saveProxyInlineEdit('${uuid.hexEncode()}');" class="ui basic small icon circular button inlineEditActionBtn"><i class="ui green save icon"></i></button>
|
||||
@ -331,6 +375,17 @@
|
||||
$("#httpProxyList").find(".editBtn").addClass("disabled");
|
||||
}
|
||||
|
||||
//handleToggleRateLimitInput will get trigger if the "require rate limit" checkbox
|
||||
// is changed and toggle the disable state of the rate limit input field
|
||||
function handleToggleRateLimitInput(){
|
||||
let isRateLimitEnabled = $("#httpProxyList input.RequireRateLimit")[0].checked;
|
||||
if (isRateLimitEnabled){
|
||||
$("#httpProxyList input.RateLimit").parent().removeClass("disabled");
|
||||
}else{
|
||||
$("#httpProxyList input.RateLimit").parent().addClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
function exitProxyInlineEdit(){
|
||||
listProxyEndpoints();
|
||||
$("#httpProxyList").find(".editBtn").removeClass("disabled");
|
||||
@ -348,6 +403,8 @@
|
||||
let requireTLS = $(row).find(".RequireTLS")[0].checked;
|
||||
let skipCertValidations = $(row).find(".SkipCertValidations")[0].checked;
|
||||
let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
|
||||
let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
|
||||
let rateLimit = $(row).find(".RateLimit").val();
|
||||
let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
|
||||
let bypassWebsocketOrigin = $(row).find(".SkipWebSocketOriginCheck")[0].checked;
|
||||
console.log(newDomain, requireTLS, skipCertValidations, requireBasicAuth)
|
||||
@ -364,6 +421,8 @@
|
||||
"tlsval": skipCertValidations,
|
||||
"bpwsorg" : bypassWebsocketOrigin,
|
||||
"bauth" :requireBasicAuth,
|
||||
"rate" :requireRateLimit,
|
||||
"ratenum" :rateLimit,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error !== undefined){
|
||||
@ -375,6 +434,21 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
||||
//and auto select TLS checkbox if https:// exists
|
||||
function cleanProxyTargetValue(input){
|
||||
let targetDomain = $(input).val().trim();
|
||||
if (targetDomain.startsWith("http://")){
|
||||
targetDomain = targetDomain.substr(7);
|
||||
$(input).val(targetDomain);
|
||||
$("#httpProxyList input.RequireTLS").parent().checkbox("set unchecked");
|
||||
}else if (targetDomain.startsWith("https://")){
|
||||
targetDomain = targetDomain.substr(8);
|
||||
$(input).val(targetDomain);
|
||||
$("#httpProxyList input.RequireTLS").parent().checkbox("set checked");
|
||||
}
|
||||
}
|
||||
|
||||
/* button events */
|
||||
function editBasicAuthCredentials(uuid){
|
||||
@ -406,6 +480,7 @@
|
||||
$("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid);
|
||||
}
|
||||
|
||||
//Open the custom header editor
|
||||
function editCustomHeaders(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
@ -414,6 +489,15 @@
|
||||
showSideWrapper("snippet/customHeaders.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
//Open the load balance option
|
||||
function editLoadBalanceOptions(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/loadBalancer.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function handleProxyRuleToggle(object){
|
||||
let endpointUUID = $(object).attr("eptuuid");
|
||||
let isChecked = object.checked;
|
||||
@ -438,10 +522,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
/* Access List handling */
|
||||
|
||||
|
||||
|
||||
//Bind on tab switch events
|
||||
tabSwitchEventBind["httprp"] = function(){
|
||||
listProxyEndpoints();
|
||||
|
@ -258,7 +258,7 @@
|
||||
|
||||
setTimeout(function(){
|
||||
//Update the checkbox
|
||||
msgbox("Proxy Root Updated");
|
||||
msgbox("Default Site Updated");
|
||||
}, 100);
|
||||
|
||||
})
|
||||
|
@ -26,9 +26,12 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Target IP Address or Domain Name with port</label>
|
||||
<input type="text" id="proxyDomain" onchange="autoCheckTls(this.value);">
|
||||
<input type="text" id="proxyDomain" onchange="autoCheckTls(this.value);">
|
||||
<small>E.g. 192.168.0.101:8000 or example.com</small>
|
||||
</div>
|
||||
<div class="field dockerOptimizations" style="display:none;">
|
||||
<button style="margin-top: -2em;" class="ui basic small button" onclick="openDockerContainersList();"><i class="blue docker icon"></i> Pick from Docker Containers</button>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="reqTls">
|
||||
@ -73,6 +76,22 @@
|
||||
<label>Allow plain HTTP access<br><small>Allow this subdomain to be connected without TLS (Require HTTP server enabled on port 80)</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="requireRateLimit">
|
||||
<label>Require Rate Limit<br><small>This proxy endpoint will be rate limited.</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Rate Limit</label>
|
||||
<div class="ui fluid right labeled input">
|
||||
<input type="number" id="proxyRateLimit" placeholder="100" min="1" max="1000" value="100">
|
||||
<div class="ui basic label">
|
||||
req / sec / IP
|
||||
</div>
|
||||
</div>
|
||||
<small>Return a 429 error code if request rate exceed the rate limit.</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="requireBasicAuth">
|
||||
@ -147,6 +166,8 @@
|
||||
var skipTLSValidation = $("#skipTLSValidation")[0].checked;
|
||||
var bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked;
|
||||
var requireBasicAuth = $("#requireBasicAuth")[0].checked;
|
||||
var proxyRateLimit = $("#proxyRateLimit").val();
|
||||
var requireRateLimit = $("#requireRateLimit")[0].checked;
|
||||
var skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked;
|
||||
var accessRuleToUse = $("#newProxyRuleAccessFilter").val();
|
||||
|
||||
@ -176,6 +197,8 @@
|
||||
bpwsorg: skipWebSocketOriginCheck,
|
||||
bypassGlobalTLS: bypassGlobalTLS,
|
||||
bauth: requireBasicAuth,
|
||||
rate: requireRateLimit,
|
||||
ratenum: proxyRateLimit,
|
||||
cred: JSON.stringify(credentials),
|
||||
access: accessRuleToUse,
|
||||
},
|
||||
@ -264,6 +287,16 @@
|
||||
}
|
||||
$("#requireBasicAuth").on('change', toggleBasicAuth);
|
||||
toggleBasicAuth();
|
||||
|
||||
function toggleRateLimit() {
|
||||
if ($("#requireRateLimit").parent().checkbox("is checked")) {
|
||||
$("#proxyRateLimit").parent().parent().removeClass("disabled");
|
||||
} else {
|
||||
$("#proxyRateLimit").parent().parent().addClass("disabled");
|
||||
}
|
||||
}
|
||||
$("#requireRateLimit").on('change', toggleRateLimit);
|
||||
toggleRateLimit();
|
||||
|
||||
|
||||
/*
|
||||
@ -397,9 +430,30 @@
|
||||
initNewProxyRuleAccessDropdownList();
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
$("#advanceProxyRules").accordion();
|
||||
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||
});
|
||||
/* Docker Optimizations */
|
||||
function initDockerUXOptimizations(){
|
||||
$.get("/api/docker/available", function(dockerAvailable){
|
||||
if (dockerAvailable){
|
||||
$(".dockerOptimizations").show();
|
||||
}else{
|
||||
$(".dockerOptimizations").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
initDockerUXOptimizations();
|
||||
|
||||
function openDockerContainersList(){
|
||||
showSideWrapper('snippet/dockerContainersList.html');
|
||||
}
|
||||
|
||||
function addContainerItem(item) {
|
||||
$('#rootname').val(item.name);
|
||||
$('#proxyDomain').val(`${item.ip}:${item.port}`)
|
||||
hideSideWrapper(true);
|
||||
}
|
||||
|
||||
/* UI Element Initialization */
|
||||
$("#advanceProxyRules").accordion();
|
||||
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||
|
||||
</script>
|
@ -68,10 +68,11 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui divider"></div>
|
||||
<h4>Global Settings</h4>
|
||||
<p>Inbound Port (Port to be proxied)</p>
|
||||
<p>Inbound Port (Reverse Proxy Listening Port)</p>
|
||||
<div class="ui action fluid notloopbackOnly input">
|
||||
<small id="applyButtonReminder">Click "Apply" button to confirm listening port changes</small>
|
||||
<input type="text" id="incomingPort" placeholder="Incoming Port" value="80">
|
||||
<button class="ui basic notloopbackOnly button" onclick="handlePortChange();"><i class="ui green checkmark icon"></i> Apply</button>
|
||||
<button class="ui green notloopbackOnly button" style="background: linear-gradient(60deg, #27e7ff, #00ca52);" onclick="handlePortChange();"><i class="ui checkmark icon"></i> Apply</button>
|
||||
</div>
|
||||
<br>
|
||||
<div id="tls" class="ui toggle notloopbackOnly checkbox">
|
||||
@ -160,6 +161,7 @@
|
||||
</div>
|
||||
<script>
|
||||
let loopbackProxiedInterface = false;
|
||||
let currentListeningPort = 80;
|
||||
$(".advanceSettings").accordion();
|
||||
|
||||
//Initial the start stop button if this is reverse proxied
|
||||
@ -176,6 +178,8 @@
|
||||
//Get the latest server status from proxy server
|
||||
function initRPStaste(){
|
||||
$.get("/api/proxy/status", function(data){
|
||||
$("#incomingPort").off("change");
|
||||
|
||||
if (data.Running == true){
|
||||
$("#startbtn").addClass("disabled");
|
||||
if (!loopbackProxiedInterface){
|
||||
@ -194,6 +198,15 @@
|
||||
$("#serverstatus").removeClass("green");
|
||||
}
|
||||
$("#incomingPort").val(data.Option.Port);
|
||||
currentListeningPort = data.Option.Port;
|
||||
$("#incomingPort").on("change", function(){
|
||||
let newPortValue = $("#incomingPort").val().trim();
|
||||
if (currentListeningPort != newPortValue){
|
||||
$("#applyButtonReminder").show();
|
||||
}else{
|
||||
$("#applyButtonReminder").hide();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -353,8 +366,11 @@
|
||||
msgbox(data.error, false, 5000);
|
||||
return;
|
||||
}
|
||||
msgbox("Setting Updated");
|
||||
msgbox("Listening Port Updated");
|
||||
initRPStaste();
|
||||
|
||||
//Hide the reminder text
|
||||
$("#applyButtonReminder").hide();
|
||||
});
|
||||
}
|
||||
|
||||
|
384
src/web/components/streamprox.html
Normal file
384
src/web/components/streamprox.html
Normal file
@ -0,0 +1,384 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Stream Proxy</h2>
|
||||
<p>Proxy traffic flow on layer 3 via TCP or UDP</p>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment" style="margin-top: 0;">
|
||||
<h3>TCP / UDP Proxy Rules</h3>
|
||||
<p>A list of TCP / UDP proxy rules created on this host.</p>
|
||||
<div style="overflow-x: auto; ">
|
||||
<table id="proxyTable" class="ui celled basic unstackable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Listening Address</th>
|
||||
<th>Target Address</th>
|
||||
<th>Mode</th>
|
||||
<th>Timeout (s)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<br>
|
||||
<button class="ui basic right floated button" onclick="initProxyConfigList();" title="Refresh List"><i class="ui green refresh icon"></i>Refresh</button>
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment" id="addproxyConfig">
|
||||
<h3>Add or Edit Stream Proxy</h3>
|
||||
<p>Create or edit a new stream proxy instance</p>
|
||||
<form id="streamProxyForm" class="ui form">
|
||||
<div class="field" style="display:none;">
|
||||
<label>UUID</label>
|
||||
<input type="text" name="uuid">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input type="text" name="name" placeholder="Config Name">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Listening Address with Port</label>
|
||||
<input type="text" name="listenAddr" placeholder="">
|
||||
<small>Address to listen on this host. e.g. :25565 or 127.0.0.1:25565. <br>
|
||||
If you are using Docker, you will also need to expose this port to host network.</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Proxy Target Address with Port</label>
|
||||
<input type="text" name="proxyAddr" placeholder="">
|
||||
<small>Server address to forward TCP / UDP package. e.g. 192.168.1.100:25565</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Timeout (s)</label>
|
||||
<input type="text" name="timeout" placeholder="" value="10">
|
||||
<small>Connection timeout in seconds</small>
|
||||
</div>
|
||||
<Br>
|
||||
<div class="field">
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" tabindex="0" name="useTCP" class="hidden">
|
||||
<label>Enable TCP<br>
|
||||
<small>Forward TCP request on this listening socket</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" tabindex="0" name="useUDP" class="hidden">
|
||||
<label>Enable UDP<br>
|
||||
<small>Forward UDP request on this listening socket</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<button id="addStreamProxyButton" class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
|
||||
<button id="editStreamProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);" style="display:none;"><i class="ui green check icon"></i> Update</button>
|
||||
<button class="ui basic red button" onclick="event.preventDefault(); cancelStreamProxyEdit(event);"><i class="ui red remove icon"></i> Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
let editingStreamProxyConfigUUID = ""; //The current editing TCP Proxy config UUID
|
||||
|
||||
$("#streamProxyForm .dropdown").dropdown();
|
||||
$('#streamProxyForm').on('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
//Check if update mode
|
||||
if ($("#editStreamProxyButton").is(":visible")){
|
||||
confirmEditTCPProxyConfig(event);
|
||||
return;
|
||||
}
|
||||
|
||||
var form = $(this);
|
||||
|
||||
var formValid = validateTCPProxyConfig(form);
|
||||
if (!formValid){
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the AJAX POST request
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/streamprox/config/add',
|
||||
data: form.serialize(),
|
||||
success: function(response) {
|
||||
if (response.error) {
|
||||
msgbox(response.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Config Added");
|
||||
}
|
||||
clearStreamProxyAddEditForm();
|
||||
initProxyConfigList();
|
||||
},
|
||||
error: function() {
|
||||
msgbox('An error occurred while processing the request', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function clearStreamProxyAddEditForm(){
|
||||
$('#streamProxyForm input, #streamProxyForm select').val('');
|
||||
$('#streamProxyForm select').dropdown('clear');
|
||||
$("#streamProxyForm input[name=timeout]").val(10);
|
||||
$("#streamProxyForm .toggle.checkbox").checkbox("set unchecked");
|
||||
}
|
||||
|
||||
function cancelStreamProxyEdit(event=undefined) {
|
||||
clearStreamProxyAddEditForm();
|
||||
$("#addStreamProxyButton").show();
|
||||
$("#editStreamProxyButton").hide();
|
||||
}
|
||||
|
||||
function validateTCPProxyConfig(form){
|
||||
//Check if name is filled. If not, generate a random name for it
|
||||
var name = form.find('input[name="name"]').val()
|
||||
if (name == ""){
|
||||
let randomName = "Proxy Rule (#" + Math.round(Date.now()/1000) + ")";
|
||||
form.find('input[name="name"]').val(randomName);
|
||||
}
|
||||
|
||||
// Validate timeout is an integer
|
||||
var timeout = parseInt(form.find('input[name="timeout"]').val());
|
||||
if (form.find('input[name="timeout"]').val() == ""){
|
||||
//Not set. Assign a random one to it
|
||||
form.find('input[name="timeout"]').val("10");
|
||||
timeout = 10;
|
||||
}
|
||||
|
||||
if (isNaN(timeout)) {
|
||||
form.find('input[name="timeout"]').parent().addClass("error");
|
||||
msgbox('Timeout must be a valid integer', false, 5000);
|
||||
return false;
|
||||
}else{
|
||||
form.find('input[name="timeout"]').parent().removeClass("error");
|
||||
}
|
||||
|
||||
// Validate mode is selected
|
||||
var mode = form.find('select[name="mode"]').val();
|
||||
if (mode === '') {
|
||||
form.find('select[name="mode"]').parent().addClass("error");
|
||||
msgbox('Please select a mode', false, 5000);
|
||||
return false;
|
||||
}else{
|
||||
form.find('select[name="mode"]').parent().removeClass("error");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderProxyConfigs(proxyConfigs) {
|
||||
var tableBody = $('#proxyTable tbody');
|
||||
tableBody.empty();
|
||||
if (proxyConfigs.length === 0) {
|
||||
var noResultsRow = $('<tr><td colspan="7"><i class="green check circle icon"></i>No Proxy Configs</td></tr>');
|
||||
tableBody.append(noResultsRow);
|
||||
} else {
|
||||
|
||||
proxyConfigs.forEach(function(config) {
|
||||
var runningLogo = 'Stopped';
|
||||
var runningClass = "stopped";
|
||||
var startButton = `<button onclick="startStreamProx('${config.UUID}');" class="ui basic mini circular icon button" title="Start Proxy"><i class="green play icon"></i></button>`;
|
||||
if (config.Running){
|
||||
runningLogo = 'Running';
|
||||
startButton = `<button onclick="stopStreamProx('${config.UUID}');" class="ui basic mini circular icon button" title="Stop Proxy"><i class="red stop icon"></i></button>`;
|
||||
runningClass = "running"
|
||||
}
|
||||
|
||||
var modeText = [];
|
||||
if (config.UseTCP){
|
||||
modeText.push("TCP")
|
||||
}
|
||||
|
||||
if (config.UseUDP){
|
||||
modeText.push("UDP")
|
||||
}
|
||||
|
||||
modeText = modeText.join(" & ")
|
||||
|
||||
var thisConfig = encodeURIComponent(JSON.stringify(config));
|
||||
|
||||
var row = $(`<tr class="streamproxConfig ${runningClass}" uuid="${config.UUID}" config="${thisConfig}">`);
|
||||
row.append($('<td>').html(`
|
||||
${config.Name}
|
||||
<div class="statusText">${runningLogo}</div>`));
|
||||
row.append($('<td>').text(config.ListeningAddress));
|
||||
row.append($('<td>').text(config.ProxyTargetAddr));
|
||||
row.append($('<td>').text(modeText));
|
||||
row.append($('<td>').text(config.Timeout));
|
||||
row.append($('<td>').html(`
|
||||
${startButton}
|
||||
<button onclick="editTCPProxyConfig('${config.UUID}');" class="ui circular basic mini icon button" title="Edit Config"><i class="edit icon"></i></button>
|
||||
<button onclick="deleteTCPProxyConfig('${config.UUID}');" class="ui circular red basic mini icon button" title="Delete Config"><i class="trash icon"></i></button>
|
||||
`));
|
||||
tableBody.append(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getConfigDetailsFromDOM(configUUID){
|
||||
let thisConfig = null;
|
||||
$(".streamproxConfig").each(function(){
|
||||
let uuid = $(this).attr("uuid");
|
||||
if (configUUID == uuid){
|
||||
//This is the one we are looking for
|
||||
thisConfig = JSON.parse(decodeURIComponent($(this).attr("config")));
|
||||
}
|
||||
});
|
||||
return thisConfig;
|
||||
}
|
||||
|
||||
function editTCPProxyConfig(configUUID){
|
||||
let targetConfig = getConfigDetailsFromDOM(configUUID);
|
||||
if (targetConfig != null){
|
||||
$("#addStreamProxyButton").hide();
|
||||
$("#editStreamProxyButton").show();
|
||||
$.each(targetConfig, function(key, value) {
|
||||
var field;
|
||||
if (key == "UseTCP"){
|
||||
let checkboxEle = $("#streamProxyForm input[name=useTCP]").parent();
|
||||
if (value === true){
|
||||
$(checkboxEle).checkbox("set checked");
|
||||
}else{
|
||||
$(checkboxEle).checkbox("set unchecked");
|
||||
}
|
||||
return;
|
||||
}else if (key == "UseUDP"){
|
||||
let checkboxEle = $("#streamProxyForm input[name=useUDP]").parent();
|
||||
if (value === true){
|
||||
$(checkboxEle).checkbox("set checked");
|
||||
}else{
|
||||
$(checkboxEle).checkbox("set unchecked");
|
||||
}
|
||||
return;
|
||||
}else if (key == "ListeningAddress"){
|
||||
field = $("#streamProxyForm input[name=listenAddr]");
|
||||
}else if (key == "ProxyTargetAddr"){
|
||||
field = $("#streamProxyForm input[name=proxyAddr]");
|
||||
}else if (key == "UUID"){
|
||||
field = $("#streamProxyForm input[name=uuid]");
|
||||
}else if (key == "Name"){
|
||||
field = $("#streamProxyForm input[name=name]");
|
||||
}else if (key == "Timeout"){
|
||||
field = $("#streamProxyForm input[name=timeout]");
|
||||
}
|
||||
|
||||
if (field != undefined && field.length > 0) {
|
||||
field.val(value);
|
||||
}
|
||||
});
|
||||
editingStreamProxyConfigUUID = configUUID;
|
||||
}else{
|
||||
msgbox("Unable to load target config", false);
|
||||
}
|
||||
}
|
||||
|
||||
function confirmEditTCPProxyConfig(event){
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
var form = $("#streamProxyForm");
|
||||
|
||||
var formValid = validateTCPProxyConfig(form);
|
||||
if (!formValid){
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the AJAX POST request
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/streamprox/config/edit',
|
||||
method: "POST",
|
||||
data: {
|
||||
uuid: $("#streamProxyForm input[name=uuid]").val().trim(),
|
||||
name: $("#streamProxyForm input[name=name]").val().trim(),
|
||||
listenAddr: $("#streamProxyForm input[name=listenAddr]").val().trim(),
|
||||
proxyAddr: $("#streamProxyForm input[name=proxyAddr]").val().trim(),
|
||||
useTCP: $("#streamProxyForm input[name=useTCP]")[0].checked ,
|
||||
useUDP: $("#streamProxyForm input[name=useUDP]")[0].checked ,
|
||||
timeout: parseInt($("#streamProxyForm input[name=timeout]").val().trim()),
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.error) {
|
||||
msgbox(response.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Config Updated");
|
||||
}
|
||||
initProxyConfigList();
|
||||
cancelStreamProxyEdit();
|
||||
clearStreamProxyAddEditForm();
|
||||
|
||||
},
|
||||
error: function() {
|
||||
msgbox('An error occurred while processing the request', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteTCPProxyConfig(configUUID){
|
||||
$.ajax({
|
||||
url: "/api/streamprox/config/delete",
|
||||
method: "POST",
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Proxy Config Removed");
|
||||
initProxyConfigList();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Start a TCP proxy by their config UUID
|
||||
function startStreamProx(configUUID){
|
||||
$.ajax({
|
||||
url: "/api/streamprox/config/start",
|
||||
method: "POST",
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Service Started");
|
||||
initProxyConfigList();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
//Stop a TCP proxy by their config UUID
|
||||
function stopStreamProx(configUUID){
|
||||
$.ajax({
|
||||
url: "/api/streamprox/config/stop",
|
||||
method: "POST",
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Service Stopped");
|
||||
initProxyConfigList();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function initProxyConfigList(){
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/api/streamprox/config/list',
|
||||
success: function(response) {
|
||||
renderProxyConfigs(response);
|
||||
},
|
||||
error: function() {
|
||||
msgbox('Unable to load proxy configs', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
initProxyConfigList();
|
||||
</script>
|
||||
</div>
|
@ -1,431 +0,0 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>TCP Proxy</h2>
|
||||
<p>Proxy traffic flow on layer 3 via TCP/IP</p>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment" style="margin-top: 0;">
|
||||
<h4>TCP Proxy Rules</h4>
|
||||
<p>A list of TCP proxy rules created on this host. To enable them, use the toggle button on the right.</p>
|
||||
<div style="overflow-x: auto; min-height: 400px;">
|
||||
<table id="proxyTable" class="ui celled unstackable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Port/Addr A</th>
|
||||
<th>Port/Addr B</th>
|
||||
<th>Mode</th>
|
||||
<th>Timeout (s)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button class="ui basic right floated button" onclick="initProxyConfigList();" title="Refresh List"><i class="ui green refresh icon"></i>Refresh</button>
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment" id="addproxyConfig">
|
||||
<h4>Add or Edit TCP Proxy</h4>
|
||||
<p>Create or edit a new proxy instance</p>
|
||||
<form id="tcpProxyForm" class="ui form">
|
||||
<div class="field" style="display:none;">
|
||||
<label>UUID</label>
|
||||
<input type="text" name="uuid">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input type="text" name="name" placeholder="Config Name">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Port A</label>
|
||||
<input type="text" name="porta" placeholder="First address or port">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Port B</label>
|
||||
<input type="text" name="portb" placeholder="Second address or port">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Timeout (s)</label>
|
||||
<input type="text" name="timeout" placeholder="Timeout (s)">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Mode</label>
|
||||
<select name="mode" class="ui dropdown">
|
||||
<option value="">Select Mode</option>
|
||||
<option value="listen">Listen</option>
|
||||
<option value="transport">Transport</option>
|
||||
<option value="starter">Starter</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="addTcpProxyButton" class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
|
||||
<button id="editTcpProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);" style="display:none;"><i class="ui green check icon"></i> Update</button>
|
||||
<button class="ui basic red button" onclick="event.preventDefault(); cancelTCPProxyEdit(event);"><i class="ui red remove icon"></i> Cancel</button>
|
||||
<div class="ui basic inverted segment" style="background: var(--theme_background_inverted); border-radius: 0.6em;">
|
||||
<p>TCP Proxy support the following TCP sockets proxy modes</p>
|
||||
<table class="ui celled padded inverted basic table">
|
||||
<thead>
|
||||
<tr><th class="single line">Mode</th>
|
||||
<th>Public-IP</th>
|
||||
<th>Concurrent Access</th>
|
||||
<th>Flow Diagram</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 class="ui center aligned inverted header">Transport</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui green check icon"></i><br>
|
||||
A: <i class="ui remove icon"></i><br>
|
||||
B: <i class="ui green check icon"></i> (or same LAN)<br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui green check icon"></i>
|
||||
</td>
|
||||
<td>Port A (e.g. 25565) <i class="arrow right icon"></i> Server<br>
|
||||
Server <i class="arrow right icon"></i> Port B (e.g. 192.168.0.2:25565)<br>
|
||||
<small>Traffic from Port A will be forward to Port B's (IP if provided and) Port</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 class="ui center aligned inverted header">Listen</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui green check icon"></i><br>
|
||||
A: <i class="ui remove icon"></i><br>
|
||||
B: <i class="ui remove icon"></i><br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui red times icon"></i>
|
||||
</td>
|
||||
<td>Port A (e.g. 8080) <i class="arrow right icon"></i> Server<br>
|
||||
Port B (e.g. 8081) <i class="arrow right icon"></i> Server<br>
|
||||
<small>Server will act as a bridge to proxy traffic between Port A and B</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 class="ui center aligned inverted header">Starter</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui times icon"></i><br>
|
||||
A: <i class="ui green check icon"></i><br>
|
||||
B: <i class="ui green check icon"></i><br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui red times icon"></i>
|
||||
</td>
|
||||
<td>Server <i class="arrow right icon"></i> Port A (e.g. remote.local.:8080) <br>
|
||||
Server <i class="arrow right icon"></i> Port B (e.g. recv.local.:8081) <br>
|
||||
<small>Port A and B will be actively bridged</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
let editingTCPProxyConfigUUID = ""; //The current editing TCP Proxy config UUID
|
||||
|
||||
$("#tcpProxyForm .dropdown").dropdown();
|
||||
$('#tcpProxyForm').on('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
//Check if update mode
|
||||
if ($("#editTcpProxyButton").is(":visible")){
|
||||
confirmEditTCPProxyConfig(event);
|
||||
return;
|
||||
}
|
||||
|
||||
var form = $(this);
|
||||
|
||||
var formValid = validateTCPProxyConfig(form);
|
||||
if (!formValid){
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the AJAX POST request
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/tcpprox/config/add',
|
||||
data: form.serialize(),
|
||||
success: function(response) {
|
||||
if (response.error) {
|
||||
msgbox(response.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Config Added");
|
||||
}
|
||||
clearTCPProxyAddEditForm();
|
||||
initProxyConfigList();
|
||||
$("#addproxyConfig").slideUp("fast");
|
||||
},
|
||||
error: function() {
|
||||
msgbox('An error occurred while processing the request', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function clearTCPProxyAddEditForm(){
|
||||
$('#tcpProxyForm input, #tcpProxyForm select').val('');
|
||||
$('#tcpProxyForm select').dropdown('clear');
|
||||
}
|
||||
|
||||
function cancelTCPProxyEdit(event=undefined) {
|
||||
clearTCPProxyAddEditForm();
|
||||
$("#addTcpProxyButton").show();
|
||||
$("#editTcpProxyButton").hide();
|
||||
}
|
||||
|
||||
function validateTCPProxyConfig(form){
|
||||
//Check if name is filled. If not, generate a random name for it
|
||||
var name = form.find('input[name="name"]').val()
|
||||
if (name == ""){
|
||||
let randomName = "Proxy Rule (#" + Math.round(Date.now()/1000) + ")";
|
||||
form.find('input[name="name"]').val(randomName);
|
||||
}
|
||||
|
||||
// Validate timeout is an integer
|
||||
var timeout = parseInt(form.find('input[name="timeout"]').val());
|
||||
if (form.find('input[name="timeout"]').val() == ""){
|
||||
//Not set. Assign a random one to it
|
||||
form.find('input[name="timeout"]').val("10");
|
||||
timeout = 10;
|
||||
}
|
||||
|
||||
if (isNaN(timeout)) {
|
||||
form.find('input[name="timeout"]').parent().addClass("error");
|
||||
msgbox('Timeout must be a valid integer', false, 5000);
|
||||
return false;
|
||||
}else{
|
||||
form.find('input[name="timeout"]').parent().removeClass("error");
|
||||
}
|
||||
|
||||
// Validate mode is selected
|
||||
var mode = form.find('select[name="mode"]').val();
|
||||
if (mode === '') {
|
||||
form.find('select[name="mode"]').parent().addClass("error");
|
||||
msgbox('Please select a mode', false, 5000);
|
||||
return false;
|
||||
}else{
|
||||
form.find('select[name="mode"]').parent().removeClass("error");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderProxyConfigs(proxyConfigs) {
|
||||
var tableBody = $('#proxyTable tbody');
|
||||
tableBody.empty();
|
||||
if (proxyConfigs.length === 0) {
|
||||
var noResultsRow = $('<tr><td colspan="7"><i class="green check circle icon"></i>No Proxy Configs</td></tr>');
|
||||
tableBody.append(noResultsRow);
|
||||
} else {
|
||||
|
||||
proxyConfigs.forEach(function(config) {
|
||||
var runningLogo = 'Stopped';
|
||||
var runningClass = "stopped";
|
||||
var startButton = `<button onclick="startTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="green play icon"></i> Start Proxy</button>`;
|
||||
if (config.Running){
|
||||
runningLogo = 'Running';
|
||||
startButton = `<button onclick="stopTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="red stop icon"></i> Stop Proxy</button>`;
|
||||
runningClass = "running"
|
||||
}
|
||||
|
||||
var modeText = "Unknown";
|
||||
if (config.Mode == 0){
|
||||
modeText = "Listen";
|
||||
}else if (config.Mode == 1){
|
||||
modeText = "Transport";
|
||||
}else if (config.Mode == 2){
|
||||
modeText = "Starter";
|
||||
}
|
||||
|
||||
var thisConfig = encodeURIComponent(JSON.stringify(config));
|
||||
|
||||
var row = $(`<tr class="tcproxConfig ${runningClass}" uuid="${config.UUID}" config="${thisConfig}">`);
|
||||
row.append($('<td>').html(`
|
||||
${config.Name}
|
||||
<div class="statusText">${runningLogo}</div>`));
|
||||
row.append($('<td>').text(config.PortA));
|
||||
row.append($('<td>').text(config.PortB));
|
||||
row.append($('<td>').text(modeText));
|
||||
row.append($('<td>').text(config.Timeout));
|
||||
row.append($('<td>').html(`
|
||||
<div class="ui basic vertical fluid tiny buttons">
|
||||
<button class="ui button" onclick="validateProxyConfig('${config.UUID}', this);" title="Validate Config"><i class="teal question circle outline icon"></i> CXN Test</button>
|
||||
${startButton}
|
||||
<button onclick="editTCPProxyConfig('${config.UUID}');" class="ui button" title="Edit Config"><i class="edit icon"></i> Edit </button>
|
||||
<button onclick="deleteTCPProxyConfig('${config.UUID}');" class="ui red basic button" title="Delete Config"><i class="trash icon"></i> Remove</button>
|
||||
</div>
|
||||
`));
|
||||
tableBody.append(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getConfigDetailsFromDOM(configUUID){
|
||||
let thisConfig = null;
|
||||
$(".tcproxConfig").each(function(){
|
||||
let uuid = $(this).attr("uuid");
|
||||
if (configUUID == uuid){
|
||||
//This is the one we are looking for
|
||||
thisConfig = JSON.parse(decodeURIComponent($(this).attr("config")));
|
||||
}
|
||||
});
|
||||
return thisConfig;
|
||||
}
|
||||
|
||||
function validateProxyConfig(configUUID, btn){
|
||||
$(btn).html(`<i class="ui loading spinner icon"></i>`);
|
||||
$.ajax({
|
||||
url: "/api/tcpprox/config/validate",
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
let errormsg = data.error.charAt(0).toUpperCase() + data.error.slice(1);
|
||||
$(btn).html(`<i class="red times icon"></i> ${errormsg}`);
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
$(btn).html(`<i class="green check icon"></i> Config Valid`);
|
||||
msgbox("Config Check Passed");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function editTCPProxyConfig(configUUID){
|
||||
let targetConfig = getConfigDetailsFromDOM(configUUID);
|
||||
if (targetConfig != null){
|
||||
$("#addTcpProxyButton").hide();
|
||||
$("#editTcpProxyButton").show();
|
||||
$.each(targetConfig, function(key, value) {
|
||||
var field = $("#tcpProxyForm").find('[name="' + key.toLowerCase() + '"]');
|
||||
if (field.length > 0) {
|
||||
if (field.is('input')) {
|
||||
field.val(value);
|
||||
}else if (field.is('select')){
|
||||
if (key.toLowerCase() == "mode"){
|
||||
if (value == 0){
|
||||
value = "listen";
|
||||
}else if (value == 1){
|
||||
value = "transport";
|
||||
}else if (value == 2){
|
||||
value = "starter";
|
||||
}
|
||||
}
|
||||
$(field).dropdown("set selected", value);
|
||||
}
|
||||
}
|
||||
});
|
||||
editingTCPProxyConfigUUID = configUUID;
|
||||
$("#addproxyConfig").slideDown("fast");
|
||||
|
||||
}else{
|
||||
msgbox("Unable to load target config", false);
|
||||
}
|
||||
}
|
||||
|
||||
function confirmEditTCPProxyConfig(event){
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
var form = $("#tcpProxyForm");
|
||||
|
||||
var formValid = validateTCPProxyConfig(form);
|
||||
if (!formValid){
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the AJAX POST request
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/tcpprox/config/edit',
|
||||
data: form.serialize(),
|
||||
success: function(response) {
|
||||
if (response.error) {
|
||||
msgbox(response.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Config Updated");
|
||||
}
|
||||
initProxyConfigList();
|
||||
cancelTCPProxyEdit();
|
||||
|
||||
},
|
||||
error: function() {
|
||||
msgbox('An error occurred while processing the request', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteTCPProxyConfig(configUUID){
|
||||
$.ajax({
|
||||
url: "/api/tcpprox/config/delete",
|
||||
method: "POST",
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Proxy Config Removed");
|
||||
initProxyConfigList();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Start a TCP proxy by their config UUID
|
||||
function startTcpProx(configUUID){
|
||||
$.ajax({
|
||||
url: "/api/tcpprox/config/start",
|
||||
method: "POST",
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Service Started");
|
||||
initProxyConfigList();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
//Stop a TCP proxy by their config UUID
|
||||
function stopTcpProx(configUUID){
|
||||
$.ajax({
|
||||
url: "/api/tcpprox/config/stop",
|
||||
method: "POST",
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Service Stopped");
|
||||
initProxyConfigList();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function initProxyConfigList(){
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/api/tcpprox/config/list',
|
||||
success: function(response) {
|
||||
renderProxyConfigs(response);
|
||||
},
|
||||
error: function() {
|
||||
msgbox('Unable to load proxy configs', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
initProxyConfigList();
|
||||
</script>
|
||||
</div>
|
@ -22,6 +22,28 @@
|
||||
|
||||
|
||||
<script>
|
||||
var uptime5xxErrorMessage = {
|
||||
"500": "Internal Server Error",
|
||||
"501": "Not Implemented",
|
||||
"502": "Bad Gateway",
|
||||
"503": "Service Unavailable",
|
||||
"504": "Gateway Timeout",
|
||||
"505": "HTTP Version Not Supported",
|
||||
"506": "Variant Also Negotiates",
|
||||
"507": "Insufficient Storage",
|
||||
"508": "Loop Detected",
|
||||
"510": "Not Extended",
|
||||
"511": "Network Authentication Required",
|
||||
"520": "Web Server Returned an Unknown Error (Cloudflare)",
|
||||
"521": "Web Server is Down (Cloudflare)",
|
||||
"522": "Connection Timed Out (Cloudflare)",
|
||||
"523": "Origin is Unreachable (Cloudflare)",
|
||||
"524": "A Timeout Occurred (Cloudflare)",
|
||||
"525": "SSL Handshake Failed (Cloudflare)",
|
||||
"526": "Invalid SSL Certificate (Cloudflare)",
|
||||
"527": "Railgun Error (Cloudflare)",
|
||||
"530": "Site is Frozen (Pantheon)"
|
||||
}
|
||||
|
||||
$('#utmEnable').checkbox({
|
||||
onChange: function() {
|
||||
@ -78,6 +100,14 @@
|
||||
return(date.toLocaleString());
|
||||
}
|
||||
|
||||
function resolveUptime5xxErrorMessage(errorCode){
|
||||
if (uptime5xxErrorMessage[errorCode] != undefined){
|
||||
return uptime5xxErrorMessage[errorCode]
|
||||
}else{
|
||||
return "Unknown Error";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function renderUptimeData(key, value){
|
||||
if (value.length == 0){
|
||||
@ -101,26 +131,33 @@
|
||||
//Render status to html
|
||||
let thisStatus = value[i];
|
||||
let dotType = "";
|
||||
if (thisStatus.Online){
|
||||
if (thisStatus.StatusCode < 200 || thisStatus.StatusCode >= 300){
|
||||
dotType = "error";
|
||||
}else{
|
||||
dotType = "online";
|
||||
}
|
||||
ontimeRate++;
|
||||
}else{
|
||||
if (thisStatus.StatusCode >= 500 && thisStatus.StatusCode < 600){
|
||||
//Special type of error, cause by downstream reverse proxy
|
||||
dotType = "error";
|
||||
}else if (thisStatus.StatusCode == 401){
|
||||
//Unauthorized error
|
||||
dotType = "error";
|
||||
}else{
|
||||
dotType = "offline";
|
||||
}
|
||||
|
||||
}
|
||||
let statusCode = thisStatus.StatusCode;
|
||||
|
||||
if (!thisStatus.Online && statusCode == 0){
|
||||
dotType = "offline";
|
||||
}else if (statusCode < 200){
|
||||
//1xx
|
||||
dotType = "error";
|
||||
ontimeRate++;
|
||||
}else if (statusCode < 300){
|
||||
//2xx
|
||||
dotType = "online";
|
||||
ontimeRate++;
|
||||
}else if (statusCode < 400){
|
||||
//3xx
|
||||
dotType = "online";
|
||||
ontimeRate++;
|
||||
}else if (statusCode < 500){
|
||||
//4xx
|
||||
dotType = "error";
|
||||
ontimeRate++;
|
||||
}else if (statusCode < 600){
|
||||
//5xx
|
||||
dotType = "error";
|
||||
}else {
|
||||
dotType = "offline";
|
||||
}
|
||||
|
||||
let datetime = format_time(thisStatus.Timestamp);
|
||||
statusDotList += `<div title="${datetime}" class="${dotType} statusDot"></div>`
|
||||
}
|
||||
@ -141,11 +178,13 @@
|
||||
onlineStatusCss = `color: #3bd671;`;
|
||||
}else{
|
||||
if (value[value.length - 1].StatusCode >= 500 && value[value.length - 1].StatusCode < 600){
|
||||
currentOnlineStatus = `<i class="exclamation circle icon"></i> Misconfigured`;
|
||||
var latestStatusCode = value[value.length - 1].StatusCode
|
||||
currentOnlineStatus = `<i class="exclamation circle icon"></i>${latestStatusCode} - ${resolveUptime5xxErrorMessage(latestStatusCode)}`;
|
||||
onlineStatusCss = `color: #f38020;`;
|
||||
reminderEle = `<small style="${onlineStatusCss}">Downstream proxy server is online with misconfigured settings</small>`;
|
||||
reminderEle = `<small style="${onlineStatusCss}">Downstream proxy server is responsive but returning server error</small>`;
|
||||
}else if (value[value.length - 1].StatusCode >= 400 && value[value.length - 1].StatusCode <= 405){
|
||||
switch(value[value.length - 1].StatusCode){
|
||||
let latestStatusCode = value[value.length - 1].StatusCode;
|
||||
switch(latestStatusCode){
|
||||
case 400:
|
||||
currentOnlineStatus = `<i class="exclamation circle icon"></i> Bad Request`;
|
||||
break;
|
||||
@ -161,6 +200,9 @@
|
||||
case 405:
|
||||
currentOnlineStatus = `<i class="exclamation circle icon"></i> Method Not Allowed`;
|
||||
break;
|
||||
default:
|
||||
currentOnlineStatus = `<i class="exclamation circle icon"></i> Status Code: ${latestStatusCode}`;
|
||||
break;
|
||||
}
|
||||
|
||||
onlineStatusCss = `color: #f38020;`;
|
||||
|
@ -217,6 +217,7 @@
|
||||
$("#zoraxyinfo .uuid").text(data.NodeUUID);
|
||||
$("#zoraxyinfo .development").text(data.Development?"Development":"Release");
|
||||
$("#zoraxyinfo .version").text(data.Version);
|
||||
$(".zrversion").text("v." + data.Version); //index footer
|
||||
$("#zoraxyinfo .boottime").text(timeConverter(data.BootTime) + ` ( ${secondsToDhms(parseInt(Date.now()/1000) - data.BootTime)} ago)`);
|
||||
$("#zoraxyinfo .zt").html(data.ZerotierConnected?`<i class="ui green check icon"></i> Connected`:`<i class="ui red times icon"></i> Link Error`);
|
||||
$("#zoraxyinfo .sshlb").html(data.EnableSshLoopback?`<i class="ui yellow exclamation triangle icon"></i> Enabled`:`Disabled`);
|
||||
@ -341,15 +342,6 @@
|
||||
form.find('input[name="username"]').parent().removeClass('error');
|
||||
}
|
||||
|
||||
// validate password
|
||||
const password = form.find('input[name="password"]').val().trim();
|
||||
if (password === '') {
|
||||
form.find('input[name="password"]').parent().addClass('error');
|
||||
isValid = false;
|
||||
} else {
|
||||
form.find('input[name="password"]').parent().removeClass('error');
|
||||
}
|
||||
|
||||
// validate sender address
|
||||
const senderAddr = form.find('input[name="senderAddr"]').val().trim();
|
||||
if (!emailRegex.test(senderAddr)) {
|
||||
|
@ -14,7 +14,7 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
|
||||
<title>404 - Host Not Found</title>
|
||||
<style>
|
||||
h1, h2, h3, h4, h5, p, a, span{
|
||||
h1, h2, h3, h4, h5, p, a, span, .ui.list .item{
|
||||
font-family: 'Noto Sans TC', sans-serif;
|
||||
font-weight: 300;
|
||||
color: rgb(88, 88, 88)
|
||||
@ -22,9 +22,6 @@
|
||||
|
||||
.diagram{
|
||||
background-color: #ebebeb;
|
||||
box-shadow:
|
||||
inset 0px 11px 8px -10px #CCC,
|
||||
inset 0px -11px 8px -10px #CCC;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
@ -135,7 +132,7 @@
|
||||
<p>Please try again in a few minutes</p>
|
||||
<h5 style="font-weight: 500;">If you are the owner of this website:</h5>
|
||||
<div class="ui bulleted list">
|
||||
<div class="item">Check if the target web server is online</div>
|
||||
<div class="item">Check if the proxy rules that match this hostname exists</div>
|
||||
<div class="item">Visit the Reverse Proxy management interface to correct any setting errors</div>
|
||||
</div>
|
||||
</div>
|
||||
|
1512
src/web/img/logo_white.ai
Normal file
1512
src/web/img/logo_white.ai
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/web/img/logo_white.png
Normal file
BIN
src/web/img/logo_white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
38
src/web/img/logo_white.svg
Normal file
38
src/web/img/logo_white.svg
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="600px" height="200px" viewBox="0 0 600 200" enable-background="new 0 0 600 200" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#EFEFEF" d="M138.761,47.403l-16.064,17.87c9.504,8.549,15.48,20.94,15.48,34.728c0,13.785-5.976,26.179-15.48,34.726
|
||||
l16.063,17.871c14.393-12.945,23.445-31.717,23.445-52.597C162.206,79.115,153.155,60.351,138.761,47.403z"/>
|
||||
<path fill="#EFEFEF" d="M44.198,152.596l16.064-17.869c-9.503-8.547-15.48-20.941-15.48-34.726c0-13.79,5.978-26.179,15.48-34.728
|
||||
l-16.063-17.87C29.807,60.351,20.753,79.115,20.753,100C20.753,120.881,29.807,139.652,44.198,152.596z"/>
|
||||
</g>
|
||||
<polygon fill="#A9D1F3" points="106.581,38.326 91.48,56.48 76.38,38.326 "/>
|
||||
<polygon fill="#A9D1F3" points="106.581,143.52 91.48,161.674 76.379,143.52 "/>
|
||||
<circle fill="#A9D1F3" cx="91.48" cy="100" r="22.422"/>
|
||||
<g>
|
||||
<path fill="#F7F7F7" d="M194.194,132.898l43.232-66.846h-39.238V54.539h56.155v8.224l-43.233,66.729h43.703v11.629h-60.619V132.898
|
||||
z"/>
|
||||
<path fill="#F7F7F7" d="M263.038,108.814c0-21.499,14.45-33.951,30.544-33.951c15.977,0,30.31,12.452,30.31,33.951
|
||||
c0,21.498-14.333,33.951-30.31,33.951C277.488,142.766,263.038,130.313,263.038,108.814z M310.029,108.814
|
||||
c0-13.627-6.344-22.791-16.447-22.791c-10.221,0-16.564,9.164-16.564,22.791c0,13.744,6.344,22.674,16.564,22.674
|
||||
C303.686,131.488,310.029,122.559,310.029,108.814z"/>
|
||||
<path fill="#F7F7F7" d="M339.869,76.391h11.042l1.176,11.629h0.234c4.582-8.223,11.396-13.156,18.444-13.156
|
||||
c3.173,0,5.169,0.471,7.166,1.293l-2.35,11.863c-2.349-0.704-3.877-1.057-6.578-1.057c-5.287,0-11.632,3.643-15.626,13.981v40.177
|
||||
h-13.509V76.391z"/>
|
||||
<path fill="#F7F7F7" d="M380.868,123.969c0-13.98,11.748-21.146,38.649-24.082c-0.115-7.402-2.819-13.98-12.334-13.98
|
||||
c-6.813,0-13.158,3.056-18.68,6.578l-5.052-9.162c6.696-4.23,15.742-8.459,26.08-8.459c16.096,0,23.497,10.104,23.497,27.374
|
||||
v38.884h-11.044l-1.058-7.4h-0.469c-5.875,5.051-12.806,9.045-20.56,9.045C388.739,142.766,380.868,135.365,380.868,123.969z
|
||||
M419.518,124.322V108.58c-19.147,2.23-25.61,7.166-25.61,14.332c0,6.461,4.348,9.047,10.104,9.047
|
||||
C409.649,131.959,414.231,129.256,419.518,124.322z"/>
|
||||
<path fill="#F7F7F7" d="M464.63,107.405l-19.383-31.015h14.686l7.636,13.039c1.996,3.643,3.995,7.285,6.109,10.927h0.587
|
||||
c1.645-3.642,3.406-7.284,5.287-10.927l6.813-13.039h14.099l-19.386,32.424l20.795,32.307h-14.685l-8.459-13.744
|
||||
c-2.115-3.76-4.346-7.754-6.697-11.396h-0.586c-1.997,3.643-3.995,7.52-5.992,11.396l-7.518,13.744h-14.098L464.63,107.405z"/>
|
||||
<path fill="#F7F7F7" d="M508.096,166.85l2.586-10.574c1.176,0.354,3.054,0.939,4.815,0.939c6.932,0,11.045-5.168,13.394-12.1
|
||||
l1.41-4.463l-25.611-64.262h13.746l11.865,33.363c1.996,5.758,3.993,12.1,5.991,18.209h0.587
|
||||
c1.645-5.992,3.406-12.334,5.053-18.209l10.456-33.363h13.038l-23.73,68.607c-5.051,13.863-11.865,23.143-25.375,23.143
|
||||
C512.914,168.141,510.329,167.672,508.096,166.85z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
@ -52,8 +52,8 @@
|
||||
<a class="item" tag="rules">
|
||||
<i class="simplistic plus square icon"></i> Create Proxy Rules
|
||||
</a>
|
||||
<a class="item" tag="tcpprox">
|
||||
<i class="simplistic exchange icon"></i> TCP Proxy
|
||||
<a class="item" tag="streamproxy">
|
||||
<i class="simplistic exchange icon"></i> Stream Proxy
|
||||
</a>
|
||||
<div class="ui divider menudivider">Access & Connections</div>
|
||||
<a class="item" tag="cert">
|
||||
@ -125,7 +125,7 @@
|
||||
<div id="zgrok" class="functiontab" target="zgrok.html"></div>
|
||||
|
||||
<!-- TCP Proxy -->
|
||||
<div id="tcpprox" class="functiontab" target="tcpprox.html"></div>
|
||||
<div id="streamproxy" class="functiontab" target="streamprox.html"></div>
|
||||
|
||||
<!-- Web Server -->
|
||||
<div id="webserv" class="functiontab" target="webserv.html"></div>
|
||||
@ -154,7 +154,7 @@
|
||||
<br><br>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui container" style="color: grey; font-size: 90%">
|
||||
<p>CopyRight Zoraxy Project and its authors © 2021 - <span class="year"></span></p>
|
||||
<p><a href="https://zoraxy.arozos.com" target="_blank">Zoraxy</a> <span class="zrversion"></span> © 2021 - <span class="year"></span> tobychui. Licensed under AGPL</p>
|
||||
</div>
|
||||
|
||||
<div id="messageBox" class="ui green floating big compact message">
|
||||
|
@ -114,7 +114,7 @@
|
||||
min-height:40px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -509,6 +509,16 @@ body{
|
||||
}
|
||||
}
|
||||
|
||||
/* Remind message for user forgetting to click Apply button*/
|
||||
#applyButtonReminder{
|
||||
position: absolute;
|
||||
bottom:-1.6em;
|
||||
left: 0;
|
||||
font-weight: bolder;
|
||||
color: #faac26;
|
||||
display:none;
|
||||
}
|
||||
|
||||
/*
|
||||
HTTP Proxy & Virtual Directory
|
||||
*/
|
||||
@ -551,23 +561,23 @@ body{
|
||||
TCP Proxy
|
||||
*/
|
||||
|
||||
.tcproxConfig td:first-child{
|
||||
.streamproxConfig td:first-child{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tcproxConfig.running td:first-child{
|
||||
border-left: 0.6em solid #02cb59 !important;
|
||||
.streamproxConfig.running td:first-child{
|
||||
border-left: 0.6em solid #01cb55 !important;
|
||||
}
|
||||
|
||||
.tcproxConfig.stopped td:first-child{
|
||||
border-left: 0.6em solid #02032a !important;
|
||||
.streamproxConfig.stopped td:first-child{
|
||||
border-left: 0.6em solid #1b1b1b !important;
|
||||
}
|
||||
|
||||
.tcproxConfig td:first-child .statusText{
|
||||
.streamproxConfig td:first-child .statusText{
|
||||
position: absolute;
|
||||
bottom: 0.3em;
|
||||
left: 0.2em;
|
||||
font-size: 1.4em;
|
||||
bottom: 0.1em;
|
||||
right: 0.2em;
|
||||
font-size: 1em;
|
||||
color:rgb(224, 224, 224);
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
|
@ -14,7 +14,7 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
|
||||
<title>521 - Web server is down</title>
|
||||
<style>
|
||||
h1, h2, h3, h4, h5, p, a, span{
|
||||
h1, h2, h3, h4, h5, p, a, span, .ui.list .item{
|
||||
font-family: 'Noto Sans TC', sans-serif;
|
||||
font-weight: 300;
|
||||
color: rgb(88, 88, 88)
|
||||
@ -22,9 +22,6 @@
|
||||
|
||||
.diagram{
|
||||
background-color: #ebebeb;
|
||||
box-shadow:
|
||||
inset 0px 11px 8px -10px #CCC,
|
||||
inset 0px -11px 8px -10px #CCC;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,22 @@
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
<style>
|
||||
.ui.tabular.menu .item.narrowpadding{
|
||||
padding: 0.6em !important;
|
||||
margin: 0.15em !important;
|
||||
}
|
||||
|
||||
#permissionPolicyEditor.disabled{
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#permissionPolicyEditor .experimental{
|
||||
background-color: rgb(241, 241, 241);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
@ -16,41 +32,93 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<p>You can define custom headers to be sent
|
||||
together with the client request to the backend server in
|
||||
this reverse proxy endpoint / host.</p>
|
||||
|
||||
<table class="ui very basic compacted unstackable celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
<th>Remove</th>
|
||||
</tr></thead>
|
||||
<tbody id="headerTable">
|
||||
<tr>
|
||||
<td colspan="3"><i class="ui green circle check icon"></i> No Additonal Header</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="ui divider"></div>
|
||||
<h4>Add Custom Header</h4>
|
||||
<p>Add custom header(s) into this proxy target</p>
|
||||
<div class="scrolling content ui form">
|
||||
<div class="three small fields credentialEntry">
|
||||
<div class="field">
|
||||
<input id="headerName" type="text" placeholder="X-Custom-Header" autocomplete="off">
|
||||
<div class="ui small pointing secondary menu">
|
||||
<a class="item active narrowpadding" data-tab="customheaders">Custom Headers</a>
|
||||
<a class="item narrowpadding" data-tab="security">Security Headers</a>
|
||||
</div>
|
||||
<div class="ui tab basic segment active" data-tab="customheaders">
|
||||
<table class="ui very basic compacted unstackable celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
<th>Remove</th>
|
||||
</tr></thead>
|
||||
<tbody id="headerTable">
|
||||
<tr>
|
||||
<td colspan="3"><i class="ui green circle check icon"></i> No Additonal Header</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
<i class="angle double right blue icon"></i> Sent additional custom headers to origin server <br>
|
||||
<i class="angle double left orange icon"></i> Inject custom headers into origin server responses
|
||||
</p>
|
||||
<div class="ui divider"></div>
|
||||
<h4>Edit Custom Header</h4>
|
||||
<p>Add or remove custom header(s) over this proxy target</p>
|
||||
<div class="scrolling content ui form">
|
||||
<div class="five small fields credentialEntry">
|
||||
<div class="field" align="center">
|
||||
<button id="toOriginButton" style="margin-top: 0.6em;" title="Downstream to Upstream" class="ui circular basic active button">Zoraxy <i class="angle double right blue icon" style="margin-right: 0.4em;"></i> Origin</button>
|
||||
<button id="toClientButton" style="margin-top: 0.6em;" title="Upstream to Downstream" class="ui circular basic button">Client <i class="angle double left orange icon" style="margin-left: 0.4em;"></i> Zoraxy</button>
|
||||
</div>
|
||||
<div class="field" align="center">
|
||||
<button id="headerModeAdd" style="margin-top: 0.6em;" class="ui circular basic active button"><i class="ui green circle add icon"></i> Add Header</button>
|
||||
<button id="headerModeRemove" style="margin-top: 0.6em;" class="ui circular basic button"><i class="ui red circle times icon"></i> Remove Header</button>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Header Key</label>
|
||||
<input id="headerName" type="text" placeholder="X-Custom-Header" autocomplete="off">
|
||||
<small>The header key is <b>NOT</b> case sensitive</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Header Value</label>
|
||||
<input id="headerValue" type="text" placeholder="value1,value2,value3" autocomplete="off">
|
||||
</div>
|
||||
<div class="field" >
|
||||
<button class="ui basic button" onclick="addCustomHeader();"><i class="green add icon"></i> Add Header Rewrite Rule</button>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<input id="headerValue" type="text" placeholder="value1,value2,value3" autocomplete="off">
|
||||
</div>
|
||||
<div class="field" >
|
||||
<button class="ui basic button" onclick="addCustomHeader();"><i class="green add icon"></i> Add Header</button>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui tab basic segment" data-tab="security">
|
||||
<h4>HTTP Strict Transport Security</h4>
|
||||
<p>Force future attempts to access this site to only use HTTPS</p>
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" id="enableHSTS" name="enableHSTS">
|
||||
<label>Enable HSTS<br>
|
||||
<small>HSTS header will be automatically ignored if the site is accessed using HTTP</small></label>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h4>Permission Policy</h4>
|
||||
<p>Explicitly declare what functionality can and cannot be used on this website. </p>
|
||||
<div class="ui toggle checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox" id="enablePP" name="enablePP">
|
||||
<label>Enable Permission Policy<br>
|
||||
<small>Enable Permission-Policy header with all allowed state.</small></label>
|
||||
</div>
|
||||
<div style="margin-top: 1em;" id="permissionPolicyEditor">
|
||||
<table class="ui celled unstackable very compact table">
|
||||
<thead>
|
||||
<tr><th>Feature</th>
|
||||
<th>Enabled</th>
|
||||
<th>Allow All (*)</th>
|
||||
<th>Self Only (self)</th>
|
||||
</tr></thead>
|
||||
<tbody id="permissionPolicyEditTable">
|
||||
<tr>
|
||||
<td colspan="4"><i class="ui loading spinner icon"></i> Generating</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<small><i class="ui yellow exclamation triangle icon"></i> Grey out fields are non-standard permission policies</small>
|
||||
<br><br>
|
||||
<button class="ui basic button" onclick="savePermissionPolicy();"><i class="green save icon"></i> Save</button>
|
||||
</div>
|
||||
|
||||
<div class="field" >
|
||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
||||
</div>
|
||||
@ -59,6 +127,9 @@
|
||||
<br><br><br><br>
|
||||
|
||||
<script>
|
||||
$('.menu .item').tab();
|
||||
let permissionPolicyKeys = [];
|
||||
|
||||
let editingEndpoint = {};
|
||||
if (window.location.hash.length > 1){
|
||||
let payloadHash = window.location.hash.substr(1);
|
||||
@ -75,6 +146,48 @@
|
||||
parent.hideSideWrapper(true);
|
||||
}
|
||||
|
||||
//Bind events to header mod mode
|
||||
$("#headerModeAdd").on("click", function(){
|
||||
$("#headerModeAdd").addClass("active");
|
||||
$("#headerModeRemove").removeClass("active");
|
||||
$("#headerValue").parent().show();
|
||||
});
|
||||
|
||||
$("#headerModeRemove").on("click", function(){
|
||||
$("#headerModeAdd").removeClass("active");
|
||||
$("#headerModeRemove").addClass("active");
|
||||
$("#headerValue").parent().hide();
|
||||
$("#headerValue").val("");
|
||||
});
|
||||
|
||||
//Bind events to header directions option
|
||||
$("#toOriginButton").on("click", function(){
|
||||
$("#toOriginButton").addClass("active");
|
||||
$("#toClientButton").removeClass("active");
|
||||
});
|
||||
|
||||
$("#toClientButton").on("click", function(){
|
||||
$("#toOriginButton").removeClass("active");
|
||||
$("#toClientButton").addClass("active");
|
||||
});
|
||||
|
||||
//Return "add" or "remove" depending on mode user selected
|
||||
function getHeaderEditMode(){
|
||||
if ($("#headerModeAdd").hasClass("active")){
|
||||
return "add";
|
||||
}
|
||||
|
||||
return "remove";
|
||||
}
|
||||
|
||||
//Return "toOrigin" or "toClient"
|
||||
function getHeaderDirection(){
|
||||
if ($("#toOriginButton").hasClass("active")){
|
||||
return "toOrigin";
|
||||
}
|
||||
return "toClient";
|
||||
}
|
||||
|
||||
//$("#debug").text(JSON.stringify(editingEndpoint));
|
||||
|
||||
function addCustomHeader(){
|
||||
@ -88,18 +201,21 @@
|
||||
$("#headerName").parent().removeClass("error");
|
||||
}
|
||||
|
||||
if (value == ""){
|
||||
$("#headerValue").parent().addClass("error");
|
||||
return
|
||||
}else{
|
||||
$("#headerValue").parent().removeClass("error");
|
||||
if (getHeaderEditMode() == "add"){
|
||||
if (value == ""){
|
||||
$("#headerValue").parent().addClass("error");
|
||||
return
|
||||
}else{
|
||||
$("#headerValue").parent().removeClass("error");
|
||||
}
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/proxy/header/add",
|
||||
data: {
|
||||
"type": editingEndpoint.ept,
|
||||
"type": getHeaderEditMode(),
|
||||
"domain": editingEndpoint.ep,
|
||||
"direction":getHeaderDirection(),
|
||||
"name": name,
|
||||
"value": value
|
||||
},
|
||||
@ -129,7 +245,7 @@
|
||||
$.ajax({
|
||||
url: "/api/proxy/header/remove",
|
||||
data: {
|
||||
"type": editingEndpoint.ept,
|
||||
//"type": editingEndpoint.ept,
|
||||
"domain": editingEndpoint.ep,
|
||||
"name": name,
|
||||
},
|
||||
@ -157,10 +273,16 @@
|
||||
|
||||
$("#headerTable").html("");
|
||||
data.forEach(header => {
|
||||
let editModeIcon = header.IsRemove?`<i class="ui red times circle icon"></i>`:`<i class="ui green add circle icon"></i>`;
|
||||
let direction = (header.Direction==0)?`<i class="angle double right blue icon"></i>`:`<i class="angle double left orange icon"></i>`;
|
||||
let valueField = header.Value;
|
||||
if (header.IsRemove){
|
||||
valueField = "<small style='color: grey;'>(Field Removed)</small>";
|
||||
}
|
||||
$("#headerTable").append(`
|
||||
<tr>
|
||||
<td>${header.Key}</td>
|
||||
<td>${header.Value}</td>
|
||||
<td>${direction} ${header.Key}</td>
|
||||
<td>${editModeIcon} ${valueField}</td>
|
||||
<td><button class="ui basic circular mini red icon button" onclick="deleteCustomHeader('${header.Key}');"><i class="ui trash icon"></i></button></td>
|
||||
</tr>
|
||||
`);
|
||||
@ -177,6 +299,218 @@
|
||||
});
|
||||
}
|
||||
listCustomHeaders();
|
||||
|
||||
//Start HSTS state
|
||||
function initHSTSState(){
|
||||
$.get("/api/proxy/header/handleHSTS?domain=" + editingEndpoint.ep, function(data){
|
||||
if (data == 0){
|
||||
//HSTS disabled
|
||||
$("#enableHSTS").parent().checkbox("set unchecked");
|
||||
}else{
|
||||
//HSTS enabled
|
||||
$("#enableHSTS").parent().checkbox("set checked");
|
||||
}
|
||||
|
||||
/* Bind events to toggles */
|
||||
$("#enableHSTS").on("change", function(){
|
||||
let HSTSEnabled = $("#enableHSTS")[0].checked;
|
||||
$.ajax({
|
||||
url: "/api/proxy/header/handleHSTS",
|
||||
method: "POST",
|
||||
data: {
|
||||
"domain": editingEndpoint.ep,
|
||||
"maxage": 31536000
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox(`HSTS ${HSTSEnabled?"Enabled":"Disabled"}`);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
initHSTSState();
|
||||
|
||||
//Return true if this is an proposed permission policy feature
|
||||
function isExperimentalFeature(header) {
|
||||
// List of experimental features
|
||||
const experimentalFeatures = [
|
||||
"clipboard-read",
|
||||
"clipboard-write",
|
||||
"gamepad",
|
||||
"speaker-selection",
|
||||
"conversion-measurement",
|
||||
"focus-without-user-activation",
|
||||
"hid",
|
||||
"idle-detection",
|
||||
"interest-cohort",
|
||||
"serial",
|
||||
"sync-script",
|
||||
"trust-token-redemption",
|
||||
"unload",
|
||||
"window-placement",
|
||||
"vertical-scroll"
|
||||
];
|
||||
|
||||
header = header.replaceAll("_","-");
|
||||
|
||||
// Check if the header is in the list of experimental features
|
||||
return experimentalFeatures.includes(header);
|
||||
}
|
||||
|
||||
/* List permission policy header from server */
|
||||
function initPermissionPolicy(){
|
||||
$.get("/api/proxy/header/handlePermissionPolicy?domain=" + editingEndpoint.ep, function(data){
|
||||
if (data.error != undefined){
|
||||
console.log(data.error);
|
||||
$("#enablePP").parent().addClass('disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
//Set checkbox initial state
|
||||
if (data.PPEnabled){
|
||||
$("#enablePP").parent().checkbox("set checked");
|
||||
$("#permissionPolicyEditor").removeClass("disabled");
|
||||
}else{
|
||||
$("#enablePP").parent().checkbox("set unchecked");
|
||||
$("#permissionPolicyEditor").addClass("disabled");
|
||||
}
|
||||
|
||||
//Bind toggle change events
|
||||
$("#enablePP").on("change", function(evt){
|
||||
//Set checkbox state
|
||||
let ppEnabled = $("#enablePP")[0].checked;
|
||||
if (ppEnabled){
|
||||
$("#permissionPolicyEditor").removeClass("disabled");
|
||||
}else{
|
||||
$("#permissionPolicyEditor").addClass("disabled");
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/proxy/header/handlePermissionPolicy",
|
||||
method: "POST",
|
||||
data: {
|
||||
enable: ppEnabled,
|
||||
domain: editingEndpoint.ep
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox(`Permission Policy ${ppEnabled?"Enabled":"Disabled"}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
//Render the table to list
|
||||
$("#permissionPolicyEditTable").html("");
|
||||
for (const [key, value] of Object.entries(data.CurrentPolicy)) {
|
||||
let allowall = "";
|
||||
let allowself = "";
|
||||
let enabled = "checked";
|
||||
if (value.length == 1 && value[0] == "*"){
|
||||
allowall = "checked";
|
||||
}else if (value.length == 1 && value[0] == "self"){
|
||||
allowself = "checked";
|
||||
}
|
||||
|
||||
if (value.length == 0){
|
||||
enabled = ""
|
||||
allowall = "checked"; //default state
|
||||
}
|
||||
|
||||
let isExperimental = isExperimentalFeature(key);
|
||||
$("#permissionPolicyEditTable").append(`<tr class="${isExperimental?"experimental":""}">
|
||||
<td>${key.replaceAll("_","-")}</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input class="enabled" type="checkbox" name="${key}" ${enabled}>
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="ui radio checkbox targetinput ${!enabled?"disabled":""}">
|
||||
<input type="radio" value="all" name="${key}-target" ${allowall} ${!enabled?"disabled=\"\"":""}>
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="ui radio checkbox targetinput ${!enabled?"disabled":""}">
|
||||
<input type="radio" value="self" name="${key}-target" ${allowself} ${!enabled?"disabled=\"\"":""}>
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`);
|
||||
|
||||
permissionPolicyKeys.push(key);
|
||||
}
|
||||
|
||||
$("#permissionPolicyEditTable .enabled").on("change", function(){
|
||||
console.log($(this)[0].checked);
|
||||
let fieldGroup = $(this).parent().parent().parent();
|
||||
if ($(this)[0].checked){
|
||||
fieldGroup.find(".targetinput").removeClass("disabled");
|
||||
fieldGroup.find("input[type=radio]").prop('disabled', false);
|
||||
}else{
|
||||
fieldGroup.find(".targetinput").addClass("disabled");
|
||||
fieldGroup.find("input[type=radio]").prop('disabled', true);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
initPermissionPolicy();
|
||||
|
||||
//Generate the permission policy object for sending to backend
|
||||
function generatePermissionPolicyObject(){
|
||||
function getStructuredFieldValueFromDOM(fieldKey){
|
||||
var policyTarget = $(`#permissionPolicyEditTable input[name="${fieldKey}-target"]:checked`).val();
|
||||
var isPolicyEnabled = $(`#permissionPolicyEditTable input[name="${fieldKey}"]`).is(':checked');
|
||||
|
||||
if (!isPolicyEnabled){
|
||||
return [];
|
||||
}
|
||||
|
||||
if (policyTarget == "all"){
|
||||
//Rewrite all to correct syntax
|
||||
policyTarget = "*";
|
||||
}
|
||||
return [policyTarget];
|
||||
}
|
||||
|
||||
let newPermissionPolicyKeyValuePair = {};
|
||||
permissionPolicyKeys.forEach(policyKey => {
|
||||
newPermissionPolicyKeyValuePair[policyKey] = getStructuredFieldValueFromDOM(policyKey);
|
||||
});
|
||||
|
||||
console.log(newPermissionPolicyKeyValuePair);
|
||||
return newPermissionPolicyKeyValuePair;
|
||||
}
|
||||
|
||||
//Handle saving of permission policy
|
||||
function savePermissionPolicy(){
|
||||
let permissionPolicy = generatePermissionPolicyObject();
|
||||
let domain = editingEndpoint.ep;
|
||||
|
||||
$.ajax({
|
||||
url: "/api/proxy/header/handlePermissionPolicy",
|
||||
method: "PUT",
|
||||
data: {
|
||||
"domain": domain,
|
||||
"pp": JSON.stringify(permissionPolicy),
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("Permission Policy Updated");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
139
src/web/snippet/dockerContainersList.html
Normal file
139
src/web/snippet/dockerContainersList.html
Normal file
@ -0,0 +1,139 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css" />
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<br />
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
List of Docker Containers
|
||||
<div class="sub header">
|
||||
Below is a list of all detected Docker containers currently running
|
||||
on the system.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="containersList" class="ui middle aligned divided list active">
|
||||
<div class="ui loader active"></div>
|
||||
</div>
|
||||
<div class="ui horizontal divider"></div>
|
||||
<div id="containersAddedListHeader" class="ui header" hidden>
|
||||
Already added containers:
|
||||
</div>
|
||||
<div
|
||||
id="containersAddedList"
|
||||
class="ui middle aligned divided list"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const lines = {};
|
||||
const linesAded = [];
|
||||
|
||||
function getDockerContainers() {
|
||||
const hostRequest = $.get("/api/proxy/list?type=host");
|
||||
const dockerRequest = $.get("/api/docker/containers");
|
||||
|
||||
// Wait for both requests to complete
|
||||
Promise.all([hostRequest, dockerRequest])
|
||||
.then(([hostData, dockerData]) => {
|
||||
if (
|
||||
dockerData.error === undefined &&
|
||||
hostData.error === undefined
|
||||
) {
|
||||
const { containers, network } = dockerData;
|
||||
const bridge = network.find(({ Name }) => Name === "bridge");
|
||||
const {
|
||||
IPAM: {
|
||||
Config: [{ Gateway: gateway }],
|
||||
},
|
||||
} = bridge;
|
||||
const existedDomains = hostData.map(({ Domain }) => Domain);
|
||||
|
||||
for (const container of containers) {
|
||||
const {
|
||||
Ports,
|
||||
Names: [name],
|
||||
} = container;
|
||||
|
||||
for (const portObject of Ports.filter(
|
||||
({ IP: ip }) => ip === "::"
|
||||
)) {
|
||||
const { IP: ip, PublicPort: port } = portObject;
|
||||
const key = `${name}-${port}`;
|
||||
|
||||
if (
|
||||
existedDomains.some((item) => item === `${gateway}:${port}`)
|
||||
) {
|
||||
linesAded.push({
|
||||
name: name.replace(/^\//, ""),
|
||||
ip: gateway,
|
||||
port,
|
||||
});
|
||||
} else if (!lines[key]) {
|
||||
lines[key] = {
|
||||
name: name.replace(/^\//, ""),
|
||||
ip: gateway,
|
||||
port,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, line] of Object.entries(lines)) {
|
||||
$("#containersList").append(
|
||||
`<div class="item">
|
||||
<div class="right floated content">
|
||||
<div class="ui button" onclick="addContainerItem('${key}');">Add</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="header">${line.name}</div>
|
||||
<div class="description">
|
||||
${line.ip}:${line.port}
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
for (const line of linesAded) {
|
||||
$("#containersAddedList").append(
|
||||
`<div class="item">
|
||||
<div class="content">
|
||||
<div class="header">${line.name}</div>
|
||||
<div class="description">
|
||||
${line.ip}:${line.port}
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
linesAded.length &&
|
||||
$("#containersAddedListHeader").removeAttr("hidden");
|
||||
$("#containersList .loader").removeClass("active");
|
||||
} else {
|
||||
parent.msgbox(
|
||||
`Error loading data: ${dockerData.error || hostData.error}`,
|
||||
false
|
||||
);
|
||||
$("#containersList").html(`<div class="ui basic segment"><i class="ui red times icon"></i> ${dockerData.error || hostData.error}</div>`);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error.responseText);
|
||||
parent.msgbox("Error loading data: " + error.message, false);
|
||||
});
|
||||
}
|
||||
|
||||
getDockerContainers();
|
||||
|
||||
function addContainerItem(item) {
|
||||
if (lines[item]) {
|
||||
parent.addContainerItem(lines[item]);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
49
src/web/snippet/loadBalancer.html
Normal file
49
src/web/snippet/loadBalancer.html
Normal file
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Load Balance
|
||||
<div class="sub header epname"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<div class="field" >
|
||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br><br><br><br>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let aliasList = [];
|
||||
let editingEndpoint = {};
|
||||
|
||||
if (window.location.hash.length > 1){
|
||||
let payloadHash = window.location.hash.substr(1);
|
||||
try{
|
||||
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
||||
$(".epname").text(payloadHash.ep);
|
||||
editingEndpoint = payloadHash;
|
||||
}catch(ex){
|
||||
console.log("Unable to load endpoint data from hash")
|
||||
}
|
||||
}
|
||||
|
||||
function closeThisWrapper(){
|
||||
parent.hideSideWrapper(true);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1242,15 +1242,32 @@
|
||||
"Name": "gandiv5",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "fieldName",
|
||||
"Title": "BaseURL",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "authZone",
|
||||
"Title": "APIKey",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "PersonalAccessToken",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": []
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gcore": {
|
||||
"Name": "gcore",
|
||||
@ -2063,35 +2080,40 @@
|
||||
"Name": "namecheap",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "domain",
|
||||
"Title": "Debug",
|
||||
"Datatype": "bool"
|
||||
},
|
||||
{
|
||||
"Title": "BaseURL",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "key",
|
||||
"Title": "APIUser",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "keyFqdn",
|
||||
"Title": "APIKey",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "keyValue",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "tld",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "sld",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "host",
|
||||
"Title": "ClientIP",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": []
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
"namedotcom": {
|
||||
"Name": "namedotcom",
|
||||
@ -2418,26 +2440,38 @@
|
||||
"Name": "ovh",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "FieldType",
|
||||
"Title": "APIEndpoint",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "SubDomain",
|
||||
"Title": "ApplicationKey",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "Target",
|
||||
"Title": "ApplicationSecret",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "Zone",
|
||||
"Title": "ConsumerKey",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "ID",
|
||||
"Datatype": "int64"
|
||||
"Title": "OAuth2Config",
|
||||
"Datatype": "*OAuth2Config"
|
||||
},
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -2875,15 +2909,28 @@
|
||||
"Name": "shellrent",
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "domainID",
|
||||
"Datatype": "int"
|
||||
"Title": "Username",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "recordID",
|
||||
"Datatype": "int"
|
||||
"Title": "Token",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": []
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "HTTPClient",
|
||||
"Datatype": "*http.Client"
|
||||
}
|
||||
]
|
||||
},
|
||||
"simply": {
|
||||
"Name": "simply",
|
||||
@ -3034,15 +3081,28 @@
|
||||
},
|
||||
"ultradns": {
|
||||
"Name": "ultradns",
|
||||
"ConfigableFields": [],
|
||||
"HiddenFields": [
|
||||
"ConfigableFields": [
|
||||
{
|
||||
"Title": "config",
|
||||
"Datatype": "*Config"
|
||||
"Title": "Username",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "client",
|
||||
"Datatype": "*client.Client"
|
||||
"Title": "Password",
|
||||
"Datatype": "string"
|
||||
},
|
||||
{
|
||||
"Title": "Endpoint",
|
||||
"Datatype": "string"
|
||||
}
|
||||
],
|
||||
"HiddenFields": [
|
||||
{
|
||||
"Title": "PropagationTimeout",
|
||||
"Datatype": "time.Duration"
|
||||
},
|
||||
{
|
||||
"Title": "PollingInterval",
|
||||
"Datatype": "time.Duration"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -132,6 +132,23 @@ func extractConfigStruct(sourceCode string) (string, string) {
|
||||
// Extract the struct name and its content.
|
||||
structName := match[1]
|
||||
structContent := match[2]
|
||||
if structName != "Config" {
|
||||
allStructs := structRegex.FindAllStringSubmatch(sourceCode, 10)
|
||||
for _, thisStruct := range allStructs {
|
||||
//fmt.Println("Name => ", test[1])
|
||||
//fmt.Println("Content => ", test[2])
|
||||
|
||||
if thisStruct[1] == "Config" {
|
||||
structName = "Config"
|
||||
structContent = thisStruct[2]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if structName != "Config" {
|
||||
panic("Unable to find Config for this provider")
|
||||
}
|
||||
}
|
||||
|
||||
return structName, structContent
|
||||
}
|
||||
|
10
tools/update_acmedns.sh
Normal file
10
tools/update_acmedns.sh
Normal file
@ -0,0 +1,10 @@
|
||||
# /bin/sh
|
||||
|
||||
# Build the acmedns
|
||||
echo "Building ACMEDNS"
|
||||
cd ../tools/dns_challenge_update/code-gen
|
||||
./update.sh
|
||||
cd ../../../
|
||||
|
||||
cp ./tools/dns_challenge_update/code-gen/acmedns/acmedns.go ./src/mod/acme/acmedns/acmedns.go
|
||||
cp ./tools/dns_challenge_update/code-gen/acmedns/providers.json ./src/mod/acme/acmedns/providers.json
|
34
tools/update_geodb.sh
Normal file
34
tools/update_geodb.sh
Normal file
@ -0,0 +1,34 @@
|
||||
#/bin/bash
|
||||
|
||||
cd ../src/mod/geodb
|
||||
|
||||
# Delete the old csv files
|
||||
rm geoipv4.csv
|
||||
rm geoipv6.csv
|
||||
|
||||
echo "Updating geodb csv files"
|
||||
|
||||
echo "Downloading IPv4 database"
|
||||
curl -f https://cdn.jsdelivr.net/npm/@ip-location-db/geo-whois-asn-country/geo-whois-asn-country-ipv4.csv -o geoipv4.csv
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to download IPv4 database"
|
||||
failed=true
|
||||
else
|
||||
echo "Successfully downloaded IPv4 database"
|
||||
fi
|
||||
|
||||
echo "Downloading IPv6 database"
|
||||
curl -f https://cdn.jsdelivr.net/npm/@ip-location-db/geo-whois-asn-country/geo-whois-asn-country-ipv6.csv -o geoipv6.csv
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to download IPv6 database"
|
||||
failed=true
|
||||
else
|
||||
echo "Successfully downloaded IPv6 database"
|
||||
fi
|
||||
|
||||
if [ "$failed" = true ]; then
|
||||
echo "One or more downloads failed. Blocking exit..."
|
||||
while :; do
|
||||
read -p "Press [Ctrl+C] to exit..." input
|
||||
done
|
||||
fi
|
Reference in New Issue
Block a user