Skip to content
Open
167 changes: 163 additions & 4 deletions extensions/business/cybersec/red_mesh/pentester_api_01.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@

"""

import ipaddress
import random

from urllib.parse import urlparse

from naeural_core.business.default.web_app.fast_api_web_app import FastApiWebAppPlugin as BasePlugin
from .redmesh_utils import PentestLocalWorker # Import PentestJob from separate module
from .redmesh_llm_agent_mixin import _RedMeshLlmAgentMixin
Expand All @@ -42,7 +45,7 @@
LLM_ANALYSIS_REMEDIATION_PLAN,
)

__VER__ = '0.8.2'
__VER__ = '0.9.0'

_CONFIG = {
**BasePlugin.CONFIG,
Expand Down Expand Up @@ -82,6 +85,10 @@
"LLM_AGENT_API_TIMEOUT": 120, # Timeout in seconds for LLM API calls
"LLM_AUTO_ANALYSIS_TYPE": "security_assessment", # Default analysis type

# RedMesh attestation submission
"ATTESTATION_ENABLED": True,
"ATTESTATION_MIN_SECONDS_BETWEEN_SUBMITS": 86400,

'VALIDATION_RULES': {
**BasePlugin.CONFIG['VALIDATION_RULES'],
},
Expand All @@ -108,6 +115,7 @@ class PentesterApi01Plugin(BasePlugin, _RedMeshLlmAgentMixin):
List of job_ids that completed locally (used for status responses).
"""
CONFIG = _CONFIG
REDMESH_ATTESTATION_DOMAIN = "0xced141225d43c56d8b224d12f0b9524a15dc86df0113c42ffa4bc859309e0d40"


def on_init(self):
Expand Down Expand Up @@ -198,6 +206,130 @@ def Pd(self, s, *args, score=-1, **kwargs):
return


def _attestation_get_tenant_private_key(self):
env_name = "R1EN_ATTESTATION_PRIVATE_KEY"
private_key = self.os_environ.get(env_name, None)
if private_key:
private_key = private_key.strip()
if not private_key:
return None
return private_key

@staticmethod
def _attestation_pack_cid_obfuscated(report_cid) -> str:
if not isinstance(report_cid, str) or len(report_cid.strip()) == 0:
return "0x" + ("00" * 10)
cid = report_cid.strip()
if len(cid) >= 10:
masked = cid[:5] + cid[-5:]
else:
masked = cid.ljust(10, "_")
safe = "".join(ch if 32 <= ord(ch) <= 126 else "_" for ch in masked)[:10]
data = safe.encode("ascii", errors="ignore")
if len(data) < 10:
data = data + (b"_" * (10 - len(data)))
return "0x" + data[:10].hex()

@staticmethod
def _attestation_extract_host(target):
if not isinstance(target, str):
return None
target = target.strip()
if not target:
return None
if "://" in target:
parsed = urlparse(target)
if parsed.hostname:
return parsed.hostname
host = target.split("/", 1)[0]
if host.count(":") == 1 and "." in host:
host = host.split(":", 1)[0]
return host

def _attestation_pack_ip_obfuscated(self, target) -> str:
host = self._attestation_extract_host(target)
if not host:
return "0x0000"
if ".." in host:
parts = host.split("..")
if len(parts) == 2 and all(part.isdigit() for part in parts):
first_octet = int(parts[0])
last_octet = int(parts[1])
if 0 <= first_octet <= 255 and 0 <= last_octet <= 255:
return f"0x{first_octet:02x}{last_octet:02x}"
try:
ip_obj = ipaddress.ip_address(host)
except Exception:
return "0x0000"
if ip_obj.version != 4:
return "0x0000"
octets = host.split(".")
first_octet = int(octets[0])
last_octet = int(octets[-1])
return f"0x{first_octet:02x}{last_octet:02x}"


def _submit_attestation(self, job_id: str, job_specs: dict, workers: dict):
if not self.cfg_attestation_enabled:
return None
tenant_private_key = self._attestation_get_tenant_private_key()
if tenant_private_key is None:
self.P(
"RedMesh attestation is enabled but tenant private key is missing. "
"Expected env var 'R1EN_ATTESTATION_PRIVATE_KEY'.",
color='y'
)
return None

run_mode = str(job_specs.get("run_mode", "SINGLEPASS")).upper()
test_mode = 1 if run_mode == "CONTINUOUS_MONITORING" else 0
node_count = len(workers) if isinstance(workers, dict) else 0
# TODO: replace placeholder score with proper RedMesh vulnerability scoring logic.
vulnerability_score = 100
target = job_specs.get("target")
report_cid = workers.get(self.ee_addr, {}).get("report_cid", None) #TODO: use the correct CID
node_eth_address = self.bc.eth_address
ip_obfuscated = self._attestation_pack_ip_obfuscated(target)
cid_obfuscated = self._attestation_pack_cid_obfuscated(report_cid)

tx_hash = self.bc.submit_attestation(
function_name="submitRedmeshAttestation",
function_args=[
test_mode,
node_count,
vulnerability_score,
ip_obfuscated,
cid_obfuscated,
],
signature_types=["bytes32", "uint8", "uint16", "uint8", "bytes2", "bytes10"],
signature_values=[
self.REDMESH_ATTESTATION_DOMAIN,
test_mode,
node_count,
vulnerability_score,
ip_obfuscated,
cid_obfuscated,
],
tx_private_key=tenant_private_key,
)

result = {
"job_id": job_id,
"tx_hash": tx_hash,
"test_mode": "C" if test_mode == 1 else "S",
"node_count": node_count,
"vulnerability_score": vulnerability_score,
"report_cid": report_cid,
"node_eth_address": node_eth_address,
}
self.P(
"Submitted RedMesh attestation for "
f"{job_id} (tx: {tx_hash}, node: {node_eth_address}, score: {vulnerability_score})",
color='g'
)
return result


def __post_init(self):
"""
Perform warmup: reconcile existing jobs in CStore, migrate legacy keys,
Expand Down Expand Up @@ -848,11 +980,38 @@ def _maybe_finalize_pass(self):
# ═══════════════════════════════════════════════════
# STATE: All peers completed current pass
# ═══════════════════════════════════════════════════
pass_history.append({
now_ts = self.time()
pass_record = {
"pass_nr": job_pass,
"completed_at": self.time(),
"completed_at": now_ts,
"reports": {addr: w.get("report_cid") for addr, w in workers.items()}
})
}

should_submit_attestation = True
if run_mode == "CONTINUOUS_MONITORING":
last_attestation_at = job_specs.get("last_attestation_at")
min_interval = self.cfg_attestation_min_seconds_between_submits
if last_attestation_at is not None and now_ts - last_attestation_at < min_interval:
should_submit_attestation = False

if should_submit_attestation:
# Best-effort on-chain summary; failures must not block pass finalization.
try:
redmesh_attestation = self._submit_attestation(
job_id=job_id,
job_specs=job_specs,
workers=workers
)
if redmesh_attestation is not None:
pass_record["redmesh_attestation"] = redmesh_attestation
job_specs["last_attestation_at"] = now_ts
except Exception as exc:
self.P(
f"Failed to submit RedMesh attestation for job {job_id}: {exc}",
color='r'
)

pass_history.append(pass_record)

# Handle SINGLEPASS - set FINALIZED and exit (no scheduling)
if run_mode == "SINGLEPASS":
Expand Down