Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
export {
createSlsaOtelAdapter,
noopSlsaOtelAdapter,
type SlsaEventAttributes,
type SlsaOtelAdapter,
} from "./slsa-otel.js";

View File

@@ -0,0 +1,74 @@
# slsa-otel
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
OpenTelemetry adapter for SLSA audit events. Every major capability in the
package — provenance generation, verification, Rekor upload, inclusion-proof
verification, policy evaluation — emits a structured span event through this
adapter. By default the adapter attaches events to the currently-active span,
so it composes naturally with the calling service's trace context without
SLSA needing to manage its own span lifecycle.
## Features
- Five well-known event names: `slsa.provenance.generated`,
`slsa.provenance.verified`, `slsa.rekor.upload`,
`slsa.rekor.inclusion_verified`, `slsa.policy.violation`.
- Typed attribute bag (`SlsaEventAttributes`) under the `slsa.*` namespace.
- Zero-config no-op default when no exporter is registered.
- Explicit `tracer` override for background / detached contexts.
## Usage: Basic Example
```ts
import { createSlsaOtelAdapter, generateProvenance } from "@suaveplan/slsa";
const otel = createSlsaOtelAdapter();
const stmt = generateProvenance({
/* ...input... */,
otel,
});
```
## Usage: Advanced Example
```ts
import { trace } from "@opentelemetry/api";
import { createSlsaOtelAdapter, RekorClient } from "@suaveplan/slsa";
const tracer = trace.getTracer("publish-pipeline");
const otel = createSlsaOtelAdapter(tracer);
const client = new RekorClient({ otel });
await tracer.startActiveSpan("publish", async (span) => {
try {
const entry = await client.uploadHashedrekord(rekord);
await client.verifyInclusionProof(entry);
} finally {
span.end();
}
});
```
## API Reference
- `SlsaEventAttributes` — named attribute bag; every field is optional.
- `SlsaOtelAdapter` — interface with five `on*` methods, all returning `void`.
- `createSlsaOtelAdapter(tracer?)` — returns a live adapter. With no
`tracer`, events attach to the active span; otherwise spans are started
inline and ended.
- `noopSlsaOtelAdapter` — default adapter that discards events.
## Implementation Notes
Span events are preferred over metrics because SLSA events are inherently
correlated: "generated", "verified", and "logged to Rekor" usually happen
inside a single CI job's trace. Counters would lose that correlation.
Attributes are typed as a closed shape so accidental typos surface as
compile-time errors, but the runtime cast to `Record<string, never>` is used
because `@opentelemetry/api`'s `AttributeValue` type is wider than our event
schema and TypeScript's invariance on index signatures would otherwise
require an unsafe upcast at every call site.

View File

@@ -0,0 +1,110 @@
import { type Span, SpanStatusCode, trace } from "@opentelemetry/api";
import { describe, expect, it } from "@suaveplan/testing/runner";
import { createSlsaOtelAdapter, noopSlsaOtelAdapter } from "./slsa-otel.js";
type RecordedEvent = { name: string; attrs: Record<string, unknown> };
function makeFakeSpan(): Span & { events: RecordedEvent[] } {
const events: RecordedEvent[] = [];
const span = {
events,
addEvent: (name: string, attrs?: Record<string, unknown>) => {
events.push({ name, attrs: attrs ?? {} });
return span;
},
end: () => {},
setAttribute: () => span,
setAttributes: () => span,
setStatus: () => span,
updateName: () => span,
recordException: () => {},
isRecording: () => true,
spanContext: () => ({
traceId: "00000000000000000000000000000001",
spanId: "0000000000000001",
traceFlags: 1,
}),
status: { code: SpanStatusCode.UNSET },
} as unknown as Span & { events: RecordedEvent[] };
return span;
}
describe("noopSlsaOtelAdapter", () => {
it("accepts all events without throwing", { timeout: 5000 }, () => {
expect(() => {
noopSlsaOtelAdapter.onProvenanceGenerated({});
noopSlsaOtelAdapter.onProvenanceVerified({});
noopSlsaOtelAdapter.onRekorUpload({});
noopSlsaOtelAdapter.onRekorInclusionVerified({});
noopSlsaOtelAdapter.onPolicyViolation({});
}).not.toThrow();
});
});
describe("createSlsaOtelAdapter", () => {
it("is callable with zero args and does not throw", {
timeout: 5000,
}, () => {
const a = createSlsaOtelAdapter();
expect(() => {
a.onProvenanceGenerated({ "slsa.result.valid": true });
a.onProvenanceVerified({});
a.onRekorUpload({});
a.onRekorInclusionVerified({});
a.onPolicyViolation({});
}).not.toThrow();
});
it("emits events on the active span", { timeout: 5000 }, () => {
const span = makeFakeSpan();
const originalGet = trace.getActiveSpan;
(
trace as unknown as { getActiveSpan: () => Span | undefined }
).getActiveSpan = () => span;
try {
const adapter = createSlsaOtelAdapter();
adapter.onProvenanceGenerated({ "slsa.builder.id": "gitlab-ci" });
adapter.onRekorUpload({ "slsa.rekor.uuid": "u1" });
} finally {
(
trace as unknown as {
getActiveSpan: () => Span | undefined;
}
).getActiveSpan = originalGet;
}
expect(span.events.length).toBe(2);
expect(span.events[0]?.name).toBe("slsa.provenance.generated");
expect(span.events[1]?.name).toBe("slsa.rekor.upload");
});
it("falls back to tracer when no active span", {
timeout: 5000,
}, () => {
const created: Array<Span & { events: RecordedEvent[] }> = [];
const tracer = {
startSpan: () => {
const s = makeFakeSpan();
created.push(s);
return s;
},
startActiveSpan: () => {
throw new Error("not used");
},
} as unknown as import("@opentelemetry/api").Tracer;
const adapter = createSlsaOtelAdapter(tracer);
adapter.onPolicyViolation({ "slsa.policy.violations": 3 });
expect(created.length).toBe(1);
expect(created[0]?.events[0]?.name).toBe("slsa.policy.violation");
});
it("silently discards when no active span and no tracer", {
timeout: 5000,
}, () => {
const adapter = createSlsaOtelAdapter();
expect(() =>
adapter.onRekorInclusionVerified({ "slsa.rekor.uuid": "z" })
).not.toThrow();
});
});

