134 lines
7.2 KiB
YAML
134 lines
7.2 KiB
YAML
|
|
# audit-core tenancy posture
|
||
|
|
#
|
||
|
|
# Declared per NetKingdom Tenancy Posture v0.1 (draft-7),
|
||
|
|
# net-kingdom/canon/standards/tenancy-posture_v0.1.md.
|
||
|
|
# Location and schema per Decision 5.4; per-path detail per Decision 5.2;
|
||
|
|
# provider block per Decision 5.5.
|
||
|
|
#
|
||
|
|
# Conformance is accuracy, not altitude (§6). Nothing here is claimed above
|
||
|
|
# what this repo can evidence today, and two rungs are deliberately declared
|
||
|
|
# lower than the mechanism in place — see `gap.E` and `gap.R`.
|
||
|
|
|
||
|
|
tenancy:
|
||
|
|
reviewed: "2026-08-17"
|
||
|
|
service_class: batch # §8.3.2. Co-resident with latency-critical
|
||
|
|
# tenant-engine on platform-pg; the mixture is
|
||
|
|
# reported by the platform, not hidden.
|
||
|
|
|
||
|
|
current: { I: 1, A: 2, E: 1, P: 1, R: 1 }
|
||
|
|
target: { I: 1, A: 2, E: 3, P: 1, R: 2 }
|
||
|
|
|
||
|
|
# §5.2 — declare per path, quote the minimum. The quoted E above is the
|
||
|
|
# minimum across paths. As of AUDIT-WP-0008-T04 both paths carry the same
|
||
|
|
# mechanism; the quoted level stays at 1 for the evidence reason in gap.E,
|
||
|
|
# not because a path is weaker.
|
||
|
|
paths:
|
||
|
|
E:
|
||
|
|
write: 2 # Sender credential bound to the sources and tenants it may
|
||
|
|
# claim; checked at one choke point (audit_core/ingestion.py).
|
||
|
|
read: 2 # Event reads filtered to permitted tenants; surfaces with no
|
||
|
|
# tenant key require full scope. Cross-tenant fetch returns
|
||
|
|
# 404, not 403, so the surface is not an existence oracle.
|
||
|
|
|
||
|
|
placement_exceptions: [] # No tenant is on dedicated substrate. All
|
||
|
|
# tenants share database audit_core on
|
||
|
|
# platform-pg.
|
||
|
|
|
||
|
|
gap:
|
||
|
|
I: >-
|
||
|
|
I1 is accurate and is not currently a target. Senders authenticate with
|
||
|
|
bearer tokens and the tenant arrives in the request body, checked against
|
||
|
|
an allowlist bound to the credential (§4.1 places request-supplied tenant
|
||
|
|
identifiers at I1 however canonical the string). The allowlist is a real
|
||
|
|
control, but it is an A-axis control; it does not make the identity
|
||
|
|
verified. Moving to I2 means senders carrying a verified token with a
|
||
|
|
tenant claim, which is a change to every sender and not audit-core's to
|
||
|
|
make alone. Recorded as accurate, not as ambition.
|
||
|
|
|
||
|
|
A: >-
|
||
|
|
A2 is accurate. Authorization is a single local boundary — permitted
|
||
|
|
sources, permitted tenants, may_write, may_read — bound once and centrally.
|
||
|
|
No flex-auth delegation. A3 is not a near-term target: flex-auth is itself
|
||
|
|
at A0 on its own self-report (/v1/check authenticates no caller), so
|
||
|
|
delegating to it today would lower this service's assurance, not raise it.
|
||
|
|
|
||
|
|
E: >-
|
||
|
|
The E2 mechanism is in place on both paths as of AUDIT-WP-0008-T04, and
|
||
|
|
the quoted level is still 1. This is deliberate. §13.2 states that a
|
||
|
|
passing CI run is not E2 evidence: the E2 artifact is adversarial, needs
|
||
|
|
separate tenant contexts compared against each other, and carries a review
|
||
|
|
date rather than a green build. The repo's cross-tenant tests are
|
||
|
|
mechanical. Under §13.1 the level is not claimable until that artifact
|
||
|
|
exists, so E stays at 1 until AUDIT-WP-0008-T05 produces it with
|
||
|
|
whitehat-security. Declaring E2 on the strength of unit tests would be the
|
||
|
|
overclaim §6 prohibits, and the read-path defect this repo just fixed was
|
||
|
|
found precisely by refusing that kind of reasoning.
|
||
|
|
E_target: >-
|
||
|
|
E3 (row-level security per rapp-postgres ADR-0003) targeted 2027-03-31.
|
||
|
|
Blocked behind the E2 artifact — §4.3 requires E2 evidence alongside any
|
||
|
|
E3 claim — and needs the EXPLAIN comparison first, since RLS disables
|
||
|
|
functional indexes built on non-leakproof functions. E4 is unreachable at
|
||
|
|
P1 by the §3.2 coupling and is not a target.
|
||
|
|
|
||
|
|
R: >-
|
||
|
|
R1 today: the platform default 30-day window applies and audit-core has
|
||
|
|
declared nothing above it. R2 is requested and in flight — audit-core has
|
||
|
|
asked rapp-postgres to add `backupRetentionDays: 30` to
|
||
|
|
consumers/audit-core.yaml, making the window declared rather than
|
||
|
|
inherited. That file is rapp-postgres's, so R2 is not audit-core's to
|
||
|
|
declare unilaterally. The erasure horizon is published on /readyz as
|
||
|
|
recoverable_days.
|
||
|
|
R_ceiling: >-
|
||
|
|
R4 is unreachable under the current design and is not a target. Per
|
||
|
|
Decision 4.5.3 — which this repo found — the hash chain commits to a
|
||
|
|
SHA-256 of the cleartext record, which survives key destruction as a
|
||
|
|
confirmation oracle over low-entropy fields. A fleet R4 target must exempt
|
||
|
|
this service explicitly. See docs/erasure-and-audit.md (AUDIT-WP-0008-T03).
|
||
|
|
|
||
|
|
credentials: >-
|
||
|
|
Stated gap against Decision 9.2 rather than a silent exclusion. Database
|
||
|
|
credentials comply with 9.1: dynamic leases re-read from a mounted Secret
|
||
|
|
at connection time, no restart on rotation. Ingest credentials do not:
|
||
|
|
they are static long-lived bearer tokens, rotated overlap-first by
|
||
|
|
publishing a replacement alongside the incumbent and then dropping the
|
||
|
|
predecessor. audit-core raised this omission during review and is not
|
||
|
|
exempting itself from the rule it asked for. No dated remedy yet; leasing
|
||
|
|
consumer-facing credentials needs a broker path that does not exist.
|
||
|
|
|
||
|
|
retention_placement: >-
|
||
|
|
audit-core's INTENT wants unbounded WORM archive. That is data.archive in
|
||
|
|
ITC-CAP terms, recorded as an unmet requirement in
|
||
|
|
data/capability/audit-core-operational.json, and it is deliberately not a
|
||
|
|
backup-window question: a 30-day WAL window is not an archive, is not
|
||
|
|
searchable, and restores instance-wide. Reviewed 2026-12-31. If no
|
||
|
|
data.archive provision is procured by then, audit-core reopens placement
|
||
|
|
under the §4.5 retention trigger, with P2 as the fallback — a worse answer
|
||
|
|
than archive, named now so it is not improvised later.
|
||
|
|
|
||
|
|
# §5.5 — audit-core provisions operations.audit to senders, so it declares
|
||
|
|
# what it makes reachable for a consumer's audit trail, not only where it
|
||
|
|
# sits. A sender's tenant separation inside the trail is audit-core's to
|
||
|
|
# enforce; the sender cannot reach a level this service does not offer.
|
||
|
|
provides:
|
||
|
|
capability: operations.audit
|
||
|
|
profile: administrative
|
||
|
|
reachable:
|
||
|
|
E2: >-
|
||
|
|
Reachable now. A sender credential is bound to the tenants it may write
|
||
|
|
for and, if it may read, to the tenants it may read back.
|
||
|
|
E3: >-
|
||
|
|
Not reachable yet. Requires rapp-postgres's ADR-0003 GUC contract
|
||
|
|
applied to the audit_core schema. Targeted 2027-03-31.
|
||
|
|
E4: >-
|
||
|
|
Unreachable. One database, one runtime credential, no per-tenant
|
||
|
|
credential and no per-tenant substrate. A sender needing a structural
|
||
|
|
guarantee that another tenant cannot reach its audit records cannot get
|
||
|
|
it here, and should be told so rather than sold E2 in E4's language
|
||
|
|
(§11.4).
|
||
|
|
R2: >-
|
||
|
|
Reachable once the declared window lands; the horizon is already
|
||
|
|
published on /readyz.
|
||
|
|
R4: >-
|
||
|
|
Unreachable by design, per R_ceiling above. A consumer with a verified
|
||
|
|
erasure obligation over its audit trail cannot discharge it here.
|