From 19ea6346963789fe42d98f155d39d038c27be926 Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Thu, 21 May 2026 23:56:41 +0300 Subject: [PATCH 1/3] Adds missing audience validation Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/oidc/actions_oidc.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/oidc/actions_oidc.go b/internal/oidc/actions_oidc.go index 93e857f..1f446a5 100644 --- a/internal/oidc/actions_oidc.go +++ b/internal/oidc/actions_oidc.go @@ -603,6 +603,9 @@ func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParamete if params.OrgName == "" { return nil, fmt.Errorf("org name is required") } + if params.Audience == "" { + return nil, fmt.Errorf("audience is required") + } if githubToken == "" { return nil, fmt.Errorf("GitHub token is required") } From 07a7f87848586c12331697ee97dd3813b3c17eb7 Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Thu, 21 May 2026 23:57:21 +0300 Subject: [PATCH 2/3] Uses safeDialer everywhere Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/handlers/cargo_registry.go | 6 +- internal/handlers/cargo_registry_test.go | 2 +- internal/handlers/composer.go | 4 +- internal/handlers/composer_test.go | 2 +- internal/handlers/docker_registry.go | 2 +- internal/handlers/goproxy_server_handler.go | 4 +- .../handlers/goproxy_server_handler_test.go | 2 +- internal/handlers/helm_registry.go | 4 +- internal/handlers/helm_registry_test.go | 2 +- internal/handlers/hex_repository.go | 6 +- internal/handlers/hex_repository_test.go | 2 +- internal/handlers/maven_repository.go | 4 +- internal/handlers/maven_repository_test.go | 2 +- internal/handlers/npm_registry.go | 4 +- internal/handlers/npm_registry_test.go | 2 +- internal/handlers/nuget_feed.go | 4 +- internal/handlers/oidc_handling_test.go | 126 +++++++++--------- internal/handlers/opentofu_registry.go | 4 +- internal/handlers/opentofu_registry_test.go | 12 +- internal/handlers/pub_repository.go | 6 +- internal/handlers/pub_repository_test.go | 2 +- internal/handlers/python_index.go | 6 +- internal/handlers/python_index_test.go | 2 +- internal/handlers/rubygems_server.go | 4 +- internal/handlers/rubygems_server_test.go | 2 +- internal/handlers/terraform_registry.go | 4 +- internal/handlers/terraform_registry_test.go | 12 +- internal/oidc/actions_oidc.go | 47 +++---- internal/oidc/actions_oidc_test.go | 10 +- internal/oidc/oidc_credential.go | 18 ++- internal/oidc/oidc_credential_test.go | 6 +- internal/oidc/oidc_registry.go | 15 ++- internal/oidc/oidc_registry_test.go | 48 +++---- proxy.go | 26 ++-- 34 files changed, 197 insertions(+), 205 deletions(-) diff --git a/internal/handlers/cargo_registry.go b/internal/handlers/cargo_registry.go index aef718f..a05f952 100644 --- a/internal/handlers/cargo_registry.go +++ b/internal/handlers/cargo_registry.go @@ -47,10 +47,10 @@ type cargoRepositoryCredentials struct { password string } -func NewCargoRegistryHandler(credentials config.Credentials) *CargoRegistryHandler { +func NewCargoRegistryHandler(credentials config.Credentials, transport http.RoundTripper) *CargoRegistryHandler { handler := CargoRegistryHandler{ credentials: []cargoRepositoryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, credential := range credentials { @@ -68,7 +68,7 @@ func NewCargoRegistryHandler(credentials config.Credentials) *CargoRegistryHandl if oidcCred, _, _ := handler.oidcRegistry.Register(credential, []string{"url"}, "cargo registry"); oidcCred != nil { continue } - } else if oidcCred, _ := oidc.CreateOIDCCredential(credential); oidcCred != nil { + } else if oidcCred, _ := oidc.CreateOIDCCredential(credential, transport); oidcCred != nil { continue } diff --git a/internal/handlers/cargo_registry_test.go b/internal/handlers/cargo_registry_test.go index 9bf6a46..aa69863 100644 --- a/internal/handlers/cargo_registry_test.go +++ b/internal/handlers/cargo_registry_test.go @@ -46,7 +46,7 @@ func TestCargoRegistryHandler(t *testing.T) { }, } - handler := NewCargoRegistryHandler(credentials) + handler := NewCargoRegistryHandler(credentials, nil) // valid request, should authenticate url := validURL diff --git a/internal/handlers/composer.go b/internal/handlers/composer.go index 4546e7a..44dd344 100644 --- a/internal/handlers/composer.go +++ b/internal/handlers/composer.go @@ -26,10 +26,10 @@ type composerCredentials struct { } // NewComposerHandler returns a new ComposerHandler. -func NewComposerHandler(creds config.Credentials) *ComposerHandler { +func NewComposerHandler(creds config.Credentials, transport http.RoundTripper) *ComposerHandler { handler := ComposerHandler{ credentials: []composerCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, cred := range creds { diff --git a/internal/handlers/composer_test.go b/internal/handlers/composer_test.go index 1327b85..3a3e6b4 100644 --- a/internal/handlers/composer_test.go +++ b/internal/handlers/composer_test.go @@ -64,7 +64,7 @@ func TestComposerHandler(t *testing.T) { "token": "", }, } - handler := NewComposerHandler(credentials) + handler := NewComposerHandler(credentials, nil) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://phpreg.bigco.com/somepkg", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/docker_registry.go b/internal/handlers/docker_registry.go index e2bda23..c6466cc 100644 --- a/internal/handlers/docker_registry.go +++ b/internal/handlers/docker_registry.go @@ -43,7 +43,7 @@ func NewDockerRegistryHandler(creds config.Credentials, transport http.RoundTrip handler := DockerRegistryHandler{ credentials: []*dockerRegistryCredentials{}, transport: transport, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } if getECRClient == nil { diff --git a/internal/handlers/goproxy_server_handler.go b/internal/handlers/goproxy_server_handler.go index f050070..6a4f9e4 100644 --- a/internal/handlers/goproxy_server_handler.go +++ b/internal/handlers/goproxy_server_handler.go @@ -24,10 +24,10 @@ type goProxyServerCredentials struct { } // NewGoProxyServerHandler returns a new GoProxyServerHandler. -func NewGoProxyServerHandler(creds config.Credentials) *GoProxyServerHandler { +func NewGoProxyServerHandler(creds config.Credentials, transport http.RoundTripper) *GoProxyServerHandler { handler := GoProxyServerHandler{ credentials: []goProxyServerCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, cred := range creds { diff --git a/internal/handlers/goproxy_server_handler_test.go b/internal/handlers/goproxy_server_handler_test.go index 54a3afd..176ef77 100644 --- a/internal/handlers/goproxy_server_handler_test.go +++ b/internal/handlers/goproxy_server_handler_test.go @@ -36,7 +36,7 @@ func TestGoProxyHandler(t *testing.T) { "password": deltaForcePassword, }, } - handler := NewGoProxyServerHandler(credentials) + handler := NewGoProxyServerHandler(credentials, nil) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://corp.dependabot.com/packages/somepkg", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/helm_registry.go b/internal/handlers/helm_registry.go index b82aa2e..cfa1839 100644 --- a/internal/handlers/helm_registry.go +++ b/internal/handlers/helm_registry.go @@ -24,10 +24,10 @@ type helmRegistryCredentials struct { } // NewHelmRegistryHandler returns a new HelmRegistryHandler. -func NewHelmRegistryHandler(creds config.Credentials) *HelmRegistryHandler { +func NewHelmRegistryHandler(creds config.Credentials, transport http.RoundTripper) *HelmRegistryHandler { handler := HelmRegistryHandler{ credentials: []helmRegistryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, cred := range creds { diff --git a/internal/handlers/helm_registry_test.go b/internal/handlers/helm_registry_test.go index cdeb825..65ff5e1 100644 --- a/internal/handlers/helm_registry_test.go +++ b/internal/handlers/helm_registry_test.go @@ -31,7 +31,7 @@ func TestHelmRegistryHandler(t *testing.T) { "password": bigCoPassword, }, } - handler := NewHelmRegistryHandler(credentials) + handler := NewHelmRegistryHandler(credentials, nil) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://helmreg.bigco.com/some_chart", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/hex_repository.go b/internal/handlers/hex_repository.go index b923263..3ff81ce 100644 --- a/internal/handlers/hex_repository.go +++ b/internal/handlers/hex_repository.go @@ -23,10 +23,10 @@ type hexRepositoryCredentials struct { authKey string } -func NewHexRepositoryHandler(creds config.Credentials) *HexRepositoryHandler { +func NewHexRepositoryHandler(creds config.Credentials, transport http.RoundTripper) *HexRepositoryHandler { handler := HexRepositoryHandler{ credentials: []hexRepositoryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, cred := range creds { @@ -43,7 +43,7 @@ func NewHexRepositoryHandler(creds config.Credentials) *HexRepositoryHandler { if oidcCred, _, _ := handler.oidcRegistry.Register(cred, []string{"url"}, "hex repository"); oidcCred != nil { continue } - } else if oidcCred, _ := oidc.CreateOIDCCredential(cred); oidcCred != nil { + } else if oidcCred, _ := oidc.CreateOIDCCredential(cred, transport); oidcCred != nil { continue } diff --git a/internal/handlers/hex_repository_test.go b/internal/handlers/hex_repository_test.go index 8ccdc0a..06f2475 100644 --- a/internal/handlers/hex_repository_test.go +++ b/internal/handlers/hex_repository_test.go @@ -29,7 +29,7 @@ func TestHexRepositoryHandler(t *testing.T) { validPath := "/repos/my_wonderful_repo/version" - handler := NewHexRepositoryHandler(credentials) + handler := NewHexRepositoryHandler(credentials, nil) // valid request, should authenticate url := validConfigUrl + validPath diff --git a/internal/handlers/maven_repository.go b/internal/handlers/maven_repository.go index 948db01..9f89261 100644 --- a/internal/handlers/maven_repository.go +++ b/internal/handlers/maven_repository.go @@ -25,10 +25,10 @@ type mavenRepositoryCredentials struct { } // NewMavenRepositoryHandler returns a new MavenRepositoryHandler. -func NewMavenRepositoryHandler(creds config.Credentials) *MavenRepositoryHandler { +func NewMavenRepositoryHandler(creds config.Credentials, transport http.RoundTripper) *MavenRepositoryHandler { handler := MavenRepositoryHandler{ credentials: []mavenRepositoryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, cred := range creds { diff --git a/internal/handlers/maven_repository_test.go b/internal/handlers/maven_repository_test.go index 7d89e1a..b2d9273 100644 --- a/internal/handlers/maven_repository_test.go +++ b/internal/handlers/maven_repository_test.go @@ -36,7 +36,7 @@ func TestMavenRepositoryHandler(t *testing.T) { "password": deltaForcePassword, }, } - handler := NewMavenRepositoryHandler(credentials) + handler := NewMavenRepositoryHandler(credentials, nil) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://corp.dependabot.com/packages/somepkg", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/npm_registry.go b/internal/handlers/npm_registry.go index c7d80b9..2a0af8a 100644 --- a/internal/handlers/npm_registry.go +++ b/internal/handlers/npm_registry.go @@ -28,10 +28,10 @@ type npmRegistryCredentials struct { } // NewNPMRegistryHandler returns a new NPMRegistryHandler, -func NewNPMRegistryHandler(creds config.Credentials) *NPMRegistryHandler { +func NewNPMRegistryHandler(creds config.Credentials, transport http.RoundTripper) *NPMRegistryHandler { handler := NPMRegistryHandler{ credentials: []npmRegistryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, cred := range creds { diff --git a/internal/handlers/npm_registry_test.go b/internal/handlers/npm_registry_test.go index 47c1655..b6e406c 100644 --- a/internal/handlers/npm_registry_test.go +++ b/internal/handlers/npm_registry_test.go @@ -41,7 +41,7 @@ func TestNPMRegistryHandler(t *testing.T) { "token": privateRegToken, }, } - handler := NewNPMRegistryHandler(credentials) + handler := NewNPMRegistryHandler(credentials, nil) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://registry.npmjs.org/private-package", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/nuget_feed.go b/internal/handlers/nuget_feed.go index 40548b4..a68e5af 100644 --- a/internal/handlers/nuget_feed.go +++ b/internal/handlers/nuget_feed.go @@ -62,12 +62,12 @@ type nugetDiscoveryAuth struct { } // NewNugetFeedHandler returns a new NugetFeedHandler. -func NewNugetFeedHandler(creds config.Credentials) *NugetFeedHandler { +func NewNugetFeedHandler(creds config.Credentials, transport http.RoundTripper) *NugetFeedHandler { handler := NugetFeedHandler{ credentials: []nugetFeedCredentials{}, credentialURLs: make(map[string]struct{}), discoverySourceURLs: make(map[string]struct{}), - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, cred := range creds { diff --git a/internal/handlers/oidc_handling_test.go b/internal/handlers/oidc_handling_test.go index 92d2df4..a126f0e 100644 --- a/internal/handlers/oidc_handling_test.go +++ b/internal/handlers/oidc_handling_test.go @@ -43,7 +43,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Cargo", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewCargoRegistryHandler(creds) + return NewCargoRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -67,7 +67,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Cargo", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewCargoRegistryHandler(creds) + return NewCargoRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -88,7 +88,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Cargo", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewCargoRegistryHandler(creds) + return NewCargoRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -108,7 +108,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Cargo", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewCargoRegistryHandler(creds) + return NewCargoRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -130,7 +130,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Cargo", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewCargoRegistryHandler(creds) + return NewCargoRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -153,7 +153,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Composer", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewComposerHandler(creds) + return NewComposerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -177,7 +177,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Composer", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewComposerHandler(creds) + return NewComposerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -198,7 +198,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Composer", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewComposerHandler(creds) + return NewComposerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -219,7 +219,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Composer", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewComposerHandler(creds) + return NewComposerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -241,7 +241,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Composer", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewComposerHandler(creds) + return NewComposerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -265,7 +265,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Docker", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewDockerRegistryHandler(creds, &http.Transport{}, nil) + return NewDockerRegistryHandler(creds, nil, nil) }, credentials: config.Credentials{ config.Credential{ @@ -289,7 +289,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Docker", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewDockerRegistryHandler(creds, &http.Transport{}, nil) + return NewDockerRegistryHandler(creds, nil, nil) }, credentials: config.Credentials{ config.Credential{ @@ -310,7 +310,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Docker with URL", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewDockerRegistryHandler(creds, &http.Transport{}, nil) + return NewDockerRegistryHandler(creds, nil, nil) }, credentials: config.Credentials{ config.Credential{ @@ -330,7 +330,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Docker", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewDockerRegistryHandler(creds, &http.Transport{}, nil) + return NewDockerRegistryHandler(creds, nil, nil) }, credentials: config.Credentials{ config.Credential{ @@ -352,7 +352,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Docker", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewDockerRegistryHandler(creds, &http.Transport{}, nil) + return NewDockerRegistryHandler(creds, nil, nil) }, credentials: config.Credentials{ config.Credential{ @@ -375,7 +375,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Go proxy", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewGoProxyServerHandler(creds) + return NewGoProxyServerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -399,7 +399,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Go proxy with host", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewGoProxyServerHandler(creds) + return NewGoProxyServerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -420,7 +420,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Go proxy", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewGoProxyServerHandler(creds) + return NewGoProxyServerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -440,7 +440,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Go proxy", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewGoProxyServerHandler(creds) + return NewGoProxyServerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -462,7 +462,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Go proxy", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewGoProxyServerHandler(creds) + return NewGoProxyServerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -485,7 +485,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Helm registry", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHelmRegistryHandler(creds) + return NewHelmRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -509,7 +509,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Helm registry", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHelmRegistryHandler(creds) + return NewHelmRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -530,7 +530,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Helm registry with url", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHelmRegistryHandler(creds) + return NewHelmRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -550,7 +550,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Helm registry", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHelmRegistryHandler(creds) + return NewHelmRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -572,7 +572,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Helm registry", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHelmRegistryHandler(creds) + return NewHelmRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -595,7 +595,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Hex", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHexRepositoryHandler(creds) + return NewHexRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -619,7 +619,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Hex", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHexRepositoryHandler(creds) + return NewHexRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -640,7 +640,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Hex", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHexRepositoryHandler(creds) + return NewHexRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -660,7 +660,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Hex", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHexRepositoryHandler(creds) + return NewHexRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -682,7 +682,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Hex", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHexRepositoryHandler(creds) + return NewHexRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -705,7 +705,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Maven", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewMavenRepositoryHandler(creds) + return NewMavenRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -729,7 +729,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Maven", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewMavenRepositoryHandler(creds) + return NewMavenRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -750,7 +750,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Maven", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewMavenRepositoryHandler(creds) + return NewMavenRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -770,7 +770,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Maven", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewMavenRepositoryHandler(creds) + return NewMavenRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -792,7 +792,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Maven", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewMavenRepositoryHandler(creds) + return NewMavenRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -815,7 +815,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NPM", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNPMRegistryHandler(creds) + return NewNPMRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -839,7 +839,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NPM", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNPMRegistryHandler(creds) + return NewNPMRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -860,7 +860,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NPM", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNPMRegistryHandler(creds) + return NewNPMRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -880,7 +880,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NPM", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNPMRegistryHandler(creds) + return NewNPMRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -902,7 +902,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NPM", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNPMRegistryHandler(creds) + return NewNPMRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1050,7 +1050,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Pub", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPubRepositoryHandler(creds) + return NewPubRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1074,7 +1074,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Pub", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPubRepositoryHandler(creds) + return NewPubRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1095,7 +1095,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Pub", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPubRepositoryHandler(creds) + return NewPubRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1115,7 +1115,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Pub", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPubRepositoryHandler(creds) + return NewPubRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1137,7 +1137,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Pub", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPubRepositoryHandler(creds) + return NewPubRepositoryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1160,7 +1160,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Python", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPythonIndexHandler(creds) + return NewPythonIndexHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1184,7 +1184,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Python", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPythonIndexHandler(creds) + return NewPythonIndexHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1205,7 +1205,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Python", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPythonIndexHandler(creds) + return NewPythonIndexHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1225,7 +1225,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Python", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPythonIndexHandler(creds) + return NewPythonIndexHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1247,7 +1247,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Python", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPythonIndexHandler(creds) + return NewPythonIndexHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1270,7 +1270,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "RubyGems", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewRubyGemsServerHandler(creds) + return NewRubyGemsServerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1294,7 +1294,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "RubyGems", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewRubyGemsServerHandler(creds) + return NewRubyGemsServerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1315,7 +1315,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "RubyGems", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewRubyGemsServerHandler(creds) + return NewRubyGemsServerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1336,7 +1336,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "RubyGems", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewRubyGemsServerHandler(creds) + return NewRubyGemsServerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1359,7 +1359,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "RubyGems", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewRubyGemsServerHandler(creds) + return NewRubyGemsServerHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1383,7 +1383,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Terraform", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewTerraformRegistryHandler(creds) + return NewTerraformRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1407,7 +1407,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Terraform with host", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewTerraformRegistryHandler(creds) + return NewTerraformRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1428,7 +1428,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Terraform", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewTerraformRegistryHandler(creds) + return NewTerraformRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1448,7 +1448,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Terraform", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewTerraformRegistryHandler(creds) + return NewTerraformRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1470,7 +1470,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Terraform", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewTerraformRegistryHandler(creds) + return NewTerraformRegistryHandler(creds, nil) }, credentials: config.Credentials{ config.Credential{ @@ -1636,7 +1636,7 @@ func TestPythonOIDCSimpleSuffixStripping(t *testing.T) { }, } - handler := NewPythonIndexHandler(creds) + handler := NewPythonIndexHandler(creds, nil) // /+simple/ should be stripped → registered as /org/feed-A/ reqA := httptest.NewRequestWithContext(t.Context(), "GET", "https://pkgs.example.com/org/feed-A/pkg/a", nil) @@ -1746,7 +1746,7 @@ func TestNPMOIDCSameHostDifferentPaths(t *testing.T) { }, } - handler := NewNPMRegistryHandler(creds) + handler := NewNPMRegistryHandler(creds, nil) // Request to feed-A path should get token A reqA := httptest.NewRequestWithContext(t.Context(), "GET", "https://pkgs.example.com/org/feed-A/some-package", nil) @@ -1799,7 +1799,7 @@ func TestTerraformOIDCSameHostDifferentPaths(t *testing.T) { }, } - handler := NewTerraformRegistryHandler(creds) + handler := NewTerraformRegistryHandler(creds, nil) // Request to feed-A path should get token A reqA := httptest.NewRequestWithContext(t.Context(), "GET", "https://terraform.example.com/org/feed-A/v1/providers/org/name", nil) diff --git a/internal/handlers/opentofu_registry.go b/internal/handlers/opentofu_registry.go index d98da66..f77d2c7 100644 --- a/internal/handlers/opentofu_registry.go +++ b/internal/handlers/opentofu_registry.go @@ -23,10 +23,10 @@ type openTofuRegistryCredentials struct { token string } -func NewOpenTofuRegistryHandler(credentials config.Credentials) *OpenTofuRegistryHandler { +func NewOpenTofuRegistryHandler(credentials config.Credentials, transport http.RoundTripper) *OpenTofuRegistryHandler { handler := OpenTofuRegistryHandler{ credentials: []openTofuRegistryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, credential := range credentials { diff --git a/internal/handlers/opentofu_registry_test.go b/internal/handlers/opentofu_registry_test.go index 6ab6a43..cab263b 100644 --- a/internal/handlers/opentofu_registry_test.go +++ b/internal/handlers/opentofu_registry_test.go @@ -65,7 +65,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { } for _, tt := range tests { t.Run(strings.Join([]string{tt.registryType, tt.host, tt.token}, " "), func(t *testing.T) { - handler := NewOpenTofuRegistryHandler(tt.credentials) + handler := NewOpenTofuRegistryHandler(tt.credentials, nil) request := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", tt.url, nil), nil) @@ -74,7 +74,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { } t.Run("HandleRequest without credentials", func(t *testing.T) { - handler := NewOpenTofuRegistryHandler(config.Credentials{}) + handler := NewOpenTofuRegistryHandler(config.Credentials{}, nil) url := "https://registry.opentofu.org/v1/providers/org/name/versions" request := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", url, nil), nil) @@ -87,7 +87,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { config.Credential{"type": "opentofu_registry", "url": "https://registry.example.com/org1", "token": "token-org1"}, config.Credential{"type": "opentofu_registry", "url": "https://registry.example.com/org2", "token": "token-org2"}, } - handler := NewOpenTofuRegistryHandler(credentials) + handler := NewOpenTofuRegistryHandler(credentials, nil) // Request to org1 path should use org1 token req1 := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", "https://registry.example.com/org1/v1/providers/foo", nil), nil) @@ -106,7 +106,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { credentials := config.Credentials{ config.Credential{"type": "opentofu_registry", "host": "registry.example.org", "token": ""}, } - handler := NewOpenTofuRegistryHandler(credentials) + handler := NewOpenTofuRegistryHandler(credentials, nil) assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty token") }) @@ -114,7 +114,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { credentials := config.Credentials{ config.Credential{"type": "opentofu_registry", "token": "some-token"}, } - handler := NewOpenTofuRegistryHandler(credentials) + handler := NewOpenTofuRegistryHandler(credentials, nil) assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty host and url") }) @@ -124,7 +124,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { config.Credential{"type": "opentofu_registry", "url": "https://registry.example.com/org", "token": "token-org"}, config.Credential{"type": "opentofu_registry", "url": "https://registry.example.com/org1", "token": "token-org1"}, } - handler := NewOpenTofuRegistryHandler(credentials) + handler := NewOpenTofuRegistryHandler(credentials, nil) assert.Equal(t, "https://registry.example.com/org1", handler.credentials[0].url, "longer path should be first") assert.Equal(t, "https://registry.example.com/org", handler.credentials[1].url, "shorter path should be second") diff --git a/internal/handlers/pub_repository.go b/internal/handlers/pub_repository.go index adc8cf1..d66359f 100644 --- a/internal/handlers/pub_repository.go +++ b/internal/handlers/pub_repository.go @@ -25,10 +25,10 @@ type pubRepositoryCredentials struct { token string } -func NewPubRepositoryHandler(credentials config.Credentials) *PubRepositoryHandler { +func NewPubRepositoryHandler(credentials config.Credentials, transport http.RoundTripper) *PubRepositoryHandler { handler := PubRepositoryHandler{ credentials: []pubRepositoryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, credential := range credentials { @@ -45,7 +45,7 @@ func NewPubRepositoryHandler(credentials config.Credentials) *PubRepositoryHandl if oidcCred, _, _ := handler.oidcRegistry.Register(credential, []string{"url"}, "pub repository"); oidcCred != nil { continue } - } else if oidcCred, _ := oidc.CreateOIDCCredential(credential); oidcCred != nil { + } else if oidcCred, _ := oidc.CreateOIDCCredential(credential, transport); oidcCred != nil { continue } diff --git a/internal/handlers/pub_repository_test.go b/internal/handlers/pub_repository_test.go index 98186e7..fc923a0 100644 --- a/internal/handlers/pub_repository_test.go +++ b/internal/handlers/pub_repository_test.go @@ -46,7 +46,7 @@ func TestPubRepositoryHandler(t *testing.T) { }, } - handler := NewPubRepositoryHandler(credentials) + handler := NewPubRepositoryHandler(credentials, nil) // valid request, should authenticate url := validURL diff --git a/internal/handlers/python_index.go b/internal/handlers/python_index.go index 4dc92b8..02ea992 100644 --- a/internal/handlers/python_index.go +++ b/internal/handlers/python_index.go @@ -29,11 +29,11 @@ type pythonIndexCredentials struct { } // NewPythonIndexHandler returns a new PythonIndexHandler. -func NewPythonIndexHandler(creds config.Credentials) *PythonIndexHandler { +func NewPythonIndexHandler(creds config.Credentials, transport http.RoundTripper) *PythonIndexHandler { handler := PythonIndexHandler{ credentials: []pythonIndexCredentials{}, downloadAuth: newPythonIndexDownloadAuthStore(), - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, cred := range creds { @@ -43,7 +43,7 @@ func NewPythonIndexHandler(creds config.Credentials) *PythonIndexHandler { indexURL := cred.GetString("index-url") - oidcCredential, _ := oidc.CreateOIDCCredential(cred) + oidcCredential, _ := oidc.CreateOIDCCredential(cred, transport) if oidcCredential != nil { // Normalize the registration URL by stripping the /simple or /+simple // suffix, matching how static credentials are matched at request time. diff --git a/internal/handlers/python_index_test.go b/internal/handlers/python_index_test.go index ce3dfd4..8ef8e41 100644 --- a/internal/handlers/python_index_test.go +++ b/internal/handlers/python_index_test.go @@ -55,7 +55,7 @@ func TestPythonIndexHandler(t *testing.T) { "token": fmt.Sprintf("%s:%s", deltaForceUser, deltaForcePassword), }, } - handler := NewPythonIndexHandler(credentials) + handler := NewPythonIndexHandler(credentials, nil) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://corp.dependabot.com/pyreg", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/rubygems_server.go b/internal/handlers/rubygems_server.go index 4ad797f..8f9445f 100644 --- a/internal/handlers/rubygems_server.go +++ b/internal/handlers/rubygems_server.go @@ -25,10 +25,10 @@ type rubyGemsServerCredentials struct { } // NewRubyGemsServerHandler returns a new RubyGemsServerHandler. -func NewRubyGemsServerHandler(creds config.Credentials) *RubyGemsServerHandler { +func NewRubyGemsServerHandler(creds config.Credentials, transport http.RoundTripper) *RubyGemsServerHandler { handler := RubyGemsServerHandler{ credentials: []rubyGemsServerCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, cred := range creds { diff --git a/internal/handlers/rubygems_server_test.go b/internal/handlers/rubygems_server_test.go index 128afa7..94c1bf9 100644 --- a/internal/handlers/rubygems_server_test.go +++ b/internal/handlers/rubygems_server_test.go @@ -37,7 +37,7 @@ func TestRubyGemsServerHandler(t *testing.T) { "token": fmt.Sprintf("%s:%s", pathUser, pathPassword), }, } - handler := NewRubyGemsServerHandler(credentials) + handler := NewRubyGemsServerHandler(credentials, nil) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://corp.dependabot.com/gems", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/terraform_registry.go b/internal/handlers/terraform_registry.go index 2430ab3..922c421 100644 --- a/internal/handlers/terraform_registry.go +++ b/internal/handlers/terraform_registry.go @@ -24,10 +24,10 @@ type terraformRegistryCredentials struct { token string } -func NewTerraformRegistryHandler(credentials config.Credentials) *TerraformRegistryHandler { +func NewTerraformRegistryHandler(credentials config.Credentials, transport http.RoundTripper) *TerraformRegistryHandler { handler := TerraformRegistryHandler{ credentials: []terraformRegistryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(), + oidcRegistry: oidc.NewOIDCRegistry(transport), } for _, credential := range credentials { diff --git a/internal/handlers/terraform_registry_test.go b/internal/handlers/terraform_registry_test.go index 401c923..c8f0a9a 100644 --- a/internal/handlers/terraform_registry_test.go +++ b/internal/handlers/terraform_registry_test.go @@ -58,7 +58,7 @@ func TestTerraformRegistryHandler(t *testing.T) { } for _, tt := range tests { t.Run(strings.Join([]string{tt.registryType, tt.host, tt.token}, " "), func(t *testing.T) { - handler := NewTerraformRegistryHandler(tt.credentials) + handler := NewTerraformRegistryHandler(tt.credentials, nil) request := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", tt.url, nil), nil) @@ -67,7 +67,7 @@ func TestTerraformRegistryHandler(t *testing.T) { } t.Run("HandleRequest without credentials", func(t *testing.T) { - handler := NewTerraformRegistryHandler(config.Credentials{}) + handler := NewTerraformRegistryHandler(config.Credentials{}, nil) url := "https://registry.terraform.io/v1/providers/org/name/versions" request := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", url, nil), nil) @@ -80,7 +80,7 @@ func TestTerraformRegistryHandler(t *testing.T) { config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org1", "token": "token-org1"}, config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org2", "token": "token-org2"}, } - handler := NewTerraformRegistryHandler(credentials) + handler := NewTerraformRegistryHandler(credentials, nil) // Request to org1 path should use org1 token req1 := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", "https://terraform.example.com/org1/v1/providers/foo", nil), nil) @@ -99,7 +99,7 @@ func TestTerraformRegistryHandler(t *testing.T) { credentials := config.Credentials{ config.Credential{"type": "terraform_registry", "host": "terraform.example.org", "token": ""}, } - handler := NewTerraformRegistryHandler(credentials) + handler := NewTerraformRegistryHandler(credentials, nil) assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty token") }) @@ -107,7 +107,7 @@ func TestTerraformRegistryHandler(t *testing.T) { credentials := config.Credentials{ config.Credential{"type": "terraform_registry", "token": "some-token"}, } - handler := NewTerraformRegistryHandler(credentials) + handler := NewTerraformRegistryHandler(credentials, nil) assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty host and url") }) @@ -117,7 +117,7 @@ func TestTerraformRegistryHandler(t *testing.T) { config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org", "token": "token-org"}, config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org1", "token": "token-org1"}, } - handler := NewTerraformRegistryHandler(credentials) + handler := NewTerraformRegistryHandler(credentials, nil) assert.Equal(t, "https://terraform.example.com/org1", handler.credentials[0].url, "longer path should be first") assert.Equal(t, "https://terraform.example.com/org", handler.credentials[1].url, "shorter path should be second") diff --git a/internal/oidc/actions_oidc.go b/internal/oidc/actions_oidc.go index 1f446a5..596f59a 100644 --- a/internal/oidc/actions_oidc.go +++ b/internal/oidc/actions_oidc.go @@ -228,7 +228,7 @@ type OIDCAccessToken struct { // githubToken: The GitHub Actions OIDC token obtained via GetTokenForAzureADExchange // // Returns an Azure AD access token scoped for Azure DevOps (499b84ac-1321-427f-aa17-267ca6975798/.default) -func GetAzureAccessToken(ctx context.Context, params AzureOIDCParameters, githubToken string) (_ *OIDCAccessToken, err error) { +func GetAzureAccessToken(ctx context.Context, params AzureOIDCParameters, githubToken string, client *http.Client) (_ *OIDCAccessToken, err error) { if params.TenantID == "" { return nil, fmt.Errorf("tenant ID is required") } @@ -252,10 +252,6 @@ func GetAzureAccessToken(ctx context.Context, params AzureOIDCParameters, github formData.Set("client_assertion", githubToken) formData.Set("grant_type", "client_credentials") - client := &http.Client{ - Timeout: 10 * time.Second, - } - req, err := http.NewRequestWithContext(ctx, "POST", tokenURL, strings.NewReader(formData.Encode())) if err != nil { return nil, fmt.Errorf("failed to create Azure token request: %w", err) @@ -296,7 +292,7 @@ func GetAzureAccessToken(ctx context.Context, params AzureOIDCParameters, github // GetAzureAccessTokenForDevOps is a convenience function that combines fetching the GitHub OIDC token // and exchanging it for an Azure AD access token in a single call. -func GetAzureAccessTokenForDevOps(ctx context.Context, params AzureOIDCParameters) (*OIDCAccessToken, error) { +func GetAzureAccessTokenForDevOps(ctx context.Context, params AzureOIDCParameters, client *http.Client) (*OIDCAccessToken, error) { if !IsOIDCConfigured() { return nil, fmt.Errorf("GitHub Actions OIDC is not configured") } @@ -308,7 +304,7 @@ func GetAzureAccessTokenForDevOps(ctx context.Context, params AzureOIDCParameter } // Exchange for Azure token - azureToken, err := GetAzureAccessToken(ctx, params, githubToken) + azureToken, err := GetAzureAccessToken(ctx, params, githubToken, client) if err != nil { return nil, fmt.Errorf("failed to exchange GitHub token for Azure token: %w", err) } @@ -324,7 +320,7 @@ func GetAzureAccessTokenForDevOps(ctx context.Context, params AzureOIDCParameter // githubToken: The GitHub Actions OIDC token obtained via GetToken // // Returns a JFrog access token -func GetJFrogAccessToken(ctx context.Context, params JFrogOIDCParameters, githubToken string) (_ *OIDCAccessToken, err error) { +func GetJFrogAccessToken(ctx context.Context, params JFrogOIDCParameters, githubToken string, client *http.Client) (_ *OIDCAccessToken, err error) { if params.JFrogURL == "" { return nil, fmt.Errorf("token URL base is required") } @@ -359,9 +355,6 @@ func GetJFrogAccessToken(ctx context.Context, params JFrogOIDCParameters, github req.Header.Set("Content-Type", "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 JFrog token request: %w", err) @@ -397,7 +390,7 @@ func GetJFrogAccessToken(ctx context.Context, params JFrogOIDCParameters, github }, nil } -func GetJFrogAccessTokenForDevOps(ctx context.Context, params JFrogOIDCParameters) (*OIDCAccessToken, error) { +func GetJFrogAccessTokenForDevOps(ctx context.Context, params JFrogOIDCParameters, client *http.Client) (*OIDCAccessToken, error) { if !IsOIDCConfigured() { return nil, fmt.Errorf("GitHub Actions OIDC is not configured") } @@ -409,7 +402,7 @@ func GetJFrogAccessTokenForDevOps(ctx context.Context, params JFrogOIDCParameter } // Exchange for JFrog token - jfrogToken, err := GetJFrogAccessToken(ctx, params, githubToken) + jfrogToken, err := GetJFrogAccessToken(ctx, params, githubToken, client) if err != nil { return nil, fmt.Errorf("failed to exchange GitHub token for JFrog token: %w", err) } @@ -425,7 +418,7 @@ func GetJFrogAccessTokenForDevOps(ctx context.Context, params JFrogOIDCParameter // githubToken: The GitHub Actions OIDC token obtained via GetToken // // Returns temporary AWS credentials -func GetAWSAccessToken(ctx context.Context, params AWSOIDCParameters, githubToken string) (_ *OIDCAccessToken, err error) { +func GetAWSAccessToken(ctx context.Context, params AWSOIDCParameters, githubToken string, client *http.Client) (_ *OIDCAccessToken, err error) { if params.Region == "" { return nil, fmt.Errorf("AWS region is required") } @@ -453,10 +446,6 @@ func GetAWSAccessToken(ctx context.Context, params AWSOIDCParameters, githubToke formData.Set("RoleSessionName", "dependabot-update") formData.Set("WebIdentityToken", githubToken) - client := &http.Client{ - Timeout: 10 * time.Second, - } - req, err := http.NewRequestWithContext(ctx, "POST", awsCodeArtifactSTSRequestUrl, strings.NewReader(formData.Encode())) if err != nil { return nil, fmt.Errorf("failed to create AWS credential request: %w", err) @@ -573,7 +562,7 @@ func GetAWSAccessToken(ctx context.Context, params AWSOIDCParameters, githubToke }, nil } -func GetAWSAccessTokenForDevOps(ctx context.Context, params AWSOIDCParameters) (*OIDCAccessToken, error) { +func GetAWSAccessTokenForDevOps(ctx context.Context, params AWSOIDCParameters, client *http.Client) (*OIDCAccessToken, error) { if !IsOIDCConfigured() { return nil, fmt.Errorf("GitHub Actions OIDC is not configured") } @@ -585,7 +574,7 @@ func GetAWSAccessTokenForDevOps(ctx context.Context, params AWSOIDCParameters) ( } // Exchange for AWS token - awsToken, err := GetAWSAccessToken(ctx, params, githubToken) + awsToken, err := GetAWSAccessToken(ctx, params, githubToken, client) if err != nil { return nil, fmt.Errorf("failed to exchange GitHub token for AWS token: %w", err) } @@ -593,7 +582,7 @@ func GetAWSAccessTokenForDevOps(ctx context.Context, params AWSOIDCParameters) ( return awsToken, nil } -func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParameters, githubToken string) (_ *OIDCAccessToken, err error) { +func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParameters, githubToken string, client *http.Client) (_ *OIDCAccessToken, err error) { if params.ServiceSlug == "" { return nil, fmt.Errorf("service slug is required") } @@ -630,9 +619,6 @@ func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParamete 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) @@ -664,7 +650,7 @@ func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParamete }, nil } -func GetCloudsmithAccessTokenForDevOps(ctx context.Context, params CloudsmithOIDCParameters) (*OIDCAccessToken, error) { +func GetCloudsmithAccessTokenForDevOps(ctx context.Context, params CloudsmithOIDCParameters, client *http.Client) (*OIDCAccessToken, error) { if !IsOIDCConfigured() { return nil, fmt.Errorf("GitHub Actions OIDC is not configured") } @@ -675,7 +661,7 @@ func GetCloudsmithAccessTokenForDevOps(ctx context.Context, params CloudsmithOID return nil, fmt.Errorf("failed to get GitHub OIDC token: %w", err) } - cloudsmithToken, err := GetCloudsmithAccessToken(ctx, params, githubToken) + cloudsmithToken, err := GetCloudsmithAccessToken(ctx, params, githubToken, client) if err != nil { return nil, fmt.Errorf("failed to exchange GitHub token for cloudsmith token: %w", err) } @@ -683,7 +669,7 @@ func GetCloudsmithAccessTokenForDevOps(ctx context.Context, params CloudsmithOID return cloudsmithToken, nil } -func GetGCPAccessToken(ctx context.Context, params GCPOIDCParameters, githubToken string) (_ *OIDCAccessToken, err error) { +func GetGCPAccessToken(ctx context.Context, params GCPOIDCParameters, githubToken string, client *http.Client) (_ *OIDCAccessToken, err error) { if params.WorkloadIdentityProvider == "" { return nil, fmt.Errorf("workload-identity-provider is required") } @@ -718,9 +704,6 @@ func GetGCPAccessToken(ctx context.Context, params GCPOIDCParameters, githubToke stsReq.Header.Set("Accept", "application/json") stsReq.Header.Set("User-Agent", "dependabot-proxy/1.0") - client := &http.Client{ - Timeout: 10 * time.Second, - } stsResp, err := client.Do(stsReq) if err != nil { return nil, fmt.Errorf("failed to execute GCP STS request: %w", err) @@ -818,7 +801,7 @@ func GetGCPAccessToken(ctx context.Context, params GCPOIDCParameters, githubToke }, nil } -func GetGCPAccessTokenForDevOps(ctx context.Context, params GCPOIDCParameters) (*OIDCAccessToken, error) { +func GetGCPAccessTokenForDevOps(ctx context.Context, params GCPOIDCParameters, client *http.Client) (*OIDCAccessToken, error) { if !IsOIDCConfigured() { return nil, fmt.Errorf("GitHub Actions OIDC is not configured") } @@ -829,7 +812,7 @@ func GetGCPAccessTokenForDevOps(ctx context.Context, params GCPOIDCParameters) ( return nil, fmt.Errorf("failed to get GitHub OIDC token: %w", err) } - gcpToken, err := GetGCPAccessToken(ctx, params, githubToken) + gcpToken, err := GetGCPAccessToken(ctx, params, githubToken, client) if err != nil { return nil, fmt.Errorf("failed to exchange GitHub token for GCP token: %w", err) } diff --git a/internal/oidc/actions_oidc_test.go b/internal/oidc/actions_oidc_test.go index bb7c4a3..2c2fd7c 100644 --- a/internal/oidc/actions_oidc_test.go +++ b/internal/oidc/actions_oidc_test.go @@ -458,7 +458,7 @@ func TestGetAzureAccessToken(t *testing.T) { TenantID: tt.tenantID, ClientID: tt.clientID, } - azureToken, err = GetAzureAccessToken(ctx, params, tt.githubToken) + azureToken, err = GetAzureAccessToken(ctx, params, tt.githubToken, http.DefaultClient) if tt.expectError { require.Error(t, err) @@ -638,7 +638,7 @@ func TestGetJFrogAccessToken(t *testing.T) { Audience: tt.audience, IdentityMappingName: tt.identityMappingName, } - jfrogToken, err = GetJFrogAccessToken(ctx, params, tt.githubToken) + jfrogToken, err = GetJFrogAccessToken(ctx, params, tt.githubToken, http.DefaultClient) if tt.expectError { require.Error(t, err) @@ -828,7 +828,7 @@ func TestGetAWSAccessToken(t *testing.T) { Domain: tt.domain, DomainOwner: tt.domainOwner, } - awsToken, err = GetAWSAccessToken(ctx, params, tt.githubToken) + awsToken, err = GetAWSAccessToken(ctx, params, tt.githubToken, http.DefaultClient) if tt.expectError { require.Error(t, err) @@ -1010,7 +1010,7 @@ func TestGetCloudsmithAccessToken(t *testing.T) { })) } - cloudsmithToken, err = GetCloudsmithAccessToken(ctx, tt.params, tt.githubToken) + cloudsmithToken, err = GetCloudsmithAccessToken(ctx, tt.params, tt.githubToken, http.DefaultClient) if tt.expectError { require.Error(t, err) @@ -1333,7 +1333,7 @@ func TestGetGCPAccessToken(t *testing.T) { })) } - gcpToken, err := GetGCPAccessToken(ctx, tt.params, tt.githubToken) + gcpToken, err := GetGCPAccessToken(ctx, tt.params, tt.githubToken, http.DefaultClient) if tt.expectError { require.Error(t, err) diff --git a/internal/oidc/oidc_credential.go b/internal/oidc/oidc_credential.go index d3d591a..67f49ab 100644 --- a/internal/oidc/oidc_credential.go +++ b/internal/oidc/oidc_credential.go @@ -3,6 +3,7 @@ package oidc import ( "context" "fmt" + "net/http" "net/url" "sync" "time" @@ -74,13 +75,14 @@ type OIDCCredential struct { tokenExpiry time.Time isRejected bool mutex sync.RWMutex + httpClient *http.Client } func (c *OIDCCredential) Provider() string { return c.parameters.Name() } -func CreateOIDCCredential(cred config.Credential) (*OIDCCredential, error) { +func CreateOIDCCredential(cred config.Credential, transport http.RoundTripper) (*OIDCCredential, error) { if !IsOIDCConfigured() { return nil, fmt.Errorf("OIDC is not configured") } @@ -173,6 +175,10 @@ func CreateOIDCCredential(cred config.Credential) (*OIDCCredential, error) { return &OIDCCredential{ parameters: parameters, + httpClient: &http.Client{ + Timeout: 10 * time.Second, + Transport: transport, + }, }, nil } @@ -201,15 +207,15 @@ func GetOrRefreshOIDCToken(cred *OIDCCredential, ctx context.Context) (string, e var err error switch params := cred.parameters.(type) { case *AzureOIDCParameters: - oidcAccessToken, err = GetAzureAccessTokenForDevOps(ctx, *params) + oidcAccessToken, err = GetAzureAccessTokenForDevOps(ctx, *params, cred.httpClient) case *JFrogOIDCParameters: - oidcAccessToken, err = GetJFrogAccessTokenForDevOps(ctx, *params) + oidcAccessToken, err = GetJFrogAccessTokenForDevOps(ctx, *params, cred.httpClient) case *AWSOIDCParameters: - oidcAccessToken, err = GetAWSAccessTokenForDevOps(ctx, *params) + oidcAccessToken, err = GetAWSAccessTokenForDevOps(ctx, *params, cred.httpClient) case *CloudsmithOIDCParameters: - oidcAccessToken, err = GetCloudsmithAccessTokenForDevOps(ctx, *params) + oidcAccessToken, err = GetCloudsmithAccessTokenForDevOps(ctx, *params, cred.httpClient) case *GCPOIDCParameters: - oidcAccessToken, err = GetGCPAccessTokenForDevOps(ctx, *params) + oidcAccessToken, err = GetGCPAccessTokenForDevOps(ctx, *params, cred.httpClient) 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 efc2413..92b58e4 100644 --- a/internal/oidc/oidc_credential_test.go +++ b/internal/oidc/oidc_credential_test.go @@ -27,7 +27,7 @@ func TestSuccessfulAuthenticationDoesNotMakeARepeatedRequest(t *testing.T) { creds, err := CreateOIDCCredential(config.Credential{ "tenant-id": "test-tenant-id", "client-id": "test-client-id", - }) + }, nil) require.NoError(t, err) // ensure of type azure @@ -88,7 +88,7 @@ func TestFailedAuthenticationIsNotRetried(t *testing.T) { creds, err := CreateOIDCCredential(config.Credential{ "tenant-id": "test-tenant-id", "client-id": "test-client-id", - }) + }, nil) require.NoError(t, err) // ensure of type azure @@ -334,7 +334,7 @@ func TestTryCreateOIDCCredential(t *testing.T) { t.Setenv(envActionsIDTokenRequestURL, "https://example.com/token") t.Setenv(envActionsIDTokenRequestToken, "test-token") - actual, _ := CreateOIDCCredential(tc.cred) + actual, _ := CreateOIDCCredential(tc.cred, nil) if tc.expectedParameters == nil { assert.Nil(t, actual) return diff --git a/internal/oidc/oidc_registry.go b/internal/oidc/oidc_registry.go index ed7dd11..9a751c5 100644 --- a/internal/oidc/oidc_registry.go +++ b/internal/oidc/oidc_registry.go @@ -17,8 +17,9 @@ import ( // and avoids key collisions when multiple registries share a host with // different paths. type OIDCRegistry struct { - byHost map[string][]oidcEntry - mutex sync.RWMutex + byHost map[string][]oidcEntry + mutex sync.RWMutex + transport http.RoundTripper } type oidcEntry struct { @@ -27,10 +28,12 @@ type oidcEntry struct { credential *OIDCCredential } -// NewOIDCRegistry creates an empty registry. -func NewOIDCRegistry() *OIDCRegistry { +// NewOIDCRegistry creates an empty registry. transport is used for outbound OIDC token requests +// and should be the same secure transport used by the rest of the proxy. +func NewOIDCRegistry(transport http.RoundTripper) *OIDCRegistry { return &OIDCRegistry{ - byHost: make(map[string][]oidcEntry), + byHost: make(map[string][]oidcEntry), + transport: transport, } } @@ -47,7 +50,7 @@ func (r *OIDCRegistry) Register( urlFields []string, registryType string, ) (*OIDCCredential, string, bool) { - oidcCredential, _ := CreateOIDCCredential(cred) + oidcCredential, _ := CreateOIDCCredential(cred, r.transport) if oidcCredential == nil { return nil, "", false } diff --git a/internal/oidc/oidc_registry_test.go b/internal/oidc/oidc_registry_test.go index defbd5c..9b58511 100644 --- a/internal/oidc/oidc_registry_test.go +++ b/internal/oidc/oidc_registry_test.go @@ -57,7 +57,7 @@ func azureCredWithRegistry(tenantID, clientID, registry string) config.Credentia func TestOIDCRegistry_Register_SingleCredential(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com/packages") oidcCred, key, ok := r.Register(cred, []string{"url"}, "test registry") @@ -69,7 +69,7 @@ func TestOIDCRegistry_Register_SingleCredential(t *testing.T) { func TestOIDCRegistry_Register_URLFieldPriority(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := config.Credential{ "type": "test_registry", @@ -87,7 +87,7 @@ func TestOIDCRegistry_Register_URLFieldPriority(t *testing.T) { func TestOIDCRegistry_Register_FallsBackToHost(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := config.Credential{ "type": "test_registry", @@ -107,7 +107,7 @@ func TestOIDCRegistry_Register_NotOIDC(t *testing.T) { t.Setenv(envActionsIDTokenRequestURL, "") t.Setenv(envActionsIDTokenRequestToken, "") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := config.Credential{ "type": "test_registry", "url": "https://registry.example.com", @@ -122,7 +122,7 @@ func TestOIDCRegistry_Register_NotOIDC(t *testing.T) { func TestOIDCRegistry_Register_NoKeyAvailable(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) // Credential with OIDC params but no URL or host cred := config.Credential{ @@ -144,7 +144,7 @@ func TestOIDCRegistry_TryAuth_SingleCredential(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com/packages") r.Register(cred, []string{"url"}, "test registry") @@ -163,7 +163,7 @@ func TestOIDCRegistry_TryAuth_SameHostDifferentPaths_NoCollision(t *testing.T) { mockAzureOIDC(t, "tenant-A", "token-feed-A") mockAzureOIDC(t, "tenant-B", "token-feed-B") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) // Two registries on the same host with different paths credA := azureCredWithURL("tenant-A", "client-A", @@ -201,7 +201,7 @@ func TestOIDCRegistry_TryAuth_HostOnlyMatchesAnyPath(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) // Register with host only (no path) cred := config.Credential{ @@ -222,7 +222,7 @@ func TestOIDCRegistry_TryAuth_HostOnlyMatchesAnyPath(t *testing.T) { func TestOIDCRegistry_TryAuth_NoMatch(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com/packages") r.Register(cred, []string{"url"}, "test registry") @@ -237,7 +237,7 @@ func TestOIDCRegistry_TryAuth_NoMatch(t *testing.T) { func TestOIDCRegistry_TryAuth_WrongPathNoMatch(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := azureCredWithURL("tenant-1", "client-1", "https://pkgs.dev.azure.com/org/_packaging/feed-A/npm/registry/") @@ -253,7 +253,7 @@ func TestOIDCRegistry_TryAuth_WrongPathNoMatch(t *testing.T) { } func TestOIDCRegistry_CredentialForRequestConcurrentRegistration(t *testing.T) { - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) credential := &OIDCCredential{ parameters: &AzureOIDCParameters{ TenantID: "tenant-1", @@ -293,7 +293,7 @@ func TestOIDCRegistry_RegisterURL(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) // Register primary URL cred := azureCredWithURL("tenant-1", "client-1", "https://nuget.example.com/v3/index.json") @@ -314,7 +314,7 @@ func TestOIDCRegistry_RegisterURL(t *testing.T) { func TestOIDCRegistry_TryAuth_PortMismatch(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com:8443/packages") r.Register(cred, []string{"url"}, "test registry") @@ -328,7 +328,7 @@ func TestOIDCRegistry_TryAuth_PortMismatch(t *testing.T) { func TestOIDCRegistry_Register_RegistryField(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := azureCredWithRegistry("tenant-1", "client-1", "ghcr.io") _, key, ok := r.Register(cred, []string{"registry"}, "docker registry") @@ -344,7 +344,7 @@ func TestOIDCRegistry_TryAuth_PathSpecificBeatsHostOnly(t *testing.T) { mockAzureOIDC(t, "tenant-1", "__host_only_token__") mockAzureOIDC(t, "tenant-2", "__path_specific_token__") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) hostOnlyCred := config.Credential{ "type": "test_registry", @@ -372,7 +372,7 @@ func TestOIDCRegistry_TryAuth_LongestPathPrefixWins(t *testing.T) { mockAzureOIDC(t, "tenant-1", "__short_prefix_token__") mockAzureOIDC(t, "tenant-2", "__long_prefix_token__") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) shortPrefixCred := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com/packages") longPrefixCred := azureCredWithURL("tenant-2", "client-2", "https://registry.example.com/packages/private") @@ -394,7 +394,7 @@ func TestOIDCRegistry_TryAuth_CaseInsensitiveHost(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := azureCredWithURL("tenant-1", "client-1", "https://Registry.Example.COM/packages") r.Register(cred, []string{"url"}, "test registry") @@ -432,7 +432,7 @@ func TestOIDCRegistry_TryAuth_Cloudsmith_UsesAPIKey(t *testing.T) { defer httpmock.DeactivateAndReset() mockCloudsmithOIDC(t, "my-org", "__cs_token__") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := cloudsmithCred("my-org", "my-service", "https://cloudsmith.io", "https://dl.cloudsmith.io/basic/my-org/my-repo") r.Register(cred, []string{"url"}, "test registry") @@ -467,7 +467,7 @@ func TestOIDCRegistry_TryAuth_GCP_UsesBearer(t *testing.T) { defer httpmock.DeactivateAndReset() mockGCPOIDC(t, "__gcp_token__") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := gcpCred("projects/123/locations/global/workloadIdentityPools/pool/providers/prov", "https://us-central1-python.pkg.dev/my-project/my-repo/simple") r.Register(cred, []string{"url"}, "test registry") @@ -486,7 +486,7 @@ func TestOIDCRegistry_TryAuth_GCP_DockerUsesBasicAuth(t *testing.T) { defer httpmock.DeactivateAndReset() mockGCPOIDC(t, "__gcp_token__") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := gcpCred("projects/123/locations/global/workloadIdentityPools/pool/providers/prov", "https://us-central1-docker.pkg.dev/my-project/my-repo") r.Register(cred, []string{"url"}, "docker registry") @@ -505,7 +505,7 @@ func TestOIDCRegistry_TryAuth_GCP_DockerUsesBasicAuth(t *testing.T) { func TestOIDCRegistry_Register_IndexURLField(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := azureCred("tenant-1", "client-1") cred["index-url"] = "https://pkgs.dev.azure.com/org/_packaging/feed/pypi/simple" @@ -522,7 +522,7 @@ func TestOIDCRegistry_TryAuth_URLWithoutProtocol(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := azureCred("tenant-1", "client-1") cred["url"] = "registry.example.com/packages" @@ -541,7 +541,7 @@ func TestOIDCRegistry_RegisterURL_MultipleOnSameHost(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred := azureCredWithURL("tenant-1", "client-1", "https://nuget.example.com/v3/index.json") oidcCred, _, ok := r.Register(cred, []string{"url"}, "nuget feed") @@ -563,7 +563,7 @@ func TestOIDCRegistry_RegisterURL_MultipleOnSameHost(t *testing.T) { func TestOIDCRegistry_Register_NoDuplicateEntries(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry() + r := NewOIDCRegistry(nil) cred1 := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com/packages") cred2 := azureCredWithURL("tenant-2", "client-2", "https://registry.example.com/packages") diff --git a/proxy.go b/proxy.go index 40bb2ea..3aefbcb 100644 --- a/proxy.go +++ b/proxy.go @@ -68,7 +68,7 @@ func newProxyWithCacheDir(envSettings config.ProxyEnvSettings, cfg *config.Confi proxy.OnRequest().DoFunc(logger.logRequest) proxy.OnResponse().DoFunc(logger.logResponse) - nugetFeedHandler := handlers.NewNugetFeedHandler(cfg.Credentials) + nugetFeedHandler := handlers.NewNugetFeedHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(nugetFeedHandler.PrepareRequest) enableCache := os.Getenv("PROXY_CACHE") == "true" @@ -98,50 +98,50 @@ func newProxyWithCacheDir(envSettings config.ProxyEnvSettings, cfg *config.Confi proxy.OnRequest().DoFunc(gitServerHandler.HandleRequest) proxy.OnResponse().DoFunc(gitServerHandler.HandleResponse) - npmRegistryHandler := handlers.NewNPMRegistryHandler(cfg.Credentials) + npmRegistryHandler := handlers.NewNPMRegistryHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(npmRegistryHandler.HandleRequest) hexOrganizationHandler := handlers.NewHexOrganizationHandler(cfg.Credentials) proxy.OnRequest().DoFunc(hexOrganizationHandler.HandleRequest) - hexRepositoryHandler := handlers.NewHexRepositoryHandler(cfg.Credentials) + hexRepositoryHandler := handlers.NewHexRepositoryHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(hexRepositoryHandler.HandleRequest) - pythonHandler := handlers.NewPythonIndexHandler(cfg.Credentials) + pythonHandler := handlers.NewPythonIndexHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(pythonHandler.HandleRequest) proxy.OnResponse().DoFunc(pythonHandler.HandleResponse) - composerHandler := handlers.NewComposerHandler(cfg.Credentials) + composerHandler := handlers.NewComposerHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(composerHandler.HandleRequest) dockerRegistryHandler := handlers.NewDockerRegistryHandler(cfg.Credentials, transport, nil) proxy.OnRequest().DoFunc(dockerRegistryHandler.HandleRequest) - rubyGemsServerHandler := handlers.NewRubyGemsServerHandler(cfg.Credentials) + rubyGemsServerHandler := handlers.NewRubyGemsServerHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(rubyGemsServerHandler.HandleRequest) proxy.OnRequest().DoFunc(nugetFeedHandler.HandleRequest) proxy.OnResponse().DoFunc(nugetFeedHandler.HandleResponse) - mavenRepositoryHandler := handlers.NewMavenRepositoryHandler(cfg.Credentials) + mavenRepositoryHandler := handlers.NewMavenRepositoryHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(mavenRepositoryHandler.HandleRequest) - terraformRegistryHandler := handlers.NewTerraformRegistryHandler(cfg.Credentials) + terraformRegistryHandler := handlers.NewTerraformRegistryHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(terraformRegistryHandler.HandleRequest) - openTofuRegistryHandler := handlers.NewOpenTofuRegistryHandler(cfg.Credentials) + openTofuRegistryHandler := handlers.NewOpenTofuRegistryHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(openTofuRegistryHandler.HandleRequest) - pubRepositoryHandler := handlers.NewPubRepositoryHandler(cfg.Credentials) + pubRepositoryHandler := handlers.NewPubRepositoryHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(pubRepositoryHandler.HandleRequest) - cargoRegistryHandler := handlers.NewCargoRegistryHandler(cfg.Credentials) + cargoRegistryHandler := handlers.NewCargoRegistryHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(cargoRegistryHandler.HandleRequest) - goProxyServerHandler := handlers.NewGoProxyServerHandler(cfg.Credentials) + goProxyServerHandler := handlers.NewGoProxyServerHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(goProxyServerHandler.HandleRequest) - helmRegistryHandler := handlers.NewHelmRegistryHandler(cfg.Credentials) + helmRegistryHandler := handlers.NewHelmRegistryHandler(cfg.Credentials, transport) proxy.OnRequest().DoFunc(helmRegistryHandler.HandleRequest) proxy.OnResponse().DoFunc(cacher.OnResponse) From 230be84218a05507785c74a70af6e7e6ef2290fc Mon Sep 17 00:00:00 2001 From: joniumGit <52005121+joniumGit@users.noreply.github.com> Date: Fri, 21 Aug 2026 23:37:10 +0300 Subject: [PATCH 3/3] Enforce bounded HTTP client for OIDC exchanges Signed-off-by: joniumGit <52005121+joniumGit@users.noreply.github.com> --- internal/handlers/cargo_registry.go | 6 +- internal/handlers/cargo_registry_test.go | 14 +- internal/handlers/composer.go | 4 +- internal/handlers/composer_test.go | 2 +- internal/handlers/docker_registry.go | 16 +- internal/handlers/docker_registry_test.go | 38 ++++- internal/handlers/goproxy_server_handler.go | 4 +- .../handlers/goproxy_server_handler_test.go | 2 +- internal/handlers/helm_registry.go | 4 +- internal/handlers/helm_registry_test.go | 2 +- internal/handlers/hex_repository.go | 6 +- internal/handlers/hex_repository_test.go | 2 +- internal/handlers/maven_repository.go | 4 +- internal/handlers/maven_repository_test.go | 2 +- internal/handlers/npm_registry.go | 4 +- internal/handlers/npm_registry_test.go | 4 +- internal/handlers/nuget_feed.go | 4 +- internal/handlers/nuget_feed_test.go | 40 ++--- internal/handlers/oidc_handling_test.go | 138 +++++++++--------- internal/handlers/opentofu_registry.go | 4 +- internal/handlers/opentofu_registry_test.go | 12 +- internal/handlers/pub_repository.go | 6 +- internal/handlers/pub_repository_test.go | 2 +- internal/handlers/python_index.go | 6 +- internal/handlers/python_index_test.go | 18 ++- internal/handlers/rubygems_server.go | 4 +- internal/handlers/rubygems_server_test.go | 2 +- internal/handlers/terraform_registry.go | 4 +- internal/handlers/terraform_registry_test.go | 12 +- internal/handlers/test_helpers.go | 12 ++ internal/oidc/actions_oidc.go | 53 +++++-- internal/oidc/actions_oidc_test.go | 64 +++++++- internal/oidc/oidc_credential.go | 10 +- internal/oidc/oidc_credential_test.go | 53 ++++++- internal/oidc/oidc_registry.go | 21 +-- internal/oidc/oidc_registry_test.go | 48 +++--- proxy.go | 33 +++-- 37 files changed, 424 insertions(+), 236 deletions(-) diff --git a/internal/handlers/cargo_registry.go b/internal/handlers/cargo_registry.go index a05f952..7109e34 100644 --- a/internal/handlers/cargo_registry.go +++ b/internal/handlers/cargo_registry.go @@ -47,10 +47,10 @@ type cargoRepositoryCredentials struct { password string } -func NewCargoRegistryHandler(credentials config.Credentials, transport http.RoundTripper) *CargoRegistryHandler { +func NewCargoRegistryHandler(credentials config.Credentials, client *http.Client) *CargoRegistryHandler { handler := CargoRegistryHandler{ credentials: []cargoRepositoryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, credential := range credentials { @@ -68,7 +68,7 @@ func NewCargoRegistryHandler(credentials config.Credentials, transport http.Roun if oidcCred, _, _ := handler.oidcRegistry.Register(credential, []string{"url"}, "cargo registry"); oidcCred != nil { continue } - } else if oidcCred, _ := oidc.CreateOIDCCredential(credential, transport); oidcCred != nil { + } else if oidcCred, _ := oidc.CreateOIDCCredential(credential, client); oidcCred != nil { continue } diff --git a/internal/handlers/cargo_registry_test.go b/internal/handlers/cargo_registry_test.go index aa69863..69f8dec 100644 --- a/internal/handlers/cargo_registry_test.go +++ b/internal/handlers/cargo_registry_test.go @@ -46,7 +46,7 @@ func TestCargoRegistryHandler(t *testing.T) { }, } - handler := NewCargoRegistryHandler(credentials, nil) + handler := NewCargoRegistryHandler(credentials, testOIDCClient) // valid request, should authenticate url := validURL @@ -108,7 +108,7 @@ func TestCargoRegistryHandlerWithHost(t *testing.T) { }, } - handler := NewCargoRegistryHandler(credentials) + handler := NewCargoRegistryHandler(credentials, testOIDCClient) // matching host should authenticate req := httptest.NewRequestWithContext(t.Context(), "GET", "https://cargo.example.com/some/path", nil) @@ -139,7 +139,7 @@ func TestCargoRegistryHandlerWithUsernamePassword(t *testing.T) { }, } - handler := NewCargoRegistryHandler(credentials) + handler := NewCargoRegistryHandler(credentials, testOIDCClient) // matching url should authenticate with basic auth req := httptest.NewRequestWithContext(t.Context(), "GET", "https://cargo.example.com/registry/crate", nil) @@ -165,7 +165,7 @@ func TestCargoRegistryHandlerWithHostAndUsernamePassword(t *testing.T) { }, } - handler := NewCargoRegistryHandler(credentials) + handler := NewCargoRegistryHandler(credentials, testOIDCClient) // matching host should authenticate with basic auth req := httptest.NewRequestWithContext(t.Context(), "GET", "https://cargo.example.com/any/path", nil) @@ -191,7 +191,7 @@ func TestCargoRegistryHandlerTokenTakesPrecedenceOverPassword(t *testing.T) { }, } - handler := NewCargoRegistryHandler(credentials) + handler := NewCargoRegistryHandler(credentials, testOIDCClient) // token should take precedence over username/password req := httptest.NewRequestWithContext(t.Context(), "GET", "https://cargo.example.com/registry/crate", nil) @@ -207,7 +207,7 @@ func TestCargoRegistryHandlerIgnoresNoUrlOrHost(t *testing.T) { }, } - handler := NewCargoRegistryHandler(credentials) + handler := NewCargoRegistryHandler(credentials, testOIDCClient) // should not authenticate any request since no url or host was provided req := httptest.NewRequestWithContext(t.Context(), "GET", "https://anything.example.com/path", nil) @@ -227,7 +227,7 @@ func TestCargoRegistryHandlerUrlScopingNotBypassedByHost(t *testing.T) { }, } - handler := NewCargoRegistryHandler(credentials) + handler := NewCargoRegistryHandler(credentials, testOIDCClient) // in-scope path should authenticate req := httptest.NewRequestWithContext(t.Context(), "GET", "https://cargo.example.com/myorg/crate", nil) diff --git a/internal/handlers/composer.go b/internal/handlers/composer.go index 44dd344..8533908 100644 --- a/internal/handlers/composer.go +++ b/internal/handlers/composer.go @@ -26,10 +26,10 @@ type composerCredentials struct { } // NewComposerHandler returns a new ComposerHandler. -func NewComposerHandler(creds config.Credentials, transport http.RoundTripper) *ComposerHandler { +func NewComposerHandler(creds config.Credentials, client *http.Client) *ComposerHandler { handler := ComposerHandler{ credentials: []composerCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, cred := range creds { diff --git a/internal/handlers/composer_test.go b/internal/handlers/composer_test.go index 3a3e6b4..e0018ed 100644 --- a/internal/handlers/composer_test.go +++ b/internal/handlers/composer_test.go @@ -64,7 +64,7 @@ func TestComposerHandler(t *testing.T) { "token": "", }, } - handler := NewComposerHandler(credentials, nil) + handler := NewComposerHandler(credentials, testOIDCClient) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://phpreg.bigco.com/somepkg", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/docker_registry.go b/internal/handlers/docker_registry.go index c6466cc..4cf3197 100644 --- a/internal/handlers/docker_registry.go +++ b/internal/handlers/docker_registry.go @@ -29,7 +29,7 @@ type ecrClient interface { GetAuthorizationToken(ctx context.Context, input *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) } -type getECRClient func(ctx context.Context, region, keyID, secretKey string) (ecrClient, error) +type getECRClient func(ctx context.Context, region, keyID, secretKey string, client *http.Client) (ecrClient, error) // DockerRegistryHandler handles requests to Docker registries, adding auth. type DockerRegistryHandler struct { @@ -39,11 +39,12 @@ type DockerRegistryHandler struct { } // NewDockerRegistryHandler returns a new DockerRegistryHandler. -func NewDockerRegistryHandler(creds config.Credentials, transport http.RoundTripper, getECRClient getECRClient) *DockerRegistryHandler { +func NewDockerRegistryHandler(creds config.Credentials, client *http.Client, getECRClient getECRClient) *DockerRegistryHandler { + oidcRegistry := oidc.NewOIDCRegistry(client) handler := DockerRegistryHandler{ credentials: []*dockerRegistryCredentials{}, - transport: transport, - oidcRegistry: oidc.NewOIDCRegistry(transport), + transport: client.Transport, + oidcRegistry: oidcRegistry, } if getECRClient == nil { @@ -69,6 +70,7 @@ func NewDockerRegistryHandler(creds config.Credentials, transport http.RoundTrip registry: registry, username: cred.GetString("username"), password: cred.GetString("password"), + httpClient: client, getECRClient: getECRClient, } handler.credentials = append(handler.credentials, registryCred) @@ -147,11 +149,12 @@ func (h *DockerRegistryHandler) HandleRequest(req *http.Request, proxyCtx *gopro return req, nil } -func defaultGetECRClient(ctx context.Context, region, keyID, secretKey string) (ecrClient, error) { +func defaultGetECRClient(ctx context.Context, region, keyID, secretKey string, client *http.Client) (ecrClient, error) { cfg, err := awsconfig.LoadDefaultConfig( ctx, awsconfig.WithRegion(region), awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(keyID, secretKey, "")), + awsconfig.WithHTTPClient(client), ) if err != nil { return nil, err @@ -166,6 +169,7 @@ type dockerRegistryCredentials struct { password string ecrUsername string ecrPassword string + httpClient *http.Client getECRClient getECRClient } @@ -185,7 +189,7 @@ func (c *dockerRegistryCredentials) getECRCredentials(requestCtx context.Context } region := match[1] - ecrSvc, err := c.getECRClient(requestCtx, region, c.username, c.password) + ecrSvc, err := c.getECRClient(requestCtx, region, c.username, c.password, c.httpClient) if err != nil { logging.RequestLogf(proxyCtx, "! failed to initialize aws ecr client (key_id=%s)", c.username) return false diff --git a/internal/handlers/docker_registry_test.go b/internal/handlers/docker_registry_test.go index 7dd3036..6d691e7 100644 --- a/internal/handlers/docker_registry_test.go +++ b/internal/handlers/docker_registry_test.go @@ -3,8 +3,10 @@ package handlers import ( "context" "encoding/base64" + "io" "net/http" "net/http/httptest" + "strings" "testing" "github.com/aws/aws-sdk-go-v2/service/ecr" @@ -12,6 +14,7 @@ import ( "github.com/elazarl/goproxy" "github.com/stackrox/docker-registry-client/registry" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/dependabot/proxy/internal/config" ) @@ -60,15 +63,17 @@ func TestDockerRegistryHandler(t *testing.T) { }, } mockECR := &mockECRClient{user: ecrDockerUser, token: ecrDockerPassword} + httpClient := &http.Client{Transport: &http.Transport{}, Timeout: testOIDCClient.Timeout} var factoryContext context.Context - getECRClient := func(ctx context.Context, region, keyID, secretKey string) (ecrClient, error) { + getECRClient := func(ctx context.Context, region, keyID, secretKey string, client *http.Client) (ecrClient, error) { factoryContext = ctx + assert.Same(t, httpClient, client, "ECR uses the bounded handler client") assert.Equal(t, "us-east-2", region, "ecr region is parsed from the registry host") assert.Equal(t, ecrKeyID, keyID, "docker username is used as the aws access key id") assert.Equal(t, ecrSecretKey, secretKey, "docker password is used as the aws secret access key") return mockECR, nil } - handler := NewDockerRegistryHandler(credentials, &http.Transport{}, getECRClient) + handler := NewDockerRegistryHandler(credentials, httpClient, getECRClient) // Regular private registry req := httptest.NewRequestWithContext(t.Context(), "GET", "https://registry.hub.docker.com/my-repo", nil) @@ -164,6 +169,35 @@ func TestDockerRegistryHandler(t *testing.T) { assert.Equal(t, "https://nexus.someco.com", trans.URL, "correct URL is set") } +//nolint:gosec // The test credentials are intentionally fake fixtures. +func TestDefaultGetECRClientUsesInjectedHTTPClient(t *testing.T) { + transport := &recordingECRTransport{} + client := &http.Client{Transport: transport, Timeout: testOIDCClient.Timeout} + + ecrClient, err := defaultGetECRClient(t.Context(), "us-east-2", "access-key", "secret-key", client) + require.NoError(t, err) + _, err = ecrClient.GetAuthorizationToken(t.Context(), &ecr.GetAuthorizationTokenInput{}) + require.NoError(t, err) + + require.NotNil(t, transport.request) + assert.Equal(t, http.MethodPost, transport.request.Method) + assert.Equal(t, "api.ecr.us-east-2.amazonaws.com", transport.request.URL.Host) +} + +type recordingECRTransport struct { + request *http.Request +} + +func (t *recordingECRTransport) RoundTrip(req *http.Request) (*http.Response, error) { + t.request = req + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(`{"authorizationData":[]}`)), + Header: make(http.Header), + Request: req, + }, nil +} + type mockECRClient struct { user string token string diff --git a/internal/handlers/goproxy_server_handler.go b/internal/handlers/goproxy_server_handler.go index 6a4f9e4..e4889e1 100644 --- a/internal/handlers/goproxy_server_handler.go +++ b/internal/handlers/goproxy_server_handler.go @@ -24,10 +24,10 @@ type goProxyServerCredentials struct { } // NewGoProxyServerHandler returns a new GoProxyServerHandler. -func NewGoProxyServerHandler(creds config.Credentials, transport http.RoundTripper) *GoProxyServerHandler { +func NewGoProxyServerHandler(creds config.Credentials, client *http.Client) *GoProxyServerHandler { handler := GoProxyServerHandler{ credentials: []goProxyServerCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, cred := range creds { diff --git a/internal/handlers/goproxy_server_handler_test.go b/internal/handlers/goproxy_server_handler_test.go index 176ef77..c8e1f69 100644 --- a/internal/handlers/goproxy_server_handler_test.go +++ b/internal/handlers/goproxy_server_handler_test.go @@ -36,7 +36,7 @@ func TestGoProxyHandler(t *testing.T) { "password": deltaForcePassword, }, } - handler := NewGoProxyServerHandler(credentials, nil) + handler := NewGoProxyServerHandler(credentials, testOIDCClient) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://corp.dependabot.com/packages/somepkg", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/helm_registry.go b/internal/handlers/helm_registry.go index cfa1839..639ced0 100644 --- a/internal/handlers/helm_registry.go +++ b/internal/handlers/helm_registry.go @@ -24,10 +24,10 @@ type helmRegistryCredentials struct { } // NewHelmRegistryHandler returns a new HelmRegistryHandler. -func NewHelmRegistryHandler(creds config.Credentials, transport http.RoundTripper) *HelmRegistryHandler { +func NewHelmRegistryHandler(creds config.Credentials, client *http.Client) *HelmRegistryHandler { handler := HelmRegistryHandler{ credentials: []helmRegistryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, cred := range creds { diff --git a/internal/handlers/helm_registry_test.go b/internal/handlers/helm_registry_test.go index 65ff5e1..129a28f 100644 --- a/internal/handlers/helm_registry_test.go +++ b/internal/handlers/helm_registry_test.go @@ -31,7 +31,7 @@ func TestHelmRegistryHandler(t *testing.T) { "password": bigCoPassword, }, } - handler := NewHelmRegistryHandler(credentials, nil) + handler := NewHelmRegistryHandler(credentials, testOIDCClient) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://helmreg.bigco.com/some_chart", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/hex_repository.go b/internal/handlers/hex_repository.go index 3ff81ce..e7c2a9f 100644 --- a/internal/handlers/hex_repository.go +++ b/internal/handlers/hex_repository.go @@ -23,10 +23,10 @@ type hexRepositoryCredentials struct { authKey string } -func NewHexRepositoryHandler(creds config.Credentials, transport http.RoundTripper) *HexRepositoryHandler { +func NewHexRepositoryHandler(creds config.Credentials, client *http.Client) *HexRepositoryHandler { handler := HexRepositoryHandler{ credentials: []hexRepositoryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, cred := range creds { @@ -43,7 +43,7 @@ func NewHexRepositoryHandler(creds config.Credentials, transport http.RoundTripp if oidcCred, _, _ := handler.oidcRegistry.Register(cred, []string{"url"}, "hex repository"); oidcCred != nil { continue } - } else if oidcCred, _ := oidc.CreateOIDCCredential(cred, transport); oidcCred != nil { + } else if oidcCred, _ := oidc.CreateOIDCCredential(cred, client); oidcCred != nil { continue } diff --git a/internal/handlers/hex_repository_test.go b/internal/handlers/hex_repository_test.go index 06f2475..a31796a 100644 --- a/internal/handlers/hex_repository_test.go +++ b/internal/handlers/hex_repository_test.go @@ -29,7 +29,7 @@ func TestHexRepositoryHandler(t *testing.T) { validPath := "/repos/my_wonderful_repo/version" - handler := NewHexRepositoryHandler(credentials, nil) + handler := NewHexRepositoryHandler(credentials, testOIDCClient) // valid request, should authenticate url := validConfigUrl + validPath diff --git a/internal/handlers/maven_repository.go b/internal/handlers/maven_repository.go index 9f89261..f5dd9a9 100644 --- a/internal/handlers/maven_repository.go +++ b/internal/handlers/maven_repository.go @@ -25,10 +25,10 @@ type mavenRepositoryCredentials struct { } // NewMavenRepositoryHandler returns a new MavenRepositoryHandler. -func NewMavenRepositoryHandler(creds config.Credentials, transport http.RoundTripper) *MavenRepositoryHandler { +func NewMavenRepositoryHandler(creds config.Credentials, client *http.Client) *MavenRepositoryHandler { handler := MavenRepositoryHandler{ credentials: []mavenRepositoryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, cred := range creds { diff --git a/internal/handlers/maven_repository_test.go b/internal/handlers/maven_repository_test.go index b2d9273..2e4f97a 100644 --- a/internal/handlers/maven_repository_test.go +++ b/internal/handlers/maven_repository_test.go @@ -36,7 +36,7 @@ func TestMavenRepositoryHandler(t *testing.T) { "password": deltaForcePassword, }, } - handler := NewMavenRepositoryHandler(credentials, nil) + handler := NewMavenRepositoryHandler(credentials, testOIDCClient) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://corp.dependabot.com/packages/somepkg", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/npm_registry.go b/internal/handlers/npm_registry.go index 2a0af8a..2542e1f 100644 --- a/internal/handlers/npm_registry.go +++ b/internal/handlers/npm_registry.go @@ -28,10 +28,10 @@ type npmRegistryCredentials struct { } // NewNPMRegistryHandler returns a new NPMRegistryHandler, -func NewNPMRegistryHandler(creds config.Credentials, transport http.RoundTripper) *NPMRegistryHandler { +func NewNPMRegistryHandler(creds config.Credentials, client *http.Client) *NPMRegistryHandler { handler := NPMRegistryHandler{ credentials: []npmRegistryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, cred := range creds { diff --git a/internal/handlers/npm_registry_test.go b/internal/handlers/npm_registry_test.go index b6e406c..641d377 100644 --- a/internal/handlers/npm_registry_test.go +++ b/internal/handlers/npm_registry_test.go @@ -41,7 +41,7 @@ func TestNPMRegistryHandler(t *testing.T) { "token": privateRegToken, }, } - handler := NewNPMRegistryHandler(credentials, nil) + handler := NewNPMRegistryHandler(credentials, testOIDCClient) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://registry.npmjs.org/private-package", nil) req = handleRequestAndClose(handler, req, nil) @@ -104,7 +104,7 @@ func TestNPMRegistryHandler_SameHostDifferentPaths(t *testing.T) { "token": teamBToken, }, } - handler := NewNPMRegistryHandler(credentials) + handler := NewNPMRegistryHandler(credentials, testOIDCClient) // Request to team-a path should use team-a token req := httptest.NewRequestWithContext(t.Context(), "GET", "https://artifactory.example.com/api/npm/team-a-npm/@scope/pkg", nil) diff --git a/internal/handlers/nuget_feed.go b/internal/handlers/nuget_feed.go index a68e5af..32e1b95 100644 --- a/internal/handlers/nuget_feed.go +++ b/internal/handlers/nuget_feed.go @@ -62,12 +62,12 @@ type nugetDiscoveryAuth struct { } // NewNugetFeedHandler returns a new NugetFeedHandler. -func NewNugetFeedHandler(creds config.Credentials, transport http.RoundTripper) *NugetFeedHandler { +func NewNugetFeedHandler(creds config.Credentials, client *http.Client) *NugetFeedHandler { handler := NugetFeedHandler{ credentials: []nugetFeedCredentials{}, credentialURLs: make(map[string]struct{}), discoverySourceURLs: make(map[string]struct{}), - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, cred := range creds { diff --git a/internal/handlers/nuget_feed_test.go b/internal/handlers/nuget_feed_test.go index 8417b58..f479f42 100644 --- a/internal/handlers/nuget_feed_test.go +++ b/internal/handlers/nuget_feed_test.go @@ -114,7 +114,7 @@ func TestNugetFeedHandler(t *testing.T) { var buf bytes.Buffer testhelpers.CaptureStandardLog(t, &buf) - handler := NewNugetFeedHandler(credentials) + handler := newTestNugetFeedHandler(credentials) discoverNugetFeed(t, handler, "https://corp.dependabot.com/nuget/", http.StatusOK, mustMarshalJSON(t, rsp)) discoverNugetFeed(t, handler, "https://nuget.example.com/v2", http.StatusOK, xmlResponse) @@ -314,7 +314,7 @@ func TestExtraAuthenticatedURLsAreReportedInTheLog(t *testing.T) { var buf bytes.Buffer testhelpers.CaptureStandardLog(t, &buf) - handler := NewNugetFeedHandler(credentials) + handler := newTestNugetFeedHandler(credentials) discoverNugetFeed(t, handler, "https://nuget.example.com/index.json", http.StatusOK, jsonResponse) logContents := buf.String() @@ -327,7 +327,7 @@ func TestNewNugetFeedHandlerDoesNotMakeHTTPRequests(t *testing.T) { httpmock.Activate() defer httpmock.DeactivateAndReset() - NewNugetFeedHandler(config.Credentials{ + newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential("https://unreachable.example.com/index.json", "some-token"), }) @@ -335,7 +335,7 @@ func TestNewNugetFeedHandlerDoesNotMakeHTTPRequests(t *testing.T) { } func TestNugetFeedHandlerDiscoversFromPreparedResponse(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential("https://nuget.example.com/index.json", "some-token"), }) discoverNugetFeed(t, handler, "https://nuget.example.com/index.json", http.StatusOK, @@ -347,7 +347,7 @@ func TestNugetFeedHandlerDiscoversFromPreparedResponse(t *testing.T) { } func TestNugetFeedHandlerSkipsDiscoveryFromUnsuccessfulResponse(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential("https://nuget.example.com/index.json", "some-token"), }) discoverNugetFeed(t, handler, "https://nuget.example.com/index.json", http.StatusUnauthorized, @@ -359,7 +359,7 @@ func TestNugetFeedHandlerSkipsDiscoveryFromUnsuccessfulResponse(t *testing.T) { } func TestNugetFeedHandlerLeavesBodylessResponseUnchanged(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential("https://nuget.example.com/index.json", "some-token"), }) @@ -377,7 +377,7 @@ func TestNugetFeedHandlerLeavesBodylessResponseUnchanged(t *testing.T) { } func TestNugetFeedHandlerReplaysBodyAfterReadError(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential("https://nuget.example.com/index.json", "some-token"), }) proxyCtx := &goproxy.ProxyCtx{} @@ -397,7 +397,7 @@ func TestNugetFeedHandlerReplaysBodyAfterReadError(t *testing.T) { } func TestNugetFeedHandlerOnlyDiscoversFromConfiguredServiceIndex(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential("https://nuget.example.com/v3/", "some-token"), }) proxyCtx := &goproxy.ProxyCtx{} @@ -450,7 +450,7 @@ func TestNugetFeedHandlerRequiresConfiguredServiceIndexSchemeForDiscovery(t *tes for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential(testCase.configuredURL, "some-token"), }) proxyCtx := &goproxy.ProxyCtx{} @@ -480,7 +480,7 @@ func TestNugetFeedHandlerKeepsHTTPAndHTTPSDiscoverySourcesDistinct(t *testing.T) {httpCredential, httpsCredential}, {httpsCredential, httpCredential}, } { - handler := NewNugetFeedHandler(credentials) + handler := newTestNugetFeedHandler(credentials) discoverNugetFeed(t, handler, "http://nuget.example.com/v3/index.json", http.StatusOK, nugetV3Response("https://http-resource.example.com/packages")) discoverNugetFeed(t, handler, "https://nuget.example.com/v3/index.json", http.StatusOK, @@ -497,7 +497,7 @@ func TestNugetFeedHandlerKeepsHTTPAndHTTPSDiscoverySourcesDistinct(t *testing.T) } func TestNugetFeedHandlerConcurrentDiscoveryIsDeduplicated(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential("https://nuget.example.com/index.json", "some-token"), }) const workers = 50 @@ -546,7 +546,7 @@ func TestNugetFeedHandlerIgnoresUnusableStaticCredentials(t *testing.T) { {unusableCredential, usableCredential}, {usableCredential, unusableCredential}, } { - handler := NewNugetFeedHandler(credentials) + handler := newTestNugetFeedHandler(credentials) req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "https://nuget.example.com/v3/index.json", nil) req = handleRequestAndClose(handler, req, &goproxy.ProxyCtx{}) assertHasTokenAuth(t, req, "Bearer", "some-token", "usable duplicate credential") @@ -556,7 +556,7 @@ func TestNugetFeedHandlerIgnoresUnusableStaticCredentials(t *testing.T) { func TestNugetFeedHandlerLogsIgnoredDuplicateResourceURL(t *testing.T) { var buf bytes.Buffer testhelpers.CaptureStandardLog(t, &buf) - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential("https://first.example.com/index.json", "first-token"), testNugetFeedCredential("https://second.example.com/index.json", "second-token"), }) @@ -572,7 +572,7 @@ func TestNugetFeedHandlerLogsIgnoredDuplicateResourceURL(t *testing.T) { } func TestNugetFeedHandlerUnusableCredentialDoesNotBlockDiscoveredCredential(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ config.Credential{ "type": "nuget_feed", "url": "https://cdn.example.com/packages", @@ -588,7 +588,7 @@ func TestNugetFeedHandlerUnusableCredentialDoesNotBlockDiscoveredCredential(t *t } func TestNugetFeedHandlerPrefersMostSpecificURLCredential(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential("https://nuget.example.com/feed", "broad-token"), testNugetFeedCredential("https://nuget.example.com/feed/specific", "specific-token"), config.Credential{ @@ -605,7 +605,7 @@ func TestNugetFeedHandlerPrefersMostSpecificURLCredential(t *testing.T) { } func TestNugetFeedHandlerDiscoversThroughCrossOriginRedirectWithoutLeakingCredentials(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential("https://nuget.example.com/index.json", "some-token"), }) @@ -646,7 +646,7 @@ func TestNugetFeedHandlerDiscoversThroughCrossOriginRedirectWithoutLeakingCreden } func TestNugetFeedHandlerAuthenticatesSameOriginServiceIndexRedirect(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential("https://nuget.example.com/index.json", "some-token"), }) @@ -687,7 +687,7 @@ func TestNugetFeedHandlerResolvesRelativeRedirectAgainstRequestedServiceIndexURL for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - handler := NewNugetFeedHandler(config.Credentials{ + handler := newTestNugetFeedHandler(config.Credentials{ testNugetFeedCredential(testCase.configuredURL, "some-token"), }) @@ -777,3 +777,7 @@ func (r *readErrorThenData) Read(p []byte) (int, error) { } func (r *readErrorThenData) Close() error { return nil } + +func newTestNugetFeedHandler(credentials config.Credentials) *NugetFeedHandler { + return NewNugetFeedHandler(credentials, testOIDCClient) +} diff --git a/internal/handlers/oidc_handling_test.go b/internal/handlers/oidc_handling_test.go index a126f0e..0793a7e 100644 --- a/internal/handlers/oidc_handling_test.go +++ b/internal/handlers/oidc_handling_test.go @@ -43,7 +43,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Cargo", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewCargoRegistryHandler(creds, nil) + return NewCargoRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -67,7 +67,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Cargo", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewCargoRegistryHandler(creds, nil) + return NewCargoRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -88,7 +88,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Cargo", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewCargoRegistryHandler(creds, nil) + return NewCargoRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -108,7 +108,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Cargo", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewCargoRegistryHandler(creds, nil) + return NewCargoRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -130,7 +130,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Cargo", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewCargoRegistryHandler(creds, nil) + return NewCargoRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -153,7 +153,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Composer", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewComposerHandler(creds, nil) + return NewComposerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -177,7 +177,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Composer", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewComposerHandler(creds, nil) + return NewComposerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -198,7 +198,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Composer", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewComposerHandler(creds, nil) + return NewComposerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -219,7 +219,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Composer", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewComposerHandler(creds, nil) + return NewComposerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -241,7 +241,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Composer", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewComposerHandler(creds, nil) + return NewComposerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -265,7 +265,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Docker", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewDockerRegistryHandler(creds, nil, nil) + return NewDockerRegistryHandler(creds, testOIDCClient, nil) }, credentials: config.Credentials{ config.Credential{ @@ -289,7 +289,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Docker", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewDockerRegistryHandler(creds, nil, nil) + return NewDockerRegistryHandler(creds, testOIDCClient, nil) }, credentials: config.Credentials{ config.Credential{ @@ -310,7 +310,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Docker with URL", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewDockerRegistryHandler(creds, nil, nil) + return NewDockerRegistryHandler(creds, testOIDCClient, nil) }, credentials: config.Credentials{ config.Credential{ @@ -330,7 +330,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Docker", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewDockerRegistryHandler(creds, nil, nil) + return NewDockerRegistryHandler(creds, testOIDCClient, nil) }, credentials: config.Credentials{ config.Credential{ @@ -352,7 +352,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Docker", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewDockerRegistryHandler(creds, nil, nil) + return NewDockerRegistryHandler(creds, testOIDCClient, nil) }, credentials: config.Credentials{ config.Credential{ @@ -375,7 +375,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Go proxy", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewGoProxyServerHandler(creds, nil) + return NewGoProxyServerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -399,7 +399,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Go proxy with host", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewGoProxyServerHandler(creds, nil) + return NewGoProxyServerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -420,7 +420,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Go proxy", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewGoProxyServerHandler(creds, nil) + return NewGoProxyServerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -440,7 +440,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Go proxy", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewGoProxyServerHandler(creds, nil) + return NewGoProxyServerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -462,7 +462,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Go proxy", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewGoProxyServerHandler(creds, nil) + return NewGoProxyServerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -485,7 +485,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Helm registry", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHelmRegistryHandler(creds, nil) + return NewHelmRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -509,7 +509,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Helm registry", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHelmRegistryHandler(creds, nil) + return NewHelmRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -530,7 +530,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Helm registry with url", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHelmRegistryHandler(creds, nil) + return NewHelmRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -550,7 +550,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Helm registry", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHelmRegistryHandler(creds, nil) + return NewHelmRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -572,7 +572,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Helm registry", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHelmRegistryHandler(creds, nil) + return NewHelmRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -595,7 +595,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Hex", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHexRepositoryHandler(creds, nil) + return NewHexRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -619,7 +619,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Hex", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHexRepositoryHandler(creds, nil) + return NewHexRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -640,7 +640,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Hex", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHexRepositoryHandler(creds, nil) + return NewHexRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -660,7 +660,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Hex", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHexRepositoryHandler(creds, nil) + return NewHexRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -682,7 +682,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Hex", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewHexRepositoryHandler(creds, nil) + return NewHexRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -705,7 +705,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Maven", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewMavenRepositoryHandler(creds, nil) + return NewMavenRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -729,7 +729,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Maven", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewMavenRepositoryHandler(creds, nil) + return NewMavenRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -750,7 +750,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Maven", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewMavenRepositoryHandler(creds, nil) + return NewMavenRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -770,7 +770,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Maven", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewMavenRepositoryHandler(creds, nil) + return NewMavenRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -792,7 +792,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Maven", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewMavenRepositoryHandler(creds, nil) + return NewMavenRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -815,7 +815,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NPM", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNPMRegistryHandler(creds, nil) + return NewNPMRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -839,7 +839,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NPM", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNPMRegistryHandler(creds, nil) + return NewNPMRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -860,7 +860,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NPM", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNPMRegistryHandler(creds, nil) + return NewNPMRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -880,7 +880,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NPM", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNPMRegistryHandler(creds, nil) + return NewNPMRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -902,7 +902,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NPM", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNPMRegistryHandler(creds, nil) + return NewNPMRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -925,7 +925,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NuGet", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNugetFeedHandler(creds) + return NewNugetFeedHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -952,7 +952,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NuGet", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNugetFeedHandler(creds) + return NewNugetFeedHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -976,7 +976,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NuGet", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNugetFeedHandler(creds) + return NewNugetFeedHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -999,7 +999,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NuGet", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNugetFeedHandler(creds) + return NewNugetFeedHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1024,7 +1024,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "NuGet", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewNugetFeedHandler(creds) + return NewNugetFeedHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1050,7 +1050,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Pub", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPubRepositoryHandler(creds, nil) + return NewPubRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1074,7 +1074,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Pub", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPubRepositoryHandler(creds, nil) + return NewPubRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1095,7 +1095,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Pub", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPubRepositoryHandler(creds, nil) + return NewPubRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1115,7 +1115,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Pub", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPubRepositoryHandler(creds, nil) + return NewPubRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1137,7 +1137,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Pub", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPubRepositoryHandler(creds, nil) + return NewPubRepositoryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1160,7 +1160,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Python", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPythonIndexHandler(creds, nil) + return NewPythonIndexHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1184,7 +1184,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Python", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPythonIndexHandler(creds, nil) + return NewPythonIndexHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1205,7 +1205,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Python", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPythonIndexHandler(creds, nil) + return NewPythonIndexHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1225,7 +1225,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Python", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPythonIndexHandler(creds, nil) + return NewPythonIndexHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1247,7 +1247,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Python", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewPythonIndexHandler(creds, nil) + return NewPythonIndexHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1270,7 +1270,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "RubyGems", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewRubyGemsServerHandler(creds, nil) + return NewRubyGemsServerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1294,7 +1294,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "RubyGems", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewRubyGemsServerHandler(creds, nil) + return NewRubyGemsServerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1315,7 +1315,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "RubyGems", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewRubyGemsServerHandler(creds, nil) + return NewRubyGemsServerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1336,7 +1336,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "RubyGems", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewRubyGemsServerHandler(creds, nil) + return NewRubyGemsServerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1359,7 +1359,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "RubyGems", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewRubyGemsServerHandler(creds, nil) + return NewRubyGemsServerHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1383,7 +1383,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Terraform", provider: "aws", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewTerraformRegistryHandler(creds, nil) + return NewTerraformRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1407,7 +1407,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Terraform with host", provider: "azure", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewTerraformRegistryHandler(creds, nil) + return NewTerraformRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1428,7 +1428,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Terraform", provider: "jfrog", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewTerraformRegistryHandler(creds, nil) + return NewTerraformRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1448,7 +1448,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Terraform", provider: "cloudsmith", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewTerraformRegistryHandler(creds, nil) + return NewTerraformRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1470,7 +1470,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) { name: "Terraform", provider: "gcp", handlerFactory: func(creds config.Credentials) oidcHandler { - return NewTerraformRegistryHandler(creds, nil) + return NewTerraformRegistryHandler(creds, testOIDCClient) }, credentials: config.Credentials{ config.Credential{ @@ -1636,7 +1636,7 @@ func TestPythonOIDCSimpleSuffixStripping(t *testing.T) { }, } - handler := NewPythonIndexHandler(creds, nil) + handler := NewPythonIndexHandler(creds, testOIDCClient) // /+simple/ should be stripped → registered as /org/feed-A/ reqA := httptest.NewRequestWithContext(t.Context(), "GET", "https://pkgs.example.com/org/feed-A/pkg/a", nil) @@ -1672,7 +1672,7 @@ func TestPythonOIDCAuthenticatesDiscoveredDownloadPrefix(t *testing.T) { "tenant-id": tenantID, "client-id": clientID, }, - }) + }, testOIDCClient) proxyCtx := &goproxy.ProxyCtx{} indexReq := httptest.NewRequestWithContext(t.Context(), @@ -1746,7 +1746,7 @@ func TestNPMOIDCSameHostDifferentPaths(t *testing.T) { }, } - handler := NewNPMRegistryHandler(creds, nil) + handler := NewNPMRegistryHandler(creds, testOIDCClient) // Request to feed-A path should get token A reqA := httptest.NewRequestWithContext(t.Context(), "GET", "https://pkgs.example.com/org/feed-A/some-package", nil) @@ -1799,7 +1799,7 @@ func TestTerraformOIDCSameHostDifferentPaths(t *testing.T) { }, } - handler := NewTerraformRegistryHandler(creds, nil) + handler := NewTerraformRegistryHandler(creds, testOIDCClient) // Request to feed-A path should get token A reqA := httptest.NewRequestWithContext(t.Context(), "GET", "https://terraform.example.com/org/feed-A/v1/providers/org/name", nil) diff --git a/internal/handlers/opentofu_registry.go b/internal/handlers/opentofu_registry.go index f77d2c7..2a0d54b 100644 --- a/internal/handlers/opentofu_registry.go +++ b/internal/handlers/opentofu_registry.go @@ -23,10 +23,10 @@ type openTofuRegistryCredentials struct { token string } -func NewOpenTofuRegistryHandler(credentials config.Credentials, transport http.RoundTripper) *OpenTofuRegistryHandler { +func NewOpenTofuRegistryHandler(credentials config.Credentials, client *http.Client) *OpenTofuRegistryHandler { handler := OpenTofuRegistryHandler{ credentials: []openTofuRegistryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, credential := range credentials { diff --git a/internal/handlers/opentofu_registry_test.go b/internal/handlers/opentofu_registry_test.go index cab263b..bb8acee 100644 --- a/internal/handlers/opentofu_registry_test.go +++ b/internal/handlers/opentofu_registry_test.go @@ -65,7 +65,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { } for _, tt := range tests { t.Run(strings.Join([]string{tt.registryType, tt.host, tt.token}, " "), func(t *testing.T) { - handler := NewOpenTofuRegistryHandler(tt.credentials, nil) + handler := NewOpenTofuRegistryHandler(tt.credentials, testOIDCClient) request := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", tt.url, nil), nil) @@ -74,7 +74,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { } t.Run("HandleRequest without credentials", func(t *testing.T) { - handler := NewOpenTofuRegistryHandler(config.Credentials{}, nil) + handler := NewOpenTofuRegistryHandler(config.Credentials{}, testOIDCClient) url := "https://registry.opentofu.org/v1/providers/org/name/versions" request := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", url, nil), nil) @@ -87,7 +87,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { config.Credential{"type": "opentofu_registry", "url": "https://registry.example.com/org1", "token": "token-org1"}, config.Credential{"type": "opentofu_registry", "url": "https://registry.example.com/org2", "token": "token-org2"}, } - handler := NewOpenTofuRegistryHandler(credentials, nil) + handler := NewOpenTofuRegistryHandler(credentials, testOIDCClient) // Request to org1 path should use org1 token req1 := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", "https://registry.example.com/org1/v1/providers/foo", nil), nil) @@ -106,7 +106,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { credentials := config.Credentials{ config.Credential{"type": "opentofu_registry", "host": "registry.example.org", "token": ""}, } - handler := NewOpenTofuRegistryHandler(credentials, nil) + handler := NewOpenTofuRegistryHandler(credentials, testOIDCClient) assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty token") }) @@ -114,7 +114,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { credentials := config.Credentials{ config.Credential{"type": "opentofu_registry", "token": "some-token"}, } - handler := NewOpenTofuRegistryHandler(credentials, nil) + handler := NewOpenTofuRegistryHandler(credentials, testOIDCClient) assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty host and url") }) @@ -124,7 +124,7 @@ func TestOpenTofuRegistryHandler(t *testing.T) { config.Credential{"type": "opentofu_registry", "url": "https://registry.example.com/org", "token": "token-org"}, config.Credential{"type": "opentofu_registry", "url": "https://registry.example.com/org1", "token": "token-org1"}, } - handler := NewOpenTofuRegistryHandler(credentials, nil) + handler := NewOpenTofuRegistryHandler(credentials, testOIDCClient) assert.Equal(t, "https://registry.example.com/org1", handler.credentials[0].url, "longer path should be first") assert.Equal(t, "https://registry.example.com/org", handler.credentials[1].url, "shorter path should be second") diff --git a/internal/handlers/pub_repository.go b/internal/handlers/pub_repository.go index d66359f..3086174 100644 --- a/internal/handlers/pub_repository.go +++ b/internal/handlers/pub_repository.go @@ -25,10 +25,10 @@ type pubRepositoryCredentials struct { token string } -func NewPubRepositoryHandler(credentials config.Credentials, transport http.RoundTripper) *PubRepositoryHandler { +func NewPubRepositoryHandler(credentials config.Credentials, client *http.Client) *PubRepositoryHandler { handler := PubRepositoryHandler{ credentials: []pubRepositoryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, credential := range credentials { @@ -45,7 +45,7 @@ func NewPubRepositoryHandler(credentials config.Credentials, transport http.Roun if oidcCred, _, _ := handler.oidcRegistry.Register(credential, []string{"url"}, "pub repository"); oidcCred != nil { continue } - } else if oidcCred, _ := oidc.CreateOIDCCredential(credential, transport); oidcCred != nil { + } else if oidcCred, _ := oidc.CreateOIDCCredential(credential, client); oidcCred != nil { continue } diff --git a/internal/handlers/pub_repository_test.go b/internal/handlers/pub_repository_test.go index fc923a0..02aff70 100644 --- a/internal/handlers/pub_repository_test.go +++ b/internal/handlers/pub_repository_test.go @@ -46,7 +46,7 @@ func TestPubRepositoryHandler(t *testing.T) { }, } - handler := NewPubRepositoryHandler(credentials, nil) + handler := NewPubRepositoryHandler(credentials, testOIDCClient) // valid request, should authenticate url := validURL diff --git a/internal/handlers/python_index.go b/internal/handlers/python_index.go index 02ea992..cb36ab7 100644 --- a/internal/handlers/python_index.go +++ b/internal/handlers/python_index.go @@ -29,11 +29,11 @@ type pythonIndexCredentials struct { } // NewPythonIndexHandler returns a new PythonIndexHandler. -func NewPythonIndexHandler(creds config.Credentials, transport http.RoundTripper) *PythonIndexHandler { +func NewPythonIndexHandler(creds config.Credentials, client *http.Client) *PythonIndexHandler { handler := PythonIndexHandler{ credentials: []pythonIndexCredentials{}, downloadAuth: newPythonIndexDownloadAuthStore(), - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, cred := range creds { @@ -43,7 +43,7 @@ func NewPythonIndexHandler(creds config.Credentials, transport http.RoundTripper indexURL := cred.GetString("index-url") - oidcCredential, _ := oidc.CreateOIDCCredential(cred, transport) + oidcCredential, _ := oidc.CreateOIDCCredential(cred, client) if oidcCredential != nil { // Normalize the registration URL by stripping the /simple or /+simple // suffix, matching how static credentials are matched at request time. diff --git a/internal/handlers/python_index_test.go b/internal/handlers/python_index_test.go index 8ef8e41..c6c5ff2 100644 --- a/internal/handlers/python_index_test.go +++ b/internal/handlers/python_index_test.go @@ -55,7 +55,7 @@ func TestPythonIndexHandler(t *testing.T) { "token": fmt.Sprintf("%s:%s", deltaForceUser, deltaForcePassword), }, } - handler := NewPythonIndexHandler(credentials, nil) + handler := newTestPythonIndexHandler(credentials) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://corp.dependabot.com/pyreg", nil) req = handleRequestAndClose(handler, req, nil) @@ -115,7 +115,7 @@ func TestPythonIndexHandler(t *testing.T) { } func TestPythonIndexHandlerAuthenticatesDiscoveredDownloadPrefixFromHTML(t *testing.T) { - handler := NewPythonIndexHandler(config.Credentials{ + handler := newTestPythonIndexHandler(config.Credentials{ config.Credential{ "type": "python_index", "index-url": "https://pkgs.example.com/my-org/my-project/_packaging/my-feed/pypi/simple/", @@ -177,7 +177,7 @@ func TestPythonIndexHandlerAuthenticatesDiscoveredDownloadPrefixFromHTML(t *test } func TestPythonIndexHandlerAuthenticatesDiscoveredDownloadPrefixFromJSON(t *testing.T) { - handler := NewPythonIndexHandler(config.Credentials{ + handler := newTestPythonIndexHandler(config.Credentials{ config.Credential{ "type": "python_index", "index-url": "https://pkgs.example.com/my-org/my-project/_packaging/my-feed/pypi/simple/", @@ -255,7 +255,7 @@ func TestPythonDownloadPrefixFromSimpleLinkRejectsUnscopedLinks(t *testing.T) { } func TestPythonIndexHandlerSkipsDiscoveryForAuthenticatedNonSimpleResponse(t *testing.T) { - handler := NewPythonIndexHandler(config.Credentials{ + handler := newTestPythonIndexHandler(config.Credentials{ config.Credential{ "type": "python_index", "index-url": "https://pkgs.example.com/org/project/", @@ -292,7 +292,7 @@ func TestPythonIndexHandlerSkipsDiscoveryForAuthenticatedNonSimpleResponse(t *te } func TestPythonIndexHandlerPreservesDiscoveredDownloadPrefixPort(t *testing.T) { - handler := NewPythonIndexHandler(config.Credentials{ + handler := newTestPythonIndexHandler(config.Credentials{ config.Credential{ "type": "python_index", "index-url": "https://pkgs.example.com:8443/my-org/my-project/_packaging/my-feed/pypi/simple/", @@ -341,7 +341,7 @@ func TestPythonIndexHandlerPreservesDiscoveredDownloadPrefixPort(t *testing.T) { } func TestPythonIndexHandlerPreservesDiscoveredDownloadPrefixIPv6Host(t *testing.T) { - handler := NewPythonIndexHandler(config.Credentials{ + handler := newTestPythonIndexHandler(config.Credentials{ config.Credential{ "type": "python_index", "index-url": "https://[2001:db8::1]/my-org/my-project/_packaging/my-feed/pypi/simple/", @@ -424,7 +424,7 @@ func TestPythonIndexDownloadAuthStoreEvictsOldestEntryAtLimit(t *testing.T) { } func TestPythonIndexHandlerSkipsDiscoveryForLargeSimpleResponse(t *testing.T) { - handler := NewPythonIndexHandler(config.Credentials{ + handler := newTestPythonIndexHandler(config.Credentials{ config.Credential{ "type": "python_index", "index-url": "https://pkgs.example.com/my-org/my-project/_packaging/my-feed/pypi/simple/", @@ -461,3 +461,7 @@ func TestPythonIndexHandlerSkipsDiscoveryForLargeSimpleResponse(t *testing.T) { downloadReq = handleRequestAndClose(handler, downloadReq, &goproxy.ProxyCtx{}) assertUnauthenticated(t, downloadReq, "large Simple API response should not be used for discovery") } + +func newTestPythonIndexHandler(credentials config.Credentials) *PythonIndexHandler { + return NewPythonIndexHandler(credentials, testOIDCClient) +} diff --git a/internal/handlers/rubygems_server.go b/internal/handlers/rubygems_server.go index 8f9445f..37ddf64 100644 --- a/internal/handlers/rubygems_server.go +++ b/internal/handlers/rubygems_server.go @@ -25,10 +25,10 @@ type rubyGemsServerCredentials struct { } // NewRubyGemsServerHandler returns a new RubyGemsServerHandler. -func NewRubyGemsServerHandler(creds config.Credentials, transport http.RoundTripper) *RubyGemsServerHandler { +func NewRubyGemsServerHandler(creds config.Credentials, client *http.Client) *RubyGemsServerHandler { handler := RubyGemsServerHandler{ credentials: []rubyGemsServerCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, cred := range creds { diff --git a/internal/handlers/rubygems_server_test.go b/internal/handlers/rubygems_server_test.go index 94c1bf9..acb8ca7 100644 --- a/internal/handlers/rubygems_server_test.go +++ b/internal/handlers/rubygems_server_test.go @@ -37,7 +37,7 @@ func TestRubyGemsServerHandler(t *testing.T) { "token": fmt.Sprintf("%s:%s", pathUser, pathPassword), }, } - handler := NewRubyGemsServerHandler(credentials, nil) + handler := NewRubyGemsServerHandler(credentials, testOIDCClient) req := httptest.NewRequestWithContext(t.Context(), "GET", "https://corp.dependabot.com/gems", nil) req = handleRequestAndClose(handler, req, nil) diff --git a/internal/handlers/terraform_registry.go b/internal/handlers/terraform_registry.go index 922c421..5e2d7f8 100644 --- a/internal/handlers/terraform_registry.go +++ b/internal/handlers/terraform_registry.go @@ -24,10 +24,10 @@ type terraformRegistryCredentials struct { token string } -func NewTerraformRegistryHandler(credentials config.Credentials, transport http.RoundTripper) *TerraformRegistryHandler { +func NewTerraformRegistryHandler(credentials config.Credentials, client *http.Client) *TerraformRegistryHandler { handler := TerraformRegistryHandler{ credentials: []terraformRegistryCredentials{}, - oidcRegistry: oidc.NewOIDCRegistry(transport), + oidcRegistry: oidc.NewOIDCRegistry(client), } for _, credential := range credentials { diff --git a/internal/handlers/terraform_registry_test.go b/internal/handlers/terraform_registry_test.go index c8f0a9a..2f835ef 100644 --- a/internal/handlers/terraform_registry_test.go +++ b/internal/handlers/terraform_registry_test.go @@ -58,7 +58,7 @@ func TestTerraformRegistryHandler(t *testing.T) { } for _, tt := range tests { t.Run(strings.Join([]string{tt.registryType, tt.host, tt.token}, " "), func(t *testing.T) { - handler := NewTerraformRegistryHandler(tt.credentials, nil) + handler := NewTerraformRegistryHandler(tt.credentials, testOIDCClient) request := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", tt.url, nil), nil) @@ -67,7 +67,7 @@ func TestTerraformRegistryHandler(t *testing.T) { } t.Run("HandleRequest without credentials", func(t *testing.T) { - handler := NewTerraformRegistryHandler(config.Credentials{}, nil) + handler := NewTerraformRegistryHandler(config.Credentials{}, testOIDCClient) url := "https://registry.terraform.io/v1/providers/org/name/versions" request := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", url, nil), nil) @@ -80,7 +80,7 @@ func TestTerraformRegistryHandler(t *testing.T) { config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org1", "token": "token-org1"}, config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org2", "token": "token-org2"}, } - handler := NewTerraformRegistryHandler(credentials, nil) + handler := NewTerraformRegistryHandler(credentials, testOIDCClient) // Request to org1 path should use org1 token req1 := handleRequestAndClose(handler, httptest.NewRequestWithContext(t.Context(), "GET", "https://terraform.example.com/org1/v1/providers/foo", nil), nil) @@ -99,7 +99,7 @@ func TestTerraformRegistryHandler(t *testing.T) { credentials := config.Credentials{ config.Credential{"type": "terraform_registry", "host": "terraform.example.org", "token": ""}, } - handler := NewTerraformRegistryHandler(credentials, nil) + handler := NewTerraformRegistryHandler(credentials, testOIDCClient) assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty token") }) @@ -107,7 +107,7 @@ func TestTerraformRegistryHandler(t *testing.T) { credentials := config.Credentials{ config.Credential{"type": "terraform_registry", "token": "some-token"}, } - handler := NewTerraformRegistryHandler(credentials, nil) + handler := NewTerraformRegistryHandler(credentials, testOIDCClient) assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty host and url") }) @@ -117,7 +117,7 @@ func TestTerraformRegistryHandler(t *testing.T) { config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org", "token": "token-org"}, config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org1", "token": "token-org1"}, } - handler := NewTerraformRegistryHandler(credentials, nil) + handler := NewTerraformRegistryHandler(credentials, testOIDCClient) assert.Equal(t, "https://terraform.example.com/org1", handler.credentials[0].url, "longer path should be first") assert.Equal(t, "https://terraform.example.com/org", handler.credentials[1].url, "shorter path should be second") diff --git a/internal/handlers/test_helpers.go b/internal/handlers/test_helpers.go index 3541323..82cde71 100644 --- a/internal/handlers/test_helpers.go +++ b/internal/handlers/test_helpers.go @@ -6,6 +6,7 @@ import ( "net/http" "strings" "testing" + "time" "github.com/elazarl/goproxy" "github.com/stretchr/testify/assert" @@ -13,6 +14,17 @@ import ( "github.com/dependabot/proxy/internal/config" ) +var testOIDCClient = &http.Client{ + Transport: currentDefaultTransport{}, + Timeout: 10 * time.Second, +} + +type currentDefaultTransport struct{} + +func (currentDefaultTransport) RoundTrip(req *http.Request) (*http.Response, error) { + return http.DefaultTransport.RoundTrip(req) +} + // handleRequestAndClose calls handler.HandleRequest and closes any response body. // Most handlers return nil responses, but the linter can't prove that. func handleRequestAndClose(handler interface { diff --git a/internal/oidc/actions_oidc.go b/internal/oidc/actions_oidc.go index 596f59a..f37a957 100644 --- a/internal/oidc/actions_oidc.go +++ b/internal/oidc/actions_oidc.go @@ -55,8 +55,11 @@ func GetRequestToken() string { return os.Getenv(envActionsIDTokenRequestToken) } -// GetToken retrieves a GitHub Actions OIDC token with an optional audience -func GetToken(ctx context.Context, audience string) (_ string, err error) { +// GetToken retrieves a GitHub Actions OIDC token with an optional audience. +func GetToken(ctx context.Context, audience string, client *http.Client) (_ string, err error) { + if err := validateHTTPClient(client); err != nil { + return "", err + } if !IsOIDCConfigured() { return "", fmt.Errorf("GitHub Actions OIDC is not available: missing %s or %s environment variables", envActionsIDTokenRequestURL, envActionsIDTokenRequestToken) @@ -76,10 +79,6 @@ func GetToken(ctx context.Context, audience string) (_ string, err error) { parsedURL.RawQuery = query.Encode() } - client := &http.Client{ - Timeout: 10 * time.Second, - } - req, err := http.NewRequestWithContext(ctx, "GET", parsedURL.String(), nil) if err != nil { return "", fmt.Errorf("failed to create OIDC request: %w", err) @@ -118,8 +117,21 @@ func GetToken(ctx context.Context, audience string) (_ string, err error) { // GetTokenForAzureADExchange retrieves a GitHub Actions OIDC token specifically // configured for Azure AD token exchange -func GetTokenForAzureADExchange(ctx context.Context) (string, error) { - return GetToken(ctx, "api://AzureADTokenExchange") +func GetTokenForAzureADExchange(ctx context.Context, client *http.Client) (string, error) { + return GetToken(ctx, "api://AzureADTokenExchange", client) +} + +func validateHTTPClient(client *http.Client) error { + if client == nil { + return fmt.Errorf("OIDC HTTP client is required") + } + if client.Transport == nil { + return fmt.Errorf("OIDC HTTP client transport is required") + } + if client.Timeout <= 0 { + return fmt.Errorf("OIDC HTTP client timeout must be positive") + } + return nil } // azureTokenResponse represents the response from Azure AD OAuth2 token endpoint @@ -229,6 +241,9 @@ type OIDCAccessToken struct { // // Returns an Azure AD access token scoped for Azure DevOps (499b84ac-1321-427f-aa17-267ca6975798/.default) func GetAzureAccessToken(ctx context.Context, params AzureOIDCParameters, githubToken string, client *http.Client) (_ *OIDCAccessToken, err error) { + if err := validateHTTPClient(client); err != nil { + return nil, err + } if params.TenantID == "" { return nil, fmt.Errorf("tenant ID is required") } @@ -298,7 +313,7 @@ func GetAzureAccessTokenForDevOps(ctx context.Context, params AzureOIDCParameter } // Get GitHub OIDC token - githubToken, err := GetTokenForAzureADExchange(ctx) + githubToken, err := GetTokenForAzureADExchange(ctx, client) if err != nil { return nil, fmt.Errorf("failed to get GitHub OIDC token: %w", err) } @@ -321,6 +336,9 @@ func GetAzureAccessTokenForDevOps(ctx context.Context, params AzureOIDCParameter // // Returns a JFrog access token func GetJFrogAccessToken(ctx context.Context, params JFrogOIDCParameters, githubToken string, client *http.Client) (_ *OIDCAccessToken, err error) { + if err := validateHTTPClient(client); err != nil { + return nil, err + } if params.JFrogURL == "" { return nil, fmt.Errorf("token URL base is required") } @@ -396,7 +414,7 @@ func GetJFrogAccessTokenForDevOps(ctx context.Context, params JFrogOIDCParameter } // Get GitHub OIDC token - githubToken, err := GetToken(ctx, params.Audience) + githubToken, err := GetToken(ctx, params.Audience, client) if err != nil { return nil, fmt.Errorf("failed to get GitHub OIDC token: %w", err) } @@ -419,6 +437,9 @@ func GetJFrogAccessTokenForDevOps(ctx context.Context, params JFrogOIDCParameter // // Returns temporary AWS credentials func GetAWSAccessToken(ctx context.Context, params AWSOIDCParameters, githubToken string, client *http.Client) (_ *OIDCAccessToken, err error) { + if err := validateHTTPClient(client); err != nil { + return nil, err + } if params.Region == "" { return nil, fmt.Errorf("AWS region is required") } @@ -568,7 +589,7 @@ func GetAWSAccessTokenForDevOps(ctx context.Context, params AWSOIDCParameters, c } // Get GitHub OIDC token - githubToken, err := GetToken(ctx, params.Audience) + githubToken, err := GetToken(ctx, params.Audience, client) if err != nil { return nil, fmt.Errorf("failed to get GitHub OIDC token: %w", err) } @@ -583,6 +604,9 @@ func GetAWSAccessTokenForDevOps(ctx context.Context, params AWSOIDCParameters, c } func GetCloudsmithAccessToken(ctx context.Context, params CloudsmithOIDCParameters, githubToken string, client *http.Client) (_ *OIDCAccessToken, err error) { + if err := validateHTTPClient(client); err != nil { + return nil, err + } if params.ServiceSlug == "" { return nil, fmt.Errorf("service slug is required") } @@ -656,7 +680,7 @@ func GetCloudsmithAccessTokenForDevOps(ctx context.Context, params CloudsmithOID } // Get GitHub OIDC token - githubToken, err := GetToken(ctx, params.Audience) + githubToken, err := GetToken(ctx, params.Audience, client) if err != nil { return nil, fmt.Errorf("failed to get GitHub OIDC token: %w", err) } @@ -670,6 +694,9 @@ func GetCloudsmithAccessTokenForDevOps(ctx context.Context, params CloudsmithOID } func GetGCPAccessToken(ctx context.Context, params GCPOIDCParameters, githubToken string, client *http.Client) (_ *OIDCAccessToken, err error) { + if err := validateHTTPClient(client); err != nil { + return nil, err + } if params.WorkloadIdentityProvider == "" { return nil, fmt.Errorf("workload-identity-provider is required") } @@ -807,7 +834,7 @@ func GetGCPAccessTokenForDevOps(ctx context.Context, params GCPOIDCParameters, c } // Get GitHub OIDC token - githubToken, err := GetToken(ctx, params.Audience) + githubToken, err := GetToken(ctx, params.Audience, client) if err != nil { return nil, fmt.Errorf("failed to get GitHub OIDC token: %w", err) } diff --git a/internal/oidc/actions_oidc_test.go b/internal/oidc/actions_oidc_test.go index 2c2fd7c..77d7a82 100644 --- a/internal/oidc/actions_oidc_test.go +++ b/internal/oidc/actions_oidc_test.go @@ -6,17 +6,33 @@ import ( "encoding/xml" "fmt" "io" + "net" "net/http" "net/http/httptest" "os" "strings" "testing" + "time" "github.com/jarcoal/httpmock" + "github.com/rs/dnscache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/dependabot/proxy/internal/dialer" ) +var testHTTPClient = &http.Client{ + Transport: currentDefaultTransport{}, + Timeout: 10 * time.Second, +} + +type currentDefaultTransport struct{} + +func (currentDefaultTransport) RoundTrip(req *http.Request) (*http.Response, error) { + return http.DefaultTransport.RoundTrip(req) +} + func unsetEnv(t *testing.T, key string) { t.Helper() @@ -193,7 +209,7 @@ func TestGetToken(t *testing.T) { ctx := context.Background() - token, err := GetToken(ctx, tt.audience) + token, err := GetToken(ctx, tt.audience, testHTTPClient) if tt.expectError { require.Error(t, err) @@ -205,6 +221,38 @@ func TestGetToken(t *testing.T) { } } +func TestGetTokenRejectsBlockedDestination(t *testing.T) { + const metadataIP = "169.254.169.254" + t.Setenv(envActionsIDTokenRequestURL, "http://"+metadataIP+"/token") + t.Setenv(envActionsIDTokenRequestToken, "request-token") + + resolver := &dnscache.Resolver{} + safeDialer := dialer.New(resolver, []net.IP{net.ParseIP(metadataIP)}) + client := &http.Client{ + Transport: &http.Transport{DialContext: safeDialer.DialContext}, + Timeout: testHTTPClient.Timeout, + } + + _, err := GetToken(t.Context(), "audience", client) + require.Error(t, err) + assert.ErrorIs(t, err, dialer.ErrForbiddenRequest) +} + +func TestGetTokenRequiresExplicitTransport(t *testing.T) { + for name, client := range map[string]*http.Client{ + "nil client": nil, + "nil transport": {Timeout: testHTTPClient.Timeout}, + "zero timeout": {Transport: http.DefaultTransport}, + } { + t.Run(name, func(t *testing.T) { + assert.NotPanics(t, func() { + _, err := GetToken(t.Context(), "", client) + assert.Error(t, err) + }) + }) + } +} + func TestGetTokenForAzureADExchange(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Verify the audience parameter is set correctly @@ -221,7 +269,7 @@ func TestGetTokenForAzureADExchange(t *testing.T) { ctx := context.Background() - token, err := GetTokenForAzureADExchange(ctx) + token, err := GetTokenForAzureADExchange(ctx, testHTTPClient) require.NoError(t, err) assert.Equal(t, "azure-exchange-token", token) @@ -309,7 +357,7 @@ func TestGetToken_URLParsing(t *testing.T) { t.Setenv(envActionsIDTokenRequestToken, "test-token") ctx := context.Background() - _, err := GetToken(ctx, tt.audience) + _, err := GetToken(ctx, tt.audience, testHTTPClient) if tt.expectError { require.Error(t, err) @@ -458,7 +506,7 @@ func TestGetAzureAccessToken(t *testing.T) { TenantID: tt.tenantID, ClientID: tt.clientID, } - azureToken, err = GetAzureAccessToken(ctx, params, tt.githubToken, http.DefaultClient) + azureToken, err = GetAzureAccessToken(ctx, params, tt.githubToken, testHTTPClient) if tt.expectError { require.Error(t, err) @@ -638,7 +686,7 @@ func TestGetJFrogAccessToken(t *testing.T) { Audience: tt.audience, IdentityMappingName: tt.identityMappingName, } - jfrogToken, err = GetJFrogAccessToken(ctx, params, tt.githubToken, http.DefaultClient) + jfrogToken, err = GetJFrogAccessToken(ctx, params, tt.githubToken, testHTTPClient) if tt.expectError { require.Error(t, err) @@ -828,7 +876,7 @@ func TestGetAWSAccessToken(t *testing.T) { Domain: tt.domain, DomainOwner: tt.domainOwner, } - awsToken, err = GetAWSAccessToken(ctx, params, tt.githubToken, http.DefaultClient) + awsToken, err = GetAWSAccessToken(ctx, params, tt.githubToken, testHTTPClient) if tt.expectError { require.Error(t, err) @@ -1010,7 +1058,7 @@ func TestGetCloudsmithAccessToken(t *testing.T) { })) } - cloudsmithToken, err = GetCloudsmithAccessToken(ctx, tt.params, tt.githubToken, http.DefaultClient) + cloudsmithToken, err = GetCloudsmithAccessToken(ctx, tt.params, tt.githubToken, testHTTPClient) if tt.expectError { require.Error(t, err) @@ -1333,7 +1381,7 @@ func TestGetGCPAccessToken(t *testing.T) { })) } - gcpToken, err := GetGCPAccessToken(ctx, tt.params, tt.githubToken, http.DefaultClient) + gcpToken, err := GetGCPAccessToken(ctx, tt.params, tt.githubToken, testHTTPClient) if tt.expectError { require.Error(t, err) diff --git a/internal/oidc/oidc_credential.go b/internal/oidc/oidc_credential.go index 67f49ab..b51cb2d 100644 --- a/internal/oidc/oidc_credential.go +++ b/internal/oidc/oidc_credential.go @@ -82,7 +82,10 @@ func (c *OIDCCredential) Provider() string { return c.parameters.Name() } -func CreateOIDCCredential(cred config.Credential, transport http.RoundTripper) (*OIDCCredential, error) { +func CreateOIDCCredential(cred config.Credential, client *http.Client) (*OIDCCredential, error) { + if err := validateHTTPClient(client); err != nil { + return nil, err + } if !IsOIDCConfigured() { return nil, fmt.Errorf("OIDC is not configured") } @@ -175,10 +178,7 @@ func CreateOIDCCredential(cred config.Credential, transport http.RoundTripper) ( return &OIDCCredential{ parameters: parameters, - httpClient: &http.Client{ - Timeout: 10 * time.Second, - Transport: transport, - }, + httpClient: client, }, nil } diff --git a/internal/oidc/oidc_credential_test.go b/internal/oidc/oidc_credential_test.go index 92b58e4..2fd4a42 100644 --- a/internal/oidc/oidc_credential_test.go +++ b/internal/oidc/oidc_credential_test.go @@ -27,7 +27,7 @@ func TestSuccessfulAuthenticationDoesNotMakeARepeatedRequest(t *testing.T) { creds, err := CreateOIDCCredential(config.Credential{ "tenant-id": "test-tenant-id", "client-id": "test-client-id", - }, nil) + }, testHTTPClient) require.NoError(t, err) // ensure of type azure @@ -88,7 +88,7 @@ func TestFailedAuthenticationIsNotRetried(t *testing.T) { creds, err := CreateOIDCCredential(config.Credential{ "tenant-id": "test-tenant-id", "client-id": "test-client-id", - }, nil) + }, testHTTPClient) require.NoError(t, err) // ensure of type azure @@ -334,7 +334,7 @@ func TestTryCreateOIDCCredential(t *testing.T) { t.Setenv(envActionsIDTokenRequestURL, "https://example.com/token") t.Setenv(envActionsIDTokenRequestToken, "test-token") - actual, _ := CreateOIDCCredential(tc.cred, nil) + actual, _ := CreateOIDCCredential(tc.cred, testHTTPClient) if tc.expectedParameters == nil { assert.Nil(t, actual) return @@ -350,3 +350,50 @@ func TestTryCreateOIDCCredential(t *testing.T) { }) } } + +func TestOIDCCredentialUsesOneClientForAssertionAndExchange(t *testing.T) { + t.Setenv(envActionsIDTokenRequestURL, "https://actions.example.test/token") + t.Setenv(envActionsIDTokenRequestToken, "request-token") + + recorder := &recordingOIDCTransport{} + client := &http.Client{Transport: recorder, Timeout: testHTTPClient.Timeout} + credential, err := CreateOIDCCredential(config.Credential{ + "tenant-id": "test-tenant-id", + "client-id": "test-client-id", + }, client) + require.NoError(t, err) + require.Same(t, client, credential.httpClient) + + token, err := GetOrRefreshOIDCToken(credential, t.Context()) + require.NoError(t, err) + assert.Equal(t, "access-token", token) + assert.Equal(t, []string{ + "GET https://actions.example.test/token?audience=api%3A%2F%2FAzureADTokenExchange", + "POST https://login.microsoftonline.com/test-tenant-id/oauth2/v2.0/token", + }, recorder.requests) +} + +type recordingOIDCTransport struct { + requests []string +} + +func (r *recordingOIDCTransport) RoundTrip(req *http.Request) (*http.Response, error) { + r.requests = append(r.requests, req.Method+" "+req.URL.String()) + + var body string + switch req.URL.Host { + case "actions.example.test": + body = `{"count":1,"value":"assertion"}` + case "login.microsoftonline.com": + body = `{"access_token":"access-token","expires_in":3600,"token_type":"Bearer"}` + default: + return nil, fmt.Errorf("unexpected OIDC request: %s", req.URL) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(body)), + Header: make(http.Header), + Request: req, + }, nil +} diff --git a/internal/oidc/oidc_registry.go b/internal/oidc/oidc_registry.go index 9a751c5..ef7f7ff 100644 --- a/internal/oidc/oidc_registry.go +++ b/internal/oidc/oidc_registry.go @@ -17,9 +17,9 @@ import ( // and avoids key collisions when multiple registries share a host with // different paths. type OIDCRegistry struct { - byHost map[string][]oidcEntry - mutex sync.RWMutex - transport http.RoundTripper + byHost map[string][]oidcEntry + mutex sync.RWMutex + client *http.Client } type oidcEntry struct { @@ -28,12 +28,15 @@ type oidcEntry struct { credential *OIDCCredential } -// NewOIDCRegistry creates an empty registry. transport is used for outbound OIDC token requests -// and should be the same secure transport used by the rest of the proxy. -func NewOIDCRegistry(transport http.RoundTripper) *OIDCRegistry { +// NewOIDCRegistry creates an empty registry. client is used for every outbound +// OIDC request and must use the same restricted transport as the proxy. +func NewOIDCRegistry(client *http.Client) *OIDCRegistry { + if err := validateHTTPClient(client); err != nil { + panic(err) + } return &OIDCRegistry{ - byHost: make(map[string][]oidcEntry), - transport: transport, + byHost: make(map[string][]oidcEntry), + client: client, } } @@ -50,7 +53,7 @@ func (r *OIDCRegistry) Register( urlFields []string, registryType string, ) (*OIDCCredential, string, bool) { - oidcCredential, _ := CreateOIDCCredential(cred, r.transport) + oidcCredential, _ := CreateOIDCCredential(cred, r.client) if oidcCredential == nil { return nil, "", false } diff --git a/internal/oidc/oidc_registry_test.go b/internal/oidc/oidc_registry_test.go index 9b58511..2b0349b 100644 --- a/internal/oidc/oidc_registry_test.go +++ b/internal/oidc/oidc_registry_test.go @@ -57,7 +57,7 @@ func azureCredWithRegistry(tenantID, clientID, registry string) config.Credentia func TestOIDCRegistry_Register_SingleCredential(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com/packages") oidcCred, key, ok := r.Register(cred, []string{"url"}, "test registry") @@ -69,7 +69,7 @@ func TestOIDCRegistry_Register_SingleCredential(t *testing.T) { func TestOIDCRegistry_Register_URLFieldPriority(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := config.Credential{ "type": "test_registry", @@ -87,7 +87,7 @@ func TestOIDCRegistry_Register_URLFieldPriority(t *testing.T) { func TestOIDCRegistry_Register_FallsBackToHost(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := config.Credential{ "type": "test_registry", @@ -107,7 +107,7 @@ func TestOIDCRegistry_Register_NotOIDC(t *testing.T) { t.Setenv(envActionsIDTokenRequestURL, "") t.Setenv(envActionsIDTokenRequestToken, "") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := config.Credential{ "type": "test_registry", "url": "https://registry.example.com", @@ -122,7 +122,7 @@ func TestOIDCRegistry_Register_NotOIDC(t *testing.T) { func TestOIDCRegistry_Register_NoKeyAvailable(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) // Credential with OIDC params but no URL or host cred := config.Credential{ @@ -144,7 +144,7 @@ func TestOIDCRegistry_TryAuth_SingleCredential(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com/packages") r.Register(cred, []string{"url"}, "test registry") @@ -163,7 +163,7 @@ func TestOIDCRegistry_TryAuth_SameHostDifferentPaths_NoCollision(t *testing.T) { mockAzureOIDC(t, "tenant-A", "token-feed-A") mockAzureOIDC(t, "tenant-B", "token-feed-B") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) // Two registries on the same host with different paths credA := azureCredWithURL("tenant-A", "client-A", @@ -201,7 +201,7 @@ func TestOIDCRegistry_TryAuth_HostOnlyMatchesAnyPath(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) // Register with host only (no path) cred := config.Credential{ @@ -222,7 +222,7 @@ func TestOIDCRegistry_TryAuth_HostOnlyMatchesAnyPath(t *testing.T) { func TestOIDCRegistry_TryAuth_NoMatch(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com/packages") r.Register(cred, []string{"url"}, "test registry") @@ -237,7 +237,7 @@ func TestOIDCRegistry_TryAuth_NoMatch(t *testing.T) { func TestOIDCRegistry_TryAuth_WrongPathNoMatch(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := azureCredWithURL("tenant-1", "client-1", "https://pkgs.dev.azure.com/org/_packaging/feed-A/npm/registry/") @@ -253,7 +253,7 @@ func TestOIDCRegistry_TryAuth_WrongPathNoMatch(t *testing.T) { } func TestOIDCRegistry_CredentialForRequestConcurrentRegistration(t *testing.T) { - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) credential := &OIDCCredential{ parameters: &AzureOIDCParameters{ TenantID: "tenant-1", @@ -293,7 +293,7 @@ func TestOIDCRegistry_RegisterURL(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) // Register primary URL cred := azureCredWithURL("tenant-1", "client-1", "https://nuget.example.com/v3/index.json") @@ -314,7 +314,7 @@ func TestOIDCRegistry_RegisterURL(t *testing.T) { func TestOIDCRegistry_TryAuth_PortMismatch(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com:8443/packages") r.Register(cred, []string{"url"}, "test registry") @@ -328,7 +328,7 @@ func TestOIDCRegistry_TryAuth_PortMismatch(t *testing.T) { func TestOIDCRegistry_Register_RegistryField(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := azureCredWithRegistry("tenant-1", "client-1", "ghcr.io") _, key, ok := r.Register(cred, []string{"registry"}, "docker registry") @@ -344,7 +344,7 @@ func TestOIDCRegistry_TryAuth_PathSpecificBeatsHostOnly(t *testing.T) { mockAzureOIDC(t, "tenant-1", "__host_only_token__") mockAzureOIDC(t, "tenant-2", "__path_specific_token__") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) hostOnlyCred := config.Credential{ "type": "test_registry", @@ -372,7 +372,7 @@ func TestOIDCRegistry_TryAuth_LongestPathPrefixWins(t *testing.T) { mockAzureOIDC(t, "tenant-1", "__short_prefix_token__") mockAzureOIDC(t, "tenant-2", "__long_prefix_token__") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) shortPrefixCred := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com/packages") longPrefixCred := azureCredWithURL("tenant-2", "client-2", "https://registry.example.com/packages/private") @@ -394,7 +394,7 @@ func TestOIDCRegistry_TryAuth_CaseInsensitiveHost(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := azureCredWithURL("tenant-1", "client-1", "https://Registry.Example.COM/packages") r.Register(cred, []string{"url"}, "test registry") @@ -432,7 +432,7 @@ func TestOIDCRegistry_TryAuth_Cloudsmith_UsesAPIKey(t *testing.T) { defer httpmock.DeactivateAndReset() mockCloudsmithOIDC(t, "my-org", "__cs_token__") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := cloudsmithCred("my-org", "my-service", "https://cloudsmith.io", "https://dl.cloudsmith.io/basic/my-org/my-repo") r.Register(cred, []string{"url"}, "test registry") @@ -467,7 +467,7 @@ func TestOIDCRegistry_TryAuth_GCP_UsesBearer(t *testing.T) { defer httpmock.DeactivateAndReset() mockGCPOIDC(t, "__gcp_token__") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := gcpCred("projects/123/locations/global/workloadIdentityPools/pool/providers/prov", "https://us-central1-python.pkg.dev/my-project/my-repo/simple") r.Register(cred, []string{"url"}, "test registry") @@ -486,7 +486,7 @@ func TestOIDCRegistry_TryAuth_GCP_DockerUsesBasicAuth(t *testing.T) { defer httpmock.DeactivateAndReset() mockGCPOIDC(t, "__gcp_token__") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := gcpCred("projects/123/locations/global/workloadIdentityPools/pool/providers/prov", "https://us-central1-docker.pkg.dev/my-project/my-repo") r.Register(cred, []string{"url"}, "docker registry") @@ -505,7 +505,7 @@ func TestOIDCRegistry_TryAuth_GCP_DockerUsesBasicAuth(t *testing.T) { func TestOIDCRegistry_Register_IndexURLField(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := azureCred("tenant-1", "client-1") cred["index-url"] = "https://pkgs.dev.azure.com/org/_packaging/feed/pypi/simple" @@ -522,7 +522,7 @@ func TestOIDCRegistry_TryAuth_URLWithoutProtocol(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := azureCred("tenant-1", "client-1") cred["url"] = "registry.example.com/packages" @@ -541,7 +541,7 @@ func TestOIDCRegistry_RegisterURL_MultipleOnSameHost(t *testing.T) { defer httpmock.DeactivateAndReset() mockAzureOIDC(t, "tenant-1", "__test_token__") - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred := azureCredWithURL("tenant-1", "client-1", "https://nuget.example.com/v3/index.json") oidcCred, _, ok := r.Register(cred, []string{"url"}, "nuget feed") @@ -563,7 +563,7 @@ func TestOIDCRegistry_RegisterURL_MultipleOnSameHost(t *testing.T) { func TestOIDCRegistry_Register_NoDuplicateEntries(t *testing.T) { setupOIDCEnv(t) - r := NewOIDCRegistry(nil) + r := NewOIDCRegistry(testHTTPClient) cred1 := azureCredWithURL("tenant-1", "client-1", "https://registry.example.com/packages") cred2 := azureCredWithURL("tenant-2", "client-2", "https://registry.example.com/packages") diff --git a/proxy.go b/proxy.go index 3aefbcb..3960609 100644 --- a/proxy.go +++ b/proxy.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "strings" + "time" "github.com/elazarl/goproxy" "github.com/rs/dnscache" @@ -51,6 +52,10 @@ func newProxyWithCacheDir(envSettings config.ProxyEnvSettings, cfg *config.Confi }, Proxy: http.ProxyFromEnvironment, } + oidcClient := &http.Client{ + Timeout: 10 * time.Second, + Transport: transport, + } apiClient := apiclient.New(envSettings.APIEndpoint, envSettings.JobToken, envSettings.JobID, apiclient.WithTransport(transport)) metricsClient := metrics.New(envSettings, apiClient) @@ -68,7 +73,7 @@ func newProxyWithCacheDir(envSettings config.ProxyEnvSettings, cfg *config.Confi proxy.OnRequest().DoFunc(logger.logRequest) proxy.OnResponse().DoFunc(logger.logResponse) - nugetFeedHandler := handlers.NewNugetFeedHandler(cfg.Credentials, transport) + nugetFeedHandler := handlers.NewNugetFeedHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(nugetFeedHandler.PrepareRequest) enableCache := os.Getenv("PROXY_CACHE") == "true" @@ -98,50 +103,50 @@ func newProxyWithCacheDir(envSettings config.ProxyEnvSettings, cfg *config.Confi proxy.OnRequest().DoFunc(gitServerHandler.HandleRequest) proxy.OnResponse().DoFunc(gitServerHandler.HandleResponse) - npmRegistryHandler := handlers.NewNPMRegistryHandler(cfg.Credentials, transport) + npmRegistryHandler := handlers.NewNPMRegistryHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(npmRegistryHandler.HandleRequest) hexOrganizationHandler := handlers.NewHexOrganizationHandler(cfg.Credentials) proxy.OnRequest().DoFunc(hexOrganizationHandler.HandleRequest) - hexRepositoryHandler := handlers.NewHexRepositoryHandler(cfg.Credentials, transport) + hexRepositoryHandler := handlers.NewHexRepositoryHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(hexRepositoryHandler.HandleRequest) - pythonHandler := handlers.NewPythonIndexHandler(cfg.Credentials, transport) + pythonHandler := handlers.NewPythonIndexHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(pythonHandler.HandleRequest) proxy.OnResponse().DoFunc(pythonHandler.HandleResponse) - composerHandler := handlers.NewComposerHandler(cfg.Credentials, transport) + composerHandler := handlers.NewComposerHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(composerHandler.HandleRequest) - dockerRegistryHandler := handlers.NewDockerRegistryHandler(cfg.Credentials, transport, nil) + dockerRegistryHandler := handlers.NewDockerRegistryHandler(cfg.Credentials, oidcClient, nil) proxy.OnRequest().DoFunc(dockerRegistryHandler.HandleRequest) - rubyGemsServerHandler := handlers.NewRubyGemsServerHandler(cfg.Credentials, transport) + rubyGemsServerHandler := handlers.NewRubyGemsServerHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(rubyGemsServerHandler.HandleRequest) proxy.OnRequest().DoFunc(nugetFeedHandler.HandleRequest) proxy.OnResponse().DoFunc(nugetFeedHandler.HandleResponse) - mavenRepositoryHandler := handlers.NewMavenRepositoryHandler(cfg.Credentials, transport) + mavenRepositoryHandler := handlers.NewMavenRepositoryHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(mavenRepositoryHandler.HandleRequest) - terraformRegistryHandler := handlers.NewTerraformRegistryHandler(cfg.Credentials, transport) + terraformRegistryHandler := handlers.NewTerraformRegistryHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(terraformRegistryHandler.HandleRequest) - openTofuRegistryHandler := handlers.NewOpenTofuRegistryHandler(cfg.Credentials, transport) + openTofuRegistryHandler := handlers.NewOpenTofuRegistryHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(openTofuRegistryHandler.HandleRequest) - pubRepositoryHandler := handlers.NewPubRepositoryHandler(cfg.Credentials, transport) + pubRepositoryHandler := handlers.NewPubRepositoryHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(pubRepositoryHandler.HandleRequest) - cargoRegistryHandler := handlers.NewCargoRegistryHandler(cfg.Credentials, transport) + cargoRegistryHandler := handlers.NewCargoRegistryHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(cargoRegistryHandler.HandleRequest) - goProxyServerHandler := handlers.NewGoProxyServerHandler(cfg.Credentials, transport) + goProxyServerHandler := handlers.NewGoProxyServerHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(goProxyServerHandler.HandleRequest) - helmRegistryHandler := handlers.NewHelmRegistryHandler(cfg.Credentials, transport) + helmRegistryHandler := handlers.NewHelmRegistryHandler(cfg.Credentials, oidcClient) proxy.OnRequest().DoFunc(helmRegistryHandler.HandleRequest) proxy.OnResponse().DoFunc(cacher.OnResponse)