33 KiB
Here’s a practical, copy‑pasteable deployment reference for getting DSSE/attestations through your gateway, packaging air‑gap kits, and running canary/blue‑green with end‑to‑end tracing in StellaOps’ SBOM → VEX pipeline.
1) Mental model (plain English)
- SBOM→VEX spine: Sbomer (produces SBOM) → Scanner (finds vulns + reachability) → Concelier (feeds & policy merge) → Excititor/Vexer (VEX verdicts) → Authority (signing/attestations) → Scheduler (rollouts) → Proof Graph (evidence store).
- DSSE (in‑toto envelope): sign statements (SBOMs, scan results, VEX) not just blobs; each hop emits an attestation.
- Gateway: all outbound (online) and inbound (offline kit ingestion) goes via NGINX/WAF with mTLS and header‑level provenance.
- Determinism: every artifact has a content hash; every decision has a reproducible Policy ID and Graph Revision ID.
2) Gateway traversal: DSSE/attestation flow
2.1 Required headers & mTLS
-
Client auth: mTLS with client cert mapped to
tenant_id+env. -
Provenance headers (added by internal callers or sidecar):
X-Stella-TraceId,X-Stella-SpanIdX-Stella-Graph-Rev(hash of dependency graph)X-Stella-Policy-Id(hash of lattice/policy bundle)X-Stella-DSSE-SHA256(hash of DSSE payload)X-Stella-Att-Chain(comma list: sbom,scan,vex)
NGINX/WAF snippet
map $ssl_client_s_dn $stella_tenant { default "unknown"; ~CN=(?<cn>[^/]+) $cn; }
map $http_x_stella_traceid $trace_id { default $request_id; }
server {
listen 443 ssl;
ssl_certificate /etc/nginx/certs/gw.crt;
ssl_certificate_key /etc/nginx/certs/gw.key;
ssl_client_certificate /etc/nginx/certs/ca.crt;
ssl_verify_client on;
# Block if required DSSE headers missing on protected routes
if ($request_uri ~* ^/(api/(scan|vex|attest))) {
if ($http_x_stella_dsse_sha256 = "") { return 400; }
if ($http_x_stella_policy_id = "") { return 400; }
}
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Request-Id $trace_id;
proxy_set_header X-Stella-Tenant $stella_tenant;
proxy_pass http://stella_core_upstream;
}
3) DSSE signing & bundling (online and air‑gap)
3.1 Canonical DSSE statement shapes (JSON)
SBOM attestation
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "image:repo/app:1.4.2", "digest": {"sha256": "<OCI_DIGEST>"}}],
"predicateType": "https://stella-ops.org/sbom@v1",
"predicate": {
"format": "CycloneDX@1.6",
"bomDigest": "sha256:<BOM_SHA>",
"sources": ["sbomer:3.2.0"],
"created": "2025-11-28T09:00:00Z"
}
}
Scan attestation
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "image:repo/app:1.4.2","digest":{"sha256":"<OCI_DIGEST>"}}],
"predicateType": "https://stella-ops.org/scan@v1",
"predicate": {
"engine": "scanner.webservice:6.0",
"reachability": "enabled",
"findingsDigest": "sha256:<FINDINGS_SHA>",
"graphRev": "<GRAPH_REV>"
}
}
VEX attestation
{
"_type":"https://in-toto.io/Statement/v1",
"subject":[{"name":"image:repo/app:1.4.2","digest":{"sha256":"<OCI_DIGEST>"}}],
"predicateType":"https://stella-ops.org/vex@v1",
"predicate":{
"format":"OpenVEX@1.0",
"policyId":"<POLICY_ID>",
"verdictsDigest":"sha256:<VEX_SHA>",
"explainabilityDigest":"sha256:<XPL_SHA>"
}
}
3.2 .NET 10 signing helper (Authority client)
// StellaOps.Authority.Client
var stmt = JsonDocument.Parse(File.ReadAllText(inputPath));
var sig = await authority.SignDsseAsync(
statement: stmt,
keyRef: "authority:kms:default",
annotations: new() {
["graphRev"] = graphRev,
["policyId"] = policyId
});
await File.WriteAllTextAsync(outputPath, sig.EnvelopeJson);
3.3 Air‑gap “offline kit” layout
offline-kit/
manifest.yaml
keys/
trust-root.pem # Root/anchor(s) (FIPS/EIDAS/GOST if needed)
offline-signer.key # Encrypted PKCS#8 (no plaintext keys in git)
artifacts/
image_app_1.4.2.oci.tgz # air‑gapped OCI save
sbom.cdx.json
scan.findings.json
vex.openvex.json
attestations/
sbom.dsse.json
scan.dsse.json
vex.dsse.json
indexes/
rekor-mirror/ # optional signed index (CAR/SQLite)
manifest.yaml
version: 1
tenant: bulstrad
env: prod
graph_rev: "<GRAPH_REV>"
policy_id: "<POLICY_ID>"
artifacts:
- name: "image:repo/app:1.4.2"
digest: "sha256:<OCI_DIGEST>"
files:
sbom: "artifacts/sbom.cdx.json"
scan: "artifacts/scan.findings.json"
vex: "artifacts/vex.openvex.json"
attestations:
- type: sbom
file: "attestations/sbom.dsse.json"
- type: scan
file: "attestations/scan.dsse.json"
- type: vex
file: "attestations/vex.dsse.json"
crypto:
profile: "FIPS" # or EIDAS/GOST/SM
pq_mode: false # enable if Dilithium/Falcon required
3.4 Building the kit (CI example)
# Save OCI image for air‑gap
ctr -n k8s.io images export artifacts/image_app_1.4.2.oci.tgz repo/app:1.4.2
# Produce SBOM, scan, VEX (deterministic order & hashing)
sbomer gen repo/app:1.4.2 > artifacts/sbom.cdx.json
scanner reach repo/app:1.4.2 --graph-rev "$GRAPH_REV" > artifacts/scan.findings.json
vexer make --policy "$POLICY_ID" artifacts/scan.findings.json > artifacts/vex.openvex.json
# Sign via Authority
stella authority sign-dsse artifacts/sbom.cdx.json > attestations/sbom.dsse.json
stella authority sign-dsse artifacts/scan.findings.json > attestations/scan.dsse.json
stella authority sign-dsse artifacts/vex.openvex.json > attestations/vex.dsse.json
# Final bundle
tar -C offline-kit -czf offline-kit.app-1.4.2.tgz .
4) CI/CD patterns (canary, blue‑green) with VEX gating
4.1 Policy gate (pipeline step)
- Fail if: VEX verdict contains any
Affectedwithseverity ≥ Highandreachability=reachableAND no runtime mitigation present. - Warn if:
Reachability=unknownbutcompensatingControl=true.
# .gitlab-ci.yml / azure-pipelines.yml analog
stages: [build, attest, gate, deploy]
gate:vex:
stage: gate
image: stellaops/cli:latest
script:
- stella vex gate \
--vex attestations/vex.dsse.json \
--policy-id "$POLICY_ID" \
--deny "affected && reachable && severity>=high && !mitigated"
4.2 Canary → Blue‑Green rollout (Kubernetes)
# canary VirtualService (Istio) – start 5% traffic
spec:
http:
- route:
- destination: {host: app-green, subset: v1} # stable
weight: 95
- destination: {host: app-blue, subset: v2} # canary
weight: 5
Promotion logic
- Deploy blue with labels
graphRev,policyId,traceSeed. - Inject sidecar that emits DSSE header echoes and OpenTelemetry spans.
- Watch SLOs (p95 latency, error rate) + Security SLO: “no new reachable High/Critical since trace start”.
- If good for N minutes → shift 5→25→50→100; else rollback to green.
4.3 Tracing: what to emit
-
Trace span attributes:
stella.graph_rev,stella.policy_id,stella.att_chain="sbom,scan,vex"stella.image_digest,stella.env,stella.tenantstella.proof_ref(URI into Proof‑of‑Integrity Graph)
-
Log/Span event on gate decisions:
vex.decision=allow|deny|warnvex.reasons=[CVE‑..., rule:... ]vex.proof_hash=sha256:...
Minimal .NET 10 OpenTelemetry wiring:
builder.Services.AddOpenTelemetry()
.WithTracing(t => t
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("StellaOps.*")
.SetResourceBuilder(ResourceBuilder.CreateDefault()
.AddService("stella.app")
.AddAttributes(new []
{
new KeyValuePair<string, object>("stella.graph_rev", graphRev),
new KeyValuePair<string, object>("stella.policy_id", policyId)
}))
.AddOtlpExporter());
5) Air‑gap ingestion (customer side)
5.1 Verify, import, and replay
# Verify DSSE envelopes against trust-root
stella authority verify offline-kit/attestations/*.dsse.json --trust-root offline-kit/keys/trust-root.pem
# Import into local mirrors
stella mirror import offline-kit/indexes/rekor-mirror
stella registry load artifacts/image_app_1.4.2.oci.tgz
# Replay scan & decisions for audit (deterministic)
stella replay --manifest offline-kit/manifest.yaml --strict
Expected outputs
Proof-Graphnode linking image digest → SBOM → Scan → VEX with signature chain.- A Receipt (JSON) recording hashes + tool versions for audit.
6) DSSE/attestation checklist (use in PRs)
Build & Sign
- SBOM generated (CycloneDX 1.6), hash recorded
- Scan findings (reachability ON), hash recorded
- VEX derived via lattice policy,
policy_idlocked - All three DSSE‑signed by Authority
- Rekor (or mirror) entry created or queued (if air‑gapped)
Gateway Compliance
- mTLS client cert validated → tenant/env mapped
- Required DSSE headers present
- TraceId propagated to all services
Determinism
- Graph Revision ID logged in traces + headers
- Same inputs → identical verdicts on replay
Deployment
- VEX gate step passes with deny‑rules configured
- Canary 5% with Security SLO watch
- Auto‑promote to blue‑green if SLOs met
Air‑Gap
- Offline kit contains: SBOM, Scan, VEX, DSSEs, trust root, OCI tar
- Manifest.yaml present with policy_id + graph_rev
- Verify → Import → Replay documented for customer
7) Reference: CI fragments you can drop in today
7.1 Make targets
GRAPH_REV := $(shell stella graph rev ./)
POLICY_ID := $(shell stella policy id ./policies)
kit:
mkdir -p offline-kit/{artifacts,attestations,keys,indexes}
sbomer gen $(IMG) > offline-kit/artifacts/sbom.cdx.json
scanner reach $(IMG) --graph-rev $(GRAPH_REV) > offline-kit/artifacts/scan.findings.json
vexer make --policy $(POLICY_ID) offline-kit/artifacts/scan.findings.json > offline-kit/artifacts/vex.openvex.json
stella authority sign-dsse offline-kit/artifacts/sbom.cdx.json > offline-kit/attestations/sbom.dsse.json
stella authority sign-dsse offline-kit/artifacts/scan.findings.json > offline-kit/attestations/scan.dsse.json
stella authority sign-dsse offline-kit/artifacts/vex.openvex.json > offline-kit/attestations/vex.dsse.json
stella offline manifest --graph-rev $(GRAPH_REV) --policy-id $(POLICY_ID) > offline-kit/manifest.yaml
tar -C offline-kit -czf offline-kit.$(VERSION).tgz .
7.2 K8s labels/annotations to keep proofs attached
metadata:
labels:
stella.graph_rev: "<GRAPH_REV>"
stella.policy_id: "<POLICY_ID>"
annotations:
stella.att_chain: "sbom,scan,vex"
stella.proof_ref: "proofgraph://images/<OCI_DIGEST>"
8) Tricky edges & how to handle them
- Huge envelopes (Rekor v2 limits): chunk large predicates (scan findings) and include
findingsDigestin DSSE; store full findings in artifact store, only digest in log. - “Unknown” reachability: gate as warn but require runtime sensor in canary; promote only if no new reachable High/Critical appears.
- Crypto sovereignty: select
crypto.profileper client (FIPS, EIDAS, GOST, SM). For PQ mode, sign dual:(ECDSA,PQ)and record both in Receipt. - Repeatability: pin tool versions; include
--seedfor any randomized analysis; freeze feeds via Concelier snapshot IDs in the kit.
9) What to ask your team to implement next (small, shippable)
- Headers at the gateway (block missing DSSE fields on protected APIs).
- Authority client in .NET 10 with
SignDsseAsync()andVerifyAsync(). - Make target
kitproducing the offline bundle with manifest. - VEX gate step wired into CI with clear deny rules.
- Istio canary YAML + OpenTelemetry attributes emitting
graph_rev/policy_id. - Replay command that re‑computes decisions and compares hashes (audit).
If you want, I can convert this into a ready‑to‑commit docs/deploy/airgap-playbook.md plus a samples/offline-kit/ skeleton and a minimal .gitlab-ci.yml in your repo.
Understood. I’ll treat this as a full implementation brief you can drop into your repo and hand to: backend devs, DevOps, security, and docs.
I’ll structure it as if it lived in:
docs/deploy/attestations-and-airgap.md- plus references to code locations (e.g.
src/StellaOps.Authority.Client/etc.)
You can forward this directly to the team.
0. Scope and objectives
0.1 What we are building
End-to-end, deterministic, attestable flow:
-
SBOM → Scanner → VEX for each image.
-
Each step emits a DSSE attestation signed by Authority.
-
Attestations are:
- Logged online (Rekor v2) or
- Bundled in an offline kit for air-gapped environments.
-
CI/CD uses these attestations to:
- Gate deployments (block if risky)
- Run canary → blue-green with security SLOs.
-
All of this is traceable via:
graphRev(Graph Revision ID)policyId(Lattice / policy bundle ID)- Proof-of-Integrity Graph.
0.2 Non-negotiable invariants
-
Lattice algorithms run in scanner.webservice, not in Concelier or Vexer/Excititor.
-
Same inputs → same graph → same VEX verdicts → same hashes.
-
No unsigned SBOM/scan/VEX artifacts are consumed by gates.
-
Every deployment points to:
- A specific image digest,
- A specific graph revision,
- A specific policy bundle.
1. Core concepts & identifiers
1.1 Graph Revision ID (graphRev)
Definition: graphRev is a deterministic SHA-256 hash over the canonical dependency graph structure and all feed versions that affected the scan.
Implementation (Scanner):
-
Scanner builds a canonical JSON of the graph:
- Sorted node list (by package name + version)
- Sorted edges
- Feed snapshot IDs (from Concelier)
- Tool versions (
scannerVersion,sbomerVersion)
-
Serialize with:
- Sorted keys
- No whitespace
-
Compute
graphRev = SHA256(canonicalJson)as hex string.
Store:
-
Persist in Scanner DB record:
ScanResult.GraphRev -
Return in Scan API.
-
Include in:
- Scan predicate
- VEX predicate
- Deployment labels/annotations
- DSSE annotations.
1.2 Policy ID (policyId)
Definition: deterministic SHA-256 hash over the policy/lattice configuration that produced VEX decisions.
Responsibility:
- Concelier owns policy/lattice bundles and publishes a policy snapshot.
- Vexer uses the snapshot referenced in Scanner’s results, it does not recompute the lattice.
Computation (Concelier):
-
Canonical JSON of:
- Rule files (normalized and sorted)
- Lattice definitions
- Region profile
-
policyId = SHA256(canonicalPolicyJson).
Consumers:
- Vexer only reads the pre-evaluated lattice result ID provided by Scanner.
- VEX predicate includes
policyId. - CI gate uses
policyIdto know exactly what policy produced a verdict.
2. Data contracts (JSON / YAML)
2.1 In-toto Statement base
All Stella statements are in-toto v1:
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "image:repo/app:1.4.2",
"digest": { "sha256": "<OCI_DIGEST>" }
}
],
"predicateType": "https://stella-ops.org/<kind>@v1",
"predicate": { }
}
Allowed predicateType values:
https://stella-ops.org/sbom@v1https://stella-ops.org/scan@v1https://stella-ops.org/vex@v1
2.1.1 SBOM predicate
{
"format": "CycloneDX@1.6",
"bomDigest": "sha256:<BOM_SHA>",
"sources": [ "sbomer:3.2.0" ],
"created": "2025-11-28T09:00:00Z"
}
SBOM JSON itself is separate, referenced via bomDigest.
2.1.2 Scan predicate
Scanner is the only component that runs lattice logic.
{
"engine": "scanner.webservice:6.0.0",
"reachability": "enabled",
"findingsDigest": "sha256:<FINDINGS_SHA>",
"graphRev": "<GRAPH_REV>",
"feedSnapshotId": "<CONCELIER_FEED_SNAPSHOT_ID>",
"latticeEvalId": "<LATTICE_EVAL_ID>"
}
findingsDigest: hash of canonical scan findings JSON.latticeEvalId: ID of the internal lattice evaluation record that Vexer will rely on (no recomputation).
2.1.3 VEX predicate (OpenVEX)
{
"format": "OpenVEX@1.0",
"policyId": "<POLICY_ID>",
"verdictsDigest": "sha256:<VEX_SHA>",
"explainabilityDigest": "sha256:<XPL_SHA>",
"graphRev": "<GRAPH_REV>",
"latticeEvalId": "<LATTICE_EVAL_ID>"
}
verdictsDigest: canonical OpenVEX doc hash.explainabilityDigest: hash of a separate explainability JSON (rationale, rules fired, etc., generated from latticeEval).
2.2 DSSE envelope
Authority returns a DSSE envelope:
{
"payloadType": "application/vnd.in-toto+json",
"payload": "<base64url(in-toto-statement-json)>",
"signatures": [
{
"keyid": "<KEY_ID>",
"sig": "<base64url(signature)>"
}
]
}
Annotations like graphRev & policyId live in:
- Rekor entry (log metadata), and/or
- Authority-side audit record.
2.3 Offline kit manifest
offline-kit/manifest.yaml:
version: 1
tenant: bulstrad
env: prod
image:
name: "image:repo/app:1.4.2"
digest: "sha256:<OCI_DIGEST>"
graph_rev: "<GRAPH_REV>"
policy_id: "<POLICY_ID>"
artifacts:
sbom: "artifacts/sbom.cdx.json"
scan: "artifacts/scan.findings.json"
vex: "artifacts/vex.openvex.json"
attestations:
sbom: "attestations/sbom.dsse.json"
scan: "attestations/scan.dsse.json"
vex: "attestations/vex.dsse.json"
feeds:
concelier_feed_snapshot_id: "<CONCELIER_FEED_SNAPSHOT_ID>"
crypto:
profile: "FIPS" # or EIDAS/GOST/SM
pq_mode: false # Dilithium/Falcon dual-sign when true
2.4 Replay receipt
A replay run must produce a receipt.json:
{
"schemaVersion": 1,
"imageDigest": "sha256:<OCI_DIGEST>",
"graphRev": "<GRAPH_REV>",
"policyId": "<POLICY_ID>",
"sbomDigest": "sha256:<BOM_SHA>",
"scanFindingsDigest": "sha256:<FINDINGS_SHA>",
"vexDigest": "sha256:<VEX_SHA>",
"tools": {
"sbomer": "3.2.0",
"scanner": "6.0.0",
"vexer": "2.1.0",
"concelier": "5.4.0"
},
"feeds": {
"concelierFeedSnapshotId": "<CONCELIER_FEED_SNAPSHOT_ID>"
},
"status": "ok" # or "mismatch"
}
Acceptance: replay is successful only if all digests match.
3. Service responsibilities
3.1 Sbomer
Code location: src/StellaOps.Sbomer/
Responsibilities:
-
Generate CycloneDX SBOM for:
- OCI images
- Local filesystem / rootfs
-
Guarantee deterministic output:
- Sorted components
- Stable IDs
-
Return:
- SBOM JSON
bomDigest(SHA-256)
-
Emit SBOM in:
- File store (for offline kit)
- Optional API.
Required changes:
-
Add endpoint:
POST /api/sbom/image- Input:
{ "image": "repo/app:1.4.2" } - Output:
sbomJson+bomDigest.
- Input:
-
Add CLI:
sbomer gen <image>→stdoutSBOM JSON.
3.2 Scanner.webservice
Code: src/StellaOps.Scanner.WebService/
Responsibilities:
-
Take SBOM & feeds snapshot from Concelier.
-
Build canonical dependency graph.
-
Run:
- Vuln matching
- Reachability analysis
- Lattice evaluation (only here).
-
Issue:
- Scan findings JSON
findingsDigestgraphRevlatticeEvalIdfeedSnapshotId
-
Call Authority to sign DSSE scan statement.
API:
-
POST /api/scan/image-
Input:
image, optionalsbomorsbomRef. -
Output:
scanFindingsJsonscanStatement(in-toto)scanDsseEnvelope.
-
Persistence:
-
Store all canonical inputs required for deterministic replay:
- SBOM digest
- Feeds snapshot ID
- Tool versions
- Graph structure
- Lattice evaluation payload.
3.3 Concelier (Feedser successor)
Code: src/StellaOps.Concelier/
Responsibilities:
-
Maintain feed snapshots:
- CVE feeds
- Vendor advisories
- Configuration.
-
Create feed snapshot IDs that are:
- Immutable
- Hash-based.
-
Publish:
feedSnapshotId→ used by Scanner.policyId→ hash of lattice/policy config.
Important: Concelier does not run lattice; it only provides configuration. Scanner consumes configs, executes lattice, and stores resulting latticeEvalId.
3.4 Vexer / Excititor
Code: src/StellaOps.Vexer/ (and/or Excititor)
Responsibilities:
-
Convert Scanner’s canonical findings & lattice evaluation into OpenVEX.
-
Never re-run lattice; only:
- Read
latticeEvalId - Render it into OpenVEX + explainability JSON.
- Read
Inputs:
scanFindingsJsonor referencegraphRevpolicyIdlatticeEvalId.
Outputs:
- OpenVEX JSON (deterministic ordering).
- Explainability JSON.
- VEX in-toto statement.
- DSSE envelope via Authority.
3.5 Authority
Code: src/StellaOps.Authority/ and src/StellaOps.Authority.Client/
Responsibilities:
-
Manage signing keys (FIPS/EIDAS/GOST/SM; optional PQ).
-
Provide HTTP API:
-
POST /api/dsse/sign- Input: in-toto statement JSON +
keyRef+ annotations. - Output: DSSE envelope JSON.
- Input: in-toto statement JSON +
-
POST /api/dsse/verify- Input: DSSE envelope + trust bundle.
- Output:
valid,keyId,alg,signedAt.
-
-
Write log records suitable for:
- Rekor v2 online
- Offline Rekor mirror.
Client library (C#):
IAuthorityClient.SignDsseAsync(JsonDocument stmt, string keyRef, IDictionary<string, string>? annotations);IAuthorityClient.VerifyDsseAsync(JsonDocument envelope, TrustBundle bundle);
3.6 Gateway (NGINX / WAF)
Responsibility: enforce headers, propagate trace IDs, authenticate tenants via mTLS.
Key headers:
X-Stella-TraceIdX-Stella-SpanIdX-Stella-Graph-RevX-Stella-Policy-IdX-Stella-DSSE-SHA256X-Stella-Att-Chain.
NGINX sketch:
map $ssl_client_s_dn $stella_tenant {
default "unknown";
~CN=(?<cn>[^/]+) $cn;
}
map $http_x_stella_traceid $trace_id {
default $request_id;
}
server {
listen 443 ssl;
ssl_certificate /etc/nginx/certs/gw.crt;
ssl_certificate_key /etc/nginx/certs/gw.key;
ssl_client_certificate /etc/nginx/certs/ca.crt;
ssl_verify_client on;
if ($request_uri ~* ^/(api/(scan|vex|attest))) {
if ($http_x_stella_dsse_sha256 = "") { return 400; }
if ($http_x_stella_policy_id = "") { return 400; }
}
proxy_set_header X-Request-Id $trace_id;
proxy_set_header X-Stella-Tenant $stella_tenant;
proxy_pass http://stella_core_upstream;
}
3.7 Proof-of-Integrity Graph
Code: src/StellaOps.ProofGraph/
Responsibilities:
-
Store edges:
ImageDigest → SBOM(bomDigest)ImageDigest → Scan(findingsDigest, graphRev, latticeEvalId)ImageDigest → VEX(vexDigest, policyId)Attestation → DSSE(keyId, alg, time)RekorEntry → Envelope.
-
Provide lookup by:
- Image digest
- GraphRev
- PolicyId.
Expose API:
GET /api/proof/image/{digest}GET /api/proof/graph/{graphRev}
4. CLI and .NET integration
4.1 CLI tools (stella)
Location: src/Tools/Stella.Cli/
Subcommands (to implement):
-
stella authority sign-dsse <input.json >output.dsse.json- Flags:
--key-ref,--annot graphRev=...,--annot policyId=....
- Flags:
-
stella offline manifest --graph-rev <rev> --policy-id <id> --image <digest> > offline-kit/manifest.yaml -
stella vex gate-
Args:
--vex(path to VEX DSSE or OpenVEX)--policy-id--denyexpression.
-
-
stella offline verify- Verifies DSSE against trust root.
-
stella offline replay- Recompute decisions from offline kit, emit
receipt.json.
- Recompute decisions from offline kit, emit
4.2 Authority client example (.NET 10)
public sealed class AuthorityClient : IAuthorityClient
{
private readonly HttpClient _http;
public AuthorityClient(HttpClient http) => _http = http;
public async Task<JsonDocument> SignDsseAsync(
JsonDocument statement,
string keyRef,
IDictionary<string, string>? annotations = null,
CancellationToken ct = default)
{
var payload = new
{
keyRef,
statement = JsonSerializer.Deserialize<object>(statement.RootElement.GetRawText()),
annotations
};
using var resp = await _http.PostAsJsonAsync("/api/dsse/sign", payload, ct);
resp.EnsureSuccessStatusCode();
await using var stream = await resp.Content.ReadAsStreamAsync(ct);
return await JsonDocument.ParseAsync(stream, cancellationToken: ct);
}
}
5. CI/CD pipeline pattern
Assume GitLab, adapt easily.
5.1 Environment variables
Define:
IMAGE– e.g.repo/app:1.4.2OCI_DIGEST– from registry.GRAPH_REV– from Scanner orstella graph rev.POLICY_ID– from Concelier.
5.2 Example pipeline
stages:
- build
- attest
- gate
- deploy
build:
stage: build
script:
- docker build -t "$IMAGE" .
- OCI_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE" | cut -d'@' -f2)
- echo "OCI_DIGEST=$OCI_DIGEST" >> build.env
artifacts:
reports:
dotenv: build.env
attest:
stage: attest
image: stellaops/cli:latest
dependencies: [build]
script:
- sbomer gen "$IMAGE" > offline-kit/artifacts/sbom.cdx.json
- scanner reach "$IMAGE" --graph-rev "$GRAPH_REV" > offline-kit/artifacts/scan.findings.json
- vexer make --policy "$POLICY_ID" offline-kit/artifacts/scan.findings.json > offline-kit/artifacts/vex.openvex.json
- stella authority sign-dsse offline-kit/artifacts/sbom.cdx.json > offline-kit/attestations/sbom.dsse.json
- stella authority sign-dsse offline-kit/artifacts/scan.findings.json > offline-kit/attestations/scan.dsse.json
- stella authority sign-dsse offline-kit/artifacts/vex.openvex.json > offline-kit/attestations/vex.dsse.json
- stella offline manifest \
--graph-rev "$GRAPH_REV" \
--policy-id "$POLICY_ID" \
--image "$OCI_DIGEST" \
> offline-kit/manifest.yaml
artifacts:
paths:
- offline-kit/
gate:vex:
stage: gate
image: stellaops/cli:latest
dependencies: [attest]
script:
- stella vex gate \
--vex offline-kit/attestations/vex.dsse.json \
--policy-id "$POLICY_ID" \
--deny "affected && reachable && severity>=high && !mitigated"
deploy:
stage: deploy
script:
- # Helm / kubectl apply using canary config
when: on_success
6. Kubernetes: canary + blue-green
6.1 Labels & annotations
Deployment template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-blue
labels:
app: app
track: blue
stella.graph_rev: "<GRAPH_REV>"
stella.policy_id: "<POLICY_ID>"
annotations:
stella.att_chain: "sbom,scan,vex"
stella.proof_ref: "proofgraph://images/<OCI_DIGEST>"
spec:
template:
metadata:
labels:
app: app
track: blue
annotations:
sidecar.opentelemetry.io/inject: "true"
spec:
containers:
- name: app
image: repo/app@sha256:<OCI_DIGEST>
env:
- name: STELLA_GRAPH_REV
valueFrom:
fieldRef:
fieldPath: metadata.labels['stella.graph_rev']
- name: STELLA_POLICY_ID
valueFrom:
fieldRef:
fieldPath: metadata.labels['stella.policy_id']
6.2 Istio VirtualService (canary 5%)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: app
spec:
hosts:
- app.example.local
http:
- route:
- destination:
host: app
subset: blue
weight: 5
- destination:
host: app
subset: green
weight: 95
Promotion logic (Scheduler or CD script):
-
Shift 5 → 25 → 50 → 100 if:
- Error rate below threshold.
- p95 latency OK.
- No new reachable High/Critical vulns appear in canary telemetry (Security SLO).
7. Air-gap flow
7.1 Producer side (online)
Steps:
-
Build image + get digest.
-
Generate SBOM, scan, VEX.
-
Sign all three via Authority (DSSE).
-
Generate
manifest.yaml. -
Optionally:
- Export container:
ctr images export artifacts/image_app_1.4.2.oci.tgz repo/app:1.4.2.
- Export container:
-
Ship
offline-kit.*.tgzthrough approved channel.
Directory shape (required):
offline-kit/
manifest.yaml
artifacts/
sbom.cdx.json
scan.findings.json
vex.openvex.json
image_app_1.4.2.oci.tgz # optional but recommended
attestations/
sbom.dsse.json
scan.dsse.json
vex.dsse.json
keys/
trust-root.pem # root certs / public keys only
indexes/
rekor-mirror/ # optional (CAR / SQLite, etc.)
7.2 Consumer side (air-gapped environment)
Commands (CLI to implement):
# 1) Verify DSSE signatures
stella offline verify \
--trust-root offline-kit/keys/trust-root.pem \
offline-kit/attestations/*.dsse.json
# 2) Import Rekor mirror (if present)
stella mirror import offline-kit/indexes/rekor-mirror
# 3) Load image
stella registry load offline-kit/artifacts/image_app_1.4.2.oci.tgz
# 4) Replay for audit
stella offline replay \
--manifest offline-kit/manifest.yaml \
--output receipt.json
Acceptance for customers:
-
Replay must show
status = "ok". -
Any hash mismatch must be treated as:
- Either misconfiguration or
- Tampering → block.
8. Tracing & observability
8.1 OpenTelemetry attributes
For all services in the chain, attach:
stella.graph_revstella.policy_idstella.image_digeststella.tenantstella.envstella.att_chain(sbom,scan,vex)stella.proof_ref
.NET 10 setup example:
builder.Services.AddOpenTelemetry()
.WithTracing(t =>
t.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService("stella.app")
.AddAttributes(new[]
{
new KeyValuePair<string, object>("stella.graph_rev", graphRev),
new KeyValuePair<string, object>("stella.policy_id", policyId)
})
)
.AddOtlpExporter());
8.2 Security gate span events
On VEX gate evaluation:
-
Add span event
vex.decisionwith attributes:vex.decision = allow|deny|warnvex.reasons = [...CVE ids / rules...]vex.proof_hash = sha256:<hash>
9. Work breakdown by area
You can assign roughly as follows.
9.1 Backend (.NET) team
-
Authority
- Implement
/api/dsse/signand/api/dsse/verify. - Implement
StellaOps.Authority.Client. - Integrate with HSM / KMS if needed.
- Implement
-
Scanner
- Implement canonical graph normalization and
graphRev. - Integrate feed snapshots from Concelier.
- Implement lattice evaluation in Scanner only.
- Produce scan in-toto statement & call Authority.
- Implement canonical graph normalization and
-
Vexer
- Implement OpenVEX rendering from
latticeEvalId. - Implement explainability JSON generation.
- Produce VEX in-toto statement & call Authority.
- Implement OpenVEX rendering from
-
Sbomer
- Ensure deterministic SBOM generation.
- Expose
bomDigest. - Optionally, direct Authority integration for SBOM DSSE.
-
ProofGraph
- Implement storage model for edges & nodes.
- Expose lookup APIs by image, graphRev, policyId.
-
CLI
- Implement
stellasubcommands as above.
- Implement
9.2 DevOps / Platform
-
Deploy Authority with HSM/KMS.
-
Configure NGINX/WAF with required headers & mTLS.
-
Build GitLab (or other) pipeline:
- Build → Attest → Gate → Deploy.
-
Implement canary → blue-green rollout strategy:
- Istio or NGINX Ingress routing.
-
Standardize K8s labels/annotations for Stella metadata.
9.3 Documentation team
Produce:
-
“Attestation & Air-Gap Overview” for clients:
- Non-technical diagrams of SBOM→Scan→VEX→Deploy.
- Explanation of DSSE & trust roots.
- Replay and audit story.
-
“Implementing Stella Gates in CI/CD”:
- Examples per CI (GitLab, GitHub Actions, Azure).
- VEX gate policy examples.
-
“Offline Kit & Air-Gap Runbook”:
- Step-by-step producer & consumer instructions.
- Screenshots or CLI examples.
- FAQ (mismatch handling, lost keys, etc.).
10. Acceptance criteria (for you to sign off)
For each image deployed:
-
There exists an SBOM DSSE, Scan DSSE, and VEX DSSE.
-
All three can be verified against configured trust roots.
-
A ProofGraph query by image digest returns:
- SBOM, Scan, VEX, their hashes, and key IDs.
-
A replay of the offline kit with the current tools & feeds:
- Either reproduces the same verdicts (
status=ok) or clearly reports mismatches.
- Either reproduces the same verdicts (
-
In CI:
- Pipelines fail when VEX gate’s deny expression is satisfied.
-
In production:
-
Every pod for that image has:
stella.graph_revandstella.policy_idlabels.- Can be traced back from logs / traces to the same graph & policy.
-
If you want, I can next convert this into concrete repository files (e.g. ready-to-commit docs/*.md + starter C# interfaces and CLI skeletons) matching your exact folder structure.