feat: Add DigestUpsertRequest and LockEntity models
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
- Introduced DigestUpsertRequest for handling digest upsert requests with properties like ChannelId, Recipient, DigestKey, Events, and CollectUntil. - Created LockEntity to represent a lightweight distributed lock entry with properties such as Id, TenantId, Resource, Owner, ExpiresAt, and CreatedAt. feat: Implement ILockRepository interface and LockRepository class - Defined ILockRepository interface with methods for acquiring and releasing locks. - Implemented LockRepository class with methods to try acquiring a lock and releasing it, using SQL for upsert operations. feat: Add SurfaceManifestPointer record for manifest pointers - Introduced SurfaceManifestPointer to represent a minimal pointer to a Surface.FS manifest associated with an image digest. feat: Create PolicySimulationInputLock and related validation logic - Added PolicySimulationInputLock record to describe policy simulation inputs and expected digests. - Implemented validation logic for policy simulation inputs, including checks for digest drift and shadow mode requirements. test: Add unit tests for ReplayVerificationService and ReplayVerifier - Created ReplayVerificationServiceTests to validate the behavior of the ReplayVerificationService under various scenarios. - Developed ReplayVerifierTests to ensure the correctness of the ReplayVerifier logic. test: Implement PolicySimulationInputLockValidatorTests - Added tests for PolicySimulationInputLockValidator to verify the validation logic against expected inputs and conditions. chore: Add cosign key example and signing scripts - Included a placeholder cosign key example for development purposes. - Added a script for signing Signals artifacts using cosign with support for both v2 and v3. chore: Create script for uploading evidence to the evidence locker - Developed a script to upload evidence to the evidence locker, ensuring required environment variables are set.
This commit is contained in:
36
docs/airgap/av-report.schema.json
Normal file
36
docs/airgap/av-report.schema.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://stellaops.local/airgap/av-report.schema.json",
|
||||
"title": "Offline AV/YARA Scan Report",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["scanner", "scannerVersion", "startedAt", "completedAt", "status", "artifacts"],
|
||||
"properties": {
|
||||
"scanner": { "type": "string" },
|
||||
"scannerVersion": { "type": "string" },
|
||||
"startedAt": { "type": "string", "format": "date-time" },
|
||||
"completedAt": { "type": "string", "format": "date-time" },
|
||||
"status": { "type": "string", "enum": ["clean", "findings", "error"] },
|
||||
"signature": { "type": "string", "description": "Optional detached signature over this report (base64)" },
|
||||
"artifacts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["path", "sha256", "result"],
|
||||
"properties": {
|
||||
"path": { "type": "string" },
|
||||
"sha256": { "type": "string", "pattern": "^[A-Fa-f0-9]{64}$" },
|
||||
"result": { "type": "string", "enum": ["clean", "suspicious", "malicious", "error"] },
|
||||
"yaraRules": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
||||
"notes": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"errors": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
"environment",
|
||||
"createdAt",
|
||||
"stalenessWindowHours",
|
||||
"replayPolicy",
|
||||
"tools",
|
||||
"feeds",
|
||||
"policies",
|
||||
@@ -24,6 +25,7 @@
|
||||
"environment": { "type": "string", "enum": ["prod", "stage", "dev", "test"] },
|
||||
"createdAt": { "type": "string", "format": "date-time" },
|
||||
"stalenessWindowHours": { "type": "integer", "minimum": 0 },
|
||||
"replayPolicy": { "type": "string", "enum": ["hash-only", "full-recompute", "policy-freeze"] },
|
||||
"tools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
55
docs/airgap/receipt.schema.json
Normal file
55
docs/airgap/receipt.schema.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://stellaops.local/airgap/receipt.schema.json",
|
||||
"title": "AirGap Ingress/Egress Receipt",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
"receiptId",
|
||||
"direction",
|
||||
"bundleId",
|
||||
"tenant",
|
||||
"operator",
|
||||
"occurredAt",
|
||||
"decision",
|
||||
"hashes"
|
||||
],
|
||||
"properties": {
|
||||
"schemaVersion": { "type": "string", "pattern": "^1\\.\\d+\\.\\d+$" },
|
||||
"receiptId": { "type": "string", "pattern": "^receipt:[A-Za-z0-9._:-]+$" },
|
||||
"direction": { "type": "string", "enum": ["ingress", "egress"] },
|
||||
"bundleId": { "type": "string", "pattern": "^offline-kit:[A-Za-z0-9._:-]+$" },
|
||||
"tenant": { "type": "string", "minLength": 1 },
|
||||
"operator": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id", "role"],
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"role": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"occurredAt": { "type": "string", "format": "date-time" },
|
||||
"decision": { "type": "string", "enum": ["allow", "deny", "quarantine"] },
|
||||
"hashes": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["bundleSha256", "manifestSha256"],
|
||||
"properties": {
|
||||
"bundleSha256": { "type": "string", "pattern": "^[A-Fa-f0-9]{64}$" },
|
||||
"manifestSha256": { "type": "string", "pattern": "^[A-Fa-f0-9]{64}$" }
|
||||
}
|
||||
},
|
||||
"dsse": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"envelopeDigest": { "type": "string", "pattern": "^sha256:[A-Fa-f0-9]{64}$" },
|
||||
"signer": { "type": "string" },
|
||||
"rekorUuid": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"notes": { "type": "string" }
|
||||
}
|
||||
}
|
||||
60
docs/airgap/runbooks/av-scan.md
Normal file
60
docs/airgap/runbooks/av-scan.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# AV/YARA Scan Runbook (AIRGAP-AV-510-011)
|
||||
|
||||
Purpose: ensure every offline-kit bundle is scanned pre-publish and post-ingest, with deterministic reports and optional signatures.
|
||||
|
||||
## Inputs
|
||||
- Bundle directory containing `manifest.json` and payload files.
|
||||
- AV scanner (e.g., ClamAV) and optional YARA rule set available locally (no network).
|
||||
|
||||
## Steps (offline)
|
||||
1. Scan all bundle files:
|
||||
```bash
|
||||
clamscan -r --max-filesize=2G --max-scansize=4G --no-summary bundle/ > reports/av-scan.txt
|
||||
```
|
||||
2. Convert to structured report:
|
||||
```bash
|
||||
python - <<'PY'
|
||||
import hashlib, json, pathlib, sys
|
||||
root = pathlib.Path("bundle")
|
||||
report = {
|
||||
"scanner": "clamav",
|
||||
"scannerVersion": "1.4.1",
|
||||
"startedAt": "2025-12-02T00:02:00Z",
|
||||
"completedAt": "2025-12-02T00:04:30Z",
|
||||
"status": "clean",
|
||||
"artifacts": [],
|
||||
"errors": []
|
||||
}
|
||||
for path in root.glob("**/*"):
|
||||
if path.is_file():
|
||||
h = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
report["artifacts"].append({
|
||||
"path": str(path.relative_to(root)),
|
||||
"sha256": h,
|
||||
"result": "clean",
|
||||
"yaraRules": []
|
||||
})
|
||||
json.dump(report, sys.stdout, indent=2)
|
||||
PY
|
||||
```
|
||||
3. Validate report against schema:
|
||||
```bash
|
||||
jq empty --argfile schema docs/airgap/av-report.schema.json 'input' < docs/airgap/samples/av-report.sample.json >/dev/null
|
||||
```
|
||||
4. Optionally sign report (detached):
|
||||
```bash
|
||||
openssl dgst -sha256 -sign airgap-av-key.pem reports/av-report.json > reports/av-report.sig
|
||||
```
|
||||
5. Update `manifest.json`:
|
||||
- Set `avScan.status` to `clean` or `findings`.
|
||||
- `avScan.reportPath` and `avScan.reportSha256` must match the generated report.
|
||||
|
||||
## Acceptance checks
|
||||
- Report validates against `docs/airgap/av-report.schema.json`.
|
||||
- `manifest.json` hashes updated and verified via `src/AirGap/scripts/verify-manifest.sh`.
|
||||
- If any artifact result is `malicious`/`suspicious`, bundle must be rejected and re-scanned after remediation.
|
||||
|
||||
## References
|
||||
- Manifest schema: `docs/airgap/manifest.schema.json`
|
||||
- Sample report: `docs/airgap/samples/av-report.sample.json`
|
||||
- Manifest verifier: `src/AirGap/scripts/verify-manifest.sh`
|
||||
@@ -1,40 +1,57 @@
|
||||
# Offline Kit Import Verification Runbook
|
||||
|
||||
This runbook supports AIRGAP-MANIFEST-510-010/014. It validates bundle integrity before import, fully offline.
|
||||
This runbook supports AIRGAP-MANIFEST-510-010, AIRGAP-REPLAY-510-013, and AIRGAP-VERIFY-510-014. It validates bundles fully offline and enforces replay depth.
|
||||
|
||||
## Inputs
|
||||
- Manifest: `offline-kit/manifest.json`
|
||||
- Bundle archive: e.g., `offline-kit/bundle.tar.gz`
|
||||
- Optional DSSE/JWS signature + public key for the manifest.
|
||||
## Replay depth levels (manifest `replayPolicy`)
|
||||
- `hash-only`: verify manifest/bundle digests, staleness window, optional signature.
|
||||
- `full-recompute`: hash-only + every chunk hash + AV report hash.
|
||||
- `policy-freeze`: full-recompute + manifest policies must include the sealed policy hash (prevents imports with drifting policy/graph material).
|
||||
|
||||
## Quick steps (offline)
|
||||
## Quick steps
|
||||
|
||||
```bash
|
||||
src/AirGap/scripts/verify-manifest.sh offline-kit/manifest.json offline-kit/bundle.tar.gz \
|
||||
offline-kit/manifest.sig offline-kit/manifest.pub.pem
|
||||
src/AirGap/scripts/verify-kit.sh \
|
||||
--manifest offline-kit/manifest.json \
|
||||
--bundle offline-kit/bundle.tar.gz \
|
||||
--signature offline-kit/manifest.sig --pubkey offline-kit/manifest.pub.pem \
|
||||
--av-report offline-kit/reports/av-report.json \
|
||||
--receipt offline-kit/receipts/ingress.json \
|
||||
--sealed-policy-hash "aa55..." \
|
||||
--depth policy-freeze
|
||||
```
|
||||
|
||||
What it does:
|
||||
1. Computes SHA-256 of manifest and bundle, compares with `hashes.manifestSha256` and `hashes.bundleSha256`.
|
||||
2. If signature + pubkey are provided, verifies the manifest signature with OpenSSL.
|
||||
## What the script enforces
|
||||
1) Manifest & bundle digests match (`hashes.*`).
|
||||
2) Optional manifest signature is valid (OpenSSL).
|
||||
3) Staleness: `createdAt` must be within `stalenessWindowHours` of `--now` (defaults to UTC now).
|
||||
4) AV: `avScan.status` must not be `findings`; if `reportSha256` is present, the provided report hash must match.
|
||||
5) Chunks (full-recompute/policy-freeze): every `chunks[].path` exists relative to the manifest and matches its recorded SHA-256.
|
||||
6) Policy-freeze: `--sealed-policy-hash` must appear in `policies[].sha256`.
|
||||
7) Optional: `--expected-graph-sha` checks the graph chunk hash; `--receipt` reuses `verify-receipt.sh` to bind the receipt to the manifest/bundle hashes.
|
||||
|
||||
## Expected manifest fields
|
||||
- `tools[]`, `feeds[]`, `policies[]` with SHA-256.
|
||||
- `chunks[]` entries for every payload file (path, sha256, size, kind).
|
||||
- `stalenessWindowHours` and `avScan` status.
|
||||
- `hashes.manifestSha256` and `hashes.bundleSha256` must match the files on disk.
|
||||
- Optional `signatures[]` (dsse/jws-detached) with `envelopeDigest`.
|
||||
Exit codes: hash mismatch (3/4), staleness (5), AV issues (6–8), chunk drift (9–10), graph mismatch (11), policy drift (12–13), bad depth (14).
|
||||
|
||||
## Failure handling
|
||||
- Hash mismatch → stop; regenerate bundle.
|
||||
- Signature failure → stop; re-validate trust roots.
|
||||
- Missing AV scan → treat as policy violation; rerun scans and update manifest.
|
||||
## Controller verify endpoint (server-side guard)
|
||||
|
||||
## Outputs
|
||||
- Exit 0 when all checks pass.
|
||||
- Exit 2–5 for missing tools/hash/signature verification issues (see script).
|
||||
`POST /system/airgap/verify` (scope `airgap:verify`) expects `VerifyRequest`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"depth": "PolicyFreeze",
|
||||
"manifestSha256": "...",
|
||||
"bundleSha256": "...",
|
||||
"computedManifestSha256": "...", // from offline verifier
|
||||
"computedBundleSha256": "...",
|
||||
"manifestCreatedAt": "2025-12-02T00:00:00Z",
|
||||
"stalenessWindowHours": 168,
|
||||
"bundlePolicyHash": "aa55...",
|
||||
"sealedPolicyHash": "aa55..." // optional, controller fills from state if omitted
|
||||
}
|
||||
```
|
||||
|
||||
The controller applies the same replay rules and returns `{ "valid": true|false, "reason": "..." }`.
|
||||
|
||||
## References
|
||||
- Schema: `docs/airgap/manifest.schema.json`
|
||||
- Sample: `docs/airgap/samples/offline-kit-manifest.sample.json`
|
||||
- Script: `src/AirGap/scripts/verify-manifest.sh`
|
||||
- Samples: `docs/airgap/samples/offline-kit-manifest.sample.json`, `docs/airgap/samples/av-report.sample.json`, `docs/airgap/samples/receipt.sample.json`
|
||||
- Scripts: `src/AirGap/scripts/verify-kit.sh`, `src/AirGap/scripts/verify-manifest.sh`, `src/AirGap/scripts/verify-receipt.sh`
|
||||
|
||||
23
docs/airgap/samples/av-report.sample.json
Normal file
23
docs/airgap/samples/av-report.sample.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "../av-report.schema.json",
|
||||
"scanner": "clamav",
|
||||
"scannerVersion": "1.4.1",
|
||||
"startedAt": "2025-12-02T00:02:00Z",
|
||||
"completedAt": "2025-12-02T00:04:30Z",
|
||||
"status": "clean",
|
||||
"artifacts": [
|
||||
{
|
||||
"path": "chunks/advisories-0001.tzst",
|
||||
"sha256": "1234123412341234123412341234123412341234123412341234123412341234",
|
||||
"result": "clean",
|
||||
"yaraRules": []
|
||||
},
|
||||
{
|
||||
"path": "chunks/vex-0001.tzst",
|
||||
"sha256": "4321432143214321432143214321432143214321432143214321432143214321",
|
||||
"result": "clean",
|
||||
"yaraRules": []
|
||||
}
|
||||
],
|
||||
"errors": []
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
"environment": "prod",
|
||||
"createdAt": "2025-12-02T00:00:00Z",
|
||||
"stalenessWindowHours": 168,
|
||||
"replayPolicy": "policy-freeze",
|
||||
"tools": [
|
||||
{ "name": "concelier-exporter", "version": "2.5.0", "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcd" },
|
||||
{ "name": "trivy-db", "version": "0.48.0", "sha256": "89abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567" }
|
||||
|
||||
21
docs/airgap/samples/receipt.sample.json
Normal file
21
docs/airgap/samples/receipt.sample.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "../receipt.schema.json",
|
||||
"schemaVersion": "1.0.0",
|
||||
"receiptId": "receipt:ingress:2025-12-02T00-00Z",
|
||||
"direction": "ingress",
|
||||
"bundleId": "offline-kit:concelier:2025-12-02",
|
||||
"tenant": "default",
|
||||
"operator": { "id": "op-123", "role": "airgap-controller" },
|
||||
"occurredAt": "2025-12-02T00:06:00Z",
|
||||
"decision": "allow",
|
||||
"hashes": {
|
||||
"bundleSha256": "d3c3f6c75c6a3f0906bcee457cc77a2d6d7c0f9d1a1d7da78c0d2ab8e0dba111",
|
||||
"manifestSha256": "29d58b9fdc5c4e65b26c03f3bd9f442ff0c7f8514b8a9225f8b6417ffabc0101"
|
||||
},
|
||||
"dsse": {
|
||||
"envelopeDigest": "sha256:cc77cc77cc77cc77cc77cc77cc77cc77cc77cc77cc77cc77cc77cc77cc77cc77",
|
||||
"signer": "airgap-receipts-dev",
|
||||
"rekorUuid": "11111111-2222-3333-4444-555555555555"
|
||||
},
|
||||
"notes": "Ingress verified, AV clean, staleness within window."
|
||||
}
|
||||
Reference in New Issue
Block a user