Merge pull request #54 from theaog/monitor_functional_ssh

mfs: add monitor functional ssh tool - GOOD WORK.
This commit is contained in:
SkyperTHC 2022-11-09 17:00:28 +00:00 committed by GitHub
commit 29c559f44c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 271 additions and 0 deletions

2
admin/monitor_ssh/.gitignore vendored Normal file

@ -0,0 +1,2 @@
mfs
run.sh

@ -0,0 +1,13 @@
VERSION := $(shell git rev-parse --short HEAD)
BUILDTIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ')
GOLDFLAGS += -s -w
GOLDFLAGS += -X main.Version=$(VERSION)
GOLDFLAGS += -X main.Buildtime=$(BUILDTIME)
GOFLAGS = -ldflags "$(GOLDFLAGS)"
build: pre
GOOS=linux GOARCH=amd64 go build $(GOFLAGS)
pre:
go mod tidy

11
admin/monitor_ssh/go.mod Normal file

@ -0,0 +1,11 @@
module mfs
go 1.19
require (
github.com/sirupsen/logrus v1.9.0
github.com/yanzay/tbot/v2 v2.2.0
golang.org/x/crypto v0.1.0
)
require golang.org/x/sys v0.2.0 // indirect

21
admin/monitor_ssh/go.sum Normal file

@ -0,0 +1,21 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yanzay/tbot/v2 v2.2.0 h1:bK1+XTwY59IskXpwtHc/ItfU2uELTTqYP3pajfBaPeM=
github.com/yanzay/tbot/v2 v2.2.0/go.mod h1:q0+8JblBq9tLAnKHdBIZsHwDvMS9TfO6mNfaAk1VrHg=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

205
admin/monitor_ssh/main.go Normal file

@ -0,0 +1,205 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"sync"
"time"
"unicode/utf8"
log "github.com/sirupsen/logrus"
"github.com/yanzay/tbot/v2"
"golang.org/x/crypto/ssh"
)
// set during compilation using ldflags
var Version string
var Buildtime string
// stores `server:secret` from -s flag
var servers = map[string]string{}
// flags
var timerFlag = flag.Duration("timer", time.Minute, "how often to connect to segfault servers")
var versionFlag = flag.Bool("version", false, "print program version")
var debugFlag = flag.Bool("debug", false, "print debug logs")
func init() {
flag.Func("s", "server:secret", func(s string) error {
split := strings.Split(s, ":")
if len(split) < 2 {
return fmt.Errorf("must provide server:secret")
}
servers[split[0]] = split[1]
return nil
})
flag.Parse()
log.SetFormatter(&log.TextFormatter{
ForceColors: true,
})
if *debugFlag {
log.SetLevel(log.DebugLevel)
log.SetReportCaller(true)
}
}
var (
// telegram bot secret key
tgKEY = mustEnv("TG_KEY")
// telegram chat id
tgCHATID = mustEnv("TG_CHATID")
)
func main() {
if *versionFlag {
fmt.Printf("%v compiled on %v from commit %v\n", os.Args[0], Buildtime, Version)
os.Exit(0)
}
log.Debugf("Telegram chat ID: %v", tgCHATID)
bot := tbot.New(tgKEY)
bot.HandleMessage("cowsay .+", func(m *tbot.Message) {
// we use cowsay to confirm supergroup chat ids
log.Printf("chat id: %v", m.Chat.ID)
text := strings.TrimPrefix(m.Text, "cowsay ")
cow := fmt.Sprintf("```\n%s\n```", cowsay(text))
bot.Client().SendMessage(m.Chat.ID, cow, tbot.OptParseModeMarkdown)
})
go func() { // run bot listener on his own thread
err := bot.Start()
if err != nil {
log.Fatal(err)
}
}()
// collects errors and sends them as message via telegram bot
var msgC = make(chan string, len(servers)) // buffered
defer close(msgC)
go func() {
for msg := range msgC {
log.Warnf("%v", msg)
_, err := bot.Client().SendMessage(tgCHATID, msg)
if err != nil {
log.Errorf("%v: %+v", err, "bah")
continue
}
}
log.Debugf("exiting...")
}()
var (
wg = &sync.WaitGroup{}
// protects `connTracker` from concurrent r/w
mu sync.Mutex
connTracker int
)
// program main loop
for {
for server, secret := range servers {
wg.Add(1)
go func(server, secret string) {
defer func() {
wg.Done()
mu.Lock()
connTracker++
mu.Unlock()
}()
err := checkServer(server, secret)
if err != nil {
log.Debug(err)
msgC <- err.Error()
}
}(server, secret)
}
log.Debug("waiting for routines to return")
wg.Wait()
time.Sleep(*timerFlag)
mu.Lock()
log.Infof("[#%v] successful connections", connTracker)
mu.Unlock()
}
}
var SSH_CLIENT_CONF = &ssh.ClientConfig{
Timeout: time.Second * 5,
ClientVersion: "SSH-2.0-Segfault",
User: "root",
Auth: []ssh.AuthMethod{ssh.Password("segfault")},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
BannerCallback: func(message string) error {
return nil
},
}
func checkServer(server, secret string) error {
const SSH_PORT = "22"
client, err := ssh.Dial("tcp", server+":"+SSH_PORT, SSH_CLIENT_CONF)
if err != nil {
return fmt.Errorf("[%v] connection failed: %v", server, err)
}
session, err := client.NewSession()
if err != nil {
return fmt.Errorf("[%v] SSH session failed: %v", server, err)
}
defer session.Close()
session.Setenv("SECRET", secret)
// configure terminal mode
modes := ssh.TerminalModes{
ssh.ECHO: 0, // supress echo
}
// run terminal session
if err := session.RequestPty("xterm", 50, 80, modes); err != nil {
return fmt.Errorf("[%v] SSH request PTY failed: %v", server, err)
}
out, err := session.CombinedOutput("uptime 2>&1")
if err != nil {
return fmt.Errorf("[%v] SSH `uptime` command failed: %v", server, err)
}
log.Infof("[%v] %v", server, string(out))
return nil
}
// mustEnv panics if our required envs are not present.
func mustEnv(s string) string {
env := os.Getenv(s)
if env == "" {
log.Fatalf("must provide environment variable: `export %v=`", s)
}
return env
}
// for fun.
func cowsay(text string) string {
lineLen := utf8.RuneCountInString(text) + 2
topLine := fmt.Sprintf(" %s ", strings.Repeat("_", lineLen))
textLine := fmt.Sprintf("< %s >", text)
bottomLine := fmt.Sprintf(" %s ", strings.Repeat("-", lineLen))
cow := `
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
`
resp := fmt.Sprintf("%s\n%s\n%s%s", topLine, textLine, bottomLine, cow)
return resp
}

@ -0,0 +1,19 @@
# Monitor Functional SSH (mfs)
Attempts to start a SSH session (concurrently) to each of our segfault.net instances and reports on telegram any failures to connect or login.
Admins must provide `TG_KEY` and `TG_CHATID` env variables to start the tool.
```bash
$ export TG_KEY="key"
$ export TG_CHATID="12345678"
```
The servers list must be supplied via CLI flags, e.g.:
```bash
$ ./mfs \
-s de.segfault.net:secret \
-s us.segfault.net:secret \
-s it.segfault.net:secret
```
By default it checks all servers every 1 minute, you can tweak the timer e.g. `-timer 5m`