test: isolate ordinary unit runs from ambient credentials and provider I/O
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
This commit is contained in:
parent
922c26da0f
commit
b593c87983
14 changed files with 592 additions and 6 deletions
20
tests/fixtures/workload-coverage/provenance.json
vendored
Normal file
20
tests/fixtures/workload-coverage/provenance.json
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
[
|
||||
{
|
||||
"repo": "rapp-core-hub",
|
||||
"source": "retained public snapshot from warden-1.3-kernel-fixes-r1 test preparation",
|
||||
"source_sha256": "98e81fb1116aa0ac9dc1a3e7aef9dd87c6d2110d98d46fbfca2f76eec8f3a77b",
|
||||
"projection_sha256": "9ce902da0c77809f1094a634244652835b162f59682e9fa6923d8c8aa4ed4a3c"
|
||||
},
|
||||
{
|
||||
"repo": "rapp-issue-core",
|
||||
"source": "retained public snapshot from warden-1.3-kernel-fixes-r1 test preparation",
|
||||
"source_sha256": "0425c9fe84e26b359b2a35fc37ef8fcb781de1aaa6229c59fcd246ee523514a5",
|
||||
"projection_sha256": "a1956deecf2f0fbca44549e5ea5e8f1c4b74ebf91a9482c64c982491af0d923b"
|
||||
},
|
||||
{
|
||||
"repo": "rapp-qonto",
|
||||
"source": "retained public snapshot from warden-1.3-kernel-fixes-r1 test preparation",
|
||||
"source_sha256": "6279dc2f3ab09519dffa1ceeedb2095f1182960e0106109a87aa5c0052446883",
|
||||
"projection_sha256": "15aa9809e8d6a120c516be5ce9adfb72c00382140c171e791083ad117f503b3f"
|
||||
}
|
||||
]
|
||||
15
tests/fixtures/workload-coverage/rapp-core-hub/declarations/rapp.yaml
vendored
Normal file
15
tests/fixtures/workload-coverage/rapp-core-hub/declarations/rapp.yaml
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Public declaration projection for the published coverage snapshot; not live evidence.
|
||||
rapp_id: rapp-core-hub
|
||||
workload_identity:
|
||||
name: core-hub
|
||||
composition:
|
||||
member_repos:
|
||||
- deployables:
|
||||
- core-hub-api
|
||||
- core-hub-migrate
|
||||
- hub-core-candidate
|
||||
- hub-core-candidate-migrate
|
||||
- repository-classification-publisher
|
||||
data_classification: confidential
|
||||
criticality: high
|
||||
readiness_state: verified
|
||||
11
tests/fixtures/workload-coverage/rapp-issue-core/declarations/rapp.yaml
vendored
Normal file
11
tests/fixtures/workload-coverage/rapp-issue-core/declarations/rapp.yaml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Public declaration projection for the published coverage snapshot; not live evidence.
|
||||
rapp_id: rapp-issue-core
|
||||
workload_identity:
|
||||
name: issue-core
|
||||
composition:
|
||||
member_repos:
|
||||
- deployables:
|
||||
- issue-core
|
||||
data_classification: confidential
|
||||
criticality: high
|
||||
readiness_state: verified
|
||||
11
tests/fixtures/workload-coverage/rapp-qonto/declarations/rapp.yaml
vendored
Normal file
11
tests/fixtures/workload-coverage/rapp-qonto/declarations/rapp.yaml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Public declaration projection for the published coverage snapshot; not live evidence.
|
||||
rapp_id: rapp-qonto
|
||||
workload_identity:
|
||||
name: qonto
|
||||
composition:
|
||||
member_repos:
|
||||
- deployables:
|
||||
- rapp-qonto
|
||||
data_classification: restricted
|
||||
criticality: critical
|
||||
readiness_state: verified
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
|
@ -12,7 +13,8 @@ from warden.case_plan import CaseError, plan_case
|
|||
from warden.cli import app
|
||||
from warden.routing.catalog import load_catalog
|
||||
|
||||
LIVE_CASE = Path.home() / "net-kingdom/security-cases/forgejo-netkingdom-oidc.yaml"
|
||||
LIVE_CASE = Path(os.environ.get("WARDEN_TEST_NETKINGDOM_ROOT",
|
||||
Path(__file__).resolve().parents[2] / "net-kingdom")) / "security-cases/forgejo-netkingdom-oidc.yaml"
|
||||
|
||||
TYPE = {
|
||||
"apiVersion": "netkingdom.io/security-case-type/v0.1",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
|
@ -12,7 +13,8 @@ from warden.case_type import check_follow_through, load_type
|
|||
from warden.cli import app
|
||||
from warden.routing.catalog import load_catalog
|
||||
|
||||
LIVE_TYPE = Path.home() / "net-kingdom/security-case-types/oidc-browser-relying-party/type.yaml"
|
||||
LIVE_TYPE = Path(os.environ.get("WARDEN_TEST_NETKINGDOM_ROOT",
|
||||
Path(__file__).resolve().parents[2] / "net-kingdom")) / "security-case-types/oidc-browser-relying-party/type.yaml"
|
||||
|
||||
BASE = {
|
||||
"apiVersion": "netkingdom.io/security-case-type/v0.1",
|
||||
|
|
|
|||
|
|
@ -273,7 +273,11 @@ class TestPepStanceMap:
|
|||
# --- classification coverage (v0.8 §6.4 obligation 3) -------------------------
|
||||
|
||||
def test_published_coverage_equals_measured_coverage():
|
||||
"""The published figure must equal what the repo actually measures.
|
||||
"""The published figure must equal its explicit public declaration snapshot.
|
||||
|
||||
This is a deterministic source check, not a claim about current estate state.
|
||||
Live measurement remains scripts/report_coverage.py with an explicit estate
|
||||
root; tests never treat the developer HOME as an authority or fixture.
|
||||
|
||||
ops-warden asked gate-house for §13.1's Coverage column and its figures are
|
||||
that column's first entries, so their accuracy is ours to hold. The register
|
||||
|
|
@ -295,7 +299,7 @@ def test_published_coverage_equals_measured_coverage():
|
|||
published = yaml.safe_load((repo / "pep-stance.yaml").read_text())[
|
||||
"classification_coverage"
|
||||
]
|
||||
measured = module.measure()
|
||||
measured = module.measure(repo / "tests/fixtures/workload-coverage")
|
||||
|
||||
for population in ("signing_targets", "routing_lanes"):
|
||||
assert published[population] == measured[population], population
|
||||
|
|
|
|||
92
tests/test_safe_unit_runner.py
Normal file
92
tests/test_safe_unit_runner.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"""Exercise installed parent and nested child controls, never real credentials."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def guarded(request):
|
||||
guard = getattr(request.config, '_unit_guard', None)
|
||||
if guard is None:
|
||||
pytest.skip('requires make test / scripts/run_unit_tests.py')
|
||||
return guard
|
||||
|
||||
|
||||
def test_parent_environment_is_allowlisted_and_home_is_temporary(guarded):
|
||||
# The external regression invocation sets synthetic BAO_TOKEN/VAULT_TOKEN/HOME.
|
||||
assert 'BAO_TOKEN' not in os.environ
|
||||
assert 'VAULT_TOKEN' not in os.environ
|
||||
assert Path(os.environ['TMPDIR']) in Path.home().parents
|
||||
assert 'PYTHONPATH' not in os.environ
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
['bao', 'token', 'lookup'], ['ssh', 'railiance01'], ['curl', 'http://127.0.0.1:8000'],
|
||||
[sys.executable, '-c', 'import os; print(os.environ)'],
|
||||
])
|
||||
def test_unexpected_process_refused_before_launch(guarded, command):
|
||||
with pytest.raises(PermissionError, match='unit isolation'):
|
||||
subprocess.run(command, check=True)
|
||||
|
||||
|
||||
def test_parent_socket_denies_even_unregistered_loopback(guarded):
|
||||
with pytest.raises(PermissionError, match='unit isolation'):
|
||||
socket.socket().connect(('127.0.0.1', 8000))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('path', ['/home/synthetic-ambient-unit-fixture/.vault-token',
|
||||
'/root/.kube/config', '/proc/self/environ',
|
||||
'/tmp/warden-synthetic-ambient-home/.vault-token'])
|
||||
def test_ambient_paths_refused_without_opening_them(guarded, path):
|
||||
with pytest.raises(PermissionError, match='unit isolation'):
|
||||
Path(path).read_bytes()
|
||||
|
||||
|
||||
def test_symlink_cannot_escape_fixture_root(guarded, tmp_path):
|
||||
link = tmp_path / 'ambient'
|
||||
link.symlink_to('/home/synthetic-ambient-unit-fixture/.vault-token')
|
||||
with pytest.raises(PermissionError, match='unit isolation'):
|
||||
link.read_bytes()
|
||||
|
||||
|
||||
def test_child_drops_even_synthetic_token_environment(guarded, monkeypatch):
|
||||
monkeypatch.setenv('BAO_TOKEN', 'synthetic-only-never-a-real-token')
|
||||
monkeypatch.setenv('VAULT_TOKEN', 'synthetic-only-never-a-real-token')
|
||||
monkeypatch.setenv('HOME', '/home/synthetic-ambient-unit-fixture')
|
||||
result = subprocess.run([sys.executable, str(ROOT / 'tests/unit_isolation_probe.py'), 'state'],
|
||||
capture_output=True, text=True, check=True, env=dict(os.environ))
|
||||
assert json.loads(result.stdout) == {'bao_token_present': False,
|
||||
'vault_token_present': False, 'home_in_temporary': True}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mode', ['socket', 'provider', 'home', 'nested'])
|
||||
def test_guard_exists_in_each_allowed_python_child(guarded, mode):
|
||||
result = subprocess.run([sys.executable, str(ROOT / 'tests/unit_isolation_probe.py'), mode],
|
||||
capture_output=True, text=True, check=True)
|
||||
assert result.stdout == 'denied\n'
|
||||
|
||||
|
||||
def test_forged_provider_double_refused(guarded, tmp_path):
|
||||
provider = tmp_path / 'bao'
|
||||
provider.write_text('#!/bin/bash\necho not-the-reviewed-fixture\n')
|
||||
provider.chmod(0o700)
|
||||
with pytest.raises(PermissionError, match='double bytes differ'):
|
||||
subprocess.run([str(provider), 'token', 'lookup'], check=True)
|
||||
|
||||
|
||||
def test_os_open_truncation_respects_read_only_source_root(guarded, tmp_path, monkeypatch):
|
||||
# Synthetic readable source outside the guard's writable subtree.
|
||||
source = tmp_path / 'synthetic-source'
|
||||
source.write_bytes(b'synthetic public source')
|
||||
monkeypatch.setattr(guarded, 'temporary', tmp_path / 'writable')
|
||||
with pytest.raises(PermissionError, match='unit isolation'):
|
||||
descriptor = os.open(source, os.O_RDONLY | os.O_TRUNC)
|
||||
os.close(descriptor)
|
||||
assert source.read_bytes() == b'synthetic public source'
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
"""Explicit lane-to-workload join tests (WARDEN-WP-0032 / RMGR-WP-0010-T06)."""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
import yaml
|
||||
|
||||
|
|
@ -10,7 +12,8 @@ ROOT = Path(__file__).resolve().parents[1]
|
|||
|
||||
|
||||
def test_repo_catalog_uses_only_explicit_workload_references():
|
||||
report = build(ROOT / "registry/routing/catalog.yaml", Path.home())
|
||||
report = build(ROOT / "registry/routing/catalog.yaml",
|
||||
ROOT / "tests/fixtures/workload-coverage")
|
||||
assert report["ok"] is True
|
||||
assert len(report["resolved"]) == 4
|
||||
assert len(report["unknown"]) == 22
|
||||
|
|
@ -67,3 +70,14 @@ def test_invalid_exact_deployable_resolves_unknown(tmp_path):
|
|||
assert not report["resolved"]
|
||||
assert report["unknown"][0]["lane"] == "x"
|
||||
assert "deployable" in report["unknown"][0]["reason"]
|
||||
|
||||
|
||||
def test_public_workload_projection_matches_provenance():
|
||||
base = ROOT / 'tests/fixtures/workload-coverage'
|
||||
provenance = json.loads((base / 'provenance.json').read_text())
|
||||
expected = {'rapp-core-hub', 'rapp-issue-core', 'rapp-qonto'}
|
||||
assert len(provenance) == len(expected)
|
||||
assert {row['repo'] for row in provenance} == expected
|
||||
for row in provenance:
|
||||
projection = base / row['repo'] / 'declarations/rapp.yaml'
|
||||
assert hashlib.sha256(projection.read_bytes()).hexdigest() == row['projection_sha256']
|
||||
|
|
|
|||
29
tests/unit_isolation_probe.py
Normal file
29
tests/unit_isolation_probe.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"""Closed child regression probes; executed only under the unit runner guard."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
mode = sys.argv[1]
|
||||
if mode == 'state':
|
||||
print(json.dumps({'bao_token_present': 'BAO_TOKEN' in os.environ,
|
||||
'vault_token_present': 'VAULT_TOKEN' in os.environ,
|
||||
'home_in_temporary': Path(os.environ['TMPDIR']) in Path.home().parents}))
|
||||
else:
|
||||
try:
|
||||
if mode == 'socket':
|
||||
socket.socket().connect(('127.0.0.1', 8000))
|
||||
elif mode == 'provider':
|
||||
subprocess.run(['bao', 'token', 'lookup'], check=True)
|
||||
elif mode == 'home':
|
||||
Path('/home/synthetic-ambient-unit-fixture/.vault-token').read_text()
|
||||
elif mode == 'nested':
|
||||
subprocess.run([sys.executable, '-c', 'print("SYNTHETIC-SECRET")'], check=True)
|
||||
else:
|
||||
raise AssertionError('unknown probe')
|
||||
except PermissionError:
|
||||
print('denied')
|
||||
else:
|
||||
raise AssertionError('external operation was not refused')
|
||||
Loading…
Add table
Add a link
Reference in a new issue