swap uuid lib, windows installer pass, README/RAF copy
- google/uuid -> gofrs/uuid/v5 across server + agent - windows.go: cross-platform binding cleanup - linux install template: disable sudo lecture for TTY-less service user - README: XZ/SolarWinds lede, stable-release note, single attack-surface block
This commit is contained in:
parent
6327c13460
commit
5758b26875
110 changed files with 613 additions and 495 deletions
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
| Section | Description |
|
||||
|---------|-------------|
|
||||
| [[p0-priority-start-here]] | **START HERE** — priority roadmap, current blockers, next steps |
|
||||
| [[OVERVIEW]] | **START HERE** — architecture overview: what RedFlag is, the two capability tiers, architectural boundaries |
|
||||
| [[core]] | Core concepts, ETHOS principles, architectural decisions |
|
||||
| [[components]] | Server, agent, web — component breakdowns |
|
||||
| [[security]] | Trust boundaries, auth layers, machine binding, key management, supply chain gate |
|
||||
|
|
|
|||
|
|
@ -5,17 +5,18 @@
|
|||
`v0.2.3.1` — June 2026 · MIT License
|
||||
|
||||
> **You're early — nearly 600 of you cloned this before it was announced.**
|
||||
> A stable release is coming soon, bringing Windows support back fully gated.
|
||||
> If you want it to keep existing, [sponsor the work](#sponsorship--consulting).
|
||||
|
||||
---
|
||||
|
||||
The update manager is part of your attack surface. Most homelab tooling ignores this. RedFlag doesn't.
|
||||
The software that patches your fleet runs as root on every box. XZ Utils came through a build pipeline. SolarWinds came through an update. The update manager is part of your attack surface — most homelab tooling ignores that. RedFlag treats it as the attack surface it is.
|
||||
|
||||
Every command the server issues is Ed25519-signed. Agents verify the signature, check the nonce, validate the timestamp, and reject anything they've seen before. The signing key never leaves your server. Hardware binding means a stolen agent config doesn't work on a different machine. You can read the security model in the code, not in marketing copy.
|
||||
|
||||
It also just manages your updates — across Linux and Windows, including Docker containers running on those hosts — from a single dashboard, with a human approval step before anything gets installed.
|
||||
The supply-chain gate goes deeper: when an update is approved, the server resolves the full dependency closure, checks every transitive artifact against OSV.dev, and mints a signed capability token binding the exact artifact hashes. A network-less privileged executor verifies the signature and every hash before anything installs — it can't reach out and can't be redirected. A known vulnerability anywhere in the closure is a full stop: the operator must override with a documented reason, or the token is never minted. The signing and hash verification have no skip path.
|
||||
|
||||
The supply-chain gate goes deeper: when an update is approved, the server resolves the full dependency closure, checks every transitive artifact against OSV.dev, and mints a signed capability token binding the exact artifact hashes. A network-less privileged executor verifies the signature and every hash before anything installs. A known vulnerability anywhere in the closure is a full stop — the operator must override with a documented reason, or the token is never minted. The signing and hash verification have no skip path.
|
||||
It also just manages your updates — across Linux and Windows, including Docker containers running on those hosts — from a single dashboard, with a human approval step before anything gets installed.
|
||||
|
||||
ConnectWise charges $50/agent/month. RedFlag doesn't.
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
|
|
@ -7,7 +7,7 @@ require (
|
|||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/docker/docker v27.4.1+incompatible
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gofrs/uuid/v5 v5.4.0
|
||||
github.com/scjalliance/comshim v0.0.0-20250111221056-b2ef9d8d7e0f
|
||||
golang.org/x/sys v0.45.0
|
||||
)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
|||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s=
|
||||
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
|
||||
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
||||
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
|
|
|
|||
2
agent/internal/cache/local.go
vendored
2
agent/internal/cache/local.go
vendored
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
"github.com/Fimeg/RedFlag/agent/internal/client"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/constants"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// LocalCache stores scan results locally for offline viewing
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
// The canonical signed message and closure hash MUST stay byte-identical across
|
||||
// this package, the server's mirror of it, and helper/src/main.rs. See
|
||||
// RAF/SUPPLY_CHAIN_GATE_PLAN.md for the contract.
|
||||
// RAF/security/05-supply-chain-gate.md for the contract.
|
||||
package capability
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/agent/internal/event"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/models"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/system"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// Auth sentinel errors. The polling loop branches on these via errors.Is rather
|
||||
|
|
@ -108,7 +108,7 @@ func (c *Client) bufferEventInternal(eventType, eventSubtype, severity, componen
|
|||
}
|
||||
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentIDPtr,
|
||||
EventType: eventType,
|
||||
EventSubtype: eventSubtype,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
"github.com/Fimeg/RedFlag/agent/internal/constants"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/version"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// MigrationState tracks migration completion status (used by migration package)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
|
||||
"sync"
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/agent/internal/client"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/config"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/installer"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/models"
|
||||
)
|
||||
|
||||
// HandleDryRunUpdate runs a package-manager dry-run for the named update so
|
||||
|
|
@ -166,10 +167,18 @@ func resolveClosureHashes(packageType, packageName string, dependencies []string
|
|||
// HandleConfirmDependencies installs a package together with the dependencies
|
||||
// the operator confirmed during the dry-run review. Empty dependency list is
|
||||
// allowed — that path resolves to a simple UpdatePackage on the named target.
|
||||
func HandleConfirmDependencies(apiClient *client.Client, cfg *config.Config, ackTracker *acknowledgment.Tracker, params map[string]interface{}, commandID string) error {
|
||||
func HandleConfirmDependencies(apiClient *client.Client, cfg *config.Config, ackTracker *acknowledgment.Tracker, params map[string]interface{}, commandID string) (err error) {
|
||||
packageType, _ := params["package_type"].(string)
|
||||
packageName, _ := params["package_name"].(string)
|
||||
|
||||
// Every error return below surfaces on the History channel as a failed
|
||||
// install event (ETHOS #1), inward via the client event buffer.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
emitInstallEvent(apiClient, models.SubtypeFailed, models.SeverityError, packageType, packageName, commandID, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
if packageType == "" || packageName == "" {
|
||||
return fmt.Errorf("package_type and package_name parameters are required")
|
||||
}
|
||||
|
|
@ -200,45 +209,25 @@ func HandleConfirmDependencies(apiClient *client.Client, cfg *config.Config, ack
|
|||
var result *installer.InstallResult
|
||||
var action string
|
||||
|
||||
// Non-gated ecosystems — type-assert to concrete type for mutation.
|
||||
switch i := inst.(type) {
|
||||
case *installer.WingetInstaller:
|
||||
if len(dependencies) > 0 {
|
||||
action = "install_with_dependencies"
|
||||
log.Printf("[INFO] [agent] [installer] install_with_deps package=%s deps=%v", packageName, dependencies)
|
||||
allPackages := append([]string{packageName}, dependencies...)
|
||||
result, err = i.InstallMultiple(allPackages)
|
||||
} else {
|
||||
action = "update"
|
||||
log.Printf("[INFO] [agent] [installer] update_package package=%s", packageName)
|
||||
result, err = i.UpdatePackage(packageName)
|
||||
}
|
||||
case *installer.WindowsUpdateInstaller:
|
||||
if len(dependencies) > 0 {
|
||||
action = "install_with_dependencies"
|
||||
log.Printf("[INFO] [agent] [installer] install_with_deps package=%s deps=%v", packageName, dependencies)
|
||||
allPackages := append([]string{packageName}, dependencies...)
|
||||
result, err = i.InstallMultiple(allPackages)
|
||||
} else {
|
||||
action = "update"
|
||||
log.Printf("[INFO] [agent] [installer] update_package package=%s", packageName)
|
||||
result, err = i.UpdatePackage(packageName)
|
||||
}
|
||||
case *installer.DockerInstaller:
|
||||
if len(dependencies) > 0 {
|
||||
action = "install_with_dependencies"
|
||||
log.Printf("[INFO] [agent] [installer] install_with_deps package=%s deps=%v", packageName, dependencies)
|
||||
allPackages := append([]string{packageName}, dependencies...)
|
||||
result, err = i.InstallMultiple(allPackages)
|
||||
} else {
|
||||
action = "update"
|
||||
log.Printf("[INFO] [agent] [installer] update_package package=%s", packageName)
|
||||
result, err = i.UpdatePackage(packageName)
|
||||
}
|
||||
default:
|
||||
// Non-gated ecosystems mutate directly. dnf/apt do not implement
|
||||
// NonGatedInstaller, so the assertion fails for them — the gate boundary
|
||||
// is structural here, not a packageType guard.
|
||||
mut, ok := inst.(installer.NonGatedInstaller)
|
||||
if !ok {
|
||||
return fmt.Errorf("[ERROR] [agent] [installer] direct_confirm_not_supported type=%s", packageType)
|
||||
}
|
||||
|
||||
if len(dependencies) > 0 {
|
||||
action = "install_with_dependencies"
|
||||
log.Printf("[INFO] [agent] [installer] install_with_deps package=%s deps=%v", packageName, dependencies)
|
||||
allPackages := append([]string{packageName}, dependencies...)
|
||||
result, err = mut.InstallMultiple(allPackages)
|
||||
} else {
|
||||
action = "update"
|
||||
log.Printf("[INFO] [agent] [installer] update_package package=%s", packageName)
|
||||
result, err = mut.UpdatePackage(packageName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
stdout, stderr, exitCode, duration := "", err.Error(), 1, 0
|
||||
if result != nil {
|
||||
|
|
@ -285,6 +274,9 @@ func HandleConfirmDependencies(apiClient *client.Client, cfg *config.Config, ack
|
|||
log.Printf("[WARNING] [agent] [installer] report_install_failed error=%v", reportErr)
|
||||
}
|
||||
|
||||
emitInstallEvent(apiClient, models.SubtypeSuccess, models.SeverityInfo, packageType, packageName, commandID,
|
||||
fmt.Sprintf("%s of %s completed in %ds", action, packageName, result.DurationSeconds))
|
||||
|
||||
log.Printf("[INFO] [agent] [installer] install_complete action=%s package=%s duration=%ds",
|
||||
action, packageName, result.DurationSeconds)
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -10,8 +10,26 @@ import (
|
|||
"github.com/Fimeg/RedFlag/agent/internal/client"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/config"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/installer"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/models"
|
||||
)
|
||||
|
||||
// emitInstallEvent records a package-install outcome on the system-event
|
||||
// channel so it reaches the History page (ETHOS #1: errors are history).
|
||||
// It routes inward through the client's event buffer, which persists to disk
|
||||
// and flushes to the server independently of the command-result path — a
|
||||
// failed install is auditable even if the result report is lost.
|
||||
func emitInstallEvent(apiClient *client.Client, subtype, severity, packageType, packageName, commandID, message string) {
|
||||
apiClient.BufferEvent(
|
||||
models.EventTypeAgentInstall, subtype, severity,
|
||||
packageType+"_installer", message,
|
||||
map[string]interface{}{
|
||||
"package_type": packageType,
|
||||
"package_name": packageName,
|
||||
"command_id": commandID,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// ExpectedHashes maps package names to their expected SHA256 hashes
|
||||
func ExpectedHashes(cfg *config.Config) map[string]string {
|
||||
hashes := make(map[string]string)
|
||||
|
|
@ -28,10 +46,19 @@ func FetchExpectedHash(apiClient *client.Client, cfg *config.Config, packageName
|
|||
return apiClient.GetExpectedHash(packageType, packageName, cfg.AgentID)
|
||||
}
|
||||
|
||||
func HandleInstallUpdates(apiClient *client.Client, cfg *config.Config, ackTracker *acknowledgment.Tracker, params map[string]interface{}, commandID string) error {
|
||||
func HandleInstallUpdates(apiClient *client.Client, cfg *config.Config, ackTracker *acknowledgment.Tracker, params map[string]interface{}, commandID string) (err error) {
|
||||
packageType, _ := params["package_type"].(string)
|
||||
packageName, _ := params["package_name"].(string)
|
||||
|
||||
// Any error return below lands on the History channel as a failed install
|
||||
// event — one inward funnel covering every failure path (factory, hash
|
||||
// verification, the install itself).
|
||||
defer func() {
|
||||
if err != nil {
|
||||
emitInstallEvent(apiClient, models.SubtypeFailed, models.SeverityError, packageType, packageName, commandID, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
if packageType == "" {
|
||||
return fmt.Errorf("package_type parameter is required")
|
||||
}
|
||||
|
|
@ -76,42 +103,23 @@ func HandleInstallUpdates(apiClient *client.Client, cfg *config.Config, ackTrack
|
|||
return fmt.Errorf("[SECURITY] [agent] [installer] direct_mutation_refused type=%s — mutation must go through capability-token path", packageType)
|
||||
}
|
||||
|
||||
// Non-gated ecosystems — type-assert to concrete type for mutation.
|
||||
switch i := inst.(type) {
|
||||
case *installer.WingetInstaller:
|
||||
if packageName != "" {
|
||||
action = "update"
|
||||
log.Printf("[INFO] [agent] [installer] updating_package package=%s type=%s", packageName, packageType)
|
||||
result, err = i.UpdatePackage(packageName)
|
||||
} else {
|
||||
action = "upgrade"
|
||||
log.Printf("[INFO] [agent] [installer] upgrading_all type=%s", packageType)
|
||||
result, err = i.Upgrade()
|
||||
}
|
||||
case *installer.WindowsUpdateInstaller:
|
||||
if packageName != "" {
|
||||
action = "update"
|
||||
log.Printf("[INFO] [agent] [installer] updating_package package=%s type=%s", packageName, packageType)
|
||||
result, err = i.UpdatePackage(packageName)
|
||||
} else {
|
||||
action = "upgrade"
|
||||
log.Printf("[INFO] [agent] [installer] upgrading_all type=%s", packageType)
|
||||
result, err = i.Upgrade()
|
||||
}
|
||||
case *installer.DockerInstaller:
|
||||
if packageName != "" {
|
||||
action = "update"
|
||||
log.Printf("[INFO] [agent] [installer] updating_package package=%s type=%s", packageName, packageType)
|
||||
result, err = i.UpdatePackage(packageName)
|
||||
} else {
|
||||
action = "upgrade"
|
||||
log.Printf("[INFO] [agent] [installer] upgrading_all type=%s", packageType)
|
||||
result, err = i.Upgrade()
|
||||
}
|
||||
default:
|
||||
// Non-gated ecosystems mutate directly. dnf/apt are excluded above and
|
||||
// do not implement NonGatedInstaller, so the assertion fails for them.
|
||||
mut, ok := inst.(installer.NonGatedInstaller)
|
||||
if !ok {
|
||||
return fmt.Errorf("[ERROR] [agent] [installer] direct_mutation_not_supported type=%s", packageType)
|
||||
}
|
||||
|
||||
if packageName != "" {
|
||||
action = "update"
|
||||
log.Printf("[INFO] [agent] [installer] updating_package package=%s type=%s", packageName, packageType)
|
||||
result, err = mut.UpdatePackage(packageName)
|
||||
} else {
|
||||
action = "upgrade"
|
||||
log.Printf("[INFO] [agent] [installer] upgrading_all type=%s", packageType)
|
||||
result, err = mut.Upgrade()
|
||||
}
|
||||
|
||||
duration := int(time.Since(startTime).Seconds())
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -162,6 +170,9 @@ func HandleInstallUpdates(apiClient *client.Client, cfg *config.Config, ackTrack
|
|||
log.Printf("[WARNING] [agent] [installer] report_failed error=%v", reportErr)
|
||||
}
|
||||
|
||||
emitInstallEvent(apiClient, models.SubtypeSuccess, models.SeverityInfo, packageType, packageName, commandID,
|
||||
fmt.Sprintf("%s of %s completed in %ds", result.Action, packageName, duration))
|
||||
|
||||
log.Printf("[INFO] [agent] [installer] install_complete action=%s type=%s duration=%ds", action, packageType, duration)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,21 @@ type Installer interface {
|
|||
VerifyHash(packageName, version, expectedSHA256 string) error
|
||||
}
|
||||
|
||||
// NonGatedInstaller is the set of ecosystems still permitted to mutate
|
||||
// directly from the agent (winget, docker, windows_update). dnf/apt
|
||||
// deliberately do NOT implement these methods — their mutation flows only
|
||||
// through the capability-token path (supplychain.Consumer → redflag-helper).
|
||||
//
|
||||
// This interface is the ledger of "what may still bypass the gate." As
|
||||
// ecosystems move behind the capability gate, drop them from this set and
|
||||
// the type assertions in the handlers begin failing for them automatically —
|
||||
// the boundary is structural, not a guard someone has to remember to add.
|
||||
type NonGatedInstaller interface {
|
||||
UpdatePackage(packageName string) (*InstallResult, error)
|
||||
Upgrade() (*InstallResult, error)
|
||||
InstallMultiple(packageNames []string) (*InstallResult, error)
|
||||
}
|
||||
|
||||
// InstallerFactory creates appropriate installer based on package type
|
||||
func InstallerFactory(packageType string, serverURL string) (Installer, error) {
|
||||
switch packageType {
|
||||
|
|
|
|||
|
|
@ -3,13 +3,28 @@ package installer
|
|||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/agent/pkg/windowsupdate"
|
||||
"github.com/go-ole/go-ole"
|
||||
"github.com/scjalliance/comshim"
|
||||
)
|
||||
|
||||
// WindowsUpdateInstaller handles Windows Update installation
|
||||
// Windows Update Agent operation result codes.
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-operationresultcode
|
||||
const (
|
||||
orcSucceeded int32 = 2
|
||||
orcSucceededWithErrors int32 = 3
|
||||
)
|
||||
|
||||
// WindowsUpdateInstaller installs Windows updates through the Windows Update
|
||||
// Agent COM API (Microsoft.Update.Session) via go-ole — the same binding the WUA
|
||||
// scanner uses. No shelling out: wuauclt's /updatenow was removed on Windows 10,
|
||||
// and Install-WindowsUpdate needs the third-party PSWindowsUpdate module. The COM
|
||||
// binding compiles cross-platform (go-ole ships non-Windows stubs); IsAvailable()
|
||||
// gates execution to Windows at runtime.
|
||||
type WindowsUpdateInstaller struct{}
|
||||
|
||||
// NewWindowsUpdateInstaller creates a new Windows Update installer
|
||||
|
|
@ -17,9 +32,8 @@ func NewWindowsUpdateInstaller() *WindowsUpdateInstaller {
|
|||
return &WindowsUpdateInstaller{}
|
||||
}
|
||||
|
||||
// IsAvailable checks if Windows Update installer is available on this system
|
||||
// IsAvailable reports whether this installer can run on the current host.
|
||||
func (i *WindowsUpdateInstaller) IsAvailable() bool {
|
||||
// Only available on Windows
|
||||
return runtime.GOOS == "windows"
|
||||
}
|
||||
|
||||
|
|
@ -28,185 +42,251 @@ func (i *WindowsUpdateInstaller) GetPackageType() string {
|
|||
return "windows_update"
|
||||
}
|
||||
|
||||
// Install installs a specific Windows update
|
||||
// Install installs a specific Windows update by title.
|
||||
func (i *WindowsUpdateInstaller) Install(packageName string) (*InstallResult, error) {
|
||||
return i.installUpdates([]string{packageName}, false)
|
||||
}
|
||||
|
||||
// InstallMultiple installs multiple Windows updates
|
||||
// InstallMultiple installs multiple Windows updates by title.
|
||||
func (i *WindowsUpdateInstaller) InstallMultiple(packageNames []string) (*InstallResult, error) {
|
||||
return i.installUpdates(packageNames, false)
|
||||
}
|
||||
|
||||
// Upgrade installs all available Windows updates
|
||||
// Upgrade installs every available Windows update.
|
||||
func (i *WindowsUpdateInstaller) Upgrade() (*InstallResult, error) {
|
||||
return i.installUpdates(nil, true) // nil means all updates
|
||||
return i.installUpdates(nil, true)
|
||||
}
|
||||
|
||||
// DryRun performs a dry run installation to check what would be installed
|
||||
// UpdatePackage updates a specific Windows update (alias for Install).
|
||||
func (i *WindowsUpdateInstaller) UpdatePackage(packageName string) (*InstallResult, error) {
|
||||
return i.Install(packageName)
|
||||
}
|
||||
|
||||
// DryRun reports which updates would be installed for the given title without
|
||||
// installing anything.
|
||||
func (i *WindowsUpdateInstaller) DryRun(packageName, version string) (*InstallResult, error) {
|
||||
return i.installUpdates([]string{packageName}, true)
|
||||
}
|
||||
|
||||
// installUpdates is the internal implementation for Windows update installation
|
||||
// VerifyHash is a no-op for Windows Update: the WUA validates update payloads
|
||||
// against Microsoft's signed catalog itself, and individual updates expose no
|
||||
// addressable download URL to hash. Fail-open by design.
|
||||
func (i *WindowsUpdateInstaller) VerifyHash(packageName, version, expectedSHA256 string) error {
|
||||
log.Printf("[INFO] [agent] [installer] hash_verification_skipped package=%s reason=windows_update_uses_wua_verification", packageName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// installUpdates searches the Windows Update Agent for the requested updates and
|
||||
// runs the download -> accept-EULA -> install lifecycle through the COM API.
|
||||
// packageNames are matched against update titles (the scanner reports Title as the
|
||||
// package name); a nil/empty slice means "all available updates" (upgrade).
|
||||
func (i *WindowsUpdateInstaller) installUpdates(packageNames []string, isDryRun bool) (*InstallResult, error) {
|
||||
if !i.IsAvailable() {
|
||||
return nil, fmt.Errorf("Windows Update installer is only available on Windows")
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
// Determine action type
|
||||
action := "install"
|
||||
if packageNames == nil {
|
||||
action = "upgrade" // Upgrade all updates
|
||||
if len(packageNames) == 0 {
|
||||
action = "upgrade"
|
||||
}
|
||||
|
||||
result := &InstallResult{
|
||||
Success: false,
|
||||
IsDryRun: isDryRun,
|
||||
Action: action,
|
||||
DurationSeconds: 0,
|
||||
Success: false,
|
||||
IsDryRun: isDryRun,
|
||||
Action: action,
|
||||
PackagesInstalled: []string{},
|
||||
Dependencies: []string{},
|
||||
Dependencies: []string{},
|
||||
}
|
||||
|
||||
// Initialize COM (mirror the WUA scanner's pattern).
|
||||
comshim.Add(1)
|
||||
defer comshim.Done()
|
||||
ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_SPEED_OVER_MEMORY)
|
||||
defer ole.CoUninitialize()
|
||||
|
||||
session, err := windowsupdate.NewUpdateSession()
|
||||
if err != nil {
|
||||
return i.fail(result, startTime, fmt.Errorf("create Windows Update session: %w", err))
|
||||
}
|
||||
|
||||
searcher, err := session.CreateUpdateSearcher()
|
||||
if err != nil {
|
||||
return i.fail(result, startTime, fmt.Errorf("create update searcher: %w", err))
|
||||
}
|
||||
|
||||
searchResult, err := searcher.Search("IsInstalled=0 AND IsHidden=0")
|
||||
if err != nil {
|
||||
return i.fail(result, startTime, fmt.Errorf("search for updates: %w", err))
|
||||
}
|
||||
|
||||
selected := selectUpdates(searchResult.Updates, packageNames)
|
||||
if len(selected) == 0 {
|
||||
if len(packageNames) == 0 {
|
||||
// Nothing to upgrade — a clean no-op, not a failure.
|
||||
result.Success = true
|
||||
result.Stdout = "No applicable Windows updates available"
|
||||
result.DurationSeconds = int(time.Since(startTime).Seconds())
|
||||
return result, nil
|
||||
}
|
||||
return i.fail(result, startTime,
|
||||
fmt.Errorf("requested update(s) not found among available Windows updates: %v", packageNames))
|
||||
}
|
||||
|
||||
if isDryRun {
|
||||
// For dry run, simulate what would be installed
|
||||
result.Success = true
|
||||
result.Stdout = i.formatDryRunOutput(packageNames)
|
||||
result.Stdout = formatSelected(selected)
|
||||
result.PackagesInstalled = updateTitles(selected) // what WOULD be installed
|
||||
result.DurationSeconds = int(time.Since(startTime).Seconds())
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Method 1: Try PowerShell Windows Update module
|
||||
var lastErr error
|
||||
var lastStderr string
|
||||
if updates, err := i.installViaPowerShell(packageNames); err == nil {
|
||||
result.Success = true
|
||||
result.Stdout = updates
|
||||
result.PackagesInstalled = packageNames
|
||||
} else {
|
||||
lastErr = err
|
||||
// Method 2: Try wuauclt (Windows Update client, deprecated since Win 10 1709)
|
||||
if updates, err := i.installViaWuauclt(packageNames); err == nil {
|
||||
result.Success = true
|
||||
result.Stdout = updates
|
||||
result.PackagesInstalled = packageNames
|
||||
} else {
|
||||
lastStderr = err.Error()
|
||||
// Accept EULAs where required before download/install.
|
||||
for _, u := range selected {
|
||||
if !u.EulaAccepted {
|
||||
if err := u.AcceptEula(); err != nil {
|
||||
return i.fail(result, startTime, fmt.Errorf("accept EULA for %q: %w", u.Title, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If neither method succeeded, report the error — do not fake success
|
||||
if !result.Success {
|
||||
result.DurationSeconds = int(time.Since(startTime).Seconds())
|
||||
result.ErrorMessage = fmt.Sprintf(
|
||||
"no working Windows update mechanism available: PowerShell failed (%v), wuauclt failed (%s)",
|
||||
lastErr, lastStderr)
|
||||
return result, fmt.Errorf("%s", result.ErrorMessage)
|
||||
// Download any updates not already cached.
|
||||
downloader, err := session.CreateUpdateDownloader()
|
||||
if err != nil {
|
||||
return i.fail(result, startTime, fmt.Errorf("create update downloader: %w", err))
|
||||
}
|
||||
dlResult, err := downloader.Download(selected)
|
||||
if err != nil {
|
||||
return i.fail(result, startTime, fmt.Errorf("download updates: %w", err))
|
||||
}
|
||||
if !wuaSucceeded(dlResult.ResultCode) {
|
||||
return i.fail(result, startTime,
|
||||
fmt.Errorf("Windows Update download failed: ResultCode=%d HResult=0x%08X", dlResult.ResultCode, uint32(dlResult.HResult)))
|
||||
}
|
||||
|
||||
// Install.
|
||||
inst, err := session.CreateUpdateInstaller()
|
||||
if err != nil {
|
||||
return i.fail(result, startTime, fmt.Errorf("create update installer: %w", err))
|
||||
}
|
||||
instResult, err := inst.Install(selected)
|
||||
if err != nil {
|
||||
return i.fail(result, startTime, fmt.Errorf("install updates: %w", err))
|
||||
}
|
||||
if !wuaSucceeded(instResult.ResultCode) {
|
||||
return i.fail(result, startTime,
|
||||
fmt.Errorf("Windows Update install failed: ResultCode=%d HResult=0x%08X", instResult.ResultCode, uint32(instResult.HResult)))
|
||||
}
|
||||
|
||||
result.Success = true
|
||||
result.PackagesInstalled = updateTitles(selected)
|
||||
result.RebootRequired = instResult.RebootRequired
|
||||
result.Stdout = fmt.Sprintf("Installed %d Windows update(s): %s", len(selected), strings.Join(updateTitles(selected), "; "))
|
||||
result.DurationSeconds = int(time.Since(startTime).Seconds())
|
||||
|
||||
// F-C1-3 fix: post-install verification marker
|
||||
// TODO(DEV-031): Wire RebootRequired into scanner-side filtering.
|
||||
// Currently this flag is set and reported to the server but the
|
||||
// scanner does not suppress recently-installed updates from results.
|
||||
if result.Success && !isDryRun {
|
||||
result.RebootRequired = true
|
||||
log.Printf("[INFO] [agent] [installer] windows_update_installed packages=%v reboot_required=%v duration=%ds",
|
||||
packageNames, result.RebootRequired, result.DurationSeconds)
|
||||
}
|
||||
log.Printf("[INFO] [agent] [installer] windows_update_installed packages=%v reboot_required=%v duration=%ds",
|
||||
result.PackagesInstalled, result.RebootRequired, result.DurationSeconds)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// installViaPowerShell uses PowerShell to install Windows updates
|
||||
func (i *WindowsUpdateInstaller) installViaPowerShell(packageNames []string) (string, error) {
|
||||
// PowerShell command to install updates
|
||||
for _, packageName := range packageNames {
|
||||
cmd := exec.Command("powershell", "-Command",
|
||||
fmt.Sprintf("Install-WindowsUpdate -Title '%s' -AcceptAll -AutoRestart", packageName))
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return string(output), fmt.Errorf("PowerShell installation failed for %s: %w (output: %s)", packageName, err, string(output))
|
||||
}
|
||||
}
|
||||
|
||||
return "Windows Updates installed via PowerShell", nil
|
||||
}
|
||||
|
||||
// UpdatePackage updates a specific Windows update (alias for Install method)
|
||||
func (i *WindowsUpdateInstaller) UpdatePackage(packageName string) (*InstallResult, error) {
|
||||
// Windows uses same logic for updating as installing
|
||||
return i.Install(packageName)
|
||||
}
|
||||
|
||||
// VerifyHash verifies the Windows update hash before installation (fail-open)
|
||||
func (i *WindowsUpdateInstaller) VerifyHash(packageName, version, expectedSHA256 string) error {
|
||||
if expectedSHA256 == "" {
|
||||
return nil // No hash to verify
|
||||
}
|
||||
|
||||
// Windows Update doesn't expose direct download URLs for individual updates
|
||||
// Hash verification would require accessing WSUS or Windows Update API
|
||||
// Fail open - Windows has its own update verification via Windows Update Agent
|
||||
log.Printf("[INFO] [agent] [installer] hash_verification_skipped package=%s reason=windows_update_uses_wua_verification", packageName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// installViaWuauclt uses traditional Windows Update client (deprecated since Win 10 1709)
|
||||
func (i *WindowsUpdateInstaller) installViaWuauclt(packageNames []string) (string, error) {
|
||||
// Force detection of updates
|
||||
cmd := exec.Command("cmd", "/c", "wuauclt /detectnow")
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return "", fmt.Errorf("wuauclt detectnow failed: %w (output: %s)", err, string(out))
|
||||
}
|
||||
|
||||
// Wait for detection
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// Install updates
|
||||
cmd = exec.Command("cmd", "/c", "wuauclt /updatenow")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return string(output), fmt.Errorf("wuauclt updatenow failed: %w (output: %s)", err, string(output))
|
||||
}
|
||||
|
||||
// wuauclt returns exit code 0 even when it does nothing on modern Windows.
|
||||
// Check stderr for meaningful failure signals.
|
||||
stderr := string(output)
|
||||
if strings.Contains(stderr, "not recognized") || strings.Contains(stderr, "failed") {
|
||||
return stderr, fmt.Errorf("wuauclt reported failure: %s", stderr)
|
||||
}
|
||||
|
||||
return "Windows Updates installation initiated via wuauclt", nil
|
||||
}
|
||||
|
||||
// formatDryRunOutput creates formatted output for dry run operations
|
||||
func (i *WindowsUpdateInstaller) formatDryRunOutput(packageNames []string) string {
|
||||
var output []string
|
||||
output = append(output, "Dry run - the following updates would be installed:")
|
||||
output = append(output, "")
|
||||
|
||||
for _, name := range packageNames {
|
||||
output = append(output, fmt.Sprintf("• %s", name))
|
||||
output = append(output, fmt.Sprintf(" Method: Windows Update (PowerShell/wuauclt)"))
|
||||
output = append(output, fmt.Sprintf(" Requires: Administrator privileges"))
|
||||
output = append(output, "")
|
||||
}
|
||||
|
||||
return strings.Join(output, "\n")
|
||||
}
|
||||
|
||||
// GetPendingUpdates returns a list of pending Windows updates
|
||||
// TODO: Replace with Microsoft.Update.Session COM API via go-ole (see CRITICAL-005)
|
||||
// GetPendingUpdates returns the titles of updates the Windows Update Agent reports
|
||||
// as applicable but not yet installed.
|
||||
func (i *WindowsUpdateInstaller) GetPendingUpdates() ([]string, error) {
|
||||
if !i.IsAvailable() {
|
||||
return nil, fmt.Errorf("Windows Update installer is only available on Windows")
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("GetPendingUpdates not implemented — requires Microsoft.Update.Session COM API via go-ole")
|
||||
comshim.Add(1)
|
||||
defer comshim.Done()
|
||||
ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_SPEED_OVER_MEMORY)
|
||||
defer ole.CoUninitialize()
|
||||
|
||||
session, err := windowsupdate.NewUpdateSession()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create Windows Update session: %w", err)
|
||||
}
|
||||
searcher, err := session.CreateUpdateSearcher()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create update searcher: %w", err)
|
||||
}
|
||||
searchResult, err := searcher.Search("IsInstalled=0 AND IsHidden=0")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search for updates: %w", err)
|
||||
}
|
||||
return updateTitles(searchResult.Updates), nil
|
||||
}
|
||||
|
||||
// fail finalizes a failed InstallResult: records the error, stamps duration, and
|
||||
// returns it alongside the error so the caller reports ground truth (no fake success).
|
||||
func (i *WindowsUpdateInstaller) fail(result *InstallResult, start time.Time, err error) (*InstallResult, error) {
|
||||
result.Success = false
|
||||
result.ErrorMessage = err.Error()
|
||||
result.Stderr = err.Error()
|
||||
result.ExitCode = 1
|
||||
result.DurationSeconds = int(time.Since(start).Seconds())
|
||||
return result, err
|
||||
}
|
||||
|
||||
// selectUpdates picks the updates to act on. An empty names slice selects every
|
||||
// available update (upgrade-all). Otherwise an update is selected when a requested
|
||||
// name matches its title (exact, then case-insensitive contains) or one of its KB
|
||||
// article IDs.
|
||||
func selectUpdates(available []*windowsupdate.IUpdate, names []string) []*windowsupdate.IUpdate {
|
||||
if len(names) == 0 {
|
||||
return available
|
||||
}
|
||||
var selected []*windowsupdate.IUpdate
|
||||
for _, u := range available {
|
||||
if updateMatchesAny(u, names) {
|
||||
selected = append(selected, u)
|
||||
}
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
func updateMatchesAny(u *windowsupdate.IUpdate, names []string) bool {
|
||||
title := strings.TrimSpace(u.Title)
|
||||
lowerTitle := strings.ToLower(title)
|
||||
for _, name := range names {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if title == name || strings.Contains(lowerTitle, strings.ToLower(name)) {
|
||||
return true
|
||||
}
|
||||
for _, kb := range u.KBArticleIDs {
|
||||
// KB IDs come back without the "KB" prefix; match either form.
|
||||
if strings.EqualFold(kb, name) || strings.EqualFold("KB"+kb, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func updateTitles(updates []*windowsupdate.IUpdate) []string {
|
||||
titles := make([]string, 0, len(updates))
|
||||
for _, u := range updates {
|
||||
titles = append(titles, u.Title)
|
||||
}
|
||||
return titles
|
||||
}
|
||||
|
||||
func formatSelected(updates []*windowsupdate.IUpdate) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("Dry run - the following Windows updates would be installed:\n")
|
||||
for _, u := range updates {
|
||||
fmt.Fprintf(&b, " - %s", u.Title)
|
||||
if len(u.KBArticleIDs) > 0 {
|
||||
fmt.Fprintf(&b, " (KB%s)", strings.Join(u.KBArticleIDs, ", KB"))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func wuaSucceeded(resultCode int32) bool {
|
||||
return resultCode == orcSucceeded || resultCode == orcSucceededWithErrors
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/agent/internal/common"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/event"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// MigrationPlan represents a complete migration plan
|
||||
|
|
@ -79,7 +79,7 @@ func (e *MigrationExecutor) bufferEvent(eventSubtype, severity, component, messa
|
|||
}
|
||||
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentIDPtr,
|
||||
EventType: "migration_failure",
|
||||
EventSubtype: eventSubtype,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/agent/internal/migration"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/migration/pathutils"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// FileValidator handles comprehensive file validation for migration
|
||||
|
|
@ -219,7 +219,7 @@ func (v *FileValidator) ValidateBackupLocation(backupPath string) error {
|
|||
}
|
||||
|
||||
// Test write permission (create a temp file)
|
||||
testFile := filepath.Join(parent, ".migration_test_"+uuid.New().String()[:8])
|
||||
testFile := filepath.Join(parent, ".migration_test_"+uuid.Must(uuid.NewV4()).String()[:8])
|
||||
if err := os.WriteFile(testFile, []byte("test"), 0600); err != nil {
|
||||
return fmt.Errorf("backup directory not writable: %w", err)
|
||||
}
|
||||
|
|
@ -335,7 +335,7 @@ func (v *FileValidator) bufferEvent(eventSubtype, severity, component, message s
|
|||
}
|
||||
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: &v.agentID,
|
||||
EventType: models.EventTypeAgentMigration, // Using model constant
|
||||
EventSubtype: eventSubtype,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// StorageMetricReport represents storage metrics from an agent
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// SystemEvent represents a unified event log entry for all system events
|
||||
|
|
@ -27,6 +27,7 @@ const (
|
|||
EventTypeAgentCheckIn = "agent_checkin"
|
||||
EventTypeAgentScan = "agent_scan"
|
||||
EventTypeAgentUpdate = "agent_update"
|
||||
EventTypeAgentInstall = "agent_install"
|
||||
EventTypeAgentConfig = "agent_config"
|
||||
EventTypeAgentMigration = "agent_migration"
|
||||
EventTypeAgentShutdown = "agent_shutdown"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/agent/internal/config"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/crypto"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/logging"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// ConfirmedTracker is a disk-persisted set of command IDs the server has confirmed
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/agent/internal/capability"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// DefaultExecutorPath is where the privileged executor binary is expected. The
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ services:
|
|||
context: .
|
||||
dockerfile: ./server/Dockerfile
|
||||
args:
|
||||
BUILD_VERSION: ${BUILD_VERSION:-0.2.3.1}
|
||||
BUILD_VERSION: ${BUILD_VERSION:-0.2.3.4}
|
||||
container_name: redflag-server
|
||||
volumes:
|
||||
- server-config:/app/config
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
// on every error path. The signing authority lives off this host; this process
|
||||
// only verifies and executes.
|
||||
//
|
||||
// Contract: see RAF/SUPPLY_CHAIN_GATE_PLAN.md. The canonical signed message and
|
||||
// Contract: see RAF/security/05-supply-chain-gate.md. The canonical signed message and
|
||||
// closure hash here MUST stay byte-identical to the Go signer/verifier.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/api/handlers"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/Fimeg/RedFlag/server/internal/api/middleware"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/Fimeg/RedFlag/server/internal/command"
|
||||
"github.com/Fimeg/RedFlag/server/internal/config"
|
||||
"github.com/Fimeg/RedFlag/server/internal/database"
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ go 1.25.0
|
|||
require (
|
||||
github.com/alexedwards/argon2id v1.0.0
|
||||
github.com/docker/docker v25.0.6+incompatible
|
||||
github.com/doug-martin/goqu/v9 v9.19.0
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/go-git/go-git/v5 v5.19.1
|
||||
github.com/gofrs/uuid/v5 v5.4.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/lib/pq v1.10.9
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
|
|
@ -28,7 +29,6 @@ require (
|
|||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/doug-martin/goqu/v9 v9.19.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
|||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
|
|
@ -88,6 +89,8 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
||||
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
|
|
@ -168,6 +171,7 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB
|
|||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type AgentEventsHandler struct {
|
||||
|
|
@ -23,7 +23,7 @@ func NewAgentEventsHandler(aq *queries.AgentQueries) *AgentEventsHandler {
|
|||
// GET /api/v1/agents/:id/events?severity=error,critical,warning&limit=50
|
||||
func (h *AgentEventsHandler) GetAgentEvents(c *gin.Context) {
|
||||
agentIDStr := c.Param("id")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// AgentTrackedSoftwareHandler exposes the per-agent binding CRUD.
|
||||
|
|
@ -36,7 +36,7 @@ func NewAgentTrackedSoftwareHandler(b *queries.AgentTrackedSoftwareQueries, u *q
|
|||
// ListByAgent returns the bindings + joined tracked_software metadata for one agent.
|
||||
// GET /admin/agents/:id/tracked-software
|
||||
func (h *AgentTrackedSoftwareHandler) ListByAgent(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent id"})
|
||||
return
|
||||
|
|
@ -53,7 +53,7 @@ func (h *AgentTrackedSoftwareHandler) ListByAgent(c *gin.Context) {
|
|||
// ListInstallations returns the agents that have the given tracked_software bound.
|
||||
// GET /admin/upstream/:id/installations
|
||||
func (h *AgentTrackedSoftwareHandler) ListInstallations(c *gin.Context) {
|
||||
softwareID, err := uuid.Parse(c.Param("id"))
|
||||
softwareID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid tracked_software id"})
|
||||
return
|
||||
|
|
@ -70,7 +70,7 @@ func (h *AgentTrackedSoftwareHandler) ListInstallations(c *gin.Context) {
|
|||
// Upsert creates or updates the binding for (agent, tracked_software).
|
||||
// POST /admin/agents/:id/tracked-software
|
||||
func (h *AgentTrackedSoftwareHandler) Upsert(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent id"})
|
||||
return
|
||||
|
|
@ -105,12 +105,12 @@ func (h *AgentTrackedSoftwareHandler) Upsert(c *gin.Context) {
|
|||
// Delete removes a binding by id, scoped to the agent.
|
||||
// DELETE /admin/agents/:id/tracked-software/:bindingID
|
||||
func (h *AgentTrackedSoftwareHandler) Delete(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent id"})
|
||||
return
|
||||
}
|
||||
bindingID, err := uuid.Parse(c.Param("bindingID"))
|
||||
bindingID, err := uuid.FromString(c.Param("bindingID"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid binding id"})
|
||||
return
|
||||
|
|
@ -148,7 +148,7 @@ func (h *AgentTrackedSoftwareHandler) Delete(c *gin.Context) {
|
|||
// POST /admin/agents/:id/tracked-software/create-update
|
||||
// B5: Install script is generated to download from Gitea and run.
|
||||
func (h *AgentTrackedSoftwareHandler) CreateUpdateFromDrift(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent id"})
|
||||
return
|
||||
|
|
@ -204,7 +204,7 @@ func (h *AgentTrackedSoftwareHandler) CreateUpdateFromDrift(c *gin.Context) {
|
|||
|
||||
// Build UpdateEvent and write to current_package_state
|
||||
updateEvent := models.UpdateEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
PackageType: binding.Ecosystem,
|
||||
PackageName: binding.Name,
|
||||
|
|
@ -249,7 +249,7 @@ func (h *AgentTrackedSoftwareHandler) CreateUpdateFromDrift(c *gin.Context) {
|
|||
// GenerateInstallScript returns a shell script that downloads the release
|
||||
// from Gitea and runs it. B5: install via one-click download script.
|
||||
func (h *AgentTrackedSoftwareHandler) GenerateInstallScript(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent id"})
|
||||
return
|
||||
|
|
@ -259,7 +259,7 @@ func (h *AgentTrackedSoftwareHandler) GenerateInstallScript(c *gin.Context) {
|
|||
bindingIDStr := c.Param("bindingID")
|
||||
var bindingID uuid.UUID
|
||||
if bindingIDStr != "" {
|
||||
bindingID, err = uuid.Parse(bindingIDStr)
|
||||
bindingID, err = uuid.FromString(bindingIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid binding id"})
|
||||
return
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/services"
|
||||
"github.com/Fimeg/RedFlag/server/internal/version"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
// AgentUpdateHandler handles agent binary update operations
|
||||
// DEPRECATED: This handler is being consolidated - will be replaced by unified update handling
|
||||
|
|
@ -59,7 +59,7 @@ func (h *AgentUpdateHandler) mintAgentSelfToken(agentID uuid.UUID, version, chec
|
|||
now := time.Now().UTC()
|
||||
token := &capability.Token{
|
||||
Version: capability.Version,
|
||||
TokenID: uuid.New().String(),
|
||||
TokenID: uuid.Must(uuid.NewV4()).String(),
|
||||
AgentID: agentID.String(),
|
||||
PackageType: "agent-self",
|
||||
Operation: "upgrade",
|
||||
|
|
@ -119,7 +119,7 @@ func (h *AgentUpdateHandler) UpdateAgent(c *gin.Context) {
|
|||
log.Printf("[DEBUG] [UpdateAgent] Parsed update request - Version: %s, Platform: %s, Nonce: %s", req.Version, req.Platform, req.Nonce)
|
||||
}
|
||||
|
||||
agentIDUUID, err := uuid.Parse(agentID)
|
||||
agentIDUUID, err := uuid.FromString(agentID)
|
||||
if err != nil {
|
||||
log.Printf("[UPDATE] Agent ID format error for %s: %v", agentID, err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID format"})
|
||||
|
|
@ -252,7 +252,7 @@ func (h *AgentUpdateHandler) UpdateAgent(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Generate nonce for replay protection
|
||||
nonceUUID := uuid.New()
|
||||
nonceUUID := uuid.Must(uuid.NewV4())
|
||||
nonceTimestamp := time.Now().UTC()
|
||||
var nonceSignature string
|
||||
if h.signingService != nil {
|
||||
|
|
@ -306,7 +306,7 @@ func (h *AgentUpdateHandler) UpdateAgent(c *gin.Context) {
|
|||
|
||||
// Create the command in database
|
||||
command := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentIDUUID,
|
||||
CommandType: commandType,
|
||||
Params: commandParams,
|
||||
|
|
@ -325,7 +325,7 @@ func (h *AgentUpdateHandler) UpdateAgent(c *gin.Context) {
|
|||
|
||||
// Log agent update initiation to system_events table
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: &agentIDUUID,
|
||||
EventType: "agent_update",
|
||||
EventSubtype: "initiated",
|
||||
|
|
@ -422,7 +422,7 @@ func (h *AgentUpdateHandler) BulkUpdateAgents(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Generate nonce for replay protection
|
||||
nonceUUID := uuid.New()
|
||||
nonceUUID := uuid.Must(uuid.NewV4())
|
||||
nonceTimestamp := time.Now().UTC()
|
||||
var nonceSignature string
|
||||
if h.signingService != nil {
|
||||
|
|
@ -445,7 +445,7 @@ func (h *AgentUpdateHandler) BulkUpdateAgents(c *gin.Context) {
|
|||
|
||||
// Create update command
|
||||
command := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: "update_agent",
|
||||
Params: map[string]interface{}{
|
||||
|
|
@ -485,7 +485,7 @@ func (h *AgentUpdateHandler) BulkUpdateAgents(c *gin.Context) {
|
|||
|
||||
// Log each bulk update initiation to system_events table
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: &agentID,
|
||||
EventType: "agent_update",
|
||||
EventSubtype: "initiated",
|
||||
|
|
@ -658,7 +658,7 @@ func (h *AgentUpdateHandler) GenerateUpdateNonce(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Parse agent ID as UUID
|
||||
agentIDUUID, err := uuid.Parse(agentID)
|
||||
agentIDUUID, err := uuid.FromString(agentID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID format"})
|
||||
return
|
||||
|
|
@ -697,7 +697,7 @@ func (h *AgentUpdateHandler) CheckForUpdateAvailable(c *gin.Context) {
|
|||
agentID := c.Param("id")
|
||||
|
||||
// Parse agent ID
|
||||
agentIDUUID, err := uuid.Parse(agentID)
|
||||
agentIDUUID, err := uuid.FromString(agentID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID format"})
|
||||
return
|
||||
|
|
@ -753,7 +753,7 @@ func (h *AgentUpdateHandler) GetUpdateStatus(c *gin.Context) {
|
|||
agentID := c.Param("id")
|
||||
|
||||
// Parse agent ID
|
||||
agentIDUUID, err := uuid.Parse(agentID)
|
||||
agentIDUUID, err := uuid.FromString(agentID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID format"})
|
||||
return
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/services"
|
||||
"github.com/Fimeg/RedFlag/server/internal/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type AgentHandler struct {
|
||||
|
|
@ -169,7 +169,7 @@ func (h *AgentHandler) queueSystemHeartbeat(agentID uuid.UUID, durationMinutes i
|
|||
return fmt.Errorf("signing service unavailable")
|
||||
}
|
||||
cmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: models.CommandTypeEnableHeartbeat,
|
||||
Params: models.JSONB{
|
||||
|
|
@ -349,7 +349,7 @@ func (h *AgentHandler) RegisterAgent(c *gin.Context) {
|
|||
|
||||
// Create new agent
|
||||
agent := &models.Agent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
Hostname: req.Hostname,
|
||||
OSType: req.OSType,
|
||||
OSVersion: req.OSVersion,
|
||||
|
|
@ -414,7 +414,7 @@ func (h *AgentHandler) RegisterAgent(c *gin.Context) {
|
|||
}
|
||||
refreshTokenExpiry := time.Now().UTC().Add(90 * 24 * time.Hour)
|
||||
tokenHash = queries.HashRefreshToken(refreshToken)
|
||||
refreshFamilyID := uuid.New() // root of this agent's rotation family (migration 045)
|
||||
refreshFamilyID := uuid.Must(uuid.NewV4()) // root of this agent's rotation family (migration 045)
|
||||
if _, err := tx.Exec("INSERT INTO refresh_tokens (agent_id, token_hash, expires_at, family_id) VALUES ($1, $2, $3, $4)",
|
||||
agent.ID, tokenHash, refreshTokenExpiry, refreshFamilyID); err != nil {
|
||||
log.Printf("[ERROR] [server] [registration] create_refresh_token_failed error=%q", err)
|
||||
|
|
@ -905,7 +905,7 @@ func (h *AgentHandler) GetCommands(c *gin.Context) {
|
|||
// Create audit command to show in history
|
||||
now := time.Now().UTC()
|
||||
auditCmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: models.CommandTypeDisableHeartbeat,
|
||||
Params: models.JSONB{},
|
||||
|
|
@ -1002,7 +1002,7 @@ func (h *AgentHandler) ListAgents(c *gin.Context) {
|
|||
// GetAgent returns a single agent by ID with last scan information
|
||||
func (h *AgentHandler) GetAgent(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1020,7 +1020,7 @@ func (h *AgentHandler) GetAgent(c *gin.Context) {
|
|||
// TriggerHeartbeat creates a heartbeat toggle command for an agent
|
||||
func (h *AgentHandler) TriggerHeartbeat(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
agentID, err := uuid.Parse(idStr)
|
||||
agentID, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1044,7 +1044,7 @@ func (h *AgentHandler) TriggerHeartbeat(c *gin.Context) {
|
|||
|
||||
// Create heartbeat command with duration parameter (manual = user-initiated)
|
||||
cmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: commandType,
|
||||
Params: models.JSONB{
|
||||
|
|
@ -1112,7 +1112,7 @@ func (h *AgentHandler) triggerSystemHeartbeat(agentID uuid.UUID, durationMinutes
|
|||
|
||||
// Create system heartbeat command
|
||||
cmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: models.CommandTypeEnableHeartbeat,
|
||||
Params: models.JSONB{
|
||||
|
|
@ -1145,7 +1145,7 @@ func (h *AgentHandler) triggerSystemHeartbeat(agentID uuid.UUID, durationMinutes
|
|||
// GetHeartbeatStatus returns the current heartbeat status for an agent
|
||||
func (h *AgentHandler) GetHeartbeatStatus(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
agentID, err := uuid.Parse(idStr)
|
||||
agentID, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1213,7 +1213,7 @@ func (h *AgentHandler) GetHeartbeatStatus(c *gin.Context) {
|
|||
// TriggerUpdate creates an update command for an agent
|
||||
func (h *AgentHandler) TriggerUpdate(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
agentID, err := uuid.Parse(idStr)
|
||||
agentID, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1252,7 +1252,7 @@ func (h *AgentHandler) TriggerUpdate(c *gin.Context) {
|
|||
|
||||
// Create update command
|
||||
cmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: models.CommandTypeInstallUpdate,
|
||||
Params: params,
|
||||
|
|
@ -1492,7 +1492,7 @@ func (h *AgentHandler) RenewToken(c *gin.Context) {
|
|||
// to recover from the old "unknown-" fallback machine ID bug (F-D1-1).
|
||||
func (h *AgentHandler) RebindMachineID(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
agentID, err := uuid.Parse(idStr)
|
||||
agentID, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1552,7 +1552,7 @@ func (h *AgentHandler) RebindMachineID(c *gin.Context) {
|
|||
// UnregisterAgent removes an agent from the system
|
||||
func (h *AgentHandler) UnregisterAgent(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
agentID, err := uuid.Parse(idStr)
|
||||
agentID, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1719,7 +1719,7 @@ func (h *AgentHandler) EnableRapidPollingMode(agentID uuid.UUID, durationMinutes
|
|||
// Rate limiting is implemented at router level in cmd/server/main.go
|
||||
func (h *AgentHandler) SetRapidPollingMode(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
agentID, err := uuid.Parse(idStr)
|
||||
agentID, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1778,7 +1778,7 @@ func (h *AgentHandler) SetRapidPollingMode(c *gin.Context) {
|
|||
|
||||
// TriggerReboot triggers a system reboot for an agent
|
||||
func (h *AgentHandler) TriggerReboot(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1808,7 +1808,7 @@ func (h *AgentHandler) TriggerReboot(c *gin.Context) {
|
|||
|
||||
// Create reboot command
|
||||
cmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: models.CommandTypeReboot,
|
||||
Params: models.JSONB{
|
||||
|
|
@ -1841,7 +1841,7 @@ func (h *AgentHandler) TriggerReboot(c *gin.Context) {
|
|||
// GET /api/v1/agents/:id/config
|
||||
func (h *AgentHandler) GetAgentConfig(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
agentID, err := uuid.Parse(idStr)
|
||||
agentID, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1923,7 +1923,7 @@ type CircuitBreakerReport struct {
|
|||
// ReportCircuitBreakerStats receives circuit breaker health from agents
|
||||
// [ISSUE-004] Enables monitoring and alerting for circuit breaker states
|
||||
func (h *AgentHandler) ReportCircuitBreakerStats(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1975,7 +1975,7 @@ func (h *AgentHandler) ReportCircuitBreakerStats(c *gin.Context) {
|
|||
// the existing Remove Agent endpoint.
|
||||
func (h *AgentHandler) RevokeAgent(c *gin.Context) {
|
||||
idParam := c.Param("id")
|
||||
agentID, err := uuid.Parse(idParam)
|
||||
agentID, err := uuid.FromString(idParam)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent id"})
|
||||
return
|
||||
|
|
@ -2004,7 +2004,7 @@ func (h *AgentHandler) RevokeAgent(c *gin.Context) {
|
|||
}
|
||||
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: &agentID,
|
||||
EventType: "agent_revoked",
|
||||
EventSubtype: "by_operator",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
)
|
||||
|
|
@ -75,7 +75,7 @@ func (h *ClientErrorHandler) GetErrors(c *gin.Context) {
|
|||
sd = sd.Where(goqu.Ex{"error_type": errorType})
|
||||
}
|
||||
if agentIDStr != "" {
|
||||
if id, err := uuid.Parse(agentIDStr); err == nil {
|
||||
if id, err := uuid.FromString(agentIDStr); err == nil {
|
||||
sd = sd.Where(goqu.Ex{"agent_id": id})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/services"
|
||||
"github.com/Fimeg/RedFlag/server/internal/logging"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type DockerHandler struct {
|
||||
|
|
@ -96,7 +96,7 @@ func (h *DockerHandler) GetContainers(c *gin.Context) {
|
|||
}
|
||||
|
||||
if agentID != "" {
|
||||
if parsedID, err := uuid.Parse(agentID); err == nil {
|
||||
if parsedID, err := uuid.FromString(agentID); err == nil {
|
||||
filter.AgentID = &parsedID
|
||||
}
|
||||
}
|
||||
|
|
@ -223,7 +223,7 @@ func (h *DockerHandler) GetContainers(c *gin.Context) {
|
|||
// GetAgentContainers returns Docker containers for a specific agent
|
||||
func (h *DockerHandler) GetAgentContainers(c *gin.Context) {
|
||||
agentIDStr := c.Param("agent_id")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -329,7 +329,7 @@ func (h *DockerHandler) ApproveUpdate(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Parse the update ID from container_id (they're the same in our implementation)
|
||||
updateID, err := uuid.Parse(containerID)
|
||||
updateID, err := uuid.FromString(containerID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid container ID"})
|
||||
return
|
||||
|
|
@ -359,7 +359,7 @@ func (h *DockerHandler) RejectUpdate(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Parse the update ID
|
||||
updateID, err := uuid.Parse(containerID)
|
||||
updateID, err := uuid.FromString(containerID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid container ID"})
|
||||
return
|
||||
|
|
@ -395,7 +395,7 @@ func (h *DockerHandler) InstallUpdate(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Parse the update ID
|
||||
updateID, err := uuid.Parse(containerID)
|
||||
updateID, err := uuid.FromString(containerID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid container ID"})
|
||||
return
|
||||
|
|
@ -411,7 +411,7 @@ func (h *DockerHandler) InstallUpdate(c *gin.Context) {
|
|||
// Create a command for the agent to install the update
|
||||
// This would trigger the agent to pull the new image
|
||||
command := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: update.AgentID,
|
||||
CommandType: models.CommandTypeInstallUpdate, // Install Docker image update
|
||||
Params: models.JSONB{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// DockerReportsHandler handles Docker image reports from agents
|
||||
|
|
@ -45,7 +45,7 @@ func (h *DockerReportsHandler) ReportDockerImages(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Validate command exists and belongs to agent
|
||||
commandID, err := uuid.Parse(req.CommandID)
|
||||
commandID, err := uuid.FromString(req.CommandID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid command ID format"})
|
||||
return
|
||||
|
|
@ -94,7 +94,7 @@ func (h *DockerReportsHandler) ReportDockerImages(c *gin.Context) {
|
|||
}
|
||||
|
||||
event := models.StoredDockerImage{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
PackageType: item.PackageType,
|
||||
PackageName: imageName + ":" + imageTag,
|
||||
|
|
@ -143,7 +143,7 @@ func (h *DockerReportsHandler) ReportDockerImages(c *gin.Context) {
|
|||
// GetAgentDockerImages retrieves Docker image updates for a specific agent
|
||||
func (h *DockerReportsHandler) GetAgentDockerImages(c *gin.Context) {
|
||||
agentIDStr := c.Param("agentId")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -204,7 +204,7 @@ func (h *DockerReportsHandler) GetAgentDockerImages(c *gin.Context) {
|
|||
// GetAgentDockerInfo retrieves detailed Docker information for an agent
|
||||
func (h *DockerReportsHandler) GetAgentDockerInfo(c *gin.Context) {
|
||||
agentIDStr := c.Param("agentId")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/Fimeg/RedFlag/server/internal/services"
|
||||
serverVersion "github.com/Fimeg/RedFlag/server/internal/version"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
|
|
@ -488,7 +488,7 @@ func (h *DownloadHandler) DownloadUpdatePackage(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
parsedPackageID, err := uuid.Parse(packageID)
|
||||
parsedPackageID, err := uuid.FromString(packageID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid package ID format"})
|
||||
return
|
||||
|
|
@ -596,7 +596,7 @@ func (h *DownloadHandler) InstallScript(c *gin.Context) {
|
|||
func parseAgentID(c *gin.Context) string {
|
||||
// 1. Header → Secure (preferred)
|
||||
if agentID := c.GetHeader("X-Agent-ID"); agentID != "" {
|
||||
if _, err := uuid.Parse(agentID); err == nil {
|
||||
if _, err := uuid.FromString(agentID); err == nil {
|
||||
log.Printf("[DEBUG] Parsed agent ID from header: %s", agentID)
|
||||
return agentID
|
||||
}
|
||||
|
|
@ -605,7 +605,7 @@ func parseAgentID(c *gin.Context) string {
|
|||
|
||||
// 2. Path parameter → Legacy compatible
|
||||
if agentID := c.Param("agent_id"); agentID != "" {
|
||||
if _, err := uuid.Parse(agentID); err == nil {
|
||||
if _, err := uuid.FromString(agentID); err == nil {
|
||||
log.Printf("[DEBUG] Parsed agent ID from path: %s", agentID)
|
||||
return agentID
|
||||
}
|
||||
|
|
@ -614,7 +614,7 @@ func parseAgentID(c *gin.Context) string {
|
|||
|
||||
// 3. Query parameter → Fallback
|
||||
if agentID := c.Query("agent_id"); agentID != "" {
|
||||
if _, err := uuid.Parse(agentID); err == nil {
|
||||
if _, err := uuid.FromString(agentID); err == nil {
|
||||
log.Printf("[DEBUG] Parsed agent ID from query: %s", agentID)
|
||||
return agentID
|
||||
}
|
||||
|
|
@ -632,7 +632,7 @@ func (h *DownloadHandler) HandleConfigDownload(c *gin.Context) {
|
|||
agentIDParam := c.Param("agent_id")
|
||||
|
||||
// Validate UUID format
|
||||
parsedAgentID, err := uuid.Parse(agentIDParam)
|
||||
parsedAgentID, err := uuid.FromString(agentIDParam)
|
||||
if err != nil {
|
||||
log.Printf("Invalid agent ID format for config download: %s, error: %v", agentIDParam, err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/api/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -142,7 +142,7 @@ func TestUpdatePackageDownloadRequiresAuth(t *testing.T) {
|
|||
func makeTestAgentJWT(t *testing.T, secret string) string {
|
||||
t.Helper()
|
||||
claims := middleware.AgentClaims{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// ReportEventsRequest represents a batch of events from an agent
|
||||
|
|
@ -36,7 +36,7 @@ func NewEventsHandler(aq *queries.AgentQueries) *EventsHandler {
|
|||
// Accepts up to 100 events per request (TD-003)
|
||||
func (h *EventsHandler) ReportEvents(c *gin.Context) {
|
||||
agentIDStr := c.Param("id")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -68,7 +68,7 @@ func (h *EventsHandler) ReportEvents(c *gin.Context) {
|
|||
for i, event := range req.Events {
|
||||
// Ensure event has ID
|
||||
if event.ID == uuid.Nil {
|
||||
event.ID = uuid.New()
|
||||
event.ID = uuid.Must(uuid.NewV4())
|
||||
}
|
||||
|
||||
// Set agent ID from URL parameter
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type MaintenanceWindowHandler struct {
|
||||
|
|
@ -31,7 +31,7 @@ func (h *MaintenanceWindowHandler) ListWindows(c *gin.Context) {
|
|||
// GetWindow returns a single maintenance window by ID.
|
||||
func (h *MaintenanceWindowHandler) GetWindow(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid window ID"})
|
||||
return
|
||||
|
|
@ -73,7 +73,7 @@ func (h *MaintenanceWindowHandler) CreateWindow(c *gin.Context) {
|
|||
// UpdateWindow updates an existing maintenance window.
|
||||
func (h *MaintenanceWindowHandler) UpdateWindow(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid window ID"})
|
||||
return
|
||||
|
|
@ -95,7 +95,7 @@ func (h *MaintenanceWindowHandler) UpdateWindow(c *gin.Context) {
|
|||
// DeleteWindow removes a maintenance window.
|
||||
func (h *MaintenanceWindowHandler) DeleteWindow(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid window ID"})
|
||||
return
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// MetricsHandler handles system and storage metrics
|
||||
|
|
@ -44,7 +44,7 @@ func (h *MetricsHandler) ReportMetrics(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Validate command exists and belongs to agent
|
||||
commandID, err := uuid.Parse(req.CommandID)
|
||||
commandID, err := uuid.FromString(req.CommandID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid command ID format"})
|
||||
return
|
||||
|
|
@ -65,7 +65,7 @@ func (h *MetricsHandler) ReportMetrics(c *gin.Context) {
|
|||
events := make([]models.StoredMetric, 0, len(req.Metrics))
|
||||
for _, item := range req.Metrics {
|
||||
event := models.StoredMetric{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
PackageType: item.PackageType,
|
||||
PackageName: item.PackageName,
|
||||
|
|
@ -114,7 +114,7 @@ func (h *MetricsHandler) ReportMetrics(c *gin.Context) {
|
|||
// GetAgentMetrics retrieves metrics for a specific agent
|
||||
func (h *MetricsHandler) GetAgentMetrics(c *gin.Context) {
|
||||
agentIDStr := c.Param("agentId")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -164,7 +164,7 @@ func (h *MetricsHandler) GetAgentMetrics(c *gin.Context) {
|
|||
// GetAgentStorageMetrics retrieves storage metrics for a specific agent
|
||||
func (h *MetricsHandler) GetAgentStorageMetrics(c *gin.Context) {
|
||||
agentIDStr := c.Param("agentId")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -211,7 +211,7 @@ func (h *MetricsHandler) GetAgentStorageMetrics(c *gin.Context) {
|
|||
// GetAgentSystemMetrics retrieves system metrics for a specific agent
|
||||
func (h *MetricsHandler) GetAgentSystemMetrics(c *gin.Context) {
|
||||
agentIDStr := c.Param("agentId")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/config"
|
||||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type RegistrationTokenHandler struct {
|
||||
|
|
@ -220,7 +220,7 @@ func (h *RegistrationTokenHandler) GetActiveRegistrationTokens(c *gin.Context) {
|
|||
func (h *RegistrationTokenHandler) GetAgentsBoundToToken(c *gin.Context) {
|
||||
// :token in the route here is the registration_tokens.id UUID, not the
|
||||
// secret token string. Mirrors the /registration-tokens/delete/:id pattern.
|
||||
tokenID, err := uuid.Parse(c.Param("token"))
|
||||
tokenID, err := uuid.FromString(c.Param("token"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid token id (expected UUID)"})
|
||||
return
|
||||
|
|
@ -274,7 +274,7 @@ func (h *RegistrationTokenHandler) DeleteRegistrationToken(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Parse UUID
|
||||
id, err := uuid.Parse(tokenID)
|
||||
id, err := uuid.FromString(tokenID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid token ID format"})
|
||||
return
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// simulateRetryCommand replicates the FIXED retry flow:
|
||||
|
|
@ -36,7 +36,7 @@ import (
|
|||
func simulateRetryCommand(original *models.AgentCommand) *models.AgentCommand {
|
||||
// Build the new command (same as handler does)
|
||||
newCmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: original.AgentID,
|
||||
CommandType: original.CommandType,
|
||||
Params: original.Params,
|
||||
|
|
@ -89,8 +89,8 @@ func TestRetryCommandEndpointProducesUnsignedCommand(t *testing.T) {
|
|||
now := time.Now()
|
||||
|
||||
original := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
AgentID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
CommandType: "install_updates",
|
||||
Params: models.JSONB{"package": "nginx", "version": "1.24.0"},
|
||||
Status: models.CommandStatusFailed,
|
||||
|
|
@ -138,8 +138,8 @@ func TestRetryCommandEndpointMustProduceSignedCommand(t *testing.T) {
|
|||
|
||||
now := time.Now()
|
||||
original := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
AgentID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
CommandType: "install_updates",
|
||||
Params: models.JSONB{"package": "nginx"},
|
||||
Status: models.CommandStatusFailed,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ func (h *ScannerConfigHandler) UpdateScannerTimeout(c *gin.Context) {
|
|||
userID := c.MustGet("user_id").(uuid.UUID)
|
||||
/*
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
EventType: "scanner_config_change",
|
||||
EventSubtype: "timeout_updated",
|
||||
Severity: "info",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
"github.com/Fimeg/RedFlag/server/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// SecuritySettingsHandler handles security settings API endpoints
|
||||
|
|
@ -65,10 +65,10 @@ func (h *SecuritySettingsHandler) UpdateSecuritySetting(c *gin.Context) {
|
|||
userIDStr := c.GetString("user_id")
|
||||
|
||||
// Parse user_id to UUID (the service expects uuid.UUID)
|
||||
userID, err := uuid.Parse(userIDStr)
|
||||
userID, err := uuid.FromString(userIDStr)
|
||||
if err != nil {
|
||||
// Fallback for numeric user IDs — use a deterministic UUID
|
||||
userID = uuid.NewSHA1(uuid.NameSpaceURL, []byte("user:"+userIDStr))
|
||||
userID = uuid.NewV5(uuid.NamespaceURL, "user:"+userIDStr)
|
||||
}
|
||||
|
||||
if err := h.securitySettingsService.ValidateSetting(category, key, req.Value); err != nil {
|
||||
|
|
@ -163,9 +163,9 @@ func (h *SecuritySettingsHandler) ApplySecuritySettings(c *gin.Context) {
|
|||
}
|
||||
|
||||
userIDStr := c.GetString("user_id")
|
||||
userID, err := uuid.Parse(userIDStr)
|
||||
userID, err := uuid.FromString(userIDStr)
|
||||
if err != nil {
|
||||
userID = uuid.NewSHA1(uuid.NameSpaceURL, []byte("user:"+userIDStr))
|
||||
userID = uuid.NewV5(uuid.NamespaceURL, "user:"+userIDStr)
|
||||
}
|
||||
|
||||
// Validate all settings first
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// StorageMetricsHandler handles storage metrics endpoints
|
||||
|
|
@ -44,7 +44,7 @@ func (h *StorageMetricsHandler) ReportStorageMetrics(c *gin.Context) {
|
|||
// Insert storage metrics with error isolation
|
||||
for _, metric := range req.Metrics {
|
||||
dbMetric := models.StorageMetric{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: req.AgentID,
|
||||
Mountpoint: metric.Mountpoint,
|
||||
Device: metric.Device,
|
||||
|
|
@ -95,7 +95,7 @@ func (h *StorageMetricsHandler) ReportStorageMetrics(c *gin.Context) {
|
|||
func (h *StorageMetricsHandler) GetStorageMetrics(c *gin.Context) {
|
||||
// Get agent ID from URL parameter (this is a dashboard endpoint, not agent endpoint)
|
||||
agentIDStr := c.Param("id")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Invalid agent ID %s: %v\n", agentIDStr, err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/services"
|
||||
"github.com/Fimeg/RedFlag/server/internal/logging"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type SubsystemHandler struct {
|
||||
|
|
@ -49,7 +49,7 @@ func (h *SubsystemHandler) SetScheduler(s *scheduler.Scheduler) {
|
|||
func (h *SubsystemHandler) signAndCreateCommand(cmd *models.AgentCommand) error {
|
||||
// Generate ID if not set (prevents zero UUID issues)
|
||||
if cmd.ID == uuid.Nil {
|
||||
cmd.ID = uuid.New()
|
||||
cmd.ID = uuid.Must(uuid.NewV4())
|
||||
}
|
||||
|
||||
// Set timestamps if not set
|
||||
|
|
@ -97,7 +97,7 @@ func (h *SubsystemHandler) signAndCreateCommand(cmd *models.AgentCommand) error
|
|||
// GetSubsystems retrieves all subsystems for an agent
|
||||
// GET /api/v1/agents/:id/subsystems
|
||||
func (h *SubsystemHandler) GetSubsystems(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
|
||||
return
|
||||
|
|
@ -115,7 +115,7 @@ func (h *SubsystemHandler) GetSubsystems(c *gin.Context) {
|
|||
// GetSubsystem retrieves a specific subsystem for an agent
|
||||
// GET /api/v1/agents/:id/subsystems/:subsystem
|
||||
func (h *SubsystemHandler) GetSubsystem(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
|
||||
return
|
||||
|
|
@ -144,7 +144,7 @@ func (h *SubsystemHandler) GetSubsystem(c *gin.Context) {
|
|||
// UpdateSubsystem updates subsystem configuration
|
||||
// PATCH /api/v1/agents/:id/subsystems/:subsystem
|
||||
func (h *SubsystemHandler) UpdateSubsystem(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
|
||||
return
|
||||
|
|
@ -184,7 +184,7 @@ func (h *SubsystemHandler) UpdateSubsystem(c *gin.Context) {
|
|||
// EnableSubsystem enables a subsystem
|
||||
// POST /api/v1/agents/:id/subsystems/:subsystem/enable
|
||||
func (h *SubsystemHandler) EnableSubsystem(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
|
||||
return
|
||||
|
|
@ -212,7 +212,7 @@ func (h *SubsystemHandler) EnableSubsystem(c *gin.Context) {
|
|||
// DisableSubsystem disables a subsystem
|
||||
// POST /api/v1/agents/:id/subsystems/:subsystem/disable
|
||||
func (h *SubsystemHandler) DisableSubsystem(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
|
||||
return
|
||||
|
|
@ -299,7 +299,7 @@ func (h *SubsystemHandler) getScanCommandType(subsystem string, agentID uuid.UUI
|
|||
// BUG-015 FIX: Supports platform-specific scanners (apt, dnf, windows, winget)
|
||||
// Frontend maps 'updates' → platform scanner, this handler creates scan_<platform> commands
|
||||
func (h *SubsystemHandler) TriggerSubsystem(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
|
||||
return
|
||||
|
|
@ -387,7 +387,7 @@ func (h *SubsystemHandler) TriggerSubsystem(c *gin.Context) {
|
|||
// GetSubsystemStats retrieves statistics for a subsystem
|
||||
// GET /api/v1/agents/:id/subsystems/:subsystem/stats
|
||||
func (h *SubsystemHandler) GetSubsystemStats(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
|
||||
return
|
||||
|
|
@ -416,7 +416,7 @@ func (h *SubsystemHandler) GetSubsystemStats(c *gin.Context) {
|
|||
// SetAutoRun enables or disables auto-run for a subsystem
|
||||
// POST /api/v1/agents/:id/subsystems/:subsystem/auto-run
|
||||
func (h *SubsystemHandler) SetAutoRun(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
|
||||
return
|
||||
|
|
@ -453,7 +453,7 @@ func (h *SubsystemHandler) SetAutoRun(c *gin.Context) {
|
|||
// SetInterval sets the interval for a subsystem
|
||||
// POST /api/v1/agents/:id/subsystems/:subsystem/interval
|
||||
func (h *SubsystemHandler) SetInterval(c *gin.Context) {
|
||||
agentID, err := uuid.Parse(c.Param("id"))
|
||||
agentID, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
|
||||
return
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/Fimeg/RedFlag/server/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// isValidResult checks if the result value complies with the database constraint
|
||||
|
|
@ -102,7 +102,7 @@ func (h *UpdateHandler) SetOrchestrator(o LifecycleAdvancer) {
|
|||
// dry-run has been made.
|
||||
func (h *UpdateHandler) EnqueueDryRun(update *models.UpdateState) error {
|
||||
command := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: update.AgentID,
|
||||
CommandType: models.CommandTypeDryRunUpdate,
|
||||
Params: map[string]interface{}{
|
||||
|
|
@ -119,7 +119,7 @@ func (h *UpdateHandler) EnqueueDryRun(update *models.UpdateState) error {
|
|||
// Collapse the agent's poll interval during the active phase (best-effort).
|
||||
if shouldEnable, err := h.shouldEnableHeartbeat(update.AgentID, 10); err == nil && shouldEnable {
|
||||
heartbeatCmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: update.AgentID,
|
||||
CommandType: models.CommandTypeEnableHeartbeat,
|
||||
Params: models.JSONB{"duration_minutes": 10},
|
||||
|
|
@ -185,7 +185,7 @@ func (h *UpdateHandler) ReportUpdates(c *gin.Context) {
|
|||
events := make([]models.UpdateEvent, 0, len(req.Updates))
|
||||
for _, item := range req.Updates {
|
||||
event := models.UpdateEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
PackageType: item.PackageType,
|
||||
PackageName: item.PackageName,
|
||||
|
|
@ -318,7 +318,7 @@ func (h *UpdateHandler) ListUpdates(c *gin.Context) {
|
|||
|
||||
// Parse agent_id if provided
|
||||
if agentIDStr := c.Query("agent_id"); agentIDStr != "" {
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err == nil {
|
||||
filters.AgentID = agentID
|
||||
}
|
||||
|
|
@ -418,7 +418,7 @@ func (h *UpdateHandler) ListPackages(c *gin.Context) {
|
|||
// GetUpdate retrieves a single update by ID
|
||||
func (h *UpdateHandler) GetUpdate(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
||||
return
|
||||
|
|
@ -439,7 +439,7 @@ func (h *UpdateHandler) GetUpdate(c *gin.Context) {
|
|||
// knowing the package coordinates.
|
||||
func (h *UpdateHandler) GetPackageFleet(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
||||
return
|
||||
|
|
@ -470,7 +470,7 @@ func (h *UpdateHandler) GetPackageFleet(c *gin.Context) {
|
|||
// an update id, for the detail pane's Version Timeline card.
|
||||
func (h *UpdateHandler) GetPackageVersions(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
||||
return
|
||||
|
|
@ -544,7 +544,7 @@ func (h *UpdateHandler) recordSupplyChainOverride(update *models.UpdateState, ho
|
|||
log.Printf("[SECURITY] [server] [supply_chain] gate_overridden id=%s pkg=%s version=%s cause=%q operator_reason=%q",
|
||||
update.ID, update.PackageName, update.AvailableVersion, hold.reason, operatorReason)
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: &update.AgentID,
|
||||
EventType: "supply_chain_override",
|
||||
EventSubtype: subtype,
|
||||
|
|
@ -571,7 +571,7 @@ func (h *UpdateHandler) recordSupplyChainOverride(update *models.UpdateState, ho
|
|||
// ApproveUpdate marks an update as approved, with OSV.dev supply chain check
|
||||
func (h *UpdateHandler) ApproveUpdate(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
||||
return
|
||||
|
|
@ -868,7 +868,7 @@ func (h *UpdateHandler) ReportLog(c *gin.Context) {
|
|||
// result-ack, by contrast, correctly clears on result-recorded — it only
|
||||
// needs to know the server received the report, not that it finalized.)
|
||||
if req.CommandID != "" {
|
||||
commandID, err := uuid.Parse(req.CommandID)
|
||||
commandID, err := uuid.FromString(req.CommandID)
|
||||
if err == nil {
|
||||
command, err := h.commandQueries.GetCommandByID(commandID)
|
||||
if err == nil && command != nil {
|
||||
|
|
@ -908,7 +908,7 @@ func (h *UpdateHandler) ReportLog(c *gin.Context) {
|
|||
}
|
||||
|
||||
logEntry := &models.UpdateLog{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
Action: req.Action,
|
||||
Subsystem: subsystem,
|
||||
|
|
@ -936,7 +936,7 @@ func (h *UpdateHandler) ReportLog(c *gin.Context) {
|
|||
|
||||
// NEW: Update command status if command_id is provided
|
||||
if req.CommandID != "" {
|
||||
commandID, err := uuid.Parse(req.CommandID)
|
||||
commandID, err := uuid.FromString(req.CommandID)
|
||||
if err != nil {
|
||||
// Log warning but don't fail the request
|
||||
log.Printf("[WARNING] [server] [updates] invalid_command_id_format command_id=%s", req.CommandID)
|
||||
|
|
@ -1045,7 +1045,7 @@ func (h *UpdateHandler) ReportLog(c *gin.Context) {
|
|||
|
||||
message := services.RenderUpdateLog(req.Action, validResult, req.Stderr)
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: &agentID,
|
||||
EventType: eventType,
|
||||
EventSubtype: "failed",
|
||||
|
|
@ -1072,7 +1072,7 @@ func (h *UpdateHandler) ReportLog(c *gin.Context) {
|
|||
// GetPackageHistory returns version history for a specific package
|
||||
func (h *UpdateHandler) GetPackageHistory(c *gin.Context) {
|
||||
agentIDStr := c.Param("agent_id")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1105,7 +1105,7 @@ func (h *UpdateHandler) GetPackageHistory(c *gin.Context) {
|
|||
// GetBatchStatus returns recent batch processing status for an agent
|
||||
func (h *UpdateHandler) GetBatchStatus(c *gin.Context) {
|
||||
agentIDStr := c.Param("agent_id")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1128,7 +1128,7 @@ func (h *UpdateHandler) GetBatchStatus(c *gin.Context) {
|
|||
// UpdatePackageStatus updates the status of a package (for when updates are installed)
|
||||
func (h *UpdateHandler) UpdatePackageStatus(c *gin.Context) {
|
||||
agentIDStr := c.Param("agent_id")
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
||||
return
|
||||
|
|
@ -1190,7 +1190,7 @@ func (h *UpdateHandler) ApproveUpdates(c *gin.Context) {
|
|||
gateMin, gateEnforcement := services.PackageAgeGateConfig()
|
||||
|
||||
for _, idStr := range req.UpdateIDs {
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID: " + idStr})
|
||||
return
|
||||
|
|
@ -1317,7 +1317,7 @@ func (h *UpdateHandler) ApproveUpdates(c *gin.Context) {
|
|||
// RejectUpdate rejects a single update
|
||||
func (h *UpdateHandler) RejectUpdate(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
||||
return
|
||||
|
|
@ -1335,7 +1335,7 @@ func (h *UpdateHandler) RejectUpdate(c *gin.Context) {
|
|||
// InstallUpdate marks an update as ready for installation and creates a dry run command for the agent
|
||||
func (h *UpdateHandler) InstallUpdate(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
||||
return
|
||||
|
|
@ -1394,7 +1394,7 @@ func (h *UpdateHandler) InstallUpdate(c *gin.Context) {
|
|||
// GetUpdateLogs retrieves installation logs for a specific update
|
||||
func (h *UpdateHandler) GetUpdateLogs(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
||||
return
|
||||
|
|
@ -1479,7 +1479,7 @@ func (h *UpdateHandler) ReportDependencies(c *gin.Context) {
|
|||
|
||||
// Automatically create installation command since no dependencies need approval
|
||||
command := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: models.CommandTypeConfirmDependencies,
|
||||
Params: map[string]interface{}{
|
||||
|
|
@ -1496,7 +1496,7 @@ func (h *UpdateHandler) ReportDependencies(c *gin.Context) {
|
|||
// Check if heartbeat should be enabled (avoid duplicates)
|
||||
if shouldEnable, err := h.shouldEnableHeartbeat(agentID, 10); err == nil && shouldEnable {
|
||||
heartbeatCmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: models.CommandTypeEnableHeartbeat,
|
||||
Params: models.JSONB{
|
||||
|
|
@ -1624,7 +1624,7 @@ func (h *UpdateHandler) pinReportedClosure(agentID uuid.UUID, req *models.Depend
|
|||
return
|
||||
}
|
||||
|
||||
updateID, err := uuid.Parse(req.UpdateID)
|
||||
updateID, err := uuid.FromString(req.UpdateID)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] [server] [capability] closure_pin_bad_update_id agent_id=%s update_id=%q error=%v",
|
||||
agentID, req.UpdateID, err)
|
||||
|
|
@ -1745,7 +1745,7 @@ func (h *UpdateHandler) enableHeartbeatBeforeInstall(agentID uuid.UUID) {
|
|||
return
|
||||
}
|
||||
heartbeatCmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: models.CommandTypeEnableHeartbeat,
|
||||
Params: models.JSONB{"duration_minutes": 10},
|
||||
|
|
@ -1803,7 +1803,7 @@ func (h *UpdateHandler) ConfirmDependenciesAuto(update *models.UpdateState) (boo
|
|||
// ConfirmDependencies handles user confirmation to proceed with dependency installation
|
||||
func (h *UpdateHandler) ConfirmDependencies(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
||||
return
|
||||
|
|
@ -1843,7 +1843,7 @@ func (h *UpdateHandler) ConfirmDependencies(c *gin.Context) {
|
|||
// Legacy command path (docker/winget/Windows): send a confirm_dependencies
|
||||
// command for the agent to execute directly.
|
||||
command := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: update.AgentID,
|
||||
CommandType: models.CommandTypeConfirmDependencies,
|
||||
Params: map[string]interface{}{
|
||||
|
|
@ -1887,7 +1887,7 @@ func (h *UpdateHandler) GetAllLogs(c *gin.Context) {
|
|||
|
||||
// Parse agent_id if provided
|
||||
if agentIDStr := c.Query("agent_id"); agentIDStr != "" {
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err == nil {
|
||||
filters.AgentID = agentID
|
||||
}
|
||||
|
|
@ -1957,7 +1957,7 @@ func (h *UpdateHandler) GetActiveOperations(c *gin.Context) {
|
|||
// contract cannot drift between entry points.
|
||||
func (h *UpdateHandler) buildRetryCommand(original *models.AgentCommand) (*models.AgentCommand, error) {
|
||||
newCommand := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: original.AgentID,
|
||||
CommandType: original.CommandType,
|
||||
Params: original.Params,
|
||||
|
|
@ -1975,7 +1975,7 @@ func (h *UpdateHandler) buildRetryCommand(original *models.AgentCommand) (*model
|
|||
// RetryCommand retries a failed, timed_out, or cancelled command by command ID.
|
||||
func (h *UpdateHandler) RetryCommand(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid command ID"})
|
||||
return
|
||||
|
|
@ -2013,7 +2013,7 @@ func (h *UpdateHandler) RetryCommand(c *gin.Context) {
|
|||
// cache ceiling that constrains client-side matching.
|
||||
func (h *UpdateHandler) RetryUpdate(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
updateID, err := uuid.Parse(idStr)
|
||||
updateID, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
||||
return
|
||||
|
|
@ -2048,7 +2048,7 @@ func (h *UpdateHandler) RetryUpdate(c *gin.Context) {
|
|||
// CancelCommand cancels a pending or sent command
|
||||
func (h *UpdateHandler) CancelCommand(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid command ID"})
|
||||
return
|
||||
|
|
@ -2066,7 +2066,7 @@ func (h *UpdateHandler) CancelCommand(c *gin.Context) {
|
|||
// command — journal it inward so it surfaces in the events API / history.
|
||||
if command != nil {
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: &command.AgentID,
|
||||
EventType: models.EventTypeCommandFailed,
|
||||
EventSubtype: "cancelled",
|
||||
|
|
@ -2188,7 +2188,7 @@ func (h *UpdateHandler) GetExpectedHash(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
agentID, err := uuid.Parse(agentIDStr)
|
||||
agentID, err := uuid.FromString(agentIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent_id"})
|
||||
return
|
||||
|
|
@ -2270,7 +2270,7 @@ func (h *UpdateHandler) ReportCapabilityResult(c *gin.Context) {
|
|||
}
|
||||
|
||||
agentID := c.MustGet("agent_id").(uuid.UUID)
|
||||
tokenID, err := uuid.Parse(c.Param("token_id"))
|
||||
tokenID, err := uuid.FromString(c.Param("token_id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid token_id"})
|
||||
return
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/Fimeg/RedFlag/server/internal/services/upstream"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// UpstreamHandler exposes the tracked_software CRUD + sync-now action.
|
||||
|
|
@ -73,7 +73,7 @@ func (h *UpstreamHandler) Create(c *gin.Context) {
|
|||
}
|
||||
|
||||
func (h *UpstreamHandler) Delete(c *gin.Context) {
|
||||
id, err := uuid.Parse(c.Param("id"))
|
||||
id, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
|
|
@ -88,7 +88,7 @@ func (h *UpstreamHandler) Delete(c *gin.Context) {
|
|||
// SyncNow forces a sync for a single tracked_software row. Blocks on the
|
||||
// fetch so the operator sees the result inline.
|
||||
func (h *UpstreamHandler) SyncNow(c *gin.Context) {
|
||||
id, err := uuid.Parse(c.Param("id"))
|
||||
id, err := uuid.FromString(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// AgentClaims represents JWT claims for agent authentication
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// MachineBindingMiddleware validates machine ID matches database record
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/api/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// makeAgentJWT creates a valid agent JWT for testing
|
||||
func makeAgentJWT(t *testing.T, secret string) string {
|
||||
t.Helper()
|
||||
claims := middleware.AgentClaims{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: middleware.JWTIssuerAgent,
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/api/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// makeWebJWT creates a valid web/admin JWT with correct issuer for testing
|
||||
|
|
@ -119,7 +119,7 @@ func TestAgentTokenRejectedByWebAuthMiddleware(t *testing.T) {
|
|||
|
||||
// Agent JWT with issuer=redflag-agent
|
||||
agentClaims := middleware.AgentClaims{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: middleware.JWTIssuerAgent,
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
// The canonical signed message and closure hash MUST stay byte-identical across
|
||||
// this package, the agent's mirror of it, and helper/src/main.rs. See
|
||||
// RAF/SUPPLY_CHAIN_GATE_PLAN.md for the contract.
|
||||
// RAF/security/05-supply-chain-gate.md for the contract.
|
||||
package capability
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// Factory creates validated AgentCommand instances
|
||||
|
|
@ -26,7 +26,7 @@ func NewFactory(commandQueries *queries.CommandQueries) *Factory {
|
|||
// Create generates a new validated AgentCommand with unique ID
|
||||
func (f *Factory) Create(agentID uuid.UUID, commandType string, params map[string]interface{}) (*models.AgentCommand, error) {
|
||||
cmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: commandType,
|
||||
Status: "pending",
|
||||
|
|
@ -57,7 +57,7 @@ func (f *Factory) CreateWithIdempotency(agentID uuid.UUID, commandType string, p
|
|||
// If no existing command found, proceed with creation
|
||||
if err.Error() == "sql: no rows in result set" || err.Error() == "command not found" {
|
||||
cmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: agentID,
|
||||
CommandType: commandType,
|
||||
Status: "pending",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/alexedwards/argon2id"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
@ -471,7 +471,7 @@ func (q *AgentQueries) SetAgentUpdating(agentID string, isUpdating bool, targetV
|
|||
// This is used to allow old agents to check in and receive updates even if they're below minimum version
|
||||
func (q *AgentQueries) HasPendingUpdateCommand(agentID string) (bool, error) {
|
||||
// Check if agent_id is a valid UUID
|
||||
agentUUID, err := uuid.Parse(agentID)
|
||||
agentUUID, err := uuid.FromString(agentID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid agent ID: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/capability"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
@ -28,11 +28,11 @@ func (q *CapabilityTokenQueries) Insert(t *capability.Token, updateID uuid.UUID)
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tokenID, err := uuid.Parse(t.TokenID)
|
||||
tokenID, err := uuid.FromString(t.TokenID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
agentID, err := uuid.Parse(t.AgentID)
|
||||
agentID, err := uuid.FromString(t.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
@ -196,7 +196,7 @@ func (q *CommandQueries) MarkCommandsReceivedTx(tx *sqlx.Tx, agentID uuid.UUID,
|
|||
|
||||
parsed := make([]uuid.UUID, 0, len(commandIDs))
|
||||
for _, idStr := range commandIDs {
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -683,7 +683,7 @@ func (q *CommandQueries) VerifyResultsRecorded(commandIDs []string) ([]string, e
|
|||
// Convert string IDs to UUIDs
|
||||
uuidIDs := make([]uuid.UUID, 0, len(commandIDs))
|
||||
for _, idStr := range commandIDs {
|
||||
id, err := uuid.Parse(idStr)
|
||||
id, err := uuid.FromString(idStr)
|
||||
if err != nil {
|
||||
// Skip invalid UUIDs
|
||||
continue
|
||||
|
|
@ -744,7 +744,7 @@ func (q *CommandQueries) HasPendingUpdateCommand(agentID string) (bool, error) {
|
|||
AND status = 'pending'
|
||||
`
|
||||
|
||||
agentUUID, err := uuid.Parse(agentID)
|
||||
agentUUID, err := uuid.FromString(agentID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid agent ID: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ func TestRetryCommandQueryDoesNotCopySignature(t *testing.T) {
|
|||
// (Struct construction from commands.go:202)
|
||||
//
|
||||
// newCommand := &models.AgentCommand{
|
||||
// ID: uuid.New(),
|
||||
// ID: uuid.Must(uuid.NewV4()),
|
||||
// AgentID: original.AgentID,
|
||||
// CommandType: original.CommandType,
|
||||
// Params: original.Params, ← Params copied
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package queries
|
|||
|
||||
import (
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ func (q *SecuritySettingsQueries) CreateSetting(category, key string, value inte
|
|||
}
|
||||
|
||||
setting := &models.SecuritySetting{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
Category: category,
|
||||
Key: key,
|
||||
Value: string(valueJSON),
|
||||
|
|
@ -200,7 +200,7 @@ func (q *SecuritySettingsQueries) DeleteSetting(category, key string) (*string,
|
|||
// CreateAuditLog creates an audit log entry for setting changes
|
||||
func (q *SecuritySettingsQueries) CreateAuditLog(settingID, userID uuid.UUID, action, oldValue, newValue, reason string) error {
|
||||
audit := &models.SecuritySettingAudit{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
SettingID: settingID,
|
||||
UserID: userID,
|
||||
Action: action,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ func (q *SigningKeyQueries) InsertSigningKey(ctx context.Context, keyID, publicK
|
|||
`
|
||||
now := time.Now().UTC()
|
||||
params := map[string]interface{}{
|
||||
"id": uuid.New(),
|
||||
"id": uuid.Must(uuid.NewV4()),
|
||||
"key_id": keyID,
|
||||
"public_key": publicKeyHex,
|
||||
"algorithm": "ed25519",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// StorageMetricsQueries handles storage metrics database operations
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/capability"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
@ -676,7 +676,7 @@ func (q *UpdateQueries) CreateUpdateEventsBatch(events []models.UpdateEvent) err
|
|||
|
||||
// Create batch record
|
||||
batch := &models.UpdateBatch{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: events[0].AgentID,
|
||||
BatchSize: len(events),
|
||||
Status: "processing",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ package logging
|
|||
import (
|
||||
"github.com/Fimeg/RedFlag/server/internal/config"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// Agent represents a registered update agent
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// AgentUpdatePackage represents a signed agent binary package
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// AgentCommand represents a command to be executed by an agent
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package models
|
|||
|
||||
import (
|
||||
"time"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// DockerPort represents a port mapping in a Docker container
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// MaintenanceWindow represents a recurring weekly maintenance window
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package models
|
|||
|
||||
import (
|
||||
"time"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// MetricsReportRequest is sent by agents when reporting system/storage metrics
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// SecurityEvent represents a security-related event that occurred
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// SecuritySetting represents a user-configurable security setting
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// SigningKey represents a signing key record in the database
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// StorageMetric represents a storage metric from an agent
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// AgentSubsystem represents a subsystem configuration for an agent
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// SystemEvent represents a unified event log entry for all system events
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// TrackedSoftware is a single piece of software the operator wants the
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// Store is the persistence surface the orchestrator drives. Satisfied by
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// Orchestrator advances the package update lifecycle on a server-side timer and
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// --- fakes -----------------------------------------------------------------
|
||||
|
|
@ -145,8 +145,8 @@ func (c fixedClock) Now() time.Time { return c.t }
|
|||
|
||||
func pkg(status models.PackageStatus, severity string, age time.Duration, now time.Time) models.UpdateState {
|
||||
return models.UpdateState{
|
||||
ID: uuid.New(),
|
||||
AgentID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
PackageType: "dnf",
|
||||
PackageName: "demo",
|
||||
Severity: severity,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// sweepAutoApprove advances eligible pending packages without an operator. Policy
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// SubsystemJob represents a scheduled subsystem scan
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func TestPriorityQueue_BasicOperations(t *testing.T) {
|
||||
|
|
@ -25,7 +25,7 @@ func TestPriorityQueue_BasicOperations(t *testing.T) {
|
|||
}
|
||||
|
||||
// Push a job
|
||||
agent1 := uuid.New()
|
||||
agent1 := uuid.Must(uuid.NewV4())
|
||||
job1 := &SubsystemJob{
|
||||
AgentID: agent1,
|
||||
AgentHostname: "agent-01",
|
||||
|
|
@ -71,17 +71,17 @@ func TestPriorityQueue_Ordering(t *testing.T) {
|
|||
// Push jobs in random order
|
||||
jobs := []*SubsystemJob{
|
||||
{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
NextRunAt: now.Add(30 * time.Minute), // Third
|
||||
},
|
||||
{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "storage",
|
||||
NextRunAt: now.Add(5 * time.Minute), // First
|
||||
},
|
||||
{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "docker",
|
||||
NextRunAt: now.Add(15 * time.Minute), // Second
|
||||
},
|
||||
|
|
@ -110,7 +110,7 @@ func TestPriorityQueue_Ordering(t *testing.T) {
|
|||
|
||||
func TestPriorityQueue_UpdateExisting(t *testing.T) {
|
||||
pq := NewPriorityQueue()
|
||||
agentID := uuid.New()
|
||||
agentID := uuid.Must(uuid.NewV4())
|
||||
now := time.Now()
|
||||
|
||||
// Push initial job
|
||||
|
|
@ -153,8 +153,8 @@ func TestPriorityQueue_UpdateExisting(t *testing.T) {
|
|||
func TestPriorityQueue_Remove(t *testing.T) {
|
||||
pq := NewPriorityQueue()
|
||||
|
||||
agent1 := uuid.New()
|
||||
agent2 := uuid.New()
|
||||
agent1 := uuid.Must(uuid.NewV4())
|
||||
agent2 := uuid.Must(uuid.NewV4())
|
||||
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: agent1,
|
||||
|
|
@ -193,7 +193,7 @@ func TestPriorityQueue_Remove(t *testing.T) {
|
|||
func TestPriorityQueue_Get(t *testing.T) {
|
||||
pq := NewPriorityQueue()
|
||||
|
||||
agentID := uuid.New()
|
||||
agentID := uuid.Must(uuid.NewV4())
|
||||
job := &SubsystemJob{
|
||||
AgentID: agentID,
|
||||
Subsystem: "updates",
|
||||
|
|
@ -214,7 +214,7 @@ func TestPriorityQueue_Get(t *testing.T) {
|
|||
}
|
||||
|
||||
// Get non-existent job
|
||||
retrieved = pq.Get(uuid.New(), "storage")
|
||||
retrieved = pq.Get(uuid.Must(uuid.NewV4()), "storage")
|
||||
if retrieved != nil {
|
||||
t.Fatal("Get should return nil for non-existent job")
|
||||
}
|
||||
|
|
@ -227,7 +227,7 @@ func TestPriorityQueue_PopBefore(t *testing.T) {
|
|||
// Add jobs with different NextRunAt times
|
||||
for i := 0; i < 5; i++ {
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
NextRunAt: now.Add(time.Duration(i*10) * time.Minute),
|
||||
})
|
||||
|
|
@ -265,7 +265,7 @@ func TestPriorityQueue_PopBeforeWithLimit(t *testing.T) {
|
|||
// Add 5 jobs all due now
|
||||
for i := 0; i < 5; i++ {
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
NextRunAt: now,
|
||||
})
|
||||
|
|
@ -288,12 +288,12 @@ func TestPriorityQueue_PeekBefore(t *testing.T) {
|
|||
now := time.Now()
|
||||
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
NextRunAt: now.Add(5 * time.Minute),
|
||||
})
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "storage",
|
||||
NextRunAt: now.Add(15 * time.Minute),
|
||||
})
|
||||
|
|
@ -317,7 +317,7 @@ func TestPriorityQueue_Clear(t *testing.T) {
|
|||
// Add some jobs
|
||||
for i := 0; i < 10; i++ {
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
NextRunAt: time.Now(),
|
||||
})
|
||||
|
|
@ -354,19 +354,19 @@ func TestPriorityQueue_GetStats(t *testing.T) {
|
|||
|
||||
// Add jobs
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
AgentHostname: "agent-01",
|
||||
Subsystem: "updates",
|
||||
NextRunAt: now.Add(5 * time.Minute),
|
||||
IntervalMinutes: 15,
|
||||
})
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "storage",
|
||||
NextRunAt: now.Add(10 * time.Minute),
|
||||
})
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
NextRunAt: now.Add(15 * time.Minute),
|
||||
})
|
||||
|
|
@ -408,7 +408,7 @@ func TestPriorityQueue_Concurrency(t *testing.T) {
|
|||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
NextRunAt: time.Now().Add(time.Duration(idx) * time.Second),
|
||||
})
|
||||
|
|
@ -460,7 +460,7 @@ func TestPriorityQueue_ConcurrentReadWrite(t *testing.T) {
|
|||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
NextRunAt: time.Now(),
|
||||
})
|
||||
|
|
@ -495,7 +495,7 @@ func BenchmarkPriorityQueue_Push(b *testing.B) {
|
|||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
NextRunAt: time.Now().Add(time.Duration(i) * time.Second),
|
||||
})
|
||||
|
|
@ -508,7 +508,7 @@ func BenchmarkPriorityQueue_Pop(b *testing.B) {
|
|||
// Pre-fill the queue
|
||||
for i := 0; i < b.N; i++ {
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
NextRunAt: time.Now().Add(time.Duration(i) * time.Second),
|
||||
})
|
||||
|
|
@ -526,7 +526,7 @@ func BenchmarkPriorityQueue_Peek(b *testing.B) {
|
|||
// Pre-fill with 10000 jobs
|
||||
for i := 0; i < 10000; i++ {
|
||||
pq.Push(&SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
NextRunAt: time.Now().Add(time.Duration(i) * time.Second),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/Fimeg/RedFlag/server/internal/services"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// Config holds scheduler configuration
|
||||
|
|
@ -473,7 +473,7 @@ func (w *worker) processJob(job *SubsystemJob) error {
|
|||
return nil
|
||||
}
|
||||
cmd := &models.AgentCommand{
|
||||
ID: uuid.New(),
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
AgentID: job.AgentID,
|
||||
CommandType: commandType,
|
||||
Params: models.JSONB{},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func TestScheduler_NewScheduler(t *testing.T) {
|
||||
|
|
@ -61,8 +61,8 @@ func TestScheduler_QueueIntegration(t *testing.T) {
|
|||
s := NewScheduler(config, nil, nil, nil, nil)
|
||||
|
||||
// Add jobs to queue
|
||||
agent1 := uuid.New()
|
||||
agent2 := uuid.New()
|
||||
agent1 := uuid.Must(uuid.NewV4())
|
||||
agent2 := uuid.Must(uuid.NewV4())
|
||||
|
||||
job1 := &SubsystemJob{
|
||||
AgentID: agent1,
|
||||
|
|
@ -193,7 +193,7 @@ func TestScheduler_ProcessQueueWithJobs(t *testing.T) {
|
|||
// Add jobs that are due now
|
||||
for i := 0; i < 5; i++ {
|
||||
job := &SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
AgentHostname: "test-agent",
|
||||
Subsystem: "updates",
|
||||
IntervalMinutes: 15,
|
||||
|
|
@ -272,7 +272,7 @@ func TestScheduler_ConcurrentQueueAccess(t *testing.T) {
|
|||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
job := &SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
IntervalMinutes: 15,
|
||||
NextRunAt: time.Now(),
|
||||
|
|
@ -308,7 +308,7 @@ func BenchmarkScheduler_ProcessQueue(b *testing.B) {
|
|||
// Pre-fill queue with jobs
|
||||
for i := 0; i < 1000; i++ {
|
||||
job := &SubsystemJob{
|
||||
AgentID: uuid.New(),
|
||||
AgentID: uuid.Must(uuid.NewV4()),
|
||||
Subsystem: "updates",
|
||||
IntervalMinutes: 15,
|
||||
NextRunAt: time.Now(),
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/config"
|
||||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ func (s *AgentLifecycleService) createAgent(
|
|||
) error {
|
||||
machineID := cfg.MachineID
|
||||
agent := &models.Agent{
|
||||
ID: uuid.MustParse(cfg.AgentID),
|
||||
ID: uuid.Must(uuid.FromString(cfg.AgentID)),
|
||||
Hostname: cfg.Hostname,
|
||||
OSType: cfg.Platform,
|
||||
AgentVersion: cfg.Version,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/server/internal/capability"
|
||||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// DefaultTokenTTL bounds how long a minted token stays valid. Short by design:
|
||||
|
|
@ -72,7 +72,7 @@ func (m *CapabilityMinter) MintForUpdate(update *models.UpdateState, closure []c
|
|||
now := time.Now().UTC()
|
||||
token := &capability.Token{
|
||||
Version: capability.Version,
|
||||
TokenID: uuid.New().String(),
|
||||
TokenID: uuid.Must(uuid.NewV4()).String(),
|
||||
AgentID: update.AgentID.String(),
|
||||
PackageType: update.PackageType,
|
||||
Operation: operation,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
@ -295,12 +295,12 @@ func (cb *ConfigBuilder) injectDeploymentValues(config map[string]interface{}, r
|
|||
func (cb *ConfigBuilder) determineAgentID(providedAgentID string) string {
|
||||
if providedAgentID != "" {
|
||||
// Validate it's a proper UUID
|
||||
if _, err := uuid.Parse(providedAgentID); err == nil {
|
||||
if _, err := uuid.FromString(providedAgentID); err == nil {
|
||||
return providedAgentID
|
||||
}
|
||||
}
|
||||
// Generate new UUID if none provided or invalid
|
||||
return uuid.New().String()
|
||||
return uuid.Must(uuid.NewV4()).String()
|
||||
}
|
||||
|
||||
// applyEnvironmentDefaults applies environment-specific configuration defaults
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue