Skip to content

feat(bundler): add headless OIDC paths for bundle --attest#707

Merged
mchmarny merged 4 commits into
mainfrom
feat/headless-oidc
Apr 30, 2026
Merged

feat(bundler): add headless OIDC paths for bundle --attest#707
mchmarny merged 4 commits into
mainfrom
feat/headless-oidc

Conversation

@lockwobr

Copy link
Copy Markdown
Contributor

Summary

Add two non-breaking OIDC paths to bundle --attest so it works on headless hosts: --identity-token (with COSIGN_IDENTITY_TOKEN env) for pre-fetched tokens, and --oidc-device-flow (with AICR_OIDC_DEVICE_FLOW env) for the OAuth 2.0 Device Authorization Grant.

Motivation / Context

Today bundle --attest resolves OIDC via exactly two paths: ambient GitHub Actions credentials, or an interactive browser callback bound to localhost:<random-port>. The interactive flow is unusable on remote build boxes, bastions, or air-gapped runners — the OIDC provider redirects to localhost on the laptop where the user logs in, never on the host running aicr. Existing workarounds (SSH port-forwarding the random callback port, or running aicr on the laptop) are gymnastics.

This change introduces two cosign-aligned, non-breaking auth paths so
attestation is reachable on any host. Selection precedence:

  1. --identity-token / COSIGN_IDENTITY_TOKEN
  2. Ambient GitHub Actions OIDC
  3. --oidc-device-flow / AICR_OIDC_DEVICE_FLOW
  4. Interactive browser (default)

Closes: #682
Related: N/A

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • Build/CI/tooling

