mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-07-08 15:21:44 +02:00
Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
83536a83f7 | |||
1183b0ed55 | |||
b00e302f6d | |||
deddb17803 | |||
aa96d831e1 | |||
c6f7f37aaf | |||
63f12dedcf | |||
136d1ecafb | |||
7193defad7 | |||
cf4c57298e | |||
d82a531a41 | |||
7694e317f7 | |||
ed4945ab7e | |||
ce8741bfc8 | |||
7a3db09811 | |||
e73f9b47d3 | |||
c248dacccf | |||
d596d6b843 | |||
6feb2d105d | |||
3a26a5b4d3 | |||
2cdd5654ed | |||
a0d362df4e | |||
334c1ab131 | |||
08d52024ab | |||
a3e16594e8 | |||
cced07ba2d | |||
2003992d75 | |||
71423d98b1 | |||
8ca716c59f | |||
fe48a9a0c3 | |||
ec973eb3bc | |||
7b69b5fa63 | |||
ce4f46cb50 | |||
3454a9b975 | |||
55bc939a37 | |||
1d63b679dc | |||
3df96350a3 | |||
34fab7b3d0 | |||
46817d0664 | |||
1db2ca61fa | |||
0b601406de | |||
b4c771cdee | |||
a486d42351 | |||
90c2199a1b | |||
161c61fac7 | |||
5ffacb1d06 | |||
75ebd0ffbe | |||
dc069f3c57 | |||
e1b512f78f | |||
8854a38f49 | |||
7583a4628c | |||
73c0ea0896 | |||
7dad7c7305 | |||
faa95b4e21 | |||
cb0e13976d | |||
ccd8dcff56 | |||
750656fd7f | |||
d9f515fdba | |||
176249a7d9 | |||
e2a449a7bc | |||
a9695e969e | |||
7ba997dfc2 | |||
d00117e878 | |||
43a84a3f1c | |||
e24f31bdef | |||
fc9240fbac | |||
e0f5431215 | |||
de658a3c6c | |||
73276b1918 | |||
abdb7d4d75 | |||
72299ace15 | |||
4d6c79f51b | |||
2c045f4f40 | |||
b8cf046ca6 | |||
026dd6b89d | |||
5805fe6ed2 | |||
3c78211800 | |||
8e648a8e1f | |||
a000893dd1 | |||
db88bfb752 |
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.
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -31,4 +31,6 @@ src/rules/*
|
||||
src/README.md
|
||||
docker/ContainerTester.sh
|
||||
docker/ImagePublisher.sh
|
||||
src/mod/acme/test/stackoverflow.pem
|
||||
src/mod/acme/test/stackoverflow.pem
|
||||
/tools/dns_challenge_update/code-gen/acmedns
|
||||
/tools/dns_challenge_update/code-gen/lego
|
||||
|
65
CHANGELOG.md
65
CHANGELOG.md
@ -1,3 +1,68 @@
|
||||
# v3.0.5 May 26 2024
|
||||
|
||||
|
||||
+ Optimized uptime monitor error message [#121](https://github.com/tobychui/zoraxy/issues/121)
|
||||
+ Optimized detection logic for internal proxy target and header rewrite condition for HTTP_HOST [#164](https://github.com/tobychui/zoraxy/issues/164)
|
||||
+ Fixed ovh DNS challenge provider form generator bug [#161](https://github.com/tobychui/zoraxy/issues/161)
|
||||
+ Added permission policy module (not enabled)
|
||||
+ Added single-use cookiejar to uptime monitor request client to handle cookie issues on some poorly written back-end server [#149](https://github.com/tobychui/zoraxy/issues/149)
|
||||
|
||||
|
||||
# v3.0.4 May 18 2024
|
||||
|
||||
## This release tidied up the contribution by [Teifun2](https://github.com/Teifun2) and added a new way to generate DNS challenge based certificate (e.g. wildcards) from Let's Encrypt without changing any environment variables. This also fixes a few previous ACME module EAB settings bug related to concurrent save.
|
||||
|
||||
You can find the DNS challenge settings under TLS / SSL > ACME snippet > Generate New Certificate > (Check the "Use a DNS Challenge" checkbox)
|
||||
|
||||
+ Optimized DNS challenge implementation [thanks to Teifun2](https://github.com/Teifun2) / Issues [#49](https://github.com/tobychui/zoraxy/issues/49) [#79](https://github.com/tobychui/zoraxy/issues/79)
|
||||
+ Removed dependencies on environment variable write and keep all data contained
|
||||
+ Fixed panic on loading certificate generated by Zoraxy v2
|
||||
+ Added automatic form generator for DNS challenge / providers
|
||||
+ Added CA name default value
|
||||
+ Added code generator for acmedns module (storing the DNS challenge provider contents extracted from lego)
|
||||
+ Fixed ACME snippet "Obtain Certificate" concurrent issues in save EAB and DNS credentials
|
||||
|
||||
|
||||
# v3.0.3 Apr 30 2024
|
||||
## Breaking Change
|
||||
|
||||
For users using SMTP with older versions, you might need to update the settings by moving the domains (the part after @ in the username and domain setup field) into the username field.
|
||||
|
||||
+ Updated SMTP UI for non email login username [#129](https://github.com/tobychui/zoraxy/issues/129)
|
||||
+ Fixed ACME cert store reload after cert request [#126](https://github.com/tobychui/zoraxy/issues/126)
|
||||
+ Fixed default rule not applying to default site when default site is set to proxy target [#130](https://github.com/tobychui/zoraxy/issues/130)
|
||||
+ Fixed blacklist-ip not working with CIDR bug
|
||||
+ Fixed minor vdir bug in tailing slash detection and redirect logic
|
||||
+ Added custom mdns name support (-mdnsname flag)
|
||||
+ Added LAN tag in statistic [#131](https://github.com/tobychui/zoraxy/issues/131)
|
||||
|
||||
|
||||
# v3.0.2 Apr 24 2024
|
||||
|
||||
+ Added alias for HTTP proxy host names [#76](https://github.com/tobychui/zoraxy/issues/76)
|
||||
+ Added separator support for create new proxy rules (use "," to add alias when creating new proxy rule)
|
||||
+ Added HTTP proxy host based access rules [#69](https://github.com/tobychui/zoraxy/issues/69)
|
||||
+ Added EAD Configuration for ACME (by [yeungalan](https://github.com/yeungalan)) [#45](https://github.com/tobychui/zoraxy/issues/45)
|
||||
+ Fixed bug for bypassGlobalTLS endpoint do not support basic-auth
|
||||
+ Fixed panic due to empty domain field in json config [#120](https://github.com/tobychui/zoraxy/issues/120)
|
||||
+ Removed dependencies on management panel css for online font files
|
||||
|
||||
# v3.0.1 Apr 04 2024
|
||||
|
||||
## Bugfixupdate for big release of V3, read update notes from V3 if you are still on V2
|
||||
|
||||
+ Added regex support for redirect (slow, don't use it unless you really needs it) [#42](https://github.com/tobychui/zoraxy/issues/42)
|
||||
+ Added new dpcore implementations for faster proxy speed
|
||||
+ Added support for CF-Connecting-IP to X-Real-IP auto rewrite [#114](https://github.com/tobychui/zoraxy/issues/114)
|
||||
+ Added enable / disable of HTTP proxy rules in runtime via slider [#108](https://github.com/tobychui/zoraxy/issues/108)
|
||||
+ Added better 404 page
|
||||
+ Added option to bypass websocket origin check [#107](https://github.com/tobychui/zoraxy/issues/107)
|
||||
+ Updated project homepage design
|
||||
+ Fixed recursive port detection logic
|
||||
+ Fixed UserAgent in resp bug
|
||||
+ Updated minimum required Go version to v1.22 (Notes: Windows 7 support is dropped) [#112](https://github.com/tobychui/zoraxy/issues/112)
|
||||
|
||||
|
||||
# v3.0.0 Feb 18 2024
|
||||
|
||||
## IMPORTANT: V3 is a big rewrite and it is incompatible with V2! There is NO migration, if you want to stay on V2, please use V2 branch!
|
||||
|
36
README.md
36
README.md
@ -2,22 +2,23 @@
|
||||
|
||||
# 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
|
||||
|
||||
- Simple to use interface with detail in-system instructions
|
||||
- Reverse Proxy
|
||||
- Reverse Proxy (HTTP/2)
|
||||
- Virtual Directory
|
||||
- WebSocket Proxy (automatic, no set-up needed)
|
||||
- Basic Auth
|
||||
- Alias Hostnames
|
||||
- Custom Headers
|
||||
- Redirection Rules
|
||||
- TLS / SSL setup and deploy
|
||||
- ACME features like auto-renew to serve your sites in http**s**
|
||||
- SNI support (one certificate contains multiple host names)
|
||||
|
||||
- SNI support (and SAN certs)
|
||||
- DNS Challenge for Let's Encrypt and [these DNS providers](https://go-acme.github.io/lego/dns/)
|
||||
- Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)
|
||||
- Global Area Network Controller Web UI (ZeroTier not included)
|
||||
- TCP Tunneling / Proxy
|
||||
@ -33,13 +34,21 @@ General purpose request (reverse) proxy and forwarding tool for networking noobs
|
||||
- SMTP config for password reset
|
||||
|
||||
## Downloads
|
||||
|
||||
[Windows](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe)
|
||||
/[Linux (amd64)](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_amd64)
|
||||
/[Linux (arm64)](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_arm64)
|
||||
|
||||
For other systems or architectures, please see [Release](https://github.com/tobychui/zoraxy/releases/latest/)
|
||||
|
||||
## Getting Started
|
||||
[Installing Zoraxy Reverse Proxy: Your Gateway to Efficient Web Routing](https://geekscircuit.com/installing-zoraxy-reverse-proxy-your-gateway-to-efficient-web-routing/)
|
||||
|
||||
Thank you for the well written and easy to follow tutorial by Reddit users [itsvmn](https://www.reddit.com/user/itsvmn/)!
|
||||
If you have no background in setting up reverse proxy or web routing, you should check this out before you start setting up your Zoraxy.
|
||||
|
||||
## Build from Source
|
||||
|
||||
Requires Go 1.22 or higher
|
||||
|
||||
```bash
|
||||
@ -57,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
|
||||
|
||||
@ -78,21 +87,23 @@ The installation method is same as Linux. If you are using a Raspberry Pi 4 or n
|
||||
The installation method is same as Linux. For other ARM SBCs, please refer to your SBC's CPU architecture and pick the one that is suitable for your device.
|
||||
|
||||
#### Docker
|
||||
|
||||
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details.
|
||||
|
||||
### Start Paramters
|
||||
|
||||
```
|
||||
Usage of zoraxy:
|
||||
-autorenew int
|
||||
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
|
||||
-fastgeoip
|
||||
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
|
||||
-info
|
||||
Show information about this program in JSON
|
||||
-log
|
||||
Log terminal output to file (default true)
|
||||
-mdns
|
||||
Enable mDNS scanner and transponder (default true)
|
||||
-mdnsname string
|
||||
mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)
|
||||
-noauth
|
||||
Disable authentication for management interface
|
||||
-port string
|
||||
@ -152,12 +163,13 @@ This allows you to have an infinite number of network members in your Global Are
|
||||
## Web SSH
|
||||
|
||||
Web SSH currently only supports Linux based OSes. The following platforms are supported:
|
||||
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/armv6 (experimental)
|
||||
- linux/386 (experimental)
|
||||
|
||||
### Loopback Connection
|
||||
### Loopback Connection
|
||||
|
||||
Loopback web SSH connection, by default, is disabled. This means that if you are trying to connect to an address like 127.0.0.1 or localhost, the system will reject your connection for security reasons. To enable loopback for testing or development purpose, use the following flags to override the loopback checking:
|
||||
|
||||
@ -166,12 +178,14 @@ Loopback web SSH connection, by default, is disabled. This means that if you are
|
||||
```
|
||||
|
||||
## Sponsor This Project
|
||||
|
||||
If you like the project and want to support us, please consider a donation. You can use the links below
|
||||
|
||||
- [tobychui (Primary author)](https://paypal.me/tobychui)
|
||||
- PassiveLemon (Docker compatibility maintainer)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This project is open-sourced under AGPL. I open-sourced this project so everyone can check for security issues and benefit all users. **If you plan to use this project in a commercial environment (which violate the AGPL terms), please contact toby@imuslab.com for an alternative license.**
|
||||
This project is open-sourced under AGPL. I open-sourced this project so everyone can check for security issues and benefit all users. **This software is intended to be free of charge. If you have acquired this software from a third-party seller, the authors of this repository bears no responsibility for any technical difficulties assistance or support.**
|
||||
|
||||
|
||||
|
@ -8,10 +8,7 @@ RUN mkdir -p /opt/zoraxy/source/ &&\
|
||||
mkdir -p /opt/zoraxy/config/ &&\
|
||||
mkdir -p /usr/local/bin/
|
||||
|
||||
COPY entrypoint.sh /opt/zoraxy/
|
||||
|
||||
RUN chmod -R 755 /opt/zoraxy/ &&\
|
||||
chmod +x /opt/zoraxy/entrypoint.sh
|
||||
RUN chmod -R 770 /opt/zoraxy/
|
||||
|
||||
VOLUME [ "/opt/zoraxy/config/" ]
|
||||
|
||||
@ -24,15 +21,15 @@ RUN go mod tidy &&\
|
||||
go build -o /usr/local/bin/zoraxy &&\
|
||||
rm -r /opt/zoraxy/source/
|
||||
|
||||
RUN chmod +x /usr/local/bin/zoraxy
|
||||
RUN chmod 755 /usr/local/bin/zoraxy &&\
|
||||
chmod +x /usr/local/bin/zoraxy
|
||||
|
||||
WORKDIR /opt/zoraxy/config/
|
||||
|
||||
ENV VERSION=$VERSION
|
||||
|
||||
ENV ARGS="-noauth=false"
|
||||
|
||||
ENTRYPOINT ["/opt/zoraxy/entrypoint.sh"]
|
||||
ENTRYPOINT "zoraxy" "-port=:8000" "${ARGS}"
|
||||
|
||||
HEALTHCHECK --interval=5s --timeout=5s --retries=2 CMD nc -vz 127.0.0.1 8000 || exit 1
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
echo "Zoraxy version $VERSION"
|
||||
|
||||
zoraxy -port=:8000 ${ARGS}
|
@ -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>
|
||||
|
18
example/README.md
Normal file
18
example/README.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Example www Folder
|
||||
|
||||
This is an example www folder that contains two sub-folders.
|
||||
|
||||
- `html/`
|
||||
- `templates/`
|
||||
|
||||
The html file contain static resources that will be served by Zoraxy build-in static web server. You can use it as a generic web server with a static site generator like [Hugo](https://gohugo.io/) or use it as a small CDN for serving your scripts / image that commonly use across many of your sites.
|
||||
|
||||
The templates folder contains the template for overriding the build in error or access denied pages. The following templates are supported
|
||||
|
||||
- notfound.html (Default site Not-Found error page)
|
||||
- whitelist.html (Error page when client being blocked by whitelist rule)
|
||||
- blacklist.html (Error page when client being blocked by blacklist rule)
|
||||
|
||||
To use the template, copy and paste the `wwww` folder to the same directory as zoraxy executable (aka the src/ file if you `go build` with the current folder tree).
|
||||
|
||||
It is worth mentioning that the uwu icons for not-found and access-denied are created by @SAWARATSUKI
|
229
example/www/html/index.html
Normal file
229
example/www/html/index.html
Normal file
@ -0,0 +1,229 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Zoraxy Firework!</title>
|
||||
<style>
|
||||
body{
|
||||
margin: 0 !important;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js" integrity="sha512-aNMyYYxdIxIaot0Y1/PLuEu3eipGCmsEUBrUq+7aVyPGMFH8z0eTP0tkqAvv34fzN6z+201d3T8HPb1svWSKHQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c"></canvas>
|
||||
<script>
|
||||
var c = document.getElementById("c");
|
||||
var ctx = c.getContext("2d");
|
||||
var cH;
|
||||
var cW;
|
||||
var bgColor = "#FF6138";
|
||||
var animations = [];
|
||||
var circles = [];
|
||||
|
||||
var colorPicker = (function() {
|
||||
var colors = ["#FF6138", "#FFBE53", "#2980B9", "#FCFCFC", "#282741"];
|
||||
var index = 0;
|
||||
function next() {
|
||||
index = index++ < colors.length-1 ? index : 0;
|
||||
return colors[index];
|
||||
}
|
||||
function current() {
|
||||
return colors[index]
|
||||
}
|
||||
return {
|
||||
next: next,
|
||||
current: current
|
||||
}
|
||||
})();
|
||||
|
||||
function removeAnimation(animation) {
|
||||
var index = animations.indexOf(animation);
|
||||
if (index > -1) animations.splice(index, 1);
|
||||
}
|
||||
|
||||
function calcPageFillRadius(x, y) {
|
||||
var l = Math.max(x - 0, cW - x);
|
||||
var h = Math.max(y - 0, cH - y);
|
||||
return Math.sqrt(Math.pow(l, 2) + Math.pow(h, 2));
|
||||
}
|
||||
|
||||
function addClickListeners() {
|
||||
document.addEventListener("touchstart", handleEvent);
|
||||
document.addEventListener("mousedown", handleEvent);
|
||||
};
|
||||
|
||||
function handleEvent(e) {
|
||||
if (e.touches) {
|
||||
e.preventDefault();
|
||||
e = e.touches[0];
|
||||
}
|
||||
var currentColor = colorPicker.current();
|
||||
var nextColor = colorPicker.next();
|
||||
var targetR = calcPageFillRadius(e.pageX, e.pageY);
|
||||
var rippleSize = Math.min(200, (cW * .4));
|
||||
var minCoverDuration = 750;
|
||||
|
||||
var pageFill = new Circle({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
r: 0,
|
||||
fill: nextColor
|
||||
});
|
||||
var fillAnimation = anime({
|
||||
targets: pageFill,
|
||||
r: targetR,
|
||||
duration: Math.max(targetR / 2 , minCoverDuration ),
|
||||
easing: "easeOutQuart",
|
||||
complete: function(){
|
||||
bgColor = pageFill.fill;
|
||||
removeAnimation(fillAnimation);
|
||||
}
|
||||
});
|
||||
|
||||
var ripple = new Circle({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
r: 0,
|
||||
fill: currentColor,
|
||||
stroke: {
|
||||
width: 3,
|
||||
color: currentColor
|
||||
},
|
||||
opacity: 1
|
||||
});
|
||||
var rippleAnimation = anime({
|
||||
targets: ripple,
|
||||
r: rippleSize,
|
||||
opacity: 0,
|
||||
easing: "easeOutExpo",
|
||||
duration: 900,
|
||||
complete: removeAnimation
|
||||
});
|
||||
|
||||
var particles = [];
|
||||
for (var i=0; i<32; i++) {
|
||||
var particle = new Circle({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
fill: currentColor,
|
||||
r: anime.random(24, 48)
|
||||
})
|
||||
particles.push(particle);
|
||||
}
|
||||
var particlesAnimation = anime({
|
||||
targets: particles,
|
||||
x: function(particle){
|
||||
return particle.x + anime.random(rippleSize, -rippleSize);
|
||||
},
|
||||
y: function(particle){
|
||||
return particle.y + anime.random(rippleSize * 1.15, -rippleSize * 1.15);
|
||||
},
|
||||
r: 0,
|
||||
easing: "easeOutExpo",
|
||||
duration: anime.random(1000,1300),
|
||||
complete: removeAnimation
|
||||
});
|
||||
animations.push(fillAnimation, rippleAnimation, particlesAnimation);
|
||||
}
|
||||
|
||||
function extend(a, b){
|
||||
for(var key in b) {
|
||||
if(b.hasOwnProperty(key)) {
|
||||
a[key] = b[key];
|
||||
}
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
var Circle = function(opts) {
|
||||
extend(this, opts);
|
||||
}
|
||||
|
||||
Circle.prototype.draw = function() {
|
||||
ctx.globalAlpha = this.opacity || 1;
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
|
||||
if (this.stroke) {
|
||||
ctx.strokeStyle = this.stroke.color;
|
||||
ctx.lineWidth = this.stroke.width;
|
||||
ctx.stroke();
|
||||
}
|
||||
if (this.fill) {
|
||||
ctx.fillStyle = this.fill;
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
var animate = anime({
|
||||
duration: Infinity,
|
||||
update: function() {
|
||||
ctx.fillStyle = bgColor;
|
||||
ctx.fillRect(0, 0, cW, cH);
|
||||
animations.forEach(function(anim) {
|
||||
anim.animatables.forEach(function(animatable) {
|
||||
animatable.target.draw();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var resizeCanvas = function() {
|
||||
cW = window.innerWidth;
|
||||
cH = window.innerHeight;
|
||||
c.width = cW * devicePixelRatio;
|
||||
c.height = cH * devicePixelRatio;
|
||||
ctx.scale(devicePixelRatio, devicePixelRatio);
|
||||
};
|
||||
|
||||
(function init() {
|
||||
resizeCanvas();
|
||||
if (window.CP) {
|
||||
// CodePen's loop detection was causin' problems
|
||||
// and I have no idea why, so...
|
||||
window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 6000;
|
||||
}
|
||||
window.addEventListener("resize", resizeCanvas);
|
||||
addClickListeners();
|
||||
if (!!window.location.pathname.match(/fullcpgrid/)) {
|
||||
startFauxClicking();
|
||||
}
|
||||
handleInactiveUser();
|
||||
})();
|
||||
|
||||
function handleInactiveUser() {
|
||||
var inactive = setTimeout(function(){
|
||||
fauxClick(cW/2, cH/2);
|
||||
}, 2000);
|
||||
|
||||
function clearInactiveTimeout() {
|
||||
clearTimeout(inactive);
|
||||
document.removeEventListener("mousedown", clearInactiveTimeout);
|
||||
document.removeEventListener("touchstart", clearInactiveTimeout);
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", clearInactiveTimeout);
|
||||
document.addEventListener("touchstart", clearInactiveTimeout);
|
||||
}
|
||||
|
||||
function startFauxClicking() {
|
||||
setTimeout(function(){
|
||||
fauxClick(anime.random( cW * .2, cW * .8), anime.random(cH * .2, cH * .8));
|
||||
startFauxClicking();
|
||||
}, anime.random(200, 900));
|
||||
}
|
||||
|
||||
function fauxClick(x, y) {
|
||||
var fauxClick = new Event("mousedown");
|
||||
fauxClick.pageX = x;
|
||||
fauxClick.pageY = y;
|
||||
document.dispatchEvent(fauxClick);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
52
example/www/templates/blacklist.html
Normal file
52
example/www/templates/blacklist.html
Normal file
File diff suppressed because one or more lines are too long
42
example/www/templates/notfound.html
Normal file
42
example/www/templates/notfound.html
Normal file
File diff suppressed because one or more lines are too long
52
example/www/templates/whitelist.html
Normal file
52
example/www/templates/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.
@ -3,9 +3,12 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -17,6 +20,157 @@ import (
|
||||
banning / whitelist a specific IP address or country code
|
||||
*/
|
||||
|
||||
/*
|
||||
General Function
|
||||
*/
|
||||
|
||||
func handleListAccessRules(w http.ResponseWriter, r *http.Request) {
|
||||
allAccessRules := accessController.ListAllAccessRules()
|
||||
js, _ := json.Marshal(allAccessRules)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func handleAttachRuleToHost(w http.ResponseWriter, r *http.Request) {
|
||||
ruleid, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid rule name")
|
||||
return
|
||||
}
|
||||
|
||||
host, err := utils.PostPara(r, "host")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid rule name")
|
||||
return
|
||||
}
|
||||
|
||||
//Check if access rule and proxy rule exists
|
||||
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(host)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid host given")
|
||||
return
|
||||
}
|
||||
if !accessController.AccessRuleExists(ruleid) {
|
||||
utils.SendErrorResponse(w, "access rule not exists")
|
||||
return
|
||||
}
|
||||
|
||||
//Update the proxy host acess rule id
|
||||
targetProxyEndpoint.AccessFilterUUID = ruleid
|
||||
targetProxyEndpoint.UpdateToRuntime()
|
||||
err = SaveReverseProxyConfig(targetProxyEndpoint)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
// Create a new access rule, require name and desc only
|
||||
func handleCreateAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||
ruleName, err := utils.PostPara(r, "name")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid rule name")
|
||||
return
|
||||
}
|
||||
ruleDesc, _ := utils.PostPara(r, "desc")
|
||||
|
||||
//Filter out injection if any
|
||||
p := bluemonday.StripTagsPolicy()
|
||||
ruleName = p.Sanitize(ruleName)
|
||||
ruleDesc = p.Sanitize(ruleDesc)
|
||||
|
||||
ruleUUID := uuid.New().String()
|
||||
newAccessRule := access.AccessRule{
|
||||
ID: ruleUUID,
|
||||
Name: ruleName,
|
||||
Desc: ruleDesc,
|
||||
BlacklistEnabled: false,
|
||||
WhitelistEnabled: false,
|
||||
}
|
||||
|
||||
//Add it to runtime
|
||||
err = accessController.AddNewAccessRule(&newAccessRule)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
// Handle removing an access rule. All proxy endpoint using this rule will be
|
||||
// set to use the default rule
|
||||
func handleRemoveAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid rule id given")
|
||||
return
|
||||
}
|
||||
|
||||
if ruleID == "default" {
|
||||
utils.SendErrorResponse(w, "default access rule cannot be removed")
|
||||
return
|
||||
}
|
||||
|
||||
ruleID = strings.TrimSpace(ruleID)
|
||||
|
||||
//Set all proxy hosts that use this access rule back to using "default"
|
||||
allProxyEndpoints := dynamicProxyRouter.GetProxyEndpointsAsMap()
|
||||
for _, proxyEndpoint := range allProxyEndpoints {
|
||||
if strings.EqualFold(proxyEndpoint.AccessFilterUUID, ruleID) {
|
||||
//This proxy endpoint is using the current access filter.
|
||||
//set it to default
|
||||
proxyEndpoint.AccessFilterUUID = "default"
|
||||
proxyEndpoint.UpdateToRuntime()
|
||||
err = SaveReverseProxyConfig(proxyEndpoint)
|
||||
if err != nil {
|
||||
SystemWideLogger.PrintAndLog("Access", "Unable to save updated proxy endpoint "+proxyEndpoint.RootOrMatchingDomain, err)
|
||||
} else {
|
||||
SystemWideLogger.PrintAndLog("Access", "Updated "+proxyEndpoint.RootOrMatchingDomain+" access filter to \"default\"", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Remove the access rule by ID
|
||||
err = accessController.RemoveAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
SystemWideLogger.PrintAndLog("Access", "Access Rule "+ruleID+" removed", nil)
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
// Only the name and desc, for other properties use blacklist / whitelist api
|
||||
func handleUpadateAccessRule(w http.ResponseWriter, r *http.Request) {
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid rule id")
|
||||
return
|
||||
}
|
||||
ruleName, err := utils.PostPara(r, "name")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid rule name")
|
||||
return
|
||||
}
|
||||
ruleDesc, _ := utils.PostPara(r, "desc")
|
||||
|
||||
//Filter anything weird
|
||||
p := bluemonday.StrictPolicy()
|
||||
ruleName = p.Sanitize(ruleName)
|
||||
ruleDesc = p.Sanitize(ruleDesc)
|
||||
|
||||
err = accessController.UpdateAccessRule(ruleID, ruleName, ruleDesc)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
/*
|
||||
Blacklist Related
|
||||
*/
|
||||
@ -28,11 +182,24 @@ func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
|
||||
bltype = "country"
|
||||
}
|
||||
|
||||
ruleID, err := utils.GetPara(r, "id")
|
||||
if err != nil {
|
||||
//Use default if not set
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
//Load the target rule from access controller
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resulst := []string{}
|
||||
if bltype == "country" {
|
||||
resulst = geodbStore.GetAllBlacklistedCountryCode()
|
||||
resulst = rule.GetAllBlacklistedCountryCode()
|
||||
} else if bltype == "ip" {
|
||||
resulst = geodbStore.GetAllBlacklistedIp()
|
||||
resulst = rule.GetAllBlacklistedIp()
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(resulst)
|
||||
@ -47,7 +214,23 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.AddCountryCodeToBlackList(countryCode)
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
comment, _ := utils.PostPara(r, "comment")
|
||||
p := bluemonday.StripTagsPolicy()
|
||||
comment = p.Sanitize(comment)
|
||||
|
||||
//Load the target rule from access controller
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rule.AddCountryCodeToBlackList(countryCode, comment)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
@ -59,7 +242,19 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.RemoveCountryCodeFromBlackList(countryCode)
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
//Load the target rule from access controller
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rule.RemoveCountryCodeFromBlackList(countryCode)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
@ -71,7 +266,24 @@ func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.AddIPToBlackList(ipAddr)
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
//Load the target rule from access controller
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
comment, _ := utils.GetPara(r, "comment")
|
||||
p := bluemonday.StripTagsPolicy()
|
||||
comment = p.Sanitize(comment)
|
||||
|
||||
rule.AddIPToBlackList(ipAddr, comment)
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
@ -81,23 +293,46 @@ func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.RemoveIPFromBlackList(ipAddr)
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
//Load the target rule from access controller
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rule.RemoveIPFromBlackList(ipAddr)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
|
||||
enable, err := utils.PostPara(r, "enable")
|
||||
enable, _ := utils.PostPara(r, "enable")
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
//Return the current enabled state
|
||||
currentEnabled := geodbStore.BlacklistEnabled
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if enable == "" {
|
||||
//enable paramter not set
|
||||
currentEnabled := rule.BlacklistEnabled
|
||||
js, _ := json.Marshal(currentEnabled)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
if enable == "true" {
|
||||
geodbStore.ToggleBlacklist(true)
|
||||
rule.ToggleBlacklist(true)
|
||||
} else if enable == "false" {
|
||||
geodbStore.ToggleBlacklist(false)
|
||||
rule.ToggleBlacklist(false)
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||
return
|
||||
@ -117,11 +352,22 @@ func handleListWhitelisted(w http.ResponseWriter, r *http.Request) {
|
||||
bltype = "country"
|
||||
}
|
||||
|
||||
resulst := []*geodb.WhitelistEntry{}
|
||||
ruleID, err := utils.GetPara(r, "id")
|
||||
if err != nil {
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resulst := []*access.WhitelistEntry{}
|
||||
if bltype == "country" {
|
||||
resulst = geodbStore.GetAllWhitelistedCountryCode()
|
||||
resulst = rule.GetAllWhitelistedCountryCode()
|
||||
} else if bltype == "ip" {
|
||||
resulst = geodbStore.GetAllWhitelistedIp()
|
||||
resulst = rule.GetAllWhitelistedIp()
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(resulst)
|
||||
@ -136,11 +382,22 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
comment, _ := utils.PostPara(r, "comment")
|
||||
p := bluemonday.StrictPolicy()
|
||||
comment = p.Sanitize(comment)
|
||||
|
||||
geodbStore.AddCountryCodeToWhitelist(countryCode, comment)
|
||||
rule.AddCountryCodeToWhitelist(countryCode, comment)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
@ -152,7 +409,18 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.RemoveCountryCodeFromWhitelist(countryCode)
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rule.RemoveCountryCodeFromWhitelist(countryCode)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
@ -164,11 +432,23 @@ func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
comment, _ := utils.PostPara(r, "comment")
|
||||
p := bluemonday.StrictPolicy()
|
||||
comment = p.Sanitize(comment)
|
||||
|
||||
geodbStore.AddIPToWhiteList(ipAddr, comment)
|
||||
rule.AddIPToWhiteList(ipAddr, comment)
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
@ -178,23 +458,45 @@ func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.RemoveIPFromWhiteList(ipAddr)
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rule.RemoveIPFromWhiteList(ipAddr)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
|
||||
enable, err := utils.PostPara(r, "enable")
|
||||
enable, _ := utils.PostPara(r, "enable")
|
||||
ruleID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
ruleID = "default"
|
||||
}
|
||||
|
||||
rule, err := accessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if enable == "" {
|
||||
//Return the current enabled state
|
||||
currentEnabled := geodbStore.WhitelistEnabled
|
||||
currentEnabled := rule.WhitelistEnabled
|
||||
js, _ := json.Marshal(currentEnabled)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
if enable == "true" {
|
||||
geodbStore.ToggleWhitelist(true)
|
||||
rule.ToggleWhitelist(true)
|
||||
} else if enable == "false" {
|
||||
geodbStore.ToggleWhitelist(false)
|
||||
rule.ToggleWhitelist(false)
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||
return
|
||||
|
40
src/acme.go
40
src/acme.go
@ -38,7 +38,7 @@ func initACME() *acme.ACMEHandler {
|
||||
port = getRandomPort(30000)
|
||||
}
|
||||
|
||||
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port))
|
||||
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb)
|
||||
}
|
||||
|
||||
// create the special routing rule for ACME
|
||||
@ -85,22 +85,26 @@ func acmeRegisterSpecialRoutingRule() {
|
||||
// This function check if the renew setup is satisfied. If not, toggle them automatically
|
||||
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
|
||||
isForceHttpsRedirectEnabledOriginally := false
|
||||
if dynamicProxyRouter.Option.Port == 443 {
|
||||
//Enable port 80 to 443 redirect
|
||||
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
|
||||
SystemWideLogger.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests")
|
||||
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
|
||||
dnsPara, _ := utils.PostBool(r, "dns")
|
||||
if !dnsPara {
|
||||
if dynamicProxyRouter.Option.Port == 443 {
|
||||
//Enable port 80 to 443 redirect
|
||||
if !dynamicProxyRouter.Option.ForceHttpsRedirect {
|
||||
SystemWideLogger.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests")
|
||||
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
|
||||
} else {
|
||||
//Set this to true, so after renew, do not turn it off
|
||||
isForceHttpsRedirectEnabledOriginally = true
|
||||
}
|
||||
|
||||
} else if dynamicProxyRouter.Option.Port == 80 {
|
||||
//Go ahead
|
||||
|
||||
} else {
|
||||
//Set this to true, so after renew, do not turn it off
|
||||
isForceHttpsRedirectEnabledOriginally = true
|
||||
//This port do not support ACME
|
||||
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
|
||||
return
|
||||
}
|
||||
|
||||
} else if dynamicProxyRouter.Option.Port == 80 {
|
||||
//Go ahead
|
||||
|
||||
} else {
|
||||
//This port do not support ACME
|
||||
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
|
||||
}
|
||||
|
||||
//Add a 3 second delay to make sure everything is settle down
|
||||
@ -109,7 +113,11 @@ func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request)
|
||||
// Pass over to the acmeHandler to deal with the communication
|
||||
acmeHandler.HandleRenewCertificate(w, r)
|
||||
|
||||
if dynamicProxyRouter.Option.Port == 443 {
|
||||
//Update the TLS cert store buffer
|
||||
tlsCertManager.UpdateLoadedCertList()
|
||||
|
||||
//Restore original settings
|
||||
if dynamicProxyRouter.Option.Port == 443 && !dnsPara {
|
||||
if !isForceHttpsRedirectEnabledOriginally {
|
||||
//Default is off. Turn the redirection off
|
||||
SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil)
|
||||
|
28
src/api.go
28
src/api.go
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
|
||||
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||
"imuslab.com/zoraxy/mod/acme/acmewizard"
|
||||
"imuslab.com/zoraxy/mod/auth"
|
||||
"imuslab.com/zoraxy/mod/netstat"
|
||||
@ -49,7 +50,9 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
||||
authRouter.HandleFunc("/api/proxy/toggle", ReverseProxyToggleRuleSet)
|
||||
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
|
||||
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
|
||||
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
||||
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
||||
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
||||
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
||||
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
||||
@ -87,6 +90,12 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
||||
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
|
||||
|
||||
//Access Rules API
|
||||
authRouter.HandleFunc("/api/access/list", handleListAccessRules)
|
||||
authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
|
||||
authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
|
||||
authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
|
||||
authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)
|
||||
//Blacklist APIs
|
||||
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
||||
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
||||
@ -94,7 +103,6 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
||||
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
||||
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
||||
|
||||
//Whitelist APIs
|
||||
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
||||
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
||||
@ -133,14 +141,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)
|
||||
@ -179,9 +186,12 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HanldeSetDNS)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
||||
authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson)
|
||||
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard
|
||||
|
||||
//Static Web Server
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/acme"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -46,6 +47,7 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
|
||||
LastModifiedDate string
|
||||
ExpireDate string
|
||||
RemainingDays int
|
||||
UseDNS bool
|
||||
}
|
||||
|
||||
results := []*CertInfo{}
|
||||
@ -81,12 +83,19 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
certInfoFilename := filepath.Join(tlsCertManager.CertStore, filename+".json")
|
||||
useDNSValidation := false //Default to false for HTTP TLS certificates
|
||||
certInfo, err := acme.LoadCertInfoJSON(certInfoFilename) //Note: Not all certs have info json
|
||||
if err == nil {
|
||||
useDNSValidation = certInfo.UseDNS
|
||||
}
|
||||
|
||||
thisCertInfo := CertInfo{
|
||||
Domain: filename,
|
||||
LastModifiedDate: modifiedTime,
|
||||
ExpireDate: certExpireTime,
|
||||
RemainingDays: expiredIn,
|
||||
UseDNS: useDNSValidation,
|
||||
}
|
||||
|
||||
results = append(results, &thisCertInfo)
|
||||
|
@ -155,7 +155,7 @@ func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
|
||||
*/
|
||||
|
||||
func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
||||
includeSysDBRaw, err := utils.GetPara(r, "includeDB")
|
||||
includeSysDBRaw, _ := utils.GetPara(r, "includeDB")
|
||||
includeSysDB := false
|
||||
if includeSysDBRaw == "true" {
|
||||
//Include the system database in backup snapshot
|
||||
@ -177,7 +177,7 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
||||
defer zipWriter.Close()
|
||||
|
||||
// Walk through the folder and add files to the zip
|
||||
err = filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error {
|
||||
err := filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -25,12 +25,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
domain, err := utils.PostPara(r, "domain")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "domain cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
portString, err := utils.PostPara(r, "port")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "port must be a valid integer")
|
||||
@ -76,7 +70,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
|
||||
//Set the email sender properties
|
||||
thisEmailSender := email.Sender{
|
||||
Hostname: strings.TrimSpace(hostname),
|
||||
Domain: strings.TrimSpace(domain),
|
||||
Port: port,
|
||||
Username: strings.TrimSpace(username),
|
||||
Password: strings.TrimSpace(password),
|
||||
@ -206,7 +199,7 @@ var (
|
||||
)
|
||||
|
||||
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
|
||||
if EmailSender.Username == "" || EmailSender.Domain == "" {
|
||||
if EmailSender.Username == "" {
|
||||
//Reset account not setup
|
||||
utils.SendErrorResponse(w, "Reset account not setup.")
|
||||
return
|
||||
@ -279,17 +272,14 @@ func HandleNewPasswordSetup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Delete the user account
|
||||
authAgent.UnregisterUser(username)
|
||||
|
||||
//Ok. Set the new password
|
||||
err = authAgent.CreateUserAccount(username, newPassword, "")
|
||||
if err != nil {
|
||||
// Un register the user account
|
||||
if err := authAgent.UnregisterUser(username); err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
//Ok. Set the new password
|
||||
if err := authAgent.CreateUserAccount(username, newPassword, ""); err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
140
src/go.mod
140
src/go.mod
@ -20,15 +20,155 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.20.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/civo/civogo v0.3.11 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.86.0 // indirect
|
||||
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/dnsimple/dnsimple-go v1.2.0 // indirect
|
||||
github.com/exoscale/egoscale v0.102.3 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/go-resty/resty/v2 v2.11.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/s2a-go v0.1.4 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
|
||||
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||
github.com/linode/linodego v1.28.0 // indirect
|
||||
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
|
||||
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/miekg/dns v1.1.58 // indirect
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect
|
||||
github.com/nrdcg/desec v0.7.0 // indirect
|
||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.10.0 // indirect
|
||||
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||
github.com/nrdcg/porkbun v0.3.0 // indirect
|
||||
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||
github.com/ovh/go-ovh v1.4.3 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pquerna/otp v1.4.0 // indirect
|
||||
github.com/sacloud/api-client-go v0.2.8 // indirect
|
||||
github.com/sacloud/go-http v0.1.6 // indirect
|
||||
github.com/sacloud/iaas-api-go v1.11.1 // indirect
|
||||
github.com/sacloud/packages-go v0.0.9 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||
github.com/softlayer/softlayer-go v1.1.3 // indirect
|
||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/stretchr/objx v0.5.1 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||
github.com/transip/gotransip/v6 v6.23.0 // indirect
|
||||
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect
|
||||
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/ratelimit v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/oauth2 v0.16.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/api v0.126.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
912
src/go.sum
912
src/go.sum
@ -1,85 +1,993 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
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/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=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE=
|
||||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo=
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2/go.mod h1:FbdwsQ2EzwvXxOPcMFYO8ogEc9uMMIj3YkmCdXdAFmk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 h1:rR8ZW79lE/ppfXTfiYSnMFv5EzmVuY4pfZWIkscIJ64=
|
||||
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-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=
|
||||
github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw=
|
||||
github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
|
||||
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
|
||||
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/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=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0 h1:LvWkxBi/bsWHqj3bFTUuDLl4OAlbaM1HDZ9YPhj5+jg=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0/go.mod h1:35MKNS46RX7Lb9EIFP2bPy3WrJu+bxU6QgLis8K1aa4=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0 h1:f3hBZWtpn9clZGXJoqahQeec9ZPZnu22g8pg+zNyif0=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0/go.mod h1:8qqfpG4mug2JLlEyWPSFhEGvJiaZ9iPmMDDMYc5Xtas=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
|
||||
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
|
||||
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
|
||||
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM=
|
||||
github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpXTstVx0rqHI=
|
||||
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/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=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
|
||||
github.com/deepmap/oapi-codegen v1.9.1 h1:yHmEnA7jSTUMQgV+uN02WpZtwHnz2CBW3mZRIxr1vtI=
|
||||
github.com/deepmap/oapi-codegen v1.9.1/go.mod h1:PLqNAhdedP8ttRpBBkzLKU3bp+Fpy+tTgeAMlztR2cw=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
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/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/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=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/exoscale/egoscale v0.102.3 h1:DYqN2ipoLKpiFoprRGQkp2av/Ze7sUYYlGhi1N62tfY=
|
||||
github.com/exoscale/egoscale v0.102.3/go.mod h1:RPf2Gah6up+6kAEayHTQwqapzXlm93f0VQas/UEGU5c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
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/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=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/getkin/kin-openapi v0.87.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ=
|
||||
github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE=
|
||||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
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-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=
|
||||
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
||||
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
|
||||
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
|
||||
github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
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/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
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=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
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/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=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
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/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/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/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=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
|
||||
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
|
||||
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/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/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=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
|
||||
github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
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=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E=
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU=
|
||||
github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
|
||||
github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
|
||||
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
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/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=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPCK0jE6YNBAevnk=
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
|
||||
github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk=
|
||||
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
|
||||
github.com/labstack/echo/v4 v4.6.3/go.mod h1:Hk5OiHj0kDqmFq7aHe7eDqI7CUhuCrfpupQtLGGLm7A=
|
||||
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
||||
github.com/lestrrat-go/codegen v1.0.2/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM=
|
||||
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
|
||||
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/jwx v1.2.7/go.mod h1:bw24IXWbavc0R2RsOtpXL7RtMyP589yZ1+L7kd09ZGA=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM=
|
||||
github.com/likexian/gokit v0.25.13/go.mod h1:qQhEWFBEfqLCO3/vOEo2EDKd+EycekVtUK4tex+l2H4=
|
||||
github.com/likexian/whois v1.15.1 h1:6vTMI8n9s1eJdmcO4R9h1x99aQWIZZX1CD3am68gApU=
|
||||
github.com/likexian/whois v1.15.1/go.mod h1:/nxmQ6YXvLz+qTxC/QFtEJNAt0zLuRxJrKiWpBJX8X0=
|
||||
github.com/linode/linodego v1.28.0 h1:lzxxJebsYg5cCWRNDLyL2StW3sfMyAwf/FYfxFjFrlk=
|
||||
github.com/linode/linodego v1.28.0/go.mod h1:5oAsx+uinHtVo6U77nXXXtox7MWzUW6aEkTOKXxA9uo=
|
||||
github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
|
||||
github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM=
|
||||
github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ=
|
||||
github.com/liquidweb/liquidweb-go v1.6.4 h1:6S0m3hHSpiLqGD7AFSb7lH/W/qr1wx+tKil9fgIbjMc=
|
||||
github.com/liquidweb/liquidweb-go v1.6.4/go.mod h1:B934JPIIcdA+uTq2Nz5PgOtG6CuCaEvQKe/Ge/5GgZ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
|
||||
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34=
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
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/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=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
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/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=
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 h1:qpB3wZR4+MPK92cTC9zZPnndkJgDgPvQqPUAgVc1NXU=
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9/go.mod h1:HUoHXDrFvidN1NK9Wb/mZKNOfDNutKkzF2Pg71M9hHA=
|
||||
github.com/nrdcg/desec v0.7.0 h1:iuGhi4pstF3+vJWwt292Oqe2+AsSPKDynQna/eu1fDs=
|
||||
github.com/nrdcg/desec v0.7.0/go.mod h1:e1uRqqKv1mJdd5+SQROAhmy75lKMphLzWIuASLkpeFY=
|
||||
github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U=
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/nrdcg/freemyip v0.2.0 h1:/GscavT4GVqAY13HExl5UyoB4wlchv6Cg5NYDGsUoJ8=
|
||||
github.com/nrdcg/freemyip v0.2.0/go.mod h1:HjF0Yz0lSb37HD2ihIyGz9esyGcxbCrrGFLPpKevbx4=
|
||||
github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM=
|
||||
github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4=
|
||||
github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk=
|
||||
github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc=
|
||||
github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw=
|
||||
github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms=
|
||||
github.com/nrdcg/porkbun v0.3.0 h1:jnRV7j2zd3hmh+tSDOGetJyy3+WklaMxbs7HtTTmWMs=
|
||||
github.com/nrdcg/porkbun v0.3.0/go.mod h1:jh1DKz96jGHW+NCdG3AmTbbnQeBlNUz1KeSgeN/cBVw=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/nzdjb/go-metaname v1.0.0 h1:sNASlZC1RM3nSudtBTE1a3ZVTDyTpjqI5WXRPrdZ9Hg=
|
||||
github.com/nzdjb/go-metaname v1.0.0/go.mod h1:0GR0LshZax1Lz4VrOrfNSE4dGvTp7HGjiemdczXT2H4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
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/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=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sacloud/api-client-go v0.2.8 h1:tIY6PZNBX900K66TqEPa4d6UIbedUczfCBnPJkzi8kw=
|
||||
github.com/sacloud/api-client-go v0.2.8/go.mod h1:0CV/kWNYlS1hCNdnk6Wx7Wdg8DPFCnv0zOIzdXjeAeY=
|
||||
github.com/sacloud/go-http v0.1.6 h1:lJGXDt9xrxJiDszRPaN9NIP8MVj10YKMzmnyzdSfI8w=
|
||||
github.com/sacloud/go-http v0.1.6/go.mod h1:oLAHoDJRkptf8sq4fE8oERLkdCh0kJWfWu+paoJY7I0=
|
||||
github.com/sacloud/iaas-api-go v1.11.1 h1:2MsFZ4H1uRdRVx2nVXuERWQ3swoFc3XreIV5hJ3Nsws=
|
||||
github.com/sacloud/iaas-api-go v1.11.1/go.mod h1:uBDSa06F/V0OnoR66jGdbH0PVnCJw+NeE9RVbVgMfss=
|
||||
github.com/sacloud/packages-go v0.0.9 h1:GbinkBLC/eirFhHpLjoDW6JV7+95Rnd2d8RWj7Afeks=
|
||||
github.com/sacloud/packages-go v0.0.9/go.mod h1:k+EEUMF2LlncjbNIJNOqLyZ9wjTESPIWIk1OA7x9j2Q=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22 h1:wJrcTdddKOI8TFxs8cemnhKP2EmKy3yfUKHj3ZdfzYo=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
|
||||
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.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=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
|
||||
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q=
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA=
|
||||
github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
|
||||
github.com/softlayer/softlayer-go v1.1.3 h1:dfFzt5eOKIAyB/b78fHMyDu5ICx0ZtxL9NRhBlf831A=
|
||||
github.com/softlayer/softlayer-go v1.1.3/go.mod h1:Pc7F57OgUKaAam7TtpqkUeqL7QyKknfiUI4R49h41/U=
|
||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ=
|
||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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/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=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/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=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/transip/gotransip/v6 v6.23.0 h1:PsTdjortrEZ8IFFifEryzjVjOy9SgK4ahlnhKBBIQgA=
|
||||
github.com/transip/gotransip/v6 v6.23.0/go.mod h1:nzv9eN2tdsUrm5nG5ZX6AugYIU4qgsMwIn2c0EZLk8c=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
|
||||
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a h1:w4PK5/N9kq8PfNxBv8a5t1bqlYRrVT7XzT7iTPTtiPk=
|
||||
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a/go.mod h1:Xwz7o+ExFtxR/i0aJDnTXuiccQJlOxDgNe6FsZC4TzQ=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ=
|
||||
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.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=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
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/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
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=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA=
|
||||
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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/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=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
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.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=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
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-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=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
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/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/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=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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/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/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=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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/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=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
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-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-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=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
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/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/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=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
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/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=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
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/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=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
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=
|
||||
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=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
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=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.13 h1:r07CLALg18f/L1KIK1ZJdbirBV349UtYT1rDWGjnaTk=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.13/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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=
|
||||
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=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
11
src/main.go
11
src/main.go
@ -12,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/acme"
|
||||
"imuslab.com/zoraxy/mod/auth"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
@ -27,7 +28,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"
|
||||
@ -40,6 +41,7 @@ var noauth = flag.Bool("noauth", false, "Disable authentication for management i
|
||||
var showver = flag.Bool("version", false, "Show version of this server")
|
||||
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
||||
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
||||
var mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
|
||||
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
||||
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||
var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
||||
@ -50,7 +52,7 @@ var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
|
||||
|
||||
var (
|
||||
name = "Zoraxy"
|
||||
version = "3.0.1"
|
||||
version = "3.0.6"
|
||||
nodeUUID = "generic"
|
||||
development = false //Set this to false to use embedded web fs
|
||||
bootTime = time.Now().Unix()
|
||||
@ -69,14 +71,15 @@ var (
|
||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||
geodbStore *geodb.Store //GeoIP database, also handle black list and whitelist features
|
||||
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
|
||||
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
|
||||
|
221
src/mod/access/access.go
Normal file
221
src/mod/access/access.go
Normal file
@ -0,0 +1,221 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
/*
|
||||
Access.go
|
||||
|
||||
This module is the new version of access control system
|
||||
where now the blacklist / whitelist are seperated from
|
||||
geodb module
|
||||
*/
|
||||
|
||||
// Create a new access controller to handle blacklist / whitelist
|
||||
func NewAccessController(options *Options) (*Controller, error) {
|
||||
sysdb := options.Database
|
||||
if sysdb == nil {
|
||||
return nil, errors.New("missing database access")
|
||||
}
|
||||
|
||||
//Create the config folder if not exists
|
||||
confFolder := options.ConfigFolder
|
||||
if !utils.FileExists(confFolder) {
|
||||
err := os.MkdirAll(confFolder, 0775)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Create the global access rule if not exists
|
||||
var defaultAccessRule = AccessRule{
|
||||
ID: "default",
|
||||
Name: "Default",
|
||||
Desc: "Default access rule for all HTTP proxy hosts",
|
||||
BlacklistEnabled: false,
|
||||
WhitelistEnabled: false,
|
||||
WhiteListCountryCode: &map[string]string{},
|
||||
WhiteListIP: &map[string]string{},
|
||||
BlackListContryCode: &map[string]string{},
|
||||
BlackListIP: &map[string]string{},
|
||||
}
|
||||
defaultRuleSettingFile := filepath.Join(confFolder, "default.json")
|
||||
if utils.FileExists(defaultRuleSettingFile) {
|
||||
//Load from file
|
||||
defaultRuleBytes, err := os.ReadFile(defaultRuleSettingFile)
|
||||
if err == nil {
|
||||
err = json.Unmarshal(defaultRuleBytes, &defaultAccessRule)
|
||||
if err != nil {
|
||||
options.Logger.PrintAndLog("Access", "Unable to parse default routing rule config file. Using default", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Create one
|
||||
js, _ := json.MarshalIndent(defaultAccessRule, "", " ")
|
||||
os.WriteFile(defaultRuleSettingFile, js, 0775)
|
||||
}
|
||||
|
||||
//Generate a controller object
|
||||
thisController := Controller{
|
||||
DefaultAccessRule: &defaultAccessRule,
|
||||
ProxyAccessRule: &sync.Map{},
|
||||
Options: options,
|
||||
}
|
||||
|
||||
//Assign default access rule parent
|
||||
thisController.DefaultAccessRule.parent = &thisController
|
||||
|
||||
//Load all acccess rules from file
|
||||
configFiles, err := filepath.Glob(options.ConfigFolder + "/*.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ProxyAccessRules := sync.Map{}
|
||||
for _, configFile := range configFiles {
|
||||
if filepath.Base(configFile) == "default.json" {
|
||||
//Skip this, as this was already loaded as default
|
||||
continue
|
||||
}
|
||||
|
||||
configContent, err := os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
options.Logger.PrintAndLog("Access", "Unable to load config "+filepath.Base(configFile), err)
|
||||
continue
|
||||
}
|
||||
|
||||
//Parse the config file into AccessRule
|
||||
thisAccessRule := AccessRule{}
|
||||
err = json.Unmarshal(configContent, &thisAccessRule)
|
||||
if err != nil {
|
||||
options.Logger.PrintAndLog("Access", "Unable to parse config "+filepath.Base(configFile), err)
|
||||
continue
|
||||
}
|
||||
thisAccessRule.parent = &thisController
|
||||
ProxyAccessRules.Store(thisAccessRule.ID, &thisAccessRule)
|
||||
}
|
||||
thisController.ProxyAccessRule = &ProxyAccessRules
|
||||
|
||||
return &thisController, nil
|
||||
}
|
||||
|
||||
// Get the global access rule
|
||||
func (c *Controller) GetGlobalAccessRule() (*AccessRule, error) {
|
||||
if c.DefaultAccessRule == nil {
|
||||
return nil, errors.New("global access rule is not set")
|
||||
}
|
||||
return c.DefaultAccessRule, nil
|
||||
}
|
||||
|
||||
// Load access rules to runtime, require rule ID
|
||||
func (c *Controller) GetAccessRuleByID(accessRuleID string) (*AccessRule, error) {
|
||||
if accessRuleID == "default" || accessRuleID == "" {
|
||||
|
||||
return c.DefaultAccessRule, nil
|
||||
}
|
||||
//Load from sync.Map, should be O(1)
|
||||
targetRule, ok := c.ProxyAccessRule.Load(accessRuleID)
|
||||
|
||||
if !ok {
|
||||
return nil, errors.New("target access rule not exists")
|
||||
}
|
||||
|
||||
ar, ok := targetRule.(*AccessRule)
|
||||
if !ok {
|
||||
return nil, errors.New("assertion of access rule failed, version too old?")
|
||||
}
|
||||
return ar, nil
|
||||
}
|
||||
|
||||
// Return all the access rules currently in runtime, including default
|
||||
func (c *Controller) ListAllAccessRules() []*AccessRule {
|
||||
results := []*AccessRule{c.DefaultAccessRule}
|
||||
c.ProxyAccessRule.Range(func(key, value interface{}) bool {
|
||||
results = append(results, value.(*AccessRule))
|
||||
return true
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// Check if an access rule exists given the rule id
|
||||
func (c *Controller) AccessRuleExists(ruleID string) bool {
|
||||
r, _ := c.GetAccessRuleByID(ruleID)
|
||||
if r != nil {
|
||||
//An access rule with identical ID exists
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Add a new access rule to runtime and save it to file
|
||||
func (c *Controller) AddNewAccessRule(newRule *AccessRule) error {
|
||||
r, _ := c.GetAccessRuleByID(newRule.ID)
|
||||
if r != nil {
|
||||
//An access rule with identical ID exists
|
||||
return errors.New("access rule already exists")
|
||||
}
|
||||
|
||||
//Check if the blacklist and whitelist are populated with empty map
|
||||
if newRule.BlackListContryCode == nil {
|
||||
newRule.BlackListContryCode = &map[string]string{}
|
||||
}
|
||||
if newRule.BlackListIP == nil {
|
||||
newRule.BlackListIP = &map[string]string{}
|
||||
}
|
||||
if newRule.WhiteListCountryCode == nil {
|
||||
newRule.WhiteListCountryCode = &map[string]string{}
|
||||
}
|
||||
if newRule.WhiteListIP == nil {
|
||||
newRule.WhiteListIP = &map[string]string{}
|
||||
}
|
||||
|
||||
//Add access rule to runtime
|
||||
newRule.parent = c
|
||||
c.ProxyAccessRule.Store(newRule.ID, newRule)
|
||||
|
||||
//Save rule to file
|
||||
newRule.SaveChanges()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update the access rule meta info.
|
||||
func (c *Controller) UpdateAccessRule(ruleID string, name string, desc string) error {
|
||||
targetAccessRule, err := c.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
///Update the name and desc
|
||||
targetAccessRule.Name = name
|
||||
targetAccessRule.Desc = desc
|
||||
|
||||
//Overwrite the rule currently in sync map
|
||||
if ruleID == "default" {
|
||||
c.DefaultAccessRule = targetAccessRule
|
||||
} else {
|
||||
c.ProxyAccessRule.Store(ruleID, targetAccessRule)
|
||||
}
|
||||
return targetAccessRule.SaveChanges()
|
||||
}
|
||||
|
||||
// Remove the access rule by its id
|
||||
func (c *Controller) RemoveAccessRuleByID(ruleID string) error {
|
||||
if !c.AccessRuleExists(ruleID) {
|
||||
return errors.New("access rule not exists")
|
||||
}
|
||||
|
||||
//Default cannot be removed
|
||||
if ruleID == "default" {
|
||||
return errors.New("default access rule cannot be removed")
|
||||
}
|
||||
|
||||
//Remove it
|
||||
return c.DeleteAccessRuleByID(ruleID)
|
||||
}
|
153
src/mod/access/accessRule.go
Normal file
153
src/mod/access/accessRule.go
Normal file
@ -0,0 +1,153 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Check both blacklist and whitelist for access for both geoIP and ip / CIDR ranges
|
||||
func (s *AccessRule) AllowIpAccess(ipaddr string) bool {
|
||||
if s.IsBlacklisted(ipaddr) {
|
||||
return false
|
||||
}
|
||||
|
||||
return s.IsWhitelisted(ipaddr)
|
||||
}
|
||||
|
||||
// Check both blacklist and whitelist for access using net.Conn
|
||||
func (s *AccessRule) AllowConnectionAccess(conn net.Conn) bool {
|
||||
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||
return s.AllowIpAccess(addr.IP.String())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Toggle black list
|
||||
func (s *AccessRule) ToggleBlacklist(enabled bool) {
|
||||
s.BlacklistEnabled = enabled
|
||||
s.SaveChanges()
|
||||
}
|
||||
|
||||
// Toggel white list
|
||||
func (s *AccessRule) ToggleWhitelist(enabled bool) {
|
||||
s.WhitelistEnabled = enabled
|
||||
s.SaveChanges()
|
||||
}
|
||||
|
||||
/*
|
||||
Check if a IP address is blacklisted, in either country or IP blacklist
|
||||
IsBlacklisted default return is false (allow access)
|
||||
*/
|
||||
func (s *AccessRule) IsBlacklisted(ipAddr string) bool {
|
||||
if !s.BlacklistEnabled {
|
||||
//Blacklist not enabled. Always return false
|
||||
return false
|
||||
}
|
||||
|
||||
if ipAddr == "" {
|
||||
//Unable to get the target IP address
|
||||
return false
|
||||
}
|
||||
|
||||
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if s.IsCountryCodeBlacklisted(countryCode.CountryIsoCode) {
|
||||
return true
|
||||
}
|
||||
|
||||
if s.IsIPBlacklisted(ipAddr) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
IsWhitelisted check if a given IP address is in the current
|
||||
server's white list.
|
||||
|
||||
Note that the Whitelist default result is true even
|
||||
when encountered error
|
||||
*/
|
||||
func (s *AccessRule) IsWhitelisted(ipAddr string) bool {
|
||||
if !s.WhitelistEnabled {
|
||||
//Whitelist not enabled. Always return true (allow access)
|
||||
return true
|
||||
}
|
||||
|
||||
if ipAddr == "" {
|
||||
//Unable to get the target IP address, assume ok
|
||||
return true
|
||||
}
|
||||
|
||||
countryCode, err := s.parent.Options.GeoDB.ResolveCountryCodeFromIP(ipAddr)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if s.IsCountryCodeWhitelisted(countryCode.CountryIsoCode) {
|
||||
return true
|
||||
}
|
||||
|
||||
if s.IsIPWhitelisted(ipAddr) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/* Utilities function */
|
||||
|
||||
// Update the current access rule to json file
|
||||
func (s *AccessRule) SaveChanges() error {
|
||||
if s.parent == nil {
|
||||
return errors.New("save failed: access rule detached from controller")
|
||||
}
|
||||
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
|
||||
js, err := json.MarshalIndent(s, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(saveTarget, js, 0775)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete this access rule, this will only delete the config file.
|
||||
// for runtime delete, use DeleteAccessRuleByID from parent Controller
|
||||
func (s *AccessRule) DeleteConfigFile() error {
|
||||
saveTarget := filepath.Join(s.parent.Options.ConfigFolder, s.ID+".json")
|
||||
return os.Remove(saveTarget)
|
||||
}
|
||||
|
||||
// Delete the access rule by given ID
|
||||
func (c *Controller) DeleteAccessRuleByID(accessRuleID string) error {
|
||||
targetAccessRule, err := c.GetAccessRuleByID(accessRuleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Delete config file associated with this access rule
|
||||
err = targetAccessRule.DeleteConfigFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Delete the access rule in runtime
|
||||
c.ProxyAccessRule.Delete(accessRuleID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a deep copy object of the access rule list
|
||||
func deepCopy(valueList map[string]string) map[string]string {
|
||||
result := map[string]string{}
|
||||
js, _ := json.Marshal(valueList)
|
||||
json.Unmarshal(js, &result)
|
||||
return result
|
||||
}
|
94
src/mod/access/blacklist.go
Normal file
94
src/mod/access/blacklist.go
Normal file
@ -0,0 +1,94 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
)
|
||||
|
||||
/*
|
||||
Blacklist.go
|
||||
|
||||
This script store the blacklist related functions
|
||||
*/
|
||||
|
||||
// Geo Blacklist
|
||||
func (s *AccessRule) AddCountryCodeToBlackList(countryCode string, comment string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
|
||||
newBlacklistCountryCode[countryCode] = comment
|
||||
s.BlackListContryCode = &newBlacklistCountryCode
|
||||
s.SaveChanges()
|
||||
}
|
||||
|
||||
func (s *AccessRule) RemoveCountryCodeFromBlackList(countryCode string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
newBlacklistCountryCode := deepCopy(*s.BlackListContryCode)
|
||||
delete(newBlacklistCountryCode, countryCode)
|
||||
s.BlackListContryCode = &newBlacklistCountryCode
|
||||
s.SaveChanges()
|
||||
}
|
||||
|
||||
func (s *AccessRule) IsCountryCodeBlacklisted(countryCode string) bool {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
blacklistMap := *s.BlackListContryCode
|
||||
_, ok := blacklistMap[countryCode]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *AccessRule) GetAllBlacklistedCountryCode() []string {
|
||||
bannedCountryCodes := []string{}
|
||||
blacklistMap := *s.BlackListContryCode
|
||||
for cc, _ := range blacklistMap {
|
||||
bannedCountryCodes = append(bannedCountryCodes, cc)
|
||||
}
|
||||
return bannedCountryCodes
|
||||
}
|
||||
|
||||
// IP Blacklsits
|
||||
func (s *AccessRule) AddIPToBlackList(ipAddr string, comment string) {
|
||||
newBlackListIP := deepCopy(*s.BlackListIP)
|
||||
newBlackListIP[ipAddr] = comment
|
||||
s.BlackListIP = &newBlackListIP
|
||||
s.SaveChanges()
|
||||
}
|
||||
|
||||
func (s *AccessRule) RemoveIPFromBlackList(ipAddr string) {
|
||||
newBlackListIP := deepCopy(*s.BlackListIP)
|
||||
delete(newBlackListIP, ipAddr)
|
||||
s.BlackListIP = &newBlackListIP
|
||||
s.SaveChanges()
|
||||
}
|
||||
|
||||
func (s *AccessRule) GetAllBlacklistedIp() []string {
|
||||
bannedIps := []string{}
|
||||
blacklistMap := *s.BlackListIP
|
||||
for ip, _ := range blacklistMap {
|
||||
bannedIps = append(bannedIps, ip)
|
||||
}
|
||||
|
||||
return bannedIps
|
||||
}
|
||||
|
||||
func (s *AccessRule) IsIPBlacklisted(ipAddr string) bool {
|
||||
IPBlacklist := *s.BlackListIP
|
||||
_, ok := IPBlacklist[ipAddr]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
|
||||
//Check for CIDR
|
||||
for ipOrCIDR, _ := range IPBlacklist {
|
||||
wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR)
|
||||
if wildcardMatch {
|
||||
return true
|
||||
}
|
||||
|
||||
cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR)
|
||||
if cidrMatch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
38
src/mod/access/typedef.go
Normal file
38
src/mod/access/typedef.go
Normal file
@ -0,0 +1,38 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Logger logger.Logger
|
||||
ConfigFolder string //Path for storing config files
|
||||
GeoDB *geodb.Store //For resolving country code
|
||||
Database *database.Database //System key-value database
|
||||
}
|
||||
|
||||
type AccessRule struct {
|
||||
ID string
|
||||
Name string
|
||||
Desc string
|
||||
BlacklistEnabled bool
|
||||
WhitelistEnabled bool
|
||||
|
||||
/* Whitelist Blacklist Table, value is comment if supported */
|
||||
WhiteListCountryCode *map[string]string
|
||||
WhiteListIP *map[string]string
|
||||
BlackListContryCode *map[string]string
|
||||
BlackListIP *map[string]string
|
||||
|
||||
parent *Controller
|
||||
}
|
||||
|
||||
type Controller struct {
|
||||
DefaultAccessRule *AccessRule
|
||||
ProxyAccessRule *sync.Map
|
||||
Options *Options
|
||||
}
|
112
src/mod/access/whitelist.go
Normal file
112
src/mod/access/whitelist.go
Normal file
@ -0,0 +1,112 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
)
|
||||
|
||||
/*
|
||||
Whitelist.go
|
||||
|
||||
This script handles whitelist related functions
|
||||
*/
|
||||
|
||||
const (
|
||||
EntryType_CountryCode int = 0
|
||||
EntryType_IP int = 1
|
||||
)
|
||||
|
||||
type WhitelistEntry struct {
|
||||
EntryType int //Entry type of whitelist, Country Code or IP
|
||||
CC string //ISO Country Code
|
||||
IP string //IP address or range
|
||||
Comment string //Comment for this entry
|
||||
}
|
||||
|
||||
//Geo Whitelist
|
||||
|
||||
func (s *AccessRule) AddCountryCodeToWhitelist(countryCode string, comment string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
|
||||
newWhitelistCC[countryCode] = comment
|
||||
s.WhiteListCountryCode = &newWhitelistCC
|
||||
s.SaveChanges()
|
||||
}
|
||||
|
||||
func (s *AccessRule) RemoveCountryCodeFromWhitelist(countryCode string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
newWhitelistCC := deepCopy(*s.WhiteListCountryCode)
|
||||
delete(newWhitelistCC, countryCode)
|
||||
s.WhiteListCountryCode = &newWhitelistCC
|
||||
s.SaveChanges()
|
||||
}
|
||||
|
||||
func (s *AccessRule) IsCountryCodeWhitelisted(countryCode string) bool {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
whitelistCC := *s.WhiteListCountryCode
|
||||
_, ok := whitelistCC[countryCode]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *AccessRule) GetAllWhitelistedCountryCode() []*WhitelistEntry {
|
||||
whitelistedCountryCode := []*WhitelistEntry{}
|
||||
whitelistCC := *s.WhiteListCountryCode
|
||||
for cc, comment := range whitelistCC {
|
||||
whitelistedCountryCode = append(whitelistedCountryCode, &WhitelistEntry{
|
||||
EntryType: EntryType_CountryCode,
|
||||
CC: cc,
|
||||
Comment: comment,
|
||||
})
|
||||
}
|
||||
return whitelistedCountryCode
|
||||
}
|
||||
|
||||
//IP Whitelist
|
||||
|
||||
func (s *AccessRule) AddIPToWhiteList(ipAddr string, comment string) {
|
||||
newWhitelistIP := deepCopy(*s.WhiteListIP)
|
||||
newWhitelistIP[ipAddr] = comment
|
||||
s.WhiteListIP = &newWhitelistIP
|
||||
s.SaveChanges()
|
||||
}
|
||||
|
||||
func (s *AccessRule) RemoveIPFromWhiteList(ipAddr string) {
|
||||
newWhitelistIP := deepCopy(*s.WhiteListIP)
|
||||
delete(newWhitelistIP, ipAddr)
|
||||
s.WhiteListIP = &newWhitelistIP
|
||||
s.SaveChanges()
|
||||
}
|
||||
|
||||
func (s *AccessRule) IsIPWhitelisted(ipAddr string) bool {
|
||||
//Check for IP wildcard and CIRD rules
|
||||
WhitelistedIP := *s.WhiteListIP
|
||||
for ipOrCIDR, _ := range WhitelistedIP {
|
||||
wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR)
|
||||
if wildcardMatch {
|
||||
return true
|
||||
}
|
||||
|
||||
cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR)
|
||||
if cidrMatch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *AccessRule) GetAllWhitelistedIp() []*WhitelistEntry {
|
||||
whitelistedIp := []*WhitelistEntry{}
|
||||
currentWhitelistedIP := *s.WhiteListIP
|
||||
for ipOrCIDR, comment := range currentWhitelistedIP {
|
||||
thisEntry := WhitelistEntry{
|
||||
EntryType: EntryType_IP,
|
||||
IP: ipOrCIDR,
|
||||
Comment: comment,
|
||||
}
|
||||
whitelistedIp = append(whitelistedIp, &thisEntry)
|
||||
}
|
||||
|
||||
return whitelistedIp
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
@ -24,6 +25,7 @@ import (
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -31,6 +33,7 @@ type CertificateInfoJSON struct {
|
||||
AcmeName string `json:"acme_name"`
|
||||
AcmeUrl string `json:"acme_url"`
|
||||
SkipTLS bool `json:"skip_tls"`
|
||||
UseDNS bool `json:"dns"`
|
||||
}
|
||||
|
||||
// ACMEUser represents a user in the ACME system.
|
||||
@ -40,6 +43,11 @@ type ACMEUser struct {
|
||||
key crypto.PrivateKey
|
||||
}
|
||||
|
||||
type EABConfig struct {
|
||||
Kid string `json:"kid"`
|
||||
HmacKey string `json:"HmacKey"`
|
||||
}
|
||||
|
||||
// GetEmail returns the email of the ACMEUser.
|
||||
func (u *ACMEUser) GetEmail() string {
|
||||
return u.Email
|
||||
@ -59,18 +67,20 @@ func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey {
|
||||
type ACMEHandler struct {
|
||||
DefaultAcmeServer string
|
||||
Port string
|
||||
Database *database.Database
|
||||
}
|
||||
|
||||
// NewACME creates a new ACMEHandler instance.
|
||||
func NewACME(acmeServer string, port string) *ACMEHandler {
|
||||
func NewACME(acmeServer string, port string, database *database.Database) *ACMEHandler {
|
||||
return &ACMEHandler{
|
||||
DefaultAcmeServer: acmeServer,
|
||||
Port: port,
|
||||
Database: database,
|
||||
}
|
||||
}
|
||||
|
||||
// ObtainCert obtains a certificate for the specified domains.
|
||||
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool) (bool, error) {
|
||||
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool, useDNS bool) (bool, error) {
|
||||
log.Println("[ACME] Obtaining certificate...")
|
||||
|
||||
// generate private key
|
||||
@ -107,6 +117,11 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
}
|
||||
}
|
||||
|
||||
//Fallback to Let's Encrypt if it is not set
|
||||
if caName == "" {
|
||||
caName = "Let's Encrypt"
|
||||
}
|
||||
|
||||
// setup the custom ACME url endpoint.
|
||||
if caUrl != "" {
|
||||
config.CADirURL = caUrl
|
||||
@ -136,17 +151,107 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
}
|
||||
|
||||
// setup how to receive challenge
|
||||
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
if useDNS {
|
||||
if !a.Database.TableExists("acme") {
|
||||
a.Database.NewTable("acme")
|
||||
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -1)")
|
||||
}
|
||||
|
||||
if !a.Database.KeyExists("acme", certificateName+"_dns_provider") || !a.Database.KeyExists("acme", certificateName+"_dns_credentials") {
|
||||
return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -2)")
|
||||
}
|
||||
|
||||
var dnsCredentials string
|
||||
err := a.Database.Read("acme", certificateName+"_dns_credentials", &dnsCredentials)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
var dnsProvider string
|
||||
err = a.Database.Read("acme", certificateName+"_dns_provider", &dnsProvider)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
provider, err := GetDnsChallengeProviderByName(dnsProvider, dnsCredentials)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = client.Challenge.SetDNS01Provider(provider)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
// New users will need to register
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
/*
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
*/
|
||||
var reg *registration.Resource
|
||||
// New users will need to register
|
||||
if client.GetExternalAccountRequired() {
|
||||
log.Println("External Account Required for this ACME Provider.")
|
||||
// IF KID and HmacEncoded is overidden
|
||||
|
||||
if !a.Database.TableExists("acme") {
|
||||
a.Database.NewTable("acme")
|
||||
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -1)")
|
||||
}
|
||||
|
||||
if !a.Database.KeyExists("acme", config.CADirURL+"_kid") || !a.Database.KeyExists("acme", config.CADirURL+"_hmacEncoded") {
|
||||
return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -2)")
|
||||
}
|
||||
|
||||
var kid string
|
||||
var hmacEncoded string
|
||||
err := a.Database.Read("acme", config.CADirURL+"_kid", &kid)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = a.Database.Read("acme", config.CADirURL+"_hmacEncoded", &hmacEncoded)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
log.Println("EAB Credential retrieved.", kid, hmacEncoded)
|
||||
if kid != "" && hmacEncoded != "" {
|
||||
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
Kid: kid,
|
||||
HmacEncoded: hmacEncoded,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
//return false, errors.New("External Account Required for this ACME Provider.")
|
||||
} else {
|
||||
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
adminUser.Registration = reg
|
||||
|
||||
@ -179,6 +284,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
AcmeName: caName,
|
||||
AcmeUrl: caUrl,
|
||||
SkipTLS: skipTLS,
|
||||
UseDNS: useDNS,
|
||||
}
|
||||
|
||||
certInfoBytes, err := json.Marshal(certInfo)
|
||||
@ -291,6 +397,8 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||
return
|
||||
}
|
||||
//Make sure the wildcard * do not goes into the filename
|
||||
filename = strings.ReplaceAll(filename, "*", "_")
|
||||
|
||||
email, err := utils.PostPara(r, "email")
|
||||
if err != nil {
|
||||
@ -329,8 +437,18 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
||||
skipTLS = true
|
||||
}
|
||||
|
||||
var dns bool
|
||||
|
||||
if dnsString, err := utils.PostPara(r, "dns"); err != nil {
|
||||
dns = false
|
||||
} else if dnsString != "true" {
|
||||
dns = false
|
||||
} else {
|
||||
dns = true
|
||||
}
|
||||
|
||||
domains := strings.Split(domainPara, ",")
|
||||
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS)
|
||||
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS, dns)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||
return
|
||||
@ -362,7 +480,7 @@ func IsPortInUse(port int) bool {
|
||||
}
|
||||
|
||||
// Load cert information from json file
|
||||
func loadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
|
||||
func LoadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
|
||||
certInfoBytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
72
src/mod/acme/acme_dns.go
Normal file
72
src/mod/acme/acme_dns.go
Normal file
@ -0,0 +1,72 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||
)
|
||||
|
||||
func GetDnsChallengeProviderByName(dnsProvider string, dnsCredentials string) (challenge.Provider, error) {
|
||||
|
||||
//Original Implementation
|
||||
/*credentials, err := extractDnsCredentials(dnsCredentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setCredentialsIntoEnvironmentVariables(credentials)
|
||||
|
||||
provider, err := dns.NewDNSChallengeProviderByName(dnsProvider)
|
||||
*/
|
||||
|
||||
//New implementation using acmedns CICD pipeline generated datatype
|
||||
return acmedns.GetDNSProviderByJsonConfig(dnsProvider, dnsCredentials)
|
||||
}
|
||||
|
||||
/*
|
||||
Original implementation of DNS ACME using OS.Env as payload
|
||||
*/
|
||||
|
||||
func setCredentialsIntoEnvironmentVariables(credentials map[string]string) {
|
||||
for key, value := range credentials {
|
||||
err := os.Setenv(key, value)
|
||||
if err != nil {
|
||||
log.Println("[ERR] Failed to set environment variable %s: %v", key, err)
|
||||
} else {
|
||||
log.Println("[INFO] Environment variable %s set successfully", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func extractDnsCredentials(input string) (map[string]string, error) {
|
||||
result := make(map[string]string)
|
||||
|
||||
// Split the input string by newline character
|
||||
lines := strings.Split(input, "\n")
|
||||
|
||||
// Iterate over each line
|
||||
for _, line := range lines {
|
||||
// Split the line by "=" character
|
||||
//use SpliyN to make sure not to split the value if the value is base64
|
||||
parts := strings.SplitN(line, "=", 1)
|
||||
|
||||
// Check if the line is in the correct format
|
||||
if len(parts) == 2 {
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
|
||||
// Add the key-value pair to the map
|
||||
result[key] = value
|
||||
|
||||
if value == "" || key == "" {
|
||||
//invalid config
|
||||
return result, errors.New("DNS credential extract failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
982
src/mod/acme/acmedns/acmedns.go
Normal file
982
src/mod/acme/acmedns/acmedns.go
Normal file
@ -0,0 +1,982 @@
|
||||
package acmedns
|
||||
/*
|
||||
THIS MODULE IS GENERATED AUTOMATICALLY
|
||||
DO NOT EDIT THIS FILE
|
||||
*/
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/allinkl"
|
||||
"github.com/go-acme/lego/v4/providers/dns/arvancloud"
|
||||
"github.com/go-acme/lego/v4/providers/dns/auroradns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/autodns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/azure"
|
||||
"github.com/go-acme/lego/v4/providers/dns/azuredns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/bindman"
|
||||
"github.com/go-acme/lego/v4/providers/dns/bluecat"
|
||||
"github.com/go-acme/lego/v4/providers/dns/brandit"
|
||||
"github.com/go-acme/lego/v4/providers/dns/bunny"
|
||||
"github.com/go-acme/lego/v4/providers/dns/checkdomain"
|
||||
"github.com/go-acme/lego/v4/providers/dns/civo"
|
||||
"github.com/go-acme/lego/v4/providers/dns/clouddns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudru"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudxns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/conoha"
|
||||
"github.com/go-acme/lego/v4/providers/dns/constellix"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cpanel"
|
||||
"github.com/go-acme/lego/v4/providers/dns/derak"
|
||||
"github.com/go-acme/lego/v4/providers/dns/desec"
|
||||
"github.com/go-acme/lego/v4/providers/dns/digitalocean"
|
||||
"github.com/go-acme/lego/v4/providers/dns/dnshomede"
|
||||
"github.com/go-acme/lego/v4/providers/dns/dnsimple"
|
||||
"github.com/go-acme/lego/v4/providers/dns/dnsmadeeasy"
|
||||
"github.com/go-acme/lego/v4/providers/dns/dnspod"
|
||||
"github.com/go-acme/lego/v4/providers/dns/dode"
|
||||
"github.com/go-acme/lego/v4/providers/dns/domeneshop"
|
||||
"github.com/go-acme/lego/v4/providers/dns/dreamhost"
|
||||
"github.com/go-acme/lego/v4/providers/dns/duckdns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/dyn"
|
||||
"github.com/go-acme/lego/v4/providers/dns/dynu"
|
||||
"github.com/go-acme/lego/v4/providers/dns/easydns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/efficientip"
|
||||
"github.com/go-acme/lego/v4/providers/dns/epik"
|
||||
"github.com/go-acme/lego/v4/providers/dns/exoscale"
|
||||
"github.com/go-acme/lego/v4/providers/dns/freemyip"
|
||||
"github.com/go-acme/lego/v4/providers/dns/gandi"
|
||||
"github.com/go-acme/lego/v4/providers/dns/gandiv5"
|
||||
"github.com/go-acme/lego/v4/providers/dns/gcore"
|
||||
"github.com/go-acme/lego/v4/providers/dns/glesys"
|
||||
"github.com/go-acme/lego/v4/providers/dns/godaddy"
|
||||
"github.com/go-acme/lego/v4/providers/dns/googledomains"
|
||||
"github.com/go-acme/lego/v4/providers/dns/hetzner"
|
||||
"github.com/go-acme/lego/v4/providers/dns/hostingde"
|
||||
"github.com/go-acme/lego/v4/providers/dns/hosttech"
|
||||
"github.com/go-acme/lego/v4/providers/dns/httpnet"
|
||||
"github.com/go-acme/lego/v4/providers/dns/hyperone"
|
||||
"github.com/go-acme/lego/v4/providers/dns/ibmcloud"
|
||||
"github.com/go-acme/lego/v4/providers/dns/iij"
|
||||
"github.com/go-acme/lego/v4/providers/dns/iijdpf"
|
||||
"github.com/go-acme/lego/v4/providers/dns/infoblox"
|
||||
"github.com/go-acme/lego/v4/providers/dns/infomaniak"
|
||||
"github.com/go-acme/lego/v4/providers/dns/internetbs"
|
||||
"github.com/go-acme/lego/v4/providers/dns/inwx"
|
||||
"github.com/go-acme/lego/v4/providers/dns/ionos"
|
||||
"github.com/go-acme/lego/v4/providers/dns/ipv64"
|
||||
"github.com/go-acme/lego/v4/providers/dns/iwantmyname"
|
||||
"github.com/go-acme/lego/v4/providers/dns/joker"
|
||||
"github.com/go-acme/lego/v4/providers/dns/liara"
|
||||
"github.com/go-acme/lego/v4/providers/dns/lightsail"
|
||||
"github.com/go-acme/lego/v4/providers/dns/linode"
|
||||
"github.com/go-acme/lego/v4/providers/dns/liquidweb"
|
||||
"github.com/go-acme/lego/v4/providers/dns/loopia"
|
||||
"github.com/go-acme/lego/v4/providers/dns/luadns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/mailinabox"
|
||||
"github.com/go-acme/lego/v4/providers/dns/metaname"
|
||||
"github.com/go-acme/lego/v4/providers/dns/mydnsjp"
|
||||
"github.com/go-acme/lego/v4/providers/dns/namecheap"
|
||||
"github.com/go-acme/lego/v4/providers/dns/namedotcom"
|
||||
"github.com/go-acme/lego/v4/providers/dns/namesilo"
|
||||
"github.com/go-acme/lego/v4/providers/dns/nearlyfreespeech"
|
||||
"github.com/go-acme/lego/v4/providers/dns/netcup"
|
||||
"github.com/go-acme/lego/v4/providers/dns/netlify"
|
||||
"github.com/go-acme/lego/v4/providers/dns/nicmanager"
|
||||
"github.com/go-acme/lego/v4/providers/dns/nifcloud"
|
||||
"github.com/go-acme/lego/v4/providers/dns/njalla"
|
||||
"github.com/go-acme/lego/v4/providers/dns/nodion"
|
||||
"github.com/go-acme/lego/v4/providers/dns/ns1"
|
||||
"github.com/go-acme/lego/v4/providers/dns/otc"
|
||||
"github.com/go-acme/lego/v4/providers/dns/ovh"
|
||||
"github.com/go-acme/lego/v4/providers/dns/pdns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/plesk"
|
||||
"github.com/go-acme/lego/v4/providers/dns/porkbun"
|
||||
"github.com/go-acme/lego/v4/providers/dns/rackspace"
|
||||
"github.com/go-acme/lego/v4/providers/dns/rcodezero"
|
||||
"github.com/go-acme/lego/v4/providers/dns/regru"
|
||||
"github.com/go-acme/lego/v4/providers/dns/rfc2136"
|
||||
"github.com/go-acme/lego/v4/providers/dns/rimuhosting"
|
||||
"github.com/go-acme/lego/v4/providers/dns/route53"
|
||||
"github.com/go-acme/lego/v4/providers/dns/safedns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/sakuracloud"
|
||||
"github.com/go-acme/lego/v4/providers/dns/scaleway"
|
||||
"github.com/go-acme/lego/v4/providers/dns/selectel"
|
||||
"github.com/go-acme/lego/v4/providers/dns/servercow"
|
||||
"github.com/go-acme/lego/v4/providers/dns/shellrent"
|
||||
"github.com/go-acme/lego/v4/providers/dns/simply"
|
||||
"github.com/go-acme/lego/v4/providers/dns/sonic"
|
||||
"github.com/go-acme/lego/v4/providers/dns/stackpath"
|
||||
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
|
||||
"github.com/go-acme/lego/v4/providers/dns/transip"
|
||||
"github.com/go-acme/lego/v4/providers/dns/ultradns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/variomedia"
|
||||
"github.com/go-acme/lego/v4/providers/dns/vegadns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/vercel"
|
||||
"github.com/go-acme/lego/v4/providers/dns/versio"
|
||||
"github.com/go-acme/lego/v4/providers/dns/vinyldns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/vkcloud"
|
||||
"github.com/go-acme/lego/v4/providers/dns/vscale"
|
||||
"github.com/go-acme/lego/v4/providers/dns/vultr"
|
||||
"github.com/go-acme/lego/v4/providers/dns/webnames"
|
||||
"github.com/go-acme/lego/v4/providers/dns/websupport"
|
||||
"github.com/go-acme/lego/v4/providers/dns/wedos"
|
||||
"github.com/go-acme/lego/v4/providers/dns/yandex"
|
||||
"github.com/go-acme/lego/v4/providers/dns/yandex360"
|
||||
"github.com/go-acme/lego/v4/providers/dns/yandexcloud"
|
||||
"github.com/go-acme/lego/v4/providers/dns/zoneee"
|
||||
"github.com/go-acme/lego/v4/providers/dns/zonomi"
|
||||
|
||||
)
|
||||
|
||||
//name is the DNS provider name, e.g. cloudflare or gandi
|
||||
//JSON (js) must be in key-value string that match ConfigableFields Title in providers.json, e.g. {"Username":"far","Password":"boo"}
|
||||
func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, error){
|
||||
switch name {
|
||||
|
||||
case "alidns":
|
||||
cfg := alidns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return alidns.NewDNSProviderConfig(cfg)
|
||||
case "allinkl":
|
||||
cfg := allinkl.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return allinkl.NewDNSProviderConfig(cfg)
|
||||
case "arvancloud":
|
||||
cfg := arvancloud.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return arvancloud.NewDNSProviderConfig(cfg)
|
||||
case "auroradns":
|
||||
cfg := auroradns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return auroradns.NewDNSProviderConfig(cfg)
|
||||
case "autodns":
|
||||
cfg := autodns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return autodns.NewDNSProviderConfig(cfg)
|
||||
case "azure":
|
||||
cfg := azure.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return azure.NewDNSProviderConfig(cfg)
|
||||
case "azuredns":
|
||||
cfg := azuredns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return azuredns.NewDNSProviderConfig(cfg)
|
||||
case "bindman":
|
||||
cfg := bindman.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bindman.NewDNSProviderConfig(cfg)
|
||||
case "bluecat":
|
||||
cfg := bluecat.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bluecat.NewDNSProviderConfig(cfg)
|
||||
case "brandit":
|
||||
cfg := brandit.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return brandit.NewDNSProviderConfig(cfg)
|
||||
case "bunny":
|
||||
cfg := bunny.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bunny.NewDNSProviderConfig(cfg)
|
||||
case "checkdomain":
|
||||
cfg := checkdomain.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return checkdomain.NewDNSProviderConfig(cfg)
|
||||
case "civo":
|
||||
cfg := civo.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return civo.NewDNSProviderConfig(cfg)
|
||||
case "clouddns":
|
||||
cfg := clouddns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clouddns.NewDNSProviderConfig(cfg)
|
||||
case "cloudflare":
|
||||
cfg := cloudflare.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cloudflare.NewDNSProviderConfig(cfg)
|
||||
case "cloudns":
|
||||
cfg := cloudns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cloudns.NewDNSProviderConfig(cfg)
|
||||
case "cloudru":
|
||||
cfg := cloudru.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cloudru.NewDNSProviderConfig(cfg)
|
||||
case "cloudxns":
|
||||
cfg := cloudxns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cloudxns.NewDNSProviderConfig(cfg)
|
||||
case "conoha":
|
||||
cfg := conoha.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conoha.NewDNSProviderConfig(cfg)
|
||||
case "constellix":
|
||||
cfg := constellix.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return constellix.NewDNSProviderConfig(cfg)
|
||||
case "cpanel":
|
||||
cfg := cpanel.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cpanel.NewDNSProviderConfig(cfg)
|
||||
case "derak":
|
||||
cfg := derak.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return derak.NewDNSProviderConfig(cfg)
|
||||
case "desec":
|
||||
cfg := desec.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desec.NewDNSProviderConfig(cfg)
|
||||
case "digitalocean":
|
||||
cfg := digitalocean.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return digitalocean.NewDNSProviderConfig(cfg)
|
||||
case "dnshomede":
|
||||
cfg := dnshomede.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dnshomede.NewDNSProviderConfig(cfg)
|
||||
case "dnsimple":
|
||||
cfg := dnsimple.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dnsimple.NewDNSProviderConfig(cfg)
|
||||
case "dnsmadeeasy":
|
||||
cfg := dnsmadeeasy.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dnsmadeeasy.NewDNSProviderConfig(cfg)
|
||||
case "dnspod":
|
||||
cfg := dnspod.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dnspod.NewDNSProviderConfig(cfg)
|
||||
case "dode":
|
||||
cfg := dode.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dode.NewDNSProviderConfig(cfg)
|
||||
case "domeneshop":
|
||||
cfg := domeneshop.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return domeneshop.NewDNSProviderConfig(cfg)
|
||||
case "dreamhost":
|
||||
cfg := dreamhost.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dreamhost.NewDNSProviderConfig(cfg)
|
||||
case "duckdns":
|
||||
cfg := duckdns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return duckdns.NewDNSProviderConfig(cfg)
|
||||
case "dyn":
|
||||
cfg := dyn.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dyn.NewDNSProviderConfig(cfg)
|
||||
case "dynu":
|
||||
cfg := dynu.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dynu.NewDNSProviderConfig(cfg)
|
||||
case "easydns":
|
||||
cfg := easydns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return easydns.NewDNSProviderConfig(cfg)
|
||||
case "efficientip":
|
||||
cfg := efficientip.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return efficientip.NewDNSProviderConfig(cfg)
|
||||
case "epik":
|
||||
cfg := epik.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return epik.NewDNSProviderConfig(cfg)
|
||||
case "exoscale":
|
||||
cfg := exoscale.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return exoscale.NewDNSProviderConfig(cfg)
|
||||
case "freemyip":
|
||||
cfg := freemyip.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return freemyip.NewDNSProviderConfig(cfg)
|
||||
case "gandi":
|
||||
cfg := gandi.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gandi.NewDNSProviderConfig(cfg)
|
||||
case "gandiv5":
|
||||
cfg := gandiv5.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gandiv5.NewDNSProviderConfig(cfg)
|
||||
case "gcore":
|
||||
cfg := gcore.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gcore.NewDNSProviderConfig(cfg)
|
||||
case "glesys":
|
||||
cfg := glesys.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return glesys.NewDNSProviderConfig(cfg)
|
||||
case "godaddy":
|
||||
cfg := godaddy.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return godaddy.NewDNSProviderConfig(cfg)
|
||||
case "googledomains":
|
||||
cfg := googledomains.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return googledomains.NewDNSProviderConfig(cfg)
|
||||
case "hetzner":
|
||||
cfg := hetzner.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hetzner.NewDNSProviderConfig(cfg)
|
||||
case "hostingde":
|
||||
cfg := hostingde.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hostingde.NewDNSProviderConfig(cfg)
|
||||
case "hosttech":
|
||||
cfg := hosttech.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hosttech.NewDNSProviderConfig(cfg)
|
||||
case "httpnet":
|
||||
cfg := httpnet.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return httpnet.NewDNSProviderConfig(cfg)
|
||||
case "hyperone":
|
||||
cfg := hyperone.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hyperone.NewDNSProviderConfig(cfg)
|
||||
case "ibmcloud":
|
||||
cfg := ibmcloud.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ibmcloud.NewDNSProviderConfig(cfg)
|
||||
case "iij":
|
||||
cfg := iij.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iij.NewDNSProviderConfig(cfg)
|
||||
case "iijdpf":
|
||||
cfg := iijdpf.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iijdpf.NewDNSProviderConfig(cfg)
|
||||
case "infoblox":
|
||||
cfg := infoblox.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return infoblox.NewDNSProviderConfig(cfg)
|
||||
case "infomaniak":
|
||||
cfg := infomaniak.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return infomaniak.NewDNSProviderConfig(cfg)
|
||||
case "internetbs":
|
||||
cfg := internetbs.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return internetbs.NewDNSProviderConfig(cfg)
|
||||
case "inwx":
|
||||
cfg := inwx.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inwx.NewDNSProviderConfig(cfg)
|
||||
case "ionos":
|
||||
cfg := ionos.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ionos.NewDNSProviderConfig(cfg)
|
||||
case "ipv64":
|
||||
cfg := ipv64.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ipv64.NewDNSProviderConfig(cfg)
|
||||
case "iwantmyname":
|
||||
cfg := iwantmyname.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iwantmyname.NewDNSProviderConfig(cfg)
|
||||
case "joker":
|
||||
cfg := joker.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return joker.NewDNSProviderConfig(cfg)
|
||||
case "liara":
|
||||
cfg := liara.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return liara.NewDNSProviderConfig(cfg)
|
||||
case "lightsail":
|
||||
cfg := lightsail.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return lightsail.NewDNSProviderConfig(cfg)
|
||||
case "linode":
|
||||
cfg := linode.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return linode.NewDNSProviderConfig(cfg)
|
||||
case "liquidweb":
|
||||
cfg := liquidweb.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return liquidweb.NewDNSProviderConfig(cfg)
|
||||
case "loopia":
|
||||
cfg := loopia.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return loopia.NewDNSProviderConfig(cfg)
|
||||
case "luadns":
|
||||
cfg := luadns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return luadns.NewDNSProviderConfig(cfg)
|
||||
case "mailinabox":
|
||||
cfg := mailinabox.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mailinabox.NewDNSProviderConfig(cfg)
|
||||
case "metaname":
|
||||
cfg := metaname.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return metaname.NewDNSProviderConfig(cfg)
|
||||
case "mydnsjp":
|
||||
cfg := mydnsjp.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mydnsjp.NewDNSProviderConfig(cfg)
|
||||
case "namecheap":
|
||||
cfg := namecheap.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return namecheap.NewDNSProviderConfig(cfg)
|
||||
case "namedotcom":
|
||||
cfg := namedotcom.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return namedotcom.NewDNSProviderConfig(cfg)
|
||||
case "namesilo":
|
||||
cfg := namesilo.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return namesilo.NewDNSProviderConfig(cfg)
|
||||
case "nearlyfreespeech":
|
||||
cfg := nearlyfreespeech.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nearlyfreespeech.NewDNSProviderConfig(cfg)
|
||||
case "netcup":
|
||||
cfg := netcup.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netcup.NewDNSProviderConfig(cfg)
|
||||
case "netlify":
|
||||
cfg := netlify.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netlify.NewDNSProviderConfig(cfg)
|
||||
case "nicmanager":
|
||||
cfg := nicmanager.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nicmanager.NewDNSProviderConfig(cfg)
|
||||
case "nifcloud":
|
||||
cfg := nifcloud.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nifcloud.NewDNSProviderConfig(cfg)
|
||||
case "njalla":
|
||||
cfg := njalla.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return njalla.NewDNSProviderConfig(cfg)
|
||||
case "nodion":
|
||||
cfg := nodion.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nodion.NewDNSProviderConfig(cfg)
|
||||
case "ns1":
|
||||
cfg := ns1.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ns1.NewDNSProviderConfig(cfg)
|
||||
case "otc":
|
||||
cfg := otc.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return otc.NewDNSProviderConfig(cfg)
|
||||
case "ovh":
|
||||
cfg := ovh.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ovh.NewDNSProviderConfig(cfg)
|
||||
case "pdns":
|
||||
cfg := pdns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pdns.NewDNSProviderConfig(cfg)
|
||||
case "plesk":
|
||||
cfg := plesk.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plesk.NewDNSProviderConfig(cfg)
|
||||
case "porkbun":
|
||||
cfg := porkbun.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return porkbun.NewDNSProviderConfig(cfg)
|
||||
case "rackspace":
|
||||
cfg := rackspace.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rackspace.NewDNSProviderConfig(cfg)
|
||||
case "rcodezero":
|
||||
cfg := rcodezero.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rcodezero.NewDNSProviderConfig(cfg)
|
||||
case "regru":
|
||||
cfg := regru.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return regru.NewDNSProviderConfig(cfg)
|
||||
case "rfc2136":
|
||||
cfg := rfc2136.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rfc2136.NewDNSProviderConfig(cfg)
|
||||
case "rimuhosting":
|
||||
cfg := rimuhosting.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rimuhosting.NewDNSProviderConfig(cfg)
|
||||
case "route53":
|
||||
cfg := route53.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return route53.NewDNSProviderConfig(cfg)
|
||||
case "safedns":
|
||||
cfg := safedns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return safedns.NewDNSProviderConfig(cfg)
|
||||
case "sakuracloud":
|
||||
cfg := sakuracloud.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sakuracloud.NewDNSProviderConfig(cfg)
|
||||
case "scaleway":
|
||||
cfg := scaleway.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return scaleway.NewDNSProviderConfig(cfg)
|
||||
case "selectel":
|
||||
cfg := selectel.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return selectel.NewDNSProviderConfig(cfg)
|
||||
case "servercow":
|
||||
cfg := servercow.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return servercow.NewDNSProviderConfig(cfg)
|
||||
case "shellrent":
|
||||
cfg := shellrent.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return shellrent.NewDNSProviderConfig(cfg)
|
||||
case "simply":
|
||||
cfg := simply.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return simply.NewDNSProviderConfig(cfg)
|
||||
case "sonic":
|
||||
cfg := sonic.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sonic.NewDNSProviderConfig(cfg)
|
||||
case "stackpath":
|
||||
cfg := stackpath.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stackpath.NewDNSProviderConfig(cfg)
|
||||
case "tencentcloud":
|
||||
cfg := tencentcloud.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tencentcloud.NewDNSProviderConfig(cfg)
|
||||
case "transip":
|
||||
cfg := transip.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transip.NewDNSProviderConfig(cfg)
|
||||
case "ultradns":
|
||||
cfg := ultradns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ultradns.NewDNSProviderConfig(cfg)
|
||||
case "variomedia":
|
||||
cfg := variomedia.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return variomedia.NewDNSProviderConfig(cfg)
|
||||
case "vegadns":
|
||||
cfg := vegadns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vegadns.NewDNSProviderConfig(cfg)
|
||||
case "vercel":
|
||||
cfg := vercel.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vercel.NewDNSProviderConfig(cfg)
|
||||
case "versio":
|
||||
cfg := versio.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return versio.NewDNSProviderConfig(cfg)
|
||||
case "vinyldns":
|
||||
cfg := vinyldns.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vinyldns.NewDNSProviderConfig(cfg)
|
||||
case "vkcloud":
|
||||
cfg := vkcloud.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vkcloud.NewDNSProviderConfig(cfg)
|
||||
case "vscale":
|
||||
cfg := vscale.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vscale.NewDNSProviderConfig(cfg)
|
||||
case "vultr":
|
||||
cfg := vultr.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vultr.NewDNSProviderConfig(cfg)
|
||||
case "webnames":
|
||||
cfg := webnames.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return webnames.NewDNSProviderConfig(cfg)
|
||||
case "websupport":
|
||||
cfg := websupport.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return websupport.NewDNSProviderConfig(cfg)
|
||||
case "wedos":
|
||||
cfg := wedos.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wedos.NewDNSProviderConfig(cfg)
|
||||
case "yandex":
|
||||
cfg := yandex.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return yandex.NewDNSProviderConfig(cfg)
|
||||
case "yandex360":
|
||||
cfg := yandex360.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return yandex360.NewDNSProviderConfig(cfg)
|
||||
case "yandexcloud":
|
||||
cfg := yandexcloud.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return yandexcloud.NewDNSProviderConfig(cfg)
|
||||
case "zoneee":
|
||||
cfg := zoneee.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return zoneee.NewDNSProviderConfig(cfg)
|
||||
case "zonomi":
|
||||
cfg := zonomi.NewDefaultConfig()
|
||||
err := json.Unmarshal([]byte(js), &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return zonomi.NewDNSProviderConfig(cfg)
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized DNS provider: %s", name)
|
||||
}
|
||||
}
|
27
src/mod/acme/acmedns/acmedns_test.go
Normal file
27
src/mod/acme/acmedns/acmedns_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
package acmedns_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||
)
|
||||
|
||||
// Test if the structure of ACME DNS config can be reflected from lego source code definations
|
||||
func TestACMEDNSConfigStructureReflector(t *testing.T) {
|
||||
providers := []string{
|
||||
"gandi",
|
||||
"cloudflare",
|
||||
"azure",
|
||||
}
|
||||
|
||||
for _, provider := range providers {
|
||||
strcture, err := acmedns.GetProviderConfigStructure(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(strcture)
|
||||
}
|
||||
|
||||
}
|
3553
src/mod/acme/acmedns/providers.json
Normal file
3553
src/mod/acme/acmedns/providers.json
Normal file
File diff suppressed because it is too large
Load Diff
80
src/mod/acme/acmedns/providerutils.go
Normal file
80
src/mod/acme/acmedns/providerutils.go
Normal file
@ -0,0 +1,80 @@
|
||||
package acmedns
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
//go:embed providers.json
|
||||
var providers []byte //A list of providers generated by acmedns code-generator
|
||||
|
||||
type ConfigTemplate struct {
|
||||
Name string `json:"Name"`
|
||||
ConfigableFields []struct {
|
||||
Title string `json:"Title"`
|
||||
Datatype string `json:"Datatype"`
|
||||
} `json:"ConfigableFields"`
|
||||
HiddenFields []struct {
|
||||
Title string `json:"Title"`
|
||||
Datatype string `json:"Datatype"`
|
||||
} `json:"HiddenFields"`
|
||||
}
|
||||
|
||||
// Return a map of string => datatype
|
||||
func GetProviderConfigStructure(providerName string) (map[string]string, error) {
|
||||
//Load the target config template from embedded providers.json
|
||||
configTemplateMap := map[string]ConfigTemplate{}
|
||||
err := json.Unmarshal(providers, &configTemplateMap)
|
||||
if err != nil {
|
||||
return map[string]string{}, err
|
||||
}
|
||||
|
||||
targetConfigTemplate, ok := configTemplateMap[providerName]
|
||||
if !ok {
|
||||
return map[string]string{}, errors.New("provider not supported")
|
||||
}
|
||||
|
||||
results := map[string]string{}
|
||||
for _, field := range targetConfigTemplate.ConfigableFields {
|
||||
results[field.Title] = field.Datatype
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// HandleServeProvidersJson return the list of supported providers as json
|
||||
func HandleServeProvidersJson(w http.ResponseWriter, r *http.Request) {
|
||||
providerName, _ := utils.GetPara(r, "name")
|
||||
if providerName == "" {
|
||||
//Send the current list of providers
|
||||
configTemplateMap := map[string]ConfigTemplate{}
|
||||
err := json.Unmarshal(providers, &configTemplateMap)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "failed to load DNS provider")
|
||||
return
|
||||
}
|
||||
|
||||
//Parse the provider names into an array
|
||||
providers := []string{}
|
||||
for providerName, _ := range configTemplateMap {
|
||||
providers = append(providers, providerName)
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(providers)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
return
|
||||
}
|
||||
//Get the config for that provider
|
||||
confTemplate, err := GetProviderConfigStructure(providerName)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(confTemplate)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
@ -344,7 +344,7 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
||||
|
||||
// Load certificate info for ACME detail
|
||||
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
|
||||
certInfo, err := loadCertInfoJSON(certInfoFilename)
|
||||
certInfo, err := LoadCertInfoJSON(certInfoFilename)
|
||||
if err != nil {
|
||||
log.Printf("Renew %s certificate error, can't get the ACME detail for cert: %v, trying org section as ca", certName, err)
|
||||
|
||||
@ -356,7 +356,7 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
||||
}
|
||||
}
|
||||
|
||||
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS)
|
||||
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl, certInfo.SkipTLS, certInfo.UseDNS)
|
||||
if err != nil {
|
||||
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
|
||||
} else {
|
||||
@ -373,3 +373,65 @@ func (a *AutoRenewer) saveRenewConfigToFile() error {
|
||||
js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
|
||||
return os.WriteFile(a.ConfigFilePath, js, 0775)
|
||||
}
|
||||
|
||||
// Handle update auto renew EAD configuration
|
||||
func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
|
||||
kid, err := utils.GetPara(r, "kid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "kid not set")
|
||||
return
|
||||
}
|
||||
|
||||
hmacEncoded, err := utils.GetPara(r, "hmacEncoded")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "hmacEncoded not set")
|
||||
return
|
||||
}
|
||||
|
||||
acmeDirectoryURL, err := utils.GetPara(r, "acmeDirectoryURL")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "acmeDirectoryURL not set")
|
||||
return
|
||||
}
|
||||
|
||||
if !a.AcmeHandler.Database.TableExists("acme") {
|
||||
a.AcmeHandler.Database.NewTable("acme")
|
||||
}
|
||||
|
||||
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_kid", kid)
|
||||
a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_hmacEncoded", hmacEncoded)
|
||||
|
||||
utils.SendOK(w)
|
||||
|
||||
}
|
||||
|
||||
// Handle update auto renew DNS configuration
|
||||
func (a *AutoRenewer) HanldeSetDNS(w http.ResponseWriter, r *http.Request) {
|
||||
dnsProvider, err := utils.PostPara(r, "dnsProvider")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "dnsProvider not set")
|
||||
return
|
||||
}
|
||||
|
||||
dnsCredentials, err := utils.PostPara(r, "dnsCredentials")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "dnsCredentials not set")
|
||||
return
|
||||
}
|
||||
|
||||
filename, err := utils.PostPara(r, "filename")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "filename not set")
|
||||
return
|
||||
}
|
||||
|
||||
if !a.AcmeHandler.Database.TableExists("acme") {
|
||||
a.AcmeHandler.Database.NewTable("acme")
|
||||
}
|
||||
|
||||
a.AcmeHandler.Database.Write("acme", filename+"_dns_provider", dnsProvider)
|
||||
a.AcmeHandler.Database.Write("acme", filename+"_dns_credentials", dnsCredentials)
|
||||
|
||||
utils.SendOK(w)
|
||||
|
||||
}
|
||||
|
@ -1,76 +0,0 @@
|
||||
package aroz
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
//To be used with arozos system
|
||||
type ArozHandler struct {
|
||||
Port string
|
||||
restfulEndpoint string
|
||||
}
|
||||
|
||||
//Information required for registering this subservice to arozos
|
||||
type ServiceInfo struct {
|
||||
Name string //Name of this module. e.g. "Audio"
|
||||
Desc string //Description for this module
|
||||
Group string //Group of the module, e.g. "system" / "media" etc
|
||||
IconPath string //Module icon image path e.g. "Audio/img/function_icon.png"
|
||||
Version string //Version of the module. Format: [0-9]*.[0-9][0-9].[0-9]
|
||||
StartDir string //Default starting dir, e.g. "Audio/index.html"
|
||||
SupportFW bool //Support floatWindow. If yes, floatWindow dir will be loaded
|
||||
LaunchFWDir string //This link will be launched instead of 'StartDir' if fw mode
|
||||
SupportEmb bool //Support embedded mode
|
||||
LaunchEmb string //This link will be launched instead of StartDir / Fw if a file is opened with this module
|
||||
InitFWSize []int //Floatwindow init size. [0] => Width, [1] => Height
|
||||
InitEmbSize []int //Embedded mode init size. [0] => Width, [1] => Height
|
||||
SupportedExt []string //Supported File Extensions. e.g. ".mp3", ".flac", ".wav"
|
||||
}
|
||||
|
||||
//This function will request the required flag from the startup paramters and parse it to the need of the arozos.
|
||||
func HandleFlagParse(info ServiceInfo) *ArozHandler {
|
||||
var infoRequestMode = flag.Bool("info", false, "Show information about this program in JSON")
|
||||
var port = flag.String("port", ":8000", "Management web interface listening port")
|
||||
var restful = flag.String("rpt", "", "Reserved")
|
||||
//Parse the flags
|
||||
flag.Parse()
|
||||
if *infoRequestMode {
|
||||
//Information request mode
|
||||
jsonString, _ := json.MarshalIndent(info, "", " ")
|
||||
fmt.Println(string(jsonString))
|
||||
os.Exit(0)
|
||||
}
|
||||
return &ArozHandler{
|
||||
Port: *port,
|
||||
restfulEndpoint: *restful,
|
||||
}
|
||||
}
|
||||
|
||||
//Get the username and resources access token from the request, return username, token
|
||||
func (a *ArozHandler) GetUserInfoFromRequest(w http.ResponseWriter, r *http.Request) (string, string) {
|
||||
username := r.Header.Get("aouser")
|
||||
token := r.Header.Get("aotoken")
|
||||
|
||||
return username, token
|
||||
}
|
||||
|
||||
func (a *ArozHandler) IsUsingExternalPermissionManager() bool {
|
||||
return !(a.restfulEndpoint == "")
|
||||
}
|
||||
|
||||
//Request gateway interface for advance permission sandbox control
|
||||
func (a *ArozHandler) RequestGatewayInterface(token string, script string) (*http.Response, error) {
|
||||
resp, err := http.PostForm(a.restfulEndpoint,
|
||||
url.Values{"token": {token}, "script": {script}})
|
||||
if err != nil {
|
||||
// handle error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
Binary file not shown.
@ -6,8 +6,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -16,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) {
|
||||
@ -32,29 +35,10 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
||||
if matchedRoutingRule != nil {
|
||||
//Matching routing rule found. Let the sub-router handle it
|
||||
if matchedRoutingRule.UseSystemAccessControl {
|
||||
//This matching rule request system access control.
|
||||
//check access logic
|
||||
respWritten := h.handleAccessRouting(w, r)
|
||||
if respWritten {
|
||||
return
|
||||
}
|
||||
}
|
||||
matchedRoutingRule.Route(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
//Inject headers
|
||||
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
|
||||
|
||||
/*
|
||||
General Access Check
|
||||
*/
|
||||
respWritten := h.handleAccessRouting(w, r)
|
||||
if respWritten {
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Redirection Routing
|
||||
*/
|
||||
@ -65,19 +49,30 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Extract request host to see if it is virtual directory or subdomain
|
||||
/*
|
||||
Host Routing
|
||||
*/
|
||||
//Extract request host to see if any proxy rule is matched
|
||||
domainOnly := r.Host
|
||||
if strings.Contains(r.Host, ":") {
|
||||
hostPath := strings.Split(r.Host, ":")
|
||||
domainOnly = hostPath[0]
|
||||
}
|
||||
|
||||
/*
|
||||
Host Routing
|
||||
*/
|
||||
|
||||
sep := h.Parent.getProxyEndpointFromHostname(domainOnly)
|
||||
if sep != nil && !sep.Disabled {
|
||||
//Matching proxy rule found
|
||||
//Access Check (blacklist / whitelist)
|
||||
ruleID := sep.AccessFilterUUID
|
||||
if sep.AccessFilterUUID == "" {
|
||||
//Use default rule
|
||||
ruleID = "default"
|
||||
}
|
||||
if h.handleAccessRouting(ruleID, w, r) {
|
||||
//Request handled by subroute
|
||||
return
|
||||
}
|
||||
|
||||
//Validate basic auth
|
||||
if sep.RequireBasicAuth {
|
||||
err := h.handleBasicAuthRouting(w, r, sep)
|
||||
if err != nil {
|
||||
@ -94,7 +89,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
} else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root {
|
||||
potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
|
||||
if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
|
||||
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
|
||||
//Missing tailing slash. Redirect to target proxy endpoint
|
||||
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
||||
return
|
||||
@ -109,6 +104,13 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
Root Router Handling
|
||||
*/
|
||||
|
||||
//Root access control based on default rule
|
||||
blocked := h.handleAccessRouting("default", w, r)
|
||||
if blocked {
|
||||
return
|
||||
}
|
||||
|
||||
//Clean up the request URI
|
||||
proxyingPath := strings.TrimSpace(r.RequestURI)
|
||||
if !strings.HasSuffix(proxyingPath, "/") {
|
||||
@ -136,7 +138,6 @@ Once entered this routing segment, the root routing options will take over
|
||||
for the routing logic.
|
||||
*/
|
||||
func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
domainOnly := r.Host
|
||||
if strings.Contains(r.Host, ":") {
|
||||
hostPath := strings.Split(r.Host, ":")
|
||||
@ -203,38 +204,3 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle access routing logic. Return true if the request is handled or blocked by the access control logic
|
||||
// if the return value is false, you can continue process the response writer
|
||||
func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Request) bool {
|
||||
//Check if this ip is in blacklist
|
||||
clientIpAddr := geodb.GetRequesterIP(r)
|
||||
if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/blacklist.html"))
|
||||
if err != nil {
|
||||
w.Write(page_forbidden)
|
||||
} else {
|
||||
w.Write(template)
|
||||
}
|
||||
h.logRequest(r, false, 403, "blacklist", "")
|
||||
return true
|
||||
}
|
||||
|
||||
//Check if this ip is in whitelist
|
||||
if !h.Parent.Option.GeodbStore.IsWhitelisted(clientIpAddr) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/whitelist.html"))
|
||||
if err != nil {
|
||||
w.Write(page_forbidden)
|
||||
} else {
|
||||
w.Write(template)
|
||||
}
|
||||
h.logRequest(r, false, 403, "whitelist", "")
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
65
src/mod/dynamicproxy/access.go
Normal file
65
src/mod/dynamicproxy/access.go
Normal file
@ -0,0 +1,65 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
)
|
||||
|
||||
// Handle access check (blacklist / whitelist), return true if request is handled (aka blocked)
|
||||
// if the return value is false, you can continue process the response writer
|
||||
func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter, r *http.Request) bool {
|
||||
accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
//Unable to load access rule. Target rule not found?
|
||||
log.Println("[Proxy] Unable to load access rule: " + ruleID)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("500 - Internal Server Error"))
|
||||
return true
|
||||
}
|
||||
|
||||
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
|
||||
if isBlocked {
|
||||
h.logRequest(r, false, 403, blockedReason, "")
|
||||
}
|
||||
return isBlocked
|
||||
}
|
||||
|
||||
// Return boolean, return true if access is blocked
|
||||
// For string, it will return the blocked reason (if any)
|
||||
func accessRequestBlocked(accessRule *access.AccessRule, templateDirectory string, w http.ResponseWriter, r *http.Request) (bool, string) {
|
||||
//Check if this ip is in blacklist
|
||||
clientIpAddr := netutils.GetRequesterIP(r)
|
||||
if accessRule.IsBlacklisted(clientIpAddr) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
template, err := os.ReadFile(filepath.Join(templateDirectory, "templates/blacklist.html"))
|
||||
if err != nil {
|
||||
w.Write(page_forbidden)
|
||||
} else {
|
||||
w.Write(template)
|
||||
}
|
||||
|
||||
return true, "blacklist"
|
||||
}
|
||||
|
||||
//Check if this ip is in whitelist
|
||||
if !accessRule.IsWhitelisted(clientIpAddr) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
template, err := os.ReadFile(filepath.Join(templateDirectory, "templates/whitelist.html"))
|
||||
if err != nil {
|
||||
w.Write(page_forbidden)
|
||||
} else {
|
||||
w.Write(template)
|
||||
}
|
||||
return true, "whitelist"
|
||||
}
|
||||
|
||||
//Not blocked.
|
||||
return false, ""
|
||||
}
|
@ -16,6 +16,16 @@ import (
|
||||
*/
|
||||
|
||||
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||
err := handleBasicAuth(w, r, pe)
|
||||
if err != nil {
|
||||
h.logRequest(r, false, 401, "host", pe.Domain)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle basic auth logic
|
||||
// do not write to http.ResponseWriter if err return is not nil (already handled by this function)
|
||||
func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||
if len(pe.BasicAuthExceptionRules) > 0 {
|
||||
//Check if the current path matches the exception rules
|
||||
for _, exceptionRule := range pe.BasicAuthExceptionRules {
|
||||
@ -44,7 +54,6 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
if !matchingFound {
|
||||
h.logRequest(r, false, 401, "host", pe.Domain)
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
w.WriteHeader(401)
|
||||
return errors.New("unauthorized")
|
||||
|
46
src/mod/dynamicproxy/customHeader.go
Normal file
46
src/mod/dynamicproxy/customHeader.go
Normal file
@ -0,0 +1,46 @@
|
||||
package dynamicproxy
|
||||
|
||||
/*
|
||||
CustomHeader.go
|
||||
|
||||
This script handle parsing and injecting custom headers
|
||||
into the dpcore routing logic
|
||||
*/
|
||||
|
||||
//SplitInboundOutboundHeaders split user defined headers into upstream and downstream headers
|
||||
//return upstream header and downstream header key-value pairs
|
||||
//if the header is expected to be deleted, the value will be set to empty string
|
||||
func (ept *ProxyEndpoint) SplitInboundOutboundHeaders() ([][]string, [][]string) {
|
||||
if len(ept.UserDefinedHeaders) == 0 {
|
||||
//Early return if there are no defined headers
|
||||
return [][]string{}, [][]string{}
|
||||
}
|
||||
|
||||
//Use pre-allocation for faster performance
|
||||
upstreamHeaders := make([][]string, len(ept.UserDefinedHeaders))
|
||||
downstreamHeaders := make([][]string, len(ept.UserDefinedHeaders))
|
||||
upstreamHeaderCounter := 0
|
||||
downstreamHeaderCounter := 0
|
||||
|
||||
//Sort the headers into upstream or downstream
|
||||
for _, customHeader := range ept.UserDefinedHeaders {
|
||||
thisHeaderSet := make([]string, 2)
|
||||
thisHeaderSet[0] = customHeader.Key
|
||||
thisHeaderSet[1] = customHeader.Value
|
||||
if customHeader.IsRemove {
|
||||
//Prevent invalid config
|
||||
thisHeaderSet[1] = ""
|
||||
}
|
||||
|
||||
//Assign to slice
|
||||
if customHeader.Direction == HeaderDirection_ZoraxyToUpstream {
|
||||
upstreamHeaders[upstreamHeaderCounter] = thisHeaderSet
|
||||
upstreamHeaderCounter++
|
||||
} else if customHeader.Direction == HeaderDirection_ZoraxyToDownstream {
|
||||
downstreamHeaders[downstreamHeaderCounter] = thisHeaderSet
|
||||
downstreamHeaderCounter++
|
||||
}
|
||||
}
|
||||
|
||||
return upstreamHeaders, downstreamHeaders
|
||||
}
|
@ -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))
|
||||
|
121
src/mod/dynamicproxy/dpcore/header.go
Normal file
121
src/mod/dynamicproxy/dpcore/header.go
Normal file
@ -0,0 +1,121 @@
|
||||
package dpcore
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Header.go
|
||||
|
||||
This script handles headers rewrite and remove
|
||||
in dpcore.
|
||||
|
||||
Added in Zoraxy v3.0.6 by tobychui
|
||||
*/
|
||||
|
||||
// removeHeaders Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
|
||||
func removeHeaders(header http.Header, noCache bool) {
|
||||
// Remove hop-by-hop headers listed in the "Connection" header.
|
||||
if c := header.Get("Connection"); c != "" {
|
||||
for _, f := range strings.Split(c, ",") {
|
||||
if f = strings.TrimSpace(f); f != "" {
|
||||
header.Del(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove hop-by-hop headers
|
||||
for _, h := range hopHeaders {
|
||||
if header.Get(h) != "" {
|
||||
header.Del(h)
|
||||
}
|
||||
}
|
||||
|
||||
//Restore the Upgrade header if any
|
||||
if header.Get("Zr-Origin-Upgrade") != "" {
|
||||
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
|
||||
header.Del("Zr-Origin-Upgrade")
|
||||
}
|
||||
|
||||
//Disable cache if nocache is set
|
||||
if noCache {
|
||||
header.Del("Cache-Control")
|
||||
header.Set("Cache-Control", "no-store")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// rewriteUserAgent rewrite the user agent based on incoming request
|
||||
func rewriteUserAgent(header http.Header, UA string) {
|
||||
//Hide Go-HTTP-Client UA if the client didnt sent us one
|
||||
if header.Get("User-Agent") == "" {
|
||||
// If the outbound request doesn't have a User-Agent header set,
|
||||
// don't send the default Go HTTP client User-Agent
|
||||
header.Del("User-Agent")
|
||||
header.Set("User-Agent", UA)
|
||||
}
|
||||
}
|
||||
|
||||
// Add X-Forwarded-For Header and rewrite X-Real-Ip according to sniffing logics
|
||||
func addXForwardedForHeader(req *http.Request) {
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
// If we aren't the first proxy retain prior
|
||||
// X-Forwarded-For information as a comma+space
|
||||
// separated list and fold multiple headers into one.
|
||||
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
req.Header.Set("X-Forwarded-For", clientIP)
|
||||
if req.TLS != nil {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
} else {
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
}
|
||||
|
||||
if req.Header.Get("X-Real-Ip") == "" {
|
||||
//Check if CF-Connecting-IP header exists
|
||||
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
|
||||
Fastly_Client_IP := req.Header.Get("Fastly-Client-IP")
|
||||
if CF_Connecting_IP != "" {
|
||||
//Use CF Connecting IP
|
||||
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
|
||||
} else if Fastly_Client_IP != "" {
|
||||
//Use Fastly Client IP
|
||||
req.Header.Set("X-Real-Ip", Fastly_Client_IP)
|
||||
} else {
|
||||
// Not exists. Fill it in with first entry in X-Forwarded-For
|
||||
ips := strings.Split(clientIP, ",")
|
||||
if len(ips) > 0 {
|
||||
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// injectUserDefinedHeaders inject the user headers from slice
|
||||
// if a value is empty string, the key will be removed from header.
|
||||
// if a key is empty string, the function will return immediately
|
||||
func injectUserDefinedHeaders(header http.Header, userHeaders [][]string) {
|
||||
for _, userHeader := range userHeaders {
|
||||
if len(userHeader) == 0 {
|
||||
//End of header slice
|
||||
return
|
||||
}
|
||||
headerKey := userHeader[0]
|
||||
headerValue := userHeader[1]
|
||||
if headerValue == "" {
|
||||
//Remove header from head
|
||||
header.Del(headerKey)
|
||||
continue
|
||||
}
|
||||
|
||||
//Default: Set header value
|
||||
header.Del(headerKey) //Remove header if it already exists
|
||||
header.Set(headerKey, headerValue)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -115,11 +115,34 @@ func (router *Router) StartProxyService() error {
|
||||
r.URL, _ = url.Parse(originalHostHeader)
|
||||
}
|
||||
|
||||
//Access Check (blacklist / whitelist)
|
||||
ruleID := sep.AccessFilterUUID
|
||||
if sep.AccessFilterUUID == "" {
|
||||
//Use default rule
|
||||
ruleID = "default"
|
||||
}
|
||||
accessRule, err := router.Option.AccessController.GetAccessRuleByID(ruleID)
|
||||
if err == nil {
|
||||
isBlocked, _ := accessRequestBlocked(accessRule, router.Option.WebDirectory, w, r)
|
||||
if isBlocked {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//Validate basic auth
|
||||
if sep.RequireBasicAuth {
|
||||
err := handleBasicAuth(w, r, sep)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: sep.Domain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: sep.RequireTLS,
|
||||
PathPrefix: "",
|
||||
Version: sep.parent.Option.HostVersion,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
@ -70,7 +66,8 @@ func (ep *ProxyEndpoint) AddUserDefinedHeader(key string, value string) error {
|
||||
func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI string) *VirtualDirectoryEndpoint {
|
||||
for _, vdir := range ep.VirtualDirectories {
|
||||
if strings.HasPrefix(requestURI, vdir.MatchingPath) {
|
||||
return vdir
|
||||
thisVdir := vdir
|
||||
return thisVdir
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -80,7 +77,8 @@ func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI str
|
||||
func (ep *ProxyEndpoint) GetVirtualDirectoryRuleByMatchingPath(matchingPath string) *VirtualDirectoryEndpoint {
|
||||
for _, vdir := range ep.VirtualDirectories {
|
||||
if vdir.MatchingPath == matchingPath {
|
||||
return vdir
|
||||
thisVdir := vdir
|
||||
return thisVdir
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
193
src/mod/dynamicproxy/permissionpolicy/permissionpolicy.go
Normal file
193
src/mod/dynamicproxy/permissionpolicy/permissionpolicy.go
Normal file
@ -0,0 +1,193 @@
|
||||
package permissionpolicy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Permisson Policy
|
||||
|
||||
This is a permission policy header modifier that changes
|
||||
the request permission related policy fields
|
||||
|
||||
author: tobychui
|
||||
*/
|
||||
|
||||
type PermissionsPolicy struct {
|
||||
Accelerometer []string `json:"accelerometer"`
|
||||
AmbientLightSensor []string `json:"ambient_light_sensor"`
|
||||
Autoplay []string `json:"autoplay"`
|
||||
Battery []string `json:"battery"`
|
||||
Camera []string `json:"camera"`
|
||||
CrossOriginIsolated []string `json:"cross_origin_isolated"`
|
||||
DisplayCapture []string `json:"display_capture"`
|
||||
DocumentDomain []string `json:"document_domain"`
|
||||
EncryptedMedia []string `json:"encrypted_media"`
|
||||
ExecutionWhileNotRendered []string `json:"execution_while_not_rendered"`
|
||||
ExecutionWhileOutOfView []string `json:"execution_while_out_of_viewport"`
|
||||
Fullscreen []string `json:"fullscreen"`
|
||||
Geolocation []string `json:"geolocation"`
|
||||
Gyroscope []string `json:"gyroscope"`
|
||||
KeyboardMap []string `json:"keyboard_map"`
|
||||
Magnetometer []string `json:"magnetometer"`
|
||||
Microphone []string `json:"microphone"`
|
||||
Midi []string `json:"midi"`
|
||||
NavigationOverride []string `json:"navigation_override"`
|
||||
Payment []string `json:"payment"`
|
||||
PictureInPicture []string `json:"picture_in_picture"`
|
||||
PublicKeyCredentialsGet []string `json:"publickey_credentials_get"`
|
||||
ScreenWakeLock []string `json:"screen_wake_lock"`
|
||||
SyncXHR []string `json:"sync_xhr"`
|
||||
USB []string `json:"usb"`
|
||||
WebShare []string `json:"web_share"`
|
||||
XRSpatialTracking []string `json:"xr_spatial_tracking"`
|
||||
ClipboardRead []string `json:"clipboard_read"`
|
||||
ClipboardWrite []string `json:"clipboard_write"`
|
||||
Gamepad []string `json:"gamepad"`
|
||||
SpeakerSelection []string `json:"speaker_selection"`
|
||||
ConversionMeasurement []string `json:"conversion_measurement"`
|
||||
FocusWithoutUserActivation []string `json:"focus_without_user_activation"`
|
||||
HID []string `json:"hid"`
|
||||
IdleDetection []string `json:"idle_detection"`
|
||||
InterestCohort []string `json:"interest_cohort"`
|
||||
Serial []string `json:"serial"`
|
||||
SyncScript []string `json:"sync_script"`
|
||||
TrustTokenRedemption []string `json:"trust_token_redemption"`
|
||||
Unload []string `json:"unload"`
|
||||
WindowPlacement []string `json:"window_placement"`
|
||||
VerticalScroll []string `json:"vertical_scroll"`
|
||||
}
|
||||
|
||||
// GetDefaultPermissionPolicy returns a PermissionsPolicy struct with all policies set to *
|
||||
func GetDefaultPermissionPolicy() *PermissionsPolicy {
|
||||
return &PermissionsPolicy{
|
||||
Accelerometer: []string{"*"},
|
||||
AmbientLightSensor: []string{"*"},
|
||||
Autoplay: []string{"*"},
|
||||
Battery: []string{"*"},
|
||||
Camera: []string{"*"},
|
||||
CrossOriginIsolated: []string{"*"},
|
||||
DisplayCapture: []string{"*"},
|
||||
DocumentDomain: []string{"*"},
|
||||
EncryptedMedia: []string{"*"},
|
||||
ExecutionWhileNotRendered: []string{"*"},
|
||||
ExecutionWhileOutOfView: []string{"*"},
|
||||
Fullscreen: []string{"*"},
|
||||
Geolocation: []string{"*"},
|
||||
Gyroscope: []string{"*"},
|
||||
KeyboardMap: []string{"*"},
|
||||
Magnetometer: []string{"*"},
|
||||
Microphone: []string{"*"},
|
||||
Midi: []string{"*"},
|
||||
NavigationOverride: []string{"*"},
|
||||
Payment: []string{"*"},
|
||||
PictureInPicture: []string{"*"},
|
||||
PublicKeyCredentialsGet: []string{"*"},
|
||||
ScreenWakeLock: []string{"*"},
|
||||
SyncXHR: []string{"*"},
|
||||
USB: []string{"*"},
|
||||
WebShare: []string{"*"},
|
||||
XRSpatialTracking: []string{"*"},
|
||||
ClipboardRead: []string{"*"},
|
||||
ClipboardWrite: []string{"*"},
|
||||
Gamepad: []string{"*"},
|
||||
SpeakerSelection: []string{"*"},
|
||||
ConversionMeasurement: []string{"*"},
|
||||
FocusWithoutUserActivation: []string{"*"},
|
||||
HID: []string{"*"},
|
||||
IdleDetection: []string{"*"},
|
||||
InterestCohort: []string{"*"},
|
||||
Serial: []string{"*"},
|
||||
SyncScript: []string{"*"},
|
||||
TrustTokenRedemption: []string{"*"},
|
||||
Unload: []string{"*"},
|
||||
WindowPlacement: []string{"*"},
|
||||
VerticalScroll: []string{"*"},
|
||||
}
|
||||
}
|
||||
|
||||
// InjectPermissionPolicyHeader inject the permission policy into headers
|
||||
func InjectPermissionPolicyHeader(w http.ResponseWriter, policy *PermissionsPolicy) {
|
||||
//Keep the original Permission Policy if exists, or there are no policy given
|
||||
if policy == nil || w.Header().Get("Permissions-Policy") != "" {
|
||||
return
|
||||
}
|
||||
|
||||
policyHeader := []string{}
|
||||
|
||||
// Helper function to add policy directives
|
||||
addDirective := func(name string, sources []string) {
|
||||
if len(sources) > 0 {
|
||||
if sources[0] == "*" {
|
||||
//Allow all
|
||||
policyHeader = append(policyHeader, fmt.Sprintf("%s=%s", name, "*"))
|
||||
} else {
|
||||
//Other than "self" which do not need double quote, others domain need double quote in place
|
||||
formatedSources := []string{}
|
||||
for _, source := range sources {
|
||||
if source == "self" {
|
||||
formatedSources = append(formatedSources, "self")
|
||||
} else {
|
||||
formatedSources = append(formatedSources, "\""+source+"\"")
|
||||
}
|
||||
}
|
||||
policyHeader = append(policyHeader, fmt.Sprintf("%s=(%s)", name, strings.Join(formatedSources, " ")))
|
||||
}
|
||||
} else {
|
||||
//There are no setting for this field. Assume no permission
|
||||
policyHeader = append(policyHeader, fmt.Sprintf("%s=()", name))
|
||||
}
|
||||
}
|
||||
|
||||
// Add each policy directive to the header
|
||||
addDirective("accelerometer", policy.Accelerometer)
|
||||
addDirective("ambient-light-sensor", policy.AmbientLightSensor)
|
||||
addDirective("autoplay", policy.Autoplay)
|
||||
addDirective("battery", policy.Battery)
|
||||
addDirective("camera", policy.Camera)
|
||||
addDirective("cross-origin-isolated", policy.CrossOriginIsolated)
|
||||
addDirective("display-capture", policy.DisplayCapture)
|
||||
addDirective("document-domain", policy.DocumentDomain)
|
||||
addDirective("encrypted-media", policy.EncryptedMedia)
|
||||
addDirective("execution-while-not-rendered", policy.ExecutionWhileNotRendered)
|
||||
addDirective("execution-while-out-of-viewport", policy.ExecutionWhileOutOfView)
|
||||
addDirective("fullscreen", policy.Fullscreen)
|
||||
addDirective("geolocation", policy.Geolocation)
|
||||
addDirective("gyroscope", policy.Gyroscope)
|
||||
addDirective("keyboard-map", policy.KeyboardMap)
|
||||
addDirective("magnetometer", policy.Magnetometer)
|
||||
addDirective("microphone", policy.Microphone)
|
||||
addDirective("midi", policy.Midi)
|
||||
addDirective("navigation-override", policy.NavigationOverride)
|
||||
addDirective("payment", policy.Payment)
|
||||
addDirective("picture-in-picture", policy.PictureInPicture)
|
||||
addDirective("publickey-credentials-get", policy.PublicKeyCredentialsGet)
|
||||
addDirective("screen-wake-lock", policy.ScreenWakeLock)
|
||||
addDirective("sync-xhr", policy.SyncXHR)
|
||||
addDirective("usb", policy.USB)
|
||||
addDirective("web-share", policy.WebShare)
|
||||
addDirective("xr-spatial-tracking", policy.XRSpatialTracking)
|
||||
addDirective("clipboard-read", policy.ClipboardRead)
|
||||
addDirective("clipboard-write", policy.ClipboardWrite)
|
||||
addDirective("gamepad", policy.Gamepad)
|
||||
addDirective("speaker-selection", policy.SpeakerSelection)
|
||||
addDirective("conversion-measurement", policy.ConversionMeasurement)
|
||||
addDirective("focus-without-user-activation", policy.FocusWithoutUserActivation)
|
||||
addDirective("hid", policy.HID)
|
||||
addDirective("idle-detection", policy.IdleDetection)
|
||||
addDirective("interest-cohort", policy.InterestCohort)
|
||||
addDirective("serial", policy.Serial)
|
||||
addDirective("sync-script", policy.SyncScript)
|
||||
addDirective("trust-token-redemption", policy.TrustTokenRedemption)
|
||||
addDirective("unload", policy.Unload)
|
||||
addDirective("window-placement", policy.WindowPlacement)
|
||||
addDirective("vertical-scroll", policy.VerticalScroll)
|
||||
|
||||
// Join the directives and set the header
|
||||
policyHeaderValue := strings.Join(policyHeader, ", ")
|
||||
|
||||
//Inject the new policy into the header
|
||||
w.Header().Set("Permissions-Policy", policyHeaderValue)
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package permissionpolicy_test
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
)
|
||||
|
||||
func TestInjectPermissionPolicyHeader(t *testing.T) {
|
||||
//Prepare the data for permission policy
|
||||
testPermissionPolicy := permissionpolicy.GetDefaultPermissionPolicy()
|
||||
testPermissionPolicy.Geolocation = []string{"self"}
|
||||
testPermissionPolicy.Microphone = []string{"self", "https://example.com"}
|
||||
testPermissionPolicy.Camera = []string{"*"}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
existingHeader string
|
||||
policy *permissionpolicy.PermissionsPolicy
|
||||
expectedHeader string
|
||||
}{
|
||||
{
|
||||
name: "Default policy with a few limitations",
|
||||
existingHeader: "",
|
||||
policy: testPermissionPolicy,
|
||||
expectedHeader: `accelerometer=*, ambient-light-sensor=*, autoplay=*, battery=*, camera=*, cross-origin-isolated=*, display-capture=*, document-domain=*, encrypted-media=*, execution-while-not-rendered=*, execution-while-out-of-viewport=*, fullscreen=*, geolocation=(self), gyroscope=*, keyboard-map=*, magnetometer=*, microphone=(self "https://example.com"), midi=*, navigation-override=*, payment=*, picture-in-picture=*, publickey-credentials-get=*, screen-wake-lock=*, sync-xhr=*, usb=*, web-share=*, xr-spatial-tracking=*, clipboard-read=*, clipboard-write=*, gamepad=*, speaker-selection=*, conversion-measurement=*, focus-without-user-activation=*, hid=*, idle-detection=*, interest-cohort=*, serial=*, sync-script=*, trust-token-redemption=*, unload=*, window-placement=*, vertical-scroll=*`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
if tt.existingHeader != "" {
|
||||
rr.Header().Set("Permissions-Policy", tt.existingHeader)
|
||||
}
|
||||
|
||||
permissionpolicy.InjectPermissionPolicyHeader(rr, tt.policy)
|
||||
|
||||
gotHeader := rr.Header().Get("Permissions-Policy")
|
||||
if !strings.Contains(gotHeader, tt.expectedHeader) {
|
||||
t.Errorf("got header %s, want %s", gotHeader, tt.expectedHeader)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
"imuslab.com/zoraxy/mod/websocketproxy"
|
||||
)
|
||||
@ -34,23 +34,45 @@ func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoi
|
||||
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
||||
ep, ok := router.ProxyEndpoints.Load(hostname)
|
||||
if ok {
|
||||
//Exact hit
|
||||
targetSubdomainEndpoint = ep.(*ProxyEndpoint)
|
||||
if !targetSubdomainEndpoint.Disabled {
|
||||
return targetSubdomainEndpoint
|
||||
}
|
||||
}
|
||||
|
||||
//No hit. Try with wildcard
|
||||
//No hit. Try with wildcard and alias
|
||||
matchProxyEndpoints := []*ProxyEndpoint{}
|
||||
router.ProxyEndpoints.Range(func(k, v interface{}) bool {
|
||||
ep := v.(*ProxyEndpoint)
|
||||
match, err := filepath.Match(ep.RootOrMatchingDomain, hostname)
|
||||
if err != nil {
|
||||
//Continue
|
||||
//Bad pattern. Skip this rule
|
||||
return true
|
||||
}
|
||||
|
||||
if match {
|
||||
//targetSubdomainEndpoint = ep
|
||||
//Wildcard matches. Skip checking alias
|
||||
matchProxyEndpoints = append(matchProxyEndpoints, ep)
|
||||
return true
|
||||
}
|
||||
|
||||
//Wildcard not match. Check for alias
|
||||
if ep.MatchingDomainAlias != nil && len(ep.MatchingDomainAlias) > 0 {
|
||||
for _, aliasDomain := range ep.MatchingDomainAlias {
|
||||
match, err := filepath.Match(aliasDomain, hostname)
|
||||
if err != nil {
|
||||
//Bad pattern. Skip this alias
|
||||
continue
|
||||
}
|
||||
|
||||
if match {
|
||||
//This alias match
|
||||
matchProxyEndpoints = append(matchProxyEndpoints, ep)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
@ -89,13 +111,6 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||
r.Header.Set("X-Forwarded-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
|
||||
@ -130,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
|
||||
@ -162,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")
|
||||
@ -197,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
|
||||
@ -224,7 +244,7 @@ func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, fo
|
||||
if h.Parent.Option.StatisticCollector != nil {
|
||||
go func() {
|
||||
requestInfo := statistic.RequestInfo{
|
||||
IpAddr: geodb.GetRequesterIP(r),
|
||||
IpAddr: netutils.GetRequesterIP(r),
|
||||
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||
Succ: succ,
|
||||
StatusCode: statusCode,
|
||||
|
@ -19,6 +19,9 @@ import (
|
||||
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
||||
//Filter the tailing slash if any
|
||||
domain := endpoint.Domain
|
||||
if len(domain) == 0 {
|
||||
return nil, errors.New("invalid endpoint config")
|
||||
}
|
||||
if domain[len(domain)-1:] == "/" {
|
||||
domain = domain[:len(domain)-1]
|
||||
}
|
||||
@ -51,6 +54,10 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
||||
//Prepare proxy routing hjandler for each of the virtual directories
|
||||
for _, vdir := range endpoint.VirtualDirectories {
|
||||
domain := vdir.Domain
|
||||
if len(domain) == 0 {
|
||||
//invalid vdir
|
||||
continue
|
||||
}
|
||||
if domain[len(domain)-1:] == "/" {
|
||||
domain = domain[:len(domain)-1]
|
||||
}
|
||||
|
@ -39,7 +39,7 @@
|
||||
<h3 style="margin-top: 1em;">403 - Forbidden</h3>
|
||||
<div class="ui divider"></div>
|
||||
<p>You do not have permission to view this directory or page. <br>
|
||||
This might cause by the region limit setting of this site.</p>
|
||||
This might be caused by the region limit setting of this site.</p>
|
||||
<div class="ui divider"></div>
|
||||
<div style="text-align: left;">
|
||||
<small>Request time: <span id="reqtime"></span></small><br>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
@ -34,7 +35,8 @@ type RouterOption struct {
|
||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||
TlsManager *tlscert.Manager
|
||||
RedirectRuleTable *redirection.RuleTable
|
||||
GeodbStore *geodb.Store //GeoIP blacklist and whitelist
|
||||
GeodbStore *geodb.Store //GeoIP resolver
|
||||
AccessController *access.Controller //Blacklist / whitelist controller
|
||||
StatisticCollector *statistic.Collector
|
||||
WebDirectory string //The static web server directory containing the templates folder
|
||||
}
|
||||
@ -70,10 +72,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
|
||||
@ -90,9 +102,10 @@ type VirtualDirectoryEndpoint struct {
|
||||
|
||||
// A proxy endpoint record, a general interface for handling inbound routing
|
||||
type ProxyEndpoint struct {
|
||||
ProxyType int //The type of this proxy, see const def
|
||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||
Domain string //Domain or IP to proxy to
|
||||
ProxyType int //The type of this proxy, see const def
|
||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||
Domain string //Domain or IP to proxy to
|
||||
|
||||
//TLS/SSL Related
|
||||
RequireTLS bool //Target domain require TLS
|
||||
@ -111,14 +124,17 @@ type ProxyEndpoint struct {
|
||||
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
|
||||
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
|
||||
|
||||
//Fallback routing logic
|
||||
DefaultSiteOption int //Fallback routing logic options
|
||||
DefaultSiteValue string //Fallback routing target, optional
|
||||
//Access Control
|
||||
AccessFilterUUID string //Access filter ID
|
||||
|
||||
Disabled bool //If the rule is disabled
|
||||
|
||||
//Fallback routing logic (Special Rule Sets Only)
|
||||
DefaultSiteOption int //Fallback routing logic options
|
||||
DefaultSiteValue string //Fallback routing target, optional
|
||||
|
||||
//Internal Logic Elements
|
||||
parent *Router
|
||||
parent *Router `json:"-"`
|
||||
proxy *dpcore.ReverseProxy `json:"-"`
|
||||
}
|
||||
|
||||
|
@ -13,18 +13,16 @@ import (
|
||||
|
||||
type Sender struct {
|
||||
Hostname string //E.g. mail.gandi.net
|
||||
Domain string //E.g. arozos.com
|
||||
Port int //E.g. 587
|
||||
Username string //Username of the email account
|
||||
Password string //Password of the email account
|
||||
SenderAddr string //e.g. admin@arozos.com
|
||||
}
|
||||
|
||||
//Create a new email sender object
|
||||
func NewEmailSender(hostname string, domain string, port int, username string, password string, senderAddr string) *Sender {
|
||||
// Create a new email sender object
|
||||
func NewEmailSender(hostname string, port int, username string, password string, senderAddr string) *Sender {
|
||||
return &Sender{
|
||||
Hostname: hostname,
|
||||
Domain: domain,
|
||||
Port: port,
|
||||
Username: username,
|
||||
Password: password,
|
||||
@ -33,24 +31,33 @@ func NewEmailSender(hostname string, domain string, port int, username string, p
|
||||
}
|
||||
|
||||
/*
|
||||
Send a email to a reciving addr
|
||||
Example Usage:
|
||||
SendEmail(
|
||||
test@example.com,
|
||||
"Free donuts",
|
||||
"Come get your free donuts on this Sunday!"
|
||||
)
|
||||
Send a email to a reciving addr
|
||||
Example Usage:
|
||||
SendEmail(
|
||||
|
||||
test@example.com,
|
||||
"Free donuts",
|
||||
"Come get your free donuts on this Sunday!"
|
||||
|
||||
)
|
||||
*/
|
||||
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
|
||||
auth := smtp.PlainAuth("", s.Username+"@"+s.Domain, 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
|
||||
|
8
src/mod/geodb/README.txt
Normal file
8
src/mod/geodb/README.txt
Normal file
@ -0,0 +1,8 @@
|
||||
The data source for geoip is licensed under CC0
|
||||
If you want to build your own version of geodb with updated whitelist,
|
||||
you can go to this repo and get the "GeoFeed + Whois + ASN" version of
|
||||
both the IPV4 and IPV6 mapping (the one without -num)
|
||||
|
||||
https://github.com/sapics/ip-location-db/tree/main/geolite2-country
|
||||
|
||||
And rename it to "gepipv4.csv" and "gepipv6.csv"
|
@ -1,91 +0,0 @@
|
||||
package geodb
|
||||
|
||||
import "strings"
|
||||
|
||||
/*
|
||||
Blacklist.go
|
||||
|
||||
This script store the blacklist related functions
|
||||
*/
|
||||
|
||||
//Geo Blacklist
|
||||
|
||||
func (s *Store) AddCountryCodeToBlackList(countryCode string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
s.sysdb.Write("blacklist-cn", countryCode, true)
|
||||
}
|
||||
|
||||
func (s *Store) RemoveCountryCodeFromBlackList(countryCode string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
s.sysdb.Delete("blacklist-cn", countryCode)
|
||||
}
|
||||
|
||||
func (s *Store) IsCountryCodeBlacklisted(countryCode string) bool {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
var isBlacklisted bool = false
|
||||
s.sysdb.Read("blacklist-cn", countryCode, &isBlacklisted)
|
||||
return isBlacklisted
|
||||
}
|
||||
|
||||
func (s *Store) GetAllBlacklistedCountryCode() []string {
|
||||
bannedCountryCodes := []string{}
|
||||
entries, err := s.sysdb.ListTable("blacklist-cn")
|
||||
if err != nil {
|
||||
return bannedCountryCodes
|
||||
}
|
||||
for _, keypairs := range entries {
|
||||
ip := string(keypairs[0])
|
||||
bannedCountryCodes = append(bannedCountryCodes, ip)
|
||||
}
|
||||
|
||||
return bannedCountryCodes
|
||||
}
|
||||
|
||||
//IP Blacklsits
|
||||
|
||||
func (s *Store) AddIPToBlackList(ipAddr string) {
|
||||
s.sysdb.Write("blacklist-ip", ipAddr, true)
|
||||
}
|
||||
|
||||
func (s *Store) RemoveIPFromBlackList(ipAddr string) {
|
||||
s.sysdb.Delete("blacklist-ip", ipAddr)
|
||||
}
|
||||
|
||||
func (s *Store) GetAllBlacklistedIp() []string {
|
||||
bannedIps := []string{}
|
||||
entries, err := s.sysdb.ListTable("blacklist-ip")
|
||||
if err != nil {
|
||||
return bannedIps
|
||||
}
|
||||
|
||||
for _, keypairs := range entries {
|
||||
ip := string(keypairs[0])
|
||||
bannedIps = append(bannedIps, ip)
|
||||
}
|
||||
|
||||
return bannedIps
|
||||
}
|
||||
|
||||
func (s *Store) IsIPBlacklisted(ipAddr string) bool {
|
||||
var isBlacklisted bool = false
|
||||
s.sysdb.Read("blacklist-ip", ipAddr, &isBlacklisted)
|
||||
if isBlacklisted {
|
||||
return true
|
||||
}
|
||||
|
||||
//Check for IP wildcard and CIRD rules
|
||||
AllBlacklistedIps := s.GetAllBlacklistedIp()
|
||||
for _, blacklistRule := range AllBlacklistedIps {
|
||||
wildcardMatch := MatchIpWildcard(ipAddr, blacklistRule)
|
||||
if wildcardMatch {
|
||||
return true
|
||||
}
|
||||
|
||||
cidrMatch := MatchIpCIDR(ipAddr, blacklistRule)
|
||||
if cidrMatch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -2,11 +2,10 @@ package geodb
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
)
|
||||
|
||||
//go:embed geoipv4.csv
|
||||
@ -16,12 +15,10 @@ var geoipv4 []byte //Geodb dataset for ipv4
|
||||
var geoipv6 []byte //Geodb dataset for ipv6
|
||||
|
||||
type Store struct {
|
||||
BlacklistEnabled bool
|
||||
WhitelistEnabled bool
|
||||
geodb [][]string //Parsed geodb list
|
||||
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
||||
geotrie *trie
|
||||
geotrieIpv6 *trie
|
||||
geodb [][]string //Parsed geodb list
|
||||
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
||||
geotrie *trie
|
||||
geotrieIpv6 *trie
|
||||
//geoipCache sync.Map
|
||||
sysdb *database.Database
|
||||
option *StoreOptions
|
||||
@ -48,40 +45,6 @@ func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blacklistEnabled := false
|
||||
whitelistEnabled := false
|
||||
if sysdb != nil {
|
||||
err = sysdb.NewTable("blacklist-cn")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = sysdb.NewTable("blacklist-ip")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = sysdb.NewTable("whitelist-cn")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = sysdb.NewTable("whitelist-ip")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = sysdb.NewTable("blackwhitelist")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sysdb.Read("blackwhitelist", "blacklistEnabled", &blacklistEnabled)
|
||||
sysdb.Read("blackwhitelist", "whitelistEnabled", &whitelistEnabled)
|
||||
} else {
|
||||
log.Println("Database pointer set to nil: Entering debug mode")
|
||||
}
|
||||
|
||||
var ipv4Trie *trie
|
||||
if !option.AllowSlowIpv4LookUp {
|
||||
ipv4Trie = constrctTrieTree(parsedGeoData)
|
||||
@ -93,27 +56,15 @@ func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
|
||||
}
|
||||
|
||||
return &Store{
|
||||
BlacklistEnabled: blacklistEnabled,
|
||||
WhitelistEnabled: whitelistEnabled,
|
||||
geodb: parsedGeoData,
|
||||
geotrie: ipv4Trie,
|
||||
geodbIpv6: parsedGeoDataIpv6,
|
||||
geotrieIpv6: ipv6Trie,
|
||||
sysdb: sysdb,
|
||||
option: option,
|
||||
geodb: parsedGeoData,
|
||||
geotrie: ipv4Trie,
|
||||
geodbIpv6: parsedGeoDataIpv6,
|
||||
geotrieIpv6: ipv6Trie,
|
||||
sysdb: sysdb,
|
||||
option: option,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) ToggleBlacklist(enabled bool) {
|
||||
s.sysdb.Write("blackwhitelist", "blacklistEnabled", enabled)
|
||||
s.BlacklistEnabled = enabled
|
||||
}
|
||||
|
||||
func (s *Store) ToggleWhitelist(enabled bool) {
|
||||
s.sysdb.Write("blackwhitelist", "whitelistEnabled", enabled)
|
||||
s.WhitelistEnabled = enabled
|
||||
}
|
||||
|
||||
func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) {
|
||||
cc := s.search(ipstring)
|
||||
return &CountryInfo{
|
||||
@ -127,93 +78,16 @@ func (s *Store) Close() {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Check if a IP address is blacklisted, in either country or IP blacklist
|
||||
IsBlacklisted default return is false (allow access)
|
||||
*/
|
||||
func (s *Store) IsBlacklisted(ipAddr string) bool {
|
||||
if !s.BlacklistEnabled {
|
||||
//Blacklist not enabled. Always return false
|
||||
return false
|
||||
}
|
||||
|
||||
if ipAddr == "" {
|
||||
//Unable to get the target IP address
|
||||
return false
|
||||
}
|
||||
|
||||
countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if s.IsCountryCodeBlacklisted(countryCode.CountryIsoCode) {
|
||||
return true
|
||||
}
|
||||
|
||||
if s.IsIPBlacklisted(ipAddr) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
IsWhitelisted check if a given IP address is in the current
|
||||
server's white list.
|
||||
|
||||
Note that the Whitelist default result is true even
|
||||
when encountered error
|
||||
*/
|
||||
func (s *Store) IsWhitelisted(ipAddr string) bool {
|
||||
if !s.WhitelistEnabled {
|
||||
//Whitelist not enabled. Always return true (allow access)
|
||||
return true
|
||||
}
|
||||
|
||||
if ipAddr == "" {
|
||||
//Unable to get the target IP address, assume ok
|
||||
return true
|
||||
}
|
||||
|
||||
countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if s.IsCountryCodeWhitelisted(countryCode.CountryIsoCode) {
|
||||
return true
|
||||
}
|
||||
|
||||
if s.IsIPWhitelisted(ipAddr) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// A helper function that check both blacklist and whitelist for access
|
||||
// for both geoIP and ip / CIDR ranges
|
||||
func (s *Store) AllowIpAccess(ipaddr string) bool {
|
||||
if s.IsBlacklisted(ipaddr) {
|
||||
return false
|
||||
}
|
||||
|
||||
return s.IsWhitelisted(ipaddr)
|
||||
}
|
||||
|
||||
func (s *Store) AllowConnectionAccess(conn net.Conn) bool {
|
||||
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||
return s.AllowIpAccess(addr.IP.String())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
|
||||
ipAddr := GetRequesterIP(r)
|
||||
ipAddr := netutils.GetRequesterIP(r)
|
||||
if ipAddr == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if netutils.IsPrivateIP(ipAddr) {
|
||||
return "LAN"
|
||||
}
|
||||
|
||||
countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
|
||||
if err != nil {
|
||||
return ""
|
||||
|
@ -51,8 +51,29 @@ func TestResolveCountryCodeFromIP(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test an IP address that should return a valid country code
|
||||
ip := "8.8.8.8"
|
||||
expected := "US"
|
||||
knownIpCountryMap := [][]string{
|
||||
{"3.224.220.101", "US"},
|
||||
{"176.113.115.113", "RU"},
|
||||
{"65.21.233.213", "FI"},
|
||||
{"94.23.207.193", "FR"},
|
||||
}
|
||||
|
||||
for _, testcase := range knownIpCountryMap {
|
||||
ip := testcase[0]
|
||||
expected := testcase[1]
|
||||
info, err := store.ResolveCountryCodeFromIP(ip)
|
||||
if err != nil {
|
||||
t.Errorf("error resolving country code for IP %s: %v", ip, err)
|
||||
return
|
||||
}
|
||||
if info.CountryIsoCode != expected {
|
||||
t.Errorf("expected country code %s, but got %s for IP %s", expected, info.CountryIsoCode, ip)
|
||||
}
|
||||
}
|
||||
|
||||
// Test an IP address that should return an empty country code
|
||||
ip := "127.0.0.1"
|
||||
expected := ""
|
||||
info, err := store.ResolveCountryCodeFromIP(ip)
|
||||
if err != nil {
|
||||
t.Errorf("error resolving country code for IP %s: %v", ip, err)
|
||||
@ -61,16 +82,4 @@ func TestResolveCountryCodeFromIP(t *testing.T) {
|
||||
if info.CountryIsoCode != expected {
|
||||
t.Errorf("expected country code %s, but got %s for IP %s", expected, info.CountryIsoCode, ip)
|
||||
}
|
||||
|
||||
// Test an IP address that should return an empty country code
|
||||
ip = "127.0.0.1"
|
||||
expected = ""
|
||||
info, err = store.ResolveCountryCodeFromIP(ip)
|
||||
if err != nil {
|
||||
t.Errorf("error resolving country code for IP %s: %v", ip, err)
|
||||
return
|
||||
}
|
||||
if info.CountryIsoCode != expected {
|
||||
t.Errorf("expected country code %s, but got %s for IP %s", expected, info.CountryIsoCode, ip)
|
||||
}
|
||||
}
|
||||
|
132305
src/mod/geodb/geoipv4.csv
132305
src/mod/geodb/geoipv4.csv
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,8 @@ import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
)
|
||||
|
||||
func (s *Store) search(ip string) string {
|
||||
@ -24,7 +26,7 @@ func (s *Store) search(ip string) string {
|
||||
|
||||
//Search in geotrie tree
|
||||
cc := ""
|
||||
if IsIPv6(ip) {
|
||||
if netutils.IsIPv6(ip) {
|
||||
if s.geotrieIpv6 == nil {
|
||||
cc = s.slowSearchIpv6(ip)
|
||||
} else {
|
||||
|
@ -1,129 +0,0 @@
|
||||
package geodb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Whitelist.go
|
||||
|
||||
This script handles whitelist related functions
|
||||
*/
|
||||
|
||||
const (
|
||||
EntryType_CountryCode int = 0
|
||||
EntryType_IP int = 1
|
||||
)
|
||||
|
||||
type WhitelistEntry struct {
|
||||
EntryType int //Entry type of whitelist, Country Code or IP
|
||||
CC string //ISO Country Code
|
||||
IP string //IP address or range
|
||||
Comment string //Comment for this entry
|
||||
}
|
||||
|
||||
//Geo Whitelist
|
||||
|
||||
func (s *Store) AddCountryCodeToWhitelist(countryCode string, comment string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
entry := WhitelistEntry{
|
||||
EntryType: EntryType_CountryCode,
|
||||
CC: countryCode,
|
||||
Comment: comment,
|
||||
}
|
||||
|
||||
s.sysdb.Write("whitelist-cn", countryCode, entry)
|
||||
}
|
||||
|
||||
func (s *Store) RemoveCountryCodeFromWhitelist(countryCode string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
s.sysdb.Delete("whitelist-cn", countryCode)
|
||||
}
|
||||
|
||||
func (s *Store) IsCountryCodeWhitelisted(countryCode string) bool {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
return s.sysdb.KeyExists("whitelist-cn", countryCode)
|
||||
}
|
||||
|
||||
func (s *Store) GetAllWhitelistedCountryCode() []*WhitelistEntry {
|
||||
whitelistedCountryCode := []*WhitelistEntry{}
|
||||
entries, err := s.sysdb.ListTable("whitelist-cn")
|
||||
if err != nil {
|
||||
return whitelistedCountryCode
|
||||
}
|
||||
for _, keypairs := range entries {
|
||||
thisWhitelistEntry := WhitelistEntry{}
|
||||
json.Unmarshal(keypairs[1], &thisWhitelistEntry)
|
||||
whitelistedCountryCode = append(whitelistedCountryCode, &thisWhitelistEntry)
|
||||
}
|
||||
|
||||
return whitelistedCountryCode
|
||||
}
|
||||
|
||||
//IP Whitelist
|
||||
|
||||
func (s *Store) AddIPToWhiteList(ipAddr string, comment string) {
|
||||
thisIpEntry := WhitelistEntry{
|
||||
EntryType: EntryType_IP,
|
||||
IP: ipAddr,
|
||||
Comment: comment,
|
||||
}
|
||||
|
||||
s.sysdb.Write("whitelist-ip", ipAddr, thisIpEntry)
|
||||
}
|
||||
|
||||
func (s *Store) RemoveIPFromWhiteList(ipAddr string) {
|
||||
s.sysdb.Delete("whitelist-ip", ipAddr)
|
||||
}
|
||||
|
||||
func (s *Store) IsIPWhitelisted(ipAddr string) bool {
|
||||
isWhitelisted := s.sysdb.KeyExists("whitelist-ip", ipAddr)
|
||||
if isWhitelisted {
|
||||
//single IP whitelist entry
|
||||
return true
|
||||
}
|
||||
|
||||
//Check for IP wildcard and CIRD rules
|
||||
AllWhitelistedIps := s.GetAllWhitelistedIpAsStringSlice()
|
||||
for _, whitelistRules := range AllWhitelistedIps {
|
||||
wildcardMatch := MatchIpWildcard(ipAddr, whitelistRules)
|
||||
if wildcardMatch {
|
||||
return true
|
||||
}
|
||||
|
||||
cidrMatch := MatchIpCIDR(ipAddr, whitelistRules)
|
||||
if cidrMatch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Store) GetAllWhitelistedIp() []*WhitelistEntry {
|
||||
whitelistedIp := []*WhitelistEntry{}
|
||||
entries, err := s.sysdb.ListTable("whitelist-ip")
|
||||
if err != nil {
|
||||
return whitelistedIp
|
||||
}
|
||||
|
||||
for _, keypairs := range entries {
|
||||
//ip := string(keypairs[0])
|
||||
thisEntry := WhitelistEntry{}
|
||||
json.Unmarshal(keypairs[1], &thisEntry)
|
||||
whitelistedIp = append(whitelistedIp, &thisEntry)
|
||||
}
|
||||
|
||||
return whitelistedIp
|
||||
}
|
||||
|
||||
func (s *Store) GetAllWhitelistedIpAsStringSlice() []string {
|
||||
allWhitelistedIPs := []string{}
|
||||
entries := s.GetAllWhitelistedIp()
|
||||
for _, entry := range entries {
|
||||
allWhitelistedIPs = append(allWhitelistedIPs, entry.IP)
|
||||
}
|
||||
|
||||
return allWhitelistedIPs
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package geodb
|
||||
package netutils
|
||||
|
||||
import (
|
||||
"net"
|
||||
@ -6,7 +6,13 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Utilities function
|
||||
/*
|
||||
MatchIP.go
|
||||
|
||||
This script contains function for matching IP address, comparing
|
||||
CIDR and IPv4 / v6 validations
|
||||
*/
|
||||
|
||||
func GetRequesterIP(r *http.Request) string {
|
||||
ip := r.Header.Get("X-Real-Ip")
|
||||
if ip == "" {
|
||||
@ -87,6 +93,10 @@ func MatchIpCIDR(ip string, cidr string) bool {
|
||||
|
||||
// Check if a ip is private IP range
|
||||
func IsPrivateIP(ipStr string) bool {
|
||||
if ipStr == "127.0.0.1" || ipStr == "::1" {
|
||||
//local loopback
|
||||
return true
|
||||
}
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false
|
@ -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) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
Jar: jar,
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -94,6 +94,7 @@ func ReverseProxtInit() {
|
||||
GeodbStore: geodbStore,
|
||||
StatisticCollector: statisticCollector,
|
||||
WebDirectory: *staticWebServerRoot,
|
||||
AccessController: accessController,
|
||||
})
|
||||
if err != nil {
|
||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to create dynamic proxy router", err)
|
||||
@ -194,6 +195,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
useTLS := (tls == "true")
|
||||
|
||||
//Bypass global TLS value / allow direct access from port 80?
|
||||
bypassGlobalTLS, _ := utils.PostPara(r, "bypassGlobalTLS")
|
||||
if bypassGlobalTLS == "" {
|
||||
bypassGlobalTLS = "false"
|
||||
@ -201,6 +203,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
useBypassGlobalTLS := bypassGlobalTLS == "true"
|
||||
|
||||
//Enable TLS validation?
|
||||
stv, _ := utils.PostPara(r, "tlsval")
|
||||
if stv == "" {
|
||||
stv = "false"
|
||||
@ -208,6 +211,17 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
skipTlsValidation := (stv == "true")
|
||||
|
||||
//Get access rule ID
|
||||
accessRuleID, _ := utils.PostPara(r, "access")
|
||||
if accessRuleID == "" {
|
||||
accessRuleID = "default"
|
||||
}
|
||||
if !accessController.AccessRuleExists(accessRuleID) {
|
||||
utils.SendErrorResponse(w, "invalid access rule ID selected")
|
||||
return
|
||||
}
|
||||
|
||||
//Require basic auth?
|
||||
rba, _ := utils.PostPara(r, "bauth")
|
||||
if rba == "" {
|
||||
rba = "false"
|
||||
@ -254,19 +268,37 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
if eptype == "host" {
|
||||
rootOrMatchingDomain, err := utils.PostPara(r, "rootname")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "subdomain not defined")
|
||||
utils.SendErrorResponse(w, "hostname not defined")
|
||||
return
|
||||
}
|
||||
rootOrMatchingDomain = strings.TrimSpace(rootOrMatchingDomain)
|
||||
|
||||
//Check if it contains ",", if yes, split the remainings as alias
|
||||
aliasHostnames := []string{}
|
||||
if strings.Contains(rootOrMatchingDomain, ",") {
|
||||
matchingDomains := strings.Split(rootOrMatchingDomain, ",")
|
||||
if len(matchingDomains) > 1 {
|
||||
rootOrMatchingDomain = matchingDomains[0]
|
||||
for _, aliasHostname := range matchingDomains[1:] {
|
||||
//Filter out any space
|
||||
aliasHostnames = append(aliasHostnames, strings.TrimSpace(aliasHostname))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Generate a proxy endpoint object
|
||||
thisProxyEndpoint := dynamicproxy.ProxyEndpoint{
|
||||
//I/O
|
||||
ProxyType: dynamicproxy.ProxyType_Host,
|
||||
RootOrMatchingDomain: rootOrMatchingDomain,
|
||||
MatchingDomainAlias: aliasHostnames,
|
||||
Domain: endpoint,
|
||||
//TLS
|
||||
RequireTLS: useTLS,
|
||||
BypassGlobalTLS: useBypassGlobalTLS,
|
||||
SkipCertValidations: skipTlsValidation,
|
||||
SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
|
||||
AccessFilterUUID: accessRuleID,
|
||||
//VDir
|
||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||
//Custom headers
|
||||
@ -439,6 +471,62 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
|
||||
rootNameOrMatchingDomain, err := utils.PostPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid ep given")
|
||||
return
|
||||
}
|
||||
|
||||
//No need to check for type as root (/) can be set to default route
|
||||
//and hence, you will not need alias
|
||||
|
||||
//Load the previous alias from current proxy rules
|
||||
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Target proxy config not found or could not be loaded")
|
||||
return
|
||||
}
|
||||
|
||||
newAliasJSON, err := utils.PostPara(r, "alias")
|
||||
if err != nil {
|
||||
//No new set of alias given
|
||||
utils.SendErrorResponse(w, "new alias not given")
|
||||
return
|
||||
}
|
||||
|
||||
//Write new alias to runtime and file
|
||||
newAlias := []string{}
|
||||
err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
|
||||
if err != nil {
|
||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to parse new alias list", err)
|
||||
utils.SendErrorResponse(w, "Invalid alias list given")
|
||||
return
|
||||
}
|
||||
|
||||
//Set the current alias
|
||||
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
|
||||
newProxyEndpoint.MatchingDomainAlias = newAlias
|
||||
|
||||
// Prepare to replace the current routing rule
|
||||
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
targetProxyEntry.Remove()
|
||||
dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
|
||||
|
||||
// Save it to file
|
||||
err = SaveReverseProxyConfig(newProxyEndpoint)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Alias update failed")
|
||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to save alias update", err)
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
ep, err := utils.GetPara(r, "ep")
|
||||
if err != nil {
|
||||
@ -740,6 +828,35 @@ func ReverseProxyToggleRuleSet(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func ReverseProxyListDetail(w http.ResponseWriter, r *http.Request) {
|
||||
eptype, err := utils.PostPara(r, "type") //Support root and host
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "type not defined")
|
||||
return
|
||||
}
|
||||
|
||||
if eptype == "host" {
|
||||
epname, err := utils.PostPara(r, "epname")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "epname not defined")
|
||||
return
|
||||
}
|
||||
endpointRaw, ok := dynamicProxyRouter.ProxyEndpoints.Load(epname)
|
||||
if !ok {
|
||||
utils.SendErrorResponse(w, "proxy rule not found")
|
||||
return
|
||||
}
|
||||
targetEndpoint := dynamicproxy.CopyEndpoint(endpointRaw.(*dynamicproxy.ProxyEndpoint))
|
||||
js, _ := json.Marshal(targetEndpoint)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else if eptype == "root" {
|
||||
js, _ := json.Marshal(dynamicProxyRouter.Root)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "Invalid type given")
|
||||
}
|
||||
}
|
||||
|
||||
func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
|
||||
eptype, err := utils.PostPara(r, "type") //Support root and host
|
||||
if err != nil {
|
||||
@ -959,9 +1076,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
|
||||
}
|
||||
|
||||
@ -971,6 +1088,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")
|
||||
@ -978,26 +1101,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)
|
||||
@ -1011,12 +1154,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")
|
||||
@ -1029,20 +1166,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 {
|
||||
|
28
src/start.go
28
src/start.go
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/acme"
|
||||
"imuslab.com/zoraxy/mod/auth"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
@ -22,7 +23,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"
|
||||
)
|
||||
@ -91,6 +92,16 @@ func startupSequence() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//Create the access controller
|
||||
accessController, err = access.NewAccessController(&access.Options{
|
||||
Database: sysdb,
|
||||
GeoDB: geodbStore,
|
||||
ConfigFolder: "./conf/access",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//Create a statistic collector
|
||||
statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{
|
||||
Database: sysdb,
|
||||
@ -149,8 +160,17 @@ func startupSequence() {
|
||||
if err != nil {
|
||||
portInt = 8000
|
||||
}
|
||||
|
||||
hostName := *mdnsName
|
||||
if hostName == "" {
|
||||
hostName = "zoraxy_" + nodeUUID
|
||||
} else {
|
||||
//Trim off the suffix
|
||||
hostName = strings.TrimSuffix(hostName, ".local")
|
||||
}
|
||||
|
||||
mdnsScanner, err = mdns.NewMDNS(mdns.NetworkHost{
|
||||
HostName: "zoraxy_" + nodeUUID,
|
||||
HostName: hostName,
|
||||
Port: portInt,
|
||||
Domain: "zoraxy.arozos.com",
|
||||
Model: "Network Gateway",
|
||||
@ -209,9 +229,9 @@ func startupSequence() {
|
||||
webSshManager = sshprox.NewSSHProxyManager()
|
||||
|
||||
//Create TCP Proxy Manager
|
||||
tcpProxyManager = tcpprox.NewTCProxy(&tcpprox.Options{
|
||||
streamProxyManager = streamproxy.NewStreamProxy(&streamproxy.Options{
|
||||
Database: sysdb,
|
||||
AccessControlHandler: geodbStore.AllowConnectionAccess,
|
||||
AccessControlHandler: accessController.DefaultAccessRule.AllowConnectionAccess,
|
||||
})
|
||||
|
||||
//Create WoL MAC storage table
|
||||
|
2
src/start.sh
Normal file
2
src/start.sh
Normal file
@ -0,0 +1,2 @@
|
||||
#/bin/bash
|
||||
sudo ./zoraxy
|
File diff suppressed because it is too large
Load Diff
@ -66,6 +66,7 @@
|
||||
<tr><th>Domain</th>
|
||||
<th>Last Update</th>
|
||||
<th>Expire At</th>
|
||||
<th>DNS Challenge</th>
|
||||
<th class="no-sort">Renew</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
</tr></thead>
|
||||
@ -147,7 +148,7 @@
|
||||
|
||||
|
||||
//Renew certificate by button press
|
||||
function renewCertificate(domain, btn=undefined){
|
||||
function renewCertificate(domain, dns, btn=undefined){
|
||||
let defaultCA = $("#defaultCA").dropdown("get value");
|
||||
if (defaultCA.trim() == ""){
|
||||
defaultCA = "Let's Encrypt";
|
||||
@ -160,7 +161,7 @@
|
||||
$(btn).addClass('disabled');
|
||||
$(btn).html(`<i class="ui loading spinner icon"></i>`);
|
||||
}
|
||||
obtainCertificate(domain, defaultCA.trim(), function(succ){
|
||||
obtainCertificate(domain, dns, defaultCA.trim(), function(succ){
|
||||
if (btn != undefined){
|
||||
$(btn).removeClass('disabled');
|
||||
if (succ){
|
||||
@ -181,7 +182,7 @@
|
||||
*/
|
||||
|
||||
// Obtain certificate from API, only support one domain
|
||||
function obtainCertificate(domains, usingCa = "Let's Encrypt", callback=undefined) {
|
||||
function obtainCertificate(domains, dns, usingCa = "Let's Encrypt", callback=undefined) {
|
||||
//Load the ACME email from server side
|
||||
let acmeEmail = "";
|
||||
$.get("/api/acme/autoRenew/email", function(data){
|
||||
@ -213,6 +214,8 @@
|
||||
}
|
||||
return;
|
||||
}
|
||||
//Filename cannot contain wildcards, and wildcards are possible with DNS challenges
|
||||
filename = filename.replace("*", "_");
|
||||
|
||||
$.ajax({
|
||||
url: "/api/acme/obtainCert",
|
||||
@ -222,6 +225,7 @@
|
||||
filename: filename,
|
||||
email: email,
|
||||
ca: usingCa,
|
||||
dns: dns
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.error) {
|
||||
@ -237,7 +241,7 @@
|
||||
msgbox("Certificate installed successfully");
|
||||
|
||||
if (callback != undefined){
|
||||
callback(false);
|
||||
callback(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -357,7 +361,8 @@
|
||||
<td>${entry.Domain}</td>
|
||||
<td>${entry.LastModifiedDate}</td>
|
||||
<td class="${isExpired?"expired":"valid"} certdate">${entry.ExpireDate} (${!isExpired?entry.RemainingDays+" days left":"Expired"})</td>
|
||||
<td><button title="Renew Certificate" class="ui mini basic icon button renewButton" onclick="renewCertificate('${entry.Domain}', this);"><i class="ui green refresh icon"></i></button></td>
|
||||
<td><i class="${entry.UseDNS?"green check": "red times"} circle outline icon"></i></td>
|
||||
<td><button title="Renew Certificate" class="ui mini basic icon button renewButton" onclick="renewCertificate('${entry.Domain}', '${entry.UseDNS}', this);"><i class="ui green refresh icon"></i></button></td>
|
||||
<td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
|
||||
</tr>`);
|
||||
});
|
||||
|
@ -7,8 +7,12 @@
|
||||
#httpProxyList .ui.toggle.checkbox input:checked ~ label::before{
|
||||
background-color: #00ca52 !important;
|
||||
}
|
||||
|
||||
.subdEntry td:not(.ignoremw){
|
||||
min-width: 200px;
|
||||
}
|
||||
</style>
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em;">
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em; min-height: 300px;">
|
||||
<table class="ui celled sortable unstackable compact table">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -16,7 +20,7 @@
|
||||
<th>Destination</th>
|
||||
<th>Virtual Directory</th>
|
||||
<th>Basic Auth</th>
|
||||
<th class="no-sort" style="min-width:100px;">Actions</th>
|
||||
<th class="no-sort" style="min-width:150px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="httpProxyList">
|
||||
@ -30,6 +34,8 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
/* List all proxy endpoints */
|
||||
function listProxyEndpoints(){
|
||||
$.get("/api/proxy/list?type=host", function(data){
|
||||
$("#httpProxyList").html(``);
|
||||
@ -42,6 +48,8 @@
|
||||
<td data-label="" colspan="5"><i class="green check circle icon"></i> No HTTP Proxy Record</td>
|
||||
</tr>`);
|
||||
}else{
|
||||
//Sort by RootOrMatchingDomain field
|
||||
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
|
||||
data.forEach(subd => {
|
||||
let tlsIcon = "";
|
||||
let subdData = encodeURIComponent(JSON.stringify(subd));
|
||||
@ -73,17 +81,33 @@
|
||||
vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;"><i class="check icon"></i> No Virtual Directory</small>`;
|
||||
}
|
||||
|
||||
var enableChecked = "checked";
|
||||
let enableChecked = "checked";
|
||||
if (subd.Disabled){
|
||||
enableChecked = "";
|
||||
}
|
||||
|
||||
let aliasDomains = ``;
|
||||
if (subd.MatchingDomainAlias != undefined && subd.MatchingDomainAlias.length > 0){
|
||||
aliasDomains = `<small class="aliasDomains" eptuuid="${subd.RootOrMatchingDomain}" style="color: #636363;">Alias: `;
|
||||
subd.MatchingDomainAlias.forEach(alias => {
|
||||
aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `;
|
||||
});
|
||||
aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
|
||||
aliasDomains += `</small><br>`;
|
||||
}
|
||||
|
||||
$("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
|
||||
<td data-label="" editable="true" datatype="inbound"><a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}</td>
|
||||
<td data-label="" editable="true" datatype="inbound">
|
||||
<a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}<br>
|
||||
${aliasDomains}
|
||||
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
|
||||
</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>
|
||||
<td class="center aligned" editable="true" datatype="action" data-label="">
|
||||
<td data-label="" editable="true" datatype="basicauth">
|
||||
${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}
|
||||
</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">
|
||||
<input type="checkbox" class="enableToggle" name="active" ${enableChecked} eptuuid="${subd.RootOrMatchingDomain}" onchange="handleProxyRuleToggle(this);">
|
||||
<label></label>
|
||||
@ -94,9 +118,87 @@
|
||||
</tr>`);
|
||||
});
|
||||
}
|
||||
|
||||
resolveAccessRuleNameOnHostRPlist();
|
||||
});
|
||||
}
|
||||
|
||||
//Perform realtime alias update without refreshing the whole page
|
||||
function updateAliasListForEndpoint(endpointName, newAliasDomainList){
|
||||
let targetEle = $(`.aliasDomains[eptuuid='${endpointName}']`);
|
||||
console.log(targetEle);
|
||||
if (targetEle.length == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
let aliasDomains = ``;
|
||||
if (newAliasDomainList != undefined && newAliasDomainList.length > 0){
|
||||
aliasDomains = `Alias: `;
|
||||
newAliasDomainList.forEach(alias => {
|
||||
aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `;
|
||||
});
|
||||
aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
|
||||
$(targetEle).html(aliasDomains);
|
||||
$(targetEle).show();
|
||||
}else{
|
||||
$(targetEle).hide();
|
||||
}
|
||||
}
|
||||
|
||||
//Resolve & Update all rule names on host PR list
|
||||
function resolveAccessRuleNameOnHostRPlist(){
|
||||
//Resolve the access filters
|
||||
$.get("/api/access/list", function(data){
|
||||
console.log(data);
|
||||
if (data.error == undefined){
|
||||
//Build a map base on the data
|
||||
let accessRuleMap = {};
|
||||
for (var i = 0; i < data.length; i++){
|
||||
accessRuleMap[data[i].ID] = data[i];
|
||||
}
|
||||
|
||||
|
||||
$(".accessRuleNameUnderHost").each(function(){
|
||||
let thisAccessRuleID = $(this).attr("ruleid");
|
||||
if (thisAccessRuleID== ""){
|
||||
thisAccessRuleID = "default"
|
||||
}
|
||||
|
||||
if (thisAccessRuleID == "default"){
|
||||
//No need to label default access rules
|
||||
$(this).html("");
|
||||
return;
|
||||
}
|
||||
|
||||
let rule = accessRuleMap[thisAccessRuleID];
|
||||
let icon = `<i class="ui grey filter icon"></i>`;
|
||||
if (rule.ID == "default"){
|
||||
icon = `<i class="ui yellow star icon"></i>`;
|
||||
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
|
||||
//This is a blacklist filter
|
||||
icon = `<i class="ui red filter icon"></i>`;
|
||||
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
|
||||
//This is a whitelist filter
|
||||
icon = `<i class="ui green filter icon"></i>`;
|
||||
}else if (rule.WhitelistEnabled && rule.BlacklistEnabled){
|
||||
//Whitelist and blacklist filter
|
||||
icon = `<i class="ui yellow filter icon"></i>`;
|
||||
}
|
||||
|
||||
if (rule != undefined){
|
||||
$(this).html(`${icon} ${rule.Name}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Update the access rule name on given epuuid, call by hostAccessEditor.html
|
||||
function updateAccessRuleNameUnderHost(epuuid, newruleUID){
|
||||
$(`tr[eptuuid='${epuuid}'].subdEntry`).find(".accessRuleNameUnderHost").attr("ruleid", newruleUID);
|
||||
resolveAccessRuleNameOnHostRPlist();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Inline editor for httprp.html
|
||||
@ -176,7 +278,7 @@
|
||||
|
||||
column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="RequireBasicAuth" ${checkstate}>
|
||||
<label>Require Basic Auth</label>
|
||||
<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>
|
||||
<div class="ui basic advance segment" style="padding: 0.4em !important; border-radius: 0.4em;">
|
||||
@ -191,6 +293,7 @@
|
||||
<label>Skip WebSocket Origin Check<br>
|
||||
<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>
|
||||
@ -213,7 +316,12 @@
|
||||
<label>Allow plain HTTP access<br>
|
||||
<small>Allow inbound connections without TLS/SSL</small></label>
|
||||
</div><br>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAliasHostnames('${uuid}');"><i class=" blue at icon"></i> Alias</button>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAccessRule('${uuid}');"><i class="ui filter icon"></i> Access Rule</button>
|
||||
|
||||
`);
|
||||
|
||||
$(".hostAccessRuleSelector").dropdown();
|
||||
}else{
|
||||
//Unknown field. Leave it untouched
|
||||
}
|
||||
@ -277,6 +385,22 @@
|
||||
showSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function editAccessRule(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function editAliasHostnames(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/aliasEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function quickEditVdir(uuid){
|
||||
openTabById("vdir");
|
||||
$("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid);
|
||||
@ -313,6 +437,9 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Access List handling */
|
||||
|
||||
|
||||
|
||||
//Bind on tab switch events
|
||||
|
@ -258,7 +258,7 @@
|
||||
|
||||
setTimeout(function(){
|
||||
//Update the checkbox
|
||||
msgbox("Proxy Root Updated");
|
||||
msgbox("Default Site Updated");
|
||||
}, 100);
|
||||
|
||||
})
|
||||
|
@ -5,6 +5,12 @@
|
||||
color: var(--theme_lgrey);
|
||||
border-radius: 1em !important;
|
||||
}
|
||||
|
||||
.ui.form .sub.field{
|
||||
background-color: var(--theme_advance);
|
||||
border-radius: 0.6em;
|
||||
padding: 1em;
|
||||
}
|
||||
</style>
|
||||
<div class="standardContainer">
|
||||
<div class="ui stackable grid">
|
||||
@ -16,7 +22,7 @@
|
||||
<div class="field">
|
||||
<label>Matching Keyword / Domain</label>
|
||||
<input type="text" id="rootname" placeholder="mydomain.com">
|
||||
<small>Support subdomain and wildcard, e.g. s1.mydomain.com or *.test.mydomain.com</small>
|
||||
<small>Support subdomain and wildcard, e.g. s1.mydomain.com or *.test.mydomain.com. Use comma (,) for alias hostnames. </small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Target IP Address or Domain Name with port</label>
|
||||
@ -37,7 +43,18 @@
|
||||
Advance Settings
|
||||
</div>
|
||||
<div class="content">
|
||||
<p></p>
|
||||
<div class="field">
|
||||
<label>Access Rule</label>
|
||||
<div class="ui selection dropdown">
|
||||
<input type="hidden" id="newProxyRuleAccessFilter" value="default">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text">Default</div>
|
||||
<div class="menu" id="newProxyRuleAccessList">
|
||||
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
|
||||
</div>
|
||||
</div>
|
||||
<small>Allow regional access control using blacklist or whitelist. Use "default" for "allow all".</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="skipTLSValidation">
|
||||
@ -121,8 +138,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$("#advanceProxyRules").accordion();
|
||||
|
||||
|
||||
//New Proxy Endpoint
|
||||
function newProxyEndpoint(){
|
||||
@ -133,7 +148,8 @@
|
||||
var bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked;
|
||||
var requireBasicAuth = $("#requireBasicAuth")[0].checked;
|
||||
var skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked;
|
||||
|
||||
var accessRuleToUse = $("#newProxyRuleAccessFilter").val();
|
||||
|
||||
if (rootname.trim() == ""){
|
||||
$("#rootname").parent().addClass("error");
|
||||
return
|
||||
@ -161,7 +177,7 @@
|
||||
bypassGlobalTLS: bypassGlobalTLS,
|
||||
bauth: requireBasicAuth,
|
||||
cred: JSON.stringify(credentials),
|
||||
|
||||
access: accessRuleToUse,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
@ -343,4 +359,47 @@
|
||||
return back;
|
||||
}
|
||||
|
||||
/*
|
||||
Access Rule dropdown Initialization
|
||||
*/
|
||||
|
||||
function initNewProxyRuleAccessDropdownList(callback=undefined){
|
||||
$.get("/api/access/list", function(data){
|
||||
if (data.error == undefined){
|
||||
$("#newProxyRuleAccessList").html("");
|
||||
data.forEach(function(rule){
|
||||
let icon = `<i class="ui grey filter icon"></i>`;
|
||||
if (rule.ID == "default"){
|
||||
icon = `<i class="ui yellow star icon"></i>`;
|
||||
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
|
||||
//This is a blacklist filter
|
||||
icon = `<i class="ui red filter icon"></i>`;
|
||||
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
|
||||
//This is a whitelist filter
|
||||
icon = `<i class="ui green filter icon"></i>`;
|
||||
}
|
||||
$("#newProxyRuleAccessList").append(`<div class="item" data-value="${rule.ID}">${icon} ${rule.Name}</div>`);
|
||||
});
|
||||
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||
if (callback != undefined){
|
||||
callback();
|
||||
}
|
||||
}else{
|
||||
msgbox("Access rule load failed: " + data.error, false);
|
||||
}
|
||||
})
|
||||
}
|
||||
initNewProxyRuleAccessDropdownList();
|
||||
|
||||
//Bind on tab switch events
|
||||
tabSwitchEventBind["rules"] = function(){
|
||||
//Update the access rule list
|
||||
initNewProxyRuleAccessDropdownList();
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
$("#advanceProxyRules").accordion();
|
||||
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||
});
|
||||
|
||||
</script>
|
@ -765,8 +765,11 @@
|
||||
let data = Object.values(visitorData);
|
||||
|
||||
Object.keys(visitorData).forEach(function(cc){
|
||||
console.log(cc);
|
||||
if (cc == ""){
|
||||
labels.push("Local / Unknown")
|
||||
labels.push("Unknown")
|
||||
}else if (cc == "lan"){
|
||||
labels.push(`LAN / Loopback`);
|
||||
}else{
|
||||
labels.push(`${getCountryName(cc)} [${cc.toUpperCase()}]` );
|
||||
}
|
||||
|
@ -68,12 +68,13 @@
|
||||
<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>
|
||||
<br><br>
|
||||
<div id="tls" class="ui toggle notloopbackOnly checkbox">
|
||||
<input type="checkbox">
|
||||
<label>Use TLS to serve proxy request</label>
|
||||
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
382
src/web/components/streamprox.html
Normal file
382
src/web/components/streamprox.html
Normal file
@ -0,0 +1,382 @@
|
||||
<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);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
},
|
||||
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>
|
@ -10,7 +10,7 @@
|
||||
<i class="red remove icon"></i>
|
||||
<div class="content">
|
||||
Uptime Monitoring service is currently unavailable
|
||||
<div class="sub header">This might cause by an error in cluster communication within the host servers. Please wait for administrator to resolve the issue.</div>
|
||||
<div class="sub header">This might be caused by an error in cluster communication within the host servers. Please wait for administrator to resolve the issue.</div>
|
||||
</div>
|
||||
</h4>
|
||||
</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;`;
|
||||
|
@ -65,21 +65,9 @@
|
||||
|
||||
<div class="field">
|
||||
<p><i class="caret down icon"></i> Credentials for SMTP server authentications</p>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>Sender Username</label>
|
||||
<input type="text" name="username" placeholder="E.g. admin">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Sender Domain</label>
|
||||
<div class="ui labeled input">
|
||||
<div class="ui basic label">
|
||||
@
|
||||
</div>
|
||||
<input type="text" name="domain" min="1" max="65534" placeholder="E.g. arozos.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Sender Username / Email</label>
|
||||
<input type="text" name="username" placeholder="e.g. admin or admin@mydomain.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
@ -229,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`);
|
||||
@ -272,7 +261,6 @@
|
||||
e.preventDefault();
|
||||
var data = {
|
||||
hostname: $('input[name=hostname]').val(),
|
||||
domain: $('input[name=domain]').val(),
|
||||
port: parseInt($('input[name=port]').val()),
|
||||
username: $('input[name=username]').val(),
|
||||
password: $('input[name=password]').val(),
|
||||
@ -306,7 +294,6 @@
|
||||
function initSMTPSettings(){
|
||||
$.get("/api/tools/smtp/get", function(data){
|
||||
$('#email-form input[name=hostname]').val(data.Hostname);
|
||||
$('#email-form input[name=domain]').val(data.Domain);
|
||||
$('#email-form input[name=port]').val(data.Port);
|
||||
$('#email-form input[name=username]').val(data.Username);
|
||||
$('#email-form input[name=senderAddr]').val(data.SenderAddr);
|
||||
@ -345,14 +332,6 @@
|
||||
form.find('input[name="hostname"]').parent().removeClass('error');
|
||||
}
|
||||
|
||||
// validate domain
|
||||
const domain = form.find('input[name="domain"]').val().trim();
|
||||
if (!domainRegex.test(domain)) {
|
||||
form.find('input[name="domain"]').parent().addClass('error');
|
||||
isValid = false;
|
||||
} else {
|
||||
form.find('input[name="domain"]').parent().removeClass('error');
|
||||
}
|
||||
|
||||
// validate username
|
||||
const username = form.find('input[name="username"]').val().trim();
|
||||
@ -363,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)) {
|
||||
|
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">
|
||||
|
@ -2,9 +2,6 @@
|
||||
index.html style overwrite
|
||||
*/
|
||||
:root{
|
||||
|
||||
|
||||
|
||||
--theme_background: linear-gradient(60deg, rgb(84, 58, 183) 0%, rgb(0, 172, 193) 100%);
|
||||
--theme_background_inverted: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%);
|
||||
--theme_green: linear-gradient(270deg, #27e7ff, #00ca52);
|
||||
@ -256,7 +253,7 @@ body{
|
||||
|
||||
.sideWrapperMenu{
|
||||
height: 3px;
|
||||
background-color: #414141;
|
||||
background: var(--theme_background);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -512,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
|
||||
*/
|
||||
@ -554,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;
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
||||
font-family: 'Lato';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Lato Bold'), local('Lato-Bold'), url(https://fonts.gstatic.com/s/lato/v17/S6u9w4BMUTPHh6UVSwaPGR_p.woff2) format('woff2');
|
||||
src: local('Lato Bold'), local('Lato-Bold'), url(fonts/S6u9w4BMUTPHh6UVSwaPGR_p.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -59,6 +59,6 @@
|
||||
font-family: 'Lato';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Lato Bold'), local('Lato-Bold'), url(https://fonts.gstatic.com/s/lato/v17/S6u9w4BMUTPHh6UVSwiPGQ.woff2) format('woff2');
|
||||
src: local('Lato Bold'), local('Lato-Bold'), url(fonts/S6u9w4BMUTPHh6UVSwiPGQ.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
267
src/web/snippet/accessRuleEditor.html
Normal file
267
src/web/snippet/accessRuleEditor.html
Normal file
@ -0,0 +1,267 @@
|
||||
<!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>
|
||||
<style>
|
||||
#refreshAccessRuleListBtn{
|
||||
position: absolute;
|
||||
top: 0.4em;
|
||||
right: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Access Rule Editor
|
||||
<div class="sub header">Create, Edit or Remove Access Rules</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui top attached tabular menu">
|
||||
<a class="active item" data-tab="new"><i class="ui green add icon"></i> New</a>
|
||||
<a class="item" data-tab="edit"><i class="ui grey edit icon"></i> Edit</a>
|
||||
</div>
|
||||
<div class="ui bottom attached active tab segment" data-tab="new">
|
||||
<p>Create a new Access Rule</p>
|
||||
<form class="ui form" id="accessRuleForm">
|
||||
<div class="field">
|
||||
<label>Rule Name</label>
|
||||
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Description</label>
|
||||
<textarea name="description" placeholder="Description" required></textarea>
|
||||
</div>
|
||||
<button class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
|
||||
</form>
|
||||
<br>
|
||||
</div>
|
||||
<div class="ui bottom attached tab segment" data-tab="edit">
|
||||
<p>Select an Access Rule to edit</p>
|
||||
<button id="refreshAccessRuleListBtn" class="ui circular basic icon button" onclick="reloadAccessRuleList()"><i class="ui green refresh icon"></i></button>
|
||||
<div class="ui selection fluid dropdown" id="accessRuleSelector">
|
||||
<input type="hidden" name="targetAccessRule" value="default">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text"></div>
|
||||
<div class="menu" id="accessRuleList">
|
||||
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<form class="ui form" id="modifyRuleInfo">
|
||||
<div class="disabled field">
|
||||
<label>Rule ID</label>
|
||||
<input type="text" name="accessRuleUUID">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Rule Name</label>
|
||||
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Description</label>
|
||||
<textarea name="description" placeholder="Description" required></textarea>
|
||||
</div>
|
||||
<button class="ui basic button" type="submit"><i class="ui green save icon"></i> Save Changes</button>
|
||||
<button class="ui basic button" onclick="removeAccessRule(event);"><i class="ui red trash icon"></i> Remove Rule</button>
|
||||
</form>
|
||||
</div>
|
||||
<br>
|
||||
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
|
||||
<br><br><br>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let accessRuleList = [];
|
||||
$('.dropdown').dropdown();
|
||||
$('.menu .item').tab();
|
||||
|
||||
function handleCreateNewAccessRule(event) {
|
||||
event.preventDefault(); // Prevent the default form submission
|
||||
const formData = new FormData(event.target);
|
||||
const accessRuleName = formData.get('accessRuleName');
|
||||
const description = formData.get('description');
|
||||
|
||||
console.log('Access Rule Name:', accessRuleName);
|
||||
console.log('Description:', description);
|
||||
|
||||
$("#accessRuleForm input[name='accessRuleName']").val("");
|
||||
$("#accessRuleForm textarea[name='description']").val("");
|
||||
|
||||
$.ajax({
|
||||
url: "/api/access/create",
|
||||
method: "POST",
|
||||
data: {
|
||||
"name": accessRuleName,
|
||||
"desc": description
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("Access Rule Created", true);
|
||||
reloadAccessRuleList();
|
||||
if (parent != undefined && parent.reloadAccessRules != undefined){
|
||||
parent.reloadAccessRules();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Handle on change of the dropdown selection
|
||||
function handleSelectEditingAccessRule(){
|
||||
const selectedValue = document.querySelector('#accessRuleSelector').querySelector('input').value;
|
||||
console.log('Selected Value:', selectedValue);
|
||||
//Load the information from list
|
||||
loadAccessRuleInfoIntoEditFields(selectedValue);
|
||||
}
|
||||
|
||||
//Load the access rules information into the fields
|
||||
function loadAccessRuleInfoIntoEditFields(targetAccessRuleUUID){
|
||||
var targetAccessRule = undefined;
|
||||
for (var i = 0; i < accessRuleList.length; i++){
|
||||
let thisAccessRule = accessRuleList[i];
|
||||
if (thisAccessRule.ID == targetAccessRuleUUID){
|
||||
targetAccessRule = thisAccessRule;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetAccessRule == undefined){
|
||||
//Target exists rule no longer exists
|
||||
return;
|
||||
}
|
||||
|
||||
let accessRuleID = targetAccessRule.ID;
|
||||
let accessRuleName = targetAccessRule.Name;
|
||||
let accessRuleDesc = targetAccessRule.Desc;
|
||||
|
||||
//Load the information into the form input field
|
||||
//Load the information into the form input field
|
||||
document.querySelector('#modifyRuleInfo input[name="accessRuleUUID"]').value = accessRuleID;
|
||||
document.querySelector('#modifyRuleInfo input[name="accessRuleName"]').value = accessRuleName;
|
||||
document.querySelector('#modifyRuleInfo textarea[name="description"]').value = accessRuleDesc;
|
||||
}
|
||||
|
||||
//Bind events to modify rule form
|
||||
document.getElementById('modifyRuleInfo').addEventListener('submit', function(event){
|
||||
event.preventDefault(); // Prevent the default form submission
|
||||
|
||||
const accessRuleUUID = document.querySelector('#modifyRuleInfo input[name="accessRuleUUID"]').value;
|
||||
const accessRuleName = document.querySelector('#modifyRuleInfo input[name="accessRuleName"]').value;
|
||||
const description = document.querySelector('#modifyRuleInfo textarea[name="description"]').value;
|
||||
|
||||
|
||||
console.log('Access Rule UUID:', accessRuleUUID);
|
||||
console.log('Access Rule Name:', accessRuleName);
|
||||
console.log('Description:', description);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/access/update",
|
||||
method: "POST",
|
||||
data: {
|
||||
"id":accessRuleUUID,
|
||||
"name":accessRuleName,
|
||||
"desc":description
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("Access rule updated", true);
|
||||
initAccessRuleList(function(){
|
||||
$("#accessRuleSelector").dropdown("set selected", accessRuleUUID);
|
||||
loadAccessRuleInfoIntoEditFields(accessRuleUUID);
|
||||
});
|
||||
if (parent != undefined && parent.reloadAccessRules != undefined){
|
||||
parent.reloadAccessRules();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function initAccessRuleList(callback=undefined){
|
||||
$.get("/api/access/list", function(data){
|
||||
if (data.error == undefined){
|
||||
$("#accessRuleList").html("");
|
||||
data.forEach(function(rule){
|
||||
let icon = `<i class="ui grey filter icon"></i>`;
|
||||
if (rule.ID == "default"){
|
||||
icon = `<i class="ui yellow star icon"></i>`;
|
||||
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
|
||||
//This is a blacklist filter
|
||||
icon = `<i class="ui red filter icon"></i>`;
|
||||
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
|
||||
//This is a whitelist filter
|
||||
icon = `<i class="ui green filter icon"></i>`;
|
||||
}
|
||||
$("#accessRuleList").append(`<div class="item" data-value="${rule.ID}">${icon} ${rule.Name}</div>`);
|
||||
});
|
||||
accessRuleList = data;
|
||||
$(".dropdown").dropdown();
|
||||
if (callback != undefined){
|
||||
callback();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
initAccessRuleList(function(){
|
||||
$("#accessRuleSelector").dropdown("set selected", "default");
|
||||
loadAccessRuleInfoIntoEditFields("default");
|
||||
});
|
||||
|
||||
function reloadAccessRuleList(){
|
||||
initAccessRuleList(function(){
|
||||
$("#accessRuleSelector").dropdown("set selected", "default");
|
||||
loadAccessRuleInfoIntoEditFields("default");
|
||||
});
|
||||
}
|
||||
|
||||
function removeAccessRule(event){
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
let accessRuleUUID = $("#modifyRuleInfo input[name='accessRuleUUID']").val();
|
||||
if (accessRuleUUID == ""){
|
||||
return;
|
||||
}
|
||||
if (accessRuleUUID == "default"){
|
||||
parent.msgbox("Default access rule cannot be removed", false);
|
||||
return;
|
||||
}
|
||||
let accessRuleName = $("#modifyRuleInfo input[name='accessRuleName']").val();
|
||||
if (confirm("Confirm removing access rule " + accessRuleName + "?")){
|
||||
$.ajax({
|
||||
url: "/api/access/remove",
|
||||
data: {
|
||||
"id": accessRuleUUID
|
||||
},
|
||||
method: "POST",
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("Access rule removed", true);
|
||||
reloadAccessRuleList();
|
||||
if (parent != undefined && parent.reloadAccessRules != undefined){
|
||||
parent.reloadAccessRules();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
document.getElementById('accessRuleSelector').addEventListener('change', handleSelectEditingAccessRule);
|
||||
document.getElementById('accessRuleForm').addEventListener('submit', handleCreateNewAccessRule);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -114,10 +114,52 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field" id="dnsChallenge">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="useDnsChallenge" onchange="toggleDnsChallenge()">
|
||||
<label>Use a DNS Challenge<br>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field dnsChallengeOnly" style="display:none;">
|
||||
<label>DNS Provider</label>
|
||||
<div class="ui search selection dropdown" id="dnsProvider">
|
||||
<input type="hidden" name="dnsProvider" value="">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text">Pick a DNS Provider</div>
|
||||
<div class="menu" id="dnsProviderList">
|
||||
<!-- Auto populate moved to acmedns module and initDNSProviderList() -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field dnsChallengeOnly" style="display:none;">
|
||||
<div class="ui divider"></div>
|
||||
<p>DNS Credentials (Leave all fields empty to use previous settings)<br>
|
||||
<small><i class="yellow exclamation triangle icon"></i> Note that domain DNS credentials are stored separately. For each new subdomain, you will need to enter a new DNS credentials.</small></p>
|
||||
<div id="dnsProviderAPIFields">
|
||||
|
||||
</div>
|
||||
<!--
|
||||
<label>Credentials File Content</label>
|
||||
<textarea id="dnsCredentials" placeholder=""></textarea>
|
||||
<small>For more information on the supported DNS Providers and their attirbutes look <a href="https://go-acme.github.io/lego/dns/" target="_blank">here</a>! </small>
|
||||
<div class="ui negative message">
|
||||
<i class="icon exclamation triangle"></i>
|
||||
These credentials will be stored as plaintext in the database and in environment variables!
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<div class="field" id="caInput" style="display:none;">
|
||||
<label>ACME Server URL</label>
|
||||
<input id="caURL" type="text" placeholder="https://example.com/acme/dictionary">
|
||||
</div>
|
||||
<div class="field" id="kidInput" style="display:none;">
|
||||
<label>EAB Credentials (KID) for current provider</label>
|
||||
<input id="eab_kid" type="text" placeholder="Leave this field blank to keep the current configuration">
|
||||
</div>
|
||||
<div class="field" id="hmacInput" style="display:none;">
|
||||
<label>EAB HMAC Key for current provider</label>
|
||||
<input id="eab_hmac" type="text" placeholder="Leave this field blank to keep the current configuration">
|
||||
</div>
|
||||
<div class="field" id="skipTLS" style="display:none;">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="skipTLSCheckbox">
|
||||
@ -314,29 +356,106 @@
|
||||
// Button click event handler for obtaining certificate
|
||||
$("#obtainButton").click(function() {
|
||||
$("#obtainButton").addClass("loading").addClass("disabled");
|
||||
obtainCertificate();
|
||||
updateCertificateEAB(function(succ){
|
||||
if (succ){
|
||||
//Continue to next step
|
||||
updateCertificateDNS(function(succ){
|
||||
if (succ){
|
||||
obtainCertificate(function(succ){
|
||||
$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
});
|
||||
}else{
|
||||
$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
console.log("update Certificate DNS process halted");
|
||||
}
|
||||
});
|
||||
}else{
|
||||
console.log("Update Certificate EAB process halted");
|
||||
$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
//On CA change in dropdown
|
||||
$("input[name=ca]").on('change', function() {
|
||||
if(this.value == "Custom ACME Server") {
|
||||
$("#caInput").show();
|
||||
$("#kidInput").show();
|
||||
$("#hmacInput").show();
|
||||
$("#skipTLS").show();
|
||||
} else {
|
||||
$("#dnsChallenge").hide();
|
||||
$(".dnsChallengeOnly").hide();
|
||||
} else if (this.value == "ZeroSSL") {
|
||||
$("#kidInput").show();
|
||||
$("#hmacInput").show();
|
||||
$("#dnsChallenge").hide();
|
||||
$(".dnsChallengeOnly").hide();
|
||||
$("#skipTLS").hide();
|
||||
} else if (this.value == "Buypass") {
|
||||
$("#kidInput").show();
|
||||
$("#hmacInput").show();
|
||||
$("#dnsChallenge").hide();
|
||||
$(".dnsChallengeOnly").hide();
|
||||
$("#skipTLS").hide();
|
||||
}else {
|
||||
$("#caInput").hide();
|
||||
$("#skipTLS").hide();
|
||||
$("#kidInput").hide();
|
||||
$("#hmacInput").hide();
|
||||
$("#dnsChallenge").show();
|
||||
if ($("#useDnsChallenge")[0].checked){
|
||||
$(".dnsChallengeOnly").show();
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
// Obtain certificate from API
|
||||
function obtainCertificate() {
|
||||
//On DNS provider dropdown change
|
||||
$("input[name=dnsProvider]").on('change', function() {
|
||||
let newProviderName = $("#dnsProvider").find("input").val();
|
||||
$.get("/api/acme/dns/providers?name=" + newProviderName, function(data){
|
||||
console.log("Loaded required config", data);
|
||||
$("#dnsProviderAPIFields").html("");
|
||||
//Generate a form for this config
|
||||
let booleanFieldsHTML = "";
|
||||
for (const [key, datatype] of Object.entries(data)) {
|
||||
if (datatype == "int"){
|
||||
$("#dnsProviderAPIFields").append(`<div class="ui fluid labeled dnsConfigField input" key="${key}" style="margin-top: 0.2em;">
|
||||
<div class="ui basic blue label" style="font-weight: 300;">
|
||||
${key}
|
||||
</div>
|
||||
<input type="number" value="300">
|
||||
</div>`);
|
||||
}else if (datatype == "bool"){
|
||||
booleanFieldsHTML += (`<div class="ui checkbox dnsConfigField" key="${key}" style="margin-top: 1em !important; padding-left: 0.4em;">
|
||||
<input type="checkbox">
|
||||
<label>${key}</label>
|
||||
</div>`);
|
||||
}else{
|
||||
//Default to string
|
||||
$("#dnsProviderAPIFields").append(`<div class="ui fluid labeled input dnsConfigField" key="${key}" style="margin-top: 0.2em;">
|
||||
<div class="ui basic label" style="font-weight: 300;">
|
||||
${key}
|
||||
</div>
|
||||
<input type="text">
|
||||
</div>`);
|
||||
}
|
||||
}
|
||||
|
||||
//Append the boolean fields at the bottom, if exists
|
||||
$("#dnsProviderAPIFields").append(booleanFieldsHTML);
|
||||
if (booleanFieldsHTML != ""){
|
||||
$(".dnsConfigField.checkbox").checkbox();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Get filename form domains and input
|
||||
function getFilename() {
|
||||
var domains = $("#domainsInput").val();
|
||||
var filename = $("#filenameInput").val();
|
||||
var email = $("#caRegisterEmail").val();
|
||||
if (email == ""){
|
||||
parent.msgbox("ACME renew email is not set", false)
|
||||
$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
return;
|
||||
}
|
||||
if (filename.trim() == "" && !domains.includes(",")){
|
||||
//Zoraxy filename are the matching name for domains.
|
||||
//Use the same as domains
|
||||
@ -350,6 +469,215 @@
|
||||
$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
//Filename cannot contain wildcards, and wildcards are possible with DNS challenges
|
||||
filename = filename.replace("*", "_");
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
// Update EAB values for autorenewal
|
||||
function updateCertificateEAB(callback=undefined) {
|
||||
var ca = $("#ca").dropdown("get value");
|
||||
var caURL = "";
|
||||
if (ca == "Custom ACME Server") {
|
||||
ca = "custom";
|
||||
caURL = $("#caURL").val();
|
||||
}else if(ca == "Buypass") {
|
||||
caURL = "https://api.buypass.com/acme/directory";
|
||||
}else if(ca == "ZeroSSL") {
|
||||
caURL = "https://acme.zerossl.com/v2/DV90";
|
||||
}
|
||||
|
||||
if(caURL == "") {
|
||||
//Skip update
|
||||
if (callback != undefined){
|
||||
callback(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var kid = $("#eab_kid").val();
|
||||
var hmac = $("#eab_hmac").val();
|
||||
|
||||
if(kid == "" || hmac == "") {
|
||||
//Skip update
|
||||
if (callback != undefined){
|
||||
callback(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(caURL + " " + kid + " " + hmac);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/acme/autoRenew/setEAB",
|
||||
method: "GET",
|
||||
data: {
|
||||
acmeDirectoryURL: caURL,
|
||||
kid: kid,
|
||||
hmacEncoded: hmac,
|
||||
},
|
||||
success: function(response) {
|
||||
//$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
if (response.error) {
|
||||
console.log("Error:", response.error);
|
||||
// Show error message
|
||||
parent.msgbox(response.error, false, 12000);
|
||||
if (callback != undefined){
|
||||
callback(false);
|
||||
}
|
||||
} else {
|
||||
console.log("Certificate EAB updated successfully");
|
||||
// Show success message
|
||||
parent.msgbox("Certificate EAB updated successfully");
|
||||
|
||||
// Renew the parent certificate list
|
||||
parent.initManagedDomainCertificateList();
|
||||
if (callback != undefined){
|
||||
callback(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function(error) {
|
||||
//$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
console.log("Failed to update EAB configuration:", error);
|
||||
parent.msgbox("Failed to update EAB configuration");
|
||||
if (callback != undefined){
|
||||
callback(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Read DNS credential from form and generate a key value structure that looks like
|
||||
// the old DNSCredential TextArea input
|
||||
|
||||
function readDnsCredentials(){
|
||||
let dnsCredentials = {};
|
||||
$(".dnsConfigField").each(function(){
|
||||
let thisKey = $(this).attr("key");
|
||||
let value = "";
|
||||
if ($(this).hasClass("checkbox")){
|
||||
//Boolean option
|
||||
let checked = $(this).find("input")[0].checked;
|
||||
dnsCredentials[thisKey] = checked;
|
||||
}else{
|
||||
//String or int options
|
||||
let value = $(this).find("input").val().trim();
|
||||
dnsCredentials[thisKey] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return dnsCredentials;
|
||||
}
|
||||
|
||||
// Update DNS values for autorenewal
|
||||
function updateCertificateDNS(callback=undefined) {
|
||||
var dns = $("#useDnsChallenge")[0].checked;
|
||||
var dnsProvider = "";
|
||||
var dnsCredentials = "";
|
||||
|
||||
if (!dns) {
|
||||
if (callback != undefined){
|
||||
callback(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Check if all fields is empty. If yes, do not update the config
|
||||
let allFieldsEmpty = true;
|
||||
$(".dnsConfigField").each(function(){
|
||||
if ($(this).find("input").val().trim() != ""){
|
||||
allFieldsEmpty = false;
|
||||
}
|
||||
});
|
||||
if (allFieldsEmpty){
|
||||
//Do not update config on server side
|
||||
if (callback != undefined){
|
||||
callback(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
dnsProvider = $("#dnsProvider").dropdown("get value");
|
||||
|
||||
//dnsCredentials = $("#dnsCredentials").val();
|
||||
dnsCredentials = readDnsCredentials();
|
||||
|
||||
if(dnsProvider == "") {
|
||||
parent.msgbox("DNS Provider cannot be empty", false, 5000);
|
||||
$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
if (callback != undefined){
|
||||
callback(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var filename = getFilename();
|
||||
if (filename == '') {
|
||||
parent.msgbox("Domain to renew cannot be empty", false, 5000);
|
||||
if (callback != undefined){
|
||||
callback(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/acme/autoRenew/setDNS",
|
||||
method: "POST",
|
||||
data: {
|
||||
filename: filename,
|
||||
dnsProvider: dnsProvider,
|
||||
dnsCredentials: JSON.stringify(dnsCredentials),
|
||||
},
|
||||
success: function(response) {
|
||||
//$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
if (response.error) {
|
||||
console.log("Error:", response.error);
|
||||
// Show error message
|
||||
parent.msgbox(response.error, false, 12000);
|
||||
if (callback != undefined){
|
||||
callback(false);
|
||||
}
|
||||
} else {
|
||||
console.log("Certificate DNS Credentials updated successfully");
|
||||
// Show success message
|
||||
parent.msgbox("Certificate DNS Credentials updated successfully");
|
||||
if (callback != undefined){
|
||||
callback(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function(error) {
|
||||
//$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
console.log("Failed to update DNS configuration:", error);
|
||||
parent.msgbox("Failed to update DNS configuration");
|
||||
if (callback != undefined){
|
||||
callback(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Obtain certificate from API
|
||||
function obtainCertificate(callback=undefined) {
|
||||
var domains = $("#domainsInput").val();
|
||||
var filename = getFilename();
|
||||
if (filename == '') {
|
||||
if (callback != undefined){
|
||||
parent.msgbox("Domain to obtain certificate cannot be empty", false)
|
||||
callback(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var email = $("#caRegisterEmail").val();
|
||||
if (email == ""){
|
||||
parent.msgbox("ACME renew email is not set", false)
|
||||
if (callback != undefined){callback(false);}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var ca = $("#ca").dropdown("get value");
|
||||
var caURL = "";
|
||||
@ -358,6 +686,8 @@
|
||||
caURL = $("#caURL").val();
|
||||
}
|
||||
|
||||
|
||||
var dns = $("#useDnsChallenge")[0].checked;
|
||||
var skipTLSValue = $("#skipTLSCheckbox")[0].checked;
|
||||
|
||||
$.ajax({
|
||||
@ -370,6 +700,7 @@
|
||||
ca: ca,
|
||||
caURL: caURL,
|
||||
skipTLS: skipTLSValue,
|
||||
dns: dns,
|
||||
},
|
||||
success: function(response) {
|
||||
$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
@ -377,6 +708,7 @@
|
||||
console.log("Error:", response.error);
|
||||
// Show error message
|
||||
parent.msgbox(response.error, false, 12000);
|
||||
if (callback != undefined){callback(false);}
|
||||
} else {
|
||||
console.log("Certificate renewed successfully");
|
||||
// Show success message
|
||||
@ -384,11 +716,14 @@
|
||||
|
||||
// Renew the parent certificate list
|
||||
parent.initManagedDomainCertificateList();
|
||||
|
||||
if (callback != undefined){callback(true);}
|
||||
}
|
||||
},
|
||||
error: function(error) {
|
||||
$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
console.log("Failed to renewed certificate:", error);
|
||||
if (callback != undefined){callback(false);}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -402,6 +737,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDnsChallenge(){
|
||||
if ( $("#useDnsChallenge")[0].checked){
|
||||
$(".dnsChallengeOnly").show();
|
||||
}else{
|
||||
$(".dnsChallengeOnly").hide();
|
||||
}
|
||||
}
|
||||
|
||||
//Grab the longest common suffix of all domains
|
||||
//not that smart technically
|
||||
function autoDetectMatchingRules(){
|
||||
@ -513,7 +856,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
//Clear up the input field when page load
|
||||
//Load the json map and create the dropdown for DNS provider names
|
||||
let dnsProviderNameMap = {};
|
||||
function initDNSProviderList(){
|
||||
$.get("dnsnames.json", function(namemap){
|
||||
dnsProviderNameMap = namemap;
|
||||
//Load a list of supported DNS provider from backend
|
||||
$("#dnsProviderList").html("");
|
||||
$.get("/api/acme/dns/providers", function(providerList){
|
||||
providerList.sort();
|
||||
providerList.forEach(providerid => {
|
||||
let providerName = providerid;
|
||||
if (dnsProviderNameMap[providerid] != undefined){
|
||||
providerName = dnsProviderNameMap[providerid];
|
||||
}
|
||||
$("#dnsProviderList").append(`<div class="item" data-value="${providerid}">${providerName}</div>`);
|
||||
});
|
||||
$("#dnsProvider").dropdown();
|
||||
setTimeout(function(){
|
||||
//The dropdown is large, it takes some time to load
|
||||
$("#dnsProvider").dropdown("set selected", "cloudflare");
|
||||
}, 300)
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
initDNSProviderList();
|
||||
|
||||
|
||||
//Clear up the input field when page load
|
||||
$("#filenameInput").val("");
|
||||
</script>
|
||||
</body>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user