Add versioned ephemeral custody lifecycle
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa
This commit is contained in:
parent
985cef2572
commit
30e6edc236
17 changed files with 2855 additions and 6 deletions
59
docs/custody-projection-contract.example.json
Normal file
59
docs/custody-projection-contract.example.json
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"interface": "railiance.custody-projection-contract",
|
||||
"version": 1,
|
||||
"workplan_id": "RAILIANCE-WP-0025",
|
||||
"engagement_id": "REPLACE-WITH-NEW-TERMINAL-UNIQUE-ID",
|
||||
"status": "replace-with-explicit-approval",
|
||||
"engagement_contract_sha256": "replace-with-64-lowercase-hex",
|
||||
"target": {
|
||||
"id": "replace-with-target-id",
|
||||
"namespace": "replace-with-target-namespace",
|
||||
"deployment": "replace-with-target-deployment",
|
||||
"container": "replace-with-target-container",
|
||||
"sender_external_secret": "replace-with-sender-external-secret",
|
||||
"revision": "replace-with-full-40-character-git-revision",
|
||||
"image_digest": "sha256:replace-with-64-lowercase-hex",
|
||||
"contract_sha256": "replace-with-64-lowercase-hex"
|
||||
},
|
||||
"runner": {
|
||||
"namespace": "whitehat",
|
||||
"service_account": "whitehat-runner",
|
||||
"secret_name": "replace-with-exact-mounted-secret-name",
|
||||
"mount_root": "/var/run/secrets/whitehat",
|
||||
"manifest_sha256": "replace-with-64-lowercase-hex"
|
||||
},
|
||||
"window": {
|
||||
"starts_at": "replace-with-RFC3339-start",
|
||||
"projection_cutoff": "replace-with-RFC3339-projection-cutoff",
|
||||
"expires_at": "replace-with-RFC3339-expiry-at-most-900-seconds-after-start"
|
||||
},
|
||||
"authority": {
|
||||
"remote": "railiance01",
|
||||
"registry_path": "replace-with-exact-platform-registry-path",
|
||||
"registry_field": "senders.json",
|
||||
"kv_mount": "platform",
|
||||
"kv_prefix": "engagements/REPLACE-WITH-NEW-TERMINAL-UNIQUE-ID/replace-with-target-id",
|
||||
"eso_service_account": "external-secrets",
|
||||
"eso_namespace": "external-secrets"
|
||||
},
|
||||
"identities": [
|
||||
{
|
||||
"handle": "token-a",
|
||||
"role": "attacker",
|
||||
"sender_name": "replace-with-new-attacker-sender",
|
||||
"tenant": "replace-with-new-attacker-fixture-tenant",
|
||||
"mount_path": "/var/run/secrets/whitehat/token-a",
|
||||
"may_read": true,
|
||||
"may_write": true
|
||||
},
|
||||
{
|
||||
"handle": "token-b",
|
||||
"role": "owner",
|
||||
"sender_name": "replace-with-new-owner-sender",
|
||||
"tenant": "replace-with-new-owner-fixture-tenant",
|
||||
"mount_path": "/var/run/secrets/whitehat/token-b",
|
||||
"may_read": true,
|
||||
"may_write": true
|
||||
}
|
||||
]
|
||||
}
|
||||
132
docs/ephemeral-custody-lifecycle.md
Normal file
132
docs/ephemeral-custody-lifecycle.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# Ephemeral custody projection lifecycle
|
||||
|
||||
`scripts/custody-projection.py` is the platform-owned interface for future
|
||||
short-lived, mount-only credential exercises. It replaces copying the dated
|
||||
`audit-core-whitehat-e2-credentials*.py`, HCL, and YAML files. Those dated
|
||||
artifacts are retained only as historical evidence and cleanup references for
|
||||
their terminal engagement ids.
|
||||
|
||||
The lifecycle does not authorize an engagement. A projection contract is
|
||||
accepted only when it is `approved`, lasts at most 900 seconds, binds exact
|
||||
target and runner revisions, names exactly two distinct sender identities, and
|
||||
uses engagement- and target-bound KV paths. Kubernetes and OpenBao resource
|
||||
names are derived from the SHA-256 of the engagement id; no previous engagement
|
||||
name is copied into a new manifest.
|
||||
|
||||
## Versioned interfaces
|
||||
|
||||
| Interface | Schema | Producer | Consumer |
|
||||
| --- | --- | --- | --- |
|
||||
| `railiance.custody-projection-contract` v1 | `schemas/custody-projection-contract.schema.json` | engagement/platform owners | platform lifecycle and Whitehat review |
|
||||
| `railiance.custody-broker-readiness` v1 | `schemas/custody-broker-readiness.schema.json` | `whitehat-security` | platform pre-projection gate |
|
||||
| `railiance.custody-projection-receipt` v1 | `schemas/custody-projection-receipt.schema.json` | platform lifecycle | Whitehat broker adapter and operator |
|
||||
| `railiance.custody-cleanup-receipt` v1 | `schemas/custody-cleanup-receipt.schema.json` | platform lifecycle | engagement owner and evidence record |
|
||||
|
||||
Receipts contain no bearer, password, value-derived fingerprint, Secret data,
|
||||
or registry body. The projection receipt carries an opaque lease id, canonical
|
||||
receipt id, exact identity roles and mount paths, contract digests, times,
|
||||
resource names, and Kubernetes UIDs. Changing any canonical field invalidates
|
||||
the receipt id.
|
||||
|
||||
## Broker gate owned directly by Whitehat
|
||||
|
||||
The owner does not need a coding agent to translate the request. Given the
|
||||
final projection contract, it can run:
|
||||
|
||||
```sh
|
||||
python3 scripts/wp0025-broker-readiness.py show \
|
||||
--contract /path/to/final-projection-contract.json
|
||||
|
||||
python3 scripts/wp0025-broker-readiness.py verify \
|
||||
--contract /path/to/final-projection-contract.json \
|
||||
--consumer-root /path/to/whitehat-security
|
||||
|
||||
python3 scripts/wp0025-broker-readiness.py approve \
|
||||
--contract /path/to/final-projection-contract.json \
|
||||
--consumer-root /path/to/whitehat-security \
|
||||
--reviewer whitehat-owner
|
||||
```
|
||||
|
||||
Verification is closed to
|
||||
`src/whitehat_security/platform_custody.py` and
|
||||
`tests/test_platform_custody_adapter.py`. It records the full consumer commit,
|
||||
adapter SHA-256, focused-test result, exact platform schema hashes, mount paths,
|
||||
and cleanup support. Approval posts the canonical receipt directly to State
|
||||
Hub as `from_agent=whitehat-security`. A stale schema, changed projection
|
||||
contract, wrong engagement, wrong target, absent adapter, or failing focused
|
||||
test does not count.
|
||||
|
||||
The platform lifecycle queries that receipt before token generation and before
|
||||
the first mutation. State Hub coordination identity is not a secret transport;
|
||||
the receipt contains no secret.
|
||||
|
||||
## Platform commands
|
||||
|
||||
The template at `docs/custody-projection-contract.example.json` intentionally
|
||||
fails validation until every placeholder and the approval status are replaced
|
||||
by the owners.
|
||||
|
||||
```sh
|
||||
python3 scripts/custody-projection.py validate \
|
||||
--contract /path/to/final-projection-contract.json
|
||||
|
||||
python3 scripts/custody-projection.py render \
|
||||
--contract /path/to/final-projection-contract.json
|
||||
|
||||
python3 scripts/custody-projection.py preflight \
|
||||
--contract /path/to/final-projection-contract.json
|
||||
```
|
||||
|
||||
`render` is value-free and creates no resource. `preflight` reads only status,
|
||||
metadata, names, image/readiness, and the broker receipt. It never reads a
|
||||
Kubernetes Secret value.
|
||||
|
||||
Inside the approved projection interval, an attached custody operator may run:
|
||||
|
||||
```sh
|
||||
python3 scripts/custody-projection.py project \
|
||||
--contract /path/to/final-projection-contract.json \
|
||||
--confirm '<engagement-id>:attended' \
|
||||
--receipt-out /approved/non-repo/path/projection.json
|
||||
```
|
||||
|
||||
If any mutation fails, the transaction invokes exact-scope cleanup. The
|
||||
operator must not release the consumer until the returned projection receipt
|
||||
passes its adapter. The receipt file is written mode `0600` even though it is
|
||||
value-safe.
|
||||
|
||||
Status and normal attended cleanup require that receipt:
|
||||
|
||||
```sh
|
||||
python3 scripts/custody-projection.py status \
|
||||
--contract /path/to/final-projection-contract.json \
|
||||
--receipt /approved/non-repo/path/projection.json
|
||||
|
||||
python3 scripts/custody-projection.py cleanup \
|
||||
--contract /path/to/final-projection-contract.json \
|
||||
--receipt /approved/non-repo/path/projection.json \
|
||||
--confirm '<engagement-id>:cleanup' \
|
||||
--receipt-out /approved/non-repo/path/cleanup.json
|
||||
```
|
||||
|
||||
Status reports `active`, `absent`, `partial`, or `mismatched`. It compares live
|
||||
resource UIDs with the receipt and does not interpret a connection error as
|
||||
absence.
|
||||
|
||||
## Expired cleanup entry point
|
||||
|
||||
`cleanup-expired` refuses until the contract expiry and still requires the
|
||||
exact projection receipt and confirmation:
|
||||
|
||||
```sh
|
||||
python3 scripts/custody-projection.py cleanup-expired \
|
||||
--contract /path/to/final-projection-contract.json \
|
||||
--receipt /approved/non-repo/path/projection.json \
|
||||
--confirm '<engagement-id>:expired' \
|
||||
--receipt-out /approved/non-repo/path/cleanup.json
|
||||
```
|
||||
|
||||
This is an approval-ready reaper entry point, not a deployed scheduler. A
|
||||
future controller still needs an owner-reviewed credential route, runtime
|
||||
identity, alerting, and rollout decision. Application `expires_at` remains the
|
||||
acceptance backstop; evidenced resource deletion remains the cleanup result.
|
||||
Loading…
Add table
Add a link
Reference in a new issue