Component(s) Affected

  • CLI (cmd/aicr, pkg/cli)
  • API server (cmd/aicrd, pkg/api, pkg/server)
  • Recipe engine / data (pkg/recipe)
  • Bundlers (pkg/bundler, pkg/component/*)
  • Collectors / snapshotter (pkg/collector, pkg/snapshotter)
  • Validator (pkg/validator)
  • Core libraries (pkg/errors, pkg/k8s)
  • Docs/examples (docs/, examples/)
  • Other: chainsaw e2e (tests/chainsaw/cli/bundle-headless-oidc-ci/)

Implementation Notes

pkg/bundler/attestation/oidc.go

  • New FetchDeviceCodeOIDCToken wraps sigstore's already-vendored NewDeviceFlowTokenGetterForIssuer (RFC 8628). User sees a verification URL plus short code on stdout — fully headless.
  • Extract shared oauthflow.OIDConnect-with-context-deadline logic into runOIDCConnect. Both FetchInteractiveOIDCToken and the new device-code helper share the same goroutine/select shape.

pkg/cli/bundle.go

  • Add --identity-token (StringFlag, Sources: cli.EnvVars("COSIGN_IDENTITY_TOKEN")).
  • Add --oidc-device-flow (BoolFlag, Sources: cli.EnvVars("AICR_OIDC_DEVICE_FLOW")).
  • Refactor selectAttester to take *bundleCmdOptions and walk the four-tier precedence above.

Flag naming. --oidc-device-flow (not --headless) names the mechanism, not the use case. Two paths in this PR are both headless — calling either of them --headless would create ambiguity. The --oidc-* namespace also matches cosign and leaves room for future --oidc-issuer / --oidc-client-id.

Testing

# Commands run (prefer `make qualify` for non-trivial changes)
make qualify

Risk Assessment

  • Low — Isolated change, well-tested, easy to revert
  • Medium — Touches multiple components or has broader impact
  • High — Breaking change, affects critical paths, or complex rollout

Rollout notes:

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
  • Changes follow existing patterns in the codebase
  • Commits are cryptographically signed (git commit -S) — GPG signing info

@coderabbitai

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@github-actions

github-actions Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Coverage Report ✅

Metric Value
Coverage 75.2%
Threshold 70%
Status Pass
Coverage Badge
![Coverage](https://img.shields.io/badge/coverage-75.2%25-green)

Merging this branch changes the coverage (1 decrease, 1 increase)

Impacted Packages Coverage Δ 🤖
github.com/NVIDIA/aicr/pkg/bundler/attestation 66.86% (-2.26%) 👎
github.com/NVIDIA/aicr/pkg/cli 52.76% (+0.84%) 👍
github.com/NVIDIA/aicr/pkg/defaults 100.00% (ø)

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/NVIDIA/aicr/pkg/bundler/attestation/doc.go 0.00% (ø) 0 0 0
github.com/NVIDIA/aicr/pkg/bundler/attestation/oidc.go 57.14% (-2.43%) 63 (+16) 36 (+8) 27 (+8) 👎
github.com/NVIDIA/aicr/pkg/bundler/attestation/resolver.go 65.00% (+65.00%) 20 (+20) 13 (+13) 7 (+7) 🌟
github.com/NVIDIA/aicr/pkg/cli/bundle.go 3.08% (+2.36%) 130 (-10) 4 (+3) 126 (-13) 👍
github.com/NVIDIA/aicr/pkg/defaults/timeouts.go 0.00% (ø) 0 0 0

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

@lockwobr lockwobr force-pushed the feat/headless-oidc branch from 28ed036 to 65d37c3 Compare April 28, 2026 18:58
coderabbitai[bot]

This comment was marked as resolved.

@lockwobr lockwobr force-pushed the feat/headless-oidc branch from 65d37c3 to 6c5056d Compare April 28, 2026 19:22
coderabbitai[bot]

This comment was marked as resolved.

@lockwobr lockwobr force-pushed the feat/headless-oidc branch from 6c5056d to 2de8cc2 Compare April 28, 2026 19:32
coderabbitai[bot]

This comment was marked as resolved.

@lockwobr lockwobr force-pushed the feat/headless-oidc branch from 2de8cc2 to 5e45c12 Compare April 28, 2026 20:07
coderabbitai[bot]

This comment was marked as resolved.

@lockwobr lockwobr force-pushed the feat/headless-oidc branch from 5e45c12 to 501f2b3 Compare April 28, 2026 20:42
@github-actions github-actions Bot added size/XL and removed size/L labels Apr 28, 2026
@lockwobr lockwobr force-pushed the feat/headless-oidc branch 2 times, most recently from dd2d8b1 to 07ee015 Compare April 29, 2026 17:48

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

Solid refactor — pulling source-precedence logic into attestation.ResolveAttester and keeping pkg/cli as a thin shim is the right shape, and the resolver tests cover the four-tier precedence well. CI is fully green.

One medium concern: the new selectAttester returns resolver errors as-is, dropping the previous "; remove --attest to skip" guidance — worth restoring at the CLI layer so headless users who hit a 5-min device-flow timeout know the escape hatch.

Other findings are low/nit: secret-via---identity-token flag visible in ps, chainsaw verification-prompt grep coupling the smoke test to live Sigstore reachability, and a couple of construction/timing-constant nits in oidc.go. Nothing blocks merge once the medium item is addressed (or explicitly punted).

Comment thread pkg/cli/bundle.go
Comment thread pkg/cli/bundle.go Outdated
Comment thread tests/chainsaw/cli/bundle-headless-oidc-ci/chainsaw-test.yaml
Comment thread pkg/bundler/attestation/oidc.go
Comment thread pkg/bundler/attestation/oidc.go
@lockwobr lockwobr force-pushed the feat/headless-oidc branch from 07ee015 to a3f0b60 Compare April 30, 2026 16:05
@mchmarny mchmarny enabled auto-merge (squash) April 30, 2026 18:42
@mchmarny mchmarny merged commit 8843981 into main Apr 30, 2026
34 checks passed
@mchmarny mchmarny deleted the feat/headless-oidc branch April 30, 2026 18:50
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.

bundle --attest has no headless-local OIDC path (device-code or --identity-token)

2 participants