package security import ( "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "log" "net" "strings" "sync" "time" "kattila-agent/config" ) var ( currentPSK string mu sync.RWMutex ) // StartKeyPoller checks the DNS record every hour func StartKeyPoller() { fetchKey() ticker := time.NewTicker(1 * time.Hour) go func() { for range ticker.C { fetchKey() } }() } func fetchKey() { dnsName := config.Cfg.DNS if dnsName == "" { log.Println("security: No DNS configured for PSK") return } txts, err := net.LookupTXT(dnsName) if err != nil { log.Printf("security: Failed to lookup TXT for %s: %v", dnsName, err) return } if len(txts) == 0 { return } key := txts[0] // Remove quotes if present key = strings.Trim(key, `"`) mu.Lock() if currentPSK != key { log.Println("security: New PSK discovered via DNS") currentPSK = key } mu.Unlock() } func GetCurrentPSK() string { mu.RLock() defer mu.RUnlock() return currentPSK } // FleetID generates a SHA256 of the PSK to uniquely identify the fleet func FleetID() string { psk := GetCurrentPSK() if psk == "" { return "" } hash := sha256.Sum256([]byte(psk)) return hex.EncodeToString(hash[:]) } func GenerateNonce() string { b := make([]byte, 16) rand.Read(b) return base64.StdEncoding.EncodeToString(b) } func SignPayload(data interface{}) string { psk := GetCurrentPSK() if psk == "" { return "" } bytes, err := json.Marshal(data) if err != nil { return "" } h := hmac.New(sha256.New, []byte(psk)) h.Write(bytes) return hex.EncodeToString(h.Sum(nil)) }