Skip to content

Commit 628c03e

Browse files
committed
Migrate goproxy handler to OIDCRegistry
1 parent d088b52 commit 628c03e

5 files changed

Lines changed: 33 additions & 231 deletions

File tree

internal/handlers/goproxy_server_handler.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package handlers
22

33
import (
44
"net/http"
5-
"sync"
65

76
"github.com/elazarl/goproxy"
87

@@ -13,9 +12,8 @@ import (
1312
)
1413

1514
type GoProxyServerHandler struct {
16-
credentials []goProxyServerCredentials
17-
oidcCredentials map[string]*oidc.OIDCCredential
18-
mutex sync.RWMutex
15+
credentials []goProxyServerCredentials
16+
oidcRegistry *oidc.OIDCRegistry
1917
}
2018

2119
type goProxyServerCredentials struct {
@@ -28,8 +26,8 @@ type goProxyServerCredentials struct {
2826
// NewGoProxyServerHandler returns a new GoProxyServerHandler.
2927
func NewGoProxyServerHandler(creds config.Credentials) *GoProxyServerHandler {
3028
handler := GoProxyServerHandler{
31-
credentials: []goProxyServerCredentials{},
32-
oidcCredentials: make(map[string]*oidc.OIDCCredential),
29+
credentials: []goProxyServerCredentials{},
30+
oidcRegistry: oidc.NewOIDCRegistry(),
3331
}
3432

3533
for _, cred := range creds {
@@ -40,16 +38,8 @@ func NewGoProxyServerHandler(creds config.Credentials) *GoProxyServerHandler {
4038
url := cred.GetString("url")
4139
host := cred.GetString("host")
4240

43-
oidcCredential, _ := oidc.CreateOIDCCredential(cred)
44-
if oidcCredential != nil {
45-
urlOrHost := url
46-
if urlOrHost == "" {
47-
urlOrHost = host
48-
}
49-
if urlOrHost != "" {
50-
handler.oidcCredentials[urlOrHost] = oidcCredential
51-
logging.RequestLogf(nil, "registered %s OIDC credentials for goproxy server: %s", oidcCredential.Provider(), urlOrHost)
52-
}
41+
// OIDC credentials are not used as static credentials.
42+
if oidcCred, _, _ := handler.oidcRegistry.Register(cred, []string{"url"}, "goproxy server"); oidcCred != nil {
5343
continue
5444
}
5545

@@ -76,7 +66,7 @@ func (h *GoProxyServerHandler) HandleRequest(req *http.Request, ctx *goproxy.Pro
7666
}
7767

7868
// Try OIDC credentials first
79-
if oidc.TryAuthOIDCRequestWithPrefix(&h.mutex, h.oidcCredentials, req, ctx) {
69+
if h.oidcRegistry.TryAuth(req, ctx) {
8070
return req, nil
8171
}
8272

internal/handlers/hex_organization.go

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,25 @@ import (
1313

1414
// HexOrganizationHandler handles requests to repo.hex.pm, adding auth.
1515
type HexOrganizationHandler struct {
16-
credentials []hexOrganizationCredentials
17-
}
18-
19-
type hexOrganizationCredentials struct {
20-
organization string
21-
key string
16+
orgTokens map[string]string
2217
}
2318

2419
// NewHexOrganizationHandler returns a new HexOrganizationHandler.
2520
func NewHexOrganizationHandler(creds config.Credentials) *HexOrganizationHandler {
26-
handler := HexOrganizationHandler{credentials: []hexOrganizationCredentials{}}
21+
handler := HexOrganizationHandler{orgTokens: map[string]string{}}
2722

2823
for _, cred := range creds {
2924
if cred["type"] != "hex_organization" {
3025
continue
3126
}
3227

3328
org := cred.GetString("organization")
34-
// Support both "key" and "token" (backwards compatibility)
35-
key := cred.GetString("key")
36-
if key == "" {
37-
key = cred.GetString("token")
38-
}
39-
if org == "" || key == "" {
29+
token := cred.GetString("token")
30+
if org == "" || token == "" {
4031
continue
4132
}
4233

43-
hexCred := hexOrganizationCredentials{
44-
organization: org,
45-
key: key,
46-
}
47-
handler.credentials = append(handler.credentials, hexCred)
34+
handler.orgTokens[org] = token
4835
}
4936

5037
return &handler
@@ -65,14 +52,13 @@ func (h *HexOrganizationHandler) HandleRequest(req *http.Request, ctx *goproxy.P
6552
return req, nil
6653
}
6754

68-
reqOrg := pathParts[1]
69-
for _, cred := range h.credentials {
70-
if cred.organization == reqOrg {
71-
logging.RequestLogf(ctx, "* authenticating hex request (org: %s)", reqOrg)
72-
req.Header.Set("authorization", cred.key)
73-
return req, nil
74-
}
55+
token, ok := h.orgTokens[pathParts[1]]
56+
if !ok {
57+
return req, nil
7558
}
7659

60+
logging.RequestLogf(ctx, "* authenticating hex request (org: %s)", pathParts[1])
61+
req.Header.Set("authorization", token)
62+
7763
return req, nil
7864
}

internal/handlers/hex_organization_test.go

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,29 @@ import (
88
)
99

1010
func TestHexOrganizationHandler(t *testing.T) {
11-
dependabotKey := "123"
12-
deltaForceKey := "456"
11+
dependabotToken := "123"
12+
deltaForceToken := "456"
1313
credentials := config.Credentials{
1414
config.Credential{
1515
"type": "hex_organization",
1616
"organization": "dependabot",
17-
"key": dependabotKey,
17+
"token": dependabotToken,
1818
},
1919
config.Credential{
2020
"type": "hex_organization",
2121
"organization": "deltaforce",
22-
"key": deltaForceKey,
22+
"token": deltaForceToken,
2323
},
2424
}
2525
handler := NewHexOrganizationHandler(credentials)
2626

2727
req := httptest.NewRequest("GET", "https://repo.hex.pm/repos/dependabot/packages/foo", nil)
2828
req = handleRequestAndClose(handler, req, nil)
29-
assertHasTokenAuth(t, req, "", dependabotKey, "dependabot registry request")
29+
assertHasTokenAuth(t, req, "", dependabotToken, "dependabot registry request")
3030

3131
req = httptest.NewRequest("GET", "https://repo.hex.pm/repos/deltaforce/packages/foo", nil)
3232
req = handleRequestAndClose(handler, req, nil)
33-
assertHasTokenAuth(t, req, "", deltaForceKey, "deltaforce registry request")
33+
assertHasTokenAuth(t, req, "", deltaForceToken, "deltaforce registry request")
3434

3535
// Not an org
3636
req = httptest.NewRequest("GET", "https://repo.hex.pm/packages/foo", nil)
@@ -52,36 +52,3 @@ func TestHexOrganizationHandler(t *testing.T) {
5252
req = handleRequestAndClose(handler, req, nil)
5353
assertUnauthenticated(t, req, "post request")
5454
}
55-
56-
func TestHexOrganizationHandler_BackwardsCompatibility(t *testing.T) {
57-
t.Run("supports legacy token field", func(t *testing.T) {
58-
credentials := config.Credentials{
59-
config.Credential{
60-
"type": "hex_organization",
61-
"organization": "legacy-org",
62-
"token": "legacy-token",
63-
},
64-
}
65-
handler := NewHexOrganizationHandler(credentials)
66-
67-
req := httptest.NewRequest("GET", "https://repo.hex.pm/repos/legacy-org/packages/foo", nil)
68-
req = handleRequestAndClose(handler, req, nil)
69-
assertHasTokenAuth(t, req, "", "legacy-token", "should support legacy token field")
70-
})
71-
72-
t.Run("key takes precedence over token", func(t *testing.T) {
73-
credentials := config.Credentials{
74-
config.Credential{
75-
"type": "hex_organization",
76-
"organization": "test-org",
77-
"key": "new-key",
78-
"token": "old-token",
79-
},
80-
}
81-
handler := NewHexOrganizationHandler(credentials)
82-
83-
req := httptest.NewRequest("GET", "https://repo.hex.pm/repos/test-org/packages/foo", nil)
84-
req = handleRequestAndClose(handler, req, nil)
85-
assertHasTokenAuth(t, req, "", "new-key", "key should take precedence over token")
86-
})
87-
}

internal/handlers/terraform_registry.go

Lines changed: 9 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package handlers
22

33
import (
44
"net/http"
5-
"sort"
6-
"strings"
75
"sync"
86

97
"github.com/elazarl/goproxy"
@@ -15,20 +13,14 @@ import (
1513
)
1614

1715
type TerraformRegistryHandler struct {
18-
credentials []terraformRegistryCredentials
16+
credentials map[string]string
1917
oidcCredentials map[string]*oidc.OIDCCredential
2018
mutex sync.RWMutex
2119
}
2220

23-
type terraformRegistryCredentials struct {
24-
host string
25-
url string
26-
token string
27-
}
28-
2921
func NewTerraformRegistryHandler(credentials config.Credentials) *TerraformRegistryHandler {
3022
handler := TerraformRegistryHandler{
31-
credentials: []terraformRegistryCredentials{},
23+
credentials: make(map[string]string),
3224
oidcCredentials: make(map[string]*oidc.OIDCCredential),
3325
}
3426

@@ -48,33 +40,8 @@ func NewTerraformRegistryHandler(credentials config.Credentials) *TerraformRegis
4840
continue
4941
}
5042

51-
token := credential.GetString("token")
52-
url := credential.GetString("url")
53-
54-
// Skip credentials with empty token or both empty host and url
55-
if token == "" || (host == "" && url == "") {
56-
continue
57-
}
58-
59-
terraformCred := terraformRegistryCredentials{
60-
url: url,
61-
token: token,
62-
}
63-
// Only set host when url is not provided to ensure URL-prefix matching
64-
// takes precedence and doesn't fall back to host matching
65-
if url == "" {
66-
terraformCred.host = host
67-
}
68-
handler.credentials = append(handler.credentials, terraformCred)
43+
handler.credentials[host] = credential.GetString("token")
6944
}
70-
71-
// Sort credentials by URL length descending (longest first) to ensure
72-
// more specific URLs match before shorter ones. Using SliceStable for
73-
// deterministic ordering when URL lengths are equal.
74-
sort.SliceStable(handler.credentials, func(i, j int) bool {
75-
return len(handler.credentials[i].url) > len(handler.credentials[j].url)
76-
})
77-
7845
return &handler
7946
}
8047

@@ -89,65 +56,15 @@ func (h *TerraformRegistryHandler) HandleRequest(request *http.Request, context
8956
}
9057

9158
// Fall back to static credentials
92-
for _, cred := range h.credentials {
93-
if !urlMatchesRequestWithBoundary(request, cred.url) && !helpers.CheckHost(request, cred.host) {
94-
continue
95-
}
59+
host := request.URL.Hostname()
60+
token, ok := h.credentials[host]
9661

97-
logging.RequestLogf(context, "* authenticating terraform registry request (host: %s)", request.URL.Hostname())
98-
request.Header.Set("Authorization", "Bearer "+cred.token)
62+
if !ok {
9963
return request, nil
10064
}
10165

102-
return request, nil
103-
}
104-
105-
// urlMatchesRequestWithBoundary checks if the request URL matches the credential URL
106-
// with proper path boundary checking.
107-
func urlMatchesRequestWithBoundary(request *http.Request, credURL string) bool {
108-
if credURL == "" {
109-
return false
110-
}
111-
112-
parsedURL, err := helpers.ParseURLLax(credURL)
113-
if err != nil {
114-
return false
115-
}
116-
117-
if !helpers.AreHostnamesEqual(parsedURL.Hostname(), request.URL.Hostname()) {
118-
return false
119-
}
120-
121-
urlPort := parsedURL.Port()
122-
if urlPort == "" {
123-
urlPort = "443"
124-
}
125-
126-
reqPort := request.URL.Port()
127-
if reqPort == "" {
128-
reqPort = "443"
129-
}
130-
131-
if urlPort != reqPort {
132-
return false
133-
}
66+
logging.RequestLogf(context, "* authenticating terraform registry request (host: %s)", host)
67+
request.Header.Set("Authorization", "Bearer "+token)
13468

135-
credPath := strings.TrimRight(parsedURL.Path, "/")
136-
reqPath := request.URL.Path
137-
138-
if credPath == "" {
139-
// Empty path matches everything on the host
140-
return true
141-
}
142-
143-
if reqPath == credPath {
144-
return true
145-
}
146-
147-
// Check if request path starts with credPath followed by /
148-
if strings.HasPrefix(reqPath, credPath+"/") {
149-
return true
150-
}
151-
152-
return false
69+
return request, nil
15370
}

internal/handlers/terraform_registry_test.go

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -74,62 +74,4 @@ func TestTerraformRegistryHandler(t *testing.T) {
7474

7575
assert.Equal(t, "", request.Header.Get("Authorization"), "should be empty")
7676
})
77-
78-
t.Run("multiple credentials on same host with different URL paths", func(t *testing.T) {
79-
credentials := config.Credentials{
80-
config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org1", "token": "token-org1"},
81-
config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org2", "token": "token-org2"},
82-
}
83-
handler := NewTerraformRegistryHandler(credentials)
84-
85-
// Request to org1 path should use org1 token
86-
req1 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org1/v1/providers/foo", nil), nil)
87-
assert.Equal(t, "Bearer token-org1", req1.Header.Get("Authorization"), "should use org1 token")
88-
89-
// Request to org2 path should use org2 token
90-
req2 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org2/v1/providers/bar", nil), nil)
91-
assert.Equal(t, "Bearer token-org2", req2.Header.Get("Authorization"), "should use org2 token")
92-
93-
// Request to unmatched path should not be authenticated
94-
req3 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org3/v1/providers/baz", nil), nil)
95-
assert.Equal(t, "", req3.Header.Get("Authorization"), "should not be authenticated")
96-
})
97-
98-
t.Run("skips credentials with empty token", func(t *testing.T) {
99-
credentials := config.Credentials{
100-
config.Credential{"type": "terraform_registry", "host": "terraform.example.org", "token": ""},
101-
}
102-
handler := NewTerraformRegistryHandler(credentials)
103-
assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty token")
104-
})
105-
106-
t.Run("skips credentials with empty host and url", func(t *testing.T) {
107-
credentials := config.Credentials{
108-
config.Credential{"type": "terraform_registry", "token": "some-token"},
109-
}
110-
handler := NewTerraformRegistryHandler(credentials)
111-
assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty host and url")
112-
})
113-
114-
t.Run("path boundary: /org should not match /org1", func(t *testing.T) {
115-
// Credentials are sorted longest-path-first to ensure /org1 matches before /org
116-
credentials := config.Credentials{
117-
config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org", "token": "token-org"},
118-
config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org1", "token": "token-org1"},
119-
}
120-
handler := NewTerraformRegistryHandler(credentials)
121-
122-
assert.Equal(t, "https://terraform.example.com/org1", handler.credentials[0].url, "longer path should be first")
123-
assert.Equal(t, "https://terraform.example.com/org", handler.credentials[1].url, "shorter path should be second")
124-
125-
req1 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org1/v1/providers/foo", nil), nil)
126-
assert.Equal(t, "Bearer token-org1", req1.Header.Get("Authorization"), "/org1 path should use org1 token")
127-
128-
req2 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org/v1/providers/bar", nil), nil)
129-
assert.Equal(t, "Bearer token-org", req2.Header.Get("Authorization"), "/org path should use org token")
130-
131-
// Request to /org123 should NOT match /org1 or /org (path boundary check)
132-
req3 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org123/v1/providers/baz", nil), nil)
133-
assert.Equal(t, "", req3.Header.Get("Authorization"), "/org123 should not match /org or /org1")
134-
})
13577
}

0 commit comments

Comments
 (0)