Skip to content

feat(verify): add trusted_root support for private Sigstore instances - #788

Open
trevor-vaughan wants to merge 1 commit into
mainfrom
opsx/768-trusted_root-support
Open

feat(verify): add trusted_root support for private Sigstore instances#788
trevor-vaughan wants to merge 1 commit into
mainfrom
opsx/768-trusted_root-support

Conversation

@trevor-vaughan

Copy link
Copy Markdown
Member

Summary


🦾 Written with LLM assistance [claude-opus-4-6]
💪 Reviewed by a human before submission


Add trusted_root support for private Sigstore instances to complyctl get signature verification.

Organizations running private Sigstore deployments (self-hosted Fulcio CA and Rekor transparency log) could not use complyctl get --verification because NewKeylessVerifier() hardcoded the public Sigstore TUF root. This change introduces a trusted_root field in VerificationConfig that accepts a filesystem path to a trusted_root.json file, allowing keyless verification against non-public Sigstore infrastructure.

Key changes:

  • Config (internal/complytime/config.go): VerificationConfig gains TrustedRoot string field with yaml:"trusted_root,omitempty" tag. ValidateVerificationConfig() enforces mutual exclusivity with key, requires issuer + identity, and validates file existence after filepath.Clean path sanitization.
  • Verifier (internal/cache/verify.go): NewKeylessVerifier() gains trustedRootPath string parameter. When non-empty, loads trust anchor via root.NewTrustedRootFromPath() instead of TUF fetch. Empty value preserves existing behavior.
  • CLI (cmd/complyctl/cli/get.go): buildVerifierFromConfig() passes cfg.TrustedRoot to NewKeylessVerifier().
  • Threat model: THR02.MIT05 documents user-supplied trust anchor risk (no TUF integrity protection, TOCTOU gap accepted as standard filesystem trust model).
  • Tests: Config validation (mutual exclusivity, missing keyless fields, file existence, path sanitization, YAML round-trip), verifier construction (fixture load, nonexistent path, invalid JSON, branch discrimination), and builder passthrough.

Related Issues

Review Hints

  • Start with internal/complytime/config.go -- the VerificationConfig struct change and ValidateVerificationConfig() additions are the entry point. Then follow through to internal/cache/verify.go (NewKeylessVerifier) and cmd/complyctl/cli/get.go (buildVerifierFromConfig one-line change).

  • The openspec/changes/custom-trusted-root/design.md file documents the four design decisions (D1-D4) and their rationale -- useful context for why this is a file path rather than inline JSON, and why a signature change was preferred over a separate constructor.

  • Test coverage is organized in three groups: config validation (internal/complytime/config_test.go), verifier construction (internal/cache/verify_test.go), and builder passthrough (cmd/complyctl/cli/get_verify_test.go). The verifier construction tests use a minimal fixture at internal/cache/testdata/trusted_root.json with empty trust material arrays -- this validates parsing, not verification capability.

  • No new dependencies. root.NewTrustedRootFromPath() is already available in the vendored sigstore-go.

@trevor-vaughan
trevor-vaughan requested a review from a team as a code owner July 29, 2026 14:43
@trevor-vaughan trevor-vaughan added the llm_assisted Filed or drafted with LLM assistance label Jul 29, 2026
@trevor-vaughan
trevor-vaughan force-pushed the opsx/768-trusted_root-support branch from 6b34744 to b85c7aa Compare July 29, 2026 14:44
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

✅ CRAP Load Analysis: PASS

Summary

Metric Value
Functions analysed 222
Avg complexity 4.1
Avg line coverage 64.6%
Avg CRAP score 7.8
CRAPload (>= 15) 24
Avg contract coverage 61.7%
Avg GazeCRAP score 14.5
GazeCRAPload (>= 15) 3
Regressions 4
Improvements 3
New functions 14

Quadrant Distribution

Quadrant Count
Q1 Safe 26
Q2 Complex but Tested 1
Q3 Simple but Underspecified 2
Q4 Dangerous 1

Regressions

