Add L0-L4 security clearance for fleet commands and AI elevation.
Gate manual and AI commands by per-agent clearance, auto-elevate stuck hosts to L4 when AI mode allows, and surface clearance in Access Depth and LOTL timeline.
This commit is contained in:
146
server/internal/api/clearance.go
Normal file
146
server/internal/api/clearance.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
fleetai "crypto-miner-server/internal/ai"
|
||||
"crypto-miner-server/internal/clearance"
|
||||
"crypto-miner-server/internal/models"
|
||||
)
|
||||
|
||||
const stuckHostFailedTierThreshold = 14
|
||||
|
||||
// ClearanceManager returns the hub's session clearance tracker.
|
||||
func (h *WSHub) ClearanceManager() *ClearanceManager {
|
||||
if h == nil {
|
||||
return nil
|
||||
}
|
||||
return h.clearance
|
||||
}
|
||||
|
||||
// ClearanceManager tracks per-agent session clearance and broadcasts changes.
|
||||
type ClearanceManager struct {
|
||||
hub *WSHub
|
||||
mu sync.RWMutex
|
||||
// agentClearance holds live session clearance keyed by agent ID.
|
||||
agentClearance map[string]int
|
||||
}
|
||||
|
||||
func NewClearanceManager(hub *WSHub) *ClearanceManager {
|
||||
return &ClearanceManager{
|
||||
hub: hub,
|
||||
agentClearance: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
// InitAgent sets baseline clearance when an agent connects.
|
||||
func (m *ClearanceManager) InitAgent(agentID string, agent *models.Agent) int {
|
||||
if m == nil {
|
||||
return clearance.DefaultClearance(agent)
|
||||
}
|
||||
level := clearance.DefaultClearance(agent)
|
||||
m.mu.Lock()
|
||||
m.agentClearance[agentID] = level
|
||||
m.mu.Unlock()
|
||||
m.pushToAgent(agentID, level)
|
||||
return level
|
||||
}
|
||||
|
||||
// RemoveAgent drops session clearance state.
|
||||
func (m *ClearanceManager) RemoveAgent(agentID string) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
delete(m.agentClearance, agentID)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Level returns the current session clearance for an agent.
|
||||
func (m *ClearanceManager) Level(agentID string) int {
|
||||
if m == nil {
|
||||
return clearance.L0
|
||||
}
|
||||
m.mu.RLock()
|
||||
level, ok := m.agentClearance[agentID]
|
||||
m.mu.RUnlock()
|
||||
if ok {
|
||||
return level
|
||||
}
|
||||
if m.hub != nil && m.hub.db != nil {
|
||||
if agent, err := m.hub.db.GetAgent(agentID); err == nil && agent != nil {
|
||||
return clearance.DefaultClearance(agent)
|
||||
}
|
||||
}
|
||||
return clearance.L0
|
||||
}
|
||||
|
||||
// RequestElevation raises clearance when needed and notifies dashboards.
|
||||
func (m *ClearanceManager) RequestElevation(agentID string, toLevel int, reason, source string) (int, error) {
|
||||
if m == nil {
|
||||
return clearance.L0, nil
|
||||
}
|
||||
current := m.Level(agentID)
|
||||
newLevel, err := clearance.RequestElevation(m.hub.db, agentID, current, toLevel, reason, source)
|
||||
if err != nil {
|
||||
return current, err
|
||||
}
|
||||
if newLevel <= current {
|
||||
return current, nil
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.agentClearance[agentID] = newLevel
|
||||
m.mu.Unlock()
|
||||
m.pushToAgent(agentID, newLevel)
|
||||
m.broadcastElevation(agentID, current, newLevel, reason, source)
|
||||
return newLevel, nil
|
||||
}
|
||||
|
||||
func (m *ClearanceManager) pushToAgent(agentID string, level int) {
|
||||
if m == nil || m.hub == nil {
|
||||
return
|
||||
}
|
||||
_ = m.hub.SendToAgent(agentID, Message{
|
||||
Type: "clearance_update",
|
||||
Payload: mustMarshal(map[string]interface{}{
|
||||
"clearance_level": level,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
func (m *ClearanceManager) broadcastElevation(agentID string, fromLevel, toLevel int, reason, source string) {
|
||||
if m == nil || m.hub == nil {
|
||||
return
|
||||
}
|
||||
m.hub.broadcastDashboard(Message{
|
||||
Type: "clearance_elevated",
|
||||
Payload: mustMarshal(map[string]interface{}{
|
||||
"agent_id": agentID,
|
||||
"from_level": fromLevel,
|
||||
"to_level": toLevel,
|
||||
"reason": reason,
|
||||
"source": source,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
// ClearanceGuardExecutor wraps FleetAIExecutor with clearance enforcement.
|
||||
type ClearanceGuardExecutor struct {
|
||||
Inner *FleetAIExecutor
|
||||
Clearance *ClearanceManager
|
||||
}
|
||||
|
||||
func (e *ClearanceGuardExecutor) Execute(agentID string, cmd fleetai.Command) (string, error) {
|
||||
if e == nil || e.Inner == nil {
|
||||
return "", fmt.Errorf("executor unavailable")
|
||||
}
|
||||
level := clearance.L0
|
||||
if e.Clearance != nil {
|
||||
level = e.Clearance.Level(agentID)
|
||||
}
|
||||
if err := clearance.EnforceClearance(cmd.Type, cmd.Args, level); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return e.Inner.Execute(agentID, cmd)
|
||||
}
|
||||
Reference in New Issue
Block a user