base ping audit impl (SSH handshake)

master
michael 2022-09-11 05:42:17 +00:00
parent df48ee3ce6
commit 8c0bbe8eb4
4 changed files with 167 additions and 0 deletions

View File

@ -13,6 +13,7 @@ This repository contains the base chain implementation that Colinear's compute m
| :-------- | :-------- |
| Leasing & settlement module | [x/colinearcore](./x/colinearcore/README.md) |
| In-memory bid database | [x/colinearcore/memdb](./x/colinearcore/memdb/README.md) |
| Hardware provider auditing | [x/colinearcore/audit](./x/colinearcore/audit/README.md) |
## Validators

View File

@ -0,0 +1,17 @@
# Peer Auditing
The auditing mechanism contained in this folder ensures that hardware providers are running the hardware they say they are. The basic requirements will be as follows:
| Requirement | Punishment |
| :-- | :-- |
| Must stake a set amount of CLR. | N/A |
| Must complete a GPU puzzle for each declared GPU. | N/A |
| Must be online for validator pings | Staked funds slashed |
## GPU Puzzle
Work in progress.
## Validator Pings
Attempts to perform an SSH handshake using Go's builtin `crypto/ssh`. If the handshake times out, this means that practically speaking, the provider is offline.

View File

@ -0,0 +1,109 @@
package audit
import (
"errors"
"fmt"
"log"
"strconv"
"time"
"github.com/dgraph-io/badger/v2"
"golang.org/x/crypto/ssh"
)
// Maps SSH ping result channel to address that is being pinged
var pingDB *badger.DB
func MountPingDB() {
var err error
pingDB, err = badger.Open(badger.DefaultOptions("").WithInMemory(true))
if err != nil {
// must force crash here, since ping db is absolutely required
log.Fatalf("Failed to mount in-memory db: %s", err)
}
}
// Begin ping to check if SSH is running on an open port on a host.
//
// NOTE: host should include a port number.
func BeginSshPing(host string) error {
k := []byte(host)
err := pingDB.Update(func(txn *badger.Txn) error {
_, err := txn.Get(k)
if err != nil {
if !errors.Is(err, badger.ErrKeyNotFound) {
// key-not-found condition required to write
return err
} else {
goto writePing
}
}
return fmt.Errorf("already pinging host %s", host)
writePing:
if err := txn.Set(k, []byte(strconv.FormatBool(false))); err != nil {
return err
}
return nil
})
if err != nil {
return err
}
sshConfig := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{ssh.Password("pass")},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 100 * time.Second,
}
go dialSshAsync(host, sshConfig)
return nil
}
// Check on ongoing SSH ping
func CheckSshPing(host string) (bool, error) {
k := []byte(host)
var pingStatus bool
err := pingDB.View(func(txn *badger.Txn) error {
resVal, err := txn.Get(k)
if err != nil {
if !errors.Is(err, badger.ErrKeyNotFound) {
return err
} else {
return fmt.Errorf("not pinging host %s", host)
}
}
err = resVal.Value(func(val []byte) error {
pingStatus, err = strconv.ParseBool(string(val))
if err != nil {
return fmt.Errorf("failed to decode ping status for host %s: %s", host, string(val))
}
return nil
})
return err
})
if err != nil {
return false, err
}
return pingStatus, nil
}
func dialSshAsync(host string, conf *ssh.ClientConfig) {
ssh.Dial("tcp", host, conf)
pingDB.Update(func(txn *badger.Txn) error {
k := []byte(host)
return txn.Set(k, []byte(strconv.FormatBool(true)))
})
// any potential resulting error is swallowed since this is run asynchronously
}

View File

@ -0,0 +1,40 @@
package audit
import (
"testing"
"time"
)
func TestCheckSSH(t *testing.T) {
MountPingDB()
host1 := "102.1.41.3:22" // should evaluate to false
host2 := "127.0.0.1:22" // should evaluate to true
if err := BeginSshPing(host1); err != nil {
panic(err)
}
if err := BeginSshPing(host2); err != nil {
panic(err)
}
time.Sleep(5 * time.Second)
res, err := CheckSshPing(host1)
if err != nil {
panic(err)
}
if res {
panic("check host 1 - res should be false")
}
res2, err := CheckSshPing(host2)
if err != nil {
panic(err)
}
if !res2 {
panic("check host 2 - res should be true")
}
}