Merge branch 'tests'

This commit is contained in:
nicksherron 2020-02-16 12:46:26 -05:00
commit bc7aebe6a6
14 changed files with 1113 additions and 272 deletions

1
.gitignore vendored
View file

@ -2,5 +2,4 @@ bin
vendor
dist
dist.bk
internal/test_data
scripts/local

View file

@ -2,9 +2,12 @@ language: go
go:
- "1.13.x"
services:
- postgresql
env:
- GO111MODULE=on
install: true
script: go run *.go version
script: go test ./... && go test github.com/nicksherron/bashhub-server/internal -postgres-uri "postgres://postgres:@localhost:5432?sslmode=disable"

View file

@ -5,61 +5,41 @@ VERSION=$(shell git tag | sort --version-sort -r | head -1)
GIT_COMMIT=$(shell git rev-parse HEAD)
GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
BUILD_DATE=$(shell date '+%Y-%m-%d-%H:%M:%S')
IMAGE_NAME := "nicksherron/bashhub-server"
IMAGE_NAME="nicksherron/bashhub-server"
default: test
default: help
help:
@echo 'Management commands for bashhub-server:'
@echo
@echo 'Usage:'
@echo ' make build Compile the project.'
@echo ' make get-deps runs dep ensure, mostly used for ci.'
@echo ' make build-alpine Compile optimized for alpine linux.'
@echo ' make package Build final docker image with just the go binary inside'
@echo ' make tag Tag image created by package with latest, git commit and version'
@echo ' make docker-build Build docker image'
@echo ' make clean Clean the directory tree'
@echo ' make test Run tests on a compiled project.'
@echo ' make push Push tagged images to registry'
@echo ' make clean Clean the directory tree.'
@echo ' make test-postgres Start postgres in ephemeral docker container and run backend tests.'
@echo ' make test-all Run test and test-postgres.'
@echo
build:
@echo "building ${BIN_NAME} ${VERSION}"
@echo "GOPATH=${GOPATH}"
go build -ldflags "-X github.com/nicksherron/bashhub-server/cmd.Version=${VERSION} -X github.com/nicksherron/bashhub-server/cmd.GitCommit=${GIT_COMMIT} -X github.com/nicksherron/bashhub-server/cmd.BuildDate=${BUILD_DATE}" -o bin/${BIN_NAME}
get-deps:
dep ensure
@echo "building $(BIN_NAME) $(VERSION)"
@echo "GOPATH=$(GOPATH)"
go build -ldflags "-X github.com/nicksherron/bashhub-server/cmd.Version=$(VERSION) -X github.com/nicksherron/bashhub-server/cmd.GitCommit=$(GIT_COMMIT) -X github.com/nicksherron/bashhub-server/cmd.BuildDate=$(BUILD_DATE)" -o bin/${BIN_NAME}
docker-build:
docker build --no-cache=true --build-arg VERSION=${VERSION} --build-arg BUILD_DATE=${BUILD_DATE} --build-arg GIT_COMMIT=${GIT_COMMIT} -t $(IMAGE_NAME) .
build-alpine:
@echo "building ${BIN_NAME} ${VERSION}"
@echo "GOPATH=${GOPATH}"
go build -ldflags '-w -linkmode external -extldflags "-static" -X github.com/nicksherron/bashhub-server/cmd.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X github.com/nicksherron/bashhub-server/cmd.BuildDate=${BUILD_DATE}' -o bin/${BIN_NAME}
package:
@echo "building image ${BIN_NAME} ${VERSION} $(GIT_COMMIT)"
docker build --build-arg VERSION=${VERSION} --build-arg GIT_COMMIT=$(GIT_COMMIT) -t $(IMAGE_NAME):local .
tag:
@echo "Tagging: latest ${VERSION} $(GIT_COMMIT)"
docker tag $(IMAGE_NAME) $(IMAGE_NAME):$(GIT_COMMIT)
docker tag $(IMAGE_NAME) $(IMAGE_NAME):${VERSION}
docker tag $(IMAGE_NAME) $(IMAGE_NAME):latest
push: docker-build tag
@echo "Pushing docker image to registry: latest ${VERSION} $(GIT_COMMIT)"
docker push $(IMAGE_NAME):$(GIT_COMMIT)
docker push $(IMAGE_NAME):${VERSION}
docker push $(IMAGE_NAME):latest
clean:
@test ! -e bin/${BIN_NAME} || rm bin/${BIN_NAME}
@test ! -e bin/$(BIN_NAME) || rm bin/$(BIN_NAME)
test:
go test ./...
test-postgres:
scripts/test_postgres.sh
test-all: test test-postgres

View file

@ -54,7 +54,7 @@ Available Commands:
Flags:
-a, --addr string Ip and port to listen and serve on. (default "http://0.0.0.0:8080")
--db string DB location (sqlite or postgres)
--db string db location (sqlite or postgres)
-h, --help help for this command
--log string Set filepath for HTTP log. "" logs to stderr.

View file

