Files
Fimeg ec3ba88459 feat: machine binding and version enforcement
migration 017 adds machine_id to agents table
middleware validates X-Machine-ID header on authed routes
agent client sends machine ID with requests
MIN_AGENT_VERSION config defaults 0.1.22
version utils added for comparison

blocks config copying attacks via hardware fingerprint
old agents get 426 upgrade required
breaking: <0.1.22 agents rejected
2025-11-02 09:30:04 -05:00

129 lines
3.4 KiB
Go

package system
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"runtime"
"strings"
"github.com/denisbrodbeck/machineid"
)
// GetMachineID generates a unique machine identifier that persists across reboots
func GetMachineID() (string, error) {
// Try machineid library first (cross-platform)
id, err := machineid.ID()
if err == nil && id != "" {
// Hash the machine ID for consistency and privacy
return hashMachineID(id), nil
}
// Fallback to OS-specific methods
switch runtime.GOOS {
case "linux":
return getLinuxMachineID()
case "windows":
return getWindowsMachineID()
case "darwin":
return getDarwinMachineID()
default:
return generateGenericMachineID()
}
}
// hashMachineID creates a consistent hash from machine ID
func hashMachineID(id string) string {
hash := sha256.Sum256([]byte(id))
return hex.EncodeToString(hash[:]) // Return full hash for uniqueness
}
// getLinuxMachineID tries multiple sources for Linux machine ID
func getLinuxMachineID() (string, error) {
// Try /etc/machine-id first (systemd)
if id, err := os.ReadFile("/etc/machine-id"); err == nil {
idStr := strings.TrimSpace(string(id))
if idStr != "" {
return hashMachineID(idStr), nil
}
}
// Try /var/lib/dbus/machine-id
if id, err := os.ReadFile("/var/lib/dbus/machine-id"); err == nil {
idStr := strings.TrimSpace(string(id))
if idStr != "" {
return hashMachineID(idStr), nil
}
}
// Try DMI product UUID
if id, err := os.ReadFile("/sys/class/dmi/id/product_uuid"); err == nil {
idStr := strings.TrimSpace(string(id))
if idStr != "" {
return hashMachineID(idStr), nil
}
}
// Try /etc/hostname as last resort
if hostname, err := os.ReadFile("/etc/hostname"); err == nil {
hostnameStr := strings.TrimSpace(string(hostname))
if hostnameStr != "" {
return hashMachineID(hostnameStr + "-linux-fallback"), nil
}
}
return generateGenericMachineID()
}
// getWindowsMachineID gets Windows machine ID
func getWindowsMachineID() (string, error) {
// Try machineid library Windows registry keys first
if id, err := machineid.ID(); err == nil && id != "" {
return hashMachineID(id), nil
}
// Fallback to generating generic ID
return generateGenericMachineID()
}
// getDarwinMachineID gets macOS machine ID
func getDarwinMachineID() (string, error) {
// Try machineid library platform-specific keys first
if id, err := machineid.ID(); err == nil && id != "" {
return hashMachineID(id), nil
}
// Fallback to generating generic ID
return generateGenericMachineID()
}
// generateGenericMachineID creates a fallback machine ID from available system info
func generateGenericMachineID() (string, error) {
// Combine hostname with other available info
hostname, _ := os.Hostname()
if hostname == "" {
hostname = "unknown"
}
// Create a reasonably unique ID from available system info
idSource := fmt.Sprintf("%s-%s-%s", hostname, runtime.GOOS, runtime.GOARCH)
return hashMachineID(idSource), nil
}
// GetEmbeddedPublicKey returns the embedded public key fingerprint
// This should be set at build time using ldflags
var EmbeddedPublicKey = "not-set-at-build-time"
// GetPublicKeyFingerprint returns the fingerprint of the embedded public key
func GetPublicKeyFingerprint() string {
if EmbeddedPublicKey == "not-set-at-build-time" {
return ""
}
// Return first 8 bytes as fingerprint
if len(EmbeddedPublicKey) >= 16 {
return EmbeddedPublicKey[:16]
}
return EmbeddedPublicKey
}