fix(reboot): surface windows99 collector readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 57s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 57s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -306,6 +306,9 @@ fi
|
||||
if [ -s "$windows99_management_file" ]; then
|
||||
scorecard_args+=(--windows99-management-file "$windows99_management_file")
|
||||
fi
|
||||
if [ -s "$windows99_vmware_file" ]; then
|
||||
scorecard_args+=(--windows99-vmware-collector-file "$windows99_vmware_file")
|
||||
fi
|
||||
if [ -s "$windows99_vmware_file" ] && grep -q '^AWOOOI_WINDOWS99_VMWARE_AUTOSTART=1$' "$windows99_vmware_file"; then
|
||||
scorecard_args+=(--windows99-vmware-file "$windows99_vmware_file")
|
||||
fi
|
||||
|
||||
@@ -54,6 +54,11 @@ def parse_args() -> argparse.Namespace:
|
||||
type=Path,
|
||||
help="Optional windows99-vmware-autostart.ps1 Verify output.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--windows99-vmware-collector-file",
|
||||
type=Path,
|
||||
help="Optional collect-windows99-vmware-verify.sh check/collect output.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--windows99-management-file",
|
||||
type=Path,
|
||||
@@ -351,6 +356,103 @@ def parse_windows99_management_readback(path: Path | None) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def parse_windows99_vmware_collector_readback(text: str) -> dict[str, Any]:
|
||||
"""Parse no-secret Windows99 VMware collector check/collect output."""
|
||||
default = {
|
||||
"schema_version": "windows99_vmware_verify_collector_v1",
|
||||
"readback_present": False,
|
||||
"status": "missing",
|
||||
"ssh_batchmode_auth_ready": False,
|
||||
"safe_next_step": "run_no_secret_collector_check_or_console_validator",
|
||||
"blockers": [],
|
||||
"forbidden_actions": [
|
||||
"windows_password_or_secret_collection",
|
||||
"host_reboot",
|
||||
"vm_power_change",
|
||||
"windows_update_policy_apply",
|
||||
],
|
||||
}
|
||||
if not text.strip():
|
||||
return default
|
||||
|
||||
kv = parse_kv(text)
|
||||
schema = str(kv.get("schema_version") or "")
|
||||
if schema != "windows99_vmware_verify_collector_v1":
|
||||
invalid = dict(default)
|
||||
invalid.update(
|
||||
{
|
||||
"readback_present": True,
|
||||
"schema_version": schema or "unknown",
|
||||
"status": "blocked_invalid_windows99_vmware_collector_readback",
|
||||
"blockers": ["windows99_no_secret_collector_readback_invalid"],
|
||||
}
|
||||
)
|
||||
return invalid
|
||||
|
||||
status = str(kv.get("verify_collection_status") or "unknown")
|
||||
ssh_ready = truthy(kv.get("ssh_batchmode_auth_ready"))
|
||||
collector_blockers_by_status = {
|
||||
"blocked_ssh_publickey_auth_missing": "windows99_ssh_publickey_auth_missing",
|
||||
"blocked_ssh_port_closed": "windows99_ssh_port_closed",
|
||||
"blocked_local_verify_script_missing": "windows99_local_verify_script_missing",
|
||||
"blocked_remote_verify_output_invalid": "windows99_remote_verify_output_invalid",
|
||||
"blocked_remote_verify_command_failed": "windows99_remote_verify_command_failed",
|
||||
}
|
||||
blockers: list[str] = []
|
||||
if status.startswith("blocked_"):
|
||||
blockers.append(
|
||||
collector_blockers_by_status.get(
|
||||
status,
|
||||
"windows99_no_secret_collector_not_ready",
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
"schema_version": schema,
|
||||
"readback_present": True,
|
||||
"status": status,
|
||||
"dry_run": str(kv.get("dry_run") or "unknown"),
|
||||
"target_host": str(kv.get("target_host") or "192.168.0.99"),
|
||||
"target_host_alias": str(kv.get("target_host_alias") or "99"),
|
||||
"port_22_open": truthy(kv.get("port_22_open")),
|
||||
"port_3389_open": truthy(kv.get("port_3389_open")),
|
||||
"port_5985_open": truthy(kv.get("port_5985_open")),
|
||||
"port_5986_open": truthy(kv.get("port_5986_open")),
|
||||
"port_9182_open": truthy(kv.get("port_9182_open")),
|
||||
"ssh_candidate_users": csv_strings(kv.get("ssh_candidate_users")),
|
||||
"ssh_auth_probed_users": int_value(kv.get("ssh_auth_probed_users")),
|
||||
"ssh_batchmode_auth_ready": ssh_ready,
|
||||
"ssh_authenticated_user": str(kv.get("ssh_authenticated_user") or ""),
|
||||
"ssh_auth_probe_exit_status": str(
|
||||
kv.get("ssh_auth_probe_exit_status") or "unknown"
|
||||
),
|
||||
"remote_verify_attempted": truthy(kv.get("remote_verify_attempted")),
|
||||
"remote_verify_exit_status": str(
|
||||
kv.get("remote_verify_exit_status") or "unknown"
|
||||
),
|
||||
"local_verify_script_present": truthy(kv.get("local_verify_script_present")),
|
||||
"safe_next_step": str(kv.get("safe_next_step") or ""),
|
||||
"blockers": blockers,
|
||||
"secret_value_read": truthy(kv.get("secret_value_read")),
|
||||
"password_prompt_allowed": truthy(kv.get("password_prompt_allowed")),
|
||||
"remote_write_performed": truthy(kv.get("remote_write_performed")),
|
||||
"host_reboot_performed": truthy(kv.get("host_reboot_performed")),
|
||||
"vm_power_change_performed": truthy(kv.get("vm_power_change_performed")),
|
||||
"windows_update_policy_apply_performed": truthy(
|
||||
kv.get("windows_update_policy_apply_performed")
|
||||
),
|
||||
"forbidden_actions": [
|
||||
"windows_password_or_secret_collection",
|
||||
"host_reboot",
|
||||
"vm_power_change",
|
||||
"windows_update_policy_apply",
|
||||
"manual_registry_edit",
|
||||
"service_restart",
|
||||
"github_api",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def read_json_object(path: Path | None) -> dict[str, Any]:
|
||||
if not path:
|
||||
return {}
|
||||
@@ -907,13 +1009,27 @@ def build_windows99_verify_collection_packet(
|
||||
*,
|
||||
windows99: dict[str, Any],
|
||||
host_boot_detection: dict[str, Any],
|
||||
windows99_management: dict[str, Any] | None = None,
|
||||
windows99_collector: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Describe the next no-secret Windows 99 verifier collection step."""
|
||||
windows99_management = windows99_management or {}
|
||||
windows99_collector = windows99_collector or {}
|
||||
host99 = host_row_by_alias(host_boot_detection, "99")
|
||||
host99_reachable = host99.get("reachable") is True
|
||||
host99_uptime_known = int_value(host99.get("uptime_seconds"), -1) >= 0
|
||||
readback_present = windows99.get("readback_present") is True
|
||||
verify_ready = windows99.get("verify_ready") is True
|
||||
local_console_reachable = (
|
||||
windows99_management.get("local_console_channel_reachable") is True
|
||||
)
|
||||
console_channels = strings(windows99_management.get("console_collection_channels"))
|
||||
collector_present = windows99_collector.get("readback_present") is True
|
||||
collector_status = str(windows99_collector.get("status") or "unknown")
|
||||
collector_ssh_ready = (
|
||||
windows99_collector.get("ssh_batchmode_auth_ready") is True
|
||||
or collector_status == "collected_windows99_vmware_verify_stdout"
|
||||
)
|
||||
blockers = strings(windows99.get("blockers"))
|
||||
collection_blockers: list[str] = []
|
||||
if not host99_reachable:
|
||||
@@ -923,6 +1039,11 @@ def build_windows99_verify_collection_packet(
|
||||
collection_blockers.extend(
|
||||
blocker for blocker in blockers if blocker not in collection_blockers
|
||||
)
|
||||
collection_blockers.extend(
|
||||
blocker
|
||||
for blocker in strings(windows99_collector.get("blockers"))
|
||||
if blocker not in collection_blockers
|
||||
)
|
||||
if not host99_uptime_known:
|
||||
collection_blockers.append("windows99_uptime_unknown")
|
||||
|
||||
@@ -933,6 +1054,14 @@ def build_windows99_verify_collection_packet(
|
||||
if host99_reachable and not readback_present
|
||||
else "blocked_windows99_verify_collection_not_ready"
|
||||
)
|
||||
available_channels: list[str] = []
|
||||
if local_console_reachable:
|
||||
available_channels.extend(console_channels or ["local_console"])
|
||||
if collector_ssh_ready:
|
||||
available_channels.append("no_secret_ssh_batchmode_verify_collector")
|
||||
if readback_present:
|
||||
available_channels.append("committed_no_secret_artifact_file")
|
||||
available_channels = unique_strings(available_channels)
|
||||
return {
|
||||
"schema_version": "windows99_vmware_verify_collection_packet_v1",
|
||||
"status": status,
|
||||
@@ -942,7 +1071,29 @@ def build_windows99_verify_collection_packet(
|
||||
"host99_uptime_known": host99_uptime_known,
|
||||
"readback_present": readback_present,
|
||||
"verify_ready": verify_ready,
|
||||
"can_collect_no_secret_verify": host99_reachable and not verify_ready,
|
||||
"can_collect_no_secret_verify": (
|
||||
host99_reachable
|
||||
and not verify_ready
|
||||
and (
|
||||
local_console_reachable
|
||||
or collector_ssh_ready
|
||||
or not collector_present
|
||||
)
|
||||
),
|
||||
"available_collection_channels": available_channels,
|
||||
"no_secret_collector_readback_present": collector_present,
|
||||
"no_secret_collector_status": collector_status,
|
||||
"no_secret_collector_safe_next_step": str(
|
||||
windows99_collector.get("safe_next_step") or ""
|
||||
),
|
||||
"no_secret_collector_ssh_batchmode_auth_ready": collector_ssh_ready,
|
||||
"no_secret_collector_port_22_open": (
|
||||
windows99_collector.get("port_22_open") is True
|
||||
),
|
||||
"no_secret_collector_remote_verify_attempted": (
|
||||
windows99_collector.get("remote_verify_attempted") is True
|
||||
),
|
||||
"no_secret_collector": windows99_collector,
|
||||
"required_vm_aliases": strings(windows99.get("required_vm_aliases"))
|
||||
or sorted(WINDOWS99_REQUIRED_VM_ALIASES),
|
||||
"expected_no_secret_output_fields": [
|
||||
@@ -1104,6 +1255,9 @@ def build_required_checks(payload: dict[str, Any]) -> dict[str, bool]:
|
||||
windows99_management = payload.get("windows99_management_channel")
|
||||
if not isinstance(windows99_management, dict):
|
||||
windows99_management = {}
|
||||
windows99_collector = payload.get("windows99_vmware_verify_collector")
|
||||
if not isinstance(windows99_collector, dict):
|
||||
windows99_collector = {}
|
||||
ssh_batch = windows99_management.get("ssh_batch")
|
||||
if not isinstance(ssh_batch, dict):
|
||||
ssh_batch = {}
|
||||
@@ -1548,6 +1702,9 @@ def enrich_machine_readback(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
windows99_management = payload.get("windows99_management_channel")
|
||||
if not isinstance(windows99_management, dict):
|
||||
windows99_management = {}
|
||||
windows99_collector = payload.get("windows99_vmware_verify_collector")
|
||||
if not isinstance(windows99_collector, dict):
|
||||
windows99_collector = {}
|
||||
ssh_batch = windows99_management.get("ssh_batch")
|
||||
if not isinstance(ssh_batch, dict):
|
||||
ssh_batch = {}
|
||||
@@ -1587,6 +1744,8 @@ def enrich_machine_readback(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
windows99_verify_collection = build_windows99_verify_collection_packet(
|
||||
windows99=windows99,
|
||||
host_boot_detection=host_boot_detection,
|
||||
windows99_management=windows99_management,
|
||||
windows99_collector=windows99_collector,
|
||||
)
|
||||
|
||||
rollups = {
|
||||
@@ -1694,6 +1853,36 @@ def enrich_machine_readback(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"windows99_verify_collection_blocker_count": len(
|
||||
strings(windows99_verify_collection.get("collection_blockers"))
|
||||
),
|
||||
"windows99_no_secret_collector_readback_present": (
|
||||
windows99_verify_collection.get("no_secret_collector_readback_present")
|
||||
is True
|
||||
),
|
||||
"windows99_no_secret_collector_status": str(
|
||||
windows99_verify_collection.get("no_secret_collector_status") or "unknown"
|
||||
),
|
||||
"windows99_no_secret_collector_ssh_batchmode_auth_ready": (
|
||||
windows99_verify_collection.get(
|
||||
"no_secret_collector_ssh_batchmode_auth_ready"
|
||||
)
|
||||
is True
|
||||
),
|
||||
"windows99_no_secret_collector_port_22_open": (
|
||||
windows99_verify_collection.get("no_secret_collector_port_22_open")
|
||||
is True
|
||||
),
|
||||
"windows99_no_secret_collector_remote_verify_attempted": (
|
||||
windows99_verify_collection.get(
|
||||
"no_secret_collector_remote_verify_attempted"
|
||||
)
|
||||
is True
|
||||
),
|
||||
"windows99_no_secret_collector_safe_next_step": str(
|
||||
windows99_verify_collection.get("no_secret_collector_safe_next_step")
|
||||
or ""
|
||||
),
|
||||
"windows99_available_collection_channels": strings(
|
||||
windows99_verify_collection.get("available_collection_channels")
|
||||
),
|
||||
"windows99_host99_reachable": (
|
||||
windows99_verify_collection["host99_reachable"] is True
|
||||
),
|
||||
@@ -1764,6 +1953,21 @@ def enrich_machine_readback(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"windows99_verify_collection_can_collect_no_secret": rollups[
|
||||
"windows99_verify_collection_can_collect_no_secret"
|
||||
],
|
||||
"windows99_no_secret_collector_readback_present": rollups[
|
||||
"windows99_no_secret_collector_readback_present"
|
||||
],
|
||||
"windows99_no_secret_collector_status": rollups[
|
||||
"windows99_no_secret_collector_status"
|
||||
],
|
||||
"windows99_no_secret_collector_ssh_batchmode_auth_ready": rollups[
|
||||
"windows99_no_secret_collector_ssh_batchmode_auth_ready"
|
||||
],
|
||||
"windows99_no_secret_collector_safe_next_step": rollups[
|
||||
"windows99_no_secret_collector_safe_next_step"
|
||||
],
|
||||
"windows99_available_collection_channels": rollups[
|
||||
"windows99_available_collection_channels"
|
||||
],
|
||||
"windows99_remote_execution_channel_ready": rollups[
|
||||
"windows99_remote_execution_channel_ready"
|
||||
],
|
||||
@@ -1837,6 +2041,12 @@ def enrich_machine_readback(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"reboot_auto_recovery_windows99_verify_collection_can_collect_no_secret": (
|
||||
rollups["windows99_verify_collection_can_collect_no_secret"]
|
||||
),
|
||||
"reboot_auto_recovery_windows99_no_secret_collector_status": rollups[
|
||||
"windows99_no_secret_collector_status"
|
||||
],
|
||||
"reboot_auto_recovery_windows99_no_secret_collector_ssh_batchmode_auth_ready": (
|
||||
rollups["windows99_no_secret_collector_ssh_batchmode_auth_ready"]
|
||||
),
|
||||
"secret_values_collected": False,
|
||||
"github_api_used": False,
|
||||
"workflow_trigger_performed": False,
|
||||
@@ -1878,6 +2088,9 @@ def build_scorecard(args: argparse.Namespace) -> dict[str, Any]:
|
||||
windows99_management = parse_windows99_management_readback(
|
||||
args.windows99_management_file
|
||||
)
|
||||
windows99_collector = parse_windows99_vmware_collector_readback(
|
||||
read_text(args.windows99_vmware_collector_file)
|
||||
)
|
||||
controls = source_controls()
|
||||
free_gib = disk_free_gib(args.disk_path)
|
||||
|
||||
@@ -2051,6 +2264,7 @@ def build_scorecard(args: argparse.Namespace) -> dict[str, Any]:
|
||||
"public_maintenance_fallback": public_maintenance,
|
||||
"windows99_vmware_autostart": windows99,
|
||||
"windows99_management_channel": windows99_management,
|
||||
"windows99_vmware_verify_collector": windows99_collector,
|
||||
"capacity": {
|
||||
"checked": free_gib is not None,
|
||||
"free_gib": round(free_gib, 3) if free_gib is not None else None,
|
||||
|
||||
@@ -172,6 +172,43 @@ WINDOWS99_MANAGEMENT_BLOCKED = {
|
||||
}
|
||||
|
||||
|
||||
WINDOWS99_COLLECTOR_PUBLICKEY_BLOCKED = """\
|
||||
schema_version=windows99_vmware_verify_collector_v1
|
||||
dry_run=true
|
||||
target_host=192.168.0.99
|
||||
target_host_alias=99
|
||||
connect_timeout_seconds=3
|
||||
ssh_timeout_seconds=3
|
||||
remote_verify_timeout_seconds=45
|
||||
port_timeout_wrapper=timeout
|
||||
ssh_auth_probe_user_limit=2
|
||||
ssh_timeout_wrapper=timeout
|
||||
port_22_open=1
|
||||
port_3389_open=1
|
||||
port_5985_open=0
|
||||
port_5986_open=0
|
||||
port_9182_open=0
|
||||
ssh_candidate_users=ogt,wooo,ooo,administrator,Administrator
|
||||
ssh_auth_probed_users=2
|
||||
ssh_batchmode_auth_ready=0
|
||||
ssh_authenticated_user=
|
||||
ssh_auth_probe_exit_status=255
|
||||
ssh_auth_probe_stdout_present=0
|
||||
remote_verify_mode=in_memory_stdin_scriptblock
|
||||
local_verify_script_present=1
|
||||
remote_verify_attempted=0
|
||||
remote_verify_exit_status=not_attempted
|
||||
verify_collection_status=blocked_ssh_publickey_auth_missing
|
||||
safe_next_step=select_existing_authorized_public_key_user_or_set_WINDOWS99_SSH_USERS_then_rerun_collector_no_password
|
||||
secret_value_read=false
|
||||
password_prompt_allowed=false
|
||||
remote_write_performed=false
|
||||
host_reboot_performed=false
|
||||
vm_power_change_performed=false
|
||||
windows_update_policy_apply_performed=false
|
||||
"""
|
||||
|
||||
|
||||
def run_scorecard(
|
||||
tmp_path: Path,
|
||||
summary: str,
|
||||
@@ -179,11 +216,13 @@ def run_scorecard(
|
||||
windows99: str = WINDOWS99_VMWARE_GREEN,
|
||||
public_maintenance: dict | None = PUBLIC_MAINTENANCE_GREEN,
|
||||
windows99_management: str | None = None,
|
||||
windows99_collector: str | None = None,
|
||||
) -> dict:
|
||||
summary_path = tmp_path / "summary.txt"
|
||||
probe_path = tmp_path / "probe.txt"
|
||||
reboot_event_path = tmp_path / "reboot-event.json"
|
||||
windows99_path = tmp_path / "windows99-vmware.txt"
|
||||
windows99_collector_path = tmp_path / "windows99-vmware-collector.txt"
|
||||
public_maintenance_path = tmp_path / "public-maintenance.json"
|
||||
summary_path.write_text(summary, encoding="utf-8")
|
||||
probe_path.write_text(probe, encoding="utf-8")
|
||||
@@ -213,6 +252,11 @@ def run_scorecard(
|
||||
if windows99_management is not None:
|
||||
windows99_management_path.write_text(windows99_management, encoding="utf-8")
|
||||
args.extend(["--windows99-management-file", str(windows99_management_path)])
|
||||
if windows99_collector is not None:
|
||||
windows99_collector_path.write_text(windows99_collector, encoding="utf-8")
|
||||
args.extend(
|
||||
["--windows99-vmware-collector-file", str(windows99_collector_path)]
|
||||
)
|
||||
result = subprocess.run(
|
||||
args,
|
||||
text=True,
|
||||
@@ -554,6 +598,55 @@ def test_windows99_management_channel_unavailable_is_visible(tmp_path: Path) ->
|
||||
assert payload["readback"]["windows99_remote_execution_channel_ready"] is False
|
||||
|
||||
|
||||
def test_windows99_no_secret_collector_publickey_blocker_is_visible(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
payload = run_scorecard(
|
||||
tmp_path,
|
||||
GREEN_SUMMARY,
|
||||
windows99="",
|
||||
windows99_management=json.dumps(WINDOWS99_MANAGEMENT_BLOCKED),
|
||||
windows99_collector=WINDOWS99_COLLECTOR_PUBLICKEY_BLOCKED,
|
||||
)
|
||||
|
||||
collection = payload["windows99_verify_collection"]
|
||||
assert collection["no_secret_collector_readback_present"] is True
|
||||
assert collection["no_secret_collector_status"] == (
|
||||
"blocked_ssh_publickey_auth_missing"
|
||||
)
|
||||
assert collection["no_secret_collector_ssh_batchmode_auth_ready"] is False
|
||||
assert collection["no_secret_collector_port_22_open"] is True
|
||||
assert collection["no_secret_collector_remote_verify_attempted"] is False
|
||||
assert collection["no_secret_collector"]["ssh_auth_probed_users"] == 2
|
||||
assert collection["no_secret_collector"]["ssh_candidate_users"] == [
|
||||
"ogt",
|
||||
"wooo",
|
||||
"ooo",
|
||||
"administrator",
|
||||
"Administrator",
|
||||
]
|
||||
assert "windows99_ssh_publickey_auth_missing" in collection[
|
||||
"collection_blockers"
|
||||
]
|
||||
assert collection["can_collect_no_secret_verify"] is True
|
||||
assert collection["available_collection_channels"] == [
|
||||
"rdp_console",
|
||||
"hyperv_vmconnect",
|
||||
]
|
||||
assert payload["rollups"]["windows99_no_secret_collector_status"] == (
|
||||
"blocked_ssh_publickey_auth_missing"
|
||||
)
|
||||
assert (
|
||||
payload["rollups"][
|
||||
"windows99_no_secret_collector_ssh_batchmode_auth_ready"
|
||||
]
|
||||
is False
|
||||
)
|
||||
assert payload["readback"]["windows99_no_secret_collector_status"] == (
|
||||
"blocked_ssh_publickey_auth_missing"
|
||||
)
|
||||
|
||||
|
||||
def test_degraded_wazuh_and_old_boot_observation_block_slo(tmp_path: Path) -> None:
|
||||
summary = GREEN_SUMMARY.replace("WAZUH_DASHBOARD_DEGRADED=0", "WAZUH_DASHBOARD_DEGRADED=1")
|
||||
probe = HOST_PROBE_GREEN.replace("uptime_seconds=150", "uptime_seconds=900")
|
||||
|
||||
Reference in New Issue
Block a user