diff --git a/.gitignore b/.gitignore index b3ab68c..4fbd076 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ bin vendor dist dist.bk -internal/test_data -scripts/local +scripts/local \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 0d8a332..5d8adcd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,12 @@ language: go go: - "1.13.x" +services: + - postgresql + env: - GO111MODULE=on install: true -script: go run *.go version \ No newline at end of file +script: go test ./... && go test github.com/nicksherron/bashhub-server/internal -postgres-uri "postgres://postgres:@localhost:5432?sslmode=disable" \ No newline at end of file diff --git a/Makefile b/Makefile index a7467f9..d1feed4 100644 --- a/Makefile +++ b/Makefile @@ -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 + + + diff --git a/README.md b/README.md index d782df3..58e5a20 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/cmd/root.go b/cmd/root.go index 1673e0b..5fa3b28 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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) } } diff --git a/cmd/transfer.go b/cmd/transfer.go index f38cf74..91dec5b 100644 --- a/cmd/transfer.go +++ b/cmd/transfer.go @@ -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) + } +} diff --git a/cmd/transfer_test.go b/cmd/transfer_test.go new file mode 100644 index 0000000..6c874f6 --- /dev/null +++ b/cmd/transfer_test.go @@ -0,0 +1,322 @@ +/* + * + * Copyright © 2020 nicksherron + * + * 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) +} diff --git a/go.mod b/go.mod index d71fb1d..f005117 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index eaeec50..aa6d5a9 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/db.go b/internal/db.go index 1a3be94..78eee46 100644 --- a/internal/db.go +++ b/internal/db.go @@ -38,32 +38,29 @@ import ( ) var ( - db *sql.DB - // DbPath is the postgres connection uri or the sqlite db file location to use for backend. - DbPath string + db *sql.DB 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,22 +533,24 @@ func (sys System) systemGet() (System, error) { func (status Status) statusGet() (Status, error) { var err error if connectionLimit != 1 { - 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 commands where process_id = $2) as sessionTotalCommands`, + 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(*) 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 - ( 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 date(created/1000, 'unixepoch') = date('now') and user_id = $1) as totalCommandsToday, - ( select count(*) from commands where process_id = $2) as sessionTotalCommands`, + 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(*) 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( &status.TotalCommands, &status.TotalSessions, &status.TotalSystems, &status.TotalCommandsToday, &status.SessionTotalCommands) @@ -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 } diff --git a/internal/server.go b/internal/server.go index e74fbd7..cbbd4aa 100644 --- a/internal/server.go +++ b/internal/server.go @@ -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 - config Config + //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 } - 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, @@ -197,7 +200,7 @@ func Run() { return &User{ Username: user.Username, SystemName: user.userGetSystemName(), - ID: user.userGetID(), + ID: user.userGetID(), }, nil } fmt.Println("failed") @@ -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) diff --git a/internal/server_test.go b/internal/server_test.go new file mode 100644 index 0000000..5a4033b --- /dev/null +++ b/internal/server_test.go @@ -0,0 +1,387 @@ +/* + * + * Copyright © 2020 nicksherron + * + * 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) + +} diff --git a/scripts/build.sh b/scripts/build.sh index 7ea40cb..01332b8 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -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 diff --git a/scripts/test_postgres.sh b/scripts/test_postgres.sh new file mode 100755 index 0000000..82daf94 --- /dev/null +++ b/scripts/test_postgres.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Copyright © 2020 nicksherron +# +# 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}