-
Notifications
You must be signed in to change notification settings - Fork 70
Description
Bug Description
The Fulcio signer in witness has a bug where it correctly fetches tokens (from GitHub Actions, files, or command line flags) but then doesn't use them with StaticTokenGetter
. This causes the signing process to timeout after 2 minutes due to the interactive OAuth flow timeout.
Problem
The issue is in signer/fulcio/fulcio.go
. After fetching tokens in various ways (GitHub Actions, file, or flag), the code doesn't use these tokens with StaticTokenGetter
. Instead, it only uses the interactive OAuth flow when it's a terminal session, which has a hardcoded 2-minute timeout.
Current Code Flow
// In signer/fulcio/fulcio.go
var raw string
switch {
case fsp.Token == "" && fsp.TokenPath == "" && os.Getenv("GITHUB_ACTIONS") == "true":
// Fetches GitHub Actions token
raw, err = fetchToken(tokenURL, requestToken, "sigstore")
case fsp.TokenPath \!= "" && fsp.Token == "":
// Reads token from file
raw = string(f)
case fsp.Token \!= "" && fsp.TokenPath == "":
// Uses provided token
raw = fsp.Token
case fsp.Token == "" && isatty.IsTerminal(os.Stdin.Fd()):
// Interactive flow - but this only runs for terminals\!
tok, err := oauthflow.OIDConnect(..., oauthflow.DefaultIDTokenGetter)
raw = tok.RawString
}
// Problem: After getting 'raw' token, it's not used unless we're in a terminal\!
Root Cause
The interactive OAuth flow timeout is in the sigstore library:
// vendor/github.com/sigstore/sigstore/pkg/oauthflow/interactive.go
func getCode(doneCh chan string, errCh chan error) (string, error) {
timeoutCh := time.NewTimer(120 * time.Second) // 2-minute timeout\!
select {
case <-timeoutCh.C:
return "", errors.New("timeout")
}
}
Failing Test
I've included a test that demonstrates this issue:
func TestGitHubActionsTokenTimeout(t *testing.T) {
// Set up GitHub Actions environment
os.Setenv("GITHUB_ACTIONS", "true")
os.Setenv("ACTIONS_ID_TOKEN_REQUEST_URL", "https://example.com/token")
os.Setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "fake-token")
fsp := New(
WithFulcioURL("https://fulcio.sigstore.dev"),
WithOidcIssuer("https://oauth2.sigstore.dev/auth"),
WithOidcClientID("sigstore"),
)
start := time.Now()
_, err := fsp.Signer(context.Background())
elapsed := time.Since(start)
// This should fail quickly with our fake URL, not after 2 minutes
if elapsed > 30*time.Second {
t.Errorf("Signer took %v, suggesting interactive OAuth timeout", elapsed)
}
}
Proposed Fix
After collecting the token, use StaticTokenGetter
when a token is available:
// After the switch statement that sets 'raw'...
if raw \!= "" {
// Use static token getter for pre-existing tokens
staticGetter := &oauthflow.StaticTokenGetter{
RawToken: raw,
}
tok, err := staticGetter.GetIDToken(nil, oauth2.Config{})
if err \!= nil {
return nil, err
}
// Continue with the token...
} else if isatty.IsTerminal(os.Stdin.Fd()) {
// Only use interactive flow if no token is available AND we're in a terminal
tok, err := oauthflow.OIDConnect(...)
if err \!= nil {
return nil, err
}
raw = tok.RawString
} else {
return nil, errors.New("no token available and not in interactive terminal")
}
Impact
This bug affects:
- GitHub Actions workflows where the attested command takes more than 2 minutes
- Any automated (non-terminal) usage where tokens are provided via file or environment
- Makes witness unusable for long-running processes like test suites or complex builds
Reproduction
- Use witness-run-action in GitHub Actions with any command that takes >2 minutes
- The runner will receive a shutdown signal after exactly 2 minutes
- The workflow will fail with a timeout error
The attached test file (signer/fulcio/fulcio_timeout_test.go
) demonstrates this issue.