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
This commit is contained in:
Fimeg
2025-11-02 09:30:04 -05:00
parent 99480f3fe3
commit ec3ba88459
48 changed files with 3811 additions and 122 deletions

View File

@@ -18,15 +18,20 @@ type Agent struct {
AgentVersion string `json:"agent_version" db:"agent_version"` // Version at registration
CurrentVersion string `json:"current_version" db:"current_version"` // Current running version
UpdateAvailable bool `json:"update_available" db:"update_available"` // Whether update is available
LastVersionCheck time.Time `json:"last_version_check" db:"last_version_check"` // Last time version was checked
LastSeen time.Time `json:"last_seen" db:"last_seen"`
Status string `json:"status" db:"status"`
Metadata JSONB `json:"metadata" db:"metadata"`
RebootRequired bool `json:"reboot_required" db:"reboot_required"`
LastRebootAt *time.Time `json:"last_reboot_at,omitempty" db:"last_reboot_at"`
RebootReason *string `json:"reboot_reason,omitempty" db:"reboot_reason"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
LastVersionCheck time.Time `json:"last_version_check" db:"last_version_check"` // Last time version was checked
MachineID *string `json:"machine_id,omitempty" db:"machine_id"` // Unique machine identifier
PublicKeyFingerprint *string `json:"public_key_fingerprint,omitempty" db:"public_key_fingerprint"` // Public key fingerprint
IsUpdating bool `json:"is_updating" db:"is_updating"` // Whether agent is currently updating
UpdatingToVersion *string `json:"updating_to_version,omitempty" db:"updating_to_version"` // Target version for ongoing update
UpdateInitiatedAt *time.Time `json:"update_initiated_at,omitempty" db:"update_initiated_at"` // When update process started
LastSeen time.Time `json:"last_seen" db:"last_seen"`
Status string `json:"status" db:"status"`
Metadata JSONB `json:"metadata" db:"metadata"`
RebootRequired bool `json:"reboot_required" db:"reboot_required"`
LastRebootAt *time.Time `json:"last_reboot_at,omitempty" db:"last_reboot_at"`
RebootReason *string `json:"reboot_reason,omitempty" db:"reboot_reason"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// AgentWithLastScan extends Agent with last scan information
@@ -69,13 +74,15 @@ type AgentSpecs struct {
// AgentRegistrationRequest is the payload for agent registration
type AgentRegistrationRequest struct {
Hostname string `json:"hostname" binding:"required"`
OSType string `json:"os_type" binding:"required"`
OSVersion string `json:"os_version"`
OSArchitecture string `json:"os_architecture"`
AgentVersion string `json:"agent_version" binding:"required"`
RegistrationToken string `json:"registration_token"` // Optional, for fallback method
Metadata map[string]string `json:"metadata"`
Hostname string `json:"hostname" binding:"required"`
OSType string `json:"os_type" binding:"required"`
OSVersion string `json:"os_version"`
OSArchitecture string `json:"os_architecture"`
AgentVersion string `json:"agent_version" binding:"required"`
RegistrationToken string `json:"registration_token"` // Optional, for fallback method
MachineID string `json:"machine_id"` // Unique machine identifier
PublicKeyFingerprint string `json:"public_key_fingerprint"` // Embedded public key fingerprint
Metadata map[string]string `json:"metadata"`
}
// AgentRegistrationResponse is returned after successful registration

View File

@@ -0,0 +1,67 @@
package models
import (
"time"
"github.com/google/uuid"
)
// AgentUpdatePackage represents a signed agent binary package
type AgentUpdatePackage struct {
ID uuid.UUID `json:"id" db:"id"`
Version string `json:"version" db:"version"`
Platform string `json:"platform" db:"platform"`
Architecture string `json:"architecture" db:"architecture"`
BinaryPath string `json:"binary_path" db:"binary_path"`
Signature string `json:"signature" db:"signature"`
Checksum string `json:"checksum" db:"checksum"`
FileSize int64 `json:"file_size" db:"file_size"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
CreatedBy string `json:"created_by" db:"created_by"`
IsActive bool `json:"is_active" db:"is_active"`
}
// AgentUpdateRequest represents a request to update an agent
type AgentUpdateRequest struct {
AgentID uuid.UUID `json:"agent_id" binding:"required"`
Version string `json:"version" binding:"required"`
Platform string `json:"platform" binding:"required"`
Scheduled *string `json:"scheduled_at,omitempty"`
}
// BulkAgentUpdateRequest represents a bulk update request
type BulkAgentUpdateRequest struct {
AgentIDs []uuid.UUID `json:"agent_ids" binding:"required"`
Version string `json:"version" binding:"required"`
Platform string `json:"platform" binding:"required"`
Scheduled *string `json:"scheduled_at,omitempty"`
}
// AgentUpdateResponse represents the response for an update request
type AgentUpdateResponse struct {
Message string `json:"message"`
UpdateID string `json:"update_id,omitempty"`
DownloadURL string `json:"download_url,omitempty"`
Signature string `json:"signature,omitempty"`
Checksum string `json:"checksum,omitempty"`
FileSize int64 `json:"file_size,omitempty"`
EstimatedTime int `json:"estimated_time_seconds,omitempty"`
}
// SignatureVerificationRequest represents a request to verify an agent's binary signature
type SignatureVerificationRequest struct {
AgentID uuid.UUID `json:"agent_id" binding:"required"`
BinaryPath string `json:"binary_path" binding:"required"`
MachineID string `json:"machine_id" binding:"required"`
PublicKey string `json:"public_key" binding:"required"`
Signature string `json:"signature" binding:"required"`
}
// SignatureVerificationResponse represents the response for signature verification
type SignatureVerificationResponse struct {
Valid bool `json:"valid"`
AgentID string `json:"agent_id"`
MachineID string `json:"machine_id"`
Fingerprint string `json:"fingerprint"`
Message string `json:"message"`
}