diff --git a/internal/handlers/oidc_handling_test.go b/internal/handlers/oidc_handling_test.go index dc742e6..69029ff 100644 --- a/internal/handlers/oidc_handling_test.go +++ b/internal/handlers/oidc_handling_test.go @@ -1225,7 +1225,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { }, urlMocks: []mockHttpRequest{}, expectedLogLines: []string{ - "registered aws OIDC credentials for terraform registry: terraform.example.com", + "registered aws OIDC credentials for terraform registry: https://terraform.example.com", }, urlsToAuthenticate: []string{ "https://terraform.example.com/some-package", @@ -1268,7 +1268,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { }, urlMocks: []mockHttpRequest{}, expectedLogLines: []string{ - "registered jfrog OIDC credentials for terraform registry: jfrog.example.com", + "registered jfrog OIDC credentials for terraform registry: https://jfrog.example.com", }, urlsToAuthenticate: []string{ "https://jfrog.example.com/some-package", @@ -1291,7 +1291,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { }, urlMocks: []mockHttpRequest{}, expectedLogLines: []string{ - "registered cloudsmith OIDC credentials for terraform registry: cloudsmith.example.com", + "registered cloudsmith OIDC credentials for terraform registry: https://cloudsmith.example.com", }, urlsToAuthenticate: []string{ "https://cloudsmith.example.com/some-package", @@ -1390,3 +1390,55 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { }) } } + +// TestOIDCSameHostDifferentPaths verifies that two OIDC credentials on the same +// host with different URL paths do not collide — each request is authenticated +// with the credential whose path is the longest prefix match. +func TestOIDCSameHostDifferentPaths(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + tenantA := "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + tenantB := "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" + clientId := "87654321-4321-4321-4321-210987654321" + + tokenUrl := "https://token.actions.example.com" //nolint:gosec // test URL + httpmock.RegisterResponder("GET", tokenUrl, + httpmock.NewStringResponder(200, `{"count":1,"value":"sometoken"}`)) + + // Two different Azure tenants → two different tokens + httpmock.RegisterResponder("POST", fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantA), + httpmock.NewStringResponder(200, `{"access_token":"__token_A__","expires_in":3600,"token_type":"Bearer"}`)) + httpmock.RegisterResponder("POST", fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantB), + httpmock.NewStringResponder(200, `{"access_token":"__token_B__","expires_in":3600,"token_type":"Bearer"}`)) + + t.Setenv("ACTIONS_ID_TOKEN_REQUEST_URL", tokenUrl) + t.Setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "sometoken") + + creds := config.Credentials{ + config.Credential{ + "type": "terraform_registry", + "url": "https://terraform.example.com/org/feed-A", + "tenant-id": tenantA, + "client-id": clientId, + }, + config.Credential{ + "type": "terraform_registry", + "url": "https://terraform.example.com/org/feed-B", + "tenant-id": tenantB, + "client-id": clientId, + }, + } + + handler := NewTerraformRegistryHandler(creds) + + // Request to feed-A path should get token A + reqA := httptest.NewRequest("GET", "https://terraform.example.com/org/feed-A/v1/providers/org/name", nil) + reqA = handleRequestAndClose(handler, reqA, nil) + assertHasTokenAuth(t, reqA, "Bearer", "__token_A__", "feed-A should use token A") + + // Request to feed-B path should get token B + reqB := httptest.NewRequest("GET", "https://terraform.example.com/org/feed-B/v1/providers/org/name", nil) + reqB = handleRequestAndClose(handler, reqB, nil) + assertHasTokenAuth(t, reqB, "Bearer", "__token_B__", "feed-B should use token B") +} diff --git a/internal/handlers/terraform_registry.go b/internal/handlers/terraform_registry.go index 078f5ae..b3b3aac 100644 --- a/internal/handlers/terraform_registry.go +++ b/internal/handlers/terraform_registry.go @@ -2,7 +2,6 @@ package handlers import ( "net/http" - "sync" "github.com/elazarl/goproxy" @@ -13,15 +12,14 @@ import ( ) type TerraformRegistryHandler struct { - credentials map[string]string - oidcCredentials map[string]*oidc.OIDCCredential - mutex sync.RWMutex + credentials map[string]string + oidcRegistry *oidc.OIDCRegistry } func NewTerraformRegistryHandler(credentials config.Credentials) *TerraformRegistryHandler { handler := TerraformRegistryHandler{ - credentials: make(map[string]string), - oidcCredentials: make(map[string]*oidc.OIDCCredential), + credentials: make(map[string]string), + oidcRegistry: oidc.NewOIDCRegistry(), } for _, credential := range credentials { @@ -29,18 +27,12 @@ func NewTerraformRegistryHandler(credentials config.Credentials) *TerraformRegis continue } - host := credential.Host() - - oidcCredential, _ := oidc.CreateOIDCCredential(credential) - if oidcCredential != nil { - if host != "" { - handler.oidcCredentials[host] = oidcCredential - logging.RequestLogf(nil, "registered %s OIDC credentials for terraform registry: %s", oidcCredential.Provider(), host) - } + // OIDC credentials are not used as static credentials. + if oidcCred, _, _ := handler.oidcRegistry.Register(credential, []string{"url"}, "terraform registry"); oidcCred != nil { continue } - handler.credentials[host] = credential.GetString("token") + handler.credentials[credential.Host()] = credential.GetString("token") } return &handler } @@ -51,7 +43,7 @@ func (h *TerraformRegistryHandler) HandleRequest(request *http.Request, context } // Try OIDC credentials first - if oidc.TryAuthOIDCRequestWithPrefix(&h.mutex, h.oidcCredentials, request, context) { + if h.oidcRegistry.TryAuth(request, context) { return request, nil }