Function Baseline CRAP Current CRAP Delta Baseline GazeCRAP Current GazeCRAP Delta
cmd/complyctl/cli/doctor.go:doctorCmd 2 4.10546875 2.10546875 N/A N/A N/A
internal/cache/sync.go:BuildLookupRef 4 5 1 N/A N/A N/A
internal/cache/sync.go:(*Sync).SyncPolicy 15.439453125 17.47058823529412 2.0311351102941195 N/A N/A N/A
internal/complytime/config.go:ValidateVerificationConfig 9 16 7 N/A 272 N/A

Improvements

Function Baseline CRAP Current CRAP Delta Baseline GazeCRAP Current GazeCRAP Delta
cmd/complyctl/cli/doctor.go:printDiagnostics 8.006910700788252 1 -7.006910700788252 N/A N/A N/A
internal/cache/complypack_sync.go:(*ComplypackSync).SyncComplypack 40.90168939548137 33.51745400565568 -7.38423538982569 N/A N/A N/A
internal/cache/verify.go:NewKeylessVerifier 42 11.880466472303205 -30.119533527696795 N/A 8 N/A

New Functions

Status Function CRAP GazeCRAP Note
+ cmd/complyctl/cli/doctor.go:summarizeResults 7.036814425244177 N/A new
+ cmd/complyctl/cli/doctor.go:countStatusSummary 4 N/A new
+ cmd/complyctl/cli/doctor.go:statusLabel 4 N/A new
+ cmd/complyctl/cli/doctor.go:resolveFormat 5 N/A new
+ cmd/complyctl/cli/doctor.go:resultLabel 2 N/A new
+ cmd/complyctl/cli/doctor.go:statusEmoji 4 N/A new
+ cmd/complyctl/cli/doctor.go:humanStatusPrefix 1 N/A new
+ cmd/complyctl/cli/doctor.go:textStatusPrefix 1 N/A new
+ cmd/complyctl/cli/doctor.go:renderDiagnostics 9.005184 N/A new
+ cmd/complyctl/cli/doctor.go:printDiagnosticsHuman 1 N/A new
+ cmd/complyctl/cli/doctor.go:printDiagnosticsText 1 N/A new
+ cmd/complyctl/cli/doctor.go:convertResult 2 N/A new
+ cmd/complyctl/cli/doctor.go:printDiagnosticsJSON 4.0092592592592595 N/A new
+ cmd/complyctl/cli/doctor.go:printDiagnosticsTo 4 N/A new

View full analysis logs

@trevor-vaughan
trevor-vaughan force-pushed the opsx/768-trusted_root-support branch from b85c7aa to e8b86dc Compare July 29, 2026 17:13
VerificationConfig gains a trusted_root field pointing to a local
trusted_root.json file. When set, NewKeylessVerifier loads the trust
anchor from disk instead of fetching the public Sigstore TUF root,
so keyless verification works with private deployments.

Validation rejects trusted_root combined with key-based verification,
requires issuer + identity, and checks file existence after
filepath.Clean.

- test(config): add validation tests for trusted_root field
  - Mutual exclusivity, missing keyless fields, file existence, path sanitization, YAML round-trip
- test(verify): add verifier construction tests for custom trusted root
- test(cli): add builder passthrough tests for TrustedRoot field
- docs(changelog): add trusted_root feature entry
- docs(threats): add THR02.MIT05 for user-supplied trust anchor risk
- docs(agents): add custom-trusted-root to recent changes
- docs(openspec): add custom-trusted-root change artifacts

Fixes #768

Assisted-by: Claude Opus 4.6
Signed-off-by: Trevor Vaughan <tvaughan@redhat.com>

@sonupreetam sonupreetam 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.

LGTM. Added some considerations after further investigation and opened issues #798 and #799 as alternatives/improvements. The threat model accepts filesystem trust as the baseline, but there are concrete mechanisms that could layer additional protection on top without abandoning the local-file approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llm_assisted Filed or drafted with LLM assistance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

verify: Add trusted_root support for private Sigstore instance verification

2 participants