Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 55 additions & 4 deletions internal/handlers/oidc_handling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) {
},
urlMocks: []mockHttpRequest{},
expectedLogLines: []string{
"registered aws OIDC credentials for python index: python.example.com",
"registered aws OIDC credentials for python index: https://python.example.com",
},
urlsToAuthenticate: []string{
"https://python.example.com/some-package",
Expand All @@ -1057,7 +1057,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) {
},
urlMocks: []mockHttpRequest{},
expectedLogLines: []string{
"registered azure OIDC credentials for python index: python.example.com",
"registered azure OIDC credentials for python index: https://python.example.com",
},
urlsToAuthenticate: []string{
"https://python.example.com/some-package",
Expand All @@ -1078,7 +1078,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) {
},
urlMocks: []mockHttpRequest{},
expectedLogLines: []string{
"registered jfrog OIDC credentials for python index: jfrog.example.com",
"registered jfrog OIDC credentials for python index: https://jfrog.example.com",
},
urlsToAuthenticate: []string{
"https://jfrog.example.com/some-package",
Expand All @@ -1101,7 +1101,7 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) {
},
urlMocks: []mockHttpRequest{},
expectedLogLines: []string{
"registered cloudsmith OIDC credentials for python index: cloudsmith.example.com",
"registered cloudsmith OIDC credentials for python index: https://cloudsmith.example.com",
},
urlsToAuthenticate: []string{
"https://cloudsmith.example.com/some-package",
Expand Down Expand Up @@ -1390,3 +1390,54 @@ func TestOIDCURLsAreAuthenticated(t *testing.T) {
})
}
}

// TestPythonOIDCSimpleSuffixStripping verifies that Python index URLs ending
// with /simple or /+simple are normalized before OIDC registration, so that
// requests to sibling paths (e.g. /org/pkg/a) still match.
func TestPythonOIDCSimpleSuffixStripping(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

tenantA := "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
tenantB := "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
clientId := "87654321-4321-4321-4321-210987654321"

tokenUrl := "https://token.actions.example.com" //nolint:gosec // test URL
httpmock.RegisterResponder("GET", tokenUrl,
httpmock.NewStringResponder(200, `{"count":1,"value":"sometoken"}`))

httpmock.RegisterResponder("POST", fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantA),
httpmock.NewStringResponder(200, `{"access_token":"__token_A__","expires_in":3600,"token_type":"Bearer"}`))
httpmock.RegisterResponder("POST", fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantB),
httpmock.NewStringResponder(200, `{"access_token":"__token_B__","expires_in":3600,"token_type":"Bearer"}`))

t.Setenv("ACTIONS_ID_TOKEN_REQUEST_URL", tokenUrl)
t.Setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "sometoken")

creds := config.Credentials{
config.Credential{
"type": "python_index",
"index-url": "https://pkgs.example.com/org/feed-A/+simple/",
"tenant-id": tenantA,
"client-id": clientId,
},
config.Credential{
"type": "python_index",
"index-url": "https://pkgs.example.com/org/feed-B/simple",
"tenant-id": tenantB,
"client-id": clientId,
},
}

handler := NewPythonIndexHandler(creds)

// /+simple/ should be stripped → registered as /org/feed-A/
reqA := httptest.NewRequest("GET", "https://pkgs.example.com/org/feed-A/pkg/a", nil)
reqA = handleRequestAndClose(handler, reqA, nil)
assertHasTokenAuth(t, reqA, "Bearer", "__token_A__", "feed-A request should use token A")

// /simple should be stripped → registered as /org/feed-B/
reqB := httptest.NewRequest("GET", "https://pkgs.example.com/org/feed-B/pkg/b", nil)
reqB = handleRequestAndClose(handler, reqB, nil)
assertHasTokenAuth(t, reqB, "Bearer", "__token_B__", "feed-B request should use token B")
}
35 changes: 19 additions & 16 deletions internal/handlers/python_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"net/http"
"regexp"
"strings"
"sync"

"github.com/elazarl/goproxy"

Expand All @@ -18,9 +17,8 @@ var simpleSuffixRe = regexp.MustCompile(`/\+?simple/?\z`)

// PythonIndexHandler handles requests to Python indexes, adding auth.
type PythonIndexHandler struct {
credentials []pythonIndexCredentials
oidcCredentials map[string]*oidc.OIDCCredential
mutex sync.RWMutex
credentials []pythonIndexCredentials
oidcRegistry *oidc.OIDCRegistry
}

type pythonIndexCredentials struct {
Expand All @@ -34,8 +32,8 @@ type pythonIndexCredentials struct {
// NewPythonIndexHandler returns a new PythonIndexHandler.
func NewPythonIndexHandler(creds config.Credentials) *PythonIndexHandler {
handler := PythonIndexHandler{
credentials: []pythonIndexCredentials{},
oidcCredentials: make(map[string]*oidc.OIDCCredential),
credentials: []pythonIndexCredentials{},
oidcRegistry: oidc.NewOIDCRegistry(),
}

for _, cred := range creds {
Expand All @@ -47,16 +45,21 @@ func NewPythonIndexHandler(creds config.Credentials) *PythonIndexHandler {

oidcCredential, _ := oidc.CreateOIDCCredential(cred)
if oidcCredential != nil {
host := cred.Host()
if host == "" && indexURL != "" {
regURL, err := helpers.ParseURLLax(indexURL)
if err == nil {
host = regURL.Hostname()
}
// Normalize the registration URL by stripping the /simple or /+simple
// suffix, matching how static credentials are matched at request time.
// Without this, a config of /dependabot/+simple/ would not prefix-match
// requests to /dependabot/pkg/a.
regURL := indexURL
if regURL == "" {
regURL = cred.GetString("url")
}
if host != "" {
handler.oidcCredentials[host] = oidcCredential
logging.RequestLogf(nil, "registered %s OIDC credentials for python index: %s", oidcCredential.Provider(), host)
if regURL != "" {
regURL = simpleSuffixRe.ReplaceAllString(regURL, "/")
} else {
regURL = cred.Host()
}
if regURL != "" {
handler.oidcRegistry.RegisterURL(regURL, oidcCredential, "python index")
}
continue
}
Expand Down Expand Up @@ -85,7 +88,7 @@ func (h *PythonIndexHandler) HandleRequest(req *http.Request, ctx *goproxy.Proxy
}

// Try OIDC credentials first
if oidc.TryAuthOIDCRequestWithPrefix(&h.mutex, h.oidcCredentials, req, ctx) {
if h.oidcRegistry.TryAuth(req, ctx) {
return req, nil
}

Expand Down
Loading