Skip to content

refactor(bundler): expose SignStatement primitive for predicate-agnostic keyless signing#852

Merged
njhensley merged 10 commits into
NVIDIA:mainfrom
njhensley:refactor/bundler-sign-statement-primitive
May 12, 2026
Merged

refactor(bundler): expose SignStatement primitive for predicate-agnostic keyless signing#852
njhensley merged 10 commits into
NVIDIA:mainfrom
njhensley:refactor/bundler-sign-statement-primitive

Conversation

@njhensley

Copy link
Copy Markdown
Member

Summary

Extract the Sigstore Fulcio + Rekor keyless-signing plumbing from KeylessAttester.Attest into a reusable SignStatement primitive in pkg/bundler/attestation, so the upcoming recipe-evidence pipeline (#754) can share it instead of duplicating ~80 lines of identical code.

Motivation / Context

ADR-007 (recipe-test-attestation, #754) introduces a second producer of in-toto Statements signed via Sigstore keyless OIDC — same DSSE envelope, same Fulcio cert flow, same Rekor inclusion proof, same protobuf-JSON bundle — but with a different predicate type (https://aicr.nvidia.com/recipe-evidence/v1). Today, all of that plumbing lives inside KeylessAttester.Attest and is hard-wired to the SLSA Provenance v1 predicate the bundler emits. Without this split, pkg/evidence/attestation/signer.go would copy/paste the same 80 LoC.

This refactor isolates the predicate-agnostic half so the second producer becomes a thin client of the existing primitive. It is intentionally a pure refactor — no observable behavior change for aicr bundle --attest.

Fixes: N/A
Related: #754, ADR-007 (docs/design/007-recipe-evidence.md)

Type of Change

  • Refactoring (no functional changes)

Component(s) Affected

  • Bundlers (pkg/bundler, pkg/component/*)

Implementation Notes

New API surface in pkg/bundler/attestation:

type SignOptions struct {
    OIDCToken string  // required
    FulcioURL string  // empty → DefaultFulcioURL
    RekorURL  string  // empty → DefaultRekorURL
}

type SignedAttestation struct {
    BundleJSON    []byte  // protobuf-JSON Sigstore bundle
    Identity      string  // SAN (email or URI) from Fulcio cert
    Issuer        string  // OIDC issuer URL from Fulcio cert extension
    RekorLogIndex int64   // first transparency-log entry, 0 if absent
}

func SignStatement(ctx context.Context, statementJSON []byte, opts SignOptions) (*SignedAttestation, error)

SignStatement takes pre-built in-toto Statement bytes (so callers own the predicate) and returns the full Sigstore bundle plus the identity claims extracted from the Fulcio certificate. The returned SignedAttestation is a strict superset of what Attest used to surface internally — adding the OIDC issuer and Rekor log index — so the new evidence pointer file can populate its full signer block without re-parsing the certificate.

Internal cleanup, also in this PR:

  • extractSignerClaims (identity + issuer) replaces extractSignerIdentity as the canonical extractor; the old function is retained as a thin wrapper so existing assertions in keyless_test.go stand unchanged.
  • extractIssuerExtension parses the Fulcio OIDC-issuer X.509 extension (OIDs 1.3.6.1.4.1.57264.1.1 legacy and .1.8 current).
  • extractRekorLogIndex returns the first transparency-log entry's index, or 0 when no Rekor entry was created.

KeylessAttester.Attest keeps its public signature and semantics: it still builds the SLSA Provenance v1 Statement, then delegates DSSE wrap + Fulcio + Rekor + bundle marshal to SignStatement. Existing callers see no change.

Considered and deferred: promoting the primitive to its own top-level package (e.g., pkg/sigstore). Decided against in this PR to keep the diff small; the only consumer outside pkg/bundler/attestation will be pkg/evidence/attestation (PR-Z), which can live with an import alias.

Testing

make qualify

Per-package results on pkg/bundler/attestation:

  • go test ./pkg/bundler/attestation/... — PASS
  • golangci-lint run — 0 issues
  • Coverage: 63.0% → 65.7% (the new extractors are at 100%; SignStatement itself is at 16% — the live Fulcio/Rekor call path still requires integration tests, same as before this PR)

New signing_test.go covers:

  • SignStatement input validation (empty statement, missing OIDC token)
  • extractSignerClaims for email-SAN certs (interactive OIDC) and URI-SAN certs (workload OIDC, e.g. GitHub Actions)
  • extractIssuerExtension for the current Fulcio OID (.1.8)
  • extractRekorLogIndex for present/absent/nil VerificationMaterial cases

Risk Assessment

  • Low — Isolated change, well-tested, easy to revert

Rollout notes: Pure internal refactor. No CLI flag, API, on-disk artifact, or wire-format changes. aicr bundle --attest continues to produce byte-identical Sigstore bundles for the same input.

Checklist

  • Tests pass locally (make test with -race)
  • Linter passes (make lint)
  • I did not skip/disable tests to make CI green
  • I added/updated tests for new functionality
  • I updated docs if user-facing behavior changed (N/A — internal refactor)
  • Changes follow existing patterns in the codebase
  • Commits are cryptographically signed (git commit -S)

njhensley and others added 2 commits May 11, 2026 17:29
…tic keyless signing

Extract the Sigstore Fulcio + Rekor keyless-signing plumbing from
KeylessAttester.Attest into a reusable SignStatement primitive that
takes pre-built in-toto Statement bytes. The Attest method keeps its
existing public contract — it now builds the SLSA Provenance v1
Statement (caller-specific) and delegates the DSSE wrap, ephemeral
keypair, Fulcio cert issuance, Rekor entry, and Sigstore-bundle
marshal (caller-agnostic) to the new function.

Motivation:

The recipe-test-attestation pipeline landing under NVIDIA#754 needs to sign
an in-toto Statement carrying a different predicate type
(https://aicr.nvidia.com/recipe-evidence/v1) but the same DSSE +
Fulcio + Rekor + protobuf-JSON output shape. Without this split,
that pipeline would duplicate ~80 lines of identical signing
plumbing in pkg/evidence/attestation/signer.go. Extracting now means
the second caller is a thin client of the bundler's primitive.

API surface:

  - SignOptions{OIDCToken, FulcioURL, RekorURL}
  - SignedAttestation{BundleJSON, Identity, Issuer, RekorLogIndex}
  - SignStatement(ctx, statementJSON []byte, opts SignOptions) (*SignedAttestation, error)

The new return type also exposes the Fulcio cert's OIDC issuer claim
and the Rekor inclusion-proof log index — a strict superset of the
existing identity-only extraction. Callers that only need identity
can ignore the extras; pkg/evidence/attestation will consume all
three to populate its pointer file's signer block.

Internal cleanup:

  - extractSignerClaims (identity + issuer) replaces extractSignerIdentity
    as the canonical extractor; extractSignerIdentity is retained as a
    thin wrapper so the existing keyless_test.go assertions stand
    unchanged.
  - extractIssuerExtension parses the Fulcio OIDC-issuer X.509
    extension (OIDs 1.3.6.1.4.1.57264.1.1 legacy and .1.8 current).
  - extractRekorLogIndex returns the first transparency-log entry's
    index, or 0 when no Rekor entry was created.

Tests cover input validation, identity-from-email and identity-from-URI
extraction (matching the two Fulcio OIDC modes), issuer extension
parsing, Rekor log-index extraction, and the empty-bundle / nil
fallbacks. The signing call itself still needs a live Sigstore stack
and remains exercised only via integration tests.

Related: NVIDIA#754, ADR-007 (`docs/design/007-recipe-evidence.md`)
@njhensley njhensley requested a review from a team as a code owner May 12, 2026 00:36
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 98ae7fca-7e6a-4123-b648-f2c13a00eae0

📥 Commits

Reviewing files that changed from the base of the PR and between 3856ef3 and e98d8c4.

📒 Files selected for processing (3)
  • pkg/bundler/attestation/keyless_test.go
  • pkg/bundler/attestation/signing.go
  • pkg/bundler/attestation/signing_test.go

📝 Walkthrough

Walkthrough

The PR extracts Sigstore keyless DSSE signing into a new SignStatement function with exported SignOptions and SignedAttestation types, implements helpers to extract signer identity, Fulcio issuer, and the first Rekor log index from a signed protobuf Bundle, updates KeylessAttester.Attest to call SignStatement and set k.identity, and adds unit tests covering validation, claim extraction, and log-index selection.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: extracting Sigstore keyless-signing logic into a reusable SignStatement primitive for predicate-agnostic signing.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the motivation, implementation details, testing approach, and risk assessment.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/bundler/attestation/signing_test.go`:
- Around line 91-116: Combine the three repetitive tests into one table-driven
test named TestExtractRekorLogIndex: create a slice of test cases each
containing a name, input *protobundle.Bundle (cases: bundle with two TlogEntries
{LogIndex:42,...}, bundle with empty VerificationMaterial, and bundle with nil
VerificationMaterial), and expected int result (42, 0, 0); then range over cases
and use t.Run(tc.name, func(t *testing.T) { if got :=
extractRekorLogIndex(tc.bundle); got != tc.want { t.Errorf("LogIndex = %d, want
%d", got, tc.want) } }). Ensure you reference the existing function
extractRekorLogIndex and the
protobundle.Bundle/VerificationMaterial/TransparencyLogEntry types when
constructing case inputs.

In `@pkg/bundler/attestation/signing.go`:
- Around line 189-200: extractIssuerExtension currently treats both OIDs
identically and returns string(ext.Value), which corrupts OID
1.3.6.1.4.1.57264.1.8 values; change extractIssuerExtension to detect the OID
and for the current OID ("1.3.6.1.4.1.57264.1.8") call asn1.Unmarshal into a Go
string (handle/unmarshal error by returning empty or falling back), while
keeping the legacy OID ("1.3.6.1.4.1.57264.1.1") behavior as string(ext.Value);
import/use the encoding/asn1 package and return the decoded UTF8 string for the
current OID.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: fcd214fb-be8d-413b-bf72-d2345e987d07

📥 Commits

Reviewing files that changed from the base of the PR and between d553bda and 8798954.

📒 Files selected for processing (3)
  • pkg/bundler/attestation/keyless.go
  • pkg/bundler/attestation/signing.go
  • pkg/bundler/attestation/signing_test.go

Comment thread pkg/bundler/attestation/signing_test.go Outdated
Comment thread pkg/bundler/attestation/signing.go
extractIssuerExtension treated both the legacy (OID 1.3.6.1.4.1.57264.1.1)
and current (OID 1.3.6.1.4.1.57264.1.8) Fulcio issuer extensions as raw
UTF-8 bytes. The current OID is actually a DER-encoded ASN.1 UTF8String
(per the Sigstore Fulcio OID spec), so the returned issuer string carried
the leading ASN.1 tag/length prefix and could not be compared against the
expected issuer URL.

Decode the current-OID value via asn1.Unmarshal; keep raw-bytes handling
for the legacy OID where the unwrapped form is correct. Consolidate the
three TestExtractRekorLogIndex_* funcs into one table-driven test, and
update the test cert helpers to stamp the issuer using a shared helper
that DER-encodes the value the same way Fulcio does.
@github-actions github-actions Bot added size/XL and removed size/L labels May 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/bundler/attestation/signing_test.go`:
- Around line 72-82: Add table-driven tests in
pkg/bundler/attestation/signing_test.go to cover the other branches of
extractIssuerExtension: create cases for the legacy OID encoding (the older
"...1.1" form) and for an ASN.1 decode failure (malformed extension bytes), then
call extractSignerClaims (or directly extractIssuerExtension) for each case and
assert the returned issuer/identity match the expected outcomes for those
branches; update TestExtractSignerClaims_PullsIssuerFromExtension (or add a new
table-driven test function) to iterate cases, include descriptive case names,
and assert both success and error-path behavior so all branches are exercised.

In `@pkg/bundler/attestation/signing.go`:
- Line 139: The info-level log at the signing step currently emits the signer
identity (variable identity) which may contain PII; change the log so identity
is not written at info level: either remove identity from the slog.Info call
entirely and keep a generic success message ("in-toto statement signed",
"rekorLogIndex", rekorIndex), or move identity into a debug-level log (use
slog.Debug with "identity", identity) or redact it (e.g., replace with a
hashed/obfuscated value) before logging; update the call to the
slog.Info/slog.Debug invocation in signing.go (the slog.Info(...) line)
accordingly.
- Around line 117-126: The sign.Bundle call can hang on network I/O; wrap it in
a child context with a timeout using the repo timeout constant from pkg/defaults
(e.g., defaults.RepoTimeout) before calling sign.Bundle in signing.go, pass that
child context via the Context field in sign.BundleOptions, and ensure you call
the returned cancel function (defer cancel()) to avoid leaks; update references
around bundle, err := sign.Bundle(...) to use the timed context instead of the
original ctx.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 5262c5e5-fc0a-413e-87bc-d89fe0e834f6

📥 Commits

Reviewing files that changed from the base of the PR and between 8798954 and ce9ac7f.

📒 Files selected for processing (2)
  • pkg/bundler/attestation/signing.go
  • pkg/bundler/attestation/signing_test.go

Comment thread pkg/bundler/attestation/signing_test.go
Comment thread pkg/bundler/attestation/signing.go
Comment thread pkg/bundler/attestation/signing.go Outdated
mchmarny
mchmarny previously approved these changes May 12, 2026
Two cleanups in pkg/bundler/attestation.SignStatement and one test
addition:

  - Wrap the sign.Bundle call in context.WithTimeout against a new
    defaults.SigstoreSignTimeout (2 minutes) so a hung Fulcio or
    Rekor peer cannot block the CLI indefinitely. The shorter caller
    deadline still wins if it is already attached.

  - Demote the post-sign INFO line so it carries only the public
    rekorLogIndex; the Fulcio SAN (identity) and OIDC issuer URL are
    moved to DEBUG. Identity is user PII for interactive OIDC and
    shouldn't show up in default-level CLI output; callers that need
    it for auditing still receive both via SignedAttestation.

  - Add TestExtractIssuerExtension covering both Fulcio OIDs, a
    malformed-ASN.1 value, and the no-issuer-extension case.
@njhensley njhensley enabled auto-merge (squash) May 12, 2026 13:33

@mchmarny mchmarny left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean refactor — the split between predicate-agnostic signing and the SLSA-specific Attest wrapper is the right cut for the upcoming recipe-evidence pipeline. ASN.1-aware handling of the current Fulcio OID and the new Sigstore signing timeout are both improvements over the prior code, not just relocations. Tests cover the new extractors well.

One medium concern worth resolving before merge: the PII rationale you added to SignStatement (identity at Debug only) is undermined by KeylessAttester.Attest still logging identity at Info — pick one policy and apply it consistently.

Two low/nit items: extractIssuerExtension returns whichever Fulcio OID appears first in the cert rather than preferring the current OID, and extractSignerIdentity is now production code maintained only for tests. Neither blocks merge.

CI: test/lint/e2e jobs all green; gate and a few scanners (CodeQL, Grype, ClamAV) still in progress.

Comment thread pkg/bundler/attestation/keyless.go Outdated
Comment thread pkg/bundler/attestation/keyless.go Outdated
Comment thread pkg/bundler/attestation/signing.go
Comment thread pkg/bundler/attestation/signing.go
njhensley and others added 2 commits May 12, 2026 09:27
… precedence

Three cleanups in pkg/bundler/attestation:

* Remove the extractSignerIdentity wrapper. It was a one-line shim
  around extractSignerClaims kept for the older tests; migrate the
  tests to call extractSignerClaims directly so the package has one
  parsing surface instead of two.

* Demote the post-sign INFO line in KeylessAttester.Attest so the
  Fulcio SAN identity flows to Debug only. Mirrors the SignStatement
  contract — identity is user PII for interactive OIDC. Callers that
  need the value still read it back via KeylessAttester.Identity().

* Rework extractIssuerExtension from a single-pass switch to a
  two-pass scan that prefers the current OID (1.3.6.1.4.1.57264.1.8)
  over the legacy one (1.3.6.1.4.1.57264.1.1) regardless of X.509
  extension ordering. A single-pass switch makes the result
  order-dependent for transitional certs that carry both. Add two
  table entries pinning the ordering invariant in both directions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/bundler/attestation/keyless_test.go (1)

100-194: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Collapse the extractSignerClaims variants into a table-driven test.

These cases only vary by bundle fixture and expected identity, so keeping them split makes the helper coverage harder to extend now that extractSignerClaims is the shared production surface.

As per coding guidelines, "Use table-driven tests for functions with multiple test cases."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/bundler/attestation/keyless_test.go` around lines 100 - 194, Collapse the
many TestExtractSignerClaims_* functions into a single table-driven test that
iterates cases containing a name, input bundle (constructed with createTestCert,
buildTestBundle or inline protobundle fixtures for
nil/NoCert/Invalid/Chain/EmptyChain), and the expected identity string, and for
each case call t.Run(case.name, func(t *testing.T){ got, _ :=
extractSignerClaims(case.bundle); if got != case.want { t.Errorf(...) } });
ensure you include cases for Email, URI, NilBundlePointer, NoCert,
InvalidCertDER, CertChain, EmptyCertChain and NoSAN and reuse existing helpers
(createTestCert, buildTestBundle) so coverage remains identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/bundler/attestation/signing.go`:
- Around line 134-136: The error returned when sign.Bundle fails should
distinguish deadline exceeded from general unavailability: inside the code
handling the sign.Bundle call (using signCtx and returning via errors.Wrap), use
stderrors.Is(err, context.DeadlineExceeded) to detect a timeout and wrap/return
errors.ErrCodeTimeout in that case; otherwise keep returning
errors.ErrCodeUnavailable. Update the error-returning branch around
sign.Bundle/signCtx to use this check and return the appropriate wrapped error
(errors.ErrCodeTimeout vs errors.ErrCodeUnavailable).
- Around line 223-230: The ASN.1 decode in the loop over cert.Extensions
currently ignores trailing bytes from asn1.Unmarshal; change the asn1.Unmarshal
call in the block that processes ext.Value to capture the returned rest (e.g.,
rest, err := asn1.Unmarshal(...)), and if err != nil OR len(rest) != 0, treat it
as a failure: log the same debug message (including "oid" and "error"/context)
and return "" so extensions with trailing bytes are rejected; this touches the
code around the ext.Value/asn1.Unmarshal call in signing.go.

---

Outside diff comments:
In `@pkg/bundler/attestation/keyless_test.go`:
- Around line 100-194: Collapse the many TestExtractSignerClaims_* functions
into a single table-driven test that iterates cases containing a name, input
bundle (constructed with createTestCert, buildTestBundle or inline protobundle
fixtures for nil/NoCert/Invalid/Chain/EmptyChain), and the expected identity
string, and for each case call t.Run(case.name, func(t *testing.T){ got, _ :=
extractSignerClaims(case.bundle); if got != case.want { t.Errorf(...) } });
ensure you include cases for Email, URI, NilBundlePointer, NoCert,
InvalidCertDER, CertChain, EmptyCertChain and NoSAN and reuse existing helpers
(createTestCert, buildTestBundle) so coverage remains identical.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 40795582-b2eb-49d0-a763-4a34df2066bc

📥 Commits

Reviewing files that changed from the base of the PR and between e34b24d and 3856ef3.

📒 Files selected for processing (4)
  • pkg/bundler/attestation/keyless.go
  • pkg/bundler/attestation/keyless_test.go
  • pkg/bundler/attestation/signing.go
  • pkg/bundler/attestation/signing_test.go

Comment thread pkg/bundler/attestation/signing.go
Comment thread pkg/bundler/attestation/signing.go
…te issuer extensions

Two correctness fixes in pkg/bundler/attestation, plus a tidy-up:

* SignStatement now distinguishes context.DeadlineExceeded from a
  generic Sigstore failure. A SigstoreSignTimeout-bounded deadline
  surfaces as ErrCodeTimeout (HTTP 504 at the API boundary, telling
  the caller "the signing flow took too long"), while Fulcio refusals
  and Rekor 5xx still flow through as ErrCodeUnavailable (502).

* extractIssuerExtension now inspects asn1.Unmarshal's rest return
  and rejects any extension carrying trailing bytes after a
  well-formed UTF8String. A tag-stuffed value that decodes cleanly
  for the first segment but appends extra data could otherwise be
  honored silently, masking either truncation or an attempt to
  smuggle additional content. Adds a table case pinning the
  rejection.

* Consolidate the seven TestExtractSignerClaims_* identity tests in
  keyless_test.go into one table-driven TestExtractSignerClaims_Identity
  covering email/URI SAN, single Certificate vs X509CertificateChain,
  nil/missing/malformed cert, empty chain, and no-SAN cases. Pure
  refactor — same coverage.
@njhensley njhensley merged commit 064a0aa into NVIDIA:main May 12, 2026
30 checks passed
@njhensley njhensley deleted the refactor/bundler-sign-statement-primitive branch May 12, 2026 16:58
njhensley added a commit to njhensley/aicr that referenced this pull request May 12, 2026
Replace the duplicated DSSE/Fulcio/Rekor plumbing in
pkg/evidence/attestation/signer.go with a thin call to
bundler/attestation.SignStatement (the predicate-agnostic primitive
landed in NVIDIA#852). KeylessSigner.Sign now packages its OIDC token and
URLs into bundleattest.SignOptions and converts the bundler's
SignedAttestation back into the local SignResult shape that pointer
files and the builder consume.

Wins:

  - ~150 LoC of duplicated signing ceremony removed (sign.Bundle,
    ephemeral keypair, Fulcio/Rekor wiring, protojson marshal,
    extractSignerClaims, extractIssuerExtension, extractRekorLogIndex).
  - Inherits the bundler primitive's improvements automatically:
    SigstoreSignTimeout deadline, ErrCodeTimeout vs ErrCodeUnavailable
    classification, ASN.1-aware OIDC issuer extraction with current/legacy
    OID precedence, PII-safe Debug-only identity logging.
  - DefaultFulcioURL/DefaultRekorURL are now consts pointing at the
    bundler's so the two packages cannot drift on the Sigstore
    public-good URLs.

KeylessSigner, NoOpSigner, SignBundle, and SignResult retain the same
public shape so the rest of the package (builder, pointer, OCI push)
is unaffected.
njhensley added a commit to njhensley/aicr that referenced this pull request May 12, 2026
Replace the duplicated DSSE/Fulcio/Rekor plumbing in
pkg/evidence/attestation/signer.go with a thin call to
bundler/attestation.SignStatement (the predicate-agnostic primitive
landed in NVIDIA#852). KeylessSigner.Sign now packages its OIDC token and
URLs into bundleattest.SignOptions and converts the bundler's
SignedAttestation back into the local SignResult shape that pointer
files and the builder consume.

Wins:

  - ~150 LoC of duplicated signing ceremony removed (sign.Bundle,
    ephemeral keypair, Fulcio/Rekor wiring, protojson marshal,
    extractSignerClaims, extractIssuerExtension, extractRekorLogIndex).
  - Inherits the bundler primitive's improvements automatically:
    SigstoreSignTimeout deadline, ErrCodeTimeout vs ErrCodeUnavailable
    classification, ASN.1-aware OIDC issuer extraction with current/legacy
    OID precedence, PII-safe Debug-only identity logging.
  - DefaultFulcioURL/DefaultRekorURL are now consts pointing at the
    bundler's so the two packages cannot drift on the Sigstore
    public-good URLs.

KeylessSigner, NoOpSigner, SignBundle, and SignResult retain the same
public shape so the rest of the package (builder, pointer, OCI push)
is unaffected.
njhensley added a commit to njhensley/aicr that referenced this pull request May 13, 2026
Replace the duplicated DSSE/Fulcio/Rekor plumbing in
pkg/evidence/attestation/signer.go with a thin call to
bundler/attestation.SignStatement (the predicate-agnostic primitive
landed in NVIDIA#852). KeylessSigner.Sign now packages its OIDC token and
URLs into bundleattest.SignOptions and converts the bundler's
SignedAttestation back into the local SignResult shape that pointer
files and the builder consume.

Wins:

  - ~150 LoC of duplicated signing ceremony removed (sign.Bundle,
    ephemeral keypair, Fulcio/Rekor wiring, protojson marshal,
    extractSignerClaims, extractIssuerExtension, extractRekorLogIndex).
  - Inherits the bundler primitive's improvements automatically:
    SigstoreSignTimeout deadline, ErrCodeTimeout vs ErrCodeUnavailable
    classification, ASN.1-aware OIDC issuer extraction with current/legacy
    OID precedence, PII-safe Debug-only identity logging.
  - DefaultFulcioURL/DefaultRekorURL are now consts pointing at the
    bundler's so the two packages cannot drift on the Sigstore
    public-good URLs.

KeylessSigner, NoOpSigner, SignBundle, and SignResult retain the same
public shape so the rest of the package (builder, pointer, OCI push)
is unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants