Files
Redflag/aggregator-server/internal/version/versions.go
jpetree331 f97d4845af feat(security): A-1 Ed25519 key rotation + A-2 replay attack fixes
Complete RedFlag codebase with two major security audit implementations.

== A-1: Ed25519 Key Rotation Support ==

Server:
- SignCommand sets SignedAt timestamp and KeyID on every signature
- signing_keys database table (migration 020) for multi-key rotation
- InitializePrimaryKey registers active key at startup
- /api/v1/public-keys endpoint for rotation-aware agents
- SigningKeyQueries for key lifecycle management

Agent:
- Key-ID-aware verification via CheckKeyRotation
- FetchAndCacheAllActiveKeys for rotation pre-caching
- Cache metadata with TTL and staleness fallback
- SecurityLogger events for key rotation and command signing

== A-2: Replay Attack Fixes (F-1 through F-7) ==

F-5 CRITICAL - RetryCommand now signs via signAndCreateCommand
F-1 HIGH     - v3 format: "{agent_id}:{cmd_id}:{type}:{hash}:{ts}"
F-7 HIGH     - Migration 026: expires_at column with partial index
F-6 HIGH     - GetPendingCommands/GetStuckCommands filter by expires_at
F-2 HIGH     - Agent-side executedIDs dedup map with cleanup
F-4 HIGH     - commandMaxAge reduced from 24h to 4h
F-3 CRITICAL - Old-format commands rejected after 48h via CreatedAt

Verification fixes: migration idempotency (ETHOS #4), log format
compliance (ETHOS #1), stale comments updated.

All 24 tests passing. Docker --no-cache build verified.
See docs/ for full audit reports and deviation log (DEV-001 to DEV-019).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 21:25:47 -04:00

87 lines
3.0 KiB
Go

package version
import (
"fmt"
"time"
)
// Version coordination for Server Authority model
// The server is the single source of truth for all version information
//
// Version Sources:
// - Agent versions: Compiled into agent via ldflags during build (see agent/internal/version)
// - Server versions: Compiled into server via ldflags during build (injected below)
// - Database: agents table stores agent_version at registration
// Build-time injected version information (SERVER AUTHORITY)
// Injected by build script during server compilation
var (
AgentVersion = "dev" // Server's agent version (format: 0.1.27)
ConfigVersion = "dev" // Config schema version (format: 3)
MinAgentVersion = "dev" // Minimum supported agent version
)
// CurrentVersions holds the authoritative version information for API responses
type CurrentVersions struct {
AgentVersion string `json:"agent_version"` // e.g., "0.1.27"
ConfigVersion string `json:"config_version"` // e.g., "3"
MinAgentVersion string `json:"min_agent_version"` // e.g., "0.1.22"
BuildTime time.Time `json:"build_time"`
}
// GetCurrentVersions returns the current version information
// Version is compiled into the server binary at build time via ldflags
func GetCurrentVersions() CurrentVersions {
// Build-time injection allows version updates without code changes
// See Dockerfile for injection via: -ldflags "-X .../version.AgentVersion=0.1.27"
return CurrentVersions{
AgentVersion: AgentVersion,
ConfigVersion: ConfigVersion,
MinAgentVersion: MinAgentVersion,
BuildTime: time.Now(),
}
}
// ExtractConfigVersionFromAgent extracts config version from agent version
// Agent version format: v0.1.23.6 where fourth octet maps to config version
func ExtractConfigVersionFromAgent(agentVersion string) string {
// Strip 'v' prefix if present
cleanVersion := agentVersion
if len(cleanVersion) > 0 && cleanVersion[0] == 'v' {
cleanVersion = cleanVersion[1:]
}
// Split version parts
parts := fmt.Sprintf("%s", cleanVersion)
if len(parts) >= 1 {
// For now, use the last octet as config version
// v0.1.23 -> "3" (last digit)
lastChar := parts[len(parts)-1:]
return lastChar
}
// Default fallback
return "3"
}
// ValidateAgentVersion checks if an agent version is compatible
func ValidateAgentVersion(agentVersion string) error {
current := GetCurrentVersions()
// Check minimum version
if agentVersion < current.MinAgentVersion {
return fmt.Errorf("agent version %s is below minimum %s", agentVersion, current.MinAgentVersion)
}
return nil
}
// GetBuildFlags returns the ldflags to inject versions into agent builds
func GetBuildFlags() []string {
versions := GetCurrentVersions()
return []string{
fmt.Sprintf("-X github.com/Fimeg/RedFlag/aggregator-agent/internal/version.Version=%s", versions.AgentVersion),
fmt.Sprintf("-X github.com/Fimeg/RedFlag/aggregator-agent/internal/version.ConfigVersion=%s", versions.ConfigVersion),
fmt.Sprintf("-X github.com/Fimeg/RedFlag/aggregator-agent/internal/version.BuildTime=%s", versions.BuildTime.Format(time.RFC3339)),
}
}