@@ -219,6 +219,74 @@ type OIDCAccessToken struct {
219219 ExpiresIn time.Duration
220220}
221221
222+ // buildJSONRequest creates a JSON POST request with standard proxy OIDC headers
223+ // (Content-Type and User-Agent). Callers can add additional headers (e.g., Accept,
224+ // Authorization) to the returned request before passing it to executeRequest.
225+ func buildJSONRequest (ctx context.Context , requestURL string , body any ) (* http.Request , error ) {
226+ bodyJSON , err := json .Marshal (body )
227+ if err != nil {
228+ return nil , fmt .Errorf ("failed to marshal request body: %w" , err )
229+ }
230+
231+ req , err := http .NewRequestWithContext (ctx , "POST" , requestURL , bytes .NewReader (bodyJSON ))
232+ if err != nil {
233+ return nil , fmt .Errorf ("failed to create request: %w" , err )
234+ }
235+
236+ req .Header .Set ("Content-Type" , "application/json" )
237+ req .Header .Set ("User-Agent" , "dependabot-proxy/1.0" )
238+
239+ return req , nil
240+ }
241+
242+ // executeRequest executes an HTTP request and returns the status code and raw
243+ // response body. The caller is responsible for checking the status code and
244+ // unmarshalling the body.
245+ func executeRequest (req * http.Request ) (int , []byte , error ) {
246+ client := & http.Client {
247+ Timeout : 10 * time .Second ,
248+ }
249+
250+ resp , err := client .Do (req )
251+ if err != nil {
252+ return 0 , nil , fmt .Errorf ("failed to execute request: %w" , err )
253+ }
254+ defer resp .Body .Close ()
255+
256+ body , err := io .ReadAll (resp .Body )
257+ if err != nil {
258+ return 0 , nil , fmt .Errorf ("failed to read response body: %w" , err )
259+ }
260+
261+ return resp .StatusCode , body , nil
262+ }
263+
264+ // getAccessTokenForDevOps is a shared wrapper for all OIDC providers' ForDevOps
265+ // functions. It checks OIDC configuration, fetches the GitHub OIDC token, and
266+ // calls the provider-specific exchange function.
267+ func getAccessTokenForDevOps (
268+ ctx context.Context ,
269+ getGitHubToken func (ctx context.Context ) (string , error ),
270+ exchange func (ctx context.Context , githubToken string ) (* OIDCAccessToken , error ),
271+ providerName string ,
272+ ) (* OIDCAccessToken , error ) {
273+ if ! IsOIDCConfigured () {
274+ return nil , fmt .Errorf ("GitHub Actions OIDC is not configured" )
275+ }
276+
277+ githubToken , err := getGitHubToken (ctx )
278+ if err != nil {
279+ return nil , fmt .Errorf ("failed to get GitHub OIDC token: %w" , err )
280+ }
281+
282+ token , err := exchange (ctx , githubToken )
283+ if err != nil {
284+ return nil , fmt .Errorf ("failed to exchange GitHub token for %s token: %w" , providerName , err )
285+ }
286+
287+ return token , nil
288+ }
289+
222290// GetAzureAccessToken exchanges a GitHub Actions OIDC token for an Azure AD access token
223291// using the OAuth 2.0 client credentials flow with federated identity credentials.
224292// This is specifically designed for authenticating with Azure DevOps.
@@ -296,23 +364,13 @@ func GetAzureAccessToken(ctx context.Context, params AzureOIDCParameters, github
296364// GetAzureAccessTokenForDevOps is a convenience function that combines fetching the GitHub OIDC token
297365// and exchanging it for an Azure AD access token in a single call.
298366func GetAzureAccessTokenForDevOps (ctx context.Context , params AzureOIDCParameters ) (* OIDCAccessToken , error ) {
299- if ! IsOIDCConfigured () {
300- return nil , fmt .Errorf ("GitHub Actions OIDC is not configured" )
301- }
302-
303- // Get GitHub OIDC token
304- githubToken , err := GetTokenForAzureADExchange (ctx )
305- if err != nil {
306- return nil , fmt .Errorf ("failed to get GitHub OIDC token: %w" , err )
307- }
308-
309- // Exchange for Azure token
310- azureToken , err := GetAzureAccessToken (ctx , params , githubToken )
311- if err != nil {
312- return nil , fmt .Errorf ("failed to exchange GitHub token for Azure token: %w" , err )
313- }
314-
315- return azureToken , nil
367+ return getAccessTokenForDevOps (ctx ,
368+ func (ctx context.Context ) (string , error ) { return GetTokenForAzureADExchange (ctx ) },
369+ func (ctx context.Context , githubToken string ) (* OIDCAccessToken , error ) {
370+ return GetAzureAccessToken (ctx , params , githubToken )
371+ },
372+ "Azure" ,
373+ )
316374}
317375
318376// GetJFrogAccessToken exchanges a GitHub Actions OIDC token for a JFrog access token
@@ -345,35 +403,18 @@ func GetJFrogAccessToken(ctx context.Context, params JFrogOIDCParameters, github
345403 }
346404 tokenURL := fmt .Sprintf ("%s/access/api/v1/oidc/token" , strings .TrimSuffix (params .JFrogURL , "/" ))
347405
348- tokenRequestJson , err := json .Marshal (tokenRequest )
349- if err != nil {
350- return nil , fmt .Errorf ("failed to marshal JFrog token request: %w" , err )
351- }
352-
353- req , err := http .NewRequestWithContext (ctx , "POST" , tokenURL , bytes .NewReader (tokenRequestJson ))
406+ req , err := buildJSONRequest (ctx , tokenURL , tokenRequest )
354407 if err != nil {
355- return nil , fmt .Errorf ("failed to create JFrog token request: %w" , err )
408+ return nil , fmt .Errorf ("JFrog token request: %w" , err )
356409 }
357410
358- req .Header .Set ("Content-Type" , "application/json" )
359- req .Header .Set ("User-Agent" , "dependabot-proxy/1.0" )
360-
361- client := & http.Client {
362- Timeout : 10 * time .Second ,
363- }
364- resp , err := client .Do (req )
365- if err != nil {
366- return nil , fmt .Errorf ("failed to execute JFrog token request: %w" , err )
367- }
368- defer resp .Body .Close ()
369-
370- body , err := io .ReadAll (resp .Body )
411+ statusCode , body , err := executeRequest (req )
371412 if err != nil {
372- return nil , fmt .Errorf ("failed to read JFrog token response body : %w" , err )
413+ return nil , fmt .Errorf ("JFrog token request : %w" , err )
373414 }
374415
375- if resp . StatusCode != http .StatusOK {
376- return nil , fmt .Errorf ("JFrog returned status %d: %s" , resp . StatusCode , string (body ))
416+ if statusCode != http .StatusOK {
417+ return nil , fmt .Errorf ("JFrog returned status %d: %s" , statusCode , string (body ))
377418 }
378419
379420 var tokenResp jfrogTokenResponse
@@ -397,23 +438,13 @@ func GetJFrogAccessToken(ctx context.Context, params JFrogOIDCParameters, github
397438}
398439
399440func GetJFrogAccessTokenForDevOps (ctx context.Context , params JFrogOIDCParameters ) (* OIDCAccessToken , error ) {
400- if ! IsOIDCConfigured () {
401- return nil , fmt .Errorf ("GitHub Actions OIDC is not configured" )
402- }
403-
404- // Get GitHub OIDC token
405- githubToken , err := GetToken (ctx , params .Audience )
406- if err != nil {
407- return nil , fmt .Errorf ("failed to get GitHub OIDC token: %w" , err )
408- }
409-
410- // Exchange for JFrog token
411- jfrogToken , err := GetJFrogAccessToken (ctx , params , githubToken )
412- if err != nil {
413- return nil , fmt .Errorf ("failed to exchange GitHub token for JFrog token: %w" , err )
414- }
415-
416- return jfrogToken , nil
441+ return getAccessTokenForDevOps (ctx ,
442+ func (ctx context.Context ) (string , error ) { return GetToken (ctx , params .Audience ) },
443+ func (ctx context.Context , githubToken string ) (* OIDCAccessToken , error ) {
444+ return GetJFrogAccessToken (ctx , params , githubToken )
445+ },
446+ "JFrog" ,
447+ )
417448}
418449
419450// GetAWSAccessToken exchanges a GitHub Actions OIDC token for temporary AWS credentials
@@ -573,23 +604,13 @@ func GetAWSAccessToken(ctx context.Context, params AWSOIDCParameters, githubToke
573604}
574605
575606func GetAWSAccessTokenForDevOps (ctx context.Context , params AWSOIDCParameters ) (* OIDCAccessToken , error ) {
576- if ! IsOIDCConfigured () {
577- return nil , fmt .Errorf ("GitHub Actions OIDC is not configured" )
578- }
579-
580- // Get GitHub OIDC token
581- githubToken , err := GetToken (ctx , params .Audience )
582- if err != nil {
583- return nil , fmt .Errorf ("failed to get GitHub OIDC token: %w" , err )
584- }
585-
586- // Exchange for AWS token
587- awsToken , err := GetAWSAccessToken (ctx , params , githubToken )
588- if err != nil {
589- return nil , fmt .Errorf ("failed to exchange GitHub token for AWS token: %w" , err )
590- }
591-
592- return awsToken , nil
607+ return getAccessTokenForDevOps (ctx ,
608+ func (ctx context.Context ) (string , error ) { return GetToken (ctx , params .Audience ) },
609+ func (ctx context.Context , githubToken string ) (* OIDCAccessToken , error ) {
610+ return GetAWSAccessToken (ctx , params , githubToken )
611+ },
612+ "AWS" ,
613+ )
593614}
594615
595616func GetCloudsmithAccessToken (ctx context.Context , params CloudsmithOIDCParameters , githubToken string ) (* OIDCAccessToken , error ) {
@@ -611,37 +632,20 @@ func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParamete
611632 ServiceSlug : params .ServiceSlug ,
612633 }
613634
614- requestBodyJson , err := json .Marshal (requestBody )
615- if err != nil {
616- return nil , fmt .Errorf ("failed to marshal cloudsmith token request: %w" , err )
617- }
618-
619635 tokenURL := fmt .Sprintf ("https://%s/openid/%s/" , params .ApiHost , params .OrgName )
620- req , err := http . NewRequestWithContext (ctx , "POST" , tokenURL , bytes . NewReader ( requestBodyJson ) )
636+ req , err := buildJSONRequest (ctx , tokenURL , requestBody )
621637 if err != nil {
622- return nil , fmt .Errorf ("failed to create cloudsmith token request: %w" , err )
638+ return nil , fmt .Errorf ("cloudsmith token request: %w" , err )
623639 }
624-
625- req .Header .Set ("Content-Type" , "application/json" )
626640 req .Header .Set ("Accept" , "application/json" )
627- req .Header .Set ("User-Agent" , "dependabot-proxy/1.0" )
628-
629- client := & http.Client {
630- Timeout : 10 * time .Second ,
631- }
632- resp , err := client .Do (req )
633- if err != nil {
634- return nil , fmt .Errorf ("failed to execute cloudsmith token request: %w" , err )
635- }
636- defer resp .Body .Close ()
637641
638- body , err := io . ReadAll ( resp . Body )
642+ statusCode , body , err := executeRequest ( req )
639643 if err != nil {
640- return nil , fmt .Errorf ("failed to read cloudsmith token response body : %w" , err )
644+ return nil , fmt .Errorf ("cloudsmith token request : %w" , err )
641645 }
642646
643- if resp . StatusCode != http .StatusOK && resp . StatusCode != http .StatusCreated {
644- return nil , fmt .Errorf ("cloudsmith returned status %d: %s" , resp . StatusCode , string (body ))
647+ if statusCode != http .StatusOK && statusCode != http .StatusCreated {
648+ return nil , fmt .Errorf ("cloudsmith returned status %d: %s" , statusCode , string (body ))
645649 }
646650
647651 var tokenResp cloudsmithTokenResponse
@@ -661,22 +665,13 @@ func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParamete
661665}
662666
663667func GetCloudsmithAccessTokenForDevOps (ctx context.Context , params CloudsmithOIDCParameters ) (* OIDCAccessToken , error ) {
664- if ! IsOIDCConfigured () {
665- return nil , fmt .Errorf ("GitHub Actions OIDC is not configured" )
666- }
667-
668- // Get GitHub OIDC token
669- githubToken , err := GetToken (ctx , params .Audience )
670- if err != nil {
671- return nil , fmt .Errorf ("failed to get GitHub OIDC token: %w" , err )
672- }
673-
674- cloudsmithToken , err := GetCloudsmithAccessToken (ctx , params , githubToken )
675- if err != nil {
676- return nil , fmt .Errorf ("failed to exchange GitHub token for cloudsmith token: %w" , err )
677- }
678-
679- return cloudsmithToken , nil
668+ return getAccessTokenForDevOps (ctx ,
669+ func (ctx context.Context ) (string , error ) { return GetToken (ctx , params .Audience ) },
670+ func (ctx context.Context , githubToken string ) (* OIDCAccessToken , error ) {
671+ return GetCloudsmithAccessToken (ctx , params , githubToken )
672+ },
673+ "cloudsmith" ,
674+ )
680675}
681676
682677func GetGCPAccessToken (ctx context.Context , params GCPOIDCParameters , githubToken string ) (* OIDCAccessToken , error ) {
@@ -700,36 +695,19 @@ func GetGCPAccessToken(ctx context.Context, params GCPOIDCParameters, githubToke
700695 Scope : "https://www.googleapis.com/auth/cloud-platform" ,
701696 }
702697
703- stsBodyJSON , err := json .Marshal (stsReqBody )
704- if err != nil {
705- return nil , fmt .Errorf ("failed to marshal GCP STS request: %w" , err )
706- }
707-
708- stsReq , err := http .NewRequestWithContext (ctx , "POST" , "https://sts.googleapis.com/v1/token" , bytes .NewReader (stsBodyJSON ))
698+ stsReq , err := buildJSONRequest (ctx , "https://sts.googleapis.com/v1/token" , stsReqBody )
709699 if err != nil {
710- return nil , fmt .Errorf ("failed to create GCP STS request : %w" , err )
700+ return nil , fmt .Errorf ("GCP STS token exchange : %w" , err )
711701 }
712-
713- stsReq .Header .Set ("Content-Type" , "application/json" )
714702 stsReq .Header .Set ("Accept" , "application/json" )
715- stsReq .Header .Set ("User-Agent" , "dependabot-proxy/1.0" )
716703
717- client := & http.Client {
718- Timeout : 10 * time .Second ,
719- }
720- stsResp , err := client .Do (stsReq )
704+ stsStatusCode , stsBody , err := executeRequest (stsReq )
721705 if err != nil {
722- return nil , fmt .Errorf ("failed to execute GCP STS request : %w" , err )
706+ return nil , fmt .Errorf ("GCP STS token exchange : %w" , err )
723707 }
724- defer stsResp .Body .Close ()
725708
726- stsBody , err := io .ReadAll (stsResp .Body )
727- if err != nil {
728- return nil , fmt .Errorf ("failed to read GCP STS response body: %w" , err )
729- }
730-
731- if stsResp .StatusCode != http .StatusOK {
732- return nil , fmt .Errorf ("GCP STS returned status %d (audience: %s): %s" , stsResp .StatusCode , params .Audience , string (stsBody ))
709+ if stsStatusCode != http .StatusOK {
710+ return nil , fmt .Errorf ("GCP STS returned status %d (audience: %s): %s" , stsStatusCode , params .Audience , string (stsBody ))
733711 }
734712
735713 var stsTokenResp gcpSTSTokenResponse
@@ -758,35 +736,21 @@ func GetGCPAccessToken(ctx context.Context, params GCPOIDCParameters, githubToke
758736 Scope : []string {"https://www.googleapis.com/auth/cloud-platform" },
759737 }
760738
761- iamBodyJSON , err := json .Marshal (iamReqBody )
762- if err != nil {
763- return nil , fmt .Errorf ("failed to marshal GCP IAM request: %w" , err )
764- }
765-
766739 iamURL := fmt .Sprintf ("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken" , params .ServiceAccount )
767- iamReq , err := http . NewRequestWithContext (ctx , "POST" , iamURL , bytes . NewReader ( iamBodyJSON ) )
740+ iamReq , err := buildJSONRequest (ctx , iamURL , iamReqBody )
768741 if err != nil {
769- return nil , fmt .Errorf ("failed to create GCP IAM request : %w" , err )
742+ return nil , fmt .Errorf ("GCP IAM impersonation : %w" , err )
770743 }
771-
772- iamReq .Header .Set ("Content-Type" , "application/json" )
773744 iamReq .Header .Set ("Accept" , "application/json" )
774745 iamReq .Header .Set ("Authorization" , fmt .Sprintf ("Bearer %s" , stsTokenResp .AccessToken ))
775- iamReq .Header .Set ("User-Agent" , "dependabot-proxy/1.0" )
776746
777- iamResp , err := client . Do (iamReq )
747+ iamStatusCode , iamBody , err := executeRequest (iamReq )
778748 if err != nil {
779- return nil , fmt .Errorf ("failed to execute GCP IAM request : %w" , err )
749+ return nil , fmt .Errorf ("GCP IAM impersonation : %w" , err )
780750 }
781- defer iamResp .Body .Close ()
782751
783- iamBody , err := io .ReadAll (iamResp .Body )
784- if err != nil {
785- return nil , fmt .Errorf ("failed to read GCP IAM response body: %w" , err )
786- }
787-
788- if iamResp .StatusCode != http .StatusOK {
789- return nil , fmt .Errorf ("GCP IAM returned status %d (service-account: %s): %s" , iamResp .StatusCode , params .ServiceAccount , string (iamBody ))
752+ if iamStatusCode != http .StatusOK {
753+ return nil , fmt .Errorf ("GCP IAM returned status %d (service-account: %s): %s" , iamStatusCode , params .ServiceAccount , string (iamBody ))
790754 }
791755
792756 var iamTokenResp gcpIAMGenerateAccessTokenResponse
@@ -815,22 +779,13 @@ func GetGCPAccessToken(ctx context.Context, params GCPOIDCParameters, githubToke
815779}
816780
817781func GetGCPAccessTokenForDevOps (ctx context.Context , params GCPOIDCParameters ) (* OIDCAccessToken , error ) {
818- if ! IsOIDCConfigured () {
819- return nil , fmt .Errorf ("GitHub Actions OIDC is not configured" )
820- }
821-
822- // Get GitHub OIDC token
823- githubToken , err := GetToken (ctx , params .Audience )
824- if err != nil {
825- return nil , fmt .Errorf ("failed to get GitHub OIDC token: %w" , err )
826- }
827-
828- gcpToken , err := GetGCPAccessToken (ctx , params , githubToken )
829- if err != nil {
830- return nil , fmt .Errorf ("failed to exchange GitHub token for GCP token: %w" , err )
831- }
832-
833- return gcpToken , nil
782+ return getAccessTokenForDevOps (ctx ,
783+ func (ctx context.Context ) (string , error ) { return GetToken (ctx , params .Audience ) },
784+ func (ctx context.Context , githubToken string ) (* OIDCAccessToken , error ) {
785+ return GetGCPAccessToken (ctx , params , githubToken )
786+ },
787+ "GCP" ,
788+ )
834789}
835790
836791func calculateContentSha256Header (payload []byte ) string {
0 commit comments