fix(observability): provision SigNoz metadata credential safely
All checks were successful
CD Pipeline / select-latest-carrier (push) Successful in 52s
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m36s
CD Pipeline / revalidate-deploy-carrier (push) Successful in 36s
CD Pipeline / build-and-deploy (push) Successful in 17m5s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 2s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 31s
CD Pipeline / revalidate-post-deploy-carrier (push) Successful in 46s
CD Pipeline / post-deploy-checks (push) Successful in 4m54s
All checks were successful
CD Pipeline / select-latest-carrier (push) Successful in 52s
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m36s
CD Pipeline / revalidate-deploy-carrier (push) Successful in 36s
CD Pipeline / build-and-deploy (push) Successful in 17m5s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 2s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 31s
CD Pipeline / revalidate-post-deploy-carrier (push) Successful in 46s
CD Pipeline / post-deploy-checks (push) Successful in 4m54s
This commit is contained in:
274
scripts/ops/tests/test_agent99_signoz_credential_provisioner.py
Normal file
274
scripts/ops/tests/test_agent99_signoz_credential_provisioner.py
Normal file
@@ -0,0 +1,274 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
TARGET = ROOT / "agent99-signoz-credential-target.py"
|
||||
PROVISIONER = ROOT / "agent99-signoz-credential-provisioner.ps1"
|
||||
|
||||
|
||||
def load_target():
|
||||
spec = importlib.util.spec_from_file_location("signoz_credential_target", TARGET)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
class SignozState:
|
||||
setup_completed = False
|
||||
fail_dashboard = False
|
||||
fail_session_cleanup = False
|
||||
lose_register_response_once = False
|
||||
deny_pat_admin_readback = False
|
||||
requests: list[tuple[str, str, dict[str, Any] | None]]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.setup_completed = False
|
||||
self.fail_dashboard = False
|
||||
self.fail_session_cleanup = False
|
||||
self.lose_register_response_once = False
|
||||
self.deny_pat_admin_readback = False
|
||||
self.requests = []
|
||||
|
||||
|
||||
def start_server(state: SignozState) -> tuple[ThreadingHTTPServer, str]:
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, *_args: object) -> None:
|
||||
return
|
||||
|
||||
def _send(self, status: int, value: dict[str, Any]) -> None:
|
||||
raw = json.dumps(value).encode()
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(raw)))
|
||||
self.end_headers()
|
||||
self.wfile.write(raw)
|
||||
|
||||
def _no_content(self) -> None:
|
||||
self.send_response(204)
|
||||
self.end_headers()
|
||||
|
||||
def _body(self) -> dict[str, Any] | None:
|
||||
length = int(self.headers.get("Content-Length", "0"))
|
||||
return json.loads(self.rfile.read(length)) if length else None
|
||||
|
||||
def do_GET(self) -> None: # noqa: N802
|
||||
state.requests.append(("GET", self.path, None))
|
||||
if self.path == "/api/v1/version":
|
||||
self._send(
|
||||
200,
|
||||
{"version": "v0.113.0", "ee": "Y", "setupCompleted": state.setup_completed},
|
||||
)
|
||||
elif self.path == "/api/v1/user/me":
|
||||
self._send(200, {"status": "success", "data": {"role": "ADMIN", "orgId": "org-1"}})
|
||||
elif self.path.startswith("/api/v2/sessions/context?"):
|
||||
self._send(
|
||||
200,
|
||||
{
|
||||
"status": "success",
|
||||
"data": {"exists": True, "orgs": [{"id": "org-1"}]},
|
||||
},
|
||||
)
|
||||
elif self.path == "/api/v1/dashboards" and state.fail_dashboard:
|
||||
self._send(401, {"status": "error"})
|
||||
elif self.path == "/api/v1/dashboards":
|
||||
assert self.headers.get("SIGNOZ-API-KEY") == "opaque-pat-value"
|
||||
self._send(200, {"status": "success", "data": []})
|
||||
elif self.path == "/api/v1/roles":
|
||||
self._send(200, {"status": "success", "data": [{"name": "ADMIN"}]})
|
||||
elif self.path == "/api/v1/pats" and state.deny_pat_admin_readback:
|
||||
self._send(403, {"status": "error"})
|
||||
elif self.path == "/api/v1/pats":
|
||||
self._send(200, {"status": "success", "data": [{"id": "pat-1"}]})
|
||||
else:
|
||||
self._send(404, {"status": "error"})
|
||||
|
||||
def do_POST(self) -> None: # noqa: N802
|
||||
body = self._body()
|
||||
state.requests.append(("POST", self.path, body))
|
||||
if self.path == "/api/v1/register":
|
||||
state.setup_completed = True
|
||||
if state.lose_register_response_once:
|
||||
state.lose_register_response_once = False
|
||||
self.connection.shutdown(2)
|
||||
self.connection.close()
|
||||
return
|
||||
self._send(200, {"status": "success", "data": {"orgId": "org-1"}})
|
||||
elif self.path == "/api/v2/sessions/email_password":
|
||||
self._send(
|
||||
200,
|
||||
{"status": "success", "data": {"accessToken": "session-jwt", "refreshToken": "refresh"}},
|
||||
)
|
||||
elif self.path == "/api/v1/pats":
|
||||
self._send(
|
||||
201,
|
||||
{"status": "success", "data": {"id": "pat-1", "token": "opaque-pat-value"}},
|
||||
)
|
||||
else:
|
||||
self._send(404, {"status": "error"})
|
||||
|
||||
def do_DELETE(self) -> None: # noqa: N802
|
||||
state.requests.append(("DELETE", self.path, None))
|
||||
if self.path == "/api/v2/sessions" and state.fail_session_cleanup:
|
||||
self._send(500, {"status": "error"})
|
||||
else:
|
||||
self._no_content()
|
||||
|
||||
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
host, port = server.server_address
|
||||
return server, f"http://{host}:{port}"
|
||||
|
||||
|
||||
def configure(module: Any, tmp_path: Path, base_url: str) -> Path:
|
||||
credential = tmp_path / "secrets" / "signoz-metadata-api-key"
|
||||
module.BASE_URL = base_url
|
||||
module.CREDENTIAL_PATH = credential
|
||||
module.RECOVERY_PASSWORD_PATH = tmp_path / "secrets" / "bootstrap.recovery"
|
||||
module.EXPECTED_UID = os.geteuid()
|
||||
module.os.chown = lambda *_args, **_kwargs: None
|
||||
return credential
|
||||
|
||||
|
||||
def test_agent99_entrypoint_is_fixed_and_never_transports_secret() -> None:
|
||||
source = PROVISIONER.read_text(encoding="utf-8")
|
||||
target = TARGET.read_text(encoding="utf-8")
|
||||
|
||||
assert '[ValidateSet("Check", "Apply", "Verify")]' in source
|
||||
assert '$TargetHost = "192.168.0.110"' in source
|
||||
assert '$ExpectedWorkItemId = "P0-OBS-002"' in source
|
||||
assert '$ExpectedRuntimeFileCount = 20' in source
|
||||
assert '"sudo -n /usr/bin/python3 - --mode $($Mode.ToLowerInvariant())"' in source
|
||||
assert "-RedirectStandardInput $TargetProgram" in source
|
||||
assert "secret_in_transport = $false" in source
|
||||
assert "Invoke-Expression" not in source
|
||||
assert "-EncodedCommand" not in source
|
||||
assert '/etc/awoooi/secrets/signoz-metadata-api-key' in target
|
||||
assert "os.O_EXCL" in target and "os.O_NOFOLLOW" in target
|
||||
assert '"role": "ADMIN"' in target
|
||||
assert '"expiresInDays": 30' in target
|
||||
assert "SIGNOZ-API-KEY" in target
|
||||
assert "status != 204" in target
|
||||
assert "token_urlsafe" in target
|
||||
assert "setup_already_completed_without_bootstrap_recovery" in target
|
||||
assert "bootstrap_recovery_removed" in target
|
||||
assert "print(password" not in target
|
||||
assert "print(token" not in target
|
||||
|
||||
|
||||
def test_apply_bootstraps_admin_persists_only_pat_and_verifies(tmp_path: Path) -> None:
|
||||
module = load_target()
|
||||
state = SignozState()
|
||||
server, base_url = start_server(state)
|
||||
credential = configure(module, tmp_path, base_url)
|
||||
try:
|
||||
result = module.apply()
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
|
||||
assert result["terminal"] == "applied_verified"
|
||||
assert result["bootstrap_is_permanent"] is True
|
||||
assert credential.read_text() == "opaque-pat-value"
|
||||
assert stat.S_IMODE(credential.stat().st_mode) == 0o400
|
||||
assert not module.RECOVERY_PASSWORD_PATH.exists()
|
||||
serialized = json.dumps(result)
|
||||
assert "opaque-pat-value" not in serialized
|
||||
assert "session-jwt" not in serialized
|
||||
assert any(method == "DELETE" and path == "/api/v2/sessions" for method, path, _ in state.requests)
|
||||
|
||||
|
||||
def test_apply_failure_revokes_pat_and_removes_credential(tmp_path: Path) -> None:
|
||||
module = load_target()
|
||||
state = SignozState()
|
||||
state.fail_dashboard = True
|
||||
server, base_url = start_server(state)
|
||||
credential = configure(module, tmp_path, base_url)
|
||||
try:
|
||||
with pytest.raises(module.ProvisionError, match="http_get_dashboards_401"):
|
||||
module.apply()
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
|
||||
assert not credential.exists()
|
||||
assert module.RECOVERY_PASSWORD_PATH.exists()
|
||||
assert ("DELETE", "/api/v1/pats/pat-1", None) in state.requests
|
||||
assert ("DELETE", "/api/v2/sessions", None) in state.requests
|
||||
|
||||
|
||||
def test_lost_registration_response_recovers_with_root_only_secret(tmp_path: Path) -> None:
|
||||
module = load_target()
|
||||
state = SignozState()
|
||||
state.lose_register_response_once = True
|
||||
server, base_url = start_server(state)
|
||||
credential = configure(module, tmp_path, base_url)
|
||||
try:
|
||||
result = module.apply()
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
|
||||
assert result["terminal"] == "applied_verified"
|
||||
assert credential.exists()
|
||||
assert not module.RECOVERY_PASSWORD_PATH.exists()
|
||||
assert any(path.startswith("/api/v2/sessions/context?") for _, path, _ in state.requests)
|
||||
|
||||
|
||||
def test_session_cleanup_failure_revokes_pat_and_preserves_recovery(tmp_path: Path) -> None:
|
||||
module = load_target()
|
||||
state = SignozState()
|
||||
state.fail_session_cleanup = True
|
||||
server, base_url = start_server(state)
|
||||
credential = configure(module, tmp_path, base_url)
|
||||
try:
|
||||
with pytest.raises(module.ProvisionError, match="session_cleanup_unverified"):
|
||||
module.apply()
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
|
||||
assert not credential.exists()
|
||||
assert module.RECOVERY_PASSWORD_PATH.exists()
|
||||
assert ("DELETE", "/api/v1/pats/pat-1", None) in state.requests
|
||||
|
||||
|
||||
def test_admin_pat_readback_failure_revokes_before_persist(tmp_path: Path) -> None:
|
||||
module = load_target()
|
||||
state = SignozState()
|
||||
state.deny_pat_admin_readback = True
|
||||
server, base_url = start_server(state)
|
||||
credential = configure(module, tmp_path, base_url)
|
||||
try:
|
||||
with pytest.raises(module.ProvisionError, match="http_get_pats_403"):
|
||||
module.apply()
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
|
||||
assert not credential.exists()
|
||||
assert module.RECOVERY_PASSWORD_PATH.exists()
|
||||
assert ("DELETE", "/api/v1/pats/pat-1", None) in state.requests
|
||||
|
||||
|
||||
def test_verify_rejects_unsafe_credential_metadata(tmp_path: Path) -> None:
|
||||
module = load_target()
|
||||
credential = tmp_path / "credential"
|
||||
credential.write_text("opaque-pat-value")
|
||||
credential.chmod(0o644)
|
||||
module.CREDENTIAL_PATH = credential
|
||||
module.EXPECTED_UID = os.geteuid()
|
||||
with pytest.raises(module.ProvisionError, match="credential_mode_invalid"):
|
||||
module.credential_state(read_value=True)
|
||||
@@ -26,7 +26,7 @@ def test_entrypoint_is_fixed_to_windows99_host110_and_p0_obs_002() -> None:
|
||||
assert "._+" not in source
|
||||
assert '$EvidenceRunId = if ($RunId -match $SafeIdPattern)' in source
|
||||
assert "Get-Agent99RuntimeSourceBinding" in source
|
||||
assert "$result.fileCount -eq 18" in source
|
||||
assert "$result.fileCount -eq 20" in source
|
||||
assert "$result.sourceRevision -eq $SourceRevision" in source
|
||||
assert "$result.entrypointHashMatched" in source
|
||||
assert '$CredentialFile = "/etc/awoooi/secrets/signoz-metadata-api-key"' in source
|
||||
@@ -293,8 +293,8 @@ def test_entrypoint_is_in_exact_agent99_runtime_bundle() -> None:
|
||||
assert expected in _quoted_array(contract, "$requiredFiles = @(", ")")
|
||||
assert expected in _quoted_array(sender, "RUNTIME_FILES=(", ")")
|
||||
assert expected in _quoted_array(receiver, "$FixedRuntimeFiles = @(", ")")
|
||||
assert 'expectedRuntimeFileCount": 18' in sender
|
||||
assert '$runtimeManifest.fileCount -eq 18' in receiver
|
||||
assert 'expectedRuntimeFileCount": 20' in sender
|
||||
assert '$runtimeManifest.fileCount -eq 20' in receiver
|
||||
|
||||
|
||||
def test_runtime_contract_check_requires_typed_signoz_transport_readback() -> None:
|
||||
|
||||
@@ -122,7 +122,7 @@ def test_agent99_atomic_bundle_owns_the_dispatch_entrypoint() -> None:
|
||||
assert "$dbExecutorRecoveryContract" in contract
|
||||
assert "awoooi_db_bounded_executor_receipt_v2" in contract
|
||||
assert "Receipt.database_sqlstate" in contract
|
||||
assert 'expectedRuntimeFileCount": 18' in transport
|
||||
assert "expectedRuntimeFileCount=18" in transport
|
||||
assert "expectedRuntimeFileCount -ne 18" in receiver
|
||||
assert "fileCount = 18" in receiver
|
||||
assert 'expectedRuntimeFileCount": 20' in transport
|
||||
assert "expectedRuntimeFileCount=20" in transport
|
||||
assert "expectedRuntimeFileCount -ne 20" in receiver
|
||||
assert "fileCount = 20" in receiver
|
||||
|
||||
@@ -984,7 +984,7 @@ $decision = Get-AgentLivePreflightDecision `
|
||||
-AgentRootPresent ([bool](Test-Path $AgentRoot)) `
|
||||
-Manifest $manifest `
|
||||
-ManifestCheckRequired ([bool]($ReadinessScope -eq "FullRuntime")) `
|
||||
-ExpectedRuntimeFileCount 18 `
|
||||
-ExpectedRuntimeFileCount 20 `
|
||||
-TaskFailureCount $taskFailures.Count `
|
||||
-RelayListenerCount $relayListeners.Count `
|
||||
-RequiredEvidenceStaleCount $requiredEvidenceStale.Count `
|
||||
|
||||
@@ -36,6 +36,8 @@ $FixedRuntimeFiles = @(
|
||||
"agent99-control-plane.ps1",
|
||||
"agent99-db-bounded-executor.ps1",
|
||||
"agent99-signoz-metadata-executor.ps1",
|
||||
"agent99-signoz-credential-provisioner.ps1",
|
||||
"agent99-signoz-credential-target.py",
|
||||
"agent99-deploy.ps1",
|
||||
"agent99-register-tasks.ps1",
|
||||
"agent99-alertmanager-alertchain-poll.ps1",
|
||||
@@ -1064,7 +1066,7 @@ try {
|
||||
}
|
||||
$sourceRevision = ([string]$envelope.sourceRevision).ToLowerInvariant()
|
||||
if ($sourceRevision -notmatch "^[0-9a-f]{40}$") { throw "invalid_source_revision" }
|
||||
if ([int]$envelope.expectedRuntimeFileCount -ne 18) { throw "invalid_expected_runtime_file_count" }
|
||||
if ([int]$envelope.expectedRuntimeFileCount -ne 20) { throw "invalid_expected_runtime_file_count" }
|
||||
|
||||
$traceId = [string]$envelope.traceId
|
||||
$runId = [string]$envelope.runId
|
||||
@@ -1099,7 +1101,7 @@ try {
|
||||
$sourceManifest = [pscustomobject]@{
|
||||
schemaVersion = "agent99_remote_source_manifest_v1"
|
||||
sourceRevision = $sourceRevision
|
||||
fileCount = 18
|
||||
fileCount = 20
|
||||
manifestSha256 = $manifestDigest
|
||||
files = @($FixedRuntimeFiles | ForEach-Object {
|
||||
[pscustomobject]@{ name = $_; sha256 = ([string]($filesByName[$_].sha256)).ToLowerInvariant() }
|
||||
@@ -1136,7 +1138,7 @@ try {
|
||||
status = "check_ready"
|
||||
mode = "check"
|
||||
sourceRevision = $sourceRevision
|
||||
expectedRuntimeFileCount = 18
|
||||
expectedRuntimeFileCount = 20
|
||||
manifestSha256 = $manifestDigest
|
||||
plannedStagingPath = $stagePath
|
||||
runtimeManifest = $currentManifest
|
||||
@@ -1223,7 +1225,7 @@ try {
|
||||
if (
|
||||
[string]$existingSourceManifest.schemaVersion -ne "agent99_remote_source_manifest_v1" -or
|
||||
[string]$existingSourceManifest.sourceRevision -ne $sourceRevision -or
|
||||
[int]$existingSourceManifest.fileCount -ne 18 -or
|
||||
[int]$existingSourceManifest.fileCount -ne 20 -or
|
||||
[string]$existingSourceManifest.manifestSha256 -ne $manifestDigest
|
||||
) { throw "existing_staging_manifest_mismatch" }
|
||||
} catch {
|
||||
@@ -1247,7 +1249,7 @@ try {
|
||||
[string]$prior.launcherContract.sha256 -match "^[0-9a-f]{64}$" -and
|
||||
$live.runtimeMatched -and
|
||||
$live.sourceRevision -eq $sourceRevision -and
|
||||
$live.fileCount -eq 18 -and
|
||||
$live.fileCount -eq 20 -and
|
||||
$live.mismatchCount -eq 0 -and
|
||||
$liveLauncherContract.ok -and
|
||||
[string]$liveLauncherContract.sha256 -eq [string]$prior.launcherContract.sha256
|
||||
@@ -1565,7 +1567,7 @@ try {
|
||||
$runtimeManifest.exists -and
|
||||
$runtimeManifest.runtimeMatched -and
|
||||
$runtimeManifest.sourceRevision -eq $sourceRevision -and
|
||||
$runtimeManifest.fileCount -eq 18 -and
|
||||
$runtimeManifest.fileCount -eq 20 -and
|
||||
$runtimeManifest.mismatchCount -eq 0 -and
|
||||
$launcherContract.ok -and
|
||||
[string]$launcherContract.sha256 -eq [string]$deploy.payload.launcherContract.sha256 -and
|
||||
|
||||
@@ -18,6 +18,8 @@ RUNTIME_FILES=(
|
||||
"agent99-control-plane.ps1"
|
||||
"agent99-db-bounded-executor.ps1"
|
||||
"agent99-signoz-metadata-executor.ps1"
|
||||
"agent99-signoz-credential-provisioner.ps1"
|
||||
"agent99-signoz-credential-target.py"
|
||||
"agent99-deploy.ps1"
|
||||
"agent99-register-tasks.ps1"
|
||||
"agent99-alertmanager-alertchain-poll.ps1"
|
||||
@@ -227,7 +229,7 @@ trace_id, run_id, work_item_id = sys.argv[4:7]
|
||||
output_path = Path(sys.argv[7])
|
||||
runtime_names = sys.argv[8:]
|
||||
|
||||
if len(runtime_names) != 18 or len(set(runtime_names)) != 18:
|
||||
if len(runtime_names) != 20 or len(set(runtime_names)) != 20:
|
||||
raise SystemExit("fixed_runtime_file_contract_failed")
|
||||
|
||||
files: list[dict[str, str]] = []
|
||||
@@ -267,7 +269,7 @@ envelope = {
|
||||
"runId": run_id,
|
||||
"workItemId": work_item_id,
|
||||
"sourceRevision": source_revision,
|
||||
"expectedRuntimeFileCount": 18,
|
||||
"expectedRuntimeFileCount": 20,
|
||||
"manifestSha256": manifest_sha256,
|
||||
"files": files,
|
||||
"livePreflight": {
|
||||
@@ -419,14 +421,14 @@ stage_token = hashlib.sha256(stage_identity.encode("utf-8")).hexdigest()[:20]
|
||||
script = r'''$ErrorActionPreference='Stop';$ProgressPreference='SilentlyContinue'
|
||||
$a='C:\Wooo\Agent99';$b=Join-Path $a 'bin';$p=Join-Path $a 'state\runtime-manifest.json'
|
||||
$r=[ordered]@{exists=$false;sourceRevision='';runtimeMatched=$false;recordedRuntimeMatched=$false;fileCount=0;mismatchCount=-1;recordedMismatchCount=-1;parseError=''}
|
||||
if(Test-Path -LiteralPath $p -PathType Leaf){try{$m=Get-Content -LiteralPath $p -Raw|ConvertFrom-Json;$rows=@($m.files);$seen=@{};$bad=0;foreach($x in $rows){$n=[string]$x.name;if(!$n-or[IO.Path]::GetFileName($n)-ne$n-or$seen.ContainsKey($n)){$bad++;continue};$seen[$n]=1;$q=Join-Path $b $n;$h=if(Test-Path -LiteralPath $q -PathType Leaf){(Get-FileHash -LiteralPath $q -Algorithm SHA256).Hash.ToLowerInvariant()}else{'missing'};if(([string]$x.runtimeSha256).ToLowerInvariant()-ne$h){$bad++}};$r.exists=$true;$r.sourceRevision=[string]$m.sourceRevision;$r.recordedRuntimeMatched=[bool]$m.runtimeMatched;$r.fileCount=[int]$m.fileCount;$r.mismatchCount=$bad;$r.recordedMismatchCount=[int]$m.mismatchCount;$r.runtimeMatched=[bool]($m.runtimeMatched-and$m.fileCount-eq 18-and$m.mismatchCount-eq 0-and$rows.Count-eq 18-and$seen.Count-eq 18-and$bad-eq 0)}catch{$r.exists=$true;$r.parseError=$_.Exception.GetType().Name}}
|
||||
if(Test-Path -LiteralPath $p -PathType Leaf){try{$m=Get-Content -LiteralPath $p -Raw|ConvertFrom-Json;$rows=@($m.files);$seen=@{};$bad=0;foreach($x in $rows){$n=[string]$x.name;if(!$n-or[IO.Path]::GetFileName($n)-ne$n-or$seen.ContainsKey($n)){$bad++;continue};$seen[$n]=1;$q=Join-Path $b $n;$h=if(Test-Path -LiteralPath $q -PathType Leaf){(Get-FileHash -LiteralPath $q -Algorithm SHA256).Hash.ToLowerInvariant()}else{'missing'};if(([string]$x.runtimeSha256).ToLowerInvariant()-ne$h){$bad++}};$r.exists=$true;$r.sourceRevision=[string]$m.sourceRevision;$r.recordedRuntimeMatched=[bool]$m.runtimeMatched;$r.fileCount=[int]$m.fileCount;$r.mismatchCount=$bad;$r.recordedMismatchCount=[int]$m.mismatchCount;$r.runtimeMatched=[bool]($m.runtimeMatched-and$m.fileCount-eq 20-and$m.mismatchCount-eq 0-and$rows.Count-eq 20-and$seen.Count-eq 20-and$bad-eq 0)}catch{$r.exists=$true;$r.parseError=$_.Exception.GetType().Name}}
|
||||
$c=[ordered]@{executed=$false;ok=$false;exitCode=-1;errorType=''}
|
||||
try{& powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File (Join-Path $b 'agent99-contract-check.ps1') -SourceRoot $b 2>$null|Out-Null;$c.executed=$true;$c.exitCode=$LASTEXITCODE;$c.ok=[bool]($c.exitCode-eq 0)}catch{$c.executed=$true;$c.errorType=$_.Exception.GetType().Name}
|
||||
$z=[ordered]@{};foreach($n in 'remoteWritePerformed,liveRuntimeWritePerformed,livePromotionAttempted,livePromotionPerformed,secretValueRead,privateKeyValueRead,tokenValueRead,environmentSecretRead,uiInteraction,vmPowerChange,hostReboot,serviceRestart,scheduledTaskRestart,scheduledTaskModification'.Split(',')){$z[$n]=$false}
|
||||
$ready=[bool]($c.executed-and$c.ok-and$c.exitCode-eq 0)
|
||||
$status=if($ready){'check_ready'}else{'check_failed_no_apply'}
|
||||
$next=if($ready){'rerun_with_apply_and_trace_run_work_item_after_source_commit_and_transport_check'}else{'repair_runtime_contract_before_apply'}
|
||||
[ordered]@{schemaVersion='agent99_remote_atomic_deploy_receipt_v1';status=$status;mode='check';sourceRevision='__SOURCE_REVISION__';expectedRuntimeFileCount=18;manifestSha256='__MANIFEST_SHA256__';plannedStagingPath='C:\Wooo\Agent99\deploy\remote-source-__STAGE_TOKEN__';runtimeManifest=[pscustomobject]$r;runtimeContract=[pscustomobject]$c;livePreflight=[ordered]@{executed=$false;reason='apply_only_after_validate_only'};transportPayloadMode='control_command_no_stdin';transientTransportPayloadStored=$false;transientTransportPayloadDeletedBeforeReceiver=$false;transportPayloadContainsSecrets=$false;operationBoundaries=$z;nextSafeAction=$next}|ConvertTo-Json -Compress -Depth 8
|
||||
[ordered]@{schemaVersion='agent99_remote_atomic_deploy_receipt_v1';status=$status;mode='check';sourceRevision='__SOURCE_REVISION__';expectedRuntimeFileCount=20;manifestSha256='__MANIFEST_SHA256__';plannedStagingPath='C:\Wooo\Agent99\deploy\remote-source-__STAGE_TOKEN__';runtimeManifest=[pscustomobject]$r;runtimeContract=[pscustomobject]$c;livePreflight=[ordered]@{executed=$false;reason='apply_only_after_validate_only'};transportPayloadMode='control_command_no_stdin';transientTransportPayloadStored=$false;transientTransportPayloadDeletedBeforeReceiver=$false;transportPayloadContainsSecrets=$false;operationBoundaries=$z;nextSafeAction=$next}|ConvertTo-Json -Compress -Depth 8
|
||||
if(-not$ready){exit 65}'''
|
||||
script = (
|
||||
script.replace("__SOURCE_REVISION__", source_revision)
|
||||
|
||||
@@ -37,7 +37,7 @@ def _base_case() -> dict[str, Any]:
|
||||
"exists": True,
|
||||
"sourceRevision": "a" * 40,
|
||||
"runtimeMatched": True,
|
||||
"fileCount": 18,
|
||||
"fileCount": 20,
|
||||
"mismatchCount": 0,
|
||||
"parseError": "",
|
||||
},
|
||||
@@ -86,7 +86,7 @@ $parameters = @{{
|
||||
AgentRootPresent = [bool]$case.agentRootPresent
|
||||
Manifest = $case.manifest
|
||||
ManifestCheckRequired = [bool]$case.manifestCheckRequired
|
||||
ExpectedRuntimeFileCount = 18
|
||||
ExpectedRuntimeFileCount = 20
|
||||
TaskFailureCount = [int]$case.taskFailureCount
|
||||
RelayListenerCount = [int]$case.relayListenerCount
|
||||
RequiredEvidenceStaleCount = [int]$case.requiredEvidenceStaleCount
|
||||
@@ -140,7 +140,7 @@ def test_warning_receipt_is_visible_without_weakening_integrity_blockers() -> No
|
||||
)
|
||||
assert "deploymentEligible = [bool]($blockingReasons.Count -eq 0)" in decision
|
||||
assert "preflightGreen = [bool]$decision.deploymentEligible" in source
|
||||
assert "-ExpectedRuntimeFileCount 18" in source
|
||||
assert "-ExpectedRuntimeFileCount 20" in source
|
||||
assert "warningReasons = $warningReasons" in source
|
||||
assert "selfHealthPerformanceIssues = @(" in source
|
||||
assert 'if ($safeHost -notmatch "^[A-Za-z0-9.:-]{1,128}$")' in source
|
||||
|
||||
@@ -26,6 +26,8 @@ EXPECTED_RUNTIME_FILES = (
|
||||
"agent99-control-plane.ps1",
|
||||
"agent99-db-bounded-executor.ps1",
|
||||
"agent99-signoz-metadata-executor.ps1",
|
||||
"agent99-signoz-credential-provisioner.ps1",
|
||||
"agent99-signoz-credential-target.py",
|
||||
"agent99-deploy.ps1",
|
||||
"agent99-register-tasks.ps1",
|
||||
"agent99-alertmanager-alertchain-poll.ps1",
|
||||
@@ -88,7 +90,7 @@ def test_sender_and_receiver_allow_exactly_the_deployer_runtime_bundle() -> None
|
||||
)
|
||||
assert preflight_count is not None
|
||||
assert int(preflight_count.group(1)) == len(EXPECTED_RUNTIME_FILES)
|
||||
assert "expectedRuntimeFileCount\": 18" in sender
|
||||
assert "expectedRuntimeFileCount\": 20" in sender
|
||||
assert "$filesByName.Count -ne $FixedRuntimeFiles.Count" in receiver
|
||||
assert "required_runtime_file_missing" in receiver
|
||||
assert "duplicate_runtime_file" in receiver
|
||||
@@ -263,7 +265,7 @@ def test_receiver_rolls_back_failed_deploy_or_failed_independent_post_verifier()
|
||||
)
|
||||
assert "$livePreflight.sourceRevision -eq $sourceRevision" in source
|
||||
assert "$runtimeManifest.sourceRevision -eq $sourceRevision" in source
|
||||
assert '$runtimeManifest.fileCount -eq 18' in source
|
||||
assert '$runtimeManifest.fileCount -eq 20' in source
|
||||
assert "function Invoke-AgentWindowsControlBaseline" in source
|
||||
assert "function Get-AgentRunEvidenceToken" in source
|
||||
assert "$evidenceRunToken = Get-AgentRunEvidenceToken -Value $RunId" in source
|
||||
|
||||
Reference in New Issue
Block a user