From 86a3909b3f2a47dbe2a32c4b29fc5806f44adac0 Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:06:33 +0200 Subject: [PATCH 1/9] Implements Cloudsmith OIDC support Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/oidc/actions_oidc.go | 99 ++++++++++++++ internal/oidc/actions_oidc_test.go | 188 ++++++++++++++++++++++++++ internal/oidc/oidc_credential.go | 45 ++++++ internal/oidc/oidc_credential_test.go | 102 ++++++++++++++ 4 files changed, 434 insertions(+) diff --git a/internal/oidc/actions_oidc.go b/internal/oidc/actions_oidc.go index 7b83fc5..187319c 100644 --- a/internal/oidc/actions_oidc.go +++ b/internal/oidc/actions_oidc.go @@ -178,6 +178,15 @@ type awsTokenResponse struct { Expiration float64 `json:"expiration"` } +type cloudsmithTokenRequest struct { + OIDCToken string `json:"oidc_token"` + ServiceSlug string `json:"service_slug"` +} + +type cloudsmithTokenResponse struct { + Token string `json:"token"` +} + // OIDCAccessToken represents an access token with its expiry information type OIDCAccessToken struct { Token string @@ -557,6 +566,96 @@ func GetAWSAccessTokenForDevOps(ctx context.Context, params AWSOIDCParameters) ( return awsToken, nil } +func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParameters, githubToken string) (*OIDCAccessToken, error) { + if params.ServiceSlug == "" { + return nil, fmt.Errorf("service slug is required") + } + if params.ApiHost == "" { + return nil, fmt.Errorf("API host is required") + } + if params.Audience == "" { + return nil, fmt.Errorf("audience is required") + } + if params.OrgName == "" { + return nil, fmt.Errorf("org name is required") + } + if githubToken == "" { + return nil, fmt.Errorf("GitHub token is required") + } + + requestBody := cloudsmithTokenRequest{ + OIDCToken: githubToken, + ServiceSlug: params.ServiceSlug, + } + + requestBodyJson, err := json.Marshal(requestBody) + if err != nil { + return nil, fmt.Errorf("failed to marshal cloudsmith token request: %w", err) + } + + tokenURL := fmt.Sprintf("https://%s/openid/%s/", params.ApiHost, params.OrgName) + req, err := http.NewRequestWithContext(ctx, "POST", tokenURL, bytes.NewReader(requestBodyJson)) + if err != nil { + return nil, fmt.Errorf("failed to create cloudsmith token request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "dependabot-proxy/1.0") + + client := &http.Client{ + Timeout: 10 * time.Second, + } + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to execute cloudsmith token request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read cloudsmith token response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Cloudsmith returned status %d: %s", resp.StatusCode, string(body)) + } + + var tokenResp cloudsmithTokenResponse + if err := json.Unmarshal(body, &tokenResp); err != nil { + return nil, fmt.Errorf("failed to parse cloudsmith token response: %w", err) + } + + if tokenResp.Token == "" { + return nil, fmt.Errorf("Cloudsmith token response does not contain a token") + } + + // Cloudsmith tokens are valid for 2 hours according to their documentation + return &OIDCAccessToken{ + Token: tokenResp.Token, + ExpiresIn: 2 * time.Hour, + }, nil +} + +func GetCloudsmithAccessTokenForDevOps(ctx context.Context, params CloudsmithOIDCParameters) (*OIDCAccessToken, error) { + if !IsOIDCConfigured() { + return nil, fmt.Errorf("GitHub Actions OIDC is not configured") + } + + // Get GitHub OIDC token + githubToken, err := GetToken(ctx, params.Audience) + if err != nil { + return nil, fmt.Errorf("failed to get GitHub OIDC token: %w", err) + } + + cloudsmithToken, err := GetCloudsmithAccessToken(ctx, params, githubToken) + if err != nil { + return nil, fmt.Errorf("failed to exchange GitHub token for cloudsmith token: %w", err) + } + + return cloudsmithToken, nil +} + func calculateContentSha256Header(payload []byte) string { payloadHash := sha256.Sum256(payload) return hex.EncodeToString(payloadHash[:]) diff --git a/internal/oidc/actions_oidc_test.go b/internal/oidc/actions_oidc_test.go index 4064402..55dcc77 100644 --- a/internal/oidc/actions_oidc_test.go +++ b/internal/oidc/actions_oidc_test.go @@ -4,12 +4,14 @@ import ( "context" "encoding/json" "encoding/xml" + "fmt" "io" "net/http" "net/http/httptest" "os" "strings" "testing" + "time" "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" @@ -832,3 +834,189 @@ func TestGetAWSAccessToken(t *testing.T) { }) } } + +func TestGetCloudsmithAccessToken(t *testing.T) { + tests := []struct { + name string + params CloudsmithOIDCParameters + githubToken string + serverHandler http.HandlerFunc + expectError bool + expectedToken string + }{ + { + name: "successful token exchange", + params: CloudsmithOIDCParameters{ + OrgName: "my-org", + ServiceSlug: "my-service", + ApiHost: "api.example.com", + Audience: "my-audience", + }, + githubToken: "test-github-jwt-token", + serverHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "api.example.com", r.Host) + assert.Equal(t, "/openid/my-org/", r.URL.Path) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + assert.Equal(t, "application/json", r.Header.Get("Accept")) + assert.Equal(t, "dependabot-proxy/1.0", r.Header.Get("User-Agent")) + + bodyBytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + + var request cloudsmithTokenRequest + err = json.Unmarshal(bodyBytes, &request) + require.NoError(t, err) + + assert.Equal(t, "test-github-jwt-token", request.OIDCToken) + assert.Equal(t, "my-service", request.ServiceSlug) + + resp := cloudsmithTokenResponse{ + Token: "test-cloudsmith-token", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + }, + expectError: false, + expectedToken: "test-cloudsmith-token", + }, + { + name: "missing service slug", + params: CloudsmithOIDCParameters{ + OrgName: "my-org", + ApiHost: "api.cloudsmith.io", + Audience: "my-audience", + }, + githubToken: "test-github-jwt-token", + expectError: true, + }, + { + name: "missing API host", + params: CloudsmithOIDCParameters{ + OrgName: "my-org", + ServiceSlug: "my-service", + Audience: "my-audience", + }, + githubToken: "test-github-jwt-token", + expectError: true, + }, + { + name: "missing audience", + params: CloudsmithOIDCParameters{ + OrgName: "my-org", + ServiceSlug: "my-service", + ApiHost: "api.cloudsmith.io", + }, + githubToken: "test-github-jwt-token", + expectError: true, + }, + { + name: "missing org name", + params: CloudsmithOIDCParameters{ + ServiceSlug: "my-service", + ApiHost: "api.cloudsmith.io", + Audience: "my-audience", + }, + githubToken: "test-github-jwt-token", + expectError: true, + }, + { + name: "missing GitHub token", + params: CloudsmithOIDCParameters{ + OrgName: "my-org", + ServiceSlug: "my-service", + ApiHost: "api.cloudsmith.io", + Audience: "my-audience", + }, + githubToken: "", + expectError: true, + }, + { + name: "Cloudsmith returns 401", + params: CloudsmithOIDCParameters{ + OrgName: "my-org", + ServiceSlug: "my-service", + ApiHost: "api.cloudsmith.io", + Audience: "my-audience", + }, + githubToken: "invalid-token", + serverHandler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`{"detail":"Invalid token."}`)) + }, + expectError: true, + }, + { + name: "Cloudsmith returns invalid JSON", + params: CloudsmithOIDCParameters{ + OrgName: "my-org", + ServiceSlug: "my-service", + ApiHost: "api.cloudsmith.io", + Audience: "my-audience", + }, + githubToken: "test-github-jwt-token", + serverHandler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{invalid json`)) + }, + expectError: true, + }, + { + name: "Cloudsmith returns empty token", + params: CloudsmithOIDCParameters{ + OrgName: "my-org", + ServiceSlug: "my-service", + ApiHost: "api.cloudsmith.io", + Audience: "my-audience", + }, + githubToken: "test-github-jwt-token", + serverHandler: func(w http.ResponseWriter, r *http.Request) { + resp := cloudsmithTokenResponse{ + Token: "", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + }, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + + var cloudsmithToken *OIDCAccessToken + var err error + + if tt.params.ApiHost != "" && tt.params.OrgName != "" { + // Create a test server for Cloudsmith + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + url := fmt.Sprintf("https://%s/openid/%s/", tt.params.ApiHost, tt.params.OrgName) + httpmock.RegisterResponder("POST", url, httpmock.Responder(func(req *http.Request) (*http.Response, error) { + if tt.serverHandler != nil { + rr := httptest.NewRecorder() + tt.serverHandler(rr, req) + return rr.Result(), nil + } + // If no handler provided but we expected one (e.g. for error cases where we don't reach the server), + // this mock might not be hit, which is fine. + return httpmock.NewStringResponse(404, "Not Found"), nil + })) + } + + cloudsmithToken, err = GetCloudsmithAccessToken(ctx, tt.params, tt.githubToken) + + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, cloudsmithToken) + assert.Equal(t, tt.expectedToken, cloudsmithToken.Token) + assert.Equal(t, 2*time.Hour, cloudsmithToken.ExpiresIn) + } + }) + } +} diff --git a/internal/oidc/oidc_credential.go b/internal/oidc/oidc_credential.go index 3914b18..c8b7de0 100644 --- a/internal/oidc/oidc_credential.go +++ b/internal/oidc/oidc_credential.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "net/url" + "os" "sync" "time" @@ -15,6 +16,14 @@ import ( "github.com/dependabot/proxy/internal/logging" ) +const ( + envActionsRepositoryOwner = "ACTIONS_REPOSITORY_OWNER" +) + +func GetRepositoryOwner() string { + return os.Getenv(envActionsRepositoryOwner) +} + type OIDCParameters interface { Name() string } @@ -52,6 +61,17 @@ func (a *AWSOIDCParameters) Name() string { return "aws" } +type CloudsmithOIDCParameters struct { + OrgName string + ServiceSlug string + ApiHost string + Audience string +} + +func (c *CloudsmithOIDCParameters) Name() string { + return "cloudsmith" +} + type OIDCCredential struct { parameters OIDCParameters cachedToken string @@ -86,6 +106,10 @@ func CreateOIDCCredential(cred config.Credential) (*OIDCCredential, error) { domain := cred.GetString("domain") domainOwner := cred.GetString("domain-owner") + // cloudsmith values + orgName := cred.GetString("oidc-namespace") + serviceSlug := cred.GetString("oidc-service-slug") + switch { case tenantID != "" && clientID != "": parameters = &AzureOIDCParameters{ @@ -119,6 +143,25 @@ func CreateOIDCCredential(cred config.Credential) (*OIDCCredential, error) { Domain: domain, DomainOwner: domainOwner, } + case orgName != "" && serviceSlug != "": + apiHost := cred.GetString("api-host") + if apiHost == "" { + apiHost = "api.cloudsmith.io" + } + audience := cred.GetString("audience") + if audience == "" { + if owner := GetRepositoryOwner(); owner != "" { + audience = fmt.Sprintf("https://github.com/%s", owner) + } else { + return nil, fmt.Errorf("missing audience for cloudsmith") + } + } + parameters = &CloudsmithOIDCParameters{ + OrgName: orgName, + ServiceSlug: serviceSlug, + ApiHost: apiHost, + Audience: audience, + } } if parameters == nil { @@ -160,6 +203,8 @@ func GetOrRefreshOIDCToken(cred *OIDCCredential, ctx context.Context) (string, e oidcAccessToken, err = GetJFrogAccessTokenForDevOps(ctx, *params) case *AWSOIDCParameters: oidcAccessToken, err = GetAWSAccessTokenForDevOps(ctx, *params) + case *CloudsmithOIDCParameters: + oidcAccessToken, err = GetCloudsmithAccessTokenForDevOps(ctx, *params) default: return "", fmt.Errorf("unsupported OIDC provider: %s", cred.Provider()) } diff --git a/internal/oidc/oidc_credential_test.go b/internal/oidc/oidc_credential_test.go index 2ce390f..c977af2 100644 --- a/internal/oidc/oidc_credential_test.go +++ b/internal/oidc/oidc_credential_test.go @@ -268,6 +268,41 @@ func TestTryCreateOIDCCredential(t *testing.T) { }, nil, }, + { + "cloudsmith", + config.Credential{ + "oidc-namespace": "my-org", + "oidc-service-slug": "my-service", + }, + &CloudsmithOIDCParameters{ + OrgName: "my-org", + ServiceSlug: "my-service", + ApiHost: "api.cloudsmith.io", + Audience: "https://github.com/my-repo-owner", + }, + }, + { + "cloudsmith with explicit values", + config.Credential{ + "oidc-namespace": "my-org", + "oidc-service-slug": "my-service", + "api-host": "api.example.com", + "audience": "my-audience", + }, + &CloudsmithOIDCParameters{ + OrgName: "my-org", + ServiceSlug: "my-service", + ApiHost: "api.example.com", + Audience: "my-audience", + }, + }, + { + "looks like cloudsmith but missing service slug", + config.Credential{ + "oidc-namespace": "my-org", + }, + nil, + }, } for _, tc := range tests { @@ -275,9 +310,11 @@ func TestTryCreateOIDCCredential(t *testing.T) { // these variables are necessary os.Setenv(envActionsIDTokenRequestURL, "https://example.com/token") os.Setenv(envActionsIDTokenRequestToken, "test-token") + os.Setenv(envActionsRepositoryOwner, "my-repo-owner") defer func() { os.Unsetenv(envActionsIDTokenRequestURL) os.Unsetenv(envActionsIDTokenRequestToken) + os.Unsetenv(envActionsRepositoryOwner) }() actual, _ := CreateOIDCCredential(tc.cred) @@ -327,9 +364,74 @@ func TestTryCreateOIDCCredential(t *testing.T) { assert.Equal(t, expectedParams.ProviderName, p.ProviderName) assert.Equal(t, expectedParams.Audience, p.Audience) assert.Equal(t, expectedParams.IdentityMappingName, p.IdentityMappingName) + case *CloudsmithOIDCParameters: + expectedParams, ok := tc.expectedParameters.(*CloudsmithOIDCParameters) + if !ok { + t.Fatalf("expected parameters of type CloudsmithOIDCParameters, but got %T", tc.expectedParameters) + } + assert.Equal(t, expectedParams.OrgName, p.OrgName) + assert.Equal(t, expectedParams.ServiceSlug, p.ServiceSlug) + assert.Equal(t, expectedParams.ApiHost, p.ApiHost) + assert.Equal(t, expectedParams.Audience, p.Audience) default: t.Fatalf("unexpected parameters type %T", actual.parameters) } }) } } + +func TestTryCreateOIDCCredentialCloudsmithRepositoryOwnerEnvironmentBehavior(t *testing.T) { + // Setup + os.Setenv(envActionsIDTokenRequestURL, "https://example.com/token") + os.Setenv(envActionsIDTokenRequestToken, "test-token") + os.Setenv(envActionsRepositoryOwner, "test-owner") + defer func() { + os.Unsetenv(envActionsIDTokenRequestURL) + os.Unsetenv(envActionsIDTokenRequestToken) + os.Unsetenv(envActionsRepositoryOwner) + }() + + cred := config.Credential{ + "oidc-namespace": "my-org", + "oidc-service-slug": "my-service", + } + creds, err := CreateOIDCCredential(cred) + + // audience available from environment variable should be used + assert.NoError(t, err) + assert.NotNil(t, creds) + params, ok := creds.parameters.(*CloudsmithOIDCParameters) + assert.True(t, ok) + assert.Equal( + t, + "https://github.com/test-owner", + params.Audience, + "expected audience to be derived from environment", + ) + + // should not override provided audience value + credWithAudience := config.Credential{ + "oidc-namespace": "my-org", + "oidc-service-slug": "my-service", + "audience": "explicit-audience", + } + credsWithAudience, err := CreateOIDCCredential(credWithAudience) + assert.NoError(t, err) + paramsWithAudience, ok := credsWithAudience.parameters.(*CloudsmithOIDCParameters) + assert.True(t, ok) + assert.Equal( + t, + "explicit-audience", + paramsWithAudience.Audience, + "expected audience to be the explicitly provided value", + ) + + // Verify error on no environment variable and no provided audience + os.Unsetenv(envActionsRepositoryOwner) + _, err = CreateOIDCCredential(cred) + assert.Error( + t, + err, + "creating cloudsmith OIDC credential without audience should fail", + ) +} From e6008be531772c74d37b4e9d5a239d3eb8761b91 Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:39:31 +0200 Subject: [PATCH 2/9] Cleans up minor format differences in cloudsmith oidc Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/oidc/actions_oidc.go | 4 ++-- internal/oidc/actions_oidc_test.go | 14 ++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/internal/oidc/actions_oidc.go b/internal/oidc/actions_oidc.go index 187319c..dc35ac2 100644 --- a/internal/oidc/actions_oidc.go +++ b/internal/oidc/actions_oidc.go @@ -618,7 +618,7 @@ func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParamete } if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Cloudsmith returned status %d: %s", resp.StatusCode, string(body)) + return nil, fmt.Errorf("cloudsmith returned status %d: %s", resp.StatusCode, string(body)) } var tokenResp cloudsmithTokenResponse @@ -627,7 +627,7 @@ func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParamete } if tokenResp.Token == "" { - return nil, fmt.Errorf("Cloudsmith token response does not contain a token") + return nil, fmt.Errorf("cloudsmith token response does not contain a token") } // Cloudsmith tokens are valid for 2 hours according to their documentation diff --git a/internal/oidc/actions_oidc_test.go b/internal/oidc/actions_oidc_test.go index 55dcc77..f560bc0 100644 --- a/internal/oidc/actions_oidc_test.go +++ b/internal/oidc/actions_oidc_test.go @@ -11,7 +11,6 @@ import ( "os" "strings" "testing" - "time" "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" @@ -996,14 +995,9 @@ func TestGetCloudsmithAccessToken(t *testing.T) { url := fmt.Sprintf("https://%s/openid/%s/", tt.params.ApiHost, tt.params.OrgName) httpmock.RegisterResponder("POST", url, httpmock.Responder(func(req *http.Request) (*http.Response, error) { - if tt.serverHandler != nil { - rr := httptest.NewRecorder() - tt.serverHandler(rr, req) - return rr.Result(), nil - } - // If no handler provided but we expected one (e.g. for error cases where we don't reach the server), - // this mock might not be hit, which is fine. - return httpmock.NewStringResponse(404, "Not Found"), nil + rr := httptest.NewRecorder() + tt.serverHandler(rr, req) + return rr.Result(), nil })) } @@ -1015,7 +1009,7 @@ func TestGetCloudsmithAccessToken(t *testing.T) { require.NoError(t, err) require.NotNil(t, cloudsmithToken) assert.Equal(t, tt.expectedToken, cloudsmithToken.Token) - assert.Equal(t, 2*time.Hour, cloudsmithToken.ExpiresIn) + assert.NotNil(t, cloudsmithToken.ExpiresIn) } }) } From 4d123c44a52d64fc238896cb54a6a8c1a067d2dd Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:53:35 +0200 Subject: [PATCH 3/9] Fixes incorrectly named environment variable Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/oidc/oidc_credential.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/oidc/oidc_credential.go b/internal/oidc/oidc_credential.go index c8b7de0..7465d2b 100644 --- a/internal/oidc/oidc_credential.go +++ b/internal/oidc/oidc_credential.go @@ -17,7 +17,7 @@ import ( ) const ( - envActionsRepositoryOwner = "ACTIONS_REPOSITORY_OWNER" + envActionsRepositoryOwner = "GITHUB_REPOSITORY_OWNER" ) func GetRepositoryOwner() string { From a4f7e7d0e830818ca0f99b6df4703b897f5f45f5 Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:55:49 +0200 Subject: [PATCH 4/9] Removes redundant check for Audience in Cloudsmith token fetch Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/oidc/actions_oidc.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/oidc/actions_oidc.go b/internal/oidc/actions_oidc.go index dc35ac2..86e60ec 100644 --- a/internal/oidc/actions_oidc.go +++ b/internal/oidc/actions_oidc.go @@ -573,9 +573,6 @@ func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParamete if params.ApiHost == "" { return nil, fmt.Errorf("API host is required") } - if params.Audience == "" { - return nil, fmt.Errorf("audience is required") - } if params.OrgName == "" { return nil, fmt.Errorf("org name is required") } From 771d8b943e8366daaea93cef81edc47ff84af8be Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:58:26 +0200 Subject: [PATCH 5/9] Removes redundant nil assertion in cloudsmith oidc tests Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/oidc/actions_oidc_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/oidc/actions_oidc_test.go b/internal/oidc/actions_oidc_test.go index f560bc0..6a08df3 100644 --- a/internal/oidc/actions_oidc_test.go +++ b/internal/oidc/actions_oidc_test.go @@ -1009,7 +1009,6 @@ func TestGetCloudsmithAccessToken(t *testing.T) { require.NoError(t, err) require.NotNil(t, cloudsmithToken) assert.Equal(t, tt.expectedToken, cloudsmithToken.Token) - assert.NotNil(t, cloudsmithToken.ExpiresIn) } }) } From 8b62ddc46177d66690a91f49c38b2a799324f779 Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:06:09 +0200 Subject: [PATCH 6/9] Adds Cloudsmith to oidc url handling tests Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/handlers/oidc_handling_test.go | 304 ++++++++++++++++++++++++ 1 file changed, 304 insertions(+) diff --git a/internal/handlers/oidc_handling_test.go b/internal/handlers/oidc_handling_test.go index 59b6853..ca76c4b 100644 --- a/internal/handlers/oidc_handling_test.go +++ b/internal/handlers/oidc_handling_test.go @@ -110,6 +110,28 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/packages/some-package", }, }, + { + name: "Cargo", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewCargoRegistryHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "cargo_registry", + "url": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for cargo registry: https://cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, // // Composer // @@ -182,6 +204,29 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/some-package", }, }, + { + name: "Composer", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewComposerHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "composer_repository", + "registry": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for composer repository: cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, + // // Docker // @@ -253,6 +298,28 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/some-package", }, }, + { + name: "Docker", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewDockerRegistryHandler(creds, &http.Transport{}, nil) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "docker_registry", + "registry": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for docker registry: https://cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, // // Go proxy // @@ -324,6 +391,28 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/packages/some-package", }, }, + { + name: "Go proxy", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewGoProxyServerHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "goproxy_server", + "url": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for goproxy server: https://cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, // // Helm // @@ -395,6 +484,28 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/some-package", }, }, + { + name: "Helm registry", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewHelmRegistryHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "helm_registry", + "registry": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for helm registry: https://cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, // // Hex // @@ -466,6 +577,28 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/some-package", }, }, + { + name: "Hex", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewHexRepositoryHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "hex_repository", + "url": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for hex repository: https://cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, // // Maven // @@ -537,6 +670,28 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/packages/some-package", }, }, + { + name: "Maven", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewMavenRepositoryHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "maven_repository", + "url": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for maven repository: cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, // // NPM // @@ -608,6 +763,28 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/some-package", }, }, + { + name: "NPM", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewNPMRegistryHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "npm_registry", + "url": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for npm registry: cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, // // NuGet // @@ -703,6 +880,36 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/v3/packages/some.package/index.json", // package url }, }, + { + name: "NuGet", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewNugetFeedHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "nuget_feed", + "url": "https://cloudsmith.example.com/v3/index.json", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{ + { + verb: "GET", + url: "https://cloudsmith.example.com/v3/index.json", + response: `{"version":"3.0.0","resources":[{"@id":"https://cloudsmith.example.com/v3/packages","@type":"PackageBaseAddress/3.0.0"}]}`, + }, + }, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for nuget feed: https://cloudsmith.example.com/v3/index.json", + " registered cloudsmith OIDC credentials for nuget resource: https://cloudsmith.example.com/v3/packages", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/v3/index.json", // base url + "https://cloudsmith.example.com/v3/packages/some.package/index.json", // package url + }, + }, // // Pub // @@ -774,6 +981,28 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/some-package", }, }, + { + name: "Pub", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewPubRepositoryHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "pub_repository", + "url": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for pub repository: https://cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, // // Python // @@ -845,6 +1074,28 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/some-package", }, }, + { + name: "Python", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewPythonIndexHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "python_index", + "url": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for python index: cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, // // RubyGems // @@ -917,6 +1168,29 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/some-package", }, }, + { + name: "RubyGems", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewRubyGemsServerHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "rubygems_server", + "url": "https://cloudsmith.example.com", + "host": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for rubygems server: https://cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, // // Terraform // @@ -988,6 +1262,28 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "https://jfrog.example.com/some-package", }, }, + { + name: "Terraform", + provider: "cloudsmith", + handlerFactory: func(creds config.Credentials) oidcHandler { + return NewTerraformRegistryHandler(creds) + }, + credentials: config.Credentials{ + config.Credential{ + "type": "terraform_registry", + "url": "https://cloudsmith.example.com", + "oidc-namespace": "space", + "oidc-service-slug": "repo", + }, + }, + urlMocks: []mockHttpRequest{}, + expectedLogLines: []string{ + "registered cloudsmith OIDC credentials for terraform registry: cloudsmith.example.com", + }, + urlsToAuthenticate: []string{ + "https://cloudsmith.example.com/some-package", + }, + }, } for _, tc := range testCases { t.Run(fmt.Sprintf("%s - %s", tc.name, tc.provider), func(t *testing.T) { @@ -1042,6 +1338,14 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "access_token": "__test_token__", "expires_in": 3600 }`)) + case "cloudsmith": + // for fallback on Audience + t.Setenv("GITHUB_REPOSITORY_OWNER", "testowner") + namespace := tc.credentials[0]["oidc-namespace"] + httpmock.RegisterResponder("POST", fmt.Sprintf("https://api.cloudsmith.io/openid/%s/", namespace), + httpmock.NewStringResponder(200, `{ + "token": "__test_token__" + }`)) default: t.Fatal("unsupported provider in test case: " + tc.provider) } From d7235e1f8d09b06de3071ce4b1204340d07e2968 Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:25:38 +0200 Subject: [PATCH 7/9] Added a more verbose error message for missing audience in cloudsmith oidc Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/oidc/oidc_credential.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/oidc/oidc_credential.go b/internal/oidc/oidc_credential.go index 7465d2b..84554cd 100644 --- a/internal/oidc/oidc_credential.go +++ b/internal/oidc/oidc_credential.go @@ -153,7 +153,10 @@ func CreateOIDCCredential(cred config.Credential) (*OIDCCredential, error) { if owner := GetRepositoryOwner(); owner != "" { audience = fmt.Sprintf("https://github.com/%s", owner) } else { - return nil, fmt.Errorf("missing audience for cloudsmith") + return nil, fmt.Errorf( + "missing audience for cloudsmith, either provide 'audience' or set '%s' in environment", + envActionsRepositoryOwner, + ) } } parameters = &CloudsmithOIDCParameters{ From 2c94e440ce78edcc04523c5dcd12c60cc4723793 Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Thu, 12 Feb 2026 21:47:16 +0200 Subject: [PATCH 8/9] Makes audience a required parameter for Cloudsmith OIDC Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/handlers/oidc_handling_test.go | 15 ++++- internal/oidc/oidc_credential.go | 25 +------- internal/oidc/oidc_credential_test.go | 79 ++++++------------------- 3 files changed, 35 insertions(+), 84 deletions(-) diff --git a/internal/handlers/oidc_handling_test.go b/internal/handlers/oidc_handling_test.go index ca76c4b..1401fed 100644 --- a/internal/handlers/oidc_handling_test.go +++ b/internal/handlers/oidc_handling_test.go @@ -122,6 +122,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "url": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -216,6 +217,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "registry": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -310,6 +312,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "registry": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -403,6 +406,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "url": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -496,6 +500,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "registry": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -589,6 +594,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "url": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -682,6 +688,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "url": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -775,6 +782,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "url": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -892,6 +900,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "url": "https://cloudsmith.example.com/v3/index.json", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{ @@ -993,6 +1002,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "url": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -1086,6 +1096,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "url": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -1181,6 +1192,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "host": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -1274,6 +1286,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "url": "https://cloudsmith.example.com", "oidc-namespace": "space", "oidc-service-slug": "repo", + "oidc-audience": "my-audience", }, }, urlMocks: []mockHttpRequest{}, @@ -1339,8 +1352,6 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "expires_in": 3600 }`)) case "cloudsmith": - // for fallback on Audience - t.Setenv("GITHUB_REPOSITORY_OWNER", "testowner") namespace := tc.credentials[0]["oidc-namespace"] httpmock.RegisterResponder("POST", fmt.Sprintf("https://api.cloudsmith.io/openid/%s/", namespace), httpmock.NewStringResponder(200, `{ diff --git a/internal/oidc/oidc_credential.go b/internal/oidc/oidc_credential.go index 84554cd..8b44f05 100644 --- a/internal/oidc/oidc_credential.go +++ b/internal/oidc/oidc_credential.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" "net/url" - "os" "sync" "time" @@ -16,14 +15,6 @@ import ( "github.com/dependabot/proxy/internal/logging" ) -const ( - envActionsRepositoryOwner = "GITHUB_REPOSITORY_OWNER" -) - -func GetRepositoryOwner() string { - return os.Getenv(envActionsRepositoryOwner) -} - type OIDCParameters interface { Name() string } @@ -109,6 +100,7 @@ func CreateOIDCCredential(cred config.Credential) (*OIDCCredential, error) { // cloudsmith values orgName := cred.GetString("oidc-namespace") serviceSlug := cred.GetString("oidc-service-slug") + cloudsmithAudience := cred.GetString("oidc-audience") switch { case tenantID != "" && clientID != "": @@ -143,27 +135,16 @@ func CreateOIDCCredential(cred config.Credential) (*OIDCCredential, error) { Domain: domain, DomainOwner: domainOwner, } - case orgName != "" && serviceSlug != "": + case orgName != "" && serviceSlug != "" && cloudsmithAudience != "": apiHost := cred.GetString("api-host") if apiHost == "" { apiHost = "api.cloudsmith.io" } - audience := cred.GetString("audience") - if audience == "" { - if owner := GetRepositoryOwner(); owner != "" { - audience = fmt.Sprintf("https://github.com/%s", owner) - } else { - return nil, fmt.Errorf( - "missing audience for cloudsmith, either provide 'audience' or set '%s' in environment", - envActionsRepositoryOwner, - ) - } - } parameters = &CloudsmithOIDCParameters{ OrgName: orgName, ServiceSlug: serviceSlug, ApiHost: apiHost, - Audience: audience, + Audience: cloudsmithAudience, } } diff --git a/internal/oidc/oidc_credential_test.go b/internal/oidc/oidc_credential_test.go index c977af2..8568fcd 100644 --- a/internal/oidc/oidc_credential_test.go +++ b/internal/oidc/oidc_credential_test.go @@ -273,12 +273,13 @@ func TestTryCreateOIDCCredential(t *testing.T) { config.Credential{ "oidc-namespace": "my-org", "oidc-service-slug": "my-service", + "oidc-audience": "my-audience", }, &CloudsmithOIDCParameters{ OrgName: "my-org", ServiceSlug: "my-service", ApiHost: "api.cloudsmith.io", - Audience: "https://github.com/my-repo-owner", + Audience: "my-audience", }, }, { @@ -287,7 +288,7 @@ func TestTryCreateOIDCCredential(t *testing.T) { "oidc-namespace": "my-org", "oidc-service-slug": "my-service", "api-host": "api.example.com", - "audience": "my-audience", + "oidc-audience": "my-audience", }, &CloudsmithOIDCParameters{ OrgName: "my-org", @@ -296,10 +297,26 @@ func TestTryCreateOIDCCredential(t *testing.T) { Audience: "my-audience", }, }, + { + "looks like cloudsmith but missing service slug and audience", + config.Credential{ + "oidc-namespace": "my-org", + }, + nil, + }, { "looks like cloudsmith but missing service slug", config.Credential{ "oidc-namespace": "my-org", + "oidc-audience": "my-audience", + }, + nil, + }, + { + "looks like cloudsmith but missing audience", + config.Credential{ + "oidc-namespace": "my-org", + "oidc-service-slug": "my-service", }, nil, }, @@ -310,11 +327,9 @@ func TestTryCreateOIDCCredential(t *testing.T) { // these variables are necessary os.Setenv(envActionsIDTokenRequestURL, "https://example.com/token") os.Setenv(envActionsIDTokenRequestToken, "test-token") - os.Setenv(envActionsRepositoryOwner, "my-repo-owner") defer func() { os.Unsetenv(envActionsIDTokenRequestURL) os.Unsetenv(envActionsIDTokenRequestToken) - os.Unsetenv(envActionsRepositoryOwner) }() actual, _ := CreateOIDCCredential(tc.cred) @@ -379,59 +394,3 @@ func TestTryCreateOIDCCredential(t *testing.T) { }) } } - -func TestTryCreateOIDCCredentialCloudsmithRepositoryOwnerEnvironmentBehavior(t *testing.T) { - // Setup - os.Setenv(envActionsIDTokenRequestURL, "https://example.com/token") - os.Setenv(envActionsIDTokenRequestToken, "test-token") - os.Setenv(envActionsRepositoryOwner, "test-owner") - defer func() { - os.Unsetenv(envActionsIDTokenRequestURL) - os.Unsetenv(envActionsIDTokenRequestToken) - os.Unsetenv(envActionsRepositoryOwner) - }() - - cred := config.Credential{ - "oidc-namespace": "my-org", - "oidc-service-slug": "my-service", - } - creds, err := CreateOIDCCredential(cred) - - // audience available from environment variable should be used - assert.NoError(t, err) - assert.NotNil(t, creds) - params, ok := creds.parameters.(*CloudsmithOIDCParameters) - assert.True(t, ok) - assert.Equal( - t, - "https://github.com/test-owner", - params.Audience, - "expected audience to be derived from environment", - ) - - // should not override provided audience value - credWithAudience := config.Credential{ - "oidc-namespace": "my-org", - "oidc-service-slug": "my-service", - "audience": "explicit-audience", - } - credsWithAudience, err := CreateOIDCCredential(credWithAudience) - assert.NoError(t, err) - paramsWithAudience, ok := credsWithAudience.parameters.(*CloudsmithOIDCParameters) - assert.True(t, ok) - assert.Equal( - t, - "explicit-audience", - paramsWithAudience.Audience, - "expected audience to be the explicitly provided value", - ) - - // Verify error on no environment variable and no provided audience - os.Unsetenv(envActionsRepositoryOwner) - _, err = CreateOIDCCredential(cred) - assert.Error( - t, - err, - "creating cloudsmith OIDC credential without audience should fail", - ) -} From 69092cfd165443e39531a207a91895c37ac5132e Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:06:45 +0200 Subject: [PATCH 9/9] Allows for both 200 and 201 responses in cloudsmith oidc token responses Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/oidc/actions_oidc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/oidc/actions_oidc.go b/internal/oidc/actions_oidc.go index 86e60ec..35fe8d2 100644 --- a/internal/oidc/actions_oidc.go +++ b/internal/oidc/actions_oidc.go @@ -614,7 +614,7 @@ func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParamete return nil, fmt.Errorf("failed to read cloudsmith token response body: %w", err) } - if resp.StatusCode != http.StatusOK { + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { return nil, fmt.Errorf("cloudsmith returned status %d: %s", resp.StatusCode, string(body)) }