feat: Implement Filesystem and MongoDB provenance writers for PackRun execution context
Some checks failed
Airgap Sealed CI Smoke / sealed-smoke (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled

- Added `FilesystemPackRunProvenanceWriter` to write provenance manifests to the filesystem.
- Introduced `MongoPackRunArtifactReader` to read artifacts from MongoDB.
- Created `MongoPackRunProvenanceWriter` to store provenance manifests in MongoDB.
- Developed unit tests for filesystem and MongoDB provenance writers.
- Established `ITimelineEventStore` and `ITimelineIngestionService` interfaces for timeline event handling.
- Implemented `TimelineIngestionService` to validate and persist timeline events with hashing.
- Created PostgreSQL schema and migration scripts for timeline indexing.
- Added dependency injection support for timeline indexer services.
- Developed tests for timeline ingestion and schema validation.
This commit is contained in:
StellaOps Bot
2025-11-30 15:38:14 +02:00
parent 8f54ffa203
commit 17d45a6d30
276 changed files with 8618 additions and 688 deletions

View File

@@ -0,0 +1,47 @@
# Export Center KMS Envelope Pattern (age + AES-GCM)
Status: Adopted for Sprint 0164-0001-0001 (ExportCenter III)
Scope: Defines deterministic envelope handling for mirror bundle encryption (`EXPORT-SVC-37-002`) and general export signing. Applies to worker path and verification docs.
## Key hierarchy
- **Content key (DEK):** 32-byte random generated per export run. Used for AES-256-GCM over encrypted payloads (`/data` subtree for mirror; optional for others).
- **Nonce:** 12-byte random per file; stored alongside ciphertext; derive Additional Authenticated Data (AAD) as `{runId}:{relativePath}` to bind file path and run.
- **Wrapping keys:**
- **age recipients** (preferred for offline): each tenant can list one or more age public keys. DEK is wrapped once per recipient using age X25519. Store `recipient`, `wrappedKey` (base64), and optional `keyId` in provenance.
- **KMS envelope** (Authority/HSM): DEK wrapped with tenant-scoped KMS key alias `export/envelope`. Store `kmsKeyId` (authority URI or external ARN) and `wrappedKey` (base64) plus KMS-provided `algorithm`.
## Write path (worker)
1) Generate DEK (32 bytes) per run; zeroize after use.
2) For each encrypted file, derive AAD = `{runId}:{relativePath}`; encrypt with AES-256-GCM (nonce per file). Store `nonce` and `ciphertext`.
3) Wrap DEK for all configured recipients:
- age: `age --encrypt --recipient <pub>` over DEK bytes → base64.
- KMS: `Encrypt`/`WrapKey` with `KeyId=export/envelope` and `EncryptionContext={runId,tenant}` → base64.
4) Record wrapping metadata in `provenance.json` under `environment.encryption.recipients[]` preserving deterministic order (age recipients lexicographically by `recipient`, then KMS entries by `kmsKeyId`).
5) Include `encryption.mode` (`age` or `aes-gcm+kms`), `aadFormat`, and `nonceFormat` in provenance for verification tooling.
## Read/verification path
1) Select a recipient entry that matches available keys (age private key or KMS key).
2) Unwrap DEK:
- age: `age --decrypt` → DEK bytes.
- KMS: `Decrypt`/`UnwrapKey` with same encryption context.
3) For each encrypted file, recompute AAD from `{runId}:{relativePath}`, decrypt with AES-256-GCM using stored `nonce`, verify tag.
4) Recompute SHA-256 of decrypted payload and compare with `export.json` entries.
## Determinism & offline posture
- Recipient lists and wrapped keys are ordered deterministically to keep `provenance.json` hashes stable across retries.
- age path works fully offline; KMS path requires Authority/HSM availability but stores all metadata to allow later decryption once KMS is reachable.
- Use fixed casing and field names: `mode`, `recipients[] {type, recipient|kmsKeyId, wrappedKey, keyId?}` and `aadFormat`.
## Testing notes
- Add regression cases that encrypt/decrypt fixtures with both age and KMS paths, asserting identical manifest/provenance hashes across reruns.
- Ensure decryption fails when AAD does not match expected `{runId}:{relativePath}` (prevents path swapping).
- Keep tests air-gap friendly: mock KMS wrapper with deterministic stub keys.
## Rollout guidance
- Default to age recipients for Offline Kit deployments; enable KMS wrapping where Authority/HSM is reachable.
- Configuration knobs:
- `ExportCenter:Encryption:Mode` = `age` | `kms`
- `ExportCenter:Encryption:Recipients` = list of age public keys
- `ExportCenter:Encryption:KmsKeyId` = tenant-specific key alias (when using KMS)
- Documented verification commands should reference this pattern (update CLI/Console guides when payloads change).