@ -30,16 +30,17 @@ import (
"github.com/spf13/cobra"
)
var cfgFile string
// rootCmd represents the base command when called without any subcommands
var (
logFile string
dbPath string
addr string
rootCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
cmd.Flags().Parse(args)
checkBhEnv()
startupMessage()
internal.Run()
internal.Run(dbPath, logFile, addr)
},
}
)
@ -54,9 +55,9 @@ func Execute() {
func init() {
cobra.OnInitialize()
rootCmd.PersistentFlags().StringVar(&internal.LogFile, "log", "", `Set filepath for HTTP log. "" logs to stderr`)
rootCmd.PersistentFlags().StringVar(&internal.DbPath, "db", dbPath(), "DB location (sqlite or postgres)")
rootCmd.PersistentFlags().StringVarP(&internal.Addr, "addr", "a", listenAddr(), "Ip and port to listen and serve on")
rootCmd.PersistentFlags().StringVar(&logFile, "log", "", `Set filepath for HTTP log. "" logs to stderr`)
rootCmd.PersistentFlags().StringVar(&dbPath, "db", sqlitePath(), "db location (sqlite or postgres)")
rootCmd.PersistentFlags().StringVarP(&addr, "addr", "a", listenAddr(), "Ip and port to listen and serve on")
}
@ -74,10 +75,10 @@ func startupMessage() {
\__ \ __/ | \ V / __/ |
|___/\___|_| \_/ \___|_|
`, Version, internal.Addr)
`, Version, addr)
color.HiGreen(banner)
fmt.Print("\n")
log.Printf("Listening and serving HTTP on %v", internal.Addr)
log.Printf("Listening and serving HTTP on %v", addr)
fmt.Print("\n")
}
@ -92,7 +93,7 @@ func listenAddr() string {
}
func dbPath() string {
func sqlitePath() string {
dbFile := "data.db"
f := filepath.Join(appDir(), dbFile)
return f
@ -118,7 +119,7 @@ func checkBhEnv() {
msg := fmt.Sprintf(`
WARNING: BH_URL is set to https://bashhub.com on this machine
If you will be running bashhub-client locally be sure to add
export BH_URL=%v to your .bashrc or .zshrc`, internal.Addr)
fmt.Println(msg, "\n")
export BH_URL=%v to your .bashrc or .zshrc`, addr)
fmt.Println(msg)
}
}

View file

@ -31,6 +31,7 @@ import (
"github.com/cheggaaa/pb/v3"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
)
type cList struct {
@ -55,31 +56,33 @@ var (
dstToken string
sysRegistered bool
workers int
unique bool
limit int
wg sync.WaitGroup
wgSrc sync.WaitGroup
cmdList commandsList
transferCmd = &cobra.Command{
Use: "transfer",
Short: "Transfer bashhub history from one server to another",
Run: func(cmd *cobra.Command, args []string) {
cmd.Flags().Parse(args)
switch {
case srcUser == "":
_ = cmd.Usage()
fmt.Print("\n\n")
log.Fatal("--src-user can't be blank")
case srcPass == "":
_ = cmd.Usage()
fmt.Print("\n\n")
log.Fatal("--src-pass can't be blank")
log.Fatal("src-user can't be blank")
case dstUser == "":
_ = cmd.Usage()
fmt.Print("\n\n")
log.Fatal("--dst-user can't be blank")
case dstPass == "":
_ = cmd.Usage()
fmt.Print("\n\n")
log.Fatal("--dst-pass can't be blank")
case srcPass == "" || dstPass == "":
if srcPass == "" {
srcPass = credentials("source")
}
if dstPass == "" {
dstPass = credentials("destination")
}
}
if workers > 10 && srcURL == "https://bashhub.com" {
@ -88,30 +91,7 @@ var (
than 10 when transferring from https://bashhub.com`)
fmt.Print(msg, "\n\n")
}
sysRegistered = false
srcToken = getToken(srcURL, srcUser, srcPass)
sysRegistered = false
dstToken = getToken(dstURL, dstUser, dstPass)
cmdList = getCommandList()
counter := 0
if !progress {
bar = pb.ProgressBarTemplate(barTemplate).Start(len(cmdList)).SetMaxWidth(70)
bar.Set("message", "transferring ")
}
client := &http.Client{}
// ignore http errors. We try and recover them
log.SetOutput(nil)
for _, v := range cmdList {
wg.Add(1)
counter++
go commandLookup(v.UUID, client, 0)
if counter > workers {
wg.Wait()
counter = 0
}
}
wg.Wait()
run()
},
}
)
@ -120,14 +100,69 @@ func init() {
rootCmd.AddCommand(transferCmd)
transferCmd.PersistentFlags().StringVar(&srcURL, "src-url", "https://bashhub.com", "source url ")
transferCmd.PersistentFlags().StringVar(&srcUser, "src-user", "", "source username")
transferCmd.PersistentFlags().StringVar(&srcPass, "src-pass", "", "source password")
transferCmd.PersistentFlags().StringVar(&srcPass, "src-pass", "", "source password (default is password prompt)")
transferCmd.PersistentFlags().StringVar(&dstURL, "dst-url", "http://localhost:8080", "destination url")
transferCmd.PersistentFlags().StringVar(&dstUser, "dst-user", "", "destination username")
transferCmd.PersistentFlags().StringVar(&dstPass, "dst-pass", "", "destination password")
transferCmd.PersistentFlags().StringVar(&dstPass, "dst-pass", "", "destination password (default is password prompt)")
transferCmd.PersistentFlags().BoolVarP(&progress, "quiet", "q", false, "don't show progress bar")
transferCmd.PersistentFlags().IntVarP(&workers, "workers", "w", 10, "max number of concurrent requests")
}
transferCmd.PersistentFlags().BoolVarP(&unique, "unique", "u", true, "don't include duplicate commands")
transferCmd.PersistentFlags().IntVarP(&limit, "number", "n", 10000, "limit number of commands to transfer")
}
func credentials(s string) string {
fmt.Printf("\nEnter %s password: ", s)
bytePassword, err := terminal.ReadPassword(0)
if err != nil {
check(err)
}
password := string(bytePassword)
return strings.TrimSpace(password)
}
func run() {
sysRegistered = false
srcToken = getToken(srcURL, srcUser, srcPass)
sysRegistered = false
dstToken = getToken(dstURL, dstUser, dstPass)
cmdList = getCommandList()
counter := 0
if !progress {
bar = pb.ProgressBarTemplate(barTemplate).Start(len(cmdList)).SetMaxWidth(70)
bar.Set("message", "transferring ")
}
fmt.Print("\nstarting transfer...\n\n")
client := &http.Client{}
pipe := make(chan []byte)
go func() {
for {
select {
case data := <-pipe:
wgSrc.Add(1)
go srcSend(data, client)
}
}
}()
// ignore http errors. We try and recover them
log.SetOutput(nil)
for _, v := range cmdList {
wg.Add(1)
counter++
go commandLookup(v.UUID, client, 0, pipe)
if counter > workers {
wg.Wait()
counter = 0
}
}
wg.Wait()
if !progress {
bar.Finish()
}
wgSrc.Wait()
}
func sysRegister(mac string, site string, user string, pass string) string {
var token string
@ -166,7 +201,8 @@ func sysRegister(mac string, site string, user string, pass string) string {
}
j := make(map[string]interface{})
json.Unmarshal(buf, &j)
err = json.Unmarshal(buf, &j)
check(err)
if len(j) == 0 {
log.Fatal("login failed for ", site)
@ -186,6 +222,7 @@ func sysRegister(mac string, site string, user string, pass string) string {
"hostname": host,
"mac": mac,
}
payloadBytes, err := json.Marshal(sys)
if err != nil {
log.Fatal(err)
@ -250,9 +287,10 @@ func getToken(site string, user string, pass string) string {
}
j := make(map[string]interface{})
json.Unmarshal(buf, &j)
err = json.Unmarshal(buf, &j)
check(err)
if len(j) == 0 {
if len(j) == 0 || resp.StatusCode == 401 {
log.Fatal("login failed for ", site)
}
@ -260,7 +298,7 @@ func getToken(site string, user string, pass string) string {
}
func getCommandList() commandsList {
u := strings.TrimSpace(srcURL) + "/api/v1/command/search?unique=true&limit=1000000"
u := strings.TrimSpace(srcURL) + fmt.Sprintf("/api/v1/command/search?unique=%v&limit=%v", unique, limit)
req, err := http.NewRequest("GET", u, nil)
if err != nil {
log.Fatal(err)
@ -291,16 +329,18 @@ func getCommandList() commandsList {
return result
}
func commandLookup(uuid string, client *http.Client, retries int) {
func commandLookup(uuid string, client *http.Client, retries int, pipe chan []byte) {
defer func() {
wg.Done()
if r := recover(); r != nil {
mem := strings.Contains(fmt.Sprintf("%v", r), "runtime error: invalid memory address")
eof := strings.Contains(fmt.Sprintf("%v", r), "EOF")
if mem || eof {
if retries < 10 {
retries++
commandLookup(uuid, client, retries)
wg.Add(1)
go commandLookup(uuid, client, retries, pipe)
} else {
log.SetOutput(os.Stderr)
log.Println("ERROR: failed over 10 times looking up command from source with uuid: ", uuid)
@ -334,7 +374,7 @@ func commandLookup(uuid string, client *http.Client, retries int) {
if err != nil {
panic(err)
}
srcSend(body, client)
pipe <- body
}
func srcSend(data []byte, client *http.Client) {
@ -342,7 +382,7 @@ func srcSend(data []byte, client *http.Client) {
if !progress {
bar.Add(1)
}
wg.Done()
wgSrc.Done()
}()
body := bytes.NewReader(data)
@ -364,3 +404,9 @@ func srcSend(data []byte, client *http.Client) {
defer resp.Body.Close()
}
func check(err error) {
if err != nil {
log.Fatal(err)
}
}

322
cmd/transfer_test.go Normal file
View file

@ -0,0 +1,322 @@
/*
*
* Copyright © 2020 nicksherron <nsherron90@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package cmd
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"os/exec"
"path/filepath"
"sync"
"syscall"
"testing"
"time"
"github.com/google/uuid"
"github.com/icrowley/fake"
"github.com/magiconair/properties/assert"
"github.com/nicksherron/bashhub-server/internal"
)
var (
testWork bool
testDir string
src *exec.Cmd
dst *exec.Cmd
sessionStartTime int64
commandsN int
)
func init() {
flag.StringVar(&srcURL, "src-url", "http://localhost:55555", "source url ")
flag.StringVar(&srcUser, "src-user", "tester", "source username")
flag.StringVar(&srcPass, "src-pass", "tester", "source password")
flag.StringVar(&dstURL, "dst-url", "http://localhost:55556", "destination url")
flag.StringVar(&dstUser, "dst-user", "tester", "destination username")
flag.StringVar(&dstPass, "dst-pass", "tester", "destination password")
flag.IntVar(&workers, "workers", 10, "max number of concurrent requests")
flag.IntVar(&commandsN, "number", 200, "number of commmands to use for test")
flag.BoolVar(&testWork, "testwork", false, "don't remove sqlite db and server log when done and print location")
}
func startServer(cmd string, args []string, writer io.Writer) (p *exec.Cmd, err error) {
cwd, err := os.Getwd()
if err != nil {
check(err)
}
parent := filepath.Dir(cwd)
if cmd, err = exec.LookPath(cmd); err == nil {
var procAttr os.ProcAttr
procAttr.Dir = parent
procAttr.Files = []*os.File{os.Stdin,
os.Stdout, os.Stderr}
p := exec.Command(cmd, args...)
p.Dir = parent
p.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
p.Stderr = writer
return p, nil
}
return nil, err
}
func startSrc() (*exec.Cmd, error) {
srcDB := filepath.Join(testDir, "src.db")
srcLog := filepath.Join(testDir, "src-server.log")
srcErr := filepath.Join(testDir, "src-stderr.log")
srcArgs := []string{"run", ".", "-a", srcURL, "--db", srcDB, "--log", srcLog}
f, err := os.Create(srcErr)
check(err)
return startServer("go", srcArgs, f)
}
func startDst() (*exec.Cmd, error) {
dstDB := filepath.Join(testDir, "dst.db")
dstLog := filepath.Join(testDir, "dst-server.log")
srcErr := filepath.Join(testDir, "dst-stderr.log")
dstArgs := []string{"run", ".", "-a", dstURL, "--db", dstDB, "--log", dstLog}
f, err := os.Create(srcErr)
check(err)
return startServer("go", dstArgs, f)
}
func TestMain(m *testing.M) {
flag.Parse()
defer cleanup()
var err error
testDir, err = ioutil.TempDir("", "bashhub-server-test-")
check(err)
src, err = startSrc()
check(err)
err = src.Start()
check(err)
dst, err = startDst()
check(err)
err = dst.Start()
check(err)
tries := 0
for {
if ping(srcURL) == nil && ping(dstURL) == nil {
break
}
tries++
if tries > 10 {
log.Fatal("failed connecting to servers after 10 attempts")
}
time.Sleep(2 * time.Second)
}
createUser(srcURL, srcUser, srcPass)
createUser(dstURL, dstUser, dstPass)
m.Run()
}
func ping(u string) error {
_, err := http.Get(fmt.Sprintf("%v/ping", u))
if err != nil {
return err
}
return nil
}
func createUser(u string, user string, pass string) {
auth := map[string]interface{}{
"email": "foo@gmail.com",
"Username": user,
"password": pass,
}
payloadBytes, err := json.Marshal(auth)
if err != nil {
log.Fatal(err)
}
body := bytes.NewReader(payloadBytes)
u = fmt.Sprintf("%v/api/v1/user", u)
req, err := http.NewRequest("POST", u, body)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
defer resp.Body.Close()
}
func TestCreateToken(t *testing.T) {
sysRegistered = false
srcToken = getToken(srcURL, srcUser, srcPass)
if srcToken == "" {
t.Fatal("srcToken token is blank")
}
sysRegistered = false
dstToken = getToken(dstURL, dstUser, dstPass)
if dstToken == "" {
t.Fatal("dstToken token is blank")
}
}
func commandInsert() {
counter := 0
sessionStartTime = time.Now().Unix() * 1000
var w sync.WaitGroup
for i := 0; i < commandsN; i++ {
w.Add(1)
counter++
go func() {
defer w.Done()
var tc internal.Command
uid, err := uuid.NewRandom()
check(err)
tc.Command = fake.Words()
tc.Path = "/dev/null"
tc.Created = time.Now().Unix() * 1000
tc.Uuid = uid.String()
tc.ExitStatus = 0
tc.SystemName = "system"
tc.SessionID = "1000"
tc.User.Username = srcUser
tc.ProcessStartTime = sessionStartTime
payloadBytes, err := json.Marshal(&tc)
check(err)
body := bytes.NewReader(payloadBytes)
req, err := http.NewRequest("POST", fmt.Sprintf("%v/api/v1/command", srcURL), body)
req.Header.Set("Content-Type", "application/json")
req.Header.Add("Authorization", srcToken)
resp, err := http.DefaultClient.Do(req)
defer resp.Body.Close()
}()
if counter > workers {
w.Wait()
counter = 0
}
}
w.Wait()
}
func TestCommandList(t *testing.T) {
commandInsert()
cmdList = getCommandList()
if len(cmdList) == 0 {
t.Fatal("command list is empty")
}
}
func TestTransfer(t *testing.T) {
progress = true
unique = false
run()
srcStatus := getStatus(t, srcURL, srcToken)
dstStatus := getStatus(t, dstURL, dstToken)
assert.Equal(t, srcStatus.TotalCommands, commandsN)
assert.Equal(t, dstStatus.TotalCommands, srcStatus.TotalCommands)
}
func BenchmarkGoInserts(b *testing.B) {
client := &http.Client{}
counter := 0
pipe := make(chan []byte)
go func() {
for {
select {
case data := <-pipe:
wgSrc.Add(1)
go srcSend(data, client)
}
}
}()
for i := 0; i < b.N; i++ {
wg.Add(1)
counter++
n := rand.Intn(commandsN)
go commandLookup(cmdList[n].UUID, client, 0, pipe)
if counter > workers {
wg.Wait()
counter = 0
}
}
wg.Wait()
if !progress {
bar.Finish()
}
wgSrc.Wait()
}
func getStatus(t *testing.T, u string, token string) internal.Status {
u = fmt.Sprintf("%v/api/v1/client-view/status?processId=1000&startTime=%v", u, sessionStartTime)
req, err := http.NewRequest("GET", u, nil)
check(err)
req.Header.Set("Content-Type", "application/json")
req.Header.Add("Authorization", token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
var status internal.Status
err = json.Unmarshal(body, &status)
if err != nil {
t.Fatal(err)
}
return status
}
func cleanup() {
defer func() {
if err := syscall.Kill(-dst.Process.Pid, syscall.SIGKILL); err != nil {
log.Println("failed to kill: ", err)
}
if err := syscall.Kill(-src.Process.Pid, syscall.SIGKILL); err != nil {
log.Println("failed to kill: ", err)
}
}()
if !testWork {
err := os.Chmod(testDir, 0777)
check(err)
err = os.RemoveAll(testDir)
check(err)
return
}
log.SetOutput(os.Stderr)
log.Println("TESTWORK=", testDir)
}

18
go.mod
View file

@ -1,17 +1,21 @@
module github.com/nicksherron/bashhub-server
go 1.13
require (
github.com/appleboy/gin-jwt/v2 v2.6.3
github.com/cheggaaa/pb/v3 v3.0.4
github.com/corpix/uarand v0.1.1 // indirect
github.com/fatih/color v1.9.0
github.com/gin-gonic/gin v1.5.0
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/google/uuid v1.1.1
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428
github.com/jinzhu/gorm v1.9.12
github.com/lib/pq v1.3.0
github.com/mattn/go-sqlite3 v2.0.1+incompatible
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd
github.com/magiconair/properties v1.8.0
github.com/manifoldco/promptui v0.7.0
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
)
go 1.13

76
go.sum
View file

@ -1,11 +1,29 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/appleboy/gin-jwt/v2 v2.6.3 h1:aK4E3DjihWEBUTjEeRnGkA5nUkmwJPL1CPonMa2usRs=
github.com/appleboy/gin-jwt/v2 v2.6.3/go.mod h1:MfPYA4ogzvOcVkRwAxT7quHOtQmVKDpTwxyUrC2DNw0=
github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/cheggaaa/pb/v3 v3.0.4 h1:QZEPYOj2ix6d5oEg63fbHmpolrnNiwjUsk+h74Yt4bM=
github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/corpix/uarand v0.1.1 h1:RMr1TWc9F4n5jiPDzFHtmaUXLKLNUFK0SgCLo4BhX/U=
github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -18,6 +36,7 @@ github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
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=
@ -37,6 +56,11 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 h1:Mo9W14pwbO9VfRe+ygqZ8dFbPpoIK1HFrG/zjTuQ+nc=
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q=
@ -48,40 +72,63 @@ github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/manifoldco/promptui v0.7.0 h1:3l11YT8tm9MnwGFQ4kETwkzpAwY2Jt9lCrumCUW4+z4=
github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw=
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -94,22 +141,26 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -118,12 +169,13 @@ google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO50
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -39,31 +39,28 @@ import (
var (
db *sql.DB
// DbPath is the postgres connection uri or the sqlite db file location to use for backend.
DbPath string
connectionLimit int
)
// DbInit initializes our db.
func dbInit() {
func dbInit(dbPath string) {
var gormdb *gorm.DB
var err error
if strings.HasPrefix(DbPath, "postgres://") {
if strings.HasPrefix(dbPath, "postgres://") {
// postgres
db, err = sql.Open("postgres", DbPath)
db, err = sql.Open("postgres", dbPath)
if err != nil {
log.Fatal(err)
}
gormdb, err = gorm.Open("postgres", DbPath)
gormdb, err = gorm.Open("postgres", dbPath)
if err != nil {
log.Fatal(err)
}
connectionLimit = 50
} else {
// sqlite
gormdb, err = gorm.Open("sqlite3", DbPath)
gormdb, err = gorm.Open("sqlite3", dbPath)
if err != nil {
log.Fatal(err)
}
@ -80,13 +77,16 @@ func dbInit() {
},
})
DbPath = fmt.Sprintf("file:%v?cache=shared&mode=rwc&_loc=auto", DbPath)
db, err = sql.Open("sqlite3_with_regex", DbPath)
dbPath = fmt.Sprintf("file:%v?cache=shared&mode=rwc&_loc=auto", dbPath)
db, err = sql.Open("sqlite3_with_regex", dbPath)
if err != nil {
log.Fatal(err)
}
db.Exec("PRAGMA journal_mode=WAL;")
_, err = db.Exec("PRAGMA journal_mode=WAL;")
if err != nil {
log.Fatal(err)
}
connectionLimit = 1
}
@ -222,7 +222,7 @@ func (cmd Command) commandInsert() int64 {
res, err := db.Exec(`
INSERT INTO commands("process_id","process_start_time","exit_status","uuid","command", "created", "path", "user_id", "system_name")
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`,
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) ON CONFLICT do nothing`,
cmd.ProcessId, cmd.ProcessStartTime, cmd.ExitStatus, cmd.Uuid, cmd.Command, cmd.Created, cmd.Path, cmd.User.ID, cmd.SystemName)
if err != nil {
log.Fatal(err)
@ -235,106 +235,107 @@ func (cmd Command) commandInsert() int64 {
}
func (cmd Command) commandGet() ([]Query, error) {
var results []Query
var rows *sql.Rows
var err error
var (
results []Query
query string
)
if cmd.Unique || cmd.Query != "" {
//postgres
if connectionLimit != 1 {
if cmd.SystemName != "" && cmd.Path != "" && cmd.Query != "" && cmd.Unique {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT * FROM (
SELECT DISTINCT ON ("command") command, "uuid", "created"
FROM commands
WHERE "user_id" = $1
AND "path" = $3
AND "system_name" = $4
AND "command" ~ $5
WHERE "user_id" = '%v'
AND "path" = '%v'
AND "system_name" = '%v'
AND "command" ~ '%v'
) c
ORDER BY "created" DESC limit $2;`, cmd.User.ID, cmd.Limit, cmd.Path, cmd.SystemName, cmd.Query)
ORDER BY "created" DESC limit '%v';`, cmd.User.ID, cmd.Path, cmd.SystemName, cmd.Query, cmd.Limit)
} else if cmd.Path != "" && cmd.Query != "" && cmd.Unique {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT * FROM (
SELECT DISTINCT ON ("command") command, "uuid", "created"
FROM commands
WHERE "user_id" = $1
AND "path" = $3
AND "command" ~ $4
WHERE "user_id" = '%v'
AND "path" = '%v'
AND "command" ~ '%v'
) c
ORDER BY "created" DESC limit $2;`, cmd.User.ID, cmd.Limit, cmd.Path, cmd.Query)
ORDER BY "created" DESC limit '%v';`, cmd.User.ID, cmd.Path, cmd.Query, cmd.Limit)
} else if cmd.SystemName != "" && cmd.Query != "" {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created"
FROM commands
WHERE "user_id" = $1
AND "system_name" = $3
AND "command" ~ $4
ORDER BY "created" DESC limit $2;`, cmd.User.ID, cmd.Limit, cmd.SystemName, cmd.Query)
WHERE "user_id" = '%v'
AND "system_name" = '%v'
AND "command" ~ '%v'
ORDER BY "created" DESC limit '%v';`, cmd.User.ID, cmd.SystemName, cmd.Query, cmd.Limit)
} else if cmd.Path != "" && cmd.Query != "" {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created"
FROM commands
WHERE "user_id" = $1
AND "path" = $3
AND "command" ~ $4
ORDER BY "created" DESC limit $2;`, cmd.User.ID, cmd.Limit, cmd.Path, cmd.Query)
WHERE "user_id" = '%v'
AND "path" = '%v'
AND "command" ~ '%v'
ORDER BY "created" DESC limit '%v';`, cmd.User.ID, cmd.Path, cmd.Query, cmd.Limit)
} else if cmd.SystemName != "" && cmd.Unique {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT * FROM (
SELECT DISTINCT ON ("command") command, "uuid", "created"
FROM commands
WHERE "user_id" = $1
AND "system_name" = $3
WHERE "user_id" = '%v'
AND "system_name" = '%v'
) c
ORDER BY "created" DESC limit $2;`, cmd.User.ID, cmd.Limit, cmd.SystemName)
ORDER BY "created" DESC limit '%v';`, cmd.User.ID, cmd.SystemName, cmd.Limit)
} else if cmd.Path != "" && cmd.Unique {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT * FROM (
SELECT DISTINCT ON ("command") command, "uuid", "created"
FROM commands
WHERE "user_id" = $1
AND "path" = $3
WHERE "user_id" = '%v'
AND "path" = '%v'
) c
ORDER BY "created" DESC limit $2;`, cmd.User.ID, cmd.Limit, cmd.Path)
ORDER BY "created" DESC limit '%v';`, cmd.User.ID, cmd.Path, cmd.Limit)
} else if cmd.Query != "" && cmd.Unique {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT * FROM (
SELECT DISTINCT ON ("command") command, "uuid", "created"
FROM commands
WHERE "user_id" = $1
AND "command" ~ $3
WHERE "user_id" = '%v'
AND "command" ~ '%v'
) c
ORDER BY "created" DESC limit $2;`, cmd.User.ID, cmd.Limit, cmd.Query)
ORDER BY "created" DESC limit '%v';`, cmd.User.ID, cmd.Query, cmd.Limit)
} else if cmd.Query != "" {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created"
FROM commands
WHERE "user_id" = $1
AND "command" ~ $3
ORDER BY "created" DESC limit $2;`, cmd.User.ID, cmd.Limit, cmd.Query)
WHERE "user_id" = '%v'
AND "command" ~ '%v'
ORDER BY "created" DESC limit '%v';`, cmd.User.ID, cmd.Query, cmd.Limit)
} else {
// unique
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT * FROM (
SELECT DISTINCT ON ("command") command, "uuid", "created"
FROM commands
WHERE "user_id" = $1
WHERE "user_id" = '%v'
) c
ORDER BY "created" DESC limit $2;`, cmd.User.ID, cmd.Limit)
ORDER BY "created" DESC limit '%v';`, cmd.User.ID, cmd.Limit)
}
} else {
// sqlite
if cmd.SystemName != "" && cmd.Path != "" && cmd.Query != "" && cmd.Unique {
// Have to use fmt.Sprintf to build queries where sqlite regexp function is used because of single quotes. Haven't found any other work around.
query := fmt.Sprintf(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = '%v'
AND "path" = '%v'
@ -342,120 +343,108 @@ func (cmd Command) commandGet() ([]Query, error) {
AND "command" regexp '%v'
GROUP BY "command" ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.Path, cmd.SystemName, cmd.Query, cmd.Limit)
rows, err = db.Query(query)
} else if cmd.SystemName != "" && cmd.Query != "" && cmd.Unique {
query := fmt.Sprintf(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = '%v'
AND "system_name" = '%v'
AND "command" regexp '%v'
GROUP BY "command" ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.SystemName, cmd.Query, cmd.Limit)
rows, err = db.Query(query)
} else if cmd.Path != "" && cmd.Query != "" && cmd.Unique {
query := fmt.Sprintf(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = '%v'
AND "path" = '%v'
AND "command" regexp '%v'
GROUP BY "command" ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.Path, cmd.Query, cmd.Limit)
rows, err = db.Query(query)
} else if cmd.SystemName != "" && cmd.Query != "" {
query := fmt.Sprintf(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = '%v'
AND "system_name" = %v'
AND "command" regexp %v'
AND "system_name" = '%v'
AND "command" regexp '%v'
ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.SystemName, cmd.Query, cmd.Limit)
rows, err = db.Query(query)
} else if cmd.Path != "" && cmd.Query != "" {
query := fmt.Sprintf(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = '%v'
AND "path" = %v'
AND "command" regexp %v'
AND "path" = '%v'
AND "command" regexp '%v'
ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.Path, cmd.Query, cmd.Limit)
rows, err = db.Query(query)
} else if cmd.SystemName != "" && cmd.Unique {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = $1
AND "system_name" = $2
GROUP BY "command" ORDER BY "created" DESC limit $3`, cmd.User.ID, cmd.SystemName, cmd.Limit)
WHERE "user_id" = '%v'
AND "system_name" = '%v'
GROUP BY "command" ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.SystemName, cmd.Limit)
} else if cmd.Path != "" && cmd.Unique {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = $1
AND "path" = $2
GROUP BY "command" ORDER BY "created" DESC limit $3`, cmd.User.ID, cmd.Path, cmd.Limit)
WHERE "user_id" = '%v'
AND "path" = '%v'
GROUP BY "command" ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.Path, cmd.Limit)
} else if cmd.Query != "" && cmd.Unique {
query := fmt.Sprintf(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = '%v'
AND "command" regexp '%v'
GROUP BY "command" ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.Query, cmd.Limit)
rows, err = db.Query(query)
} else if cmd.Query != "" {
query := fmt.Sprintf(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = '%v'
AND "command" regexp'%v'
ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.Query, cmd.Limit)
rows, err = db.Query(query)
} else {
// unique
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created"
FROM commands
WHERE "user_id" = $1
GROUP BY "command" ORDER BY "created" DESC limit $2;`, cmd.User.ID, cmd.Limit)
WHERE "user_id" = '%v'
GROUP BY "command" ORDER BY "created" DESC limit '%v';`, cmd.User.ID, cmd.Limit)
}
}
} else {
if cmd.Path != "" {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = $1
AND "path" = $3
ORDER BY "created" DESC limit $2`, cmd.User.ID, cmd.Limit, cmd.Path)
WHERE "user_id" = '%v'
AND "path" = '%v'
ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.Path, cmd.Limit)
} else if cmd.SystemName != "" {
rows, err = db.Query(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = $1
AND "system_name" = $3
ORDER BY "created" DESC limit $2`, cmd.User.ID, cmd.Limit, cmd.SystemName)
query = fmt.Sprintf(`SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = '%v'
AND "system_name" = '%v'
ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.SystemName, cmd.Limit)
} else {
rows, err = db.Query(`
query = fmt.Sprintf(`
SELECT "command", "uuid", "created" FROM commands
WHERE "user_id" = $1
ORDER BY "created" DESC limit $2`, cmd.User.ID, cmd.Limit)
WHERE "user_id" = '%v'
ORDER BY "created" DESC limit '%v'`, cmd.User.ID, cmd.Limit)
}
}
rows, err := db.Query(query)
if err != nil {
return []Query{}, nil
return []Query{}, err
}
defer rows.Close()
for rows.Next() {
var result Query
err = rows.Scan(&result.Command, &result.Uuid, &result.Created)
if err != nil {
return []Query{}, nil
return []Query{}, err
}
results = append(results, result)
}
@ -466,11 +455,11 @@ func (cmd Command) commandGet() ([]Query, error) {
func (cmd Command) commandGetUUID() (Query, error) {
var result Query
err := db.QueryRow(`
SELECT "command","path", "created" , "uuid", "exit_status", "system_name"
SELECT "command","path", "created" , "uuid", "exit_status", "system_name", "process_id"
FROM commands
WHERE "uuid" = $1
AND "user_id" = $2`, cmd.Uuid, cmd.User.ID).Scan(&result.Command, &result.Path, &result.Created, &result.Uuid,
&result.ExitStatus, &result.SystemName)
&result.ExitStatus, &result.SystemName, &result.SessionID)
if err != nil {
return Query{}, err
}
@ -544,20 +533,22 @@ func (sys System) systemGet() (System, error) {
func (status Status) statusGet() (Status, error) {
var err error
if connectionLimit != 1 {
err = db.QueryRow(`select
err = db.QueryRow(`
select
( select count(*) from commands where user_id = $1) as totalCommands,
( select count(distinct process_id) from commands where user_id = $1) as totalSessions,
( select count(distinct system_name) from commands where user_id = $1) as totalSystems,
( select count (*) from commands where to_timestamp(cast(created/1000 as bigint))::date = now()::date and user_id = $1) as totalCommandsToday,
( select count(*) from systems where user_id = $1) as totalSystems,
( select count(*) from commands where to_timestamp(cast(created/1000 as bigint))::date = now()::date and user_id = $1) as totalCommandsToday,
( select count(*) from commands where process_id = $2) as sessionTotalCommands`,
status.User.ID, status.ProcessID).Scan(
&status.TotalCommands, &status.TotalSessions, &status.TotalSystems,
&status.TotalCommandsToday, &status.SessionTotalCommands)
} else {
err = db.QueryRow(`select
err = db.QueryRow(`
select
( select count(*) from commands where user_id = $1) as totalCommands,
( select count(distinct process_id) from commands where user_id = $1) as totalSessions,
( select count(distinct system_name) from commands where user_id = $1) as totalSystems,
( select count(*) from systems where user_id = $1) as totalSystems,
( select count(*) from commands where date(created/1000, 'unixepoch') = date('now') and user_id = $1) as totalCommandsToday,
( select count(*) from commands where process_id = $2) as sessionTotalCommands`,
status.User.ID, status.ProcessID).Scan(
@ -570,14 +561,13 @@ func (status Status) statusGet() (Status, error) {
return status, err
}
func importCommands(imp Import) {
_, err := db.Exec(`INSERT INTO commands
("command", "path", "created", "uuid", "exit_status",
"system_name", "session_id", "user_id" )
VALUES ($1,$2,$3,$4,$5,$6,$7,(select "id" from users where "username" = $8)) ON CONFLICT do nothing`,
imp.Command, imp.Path, imp.Created, imp.Uuid, imp.ExitStatus,
imp.SystemName, imp.SessionID, imp.Username)
func importCommands(imp Import) error {
_, err := db.Exec(`
INSERT INTO commands ("command", "path", "created", "uuid", "exit_status","system_name", "session_id", "user_id" )
VALUES ($1,$2,$3,$4,$5,$6,$7 ,(select "id" from users where "username" = $8)) ON CONFLICT do nothing`,
imp.Command, imp.Path, imp.Created, imp.Uuid, imp.ExitStatus, imp.SystemName, imp.SessionID, imp.Username)
if err != nil {
log.Println(err)
return err
}
return nil
}

View file

@ -20,6 +20,8 @@ package internal
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
@ -103,42 +105,43 @@ type Import Query
var (
// Addr is the listen and server address for our server (gin)
Addr string
//Addr string
// LogFile is the log file location for http logging. Default is stderr.
LogFile string
//logFile string
config Config
)
func getLog() *os.File {
if LogFile != "" {
f, err := os.Create(LogFile)
func getLog(logFile string) io.Writer {
switch {
case logFile == "/dev/null":
return ioutil.Discard
case logFile != "":
f, err := os.Create(logFile)
if err != nil {
log.Fatal(err)
}
return f
}
default:
return os.Stderr
}
}
// LoggerWithFormatter instance a Logger middleware with the specified log format function.
func loggerWithFormatterWriter(f gin.LogFormatter) gin.HandlerFunc {
func loggerWithFormatterWriter(logFile string, f gin.LogFormatter) gin.HandlerFunc {
return gin.LoggerWithConfig(gin.LoggerConfig{
Formatter: f,
Output: getLog(),
Output: getLog(logFile),
})
}
// Run starts server
func Run() {
// Initialize backend
dbInit()
// configure routes and middleware
func setupRouter(dbPath string, logFile string) *gin.Engine {
dbInit(dbPath)
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
r.Use(loggerWithFormatterWriter(func(param gin.LogFormatterParams) string {
r.Use(loggerWithFormatterWriter(logFile, func(param gin.LogFormatterParams) string {
return fmt.Sprintf("[BASHHUB-SERVER] %v | %3d | %13v | %15s | %-7s %s\n",
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
param.StatusCode,
@ -278,10 +281,9 @@ func Run() {
command.Limit = num
}
}
command.Unique = false
if c.Query("unique") == "true" {
command.Unique = true
} else {
command.Unique = false
}
command.Path = c.Query("path")
command.Query = c.Query("query")
@ -452,13 +454,26 @@ func Run() {
return
}
claims := jwt.ExtractClaims(c)
imp.Username = claims["username"].(string)
importCommands(imp)
user := claims["username"].(string)
imp.Username = user
err = importCommands(imp)
if err != nil {
log.Println(err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.AbortWithStatus(http.StatusOK)
})
Addr = strings.ReplaceAll(Addr, "http://", "")
err = r.Run(Addr)
return r
}
// Run starts server
func Run(dbFile string, logFile string, addr string) {
r := setupRouter(dbFile, logFile)
addr = strings.ReplaceAll(addr, "http://", "")
err := r.Run(addr)
if err != nil {
fmt.Println("Error: \t", err)

387
internal/server_test.go Normal file
View file

@ -0,0 +1,387 @@
/*
*
* Copyright © 2020 nicksherron <nsherron90@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package internal
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
var (
testWork = flag.Bool("testwork", false, "don't remove sqlite db and server log when done and print location")
postgres = flag.String("postgres-uri", "", "postgres uri to use for postgres tests")
sessionStartTime int64
pid string
dir string
router *gin.Engine
sysRegistered bool
jwtToken string
testDir string
system sysStruct
)
type sysStruct struct {
user string
pass string
mac int
email string
systemName string
host string
}
func TestMain(m *testing.M) {
flag.Parse()
defer dirCleanup()
var err error
testDir, err = ioutil.TempDir("", "bashhub-server-test-")
check(err)
dir = "/tmp/foo"
dbPath := filepath.Join(testDir, "test.db")
logFile := filepath.Join(testDir, "server.log")
log.Print("sqlite tests")
router = setupRouter(dbPath, logFile)
system = sysStruct{
user: "tester",
pass: "tester",
mac: 888888888888888,
email: "test@email.com",
host: "some-host",
}
m.Run()
if *postgres != "" {
log.Print("postgres tests")
dbPath := *postgres
logFile := filepath.Join(testDir, "postgres-server.log")
router = setupRouter(dbPath, logFile)
m.Run()
}
}
func testRequest(method string, u string, body io.Reader) *httptest.ResponseRecorder {
w := httptest.NewRecorder()
req, _ := http.NewRequest(method, u, body)
req.Header.Set("Content-Type", "application/json")
req.Header.Add("Authorization", jwtToken)
router.ServeHTTP(w, req)
return w
}
func check(err error) {
if err != nil {
log.Fatal(err)
}
}
func createUser(t *testing.T) {
auth := map[string]interface{}{
"email": system.email,
"Username": system.user,
"password": system.pass,
}
payloadBytes, err := json.Marshal(auth)
if err != nil {
log.Fatal(err)
}
body := bytes.NewReader(payloadBytes)
w := testRequest("POST", "/api/v1/user", body)
assert.Equal(t, 200, w.Code)
}
func getToken(t *testing.T) string {
auth := map[string]interface{}{
"username": system.user,
"password": system.pass,
"mac": strconv.Itoa(system.mac),
}
payloadBytes, err := json.Marshal(auth)
if err != nil {
log.Fatal(err)
}
body := bytes.NewReader(payloadBytes)
w := testRequest("POST", "/api/v1/login", body)
assert.Equal(t, 200, w.Code)
buf, err := ioutil.ReadAll(w.Body)
if err != nil {
t.Fatal(err)
}
j := make(map[string]interface{})
err = json.Unmarshal(buf, &j)
check(err)
if len(j) == 0 {
t.Fatal("login failed for getToken")
}
token := fmt.Sprintf("Bearer %v", j["accessToken"])
if !sysRegistered {
// register system
return sysRegister(t, token)
}
return token
}
func sysRegister(t *testing.T, token string) string {
jwtToken = token
sysPayload := map[string]interface{}{
"clientVersion": "1.2.0",
"name": system.systemName,
"hostname": system.host,
"mac": strconv.Itoa(system.mac),
}
payloadBytes, err := json.Marshal(sysPayload)
check(err)
body := bytes.NewReader(payloadBytes)
w := testRequest("POST", "/api/v1/system", body)
assert.Equal(t, 201, w.Code)
sysRegistered = true
return getToken(t)
}
func TestToken(t *testing.T) {
createUser(t)
systems := []string{
"system-1",
"system-2",
"system-3",
}
for _, sys := range systems {
system.systemName = sys
system.mac++
sysRegistered = false
jwtToken = getToken(t)
}
}
func TestCommandInsert(t *testing.T) {
var commandTests = []Command{
{ExitStatus: 0, Command: "cat foo.txt"},
{ExitStatus: 0, Command: "ls"},
{ExitStatus: 0, Command: "pwd"},
{ExitStatus: 0, Command: "whoami"},
{ExitStatus: 0, Command: "which cat"},
{ExitStatus: 0, Command: "head foo.txt"},
{ExitStatus: 0, Command: "sed 's/fooobaar/foobar/g' somefile.txt"},
{ExitStatus: 0, Command: "curl google.com"},
{ExitStatus: 0, Command: "file /dev/null"},
{ExitStatus: 0, Command: "df -h"},
{ExitStatus: 127, Command: "catt"},
{ExitStatus: 127, Command: "cay"},
}
sessionStartTime = time.Now().Unix() * 1000
for i := 0; i < 5; i++ {
for _, tc := range commandTests {
uid, err := uuid.NewRandom()
if err != nil {
t.Fatal(err)
}
tc.ProcessId = i
tc.Path = dir
tc.Created = time.Now().Unix() * 1000
tc.ProcessStartTime = sessionStartTime
tc.Uuid = uid.String()
payloadBytes, err := json.Marshal(&tc)
if err != nil {
t.Fatal(err)
}
body := bytes.NewReader(payloadBytes)
w := testRequest("POST", "/api/v1/command", body)
assert.Equal(t, 200, w.Code)
}
}
}
func TestCommandQuery(t *testing.T) {
type queryTest struct {
query string
expect int
}
var queryTests = []queryTest{
{query: fmt.Sprintf("path=%v&unique=true&systemName=%v&query=^curl", url.QueryEscape(dir), system.systemName), expect: 1},
{query: fmt.Sprintf("path=%v&query=^curl&unique=true", url.QueryEscape(dir)), expect: 1},
{query: fmt.Sprintf("systemName=%v&query=^curl", system.systemName), expect: 5},
{query: fmt.Sprintf("path=%v&query=^curl", url.QueryEscape(dir)), expect: 5},
{query: fmt.Sprintf("systemName=%v&unique=true", system.systemName), expect: 10},
{query: fmt.Sprintf("path=%v&unique=true", url.QueryEscape(dir)), expect: 10},
{query: fmt.Sprintf("path=%v", url.QueryEscape(dir)), expect: 50},
{query: fmt.Sprintf("systemName=%v", system.systemName), expect: 50},
{query: "query=^curl&unique=true", expect: 1},
{query: "query=^curl", expect: 5},
{query: "unique=true", expect: 10},
{query: "limit=1", expect: 1},
}
for _, v := range queryTests {
func() {
u := fmt.Sprintf("/api/v1/command/search?%v", v.query)
w := testRequest("GET", u, nil)
assert.Equal(t, 200, w.Code)
b, err := ioutil.ReadAll(w.Body)
if err != nil {
t.Fatal(err)
}
var data []Query
err = json.Unmarshal(b, &data)
if err != nil {
t.Fatal(err)
}
if v.expect != len(data) {
t.Fatalf("expected: %v, got: %v -- query: %v ", v.expect, len(data), v.query)
}
assert.Contains(t, system.systemName, data[0].SystemName)
assert.Contains(t, dir, data[0].Path)
}()
}
}
func TestCommandFindDelete(t *testing.T) {
var record Command
func() {
v := url.Values{}
v.Add("limit", "1")
v.Add("unique", "true")
u := fmt.Sprintf("/api/v1/command/search?%v", v.Encode())
w := testRequest("GET", u, nil)
assert.Equal(t, 200, w.Code)
b, err := ioutil.ReadAll(w.Body)
if err != nil {
t.Fatal(err)
}
var data []Command
err = json.Unmarshal(b, &data)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, 1, len(data))
record = data[0]
}()
func() {
u := fmt.Sprintf("/api/v1/command/%v", record.Uuid)
w := testRequest("GET", u, nil)
assert.Equal(t, 200, w.Code)
b, err := ioutil.ReadAll(w.Body)
if err != nil {
t.Fatal(err)
}
var data Command
err = json.Unmarshal(b, &data)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, record.Uuid, data.Uuid)
pid = data.SessionID
}()
func() {
u := fmt.Sprintf("/api/v1/command/%v", record.Uuid)
w := testRequest("DELETE", u, nil)
assert.Equal(t, 200, w.Code)
}()
func() {
w := testRequest("GET", "/api/v1/command/search?", nil)
assert.Equal(t, 200, w.Code)
b, err := ioutil.ReadAll(w.Body)
if err != nil {
t.Fatal(err)
}
var data []Command
err = json.Unmarshal(b, &data)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, 49, len(data))
}()
}
func TestStatus(t *testing.T) {
u := fmt.Sprintf("/api/v1/client-view/status?processId=%v&startTime=%v", pid, sessionStartTime)
w := testRequest("GET", u, nil)
assert.Equal(t, 200, w.Code)
b, err := ioutil.ReadAll(w.Body)
if err != nil {
t.Fatal(err)
}
var status Status
err = json.Unmarshal(b, &status)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, status.TotalCommands, 49)
assert.Equal(t, status.TotalSessions, 5)
assert.Equal(t, status.TotalSystems, 3)
assert.Equal(t, status.TotalCommandsToday, 49)
assert.Equal(t, status.SessionTotalCommands, 9)
}
func dirCleanup() {
if !*testWork {
err := os.Chmod(testDir, 0777)
check(err)
err = os.RemoveAll(testDir)
check(err)
return
}
log.Println("TESTWORK=", testDir)
}

View file

@ -41,7 +41,7 @@ export GO111MODULE=on
xgo \
-out="bashhub-server-${version}" \
--targets="windows/*,darwin/amd64,linux/386,linux/amd64" \
--targets="linux/arm64,linux/arm-7,linux/amd64,linux/386,darwin/amd64,windows/*" \
--dest=dist \
-ldflags "-X github.com/nicksherron/bashhub-server/cmd.Version=${version}
-X github.com/nicksherron/bashhub-server/cmd.GitCommit=${commit}
@ -56,10 +56,12 @@ sudo chown -R $USER: dist/
darwin_amd64=bashhub-server_${version}_darwin_amd64
linux_386=bashhub-server_${version}_linux_386
linux_amd64=bashhub-server_${version}_linux_amd64
linux_arm64=bashhub-server_${version}_linux_arm64
linux_arm_7=bashhub-server_${version}_linux_arm-7
windows_386=bashhub-server_${version}_windows_386
windows_amd64=bashhub-server_${version}_windows_amd64
mkdir dist/{$darwin_amd64,$linux_386,$linux_amd64,$windows_386,$windows_amd64}
mkdir dist/{$darwin_amd64,$linux_386,$linux_amd64,$linux_arm64,$linux_arm_7,$windows_386,$windows_amd64}
pushd dist
@ -72,13 +74,19 @@ mv bashhub-server-${version}-linux-386 ${linux_386}/bashhub-server \
mv bashhub-server-${version}-linux-amd64 ${linux_amd64}/bashhub-server \
&& tar czvf ${linux_amd64}.tar.gz ${linux_amd64}
mv bashhub-server-${version}-linux-arm64 ${linux_arm64}/bashhub-server \
&& tar czvf ${linux_arm64}.tar.gz ${linux_arm64}
mv bashhub-server-${version}-linux-arm-7 ${linux_arm_7}/bashhub-server \
&& tar czvf ${linux_arm_7}.tar.gz ${linux_arm_7}
mv bashhub-server-${version}-windows-4.0-386.exe ${windows_386}/bashhub-server \
&& zip -r ${windows_386}.zip ${windows_386}
mv bashhub-server-${version}-windows-4.0-amd64.exe ${windows_amd64}/bashhub-server \
&& zip -r ${windows_amd64}.zip ${windows_amd64}
rm -rf {$darwin_amd64,$linux_386,$linux_amd64,$windows_386,$windows_amd64}
rm -rf {$darwin_amd64,$linux_386,$linux_amd64,$linux_arm64,$linux_arm_7,$windows_386,$windows_amd64}
shasum -a 256 * > bashhub-server_${version}_checksums.txt

34
scripts/test_postgres.sh Executable file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Copyright © 2020 nicksherron <nsherron90@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
set -eou pipefail
CONTAINER="bashhub-postgres-test"
docker run -d --rm --name ${CONTAINER} -p 5444:5432 postgres
until [ "$(docker exec bashhub-postgres-test pg_isready \
-p 5432 -h localhost -U postgres -d postgres)" == "localhost:5432 - accepting connections" ]; do
sleep 0.1;
done;
go test github.com/nicksherron/bashhub-server/internal \
-postgres-uri "postgres://postgres:@localhost:5444?sslmode=disable"
docker stop -t 0 ${CONTAINER} & docker wait ${CONTAINER}