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>
112 lines
2.9 KiB
Go
112 lines
2.9 KiB
Go
package version
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Build-time injected version information (SERVER AUTHORITY)
|
|
// Injected by server during build via ldflags
|
|
var (
|
|
Version = "dev" // Agent version (format: 0.1.26.0)
|
|
ConfigVersion = "dev" // Config schema version (format: 0, 1, 2, etc.)
|
|
BuildTime = "unknown"
|
|
GitCommit = "unknown"
|
|
GoVersion = runtime.Version()
|
|
)
|
|
|
|
// ExtractConfigVersionFromAgent extracts the config version from the agent version
|
|
// Agent version format: v0.1.23.6 where the fourth octet (.6) maps to config version
|
|
// This provides the traditional mapping when only agent version is available
|
|
func ExtractConfigVersionFromAgent(agentVer string) string {
|
|
// Strip 'v' prefix if present
|
|
cleanVersion := strings.TrimPrefix(agentVer, "v")
|
|
|
|
// Split version parts
|
|
parts := strings.Split(cleanVersion, ".")
|
|
if len(parts) == 4 {
|
|
// Return the fourth octet as the config version
|
|
// v0.1.23.6 → "6"
|
|
return parts[3]
|
|
}
|
|
|
|
// If we have a build-time injected ConfigVersion, use it
|
|
if ConfigVersion != "dev" {
|
|
return ConfigVersion
|
|
}
|
|
|
|
// Default fallback
|
|
return "6"
|
|
}
|
|
|
|
// Info holds complete version information
|
|
type Info struct {
|
|
AgentVersion string `json:"agent_version"`
|
|
ConfigVersion string `json:"config_version"`
|
|
BuildTime string `json:"build_time"`
|
|
GitCommit string `json:"git_commit"`
|
|
GoVersion string `json:"go_version"`
|
|
BuildTimestamp int64 `json:"build_timestamp"`
|
|
}
|
|
|
|
// GetInfo returns complete version information
|
|
func GetInfo() Info {
|
|
// Parse build time if available
|
|
timestamp := time.Now().Unix()
|
|
if BuildTime != "unknown" {
|
|
if t, err := time.Parse(time.RFC3339, BuildTime); err == nil {
|
|
timestamp = t.Unix()
|
|
}
|
|
}
|
|
|
|
return Info{
|
|
AgentVersion: Version,
|
|
ConfigVersion: ConfigVersion,
|
|
BuildTime: BuildTime,
|
|
GitCommit: GitCommit,
|
|
GoVersion: GoVersion,
|
|
BuildTimestamp: timestamp,
|
|
}
|
|
}
|
|
|
|
// String returns a human-readable version string
|
|
func String() string {
|
|
return fmt.Sprintf("RedFlag Agent v%s (config v%s)", Version, ConfigVersion)
|
|
}
|
|
|
|
// FullString returns detailed version information
|
|
func FullString() string {
|
|
info := GetInfo()
|
|
return fmt.Sprintf("RedFlag Agent v%s (config v%s)\n"+
|
|
"Built: %s\n"+
|
|
"Commit: %s\n"+
|
|
"Go: %s",
|
|
info.AgentVersion,
|
|
info.ConfigVersion,
|
|
info.BuildTime,
|
|
info.GitCommit,
|
|
info.GoVersion)
|
|
}
|
|
|
|
// CheckCompatible checks if the given config version is compatible with this agent
|
|
func CheckCompatible(configVer string) error {
|
|
if configVer == "" {
|
|
return fmt.Errorf("config version is empty")
|
|
}
|
|
|
|
// For now, require exact match
|
|
// In the future, we may support backward/forward compatibility matrices
|
|
if configVer != ConfigVersion {
|
|
return fmt.Errorf("config version mismatch: agent expects v%s, config has v%s",
|
|
ConfigVersion, configVer)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Valid checks if version information is properly set
|
|
func Valid() bool {
|
|
return Version != "dev" && ConfigVersion != "dev"
|
|
} |