Session 4 complete - RedFlag update management platform
🚩 Private development - version retention only ✅ Complete web dashboard (React + TypeScript + TailwindCSS) ✅ Production-ready server backend (Go + Gin + PostgreSQL) ✅ Linux agent with APT + Docker scanning + local CLI tools ✅ JWT authentication and REST API ✅ Update discovery and approval workflow 🚧 Status: Alpha software - active development 📦 Purpose: Version retention during development ⚠️ Not for public use or deployment
This commit is contained in:
242
aggregator-agent/internal/client/client.go
Normal file
242
aggregator-agent/internal/client/client.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Client handles API communication with the server
|
||||
type Client struct {
|
||||
baseURL string
|
||||
token string
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new API client
|
||||
func NewClient(baseURL, token string) *Client {
|
||||
return &Client{
|
||||
baseURL: baseURL,
|
||||
token: token,
|
||||
http: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRequest is the payload for agent registration
|
||||
type RegisterRequest struct {
|
||||
Hostname string `json:"hostname"`
|
||||
OSType string `json:"os_type"`
|
||||
OSVersion string `json:"os_version"`
|
||||
OSArchitecture string `json:"os_architecture"`
|
||||
AgentVersion string `json:"agent_version"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
}
|
||||
|
||||
// RegisterResponse is returned after successful registration
|
||||
type RegisterResponse struct {
|
||||
AgentID uuid.UUID `json:"agent_id"`
|
||||
Token string `json:"token"`
|
||||
Config map[string]interface{} `json:"config"`
|
||||
}
|
||||
|
||||
// Register registers the agent with the server
|
||||
func (c *Client) Register(req RegisterRequest) (*RegisterResponse, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/agents/register", c.baseURL)
|
||||
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.http.Do(httpReq)
|
||||
if err != nil {
|
||||
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))
|
||||
}
|
||||
|
||||
var result RegisterResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update client token
|
||||
c.token = result.Token
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Command represents a command from the server
|
||||
type Command struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
}
|
||||
|
||||
// CommandsResponse contains pending commands
|
||||
type CommandsResponse struct {
|
||||
Commands []Command `json:"commands"`
|
||||
}
|
||||
|
||||
// GetCommands retrieves pending commands from the server
|
||||
func (c *Client) GetCommands(agentID uuid.UUID) ([]Command, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/agents/%s/commands", c.baseURL, agentID)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("failed to get commands: %s - %s", resp.Status, string(bodyBytes))
|
||||
}
|
||||
|
||||
var result CommandsResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Commands, nil
|
||||
}
|
||||
|
||||
// UpdateReport represents discovered updates
|
||||
type UpdateReport struct {
|
||||
CommandID string `json:"command_id"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Updates []UpdateReportItem `json:"updates"`
|
||||
}
|
||||
|
||||
// UpdateReportItem represents a single update
|
||||
type UpdateReportItem struct {
|
||||
PackageType string `json:"package_type"`
|
||||
PackageName string `json:"package_name"`
|
||||
PackageDescription string `json:"package_description"`
|
||||
CurrentVersion string `json:"current_version"`
|
||||
AvailableVersion string `json:"available_version"`
|
||||
Severity string `json:"severity"`
|
||||
CVEList []string `json:"cve_list"`
|
||||
KBID string `json:"kb_id"`
|
||||
RepositorySource string `json:"repository_source"`
|
||||
SizeBytes int64 `json:"size_bytes"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
// ReportUpdates sends discovered updates to the server
|
||||
func (c *Client) ReportUpdates(agentID uuid.UUID, report UpdateReport) error {
|
||||
url := fmt.Sprintf("%s/api/v1/agents/%s/updates", c.baseURL, agentID)
|
||||
|
||||
body, err := json.Marshal(report)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("failed to report updates: %s - %s", resp.Status, string(bodyBytes))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogReport represents an execution log
|
||||
type LogReport struct {
|
||||
CommandID string `json:"command_id"`
|
||||
Action string `json:"action"`
|
||||
Result string `json:"result"`
|
||||
Stdout string `json:"stdout"`
|
||||
Stderr string `json:"stderr"`
|
||||
ExitCode int `json:"exit_code"`
|
||||
DurationSeconds int `json:"duration_seconds"`
|
||||
}
|
||||
|
||||
// ReportLog sends an execution log to the server
|
||||
func (c *Client) ReportLog(agentID uuid.UUID, report LogReport) error {
|
||||
url := fmt.Sprintf("%s/api/v1/agents/%s/logs", c.baseURL, agentID)
|
||||
|
||||
body, err := json.Marshal(report)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("failed to report log: %s - %s", resp.Status, string(bodyBytes))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetectSystem returns basic system information
|
||||
func DetectSystem() (osType, osVersion, osArch string) {
|
||||
osType = runtime.GOOS
|
||||
osArch = runtime.GOARCH
|
||||
|
||||
// Read OS version (simplified for now)
|
||||
switch osType {
|
||||
case "linux":
|
||||
data, _ := os.ReadFile("/etc/os-release")
|
||||
if data != nil {
|
||||
// Parse os-release file (simplified)
|
||||
osVersion = "Linux"
|
||||
}
|
||||
case "windows":
|
||||
osVersion = "Windows"
|
||||
case "darwin":
|
||||
osVersion = "macOS"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user