diff --git a/apps/api/src/services/harbor_registry_controlled_recovery_receipt.py b/apps/api/src/services/harbor_registry_controlled_recovery_receipt.py index d56d596b..86ab5318 100644 --- a/apps/api/src/services/harbor_registry_controlled_recovery_receipt.py +++ b/apps/api/src/services/harbor_registry_controlled_recovery_receipt.py @@ -198,6 +198,21 @@ def validate_harbor_registry_controlled_recovery_receipt( "ssh_local_repair_control_channel_metadata_ready": ssh_local[ "control_channel_metadata_ready" ], + "ssh_local_repair_account_metadata_ready": ssh_local[ + "account_metadata_ready" + ], + "ssh_local_repair_target_user_account_locked": ssh_local[ + "target_user_account_locked" + ], + "ssh_local_repair_target_user_shell_executable": ssh_local[ + "target_user_shell_executable" + ], + "ssh_local_repair_sshd_pubkeyauthentication": ssh_local[ + "sshd_pubkeyauthentication" + ], + "ssh_local_repair_sshd_authorized_keys_file_default": ssh_local[ + "sshd_authorized_keys_file_default" + ], "watchdog_check_receipt_seen": watchdog_check["receipt_seen"], "watchdog_check_harbor_ready": watchdog_check["harbor_ready"], "watchdog_repair_receipt_seen": watchdog_repair["receipt_seen"], @@ -936,15 +951,23 @@ def _parse_ssh_local_repair_output(output: str) -> dict[str, Any]: "SSHD_CONFIG_SYNTAX_AFTER_APPLY=ok" in output or not _bool_from_field(fields.get("APPLY")) ) - user_exists = "USER_STATUS user=" in output and "exists=1" in output - authorized_keys_exists = ( - "AUTHORIZED_KEYS_STATUS" in output and "exists=1" in output + user_status = _first_key_value_line(output, prefix="USER_STATUS ") + authorized_keys_status = _first_key_value_line( + output, + prefix="AUTHORIZED_KEYS_STATUS ", ) + user_exists = _bool_from_field(user_status.get("exists")) + authorized_keys_exists = _bool_from_field(authorized_keys_status.get("exists")) account_locked = _bool_from_field(fields.get("account_locked")) shell_executable = _bool_from_field(fields.get("shell_executable")) sshd_effective_config_available = _bool_from_field(fields.get("available")) pubkey_authentication = str(fields.get("pubkeyauthentication") or "") + password_authentication = str(fields.get("passwordauthentication") or "") + kbdinteractive_authentication = str( + fields.get("kbdinteractiveauthentication") or "" + ) usepam = str(fields.get("usepam") or "") + maxstartups = str(fields.get("maxstartups") or "") authorized_keys_file_default = _bool_from_field( fields.get("authorized_keys_file_default") ) @@ -968,7 +991,10 @@ def _parse_ssh_local_repair_output(output: str) -> dict[str, Any]: "target_user_shell_executable": shell_executable, "sshd_effective_config_available": sshd_effective_config_available, "sshd_pubkeyauthentication": pubkey_authentication, + "sshd_passwordauthentication": password_authentication, + "sshd_kbdinteractiveauthentication": kbdinteractive_authentication, "sshd_usepam": usepam, + "sshd_maxstartups": maxstartups, "sshd_authorized_keys_file_default": authorized_keys_file_default, "account_metadata_ready": account_metadata_ready, "authorized_keys_metadata_present": authorized_keys_exists, @@ -986,6 +1012,13 @@ def _parse_ssh_local_repair_output(output: str) -> dict[str, Any]: } +def _first_key_value_line(output: str, *, prefix: str) -> dict[str, str]: + for line in output.splitlines(): + if line.startswith(prefix): + return _parse_key_values(line) + return {} + + def _parse_watchdog_output(output: str) -> dict[str, Any]: fields = _parse_key_values(output) marker_seen = "AWOOOI_HARBOR_WATCHDOG_CHECK" in output @@ -1804,6 +1837,7 @@ def _active_blockers( if ssh_diagnosis["node_high_load_seen"]: blockers.append("ssh_publickey_node_high_load_on_110") if ssh_local["receipt_seen"] and not ssh_local["control_channel_metadata_ready"]: + blockers.extend(_ssh_local_metadata_blockers(ssh_local)) blockers.append("ssh_local_repair_receipt_metadata_not_ready") if not watchdog_check["receipt_seen"]: blockers.append("harbor_watchdog_check_receipt_missing") @@ -1826,6 +1860,29 @@ def _active_blockers( return _unique_strings(blockers) +def _ssh_local_metadata_blockers(ssh_local: dict[str, Any]) -> list[str]: + blockers: list[str] = [] + if not ssh_local["sshd_config_syntax_ok"]: + blockers.append("ssh_local_repair_sshd_config_syntax_not_ok") + if not ssh_local["sshd_config_syntax_after_apply_ok"]: + blockers.append("ssh_local_repair_sshd_config_syntax_after_apply_not_ok") + if not ssh_local["target_user_exists"]: + blockers.append("ssh_local_repair_target_user_missing") + if ssh_local["target_user_account_locked"]: + blockers.append("ssh_local_repair_target_user_account_locked") + if not ssh_local["target_user_shell_executable"]: + blockers.append("ssh_local_repair_target_user_shell_not_executable") + if not ssh_local["authorized_keys_metadata_present"]: + blockers.append("ssh_local_repair_authorized_keys_missing") + if not ssh_local["sshd_effective_config_available"]: + blockers.append("ssh_local_repair_sshd_effective_config_unavailable") + if ssh_local["sshd_pubkeyauthentication"] != "yes": + blockers.append("ssh_local_repair_sshd_pubkeyauthentication_not_enabled") + if not ssh_local["sshd_authorized_keys_file_default"]: + blockers.append("ssh_local_repair_sshd_authorized_keys_file_drift") + return blockers + + def _status( *, ssh_diagnosis: dict[str, Any], diff --git a/apps/api/tests/test_harbor_registry_controlled_recovery_receipt.py b/apps/api/tests/test_harbor_registry_controlled_recovery_receipt.py index 865db211..6507556e 100644 --- a/apps/api/tests/test_harbor_registry_controlled_recovery_receipt.py +++ b/apps/api/tests/test_harbor_registry_controlled_recovery_receipt.py @@ -75,6 +75,78 @@ def test_harbor_recovery_receipt_accepts_verified_repair() -> None: assert payload["learning_writeback_contracts"][0]["raw_log_allowed"] is False +def test_harbor_recovery_receipt_surfaces_ssh_local_metadata_blockers() -> None: + ssh_local_output = ( + _ssh_local_apply_output() + .replace( + "account_locked=false shell=/bin/bash shell_exists=true shell_executable=true", + "account_locked=true shell=/bin/false shell_exists=true shell_executable=false", + ) + .replace( + "AUTHORIZED_KEYS_STATUS path=/home/wooo/.ssh/authorized_keys exists=1 bytes=380 lines=1", + "AUTHORIZED_KEYS_STATUS path=/home/wooo/.ssh/authorized_keys exists=0", + ) + .replace( + "pubkeyauthentication=yes passwordauthentication=no " + "kbdinteractiveauthentication=no usepam=yes maxstartups=10:30:100 " + "authorized_keys_file_default=true", + "pubkeyauthentication=no passwordauthentication=no " + "kbdinteractiveauthentication=no usepam=yes maxstartups=10:30:100 " + "authorized_keys_file_default=false", + ) + ) + + payload = validate_harbor_registry_controlled_recovery_receipt( + {"ssh_local_repair_output": ssh_local_output} + ) + + assert payload["status"] == ( + "ssh_local_repair_receipt_waiting_harbor_watchdog_check" + ) + assert "ssh_local_repair_target_user_account_locked" in payload[ + "active_blockers" + ] + assert "ssh_local_repair_target_user_shell_not_executable" in payload[ + "active_blockers" + ] + assert "ssh_local_repair_authorized_keys_missing" in payload["active_blockers"] + assert "ssh_local_repair_sshd_pubkeyauthentication_not_enabled" in payload[ + "active_blockers" + ] + assert "ssh_local_repair_sshd_authorized_keys_file_drift" in payload[ + "active_blockers" + ] + assert "ssh_local_repair_receipt_metadata_not_ready" in payload[ + "active_blockers" + ] + ssh_local = payload["readback"]["ssh_local_repair"] + assert ssh_local["control_channel_metadata_ready"] is False + assert ssh_local["account_metadata_ready"] is False + assert ssh_local["target_user_account_locked"] is True + assert ssh_local["target_user_shell_executable"] is False + assert ssh_local["authorized_keys_metadata_present"] is False + assert ssh_local["sshd_pubkeyauthentication"] == "no" + assert ssh_local["sshd_passwordauthentication"] == "no" + assert ssh_local["sshd_kbdinteractiveauthentication"] == "no" + assert ssh_local["sshd_maxstartups"] == "10:30:100" + assert ssh_local["sshd_authorized_keys_file_default"] is False + assert payload["rollups"]["ssh_local_repair_account_metadata_ready"] is False + assert ( + payload["rollups"]["ssh_local_repair_target_user_account_locked"] is True + ) + assert ( + payload["rollups"]["ssh_local_repair_target_user_shell_executable"] + is False + ) + assert payload["rollups"]["ssh_local_repair_sshd_pubkeyauthentication"] == "no" + assert ( + payload["rollups"]["ssh_local_repair_sshd_authorized_keys_file_default"] + is False + ) + assert payload["input_redaction"]["ssh_local_repair_output"]["line_count"] > 0 + assert "secret-token-like-content" not in str(payload) + + def test_harbor_recovery_receipt_routes_unhealthy_check_to_repair_once() -> None: payload = validate_harbor_registry_controlled_recovery_receipt( {