345 lines
8.0 KiB
Go
345 lines
8.0 KiB
Go
package store_test
|
|
|
|
import (
|
|
"math/rand"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.t-juice.club/torjus/apiary/honeypot/ssh/store"
|
|
"git.t-juice.club/torjus/apiary/models"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func testLoginAttemptStore(s store.LoginAttemptStore, t *testing.T) {
|
|
t.Run("Simple", func(t *testing.T) {
|
|
testAttempts := randomAttempts(10)
|
|
|
|
for _, attempt := range testAttempts {
|
|
if err := s.AddAttempt(attempt); err != nil {
|
|
t.Fatalf("Error adding attempt: %s", err)
|
|
}
|
|
}
|
|
|
|
all, err := s.All()
|
|
if err != nil {
|
|
t.Fatalf("Error getting all attempts: %s", err)
|
|
}
|
|
var count int
|
|
for range all {
|
|
count++
|
|
}
|
|
if count != len(testAttempts) {
|
|
t.Errorf("All returned wrong amount. Got %d want %d", count, len(testAttempts))
|
|
}
|
|
stats, err := s.Stats(store.LoginStatsTotals, 1)
|
|
if err != nil {
|
|
t.Errorf("Stats returned error: %s", err)
|
|
}
|
|
for _, stat := range stats {
|
|
if stat.Name == "TotalLoginAttempts" && stat.Count != len(testAttempts) {
|
|
t.Errorf("Stats for total attempts is wrong. Got %d want %d", stat.Count, len(testAttempts))
|
|
}
|
|
}
|
|
})
|
|
t.Run("Query", func(t *testing.T) {
|
|
testAttempts := []*models.LoginAttempt{
|
|
{
|
|
Date: time.Now(),
|
|
RemoteIP: net.ParseIP("127.0.0.1"),
|
|
Username: "corndog",
|
|
Password: "corndog",
|
|
},
|
|
{
|
|
Date: time.Now(),
|
|
RemoteIP: net.ParseIP("127.0.0.1"),
|
|
Username: "corndog",
|
|
Password: "c0rnd0g",
|
|
},
|
|
{
|
|
Date: time.Now(),
|
|
RemoteIP: net.ParseIP("10.0.0.1"),
|
|
Username: "root",
|
|
Password: "password",
|
|
},
|
|
{
|
|
Date: time.Now(),
|
|
RemoteIP: net.ParseIP("10.0.0.2"),
|
|
Username: "ubnt",
|
|
Password: "password",
|
|
},
|
|
}
|
|
|
|
for _, attempt := range testAttempts {
|
|
err := s.AddAttempt(attempt)
|
|
if err != nil {
|
|
t.Fatalf("Unable to add attempt: %s", err)
|
|
}
|
|
}
|
|
testCases := []struct {
|
|
Name string
|
|
Query store.AttemptQuery
|
|
ExpectedResult []models.LoginAttempt
|
|
}{
|
|
{
|
|
Name: "password one result",
|
|
Query: store.AttemptQuery{QueryType: store.AttemptQueryTypePassword, Query: "corndog"},
|
|
ExpectedResult: []models.LoginAttempt{
|
|
{
|
|
RemoteIP: net.ParseIP("127.0.0.1"),
|
|
Username: "corndog",
|
|
Password: "corndog",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "username one result",
|
|
Query: store.AttemptQuery{QueryType: store.AttemptQueryTypeUsername, Query: "root"},
|
|
ExpectedResult: []models.LoginAttempt{
|
|
{
|
|
RemoteIP: net.ParseIP("10.0.0.1"),
|
|
Username: "root",
|
|
Password: "password",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "username two results",
|
|
Query: store.AttemptQuery{QueryType: store.AttemptQueryTypeUsername, Query: "corndog"},
|
|
ExpectedResult: []models.LoginAttempt{
|
|
{
|
|
RemoteIP: net.ParseIP("127.0.0.1"),
|
|
Username: "corndog",
|
|
Password: "c0rnd0g",
|
|
},
|
|
{
|
|
RemoteIP: net.ParseIP("127.0.0.1"),
|
|
Username: "corndog",
|
|
Password: "corndog",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
res, err := s.Query(tc.Query)
|
|
if err != nil {
|
|
t.Errorf("Error performing query: %s", err)
|
|
}
|
|
if !equalAttempts(res, tc.ExpectedResult) {
|
|
t.Errorf("Query did not return expected results")
|
|
t.Logf("%+v", res)
|
|
t.Logf("%+v", tc.ExpectedResult)
|
|
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("QueryCache", func(t *testing.T) {
|
|
err := s.AddAttempt(&models.LoginAttempt{RemoteIP: net.ParseIP("127.0.0.1"), Username: "test", Password: "test"})
|
|
if err != nil {
|
|
t.Fatalf("Error adding attempt: %s", err)
|
|
}
|
|
|
|
res, err := s.Query(store.AttemptQuery{QueryType: store.AttemptQueryTypeUsername, Query: "test"})
|
|
if err != nil {
|
|
t.Fatalf("Error adding attempt: %s", err)
|
|
}
|
|
if len(res) != 1 {
|
|
t.Errorf("Wrong amount of results")
|
|
}
|
|
|
|
err = s.AddAttempt(&models.LoginAttempt{RemoteIP: net.ParseIP("127.0.0.1"), Username: "test", Password: "best"})
|
|
if err != nil {
|
|
t.Fatalf("Error adding attempt: %s", err)
|
|
}
|
|
res, err = s.Query(store.AttemptQuery{QueryType: store.AttemptQueryTypeUsername, Query: "test"})
|
|
if err != nil {
|
|
t.Fatalf("Error adding attempt: %s", err)
|
|
}
|
|
if len(res) != 2 {
|
|
t.Errorf("Wrong amount of results")
|
|
}
|
|
})
|
|
t.Run("QueryStats", func(t *testing.T) {
|
|
firstStats, err := s.Stats(store.LoginStatsTotals, 1)
|
|
if err != nil {
|
|
t.Fatalf("Error getting stats: %s", err)
|
|
}
|
|
err = s.AddAttempt(&models.LoginAttempt{RemoteIP: net.ParseIP("127.0.0.1"), Username: "test", Password: "best"})
|
|
if err != nil {
|
|
t.Fatalf("Error adding attempt: %s", err)
|
|
}
|
|
secondStats, err := s.Stats(store.LoginStatsTotals, 1)
|
|
if err != nil {
|
|
t.Fatalf("Error getting stats: %s", err)
|
|
}
|
|
var firstCount, secondCount int
|
|
for _, stat := range firstStats {
|
|
if stat.Name == "TotalLoginAttempts" {
|
|
firstCount = stat.Count
|
|
}
|
|
}
|
|
for _, stat := range secondStats {
|
|
if stat.Name == "TotalLoginAttempts" {
|
|
secondCount = stat.Count
|
|
}
|
|
}
|
|
|
|
if secondCount != firstCount+1 {
|
|
t.Errorf("TotalLoginAttempts did not increment")
|
|
}
|
|
})
|
|
}
|
|
|
|
func benchmarkLoginAttemptStore(setupFunc func() store.LoginAttemptStore, b *testing.B) {
|
|
b.Run("BenchmarkAdd", func(b *testing.B) {
|
|
s := setupFunc()
|
|
for i := 0; i < b.N; i++ {
|
|
attempt := randomAttempts(1)
|
|
err := s.AddAttempt(attempt[0])
|
|
if err != nil {
|
|
b.Fatalf("Error adding attempt: %s", err)
|
|
}
|
|
}
|
|
})
|
|
b.Run("BenchmarkAdd10k", func(b *testing.B) {
|
|
attempts := randomAttempts(10_000)
|
|
for i := 0; i < b.N; i++ {
|
|
b.StopTimer()
|
|
s := setupFunc()
|
|
b.StartTimer()
|
|
for _, attempt := range attempts {
|
|
err := s.AddAttempt(attempt)
|
|
if err != nil {
|
|
b.Fatalf("Error adding attempt: %s", err)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
b.Run("BenchmarkAll10k", func(b *testing.B) {
|
|
s := setupFunc()
|
|
attempts := randomAttempts(10_000)
|
|
for _, attempt := range attempts {
|
|
err := s.AddAttempt(attempt)
|
|
if err != nil {
|
|
b.Fatalf("Error adding attempt: %s", err)
|
|
}
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
all, err := s.All()
|
|
if err != nil {
|
|
b.Fatalf("Error fetchin all: %s", err)
|
|
}
|
|
var count int
|
|
for range all {
|
|
count++
|
|
}
|
|
_ = count
|
|
}
|
|
})
|
|
}
|
|
|
|
func randomAttempts(count int) []*models.LoginAttempt {
|
|
var attempts []*models.LoginAttempt
|
|
for i := 0; i < count; i++ {
|
|
attempt := &models.LoginAttempt{
|
|
Date: time.Now(),
|
|
RemoteIP: randomIP(),
|
|
Username: randomString(8),
|
|
Password: randomString(8),
|
|
Country: randomCountry(),
|
|
ConnectionUUID: uuid.Must(uuid.NewRandom()),
|
|
SSHClientVersion: "SSH TEST LOL",
|
|
}
|
|
attempts = append(attempts, attempt)
|
|
}
|
|
return attempts
|
|
}
|
|
|
|
func randomIP() net.IP {
|
|
a := byte(rand.Intn(254))
|
|
b := byte(rand.Intn(254))
|
|
c := byte(rand.Intn(254))
|
|
d := byte(rand.Intn(254))
|
|
return net.IPv4(a, b, c, d)
|
|
}
|
|
|
|
func randomString(n int) string {
|
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
|
|
b := make([]byte, n)
|
|
for i := range b {
|
|
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func randomCountry() string {
|
|
switch rand.Intn(10) {
|
|
case 1:
|
|
return "CN"
|
|
case 2:
|
|
return "US"
|
|
case 3:
|
|
return "NO"
|
|
case 4:
|
|
return "RU"
|
|
case 5:
|
|
return "DE"
|
|
case 6:
|
|
return "FI"
|
|
case 7:
|
|
return "BR"
|
|
default:
|
|
return "SE"
|
|
}
|
|
}
|
|
|
|
func equalAttempts(a, b []models.LoginAttempt) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
|
|
aFound := make([]bool, len(a))
|
|
|
|
for i, aAttempt := range a {
|
|
for _, bAttempt := range b {
|
|
if aAttempt.Username == bAttempt.Username &&
|
|
aAttempt.Password == bAttempt.Password &&
|
|
aAttempt.RemoteIP.String() == bAttempt.RemoteIP.String() {
|
|
aFound[i] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, found := range aFound {
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func fuzzLoginAttemptStore(s store.LoginAttemptStore, f *testing.F) {
|
|
usernames := []string{"username", "root", "ubnt", "pi", "admin"}
|
|
for _, username := range usernames {
|
|
f.Add(username)
|
|
}
|
|
f.Fuzz(func(t *testing.T, orig string) {
|
|
attempt := models.LoginAttempt{
|
|
Date: time.Now(),
|
|
Username: orig,
|
|
Password: randomString(8),
|
|
Country: "NO",
|
|
}
|
|
|
|
if err := s.AddAttempt(&attempt); err != nil {
|
|
t.Fatalf("error adding: %s", err)
|
|
}
|
|
})
|
|
}
|