diff --git a/internal/handlers/oidc_handling_test.go b/internal/handlers/oidc_handling_test.go index 59b6853..1401fed 100644 --- a/internal/handlers/oidc_handling_test.go +++ b/internal/handlers/oidc_handling_test.go @@ -110,6 +110,29 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +205,30 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +300,29 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +394,29 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +488,29 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +582,29 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +676,29 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +770,29 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +888,37 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +990,29 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +1084,29 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +1179,30 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +1274,29 @@ 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", + "oidc-audience": "my-audience", + }, + }, + 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 +1351,12 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { "access_token": "__test_token__", "expires_in": 3600 }`)) + case "cloudsmith": + 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) } diff --git a/internal/oidc/actions_oidc.go b/internal/oidc/actions_oidc.go index 7b83fc5..35fe8d2 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,93 @@ 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.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 && resp.StatusCode != http.StatusCreated { + 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..6a08df3 100644 --- a/internal/oidc/actions_oidc_test.go +++ b/internal/oidc/actions_oidc_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "encoding/xml" + "fmt" "io" "net/http" "net/http/httptest" @@ -832,3 +833,183 @@ 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) { + rr := httptest.NewRecorder() + tt.serverHandler(rr, req) + return rr.Result(), 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) + } + }) + } +} diff --git a/internal/oidc/oidc_credential.go b/internal/oidc/oidc_credential.go index 3914b18..8b44f05 100644 --- a/internal/oidc/oidc_credential.go +++ b/internal/oidc/oidc_credential.go @@ -52,6 +52,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 +97,11 @@ 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") + cloudsmithAudience := cred.GetString("oidc-audience") + switch { case tenantID != "" && clientID != "": parameters = &AzureOIDCParameters{ @@ -119,6 +135,17 @@ func CreateOIDCCredential(cred config.Credential) (*OIDCCredential, error) { Domain: domain, DomainOwner: domainOwner, } + case orgName != "" && serviceSlug != "" && cloudsmithAudience != "": + apiHost := cred.GetString("api-host") + if apiHost == "" { + apiHost = "api.cloudsmith.io" + } + parameters = &CloudsmithOIDCParameters{ + OrgName: orgName, + ServiceSlug: serviceSlug, + ApiHost: apiHost, + Audience: cloudsmithAudience, + } } if parameters == nil { @@ -160,6 +187,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..8568fcd 100644 --- a/internal/oidc/oidc_credential_test.go +++ b/internal/oidc/oidc_credential_test.go @@ -268,6 +268,58 @@ func TestTryCreateOIDCCredential(t *testing.T) { }, nil, }, + { + "cloudsmith", + 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: "my-audience", + }, + }, + { + "cloudsmith with explicit values", + config.Credential{ + "oidc-namespace": "my-org", + "oidc-service-slug": "my-service", + "api-host": "api.example.com", + "oidc-audience": "my-audience", + }, + &CloudsmithOIDCParameters{ + OrgName: "my-org", + ServiceSlug: "my-service", + ApiHost: "api.example.com", + 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, + }, } for _, tc := range tests { @@ -327,6 +379,15 @@ 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) }