WIP: Save current state - security subsystems, migrations, logging
This commit is contained in:
@@ -7,10 +7,13 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/aggregator-agent/internal/event"
|
||||
"github.com/Fimeg/RedFlag/aggregator-agent/internal/models"
|
||||
"github.com/Fimeg/RedFlag/aggregator-agent/internal/system"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@@ -23,6 +26,8 @@ type Client struct {
|
||||
RapidPollingEnabled bool
|
||||
RapidPollingUntil time.Time
|
||||
machineID string // Cached machine ID for security binding
|
||||
eventBuffer *event.Buffer
|
||||
agentID uuid.UUID
|
||||
}
|
||||
|
||||
// NewClient creates a new API client
|
||||
@@ -45,6 +50,58 @@ func NewClient(baseURL, token string) *Client {
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientWithEventBuffer creates a new API client with event buffering capability
|
||||
func NewClientWithEventBuffer(baseURL, token string, statePath string, agentID uuid.UUID) *Client {
|
||||
client := NewClient(baseURL, token)
|
||||
client.agentID = agentID
|
||||
|
||||
// Initialize event buffer if state path is provided
|
||||
if statePath != "" {
|
||||
eventBufferPath := filepath.Join(statePath, "events_buffer.json")
|
||||
client.eventBuffer = event.NewBuffer(eventBufferPath)
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// bufferEvent buffers a system event for later reporting
|
||||
func (c *Client) bufferEvent(eventType, eventSubtype, severity, component, message string, metadata map[string]interface{}) {
|
||||
if c.eventBuffer == nil {
|
||||
return // Event buffering not enabled
|
||||
}
|
||||
|
||||
// Use agent ID if available, otherwise create event with nil agent ID
|
||||
var agentIDPtr *uuid.UUID
|
||||
if c.agentID != uuid.Nil {
|
||||
agentIDPtr = &c.agentID
|
||||
}
|
||||
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
AgentID: agentIDPtr,
|
||||
EventType: eventType,
|
||||
EventSubtype: eventSubtype,
|
||||
Severity: severity,
|
||||
Component: component,
|
||||
Message: message,
|
||||
Metadata: metadata,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Buffer the event (best effort - don't fail if buffering fails)
|
||||
if err := c.eventBuffer.BufferEvent(event); err != nil {
|
||||
fmt.Printf("Warning: Failed to buffer event: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBufferedEvents returns all buffered events and clears the buffer
|
||||
func (c *Client) GetBufferedEvents() ([]*models.SystemEvent, error) {
|
||||
if c.eventBuffer == nil {
|
||||
return nil, nil // Event buffering not enabled
|
||||
}
|
||||
return c.eventBuffer.GetBufferedEvents()
|
||||
}
|
||||
|
||||
// addMachineIDHeader adds X-Machine-ID header to authenticated requests (v0.1.22+)
|
||||
func (c *Client) addMachineIDHeader(req *http.Request) {
|
||||
if c.machineID != "" {
|
||||
@@ -95,11 +152,25 @@ func (c *Client) Register(req RegisterRequest) (*RegisterResponse, error) {
|
||||
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
// Buffer registration failure event
|
||||
c.bufferEvent("registration_failure", "marshal_error", "error", "client",
|
||||
fmt.Sprintf("Failed to marshal registration request: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"hostname": req.Hostname,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
// Buffer registration failure event
|
||||
c.bufferEvent("registration_failure", "request_creation_error", "error", "client",
|
||||
fmt.Sprintf("Failed to create registration request: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"hostname": req.Hostname,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
@@ -112,22 +183,49 @@ func (c *Client) Register(req RegisterRequest) (*RegisterResponse, error) {
|
||||
|
||||
resp, err := c.http.Do(httpReq)
|
||||
if err != nil {
|
||||
// Buffer registration failure event
|
||||
c.bufferEvent("registration_failure", "network_error", "error", "client",
|
||||
fmt.Sprintf("Registration request failed: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"hostname": req.Hostname,
|
||||
"server_url": c.baseURL,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("registration failed: %s - %s", resp.Status, string(bodyBytes))
|
||||
errorMsg := fmt.Sprintf("registration failed: %s - %s", resp.Status, string(bodyBytes))
|
||||
|
||||
// Buffer registration failure event
|
||||
c.bufferEvent("registration_failure", "api_error", "error", "client",
|
||||
errorMsg,
|
||||
map[string]interface{}{
|
||||
"status_code": resp.StatusCode,
|
||||
"response_body": string(bodyBytes),
|
||||
"hostname": req.Hostname,
|
||||
"server_url": c.baseURL,
|
||||
})
|
||||
return nil, fmt.Errorf(errorMsg)
|
||||
}
|
||||
|
||||
var result RegisterResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
// Buffer registration failure event
|
||||
c.bufferEvent("registration_failure", "decode_error", "error", "client",
|
||||
fmt.Sprintf("Failed to decode registration response: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"hostname": req.Hostname,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update client token
|
||||
// Update client token and agent ID
|
||||
c.token = result.Token
|
||||
c.agentID = result.AgentID
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
@@ -136,6 +234,7 @@ func (c *Client) Register(req RegisterRequest) (*RegisterResponse, error) {
|
||||
type TokenRenewalRequest struct {
|
||||
AgentID uuid.UUID `json:"agent_id"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
AgentVersion string `json:"agent_version,omitempty"` // Agent's current version for upgrade tracking
|
||||
}
|
||||
|
||||
// TokenRenewalResponse is returned after successful token renewal
|
||||
@@ -144,38 +243,79 @@ type TokenRenewalResponse struct {
|
||||
}
|
||||
|
||||
// RenewToken uses refresh token to get a new access token (proper implementation)
|
||||
func (c *Client) RenewToken(agentID uuid.UUID, refreshToken string) error {
|
||||
func (c *Client) RenewToken(agentID uuid.UUID, refreshToken string, agentVersion string) error {
|
||||
url := fmt.Sprintf("%s/api/v1/agents/renew", c.baseURL)
|
||||
|
||||
renewalReq := TokenRenewalRequest{
|
||||
AgentID: agentID,
|
||||
RefreshToken: refreshToken,
|
||||
AgentVersion: agentVersion,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(renewalReq)
|
||||
if err != nil {
|
||||
// Buffer token renewal failure event
|
||||
c.bufferEvent("token_renewal_failure", "marshal_error", "error", "client",
|
||||
fmt.Sprintf("Failed to marshal token renewal request: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"agent_id": agentID.String(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
// Buffer token renewal failure event
|
||||
c.bufferEvent("token_renewal_failure", "request_creation_error", "error", "client",
|
||||
fmt.Sprintf("Failed to create token renewal request: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"agent_id": agentID.String(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.http.Do(httpReq)
|
||||
if err != nil {
|
||||
// Buffer token renewal failure event
|
||||
c.bufferEvent("token_renewal_failure", "network_error", "error", "client",
|
||||
fmt.Sprintf("Token renewal request failed: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"agent_id": agentID.String(),
|
||||
"server_url": c.baseURL,
|
||||
})
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("token renewal failed: %s - %s", resp.Status, string(bodyBytes))
|
||||
errorMsg := fmt.Sprintf("token renewal failed: %s - %s", resp.Status, string(bodyBytes))
|
||||
|
||||
// Buffer token renewal failure event
|
||||
c.bufferEvent("token_renewal_failure", "api_error", "error", "client",
|
||||
errorMsg,
|
||||
map[string]interface{}{
|
||||
"status_code": resp.StatusCode,
|
||||
"response_body": string(bodyBytes),
|
||||
"agent_id": agentID.String(),
|
||||
"server_url": c.baseURL,
|
||||
})
|
||||
return fmt.Errorf(errorMsg)
|
||||
}
|
||||
|
||||
var result TokenRenewalResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
// Buffer token renewal failure event
|
||||
c.bufferEvent("token_renewal_failure", "decode_error", "error", "client",
|
||||
fmt.Sprintf("Failed to decode token renewal response: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"agent_id": agentID.String(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -187,11 +327,15 @@ func (c *Client) RenewToken(agentID uuid.UUID, refreshToken string) error {
|
||||
|
||||
// Command represents a command from the server
|
||||
type Command struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
Signature string `json:"signature,omitempty"` // Ed25519 signature of the command
|
||||
}
|
||||
|
||||
// CommandItem is an alias for Command for consistency with server models
|
||||
type CommandItem = Command
|
||||
|
||||
// CommandsResponse contains pending commands
|
||||
type CommandsResponse struct {
|
||||
Commands []Command `json:"commands"`
|
||||
|
||||
Reference in New Issue
Block a user