Altered the badger db to derive the HWID from more static sources and to have a fallback in the event of a failure
This commit is contained in:
+2
-2
@@ -195,8 +195,8 @@ var (
|
|||||||
|
|
||||||
// Write history records to file if any
|
// Write history records to file if any
|
||||||
if len(historyRecords) > 0 {
|
if len(historyRecords) > 0 {
|
||||||
fmt.Println("[*] Records Found: %d\n", len(historyRecords))
|
fmt.Printf("[*] Records Found: %d\n", len(historyRecords))
|
||||||
fmt.Println("[*] WHOIS History being written to file: %s%s\n", whoisOutputFile, fType.Extension())
|
fmt.Printf("[*] WHOIS History being written to file: %s%s\n", whoisOutputFile, fType.Extension())
|
||||||
writeErr := export.WriteWhoIsHistoryToFile(historyRecords, filename, fType)
|
writeErr := export.WriteWhoIsHistoryToFile(historyRecords, filename, fType)
|
||||||
if writeErr != nil {
|
if writeErr != nil {
|
||||||
if debugGlobal {
|
if debugGlobal {
|
||||||
|
|||||||
+235
-12
@@ -3,15 +3,19 @@ package badger
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/dgraph-io/badger/v4"
|
"fmt"
|
||||||
"go.uber.org/zap"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dgraph-io/badger/v4"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -21,13 +25,35 @@ var (
|
|||||||
once sync.Once
|
once sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetHardwareEntropy() []byte {
|
const fingerprintSalt = "CrowsNest-static-salt-value"
|
||||||
|
|
||||||
|
func GetHardwareEntropy() ([]byte, error) {
|
||||||
|
source, machineID, err := getMachineID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fingerprint := strings.Join([]string{
|
||||||
|
"v2",
|
||||||
|
runtime.GOOS,
|
||||||
|
source,
|
||||||
|
machineID,
|
||||||
|
fingerprintSalt,
|
||||||
|
}, ":")
|
||||||
|
|
||||||
|
return hashFingerprint(fingerprint), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLegacyHardwareEntropy() []byte {
|
||||||
// Get hostname
|
// Get hostname
|
||||||
hostname, err := os.Hostname()
|
hostname, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostname = "unknown-host"
|
hostname = "unknown-host"
|
||||||
log.Printf("Error getting hostname: %v", err)
|
log.Printf("Error getting hostname: %v", err)
|
||||||
}
|
}
|
||||||
|
if legacyHostname := strings.TrimSpace(os.Getenv("CROWSNEST_LEGACY_HOSTNAME")); legacyHostname != "" {
|
||||||
|
hostname = legacyHostname
|
||||||
|
}
|
||||||
|
|
||||||
// Get username
|
// Get username
|
||||||
currentUser, err := user.Current()
|
currentUser, err := user.Current()
|
||||||
@@ -35,9 +61,15 @@ func GetHardwareEntropy() []byte {
|
|||||||
if err == nil && currentUser != nil {
|
if err == nil && currentUser != nil {
|
||||||
username = currentUser.Username
|
username = currentUser.Username
|
||||||
}
|
}
|
||||||
|
if legacyUsername := strings.TrimSpace(os.Getenv("CROWSNEST_LEGACY_USERNAME")); legacyUsername != "" {
|
||||||
|
username = legacyUsername
|
||||||
|
}
|
||||||
|
|
||||||
// Get OS and architecture info
|
// Get OS and architecture info
|
||||||
osInfo := runtime.GOOS + "-" + runtime.GOARCH
|
osInfo := runtime.GOOS + "-" + runtime.GOARCH
|
||||||
|
if legacyOSInfo := strings.TrimSpace(os.Getenv("CROWSNEST_LEGACY_OSINFO")); legacyOSInfo != "" {
|
||||||
|
osInfo = legacyOSInfo
|
||||||
|
}
|
||||||
|
|
||||||
// Combine all information for a unique but consistent fingerprint
|
// Combine all information for a unique but consistent fingerprint
|
||||||
fingerprint := strings.Join([]string{
|
fingerprint := strings.Join([]string{
|
||||||
@@ -45,14 +77,93 @@ func GetHardwareEntropy() []byte {
|
|||||||
username,
|
username,
|
||||||
osInfo,
|
osInfo,
|
||||||
// You could add a static salt here for additional security
|
// You could add a static salt here for additional security
|
||||||
"CrowsNest-static-salt-value",
|
fingerprintSalt,
|
||||||
}, ":")
|
}, ":")
|
||||||
|
|
||||||
// Hash the fingerprint to get a 32-byte key
|
// Hash the fingerprint to get a 32-byte key
|
||||||
|
return hashFingerprint(fingerprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashFingerprint(fingerprint string) []byte {
|
||||||
sum := sha256.Sum256([]byte(fingerprint))
|
sum := sha256.Sum256([]byte(fingerprint))
|
||||||
return sum[:]
|
return sum[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getMachineID() (string, string, error) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
return getDarwinMachineID()
|
||||||
|
case "linux":
|
||||||
|
return getLinuxMachineID()
|
||||||
|
case "windows":
|
||||||
|
return getWindowsMachineID()
|
||||||
|
default:
|
||||||
|
return "", "", fmt.Errorf("stable machine id is not implemented for %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDarwinMachineID() (string, string, error) {
|
||||||
|
out, err := exec.Command("ioreg", "-rd1", "-c", "IOPlatformExpertDevice").Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("run ioreg: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range strings.Split(string(out), "\n") {
|
||||||
|
if !strings.Contains(line, "IOPlatformUUID") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if id := normalizeMachineID(lastQuotedValue(line)); id != "" {
|
||||||
|
return "darwin-ioplatformuuid", id, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", "", errors.New("IOPlatformUUID not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLinuxMachineID() (string, string, error) {
|
||||||
|
for _, path := range []string{"/etc/machine-id", "/var/lib/dbus/machine-id"} {
|
||||||
|
out, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if id := normalizeMachineID(string(out)); id != "" {
|
||||||
|
return "linux-machine-id", id, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", "", errors.New("machine-id not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWindowsMachineID() (string, string, error) {
|
||||||
|
out, err := exec.Command("reg", "query", `HKLM\SOFTWARE\Microsoft\Cryptography`, "/v", "MachineGuid").Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("query MachineGuid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range strings.Split(string(out), "\n") {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) >= 3 && strings.EqualFold(fields[0], "MachineGuid") {
|
||||||
|
if id := normalizeMachineID(fields[len(fields)-1]); id != "" {
|
||||||
|
return "windows-machineguid", id, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", "", errors.New("MachineGuid not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastQuotedValue(line string) string {
|
||||||
|
values := strings.Split(line, "\"")
|
||||||
|
if len(values) < 4 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return values[len(values)-2]
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeMachineID(id string) string {
|
||||||
|
return strings.ToLower(strings.TrimSpace(id))
|
||||||
|
}
|
||||||
|
|
||||||
func Start(dirPath string) *badger.DB {
|
func Start(dirPath string) *badger.DB {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -65,7 +176,7 @@ func Start(dirPath string) *badger.DB {
|
|||||||
}
|
}
|
||||||
rootDir = dirPath
|
rootDir = dirPath
|
||||||
|
|
||||||
encryptionKey = GetHardwareEntropy()
|
encryptionKey, err = GetHardwareEntropy()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Fatal("get_encryption_key",
|
zap.L().Fatal("get_encryption_key",
|
||||||
zap.String("message", "failed to get encryption key"),
|
zap.String("message", "failed to get encryption key"),
|
||||||
@@ -74,22 +185,134 @@ func Start(dirPath string) *badger.DB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
badgerDB := filepath.Join(rootDir, "badger.db")
|
badgerDB := filepath.Join(rootDir, "badger.db")
|
||||||
opts := badger.DefaultOptions(badgerDB).
|
db, err = openBadger(badgerDB, encryptionKey)
|
||||||
WithEncryptionKey(encryptionKey).
|
|
||||||
WithIndexCacheSize(10 << 20). // 10MB
|
|
||||||
WithLoggingLevel(badger.ERROR)
|
|
||||||
db, err = badger.Open(opts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Fatal("new_badger_db",
|
zap.L().Warn("open_badger_db",
|
||||||
zap.String("message", "failed to open badger database"),
|
zap.String("message", "failed to open badger database with stable machine key; trying legacy key"),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
|
db, err = openBadgerWithLegacyMigration(badgerDB, encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Fatal("new_badger_db",
|
||||||
|
zap.String("message", "failed to open badger database"),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openBadger(dbPath string, key []byte) (*badger.DB, error) {
|
||||||
|
opts := badger.DefaultOptions(dbPath).
|
||||||
|
WithEncryptionKey(key).
|
||||||
|
WithIndexCacheSize(10 << 20). // 10MB
|
||||||
|
WithLoggingLevel(badger.ERROR)
|
||||||
|
return badger.Open(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openBadgerWithLegacyMigration(dbPath string, stableKey []byte) (*badger.DB, error) {
|
||||||
|
legacyKey := GetLegacyHardwareEntropy()
|
||||||
|
legacyDB, err := openBadger(dbPath, legacyKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("stable key failed and legacy key failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
migratedDB, err := migrateBadgerEncryption(dbPath, legacyDB, stableKey)
|
||||||
|
if err != nil {
|
||||||
|
if closeErr := legacyDB.Close(); closeErr != nil {
|
||||||
|
zap.L().Error("close_legacy_badger_db", zap.Error(closeErr))
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return migratedDB, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateBadgerEncryption(dbPath string, legacyDB *badger.DB, stableKey []byte) (*badger.DB, error) {
|
||||||
|
parentDir := filepath.Dir(dbPath)
|
||||||
|
timestamp := time.Now().Format("20060102-150405")
|
||||||
|
migrationPath := filepath.Join(parentDir, fmt.Sprintf(".%s.migrating-%s", filepath.Base(dbPath), timestamp))
|
||||||
|
backupPath := filepath.Join(parentDir, fmt.Sprintf("%s.legacy-backup-%s", filepath.Base(dbPath), timestamp))
|
||||||
|
|
||||||
|
newDB, err := openBadger(migrationPath, stableKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open migration badger db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyBadgerData(legacyDB, newDB); err != nil {
|
||||||
|
_ = newDB.Close()
|
||||||
|
_ = os.RemoveAll(migrationPath)
|
||||||
|
return nil, fmt.Errorf("copy legacy badger data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := legacyDB.Close(); err != nil {
|
||||||
|
_ = newDB.Close()
|
||||||
|
_ = os.RemoveAll(migrationPath)
|
||||||
|
return nil, fmt.Errorf("close legacy badger db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := newDB.Close(); err != nil {
|
||||||
|
_ = os.RemoveAll(migrationPath)
|
||||||
|
return nil, fmt.Errorf("close migration badger db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(dbPath, backupPath); err != nil {
|
||||||
|
_ = os.RemoveAll(migrationPath)
|
||||||
|
return nil, fmt.Errorf("backup legacy badger db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(migrationPath, dbPath); err != nil {
|
||||||
|
if restoreErr := os.Rename(backupPath, dbPath); restoreErr != nil {
|
||||||
|
return nil, fmt.Errorf("promote migrated badger db: %w; restore legacy backup: %v", err, restoreErr)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("promote migrated badger db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := openBadger(dbPath, stableKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open migrated badger db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.L().Info("migrated_badger_encryption",
|
||||||
|
zap.String("backup", backupPath),
|
||||||
|
zap.String("path", dbPath),
|
||||||
|
)
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyBadgerData(src *badger.DB, dst *badger.DB) error {
|
||||||
|
return src.View(func(srcTxn *badger.Txn) error {
|
||||||
|
opts := badger.DefaultIteratorOptions
|
||||||
|
opts.PrefetchValues = true
|
||||||
|
iter := srcTxn.NewIterator(opts)
|
||||||
|
defer iter.Close()
|
||||||
|
|
||||||
|
return dst.Update(func(dstTxn *badger.Txn) error {
|
||||||
|
for iter.Rewind(); iter.Valid(); iter.Next() {
|
||||||
|
item := iter.Item()
|
||||||
|
if item.IsDeletedOrExpired() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := item.KeyCopy(nil)
|
||||||
|
value, err := item.ValueCopy(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := badger.NewEntry(key, value).WithMeta(item.UserMeta())
|
||||||
|
entry.ExpiresAt = item.ExpiresAt()
|
||||||
|
if err := dstTxn.SetEntry(entry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func Close() {
|
func Close() {
|
||||||
err := db.Close()
|
err := db.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package badger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
badgerapi "github.com/dgraph-io/badger/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMigrateBadgerEncryptionCopiesDataToStableKey(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
dbPath := filepath.Join(dir, "badger.db")
|
||||||
|
legacyKey := testKey("legacy-key")
|
||||||
|
stableKey := testKey("stable-key")
|
||||||
|
|
||||||
|
legacyDB, err := openBadger(dbPath, legacyKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open legacy db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := legacyDB.Update(func(txn *badgerapi.Txn) error {
|
||||||
|
return txn.Set([]byte("cfg:api_key"), []byte("secret"))
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("seed legacy db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
migratedDB, err := migrateBadgerEncryption(dbPath, legacyDB, stableKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("migrate db: %v", err)
|
||||||
|
}
|
||||||
|
defer migratedDB.Close()
|
||||||
|
|
||||||
|
var got string
|
||||||
|
if err := migratedDB.View(func(txn *badgerapi.Txn) error {
|
||||||
|
item, err := txn.Get([]byte("cfg:api_key"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return item.Value(func(value []byte) error {
|
||||||
|
got = string(value)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("read migrated db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != "secret" {
|
||||||
|
t.Fatalf("migrated value = %q, want %q", got, "secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read temp dir: %v", err)
|
||||||
|
}
|
||||||
|
foundBackup := false
|
||||||
|
for _, entry := range entries {
|
||||||
|
if strings.HasPrefix(entry.Name(), "badger.db.legacy-backup-") {
|
||||||
|
foundBackup = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundBackup {
|
||||||
|
t.Fatal("legacy backup directory was not created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testKey(value string) []byte {
|
||||||
|
sum := sha256.Sum256([]byte(value))
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user