fix(agent99): bind host112 apply to immutable receipt

This commit is contained in:
ogt
2026-07-14 11:54:05 +08:00
parent 316605df87
commit d7a17e58b2
8 changed files with 766 additions and 61 deletions

View File

@@ -235,7 +235,13 @@ def test_phase_budget_and_systemd_outer_timeout_are_aligned() -> None:
assert 'if [ "$APPLY_PHASE_TIMEOUT_BUDGET_SECONDS" -gt "$EXECUTOR_DEADLINE_SECONDS" ]; then' in source
assert 'if [ "$MANAGER_VERIFY_INTERVAL_SECONDS" -gt "$MANAGER_VERIFY_TIMEOUT_SECONDS" ]; then' in source
assert "TimeoutStartSec=480" in service
assert "Invoke-HostSshText $targetHost $applyCommand 590 1" in control
assert "$script:Agent99Host112ExecutorBudgetSeconds = 420" in control
assert "$script:Agent99Host112ApplyTransportTimeoutSeconds = 510" in control
assert "Invoke-AgentHost112SshText $applyCommand $script:Agent99Host112ApplyTransportTimeoutSeconds 1" in control
assert "executorReserveSeconds -ge 60" in control
assert "queueReserveSeconds -ge 300" in control
assert "clientReserveSeconds -ge 120" in control
assert '$process.WaitForExit($script:Agent99QueueOuterDeadlineSeconds * 1000)' in control
def test_receipts_and_readbacks_are_immutable_boot_bound_and_replay_safe() -> None:
@@ -396,8 +402,8 @@ def test_agent99_propagates_identity_and_requires_independent_receipt() -> None:
assert f'[string]`${parameter} = ""' in bootstrap
assert 'reason = "controlled_dispatch_identity_unsafe"' in control
assert 'applyBlockedReason = "control_identity_incomplete_or_unsafe"' in function
assert '$before.managerPreflightStatus -eq "ready"' in function
assert 'receiptStatus -eq "written"' in function
assert '$dryRun.managerPreflightStatus -eq "ready"' in function
assert 'receiptStatus -eq "written_immutable"' in function
assert '$after.managerVerified' in function
assert 'name = "wazuh_manager_service_active"' in function
assert 'name = "wazuh_analysisd_process_present"' in function
@@ -406,3 +412,94 @@ def test_agent99_propagates_identity_and_requires_independent_receipt() -> None:
assert 'name = "wazuh_manager_api_55000_tcp"' in function
assert "host112_readback_transport_unavailable" in function
assert 'terminal = "no_write_control_identity_incomplete_or_unsafe"' in function
def test_agent99_manager_success_requires_apply_and_immutable_receipt_chain() -> None:
control = read(CONTROL)
function = control[control.index("function Invoke-AgentHost112GuestRecovery") :]
function = function[: function.index("function Test-HostReachability")]
assert "$applyTransport -and $applyTransport.ok" in function
assert '$applyReceipt.durableReceiptTerminal -eq "verified_healthy"' in function
assert "$applyReceipt.managerIndependentVerifierReady" in function
assert "$applyReceipt.managerRuntimeWritePerformed" in function
assert '[string]$applyReceipt.managerAttemptCount -eq "1"' in function
assert "$durableReceiptReadback.receiptPath -eq $applyReceipt.receiptPath" in function
assert "$durableReceiptReadback.receiptSha256 -eq $applyReceipt.receiptSha256" in function
assert "$durableReceiptReadback.durableReceiptTerminal -eq $applyReceipt.durableReceiptTerminal" in function
assert '$durableReceiptReadback.durableReceiptTerminal -eq "verified_healthy"' in function
assert "$afterManagerVerifierVerified" in function
assert 'afterVerifierRole = "independent_second_verifier_only"' in function
assert "$after.ready -and" in function
assert "$managerApplyVerified" in function
assert "existingReceiptTerminalAccepted" in function
assert "immutable_receipt_replay_not_successful" in function
assert "host112_failed_apply_cannot_be_washed_green" in read(
ROOT / "agent99-synthetic-tests.ps1"
)
def test_agent99_uses_correlated_sudo_dry_run_over_canonical_direct_key() -> None:
control = read(CONTROL)
assert "function Invoke-AgentHost112SshText" in control
assert 'expectedUser = "kali"' in control
assert 'route = "direct_host112_kali"' in control
assert "Invoke-SshText -TargetHost $transportContract.targetHost" in control
assert "--dry-run --trace-id {0} --run-id {1} --work-item-id {2}" in control
assert "Invoke-AgentHost112SshText $dryRunCommand" in control
assert "$dryRun.identityMatches" in control
assert "$dryRun.managerPreflightPresent" in control
assert "$dryRun.transportOk" in control
assert "-not $dryRun.durableReceiptPresent" in control
def test_unhealthy_executor_exit_one_is_delivered_readback_not_transport_failure() -> None:
control = read(CONTROL)
function = control[control.index("function Invoke-AgentHost112GuestRecovery") :]
function = function[: function.index("function Test-HostReachability")]
synthetic = read(ROOT / "agent99-synthetic-tests.ps1")
assert function.count('case "$executor_exit" in 0|1) exit 0') == 3
assert "$before.executorExitCode -in @(\"0\", \"1\")" in function
assert "$before.readbackPresent -and" in function
assert "$before.identityMatches -and" in function
assert '$beforeTransport = Invoke-AgentHost112SshText $checkCommand' in function
assert 'New-Host112SyntheticRow "check" 0 0 "none" 0 "not_requested" 0 "unknown" 1' in synthetic
assert "host112_general_candidate_not_blocked_by_healthy_manager" in synthetic
def test_general_guest_candidate_is_not_blocked_by_manager_candidate() -> None:
control = read(CONTROL)
function = control[control.index("function Invoke-AgentHost112GuestRecovery") :]
function = function[: function.index("function Test-HostReachability")]
synthetic = read(ROOT / "agent99-synthetic-tests.ps1")
assert "$generalCandidateRequired = [bool]($beforeVerified -and -not $before.generalReady)" in function
assert "$managerCandidateRequired = [bool]($beforeVerified -and -not $before.managerVerified)" in function
assert "$generalApplyEligible = [bool]($generalCandidateRequired -and $generalDryRunVerified)" in function
assert "$managerApplyEligible = [bool]($managerCandidateRequired -and $managerDryRunVerified)" in function
assert "$applyEligible = [bool]($generalApplyEligible -or $managerApplyEligible)" in function
assert "managerNoAttemptPreserved" in function
assert "host112_general_candidate_not_blocked_by_healthy_manager" in synthetic
assert '-not $generalOnly.managerCandidateRequired' in synthetic
assert '[string]$generalOnly.applyReceipt.managerAttemptCount -eq "0"' in synthetic
assert '-not $generalOnly.applyReceipt.managerRuntimeWritePerformed' in synthetic
def test_deploy_and_bootstrap_canonicalize_host112_direct_transport() -> None:
deploy = read(ROOT / "agent99-deploy.ps1")
bootstrap = read(BOOTSTRAP)
generic_config = read(ROOT / "agent99.config.example.json")
host_config = read(ROOT / "agent99.config.99.example.json")
assert 'Set-AgentProperty $config.sshUsers "192.168.0.112" "kali"' in deploy
assert 'name = "host112_canonical_direct_route"' in deploy
assert 'Host112 canonical direct SSH config post-verifier failed.' in deploy
assert "Ensure-AgentHost112CanonicalSshConfig" in bootstrap
assert 'Set-AgentBootstrapProperty $config.sshUsers "192.168.0.112" "kali"' in bootstrap
assert 'Host112 canonical direct SSH config bootstrap verifier failed.' in bootstrap
for config in (generic_config, host_config):
assert '"192.168.0.112": "kali"' in config
assert '"directHosts"' in config
assert '"192.168.0.112"' in config