Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Implement `SbomIngestServiceCollectionExtensionsTests` to verify the SBOM ingestion pipeline exports snapshots correctly. - Create `SbomIngestTransformerTests` to ensure the transformation produces expected nodes and edges, including deduplication of license nodes and normalization of timestamps. - Add `SbomSnapshotExporterTests` to test the export functionality for manifest, adjacency, nodes, and edges. - Introduce `VexOverlayTransformerTests` to validate the transformation of VEX nodes and edges. - Set up project file for the test project with necessary dependencies and configurations. - Include JSON fixture files for testing purposes.
76 lines
4.4 KiB
Markdown
76 lines
4.4 KiB
Markdown
# Evidence Locker Bundle Packaging
|
|
|
|
> Sprint 160 / Task EVID-OBS-54-002 — deterministic tarball packaging for download/export.
|
|
|
|
The Evidence Locker emits a **single `bundle.tgz` artifact** for every sealed bundle. The artifact is deterministic so that operators can re-run packaging and obtain identical bytes when the manifest and signature are unchanged.
|
|
|
|
## Layout
|
|
|
|
The tar stream is written with **POSIX/PAX entries** and wrapped in a gzip layer:
|
|
|
|
```
|
|
bundle.tgz
|
|
├── manifest.json # Re-emitted DSSE payload (pretty JSON, canonical ordering)
|
|
├── signature.json # DSSE signature + key metadata + RFC3161 timestamp (if present)
|
|
├── bundle.json # Locker metadata (ids, status, root hash, storage key, timestamps)
|
|
├── checksums.txt # SHA-256 root hash + per-entry hashes from the manifest
|
|
└── instructions.txt # Offline verification steps and retention guidance
|
|
```
|
|
|
|
### Determinism traits
|
|
|
|
- **Gzip header timestamp** is pinned to `2025-01-01T00:00:00Z` so CI fixtures remain stable.
|
|
- All tar entries use the same fixed mtime/atime/ctime, `0644` permissions, and UTF-8 encoding.
|
|
- JSON files are serialized with `JsonSerializerDefaults.Web` + indentation to stabilise ordering.
|
|
- `checksums.txt` sorts manifest entries by `canonicalPath` and prefixes the Merkle root (`root <hash>`).
|
|
- `instructions.txt` conditionally adds timestamp verification steps when an RFC3161 token exists.
|
|
|
|
## Download endpoint
|
|
|
|
`GET /evidence/{bundleId}/download`
|
|
|
|
- Requires scopes: `evidence:read`.
|
|
- Streams `application/gzip` content with `Content-Disposition: attachment; filename="bundle.tgz"`.
|
|
- Emits quota headers (`X-Stella-Quota-*`) and audit events mirroring snapshot fetches.
|
|
- Returns `404` when the bundle is not sealed or the package has not been materialised.
|
|
|
|
The endpoint reuses `EvidenceBundlePackagingService` and caches the packaged object in the configured object store (`tenants/{tenant}/bundles/{bundle}/bundle.tgz`). If the underlying storage key changes (for example, during migration from filesystem to S3), the repository is updated atomically.
|
|
|
|
## Verification guidance
|
|
|
|
1. Download `bundle.tgz` and read `instructions.txt`; the first section lists bundle id, root hash, and creation/timestamp information.
|
|
2. Verify `checksums.txt` against the transferred archive to detect transit corruption.
|
|
3. Use the StellaOps CLI (`stella evidence verify bundle.tgz`) or the provenance verifier library to validate `signature.json`.
|
|
4. When present, validate the RFC3161 timestamp token with the configured TSA endpoint.
|
|
|
|
These steps match the offline procedure described in `docs/forensics/evidence-locker.md` (Portable Evidence section). Update that guide whenever packaging fields change.
|
|
|
|
## Portable bundle (`portable-bundle-v1.tgz`)
|
|
|
|
When sealed or air-gapped environments need a redacted evidence artifact, request:
|
|
|
|
`GET /evidence/{bundleId}/portable`
|
|
|
|
The portable archive is deterministic and contains only non-sensitive metadata:
|
|
|
|
```
|
|
portable-bundle-v1.tgz
|
|
├── manifest.json # Canonical manifest (identical to sealed bundle)
|
|
├── signature.json # DSSE signature + optional RFC3161 token
|
|
├── bundle.json # Redacted metadata (no tenant/storage identifiers)
|
|
├── checksums.txt # SHA-256 root + entry checksums
|
|
├── instructions-portable.txt # Sealed-mode transfer + verification guidance
|
|
└── verify-offline.sh # Offline verification helper script (POSIX shell)
|
|
```
|
|
|
|
Portable packaging traits:
|
|
|
|
- `bundle.json` excludes tenant identifiers, storage keys, and free-form descriptions. It adds `portableGeneratedAt` along with entry counts and totals for audit purposes.
|
|
- `incidentMetadata` is preserved only when incident mode injects `incident.*` keys into the manifest metadata.
|
|
- `verify-offline.sh` extracts the bundle, validates checksums (using `sha256sum`/`shasum`), surfaces the Merkle root hash, and reminds operators to run `stella evidence verify --bundle <archive>`.
|
|
- `instructions-portable.txt` mirrors the sealed documentation but calls out the offline script and redaction constraints.
|
|
|
|
Portable bundles reuse the same DSSE payload and timestamp, so downstream verifiers can validate signatures without additional configuration. The Evidence Locker tracks the portable storage key separately to honour write-once semantics for both sealed and portable artifacts.
|
|
|
|
For step-by-step sealed-mode guidance see `docs/airgap/portable-evidence.md`.
|