12 Commits
74 changed files with 1817 additions and 596 deletions
+2
View File
@@ -21,6 +21,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: create dist directory
run: mkdir frontend/dist && touch frontend/dist/revsuit
- name: Run golangci-lint
uses: golangci/[email protected]
with:
+27 -47
View File
@@ -4,60 +4,40 @@ on:
name: Build
jobs:
release-linux:
name: release linux
name: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/setup-go@v2
with:
go-version: '^1.16.0'
- name: compile
run: |
GOARCH="amd64" go build -v -ldflags="-s -w -extldflags=-static" -tags netgo -trimpath -o "bin/revsuit_linux_amd64" ./cmd/revsuit/revsuit.go
# GOARCH="386" go build -v -ldflags="-s -w -extldflags=-static" -tags netgo -trimpath -o "bin/revsuit_linux_i386" ./cmd/revsuit/revsuit.go
# GOARCH="arm" go build -v -ldflags="-s -w -extldflags=-static" -tags netgo -trimpath -o "bin/revsuit_linux_arm" ./cmd/revsuit/revsuit.go
# GOARCH="arm64" go build -v -ldflags="-s -w -extldflags=-static" -tags netgo -trimpath -o "bin/revsuit_linux_arm64" ./cmd/revsuit/revsuit.go
- name: Upload the artifacts
uses: li4n0/upload-release-action@v2
- name: install nodejs
uses: actions/setup-node@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: bin/*
tag: ${{ github.ref }}
overwrite: true
file_glob: true
release-darwin-amd64:
name: release darwin/amd64
runs-on: macos-latest
steps:
- uses: actions/checkout@master
- uses: actions/setup-go@v2
with:
go-version: '^1.16.0'
- name: compile
run: |
GOARCH="amd64" go build -v -ldflags="-s -w" -trimpath -o "bin/revsuit_darwin_amd64" ./cmd/revsuit/revsuit.go
# GOARCH="arm64" go build -v -ldflags="-s -w" -trimpath -o "bin/revsuit_darwin_arm64" ./cmd/revsuit/revsuit.go
- name: Upload the artifacts
uses: li4n0/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: bin/*
tag: ${{ github.ref }}
overwrite: true
file_glob: true
release-windows:
name: release windows
runs-on: windows-latest
steps:
- uses: actions/checkout@master
- uses: actions/setup-go@v2
with:
go-version: '^1.16.0'
- name: compile
run: |
env GOARCH=amd64 go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_windows_amd64.exe" ./cmd/revsuit/revsuit.go
# env GOARCH=386 go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_windows_i386.exe" ./cmd/revsuit/revsuit.go
# env GOARCH=arm go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_windows_arm.exe" ./cmd/revsuit/revsuit.go
node-version: '12'
- name: install yarn
run: npm install -g yarn
- name: build frontend
run: cd frontend && yarn install && yarn build
- name: get the version
id: get_version
run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3)
- name: compile linux amd64
run: GOARCH="amd64" go build -ldflags="-s -w -X 'github.com/li4n0/revsuit/pkg/server.VERSION=${{ steps.get_version.outputs.VERSION }}'" -tags netgo -trimpath -o "bin/revsuit_linux_amd64" ./cmd/revsuit/revsuit.go
- name: compile linux arm64
run: GOARCH="arm64" go build -ldflags="-s -w -X 'github.com/li4n0/revsuit/pkg/server.VERSION=${{ steps.get_version.outputs.VERSION }}'" -tags netgo -trimpath -o "bin/revsuit_linux_arm64" ./cmd/revsuit/revsuit.go
- name: compile darwin amd64
run: GOOS="darwin" GOARCH="amd64" go build -ldflags="-s -w -X 'github.com/li4n0/revsuit/pkg/server.VERSION=${{ steps.get_version.outputs.VERSION }}'" -trimpath -o "bin/revsuit_darwin_amd64" ./cmd/revsuit/revsuit.go
- name: compile darwin arm64
run: GOOS="darwin" GOARCH="arm64" go build -ldflags="-s -w -X 'github.com/li4n0/revsuit/pkg/server.VERSION=${{ steps.get_version.outputs.VERSION }}'" -trimpath -o "bin/revsuit_darwin_arm64" ./cmd/revsuit/revsuit.go
- name: compile windows amd64
run: GOOS="windows" GOARCH="amd64" go build -ldflags="-s -w -X 'github.com/li4n0/revsuit/pkg/server.VERSION=${{ steps.get_version.outputs.VERSION }}' " -trimpath -o "bin/revsuit_windows_amd64.exe" ./cmd/revsuit/revsuit.go
- name: Upload the artifacts
uses: li4n0/upload-release-action@v2
with:
+5 -2
View File
@@ -11,7 +11,7 @@ GeoLite2-City.mmdb
config.yaml*
# binary
revsuit
/revsuit
# Editor directories and files
.idea
@@ -23,6 +23,9 @@ revsuit
*.sw?
## node
package.json
/package.json
package-lock.json
node_modules/
## frontend dist
dist/
+15
View File
@@ -2,6 +2,21 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [0.5.0](https://github.com/Li4n0/revsuit/compare/v0.4.0...v0.5.0) (2022-01-16)
### Features
* **build:** replace sqlite driver with which does not depend cgo ([#52](https://github.com/Li4n0/revsuit/issues/52)) ([82a2023](https://github.com/Li4n0/revsuit/commit/82a20236e1b1a092277b645554a38513e3bb4087))
* **dns:** support use multiple root domains ([#51](https://github.com/Li4n0/revsuit/issues/51)) ([913e0f7](https://github.com/Li4n0/revsuit/commit/913e0f78db380cb1beeb88b57dc30f47e7c26c1c))
* **ldap:** add ldap protocol support ([#50](https://github.com/Li4n0/revsuit/issues/50)) ([fc505b0](https://github.com/Li4n0/revsuit/commit/fc505b0733a8375d29b910aa897c6e0b642ab34b))
* support auto check upgrade ([#53](https://github.com/Li4n0/revsuit/issues/53)) ([f6afe0f](https://github.com/Li4n0/revsuit/commit/f6afe0fa12188f17b74548129035f7b89a149660))
### Bug Fixes
* **settings:** fix the bug of update rmi & mysql addr settings not working ([#49](https://github.com/Li4n0/revsuit/issues/49)) ([06ae3d0](https://github.com/Li4n0/revsuit/commit/06ae3d0555eff3bf2d6a53b2c405d1dd50beea87))
## [0.4.0](https://github.com/Li4n0/revsuit/compare/v0.3.0...v0.4.0) (2021-12-26)
+6 -2
View File
@@ -2,10 +2,10 @@
RevSuit will generate a profile template the first time it is run, and you can edit its contents to implement some customization requirements.
```yaml
version: 1.3
version: 1.5
addr: :10000 # Address of the HTTP service will listen
token: # Authentication Token, both the admin page and the client need to be authenticated by this Token
domain: # The domain name used by the platform
domains: [] # The domain names used by the platform
external_ip: # The external IP of the platform, you need to make sure that the target you want to test can access the platform through this IP
admin_path_prefix: "/revsuit" # The http path prefix for the admin page, the page will be located at: /admin_path_prefix/admin
database: revsuit.db # Database connection information, support using Sqlite3, MySQL, Postgres
@@ -13,6 +13,7 @@ database: revsuit.db # Database connection information, support
# database: "postgres://host=127.0.0.1 user=root password=password dbname=revsuit port=5432 sslmode=disable TimeZone=Asia/Shanghai"
log_level: info # Output log levels, divided into: debug, info, warning, error, fatal
check_upgrade: true # Whether to automatically check for updates
ip_location_database: # IP database related configuration
database: "qqwry" # qqwry or geoip.
@@ -26,6 +27,9 @@ dns: # DNS receive connection related configura
rmi: # RMI receive connection related configuration
enable: true
addr: :1099 # Address of the RMI service will listen
ldap:
enable: true
addr: :1389 # Address of the Ldap service will listen
mysql: # MySQL receive connection related configuration
enable: true
addr: :3306 # Address of the MySQL service will listen
+7 -3
View File
@@ -1,10 +1,10 @@
## 配置文件说明
```yaml
version: 1.3
version: 1.5
addr: :10000 # HTTP 服务监听的地址
token: # 鉴权Token,管理页面和客户端都需要通过该 Token 进行鉴权
domain: # 反连平台绑定的域名
domains: [] # 反连平台绑定的域名
external_ip: # 反连平台的外部IP,需要确保你想测试的目标能通过该 IP 访问到平台
admin_path_prefix: "/revsuit" # 管理页面的 http path 前缀,管理页面将位于:/admin_path_prefix/admin
database: revsuit.db # 数据库连接信息 支持Sqlite3、MySQL、Postgres
@@ -12,7 +12,8 @@ database: revsuit.db # 数据库连接信息 支持Sqlite3、My
# database: "postgres://host=127.0.0.1 user=root password=password dbname=revsuit port=5432 sslmode=disable TimeZone=Asia/Shanghai"
log_level: info # 输出日志的级别,分为:debug、info、warning、error、fatal
check_upgrade: false # 是否自动检查更新
ip_location_database: # IP 数据库相关配置
database: "qqwry" # qqwry 或者 geoip.
geo_license_key: "" # 如果你使用 GeoIP 则该字段为必填
@@ -25,6 +26,9 @@ dns: # DNS 反连相关配置
rmi: # RMI 反连相关配置
enable: true
addr: :1099 # RMI 服务监听的地址
ldap:
enable: true
addr: :1389 # Ldap 服务监听的地址
mysql: # MySQL 反连相关配置
enable: true
addr: :3306 # MySQL 服务监听的地址
+10 -4
View File
@@ -5,7 +5,7 @@ English [简体中文](./README.zh-CN.md)
## Overview
RevSuit is a flexible and powerful reverse connection platform designed for receiving connection from target host in
penetration. It currently supports HTTP, DNS, RMI, MySQL and FTP protocols.
penetration. It currently supports HTTP, DNS, RMI, LDAP, MySQL and FTP protocols.
Flexible:
@@ -26,11 +26,16 @@ Powerful:
### Installation and Configuration
Download the latest release firstly. For the time being, only the binary for amd64 architecture is available for
download, if you need to use it in other architectures, you can compile it yourself.
Download the latest release directly or build by following steps:
```bash
git clone https://github.com/Li4n0/revsuit.git
cd revsuit/frontend && yarn install && yarn build
cd ../ && go build ./cmd/revsuit/revsuit.go
```
RevSuit will generate default configuration file on first run. Modify the configuration file according to your needs,
then re-run. A detailed description of the configuration file can be found at: [Configuration Notes](./CONFIG.md)
then re-run. A detailed description of the configuration file can be found at: [Configuration Notes](./CONFIG.md)
### Run
@@ -241,6 +246,7 @@ This project draws on the code of the following outstanding projects:
* [https://github.com/rmb122/rogue_mysql_server](https://github.com/rmb122/rogue_mysql_server)
* [https://github.com/256dpi/newdns](https://github.com/256dpi/newdns)
* [https://github.com/EmYiQing/JNDIScan](https://github.com/EmYiQing/JNDIScan)
### Specifically
+9 -2
View File
@@ -4,7 +4,7 @@
## 简介
RevSuit 是一款灵活并且强大的反连平台。目前支持 HTTP、DNS、RMI、MySQL 和 FTP 协议。
RevSuit 是一款灵活并且强大的反连平台。目前支持 HTTP、DNS、RMI、LDAP、MySQL 和 FTP 协议。
灵活:
@@ -23,7 +23,13 @@ RevSuit 是一款灵活并且强大的反连平台。目前支持 HTTP、DNS、R
### 安装及配置
在 Releases 里下载最新版本的 RevSuit。暂时只提供 amd64 架构下的二进制文件下载,如需要在其它架构下使用,可以自行编译
在 Releases 里下载最新版本的 RevSuit,或者通过以下命令自行编译
```bash
git clone https://github.com/Li4n0/revsuit.git
cd revsuit/frontend && yarn install && yarn build
cd ../ && go build ./cmd/revsuit/revsuit.go
```
RevSuit首次运行将会在当前目录生成配置文件,您可以根据需要自定义其中的某些内容。配置文件的详细说明可见:[配置说明](./CONFIG.zh-CN.md)
@@ -211,6 +217,7 @@ payload,最后被扫描器发出的请求是:`['https://www.testvuln.com?url
* [https://github.com/rmb122/rogue_mysql_server](https://github.com/rmb122/rogue_mysql_server)
* [https://github.com/256dpi/newdns](https://github.com/256dpi/newdns)
* [https://github.com/EmYiQing/JNDIScan](https://github.com/EmYiQing/JNDIScan)
### 特别感谢
-1
View File
@@ -1 +0,0 @@
.custom-icons-list[data-v-05a908c2] .anticon{margin-right:6px}body[data-v-3b44ba10],html[data-v-3b44ba10]{height:100%;margin:0}.fade-transform-enter-active[data-v-3b44ba10],.fade-transform-leave-active[data-v-3b44ba10]{transition:all .3s;opacity:0}.fade-transform-enter[data-v-3b44ba10],.fade-transform-leave[data-v-3b44ba10]{opacity:0}.fade-transform-enter-to[data-v-3b44ba10]{opacity:0}.ant-menu-item>span>a[data-v-3b44ba10]{color:hsla(0,0%,100%,.65)}.ant-menu-item-selected>span>a[data-v-3b44ba10]{color:#fff}#app[data-v-3b44ba10]{min-height:100%}#app .trigger[data-v-3b44ba10]{font-size:18px;line-height:64px;padding:0 24px;cursor:pointer;transition:color .3s}#app .trigger[data-v-3b44ba10]:hover{color:#1890ff}#app .logo[data-v-3b44ba10]{height:32px;background:#0a1d2d;margin:16px;text-align:center;font-size:1.2rem;color:#fff;padding-bottom:5px;border-bottom:2px solid #b6befa}.copyright[data-v-3b44ba10]{color:#888;text-align:right;margin-right:1rem}
-1
View File
@@ -1 +0,0 @@
#add-rule[data-v-50ca3b7c]{margin-bottom:10px}
-1
View File
@@ -1 +0,0 @@
#add-rule[data-v-4fed8448]{margin-bottom:10px}
-1
View File
@@ -1 +0,0 @@
.gray-table-row{background-color:#f5f5f5}
-1
View File
@@ -1 +0,0 @@
.gray-table-row{background-color:#f5f5f5}
-1
View File
@@ -1 +0,0 @@
#add-rule[data-v-3819ffac]{margin-bottom:10px}
-1
View File
@@ -1 +0,0 @@
.gray-table-row{background-color:#f5f5f5}
-1
View File
@@ -1 +0,0 @@
.gray-table-row{background-color:#f5f5f5}
-1
View File
@@ -1 +0,0 @@
.gray-table-row{background-color:#f5f5f5}
-1
View File
@@ -1 +0,0 @@
#add-rule[data-v-3db1b286]{margin-bottom:10px}
-1
View File
@@ -1 +0,0 @@
#add-rule[data-v-b1cc51ba]{margin-bottom:10px}.full-screen-icon[data-v-b1cc51ba]{position:absolute;z-index:5;color:#9e9e9e;right:1px;font-size:1rem;cursor:pointer;transition:font-size .1s}.full-screen-icon[data-v-b1cc51ba]:hover{font-size:1.1rem;transition:font-size .1s}
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>RevSuit Control Panel</title><link href="/revsuit/admin/css/chunk-016c45cf.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-0cb5e9e6.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-193ec343.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-46a53d26.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-4ffb84ba.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-5fb70d83.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-72839f90.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-740736c7.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-ae691e78.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-f1802c3c.css" rel="prefetch"><link href="/revsuit/admin/js/chunk-016c45cf.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-0cb5e9e6.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-193ec343.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-27186dd0.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-46a53d26.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-4ffb84ba.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-5fb70d83.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-72839f90.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-740736c7.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-ae691e78.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-b241c712.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-f1802c3c.js" rel="prefetch"><link href="/revsuit/admin/css/app.css" rel="preload" as="style"><link href="/revsuit/admin/css/chunk-vendors.css" rel="preload" as="style"><link href="/revsuit/admin/js/app.js" rel="preload" as="script"><link href="/revsuit/admin/js/chunk-vendors.js" rel="preload" as="script"><link href="/revsuit/admin/css/chunk-vendors.css" rel="stylesheet"><link href="/revsuit/admin/css/app.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but revsuit-frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/revsuit/admin/js/chunk-vendors.js"></script><script src="/revsuit/admin/js/app.js"></script></body></html>
-1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+14 -15
View File
@@ -19,7 +19,7 @@
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/cli-service": "^4.5.11",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
@@ -4810,8 +4810,7 @@
"node_modules/de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npm.taobao.org/de-indent/download/de-indent-1.0.2.tgz",
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
"dev": true
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0="
},
"node_modules/debug": {
"version": "4.3.1",
@@ -6899,7 +6898,6 @@
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/he/download/he-1.2.0.tgz",
"integrity": "sha1-hK5l+n6vsWX922FWauFLrwVmTw8=",
"dev": true,
"bin": {
"he": "bin/he"
}
@@ -13521,7 +13519,6 @@
"version": "2.6.12",
"resolved": "https://registry.npm.taobao.org/vue-template-compiler/download/vue-template-compiler-2.6.12.tgz",
"integrity": "sha1-lH7XGWdEyKUoXr4SM/6WBDf8xX4=",
"dev": true,
"dependencies": {
"de-indent": "^1.0.2",
"he": "^1.1.0"
@@ -16436,7 +16433,8 @@
"version": "4.5.11",
"resolved": "https://registry.npm.taobao.org/@vue/cli-plugin-vuex/download/@vue/cli-plugin-vuex-4.5.11.tgz?cache=0&sync_timestamp=1615453605559&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fcli-plugin-vuex%2Fdownload%2F%40vue%2Fcli-plugin-vuex-4.5.11.tgz",
"integrity": "sha1-9vYZvPtmyGzEU0DXMVKERjXlSL0=",
"dev": true
"dev": true,
"requires": {}
},
"@vue/cli-service": {
"version": "4.5.11",
@@ -16590,7 +16588,8 @@
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/@vue/preload-webpack-plugin/download/@vue/preload-webpack-plugin-1.1.2.tgz",
"integrity": "sha1-zrkktOyzucQ4ccekKaAvhCPmIas=",
"dev": true
"dev": true,
"requires": {}
},
"@vue/web-component-wrapper": {
"version": "1.3.0",
@@ -16805,7 +16804,8 @@
"version": "5.3.1",
"resolved": "https://registry.npm.taobao.org/acorn-jsx/download/acorn-jsx-5.3.1.tgz",
"integrity": "sha1-/IZh4Rt6wVOcR9v+oucrOvNNJns=",
"dev": true
"dev": true,
"requires": {}
},
"acorn-walk": {
"version": "7.2.0",
@@ -16853,13 +16853,15 @@
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/ajv-errors/download/ajv-errors-1.0.1.tgz?cache=0&sync_timestamp=1615411237786&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv-errors%2Fdownload%2Fajv-errors-1.0.1.tgz",
"integrity": "sha1-81mGrOuRr63sQQL72FAUlQzvpk0=",
"dev": true
"dev": true,
"requires": {}
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-3.5.2.tgz?cache=0&sync_timestamp=1615647295573&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv-keywords%2Fdownload%2Fajv-keywords-3.5.2.tgz",
"integrity": "sha1-MfKdpatuANHC0yms97WSlhTVAU0=",
"dev": true
"dev": true,
"requires": {}
},
"alphanum-sort": {
"version": "1.0.2",
@@ -18899,8 +18901,7 @@
"de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npm.taobao.org/de-indent/download/de-indent-1.0.2.tgz",
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
"dev": true
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0="
},
"debug": {
"version": "4.3.1",
@@ -20633,8 +20634,7 @@
"he": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/he/download/he-1.2.0.tgz",
"integrity": "sha1-hK5l+n6vsWX922FWauFLrwVmTw8=",
"dev": true
"integrity": "sha1-hK5l+n6vsWX922FWauFLrwVmTw8="
},
"hex-color-regex": {
"version": "1.1.0",
@@ -26188,7 +26188,6 @@
"version": "2.6.12",
"resolved": "https://registry.npm.taobao.org/vue-template-compiler/download/vue-template-compiler-2.6.12.tgz",
"integrity": "sha1-lH7XGWdEyKUoXr4SM/6WBDf8xX4=",
"dev": true,
"requires": {
"de-indent": "^1.0.2",
"he": "^1.1.0"
+1 -1
View File
@@ -19,7 +19,7 @@
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/cli-service": "^4.5.11",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
+27 -6
View File
@@ -21,6 +21,9 @@
<a-menu-item key="/logs/rmi">
<router-link to="/logs/rmi">RMI Logs</router-link>
</a-menu-item>
<a-menu-item key="/logs/ldap">
<router-link to="/logs/ldap">LDAP Logs</router-link>
</a-menu-item>
<a-menu-item key="/logs/mysql">
<router-link to="/logs/mysql">MySQL Logs</router-link>
</a-menu-item>
@@ -39,6 +42,9 @@
<a-menu-item key="/rules/rmi">
<router-link to="/rules/rmi">RMI Rules</router-link>
</a-menu-item>
<a-menu-item key="/rules/ldap">
<router-link to="/rules/ldap">LDAP Rules</router-link>
</a-menu-item>
<a-menu-item key="/rules/mysql">
<router-link to="/rules/mysql">MySQL Rules</router-link>
</a-menu-item>
@@ -112,10 +118,17 @@
</transition>
</a-layout-content>
<div class="copyright">
<p class="">
RevSuit Current Version: {{ this.version }} &copy;2021 <a href="https://github.com/Li4n0">Li4n0</a>. <a
<p v-if="!this.upgrade.upgradeable">
RevSuit Current Version: {{ this.version }}&copy; 2021 <a href="https://github.com/Li4n0">Li4n0</a><a
href="https://github.com/Li4n0/revsuit">GitHub</a>
</p>
<p v-else>
RevSuit {{ this.version }}(Upgrade Available: <a
target="_blank"
:href="this.upgrade.release">v{{ this.upgrade.version }}</a>)&copy; 2021 <a
href="https://github.com/Li4n0">Li4n0</a><a href="https://github.com/Li4n0/revsuit">GitHub</a>
</p>
</div>
</a-layout>
<Auth></Auth>
@@ -124,7 +137,7 @@
<script>
import Auth from '@/components/Auth'
import RuleIcon from "@/components/Icon";
import {getVersion} from "@/api/auth";
import {getVersion, getUpgrade} from "@/api/version";
import {store} from "@/main";
@@ -140,6 +153,7 @@ export default {
showLogNum: false,
openKeys: [],
version: "",
upgrade: {},
};
},
computed: {
@@ -156,11 +170,16 @@ export default {
this.$refs.content.fetch()
}, this.refreshInterval * 1000)
},
GetVersion() {
_getVersion() {
getVersion().then(res => {
this.version = res.data.result
})
},
_getUpgrade() {
getUpgrade().then(res => {
this.upgrade = res.data.result
})
},
deleteLogs() {
this.$refs.content.delete()
},
@@ -174,7 +193,8 @@ export default {
if (localStorage.getItem("autoRefresh") === null) {
this.autoRefresh = true;
}
this.GetVersion()
this._getVersion()
this._getUpgrade()
},
created() {
let unwatch = this.$watch('$route', function (to, from) {
@@ -218,7 +238,8 @@ export default {
'store.authed'(val) {
if (val) {
this.$refs.content.fetch()
this.GetVersion()
this._getVersion()
this._getUpgrade()
}
},
'store.pageSize'() {
-7
View File
@@ -6,11 +6,4 @@ export function auth(token) {
method: 'get',
headers: {"Token": token},
})
}
export function getVersion() {
return request({
url: '/version',
method: 'get',
})
}
+16
View File
@@ -66,6 +66,22 @@ export function deleteRmiRecord(params) {
})
}
export function getLdapRecord(params) {
return request({
url: '/record/ldap',
params: params,
method: 'get'
})
}
export function deleteLdapRecord(params) {
return request({
url: '/record/ldap',
params: params,
method: 'delete'
})
}
export function getFtpRecord(params) {
return request({
url: '/record/ftp',
+24
View File
@@ -97,6 +97,30 @@ export function deleteRmiRule(data) {
})
}
export function getLdapRule(params) {
return request({
url: '/rule/ldap',
params: params,
method: 'get'
})
}
export function upsertLdapRule(data) {
return request({
url: '/rule/ldap',
data: data,
method: 'post'
})
}
export function deleteLdapRule(data) {
return request({
url: '/rule/ldap',
data: data,
method: 'delete'
})
}
export function getFtpRule(params) {
return request({
url: '/rule/ftp',
+14
View File
@@ -42,6 +42,20 @@ export function updateRmiConfig(data) {
})
}
export function getLdapConfig() {
return request({
url: '/setting/getLdapConfig',
})
}
export function updateLdapConfig(data) {
return request({
url: '/setting/updateLdapConfig',
method: "post",
data: data,
})
}
export function getMySQLConfig() {
return request({
url: '/setting/getMySQLConfig',
+15
View File
@@ -0,0 +1,15 @@
import request from "@/api/index";
export function getVersion() {
return request({
url: '/version',
method: 'get',
})
}
export function getUpgrade() {
return request({
url: '/getUpgrade',
method: 'get',
})
}
+10
View File
@@ -30,6 +30,11 @@ const routes = [
name: 'RmiLogs',
component: () => import( '../views/logs/Rmi')
},
{
path: '/logs/ldap',
name: 'LdapLogs',
component: () => import( '../views/logs/Ldap')
},
{
path: '/logs/ftp',
name: 'FtpLogs',
@@ -56,6 +61,11 @@ const routes = [
name: 'RmiRules',
component: () => import( '../views/rules/Rmi')
},
{
path: '/rules/ldap',
name: 'LdapRules',
component: () => import( '../views/rules/Ldap')
},
{
path: '/rules/ftp',
name: 'FtpRules',
+173
View File
@@ -0,0 +1,173 @@
<template>
<a-table
style="overflow-x: auto;"
:columns="columns"
:data-source="data"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
:rowClassName="(record, index) => index % 2 === 0 ? '' : 'gray-table-row'"
>
<filter-dropdown
slot="filterDropdown"
slot-scope="{ setSelectedKeys, selectedKeys, clearFilters, column }"
:set-selected-keys="setSelectedKeys"
:selected-keys="selectedKeys"
:clear-filters="clearFilters"
:column="column"
:filters="filters"
:fetch="fetch"
/>
<a-icon
slot="filterIcon"
slot-scope="filtered"
type="search"
:style="{ color: filtered ? '#108ee9' : undefined }"
/>
<span slot="time" slot-scope="time">
{{ new Date(time).format("yyyy-MM-dd hh:mm:ss") }}
</span>
</a-table>
</template>
<style>
.gray-table-row {
background-color: #f5f5f5;
}
</style>
<script>
import {deleteLdapRecord, getLdapRecord} from '@/api/record'
import {store} from '@/main'
import FilterDropdown from '@/components/FilterDropdown'
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
sorter: true,
sortDirections: ['descend', 'ascend'],
},
{
title: 'REQUEST TIME',
dataIndex: 'request_time',
key: 'request_time',
scopedSlots: {customRender: 'time'},
},
{
title: 'RULE',
dataIndex: 'rule_name',
key: 'rule_name',
scopedSlots: {
filterDropdown: 'filterDropdown',
filterIcon: 'filterIcon',
},
},
{
title: 'FLAG',
dataIndex: 'flag',
key: 'flag',
scopedSlots: {
filterDropdown: 'filterDropdown',
filterIcon: 'filterIcon',
},
},
{
title: 'PATH',
dataIndex: 'path',
key: 'path',
ellipsis: true,
scopedSlots: {
filterDropdown: 'filterDropdown',
filterIcon: 'filterIcon',
},
},
{
title: 'REMOTE IP',
key: 'remote_ip',
dataIndex: 'remote_ip',
scopedSlots: {
filterDropdown: 'filterDropdown',
filterIcon: 'filterIcon',
},
},
{
title: 'IP AREA',
key: 'ip_area',
dataIndex: 'ip_area'
}
];
export default {
name: 'LdapLogs',
data() {
return {
store,
data: [],
pagination: {
current: 1, showSizeChanger: true, pageSize: store.pageSize,
onShowSizeChange: (current, size) => {
store.pageSize = size
}
},
filters: {},
order: "desc",
loading: false,
columns,
};
},
methods: {
handleTableChange(pagination, filters, sorter) {
const pager = {...this.pagination};
pager.current = pagination.current;
this.pagination = pager;
this.order = sorter.order === "ascend" ? "asc" : "desc"
this.fetch();
},
fetch: function () {
let params = {
...this.filters,
page: this.pagination.current,
pageSize: this.pagination.pageSize,
order: this.order
}
this.loading = true;
getLdapRecord(params).then(res => {
let result = res.data.result
this.data = result.data
const pagination = {...this.pagination};
pagination.total = result.count;
this.pagination = pagination;
this.loading = false
}).catch(e => {
if (e.response.status !== 403) {
this.$message.error('Unknown error with status code: ' + e.response.status)
}
})
},
delete: function () {
let params = {
...this.filters,
}
this.loading = true;
deleteLdapRecord(params).then(() => {
this.$message.success('Deleted successfully')
this.filters = {}
this.fetch()
}).catch(e => {
if (e.response.status !== 403) {
this.$message.error('Failed to delete: ' + e.response.data.error)
}
})
},
},
mounted() {
this.fetch();
},
components: {
FilterDropdown
}
}
</script>
+327
View File
@@ -0,0 +1,327 @@
<template xmlns:a-col="http://www.w3.org/1999/html">
<div>
<a-button id="add-rule" type="primary" @click="addRule">
<a-icon type="plus"/>
New Rule
</a-button>
<!-- rule form-->
<a-drawer
:title="formAction+ ' RMI rule'"
:width="490"
:visible="formVisible"
:body-style="{ paddingBottom: '80px' }"
@close="closeDrawer"
>
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
<BasicRule :form="form" :readOnly="formReadOnly" flagFormat="path"/>
</a-form-model>
<div
:style="{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '10px 16px',
background: '#fff',
textAlign: 'right',
zIndex: 1,
}"
>
<a-button :style="{ marginRight: '8px' }" @click="handleCancel">
Cancel
</a-button>
<a-button type="primary" :disabled="formReadOnly" @click="handleSubmit">
Submit
</a-button>
</div>
</a-drawer>
<!-- rule table -->
<a-table
style="overflow-x: auto;"
:columns="columns"
:data-source="data"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
>
<div
slot="filterDropdown"
slot-scope="{ setSelectedKeys, selectedKeys, clearFilters, column }"
style="padding: 8px"
>
<a-input
:placeholder="`Search ${column.dataIndex}`"
:value="selectedKeys[0]"
style="width: 188px; margin-bottom: 8px; display: block;"
@change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
@pressEnter="() => {filters[column.dataIndex] = selectedKeys[0];fetch()}"
/>
<a-button
type="primary"
icon="search"
size="small"
style="width: 90px; margin-right: 8px"
@click="() => {filters[column.dataIndex] = selectedKeys[0];fetch()}"
>
Search
</a-button>
</div>
<a-icon
slot="filterIcon"
slot-scope="filtered"
type="search"
:style="{ color: filtered ? '#108ee9' : undefined }"
/>
<span slot="rank" slot-scope="rank">
<a-tag
:color="'#'+(0x2db7f5+rank*80).toString(16)"
>
{{ rank }}
</a-tag>
</span>
<span slot="switchRender" slot-scope="checked,record,index,dataIndex">
<a-switch :checked="checked" @click="clickSwitch(record,dataIndex.dataIndex)"></a-switch>
</span>
<span slot="valueRender" slot-scope="values">
<span v-for="value in values.split(',')" :key="value">{{ value }}<br/></span>
</span>
<span slot="action" slot-scope="text,record,index">
<a-button @click="viewRule(record)" style="
color: #67C23A;
background-color: transparent;
border-color: #67C23A;
text-shadow: none;
margin:0 10px 3px 0;
" size="small" ghost>View</a-button>
<a-button @click="editRule(record,index)" style="
color: #909399;
background-color: transparent;
border-color: #909399;
text-shadow: none;
margin:0 10px 3px 0;
" size="small" ghost>Edit</a-button>
<a-popconfirm
title="Are you sure delete this task?"
ok-text="Yes"
cancel-text="No"
@confirm="deleteRule(record,index)"
>
<a-button type="danger" size="small" ghost>Delete</a-button>
</a-popconfirm>
</span>
</a-table>
</div>
</template>
<style scoped>
#add-rule {
margin-bottom: 10px;
}
</style>
<script>
import {getLdapRule, upsertLdapRule, deleteLdapRule} from '@/api/rule'
import {store} from '@/main'
import BasicRule from "@/components/BasicRule";
const VIEW = "View"
const EDIT = "Edit"
const CREATE = "Create"
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
sorter: true,
sortDirections: ['descend', 'ascend'],
},
{
title: 'NAME',
dataIndex: 'name',
key: 'name',
scopedSlots: {
filterDropdown: 'filterDropdown',
filterIcon: 'filterIcon',
},
},
{
title: 'FLAG FORMAT',
dataIndex: 'flag_format',
key: 'flag_format',
ellipsis: true,
},
{
title: 'RANK',
dataIndex: 'rank',
key: 'rank',
scopedSlots: {
customRender: 'rank',
},
},
{
title: 'PUSH TO CLIENT',
dataIndex: 'push_to_client',
key: 'push_to_client',
scopedSlots: {
customRender: 'switchRender',
}
},
{
title: 'NOTICE',
dataIndex: 'notice',
key: 'notice',
scopedSlots: {
customRender: 'switchRender',
}
},
{
title: 'Action',
key: 'action',
scopedSlots: {customRender: 'action'},
},
];
export default {
name: 'LdapRules',
data() {
return {
store,
data: [],
formVisible: false,
pagination: {
current: 1, showSizeChanger: true, pageSize: store.pageSize,
onShowSizeChange: (current, size) => {
store.pageSize = size
}
},
filters: {},
loading: false,
columns,
form: {},
formReadOnly: false,
formAction: "", // View ,Create or Edit
}
},
methods: {
handleTableChange(pagination, filters, sorter) {
const pager = {...this.pagination};
pager.current = pagination.current;
this.pagination = pager;
this.order = sorter.order === "ascend" ? "asc" : "desc"
this.fetch();
},
fetch: function () {
let params = {
...this.filters,
page: this.pagination.current,
pageSize: this.pagination.pageSize,
order: this.order
}
this.loading = true;
getLdapRule(params).then(res => {
let result = res.data.result
this.data = result.data
const pagination = {...this.pagination};
pagination.total = result.count;
this.pagination = pagination;
this.loading = false
}).catch(e => {
if (e.response.status !== 403) {
this.$message.error('Unknown error with status code: ' + e.response.status)
}
})
},
clickSwitch(record, prop) {
record[prop] = !record[prop]
upsertLdapRule(record).then().catch(e => {
this.$notification.error({
message: 'Edit failed',
description:
e.response.data.error,
style: {
width: '600px',
marginLeft: `${335 - 600}px`,
},
duration: 4
});
})
},
addRule() {
this.form = {}
this.showForm(CREATE)
},
viewRule(record) {
this.form = record
this.showForm(VIEW)
},
editRule(record) {
this.form = JSON.parse(JSON.stringify(record))
this.showForm(EDIT)
},
deleteRule(record, index) {
deleteLdapRule(record).then(() => {
this.data.splice(index, 1)
}).catch(e => {
this.$notification.error({
message: 'Error',
description:
e.response.data.error,
style: {
width: '600px',
marginLeft: `${335 - 600}px`,
},
duration: 4
});
})
},
showForm(action) {
this.formAction = action
this.formReadOnly = action === VIEW;
this.formVisible = true;
},
closeDrawer() {
this.formVisible = false;
},
handleSubmit() {
this.$refs.form.validate(valid => {
if (valid) {
upsertLdapRule(this.form).then(() => {
this.closeDrawer()
this.fetch({page: this.pagination.current});
this.$notification.info({
message: 'Success',
style: {
width: '600px',
marginLeft: `${335 - 600}px`,
},
duration: 2.5
});
}).catch(e => {
this.$notification.error({
message: this.formAction + ' failed',
description:
e.response.data.error,
style: {
width: '600px',
marginLeft: `${335 - 600}px`,
},
duration: 4
});
})
}
})
},
handleCancel() {
this.form = {}
this.closeDrawer()
},
},
mounted() {
this.fetch({page: "1"});
},
components: {
BasicRule,
}
}
</script>
+102
View File
@@ -0,0 +1,102 @@
<template>
<div>
<SettingForm :form="form" :spinning="spinning"></SettingForm>
<a-result v-if="status"
:status="status"
:title="title"
:sub-title="subTitle"
>
<template #extra>
<a-row>
<a-col :span="12" :offset="7">
<div style="text-align: left">
<li v-for="err in errors" :key="err">{{ err }}</li>
</div>
</a-col>
</a-row>
</template>
</a-result>
</div>
</template>
<script>
import {getLdapConfig, updateLdapConfig} from "@/api/settings"
import SettingForm from "@/components/SettingForm";
export default {
name: "Ldap",
data() {
return {
form: {},
spinning: false,
status: "",
title: "",
subTitle: "",
errors: []
}
},
methods: {
getConfig() {
getLdapConfig().then(res => {
this.form = res.data
}).catch(e => {
this.$notification.error({
message: 'Error',
description:
e.response.data.error,
style: {
width: '600px',
marginLeft: `${335 - 600}px`,
},
duration: 4
});
})
},
updateConfig() {
this.spinning = true
let targetConfig = JSON.stringify(this.form)
updateLdapConfig(this.form).then(
() => {
setTimeout(() => {
getLdapConfig().then((res) => {
this.spinning = false
let nowConfig = JSON.stringify(res.data)
if (nowConfig !== targetConfig) {
this.status = "warning"
this.title = "Updating the configuration seems to have failed"
this.subTitle = "Please check your config options carefully."
}
this.form = res.data
this.status = ""
this.title = ""
this.subTitle = ""
}
)
}, 3000)
}
).catch(e => {
this.$notification.error({
message: 'Error',
description:
e.response.data.error,
style: {
width: '600px',
marginLeft: `${335 - 600}px`,
},
duration: 4
});
})
}
},
mounted() {
this.getConfig()
},
components: {
SettingForm
}
}
</script>
<style scoped>
</style>
+4
View File
@@ -8,6 +8,7 @@
<http ref="settings" v-if="tabKey==='HTTP'"></http>
<dns ref="settings" v-else-if="tabKey==='DNS'"></dns>
<rmi ref="settings" v-else-if="tabKey==='RMI'"></rmi>
<ldap ref="settings" v-else-if="tabKey==='LDAP'"></ldap>
<mysql ref="settings" v-else-if="tabKey==='MYSQL'"></mysql>
<ftp ref="settings" v-else-if="tabKey==='FTP'"></ftp>
<notice ref="settings" v-else-if="tabKey==='NOTICE'"></notice>
@@ -20,6 +21,7 @@
import Http from "./Http"
import Dns from "./Dns"
import Rmi from "./Rmi"
import Ldap from "./Ldap"
import Mysql from "./Mysql"
import Ftp from "./Ftp"
import Notice from "./Notice"
@@ -33,6 +35,7 @@ export default {
{key: "HTTP", tab: "PLATFORM|HTTP"},
{key: "DNS", tab: "DNS"},
{key: "RMI", tab: "RMI"},
{key: "LDAP", tab: "LDAP"},
{key: "MYSQL", tab: "MYSQL"},
{key: "FTP", tab: "FTP"},
{key: "NOTICE", tab: "NOTICE"},
@@ -51,6 +54,7 @@ export default {
Http,
Dns,
Rmi,
Ldap,
Mysql,
Ftp,
Notice,
+3 -3
View File
@@ -4,32 +4,32 @@ go 1.16
require (
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 // indirect
github.com/blang/semver v3.5.1+incompatible
github.com/fatih/color v1.10.0 // indirect
github.com/gabriel-vasile/mimetype v1.2.0
github.com/gin-gonic/gin v1.7.2
github.com/glebarez/sqlite v1.3.3
github.com/go-playground/validator/v10 v10.7.0 // indirect
github.com/go-sql-driver/mysql v1.6.0
github.com/golang/protobuf v1.5.2 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-sqlite3 v1.14.6 // indirect
github.com/miekg/dns v1.1.38
github.com/oschwald/geoip2-golang v1.5.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/rhysd/go-github-selfupdate v1.2.3
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sinlov/qqwry-golang v0.0.0-20191204062238-bea9868bbbf4
github.com/stretchr/testify v1.7.0
github.com/ugorji/go v1.2.6 // indirect
github.com/urfave/cli/v2 v2.3.0
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
gorm.io/driver/mysql v1.2.1
gorm.io/driver/postgres v1.2.3
gorm.io/driver/sqlite v1.1.4
gorm.io/gorm v1.22.4
unknwon.dev/clog/v2 v2.2.0
vitess.io/vitess v0.9.0
+148 -9
View File
@@ -75,6 +75,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
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/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/buger/jsonparser v0.0.0-20200322175846-f7e751efca13/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -144,6 +145,10 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/glebarez/go-sqlite v1.14.5 h1:kSjlUZFt/Uy4ZzJG/FjzNVfHfFqqUKTHVy+J6EKQKhk=
github.com/glebarez/go-sqlite v1.14.5/go.mod h1:uw5Rp1iEmkACQBl+djShb9eBdoLR9nRRqpCpbdt+1pM=
github.com/glebarez/sqlite v1.3.3 h1:Sp3DGh6ZbSnv2l8dV5ECRvlLLHZvUhxkq4RXlO4dqbs=
github.com/glebarez/sqlite v1.3.3/go.mod h1:18ms2rP2nM4lUdLwNKSL/p/xPkS0Kkwnz02s27ojfNk=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -246,9 +251,13 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
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.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v27 v27.0.4/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0=
github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -259,6 +268,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
@@ -321,6 +332,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
@@ -374,7 +387,6 @@ github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.3 h1:PlHq1bSCSZL9K0wUhbm2pGLoTWs2GwVhsP6emvGV/ZI=
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
@@ -392,6 +404,7 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -450,9 +463,7 @@ github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
@@ -494,7 +505,9 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@@ -545,6 +558,10 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rhysd/go-github-selfupdate v1.2.3 h1:iaa+J202f+Nc+A8zi75uccC8Wg3omaM7HDeimXA22Ag=
github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
@@ -606,6 +623,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tchap/go-patricia v0.0.0-20160729071656-dd168db6051b/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
github.com/tebeka/selenium v0.9.9/go.mod h1:5Fr8+pUvU6B1OiPfkdCKdXZyr5znvVkxuPd0NOdZCQc=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tinylib/msgp v1.1.1/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
@@ -622,6 +641,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
@@ -671,6 +692,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/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-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
@@ -728,7 +750,9 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/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 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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=
@@ -775,9 +799,11 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -786,6 +812,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -826,6 +853,7 @@ golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtn
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-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201202200335-bef1c476418a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -843,8 +871,10 @@ google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn
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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
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 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
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=
@@ -913,9 +943,6 @@ gorm.io/driver/mysql v1.2.1 h1:h+3f1l9Ng2C072Y2tIiLgPpWN78r1KXL7bHJ0nTjlhU=
gorm.io/driver/mysql v1.2.1/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To=
gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs=
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/gorm v1.22.4 h1:8aPcyEJhY0MAt8aY6Dc524Pn+pO29K+ydu+e/cXSpQM=
gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
@@ -940,11 +967,123 @@ k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
modernc.org/ccgo/v3 v3.12.88/go.mod h1:0MFzUHIuSIthpVZyMWiFYMwjiFnhrN5MkvBrUwON+ZM=
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
modernc.org/ccgo/v3 v3.12.95/go.mod h1:ZcLyvtocXYi8uF+9Ebm3G8EF8HNY5hGomBqthDp4eC8=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
modernc.org/libc v1.11.90/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
modernc.org/libc v1.11.99/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
modernc.org/libc v1.11.104 h1:gxoa5b3HPo7OzD4tKZjgnwXk/w//u1oovvjSMP3Q96Q=
modernc.org/libc v1.11.104/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.14.3 h1:psrTwgpEujgWEP3FNdsC9yNh5tSeA77U0GeWhHH4XmQ=
modernc.org/sqlite v1.14.3/go.mod h1:xMpicS1i2MJ4C8+Ap0vYBqTwYfpFvdnPE6brbFOtV2Y=
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/tcl v1.9.2/go.mod h1:aw7OnlIoiuJgu1gwbTZtrKnGpDqH9wyH++jZcxdqNsg=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
modernc.org/z v1.2.20/go.mod h1:zU9FiF4PbHdOTUxw+IF8j7ArBMRPsHgq10uVPt6xTzo=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
+1 -1
View File
@@ -6,6 +6,6 @@ const banner = `
/ /_/ / _ \ | / /\__ \/ / / / / __/
/ _, _/ __/ |/ /___/ / /_/ / / /_
/_/ |_|\___/|___//____/\__,_/_/\__/
v%s
%s
https://revsuit.pro
`
+14
View File
@@ -42,6 +42,15 @@ func Start() {
Usage: "load configuration from FILE (default: config.yaml)",
},
},
Commands: []*cli.Command{
{
Name: "upgrade",
Action: func(c *cli.Context) error {
checkUpdateFromCli()
return nil
},
},
},
Action: func(c *cli.Context) error {
var configFile = "config.yaml"
if c.Path("config") != "" {
@@ -81,6 +90,11 @@ func Start() {
if c.String("log") != "" {
conf.LogLevel = c.String("log")
}
if conf.CheckUpgrade {
checkUpdateFromCli()
}
server.New(conf).Run()
return nil
},
+6 -2
View File
@@ -1,11 +1,12 @@
version: 1.3
version: 1.5
addr: :10000
token:
domain:
domains: []
external_ip: # You need to make sure that your target can access the server through this ip.
admin_path_prefix: "/revsuit"
database: revsuit.db
log_level: info
check_upgrade: true
ip_location_database:
database: "qqwry" # qqwry or geoip.
@@ -19,6 +20,9 @@ dns:
rmi:
enable: true
addr: :1099
ldap:
enable: true
addr: :1389
mysql:
enable: true
addr: :3306
+56
View File
@@ -0,0 +1,56 @@
package cli
import (
"fmt"
"os"
"strings"
"github.com/li4n0/revsuit/internal/update"
"github.com/li4n0/revsuit/pkg/server"
"github.com/pkg/errors"
"github.com/rhysd/go-github-selfupdate/selfupdate"
log "unknwon.dev/clog/v2"
)
func checkUpdateFromCli() {
log.Info("Checking for updates...")
if upgradeable, release, err := update.CheckUpgrade(server.VERSION); err != nil {
log.Warn(err.Error())
} else if upgradeable {
err := updateFromCli(release)
if err != nil {
log.Warn(err.Error())
}
} else {
log.Info("The current version is the latest")
}
}
func updateFromCli(release *selfupdate.Release) error {
releaseNotes := release.ReleaseNotes
releaseNotes = strings.Replace(releaseNotes, "\n\n", "\n", -1)
releaseNotes = strings.Replace(releaseNotes, "\r\n\r\n", "\n", -1)
releaseNotes = strings.Replace(releaseNotes, "**", "", -1)
log.Info("New version detected: %s\n%s", release.URL, releaseNotes)
log.Warn("Do you want to update to v%s?[Y/n]:", release.Version)
var upgrade string
_, _ = fmt.Scanln(&upgrade)
if upgrade != "n" {
exe, err := os.Executable()
if err != nil {
log.Error("error when locate executable path, err: %v", err)
}
assetURL := "https://upgrade.revsuit.pro" + strings.TrimPrefix(release.AssetURL, "https://github.com/Li4n0/revsuit/releases") + "?from=" + server.VERSION
log.Info("Downloading from %s", assetURL)
if err := selfupdate.UpdateTo(assetURL, exe); err != nil {
return errors.Wrap(err, "error when download asset")
}
log.Info("Successfully updated to v%s", release.Version)
return nil
}
return nil
}
+1 -1
View File
@@ -3,7 +3,7 @@ package database
import (
"strings"
"gorm.io/driver/sqlite"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
)
+25
View File
@@ -0,0 +1,25 @@
package update
import (
"strings"
"github.com/blang/semver"
"github.com/pkg/errors"
"github.com/rhysd/go-github-selfupdate/selfupdate"
)
func CheckUpgrade(currentVersion string) (bool, *selfupdate.Release, error) {
latest, found, err := selfupdate.DetectLatest("Li4n0/revsuit")
if err != nil {
return false, nil, errors.Wrap(err, "error when detecting version")
}
v, err := semver.Parse(strings.TrimPrefix(currentVersion, "v"))
if err != nil {
return false, nil, errors.Wrap(err, "error when parse version")
}
if !found || latest.Version.LTE(v) {
return false, nil, nil
}
return true, latest, nil
}
+32 -29
View File
@@ -17,11 +17,11 @@ import (
type Server struct {
Config
serverDomain string
serverIP string
rules []*Rule
rulesLock sync.RWMutex
livingLock sync.Mutex
serverDomains []string
serverIP string
rules []*Rule
rulesLock sync.RWMutex
livingLock sync.Mutex
}
var (
@@ -37,8 +37,8 @@ func GetServer() *Server {
return server
}
func (s *Server) SetServerDomain(serverDomain string) *Server {
s.serverDomain = serverDomain
func (s *Server) SetServerDomain(serverDomains []string) *Server {
s.serverDomains = serverDomains
return s
}
@@ -217,39 +217,42 @@ func (s *Server) Run() {
// create server
server := newdns.NewServer(newdns.Config{
Handler: func(name string) (*newdns.Zone, error) {
if name == s.serverDomain+"." {
return &newdns.Zone{
Name: getZoneName(s.serverDomain),
MasterNameServer: "ns1.hostmaster.com.",
AllNameServers: []string{
"ns1.hostmaster.com.",
"ns2.hostmaster.com.",
"ns3.hostmaster.com.",
},
Handler: func(_, remoteAddr string) ([]newdns.Set, error) {
return []newdns.Set{
{
Name: name,
Type: newdns.A,
TTL: 10,
Records: []newdns.Record{
{
Address: s.serverIP,
},
}}}, nil
}}, nil
for _, serverDomain := range s.serverDomains {
if name == serverDomain+"." {
return &newdns.Zone{
Name: getZoneName(serverDomain),
MasterNameServer: "ns1.hostmaster.com.",
AllNameServers: []string{
"ns1.hostmaster.com.",
"ns2.hostmaster.com.",
"ns3.hostmaster.com.",
},
Handler: func(_, remoteAddr string) ([]newdns.Set, error) {
return []newdns.Set{
{
Name: name,
Type: newdns.A,
TTL: 10,
Records: []newdns.Record{
{
Address: s.serverIP,
},
}}}, nil
}}, nil
}
}
return s.newZone(name), nil
},
})
// run server
log.Info("Starting DNS Server at :53, resolve %s to %s", s.serverDomain, s.serverIP)
log.Info("Starting DNS Server at :53, resolve %v to %s", s.serverDomains, s.serverIP)
go func() {
s.livingLock.Lock()
if !s.Enable {
server.Close()
}
s.livingLock.Unlock()
}()
err := server.Run(s.Addr)
+3 -13
View File
@@ -101,10 +101,7 @@ func ListRules(c *gin.Context) {
}
db := database.DB.Model(&dnsRule)
if dnsRule.Name != "" {
db.Where("name = ?", dnsRule.Name)
}
db.Count(&count)
db.Where(&dnsRule).Count(&count)
page, err := strconv.Atoi(c.Query("page"))
if err != nil {
@@ -138,10 +135,7 @@ func ListRules(c *gin.Context) {
// UpsertRules creates or updates dns rule from user submit
func UpsertRules(c *gin.Context) {
var (
dnsRule Rule
update bool
)
var dnsRule Rule
if err := c.ShouldBind(&dnsRule); err != nil {
c.JSON(400, gin.H{
@@ -152,10 +146,6 @@ func UpsertRules(c *gin.Context) {
return
}
if dnsRule.ID != 0 {
update = true
}
if err := dnsRule.CreateOrUpdate(); err != nil {
c.JSON(400, gin.H{
"status": "failed",
@@ -165,7 +155,7 @@ func UpsertRules(c *gin.Context) {
return
}
if update {
if dnsRule.ID != 0 {
log.Trace("DNS rule[id:%d] has been updated", dnsRule.ID)
} else {
log.Trace("DNS rule[id:%d] has been created", dnsRule.ID)
+3 -2
View File
@@ -393,12 +393,13 @@ func (s *Server) Run() {
if !s.Enable {
_ = listener.Close()
}
s.livingLock.Unlock()
}()
for s.Enable {
for {
tcpConn, err := listener.Accept()
if err != nil {
if !strings.Contains(err.Error(), net.ErrClosed.Error()) {
if !errors.Is(err, net.ErrClosed) {
log.Warn("FTP accept connection error: %v", err)
} else {
break
+3 -13
View File
@@ -96,10 +96,7 @@ func ListRules(c *gin.Context) {
}
db := database.DB.Model(&ftpRule)
if ftpRule.Name != "" {
db.Where("name = ?", ftpRule.Name)
}
db.Count(&count)
db.Where(&ftpRule).Count(&count)
page, err := strconv.Atoi(c.Query("page"))
if err != nil {
@@ -133,10 +130,7 @@ func ListRules(c *gin.Context) {
// UpsertRules creates or updates ftp rule from user submit
func UpsertRules(c *gin.Context) {
var (
ftpRule Rule
update bool
)
var ftpRule Rule
if err := c.ShouldBind(&ftpRule); err != nil {
c.JSON(400, gin.H{
@@ -147,10 +141,6 @@ func UpsertRules(c *gin.Context) {
return
}
if ftpRule.ID != 0 {
update = true
}
if err := ftpRule.CreateOrUpdate(); err != nil {
c.JSON(400, gin.H{
"status": "failed",
@@ -160,7 +150,7 @@ func UpsertRules(c *gin.Context) {
return
}
if update {
if ftpRule.ID != 0 {
log.Trace("FTP rule[id:%d] has been updated", ftpRule.ID)
} else {
log.Trace("FTP rule[id:%d] has been created", ftpRule.ID)
+6
View File
@@ -0,0 +1,6 @@
package ldap
type Config struct {
Enable bool
Addr string
}
+198
View File
@@ -0,0 +1,198 @@
package ldap
import (
"bytes"
"net"
"sync"
"time"
"github.com/li4n0/revsuit/internal/database"
"github.com/li4n0/revsuit/internal/ipinfo"
"github.com/li4n0/revsuit/internal/recycler"
"github.com/pkg/errors"
log "unknwon.dev/clog/v2"
)
type Server struct {
Config
rules []*Rule
rulesLock sync.RWMutex
livingLock sync.Mutex
}
var (
server *Server
once sync.Once
)
func GetServer() *Server {
once.Do(func() {
server = &Server{rulesLock: sync.RWMutex{}, livingLock: sync.Mutex{}}
})
return server
}
func (s *Server) getRules() []*Rule {
defer s.rulesLock.RUnlock()
s.rulesLock.RLock()
return s.rules
}
func (s *Server) UpdateRules() error {
db := database.DB.Model(new(Rule))
defer s.rulesLock.Unlock()
s.rulesLock.Lock()
return errors.Wrap(db.Order("base_rank desc").Find(&s.rules).Error, "LDAP update rules error")
}
func (s *Server) handleConnection(conn net.Conn) {
defer func() {
_ = conn.Close()
if err := recover(); err != nil {
recycler.Recycle(err)
}
}()
if err := conn.SetDeadline(time.Now().Add(time.Second * 30)); err != nil {
log.Warn("LDAP set connection deadline error:%v", err)
return
}
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
buf := make([]byte, 1024)
_, err := conn.Read(buf)
if err != nil {
log.Warn("LDAP read connection error:%v", err)
return
}
if !bytes.Contains(buf, []byte{
0x30, 0x0c, 0x02, 0x01, 0x01, 0x60, 0x07,
0x02, 0x01, 0x03, 0x04, 0x00, 0x80, 0x00}) {
return
}
send := []byte{
0x30, 0x0c, 0x02, 0x01, 0x01, 0x61, 0x07,
0x0a, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00,
}
_, err = conn.Write(send)
if err != nil {
log.Warn("LDAP write connection error: %v", err)
return
}
_, err = conn.Read(buf)
if err != nil {
log.Warn("LDAP read connection error:%v", err)
return
}
length := buf[8]
pathBytes := bytes.Buffer{}
for i := 1; i <= int(length); i++ {
temp := []byte{buf[8+i]}
pathBytes.Write(temp)
}
path := pathBytes.String()
for _, _rule := range s.getRules() {
flag, flagGroup, _ := _rule.Match(path)
if flag == "" {
continue
}
area := ipinfo.Area(ip)
// create new record
r, err := NewRecord(_rule, flag, path, ip, area)
if err != nil {
log.Warn("LDAP record[rule_id:%d] created failed :%s", _rule.ID, err)
return
}
log.Info("LDAP record[id:%d rule:%s remote_ip:%s] has been created", r.ID, _rule.Name, ip)
//only send to client when this connection recorded first time.
if _rule.PushToClient {
if flagGroup != "" {
var count int64
database.DB.Where("rule_name=? and path like ?", _rule.Name, "%"+flagGroup+"%").Model(&Record{}).Count(&count)
if count <= 1 {
r.PushToClient()
log.Trace("LDAP record[id:%d, flagGroup:%s] has been put to client message queue", r.ID, flagGroup)
}
} else {
r.PushToClient()
log.Trace("LDAP record[id:%d, flag:%s] has been put to client message queue", r.ID, flag)
}
}
//send notice
if _rule.Notice {
go func() {
r.Notice()
log.Trace("LDAP record[id:%d] notice has been sent", r.ID)
}()
}
return
}
}
func (s *Server) Stop() {
log.Info("LDAP Server is stopping...")
s.Enable = false
s.livingLock.Unlock()
}
func (s *Server) Restart() {
s.Stop()
time.Sleep(time.Second * 2)
go s.Run()
}
func (s *Server) Run() {
s.Enable = true
s.livingLock.Lock()
defer func() {
if s.Enable {
log.Error("LDAP Server exited unexpectedly")
}
s.Enable = false
s.livingLock.Unlock()
}()
if err := s.UpdateRules(); err != nil {
log.Error(err.Error())
return
}
// run server
log.Info("Starting LDAP Server at %v", s.Addr)
listener, err := net.Listen("tcp", s.Addr)
if err != nil {
log.Error(err.Error())
return
}
go func() {
s.livingLock.Lock()
if !s.Enable {
_ = listener.Close()
}
s.livingLock.Unlock()
}()
for {
tcpConn, err := listener.Accept()
if err != nil {
if !errors.Is(err, net.ErrClosed) {
log.Warn("LDAP accept connection error: %v", err)
} else {
break
}
continue
}
go s.handleConnection(tcpConn)
}
}
+139
View File
@@ -0,0 +1,139 @@
package ldap
import (
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/li4n0/revsuit/internal/database"
"github.com/li4n0/revsuit/internal/notice"
"github.com/li4n0/revsuit/internal/record"
"gorm.io/gorm"
log "unknwon.dev/clog/v2"
)
var _ record.Record = (*Record)(nil)
type Record struct {
Path string `form:"path" json:"path"`
record.BaseRecord
Rule Rule `gorm:"foreignKey:RuleName;references:Name;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" form:"-" json:"-" notice:"-"`
}
func (Record) TableName() string {
return "ldap_records"
}
func (r Record) Notice() {
notice.Notice(r)
}
func NewRecord(rule *Rule, flag, path, ip, area string) (r *Record, err error) {
r = &Record{
BaseRecord: record.BaseRecord{
Flag: flag,
RemoteIP: ip,
IpArea: area,
RequestTime: time.Now(),
},
Path: path,
Rule: *rule,
}
return r, database.DB.Create(r).Error
}
func Records(c *gin.Context) {
var (
ldapRecord Record
res []Record
count int64
order = c.Query("order")
pageSize = 10
)
if c.Query("pageSize") != "" {
if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
if n > 0 && n < 100 {
pageSize = n
}
}
}
if err := c.ShouldBind(&ldapRecord); err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"result": nil,
})
return
}
db := database.DB.Model(&ldapRecord)
if ldapRecord.Flag != "" {
db.Where("flag = ?", ldapRecord.Flag)
}
if ldapRecord.Path != "" {
db.Where("path like ?", "%"+ldapRecord.Path+"%")
}
if ldapRecord.RemoteIP != "" {
db.Where("remote_ip = ?", ldapRecord.RemoteIP)
}
if ldapRecord.RuleName != "" {
db.Where("rule_name = ?", ldapRecord.RuleName)
}
//Delete records
if c.Request.Method == http.MethodDelete {
if err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&res).Error; err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"data": nil,
})
return
}
if database.Driver == database.Sqlite {
db.Exec("VACUUM")
}
c.JSON(200, gin.H{
"status": "succeed",
"error": nil,
})
log.Info("%d ldap records deleted by %s", db.RowsAffected, c.Request.RemoteAddr)
return
}
//List records
page, err := strconv.Atoi(c.Query("page"))
if err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"result": nil,
})
return
}
if order != "asc" {
order = "desc"
}
if err := db.Order("id" + " " + order).Count(&count).Offset((page - 1) * pageSize).Limit(pageSize).Find(&res).Error; err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"data": nil,
})
return
}
c.JSON(200, gin.H{
"status": "succeed",
"error": nil,
"result": gin.H{"count": count, "data": res},
})
}
+192
View File
@@ -0,0 +1,192 @@
package ldap
import (
"strconv"
"github.com/gin-gonic/gin"
"github.com/li4n0/revsuit/internal/database"
"github.com/li4n0/revsuit/internal/rule"
"gorm.io/gorm/clause"
log "unknwon.dev/clog/v2"
)
// Rule LDAP rule struct
type Rule struct {
rule.BaseRule `yaml:",inline"`
}
func (Rule) TableName() string {
return "ldap_rules"
}
// NewRule new ldap rule struct
func NewRule(name, flagFormat string, pushToClient, notice bool) *Rule {
return &Rule{
BaseRule: rule.BaseRule{
Name: name,
FlagFormat: flagFormat,
PushToClient: pushToClient,
Notice: notice,
},
}
}
// CreateOrUpdate creates or updates the ldap rule in database and ruleSet
func (r *Rule) CreateOrUpdate() (err error) {
db := database.DB.Model(r)
err = db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns(
[]string{
"name",
"flag_format",
"base_rank",
"push_to_client",
"notice",
}),
}).Create(r).Error
if err != nil {
return
}
err = GetServer().UpdateRules()
return err
}
// Delete deletes the ldap rule in database and ruleSet
func (r *Rule) Delete() (err error) {
db := database.DB.Model(r)
err = db.Delete(r).Error
if err != nil {
return
}
err = GetServer().UpdateRules()
return err
}
// ListRules lists all ldap rules those satisfy the filter
func ListRules(c *gin.Context) {
var (
ldapRule Rule
res []Rule
count int64
order = c.Query("order")
pageSize = 10
)
if c.Query("pageSize") != "" {
if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
if n > 0 && n < 100 {
pageSize = n
}
}
}
if err := c.ShouldBind(&ldapRule); err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"result": nil,
})
return
}
db := database.DB.Model(&ldapRule)
db.Where(&ldapRule).Count(&count)
page, err := strconv.Atoi(c.Query("page"))
if err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"result": nil,
})
return
}
if order != "asc" {
order = "desc"
}
if err := db.Order("base_rank desc").Order("id" + " " + order).Count(&count).Offset((page - 1) * pageSize).Limit(pageSize).Find(&res).Error; err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"data": nil,
})
return
}
c.JSON(200, gin.H{
"status": "succeed",
"error": nil,
"result": gin.H{"count": count, "data": res},
})
}
// UpsertRules creates or updates ldap rule from user submit
func UpsertRules(c *gin.Context) {
var ldapRule Rule
if err := c.ShouldBind(&ldapRule); err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"data": nil,
})
return
}
if err := ldapRule.CreateOrUpdate(); err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"result": nil,
})
return
}
if ldapRule.ID != 0 {
log.Trace("LDAP rule[id:%d] has been updated", ldapRule.ID)
} else {
log.Trace("LDAP rule[id:%d] has been created", ldapRule.ID)
}
c.JSON(200, gin.H{
"status": "succeed",
"error": nil,
"result": nil,
})
}
// DeleteRules deletes ldap rule from user submit
func DeleteRules(c *gin.Context) {
var ldapRule Rule
if err := c.ShouldBind(&ldapRule); err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"data": nil,
})
return
}
if err := ldapRule.Delete(); err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"data": nil,
})
return
}
log.Trace("LDAP rule[id:%d] has been deleted", ldapRule.ID)
c.JSON(200, gin.H{
"status": "succeed",
"error": nil,
"data": nil,
})
}
+2 -1
View File
@@ -273,7 +273,7 @@ func (s *Server) Stop() {
}
func (s *Server) Restart() {
s.Enable = false
s.Stop()
time.Sleep(time.Second * 2)
go s.Run()
}
@@ -310,6 +310,7 @@ func (s *Server) Run() {
if !s.Enable {
s.listener.Close()
}
s.livingLock.Unlock()
}()
s.listener.Accept()
+3 -13
View File
@@ -84,10 +84,7 @@ func ListRules(c *gin.Context) {
}
db := database.DB.Model(&mysqlRule)
if mysqlRule.Name != "" {
db.Where("name = ?", mysqlRule.Name)
}
db.Count(&count)
db.Where(&mysqlRule).Count(&count)
page, err := strconv.Atoi(c.Query("page"))
if err != nil {
@@ -121,10 +118,7 @@ func ListRules(c *gin.Context) {
// UpsertRules create or update mysql rule from user submit
func UpsertRules(c *gin.Context) {
var (
mysqlRule Rule
update bool
)
var mysqlRule Rule
if err := c.ShouldBind(&mysqlRule); err != nil {
c.JSON(400, gin.H{
@@ -135,10 +129,6 @@ func UpsertRules(c *gin.Context) {
return
}
if mysqlRule.ID != 0 {
update = true
}
if err := mysqlRule.CreateOrUpdate(); err != nil {
c.JSON(400, gin.H{
"status": "failed",
@@ -148,7 +138,7 @@ func UpsertRules(c *gin.Context) {
return
}
if update {
if mysqlRule.ID != 0 {
log.Trace("MySQL rule[id:%d] has been updated", mysqlRule.ID)
} else {
log.Trace("MySQL rule[id:%d] has been created", mysqlRule.ID)
+3 -13
View File
@@ -102,10 +102,7 @@ func ListRules(c *gin.Context) {
}
db := database.DB.Model(&httpRule)
if httpRule.Name != "" {
db.Where("name = ?", httpRule.Name)
}
db.Count(&count)
db.Where(&httpRule).Count(&count)
page, err := strconv.Atoi(c.Query("page"))
if err != nil {
@@ -139,10 +136,7 @@ func ListRules(c *gin.Context) {
// UpsertRules create or update http rule from user submit
func UpsertRules(c *gin.Context) {
var (
httpRule Rule
update bool
)
var httpRule Rule
if err := c.ShouldBind(&httpRule); err != nil {
c.JSON(400, gin.H{
@@ -153,10 +147,6 @@ func UpsertRules(c *gin.Context) {
return
}
if httpRule.ID != 0 {
update = true
}
if err := httpRule.CreateOrUpdate(); err != nil {
c.JSON(400, gin.H{
"status": "failed",
@@ -166,7 +156,7 @@ func UpsertRules(c *gin.Context) {
return
}
if update {
if httpRule.ID != 0 {
log.Trace("HTTP rule[id:%d] has been updated", httpRule.ID)
} else {
log.Trace("HTTP rule[id:%d] has been created", httpRule.ID)
+4 -3
View File
@@ -154,7 +154,7 @@ func (s *Server) Stop() {
}
func (s *Server) Restart() {
s.Enable = false
s.Stop()
time.Sleep(time.Second * 2)
go s.Run()
}
@@ -189,12 +189,13 @@ func (s *Server) Run() {
if !s.Enable {
_ = listener.Close()
}
s.livingLock.Unlock()
}()
for s.Enable {
for {
tcpConn, err := listener.Accept()
if err != nil {
if !strings.Contains(err.Error(), net.ErrClosed.Error()) {
if !errors.Is(err, net.ErrClosed) {
log.Warn("RMI accept connection error: %v", err)
} else {
break
+3 -13
View File
@@ -93,10 +93,7 @@ func ListRules(c *gin.Context) {
}
db := database.DB.Model(&rmiRule)
if rmiRule.Name != "" {
db.Where("name = ?", rmiRule.Name)
}
db.Count(&count)
db.Where(&rmiRule).Count(&count)
page, err := strconv.Atoi(c.Query("page"))
if err != nil {
@@ -130,10 +127,7 @@ func ListRules(c *gin.Context) {
// UpsertRules creates or updates rmi rule from user submit
func UpsertRules(c *gin.Context) {
var (
rmiRule Rule
update bool
)
var rmiRule Rule
if err := c.ShouldBind(&rmiRule); err != nil {
c.JSON(400, gin.H{
@@ -144,10 +138,6 @@ func UpsertRules(c *gin.Context) {
return
}
if rmiRule.ID != 0 {
update = true
}
if err := rmiRule.CreateOrUpdate(); err != nil {
c.JSON(400, gin.H{
"status": "failed",
@@ -157,7 +147,7 @@ func UpsertRules(c *gin.Context) {
return
}
if update {
if rmiRule.ID != 0 {
log.Trace("RMI rule[id:%d] has been updated", rmiRule.ID)
} else {
log.Trace("RMI rule[id:%d] has been created", rmiRule.ID)
+4 -1
View File
@@ -4,6 +4,7 @@ import (
"github.com/li4n0/revsuit/internal/ipinfo"
"github.com/li4n0/revsuit/pkg/dns"
"github.com/li4n0/revsuit/pkg/ftp"
"github.com/li4n0/revsuit/pkg/ldap"
"github.com/li4n0/revsuit/pkg/mysql"
"github.com/li4n0/revsuit/pkg/rhttp"
"github.com/li4n0/revsuit/pkg/rmi"
@@ -20,16 +21,18 @@ type Config struct {
Version float64
Addr string
Token string
Domain string
Domains []string
ExternalIP string `yaml:"external_ip"`
AdminPathPrefix string `yaml:"admin_path_prefix"`
Database string
LogLevel string `yaml:"log_level"`
CheckUpgrade bool `yaml:"check_upgrade"`
IpLocationDatabase ipinfo.Config `yaml:"ip_location_database"`
Notice noticeConfig
HTTP rhttp.Config
DNS dns.Config
MySQL mysql.Config
RMI rmi.Config
LDAP ldap.Config
FTP ftp.Config
}
+40
View File
@@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/li4n0/revsuit/internal/recycler"
"github.com/li4n0/revsuit/internal/update"
log "unknwon.dev/clog/v2"
)
@@ -88,3 +89,42 @@ func version(c *gin.Context) {
"result": VERSION,
})
}
func (revsuit *Revsuit) getUpgrade(c *gin.Context) {
if !revsuit.config.CheckUpgrade {
c.JSON(200, gin.H{
"status": "succeed",
"error": nil,
"result": gin.H{
"upgradeable": false,
"message": "config of check upgrade is false",
},
})
return
}
if upgradeable, release, err := update.CheckUpgrade(VERSION); err == nil && upgradeable {
c.JSON(200, gin.H{
"status": "succeed",
"error": nil,
"result": gin.H{
"upgradeable": true,
"version": release.Version,
"release": release.URL,
},
})
} else {
message := VERSION + " is the latest"
if err != nil {
message = err.Error()
}
c.JSON(200, gin.H{
"status": "succeed",
"error": nil,
"result": gin.H{
"upgradeable": false,
"message": message,
},
})
}
}
+13
View File
@@ -10,6 +10,7 @@ import (
"github.com/li4n0/revsuit/internal/file"
"github.com/li4n0/revsuit/pkg/dns"
"github.com/li4n0/revsuit/pkg/ftp"
"github.com/li4n0/revsuit/pkg/ldap"
"github.com/li4n0/revsuit/pkg/mysql"
"github.com/li4n0/revsuit/pkg/rhttp"
"github.com/li4n0/revsuit/pkg/rmi"
@@ -47,6 +48,7 @@ func (revsuit *Revsuit) registerPlatformRouter() {
api.GET("/events", revsuit.events)
api.GET("/ping", ping)
api.GET("/version", version)
api.GET("/getUpgrade", revsuit.getUpgrade)
}
func (revsuit *Revsuit) registerHttpRouter() {
@@ -69,6 +71,8 @@ func (revsuit *Revsuit) registerHttpRouter() {
settingsGroup.POST("/updateDnsConfig", revsuit.updateDnsConfig)
settingsGroup.GET("/getRmiConfig", revsuit.getRmiConfig)
settingsGroup.POST("/updateRmiConfig", revsuit.updateRmiConfig)
settingsGroup.GET("/getLdapConfig", revsuit.getLdapConfig)
settingsGroup.POST("/updateLdapConfig", revsuit.updateLdapConfig)
settingsGroup.GET("/getMySQLConfig", revsuit.getMySQLConfig)
settingsGroup.POST("/updateMySQLConfig", revsuit.updateMySQLConfig)
settingsGroup.GET("/getFtpConfig", revsuit.getFtpConfig)
@@ -95,6 +99,10 @@ func (revsuit *Revsuit) registerHttpRouter() {
rmiGroup.GET("", rmi.Records)
rmiGroup.DELETE("", rmi.Records)
ldapGroup := recordGroup.Group("/ldap")
ldapGroup.GET("", ldap.Records)
ldapGroup.DELETE("", ldap.Records)
ftpGroup := recordGroup.Group("/ftp")
ftpGroup.GET("", ftp.Records)
ftpGroup.DELETE("", ftp.Records)
@@ -122,6 +130,11 @@ func (revsuit *Revsuit) registerHttpRouter() {
rmiGroup.POST("", rmi.UpsertRules)
rmiGroup.DELETE("", rmi.DeleteRules)
ldapGroup = ruleGroup.Group("/ldap")
ldapGroup.GET("", ldap.ListRules)
ldapGroup.POST("", ldap.UpsertRules)
ldapGroup.DELETE("", ldap.DeleteRules)
ftpGroup = ruleGroup.Group("/ftp")
ftpGroup.GET("", ftp.ListRules)
ftpGroup.POST("", ftp.UpsertRules)
+36 -48
View File
@@ -11,6 +11,7 @@ import (
"github.com/li4n0/revsuit/internal/record"
"github.com/li4n0/revsuit/pkg/dns"
"github.com/li4n0/revsuit/pkg/ftp"
"github.com/li4n0/revsuit/pkg/ldap"
"github.com/li4n0/revsuit/pkg/mysql"
http "github.com/li4n0/revsuit/pkg/rhttp"
"github.com/li4n0/revsuit/pkg/rmi"
@@ -18,7 +19,7 @@ import (
log "unknwon.dev/clog/v2"
)
const VERSION = "0.4.0"
var VERSION = ""
type Revsuit struct {
config *Config
@@ -28,6 +29,7 @@ type Revsuit struct {
dns *dns.Server
mysql *mysql.Server
rmi *rmi.Server
ldap *ldap.Server
ftp *ftp.Server
clients map[int]*gin.Context
@@ -66,55 +68,35 @@ func initDatabase(dsn string) {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&http.Record{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&dns.Record{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&mysql.Record{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&http.Rule{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&dns.Rule{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&mysql.Rule{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&file.MySQLFile{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&rmi.Record{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&rmi.Rule{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&ftp.Record{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&ftp.Rule{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&file.FTPFile{})
err = database.DB.AutoMigrate(&http.Record{}, &http.Rule{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&dns.Record{}, &dns.Rule{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&mysql.Record{}, &mysql.Rule{}, &file.MySQLFile{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&rmi.Record{}, &rmi.Rule{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&ldap.Record{}, &ldap.Rule{})
if err != nil {
log.Fatal(err.Error())
}
err = database.DB.AutoMigrate(&ftp.Record{}, &ftp.Rule{}, &file.FTPFile{})
if err != nil {
log.Fatal(err.Error())
}
}
func initLog(level string) (logLevel log.Level) {
@@ -198,6 +180,9 @@ func New(c *Config) *Revsuit {
s.rmi = rmi.GetServer()
s.rmi.Config = c.RMI
s.ldap = ldap.GetServer()
s.ldap.Config = c.LDAP
s.ftp = ftp.GetServer()
s.ftp.Config = c.FTP
@@ -210,8 +195,8 @@ func New(c *Config) *Revsuit {
if c.HTTP.IpHeader != "" {
s.http.SetIpHeader(c.HTTP.IpHeader)
}
if c.Domain != "" {
s.dns.SetServerDomain(c.Domain)
if len(c.Domains) != 0 {
s.dns.SetServerDomain(c.Domains)
}
if c.ExternalIP != "" {
s.dns.SetServerIP(c.ExternalIP)
@@ -232,6 +217,9 @@ func (revsuit *Revsuit) Run() {
if revsuit.rmi != nil && revsuit.rmi.Enable {
go revsuit.rmi.Run()
}
if revsuit.ldap != nil && revsuit.ldap.Enable {
go revsuit.ldap.Run()
}
if revsuit.mysql != nil && revsuit.mysql.Enable {
go revsuit.mysql.Run()
}
+71 -10
View File
@@ -3,12 +3,14 @@ package server
import (
"fmt"
"io"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/li4n0/revsuit/internal/database"
"github.com/li4n0/revsuit/pkg/dns"
"github.com/li4n0/revsuit/pkg/ftp"
"github.com/li4n0/revsuit/pkg/ldap"
"github.com/li4n0/revsuit/pkg/mysql"
"github.com/li4n0/revsuit/pkg/rhttp"
"github.com/li4n0/revsuit/pkg/rmi"
@@ -22,6 +24,7 @@ type Rules struct {
Dns []dns.Rule
Mysql []mysql.Rule
Rmi []rmi.Rule
Ldap []ldap.Rule
Ftp []ftp.Rule
}
@@ -35,6 +38,7 @@ func exportRules(c *gin.Context) {
db.Model(&dns.Rule{}).Find(&rules.Dns)
db.Model(&mysql.Rule{}).Find(&rules.Mysql)
db.Model(&rmi.Rule{}).Find(&rules.Rmi)
db.Model(&rmi.Rule{}).Find(&rules.Ldap)
db.Model(&ftp.Rule{}).Find(&rules.Ftp)
out, err := yaml.Marshal(rules)
@@ -88,7 +92,7 @@ func (revsuit *Revsuit) importRules(c *gin.Context) {
for _, rule := range rules.Http {
if err := db.Model(&rhttp.Rule{}).Create(&rule).Error; err != nil {
errs = append(errs, errors.Wrap(err, fmt.Sprintf("http rule[%s]", rule.Name)).Error())
errs = append(errs, errors.Wrapf(err, "http rule[%s]", rule.Name).Error())
continue
}
count++
@@ -99,7 +103,7 @@ func (revsuit *Revsuit) importRules(c *gin.Context) {
for _, rule := range rules.Dns {
if err := db.Model(&dns.Rule{}).Create(&rule).Error; err != nil {
errs = append(errs, errors.Wrap(err, fmt.Sprintf("dns rule[%s]", rule.Name)).Error())
errs = append(errs, errors.Wrapf(err, "dns rule[%s]", rule.Name).Error())
continue
}
count++
@@ -110,7 +114,7 @@ func (revsuit *Revsuit) importRules(c *gin.Context) {
for _, rule := range rules.Mysql {
if err := db.Model(&mysql.Rule{}).Create(&rule).Error; err != nil {
errs = append(errs, errors.Wrap(err, fmt.Sprintf("mysql rule[%s]", rule.Name)).Error())
errs = append(errs, errors.Wrapf(err, "mysql rule[%s]", rule.Name).Error())
continue
}
count++
@@ -121,7 +125,7 @@ func (revsuit *Revsuit) importRules(c *gin.Context) {
for _, rule := range rules.Rmi {
if err := db.Model(&rmi.Rule{}).Create(&rule).Error; err != nil {
errs = append(errs, errors.Wrap(err, fmt.Sprintf("rmi rule[%s]", rule.Name)).Error())
errs = append(errs, errors.Wrapf(err, "rmi rule[%s]", rule.Name).Error())
continue
}
count++
@@ -130,9 +134,20 @@ func (revsuit *Revsuit) importRules(c *gin.Context) {
errs = append(errs, err.Error())
}
for _, rule := range rules.Ldap {
if err := db.Model(&ldap.Rule{}).Create(&rule).Error; err != nil {
errs = append(errs, errors.Wrapf(err, "ldap rule[%s]", rule.Name).Error())
continue
}
count++
}
if err := revsuit.ldap.UpdateRules(); err != nil {
errs = append(errs, err.Error())
}
for _, rule := range rules.Ftp {
if err := db.Model(&ftp.Rule{}).Create(&rule).Error; err != nil {
errs = append(errs, errors.Wrap(err, fmt.Sprintf("ftp rule[%s]", rule.Name)).Error())
errs = append(errs, errors.Wrapf(err, "ftp rule[%s]", rule.Name).Error())
continue
}
count++
@@ -152,7 +167,7 @@ func (revsuit *Revsuit) getPlatformConfig(c *gin.Context) {
var res = make(map[string]string)
res["Addr"] = revsuit.config.Addr
res["Token"] = revsuit.config.Token
res["Domain"] = revsuit.config.Domain
res["Domains"] = strings.Join(revsuit.config.Domains, ",")
res["AdminPathPrefix"] = revsuit.config.AdminPathPrefix
res["ExternalIP"] = revsuit.config.ExternalIP
res["Database"] = revsuit.config.Database
@@ -201,10 +216,10 @@ func (revsuit *Revsuit) updatePlatformConfig(c *gin.Context) {
log.Info("Update http config [ip_header] to %s", form["IpHeader"])
}
if form["Domain"] != revsuit.config.Domain {
revsuit.config.Domain = form["Domain"]
revsuit.dns.SetServerDomain(form["Domain"])
log.Info("Update platform config [domain] to %s", form["Domain"])
if form["Domains"] != strings.Join(revsuit.config.Domains, ",") {
revsuit.config.Domains = strings.Split(form["Domains"], ",")
revsuit.dns.SetServerDomain(revsuit.config.Domains)
log.Info("Update platform config [domain] to %v", revsuit.config.Domains)
if revsuit.dns.Enable {
revsuit.dns.Restart()
}
@@ -293,6 +308,11 @@ func (revsuit *Revsuit) updateDnsConfig(c *gin.Context) {
return
}
if form.Addr != revsuit.dns.Addr {
revsuit.dns.Addr = form.Addr
log.Info("Update dns config [addr] to %s", form.Addr)
}
if form.Enable != revsuit.dns.Enable {
log.Info("Update dns config [enable] to %v", form.Enable)
if form.Enable {
@@ -302,6 +322,11 @@ func (revsuit *Revsuit) updateDnsConfig(c *gin.Context) {
}
return
}
if revsuit.dns.Enable {
revsuit.dns.Restart()
}
}
func (revsuit *Revsuit) getMySQLConfig(c *gin.Context) {
@@ -381,6 +406,42 @@ func (revsuit *Revsuit) updateRmiConfig(c *gin.Context) {
}
}
func (revsuit *Revsuit) getLdapConfig(c *gin.Context) {
c.JSON(200, revsuit.ldap.Config)
}
func (revsuit *Revsuit) updateLdapConfig(c *gin.Context) {
var form = ldap.Config{}
if err := c.ShouldBindJSON(&form); err != nil {
c.JSON(400, gin.H{
"status": "failed",
"error": err.Error(),
"data": nil,
})
return
}
if form.Addr != revsuit.ldap.Addr {
revsuit.ldap.Addr = form.Addr
log.Info("Update ldap config [addr] to %s", form.Addr)
}
if form.Enable != revsuit.ldap.Enable {
log.Info("Update ldap config [enable] to %v", form.Enable)
if form.Enable {
go revsuit.ldap.Run()
} else {
revsuit.ldap.Stop()
}
return
}
if revsuit.ldap.Enable {
revsuit.ldap.Restart()
}
}
func (revsuit *Revsuit) getNoticeConfig(c *gin.Context) {
c.JSON(200, revsuit.config.Notice)
}