Add unit tests for SBOM ingestion and transformation
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
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.
This commit is contained in:
@@ -514,11 +514,31 @@ sequenceDiagram
|
||||
* **Templates**: compile and cache per rule+channel+locale; version with rule `updatedAt` to invalidate.
|
||||
* **Rules**: store raw YAML + parsed AST; validate with schema + static checks (e.g., nonsensical combos).
|
||||
* **Secrets**: pluggable secret resolver (Authority Secret proxy, K8s, Vault).
|
||||
* **Rate limiting**: `System.Threading.RateLimiting` + per‑connector adapters.
|
||||
* **Rate limiting**: `System.Threading.RateLimiting` + per-connector adapters.
|
||||
|
||||
---
|
||||
|
||||
## 19) Roadmap (post‑v1)
|
||||
## 19) Air-gapped bootstrap configuration
|
||||
|
||||
Air-gapped deployments ship a deterministic Notifier profile inside the
|
||||
Bootstrap Pack. The artefacts live under `bootstrap/notify/` after running the
|
||||
Offline Kit builder and include:
|
||||
|
||||
- `notify.yaml` — configuration derived from `etc/notify.airgap.yaml`, pointing
|
||||
to the sealed MongoDB/Authority endpoints and loading connectors from the
|
||||
local plug-in directory.
|
||||
- `notify-web.secret.example` — template for the Authority client secret,
|
||||
intended to be renamed to `notify-web.secret` before deployment.
|
||||
- `README.md` — operator guide (`docs/modules/notify/bootstrap-pack.md`).
|
||||
|
||||
These files are copied automatically by `ops/offline-kit/build_offline_kit.py`
|
||||
via `copy_bootstrap_configs`. Operators mount the configuration and secret into
|
||||
the `StellaOps.Notifier.WebService` container (Compose or Kubernetes) to keep
|
||||
sealed-mode roll-outs reproducible.
|
||||
|
||||
---
|
||||
|
||||
## 20) Roadmap (post-v1)
|
||||
|
||||
* **PagerDuty/Opsgenie** connectors; **Jira** ticket creation.
|
||||
* **User inbox** (in‑app notifications) + mobile push via webhook relay.
|
||||
|
||||
59
docs/modules/notify/bootstrap-pack.md
Normal file
59
docs/modules/notify/bootstrap-pack.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Notifier Bootstrap Pack Guide
|
||||
|
||||
The Bootstrap Pack gives operators a deterministic set of configuration files
|
||||
to stage the Notifier service in sealed or fully air-gapped environments. The
|
||||
assets ship alongside the Offline Kit under `bootstrap/notify/` and can be
|
||||
copied directly onto the hosts that run `StellaOps.Notifier.WebService`.
|
||||
|
||||
## Contents
|
||||
|
||||
| File | Purpose |
|
||||
| ---- | ------- |
|
||||
| `notify.yaml` | Sealed-mode configuration derived from `etc/notify.airgap.yaml`. It disables external resolution by pointing to in-cluster services and honours the shared `EgressPolicy`. |
|
||||
| `notify-web.secret.example` | Deterministic template for the Authority client secret. Replace the value before running the service. |
|
||||
| `rules/airgap-ops.rule.json` | Bootstrap rule subscribing to air-gap drift, bundle import, and portable export completion events. Update channel identifiers before import. |
|
||||
| `templates/airgap-ops-email.template.json` | Email template used by the bootstrap rule with remediation guidance, checksum context, and download locations. |
|
||||
| `README.md` | This guide, also embedded in the pack for quick operator reference. |
|
||||
|
||||
## Usage
|
||||
|
||||
1. **Populate secrets** – copy `notify-web.secret.example` to
|
||||
`notify-web.secret`, change `NOTIFY_WEB_CLIENT_SECRET` to the value issued by
|
||||
Authority, and store it with restrictive permissions (for example
|
||||
`chmod 600`).
|
||||
2. **Drop configuration** – place `notify.yaml` in the location expected by
|
||||
the runtime (`/app/etc/notify.yaml` for the containers we ship). The file
|
||||
assumes MongoDB is reachable at `mongodb://stellaops:airgap-password@mongo:27017`
|
||||
and Authority at `https://authority.airgap.local` – adjust if your
|
||||
deployment uses different hostnames.
|
||||
3. **Import rule/template** – with the Notify CLI or REST API, import
|
||||
`templates/airgap-ops-email.template.json` first, then
|
||||
`rules/airgap-ops.rule.json`. Update the `channel` identifiers inside the
|
||||
rule so they match your sealed SMTP relay (for example `email:airgap-ops`).
|
||||
The rule now also delivers portable export completion notices; ensure your
|
||||
downstream process watches for checksum and location details in the payload.
|
||||
4. **Mount secrets/config** – for Docker Compose use:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./bootstrap/notify/notify.yaml:/app/etc/notify.yaml:ro
|
||||
env_file:
|
||||
- ./bootstrap/notify/notify-web.secret
|
||||
```
|
||||
|
||||
In Kubernetes, create a Secret from the two files and mount them into the
|
||||
Notifier pod.
|
||||
5. **Verify sealed mode** – with the configuration in place the Notifier
|
||||
resolves channels that point to local relays (SMTP, syslog, file sink). Any
|
||||
attempt to contact an external webhook is denied by `StellaOps.AirGap.Policy`
|
||||
with remediation guidance.
|
||||
|
||||
## How it is packaged
|
||||
|
||||
`ops/offline-kit/build_offline_kit.py` automatically copies the configuration
|
||||
and secret template into `bootstrap/notify/` during Offline Kit creation. The
|
||||
same staging directory is what we sign and publish as the Bootstrap Pack, so
|
||||
the artefacts stay deterministic across releases.
|
||||
|
||||
Refer to `etc/notify.airgap.yaml` if you need to regenerate the pack or build a
|
||||
site-specific overlay from source control.
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"eventId": "99b40f9c-4a34-4a5f-9e1a-0c1e5a97c0bb",
|
||||
"kind": "airgap.bundle.import",
|
||||
"version": "1",
|
||||
"tenant": "tenant-01",
|
||||
"ts": "2025-10-30T22:05:18+00:00",
|
||||
"actor": "airgap-importer",
|
||||
"payload": {
|
||||
"bundleId": "mirror-2025-10-30",
|
||||
"importedAt": "2025-10-30T22:02:03Z",
|
||||
"links": {
|
||||
"audit": "https://authority.airgap.local/authority/audit/airgap/entries/mirror-2025-10-30",
|
||||
"docs": "https://docs.stella-ops.org/airgap/airgap-mode.html#import"
|
||||
},
|
||||
"remediation": "Review bundle warnings and plan the next mirror export to keep advisories within the allowed freshness budget.",
|
||||
"severity": "medium",
|
||||
"status": "completed",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "feeds.outdated",
|
||||
"message": "OSV feed is 2 days old; schedule next mirror export sooner."
|
||||
}
|
||||
]
|
||||
},
|
||||
"attributes": {
|
||||
"importer": "ops:olivia",
|
||||
"category": "airgap"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"eventId": "6a9f4e37-5053-4a40-9761-5c0a0a857ee2",
|
||||
"kind": "airgap.portable.export.completed",
|
||||
"version": "1",
|
||||
"tenant": "tenant-01",
|
||||
"ts": "2025-11-02T21:45:12+00:00",
|
||||
"actor": "export-center",
|
||||
"payload": {
|
||||
"bundleId": "portable-2025-11-02",
|
||||
"checksum": {
|
||||
"sha256": "f4f56c7d9a68ee4d4c324e7d782f9e2a3c1c4b2fa83f39c7c4b5f9f047d3af11",
|
||||
"sha512": "c2d1b5ec4784f8cfe317aa7c501c7fd736b119f1d0d1eaa99bf3d6f4f70a85b7e699f356e2f3d3d8c536a8a6f2a506d1c2f458f8829b6f8d9c4abe0ac7edb5a1"
|
||||
},
|
||||
"exportedAt": "2025-11-02T21:43:05Z",
|
||||
"links": {
|
||||
"docs": "https://docs.stella-ops.org/airgap/portable-evidence.html",
|
||||
"manifest": "https://authority.airgap.local/export/bundles/portable-2025-11-02/export.json"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"availableUntil": "2025-11-09T00:00:00Z",
|
||||
"path": "/data/offline-kit/bootstrap/exports/portable/portable-2025-11-02.tar.zst",
|
||||
"type": "file"
|
||||
},
|
||||
{
|
||||
"reference": "registry.airgap.local/offline/portable@sha256:f4f56c7d9a68ee4d4c324e7d782f9e2a3c1c4b2fa83f39c7c4b5f9f047d3af11",
|
||||
"type": "oci"
|
||||
}
|
||||
],
|
||||
"profile": "portable-evidence:mirror-full",
|
||||
"remediation": "Distribute the portable evidence bundle to downstream enclaves before the freshness budget expires.",
|
||||
"severity": "medium",
|
||||
"sizeBytes": 73400320,
|
||||
"status": "completed"
|
||||
},
|
||||
"attributes": {
|
||||
"profile": "portable-evidence",
|
||||
"category": "airgap"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"eventId": "5cf2d3b2-9b90-4f7d-9a9a-54b8b5a5e3aa",
|
||||
"kind": "airgap.time.drift",
|
||||
"version": "1",
|
||||
"tenant": "tenant-01",
|
||||
"ts": "2025-10-31T06:15:00+00:00",
|
||||
"actor": "airgap-time",
|
||||
"payload": {
|
||||
"anchorId": "anchor-2025-10-28",
|
||||
"anchorIssuedAt": "2025-10-28T05:00:00Z",
|
||||
"budgetSeconds": 259200,
|
||||
"driftSeconds": 216000,
|
||||
"links": {
|
||||
"docs": "https://docs.stella-ops.org/airgap/staleness-and-time.html"
|
||||
},
|
||||
"nextAnchorDueBy": "2025-11-01T05:00:00Z",
|
||||
"remainingSeconds": 43200,
|
||||
"remediation": "Import the latest mirror bundle to refresh the time anchor or extend the budget via policy override before sealed mode blocks scheduled jobs.",
|
||||
"severity": "high",
|
||||
"status": "critical"
|
||||
},
|
||||
"attributes": {
|
||||
"category": "airgap",
|
||||
"anchorBudgetHours": "72"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user