From a7d6c526754479dff3eb59b0f0e54924849e42b5 Mon Sep 17 00:00:00 2001 From: Dustin Kirkland Date: Tue, 9 Jun 2026 13:53:59 -0500 Subject: [PATCH 1/2] fulcio: add 'device' TokenProvider for OAuth 2.0 device-code flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new value for the GITSIGN_TOKEN_PROVIDER env var / gitsign.tokenProvider config: 'device'. When set, gitsign performs an RFC 8628 device authorization grant directly against cfg.Issuer (default oauth2.sigstore.dev) using the sigstore library's existing oauthflow.NewDeviceFlowTokenGetterForIssuer. The user opens the printed verification URL on any browser (phone, laptop, anywhere) to complete OAuth consent. The SSH session itself never needs a browser, port forward, or localhost callback — useful on remote/headless developer hosts where the interactive flow can't open a local browser. Implementation is parallel to the existing 'interactive' special case: 'device' sets authFlow directly to the DeviceFlowTokenGetter rather than going through the cosign providers registry and the StaticTokenGetter shim. The other registry-backed providers (spiffe, google-impersonation, etc.) are unchanged. Signed-off-by: Dustin Kirkland --- README.md | 4 ++-- internal/config/config.go | 7 ++++++- internal/fulcio/identity.go | 9 +++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e8549bb7..10bd3ee4 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ The following config options are supported: | redirectURL | | OIDC Redirect URL | | rekor | https://rekor.sigstore.dev | Address of Rekor server | | connectorID | | Optional Connector ID to auto-select to pre-select auth flow to use. For the public sigstore instance, valid values are:
- `https://github.com/login/oauth`
- `https://accounts.google.com`
- `https://login.microsoftonline.com` | -| tokenProvider | | Optional OIDC token provider to use to fetch tokens. If not set, any available providers are used. valid values are:
- `interactive`
- `spiffe`
- `google-workload-identity`
- `google-impersonation`
- `github-actions`
- `filesystem`
- `buildkite-agent` | +| tokenProvider | | Optional OIDC token provider to use to fetch tokens. If not set, any available providers are used. valid values are:
- `interactive`
- `device` (OAuth 2.0 device flow — for headless / remote-SSH workflows)
- `spiffe`
- `google-workload-identity`
- `google-impersonation`
- `github-actions`
- `filesystem`
- `buildkite-agent` | | timestampServerURL | | Address of timestamping authority. If set, a trusted timestamp will be included in the signature. | | timestampCertChain | | Path to PEM encoded certificate chain for RFC3161 Timestamp Authority verification. | | autoclose | true | If true, autoclose the browser window after `autocloseTimeout`. In order for autoclose to work you must also set `connectorID`. | @@ -93,7 +93,7 @@ The following config options are supported: | ---------------------------- | ------------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | GITSIGN_CREDENTIAL_CACHE | | | Optional path to [gitsign-credential-cache](cmd/gitsign-credential-cache/README.md) socket. | | GITSIGN_CONNECTOR_ID | ✅ | | Optional Connector ID to auto-select to pre-select auth flow to use. For the public sigstore instance, valid values are:
- `https://github.com/login/oauth`
- `https://accounts.google.com`
- `https://login.microsoftonline.com` | -| GITSIGN_TOKEN_PROVIDER | ✅ | | Optional OIDC token provider to use to fetch tokens. If not set, any available providers are used. valid values are:
- `interactive`
- `spiffe`
- `google-workload-identity`
- `google-impersonation`
- `github-actions`
- `filesystem`
- `buildkite-agent` | +| GITSIGN_TOKEN_PROVIDER | ✅ | | Optional OIDC token provider to use to fetch tokens. If not set, any available providers are used. valid values are:
- `interactive`
- `device` (OAuth 2.0 device flow — for headless / remote-SSH workflows)
- `spiffe`
- `google-workload-identity`
- `google-impersonation`
- `github-actions`
- `filesystem`
- `buildkite-agent` | | GITSIGN_FULCIO_URL | ✅ | https://fulcio.sigstore.dev | Address of Fulcio server | | GITSIGN_LOG | ❌ | | Path to log status output. Helpful for debugging when no TTY is available in the environment. | | GITSIGN_OIDC_CLIENT_ID | ✅ | sigstore | OIDC client ID for application diff --git a/internal/config/config.go b/internal/config/config.go index bc6149da..bd8c6881 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -77,7 +77,12 @@ type Config struct { ConnectorID string // TokenProviders select a OIDC token provider to use to fetch tokens. If not set, all providers are attempted. // See https://github.com/sigstore/cosign/tree/main/pkg/providers for more details. - // Valid values are: [interactive, spiffe, google-workload-identity, google-impersonation, github-actions, filesystem, buildkite-agent] + // Valid values are: [interactive, device, spiffe, google-workload-identity, google-impersonation, github-actions, filesystem, buildkite-agent] + // + // "device" runs the OAuth 2.0 device authorization grant (RFC 8628) against + // the configured Issuer — the user opens the printed verification URL on any + // browser to complete consent; the SSH session never needs a browser or + // port forward. Intended for headless / remote-SSH developer workflows. TokenProvider string // Timestamp Authority address to use to get a trusted timestamp diff --git a/internal/fulcio/identity.go b/internal/fulcio/identity.go index ba28505c..7888856a 100644 --- a/internal/fulcio/identity.go +++ b/internal/fulcio/identity.go @@ -238,6 +238,15 @@ func (f *IdentityFactory) NewIdentity(ctx context.Context, cfg *config.Config) ( if cfg.TokenProvider == "" && providers.Enabled(ctx) { // If no token provider is set, look for any available provider to use. provider = defaultFlowProvider{} + } else if cfg.TokenProvider == "device" { + // Special-case: use the OAuth 2.0 device authorization grant (RFC 8628) + // directly via the sigstore oauthflow package. The user opens the + // printed verification URL on any browser (phone, laptop, anywhere) to + // complete OAuth consent; the SSH session itself never needs a browser, + // port forward, or localhost callback. Useful on remote/headless dev + // hosts where the interactive flow can't open a browser locally. + fmt.Fprintln(f.out, "using device authorization flow") // nolint:errcheck + authFlow = oauthflow.NewDeviceFlowTokenGetterForIssuer(cfg.Issuer) } else if cfg.TokenProvider != "" && cfg.TokenProvider != "interactive" { fmt.Fprintln(f.out, "using token provider", cfg.TokenProvider) // nolint:errcheck From fe1e62346d3c346a343d089e8b4c3fb57716bc94 Mon Sep 17 00:00:00 2001 From: Billy Lynch Date: Tue, 28 Jul 2026 16:54:38 -0400 Subject: [PATCH 2/2] fulcio: rewrite if-else chain as switch to satisfy gocritic Signed-off-by: Billy Lynch Co-Authored-By: Claude Opus 4.8 --- internal/fulcio/identity.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/fulcio/identity.go b/internal/fulcio/identity.go index 7888856a..cc33723f 100644 --- a/internal/fulcio/identity.go +++ b/internal/fulcio/identity.go @@ -235,10 +235,11 @@ func (f *IdentityFactory) NewIdentity(ctx context.Context, cfg *config.Config) ( // If enabled, try OIDC token providers to get a token. unless the token provider is "interactive" (in which case always do default interactive flow). var provider providers.Interface - if cfg.TokenProvider == "" && providers.Enabled(ctx) { + switch { + case cfg.TokenProvider == "" && providers.Enabled(ctx): // If no token provider is set, look for any available provider to use. provider = defaultFlowProvider{} - } else if cfg.TokenProvider == "device" { + case cfg.TokenProvider == "device": // Special-case: use the OAuth 2.0 device authorization grant (RFC 8628) // directly via the sigstore oauthflow package. The user opens the // printed verification URL on any browser (phone, laptop, anywhere) to @@ -247,7 +248,7 @@ func (f *IdentityFactory) NewIdentity(ctx context.Context, cfg *config.Config) ( // hosts where the interactive flow can't open a browser locally. fmt.Fprintln(f.out, "using device authorization flow") // nolint:errcheck authFlow = oauthflow.NewDeviceFlowTokenGetterForIssuer(cfg.Issuer) - } else if cfg.TokenProvider != "" && cfg.TokenProvider != "interactive" { + case cfg.TokenProvider != "" && cfg.TokenProvider != "interactive": fmt.Fprintln(f.out, "using token provider", cfg.TokenProvider) // nolint:errcheck // If a token provider is explicitly set always use it, unless it's "interactive",