AUDIT-WP-0018-T02 done: nk-bounded-executor sender scope and sso ingress prepared
- deploy/senders-scope.{json,yaml}: nk-bounded-executor (source
net-kingdom.bounded-executor, tenants [platform], write-only,
secret_policy reject, load-bearing); inert until a token is seeded
- senders.py: the scope overlay may tighten secret_policy redact->reject,
never relax it
- networkpolicies.yaml: audit-core-nk-bounded-executor-ingress (sso AND
app.kubernetes.io/name=bounded-executor, TCP 8080); not applied
- tests on the executor's envelope shape: 202, 200 duplicate, 409, wrong
source/tenant, secret-shaped step check 400
- T03 unblocked (window bound = admission window.not_after)
- docs: emitter defect: executor AuditCore.emit rejects 202
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
This commit is contained in:
parent
716900b8a7
commit
7728fde9b4
9 changed files with 452 additions and 11 deletions
|
|
@ -113,8 +113,8 @@
|
|||
| task | AUDIT-WP-0017-T06 | todo | — | workplans/AUDIT-WP-0017-production-audit-sources-and-correlation.md |
|
||||
| task | AUDIT-WP-0017-T07 | wait | — | workplans/AUDIT-WP-0017-production-audit-sources-and-correlation.md |
|
||||
| task | AUDIT-WP-0018-T01 | done | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
|
||||
| task | AUDIT-WP-0018-T02 | wait | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
|
||||
| task | AUDIT-WP-0018-T03 | wait | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
|
||||
| task | AUDIT-WP-0018-T02 | done | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
|
||||
| task | AUDIT-WP-0018-T03 | todo | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
|
||||
| intake | AUDIT-IN-0001 | closed | — | intakes/intakes.md |
|
||||
| intake | AUDIT-IN-0002 | closed | — | intakes/intakes.md |
|
||||
| intake | AUDIT-IN-0003 | closed | — | intakes/intakes.md |
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ from datetime import datetime, timezone
|
|||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
from audit_core.redaction import POLICIES, POLICY_REDACT
|
||||
from audit_core.redaction import POLICIES, POLICY_REDACT, POLICY_REJECT
|
||||
from audit_core.emission_cadence import parse_declaration
|
||||
|
||||
WILDCARD = "*"
|
||||
|
|
@ -314,7 +314,7 @@ def _apply_scope_overlay(
|
|||
may_read=(
|
||||
bool(extra["may_read"]) if "may_read" in extra else identity.may_read
|
||||
),
|
||||
secret_policy=identity.secret_policy,
|
||||
secret_policy=_overlay_secret_policy(identity, extra),
|
||||
expires_at=identity.expires_at,
|
||||
evidence_kind=_overlay_evidence_kind(identity, extra),
|
||||
completeness_trade=(
|
||||
|
|
@ -349,6 +349,32 @@ def _overlay_evidence_kind(identity: SenderIdentity, extra: dict[str, Any]) -> s
|
|||
return declared
|
||||
|
||||
|
||||
def _overlay_secret_policy(identity: SenderIdentity, extra: dict[str, Any]) -> str:
|
||||
"""Let the overlay tighten redact to reject, never relax reject to redact.
|
||||
|
||||
AUDIT-WP-0018-T02. A sender whose events are kept by hash (the bounded
|
||||
executor's receipts) needs ``reject``: silent redaction would store a body
|
||||
that no longer matches the hash the emitter computed. Declaring that in
|
||||
Git means a Secret entry minted with the default cannot quietly weaken it.
|
||||
The opposite direction would let a ConfigMap refresh start storing
|
||||
secret-shaped fields, so it is refused.
|
||||
"""
|
||||
if "secret_policy" not in extra:
|
||||
return identity.secret_policy
|
||||
declared = str(extra["secret_policy"])
|
||||
if declared not in POLICIES:
|
||||
raise ValueError(
|
||||
f"sender {identity.name!r}: secret_policy must be one of {POLICIES}, "
|
||||
f"got {declared!r}"
|
||||
)
|
||||
if identity.secret_policy == POLICY_REJECT and declared == POLICY_REDACT:
|
||||
raise ValueError(
|
||||
f"sender {identity.name!r}: the scope overlay may not relax "
|
||||
"secret_policy from reject to redact"
|
||||
)
|
||||
return declared
|
||||
|
||||
|
||||
def _overlay_heartbeats(
|
||||
identity: SenderIdentity, extra: dict[str, Any]
|
||||
) -> tuple[tuple[str, int], ...]:
|
||||
|
|
|
|||
|
|
@ -63,6 +63,33 @@ spec:
|
|||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: audit-core-nk-bounded-executor-ingress
|
||||
namespace: audit-core
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: audit-core
|
||||
policyTypes: [Ingress]
|
||||
ingress:
|
||||
# AUDIT-WP-0018-T02. NetKingdom's bounded executor (Job pods run as
|
||||
# ServiceAccount nk-bind-executor, package rapp-bounded-executor). Both
|
||||
# selectors belong to one peer and are therefore ANDed. The whole sso
|
||||
# namespace is NOT admitted: Authelia, LLDAP and KeyCape run there too.
|
||||
# Prepared, not applied: the rollout is founder-run, together with the
|
||||
# nk-bounded-executor registry seed.
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: sso
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: bounded-executor
|
||||
ports:
|
||||
- {protocol: TCP, port: 8080}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: audit-core-whitehat-ingress
|
||||
namespace: audit-core
|
||||
|
|
|
|||
|
|
@ -49,5 +49,18 @@
|
|||
"heartbeat_classes": {
|
||||
"informed-decision.memo.posed": 86400
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nk-bounded-executor",
|
||||
"sources": [
|
||||
"net-kingdom.bounded-executor"
|
||||
],
|
||||
"tenants": [
|
||||
"platform"
|
||||
],
|
||||
"may_write": true,
|
||||
"may_read": false,
|
||||
"secret_policy": "reject",
|
||||
"evidence_kind": "load-bearing"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
# entry here for a sender with no token yet is inert. That is what makes the
|
||||
# approval-engine entry safe to land ahead of its credential.
|
||||
#
|
||||
# secret_policy may be tightened here (redact -> reject) but never relaxed
|
||||
# (AUDIT-WP-0018-T02): nk-bounded-executor receipts are kept by hash, so a
|
||||
# secret-shaped field must refuse the event rather than be silently masked.
|
||||
#
|
||||
# evidence_kind may be raised here (attributive -> load-bearing) but never
|
||||
# lowered: a ConfigMap refresh must not be able to drop a source's §9.6
|
||||
# atomicity and detection obligations without anyone deciding to.
|
||||
|
|
@ -71,5 +75,18 @@ data:
|
|||
"heartbeat_classes": {
|
||||
"informed-decision.memo.posed": 86400
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nk-bounded-executor",
|
||||
"sources": [
|
||||
"net-kingdom.bounded-executor"
|
||||
],
|
||||
"tenants": [
|
||||
"platform"
|
||||
],
|
||||
"may_write": true,
|
||||
"may_read": false,
|
||||
"secret_policy": "reject",
|
||||
"evidence_kind": "load-bearing"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -163,3 +163,46 @@ value appears in this repository.
|
|||
entry.
|
||||
- **Founder:** the live registry entry and NetworkPolicy rollout are an approved
|
||||
live change. Audit-core prepares them; the founder runs them.
|
||||
|
||||
## Status 2026-10-31 — T02 prepared after NetKingdom's answers
|
||||
|
||||
NetKingdom answered on hub messages `ed03ba30` and `deea5bf8`. The answers are
|
||||
based on executor.py at net-kingdom a020758 and rapp-bounded-executor 9826a24.
|
||||
|
||||
- Source `net-kingdom.bounded-executor`. Event types `execution.run.started` and
|
||||
`execution.attribution.receipt`. Tenant `platform`, which is the exact
|
||||
string. Note that it is not `tenant:platform`, which approval-engine and
|
||||
informed-decision use. The `started` event is adopted, so the stream is
|
||||
`load-bearing`.
|
||||
- **Registry entry (prepared, not seeded):** `nk-bounded-executor` in
|
||||
`deploy/senders-scope.json` and `deploy/senders-scope.yaml`. The entry admits
|
||||
nothing until the Secret carries a token for that name. The overlay may now
|
||||
tighten `secret_policy` from `redact` to `reject`, but it may never relax it
|
||||
(`audit_core/senders.py`). Even if the Secret entry is minted with the
|
||||
default, the result is therefore `reject`.
|
||||
- **Ingress (prepared, not applied):** `audit-core-nk-bounded-executor-ingress`
|
||||
in `deploy/networkpolicies.yaml` admits only namespace `sso` combined (AND)
|
||||
with pod label `app.kubernetes.io/name=bounded-executor`, on TCP 8080.
|
||||
- **Tests:** `tests/test_bounded_executor_sender.py` runs on the executor's
|
||||
envelope shape. It checks: 202 accept; 200 duplicate on retry; 409 on a
|
||||
reused run id with a different body; 400 `source_not_allowed` and
|
||||
`tenant_not_allowed`; 400 `secret_shaped_field` for a secret-shaped step
|
||||
check.
|
||||
- **Defect found in the emitter:** `bounded-executor/clients.py`
|
||||
`AuditCore.emit` accepts only 200 and 201. Audit-core answers a first accept
|
||||
with **202**, so the executor would treat a stored `started` event as a
|
||||
failure and stop with `audit_unavailable`. NetKingdom has to accept 202
|
||||
(accepted) and 200 (duplicate). It should treat 409 as a run-id-reuse
|
||||
defect and must not retry it.
|
||||
- **T03 window bound:** a `started` event that has no receipt with the same
|
||||
`correlation_id` becomes a finding after the admission's `window.not_after`.
|
||||
Pilot windows are 2 h.
|
||||
|
||||
Remaining work:
|
||||
|
||||
- **Founder-run:** mint and seed the Secret entry, then roll out the ConfigMap
|
||||
and the NetworkPolicy.
|
||||
- **NetKingdom:** the versioned receipt schema with fixtures (NK-WP-0040-T02)
|
||||
and the 202 fix.
|
||||
- **railiance-platform:** custody of the sender bearer (ESO projection to
|
||||
`sso`).
|
||||
|
|
|
|||
267
tests/test_bounded_executor_sender.py
Normal file
267
tests/test_bounded_executor_sender.py
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
"""AUDIT-WP-0018-T02. The bounded executor as a registered sender, on fixtures.
|
||||
|
||||
The envelopes below follow net-kingdom ``bounded-executor/executor.py``
|
||||
(``Run._event`` / ``Run._receipt``, commit a020758) key for key. Values are
|
||||
synthetic. The registry is built the way production builds it: a Secret-shaped
|
||||
``AUDIT_CORE_SENDERS`` entry (here deliberately minted with the defaults) plus
|
||||
the Git-declared scope overlay in ``deploy/senders-scope.json``, so these tests
|
||||
also prove that the overlay alone yields the declared posture.
|
||||
|
||||
Canonical receipt schema validation is AUDIT-WP-0014-T02 and waits on
|
||||
NetKingdom's versioned schema (NK-WP-0040-T02); nothing here validates the
|
||||
receipt beyond the envelope and the secret-shaped-field scan.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from audit_core.ingestion import IngestionApplication
|
||||
from audit_core.senders import SenderRegistry
|
||||
from audit_core.sqlite_backend import SQLiteAuditBackend
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCOPE_FILE = ROOT / "deploy" / "senders-scope.json"
|
||||
|
||||
TOKEN = "fixture-only-executor"
|
||||
RUN_ID = "3f0c2a8e-0000-4000-8000-000000000001"
|
||||
ADMISSION = "bea-pilot-0001"
|
||||
|
||||
|
||||
def started_event():
|
||||
return {
|
||||
"id": f"run:{RUN_ID}:started",
|
||||
"type": "execution.run.started",
|
||||
"correlation_id": RUN_ID,
|
||||
"subject": f"admission:{ADMISSION}",
|
||||
"source": "net-kingdom.bounded-executor",
|
||||
"tenant": "platform",
|
||||
"occurred_at": "2026-10-31T10:00:05+00:00",
|
||||
"data": {
|
||||
"run_id": RUN_ID,
|
||||
"admission": ADMISSION,
|
||||
"approval": "apr-0001",
|
||||
"consumed_at": "2026-10-31T10:00:04Z",
|
||||
"executing_workload": "service:nk-bind-executor",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def receipt_event():
|
||||
return {
|
||||
"id": f"run:{RUN_ID}:receipt",
|
||||
"type": "execution.attribution.receipt",
|
||||
"correlation_id": RUN_ID,
|
||||
"subject": f"admission:{ADMISSION}",
|
||||
"source": "net-kingdom.bounded-executor",
|
||||
"tenant": "platform",
|
||||
"occurred_at": "2026-10-31T10:03:00+00:00",
|
||||
"data": {
|
||||
"run_id": RUN_ID,
|
||||
"initiator": {"sub": "approver-subject-1", "role": "approver"},
|
||||
"executing_workload": "service:nk-bind-executor",
|
||||
"runtime_principal": "system:serviceaccount:sso:nk-bind-executor",
|
||||
"tenant": "platform",
|
||||
"environment": "railiance01",
|
||||
"source_commit": "a020758",
|
||||
"artifact_digest": "sha256:" + "a" * 64,
|
||||
"target": f"admission:{ADMISSION}",
|
||||
"action": "bounded-execution",
|
||||
"decision_refs": [
|
||||
{"label": "admission", "ref": ADMISSION},
|
||||
{"label": "admission_digest", "ref": "sha256:" + "b" * 64},
|
||||
{"label": "approval", "ref": "apr-0001"},
|
||||
],
|
||||
"pdp": "not-evaluated",
|
||||
"time_interval": {
|
||||
"start": "2026-10-31T10:00:04Z",
|
||||
"end": "2026-10-31T10:03:00Z",
|
||||
"clock_source": "system-utc",
|
||||
},
|
||||
"outcome": "completed",
|
||||
"reverted": False,
|
||||
"attempts": 1,
|
||||
"budget": {
|
||||
"limit": {"wall_time_seconds": 600, "attempts": 1},
|
||||
"used": {"wall_time_seconds": 176.2, "attempts": 1},
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "s1", "kind": "step", "operation": "rollout-restart",
|
||||
"target": "deployment/authelia", "consumer": "sso",
|
||||
"outcome": "done",
|
||||
"checks": [{"id": "c1", "result": "pass"}],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def registry():
|
||||
# Minted with the defaults on purpose (redact, attributive, wildcard
|
||||
# source/tenant): the Git overlay must narrow and tighten it on its own.
|
||||
secret = json.dumps([
|
||||
{"name": "nk-bounded-executor", "tokens": [TOKEN], "sources": ["*"], "tenants": ["*"]},
|
||||
{"name": "operator", "tokens": ["operator"], "sources": ["*"], "tenants": ["*"],
|
||||
"may_write": False, "may_read": True},
|
||||
])
|
||||
return SenderRegistry.from_env({
|
||||
"AUDIT_CORE_SENDERS": secret,
|
||||
"AUDIT_CORE_SENDERS_SCOPE_PATH": str(SCOPE_FILE),
|
||||
})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app(tmp_path):
|
||||
return IngestionApplication(SQLiteAuditBackend(str(tmp_path / "executor.db")), registry())
|
||||
|
||||
|
||||
def post(app, event, *, token=TOKEN):
|
||||
raw = json.dumps(event).encode()
|
||||
environ = {
|
||||
"PATH_INFO": "/v1/events",
|
||||
"REQUEST_METHOD": "POST",
|
||||
"CONTENT_LENGTH": str(len(raw)),
|
||||
"wsgi.input": io.BytesIO(raw),
|
||||
"HTTP_AUTHORIZATION": f"Bearer {token}",
|
||||
# The executor sets Idempotency-Key = envelope id (clients.AuditCore.emit).
|
||||
"HTTP_IDEMPOTENCY_KEY": event["id"],
|
||||
}
|
||||
result = {}
|
||||
out = b"".join(app(environ, lambda status, headers: result.update(status=status)))
|
||||
return int(result["status"].split()[0]), (json.loads(out) if out else {})
|
||||
|
||||
|
||||
# --- registration -----------------------------------------------------------
|
||||
|
||||
def test_scope_entry_is_exact_write_only_reject_load_bearing():
|
||||
entry = next(e for e in json.loads(SCOPE_FILE.read_text()) if e["name"] == "nk-bounded-executor")
|
||||
assert entry == {
|
||||
"name": "nk-bounded-executor",
|
||||
"sources": ["net-kingdom.bounded-executor"],
|
||||
"tenants": ["platform"],
|
||||
"may_write": True,
|
||||
"may_read": False,
|
||||
"secret_policy": "reject",
|
||||
"evidence_kind": "load-bearing",
|
||||
}
|
||||
|
||||
|
||||
def test_overlay_narrows_a_default_minted_secret_entry():
|
||||
identity = registry().authenticate(f"Bearer {TOKEN}")
|
||||
assert identity.name == "nk-bounded-executor"
|
||||
assert identity.sources == frozenset({"net-kingdom.bounded-executor"})
|
||||
assert identity.tenants == frozenset({"platform"})
|
||||
assert identity.may_write and not identity.may_read
|
||||
assert identity.secret_policy == "reject"
|
||||
assert identity.is_load_bearing
|
||||
assert identity.completeness_trade is None
|
||||
|
||||
|
||||
def test_scope_entry_admits_nothing_without_a_token():
|
||||
"""Safe to land ahead of the founder-run seed."""
|
||||
reg = SenderRegistry.from_env({
|
||||
"AUDIT_CORE_SENDERS": json.dumps(
|
||||
[{"name": "user-engine", "tokens": ["live"], "sources": ["user-engine"]}]
|
||||
),
|
||||
"AUDIT_CORE_SENDERS_SCOPE_PATH": str(SCOPE_FILE),
|
||||
})
|
||||
assert [i.name for i in reg.identities] == ["user-engine"]
|
||||
|
||||
|
||||
def test_overlay_may_not_relax_reject_to_redact():
|
||||
with pytest.raises(ValueError, match="may not relax"):
|
||||
SenderRegistry.from_env({
|
||||
"AUDIT_CORE_SENDERS": json.dumps([{
|
||||
"name": "nk-bounded-executor", "tokens": ["t"],
|
||||
"sources": ["net-kingdom.bounded-executor"], "secret_policy": "reject",
|
||||
}]),
|
||||
"AUDIT_CORE_SENDERS_SCOPE": json.dumps(
|
||||
[{"name": "nk-bounded-executor", "secret_policy": "redact"}]
|
||||
),
|
||||
})
|
||||
|
||||
|
||||
def test_overlay_rejects_an_unknown_secret_policy():
|
||||
with pytest.raises(ValueError, match="secret_policy must be one of"):
|
||||
SenderRegistry.from_env({
|
||||
"AUDIT_CORE_SENDERS": json.dumps([{"name": "x", "tokens": ["t"], "sources": ["x"]}]),
|
||||
"AUDIT_CORE_SENDERS_SCOPE": json.dumps([{"name": "x", "secret_policy": "ignore"}]),
|
||||
})
|
||||
|
||||
|
||||
# --- ingest behaviour on the executor's real envelope shape -----------------
|
||||
|
||||
@pytest.mark.parametrize("make", [started_event, receipt_event])
|
||||
def test_accept_is_202(app, make):
|
||||
status, body = post(app, make())
|
||||
assert status == 202
|
||||
assert body["status"] == "accepted"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("make", [started_event, receipt_event])
|
||||
def test_retry_is_a_200_duplicate(app, make):
|
||||
assert post(app, make())[0] == 202
|
||||
status, body = post(app, make())
|
||||
assert status == 200
|
||||
assert body["status"] == "duplicate"
|
||||
|
||||
|
||||
def test_started_and_receipt_share_the_run_correlation(app):
|
||||
assert post(app, started_event())[0] == 202
|
||||
assert post(app, receipt_event())[0] == 202
|
||||
|
||||
|
||||
def test_reused_run_id_with_a_different_body_is_a_409(app):
|
||||
assert post(app, receipt_event())[0] == 202
|
||||
other = receipt_event()
|
||||
other["data"]["outcome"] = "reverted"
|
||||
other["data"]["reverted"] = True
|
||||
status, body = post(app, other)
|
||||
assert status == 409
|
||||
assert body["error"] == "event_id_conflict"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("source", [
|
||||
"net-kingdom", "bounded-executor", "net-kingdom.bounded-executor ", "approval-engine",
|
||||
])
|
||||
def test_wrong_source_is_refused(app, source):
|
||||
event = receipt_event()
|
||||
event["source"] = source
|
||||
status, body = post(app, event)
|
||||
assert status == 400
|
||||
assert body["error"] == "source_not_allowed"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tenant", [
|
||||
"tenant:platform", "Platform", "platform ", "tenant:friendly:binky", "*",
|
||||
])
|
||||
def test_wrong_tenant_is_refused(app, tenant):
|
||||
event = receipt_event()
|
||||
event["tenant"] = tenant
|
||||
status, body = post(app, event)
|
||||
assert status == 400
|
||||
assert body["error"] == "tenant_not_allowed"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field", ["token", "password", "client_secret", "private_key"])
|
||||
def test_secret_shaped_step_check_is_refused_with_400(app, field):
|
||||
event = receipt_event()
|
||||
event["data"]["steps"][0]["checks"][0][field] = "synthetic-not-a-secret"
|
||||
status, body = post(app, event)
|
||||
assert status == 400
|
||||
assert body["error"] == "secret_shaped_field"
|
||||
# Not stored: the clean receipt for the same run is still accepted afterwards.
|
||||
assert post(app, receipt_event())[0] == 202
|
||||
|
||||
|
||||
def test_the_real_receipt_shape_carries_no_secret_shaped_key(app):
|
||||
"""If the executor ever adds such a key, its receipts start bouncing; this
|
||||
pins that the current shape passes the reject policy unmodified."""
|
||||
from audit_core.redaction import scan
|
||||
|
||||
assert scan(started_event()["data"]) == []
|
||||
assert scan(receipt_event()["data"]) == []
|
||||
|
|
@ -129,9 +129,42 @@ def test_every_sender_has_exactly_one_ingress_rule():
|
|||
scope = json.loads((ROOT / "deploy" / "senders-scope.json").read_text())
|
||||
senders = {entry["name"] for entry in scope}
|
||||
assert senders == {
|
||||
"user-engine", "approval-engine", "informed-decision", "tenant-engine"
|
||||
"user-engine", "approval-engine", "informed-decision", "tenant-engine",
|
||||
"nk-bounded-executor",
|
||||
}
|
||||
# user-engine's rule predates the per-sender naming convention.
|
||||
assert "name: audit-core-sender-ingress" in text
|
||||
for sender in senders - {"user-engine"}:
|
||||
assert f"name: audit-core-{sender}-ingress" in text
|
||||
|
||||
|
||||
def test_bounded_executor_ingress_is_bound_to_sso_and_executor_pod_label():
|
||||
"""AUDIT-WP-0018-T02. Only the executor's pods, not the whole sso namespace.
|
||||
|
||||
Authelia, LLDAP and KeyCape run in sso too; a namespace-only rule would
|
||||
let any of them reach the receiver's write port.
|
||||
"""
|
||||
documents = (ROOT / "deploy" / "networkpolicies.yaml").read_text().split("\n---\n")
|
||||
policy = next(
|
||||
document
|
||||
for document in documents
|
||||
if "name: audit-core-nk-bounded-executor-ingress" in document
|
||||
)
|
||||
expected_peer = """ - namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: sso
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: bounded-executor"""
|
||||
assert expected_peer in policy
|
||||
assert policy.count(" - namespaceSelector:") == 1
|
||||
assert policy.count(" - {protocol: TCP, port:") == 1
|
||||
assert " - {protocol: TCP, port: 8080}" in policy
|
||||
|
||||
|
||||
def test_no_egress_to_sso_is_created():
|
||||
"""The receiver answers the executor; it never calls into sso."""
|
||||
documents = (ROOT / "deploy" / "networkpolicies.yaml").read_text().split("\n---\n")
|
||||
for document in documents:
|
||||
if "Egress" in document and "egress:" in document:
|
||||
assert "kubernetes.io/metadata.name: sso" not in document
|
||||
|
|
|
|||
|
|
@ -46,10 +46,8 @@ targets and actions, and has to make `decision_refs` typed.
|
|||
|
||||
```task
|
||||
id: AUDIT-WP-0018-T02
|
||||
status: wait
|
||||
status: done
|
||||
priority: high
|
||||
blocking_reason: "NetKingdom must confirm source, event types, tenant binding and the started event; the rapp-* package must name pod labels and ServiceAccount; railiance-platform must route the ESO-projected bearer."
|
||||
blocked_on: message-from:net-kingdom
|
||||
state_hub_task_id: "3f251d30-e62d-5849-88d9-d033097b4f7d"
|
||||
```
|
||||
|
||||
|
|
@ -70,14 +68,27 @@ Then test on synthetic fixtures:
|
|||
|
||||
The live registry seed and the rollout are founder-run.
|
||||
|
||||
Done 2026-10-31, from NetKingdom hub messages ed03ba30 and deea5bf8. Scope
|
||||
entry: `deploy/senders-scope.{json,yaml}`. The overlay may now tighten
|
||||
`secret_policy` but never relax it. Ingress:
|
||||
`audit-core-nk-bounded-executor-ingress`. Tests:
|
||||
`tests/test_bounded_executor_sender.py`. Nothing is applied.
|
||||
|
||||
- **Founder-run:** mint the Secret entry, seed it, and roll out the ConfigMap
|
||||
and the NetworkPolicy.
|
||||
- **Still owed by NetKingdom:** NK-WP-0040-T02 (schema and fixtures). Also the
|
||||
executor must accept 202: `clients.AuditCore.emit` accepts only 200 and 201
|
||||
today.
|
||||
- **Still owed by railiance-platform:** bearer custody.
|
||||
|
||||
See `docs/bounded-executor-receipts.md`, "Status 2026-10-31".
|
||||
|
||||
## Detect runs that started without a receipt
|
||||
|
||||
```task
|
||||
id: AUDIT-WP-0018-T03
|
||||
status: wait
|
||||
status: todo
|
||||
priority: medium
|
||||
blocking_reason: "Depends on NetKingdom adopting the execution.run.started event and stating the admission window bound after which a missing receipt is a finding."
|
||||
blocked_on: message-from:net-kingdom
|
||||
state_hub_task_id: "ec2cd9b7-be65-5792-b546-ffd0b3937856"
|
||||
```
|
||||
|
||||
|
|
@ -88,6 +99,10 @@ declared window. Reuse the existing stream-completeness/cadence surface
|
|||
adding a new monitor. The report is a finding for the verifier and the
|
||||
founder. Audit-core does not infer what the run did.
|
||||
|
||||
Unblocked 2026-10-31. NetKingdom adopted the started event and set the
|
||||
bound: the admission's `window.not_after`. Pilot windows are 2 h. The receipt
|
||||
also carries `time_interval.end`.
|
||||
|
||||
## Founder decision 2026-10-31
|
||||
|
||||
Approved by the founder (`proposed` -> `ready`) after the NK-WP-0056-T02 owner answers (hub decision 19afd15e). Founder adopted the `execution.run.started` event plus one receipt per run with an admission-level target/action, `steps[]` detail and labelled `decision_refs`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue