diff --git a/internal/handlers/oidc_handling_test.go b/internal/handlers/oidc_handling_test.go index 0f603d7..76d7064 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", @@ -1492,3 +1492,56 @@ func TestNPMOIDCSameHostDifferentPaths(t *testing.T) { reqB = handleRequestAndClose(handler, reqB, nil) assertHasTokenAuth(t, reqB, "Bearer", "__token_B__", "feed-B should use token B") } + +// TestTerraformOIDCSameHostDifferentPaths verifies that two terraform 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 TestTerraformOIDCSameHostDifferentPaths(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 6ea639e..df6b09c 100644 --- a/internal/handlers/terraform_registry.go +++ b/internal/handlers/terraform_registry.go @@ -4,7 +4,6 @@ import ( "net/http" "sort" "strings" - "sync" "github.com/elazarl/goproxy" @@ -15,9 +14,8 @@ import ( ) type TerraformRegistryHandler struct { - credentials []terraformRegistryCredentials - oidcCredentials map[string]*oidc.OIDCCredential - mutex sync.RWMutex + credentials []terraformRegistryCredentials + oidcRegistry *oidc.OIDCRegistry } type terraformRegistryCredentials struct { @@ -28,8 +26,8 @@ type terraformRegistryCredentials struct { func NewTerraformRegistryHandler(credentials config.Credentials) *TerraformRegistryHandler { handler := TerraformRegistryHandler{ - credentials: []terraformRegistryCredentials{}, - oidcCredentials: make(map[string]*oidc.OIDCCredential), + credentials: []terraformRegistryCredentials{}, + oidcRegistry: oidc.NewOIDCRegistry(), } for _, credential := range credentials { @@ -37,17 +35,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 } + host := credential.Host() token := credential.GetString("token") url := credential.GetString("url") @@ -84,7 +77,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 }