From a2db99aacb9a6a39265b114dbe9228eedfd6aed5 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 22 Jul 2026 15:22:54 +0800 Subject: [PATCH] fix(signoz): exclude full controller ancestry from guard --- agent99-signoz-metadata-executor.ps1 | 13 +++- ...oz_metadata_executor_runtime_entrypoint.py | 73 +++++++++++++++++-- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/agent99-signoz-metadata-executor.ps1 b/agent99-signoz-metadata-executor.ps1 index 2f8d7b66d..a3b06bb48 100644 --- a/agent99-signoz-metadata-executor.ps1 +++ b/agent99-signoz-metadata-executor.ps1 @@ -38,11 +38,11 @@ $SignozImageId = "sha256:c17991d10966aedcb0d4d83805a0baf5310f1553d90611629036fcf $SignozContainer = "signoz" $OperationLock = "/tmp/awoooi-signoz-backup-operation.lock" $TargetKillAfterSeconds = 15 -$TargetGuardWorstCaseSeconds = 95 +$TargetGuardWorstCaseSeconds = 143 $CheckVerifyTargetTimeoutSeconds = 90 $ApplyTargetTimeoutSeconds = 720 $CheckVerifyControllerWaitSeconds = 300 -$ApplyControllerWaitSeconds = 900 +$ApplyControllerWaitSeconds = 960 $ControllerTransportReserveSeconds = 30 $TargetProgramTimeoutSeconds = if ($Mode -eq "Apply") { $ApplyTargetTimeoutSeconds } else { $CheckVerifyTargetTimeoutSeconds } $ControllerWaitSeconds = if ($Mode -eq "Apply") { $ApplyControllerWaitSeconds } else { $CheckVerifyControllerWaitSeconds } @@ -182,7 +182,14 @@ function New-Agent99SignozLockedRemoteCommand { "[ `"`$(/usr/bin/timeout --kill-after=5s 15s /usr/bin/curl -sS -o /dev/null -w `"%{http_code}`" $BaseUrl/api/v1/health)`" = `"200`" ]; " + "set +e; active_output=`$(/usr/bin/timeout --kill-after=5s 10s /usr/bin/pgrep -af `"([s]ignoz-metadata-export[.]py|[s]ignoz-metadata-restore-drill[.]py|(^|/|[[:space:]])([b]ackup-signoz|[c]lickhouse-native-backup|[c]lickhouse-native-restore-drill|[r]un-signoz-backup-canary)[.]sh([[:space:]]|$))`"); active_rc=`$?; set -e; " + "[ `"`$active_rc`" -eq 0 ] || [ `"`$active_rc`" -eq 1 ]; " + - "set +e; external_output=`$(/usr/bin/printf `"%s\n`" `"`$active_output`" | /usr/bin/grep -Ev `"^(`$`$|`$PPID)[[:space:]]`"); filter_rc=`$?; set -e; " + + "ancestor_pattern=`$`$; ancestor_cursor=`$PPID; ancestor_complete=0; " + + "for ancestor_step in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do " + + "case `"`$ancestor_cursor`" in `"`"|*[!0-9]*) exit 70;; esac; " + + "ancestor_pattern=`"`$ancestor_pattern|`$ancestor_cursor`"; " + + "if [ `"`$ancestor_cursor`" -eq 1 ]; then ancestor_complete=1; break; fi; " + + "ancestor_cursor=`$(/usr/bin/timeout --signal=TERM --kill-after=1s 2s /bin/ps -o ppid= -p `"`$ancestor_cursor`" | /usr/bin/tr -d `"[:space:]`"); " + + "done; [ `"`$ancestor_complete`" -eq 1 ]; " + + "set +e; external_output=`$(/usr/bin/printf `"%s\n`" `"`$active_output`" | /usr/bin/grep -Ev `"^(`$ancestor_pattern)[[:space:]]`"); filter_rc=`$?; set -e; " + "[ `"`$filter_rc`" -eq 0 ] || [ `"`$filter_rc`" -eq 1 ]; " + "[ -z `"`$external_output`" ]; " + "exec /usr/bin/timeout --signal=TERM --kill-after=$($TargetKillAfterSeconds)s " + diff --git a/scripts/ops/tests/test_agent99_signoz_metadata_executor_runtime_entrypoint.py b/scripts/ops/tests/test_agent99_signoz_metadata_executor_runtime_entrypoint.py index dbde961d2..cf0a5a083 100644 --- a/scripts/ops/tests/test_agent99_signoz_metadata_executor_runtime_entrypoint.py +++ b/scripts/ops/tests/test_agent99_signoz_metadata_executor_runtime_entrypoint.py @@ -1,6 +1,7 @@ from __future__ import annotations import re +import shutil import subprocess from pathlib import Path @@ -72,11 +73,11 @@ def test_entrypoint_uses_fixed_public_key_transport_and_no_arbitrary_shell() -> assert '$OperationLock = "/tmp/awoooi-signoz-backup-operation.lock"' in source assert '[ -f `"$OperationLock`" ] && [ ! -L `"$OperationLock`" ]' in source assert 'exec 8<`"$OperationLock`"; /usr/bin/flock -n 8' in source - assert "$TargetGuardWorstCaseSeconds = 95" in source + assert "$TargetGuardWorstCaseSeconds = 143" in source assert "$CheckVerifyTargetTimeoutSeconds = 90" in source assert "$ApplyTargetTimeoutSeconds = 720" in source assert "$CheckVerifyControllerWaitSeconds = 300" in source - assert "$ApplyControllerWaitSeconds = 900" in source + assert "$ApplyControllerWaitSeconds = 960" in source assert "$ControllerTransportReserveSeconds = 30" in source assert ( "New-Agent99SignozLockedRemoteCommand $applyProgram " @@ -104,8 +105,20 @@ def test_entrypoint_uses_fixed_public_key_transport_and_no_arbitrary_shell() -> '[ `"`$active_rc`" -eq 0 ] || [ `"`$active_rc`" -eq 1 ]' in lock_helper ) + assert "ancestor_pattern=`$`$; ancestor_cursor=`$PPID" in lock_helper assert ( - '/usr/bin/grep -Ev `"^(`$`$|`$PPID)[[:space:]]`"' + "for ancestor_step in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16" + in lock_helper + ) + assert "/bin/ps -o ppid= -p" in lock_helper + assert ( + "/usr/bin/timeout --signal=TERM --kill-after=1s 2s /bin/ps" + in lock_helper + ) + assert "ancestor_complete=1; break" in lock_helper + assert '[ `"`$ancestor_complete`" -eq 1 ]' in lock_helper + assert ( + '/usr/bin/grep -Ev `"^(`$ancestor_pattern)[[:space:]]`"' in lock_helper ) assert "filter_rc=`$?; set -e" in lock_helper @@ -168,16 +181,24 @@ def test_locked_process_guard_rendered_shell_behavior( row_scenario: str, expected: bool, ) -> None: + timeout_path = shutil.which("timeout") + assert timeout_path is not None + rendered_gate = _render_locked_process_gate().replace( + "/usr/bin/timeout", timeout_path + ) setup = { "empty": 'active_output=""; ', "ancestors": ( + 'grandparent=$(/bin/ps -o ppid= -p "$PPID" | ' + '/usr/bin/tr -d "[:space:]"); ' 'active_output="$$ bash -c fixed-export\n' - '$PPID sudo bash -c fixed-export"; ' + '$PPID sudo bash -c fixed-export\n' + '$grandparent outer-shell fixed-export"; ' ), "external": ( 'active_output="$$ bash -c fixed-export\n' '$PPID sudo bash -c fixed-export\n' - '812 python3 signoz-metadata-export.py --apply"; ' + '99999999 python3 signoz-metadata-export.py --apply"; ' ), }[row_scenario] completed = subprocess.run( @@ -185,11 +206,12 @@ def test_locked_process_guard_rendered_shell_behavior( "/bin/bash", "-c", f"set -euo pipefail; active_rc={pgrep_rc}; {setup}" - f"{_render_locked_process_gate()}printf guard_accepted", + f"{rendered_gate}printf guard_accepted", ), check=False, capture_output=True, text=True, + timeout=10, ) assert (completed.returncode == 0) is expected assert (completed.stdout == "guard_accepted") is expected @@ -208,6 +230,41 @@ def test_locked_process_guard_rendered_filter_error_fails_closed() -> None: check=False, capture_output=True, text=True, + timeout=10, + ) + assert completed.returncode != 0 + assert completed.stdout == "" + + +@pytest.mark.parametrize( + "replacement", + ( + "/usr/bin/false", + "/usr/bin/printf not-a-pid", + "/usr/bin/printf 99999999", + ), +) +def test_locked_process_guard_ancestor_walk_faults_fail_closed( + replacement: str, +) -> None: + gate = _render_locked_process_gate() + lookup = ( + "/usr/bin/timeout --signal=TERM --kill-after=1s 2s " + '/bin/ps -o ppid= -p "$ancestor_cursor"' + ) + assert gate.count(lookup) == 1 + fault_injected_gate = gate.replace(lookup, replacement) + completed = subprocess.run( + ( + "/bin/bash", + "-c", + "set -euo pipefail; active_rc=1; active_output=\"\"; " + f"{fault_injected_gate}printf guard_accepted", + ), + check=False, + capture_output=True, + text=True, + timeout=10, ) assert completed.returncode != 0 assert completed.stdout == "" @@ -303,11 +360,11 @@ def test_entrypoint_typed_readbacks_are_exact_and_preserve_unknown_state() -> No assert "$RemoteResult.completed -ne $true" in source assert "$RemoteResult.timedOut" in source - guard = 95 + guard = 143 kill_after = 15 transport_reserve = 30 assert guard + 90 + kill_after + transport_reserve < 300 - assert guard + 720 + kill_after + transport_reserve < 900 + assert guard + 720 + kill_after + transport_reserve < 960 def test_entrypoint_never_reads_or_persists_credential_value_in_controller() -> None: