66 "encoding/json"
77 "fmt"
88 "io"
9+ "net/http"
10+ "net/url"
911 "os"
1012 "path/filepath"
1113 "time"
@@ -245,6 +247,17 @@ func signProvenanceWithSigstore(ctx context.Context, statement *in_toto.Statemen
245247
246248 // Configure Fulcio for GitHub OIDC if we have a token
247249 if os .Getenv ("ACTIONS_ID_TOKEN_REQUEST_TOKEN" ) != "" {
250+ // Fetch the GitHub OIDC token for Sigstore
251+ idToken , err := fetchGitHubOIDCToken (ctx , "sigstore" )
252+ if err != nil {
253+ return nil , & SigningError {
254+ Type : ErrorTypeSigstore ,
255+ Artifact : statement .Subject [0 ].Name ,
256+ Message : fmt .Sprintf ("failed to fetch GitHub OIDC token: %v" , err ),
257+ Cause : err ,
258+ }
259+ }
260+
248261 // Select Fulcio service from signing config
249262 fulcioService , err := root .SelectService (signingConfig .FulcioCertificateAuthorityURLs (), sign .FulcioAPIVersions , time .Now ())
250263 if err != nil {
@@ -263,8 +276,7 @@ func signProvenanceWithSigstore(ctx context.Context, statement *in_toto.Statemen
263276 }
264277 bundleOpts .CertificateProvider = sign .NewFulcio (fulcioOpts )
265278 bundleOpts .CertificateProviderOptions = & sign.CertificateProviderOptions {
266- // Let sigstore-go automatically handle GitHub OIDC
267- // It will use ACTIONS_ID_TOKEN_REQUEST_TOKEN/URL automatically
279+ IDToken : idToken ,
268280 }
269281
270282 // Configure Rekor transparency log
@@ -354,3 +366,71 @@ func validateSigstoreEnvironment() error {
354366 log .Debug ("Sigstore environment validation passed" )
355367 return nil
356368}
369+
370+ // fetchGitHubOIDCToken fetches an OIDC token from GitHub Actions for Sigstore.
371+ // It uses the ACTIONS_ID_TOKEN_REQUEST_TOKEN and ACTIONS_ID_TOKEN_REQUEST_URL
372+ // environment variables to authenticate and retrieve a JWT token with the specified audience.
373+ func fetchGitHubOIDCToken (ctx context.Context , audience string ) (string , error ) {
374+ requestURL := os .Getenv ("ACTIONS_ID_TOKEN_REQUEST_URL" )
375+ requestToken := os .Getenv ("ACTIONS_ID_TOKEN_REQUEST_TOKEN" )
376+
377+ if requestURL == "" || requestToken == "" {
378+ return "" , fmt .Errorf ("GitHub OIDC environment not configured" )
379+ }
380+
381+ // Parse the request URL
382+ u , err := url .Parse (requestURL )
383+ if err != nil {
384+ return "" , fmt .Errorf ("failed to parse ACTIONS_ID_TOKEN_REQUEST_URL: %w" , err )
385+ }
386+
387+ // Add the audience parameter
388+ q := u .Query ()
389+ q .Set ("audience" , audience )
390+ u .RawQuery = q .Encode ()
391+
392+ // Create HTTP request with context
393+ req , err := http .NewRequestWithContext (ctx , http .MethodGet , u .String (), nil )
394+ if err != nil {
395+ return "" , fmt .Errorf ("failed to create request: %w" , err )
396+ }
397+
398+ req .Header .Set ("Authorization" , fmt .Sprintf ("Bearer %s" , requestToken ))
399+
400+ // Execute request with timeout
401+ client := & http.Client {Timeout : 30 * time .Second }
402+ resp , err := client .Do (req )
403+ if err != nil {
404+ return "" , fmt .Errorf ("failed to fetch token: %w" , err )
405+ }
406+ defer resp .Body .Close ()
407+
408+ // Check response status
409+ if resp .StatusCode != http .StatusOK {
410+ bodyBytes , _ := io .ReadAll (resp .Body )
411+ return "" , fmt .Errorf ("failed to get OIDC token, status: %d, body: %s" ,
412+ resp .StatusCode , string (bodyBytes ))
413+ }
414+
415+ // Parse response
416+ var payload struct {
417+ Value string `json:"value"`
418+ }
419+ if err := json .NewDecoder (resp .Body ).Decode (& payload ); err != nil {
420+ return "" , fmt .Errorf ("failed to decode response: %w" , err )
421+ }
422+
423+ if payload .Value == "" {
424+ return "" , fmt .Errorf ("received empty token from GitHub OIDC" )
425+ }
426+
427+ return payload .Value , nil
428+ }
429+
430+ // getEnvOrDefault returns environment variable value or default
431+ func getEnvOrDefault (key , defaultValue string ) string {
432+ if value := os .Getenv (key ); value != "" {
433+ return value
434+ }
435+ return defaultValue
436+ }
0 commit comments