View File

@@ -0,0 +1,70 @@
import { type Tracer, trace } from "@opentelemetry/api";
/**
* Attributes emitted on SLSA audit events. Keyed under the `slsa.*` namespace
* so they integrate cleanly with OTel collectors that filter on attribute
* prefix.
*/
export interface SlsaEventAttributes {
"slsa.statement.digest"?: string;
"slsa.builder.id"?: string;
"slsa.source.repository"?: string;
"slsa.result.valid"?: boolean;
"slsa.rekor.uuid"?: string;
"slsa.rekor.log_index"?: number;
"slsa.rekor.tree_size"?: number;
"slsa.policy.violations"?: number;
"slsa.policy.level"?: string;
}
/**
* Adapter emitting structured span events on the currently-active span (or a
* configured tracer). Default behaviour is no-op when no exporter is
* registered, matching `@opentelemetry/api`'s expected shape.
*/
export interface SlsaOtelAdapter {
onProvenanceGenerated(attrs: SlsaEventAttributes): void;
onProvenanceVerified(attrs: SlsaEventAttributes): void;
onRekorUpload(attrs: SlsaEventAttributes): void;
onRekorInclusionVerified(attrs: SlsaEventAttributes): void;
onPolicyViolation(attrs: SlsaEventAttributes): void;
}
/**
* Build a `SlsaOtelAdapter` that emits span events to the current active
* span (via `trace.getActiveSpan()`). Providing an explicit `tracer` makes
* the adapter create its own spans — useful for background processes that
* do not run inside a parent context.
*/
export function createSlsaOtelAdapter(tracer?: Tracer): SlsaOtelAdapter {
const emit = (name: string, attrs: SlsaEventAttributes): void => {
const active = trace.getActiveSpan();
if (active) {
active.addEvent(name, attrs as Record<string, never>);
return;
}
if (tracer) {
const span = tracer.startSpan(name);
span.addEvent(name, attrs as Record<string, never>);
span.end();
}
};
return {
onProvenanceGenerated: (a) => emit("slsa.provenance.generated", a),
onProvenanceVerified: (a) => emit("slsa.provenance.verified", a),
onRekorUpload: (a) => emit("slsa.rekor.upload", a),
onRekorInclusionVerified: (a) =>
emit("slsa.rekor.inclusion_verified", a),
onPolicyViolation: (a) => emit("slsa.policy.violation", a),
};
}
/** A no-op adapter used as the default when callers do not supply one. */
export const noopSlsaOtelAdapter: SlsaOtelAdapter = {
onProvenanceGenerated: () => {},
onProvenanceVerified: () => {},
onRekorUpload: () => {},
onRekorInclusionVerified: () => {},
onPolicyViolation: () => {},
};